aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libcmd/repl.cc2
-rw-r--r--src/libexpr/eval.cc54
-rw-r--r--src/libexpr/eval.hh7
-rw-r--r--src/libexpr/flake/flake.cc2
-rw-r--r--src/libexpr/primops.cc292
-rw-r--r--src/libexpr/primops/context.cc4
-rw-r--r--src/libexpr/primops/fetchMercurial.cc8
-rw-r--r--src/libexpr/primops/fetchTree.cc9
-rw-r--r--src/libexpr/tests/error_traces.cc1228
-rw-r--r--src/libexpr/value.hh2
-rw-r--r--src/libstore/derivations.hh1
-rw-r--r--src/libstore/misc.cc2
-rw-r--r--src/libstore/outputs-spec.cc22
-rw-r--r--src/libstore/path-regex.hh7
-rw-r--r--src/libstore/path.cc6
-rw-r--r--src/libstore/path.hh3
-rw-r--r--src/libstore/realisation.hh2
-rw-r--r--src/libstore/tests/libstoretests.hh23
-rw-r--r--src/libstore/tests/local.mk2
-rw-r--r--src/libstore/tests/outputs-spec.cc14
-rw-r--r--src/libstore/tests/path.cc144
-rw-r--r--src/libutil/monitor-fd.hh53
-rw-r--r--src/libutil/regex-combinators.hh30
-rw-r--r--src/nix/flake.cc13
24 files changed, 1745 insertions, 185 deletions
diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
index 9b12f8fa2..4158439b6 100644
--- a/src/libcmd/repl.cc
+++ b/src/libcmd/repl.cc
@@ -397,7 +397,7 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
Expr * e = parseString(expr);
Value v;
e->eval(*state, *env, v);
- state->forceAttrs(v, noPos, "nevermind, it is ignored anyway");
+ state->forceAttrs(v, noPos, "while evaluating an attrset for the purpose of completion (this error should not be displayed; file an issue?)");
for (auto & i : *v.attrs) {
std::string_view name = state->symbols[i.name];
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 277cbb5f9..1828b8c2e 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -11,7 +11,9 @@
#include <algorithm>
#include <chrono>
+#include <iostream>
#include <cstring>
+#include <optional>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
@@ -1927,7 +1929,9 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
/* skip canonization of first path, which would only be not
canonized in the first place if it's coming from a ./${foo} type
path */
- auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first, "while evaluating a path segment");
+ auto part = state.coerceToString(i_pos, vTmp, context,
+ "while evaluating a path segment",
+ false, firstType == nString, !first);
sSize += part->size();
s.emplace_back(std::move(part));
}
@@ -2123,15 +2127,16 @@ std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value &
if (i != v.attrs->end()) {
Value v1;
callFunction(*i->value, v, v1, pos);
- return coerceToString(pos, v1, context, coerceMore, copyToStore,
- "while evaluating the result of the `toString` attribute").toOwned();
+ return coerceToString(pos, v1, context,
+ "while evaluating the result of the `__toString` attribute",
+ coerceMore, copyToStore).toOwned();
}
return {};
}
-BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context,
- bool coerceMore, bool copyToStore, bool canonicalizePath, std::string_view errorCtx)
+BackedStringView EvalState::coerceToString(const PosIdx pos, Value &v, PathSet &context,
+ std::string_view errorCtx, bool coerceMore, bool copyToStore, bool canonicalizePath)
{
forceValue(v, pos);
@@ -2154,13 +2159,23 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
if (maybeString)
return std::move(*maybeString);
auto i = v.attrs->find(sOutPath);
- if (i == v.attrs->end())
- error("cannot coerce a set to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
- return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx);
+ if (i == v.attrs->end()) {
+ error("cannot coerce %1% to a string", showType(v))
+ .withTrace(pos, errorCtx)
+ .debugThrow<TypeError>();
+ }
+ return coerceToString(pos, *i->value, context, errorCtx,
+ coerceMore, copyToStore, canonicalizePath);
}
- if (v.type() == nExternal)
- return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore, errorCtx);
+ if (v.type() == nExternal) {
+ try {
+ return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore);
+ } catch (Error & e) {
+ e.addTrace(nullptr, errorCtx);
+ throw;
+ }
+ }
if (coerceMore) {
/* Note that `false' is represented as an empty string for
@@ -2175,8 +2190,9 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
std::string result;
for (auto [n, v2] : enumerate(v.listItems())) {
try {
- result += *coerceToString(noPos, *v2, context, coerceMore, copyToStore, canonicalizePath,
- "while evaluating one element of the list");
+ result += *coerceToString(noPos, *v2, context,
+ "while evaluating one element of the list",
+ coerceMore, copyToStore, canonicalizePath);
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
@@ -2190,7 +2206,9 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
}
}
- error("cannot coerce %1% to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
+ error("cannot coerce %1% to a string", showType(v))
+ .withTrace(pos, errorCtx)
+ .debugThrow<TypeError>();
}
@@ -2220,7 +2238,7 @@ StorePath EvalState::copyPathToStore(PathSet & context, const Path & path)
Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
{
- auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned();
+ auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
if (path == "" || path[0] != '/')
error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
return path;
@@ -2229,7 +2247,7 @@ Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
{
- auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned();
+ auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
if (auto storePath = store->maybeParseStorePath(path))
return *storePath;
error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
@@ -2433,13 +2451,11 @@ void EvalState::printStats()
}
-std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const
+std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const
{
- auto e = TypeError({
+ throw TypeError({
.msg = hintfmt("cannot coerce %1% to a string", showType())
});
- e.addTrace(pos, errorCtx);
- throw e;
}
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index 46b8cbaa5..e4d5906bd 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -203,6 +203,9 @@ public:
throw std::move(error);
}
+ // This is dangerous, but gets in line with the idea that error creation and
+ // throwing should not allocate on the stack of hot functions.
+ // as long as errors are immediately thrown, it works.
ErrorBuilder * errorBuilder;
template<typename... Args>
@@ -375,9 +378,9 @@ public:
booleans and lists to a string. If `copyToStore' is set,
referenced paths are copied to the Nix store as a side effect. */
BackedStringView coerceToString(const PosIdx pos, Value & v, PathSet & context,
+ std::string_view errorCtx,
bool coerceMore = false, bool copyToStore = true,
- bool canonicalizePath = true,
- std::string_view errorCtx = "");
+ bool canonicalizePath = true);
StorePath copyPathToStore(PathSet & context, const Path & path);
diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc
index fc4be5678..336eb274d 100644
--- a/src/libexpr/flake/flake.cc
+++ b/src/libexpr/flake/flake.cc
@@ -264,7 +264,7 @@ static Flake getFlake(
PathSet emptyContext = {};
flake.config.settings.emplace(
state.symbols[setting.name],
- state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, true, "") .toOwned());
+ state.coerceToString(setting.pos, *setting.value, emptyContext, "", false, true, true) .toOwned());
}
else if (setting.value->type() == nInt)
flake.config.settings.emplace(
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 3b32625b1..8b54c4477 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -350,26 +350,22 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto elems = args[0]->listElems();
auto count = args[0]->listSize();
if (count == 0)
- state.debugThrowLastTrace(EvalError({
- .msg = hintfmt("at least one argument to 'exec' required"),
- .errPos = state.positions[pos]
- }));
+ state.error("at least one argument to 'exec' required").atPos(pos).debugThrow<EvalError>();
PathSet context;
- auto program = state.coerceToString(pos, *elems[0], context, false, false,
- "while evaluating the first element of the argument passed to builtins.exec").toOwned();
+ auto program = state.coerceToString(pos, *elems[0], context,
+ "while evaluating the first element of the argument passed to builtins.exec",
+ false, false).toOwned();
Strings commandArgs;
for (unsigned int i = 1; i < args[0]->listSize(); ++i) {
- commandArgs.push_back(state.coerceToString(pos, *elems[i], context, false, false,
- "while evaluating an element of the argument passed to builtins.exec").toOwned());
+ commandArgs.push_back(
+ state.coerceToString(pos, *elems[i], context,
+ "while evaluating an element of the argument passed to builtins.exec",
+ false, false).toOwned());
}
try {
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
} catch (InvalidPathError & e) {
- state.debugThrowLastTrace(EvalError({
- .msg = hintfmt("cannot execute '%1%', since path '%2%' is not valid",
- program, e.path),
- .errPos = state.positions[pos]
- }));
+ state.error("cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow<EvalError>();
}
auto output = runProgram(program, true, commandArgs);
@@ -598,7 +594,8 @@ struct CompareValues
state.error("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow<EvalError>();
}
} catch (Error & e) {
- e.addTrace(nullptr, errorCtx);
+ if (!errorCtx.empty())
+ e.addTrace(nullptr, errorCtx);
throw;
}
}
@@ -620,15 +617,7 @@ static Bindings::iterator getAttr(
{
Bindings::iterator value = attrSet->find(attrSym);
if (value == attrSet->end()) {
- throw TypeError({
- .msg = hintfmt("attribute '%s' missing %s", state.symbols[attrSym], normaltxt(errorCtx)),
- .errPos = state.positions[attrSet->pos],
- });
- // TODO XXX
- // Adding another trace for the function name to make it clear
- // which call received wrong arguments.
- //e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", funcName));
- //state.debugThrowLastTrace(e);
+ state.error("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow<TypeError>();
}
return value;
}
@@ -801,8 +790,10 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
v = *args[1];
} catch (Error & e) {
PathSet context;
- e.addTrace(nullptr, state.coerceToString(pos, *args[0], context,
- "while evaluating the error message passed to builtins.addErrorContext").toOwned());
+ auto message = state.coerceToString(pos, *args[0], context,
+ "while evaluating the error message passed to builtins.addErrorContext",
+ false, false).toOwned();
+ e.addTrace(nullptr, message, true);
throw;
}
}
@@ -1006,6 +997,7 @@ static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Val
* Derivations
*************************************************************/
+static void derivationStrictInternal(EvalState & state, const std::string & name, Bindings * attrs, Value & v);
/* Construct (as a unobservable side effect) a Nix derivation
expression that performs the derivation described by the argument
@@ -1016,32 +1008,68 @@ static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Val
derivation. */
static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- using nlohmann::json;
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.derivationStrict");
+ Bindings * attrs = args[0]->attrs;
+
/* Figure out the name first (for stack backtraces). */
- Bindings::iterator attr = getAttr(state, state.sName, args[0]->attrs, "in the attrset passed as argument to builtins.derivationStrict");
+ Bindings::iterator nameAttr = getAttr(state, state.sName, attrs, "in the attrset passed as argument to builtins.derivationStrict");
std::string drvName;
- const auto posDrvName = attr->pos;
try {
- drvName = state.forceStringNoCtx(*attr->value, pos, "while evaluating the `name` attribute passed to builtins.derivationStrict");
+ drvName = state.forceStringNoCtx(*nameAttr->value, pos, "while evaluating the `name` attribute passed to builtins.derivationStrict");
+ } catch (Error & e) {
+ e.addTrace(state.positions[nameAttr->pos], "while evaluating the derivation attribute 'name'");
+ throw;
+ }
+
+ try {
+ derivationStrictInternal(state, drvName, attrs, v);
} catch (Error & e) {
- e.addTrace(state.positions[posDrvName], "while evaluating the derivation attribute 'name'");
+ Pos pos = state.positions[nameAttr->pos];
+ /*
+ * Here we make two abuses of the error system
+ *
+ * 1. We print the location as a string to avoid a code snippet being
+ * printed. While the location of the name attribute is a good hint, the
+ * exact code there is irrelevant.
+ *
+ * 2. We mark this trace as a frame trace, meaning that we stop printing
+ * less important traces from now on. In particular, this prevents the
+ * display of the automatic "while calling builtins.derivationStrict"
+ * trace, which is of little use for the public we target here.
+ *
+ * Please keep in mind that error reporting is done on a best-effort
+ * basis in nix. There is no accurate location for a derivation, as it
+ * often results from the composition of several functions
+ * (derivationStrict, derivation, mkDerivation, mkPythonModule, etc.)
+ */
+ e.addTrace(nullptr, hintfmt(
+ "while evaluating derivation '%s'\n"
+ " whose name attribute is located at %s",
+ drvName, pos), true);
throw;
}
+}
+static void derivationStrictInternal(EvalState & state, const std::string &
+drvName, Bindings * attrs, Value & v)
+{
/* Check whether attributes should be passed as a JSON file. */
+ using nlohmann::json;
std::optional<json> jsonObject;
- attr = args[0]->attrs->find(state.sStructuredAttrs);
- if (attr != args[0]->attrs->end() && state.forceBool(*attr->value, pos, "while evaluating the `__structuredAttrs` attribute passed to builtins.derivationStrict"))
+ auto attr = attrs->find(state.sStructuredAttrs);
+ if (attr != attrs->end() &&
+ state.forceBool(*attr->value, noPos,
+ "while evaluating the `__structuredAttrs` "
+ "attribute passed to builtins.derivationStrict"))
jsonObject = json::object();
/* Check whether null attributes should be ignored. */
bool ignoreNulls = false;
- attr = args[0]->attrs->find(state.sIgnoreNulls);
- if (attr != args[0]->attrs->end())
- ignoreNulls = state.forceBool(*attr->value, pos, "while evaluating the `__ignoreNulls` attribute passed to builtins.derivationStrict");
+ attr = attrs->find(state.sIgnoreNulls);
+ if (attr != attrs->end())
+ ignoreNulls = state.forceBool(*attr->value, noPos, "while evaluating the `__ignoreNulls` attribute " "passed to builtins.derivationStrict");
/* Build the derivation expression by processing the attributes. */
Derivation drv;
@@ -1058,7 +1086,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
StringSet outputs;
outputs.insert("out");
- for (auto & i : args[0]->attrs->lexicographicOrder(state.symbols)) {
+ for (auto & i : attrs->lexicographicOrder(state.symbols)) {
if (i->name == state.sIgnoreNulls) continue;
const std::string & key = state.symbols[i->name];
vomit("processing attribute '%1%'", key);
@@ -1069,7 +1097,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
else
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
- .errPos = state.positions[posDrvName]
+ .errPos = state.positions[noPos]
}));
};
@@ -1079,7 +1107,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (outputs.find(j) != outputs.end())
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("duplicate derivation output '%1%'", j),
- .errPos = state.positions[posDrvName]
+ .errPos = state.positions[noPos]
}));
/* !!! Check whether j is a valid attribute
name. */
@@ -1089,34 +1117,35 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (j == "drv")
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid derivation output name 'drv'" ),
- .errPos = state.positions[posDrvName]
+ .errPos = state.positions[noPos]
}));
outputs.insert(j);
}
if (outputs.empty())
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("derivation cannot have an empty set of outputs"),
- .errPos = state.positions[posDrvName]
+ .errPos = state.positions[noPos]
}));
};
try {
+ // This try-catch block adds context for most errors.
+ // Use this empty error context to signify that we defer to it.
+ const std::string_view context_below("");
if (ignoreNulls) {
- state.forceValue(*i->value, pos);
+ state.forceValue(*i->value, noPos);
if (i->value->type() == nNull) continue;
}
if (i->name == state.sContentAddressed) {
- contentAddressed = state.forceBool(*i->value, pos,
- "while evaluating the `__contentAddressed` attribute passed to builtins.derivationStrict");
+ contentAddressed = state.forceBool(*i->value, noPos, context_below);
if (contentAddressed)
settings.requireExperimentalFeature(Xp::CaDerivations);
}
else if (i->name == state.sImpure) {
- isImpure = state.forceBool(*i->value, pos,
- "while evaluating the 'impure' attribute passed to builtins.derivationStrict");
+ isImpure = state.forceBool(*i->value, noPos, context_below);
if (isImpure)
settings.requireExperimentalFeature(Xp::ImpureDerivations);
}
@@ -1124,11 +1153,11 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
/* The `args' attribute is special: it supplies the
command-line arguments to the builder. */
else if (i->name == state.sArgs) {
- state.forceList(*i->value, pos,
- "while evaluating the `args` attribute passed to builtins.derivationStrict");
+ state.forceList(*i->value, noPos, context_below);
for (auto elem : i->value->listItems()) {
- auto s = state.coerceToString(posDrvName, *elem, context, true,
- "while evaluating an element of the `args` argument passed to builtins.derivationStrict").toOwned();
+ auto s = state.coerceToString(noPos, *elem, context,
+ "while evaluating an element of the argument list",
+ true).toOwned();
drv.args.push_back(s);
}
}
@@ -1141,29 +1170,29 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (i->name == state.sStructuredAttrs) continue;
- (*jsonObject)[key] = printValueAsJSON(state, true, *i->value, pos, context);
+ (*jsonObject)[key] = printValueAsJSON(state, true, *i->value, noPos, context);
if (i->name == state.sBuilder)
- drv.builder = state.forceString(*i->value, context, posDrvName, "while evaluating the `builder` attribute passed to builtins.derivationStrict");
+ drv.builder = state.forceString(*i->value, context, noPos, context_below);
else if (i->name == state.sSystem)
- drv.platform = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `system` attribute passed to builtins.derivationStrict");
+ drv.platform = state.forceStringNoCtx(*i->value, noPos, context_below);
else if (i->name == state.sOutputHash)
- outputHash = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHash` attribute passed to builtins.derivationStrict");
+ outputHash = state.forceStringNoCtx(*i->value, noPos, context_below);
else if (i->name == state.sOutputHashAlgo)
- outputHashAlgo = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHashAlgo` attribute passed to builtins.derivationStrict");
+ outputHashAlgo = state.forceStringNoCtx(*i->value, noPos, context_below);
else if (i->name == state.sOutputHashMode)
- handleHashMode(state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHashMode` attribute passed to builtins.derivationStrict"));
+ handleHashMode(state.forceStringNoCtx(*i->value, noPos, context_below));
else if (i->name == state.sOutputs) {
/* Require ‘outputs’ to be a list of strings. */
- state.forceList(*i->value, posDrvName, "while evaluating the `outputs` attribute passed to builtins.derivationStrict");
+ state.forceList(*i->value, noPos, context_below);
Strings ss;
for (auto elem : i->value->listItems())
- ss.emplace_back(state.forceStringNoCtx(*elem, posDrvName, "while evaluating an element of the `outputs` attribute passed to builtins.derivationStrict"));
+ ss.emplace_back(state.forceStringNoCtx(*elem, noPos, context_below));
handleOutputs(ss);
}
} else {
- auto s = state.coerceToString(i->pos, *i->value, context, true, "while evaluating an attribute passed to builtins.derivationStrict").toOwned();
+ auto s = state.coerceToString(noPos, *i->value, context, context_below, true).toOwned();
drv.env.emplace(key, s);
if (i->name == state.sBuilder) drv.builder = std::move(s);
else if (i->name == state.sSystem) drv.platform = std::move(s);
@@ -1177,8 +1206,8 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
}
} catch (Error & e) {
- e.addTrace(nullptr,
- hintfmt("while evaluating the attribute '%1%' of the derivation '%2%'", key, drvName),
+ e.addTrace(state.positions[i->pos],
+ hintfmt("while evaluating attribute '%1%' of derivation '%2%'", key, drvName),
true);
throw;
}
@@ -1223,20 +1252,20 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (drv.builder == "")
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("required attribute 'builder' missing"),
- .errPos = state.positions[posDrvName]
+ .errPos = state.positions[noPos]
}));
if (drv.platform == "")
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("required attribute 'system' missing"),
- .errPos = state.positions[posDrvName]
+ .errPos = state.positions[noPos]
}));
/* Check whether the derivation name is valid. */
if (isDerivation(drvName))
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("derivation names are not allowed to end in '%s'", drvExtension),
- .errPos = state.positions[posDrvName]
+ .errPos = state.positions[noPos]
}));
if (outputHash) {
@@ -1247,7 +1276,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (outputs.size() != 1 || *(outputs.begin()) != "out")
state.debugThrowLastTrace(Error({
.msg = hintfmt("multiple outputs are not supported in fixed-output derivations"),
- .errPos = state.positions[posDrvName]
+ .errPos = state.positions[noPos]
}));
auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo));
@@ -1274,7 +1303,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (contentAddressed && isImpure)
throw EvalError({
.msg = hintfmt("derivation cannot be both content-addressed and impure"),
- .errPos = state.positions[posDrvName]
+ .errPos = state.positions[noPos]
});
auto ht = parseHashTypeOpt(outputHashAlgo).value_or(htSHA256);
@@ -1318,7 +1347,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (!h)
throw AssertionError({
.msg = hintfmt("derivation produced no hash for output '%s'", i),
- .errPos = state.positions[posDrvName],
+ .errPos = state.positions[noPos],
});
auto outPath = state.store->makeOutputPath(i, *h, drvName);
drv.env[i] = state.store->printStorePath(outPath);
@@ -1351,11 +1380,12 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
drvHashes.lock()->insert_or_assign(drvPath, h);
}
- auto attrs = state.buildBindings(1 + drv.outputs.size());
- attrs.alloc(state.sDrvPath).mkString(drvPathS, {"=" + drvPathS});
+ auto result = state.buildBindings(1 + drv.outputs.size());
+ result.alloc(state.sDrvPath).mkString(drvPathS, {"=" + drvPathS});
for (auto & i : drv.outputs)
- mkOutputString(state, attrs, drvPath, drv, i);
- v.mkAttrs(attrs);
+ mkOutputString(state, result, drvPath, drv, i);
+
+ v.mkAttrs(result);
}
static RegisterPrimOp primop_derivationStrict(RegisterPrimOp::Info {
@@ -1498,7 +1528,9 @@ static RegisterPrimOp primop_pathExists({
static void prim_baseNameOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.baseNameOf")), context);
+ v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context,
+ "while evaluating the first argument passed to builtins.baseNameOf",
+ false, false)), context);
}
static RegisterPrimOp primop_baseNameOf({
@@ -1518,7 +1550,9 @@ static RegisterPrimOp primop_baseNameOf({
static void prim_dirOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- auto path = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.dirOf");
+ auto path = state.coerceToString(pos, *args[0], context,
+ "while evaluating the first argument passed to builtins.dirOf",
+ false, false);
auto dir = dirOf(*path);
if (args[0]->type() == nPath) v.mkPath(dir); else v.mkString(dir, context);
}
@@ -1584,8 +1618,9 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
i = getAttr(state, state.sPath, v2->attrs, "in an element of the __nixPath");
PathSet context;
- auto path = state.coerceToString(pos, *i->value, context, false, false,
- "while evaluating the `path` attribute of an element of the list passed to builtins.findFile").toOwned();
+ auto path = state.coerceToString(pos, *i->value, context,
+ "while evaluating the `path` attribute of an element of the list passed to builtins.findFile",
+ false, false).toOwned();
try {
auto rewrites = state.realiseContext(context);
@@ -1638,23 +1673,73 @@ static RegisterPrimOp primop_hashFile({
.fun = prim_hashFile,
});
+
+/* Stringize a directory entry enum. Used by `readFileType' and `readDir'. */
+static const char * dirEntTypeToString(unsigned char dtType)
+{
+ /* Enum DT_(DIR|LNK|REG|UNKNOWN) */
+ switch(dtType) {
+ case DT_REG: return "regular"; break;
+ case DT_DIR: return "directory"; break;
+ case DT_LNK: return "symlink"; break;
+ default: return "unknown"; break;
+ }
+ return "unknown"; /* Unreachable */
+}
+
+
+static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v)
+{
+ auto path = realisePath(state, pos, *args[0]);
+ /* Retrieve the directory entry type and stringize it. */
+ v.mkString(dirEntTypeToString(getFileType(path)));
+}
+
+static RegisterPrimOp primop_readFileType({
+ .name = "__readFileType",
+ .args = {"p"},
+ .doc = R"(
+ Determine the directory entry type of a filesystem node, being
+ one of "directory", "regular", "symlink", or "unknown".
+ )",
+ .fun = prim_readFileType,
+});
+
/* Read a directory (without . or ..) */
static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto path = realisePath(state, pos, *args[0]);
+ // Retrieve directory entries for all nodes in a directory.
+ // This is similar to `getFileType` but is optimized to reduce system calls
+ // on many systems.
DirEntries entries = readDirectory(path);
auto attrs = state.buildBindings(entries.size());
+ // If we hit unknown directory entry types we may need to fallback to
+ // using `getFileType` on some systems.
+ // In order to reduce system calls we make each lookup lazy by using
+ // `builtins.readFileType` application.
+ Value * readFileType = nullptr;
+
for (auto & ent : entries) {
- if (ent.type == DT_UNKNOWN)
- ent.type = getFileType(path + "/" + ent.name);
- attrs.alloc(ent.name).mkString(
- ent.type == DT_REG ? "regular" :
- ent.type == DT_DIR ? "directory" :
- ent.type == DT_LNK ? "symlink" :
- "unknown");
+ auto & attr = attrs.alloc(ent.name);
+ if (ent.type == DT_UNKNOWN) {
+ // Some filesystems or operating systems may not be able to return
+ // detailed node info quickly in this case we produce a thunk to
+ // query the file type lazily.
+ auto epath = state.allocValue();
+ Path path2 = path + "/" + ent.name;
+ epath->mkString(path2);
+ if (!readFileType)
+ readFileType = &state.getBuiltin("readFileType");
+ attr.mkApp(readFileType, epath);
+ } else {
+ // This branch of the conditional is much more likely.
+ // Here we just stringize the directory entry type.
+ attr.mkString(dirEntTypeToString(ent.type));
+ }
}
v.mkAttrs(attrs);
@@ -2634,14 +2719,9 @@ static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * arg
for (unsigned int n = 0; n < listSize; ++n) {
Value * vElem = listElems[n];
- try {
- state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith");
- for (auto & attr : *vElem->attrs)
- attrsSeen[attr.name].first++;
- } catch (TypeError & e) {
- e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "zipAttrsWith"));
- state.debugThrowLastTrace(e);
- }
+ state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith");
+ for (auto & attr : *vElem->attrs)
+ attrsSeen[attr.name].first++;
}
auto attrs = state.buildBindings(attrsSeen.size());
@@ -3025,13 +3105,13 @@ static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Va
auto len = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList");
if (len < 0)
- state.debugThrowLastTrace(EvalError({
- .msg = hintfmt("cannot create list of size %1%", len),
- .errPos = state.positions[pos]
- }));
+ state.error("cannot create list of size %1%", len).debugThrow<EvalError>();
- state.mkList(v, len);
+ // More strict than striclty (!) necessary, but acceptable
+ // as evaluating map without accessing any values makes little sense.
+ state.forceFunction(*args[0], noPos, "while evaluating the first argument passed to builtins.genList");
+ state.mkList(v, len);
for (unsigned int n = 0; n < (unsigned int) len; ++n) {
auto arg = state.allocValue();
arg->mkInt(n);
@@ -3079,6 +3159,8 @@ static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value
auto comparator = [&](Value * a, Value * b) {
/* Optimization: if the comparator is lessThan, bypass
callFunction. */
+ /* TODO: (layus) this is absurd. An optimisation like this
+ should be outside the lambda creation */
if (args[0]->isPrimOp() && args[0]->primOp->fun == prim_lessThan)
return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b);
@@ -3239,12 +3321,7 @@ static void prim_concatMap(EvalState & state, const PosIdx pos, Value * * args,
for (unsigned int n = 0; n < nrLists; ++n) {
Value * vElem = args[1]->listElems()[n];
state.callFunction(*args[0], *vElem, lists[n], pos);
- try {
- state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "while evaluating the return value of the function passed to buitlins.concatMap");
- } catch (TypeError &e) {
- e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "concatMap"));
- state.debugThrowLastTrace(e);
- }
+ state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "while evaluating the return value of the function passed to buitlins.concatMap");
len += lists[n].listSize();
}
@@ -3424,7 +3501,7 @@ static void prim_lessThan(EvalState & state, const PosIdx pos, Value * * args, V
state.forceValue(*args[0], pos);
state.forceValue(*args[1], pos);
// pos is exact here, no need for a message.
- CompareValues comp(state, pos, "");
+ CompareValues comp(state, noPos, "");
v.mkBool(comp(args[0], args[1]));
}
@@ -3451,7 +3528,9 @@ static RegisterPrimOp primop_lessThan({
static void prim_toString(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- auto s = state.coerceToString(pos, *args[0], context, true, false, "while evaluating the first argument passed to builtins.toString");
+ auto s = state.coerceToString(pos, *args[0], context,
+ "while evaluating the first argument passed to builtins.toString",
+ true, false);
v.mkString(*s, context);
}
@@ -3803,21 +3882,18 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.replaceStrings");
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.replaceStrings");
if (args[0]->listSize() != args[1]->listSize())
- state.debugThrowLastTrace(EvalError({
- .msg = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"),
- .errPos = state.positions[pos]
- }));
+ state.error("'from' and 'to' arguments passed to builtins.replaceStrings have different lengths").atPos(pos).debugThrow<EvalError>();
std::vector<std::string> from;
from.reserve(args[0]->listSize());
for (auto elem : args[0]->listItems())
- from.emplace_back(state.forceString(*elem, pos, "while evaluating one of the strings to replace in builtins.replaceStrings"));
+ from.emplace_back(state.forceString(*elem, pos, "while evaluating one of the strings to replace passed to builtins.replaceStrings"));
std::vector<std::pair<std::string, PathSet>> to;
to.reserve(args[1]->listSize());
for (auto elem : args[1]->listItems()) {
PathSet ctx;
- auto s = state.forceString(*elem, ctx, pos, "while evaluating one of the replacement strings of builtins.replaceStrings");
+ auto s = state.forceString(*elem, ctx, pos, "while evaluating one of the replacement strings passed to builtins.replaceStrings");
to.emplace_back(s, std::move(ctx));
}
diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc
index 0c65a6b98..db43e5771 100644
--- a/src/libexpr/primops/context.cc
+++ b/src/libexpr/primops/context.cc
@@ -83,15 +83,13 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
auto contextInfos = std::map<StorePath, ContextInfo>();
for (const auto & p : context) {
- Path drv;
- std::string output;
NixStringContextElem ctx = NixStringContextElem::parse(*state.store, p);
std::visit(overloaded {
[&](NixStringContextElem::DrvDeep & d) {
contextInfos[d.drvPath].allOutputs = true;
},
[&](NixStringContextElem::Built & b) {
- contextInfos[b.drvPath].outputs.emplace_back(std::move(output));
+ contextInfos[b.drvPath].outputs.emplace_back(std::move(b.output));
},
[&](NixStringContextElem::Opaque & o) {
contextInfos[o.path].path = true;
diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc
index c9c93bdba..c41bd60b6 100644
--- a/src/libexpr/primops/fetchMercurial.cc
+++ b/src/libexpr/primops/fetchMercurial.cc
@@ -22,7 +22,9 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
for (auto & attr : *args[0]->attrs) {
std::string_view n(state.symbols[attr.name]);
if (n == "url")
- url = state.coerceToString(attr.pos, *attr.value, context, false, false, "while evaluating the `url` attribute passed to builtins.fetchMercurial").toOwned();
+ url = state.coerceToString(attr.pos, *attr.value, context,
+ "while evaluating the `url` attribute passed to builtins.fetchMercurial",
+ false, false).toOwned();
else if (n == "rev") {
// Ugly: unlike fetchGit, here the "rev" attribute can
// be both a revision or a branch/tag name.
@@ -48,7 +50,9 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
});
} else
- url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.fetchMercurial").toOwned();
+ url = state.coerceToString(pos, *args[0], context,
+ "while evaluating the first argument passed to builtins.fetchMercurial",
+ false, false).toOwned();
// FIXME: git externals probably can be used to bypass the URI
// whitelist. Ah well.
diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc
index 560a086f0..e194462e4 100644
--- a/src/libexpr/primops/fetchTree.cc
+++ b/src/libexpr/primops/fetchTree.cc
@@ -125,7 +125,7 @@ static void fetchTree(
if (attr.name == state.sType) continue;
state.forceValue(*attr.value, attr.pos);
if (attr.value->type() == nPath || attr.value->type() == nString) {
- auto s = state.coerceToString(attr.pos, *attr.value, context, false, false, "").toOwned();
+ auto s = state.coerceToString(attr.pos, *attr.value, context, "", false, false).toOwned();
attrs.emplace(state.symbols[attr.name],
state.symbols[attr.name] == "url"
? type == "git"
@@ -151,7 +151,9 @@ static void fetchTree(
input = fetchers::Input::fromAttrs(std::move(attrs));
} else {
- auto url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to the fetcher").toOwned();
+ auto url = state.coerceToString(pos, *args[0], context,
+ "while evaluating the first argument passed to the fetcher",
+ false, false).toOwned();
if (type == "git") {
fetchers::Attrs attrs;
@@ -218,6 +220,9 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
} else
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
+ if (who == "fetchTarball")
+ url = evalSettings.resolvePseudoUrl(*url);
+
state.checkURI(*url);
if (name == "")
diff --git a/src/libexpr/tests/error_traces.cc b/src/libexpr/tests/error_traces.cc
index 8741ecdd2..5e2213f69 100644
--- a/src/libexpr/tests/error_traces.cc
+++ b/src/libexpr/tests/error_traces.cc
@@ -10,16 +10,71 @@ namespace nix {
// Testing eval of PrimOp's
class ErrorTraceTest : public LibExprTest { };
+ TEST_F(ErrorTraceTest, TraceBuilder) {
+ ASSERT_THROW(
+ state.error("Not much").debugThrow<EvalError>(),
+ EvalError
+ );
+
+ ASSERT_THROW(
+ state.error("Not much").withTrace(noPos, "No more").debugThrow<EvalError>(),
+ EvalError
+ );
+
+ ASSERT_THROW(
+ try {
+ try {
+ state.error("Not much").withTrace(noPos, "No more").debugThrow<EvalError>();
+ } catch (Error & e) {
+ e.addTrace(state.positions[noPos], "Something", "");
+ throw;
+ }
+ } catch (BaseError & e) {
+ ASSERT_EQ(PrintToString(e.info().msg),
+ PrintToString(hintfmt("Not much")));
+ auto trace = e.info().traces.rbegin();
+ ASSERT_EQ(e.info().traces.size(), 2);
+ ASSERT_EQ(PrintToString(trace->hint),
+ PrintToString(hintfmt("No more")));
+ trace++;
+ ASSERT_EQ(PrintToString(trace->hint),
+ PrintToString(hintfmt("Something")));
+ throw;
+ }
+ , EvalError
+ );
+ }
+
+ TEST_F(ErrorTraceTest, NestedThrows) {
+ try {
+ state.error("Not much").withTrace(noPos, "No more").debugThrow<EvalError>();
+ } catch (BaseError & e) {
+ try {
+ state.error("Not much more").debugThrow<EvalError>();
+ } catch (Error & e2) {
+ e.addTrace(state.positions[noPos], "Something", "");
+ //e2.addTrace(state.positions[noPos], "Something", "");
+ ASSERT_TRUE(e.info().traces.size() == 2);
+ ASSERT_TRUE(e2.info().traces.size() == 0);
+ ASSERT_FALSE(&e.info() == &e2.info());
+ }
+ }
+ }
+
#define ASSERT_TRACE1(args, type, message) \
ASSERT_THROW( \
+ std::string expr(args); \
+ std::string name = expr.substr(0, expr.find(" ")); \
try { \
- eval("builtins." args); \
+ Value v = eval("builtins." args); \
+ state.forceValueDeep(v); \
} catch (BaseError & e) { \
ASSERT_EQ(PrintToString(e.info().msg), \
PrintToString(message)); \
+ ASSERT_EQ(e.info().traces.size(), 1) << "while testing " args << std::endl << e.what(); \
auto trace = e.info().traces.rbegin(); \
ASSERT_EQ(PrintToString(trace->hint), \
- PrintToString(hintfmt("while calling the '%s' builtin", "genericClosure"))); \
+ PrintToString(hintfmt("while calling the '%s' builtin", name))); \
throw; \
} \
, type \
@@ -27,39 +82,42 @@ namespace nix {
#define ASSERT_TRACE2(args, type, message, context) \
ASSERT_THROW( \
+ std::string expr(args); \
+ std::string name = expr.substr(0, expr.find(" ")); \
try { \
- eval("builtins." args); \
+ Value v = eval("builtins." args); \
+ state.forceValueDeep(v); \
} catch (BaseError & e) { \
ASSERT_EQ(PrintToString(e.info().msg), \
PrintToString(message)); \
+ ASSERT_EQ(e.info().traces.size(), 2) << "while testing " args << std::endl << e.what(); \
auto trace = e.info().traces.rbegin(); \
ASSERT_EQ(PrintToString(trace->hint), \
PrintToString(context)); \
++trace; \
ASSERT_EQ(PrintToString(trace->hint), \
- PrintToString(hintfmt("while calling the '%s' builtin", "genericClosure"))); \
+ PrintToString(hintfmt("while calling the '%s' builtin", name))); \
throw; \
} \
, type \
)
- TEST_F(ErrorTraceTest, genericClosure) { \
+ TEST_F(ErrorTraceTest, genericClosure) {
ASSERT_TRACE2("genericClosure 1",
TypeError,
hintfmt("value is %s while a set was expected", "an integer"),
hintfmt("while evaluating the first argument passed to builtins.genericClosure"));
- ASSERT_TRACE1("genericClosure {}",
+ ASSERT_TRACE2("genericClosure {}",
TypeError,
- hintfmt("attribute '%s' missing %s", "startSet", normaltxt("in the attrset passed as argument to builtins.genericClosure")));
+ hintfmt("attribute '%s' missing", "startSet"),
+ hintfmt("in the attrset passed as argument to builtins.genericClosure"));
ASSERT_TRACE2("genericClosure { startSet = 1; }",
TypeError,
hintfmt("value is %s while a list was expected", "an integer"),
hintfmt("while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure"));
- // Okay: "genericClosure { startSet = []; }"
-
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = true; }",
TypeError,
hintfmt("value is %s while a function was expected", "a Boolean"),
@@ -68,16 +126,17 @@ namespace nix {
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: true; }",
TypeError,
hintfmt("value is %s while a list was expected", "a Boolean"),
- hintfmt("while evaluating the return value of the `operator` passed to builtins.genericClosure")); // TODO: inconsistent naming
+ hintfmt("while evaluating the return value of the `operator` passed to builtins.genericClosure"));
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [ true ]; }",
TypeError,
hintfmt("value is %s while a set was expected", "a Boolean"),
hintfmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"));
- ASSERT_TRACE1("genericClosure { startSet = [{ key = 1;}]; operator = item: [ {} ]; }",
+ ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [ {} ]; }",
TypeError,
- hintfmt("attribute '%s' missing %s", "key", normaltxt("in one of the attrsets generated by (or initially passed to) builtins.genericClosure")));
+ hintfmt("attribute '%s' missing", "key"),
+ hintfmt("in one of the attrsets generated by (or initially passed to) builtins.genericClosure"));
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [{ key = ''a''; }]; }",
EvalError,
@@ -91,4 +150,1149 @@ namespace nix {
}
+
+ TEST_F(ErrorTraceTest, replaceStrings) {
+ ASSERT_TRACE2("replaceStrings 0 0 {}",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.replaceStrings"));
+
+ ASSERT_TRACE2("replaceStrings [] 0 {}",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "an integer"),
+ hintfmt("while evaluating the second argument passed to builtins.replaceStrings"));
+
+ ASSERT_TRACE1("replaceStrings [ 0 ] [] {}",
+ EvalError,
+ hintfmt("'from' and 'to' arguments passed to builtins.replaceStrings have different lengths"));
+
+ ASSERT_TRACE2("replaceStrings [ 1 ] [ \"new\" ] {}",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "an integer"),
+ hintfmt("while evaluating one of the strings to replace passed to builtins.replaceStrings"));
+
+ ASSERT_TRACE2("replaceStrings [ \"old\" ] [ true ] {}",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "a Boolean"),
+ hintfmt("while evaluating one of the replacement strings passed to builtins.replaceStrings"));
+
+ ASSERT_TRACE2("replaceStrings [ \"old\" ] [ \"new\" ] {}",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "a set"),
+ hintfmt("while evaluating the third argument passed to builtins.replaceStrings"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, scopedImport) {
+ }
+
+
+ TEST_F(ErrorTraceTest, import) {
+ }
+
+
+ TEST_F(ErrorTraceTest, typeOf) {
+ }
+
+
+ TEST_F(ErrorTraceTest, isNull) {
+ }
+
+
+ TEST_F(ErrorTraceTest, isFunction) {
+ }
+
+
+ TEST_F(ErrorTraceTest, isInt) {
+ }
+
+
+ TEST_F(ErrorTraceTest, isFloat) {
+ }
+
+
+ TEST_F(ErrorTraceTest, isString) {
+ }
+
+
+ TEST_F(ErrorTraceTest, isBool) {
+ }
+
+
+ TEST_F(ErrorTraceTest, isPath) {
+ }
+
+
+ TEST_F(ErrorTraceTest, break) {
+ }
+
+
+ TEST_F(ErrorTraceTest, abort) {
+ }
+
+
+ TEST_F(ErrorTraceTest, throw) {
+ }
+
+
+ TEST_F(ErrorTraceTest, addErrorContext) {
+ }
+
+
+ TEST_F(ErrorTraceTest, ceil) {
+ ASSERT_TRACE2("ceil \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a float was expected", "a string"),
+ hintfmt("while evaluating the first argument passed to builtins.ceil"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, floor) {
+ ASSERT_TRACE2("floor \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a float was expected", "a string"),
+ hintfmt("while evaluating the first argument passed to builtins.floor"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, tryEval) {
+ }
+
+
+ TEST_F(ErrorTraceTest, getEnv) {
+ ASSERT_TRACE2("getEnv [ ]",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "a list"),
+ hintfmt("while evaluating the first argument passed to builtins.getEnv"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, seq) {
+ }
+
+
+ TEST_F(ErrorTraceTest, deepSeq) {
+ }
+
+
+ TEST_F(ErrorTraceTest, trace) {
+ }
+
+
+ TEST_F(ErrorTraceTest, placeholder) {
+ ASSERT_TRACE2("placeholder []",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "a list"),
+ hintfmt("while evaluating the first argument passed to builtins.placeholder"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, toPath) {
+ ASSERT_TRACE2("toPath []",
+ TypeError,
+ hintfmt("cannot coerce %s to a string", "a list"),
+ hintfmt("while evaluating the first argument passed to builtins.toPath"));
+
+ ASSERT_TRACE2("toPath \"foo\"",
+ EvalError,
+ hintfmt("string '%s' doesn't represent an absolute path", "foo"),
+ hintfmt("while evaluating the first argument passed to builtins.toPath"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, storePath) {
+ ASSERT_TRACE2("storePath true",
+ TypeError,
+ hintfmt("cannot coerce %s to a string", "a Boolean"),
+ hintfmt("while evaluating the first argument passed to builtins.storePath"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, pathExists) {
+ ASSERT_TRACE2("pathExists []",
+ TypeError,
+ hintfmt("cannot coerce %s to a string", "a list"),
+ hintfmt("while realising the context of a path"));
+
+ ASSERT_TRACE2("pathExists \"zorglub\"",
+ EvalError,
+ hintfmt("string '%s' doesn't represent an absolute path", "zorglub"),
+ hintfmt("while realising the context of a path"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, baseNameOf) {
+ ASSERT_TRACE2("baseNameOf []",
+ TypeError,
+ hintfmt("cannot coerce %s to a string", "a list"),
+ hintfmt("while evaluating the first argument passed to builtins.baseNameOf"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, dirOf) {
+ }
+
+
+ TEST_F(ErrorTraceTest, readFile) {
+ }
+
+
+ TEST_F(ErrorTraceTest, findFile) {
+ }
+
+
+ TEST_F(ErrorTraceTest, hashFile) {
+ }
+
+
+ TEST_F(ErrorTraceTest, readDir) {
+ }
+
+
+ TEST_F(ErrorTraceTest, toXML) {
+ }
+
+
+ TEST_F(ErrorTraceTest, toJSON) {
+ }
+
+
+ TEST_F(ErrorTraceTest, fromJSON) {
+ }
+
+
+ TEST_F(ErrorTraceTest, toFile) {
+ }
+
+
+ TEST_F(ErrorTraceTest, filterSource) {
+ ASSERT_TRACE2("filterSource [] []",
+ TypeError,
+ hintfmt("cannot coerce %s to a string", "a list"),
+ hintfmt("while evaluating the second argument (the path to filter) passed to builtins.filterSource"));
+
+ ASSERT_TRACE2("filterSource [] \"foo\"",
+ EvalError,
+ hintfmt("string '%s' doesn't represent an absolute path", "foo"),
+ hintfmt("while evaluating the second argument (the path to filter) passed to builtins.filterSource"));
+
+ ASSERT_TRACE2("filterSource [] ./.",
+ TypeError,
+ hintfmt("value is %s while a function was expected", "a list"),
+ hintfmt("while evaluating the first argument passed to builtins.filterSource"));
+
+ // Usupported by store "dummy"
+
+ // ASSERT_TRACE2("filterSource (_: 1) ./.",
+ // TypeError,
+ // hintfmt("attempt to call something which is not a function but %s", "an integer"),
+ // hintfmt("while adding path '/home/layus/projects/nix'"));
+
+ // ASSERT_TRACE2("filterSource (_: _: 1) ./.",
+ // TypeError,
+ // hintfmt("value is %s while a Boolean was expected", "an integer"),
+ // hintfmt("while evaluating the return value of the path filter function"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, path) {
+ }
+
+
+ TEST_F(ErrorTraceTest, attrNames) {
+ ASSERT_TRACE2("attrNames []",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "a list"),
+ hintfmt("while evaluating the argument passed to builtins.attrNames"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, attrValues) {
+ ASSERT_TRACE2("attrValues []",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "a list"),
+ hintfmt("while evaluating the argument passed to builtins.attrValues"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, getAttr) {
+ ASSERT_TRACE2("getAttr [] []",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "a list"),
+ hintfmt("while evaluating the first argument passed to builtins.getAttr"));
+
+ ASSERT_TRACE2("getAttr \"foo\" []",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "a list"),
+ hintfmt("while evaluating the second argument passed to builtins.getAttr"));
+
+ ASSERT_TRACE2("getAttr \"foo\" {}",
+ TypeError,
+ hintfmt("attribute '%s' missing", "foo"),
+ hintfmt("in the attribute set under consideration"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, unsafeGetAttrPos) {
+ }
+
+
+ TEST_F(ErrorTraceTest, hasAttr) {
+ ASSERT_TRACE2("hasAttr [] []",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "a list"),
+ hintfmt("while evaluating the first argument passed to builtins.hasAttr"));
+
+ ASSERT_TRACE2("hasAttr \"foo\" []",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "a list"),
+ hintfmt("while evaluating the second argument passed to builtins.hasAttr"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, isAttrs) {
+ }
+
+
+ TEST_F(ErrorTraceTest, removeAttrs) {
+ ASSERT_TRACE2("removeAttrs \"\" \"\"",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "a string"),
+ hintfmt("while evaluating the first argument passed to builtins.removeAttrs"));
+
+ ASSERT_TRACE2("removeAttrs \"\" [ 1 ]",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "a string"),
+ hintfmt("while evaluating the first argument passed to builtins.removeAttrs"));
+
+ ASSERT_TRACE2("removeAttrs \"\" [ \"1\" ]",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "a string"),
+ hintfmt("while evaluating the first argument passed to builtins.removeAttrs"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, listToAttrs) {
+ ASSERT_TRACE2("listToAttrs 1",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "an integer"),
+ hintfmt("while evaluating the argument passed to builtins.listToAttrs"));
+
+ ASSERT_TRACE2("listToAttrs [ 1 ]",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "an integer"),
+ hintfmt("while evaluating an element of the list passed to builtins.listToAttrs"));
+
+ ASSERT_TRACE2("listToAttrs [ {} ]",
+ TypeError,
+ hintfmt("attribute '%s' missing", "name"),
+ hintfmt("in a {name=...; value=...;} pair"));
+
+ ASSERT_TRACE2("listToAttrs [ { name = 1; } ]",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "an integer"),
+ hintfmt("while evaluating the `name` attribute of an element of the list passed to builtins.listToAttrs"));
+
+ ASSERT_TRACE2("listToAttrs [ { name = \"foo\"; } ]",
+ TypeError,
+ hintfmt("attribute '%s' missing", "value"),
+ hintfmt("in a {name=...; value=...;} pair"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, intersectAttrs) {
+ ASSERT_TRACE2("intersectAttrs [] []",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "a list"),
+ hintfmt("while evaluating the first argument passed to builtins.intersectAttrs"));
+
+ ASSERT_TRACE2("intersectAttrs {} []",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "a list"),
+ hintfmt("while evaluating the second argument passed to builtins.intersectAttrs"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, catAttrs) {
+ ASSERT_TRACE2("catAttrs [] {}",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "a list"),
+ hintfmt("while evaluating the first argument passed to builtins.catAttrs"));
+
+ ASSERT_TRACE2("catAttrs \"foo\" {}",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a set"),
+ hintfmt("while evaluating the second argument passed to builtins.catAttrs"));
+
+ ASSERT_TRACE2("catAttrs \"foo\" [ 1 ]",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "an integer"),
+ hintfmt("while evaluating an element in the list passed as second argument to builtins.catAttrs"));
+
+ ASSERT_TRACE2("catAttrs \"foo\" [ { foo = 1; } 1 { bar = 5;} ]",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "an integer"),
+ hintfmt("while evaluating an element in the list passed as second argument to builtins.catAttrs"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, functionArgs) {
+ ASSERT_TRACE1("functionArgs {}",
+ TypeError,
+ hintfmt("'functionArgs' requires a function"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, mapAttrs) {
+ ASSERT_TRACE2("mapAttrs [] []",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "a list"),
+ hintfmt("while evaluating the second argument passed to builtins.mapAttrs"));
+
+ // XXX: defered
+ // ASSERT_TRACE2("mapAttrs \"\" { foo.bar = 1; }",
+ // TypeError,
+ // hintfmt("attempt to call something which is not a function but %s", "a string"),
+ // hintfmt("while evaluating the attribute 'foo'"));
+
+ // ASSERT_TRACE2("mapAttrs (x: x + \"1\") { foo.bar = 1; }",
+ // TypeError,
+ // hintfmt("attempt to call something which is not a function but %s", "a string"),
+ // hintfmt("while evaluating the attribute 'foo'"));
+
+ // ASSERT_TRACE2("mapAttrs (x: y: x + 1) { foo.bar = 1; }",
+ // TypeError,
+ // hintfmt("cannot coerce %s to a string", "an integer"),
+ // hintfmt("while evaluating a path segment"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, zipAttrsWith) {
+ ASSERT_TRACE2("zipAttrsWith [] [ 1 ]",
+ TypeError,
+ hintfmt("value is %s while a function was expected", "a list"),
+ hintfmt("while evaluating the first argument passed to builtins.zipAttrsWith"));
+
+ ASSERT_TRACE2("zipAttrsWith (_: 1) [ 1 ]",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "an integer"),
+ hintfmt("while evaluating a value of the list passed as second argument to builtins.zipAttrsWith"));
+
+ // XXX: How to properly tell that the fucntion takes two arguments ?
+ // The same question also applies to sort, and maybe others.
+ // Due to lazyness, we only create a thunk, and it fails later on.
+ // ASSERT_TRACE2("zipAttrsWith (_: 1) [ { foo = 1; } ]",
+ // TypeError,
+ // hintfmt("attempt to call something which is not a function but %s", "an integer"),
+ // hintfmt("while evaluating the attribute 'foo'"));
+
+ // XXX: Also deferred deeply
+ // ASSERT_TRACE2("zipAttrsWith (a: b: a + b) [ { foo = 1; } { foo = 2; } ]",
+ // TypeError,
+ // hintfmt("cannot coerce %s to a string", "a list"),
+ // hintfmt("while evaluating a path segment"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, isList) {
+ }
+
+
+ TEST_F(ErrorTraceTest, elemAt) {
+ ASSERT_TRACE2("elemAt \"foo\" (-1)",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a string"),
+ hintfmt("while evaluating the first argument passed to builtins.elemAt"));
+
+ ASSERT_TRACE1("elemAt [] (-1)",
+ Error,
+ hintfmt("list index %d is out of bounds", -1));
+
+ ASSERT_TRACE1("elemAt [\"foo\"] 3",
+ Error,
+ hintfmt("list index %d is out of bounds", 3));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, head) {
+ ASSERT_TRACE2("head 1",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.elemAt"));
+
+ ASSERT_TRACE1("head []",
+ Error,
+ hintfmt("list index %d is out of bounds", 0));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, tail) {
+ ASSERT_TRACE2("tail 1",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.tail"));
+
+ ASSERT_TRACE1("tail []",
+ Error,
+ hintfmt("'tail' called on an empty list"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, map) {
+ ASSERT_TRACE2("map 1 \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a string"),
+ hintfmt("while evaluating the second argument passed to builtins.map"));
+
+ ASSERT_TRACE2("map 1 [ 1 ]",
+ TypeError,
+ hintfmt("value is %s while a function was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.map"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, filter) {
+ ASSERT_TRACE2("filter 1 \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a string"),
+ hintfmt("while evaluating the second argument passed to builtins.filter"));
+
+ ASSERT_TRACE2("filter 1 [ \"foo\" ]",
+ TypeError,
+ hintfmt("value is %s while a function was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.filter"));
+
+ ASSERT_TRACE2("filter (_: 5) [ \"foo\" ]",
+ TypeError,
+ hintfmt("value is %s while a Boolean was expected", "an integer"),
+ hintfmt("while evaluating the return value of the filtering function passed to builtins.filter"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, elem) {
+ ASSERT_TRACE2("elem 1 \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a string"),
+ hintfmt("while evaluating the second argument passed to builtins.elem"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, concatLists) {
+ ASSERT_TRACE2("concatLists 1",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.concatLists"));
+
+ ASSERT_TRACE2("concatLists [ 1 ]",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "an integer"),
+ hintfmt("while evaluating a value of the list passed to builtins.concatLists"));
+
+ ASSERT_TRACE2("concatLists [ [1] \"foo\" ]",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a string"),
+ hintfmt("while evaluating a value of the list passed to builtins.concatLists"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, length) {
+ ASSERT_TRACE2("length 1",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.length"));
+
+ ASSERT_TRACE2("length \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a string"),
+ hintfmt("while evaluating the first argument passed to builtins.length"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, foldlPrime) {
+ ASSERT_TRACE2("foldl' 1 \"foo\" true",
+ TypeError,
+ hintfmt("value is %s while a function was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.foldlStrict"));
+
+ ASSERT_TRACE2("foldl' (_: 1) \"foo\" true",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a Boolean"),
+ hintfmt("while evaluating the third argument passed to builtins.foldlStrict"));
+
+ ASSERT_TRACE1("foldl' (_: 1) \"foo\" [ true ]",
+ TypeError,
+ hintfmt("attempt to call something which is not a function but %s", "an integer"));
+
+ ASSERT_TRACE2("foldl' (a: b: a && b) \"foo\" [ true ]",
+ TypeError,
+ hintfmt("value is %s while a Boolean was expected", "a string"),
+ hintfmt("in the left operand of the AND (&&) operator"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, any) {
+ ASSERT_TRACE2("any 1 \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a function was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.any"));
+
+ ASSERT_TRACE2("any (_: 1) \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a string"),
+ hintfmt("while evaluating the second argument passed to builtins.any"));
+
+ ASSERT_TRACE2("any (_: 1) [ \"foo\" ]",
+ TypeError,
+ hintfmt("value is %s while a Boolean was expected", "an integer"),
+ hintfmt("while evaluating the return value of the function passed to builtins.any"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, all) {
+ ASSERT_TRACE2("all 1 \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a function was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.all"));
+
+ ASSERT_TRACE2("all (_: 1) \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a string"),
+ hintfmt("while evaluating the second argument passed to builtins.all"));
+
+ ASSERT_TRACE2("all (_: 1) [ \"foo\" ]",
+ TypeError,
+ hintfmt("value is %s while a Boolean was expected", "an integer"),
+ hintfmt("while evaluating the return value of the function passed to builtins.all"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, genList) {
+ ASSERT_TRACE2("genList 1 \"foo\"",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a string"),
+ hintfmt("while evaluating the second argument passed to builtins.genList"));
+
+ ASSERT_TRACE2("genList 1 2",
+ TypeError,
+ hintfmt("value is %s while a function was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.genList", "an integer"));
+
+ // XXX: defered
+ // ASSERT_TRACE2("genList (x: x + \"foo\") 2 #TODO",
+ // TypeError,
+ // hintfmt("cannot add %s to an integer", "a string"),
+ // hintfmt("while evaluating anonymous lambda"));
+
+ ASSERT_TRACE1("genList false (-3)",
+ EvalError,
+ hintfmt("cannot create list of size %d", -3));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, sort) {
+ ASSERT_TRACE2("sort 1 \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a string"),
+ hintfmt("while evaluating the second argument passed to builtins.sort"));
+
+ ASSERT_TRACE2("sort 1 [ \"foo\" ]",
+ TypeError,
+ hintfmt("value is %s while a function was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.sort"));
+
+ ASSERT_TRACE1("sort (_: 1) [ \"foo\" \"bar\" ]",
+ TypeError,
+ hintfmt("attempt to call something which is not a function but %s", "an integer"));
+
+ ASSERT_TRACE2("sort (_: _: 1) [ \"foo\" \"bar\" ]",
+ TypeError,
+ hintfmt("value is %s while a Boolean was expected", "an integer"),
+ hintfmt("while evaluating the return value of the sorting function passed to builtins.sort"));
+
+ // XXX: Trace too deep, need better asserts
+ // ASSERT_TRACE1("sort (a: b: a <= b) [ \"foo\" {} ] # TODO",
+ // TypeError,
+ // hintfmt("cannot compare %s with %s", "a string", "a set"));
+
+ // ASSERT_TRACE1("sort (a: b: a <= b) [ {} {} ] # TODO",
+ // TypeError,
+ // hintfmt("cannot compare %s with %s; values of that type are incomparable", "a set", "a set"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, partition) {
+ ASSERT_TRACE2("partition 1 \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a function was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.partition"));
+
+ ASSERT_TRACE2("partition (_: 1) \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a string"),
+ hintfmt("while evaluating the second argument passed to builtins.partition"));
+
+ ASSERT_TRACE2("partition (_: 1) [ \"foo\" ]",
+ TypeError,
+ hintfmt("value is %s while a Boolean was expected", "an integer"),
+ hintfmt("while evaluating the return value of the partition function passed to builtins.partition"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, groupBy) {
+ ASSERT_TRACE2("groupBy 1 \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a function was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.groupBy"));
+
+ ASSERT_TRACE2("groupBy (_: 1) \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a string"),
+ hintfmt("while evaluating the second argument passed to builtins.groupBy"));
+
+ ASSERT_TRACE2("groupBy (x: x) [ \"foo\" \"bar\" 1 ]",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "an integer"),
+ hintfmt("while evaluating the return value of the grouping function passed to builtins.groupBy"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, concatMap) {
+ ASSERT_TRACE2("concatMap 1 \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a function was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.concatMap"));
+
+ ASSERT_TRACE2("concatMap (x: 1) \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a string"),
+ hintfmt("while evaluating the second argument passed to builtins.concatMap"));
+
+ ASSERT_TRACE2("concatMap (x: 1) [ \"foo\" ] # TODO",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "an integer"),
+ hintfmt("while evaluating the return value of the function passed to buitlins.concatMap"));
+
+ ASSERT_TRACE2("concatMap (x: \"foo\") [ 1 2 ] # TODO",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a string"),
+ hintfmt("while evaluating the return value of the function passed to buitlins.concatMap"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, add) {
+ ASSERT_TRACE2("add \"foo\" 1",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a string"),
+ hintfmt("while evaluating the first argument of the addition"));
+
+ ASSERT_TRACE2("add 1 \"foo\"",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a string"),
+ hintfmt("while evaluating the second argument of the addition"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, sub) {
+ ASSERT_TRACE2("sub \"foo\" 1",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a string"),
+ hintfmt("while evaluating the first argument of the subtraction"));
+
+ ASSERT_TRACE2("sub 1 \"foo\"",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a string"),
+ hintfmt("while evaluating the second argument of the subtraction"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, mul) {
+ ASSERT_TRACE2("mul \"foo\" 1",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a string"),
+ hintfmt("while evaluating the first argument of the multiplication"));
+
+ ASSERT_TRACE2("mul 1 \"foo\"",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a string"),
+ hintfmt("while evaluating the second argument of the multiplication"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, div) {
+ ASSERT_TRACE2("div \"foo\" 1 # TODO: an integer was expected -> a number",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a string"),
+ hintfmt("while evaluating the first operand of the division"));
+
+ ASSERT_TRACE2("div 1 \"foo\"",
+ TypeError,
+ hintfmt("value is %s while a float was expected", "a string"),
+ hintfmt("while evaluating the second operand of the division"));
+
+ ASSERT_TRACE1("div \"foo\" 0",
+ EvalError,
+ hintfmt("division by zero"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, bitAnd) {
+ ASSERT_TRACE2("bitAnd 1.1 2",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a float"),
+ hintfmt("while evaluating the first argument passed to builtins.bitAnd"));
+
+ ASSERT_TRACE2("bitAnd 1 2.2",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a float"),
+ hintfmt("while evaluating the second argument passed to builtins.bitAnd"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, bitOr) {
+ ASSERT_TRACE2("bitOr 1.1 2",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a float"),
+ hintfmt("while evaluating the first argument passed to builtins.bitOr"));
+
+ ASSERT_TRACE2("bitOr 1 2.2",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a float"),
+ hintfmt("while evaluating the second argument passed to builtins.bitOr"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, bitXor) {
+ ASSERT_TRACE2("bitXor 1.1 2",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a float"),
+ hintfmt("while evaluating the first argument passed to builtins.bitXor"));
+
+ ASSERT_TRACE2("bitXor 1 2.2",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a float"),
+ hintfmt("while evaluating the second argument passed to builtins.bitXor"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, lessThan) {
+ ASSERT_TRACE1("lessThan 1 \"foo\"",
+ EvalError,
+ hintfmt("cannot compare %s with %s", "an integer", "a string"));
+
+ ASSERT_TRACE1("lessThan {} {}",
+ EvalError,
+ hintfmt("cannot compare %s with %s; values of that type are incomparable", "a set", "a set"));
+
+ ASSERT_TRACE2("lessThan [ 1 2 ] [ \"foo\" ]",
+ EvalError,
+ hintfmt("cannot compare %s with %s", "an integer", "a string"),
+ hintfmt("while comparing two list elements"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, toString) {
+ ASSERT_TRACE2("toString { a = 1; }",
+ TypeError,
+ hintfmt("cannot coerce %s to a string", "a set"),
+ hintfmt("while evaluating the first argument passed to builtins.toString"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, substring) {
+ ASSERT_TRACE2("substring {} \"foo\" true",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a set"),
+ hintfmt("while evaluating the first argument (the start offset) passed to builtins.substring"));
+
+ ASSERT_TRACE2("substring 3 \"foo\" true",
+ TypeError,
+ hintfmt("value is %s while an integer was expected", "a string"),
+ hintfmt("while evaluating the second argument (the substring length) passed to builtins.substring"));
+
+ ASSERT_TRACE2("substring 0 3 {}",
+ TypeError,
+ hintfmt("cannot coerce %s to a string", "a set"),
+ hintfmt("while evaluating the third argument (the string) passed to builtins.substring"));
+
+ ASSERT_TRACE1("substring (-3) 3 \"sometext\"",
+ EvalError,
+ hintfmt("negative start position in 'substring'"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, stringLength) {
+ ASSERT_TRACE2("stringLength {} # TODO: context is missing ???",
+ TypeError,
+ hintfmt("cannot coerce %s to a string", "a set"),
+ hintfmt("while evaluating the argument passed to builtins.stringLength"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, hashString) {
+ ASSERT_TRACE2("hashString 1 {}",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.hashString"));
+
+ ASSERT_TRACE1("hashString \"foo\" \"content\"",
+ UsageError,
+ hintfmt("unknown hash algorithm '%s'", "foo"));
+
+ ASSERT_TRACE2("hashString \"sha256\" {}",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "a set"),
+ hintfmt("while evaluating the second argument passed to builtins.hashString"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, match) {
+ ASSERT_TRACE2("match 1 {}",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.match"));
+
+ ASSERT_TRACE2("match \"foo\" {}",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "a set"),
+ hintfmt("while evaluating the second argument passed to builtins.match"));
+
+ ASSERT_TRACE1("match \"(.*\" \"\"",
+ EvalError,
+ hintfmt("invalid regular expression '%s'", "(.*"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, split) {
+ ASSERT_TRACE2("split 1 {}",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.split"));
+
+ ASSERT_TRACE2("split \"foo\" {}",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "a set"),
+ hintfmt("while evaluating the second argument passed to builtins.split"));
+
+ ASSERT_TRACE1("split \"f(o*o\" \"1foo2\"",
+ EvalError,
+ hintfmt("invalid regular expression '%s'", "f(o*o"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, concatStringsSep) {
+ ASSERT_TRACE2("concatStringsSep 1 {}",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "an integer"),
+ hintfmt("while evaluating the first argument (the separator string) passed to builtins.concatStringsSep"));
+
+ ASSERT_TRACE2("concatStringsSep \"foo\" {}",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a set"),
+ hintfmt("while evaluating the second argument (the list of strings to concat) passed to builtins.concatStringsSep"));
+
+ ASSERT_TRACE2("concatStringsSep \"foo\" [ 1 2 {} ] # TODO: coerce to string is buggy",
+ TypeError,
+ hintfmt("cannot coerce %s to a string", "an integer"),
+ hintfmt("while evaluating one element of the list of strings to concat passed to builtins.concatStringsSep"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, parseDrvName) {
+ ASSERT_TRACE2("parseDrvName 1",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.parseDrvName"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, compareVersions) {
+ ASSERT_TRACE2("compareVersions 1 {}",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.compareVersions"));
+
+ ASSERT_TRACE2("compareVersions \"abd\" {}",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "a set"),
+ hintfmt("while evaluating the second argument passed to builtins.compareVersions"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, splitVersion) {
+ ASSERT_TRACE2("splitVersion 1",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.splitVersion"));
+
+ }
+
+
+ TEST_F(ErrorTraceTest, traceVerbose) {
+ }
+
+
+ /* // Needs different ASSERTs
+ TEST_F(ErrorTraceTest, derivationStrict) {
+ ASSERT_TRACE2("derivationStrict \"\"",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "a string"),
+ hintfmt("while evaluating the argument passed to builtins.derivationStrict"));
+
+ ASSERT_TRACE2("derivationStrict {}",
+ TypeError,
+ hintfmt("attribute '%s' missing", "name"),
+ hintfmt("in the attrset passed as argument to builtins.derivationStrict"));
+
+ ASSERT_TRACE2("derivationStrict { name = 1; }",
+ TypeError,
+ hintfmt("value is %s while a string was expected", "an integer"),
+ hintfmt("while evaluating the `name` attribute passed to builtins.derivationStrict"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; }",
+ TypeError,
+ hintfmt("required attribute 'builder' missing"),
+ hintfmt("while evaluating derivation 'foo'"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; __structuredAttrs = 15; }",
+ TypeError,
+ hintfmt("value is %s while a Boolean was expected", "an integer"),
+ hintfmt("while evaluating the `__structuredAttrs` attribute passed to builtins.derivationStrict"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; __ignoreNulls = 15; }",
+ TypeError,
+ hintfmt("value is %s while a Boolean was expected", "an integer"),
+ hintfmt("while evaluating the `__ignoreNulls` attribute passed to builtins.derivationStrict"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; outputHashMode = 15; }",
+ TypeError,
+ hintfmt("invalid value '15' for 'outputHashMode' attribute"),
+ hintfmt("while evaluating the attribute 'outputHashMode' of derivation 'foo'"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; outputHashMode = \"custom\"; }",
+ TypeError,
+ hintfmt("invalid value 'custom' for 'outputHashMode' attribute"),
+ hintfmt("while evaluating the attribute 'outputHashMode' of derivation 'foo'"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = {}; }",
+ TypeError,
+ hintfmt("cannot coerce %s to a string", "a set"),
+ hintfmt("while evaluating the attribute 'system' of derivation 'foo'"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = {}; }",
+ TypeError,
+ hintfmt("cannot coerce %s to a string", "a set"),
+ hintfmt("while evaluating the attribute 'outputs' of derivation 'foo'"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"drv\"; }",
+ TypeError,
+ hintfmt("invalid derivation output name 'drv'"),
+ hintfmt("while evaluating the attribute 'outputs' of derivation 'foo'"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = []; }",
+ TypeError,
+ hintfmt("derivation cannot have an empty set of outputs"),
+ hintfmt("while evaluating the attribute 'outputs' of derivation 'foo'"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = [ \"drv\" ]; }",
+ TypeError,
+ hintfmt("invalid derivation output name 'drv'"),
+ hintfmt("while evaluating the attribute 'outputs' of derivation 'foo'"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = [ \"out\" \"out\" ]; }",
+ TypeError,
+ hintfmt("duplicate derivation output 'out'"),
+ hintfmt("while evaluating the attribute 'outputs' of derivation 'foo'"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; __contentAddressed = \"true\"; }",
+ TypeError,
+ hintfmt("value is %s while a Boolean was expected", "a string"),
+ hintfmt("while evaluating the attribute '__contentAddressed' of derivation 'foo'"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; __impure = \"true\"; }",
+ TypeError,
+ hintfmt("value is %s while a Boolean was expected", "a string"),
+ hintfmt("while evaluating the attribute '__impure' of derivation 'foo'"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; __impure = \"true\"; }",
+ TypeError,
+ hintfmt("value is %s while a Boolean was expected", "a string"),
+ hintfmt("while evaluating the attribute '__impure' of derivation 'foo'"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; args = \"foo\"; }",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a string"),
+ hintfmt("while evaluating the attribute 'args' of derivation 'foo'"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; args = [ {} ]; }",
+ TypeError,
+ hintfmt("cannot coerce %s to a string", "a set"),
+ hintfmt("while evaluating an element of the argument list"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; args = [ \"a\" {} ]; }",
+ TypeError,
+ hintfmt("cannot coerce %s to a string", "a set"),
+ hintfmt("while evaluating an element of the argument list"));
+
+ ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; FOO = {}; }",
+ TypeError,
+ hintfmt("cannot coerce %s to a string", "a set"),
+ hintfmt("while evaluating the attribute 'FOO' of derivation 'foo'"));
+
+ }
+ */
+
} /* namespace nix */
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index 7d3f6d700..508dbe218 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -89,7 +89,7 @@ class ExternalValueBase
/* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
* error.
*/
- virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const;
+ virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const;
/* Compare to another value of the same type. Defaults to uncomparable,
* i.e. always false.
diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh
index 7ee3ded6a..f42c13cdc 100644
--- a/src/libstore/derivations.hh
+++ b/src/libstore/derivations.hh
@@ -13,6 +13,7 @@
namespace nix {
+class Store;
/* Abstract syntax of derivations. */
diff --git a/src/libstore/misc.cc b/src/libstore/misc.cc
index da96dcebc..1c187535d 100644
--- a/src/libstore/misc.cc
+++ b/src/libstore/misc.cc
@@ -319,7 +319,7 @@ OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd,
[&](const OutputsSpec::Names & names) {
return static_cast<std::set<std::string>>(names);
},
- }, bfd.outputs);
+ }, bfd.outputs.raw());
for (auto & output : outputNames) {
auto outputHash = get(outputHashes, output);
if (!outputHash)
diff --git a/src/libstore/outputs-spec.cc b/src/libstore/outputs-spec.cc
index d0f39a854..e26c38138 100644
--- a/src/libstore/outputs-spec.cc
+++ b/src/libstore/outputs-spec.cc
@@ -1,8 +1,10 @@
+#include <regex>
+#include <nlohmann/json.hpp>
+
#include "util.hh"
+#include "regex-combinators.hh"
#include "outputs-spec.hh"
-#include "nlohmann/json.hpp"
-
-#include <regex>
+#include "path-regex.hh"
namespace nix {
@@ -18,10 +20,14 @@ bool OutputsSpec::contains(const std::string & outputName) const
}, raw());
}
+static std::string outputSpecRegexStr =
+ regex::either(
+ regex::group(R"(\*)"),
+ regex::group(regex::list(nameRegexStr)));
std::optional<OutputsSpec> OutputsSpec::parseOpt(std::string_view s)
{
- static std::regex regex(R"((\*)|([a-z]+(,[a-z]+)*))");
+ static std::regex regex(std::string { outputSpecRegexStr });
std::smatch match;
std::string s2 { s }; // until some improves std::regex
@@ -42,7 +48,7 @@ OutputsSpec OutputsSpec::parse(std::string_view s)
{
std::optional spec = parseOpt(s);
if (!spec)
- throw Error("Invalid outputs specifier: '%s'", s);
+ throw Error("invalid outputs specifier '%s'", s);
return *spec;
}
@@ -65,7 +71,7 @@ std::pair<std::string_view, ExtendedOutputsSpec> ExtendedOutputsSpec::parse(std:
{
std::optional spec = parseOpt(s);
if (!spec)
- throw Error("Invalid extended outputs specifier: '%s'", s);
+ throw Error("invalid extended outputs specifier '%s'", s);
return *spec;
}
@@ -163,7 +169,7 @@ void adl_serializer<OutputsSpec>::to_json(json & json, OutputsSpec t) {
[&](const OutputsSpec::Names & names) {
json = names;
},
- }, t);
+ }, t.raw());
}
@@ -183,7 +189,7 @@ void adl_serializer<ExtendedOutputsSpec>::to_json(json & json, ExtendedOutputsSp
[&](const ExtendedOutputsSpec::Explicit & e) {
adl_serializer<OutputsSpec>::to_json(json, e);
},
- }, t);
+ }, t.raw());
}
}
diff --git a/src/libstore/path-regex.hh b/src/libstore/path-regex.hh
new file mode 100644
index 000000000..6893c3876
--- /dev/null
+++ b/src/libstore/path-regex.hh
@@ -0,0 +1,7 @@
+#pragma once
+
+namespace nix {
+
+static constexpr std::string_view nameRegexStr = R"([0-9a-zA-Z\+\-\._\?=]+)";
+
+}
diff --git a/src/libstore/path.cc b/src/libstore/path.cc
index 392db225e..46be54281 100644
--- a/src/libstore/path.cc
+++ b/src/libstore/path.cc
@@ -8,8 +8,10 @@ static void checkName(std::string_view path, std::string_view name)
{
if (name.empty())
throw BadStorePath("store path '%s' has an empty name", path);
- if (name.size() > 211)
- throw BadStorePath("store path '%s' has a name longer than 211 characters", path);
+ if (name.size() > StorePath::MaxPathLen)
+ throw BadStorePath("store path '%s' has a name longer than '%d characters",
+ StorePath::MaxPathLen, path);
+ // See nameRegexStr for the definition
for (auto c : name)
if (!((c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'z')
diff --git a/src/libstore/path.hh b/src/libstore/path.hh
index 7f13c11e9..1e5579b90 100644
--- a/src/libstore/path.hh
+++ b/src/libstore/path.hh
@@ -6,7 +6,6 @@
namespace nix {
-class Store;
struct Hash;
class StorePath
@@ -18,6 +17,8 @@ public:
/* Size of the hash part of store paths, in base-32 characters. */
constexpr static size_t HashLen = 32; // i.e. 160 bits
+ constexpr static size_t MaxPathLen = 211;
+
StorePath() = delete;
StorePath(std::string_view baseName);
diff --git a/src/libstore/realisation.hh b/src/libstore/realisation.hh
index 9429c7004..48d0283de 100644
--- a/src/libstore/realisation.hh
+++ b/src/libstore/realisation.hh
@@ -10,6 +10,8 @@
namespace nix {
+class Store;
+
struct DrvOutput {
// The hash modulo of the derivation
Hash drvHash;
diff --git a/src/libstore/tests/libstoretests.hh b/src/libstore/tests/libstoretests.hh
new file mode 100644
index 000000000..05397659b
--- /dev/null
+++ b/src/libstore/tests/libstoretests.hh
@@ -0,0 +1,23 @@
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+#include "store-api.hh"
+
+namespace nix {
+
+class LibStoreTest : public ::testing::Test {
+ public:
+ static void SetUpTestSuite() {
+ initLibStore();
+ }
+
+ protected:
+ LibStoreTest()
+ : store(openStore("dummy://"))
+ { }
+
+ ref<Store> store;
+};
+
+
+} /* namespace nix */
diff --git a/src/libstore/tests/local.mk b/src/libstore/tests/local.mk
index f74295d97..a2cf8a0cf 100644
--- a/src/libstore/tests/local.mk
+++ b/src/libstore/tests/local.mk
@@ -12,4 +12,4 @@ libstore-tests_CXXFLAGS += -I src/libstore -I src/libutil
libstore-tests_LIBS = libstore libutil
-libstore-tests_LDFLAGS := $(GTEST_LIBS)
+libstore-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS)
diff --git a/src/libstore/tests/outputs-spec.cc b/src/libstore/tests/outputs-spec.cc
index c9c2cafd0..06e4cabbd 100644
--- a/src/libstore/tests/outputs-spec.cc
+++ b/src/libstore/tests/outputs-spec.cc
@@ -40,6 +40,20 @@ TEST(OutputsSpec, names_out) {
ASSERT_EQ(expected.to_string(), str);
}
+TEST(OutputsSpec, names_underscore) {
+ std::string_view str = "a_b";
+ OutputsSpec expected = OutputsSpec::Names { "a_b" };
+ ASSERT_EQ(OutputsSpec::parse(str), expected);
+ ASSERT_EQ(expected.to_string(), str);
+}
+
+TEST(OutputsSpec, names_numberic) {
+ std::string_view str = "01";
+ OutputsSpec expected = OutputsSpec::Names { "01" };
+ ASSERT_EQ(OutputsSpec::parse(str), expected);
+ ASSERT_EQ(expected.to_string(), str);
+}
+
TEST(OutputsSpec, names_out_bin) {
OutputsSpec expected = OutputsSpec::Names { "out", "bin" };
ASSERT_EQ(OutputsSpec::parse("out,bin"), expected);
diff --git a/src/libstore/tests/path.cc b/src/libstore/tests/path.cc
new file mode 100644
index 000000000..8ea252c92
--- /dev/null
+++ b/src/libstore/tests/path.cc
@@ -0,0 +1,144 @@
+#include <regex>
+
+#include <nlohmann/json.hpp>
+#include <gtest/gtest.h>
+#include <rapidcheck/gtest.h>
+
+#include "path-regex.hh"
+#include "store-api.hh"
+
+#include "libstoretests.hh"
+
+namespace nix {
+
+#define STORE_DIR "/nix/store/"
+#define HASH_PART "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q"
+
+class StorePathTest : public LibStoreTest
+{
+};
+
+static std::regex nameRegex { std::string { nameRegexStr } };
+
+#define TEST_DONT_PARSE(NAME, STR) \
+ TEST_F(StorePathTest, bad_ ## NAME) { \
+ std::string_view str = \
+ STORE_DIR HASH_PART "-" STR; \
+ ASSERT_THROW( \
+ store->parseStorePath(str), \
+ BadStorePath); \
+ std::string name { STR }; \
+ EXPECT_FALSE(std::regex_match(name, nameRegex)); \
+ }
+
+TEST_DONT_PARSE(empty, "")
+TEST_DONT_PARSE(garbage, "&*()")
+TEST_DONT_PARSE(double_star, "**")
+TEST_DONT_PARSE(star_first, "*,foo")
+TEST_DONT_PARSE(star_second, "foo,*")
+TEST_DONT_PARSE(bang, "foo!o")
+
+#undef TEST_DONT_PARSE
+
+#define TEST_DO_PARSE(NAME, STR) \
+ TEST_F(StorePathTest, good_ ## NAME) { \
+ std::string_view str = \
+ STORE_DIR HASH_PART "-" STR; \
+ auto p = store->parseStorePath(str); \
+ std::string name { p.name() }; \
+ EXPECT_TRUE(std::regex_match(name, nameRegex)); \
+ }
+
+// 0-9 a-z A-Z + - . _ ? =
+
+TEST_DO_PARSE(numbers, "02345")
+TEST_DO_PARSE(lower_case, "foo")
+TEST_DO_PARSE(upper_case, "FOO")
+TEST_DO_PARSE(plus, "foo+bar")
+TEST_DO_PARSE(dash, "foo-dev")
+TEST_DO_PARSE(underscore, "foo_bar")
+TEST_DO_PARSE(period, "foo.txt")
+TEST_DO_PARSE(question_mark, "foo?why")
+TEST_DO_PARSE(equals_sign, "foo=foo")
+
+#undef TEST_DO_PARSE
+
+// For rapidcheck
+void showValue(const StorePath & p, std::ostream & os) {
+ os << p.to_string();
+}
+
+}
+
+namespace rc {
+using namespace nix;
+
+template<>
+struct Arbitrary<StorePath> {
+ static Gen<StorePath> arbitrary();
+};
+
+Gen<StorePath> Arbitrary<StorePath>::arbitrary()
+{
+ auto len = *gen::inRange<size_t>(1, StorePath::MaxPathLen);
+
+ std::string pre { HASH_PART "-" };
+ pre.reserve(pre.size() + len);
+
+ for (size_t c = 0; c < len; ++c) {
+ switch (auto i = *gen::inRange<uint8_t>(0, 10 + 2 * 26 + 6)) {
+ case 0 ... 9:
+ pre += '0' + i;
+ case 10 ... 35:
+ pre += 'A' + (i - 10);
+ break;
+ case 36 ... 61:
+ pre += 'a' + (i - 36);
+ break;
+ case 62:
+ pre += '+';
+ break;
+ case 63:
+ pre += '-';
+ break;
+ case 64:
+ pre += '.';
+ break;
+ case 65:
+ pre += '_';
+ break;
+ case 66:
+ pre += '?';
+ break;
+ case 67:
+ pre += '=';
+ break;
+ default:
+ assert(false);
+ }
+ }
+
+ return gen::just(StorePath { pre });
+}
+
+} // namespace rc
+
+namespace nix {
+
+RC_GTEST_FIXTURE_PROP(
+ StorePathTest,
+ prop_regex_accept,
+ (const StorePath & p))
+{
+ RC_ASSERT(std::regex_match(std::string { p.name() }, nameRegex));
+}
+
+RC_GTEST_FIXTURE_PROP(
+ StorePathTest,
+ prop_round_rip,
+ (const StorePath & p))
+{
+ RC_ASSERT(p == store->parseStorePath(store->printStorePath(p)));
+}
+
+}
diff --git a/src/libutil/monitor-fd.hh b/src/libutil/monitor-fd.hh
index 5ee0b88ef..9518cf8aa 100644
--- a/src/libutil/monitor-fd.hh
+++ b/src/libutil/monitor-fd.hh
@@ -22,27 +22,38 @@ public:
{
thread = std::thread([fd]() {
while (true) {
- /* Wait indefinitely until a POLLHUP occurs. */
- struct pollfd fds[1];
- fds[0].fd = fd;
- /* This shouldn't be necessary, but macOS doesn't seem to
- like a zeroed out events field.
- See rdar://37537852.
- */
- fds[0].events = POLLHUP;
- auto count = poll(fds, 1, -1);
- if (count == -1) abort(); // can't happen
- /* This shouldn't happen, but can on macOS due to a bug.
- See rdar://37550628.
-
- This may eventually need a delay or further
- coordination with the main thread if spinning proves
- too harmful.
- */
- if (count == 0) continue;
- assert(fds[0].revents & POLLHUP);
- triggerInterrupt();
- break;
+ /* Wait indefinitely until a POLLHUP occurs. */
+ struct pollfd fds[1];
+ fds[0].fd = fd;
+ /* Polling for no specific events (i.e. just waiting
+ for an error/hangup) doesn't work on macOS
+ anymore. So wait for read events and ignore
+ them. */
+ fds[0].events =
+ #ifdef __APPLE__
+ POLLRDNORM
+ #else
+ 0
+ #endif
+ ;
+ auto count = poll(fds, 1, -1);
+ if (count == -1) abort(); // can't happen
+ /* This shouldn't happen, but can on macOS due to a bug.
+ See rdar://37550628.
+
+ This may eventually need a delay or further
+ coordination with the main thread if spinning proves
+ too harmful.
+ */
+ if (count == 0) continue;
+ if (fds[0].revents & POLLHUP) {
+ triggerInterrupt();
+ break;
+ }
+ /* This will only happen on macOS. We sleep a bit to
+ avoid waking up too often if the client is sending
+ input. */
+ sleep(1);
}
});
};
diff --git a/src/libutil/regex-combinators.hh b/src/libutil/regex-combinators.hh
new file mode 100644
index 000000000..0b997b25a
--- /dev/null
+++ b/src/libutil/regex-combinators.hh
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <string_view>
+
+namespace nix::regex {
+
+// TODO use constexpr string building like
+// https://github.com/akrzemi1/static_string/blob/master/include/ak_toolkit/static_string.hpp
+
+static inline std::string either(std::string_view a, std::string_view b)
+{
+ return std::string { a } + "|" + b;
+}
+
+static inline std::string group(std::string_view a)
+{
+ return std::string { "(" } + a + ")";
+}
+
+static inline std::string many(std::string_view a)
+{
+ return std::string { "(?:" } + a + ")*";
+}
+
+static inline std::string list(std::string_view a)
+{
+ return std::string { a } + many(group("," + a));
+}
+
+}
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index d16d88ef8..020c1b182 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -655,6 +655,19 @@ struct CmdFlakeCheck : FlakeCommand
}
}
+ else if (
+ name == "lib"
+ || name == "darwinConfigurations"
+ || name == "darwinModules"
+ || name == "flakeModule"
+ || name == "flakeModules"
+ || name == "herculesCI"
+ || name == "homeConfigurations"
+ || name == "nixopsConfigurations"
+ )
+ // Known but unchecked community attribute
+ ;
+
else
warn("unknown flake output '%s'", name);