coliru

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

commit 757f1c0fda08bdb4055b25526383a67a21b0284a
parent 0f5dfafbf7097df7bc083618c5bc9f52eddae03e
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Tue, 25 Jun 2024 17:50:37 -0700

Implement basic integration tests

Diffstat:
MREADME.md | 4++--
Msrc/utils.rs | 50+++++++++++++++++++++++++-------------------------
Mtests/basic.rs | 244++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mtests/common/mod.rs | 18+++++++++++++++---
4 files changed, 284 insertions(+), 32 deletions(-)

diff --git a/README.md b/README.md @@ -47,11 +47,11 @@ steps: link: - src: vim_dotfiles/.vimrc dst: ~/.vimrc - tags: [ windows, linux, macos ] + tags: [ linux, macos ] - run: - src: install_programs.sh prefix: bash # Unecessary if install_programs.sh is executable - postfix: -y + postfix: $COLIRU_RULES -y tags: [ linux ] ``` diff --git a/src/utils.rs b/src/utils.rs @@ -120,8 +120,8 @@ mod tests { dir } - /// Create (or overwrite) a file with certain contents. - fn create_file(path: &Path, contents: &str) { + /// Writes a string to a file, overwriting it if it already exists. + fn write_file(path: &Path, contents: &str) { let mut file = fs::File::create(path).unwrap(); file.write_all(contents.as_bytes()).unwrap(); } @@ -132,11 +132,11 @@ mod tests { let src = &tmp.dir.join("foo"); let dst = &tmp.dir.join("dir1").join("dir2").join("bar"); - create_file(src, "old contents of foo"); + write_file(src, "old contents of foo"); let result = copy_file(src.to_str().unwrap(), dst.to_str().unwrap()); - create_file(src, "new contents of foo"); + write_file(src, "new contents of foo"); let contents = fs::read_to_string(dst).unwrap(); assert_eq!(result.is_ok(), true); assert_eq!(contents, "old contents of foo"); @@ -148,12 +148,12 @@ mod tests { let src = &tmp.dir.join("foo"); let dst = &tmp.dir.join("bar"); - create_file(src, "old contents of foo"); - create_file(dst, "old contents of bar"); + write_file(src, "old contents of foo"); + write_file(dst, "old contents of bar"); let result = copy_file(src.to_str().unwrap(), dst.to_str().unwrap()); - create_file(src, "new contents of foo"); + write_file(src, "new contents of foo"); let contents = fs::read_to_string(dst).unwrap(); assert_eq!(result.is_ok(), true); assert_eq!(contents, "old contents of foo"); @@ -166,12 +166,12 @@ mod tests { let src = &tmp.dir.join("foo"); let dst = &tmp.dir.join("bar"); - create_file(src, "old contents of foo"); + write_file(src, "old contents of foo"); symlink("missing", dst).unwrap(); let result = copy_file(src.to_str().unwrap(), dst.to_str().unwrap()); - create_file(src, "new contents of foo"); + write_file(src, "new contents of foo"); let contents = fs::read_to_string(dst).unwrap(); assert_eq!(result.is_ok(), true); assert_eq!(contents, "old contents of foo"); @@ -185,11 +185,11 @@ mod tests { let src = &tmp.dir.join("foo"); let dst = &tmp.dir.join("dir").join("bar"); let dst_tilde = "~/test_copy_file_tilde_expansion/dir/bar"; - create_file(src, "old contents of foo"); + write_file(src, "old contents of foo"); let result = copy_file(src.to_str().unwrap(), dst_tilde); - create_file(src, "new contents of foo"); + write_file(src, "new contents of foo"); let contents = fs::read_to_string(dst).unwrap(); assert_eq!(result.is_ok(), true); assert_eq!(contents, "old contents of foo"); @@ -202,11 +202,11 @@ mod tests { let src = &tmp.dir.join("foo"); let dst = &tmp.dir.join("dir1").join("dir2").join("bar"); - create_file(src, "old contents of foo"); + write_file(src, "old contents of foo"); let result = link_file(src.to_str().unwrap(), dst.to_str().unwrap()); - create_file(src, "new contents of foo"); + write_file(src, "new contents of foo"); let contents = fs::read_to_string(dst).unwrap(); assert_eq!(result.is_ok(), true); assert_eq!(contents, "new contents of foo"); @@ -219,12 +219,12 @@ mod tests { let src = &tmp.dir.join("foo"); let dst = &tmp.dir.join("bar"); - create_file(src, "old contents of foo"); - create_file(dst, "old contents of bar"); + write_file(src, "old contents of foo"); + write_file(dst, "old contents of bar"); let result = link_file(src.to_str().unwrap(), dst.to_str().unwrap()); - create_file(src, "new contents of foo"); + write_file(src, "new contents of foo"); let contents = fs::read_to_string(dst).unwrap(); assert_eq!(result.is_ok(), true); assert_eq!(contents, "new contents of foo"); @@ -237,12 +237,12 @@ mod tests { let src = &tmp.dir.join("foo"); let dst = &tmp.dir.join("bar"); - create_file(src, "old contents of foo"); + write_file(src, "old contents of foo"); symlink("missing", dst).unwrap(); let result = link_file(src.to_str().unwrap(), dst.to_str().unwrap()); - create_file(src, "new contents of foo"); + write_file(src, "new contents of foo"); let contents = fs::read_to_string(dst).unwrap(); assert_eq!(result.is_ok(), true); assert_eq!(contents, "new contents of foo"); @@ -256,11 +256,11 @@ mod tests { let src = &tmp.dir.join("foo"); let dst = &tmp.dir.join("dir").join("bar"); let dst_tilde = "~/test_link_file_tilde_expansion/dir/bar"; - create_file(src, "old contents of foo"); + write_file(src, "old contents of foo"); let result = link_file(src.to_str().unwrap(), dst_tilde); - create_file(src, "new contents of foo"); + write_file(src, "new contents of foo"); let contents = fs::read_to_string(dst).unwrap(); assert_eq!(result.is_ok(), true); assert_eq!(contents, "new contents of foo"); @@ -274,11 +274,11 @@ mod tests { let src = &tmp.dir.join("foo"); let src_rel = "test_link_file_relative_source/foo"; let dst = &tmp.dir.join("dir1").join("dir2").join("bar"); - create_file(src, "old contents of foo"); + write_file(src, "old contents of foo"); let result = link_file(src_rel, dst.to_str().unwrap()); - create_file(src, "new contents of foo"); + write_file(src, "new contents of foo"); let contents = fs::read_to_string(dst).unwrap(); let link = fs::read_link(dst).unwrap(); assert_eq!(result.is_ok(), true); @@ -292,7 +292,7 @@ mod tests { let tmp = setup("test_run_script_successful"); let src = &tmp.dir.join("foo"); - create_file(src, "exit 0"); + write_file(src, "exit 0"); let result = run_script(src.to_str().unwrap(), "bash", ""); @@ -305,7 +305,7 @@ mod tests { let tmp = setup("test_run_script_failure"); let src = &tmp.dir.join("foo"); - create_file(src, "exit 2"); + write_file(src, "exit 2"); let result = run_script(src.to_str().unwrap(), "bash", ""); @@ -320,7 +320,7 @@ mod tests { let src = &tmp.dir.join("foo"); let dst = &tmp.dir.join("bar"); - create_file(src, &format!("echo $@ > {}", dst.to_str().unwrap())); + write_file(src, &format!("echo $@ > {}", dst.to_str().unwrap())); let result = run_script(src.to_str().unwrap(), "bash", "arg1 arg2"); diff --git a/tests/basic.rs b/tests/basic.rs @@ -1,6 +1,37 @@ mod common; -use common::{get_output, setup}; +use common::*; +use std::fs::{create_dir_all, read_to_string, remove_file}; +use std::path::Path; + +/// Create a basic manifest file and its associated dotfiles in a directory +fn manifest_1(dir: &Path) { + let manifest = "\ +steps: + - copy: + - src: git_dotfiles/.gitconfig + dst: ~/.gitconfig + link: + - src: vim_dotfiles/.vimrc + dst: ~/.vimrc + tags: [ windows, linux, macos ] + - link: + - src: vim_dotfiles/.vimrc + dst: ~/_vimrc + tags: [ windows ] + - run: + - src: install_programs.sh + prefix: bash # Unecessary if install_programs.sh is executable + postfix: $COLIRU_RULES -y + tags: [ linux ] +"; + write_file(&dir.join("manifest.yml"), manifest); + create_dir_all(&dir.join("git_dotfiles")).unwrap(); + write_file(&dir.join("git_dotfiles").join(".gitconfig"), "git config"); + create_dir_all(&dir.join("vim_dotfiles")).unwrap(); + write_file(&dir.join("vim_dotfiles").join(".vimrc"), "vim config"); + write_file(&dir.join("install_programs.sh"), "echo $@ > log"); +} #[test] fn test_help() { @@ -21,5 +52,214 @@ Options: -h, --help Print help -V, --version Print version "; - assert_eq!(&get_output(&mut cmd), expected); + assert_eq!(&stdout_to_string(&mut cmd), expected); + assert_eq!(&stderr_to_string(&mut cmd), ""); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_standard() { + let (dir, mut cmd) = setup("test_standard"); + cmd.args(["manifest.yml", "-t", "linux"]); + manifest_1(&dir.dir); + + let expected = "\ +[1/3] Copy git_dotfiles/.gitconfig to ~/.gitconfig +[1/3] Link vim_dotfiles/.vimrc to ~/.vimrc +[3/3] Run bash install_programs.sh linux -y +"; + 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("git_dotfiles").join(".gitconfig"), "git #2"); + write_file(&dir.dir.join("vim_dotfiles").join(".vimrc"), "vim #2"); + let git_contents = read_to_string(&dir.dir.join(".gitconfig")).unwrap(); + let vim1_contents = read_to_string(&dir.dir.join(".vimrc")).unwrap(); + let vim2_exists = dir.dir.join("_vimrc").exists(); + let log_contents = read_to_string(&dir.dir.join("log")).unwrap(); + assert_eq!(git_contents, "git config"); + assert_eq!(vim1_contents, "vim #2"); + assert_eq!(vim2_exists, false); + assert_eq!(log_contents, "linux -y\n"); +} + +#[test] +fn test_run_alternate_tag_rules_1() { + let (dir, mut cmd) = setup("test_run_alternate_tag_rules_1"); + cmd.args(["manifest.yml", "-t", "macos"]); + manifest_1(&dir.dir); + + let expected = "\ +[1/3] Copy git_dotfiles/.gitconfig to ~/.gitconfig +[1/3] Link vim_dotfiles/.vimrc to ~/.vimrc +"; + 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("git_dotfiles").join(".gitconfig"), "git #2"); + write_file(&dir.dir.join("vim_dotfiles").join(".vimrc"), "vim #2"); + let git_contents = read_to_string(&dir.dir.join(".gitconfig")).unwrap(); + let vim1_contents = read_to_string(&dir.dir.join(".vimrc")).unwrap(); + let vim2_exists = dir.dir.join("_vimrc").exists(); + let log_exists = dir.dir.join("log").exists(); + assert_eq!(git_contents, "git config"); + assert_eq!(vim1_contents, "vim #2"); + assert_eq!(vim2_exists, false); + assert_eq!(log_exists, false); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_run_alternate_tag_rules_2() { + let (dir, mut cmd) = setup("test_run_alternate_tag_rules_2"); + cmd.args(["manifest.yml", "-t", "linux,windows", "^macos"]); + manifest_1(&dir.dir); + + let expected = "\ +[2/3] Link vim_dotfiles/.vimrc to ~/_vimrc +[3/3] Run bash install_programs.sh linux,windows ^macos -y +"; + 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("vim_dotfiles").join(".vimrc"), "vim #2"); + let git_exists = dir.dir.join(".gitconfig").exists(); + let vim1_exists = dir.dir.join(".vimrc").exists(); + let vim2_contents = read_to_string(&dir.dir.join("_vimrc")).unwrap(); + let log_contents = read_to_string(&dir.dir.join("log")).unwrap(); + assert_eq!(git_exists, false); + assert_eq!(vim1_exists, false); + assert_eq!(vim2_contents, "vim #2"); + assert_eq!(log_contents, "linux,windows ^macos -y\n"); +} + +#[test] +fn test_dry_run() { + let (dir, mut cmd) = setup("test_dry_run"); + cmd.args(["manifest.yml", "--dry-run", "-t", "linux"]); + manifest_1(&dir.dir); + + let expected = "\ +[1/3] Copy git_dotfiles/.gitconfig to ~/.gitconfig (DRY RUN) +[1/3] Link vim_dotfiles/.vimrc to ~/.vimrc (DRY RUN) +[3/3] Run bash install_programs.sh linux -y (DRY RUN) +"; + assert_eq!(&stdout_to_string(&mut cmd), expected); + assert_eq!(&stderr_to_string(&mut cmd), ""); + + // Assert files are correctly copied/linked/run + let git_exists = dir.dir.join(".gitconfig").exists(); + let vim1_exists = dir.dir.join(".vimrc").exists(); + let vim2_exists = dir.dir.join("_vimrc").exists(); + let log_exists = dir.dir.join("log").exists(); + assert_eq!(git_exists, false); + assert_eq!(vim1_exists, false); + assert_eq!(vim2_exists, false); + assert_eq!(log_exists, false); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_copy() { + let (dir, mut cmd) = setup("test_copy"); + cmd.args(["manifest.yml", "--copy", "-t", "linux"]); + manifest_1(&dir.dir); + + let expected = "\ +[1/3] Copy git_dotfiles/.gitconfig to ~/.gitconfig +[1/3] Copy vim_dotfiles/.vimrc to ~/.vimrc +[3/3] Run bash install_programs.sh linux -y +"; + 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("git_dotfiles").join(".gitconfig"), "git #2"); + write_file(&dir.dir.join("vim_dotfiles").join(".vimrc"), "vim #2"); + let git_contents = read_to_string(&dir.dir.join(".gitconfig")).unwrap(); + let vim1_contents = read_to_string(&dir.dir.join(".vimrc")).unwrap(); + let vim2_exists = dir.dir.join("_vimrc").exists(); + let log_contents = read_to_string(&dir.dir.join("log")).unwrap(); + assert_eq!(git_contents, "git config"); + assert_eq!(vim1_contents, "vim config"); + assert_eq!(vim2_exists, false); + assert_eq!(log_contents, "linux -y\n"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_run_failure() { + let (dir, mut cmd) = setup("test_run_failure"); + cmd.args(["manifest.yml", "-t", "linux"]); + manifest_1(&dir.dir); + write_file(&dir.dir.join("install_programs.sh"), "exit 1"); + + let expected_stdout = "\ +[1/3] Copy git_dotfiles/.gitconfig to ~/.gitconfig +[1/3] Link vim_dotfiles/.vimrc to ~/.vimrc +[3/3] Run bash install_programs.sh linux -y +"; + let expected_stderr = " Error: Process exited with exit status: 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("git_dotfiles").join(".gitconfig"), "git #2"); + write_file(&dir.dir.join("vim_dotfiles").join(".vimrc"), "vim #2"); + let git_contents = read_to_string(&dir.dir.join(".gitconfig")).unwrap(); + let vim1_contents = read_to_string(&dir.dir.join(".vimrc")).unwrap(); + let vim2_exists = dir.dir.join("_vimrc").exists(); + assert_eq!(git_contents, "git config"); + assert_eq!(vim1_contents, "vim #2"); + assert_eq!(vim2_exists, false); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_missing_file() { + let (dir, mut cmd) = setup("test_missing_file"); + cmd.args(["manifest.yml", "-t", "linux"]); + manifest_1(&dir.dir); + remove_file(&dir.dir.join("vim_dotfiles").join(".vimrc")).unwrap(); + + let expected_stdout = "\ +[1/3] Copy git_dotfiles/.gitconfig to ~/.gitconfig +[1/3] Link vim_dotfiles/.vimrc to ~/.vimrc +[3/3] Run bash install_programs.sh linux -y +"; + 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("git_dotfiles").join(".gitconfig"), "git #2"); + let git_contents = read_to_string(&dir.dir.join(".gitconfig")).unwrap(); + let log_contents = read_to_string(&dir.dir.join("log")).unwrap(); + assert_eq!(git_contents, "git config"); + assert_eq!(log_contents, "linux -y\n"); +} + +#[test] +fn test_empty_manifest() { + let (dir, mut cmd) = setup("test_empty_manifest"); + cmd.args(["manifest.yml"]); + write_file(&dir.dir.join("manifest.yml"), ""); + + let expected = "Error: missing field `steps`\n"; + assert_eq!(&stdout_to_string(&mut cmd), ""); + assert_eq!(&stderr_to_string(&mut cmd), expected); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_missing_manifest() { + let (_dir, mut cmd) = setup("test_missing_manifest"); + cmd.args(["missing.yml"]); + + let expected = "Error: No such file or directory (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 @@ -1,6 +1,7 @@ use std::env; use std::fs; -use std::path::PathBuf; +use std::io::Write; +use std::path::{Path, PathBuf}; use std::process::Command; /// Stores the path to a temporary directory that is automatically deleted @@ -8,7 +9,7 @@ use std::process::Command; /// /// Adapted from ripgrep's tests (crates/ignore/src/lib.rs) pub struct TempDir { - dir: PathBuf + pub dir: PathBuf } impl Drop for TempDir { fn drop(&mut self) { @@ -43,7 +44,18 @@ pub fn setup(name: &str) -> (TempDir, Command) { (dir, cmd) } +/// Writes a string to a file, overwriting it if it already exists. +pub fn write_file(path: &Path, contents: &str) { + let mut file = fs::File::create(path).unwrap(); + file.write_all(contents.as_bytes()).unwrap(); +} + /// Returns the stdout of a command as a String. -pub fn get_output(cmd: &mut Command) -> String { +pub fn stdout_to_string(cmd: &mut Command) -> String { String::from_utf8_lossy(&cmd.output().unwrap().stdout).into_owned() } + +/// Returns the stderr of a command as a String. +pub fn stderr_to_string(cmd: &mut Command) -> String { + String::from_utf8_lossy(&cmd.output().unwrap().stderr).into_owned() +}