/ crates / fs-mistrust / src / testing.rs
testing.rs
  1  //! Testing support functions, to more easily make a bunch of directories and
  2  //! links.
  3  //!
  4  //! This module is only built when compiling tests.
  5  
  6  use std::{
  7      fs::{self, File},
  8      io::Write,
  9      path::{Path, PathBuf},
 10  };
 11  
 12  #[cfg(target_family = "unix")]
 13  use std::os::unix::{self, fs::PermissionsExt};
 14  
 15  use crate::Mistrust;
 16  
 17  /// A temporary directory with convenience functions to build items inside it.
 18  #[derive(Debug)]
 19  pub(crate) struct Dir {
 20      /// The temporary directory
 21      toplevel: tempfile::TempDir,
 22      /// Canonicalized path to the temporary directory
 23      canonical_root: PathBuf,
 24  }
 25  
 26  /// When creating a link, are we creating a directory link or a file link?
 27  ///
 28  /// (These are the same on Unix, and different on windows.)
 29  #[cfg(target_family = "unix")]
 30  #[derive(Copy, Clone, Debug)]
 31  pub(crate) enum LinkType {
 32      Dir,
 33      File,
 34  }
 35  
 36  impl Dir {
 37      /// Make a new temporary directory
 38      pub(crate) fn new() -> Self {
 39          let toplevel = tempfile::TempDir::new().expect("Can't get tempfile");
 40          let canonical_root = toplevel.path().canonicalize().expect("Can't canonicalize");
 41  
 42          Dir {
 43              toplevel,
 44              canonical_root,
 45          }
 46      }
 47  
 48      /// Return the canonical path of the directory's root.
 49      pub(crate) fn canonical_root(&self) -> &Path {
 50          self.canonical_root.as_path()
 51      }
 52  
 53      /// Return the path to the temporary directory's root relative to our working directory.
 54      pub(crate) fn relative_root(&self) -> PathBuf {
 55          let mut cwd = std::env::current_dir().expect("no cwd");
 56          let mut relative = PathBuf::new();
 57          // TODO(nickm): I am reasonably confident that this will not work
 58          // correctly on windows.
 59          while !self.toplevel.path().starts_with(&cwd) {
 60              assert!(cwd.pop());
 61              relative.push("..");
 62          }
 63          relative.join(
 64              self.toplevel
 65                  .path()
 66                  .strip_prefix(cwd)
 67                  .expect("error computing common ancestor"),
 68          )
 69      }
 70  
 71      /// Return the path of `p` within this temporary directory.
 72      ///
 73      /// Requires that `p` is a relative path.
 74      pub(crate) fn path(&self, p: impl AsRef<Path>) -> PathBuf {
 75          let p = p.as_ref();
 76          assert!(p.is_relative());
 77          self.canonical_root.join(p)
 78      }
 79  
 80      /// Make a  directory at `p` within this temporary directory, creating
 81      /// parent directories as needed.
 82      ///
 83      /// Requires that `p` is a relative path.
 84      pub(crate) fn dir(&self, p: impl AsRef<Path>) {
 85          fs::create_dir_all(self.path(p)).expect("Can't create directory.");
 86      }
 87  
 88      /// Make a small file at `p` within this temporary directory, creating
 89      /// parent directories as needed.
 90      ///
 91      /// Requires that `p` is a relative path.
 92      pub(crate) fn file(&self, p: impl AsRef<Path>) {
 93          self.dir(p.as_ref().parent().expect("Tempdir had no parent"));
 94          let mut f = File::create(self.path(p)).expect("Can't create file");
 95          f.write_all(&b"This space is intentionally left blank"[..])
 96              .expect("Can't write");
 97      }
 98  
 99      /// Make a relative link from "original" to "link" within this temporary
100      /// directory, where `original` is relative
101      /// to the directory containing `link`, and `link` is relative to the temporary directory.
102      #[cfg(target_family = "unix")]
103      pub(crate) fn link_rel(
104          &self,
105          link_type: LinkType,
106          original: impl AsRef<Path>,
107          link: impl AsRef<Path>,
108      ) {
109          {
110              let _ = link_type;
111              unix::fs::symlink(original.as_ref(), self.path(link)).expect("Can't symlink");
112          }
113  
114          // Windows does support symlinks but it requires elevated privileges. For more information,
115          // please have a look at:
116          // https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links
117      }
118  
119      /// As `link_rel`, but create an absolute link.  `original` is now relative
120      /// to the temporary directory.
121      #[cfg(target_family = "unix")]
122      pub(crate) fn link_abs(
123          &self,
124          link_type: LinkType,
125          original: impl AsRef<Path>,
126          link: impl AsRef<Path>,
127      ) {
128          self.link_rel(link_type, self.path(original), link);
129      }
130  
131      /// Change the unix permissions of a file.
132      ///
133      /// Requires that `p` is a relative path.
134      ///
135      /// Does nothing on windows.
136      pub(crate) fn chmod(&self, p: impl AsRef<Path>, mode: u32) {
137          #[cfg(target_family = "unix")]
138          {
139              let perm = fs::Permissions::from_mode(mode);
140              fs::set_permissions(self.path(p), perm).expect("can't chmod");
141          }
142          #[cfg(not(target_family = "unix"))]
143          {
144              let (_, _) = (p, mode);
145          }
146      }
147  }
148  
149  /// A utility type to represent the different operations available for a MistrustBuilder.
150  #[derive(Debug)]
151  pub(crate) enum MistrustOp<'a> {
152      IgnorePrefix(&'a Path),
153      DangerouslyTrustEveryone(),
154      TrustNoGroupId(),
155  
156      #[cfg(target_family = "unix")]
157      TrustAdminOnly(),
158  
159      #[cfg(target_family = "unix")]
160      TrustGroup(u32),
161  }
162  
163  /// A convenience function to construct a Mistrust type using a set of given operations.
164  pub(crate) fn mistrust_build(ops: &[MistrustOp]) -> Mistrust {
165      ops.iter()
166          .fold(&mut Mistrust::builder(), |m, op| {
167              match op {
168                  MistrustOp::IgnorePrefix(prefix) => m.ignore_prefix(prefix),
169  
170                  MistrustOp::DangerouslyTrustEveryone() => m.dangerously_trust_everyone(),
171  
172                  MistrustOp::TrustNoGroupId() => {
173                      // We call `m.trust_no_group_id()` on platforms where it is available.
174                      // Otherwise, we simply return `m` unmodified here.
175                      #[cfg(all(
176                          target_family = "unix",
177                          not(target_os = "ios"),
178                          not(target_os = "android")
179                      ))]
180                      return m.trust_no_group_id();
181  
182                      #[cfg(not(all(
183                          target_family = "unix",
184                          not(target_os = "ios"),
185                          not(target_os = "android")
186                      )))]
187                      return m;
188                  }
189  
190                  #[cfg(target_family = "unix")]
191                  MistrustOp::TrustAdminOnly() => m.trust_admin_only(),
192  
193                  #[cfg(target_family = "unix")]
194                  MistrustOp::TrustGroup(gid) => m.trust_group(*gid),
195              }
196          })
197          .build()
198          .expect("Unable to build Mistrust object")
199  }