Skip to content

Commit

Permalink
tests: initial setup of integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
acheronfail committed Apr 2, 2020
1 parent fae1c3d commit fae1435
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ homepage = "https://github.com/acheronfail/gash"
repository = "https://github.com/acheronfail/gash"
build = "ci/build-clap-v3.rs"

[[test]]
name = "integration"
path = "tests_integration/mod.rs"

[dependencies]
anyhow = "1.0.28"
clap = { version = "3.0.0-beta.1", git = "https://github.com/clap-rs/clap" }
Expand Down
36 changes: 36 additions & 0 deletions tests_integration/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#[macro_export]
macro_rules! gashtest {
($name:ident, $func:expr) => {
#[test]
fn $name() {
let cmd = crate::util::setup(stringify!($name));
$func(cmd);
}
};
}

#[macro_export]
macro_rules! eqnice {
($expected:expr, $got:expr) => {
let expected = &*$expected;
let got = &*$got;
if expected != got {
panic!(
"
printed outputs differ!
expected:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
got:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
",
expected, got
);
}
};
}
9 changes: 9 additions & 0 deletions tests_integration/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Macros useful for testing.
#[macro_use]
mod macros;

// Utilities for making tests easier to write.
mod util;

// Tests themselves.
mod test_prefix;
47 changes: 47 additions & 0 deletions tests_integration/test_prefix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std::fs;

use crate::util::{git_last_hash, TestCommand};

// Simple run with prefix.
gashtest!(it_finds_a_prefix, |mut tcmd: TestCommand| {
let prefix = "dead";
let stdout = tcmd.args(&[prefix]).stdout();

let expected = format!(
"\
Searching for hash with prefix {prefix}
Found hash {prefix}{hash}
Patching last commit to include new hash... Success!
",
prefix = prefix,
hash = &git_last_hash(tcmd.dir())[prefix.len()..]
);

eqnice!(expected, stdout);
});

// Does not patch the commit with --dry-run.
gashtest!(dry_run_long_prefix, |mut tcmd: TestCommand| {
let hash_before = git_last_hash(tcmd.dir());
let stdout = tcmd.args(&["dead", "--dry-run"]).stdout();
let hash_after = git_last_hash(tcmd.dir());

assert_eq!(
true,
stdout.contains(&"Not amending commit due to --dry-run")
);
assert_eq!(hash_before, hash_after);
});

// Test fails if not run in a git repostiory.
gashtest!(it_does_not_work_outside_of_git, |mut tcmd: TestCommand| {
fs::remove_dir_all(&tcmd.dir().join(".git")).unwrap();

let expected = "\
Error: the command: 'git rev-parse HEAD' failed with:
fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
";
eqnice!(expected, tcmd.args(&["dead"]).stderr());
});
153 changes: 153 additions & 0 deletions tests_integration/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
use std::env;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::sync::atomic::{AtomicUsize, Ordering};

const TEST_DIR: &str = "gash-tests";
const NEXT_ID: AtomicUsize = AtomicUsize::new(0);

pub fn git(dir: impl AsRef<Path>, args: &[&str]) -> Result<String, String> {
let dir = dir.as_ref();
let output = Command::new("git")
.current_dir(&dir)
// Do not use system git config (/etc/gitconfig).
.env("GIT_CONFIG_NOSYSTEM", "1")
// Change `HOME` so ~/.gitconfig isn't used.
.env("HOME", &format!("{}", dir.display()))
.args(args)
.output()
.expect("Failed to run git");

if !output.status.success() {
Err(format!("Failed to run git!\n{:?}", output))
} else {
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}
}

pub fn git_last_hash(dir: impl AsRef<Path>) -> String {
git(&dir, &["rev-parse", "HEAD"]).unwrap()
}

#[derive(Debug)]
pub struct TestCommand {
cmd: Command,
dir: PathBuf,
exe: PathBuf,
}

impl TestCommand {
pub fn new(name: &str) -> TestCommand {
// Find the location of the binary we're testing.
let exe = env::current_exe()
.unwrap()
.parent()
.unwrap()
.join(format!("../gash{}", env::consts::EXE_SUFFIX));

// Create a temporary directory for each test.
let next_id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
let dir = env::temp_dir()
.join(TEST_DIR)
.join(name)
.join(&format!("{}", next_id));

if dir.exists() {
fs::remove_dir_all(&dir).expect("Failed to remove pre-existing directory");
}

// Initialise a git repository with a single commit.
TestCommand::git_init(&dir);

// Create a command.
let mut cmd = Command::new(&exe);
cmd.current_dir(&dir);
// Do not use system git config (/etc/gitconfig).
cmd.env("GIT_CONFIG_NOSYSTEM", "1");
// Change `HOME` so ~/.gitconfig isn't used.
cmd.env("HOME", &format!("{}", dir.display()));

TestCommand { cmd, dir, exe }
}

pub fn dir(&self) -> PathBuf {
self.dir.clone()
}

fn git_init(dir: impl AsRef<Path>) {
let dir = dir.as_ref();
if dir.exists() {
panic!("The path {} already exists!", dir.display());
}

// Run from parent directory since this creates the target directory.
let parent_dir = dir.parent().unwrap();
fs::create_dir_all(&parent_dir).unwrap();

// Initialise the repository.
git(&parent_dir, &["init", &format!("{}", dir.display())]).unwrap();

// Set "user.name" and "user.email" in the new repository.
git(&dir, &["config", "user.name", "elliot alderson"]).unwrap();
git(
&dir,
&["config", "user.email", "[email protected]"],
)
.unwrap();

// Add an initial commit.
fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(dir.join("foo"))
.unwrap()
.write(b"hello")
.unwrap();
git(&dir, &["add", "foo"]).unwrap();
git(&dir, &["commit", "-m", "initial commit"]).unwrap();
}

pub fn args(&mut self, args: &[&str]) -> &mut Self {
self.cmd.args(args);
self
}

pub fn stdout(&mut self) -> String {
let output = self.cmd.output().unwrap();
self.expect_success(&output);
String::from_utf8_lossy(&output.stdout).to_string()
}

pub fn stderr(&mut self) -> String {
let output = self.cmd.output().unwrap();
String::from_utf8_lossy(&output.stderr).to_string()
}

fn expect_success(&self, output: &Output) {
if !output.status.success() {
panic!(
"\n\n==========\n\
command failed but expected success!\
\
\n\ncommand: {:?}\
\n\ncwd: {}\
\n\nstatus: {}\
\n\nstdout: {}\
\n\nstderr: {}\
\n\n==========\n",
self.cmd,
self.dir.display(),
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
}
}

pub fn setup(test_name: &str) -> TestCommand {
TestCommand::new(test_name)
}

0 comments on commit fae1435

Please sign in to comment.