Skip to content

Catch caching of transient flake evaluation error #12717

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4653,6 +4653,16 @@ static RegisterPrimOp primop_splitVersion({
.fun = prim_splitVersion,
});

static void prim_testThrowErrorMaybe(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
if (getEnv("_NIX_TEST_DO_THROW_ERROR") == "1") {
state.error<EvalError>("this is a dummy error").atPos(pos).debugThrow();
} else {
state.forceValue(*args[0], pos);
v = *args[0];
}
}


/*************************************************************
* Primop registration
Expand Down Expand Up @@ -4880,6 +4890,16 @@ void EvalState::createBaseEnv()
.fun = settings.traceVerbose ? prim_trace : prim_second,
});

if (getEnv("_NIX_TEST_PRIMOPS") == "1") {
addPrimOp({
.name = "__testThrowErrorMaybe",
.args = { "e" },
.arity = 1,
.doc = "throw dummy error if _NIX_TEST_DO_THROW_ERROR == 1, return arg otherwise",
.fun = prim_testThrowErrorMaybe
});
}

/* Add a value containing the current Nix expression search path. */
auto list = buildList(lookupPath.elements.size());
for (const auto & [n, i] : enumerate(lookupPath.elements)) {
Expand Down
34 changes: 34 additions & 0 deletions tests/functional/flakes/eval-cache.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ cat >"$flake1Dir/flake.nix" <<EOF
in
assert (f 100); self.drv;
ifd = assert (import self.drv); self.drv;
artificialTwoDifferentThrows = builtins.testThrowErrorMaybe (throw "real throw");
artificialThrowOrDrv = builtins.testThrowErrorMaybe self.drv;
};
}
EOF
Expand All @@ -48,3 +50,35 @@ nix build --no-link "$flake1Dir#stack-depth"
expect 1 nix build "$flake1Dir#ifd" --option allow-import-from-derivation false 2>&1 \
| grepQuiet 'error: cannot build .* during evaluation because the option '\''allow-import-from-derivation'\'' is disabled'
nix build --no-link "$flake1Dir#ifd"

# make sure this builtin does not reach production
nix eval --expr 'assert ! (builtins ? testThrowErrorMaybe); null'

# and that it does reach the testing environment
export _NIX_TEST_PRIMOPS=1
nix eval --expr 'assert (builtins.testThrowErrorMaybe true); null'

# and that it works
_NIX_TEST_DO_THROW_ERROR=1 expect 1 nix eval --expr 'builtins.testThrowErrorMaybe true' 2>&1 \
| grepQuiet 'this is a dummy error'


# make sure error details do not get cached
_NIX_TEST_DO_THROW_ERROR=1 expect 1 nix eval "$flake1Dir#artificialTwoDifferentThrows" 2>&1 \
| grepQuiet "this is a dummy error"
expect 1 nix eval "$flake1Dir#artificialTwoDifferentThrows" 2>&1 \
| grepQuiet "real throw"

# If a cached error cannot be reproduced, do not continue as usual.
# Instead, throw an error.
#
# To be clear, the following test case should never occur in production.
# But it might, due to an implementation error in Nix or plugins.

# create an artificial exception cache entry
_NIX_TEST_DO_THROW_ERROR=1 expect 1 nix build "$flake1Dir#artificialThrowOrDrv" 2>&1 \
| grepQuiet "this is a dummy error"

# Invoke again but without the artificial throwing of an exception
expect 1 nix build "$flake1Dir#artificialThrowOrDrv" 2>&1 \
| grepQuiet "unexpected evaluation success despite the existence of a cached error. This should not happen. It is a bug in the implementation of Nix or a plugin. A transient exception should not have been cached."
Loading