Skip to content

Thread Safe Current Working Directory

License

Notifications You must be signed in to change notification settings

G-M0N3Y-2503/current_dir

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

current_dir

Crates.io Version GitHub Actions Workflow Status docs.rs Code Coverage Minimum

A utility crate that helps using set_current_dir() and current_dir() in a thread safe manner.
This is generally useful for #[test]s that depend on different current working directories each as they are run in multiple threads by default.

Why can't I just use set_current_dir() and current_dir() directly?

The current working directory is global to the whole process, so if you only use a single thread or you never change the current working directory, go ahead!
Otherwise, changing the current working directory without synchronising may lead to unexpected behaviour.

Cwd Example

# use std::error::Error;
# fn main() -> Result<(), Box<dyn Error>> {
      use std::env::temp_dir;
      use current_dir::*;

      let mut locked_cwd = Cwd::mutex().lock()?;
      locked_cwd.set(temp_dir())?;
      // cwd == /tmp
#     assert_eq!(locked_cwd.get()?, temp_dir());
#     drop(locked_cwd);
#
#     Ok(())
# }

or you can just use set_current_dir() and current_dir() with a locked current working directory.

# use std::error::Error;
# fn main() -> Result<(), Box<dyn Error>> {
      use std::env::{set_current_dir, temp_dir};
      use current_dir::*;

      let locked_cwd = Cwd::mutex().lock()?;
      set_current_dir(temp_dir())?;
      // cwd == /tmp
#     assert_eq!(locked_cwd.get()?, temp_dir());
#     drop(locked_cwd);
#
#     Ok(())
# }

CwdGuard Example

# use std::error::Error;
# fn main() -> Result<(), Box<dyn Error>> {
      use std::{env::temp_dir, fs::create_dir_all};
      use current_dir::*;
#
#     let test_dirs = temp_dir().join("sub/sub");
#     if !test_dirs.exists() {
#         create_dir_all(&test_dirs)?;
#     }

      let mut locked_cwd = Cwd::mutex().lock()?;
      locked_cwd.set(temp_dir())?;
      // cwd == /tmp
#     assert_eq!(locked_cwd.get()?, temp_dir());
      {
          let mut cwd_guard = CwdGuard::try_from(&mut *locked_cwd)?;
          cwd_guard.set("sub")?;
          // cwd == /tmp/sub
#         assert_eq!(cwd_guard.get()?, temp_dir().join("sub"));
          {
              let mut sub_cwd_guard = CwdGuard::try_from(&mut cwd_guard)?;
              sub_cwd_guard.set("sub")?;
              // cwd == /tmp/sub/sub
#             assert_eq!(sub_cwd_guard.get()?, temp_dir().join("sub/sub"));
              {
                  let mut sub_sub_cwd_guard = CwdGuard::try_from(&mut sub_cwd_guard)?;
                  sub_sub_cwd_guard.set(temp_dir())?;
                  // cwd == /tmp
#                 assert_eq!(sub_sub_cwd_guard.get()?, temp_dir());
              }
              // cwd == /tmp/sub/sub
#             assert_eq!(sub_cwd_guard.get()?, temp_dir().join("sub/sub"));
          }
          // cwd == /tmp/sub
#         assert_eq!(cwd_guard.get()?, temp_dir().join("sub"));
      }
      // cwd == /tmp
#     assert_eq!(locked_cwd.get()?, temp_dir());
#     drop(locked_cwd);
#
#     Ok(())
# }

Poison cleanup Example

# use std::error::Error;
# fn main() -> Result<(), Box<dyn Error>> {
      use std::{
          env::temp_dir,
          error::Error,
          fs::{create_dir_all, remove_dir},
          panic,
      };
      use current_dir::*;

      let test_dir = temp_dir().join("cwd");
#     if !test_dir.exists() {
#         create_dir_all(&test_dir)?;
#     }

      panic::catch_unwind(|| {
          let mut locked_cwd = Cwd::mutex().lock().unwrap();
          locked_cwd.set(&test_dir)?;

          // removing the CWD before the CwdGuard is dropped will cause a panic on drop.
          let cwd_guard = CwdGuard::try_from(&mut *locked_cwd)?;
          remove_dir(&test_dir)?;
          drop(cwd_guard);
#
#         Ok::<_, Box<dyn Error>>(())
      })
      .expect_err("panicked");

      let mut poisoned_locked_cwd = Cwd::mutex().lock().expect_err("cwd poisoned");
      let expected_cwd = poisoned_locked_cwd.get_ref().get_expected().unwrap();
#     assert_eq!(expected_cwd, test_dir);

      // Fix poisoned cwd
      create_dir_all(&expected_cwd)?;
      poisoned_locked_cwd.get_mut().set(&expected_cwd)?;
      Cwd::mutex().clear_poison();
      let locked_cwd = poisoned_locked_cwd.into_inner();
#     drop(locked_cwd);
#
#     Ok(())
# }