Skip to content

Commit

Permalink
Merge pull request #86 from philiptaron/integrate-test-packages
Browse files Browse the repository at this point in the history
eval: remove is_test and embed mock-nixpkgs.nix
  • Loading branch information
infinisil authored Jul 26, 2024
2 parents 725b75b + d0bede3 commit 749a7fc
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 37 deletions.
12 changes: 2 additions & 10 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ let
};
inherit (pkgs) lib;

testNixpkgsPath = ./tests/mock-nixpkgs.nix;
nixpkgsLibPath = nixpkgs + "/lib";

# Needed to make Nix evaluation work inside nix builds
initNix = ''
export TEST_ROOT=$(pwd)/test-tmp
Expand Down Expand Up @@ -49,18 +46,13 @@ let

packages = {
build = pkgs.callPackage ./package.nix {
inherit
nixpkgsLibPath
initNix
testNixpkgsPath
version
;
inherit initNix version;
nix = defaultNixPackage;
};

shell = pkgs.mkShell {
env.NIX_CHECK_BY_NAME_NIX_PACKAGE = lib.getBin defaultNixPackage;
env.NIX_PATH = "test-nixpkgs=${toString testNixpkgsPath}:test-nixpkgs/lib=${toString nixpkgsLibPath}";
env.NIX_CHECK_BY_NAME_NIXPKGS_LIB = "${nixpkgs}/lib";
env.RUST_SRC_PATH = "${pkgs.rustPlatform.rustLibSrc}";
inputsFrom = [ packages.build ];
nativeBuildInputs = with pkgs; [
Expand Down
12 changes: 9 additions & 3 deletions package.nix
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
lib,
rustPlatform,
path,
nix,
nixVersions,
lixVersions,
Expand All @@ -16,9 +17,7 @@
lixVersions.latest
],

nixpkgsLibPath,
initNix,
testNixpkgsPath,
version,
}:
let
Expand All @@ -27,6 +26,7 @@ in
rustPlatform.buildRustPackage {
pname = "nixpkgs-check-by-name";
inherit version;

src = fs.toSource {
root = ./.;
fileset = fs.unions [
Expand All @@ -36,18 +36,24 @@ rustPlatform.buildRustPackage {
./tests
];
};

cargoLock.lockFile = ./Cargo.lock;

nativeBuildInputs = [
clippy
makeWrapper
];

env.NIX_CHECK_BY_NAME_NIX_PACKAGE = lib.getBin nix;
env.NIX_PATH = "test-nixpkgs=${testNixpkgsPath}:test-nixpkgs/lib=${nixpkgsLibPath}";
env.NIX_CHECK_BY_NAME_NIXPKGS_LIB = "${path}/lib";

checkPhase = ''
# This path will be symlinked to the current version that is being tested
nixPackage=$(mktemp -d)/nix
# For initNix
export PATH=$nixPackage/bin:$PATH
# This is what nixpkgs-check-by-name uses
export NIX_CHECK_BY_NAME_NIX_PACKAGE=$nixPackage
Expand Down
71 changes: 57 additions & 14 deletions src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ use crate::validation::ResultIteratorExt as _;
use crate::validation::{self, Validation::Success};
use crate::NixFileStore;
use relative_path::RelativePathBuf;
use std::fs;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::{env, fs, process};

use anyhow::Context;
use serde::Deserialize;
use std::path::PathBuf;
use std::process;
use tempfile::Builder;

const EVAL_NIX: &[u8] = include_bytes!("eval.nix");
Expand Down Expand Up @@ -100,14 +98,63 @@ pub enum DefinitionVariant {
},
}

/// Pass through variables needed to make Nix evaluation work inside Nix build. See `initNix`.
/// If these variables don't exist, assume we're not in a Nix sandbox.
fn pass_through_environment_variables_for_nix_eval_in_nix_build(command: &mut process::Command) {
for variable in [
"NIX_CONF_DIR",
"NIX_LOCALSTATE_DIR",
"NIX_LOG_DIR",
"NIX_STATE_DIR",
"NIX_STORE_DIR",
] {
if let Ok(value) = env::var(variable) {
command.env(variable, value);
}
}
}

#[cfg(not(test))]
fn mutate_nix_instatiate_arguments_based_on_cfg(
_work_dir_path: &Path,
command: &mut process::Command,
) -> anyhow::Result<()> {
command.arg("--show-trace");

Ok(())
}

/// Tests need to be able to mock out `<nixpkgs>`; do that for them.
#[cfg(test)]
fn mutate_nix_instatiate_arguments_based_on_cfg(
work_dir_path: &Path,
command: &mut process::Command,
) -> anyhow::Result<()> {
const MOCK_NIXPKGS: &[u8] = include_bytes!("../tests/mock-nixpkgs.nix");
let mock_nixpkgs_path = work_dir_path.join("mock-nixpkgs.nix");
fs::write(&mock_nixpkgs_path, MOCK_NIXPKGS)?;

// Wire it up so that it can be imported as `import <test-nixpkgs> { }`.
command.arg("-I");
command.arg(&format!("test-nixpkgs={}", mock_nixpkgs_path.display()));

// Retrieve the path to the real nixpkgs lib, then wire it up to `import <test-nixpkgs/lib>`.
let nixpkgs_lib = env::var("NIX_CHECK_BY_NAME_NIXPKGS_LIB")
.with_context(|| "Could not get environment variable NIX_CHECK_BY_NAME_NIXPKGS_LIB")?;

command.arg("-I");
command.arg(&format!("test-nixpkgs/lib={nixpkgs_lib}"));

Ok(())
}

/// Check that the Nixpkgs attribute values corresponding to the packages in `pkgs/by-name` are of
/// the form `callPackage <package_file> { ... }`. See the `./eval.nix` file for how this is
/// achieved on the Nix side.
pub fn check_values(
nixpkgs_path: &Path,
nix_file_store: &mut NixFileStore,
package_names: Vec<String>,
is_test: bool,
) -> validation::Result<ratchet::Nixpkgs> {
let work_dir = Builder::new()
.prefix("nixpkgs-check-by-name")
Expand All @@ -132,14 +179,16 @@ pub fn check_values(
fs::write(&eval_nix_path, EVAL_NIX)?;

// Pinning Nix in this way makes the tool more reproducible
let nix_package = std::env::var("NIX_CHECK_BY_NAME_NIX_PACKAGE")
let nix_package = env::var("NIX_CHECK_BY_NAME_NIX_PACKAGE")
.with_context(|| "Could not get environment variable NIX_CHECK_BY_NAME_NIX_PACKAGE")?;

// With restrict-eval, only paths in NIX_PATH can be accessed. We explicitly specify them here.
let mut command = process::Command::new(format!("{nix_package}/bin/nix-instantiate"));
command
// Capture stderr so that it can be printed later in case of failure
.stderr(process::Stdio::piped())
// Clear environment so that nothing from the outside influences this `nix-instantiate`.
.env_clear()
.args([
"--eval",
"--json",
Expand All @@ -159,14 +208,8 @@ pub fn check_values(
.arg("-I")
.arg(nixpkgs_path);

// Only do these actions if we're not running tests.
if !is_test {
// Clear NIX_PATH to be sure it doesn't influence the result.
// During tests we need to have <mock-nixpkgs> available.
command.env_remove("NIX_PATH");
// Show the full Nix error trace, but not in tests because the full trace is super impure.
command.arg("--show-trace");
}
pass_through_environment_variables_for_nix_eval_in_nix_build(&mut command);
mutate_nix_instatiate_arguments_based_on_cfg(&work_dir_path, &mut command)?;

command.arg(eval_nix_path);

Expand Down
18 changes: 8 additions & 10 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub struct Args {

fn main() -> ExitCode {
let args = Args::parse();
match process(args.base, args.nixpkgs, false, &mut io::stderr()) {
match process(args.base, args.nixpkgs, &mut io::stderr()) {
Ok(true) => ExitCode::SUCCESS,
Ok(false) => ExitCode::from(1),
Err(e) => {
Expand All @@ -62,8 +62,6 @@ fn main() -> ExitCode {
/// # Arguments
/// - `base_nixpkgs`: Path to the base Nixpkgs to run ratchet checks against.
/// - `main_nixpkgs`: Path to the main Nixpkgs to check.
/// - `is_test`: Whether we're running a test right now, which e.g. allows access to the
/// <mock-nixpkgs> NIX_PATH entry
/// - `error_writer`: An `io::Write` value to write validation errors to, if any.
///
/// # Return value
Expand All @@ -73,12 +71,11 @@ fn main() -> ExitCode {
pub fn process<W: io::Write>(
base_nixpkgs: PathBuf,
main_nixpkgs: PathBuf,
is_test: bool,
error_writer: &mut W,
) -> anyhow::Result<bool> {
// Very easy to parallelise this, since it's totally independent
let base_thread = thread::spawn(move || check_nixpkgs(&base_nixpkgs, is_test));
let main_result = check_nixpkgs(&main_nixpkgs, is_test)?;
let base_thread = thread::spawn(move || check_nixpkgs(&base_nixpkgs));
let main_result = check_nixpkgs(&main_nixpkgs)?;

let base_result = match base_thread.join() {
Ok(res) => res?,
Expand Down Expand Up @@ -154,7 +151,7 @@ pub fn process<W: io::Write>(
/// This does not include ratchet checks, see ../README.md#ratchet-checks
/// Instead a `ratchet::Nixpkgs` value is returned, whose `compare` method allows performing the
/// ratchet check against another result.
pub fn check_nixpkgs(nixpkgs_path: &Path, is_test: bool) -> validation::Result<ratchet::Nixpkgs> {
pub fn check_nixpkgs(nixpkgs_path: &Path) -> validation::Result<ratchet::Nixpkgs> {
let mut nix_file_store = NixFileStore::default();

Ok({
Expand All @@ -169,9 +166,10 @@ pub fn check_nixpkgs(nixpkgs_path: &Path, is_test: bool) -> validation::Result<r
// No pkgs/by-name directory, always valid
Success(ratchet::Nixpkgs::default())
} else {
check_structure(&nixpkgs_path, &mut nix_file_store)?.result_map(|package_names|
check_structure(&nixpkgs_path, &mut nix_file_store)?.result_map(|package_names| {
// Only if we could successfully parse the structure, we do the evaluation checks
eval::check_values(&nixpkgs_path, &mut nix_file_store, package_names, is_test))?
eval::check_values(&nixpkgs_path, &mut nix_file_store, package_names)
})?
}
})
}
Expand Down Expand Up @@ -302,7 +300,7 @@ mod tests {
],
|| -> anyhow::Result<_> {
let mut writer = vec![];
process(base_nixpkgs.to_owned(), path.to_owned(), true, &mut writer)
process(base_nixpkgs.to_owned(), path.to_owned(), &mut writer)
.with_context(|| format!("Failed test case {name}"))?;
Ok(writer)
},
Expand Down

0 comments on commit 749a7fc

Please sign in to comment.