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 }