mod.rs (7321B)
1 //! Coliru testing utilities 2 //! 3 //! These utilities create and manage resources used in integration and 4 //! end-to-end tests, including temporary directories, processes running coliru 5 //! commands, and test dotfile repositories. There are also functions for basic 6 //! I/O and capturing command output. All temporary directories are located 7 //! under the `.temp` directory and are unique to each test according to name. 8 9 #![allow(dead_code)] 10 11 use std::env; 12 use std::fs; 13 use std::io::Write; 14 use std::path::{Path, PathBuf}; 15 use std::process::Command; 16 17 /// The SSH test server 18 // StrictHostKeyChecking option and correct port are set automatically in 19 // src/ssh.rs when COLIRU_TEST environment variable is set. 20 pub const SSH_HOST: &str = "test@localhost"; 21 22 /// A set of temporary directories that are automatically deleted when the value 23 /// is dropped 24 pub struct TempDirs { 25 /// A temporary directory that is located at or in `~/` on Unix 26 pub home: PathBuf, 27 28 /// A temporary directory that is located at or under the current working 29 /// directory 30 pub local: PathBuf, 31 32 /// A temporary directory that is mounted to the SSH server under `~/` 33 pub ssh: PathBuf, 34 35 /// A temporary directory that is mounted to the SSH server under 36 /// `~/.coliru` 37 pub ssh_cwd: PathBuf, 38 } 39 impl Drop for TempDirs { 40 fn drop(&mut self) { 41 fs::remove_dir_all(&self.home).unwrap(); 42 fs::remove_dir_all(&self.local).unwrap(); 43 fs::remove_dir_all(&self.ssh).unwrap(); 44 fs::remove_dir_all(&self.ssh_cwd).unwrap(); 45 } 46 } 47 impl TempDirs { 48 /// Creates a new set of temporary directories with a certain name 49 /// 50 /// ``` 51 /// let dirs = TempDirs::new("test_foo"); 52 /// ``` 53 fn new(name: &str) -> TempDirs { 54 // The working directory of the main process is always the repository 55 // root 56 let dir = env::current_dir().unwrap().join("tests").join(".temp"); 57 58 let home = dir.join("home").join(name); 59 let local = dir.join("local").join(name); 60 let ssh = dir.join("ssh").join(name); 61 let ssh_cwd = dir.join("ssh").join(".coliru").join(name); 62 63 assert_eq!(home.exists(), false); 64 assert_eq!(local.exists(), false); 65 assert_eq!(ssh.exists(), false); 66 assert_eq!(ssh_cwd.exists(), false); 67 68 fs::create_dir_all(&home).unwrap(); 69 fs::create_dir_all(&local).unwrap(); 70 fs::create_dir_all(&ssh).unwrap(); 71 fs::create_dir_all(&ssh_cwd).unwrap(); 72 73 TempDirs { home, local, ssh, ssh_cwd } 74 } 75 } 76 77 /// Initializes temporary directories for integration tests 78 /// 79 /// On Unix, `$HOME` is set to the parent directory of the home temporary 80 /// directory, which is the same for all integration tests. This prevents issues 81 /// when tests are run in multiple threads. 82 /// 83 /// ``` 84 /// let dirs = setup_integration("test_foo"); 85 /// ``` 86 pub fn setup_integration(name: &str) -> TempDirs { 87 let dirs = TempDirs::new(name); 88 if cfg!(target_family = "unix") { 89 env::set_var("HOME", dirs.home.parent().unwrap()); 90 } 91 env::set_var("COLIRU_TEST", "1"); 92 dirs 93 } 94 95 /// Initializes temporary directories and a coliru Command for E2E tests 96 /// 97 /// The Command's working directory is set to the local temporary directory, and 98 /// on Unix, the Command's `$HOME` variable is set to the home temporary 99 /// directory. 100 /// 101 /// ``` 102 /// let (dirs, cmd) = setup_e2e("test_foo"); 103 /// ``` 104 fn setup_e2e(name: &str) -> (TempDirs, Command) { 105 let dirs = TempDirs::new(name); 106 107 let exe = env::current_exe().unwrap().parent().unwrap().to_path_buf() 108 .join(format!("../coliru{}", env::consts::EXE_SUFFIX)); 109 let mut cmd = Command::new(exe); 110 cmd.current_dir(&dirs.local); 111 if cfg!(target_family = "unix") { 112 cmd.env("HOME", &dirs.home); 113 } 114 cmd.env("COLIRU_TEST", "1"); 115 116 (dirs, cmd) 117 } 118 119 /// Initializes temporary directories and a coliru Command for local E2E tests 120 /// 121 /// A test dotfile repository is copied to the working directory (mapped to the 122 /// `local` temporary directory), to be installed to the home directory on Unix 123 /// (mapped to the `home` temporary directory) and the current working directory 124 /// on Windows (mapped to the `local` temporary directory). 125 /// 126 /// ``` 127 /// let (dirs, cmd) = setup_e2e_local("test_foo"); 128 /// ``` 129 pub fn setup_e2e_local(name: &str) -> (TempDirs, Command) { 130 let (dirs, cmd) = setup_e2e(name); 131 132 // It's difficult to mock $HOME on Windows, so install dotfiles in CWD 133 let home_dir = if cfg!(target_family = "unix") { "~/" } else { "" }; 134 copy_manifest(&dirs.local, home_dir, ""); 135 136 (dirs, cmd) 137 } 138 139 /// Initializes temporary directories and a coliru Command for SSH E2E tests 140 /// 141 /// A test dotfile repository is copied to the working directory (mapped to the 142 /// `local` temporary directory), to be installed over SSH to `~/test_name/` 143 /// (mapped to the `ssh` temporary directory), with scripts copied to 144 /// `~/.coliru/test_name/` (mapped to the `ssh_cwd` temporary directory). The 145 /// `--host` flag is set to the test SSH server. 146 /// 147 /// ``` 148 /// let (dirs, cmd) = setup_e2e_ssh("test_foo"); 149 /// ``` 150 pub fn setup_e2e_ssh(name: &str) -> (TempDirs, Command) { 151 let (dirs, mut cmd) = setup_e2e(name); 152 cmd.args(["--host", SSH_HOST]); 153 154 // Replace ~/ and scripts/ with custom directory to isolate SSH tests 155 copy_manifest(&dirs.local, &format!("~/{name}/"), &format!("{name}/")); 156 157 (dirs, cmd) 158 } 159 160 /// Initializes a basic dotfiles repository in a directory 161 /// 162 /// The dotfiles from `examples/test/` are used as a starting template. All 163 /// occurrences of `~/` and `scripts/` are replaced with `home_dir` and 164 /// `script_dir`, respectively, to allow dotfiles to be isolated across tests 165 /// when necessary. 166 /// 167 /// ``` 168 /// copy_manifest(&Path::new("/tmp/dotfiles/"), "~/", "scripts/"); 169 /// ``` 170 fn copy_manifest(dir: &Path, home_dir: &str, script_dir: &str) { 171 let examples = env::current_exe().unwrap().parent().unwrap().to_path_buf() 172 .join("../../../examples/test"); 173 174 let copy_file = |path: &str| { 175 let mut contents = read_file(&examples.join(path)); 176 contents = contents.replace("~/", home_dir); 177 contents = contents.replace("scripts/", script_dir); 178 let dst = path.replace("scripts/", script_dir); 179 write_file(&dir.join(dst), &contents); 180 }; 181 182 copy_file("manifest.yml"); 183 fs::create_dir_all(&dir.join(script_dir)).unwrap(); 184 copy_file("scripts/script.bat"); 185 copy_file("scripts/script.sh"); 186 copy_file("scripts/foo"); 187 copy_file("bashrc"); 188 copy_file("gitconfig"); 189 copy_file("vimrc"); 190 191 } 192 193 /// Writes a string to a file, overwriting it if it already exists 194 pub fn write_file(path: &Path, contents: &str) { 195 let mut file = fs::File::create(path).unwrap(); 196 file.write_all(contents.as_bytes()).unwrap(); 197 } 198 199 /// Reads the contents of a file into a string 200 pub fn read_file(path: &Path) -> String { 201 fs::read_to_string(path).unwrap() 202 } 203 204 /// Run a command and return its output (stdout and stderr) and exit status 205 pub fn run_command(cmd: &mut Command) -> (String, String, Option<i32>) { 206 let output = cmd.output().unwrap(); 207 ( 208 String::from_utf8_lossy(&output.stdout).into_owned(), 209 String::from_utf8_lossy(&output.stderr).into_owned(), 210 output.status.code(), 211 ) 212 }