Repository navigation
Add shared test fixtures and helper functions #76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -69,20 +69,35 @@ impl fmt::Display for PrefixedPath { | |
| } | ||
| } | ||
|
|
||
| static IO_THREAD_CONTROLLER_ROOT_PATH: LazyLock<std::path::PathBuf> = LazyLock::new(|| { | ||
| static ROOT_PATH: LazyLock<std::path::PathBuf> = LazyLock::new(|| { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the rename? |
||
| std::path::PathBuf::from( | ||
| env::var("IO_THREAD_CONTROLLER_ROOT_PATH").unwrap_or(String::from("/")), | ||
| ) | ||
| }); | ||
|
|
||
| /// Returns the path new `util::Path`s will have prefixed. In non-test | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand this sentence. |
||
| /// configurations, this will be either `/` or the contents of the | ||
| /// `IO_THREAD_CONTROLLER_ROOT_PATH` env var. | ||
| /// | ||
| /// In test configurations, this may differ per thread to support concurrently | ||
| /// running unit tests. | ||
| pub(crate) fn effective_root() -> std::path::PathBuf { | ||
| #[cfg(test)] | ||
| if let Some(root) = crate::test::mockdir::ROOT_OVERRIDE.with(|slot| slot.borrow().clone()) { | ||
| return root; | ||
| } | ||
|
|
||
| ROOT_PATH.clone() | ||
| } | ||
|
|
||
| impl PrefixedPath { | ||
| pub fn new<S>(s: &S) -> Self | ||
| where | ||
| S: AsRef<OsStr> + ?Sized, | ||
| { | ||
| let mut pb = std::path::PathBuf::from(s); | ||
| if pb.is_absolute() { | ||
| pb = IO_THREAD_CONTROLLER_ROOT_PATH.join(pb.strip_prefix("/").expect("absolute path")) | ||
| pb = effective_root().join(pb.strip_prefix("/").expect("absolute path")) | ||
| } | ||
| Self { path: pb } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
| // Copyright (c) 2026 Nutanix, Inc. | ||
| // | ||
| // Author: Leonardo Forchini <leonardo.forchini@nutanix.com> | ||
| // | ||
| //! `tempfile::TempDir` wrapper which controls the prefix of `util::Path`s. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you elaborate? How does it control it? Why is it needed? What's the use case? |
||
|
|
||
| use std::{cell::RefCell, path::PathBuf}; | ||
|
|
||
| use rstest::fixture; | ||
|
|
||
| thread_local! { | ||
| pub static ROOT_OVERRIDE: RefCell<Option<PathBuf>> = const { RefCell::new(None) }; | ||
| } | ||
|
|
||
| /// Scoped root override for tests that access absolute paths through | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't get what you mean by "scoped" here. |
||
| /// `util::Path`. | ||
| pub(crate) struct RootOverride { | ||
| previous: Option<std::path::PathBuf>, | ||
| } | ||
|
|
||
| impl Drop for RootOverride { | ||
| fn drop(&mut self) { | ||
| ROOT_OVERRIDE.with(|root| { | ||
| root.replace(self.previous.take()); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /// Override the root used by `util::Path` on the current test thread. | ||
| pub(crate) fn override_root(root: impl Into<PathBuf>) -> RootOverride { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I presume this replaces the first absolute component with
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, why is this a free function and not in |
||
| let previous = ROOT_OVERRIDE.with(|slot| slot.replace(Some(root.into()))); | ||
| RootOverride { previous } | ||
| } | ||
|
|
||
| /// Temporary mock root installed as the current test thread's `util::Path` | ||
| /// prefix. | ||
| pub(crate) struct MockDir { | ||
| path: std::path::PathBuf, | ||
| _root_override: RootOverride, | ||
| _dir: tempfile::TempDir, | ||
| } | ||
|
|
||
| impl MockDir { | ||
| pub(crate) fn new() -> Self { | ||
| let dir = tempfile::tempdir().unwrap(); | ||
| let path = dir.path(); | ||
|
|
||
| Self { | ||
| path: path.to_path_buf(), | ||
| _root_override: override_root(path), | ||
| _dir: dir, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Create a new `MockDir` as an `rstest` fixture | ||
| #[fixture] | ||
| pub(crate) fn mockdir() -> MockDir { | ||
| MockDir::new() | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use std::path::Path; | ||
|
|
||
| use rstest::rstest; | ||
|
|
||
| use super::*; | ||
| use crate::path::{PrefixedPath, effective_root}; | ||
|
|
||
| /// Ensure that a new `MockDir` actually changes the effective root. | ||
| #[test] | ||
| fn test_mockdir_changes_effective_root() { | ||
| let old_root = effective_root(); | ||
|
|
||
| let _mockdir = MockDir::new(); | ||
| assert_ne!(old_root, effective_root()); | ||
| } | ||
|
|
||
| /// Ensure new `util::Paths`s use a given `MockDir`'s prefix. | ||
| #[rstest] | ||
| fn test_mockdir_modifies_util_paths(mockdir: MockDir) { | ||
| let util_path = PrefixedPath::from("/etc/passwd"); | ||
| assert_ne!(mockdir.path, Path::new("/")); | ||
| assert_eq!(util_path.to_path_buf(), mockdir.path.join("etc/passwd")); | ||
| } | ||
|
|
||
| /// Ensure paths created before a new `MockDir` are not prefixed with its | ||
| /// dir. | ||
| // TODO is this the behaviour we want? MockDirs will mostly be used as | ||
| // fixtures so I don't think it should come up too often. | ||
| #[test] | ||
| fn test_mockdir_preserves_existing_paths() { | ||
| let util_path = PrefixedPath::from("/etc/passwd"); | ||
|
|
||
| let mockdir = MockDir::new(); | ||
| assert_ne!(util_path.to_path_buf(), mockdir.path.join("etc/passwd")); | ||
| } | ||
|
|
||
| /// Ensure once a `MockDir` goes out of scope the effective root is | ||
| /// restored. | ||
| #[test] | ||
| fn test_mockdir_reverts_on_drop() { | ||
| let _outer = MockDir::new(); | ||
| let outer_path = effective_root(); | ||
|
|
||
| let inner_path = { | ||
| let _inner = MockDir::new(); | ||
| effective_root() | ||
| }; | ||
|
|
||
| let post_drop_path = effective_root(); | ||
|
|
||
| assert_ne!(outer_path, inner_path); | ||
| assert_eq!(outer_path, post_drop_path); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
| // Copyright (c) 2026 Nutanix, Inc. | ||
| // | ||
| // Author: Leonardo Forchini <leonardo.forchini@nutanix.com> | ||
| // | ||
|
|
||
| pub mod mockdir; | ||
|
|
||
| pub use mockdir::*; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit,
test::{MockDir, mockdir::mockdir}