coliru

A minimal, flexible, dotfile installer
git clone https://git.ashermorgan.net/coliru/
Log | Files | Refs | README

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 }