summaryrefslogtreecommitdiff
path: root/tests/e2e/mod.rs
blob: a1e7e7f38aa814b987178206c7a4a3dcfa994f25 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Support code for e2e tests, which run n2 as a binary.

mod basic;
mod directories;
mod discovered;
mod missing;
mod regen;
mod validations;

use anyhow::anyhow;

pub fn n2_binary() -> std::path::PathBuf {
    std::env::current_exe()
        .expect("test binary path")
        .parent()
        .expect("test binary directory")
        .parent()
        .expect("binary directory")
        .join("n2")
}

pub fn n2_command(args: Vec<&str>) -> std::process::Command {
    let mut cmd = std::process::Command::new(n2_binary());
    cmd.args(args);
    cmd
}

fn print_output(out: &std::process::Output) {
    // Gross: use print! instead of writing to stdout so Rust test
    // framework can capture it.
    print!("{}", std::str::from_utf8(&out.stdout).unwrap());
    print!("{}", std::str::from_utf8(&out.stderr).unwrap());
}

pub fn assert_output_contains(out: &std::process::Output, text: &str) {
    let out = std::str::from_utf8(&out.stdout).unwrap();
    if !out.contains(text) {
        panic!(
            "assertion failed; expected output to contain {:?} but got:\n{}",
            text, out
        );
    }
}

pub fn assert_output_not_contains(out: &std::process::Output, text: &str) {
    let out = std::str::from_utf8(&out.stdout).unwrap();
    if out.contains(text) {
        panic!(
            "assertion failed; expected output to not contain {:?} but got:\n{}",
            text, out
        );
    }
}

/// Manages a temporary directory for invoking n2.
pub struct TestSpace {
    dir: tempfile::TempDir,
}
impl TestSpace {
    pub fn new() -> anyhow::Result<Self> {
        let dir = tempfile::tempdir()?;
        Ok(TestSpace { dir })
    }

    /// Write a file into the working space.
    pub fn write(&self, path: &str, content: &str) -> std::io::Result<()> {
        std::fs::write(self.dir.path().join(path), content)
    }

    /// Read a file from the working space.
    pub fn read(&self, path: &str) -> anyhow::Result<Vec<u8>> {
        let path = self.dir.path().join(path);
        std::fs::read(&path).map_err(|err| anyhow!("read {}: {}", path.display(), err))
    }

    pub fn metadata(&self, path: &str) -> std::io::Result<std::fs::Metadata> {
        std::fs::metadata(self.dir.path().join(path))
    }

    pub fn sub_mtime(&self, path: &str, dur: std::time::Duration) -> anyhow::Result<()> {
        let path = self.dir.path().join(path);
        let t = std::time::SystemTime::now() - dur;
        filetime::set_file_mtime(path, filetime::FileTime::from_system_time(t))?;
        Ok(())
    }

    /// Invoke n2, returning process output.
    pub fn run(&self, cmd: &mut std::process::Command) -> std::io::Result<std::process::Output> {
        cmd.current_dir(self.dir.path()).output()
    }

    /// Like run, but also print output if the build failed.
    pub fn run_expect(
        &self,
        cmd: &mut std::process::Command,
    ) -> anyhow::Result<std::process::Output> {
        let out = self.run(cmd)?;
        if !out.status.success() {
            print_output(&out);
            anyhow::bail!("build failed, status {}", out.status);
        }
        Ok(out)
    }

    /// Persist the temp dir locally and abort the test.  Debugging helper.
    #[allow(dead_code)]
    pub fn eject(self) -> ! {
        panic!("ejected at {:?}", self.dir.into_path());
    }
}

// Ensure TOUCH_RULE has the same description and number of lines of text
// on Windows/non-Windows to make tests agnostic to platform.

#[cfg(unix)]
pub const TOUCH_RULE: &str = "
rule touch
  command = touch $out
  description = touch $out
";

#[cfg(windows)]
pub const TOUCH_RULE: &str = "
rule touch
  command = cmd /c type nul > $out
  description = touch $out
";

#[cfg(unix)]
pub const ECHO_RULE: &str = "
rule echo
  command = echo $text
  description = echo $out
";

#[cfg(windows)]
pub const ECHO_RULE: &str = "
rule echo
  command = cmd /c echo $text
  description = echo $out
";