From 072d8e8cf2f450a145501e5d1415fe9f705ea72b Mon Sep 17 00:00:00 2001 From: Andronik Ordian Date: Fri, 3 Jan 2020 10:35:10 +0300 Subject: [PATCH] extract common kvdb tests into a crate (#301) * kvdb-test-utils: extract common KeyValueDB tests into a crate * kvdb-memorydb: use kvdb-test-utils for tests * kvdb-test-utils: un-unwrap-ify * kvdb-rocksdb: use kvdb-test-utils for tests * kvdb-web: use kvdb-test-utils for tests * update year in license headers * Cargo.toml: add newlines * rename kvdb-test-utils to kvdb-shared-tests --- Cargo.toml | 1 + kvdb-memorydb/Cargo.toml | 3 + kvdb-memorydb/src/lib.rs | 99 +++----------- kvdb-rocksdb/Cargo.toml | 4 +- kvdb-rocksdb/src/lib.rs | 228 +++++++------------------------- kvdb-shared-tests/Cargo.toml | 10 ++ kvdb-shared-tests/src/lib.rs | 243 +++++++++++++++++++++++++++++++++++ kvdb-web/Cargo.toml | 3 +- kvdb-web/tests/indexed_db.rs | 51 +++++++- 9 files changed, 369 insertions(+), 273 deletions(-) create mode 100644 kvdb-shared-tests/Cargo.toml create mode 100644 kvdb-shared-tests/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index c2511404c..1ba7acb78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "kvdb", "kvdb-memorydb", "kvdb-rocksdb", + "kvdb-shared-tests", "kvdb-web", "parity-bytes", "parity-crypto", diff --git a/kvdb-memorydb/Cargo.toml b/kvdb-memorydb/Cargo.toml index 1451a66b8..bf7d7215c 100644 --- a/kvdb-memorydb/Cargo.toml +++ b/kvdb-memorydb/Cargo.toml @@ -11,3 +11,6 @@ edition = "2018" parity-util-mem = { path = "../parity-util-mem", version = "0.3" } parking_lot = "0.9.0" kvdb = { version = "0.2", path = "../kvdb" } + +[dev-dependencies] +kvdb-shared-tests = { path = "../kvdb-shared-tests", version = "0.1" } diff --git a/kvdb-memorydb/src/lib.rs b/kvdb-memorydb/src/lib.rs index ea0c85649..1f40d24cc 100644 --- a/kvdb-memorydb/src/lib.rs +++ b/kvdb-memorydb/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// Copyright 2015-2020 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify @@ -116,104 +116,43 @@ impl KeyValueDB for InMemory { #[cfg(test)] mod tests { - use super::{create, KeyValueDB}; + use super::create; + use kvdb_shared_tests as st; + use std::io; #[test] - fn get_fails_with_non_existing_column() { + fn get_fails_with_non_existing_column() -> io::Result<()> { let db = create(1); - assert!(db.get(1, &[]).is_err()); + st::test_get_fails_with_non_existing_column(&db) } #[test] - fn put_and_get() { + fn put_and_get() -> io::Result<()> { let db = create(1); - - let key1 = b"key1"; - - let mut transaction = db.transaction(); - transaction.put(0, key1, b"horse"); - db.write_buffered(transaction); - assert_eq!(&*db.get(0, key1).unwrap().unwrap(), b"horse"); + st::test_put_and_get(&db) } #[test] - fn delete_and_get() { + fn delete_and_get() -> io::Result<()> { let db = create(1); - - let key1 = b"key1"; - - let mut transaction = db.transaction(); - transaction.put(0, key1, b"horse"); - db.write_buffered(transaction); - assert_eq!(&*db.get(0, key1).unwrap().unwrap(), b"horse"); - - let mut transaction = db.transaction(); - transaction.delete(0, key1); - db.write_buffered(transaction); - assert!(db.get(0, key1).unwrap().is_none()); + st::test_delete_and_get(&db) } #[test] - fn iter() { + fn iter() -> io::Result<()> { let db = create(1); - - let key1 = b"key1"; - let key2 = b"key2"; - - let mut transaction = db.transaction(); - transaction.put(0, key1, key1); - transaction.put(0, key2, key2); - db.write_buffered(transaction); - - let contents: Vec<_> = db.iter(0).into_iter().collect(); - assert_eq!(contents.len(), 2); - assert_eq!(&*contents[0].0, key1); - assert_eq!(&*contents[0].1, key1); - assert_eq!(&*contents[1].0, key2); - assert_eq!(&*contents[1].1, key2); + st::test_iter(&db) } #[test] - fn iter_from_prefix() { + fn iter_from_prefix() -> io::Result<()> { let db = create(1); + st::test_iter_from_prefix(&db) + } - let key1 = b"0"; - let key2 = b"a"; - let key3 = b"ab"; - - let mut transaction = db.transaction(); - transaction.put(0, key1, key1); - transaction.put(0, key2, key2); - transaction.put(0, key3, key3); - db.write_buffered(transaction); - - let contents: Vec<_> = db.iter_from_prefix(0, b"").into_iter().collect(); - assert_eq!(contents.len(), 3); - assert_eq!(&*contents[0].0, key1); - assert_eq!(&*contents[0].1, key1); - assert_eq!(&*contents[1].0, key2); - assert_eq!(&*contents[1].1, key2); - assert_eq!(&*contents[2].0, key3); - assert_eq!(&*contents[2].1, key3); - - let contents: Vec<_> = db.iter_from_prefix(0, b"0").into_iter().collect(); - assert_eq!(contents.len(), 1); - assert_eq!(&*contents[0].0, key1); - assert_eq!(&*contents[0].1, key1); - - let contents: Vec<_> = db.iter_from_prefix(0, b"a").into_iter().collect(); - assert_eq!(contents.len(), 2); - assert_eq!(&*contents[0].0, key2); - assert_eq!(&*contents[0].1, key2); - assert_eq!(&*contents[1].0, key3); - assert_eq!(&*contents[1].1, key3); - - let contents: Vec<_> = db.iter_from_prefix(0, b"ab").into_iter().collect(); - assert_eq!(contents.len(), 1); - assert_eq!(&*contents[0].0, key3); - assert_eq!(&*contents[0].1, key3); - - let contents: Vec<_> = db.iter_from_prefix(0, b"abc").into_iter().collect(); - assert_eq!(contents.len(), 0); + #[test] + fn complex() -> io::Result<()> { + let db = create(1); + st::test_complex(&db) } } diff --git a/kvdb-rocksdb/Cargo.toml b/kvdb-rocksdb/Cargo.toml index 695c08def..bc71a0b93 100644 --- a/kvdb-rocksdb/Cargo.toml +++ b/kvdb-rocksdb/Cargo.toml @@ -3,7 +3,7 @@ name = "kvdb-rocksdb" version = "0.3.0" authors = ["Parity Technologies "] repository = "https://github.com/paritytech/parity-common" -description = "kvdb implementation backed by rocksDB" +description = "kvdb implementation backed by RocksDB" license = "GPL-3.0" edition = "2018" @@ -27,6 +27,6 @@ parity-util-mem = { path = "../parity-util-mem", version = "0.3" } [dev-dependencies] alloc_counter = "0.0.4" criterion = "0.3" -ethereum-types = { version = "0.8.0", path = "../ethereum-types" } +kvdb-shared-tests = { path = "../kvdb-shared-tests", version = "0.1" } rand = "0.7.2" tempdir = "0.3.7" diff --git a/kvdb-rocksdb/src/lib.rs b/kvdb-rocksdb/src/lib.rs index cb09432dc..1dd64bbcc 100644 --- a/kvdb-rocksdb/src/lib.rs +++ b/kvdb-rocksdb/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2015-2019 Parity Technologies (UK) Ltd. +// Copyright 2015-2020 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify @@ -527,7 +527,9 @@ impl Database { match *self.db.read() { Some(ref cfs) => { self.stats.tally_reads(1); - let overlay = &self.overlay.read()[col as usize]; + let guard = self.overlay.read(); + let overlay = + guard.get(col as usize).ok_or_else(|| other_io_err("kvdb column index is out of bounds"))?; match overlay.get(key) { Some(&KeyState::Insert(ref value)) => Ok(Some(value.clone())), Some(&KeyState::Delete) => Ok(None), @@ -777,73 +779,56 @@ impl Drop for Database { #[cfg(test)] mod tests { use super::*; - use ethereum_types::H256; - use std::io::Read; - use std::str::FromStr; + use kvdb_shared_tests as st; + use std::io::{self, Read}; use tempdir::TempDir; - fn test_db(config: &DatabaseConfig) { - let tempdir = TempDir::new("").unwrap(); - let db = Database::open(config, tempdir.path().to_str().unwrap()).unwrap(); - - let key1 = H256::from_str("02c69be41d0b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc").unwrap(); - let key2 = H256::from_str("03c69be41d0b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc").unwrap(); - let key3 = H256::from_str("04c00000000b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc").unwrap(); - let key4 = H256::from_str("04c01111110b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc").unwrap(); - let key5 = H256::from_str("04c02222220b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc").unwrap(); - - let mut batch = db.transaction(); - batch.put(0, key1.as_bytes(), b"cat"); - batch.put(0, key2.as_bytes(), b"dog"); - batch.put(0, key3.as_bytes(), b"caterpillar"); - batch.put(0, key4.as_bytes(), b"beef"); - batch.put(0, key5.as_bytes(), b"fish"); - db.write(batch).unwrap(); - - assert_eq!(&*db.get(0, key1.as_bytes()).unwrap().unwrap(), b"cat"); - - let contents: Vec<_> = db.iter(0).into_iter().collect(); - assert_eq!(contents.len(), 5); - assert_eq!(&*contents[0].0, key1.as_bytes()); - assert_eq!(&*contents[0].1, b"cat"); - assert_eq!(&*contents[1].0, key2.as_bytes()); - assert_eq!(&*contents[1].1, b"dog"); - - let mut prefix_iter = db.iter_from_prefix(0, &[0x04, 0xc0]); - assert_eq!(*prefix_iter.next().unwrap().1, b"caterpillar"[..]); - assert_eq!(*prefix_iter.next().unwrap().1, b"beef"[..]); - assert_eq!(*prefix_iter.next().unwrap().1, b"fish"[..]); + fn create(columns: u32) -> io::Result { + let tempdir = TempDir::new("")?; + let config = DatabaseConfig::with_columns(columns); + Database::open(&config, tempdir.path().to_str().expect("tempdir path is valid unicode")) + } - let mut batch = db.transaction(); - batch.delete(0, key1.as_bytes()); - db.write(batch).unwrap(); + #[test] + fn get_fails_with_non_existing_column() -> io::Result<()> { + let db = create(1)?; + st::test_get_fails_with_non_existing_column(&db) + } - assert!(db.get(0, key1.as_bytes()).unwrap().is_none()); + #[test] + fn put_and_get() -> io::Result<()> { + let db = create(1)?; + st::test_put_and_get(&db) + } - let mut batch = db.transaction(); - batch.put(0, key1.as_bytes(), b"cat"); - db.write(batch).unwrap(); + #[test] + fn delete_and_get() -> io::Result<()> { + let db = create(1)?; + st::test_delete_and_get(&db) + } - let mut transaction = db.transaction(); - transaction.put(0, key3.as_bytes(), b"elephant"); - transaction.delete(0, key1.as_bytes()); - db.write(transaction).unwrap(); - assert!(db.get(0, key1.as_bytes()).unwrap().is_none()); - assert_eq!(&*db.get(0, key3.as_bytes()).unwrap().unwrap(), b"elephant"); + #[test] + fn iter() -> io::Result<()> { + let db = create(1)?; + st::test_iter(&db) + } - assert_eq!(&*db.get_by_prefix(0, key3.as_bytes()).unwrap(), b"elephant"); - assert_eq!(&*db.get_by_prefix(0, key2.as_bytes()).unwrap(), b"dog"); + #[test] + fn iter_from_prefix() -> io::Result<()> { + let db = create(1)?; + st::test_iter_from_prefix(&db) + } - let mut transaction = db.transaction(); - transaction.put(0, key1.as_bytes(), b"horse"); - transaction.delete(0, key3.as_bytes()); - db.write_buffered(transaction); - assert!(db.get(0, key3.as_bytes()).unwrap().is_none()); - assert_eq!(&*db.get(0, key1.as_bytes()).unwrap().unwrap(), b"horse"); + #[test] + fn complex() -> io::Result<()> { + let db = create(1)?; + st::test_complex(&db) + } - db.flush().unwrap(); - assert!(db.get(0, key3.as_bytes()).unwrap().is_none()); - assert_eq!(&*db.get(0, key1.as_bytes()).unwrap().unwrap(), b"horse"); + #[test] + fn stats() -> io::Result<()> { + let db = create(3)?; + st::test_io_stats(&db) } #[test] @@ -876,14 +861,6 @@ mod tests { } } - #[test] - fn kvdb() { - let tempdir = TempDir::new("").unwrap(); - let config = DatabaseConfig::default(); - let _ = Database::open(&config, tempdir.path().to_str().unwrap()).unwrap(); - test_db(&config); - } - #[test] #[cfg(target_os = "linux")] fn df_to_rotational() { @@ -977,121 +954,6 @@ mod tests { assert_eq!(db.num_keys(0).unwrap(), 1, "adding a key increases the count"); } - #[test] - fn stats() { - use kvdb::IoStatsKind; - - let tempdir = TempDir::new("").unwrap(); - let config = DatabaseConfig::with_columns(3); - let db = Database::open(&config, tempdir.path().to_str().unwrap()).unwrap(); - - let key1 = b"kkk"; - let mut batch = db.transaction(); - batch.put(0, key1, key1); - batch.put(1, key1, key1); - batch.put(2, key1, key1); - - for _ in 0..10 { - db.get(0, key1).unwrap(); - } - - db.write(batch).unwrap(); - - let io_stats = db.io_stats(IoStatsKind::SincePrevious); - assert_eq!(io_stats.transactions, 1); - assert_eq!(io_stats.writes, 3); - assert_eq!(io_stats.bytes_written, 18); - assert_eq!(io_stats.reads, 10); - assert_eq!(io_stats.bytes_read, 30); - - let new_io_stats = db.io_stats(IoStatsKind::SincePrevious); - // Since we taken previous statistic period, - // this is expected to be totally empty. - assert_eq!(new_io_stats.transactions, 0); - - // but the overall should be there - let new_io_stats = db.io_stats(IoStatsKind::Overall); - assert_eq!(new_io_stats.bytes_written, 18); - - let mut batch = db.transaction(); - batch.delete(0, key1); - batch.delete(1, key1); - batch.delete(2, key1); - - // transaction is not commited yet - assert_eq!(db.io_stats(IoStatsKind::SincePrevious).writes, 0); - - db.write(batch).unwrap(); - // now it is, and delete is counted as write - assert_eq!(db.io_stats(IoStatsKind::SincePrevious).writes, 3); - } - - #[test] - fn test_iter_by_prefix() { - let tempdir = TempDir::new("").unwrap(); - let config = DatabaseConfig::with_columns(1); - let db = Database::open(&config, tempdir.path().to_str().unwrap()).unwrap(); - - let key1 = b"0"; - let key2 = b"ab"; - let key3 = b"abc"; - let key4 = b"abcd"; - - let mut batch = db.transaction(); - batch.put(0, key1, key1); - batch.put(0, key2, key2); - batch.put(0, key3, key3); - batch.put(0, key4, key4); - db.write(batch).unwrap(); - - // empty prefix - let contents: Vec<_> = db.iter_from_prefix(0, b"").into_iter().collect(); - assert_eq!(contents.len(), 4); - assert_eq!(&*contents[0].0, key1); - assert_eq!(&*contents[1].0, key2); - assert_eq!(&*contents[2].0, key3); - assert_eq!(&*contents[3].0, key4); - - // prefix a - let contents: Vec<_> = db.iter_from_prefix(0, b"a").into_iter().collect(); - assert_eq!(contents.len(), 3); - assert_eq!(&*contents[0].0, key2); - assert_eq!(&*contents[1].0, key3); - assert_eq!(&*contents[2].0, key4); - - // prefix abc - let contents: Vec<_> = db.iter_from_prefix(0, b"abc").into_iter().collect(); - assert_eq!(contents.len(), 2); - assert_eq!(&*contents[0].0, key3); - assert_eq!(&*contents[1].0, key4); - - // prefix abcde - let contents: Vec<_> = db.iter_from_prefix(0, b"abcde").into_iter().collect(); - assert_eq!(contents.len(), 0); - - // prefix 0 - let contents: Vec<_> = db.iter_from_prefix(0, b"0").into_iter().collect(); - assert_eq!(contents.len(), 1); - assert_eq!(&*contents[0].0, key1); - } - - #[test] - fn write_clears_buffered_ops() { - let tempdir = TempDir::new("").unwrap(); - let config = DatabaseConfig::with_columns(1); - let db = Database::open(&config, tempdir.path().to_str().unwrap()).unwrap(); - - let mut batch = db.transaction(); - batch.put(0, b"foo", b"bar"); - db.write_buffered(batch); - - let mut batch = db.transaction(); - batch.put(0, b"foo", b"baz"); - db.write(batch).unwrap(); - - assert_eq!(db.get(0, b"foo").unwrap().unwrap(), b"baz"); - } - #[test] fn default_memory_budget() { let c = DatabaseConfig::default(); diff --git a/kvdb-shared-tests/Cargo.toml b/kvdb-shared-tests/Cargo.toml new file mode 100644 index 000000000..4679a4900 --- /dev/null +++ b/kvdb-shared-tests/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "kvdb-shared-tests" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" +description = "Shared tests for kvdb functionality, to be executed against actual implementations" +license = "GPL-3.0" + +[dependencies] +kvdb = { path = "../kvdb", version = "0.2" } diff --git a/kvdb-shared-tests/src/lib.rs b/kvdb-shared-tests/src/lib.rs new file mode 100644 index 000000000..28613c4f3 --- /dev/null +++ b/kvdb-shared-tests/src/lib.rs @@ -0,0 +1,243 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Shared tests for kvdb functionality, to be executed against actual implementations. + +use kvdb::{IoStatsKind, KeyValueDB}; +use std::io; + +/// A test for `KeyValueDB::get`. +pub fn test_put_and_get(db: &dyn KeyValueDB) -> io::Result<()> { + let key1 = b"key1"; + + let mut transaction = db.transaction(); + transaction.put(0, key1, b"horse"); + db.write_buffered(transaction); + assert_eq!(&*db.get(0, key1)?.unwrap(), b"horse"); + Ok(()) +} + +/// A test for `KeyValueDB::get`. +pub fn test_delete_and_get(db: &dyn KeyValueDB) -> io::Result<()> { + let key1 = b"key1"; + + let mut transaction = db.transaction(); + transaction.put(0, key1, b"horse"); + db.write_buffered(transaction); + assert_eq!(&*db.get(0, key1)?.unwrap(), b"horse"); + + let mut transaction = db.transaction(); + transaction.delete(0, key1); + db.write_buffered(transaction); + assert!(db.get(0, key1)?.is_none()); + Ok(()) +} + +/// A test for `KeyValueDB::get`. +/// Assumes the `db` has only 1 column. +pub fn test_get_fails_with_non_existing_column(db: &dyn KeyValueDB) -> io::Result<()> { + assert!(db.get(1, &[]).is_err()); + Ok(()) +} + +/// A test for `KeyValueDB::write`. +pub fn test_write_clears_buffered_ops(db: &dyn KeyValueDB) -> io::Result<()> { + let mut batch = db.transaction(); + batch.put(0, b"foo", b"bar"); + db.write_buffered(batch); + + assert_eq!(db.get(0, b"foo")?.unwrap(), b"bar"); + + let mut batch = db.transaction(); + batch.put(0, b"foo", b"baz"); + db.write(batch)?; + + assert_eq!(db.get(0, b"foo")?.unwrap(), b"baz"); + Ok(()) +} + +/// A test for `KeyValueDB::iter`. +pub fn test_iter(db: &dyn KeyValueDB) -> io::Result<()> { + let key1 = b"key1"; + let key2 = b"key2"; + + let mut transaction = db.transaction(); + transaction.put(0, key1, key1); + transaction.put(0, key2, key2); + db.write_buffered(transaction); + + let contents: Vec<_> = db.iter(0).into_iter().collect(); + assert_eq!(contents.len(), 2); + assert_eq!(&*contents[0].0, key1); + assert_eq!(&*contents[0].1, key1); + assert_eq!(&*contents[1].0, key2); + assert_eq!(&*contents[1].1, key2); + Ok(()) +} + +/// A test for `KeyValueDB::iter_from_prefix`. +pub fn test_iter_from_prefix(db: &dyn KeyValueDB) -> io::Result<()> { + let key1 = b"0"; + let key2 = b"ab"; + let key3 = b"abc"; + let key4 = b"abcd"; + + let mut batch = db.transaction(); + batch.put(0, key1, key1); + batch.put(0, key2, key2); + batch.put(0, key3, key3); + batch.put(0, key4, key4); + db.write(batch)?; + + // empty prefix + let contents: Vec<_> = db.iter_from_prefix(0, b"").into_iter().collect(); + assert_eq!(contents.len(), 4); + assert_eq!(&*contents[0].0, key1); + assert_eq!(&*contents[1].0, key2); + assert_eq!(&*contents[2].0, key3); + assert_eq!(&*contents[3].0, key4); + + // prefix a + let contents: Vec<_> = db.iter_from_prefix(0, b"a").into_iter().collect(); + assert_eq!(contents.len(), 3); + assert_eq!(&*contents[0].0, key2); + assert_eq!(&*contents[1].0, key3); + assert_eq!(&*contents[2].0, key4); + + // prefix abc + let contents: Vec<_> = db.iter_from_prefix(0, b"abc").into_iter().collect(); + assert_eq!(contents.len(), 2); + assert_eq!(&*contents[0].0, key3); + assert_eq!(&*contents[1].0, key4); + + // prefix abcde + let contents: Vec<_> = db.iter_from_prefix(0, b"abcde").into_iter().collect(); + assert_eq!(contents.len(), 0); + + // prefix 0 + let contents: Vec<_> = db.iter_from_prefix(0, b"0").into_iter().collect(); + assert_eq!(contents.len(), 1); + assert_eq!(&*contents[0].0, key1); + Ok(()) +} + +/// A test for `KeyValueDB::io_stats`. +/// Assumes that the `db` has at least 3 columns. +pub fn test_io_stats(db: &dyn KeyValueDB) -> io::Result<()> { + let key1 = b"kkk"; + let mut batch = db.transaction(); + batch.put(0, key1, key1); + batch.put(1, key1, key1); + batch.put(2, key1, key1); + + for _ in 0..10 { + db.get(0, key1)?; + } + + db.write(batch)?; + + let io_stats = db.io_stats(IoStatsKind::SincePrevious); + assert_eq!(io_stats.transactions, 1); + assert_eq!(io_stats.writes, 3); + assert_eq!(io_stats.bytes_written, 18); + assert_eq!(io_stats.reads, 10); + assert_eq!(io_stats.bytes_read, 30); + + let new_io_stats = db.io_stats(IoStatsKind::SincePrevious); + // Since we taken previous statistic period, + // this is expected to be totally empty. + assert_eq!(new_io_stats.transactions, 0); + + // but the overall should be there + let new_io_stats = db.io_stats(IoStatsKind::Overall); + assert_eq!(new_io_stats.bytes_written, 18); + + let mut batch = db.transaction(); + batch.delete(0, key1); + batch.delete(1, key1); + batch.delete(2, key1); + + // transaction is not commited yet + assert_eq!(db.io_stats(IoStatsKind::SincePrevious).writes, 0); + + db.write(batch)?; + // now it is, and delete is counted as write + assert_eq!(db.io_stats(IoStatsKind::SincePrevious).writes, 3); + Ok(()) +} + +/// A complex test. +pub fn test_complex(db: &dyn KeyValueDB) -> io::Result<()> { + let key1 = b"02c69be41d0b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc"; + let key2 = b"03c69be41d0b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc"; + let key3 = b"04c00000000b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc"; + let key4 = b"04c01111110b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc"; + let key5 = b"04c02222220b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc"; + + let mut batch = db.transaction(); + batch.put(0, key1, b"cat"); + batch.put(0, key2, b"dog"); + batch.put(0, key3, b"caterpillar"); + batch.put(0, key4, b"beef"); + batch.put(0, key5, b"fish"); + db.write(batch)?; + + assert_eq!(&*db.get(0, key1)?.unwrap(), b"cat"); + + let contents: Vec<_> = db.iter(0).into_iter().collect(); + assert_eq!(contents.len(), 5); + assert_eq!(contents[0].0.to_vec(), key1.to_vec()); + assert_eq!(&*contents[0].1, b"cat"); + assert_eq!(contents[1].0.to_vec(), key2.to_vec()); + assert_eq!(&*contents[1].1, b"dog"); + + let mut prefix_iter = db.iter_from_prefix(0, b"04c0"); + assert_eq!(*prefix_iter.next().unwrap().1, b"caterpillar"[..]); + assert_eq!(*prefix_iter.next().unwrap().1, b"beef"[..]); + assert_eq!(*prefix_iter.next().unwrap().1, b"fish"[..]); + + let mut batch = db.transaction(); + batch.delete(0, key1); + db.write(batch)?; + + assert!(db.get(0, key1)?.is_none()); + + let mut batch = db.transaction(); + batch.put(0, key1, b"cat"); + db.write(batch)?; + + let mut transaction = db.transaction(); + transaction.put(0, key3, b"elephant"); + transaction.delete(0, key1); + db.write(transaction)?; + assert!(db.get(0, key1)?.is_none()); + assert_eq!(&*db.get(0, key3)?.unwrap(), b"elephant"); + + assert_eq!(&*db.get_by_prefix(0, key3).unwrap(), b"elephant"); + assert_eq!(&*db.get_by_prefix(0, key2).unwrap(), b"dog"); + + let mut transaction = db.transaction(); + transaction.put(0, key1, b"horse"); + transaction.delete(0, key3); + db.write_buffered(transaction); + assert!(db.get(0, key3)?.is_none()); + assert_eq!(&*db.get(0, key1)?.unwrap(), b"horse"); + + db.flush()?; + assert!(db.get(0, key3)?.is_none()); + assert_eq!(&*db.get(0, key1)?.unwrap(), b"horse"); + Ok(()) +} diff --git a/kvdb-web/Cargo.toml b/kvdb-web/Cargo.toml index fb3af40ea..5307cd70e 100644 --- a/kvdb-web/Cargo.toml +++ b/kvdb-web/Cargo.toml @@ -38,6 +38,7 @@ features = [ ] [dev-dependencies] -wasm-bindgen-test = "0.3.4" console_log = "0.1.2" +kvdb-shared-tests = { path = "../kvdb-shared-tests", version = "0.1" } +wasm-bindgen-test = "0.3.4" wasm-bindgen-futures = "0.4.4" diff --git a/kvdb-web/tests/indexed_db.rs b/kvdb-web/tests/indexed_db.rs index 9dc0556d4..fe5d8f6a3 100644 --- a/kvdb-web/tests/indexed_db.rs +++ b/kvdb-web/tests/indexed_db.rs @@ -1,4 +1,4 @@ -// Copyright 2019 Parity Technologies (UK) Ltd. +// Copyright 2019-2020 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify @@ -18,21 +18,58 @@ use futures::future::TryFutureExt as _; +use kvdb_shared_tests as st; use kvdb_web::{Database, KeyValueDB as _}; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); +async fn open_db(col: u32, name: &str) -> Database { + Database::open(name.into(), col).unwrap_or_else(|err| panic!("{}", err)).await +} + +#[wasm_bindgen_test] +async fn get_fails_with_non_existing_column() { + let db = open_db(1, "get_fails_with_non_existing_column").await; + st::test_get_fails_with_non_existing_column(&db).unwrap() +} + +#[wasm_bindgen_test] +async fn put_and_get() { + let db = open_db(1, "put_and_get").await; + st::test_put_and_get(&db).unwrap() +} + +#[wasm_bindgen_test] +async fn delete_and_get() { + let db = open_db(1, "delete_and_get").await; + st::test_delete_and_get(&db).unwrap() +} + +#[wasm_bindgen_test] +async fn iter() { + let db = open_db(1, "iter").await; + st::test_iter(&db).unwrap() +} + +#[wasm_bindgen_test] +async fn iter_from_prefix() { + let db = open_db(1, "iter_from_prefix").await; + st::test_iter_from_prefix(&db).unwrap() +} + +#[wasm_bindgen_test] +async fn complex() { + let db = open_db(1, "complex").await; + st::test_complex(&db).unwrap() +} + #[wasm_bindgen_test] async fn reopen_the_database_with_more_columns() { let _ = console_log::init_with_level(log::Level::Trace); - async fn open_db(col: u32) -> Database { - Database::open("MyAsyncTest".into(), col).unwrap_or_else(|err| panic!("{}", err)).await - } - - let db = open_db(1).await; + let db = open_db(1, "reopen_the_database_with_more_columns").await; // Write a value into the database let mut batch = db.transaction(); @@ -48,7 +85,7 @@ async fn reopen_the_database_with_more_columns() { drop(db); // Reopen it again with 3 columns - let db = open_db(3).await; + let db = open_db(3, "reopen_the_database_with_more_columns").await; // The value should still be present assert_eq!(db.get(0, b"hello").unwrap().unwrap(), b"world");