diff options
Diffstat (limited to 'tests/unit/libexpr/primops.cc')
-rw-r--r-- | tests/unit/libexpr/primops.cc | 832 |
1 files changed, 832 insertions, 0 deletions
diff --git a/tests/unit/libexpr/primops.cc b/tests/unit/libexpr/primops.cc new file mode 100644 index 000000000..ce3b5d11f --- /dev/null +++ b/tests/unit/libexpr/primops.cc @@ -0,0 +1,832 @@ +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#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<CaptureLogger> tempLogger; + public: + CaptureLogging() : tempLogger(std::make_unique<CaptureLogger>()) { + 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<int>(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<int>(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<int>(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<int> 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<int> 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<std::tuple<std::string, std::string_view>> + {}; + + 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(nixVersion)); + } + + TEST_F(PrimOpTest, currentSystem) { + auto v = eval("builtins.currentSystem"); + ASSERT_THAT(v, IsStringEq(settings.thisSystem.get())); + } + + 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<std::string_view> 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<std::tuple<std::string, const int>> + {}; + + 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<std::tuple<std::string, std::string_view, std::string_view>> + {}; + + 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<std::string_view> 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 */ |