diff --git a/Cargo.toml b/Cargo.toml index e309e0b..0759728 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ wrap-key-to-file = ["chacha20poly1305"] chunked = [] encrypted-chunked = ["chunked", "chacha20poly1305/stream"] manage = [] +migration-tests = ["manage"] virt = ["std", "trussed/virt"] std = [] diff --git a/src/manage.rs b/src/manage.rs index 3983211..fb897c6 100644 --- a/src/manage.rs +++ b/src/manage.rs @@ -15,6 +15,9 @@ use trussed::{ Error, }; +#[cfg(feature = "migration-tests")] +pub mod test_utils; + #[derive(Debug)] pub struct Migrator { /// The function performing the migration diff --git a/src/manage/test_utils.rs b/src/manage/test_utils.rs new file mode 100644 index 0000000..eae7b94 --- /dev/null +++ b/src/manage/test_utils.rs @@ -0,0 +1,131 @@ +// Copyright (C) Nitrokey GmbH +// SPDX-License-Identifier: Apache-2.0 or MIT + +use littlefs2::{ + fs::Filesystem, io::Error, object_safe::DynFilesystem, path, path::Path, ram_storage, +}; + +/// Represent a directory of data +pub enum FsValues { + Dir(&'static [(&'static Path, FsValues)]), + File(usize), +} + +type Result = core::result::Result; + +/// Prepare the filesystem for a given tests values +pub fn prepare_fs(fs: &dyn DynFilesystem, value: &FsValues, path: &Path) { + match value { + FsValues::File(f_data_len) => { + fs.create_file_and_then(path, &mut |f| { + f.set_len(*f_data_len).unwrap(); + Ok(()) + }) + .unwrap(); + } + FsValues::Dir(d) => { + if path != path!("/") { + fs.create_dir(path).unwrap(); + } + for (p, v) in *d { + prepare_fs(fs, v, &path.join(p)); + } + } + } +} + +/// Test equality between the filesystem and the expected values +pub fn test_fs_equality(fs: &dyn DynFilesystem, value: &FsValues, path: &Path) { + match value { + FsValues::Dir(d) => { + let mut expected_iter = d.iter(); + fs.read_dir_and_then(path, &mut |dir| { + // skip . and .. + dir.next().unwrap().unwrap(); + dir.next().unwrap().unwrap(); + for (expected_path, expected_values) in expected_iter.by_ref() { + let entry = dir.next().unwrap().unwrap(); + assert_eq!(entry.file_name(), *expected_path); + test_fs_equality(fs, expected_values, &path.join(expected_path)); + } + assert!(dir.next().is_none()); + Ok(()) + }) + .unwrap(); + } + FsValues::File(f_data_len) => { + fs.open_file_and_then(path, &mut |f| { + let mut buf = [0; 512]; + let data = f.read(&mut buf).unwrap(); + assert_eq!(data, *f_data_len); + Ok(()) + }) + .unwrap(); + } + } +} + +ram_storage!( + name=NoBackendStorage, + backend=RamDirect, + trait=littlefs2::driver::Storage, + erase_value=0xff, + read_size=16, + write_size=16, + cache_size_ty=littlefs2::consts::U512, + block_size=512, + block_count=128, + lookahead_size_ty=littlefs2::consts::U8, + filename_max_plus_one_ty=littlefs2::consts::U256, + path_max_plus_one_ty=littlefs2::consts::U256, + result=Result, +); + +pub fn test_migration_one( + before: &FsValues, + after: &FsValues, + migrate: impl Fn(&dyn DynFilesystem) -> Result<(), Error>, +) { + test_migration( + before, + after, + &FsValues::Dir(&[]), + &FsValues::Dir(&[]), + |ifs, _efs| migrate(ifs), + ); +} + +pub fn test_migration( + before_ifs: &FsValues, + after_ifs: &FsValues, + before_efs: &FsValues, + after_efs: &FsValues, + migrate: impl Fn(&dyn DynFilesystem, &dyn DynFilesystem) -> Result<(), Error>, +) { + let mut storage_ifs = RamDirect::default(); + let mut storage_efs = RamDirect::default(); + + let backend_efs = &mut NoBackendStorage::new(&mut storage_efs); + let backend_ifs = &mut NoBackendStorage::new(&mut storage_ifs); + + Filesystem::format(backend_ifs).unwrap(); + Filesystem::format(backend_efs).unwrap(); + + Filesystem::mount_and_then(backend_ifs, |ifs| { + Filesystem::mount_and_then(backend_efs, |efs| { + prepare_fs(ifs, before_ifs, path!("/")); + prepare_fs(efs, before_efs, path!("/")); + + test_fs_equality(ifs, before_ifs, path!("/")); + test_fs_equality(efs, before_efs, path!("/")); + + migrate(ifs, efs).unwrap(); + test_fs_equality(efs, after_efs, path!("/")); + test_fs_equality(ifs, after_ifs, path!("/")); + Ok(()) + }) + .unwrap(); + Ok(()) + }) + .unwrap(); +}