#include #include #include "eval-settings.hh" #include "tests/libexpr.hh" namespace nix { class CaptureLogger : public Logger { std::ostringstream oss; public: CaptureLogger() {} std::string get() const { return oss.str(); } void log(Verbosity lvl, std::string_view s) override { oss << s << std::endl; } void logEI(const ErrorInfo & ei) override { showErrorInfo(oss, ei, loggerSettings.showTrace.get()); } }; class CaptureLogging { Logger * oldLogger; std::unique_ptr tempLogger; public: CaptureLogging() : tempLogger(std::make_unique()) { oldLogger = logger; logger = tempLogger.get(); } ~CaptureLogging() { logger = oldLogger; } std::string get() const { return tempLogger->get(); } }; // Testing eval of PrimOp's class PrimOpTest : public LibExprTest {}; TEST_F(PrimOpTest, throw) { ASSERT_THROW(eval("throw \"foo\""), ThrownError); } TEST_F(PrimOpTest, abort) { ASSERT_THROW(eval("abort \"abort\""), Abort); } TEST_F(PrimOpTest, ceil) { auto v = eval("builtins.ceil 1.9"); ASSERT_THAT(v, IsIntEq(2)); } TEST_F(PrimOpTest, floor) { auto v = eval("builtins.floor 1.9"); ASSERT_THAT(v, IsIntEq(1)); } TEST_F(PrimOpTest, tryEvalFailure) { auto v = eval("builtins.tryEval (throw \"\")"); ASSERT_THAT(v, IsAttrsOfSize(2)); auto s = createSymbol("success"); auto p = v.attrs->get(s); ASSERT_NE(p, nullptr); ASSERT_THAT(*p->value, IsFalse()); } TEST_F(PrimOpTest, tryEvalSuccess) { auto v = eval("builtins.tryEval 123"); ASSERT_THAT(v, IsAttrs()); auto s = createSymbol("success"); auto p = v.attrs->get(s); ASSERT_NE(p, nullptr); ASSERT_THAT(*p->value, IsTrue()); s = createSymbol("value"); p = v.attrs->get(s); ASSERT_NE(p, nullptr); ASSERT_THAT(*p->value, IsIntEq(123)); } TEST_F(PrimOpTest, getEnv) { setenv("_NIX_UNIT_TEST_ENV_VALUE", "test value", 1); auto v = eval("builtins.getEnv \"_NIX_UNIT_TEST_ENV_VALUE\""); ASSERT_THAT(v, IsStringEq("test value")); } TEST_F(PrimOpTest, seq) { ASSERT_THROW(eval("let x = throw \"test\"; in builtins.seq x { }"), ThrownError); } TEST_F(PrimOpTest, seqNotDeep) { auto v = eval("let x = { z = throw \"test\"; }; in builtins.seq x { }"); ASSERT_THAT(v, IsAttrs()); } TEST_F(PrimOpTest, deepSeq) { ASSERT_THROW(eval("let x = { z = throw \"test\"; }; in builtins.deepSeq x { }"), ThrownError); } TEST_F(PrimOpTest, trace) { CaptureLogging l; auto v = eval("builtins.trace \"test string 123\" 123"); ASSERT_THAT(v, IsIntEq(123)); auto text = l.get(); ASSERT_NE(text.find("test string 123"), std::string::npos); } TEST_F(PrimOpTest, placeholder) { auto v = eval("builtins.placeholder \"out\""); ASSERT_THAT(v, IsStringEq("/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9")); } TEST_F(PrimOpTest, baseNameOf) { auto v = eval("builtins.baseNameOf /some/path"); ASSERT_THAT(v, IsStringEq("path")); } TEST_F(PrimOpTest, dirOf) { auto v = eval("builtins.dirOf /some/path"); ASSERT_THAT(v, IsPathEq("/some")); } TEST_F(PrimOpTest, attrValues) { auto v = eval("builtins.attrValues { x = \"foo\"; a = 1; }"); ASSERT_THAT(v, IsListOfSize(2)); ASSERT_THAT(*v.listElems()[0], IsIntEq(1)); ASSERT_THAT(*v.listElems()[1], IsStringEq("foo")); } TEST_F(PrimOpTest, getAttr) { auto v = eval("builtins.getAttr \"x\" { x = \"foo\"; }"); ASSERT_THAT(v, IsStringEq("foo")); } TEST_F(PrimOpTest, getAttrNotFound) { // FIXME: TypeError is really bad here, also the error wording is worse // than on Nix <=2.3 ASSERT_THROW(eval("builtins.getAttr \"y\" { }"), TypeError); } TEST_F(PrimOpTest, unsafeGetAttrPos) { // The `y` attribute is at position const char* expr = "builtins.unsafeGetAttrPos \"y\" { y = \"x\"; }"; auto v = eval(expr); ASSERT_THAT(v, IsNull()); } TEST_F(PrimOpTest, hasAttr) { auto v = eval("builtins.hasAttr \"x\" { x = 1; }"); ASSERT_THAT(v, IsTrue()); } TEST_F(PrimOpTest, hasAttrNotFound) { auto v = eval("builtins.hasAttr \"x\" { }"); ASSERT_THAT(v, IsFalse()); } TEST_F(PrimOpTest, isAttrs) { auto v = eval("builtins.isAttrs {}"); ASSERT_THAT(v, IsTrue()); } TEST_F(PrimOpTest, isAttrsFalse) { auto v = eval("builtins.isAttrs null"); ASSERT_THAT(v, IsFalse()); } TEST_F(PrimOpTest, removeAttrs) { auto v = eval("builtins.removeAttrs { x = 1; } [\"x\"]"); ASSERT_THAT(v, IsAttrsOfSize(0)); } TEST_F(PrimOpTest, removeAttrsRetains) { auto v = eval("builtins.removeAttrs { x = 1; y = 2; } [\"x\"]"); ASSERT_THAT(v, IsAttrsOfSize(1)); ASSERT_NE(v.attrs->find(createSymbol("y")), nullptr); } TEST_F(PrimOpTest, listToAttrsEmptyList) { auto v = eval("builtins.listToAttrs []"); ASSERT_THAT(v, IsAttrsOfSize(0)); ASSERT_EQ(v.type(), nAttrs); ASSERT_EQ(v.attrs->size(), 0); } TEST_F(PrimOpTest, listToAttrsNotFieldName) { ASSERT_THROW(eval("builtins.listToAttrs [{}]"), Error); } TEST_F(PrimOpTest, listToAttrs) { auto v = eval("builtins.listToAttrs [ { name = \"key\"; value = 123; } ]"); ASSERT_THAT(v, IsAttrsOfSize(1)); auto key = v.attrs->find(createSymbol("key")); ASSERT_NE(key, nullptr); ASSERT_THAT(*key->value, IsIntEq(123)); } TEST_F(PrimOpTest, intersectAttrs) { auto v = eval("builtins.intersectAttrs { a = 1; b = 2; } { b = 3; c = 4; }"); ASSERT_THAT(v, IsAttrsOfSize(1)); auto b = v.attrs->find(createSymbol("b")); ASSERT_NE(b, nullptr); ASSERT_THAT(*b->value, IsIntEq(3)); } TEST_F(PrimOpTest, catAttrs) { auto v = eval("builtins.catAttrs \"a\" [{a = 1;} {b = 0;} {a = 2;}]"); ASSERT_THAT(v, IsListOfSize(2)); ASSERT_THAT(*v.listElems()[0], IsIntEq(1)); ASSERT_THAT(*v.listElems()[1], IsIntEq(2)); } TEST_F(PrimOpTest, functionArgs) { auto v = eval("builtins.functionArgs ({ x, y ? 123}: 1)"); ASSERT_THAT(v, IsAttrsOfSize(2)); auto x = v.attrs->find(createSymbol("x")); ASSERT_NE(x, nullptr); ASSERT_THAT(*x->value, IsFalse()); auto y = v.attrs->find(createSymbol("y")); ASSERT_NE(y, nullptr); ASSERT_THAT(*y->value, IsTrue()); } TEST_F(PrimOpTest, mapAttrs) { auto v = eval("builtins.mapAttrs (name: value: value * 10) { a = 1; b = 2; }"); ASSERT_THAT(v, IsAttrsOfSize(2)); auto a = v.attrs->find(createSymbol("a")); ASSERT_NE(a, nullptr); ASSERT_THAT(*a->value, IsThunk()); state.forceValue(*a->value, noPos); ASSERT_THAT(*a->value, IsIntEq(10)); auto b = v.attrs->find(createSymbol("b")); ASSERT_NE(b, nullptr); ASSERT_THAT(*b->value, IsThunk()); state.forceValue(*b->value, noPos); ASSERT_THAT(*b->value, IsIntEq(20)); } TEST_F(PrimOpTest, isList) { auto v = eval("builtins.isList []"); ASSERT_THAT(v, IsTrue()); } TEST_F(PrimOpTest, isListFalse) { auto v = eval("builtins.isList null"); ASSERT_THAT(v, IsFalse()); } TEST_F(PrimOpTest, elemtAt) { auto v = eval("builtins.elemAt [0 1 2 3] 3"); ASSERT_THAT(v, IsIntEq(3)); } TEST_F(PrimOpTest, elemtAtOutOfBounds) { ASSERT_THROW(eval("builtins.elemAt [0 1 2 3] 5"), Error); } TEST_F(PrimOpTest, head) { auto v = eval("builtins.head [ 3 2 1 0 ]"); ASSERT_THAT(v, IsIntEq(3)); } TEST_F(PrimOpTest, headEmpty) { ASSERT_THROW(eval("builtins.head [ ]"), Error); } TEST_F(PrimOpTest, headWrongType) { ASSERT_THROW(eval("builtins.head { }"), Error); } TEST_F(PrimOpTest, tail) { auto v = eval("builtins.tail [ 3 2 1 0 ]"); ASSERT_THAT(v, IsListOfSize(3)); for (const auto [n, elem] : enumerate(v.listItems())) ASSERT_THAT(*elem, IsIntEq(2 - static_cast(n))); } TEST_F(PrimOpTest, tailEmpty) { ASSERT_THROW(eval("builtins.tail []"), Error); } TEST_F(PrimOpTest, map) { auto v = eval("map (x: \"foo\" + x) [ \"bar\" \"bla\" \"abc\" ]"); ASSERT_THAT(v, IsListOfSize(3)); auto elem = v.listElems()[0]; ASSERT_THAT(*elem, IsThunk()); state.forceValue(*elem, noPos); ASSERT_THAT(*elem, IsStringEq("foobar")); elem = v.listElems()[1]; ASSERT_THAT(*elem, IsThunk()); state.forceValue(*elem, noPos); ASSERT_THAT(*elem, IsStringEq("foobla")); elem = v.listElems()[2]; ASSERT_THAT(*elem, IsThunk()); state.forceValue(*elem, noPos); ASSERT_THAT(*elem, IsStringEq("fooabc")); } TEST_F(PrimOpTest, filter) { auto v = eval("builtins.filter (x: x == 2) [ 3 2 3 2 3 2 ]"); ASSERT_THAT(v, IsListOfSize(3)); for (const auto elem : v.listItems()) ASSERT_THAT(*elem, IsIntEq(2)); } TEST_F(PrimOpTest, elemTrue) { auto v = eval("builtins.elem 3 [ 1 2 3 4 5 ]"); ASSERT_THAT(v, IsTrue()); } TEST_F(PrimOpTest, elemFalse) { auto v = eval("builtins.elem 6 [ 1 2 3 4 5 ]"); ASSERT_THAT(v, IsFalse()); } TEST_F(PrimOpTest, concatLists) { auto v = eval("builtins.concatLists [[1 2] [3 4]]"); ASSERT_THAT(v, IsListOfSize(4)); for (const auto [i, elem] : enumerate(v.listItems())) ASSERT_THAT(*elem, IsIntEq(static_cast(i)+1)); } TEST_F(PrimOpTest, length) { auto v = eval("builtins.length [ 1 2 3 ]"); ASSERT_THAT(v, IsIntEq(3)); } TEST_F(PrimOpTest, foldStrict) { auto v = eval("builtins.foldl' (a: b: a + b) 0 [1 2 3]"); ASSERT_THAT(v, IsIntEq(6)); } TEST_F(PrimOpTest, anyTrue) { auto v = eval("builtins.any (x: x == 2) [ 1 2 3 ]"); ASSERT_THAT(v, IsTrue()); } TEST_F(PrimOpTest, anyFalse) { auto v = eval("builtins.any (x: x == 5) [ 1 2 3 ]"); ASSERT_THAT(v, IsFalse()); } TEST_F(PrimOpTest, allTrue) { auto v = eval("builtins.all (x: x > 0) [ 1 2 3 ]"); ASSERT_THAT(v, IsTrue()); } TEST_F(PrimOpTest, allFalse) { auto v = eval("builtins.all (x: x <= 0) [ 1 2 3 ]"); ASSERT_THAT(v, IsFalse()); } TEST_F(PrimOpTest, genList) { auto v = eval("builtins.genList (x: x + 1) 3"); ASSERT_EQ(v.type(), nList); ASSERT_EQ(v.listSize(), 3); for (const auto [i, elem] : enumerate(v.listItems())) { ASSERT_THAT(*elem, IsThunk()); state.forceValue(*elem, noPos); ASSERT_THAT(*elem, IsIntEq(static_cast(i)+1)); } } TEST_F(PrimOpTest, sortLessThan) { auto v = eval("builtins.sort builtins.lessThan [ 483 249 526 147 42 77 ]"); ASSERT_EQ(v.type(), nList); ASSERT_EQ(v.listSize(), 6); const std::vector numbers = { 42, 77, 147, 249, 483, 526 }; for (const auto [n, elem] : enumerate(v.listItems())) ASSERT_THAT(*elem, IsIntEq(numbers[n])); } TEST_F(PrimOpTest, partition) { auto v = eval("builtins.partition (x: x > 10) [1 23 9 3 42]"); ASSERT_THAT(v, IsAttrsOfSize(2)); auto right = v.attrs->get(createSymbol("right")); ASSERT_NE(right, nullptr); ASSERT_THAT(*right->value, IsListOfSize(2)); ASSERT_THAT(*right->value->listElems()[0], IsIntEq(23)); ASSERT_THAT(*right->value->listElems()[1], IsIntEq(42)); auto wrong = v.attrs->get(createSymbol("wrong")); ASSERT_NE(wrong, nullptr); ASSERT_EQ(wrong->value->type(), nList); ASSERT_EQ(wrong->value->listSize(), 3); ASSERT_THAT(*wrong->value, IsListOfSize(3)); ASSERT_THAT(*wrong->value->listElems()[0], IsIntEq(1)); ASSERT_THAT(*wrong->value->listElems()[1], IsIntEq(9)); ASSERT_THAT(*wrong->value->listElems()[2], IsIntEq(3)); } TEST_F(PrimOpTest, concatMap) { auto v = eval("builtins.concatMap (x: x ++ [0]) [ [1 2] [3 4] ]"); ASSERT_EQ(v.type(), nList); ASSERT_EQ(v.listSize(), 6); const std::vector numbers = { 1, 2, 0, 3, 4, 0 }; for (const auto [n, elem] : enumerate(v.listItems())) ASSERT_THAT(*elem, IsIntEq(numbers[n])); } TEST_F(PrimOpTest, addInt) { auto v = eval("builtins.add 3 5"); ASSERT_THAT(v, IsIntEq(8)); } TEST_F(PrimOpTest, addFloat) { auto v = eval("builtins.add 3.0 5.0"); ASSERT_THAT(v, IsFloatEq(8.0)); } TEST_F(PrimOpTest, addFloatToInt) { auto v = eval("builtins.add 3.0 5"); ASSERT_THAT(v, IsFloatEq(8.0)); v = eval("builtins.add 3 5.0"); ASSERT_THAT(v, IsFloatEq(8.0)); } TEST_F(PrimOpTest, subInt) { auto v = eval("builtins.sub 5 2"); ASSERT_THAT(v, IsIntEq(3)); } TEST_F(PrimOpTest, subFloat) { auto v = eval("builtins.sub 5.0 2.0"); ASSERT_THAT(v, IsFloatEq(3.0)); } TEST_F(PrimOpTest, subFloatFromInt) { auto v = eval("builtins.sub 5.0 2"); ASSERT_THAT(v, IsFloatEq(3.0)); v = eval("builtins.sub 4 2.0"); ASSERT_THAT(v, IsFloatEq(2.0)); } TEST_F(PrimOpTest, mulInt) { auto v = eval("builtins.mul 3 5"); ASSERT_THAT(v, IsIntEq(15)); } TEST_F(PrimOpTest, mulFloat) { auto v = eval("builtins.mul 3.0 5.0"); ASSERT_THAT(v, IsFloatEq(15.0)); } TEST_F(PrimOpTest, mulFloatMixed) { auto v = eval("builtins.mul 3 5.0"); ASSERT_THAT(v, IsFloatEq(15.0)); v = eval("builtins.mul 2.0 5"); ASSERT_THAT(v, IsFloatEq(10.0)); } TEST_F(PrimOpTest, divInt) { auto v = eval("builtins.div 5 (-1)"); ASSERT_THAT(v, IsIntEq(-5)); } TEST_F(PrimOpTest, divIntZero) { ASSERT_THROW(eval("builtins.div 5 0"), EvalError); } TEST_F(PrimOpTest, divFloat) { auto v = eval("builtins.div 5.0 (-1)"); ASSERT_THAT(v, IsFloatEq(-5.0)); } TEST_F(PrimOpTest, divFloatZero) { ASSERT_THROW(eval("builtins.div 5.0 0.0"), EvalError); } TEST_F(PrimOpTest, bitOr) { auto v = eval("builtins.bitOr 1 2"); ASSERT_THAT(v, IsIntEq(3)); } TEST_F(PrimOpTest, bitXor) { auto v = eval("builtins.bitXor 3 2"); ASSERT_THAT(v, IsIntEq(1)); } TEST_F(PrimOpTest, lessThanFalse) { auto v = eval("builtins.lessThan 3 1"); ASSERT_THAT(v, IsFalse()); } TEST_F(PrimOpTest, lessThanTrue) { auto v = eval("builtins.lessThan 1 3"); ASSERT_THAT(v, IsTrue()); } TEST_F(PrimOpTest, toStringAttrsThrows) { ASSERT_THROW(eval("builtins.toString {}"), EvalError); } TEST_F(PrimOpTest, toStringLambdaThrows) { ASSERT_THROW(eval("builtins.toString (x: x)"), EvalError); } class ToStringPrimOpTest : public PrimOpTest, public testing::WithParamInterface> {}; TEST_P(ToStringPrimOpTest, toString) { const auto [input, output] = GetParam(); auto v = eval(input); ASSERT_THAT(v, IsStringEq(output)); } #define CASE(input, output) (std::make_tuple(std::string_view("builtins.toString " input), std::string_view(output))) INSTANTIATE_TEST_SUITE_P( toString, ToStringPrimOpTest, testing::Values( CASE(R"("foo")", "foo"), CASE(R"(1)", "1"), CASE(R"([1 2 3])", "1 2 3"), CASE(R"(.123)", "0.123000"), CASE(R"(true)", "1"), CASE(R"(false)", ""), CASE(R"(null)", ""), CASE(R"({ v = "bar"; __toString = self: self.v; })", "bar"), CASE(R"({ v = "bar"; __toString = self: self.v; outPath = "foo"; })", "bar"), CASE(R"({ outPath = "foo"; })", "foo"), CASE(R"(./test)", "/test") ) ); #undef CASE TEST_F(PrimOpTest, substring){ auto v = eval("builtins.substring 0 3 \"nixos\""); ASSERT_THAT(v, IsStringEq("nix")); } TEST_F(PrimOpTest, substringSmallerString){ auto v = eval("builtins.substring 0 3 \"n\""); ASSERT_THAT(v, IsStringEq("n")); } TEST_F(PrimOpTest, substringEmptyString){ auto v = eval("builtins.substring 1 3 \"\""); ASSERT_THAT(v, IsStringEq("")); } TEST_F(PrimOpTest, stringLength) { auto v = eval("builtins.stringLength \"123\""); ASSERT_THAT(v, IsIntEq(3)); } TEST_F(PrimOpTest, hashStringMd5) { auto v = eval("builtins.hashString \"md5\" \"asdf\""); ASSERT_THAT(v, IsStringEq("912ec803b2ce49e4a541068d495ab570")); } TEST_F(PrimOpTest, hashStringSha1) { auto v = eval("builtins.hashString \"sha1\" \"asdf\""); ASSERT_THAT(v, IsStringEq("3da541559918a808c2402bba5012f6c60b27661c")); } TEST_F(PrimOpTest, hashStringSha256) { auto v = eval("builtins.hashString \"sha256\" \"asdf\""); ASSERT_THAT(v, IsStringEq("f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b")); } TEST_F(PrimOpTest, hashStringSha512) { auto v = eval("builtins.hashString \"sha512\" \"asdf\""); ASSERT_THAT(v, IsStringEq("401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1")); } TEST_F(PrimOpTest, hashStringInvalidHashType) { ASSERT_THROW(eval("builtins.hashString \"foobar\" \"asdf\""), Error); } TEST_F(PrimOpTest, nixPath) { auto v = eval("builtins.nixPath"); ASSERT_EQ(v.type(), nList); // We can't test much more as currently the EvalSettings are a global // that we can't easily swap / replace } TEST_F(PrimOpTest, langVersion) { auto v = eval("builtins.langVersion"); ASSERT_EQ(v.type(), nInt); } TEST_F(PrimOpTest, storeDir) { auto v = eval("builtins.storeDir"); ASSERT_THAT(v, IsStringEq(settings.nixStore)); } TEST_F(PrimOpTest, nixVersion) { auto v = eval("builtins.nixVersion"); ASSERT_THAT(v, IsStringEq("2.18.3-lix")); } TEST_F(PrimOpTest, currentSystem) { auto v = eval("builtins.currentSystem"); ASSERT_THAT(v, IsStringEq(evalSettings.getCurrentSystem())); } TEST_F(PrimOpTest, derivation) { auto v = eval("derivation"); ASSERT_EQ(v.type(), nFunction); ASSERT_TRUE(v.isLambda()); ASSERT_NE(v.lambda.fun, nullptr); ASSERT_TRUE(v.lambda.fun->hasFormals()); } TEST_F(PrimOpTest, currentTime) { auto v = eval("builtins.currentTime"); ASSERT_EQ(v.type(), nInt); ASSERT_TRUE(v.integer > 0); } TEST_F(PrimOpTest, splitVersion) { auto v = eval("builtins.splitVersion \"1.2.3git\""); ASSERT_THAT(v, IsListOfSize(4)); const std::vector strings = { "1", "2", "3", "git" }; for (const auto [n, p] : enumerate(v.listItems())) ASSERT_THAT(*p, IsStringEq(strings[n])); } class CompareVersionsPrimOpTest : public PrimOpTest, public testing::WithParamInterface> {}; TEST_P(CompareVersionsPrimOpTest, compareVersions) { auto [expression, expectation] = GetParam(); auto v = eval(expression); ASSERT_THAT(v, IsIntEq(expectation)); } #define CASE(a, b, expected) (std::make_tuple("builtins.compareVersions \"" #a "\" \"" #b "\"", expected)) INSTANTIATE_TEST_SUITE_P( compareVersions, CompareVersionsPrimOpTest, testing::Values( // The first two are weird cases. Intuition tells they should // be the same but they aren't. CASE(1.0, 1.0.0, -1), CASE(1.0.0, 1.0, 1), // the following are from the nix-env manual: CASE(1.0, 2.3, -1), CASE(2.1, 2.3, -1), CASE(2.3, 2.3, 0), CASE(2.5, 2.3, 1), CASE(3.1, 2.3, 1), CASE(2.3.1, 2.3, 1), CASE(2.3.1, 2.3a, 1), CASE(2.3pre1, 2.3, -1), CASE(2.3pre3, 2.3pre12, -1), CASE(2.3a, 2.3c, -1), CASE(2.3pre1, 2.3c, -1), CASE(2.3pre1, 2.3q, -1) ) ); #undef CASE class ParseDrvNamePrimOpTest : public PrimOpTest, public testing::WithParamInterface> {}; TEST_P(ParseDrvNamePrimOpTest, parseDrvName) { auto [input, expectedName, expectedVersion] = GetParam(); const auto expr = fmt("builtins.parseDrvName \"%1%\"", input); auto v = eval(expr); ASSERT_THAT(v, IsAttrsOfSize(2)); auto name = v.attrs->find(createSymbol("name")); ASSERT_TRUE(name); ASSERT_THAT(*name->value, IsStringEq(expectedName)); auto version = v.attrs->find(createSymbol("version")); ASSERT_TRUE(version); ASSERT_THAT(*version->value, IsStringEq(expectedVersion)); } INSTANTIATE_TEST_SUITE_P( parseDrvName, ParseDrvNamePrimOpTest, testing::Values( std::make_tuple("nix-0.12pre12876", "nix", "0.12pre12876"), std::make_tuple("a-b-c-1234pre5+git", "a-b-c", "1234pre5+git") ) ); TEST_F(PrimOpTest, replaceStrings) { // FIXME: add a test that verifies the string context is as expected auto v = eval("builtins.replaceStrings [\"oo\" \"a\"] [\"a\" \"i\"] \"foobar\""); ASSERT_EQ(v.type(), nString); ASSERT_EQ(v.string.s, std::string_view("fabir")); } TEST_F(PrimOpTest, concatStringsSep) { // FIXME: add a test that verifies the string context is as expected auto v = eval("builtins.concatStringsSep \"%\" [\"foo\" \"bar\" \"baz\"]"); ASSERT_EQ(v.type(), nString); ASSERT_EQ(std::string_view(v.string.s), "foo%bar%baz"); } TEST_F(PrimOpTest, split1) { // v = [ "" [ "a" ] "c" ] auto v = eval("builtins.split \"(a)b\" \"abc\""); ASSERT_THAT(v, IsListOfSize(3)); ASSERT_THAT(*v.listElems()[0], IsStringEq("")); ASSERT_THAT(*v.listElems()[1], IsListOfSize(1)); ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a")); ASSERT_THAT(*v.listElems()[2], IsStringEq("c")); } TEST_F(PrimOpTest, split2) { // v is expected to be a list [ "" [ "a" ] "b" [ "c"] "" ] auto v = eval("builtins.split \"([ac])\" \"abc\""); ASSERT_THAT(v, IsListOfSize(5)); ASSERT_THAT(*v.listElems()[0], IsStringEq("")); ASSERT_THAT(*v.listElems()[1], IsListOfSize(1)); ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a")); ASSERT_THAT(*v.listElems()[2], IsStringEq("b")); ASSERT_THAT(*v.listElems()[3], IsListOfSize(1)); ASSERT_THAT(*v.listElems()[3]->listElems()[0], IsStringEq("c")); ASSERT_THAT(*v.listElems()[4], IsStringEq("")); } TEST_F(PrimOpTest, split3) { auto v = eval("builtins.split \"(a)|(c)\" \"abc\""); ASSERT_THAT(v, IsListOfSize(5)); // First list element ASSERT_THAT(*v.listElems()[0], IsStringEq("")); // 2nd list element is a list [ "" null ] ASSERT_THAT(*v.listElems()[1], IsListOfSize(2)); ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a")); ASSERT_THAT(*v.listElems()[1]->listElems()[1], IsNull()); // 3rd element ASSERT_THAT(*v.listElems()[2], IsStringEq("b")); // 4th element is a list: [ null "c" ] ASSERT_THAT(*v.listElems()[3], IsListOfSize(2)); ASSERT_THAT(*v.listElems()[3]->listElems()[0], IsNull()); ASSERT_THAT(*v.listElems()[3]->listElems()[1], IsStringEq("c")); // 5th element is the empty string ASSERT_THAT(*v.listElems()[4], IsStringEq("")); } TEST_F(PrimOpTest, split4) { auto v = eval("builtins.split \"([[:upper:]]+)\" \" FOO \""); ASSERT_THAT(v, IsListOfSize(3)); auto first = v.listElems()[0]; auto second = v.listElems()[1]; auto third = v.listElems()[2]; ASSERT_THAT(*first, IsStringEq(" ")); ASSERT_THAT(*second, IsListOfSize(1)); ASSERT_THAT(*second->listElems()[0], IsStringEq("FOO")); ASSERT_THAT(*third, IsStringEq(" ")); } TEST_F(PrimOpTest, match1) { auto v = eval("builtins.match \"ab\" \"abc\""); ASSERT_THAT(v, IsNull()); } TEST_F(PrimOpTest, match2) { auto v = eval("builtins.match \"abc\" \"abc\""); ASSERT_THAT(v, IsListOfSize(0)); } TEST_F(PrimOpTest, match3) { auto v = eval("builtins.match \"a(b)(c)\" \"abc\""); ASSERT_THAT(v, IsListOfSize(2)); ASSERT_THAT(*v.listElems()[0], IsStringEq("b")); ASSERT_THAT(*v.listElems()[1], IsStringEq("c")); } TEST_F(PrimOpTest, match4) { auto v = eval("builtins.match \"[[:space:]]+([[:upper:]]+)[[:space:]]+\" \" FOO \""); ASSERT_THAT(v, IsListOfSize(1)); ASSERT_THAT(*v.listElems()[0], IsStringEq("FOO")); } TEST_F(PrimOpTest, attrNames) { auto v = eval("builtins.attrNames { x = 1; y = 2; z = 3; a = 2; }"); ASSERT_THAT(v, IsListOfSize(4)); // ensure that the list is sorted const std::vector expected { "a", "x", "y", "z" }; for (const auto [n, elem] : enumerate(v.listItems())) ASSERT_THAT(*elem, IsStringEq(expected[n])); } TEST_F(PrimOpTest, genericClosure_not_strict) { // Operator should not be used when startSet is empty auto v = eval("builtins.genericClosure { startSet = []; }"); ASSERT_THAT(v, IsListOfSize(0)); } } /* namespace nix */