coliru

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

commit 7a92ec2027c0d172e402efbc48357cd264ee7c82
parent 8c1b2f12888886685a57b9e68ad5b7948370ac24
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Wed, 26 Jun 2024 15:54:42 -0700

Add tests for Windows

Diffstat:
M.github/workflows/build-and-test.yml | 14+++++++++++++-
Aexamples/manifest-windows-test.yml | 25+++++++++++++++++++++++++
Msrc/manifest.rs | 17+++++++++++++----
Msrc/utils.rs | 51++++++++++++++++++++++++++++++++++++++++++++++-----
Mtests/basic.rs | 222++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mtests/common/mod.rs | 5+++++
6 files changed, 277 insertions(+), 57 deletions(-)

diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml @@ -8,7 +8,7 @@ env: CARGO_TERM_COLOR: always jobs: - build: + build-and-test-linux: runs-on: ubuntu-latest steps: @@ -19,3 +19,15 @@ jobs: - name: Run tests run: cargo test --verbose + + build-and-test-windows: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Build + run: cargo build --verbose + + - name: Run tests + run: cargo test --verbose diff --git a/examples/manifest-windows-test.yml b/examples/manifest-windows-test.yml @@ -0,0 +1,25 @@ +# Identical to manifest.yml, but uses relative paths instead of paths with +# tildes becuase $HOME is difficult to mock on windows. + +steps: + - copy: + - src: gitconfig + dst: .gitconfig.coliru + tags: [ windows, linux, macos ] + + - link: + - src: vimrc + dst: .vimrc.coliru # Will create symbolic link on Linux & MacOS + run: + - src: script.sh + prefix: sh # unecessary on Unix if script.sh is executable + postfix: arg1 $COLIRU_RULES + tags: [ linux, macos ] + + - link: + - src: vimrc + dst: _vimrc.coliru # Will create hard link on Windows + run: + - src: script.bat + postfix: arg1 $COLIRU_RULES + tags: [ windows ] diff --git a/src/manifest.rs b/src/manifest.rs @@ -67,7 +67,7 @@ mod tests { use super::*; #[test] - #[cfg(target_os = "linux")] + #[cfg(target_family = "unix")] fn parse_manifest_file_missing() { let expected = "No such file or directory (os error 2)"; let actual = parse_manifest_file(Path::new("examples/missing.yml")); @@ -75,10 +75,19 @@ mod tests { } #[test] + #[cfg(target_family = "windows")] + fn parse_manifest_file_missing() { + let exp = "The system cannot find the file specified. (os error 2)"; + let actual = parse_manifest_file(Path::new("examples/missing.yml")); + assert_eq!(actual, Err(String::from(exp))); + } + + #[test] fn parse_manifest_file_invalid() { - let expected = "steps[0].copy[0]: missing field `src` at line 5 column 7"; - let actual = parse_manifest_file(Path::new("examples/manifest-invalid.yml")); - assert_eq!(actual, Err(String::from(expected))); + let path = Path::new("examples/manifest-invalid.yml"); + let exp = "steps[0].copy[0]: missing field `src` at line 5 column 7"; + let actual = parse_manifest_file(path); + assert_eq!(actual, Err(String::from(exp))); } #[test] diff --git a/src/utils.rs b/src/utils.rs @@ -196,7 +196,6 @@ mod tests { } #[test] - #[cfg(target_family = "unix")] fn test_link_file_create_dirs() { let tmp = setup("test_link_file_create_dirs"); @@ -213,7 +212,6 @@ mod tests { } #[test] - #[cfg(target_family = "unix")] fn test_link_file_existing_file() { let tmp = setup("test_link_file_existing_file"); @@ -294,7 +292,20 @@ mod tests { let src = &tmp.dir.join("foo"); write_file(src, "exit 0"); - let result = run_script(src.to_str().unwrap(), "bash", ""); + let result = run_script(src.to_str().unwrap(), "sh", ""); + + assert_eq!(result.is_ok(), true); + } + + #[test] + #[cfg(target_family = "windows")] + fn test_run_script_successful() { + let tmp = setup("test_run_script_successful"); + + let src = &tmp.dir.join("foo.bat"); + write_file(src, "exit 0"); + + let result = run_script(src.to_str().unwrap(), "", ""); assert_eq!(result.is_ok(), true); } @@ -307,13 +318,27 @@ mod tests { let src = &tmp.dir.join("foo"); write_file(src, "exit 2"); - let result = run_script(src.to_str().unwrap(), "bash", ""); + let result = run_script(src.to_str().unwrap(), "sh", ""); assert_eq!(result.is_ok(), false); assert_eq!(result.unwrap_err(), "Process exited with exit status: 2"); } #[test] + #[cfg(target_family = "windows")] + fn test_run_script_failure() { + let tmp = setup("test_run_script_failure"); + + let src = &tmp.dir.join("foo.bat"); + write_file(src, "exit 1"); + + let result = run_script(src.to_str().unwrap(), "", ""); + + assert_eq!(result.is_ok(), false); + assert_eq!(result.unwrap_err(), "Process exited with exit code: 1"); + } + + #[test] #[cfg(target_family = "unix")] fn test_run_script_postfix() { let tmp = setup("test_run_script_postfix"); @@ -322,10 +347,26 @@ mod tests { let dst = &tmp.dir.join("bar"); write_file(src, &format!("echo $@ > {}", dst.to_str().unwrap())); - let result = run_script(src.to_str().unwrap(), "bash", "arg1 arg2"); + let result = run_script(src.to_str().unwrap(), "sh", "arg1 arg2"); let contents = fs::read_to_string(dst).unwrap(); assert_eq!(result.is_ok(), true); assert_eq!(contents, "arg1 arg2\n"); } + + #[test] + #[cfg(target_family = "windows")] + fn test_run_script_postfix() { + let tmp = setup("test_run_script_postfix"); + + let src = &tmp.dir.join("foo.bat"); + let dst = &tmp.dir.join("bar"); + write_file(src, &format!("echo %* > {}", dst.to_str().unwrap())); + + let result = run_script(src.to_str().unwrap(), "", "arg1 arg2"); + + let contents = fs::read_to_string(dst).unwrap(); + assert_eq!(result.is_ok(), true); + assert_eq!(contents, "arg1 arg2 \r\n"); + } } diff --git a/tests/basic.rs b/tests/basic.rs @@ -1,8 +1,8 @@ mod common; use common::*; -use std::env::current_exe; -use std::fs::{copy, read_to_string, remove_file}; +use std::env::{current_exe, consts::EXE_SUFFIX}; +use std::fs::{copy, remove_file}; use std::path::Path; /// Create a basic manifest file and its associated dotfiles in a directory @@ -16,6 +16,7 @@ fn manifest_1(dir: &Path) { copy_file("script.bat"); copy_file("script.sh"); copy_file("manifest.yml"); + copy_file("manifest-windows-test.yml"); // Create simplified config files write_file(&dir.join("gitconfig"), "git #1"); @@ -27,10 +28,10 @@ fn manifest_1(dir: &Path) { fn test_help() { let (_dir, mut cmd) = setup("test_help"); cmd.arg("--help"); - let expected = "\ + let expected = format!("\ A minimal, flexible, dotfile installer -Usage: coliru [OPTIONS] <MANIFEST> +Usage: coliru{EXE_SUFFIX} [OPTIONS] <MANIFEST> Arguments: <MANIFEST> The path to the coliru YAML manifest file @@ -41,13 +42,13 @@ Options: -n, --dry-run Do a trial run without any permanent changes -h, --help Print help -V, --version Print version -"; - assert_eq!(&stdout_to_string(&mut cmd), expected); +"); + assert_eq!(stdout_to_string(&mut cmd), expected); assert_eq!(&stderr_to_string(&mut cmd), ""); } #[test] -#[cfg(target_os = "linux")] +#[cfg(target_family = "unix")] fn test_standard() { let (dir, mut cmd) = setup("test_standard"); cmd.args(["manifest.yml", "-t", "linux"]); @@ -65,10 +66,10 @@ script.sh called with arg1 linux // Assert files are correctly copied/linked/run write_file(&dir.dir.join("gitconfig"), "git #2"); write_file(&dir.dir.join("vimrc"), "vim #2"); - let git_contents = read_to_string(&dir.dir.join(".gitconfig.coliru")).unwrap(); - let vim1_contents = read_to_string(&dir.dir.join(".vimrc.coliru")).unwrap(); - let vim2_exists = dir.dir.join("_vimrc").exists(); - let log_contents = read_to_string(&dir.dir.join("log.txt")).unwrap(); + let git_contents = read_file(&dir.dir.join(".gitconfig.coliru")); + let vim1_contents = read_file(&dir.dir.join(".vimrc.coliru")); + let vim2_exists = dir.dir.join("_vimrc.coliru").exists(); + let log_contents = read_file(&dir.dir.join("log.txt")); assert_eq!(git_contents, "git #1"); assert_eq!(vim1_contents, "vim #2"); assert_eq!(vim2_exists, false); @@ -76,58 +77,88 @@ script.sh called with arg1 linux } #[test] +#[cfg(target_family = "windows")] +fn test_standard() { + let (dir, mut cmd) = setup("test_standard"); + cmd.args(["manifest-windows-test.yml", "-t", "windows"]); + manifest_1(&dir.dir); + + let expected = "\ +[1/3] Copy gitconfig to .gitconfig.coliru +[3/3] Link vimrc to _vimrc.coliru +[3/3] Run script.bat arg1 windows +script.bat called with arg1 windows\r +"; + assert_eq!(&stdout_to_string(&mut cmd), expected); + assert_eq!(&stderr_to_string(&mut cmd), ""); + + // Assert files are correctly copied/linked/run + write_file(&dir.dir.join("gitconfig"), "git #2"); + write_file(&dir.dir.join("vimrc"), "vim #2"); + let git_contents = read_file(&dir.dir.join(".gitconfig.coliru")); + let vim1_exists = dir.dir.join(".vimrc.coliru").exists(); + let vim2_contents = read_file(&dir.dir.join("_vimrc.coliru")); + let log_contents = read_file(&dir.dir.join("log.txt")); + assert_eq!(git_contents, "git #1"); + assert_eq!(vim1_exists, false); + assert_eq!(vim2_contents, "vim #2"); + assert_eq!(log_contents, "script.bat called with arg1 windows \r\n"); +} + +#[test] +#[cfg(target_family = "unix")] fn test_run_alternate_tag_rules_1() { let (dir, mut cmd) = setup("test_run_alternate_tag_rules_1"); - cmd.args(["manifest.yml", "-t", "macos"]); + cmd.args(["manifest.yml", "-t", "linux", "^windows"]); manifest_1(&dir.dir); let expected = "\ -[1/3] Copy gitconfig to ~/.gitconfig.coliru [2/3] Link vimrc to ~/.vimrc.coliru -[2/3] Run sh script.sh arg1 macos -script.sh called with arg1 macos +[2/3] Run sh script.sh arg1 linux ^windows +script.sh called with arg1 linux ^windows "; assert_eq!(&stdout_to_string(&mut cmd), expected); assert_eq!(&stderr_to_string(&mut cmd), ""); // Assert files are correctly copied/linked/run - write_file(&dir.dir.join("gitconfig"), "git #2"); write_file(&dir.dir.join("vimrc"), "vim #2"); - let git_contents = read_to_string(&dir.dir.join(".gitconfig.coliru")).unwrap(); - let vim1_contents = read_to_string(&dir.dir.join(".vimrc.coliru")).unwrap(); - let vim2_exists = dir.dir.join("_vimrc").exists(); - let log_contents = read_to_string(&dir.dir.join("log.txt")).unwrap(); - assert_eq!(git_contents, "git #1"); + let git_exists = dir.dir.join(".gitconfig.coliru").exists(); + let vim1_contents = read_file(&dir.dir.join(".vimrc.coliru")); + let vim2_exists = dir.dir.join("_vimrc.coliru").exists(); + let log_contents = read_file(&dir.dir.join("log.txt")); + assert_eq!(git_exists, false); assert_eq!(vim1_contents, "vim #2"); assert_eq!(vim2_exists, false); - assert_eq!(log_contents, "script.sh called with arg1 macos\n"); + assert_eq!(log_contents, "script.sh called with arg1 linux ^windows\n"); } #[test] -#[cfg(target_os = "linux")] +#[cfg(target_family = "unix")] fn test_run_alternate_tag_rules_2() { let (dir, mut cmd) = setup("test_run_alternate_tag_rules_2"); - cmd.args(["manifest.yml", "-t", "linux,macos", "^windows"]); + cmd.args(["manifest.yml", "-t", "macos"]); manifest_1(&dir.dir); let expected = "\ +[1/3] Copy gitconfig to ~/.gitconfig.coliru [2/3] Link vimrc to ~/.vimrc.coliru -[2/3] Run sh script.sh arg1 linux,macos ^windows -script.sh called with arg1 linux,macos ^windows +[2/3] Run sh script.sh arg1 macos +script.sh called with arg1 macos "; assert_eq!(&stdout_to_string(&mut cmd), expected); assert_eq!(&stderr_to_string(&mut cmd), ""); // Assert files are correctly copied/linked/run + write_file(&dir.dir.join("gitconfig"), "git #2"); write_file(&dir.dir.join("vimrc"), "vim #2"); - let git_exists = dir.dir.join(".gitconfig.coliru").exists(); - let vim1_contents = read_to_string(&dir.dir.join(".vimrc.coliru")).unwrap(); - let vim2_exists = dir.dir.join("_vimrc").exists(); - let log_contents = read_to_string(&dir.dir.join("log.txt")).unwrap(); - assert_eq!(git_exists, false); + let git_contents = read_file(&dir.dir.join(".gitconfig.coliru")); + let vim1_contents = read_file(&dir.dir.join(".vimrc.coliru")); + let vim2_exists = dir.dir.join("_vimrc.coliru").exists(); + let log_contents = read_file(&dir.dir.join("log.txt")); + assert_eq!(git_contents, "git #1"); assert_eq!(vim1_contents, "vim #2"); assert_eq!(vim2_exists, false); - assert_eq!(log_contents, "script.sh called with arg1 linux,macos ^windows\n"); + assert_eq!(log_contents, "script.sh called with arg1 macos\n"); } #[test] @@ -147,7 +178,7 @@ fn test_dry_run() { // Assert files are correctly copied/linked/run let git_exists = dir.dir.join(".gitconfig.coliru").exists(); let vim1_exists = dir.dir.join(".vimrc.coliru").exists(); - let vim2_exists = dir.dir.join("_vimrc").exists(); + let vim2_exists = dir.dir.join("_vimrc.coliru").exists(); let log_exists = dir.dir.join("log.txt").exists(); assert_eq!(git_exists, false); assert_eq!(vim1_exists, false); @@ -156,7 +187,7 @@ fn test_dry_run() { } #[test] -#[cfg(target_os = "linux")] +#[cfg(target_family = "unix")] fn test_copy() { let (dir, mut cmd) = setup("test_copy"); cmd.args(["manifest.yml", "--copy", "-t", "linux"]); @@ -174,10 +205,10 @@ script.sh called with arg1 linux // Assert files are correctly copied/linked/run write_file(&dir.dir.join("gitconfig"), "git #2"); write_file(&dir.dir.join("vimrc"), "vim #2"); - let git_contents = read_to_string(&dir.dir.join(".gitconfig.coliru")).unwrap(); - let vim1_contents = read_to_string(&dir.dir.join(".vimrc.coliru")).unwrap(); - let vim2_exists = dir.dir.join("_vimrc").exists(); - let log_contents = read_to_string(&dir.dir.join("log.txt")).unwrap(); + let git_contents = read_file(&dir.dir.join(".gitconfig.coliru")); + let vim1_contents = read_file(&dir.dir.join(".vimrc.coliru")); + let vim2_exists = dir.dir.join("_vimrc.coliru").exists(); + let log_contents = read_file(&dir.dir.join("log.txt")); assert_eq!(git_contents, "git #1"); assert_eq!(vim1_contents, "vim #1"); assert_eq!(vim2_exists, false); @@ -185,7 +216,36 @@ script.sh called with arg1 linux } #[test] -#[cfg(target_os = "linux")] +#[cfg(target_family = "windows")] +fn test_copy() { + let (dir, mut cmd) = setup("test_copy"); + cmd.args(["manifest-windows-test.yml", "--copy", "-t", "windows"]); + manifest_1(&dir.dir); + + let expected = "\ +[1/3] Copy gitconfig to .gitconfig.coliru +[3/3] Copy vimrc to _vimrc.coliru +[3/3] Run script.bat arg1 windows +script.bat called with arg1 windows\r +"; + assert_eq!(&stdout_to_string(&mut cmd), expected); + assert_eq!(&stderr_to_string(&mut cmd), ""); + + // Assert files are correctly copied/linked/run + write_file(&dir.dir.join("gitconfig"), "git #2"); + write_file(&dir.dir.join("vimrc"), "vim #2"); + let git_contents = read_file(&dir.dir.join(".gitconfig.coliru")); + let vim1_exists = dir.dir.join(".vimrc.coliru").exists(); + let vim2_contents = read_file(&dir.dir.join("_vimrc.coliru")); + let log_contents = read_file(&dir.dir.join("log.txt")); + assert_eq!(git_contents, "git #1"); + assert_eq!(vim1_exists, false); + assert_eq!(vim2_contents, "vim #1"); + assert_eq!(log_contents, "script.bat called with arg1 windows \r\n"); +} + +#[test] +#[cfg(target_family = "unix")] fn test_run_failure() { let (dir, mut cmd) = setup("test_run_failure"); cmd.args(["manifest.yml", "-t", "linux"]); @@ -204,16 +264,44 @@ fn test_run_failure() { // Assert files are correctly copied/linked/run write_file(&dir.dir.join("gitconfig"), "git #2"); write_file(&dir.dir.join("vimrc"), "vim #2"); - let git_contents = read_to_string(&dir.dir.join(".gitconfig.coliru")).unwrap(); - let vim1_contents = read_to_string(&dir.dir.join(".vimrc.coliru")).unwrap(); - let vim2_exists = dir.dir.join("_vimrc").exists(); + let git_contents = read_file(&dir.dir.join(".gitconfig.coliru")); + let vim1_contents = read_file(&dir.dir.join(".vimrc.coliru")); + let vim2_exists = dir.dir.join("_vimrc.coliru").exists(); assert_eq!(git_contents, "git #1"); assert_eq!(vim1_contents, "vim #2"); assert_eq!(vim2_exists, false); } #[test] -#[cfg(target_os = "linux")] +#[cfg(target_family = "windows")] +fn test_run_failure() { + let (dir, mut cmd) = setup("test_run_failure"); + cmd.args(["manifest-windows-test.yml", "-t", "windows"]); + manifest_1(&dir.dir); + write_file(&dir.dir.join("script.bat"), "@echo off\r\nexit 1"); + + let expected_stdout = "\ +[1/3] Copy gitconfig to .gitconfig.coliru +[3/3] Link vimrc to _vimrc.coliru +[3/3] Run script.bat arg1 windows +"; + let expected_stderr = " Error: Process exited with exit code: 1\n"; + assert_eq!(&stdout_to_string(&mut cmd), expected_stdout); + assert_eq!(&stderr_to_string(&mut cmd), expected_stderr); + + // Assert files are correctly copied/linked/run + write_file(&dir.dir.join("gitconfig"), "git #2"); + write_file(&dir.dir.join("vimrc"), "vim #2"); + let git_contents = read_file(&dir.dir.join(".gitconfig.coliru")); + let vim1_exists = dir.dir.join(".vimrc.coliru").exists(); + let vim2_contents = read_file(&dir.dir.join("_vimrc.coliru")); + assert_eq!(git_contents, "git #1"); + assert_eq!(vim1_exists, false); + assert_eq!(vim2_contents, "vim #2"); +} + +#[test] +#[cfg(target_family = "unix")] fn test_missing_file() { let (dir, mut cmd) = setup("test_missing_file"); cmd.args(["manifest.yml", "-t", "linux"]); @@ -226,19 +314,47 @@ fn test_missing_file() { [2/3] Run sh script.sh arg1 linux script.sh called with arg1 linux "; - let expected_stderr = " Error: No such file or directory (os error 2)\n"; + let expected_stderr = " Error: No such file or directory \ + (os error 2)\n"; assert_eq!(&stdout_to_string(&mut cmd), expected_stdout); assert_eq!(&stderr_to_string(&mut cmd), expected_stderr); // Assert files are correctly copied/linked/run write_file(&dir.dir.join("gitconfig"), "git #2"); - let git_contents = read_to_string(&dir.dir.join(".gitconfig.coliru")).unwrap(); - let log_contents = read_to_string(&dir.dir.join("log.txt")).unwrap(); + let git_contents = read_file(&dir.dir.join(".gitconfig.coliru")); + let log_contents = read_file(&dir.dir.join("log.txt")); assert_eq!(git_contents, "git #1"); assert_eq!(log_contents, "script.sh called with arg1 linux\n"); } #[test] +#[cfg(target_family = "windows")] +fn test_missing_file() { + let (dir, mut cmd) = setup("test_missing_file"); + cmd.args(["manifest-windows-test.yml", "-t", "windows"]); + manifest_1(&dir.dir); + remove_file(&dir.dir.join("vimrc")).unwrap(); + + let expected_stdout = "\ +[1/3] Copy gitconfig to .gitconfig.coliru +[3/3] Link vimrc to _vimrc.coliru +[3/3] Run script.bat arg1 windows +script.bat called with arg1 windows\r +"; + let expected_stderr = " Error: The system cannot find the file specified. \ + (os error 2)\n"; + assert_eq!(&stdout_to_string(&mut cmd), expected_stdout); + assert_eq!(&stderr_to_string(&mut cmd), expected_stderr); + + // Assert files are correctly copied/linked/run + write_file(&dir.dir.join("gitconfig"), "git #2"); + let git_contents = read_file(&dir.dir.join(".gitconfig.coliru")); + let log_contents = read_file(&dir.dir.join("log.txt")); + assert_eq!(git_contents, "git #1"); + assert_eq!(log_contents, "script.bat called with arg1 windows \r\n"); +} + +#[test] fn test_empty_manifest() { let (dir, mut cmd) = setup("test_empty_manifest"); cmd.args(["manifest.yml"]); @@ -250,7 +366,7 @@ fn test_empty_manifest() { } #[test] -#[cfg(target_os = "linux")] +#[cfg(target_family = "unix")] fn test_missing_manifest() { let (_dir, mut cmd) = setup("test_missing_manifest"); cmd.args(["missing.yml"]); @@ -259,3 +375,15 @@ fn test_missing_manifest() { assert_eq!(&stdout_to_string(&mut cmd), ""); assert_eq!(&stderr_to_string(&mut cmd), expected); } + +#[test] +#[cfg(target_family = "windows")] +fn test_missing_manifest() { + let (_dir, mut cmd) = setup("test_missing_manifest"); + cmd.args(["missing-windows-test.yml"]); + + let expected = "Error: The system cannot find the file specified. \ + (os error 2)\n"; + assert_eq!(&stdout_to_string(&mut cmd), ""); + assert_eq!(&stderr_to_string(&mut cmd), expected); +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs @@ -50,6 +50,11 @@ pub fn write_file(path: &Path, contents: &str) { file.write_all(contents.as_bytes()).unwrap(); } +/// Reads the contents of a file into a string. +pub fn read_file(path: &Path) -> String { + fs::read_to_string(path).unwrap() +} + /// Returns the stdout of a command as a String. pub fn stdout_to_string(cmd: &mut Command) -> String { String::from_utf8_lossy(&cmd.output().unwrap().stdout).into_owned()