diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
commit | 9835e2ae736235810b4ea1c162ca5e65c547e770 (patch) | |
tree | 3fcebf40ed70e581d776a8a4c65923e8ec20e026 /vendor/gix-fs/tests/dir | |
parent | Releasing progress-linux version 1.70.0+dfsg2-1~progress7.99u1. (diff) | |
download | rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.tar.xz rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.zip |
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-fs/tests/dir')
-rw-r--r-- | vendor/gix-fs/tests/dir/create.rs | 194 | ||||
-rw-r--r-- | vendor/gix-fs/tests/dir/mod.rs | 2 | ||||
-rw-r--r-- | vendor/gix-fs/tests/dir/remove.rs | 146 |
3 files changed, 342 insertions, 0 deletions
diff --git a/vendor/gix-fs/tests/dir/create.rs b/vendor/gix-fs/tests/dir/create.rs new file mode 100644 index 000000000..6693fd071 --- /dev/null +++ b/vendor/gix-fs/tests/dir/create.rs @@ -0,0 +1,194 @@ +mod all { + use gix_fs::dir::create; + + #[test] + fn a_deeply_nested_directory() -> crate::Result { + let dir = tempfile::tempdir()?; + let target = &dir.path().join("1").join("2").join("3").join("4").join("5").join("6"); + let dir = create::all(target, Default::default())?; + assert_eq!(dir, target, "all subdirectories can be created"); + Ok(()) + } +} +mod iter { + pub use std::io::ErrorKind::*; + + use gix_fs::dir::{ + create, + create::{Error::*, Retries}, + }; + + #[test] + fn an_existing_directory_causes_immediate_success() -> crate::Result { + let dir = tempfile::tempdir()?; + let mut it = create::Iter::new(dir.path()); + assert_eq!( + it.next().expect("item").expect("success"), + dir.path(), + "first iteration is immediately successful" + ); + assert!(it.next().is_none(), "iterator exhausted afterwards"); + Ok(()) + } + + #[test] + fn a_single_directory_can_be_created_too() -> crate::Result { + let dir = tempfile::tempdir()?; + let new_dir = dir.path().join("new"); + let mut it = create::Iter::new(&new_dir); + assert_eq!( + it.next().expect("item").expect("success"), + &new_dir, + "first iteration is immediately successful" + ); + assert!(it.next().is_none(), "iterator exhausted afterwards"); + assert!(new_dir.is_dir(), "the directory exists"); + Ok(()) + } + + #[test] + fn multiple_intermediate_directories_are_created_automatically() -> crate::Result { + let dir = tempfile::tempdir()?; + let new_dir = dir.path().join("s1").join("s2").join("new"); + let mut it = create::Iter::new(&new_dir); + assert!( + matches!(it.next(), Some(Err(Intermediate{dir, kind: k})) if k == NotFound && dir == new_dir), + "dir is not present" + ); + assert!( + matches!(it.next(), Some(Err(Intermediate{dir, kind:k})) if k == NotFound && dir == new_dir.parent().unwrap()), + "parent dir is not present" + ); + assert_eq!( + it.next().expect("item").expect("success"), + new_dir.parent().unwrap().parent().unwrap(), + "first subdir is created" + ); + assert_eq!( + it.next().expect("item").expect("success"), + new_dir.parent().unwrap(), + "second subdir is created" + ); + assert_eq!( + it.next().expect("item").expect("success"), + new_dir, + "target directory is created" + ); + assert!(it.next().is_none(), "iterator depleted"); + assert!(new_dir.is_dir(), "the directory exists"); + Ok(()) + } + + #[test] + fn multiple_intermediate_directories_are_created_up_to_retries_limit() -> crate::Result { + let dir = tempfile::tempdir()?; + let new_dir = dir.path().join("s1").join("s2").join("new"); + let mut it = create::Iter::new_with_retries( + &new_dir, + Retries { + on_create_directory_failure: 1, + ..Default::default() + }, + ); + assert!( + matches!(it.next(), Some(Err(Permanent{ retries_left, dir, err, ..})) if retries_left.on_create_directory_failure == 0 + && err.kind() == NotFound + && dir == new_dir), + "parent dir is not present and we run out of attempts" + ); + assert!(it.next().is_none(), "iterator depleted"); + assert!(!new_dir.is_dir(), "the wasn't created"); + Ok(()) + } + + #[test] + fn an_existing_file_makes_directory_creation_fail_permanently() -> crate::Result { + let dir = tempfile::tempdir()?; + let new_dir = dir.path().join("also-file"); + std::fs::write(&new_dir, [42])?; + assert!(new_dir.is_file()); + + let mut it = create::Iter::new(&new_dir); + assert!( + matches!(it.next(), Some(Err(Permanent{ dir, err, .. })) if err.kind() == AlreadyExists + && dir == new_dir), + "parent dir is not present and we run out of attempts" + ); + assert!(it.next().is_none(), "iterator depleted"); + assert!(new_dir.is_file(), "file is untouched"); + Ok(()) + } + #[test] + fn racy_directory_creation_with_new_directory_being_deleted_not_enough_retries() -> crate::Result { + let dir = tempfile::tempdir()?; + let new_dir = dir.path().join("a").join("new"); + let parent_dir = new_dir.parent().unwrap(); + let mut it = create::Iter::new_with_retries( + &new_dir, + Retries { + to_create_entire_directory: 2, + on_create_directory_failure: 2, + ..Default::default() + }, + ); + + assert!( + matches!(it.nth(1), Some(Ok(dir)) if dir == parent_dir), + "parent dir is created" + ); + // Someone deletes the new directory + std::fs::remove_dir(parent_dir)?; + + assert!( + matches!(it.nth(1), Some(Ok(dir)) if dir == parent_dir), + "parent dir is created" + ); + // Someone deletes the new directory, again + std::fs::remove_dir(parent_dir)?; + + assert!( + matches!(it.next(), Some(Err(Permanent{ retries_left, dir, err, .. })) if retries_left.to_create_entire_directory == 0 + && retries_left.on_create_directory_failure == 1 + && err.kind() == NotFound + && dir == new_dir), + "we run out of attempts to retry to combat against raciness" + ); + Ok(()) + } + + #[test] + fn racy_directory_creation_with_new_directory_being_deleted() -> crate::Result { + let dir = tempfile::tempdir()?; + let new_dir = dir.path().join("a").join("new"); + let parent_dir = new_dir.parent().unwrap(); + let mut it = create::Iter::new(&new_dir); + + assert!( + matches!(it.next(), Some(Err(Intermediate{dir, kind:k})) if k == NotFound && dir == new_dir), + "dir is not present, and we go up a level" + ); + assert!( + matches!(it.next(), Some(Ok(dir)) if dir == parent_dir), + "parent dir is created" + ); + // Someone deletes the new directory + std::fs::remove_dir(parent_dir)?; + + assert!( + matches!(it.next(), Some(Err(Intermediate{dir, kind:k})) if k == NotFound && dir == new_dir), + "now when it tries the actual dir its not found" + ); + assert!( + matches!(it.next(), Some(Ok(dir)) if dir == parent_dir), + "parent dir is created as it retries" + ); + assert!( + matches!(it.next(), Some(Ok(dir)) if dir == new_dir), + "target dir is created successfully" + ); + assert!(it.next().is_none(), "iterator depleted"); + assert!(new_dir.is_dir()); + + Ok(()) + } +} diff --git a/vendor/gix-fs/tests/dir/mod.rs b/vendor/gix-fs/tests/dir/mod.rs new file mode 100644 index 000000000..0008e7ee8 --- /dev/null +++ b/vendor/gix-fs/tests/dir/mod.rs @@ -0,0 +1,2 @@ +mod create; +mod remove; diff --git a/vendor/gix-fs/tests/dir/remove.rs b/vendor/gix-fs/tests/dir/remove.rs new file mode 100644 index 000000000..4b08e5147 --- /dev/null +++ b/vendor/gix-fs/tests/dir/remove.rs @@ -0,0 +1,146 @@ +mod empty_upwards_until_boundary { + use std::{io, path::Path}; + + use gix_fs::dir::remove; + + #[test] + fn boundary_must_contain_target_dir() -> crate::Result { + let dir = tempfile::tempdir()?; + let (target, boundary) = (dir.path().join("a"), dir.path().join("b")); + std::fs::create_dir(&target)?; + std::fs::create_dir(&boundary)?; + assert!(matches!(remove::empty_upward_until_boundary(&target, &boundary), + Err(err) if err.kind() == io::ErrorKind::InvalidInput)); + assert!(target.is_dir()); + assert!(boundary.is_dir()); + Ok(()) + } + #[test] + fn target_directory_non_existing_causes_existing_parents_not_to_be_deleted() -> crate::Result { + let dir = tempfile::tempdir()?; + let parent = dir.path().join("a"); + std::fs::create_dir(&parent)?; + let target = parent.join("not-existing"); + assert_eq!(remove::empty_upward_until_boundary(&target, dir.path())?, target); + assert!( + parent.is_dir(), + "the parent wasn't touched if the target already wasn't present" + ); + Ok(()) + } + + #[test] + fn target_directory_being_a_file_immediately_fails() -> crate::Result { + let dir = tempfile::tempdir()?; + let target = dir.path().join("actually-a-file"); + std::fs::write(&target, [42])?; + assert!(remove::empty_upward_until_boundary(&target, dir.path()).is_err()); // TODO: check for IsNotADirectory when it becomes stable + assert!(target.is_file(), "it didn't touch the file"); + assert!(dir.path().is_dir(), "it won't touch the boundary"); + Ok(()) + } + #[test] + fn boundary_being_the_target_dir_always_succeeds_and_we_do_nothing() -> crate::Result { + let dir = tempfile::tempdir()?; + assert_eq!(remove::empty_upward_until_boundary(dir.path(), dir.path())?, dir.path()); + assert!(dir.path().is_dir(), "it won't touch the boundary"); + Ok(()) + } + #[test] + fn a_directory_which_doesnt_exist_to_start_with_is_ok() -> crate::Result { + let dir = tempfile::tempdir()?; + let target = dir.path().join("does-not-exist"); + assert_eq!(remove::empty_upward_until_boundary(&target, dir.path())?, target); + assert!(dir.path().is_dir(), "it won't touch the boundary"); + Ok(()) + } + #[test] + fn boundary_directory_doesnt_have_to_exist_either_if_the_target_doesnt() -> crate::Result { + let boundary = Path::new("/boundary"); + let target = Path::new("/boundary/target"); + assert_eq!(remove::empty_upward_until_boundary(target, boundary)?, target); + Ok(()) + } + #[test] + fn nested_directory_deletion_works() -> crate::Result { + let dir = tempfile::tempdir()?; + let nested = dir.path().join("a").join("b").join("to-delete"); + std::fs::create_dir_all(&nested)?; + assert_eq!(remove::empty_upward_until_boundary(&nested, dir.path())?, nested); + assert!(!nested.is_dir(), "it actually deleted the nested directory"); + assert!(!nested.parent().unwrap().is_dir(), "parent one was deleted"); + assert!( + !nested.parent().unwrap().parent().unwrap().is_dir(), + "parent two was deleted" + ); + assert!(dir.path().is_dir(), "it won't touch the boundary"); + Ok(()) + } +} + +mod empty_depth_first { + use std::{ + fs::{create_dir, create_dir_all}, + path::Path, + }; + + #[test] + fn non_empty_anywhere_and_deletion_fails() -> crate::Result { + let dir = tempfile::TempDir::new()?; + let touch = |base: &Path, name: &str| create_dir_all(base).and_then(|_| std::fs::write(base.join(name), b"")); + + let nested_parent = dir.path().join("a"); + touch(&nested_parent, "hello.ext")?; + + let tree_parent = dir.path().join("tree"); + touch(&tree_parent.join("a").join("b"), "hello.ext")?; + create_dir_all(tree_parent.join("one").join("two").join("empty"))?; + + assert!(gix_fs::dir::remove::empty_depth_first(nested_parent).is_err()); + Ok(()) + } + + #[test] + fn nested_empty_and_single_empty_delete_successfully() { + let dir = tempfile::TempDir::new().unwrap(); + let nested_parent = dir.path().join("a"); + let nested = nested_parent.join("b").join("leaf"); + create_dir_all(nested).unwrap(); + + let single_parent = dir.path().join("single"); + create_dir(&single_parent).unwrap(); + + let tree_parent = dir.path().join("tree"); + create_dir_all(tree_parent.join("a").join("b")).unwrap(); + create_dir_all(tree_parent.join("one").join("two").join("three")).unwrap(); + create_dir_all(tree_parent.join("c")).unwrap(); + for empty in &[nested_parent, single_parent, tree_parent] { + gix_fs::dir::remove::empty_depth_first(empty).unwrap(); + } + } +} + +/// We assume that all checks above also apply to the iterator, so won't repeat them here +/// Test outside interference only +mod iter { + use gix_fs::dir::remove; + + #[test] + fn racy_directory_creation_during_deletion_always_wins_immediately() -> crate::Result { + let dir = tempfile::tempdir()?; + let nested = dir.path().join("a").join("b").join("to-delete"); + std::fs::create_dir_all(&nested)?; + + let mut it = remove::Iter::new(&nested, dir.path())?; + assert_eq!(it.next().expect("item")?, nested, "delete leaves directory"); + + // recreate the deleted directory in racy fashion, causing the next-to-delete directory not to be empty. + std::fs::create_dir(&nested)?; + assert!( + it.next().expect("err item").is_err(), + "cannot delete non-empty directory" // TODO: check for IsADirectory when it becomes stable + ); + assert!(it.next().is_none(), "iterator is depleted"); + Ok(()) + } +} |