dfe137786c
Make constructing of a new Evaluation use the builder pattern rather than setting public mutable fields. This is currently a pure refactor (no functionality has changed) but has a few advantages: - We've encapsulated the internals of the fields in Evaluation, meaning we can change them without too much breakage of clients - We have type safety that prevents us from ever changing the fields of an Evaluation after it's built (which matters more in a world where we reuse Evaluations). More importantly, this paves the road for doing different things with the construction of an Evaluation - notably, sharing certain things like the GlobalsMap across subsequent evaluations in eg the REPL. Fixes: b/262 Change-Id: I4a27116faac14cdd144fc7c992d14ae095a1aca4 Reviewed-on: https://cl.tvl.fyi/c/depot/+/11956 Tested-by: BuildkiteCI Autosubmit: aspen <root@gws.fyi> Reviewed-by: flokli <flokli@flokli.de>
179 lines
5.6 KiB
Rust
179 lines
5.6 KiB
Rust
//! Tests which use upstream nix as an oracle to test evaluation against
|
|
|
|
use std::{env, path::PathBuf, process::Command};
|
|
|
|
use pretty_assertions::assert_eq;
|
|
|
|
fn nix_binary_path() -> PathBuf {
|
|
env::var("NIX_INSTANTIATE_BINARY_PATH")
|
|
.unwrap_or_else(|_| "nix-instantiate".to_owned())
|
|
.into()
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
enum Strictness {
|
|
Lazy,
|
|
Strict,
|
|
}
|
|
|
|
fn nix_eval(expr: &str, strictness: Strictness) -> String {
|
|
let store_dir = tempfile::tempdir().unwrap();
|
|
|
|
let mut args = match strictness {
|
|
Strictness::Lazy => vec![],
|
|
Strictness::Strict => vec!["--strict"],
|
|
};
|
|
args.extend_from_slice(&["--eval", "-E"]);
|
|
|
|
let output = Command::new(nix_binary_path())
|
|
.args(&args[..])
|
|
.arg(format!("({expr})"))
|
|
.env(
|
|
"NIX_REMOTE",
|
|
format!(
|
|
"local?root={}",
|
|
store_dir
|
|
.path()
|
|
.canonicalize()
|
|
.expect("valid path")
|
|
.display()
|
|
),
|
|
)
|
|
.output()
|
|
.unwrap();
|
|
if !output.status.success() {
|
|
panic!(
|
|
"nix eval {expr} failed!\n stdout: {}\n stderr: {}",
|
|
String::from_utf8_lossy(&output.stdout),
|
|
String::from_utf8_lossy(&output.stderr)
|
|
)
|
|
}
|
|
|
|
String::from_utf8(output.stdout).unwrap()
|
|
}
|
|
|
|
/// Compare the evaluation of the given nix expression in nix (using the
|
|
/// `NIX_INSTANTIATE_BINARY_PATH` env var to resolve the `nix-instantiate` binary) and tvix, and
|
|
/// assert that the result is identical
|
|
#[track_caller]
|
|
#[cfg(feature = "impure")]
|
|
fn compare_eval(expr: &str, strictness: Strictness) {
|
|
use tvix_eval::EvalIO;
|
|
|
|
let nix_result = nix_eval(expr, strictness);
|
|
let mut eval_builder = tvix_eval::Evaluation::builder_pure();
|
|
if matches!(strictness, Strictness::Strict) {
|
|
eval_builder = eval_builder.strict();
|
|
}
|
|
let eval = eval_builder
|
|
.io_handle(Box::new(tvix_eval::StdIO) as Box<dyn EvalIO>)
|
|
.build();
|
|
|
|
let tvix_result = eval
|
|
.evaluate(expr, None)
|
|
.value
|
|
.expect("tvix evaluation should succeed")
|
|
.to_string();
|
|
|
|
assert_eq!(nix_result.trim(), tvix_result);
|
|
}
|
|
|
|
/// Generate a suite of tests which call [`compare_eval`] on expressions, checking that nix and tvix
|
|
/// return identical results.
|
|
macro_rules! compare_eval_tests {
|
|
($strictness:expr, {}) => {};
|
|
($strictness:expr, {$(#[$meta:meta])* $test_name: ident($expr: expr); $($rest:tt)*}) => {
|
|
#[cfg(feature = "impure")]
|
|
#[test]
|
|
$(#[$meta])*
|
|
fn $test_name() {
|
|
compare_eval($expr, $strictness);
|
|
}
|
|
|
|
compare_eval_tests!($strictness, { $($rest)* });
|
|
}
|
|
}
|
|
|
|
macro_rules! compare_strict_eval_tests {
|
|
($($tests:tt)*) => {
|
|
compare_eval_tests!(Strictness::Strict, { $($tests)* });
|
|
}
|
|
}
|
|
|
|
macro_rules! compare_lazy_eval_tests {
|
|
($($tests:tt)*) => {
|
|
compare_eval_tests!(Strictness::Lazy, { $($tests)* });
|
|
}
|
|
}
|
|
|
|
compare_strict_eval_tests! {
|
|
literal_int("1");
|
|
add_ints("1 + 1");
|
|
add_lists("[1 2] ++ [3 4]");
|
|
add_paths(r#"[
|
|
(./. + "/")
|
|
(./foo + "bar")
|
|
(let name = "bar"; in ./foo + name)
|
|
(let name = "bar"; in ./foo + "${name}")
|
|
(let name = "bar"; in ./foo + "/" + "${name}")
|
|
(let name = "bar"; in ./foo + "/${name}")
|
|
(./. + ./.)
|
|
]"#);
|
|
}
|
|
|
|
// TODO(sterni): tvix_tests should gain support for something similar in the future,
|
|
// but this requires messing with the path naming which would break compat with
|
|
// C++ Nix's test suite
|
|
compare_lazy_eval_tests! {
|
|
// Wrap every expression type supported by [Compiler::compile] in a list
|
|
// with lazy evaluation enabled, so we can check it being thunked or not
|
|
// against C++ Nix.
|
|
unthunked_literals_in_list("[ https://tvl.fyi 1 1.2 ]");
|
|
unthunked_path_in_list("[ ./nix_oracle.rs ]");
|
|
unthunked_string_literal_in_list("[ \":thonking:\" ]");
|
|
thunked_unary_ops_in_list("[ (!true) (-1) ]");
|
|
thunked_bin_ops_in_list(r#"
|
|
let
|
|
# Necessary to fool the optimiser for && and ||
|
|
true' = true;
|
|
false' = false;
|
|
in
|
|
[
|
|
(true' && false')
|
|
(true' || false')
|
|
(false -> true)
|
|
(40 + 2)
|
|
(43 - 1)
|
|
(21 * 2)
|
|
(126 / 3)
|
|
({ } // { bar = null; })
|
|
(12 == 13)
|
|
(3 < 2)
|
|
(4 > 2)
|
|
(23 >= 42)
|
|
(33 <= 22)
|
|
([ ] ++ [ ])
|
|
(42 != null)
|
|
]
|
|
"#);
|
|
thunked_has_attrs_in_list("[ ({ } ? foo) ]");
|
|
thunked_list_in_list("[ [ 1 2 3 ] ]");
|
|
thunked_attr_set_in_list("[ { foo = null; } ]");
|
|
thunked_select_in_list("[ ({ foo = null; }.bar) ]");
|
|
thunked_assert_in_list("[ (assert false; 12) ]");
|
|
thunked_if_in_list("[ (if false then 13 else 12) ]");
|
|
thunked_let_in_list("[ (let foo = 12; in foo) ]");
|
|
thunked_with_in_list("[ (with { foo = 13; }; fooo) ]");
|
|
unthunked_identifier_in_list("let foo = 12; in [ foo ]");
|
|
thunked_lambda_in_list("[ (x: x) ]");
|
|
thunked_function_application_in_list("[ (builtins.add 1 2) ]");
|
|
thunked_legacy_let_in_list("[ (let { foo = 12; body = foo; }) ]");
|
|
unthunked_relative_path("[ ./foo ]");
|
|
unthunked_home_relative_path("[ ~/foo ]");
|
|
unthunked_absolute_path("[ /foo ]");
|
|
|
|
unthunked_formals_fallback_literal("({ foo ? 12 }: [ foo ]) { }");
|
|
unthunked_formals_fallback_string_literal("({ foo ? \"wiggly\" }: [ foo ]) { }");
|
|
thunked_formals_fallback_application("({ foo ? builtins.add 1 2 }: [ foo ]) { }");
|
|
thunked_formals_fallback_name_resolution_literal("({ foo ? bar, bar ? 12 }: [ foo ]) { }");
|
|
}
|