ofborg/nix.rs: Create Command's separately and add some rudimentary tests

This commit is contained in:
Graham Christensen 2018-01-19 11:11:02 -05:00
parent b17005eabb
commit b29650ffed
No known key found for this signature in database
GPG key ID: ACA1C1D120C83D5C
4 changed files with 277 additions and 17 deletions

View file

@ -31,6 +31,12 @@ impl Nix {
} }
pub fn safely_build_attrs(&self, nixpkgs: &Path, file: &str, attrs: Vec<String>) -> Result<File,File> { pub fn safely_build_attrs(&self, nixpkgs: &Path, file: &str, attrs: Vec<String>) -> Result<File,File> {
let cmd = self.safely_build_attrs_cmd(nixpkgs, file, attrs);
return self.run(cmd, true);
}
pub fn safely_build_attrs_cmd(&self, nixpkgs: &Path, file: &str, attrs: Vec<String>) -> Command {
let mut attrargs: Vec<String> = Vec::with_capacity(3 + (attrs.len() * 2)); let mut attrargs: Vec<String> = Vec::with_capacity(3 + (attrs.len() * 2));
attrargs.push(file.to_owned()); attrargs.push(file.to_owned());
attrargs.push(String::from("--no-out-link")); attrargs.push(String::from("--no-out-link"));
@ -40,14 +46,14 @@ impl Nix {
attrargs.push(attr); attrargs.push(attr);
} }
return self.safely("nix-build", nixpkgs, attrargs, true); return self.safe_command("nix-build", nixpkgs, attrargs);
} }
pub fn safely(&self, cmd: &str, nixpkgs: &Path, args: Vec<String>, keep_stdout: bool) -> Result<File,File> { pub fn safely(&self, cmd: &str, nixpkgs: &Path, args: Vec<String>, keep_stdout: bool) -> Result<File,File> {
let mut nixpath = OsString::new(); return self.run(self.safe_command(cmd, nixpkgs, args), keep_stdout);
nixpath.push("nixpkgs="); }
nixpath.push(nixpkgs.as_os_str());
pub fn run(&self, mut cmd: Command, keep_stdout: bool) -> Result<File,File> {
let stderr = tempfile().expect("Fetching a stderr tempfile"); let stderr = tempfile().expect("Fetching a stderr tempfile");
let mut reader = stderr.try_clone().expect("Cloning stderr to the reader"); let mut reader = stderr.try_clone().expect("Cloning stderr to the reader");
@ -60,22 +66,11 @@ impl Nix {
stdout = Stdio::null(); stdout = Stdio::null();
} }
let status = cmd
let status = Command::new(cmd)
.env_clear()
.current_dir(nixpkgs)
.stdout(Stdio::from(stdout)) .stdout(Stdio::from(stdout))
.stderr(Stdio::from(stderr)) .stderr(Stdio::from(stderr))
.env("HOME", "/homeless-shelter")
.env("NIX_PATH", nixpath)
.env("NIX_REMOTE", &self.remote)
.args(&["--show-trace"])
.args(&["--option", "restrict-eval", "true"])
.args(&["--option", "build-timeout", &format!("{}", self.build_timeout)])
.args(&["--argstr", "system", &self.system])
.args(args)
.status() .status()
.expect(format!("Running {:?}", cmd).as_ref()); .expect(format!("Running a program ...").as_ref());
reader.seek(SeekFrom::Start(0)).expect("Seeking to Start(0)"); reader.seek(SeekFrom::Start(0)).expect("Seeking to Start(0)");
@ -85,4 +80,217 @@ impl Nix {
return Err(reader) return Err(reader)
} }
} }
pub fn safe_command(&self, cmd: &str, nixpkgs: &Path, args: Vec<String>) -> Command {
let mut nixpath = OsString::new();
nixpath.push("nixpkgs=");
nixpath.push(nixpkgs.as_os_str());
let mut command = Command::new(cmd);
command.env_clear();
command.current_dir(nixpkgs);
command.env("HOME", "/homeless-shelter");
command.env("NIX_PATH", nixpath);
command.env("NIX_REMOTE", &self.remote);
command.args(&["--show-trace"]);
command.args(&["--option", "restrict-eval", "true"]);
command.args(&["--option", "build-timeout", &format!("{}", self.build_timeout)]);
command.args(&["--argstr", "system", &self.system]);
command.args(args);
return command;
}
}
#[cfg(test)]
mod tests {
fn nix() -> Nix {
Nix::new("x86_64-linux".to_owned(), "daemon".to_owned(), 1800)
}
fn build_path() -> PathBuf {
let mut cwd = env::current_dir().unwrap();
cwd.push(Path::new("./test-srcs/build"));
return cwd;
}
fn passing_eval_path() -> PathBuf {
let mut cwd = env::current_dir().unwrap();
cwd.push(Path::new("./test-srcs/eval"));
return cwd;
}
#[derive(Debug)]
enum Expect {
Pass,
Fail,
}
fn assert_run(res: Result<File,File>, expected: Expect, require: Vec<&str>) {
let expectation_held: bool = match expected {
Expect::Pass => res.is_ok(),
Expect::Fail => res.is_err(),
};
let file: File = match res {
Ok(file) => file,
Err(file) => file,
};
let lines: Vec<String> = BufReader::new(file)
.lines()
.into_iter()
.filter(|line| line.is_ok())
.map(|line| line.unwrap())
.collect();
let buildlog = lines
.into_iter()
.map(|line| format!(" | {}", line))
.collect::<Vec<String>>()
.join("\n");
let total_requirements = require.len();
let mut missed_requirements: usize = 0;
let requirements_held: Vec<Result<String, String>> =
require.into_iter()
.map(|line| line.to_owned())
.map(|line|
if buildlog.contains(&line) {
Ok(line)
} else {
missed_requirements += 1;
Err(line)
}
)
.collect();
let mut prefixes: Vec<String> = vec![
"".to_owned(),
"".to_owned(),
];
if !expectation_held {
prefixes.push(format!(
"The run was expected to {:?}, but did not.",
expected
));
prefixes.push("".to_owned());
} else {
prefixes.push(format!(
"The run was expected to {:?}, and did.",
expected
));
prefixes.push("".to_owned());
}
let mut suffixes = vec![
"".to_owned(),
format!("{} out of {} required lines matched.",
(total_requirements - missed_requirements),
total_requirements
),
"".to_owned(),
];
for expected_line in requirements_held {
suffixes.push(format!(" - {:?}", expected_line));
}
suffixes.push("".to_owned());
let output_blocks: Vec<Vec<String>> = vec![
prefixes,
vec![buildlog, "".to_owned()],
suffixes,
];
let output_blocks_strings: Vec<String> =
output_blocks
.into_iter()
.map(|lines| lines.join("\n"))
.collect();
let output: String = output_blocks_strings
.join("\n");
if expectation_held && missed_requirements == 0 {
} else {
panic!(output);
}
}
use super::*;
use std::io::BufReader;
use std::io::BufRead;
use std::path::PathBuf;
use std::env;
#[test]
fn safely_build_attrs_success() {
let nix = nix();
let ret: Result<File,File> = nix.safely_build_attrs(
build_path().as_path(),
"default.nix",
vec![String::from("success")]
);
assert_run(ret, Expect::Pass, vec![
"-success.drv",
"building path(s)",
"hi",
"-success"
]);
}
#[test]
fn safely_build_attrs_failure() {
let nix = nix();
let ret: Result<File,File> = nix.safely_build_attrs(
build_path().as_path(),
"default.nix",
vec![String::from("failed")]
);
assert_run(ret, Expect::Fail, vec![
"-failed.drv",
"building path(s)",
"hi",
"failed to produce output path"
]);
}
#[test]
fn strict_sandboxing() {
let ret: Result<File,File> = nix().safely_build_attrs(
build_path().as_path(),
"default.nix",
vec![String::from("sandbox-violation")]
);
assert_run(ret, Expect::Fail, vec![
"error: while evaluating the attribute",
"access to path",
"is forbidden in restricted mode"
]);
}
#[test]
fn instantiation() {
let ret: Result<File,File> = nix().safely(
"nix-instantiate",
passing_eval_path().as_path(),
vec![],
true
);
assert_run(ret, Expect::Pass, vec![
"the result might be removed by the garbage collector",
"-failed.drv",
"-success.drv"
]);
}
} }

View file

@ -0,0 +1,27 @@
let
nix = import <nix/config.nix>;
in {
success = derivation {
name = "success";
system = builtins.currentSystem;
builder = nix.shell;
args = [
"-c"
"echo hi; echo ${toString builtins.currentTime} > $out" ];
};
failed = derivation {
name = "failed";
system = builtins.currentSystem;
builder = nix.shell;
args = [
"-c"
"echo hi; echo ${toString builtins.currentTime}" ];
};
sandbox-violation = derivation {
name = "sandbox-violation";
system = builtins.currentSystem;
builder = ./../../src;
};
}

View file

@ -0,0 +1,4 @@
#!/bin/sh
echo "$@"
echo hi

View file

@ -0,0 +1,21 @@
let
nix = import <nix/config.nix>;
in rec {
success = derivation {
name = "success";
system = builtins.currentSystem;
builder = nix.shell;
args = [
"-c"
"echo hi; echo ${toString builtins.currentTime} > $out" ];
};
failed = derivation {
name = "failed";
system = builtins.currentSystem;
builder = nix.shell;
args = [
"-c"
"echo hi; echo ${toString builtins.currentTime}; echo ${success}" ];
};
}