#include "tests/libexpr.hh" #include "lix/libexpr/value.hh" #include "lix/libexpr/print.hh" namespace nix { using namespace testing; struct ValuePrintingTests : LibExprTest { template void test(Value v, std::string_view expected, A... args) { std::stringstream out; v.print(state, out, args...); ASSERT_EQ(out.str(), expected); } }; TEST_F(ValuePrintingTests, tInt) { Value vInt; vInt.mkInt(10); test(vInt, "10"); } TEST_F(ValuePrintingTests, tBool) { Value vBool; vBool.mkBool(true); test(vBool, "true"); } TEST_F(ValuePrintingTests, tString) { Value vString; vString.mkString("some-string"); test(vString, "\"some-string\""); } TEST_F(ValuePrintingTests, tPath) { Value vPath; vPath.mkString("/foo"); test(vPath, "\"/foo\""); } TEST_F(ValuePrintingTests, tNull) { Value vNull; vNull.mkNull(); test(vNull, "null"); } TEST_F(ValuePrintingTests, tAttrs) { Value vOne; vOne.mkInt(1); Value vTwo; vTwo.mkInt(2); BindingsBuilder builder(state, state.allocBindings(10)); builder.insert(state.symbols.create("one"), &vOne); builder.insert(state.symbols.create("two"), &vTwo); Value vAttrs; vAttrs.mkAttrs(builder.finish()); test(vAttrs, "{ one = 1; two = 2; }"); } TEST_F(ValuePrintingTests, tList) { Value vOne; vOne.mkInt(1); Value vTwo; vTwo.mkInt(2); Value vList; state.mkList(vList, 5); vList.bigList.elems[0] = &vOne; vList.bigList.elems[1] = &vTwo; vList.bigList.size = 3; test(vList, "[ 1 2 «nullptr» ]"); } TEST_F(ValuePrintingTests, vThunk) { Value vThunk; ExprInt e(0); vThunk.mkThunk(nullptr, e); test(vThunk, "«thunk»"); } TEST_F(ValuePrintingTests, vApp) { Value vApp; vApp.mkApp(nullptr, nullptr); test(vApp, "«thunk»"); } TEST_F(ValuePrintingTests, vLambda) { Env env { .up = nullptr, .values = { } }; PosTable::Origin origin = state.positions.addOrigin(std::monostate(), 1); auto posIdx = state.positions.add(origin, 0); ExprLambda eLambda(posIdx, createSymbol("a"), std::make_unique(), std::make_unique(0)); Value vLambda; vLambda.mkLambda(&env, &eLambda); test(vLambda, "«lambda @ «none»:1:1»"); eLambda.setName(createSymbol("puppy")); test(vLambda, "«lambda puppy @ «none»:1:1»"); } TEST_F(ValuePrintingTests, vPrimOp) { Value vPrimOp; PrimOp primOp{ .name = "puppy" }; vPrimOp.mkPrimOp(&primOp); test(vPrimOp, "«primop puppy»"); } TEST_F(ValuePrintingTests, vPrimOpApp) { PrimOp primOp{ .name = "puppy" }; Value vPrimOp; vPrimOp.mkPrimOp(&primOp); Value vPrimOpApp; vPrimOpApp.mkPrimOpApp(&vPrimOp, nullptr); test(vPrimOpApp, "«partially applied primop puppy»"); } TEST_F(ValuePrintingTests, vExternal) { struct MyExternal : ExternalValueBase { public: std::string showType() const override { return ""; } std::string typeOf() const override { return ""; } virtual std::ostream & print(std::ostream & str) const override { str << "testing-external!"; return str; } } myExternal; Value vExternal; vExternal.mkExternal(&myExternal); test(vExternal, "testing-external!"); } TEST_F(ValuePrintingTests, vFloat) { Value vFloat; vFloat.mkFloat(2.0); test(vFloat, "2"); } TEST_F(ValuePrintingTests, vBlackhole) { Value vBlackhole; vBlackhole.mkBlackhole(); test(vBlackhole, "«potential infinite recursion»"); } TEST_F(ValuePrintingTests, depthAttrs) { Value vZero; vZero.mkInt(0); Value vOne; vOne.mkInt(1); Value vTwo; vTwo.mkInt(2); BindingsBuilder builderEmpty(state, state.allocBindings(0)); Value vAttrsEmpty; vAttrsEmpty.mkAttrs(builderEmpty.finish()); BindingsBuilder builderNested(state, state.allocBindings(1)); builderNested.insert(state.symbols.create("zero"), &vZero); Value vAttrsNested; vAttrsNested.mkAttrs(builderNested.finish()); BindingsBuilder builder(state, state.allocBindings(10)); builder.insert(state.symbols.create("one"), &vOne); builder.insert(state.symbols.create("two"), &vTwo); builder.insert(state.symbols.create("empty"), &vAttrsEmpty); builder.insert(state.symbols.create("nested"), &vAttrsNested); Value vAttrs; vAttrs.mkAttrs(builder.finish()); BindingsBuilder builder2(state, state.allocBindings(10)); builder2.insert(state.symbols.create("one"), &vOne); builder2.insert(state.symbols.create("two"), &vTwo); builder2.insert(state.symbols.create("nested"), &vAttrs); Value vNested; vNested.mkAttrs(builder2.finish()); test(vNested, "{ nested = { ... }; one = 1; two = 2; }", PrintOptions { .maxDepth = 1 }); test(vNested, "{ nested = { empty = { }; nested = { ... }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 2 }); test(vNested, "{ nested = { empty = { }; nested = { zero = 0; }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 3 }); test(vNested, "{ nested = { empty = { }; nested = { zero = 0; }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 4 }); } TEST_F(ValuePrintingTests, depthList) { Value vOne; vOne.mkInt(1); Value vTwo; vTwo.mkInt(2); BindingsBuilder builder(state, state.allocBindings(10)); builder.insert(state.symbols.create("one"), &vOne); builder.insert(state.symbols.create("two"), &vTwo); Value vAttrs; vAttrs.mkAttrs(builder.finish()); BindingsBuilder builder2(state, state.allocBindings(10)); builder2.insert(state.symbols.create("one"), &vOne); builder2.insert(state.symbols.create("two"), &vTwo); builder2.insert(state.symbols.create("nested"), &vAttrs); Value vNested; vNested.mkAttrs(builder2.finish()); Value vList; state.mkList(vList, 5); vList.bigList.elems[0] = &vOne; vList.bigList.elems[1] = &vTwo; vList.bigList.elems[2] = &vNested; vList.bigList.size = 3; test(vList, "[ 1 2 { ... } ]", PrintOptions { .maxDepth = 1 }); test(vList, "[ 1 2 { nested = { ... }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 2 }); test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 3 }); test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 4 }); test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 5 }); } struct StringPrintingTests : LibExprTest { template void test(std::string_view literal, std::string_view expected, unsigned int maxLength, A... args) { Value v; v.mkString(literal); std::stringstream out; printValue(state, out, v, PrintOptions { .maxStringLength = maxLength }); ASSERT_EQ(out.str(), expected); } }; TEST_F(StringPrintingTests, maxLengthTruncation) { test("abcdefghi", "\"abcdefghi\"", 10); test("abcdefghij", "\"abcdefghij\"", 10); test("abcdefghijk", "\"abcdefghij\" «1 byte elided»", 10); test("abcdefghijkl", "\"abcdefghij\" «2 bytes elided»", 10); test("abcdefghijklm", "\"abcdefghij\" «3 bytes elided»", 10); } // Check that printing an attrset shows 'important' attributes like `type` // first, but only reorder the attrs when we have a maxAttrs budget. TEST_F(ValuePrintingTests, attrsTypeFirst) { Value vType; vType.mkString("puppy"); Value vApple; vApple.mkString("apple"); BindingsBuilder builder(state, state.allocBindings(10)); builder.insert(state.symbols.create("type"), &vType); builder.insert(state.symbols.create("apple"), &vApple); Value vAttrs; vAttrs.mkAttrs(builder.finish()); test(vAttrs, "{ type = \"puppy\"; apple = \"apple\"; }", PrintOptions { .maxAttrs = 100 }); test(vAttrs, "{ apple = \"apple\"; type = \"puppy\"; }", PrintOptions { }); } TEST_F(ValuePrintingTests, ansiColorsInt) { Value v; v.mkInt(10); test(v, ANSI_CYAN "10" ANSI_NORMAL, PrintOptions { .ansiColors = true }); } TEST_F(ValuePrintingTests, ansiColorsFloat) { Value v; v.mkFloat(1.6); test(v, ANSI_CYAN "1.6" ANSI_NORMAL, PrintOptions { .ansiColors = true }); } TEST_F(ValuePrintingTests, ansiColorsBool) { Value v; v.mkBool(true); test(v, ANSI_CYAN "true" ANSI_NORMAL, PrintOptions { .ansiColors = true }); } TEST_F(ValuePrintingTests, ansiColorsString) { Value v; v.mkString("puppy"); test(v, ANSI_MAGENTA "\"puppy\"" ANSI_NORMAL, PrintOptions { .ansiColors = true }); } TEST_F(ValuePrintingTests, ansiColorsStringElided) { Value v; v.mkString("puppy"); test(v, ANSI_MAGENTA "\"pup\" " ANSI_FAINT "«2 bytes elided»" ANSI_NORMAL, PrintOptions { .ansiColors = true, .maxStringLength = 3 }); } TEST_F(ValuePrintingTests, ansiColorsPath) { Value v; v.mkPath(state.rootPath(CanonPath("puppy"))); test(v, ANSI_GREEN "/puppy" ANSI_NORMAL, PrintOptions { .ansiColors = true }); } TEST_F(ValuePrintingTests, ansiColorsNull) { Value v; v.mkNull(); test(v, ANSI_CYAN "null" ANSI_NORMAL, PrintOptions { .ansiColors = true }); } TEST_F(ValuePrintingTests, ansiColorsAttrs) { Value vOne; vOne.mkInt(1); Value vTwo; vTwo.mkInt(2); BindingsBuilder builder(state, state.allocBindings(10)); builder.insert(state.symbols.create("one"), &vOne); builder.insert(state.symbols.create("two"), &vTwo); Value vAttrs; vAttrs.mkAttrs(builder.finish()); test(vAttrs, "{ one = " ANSI_CYAN "1" ANSI_NORMAL "; two = " ANSI_CYAN "2" ANSI_NORMAL "; }", PrintOptions { .ansiColors = true }); } TEST_F(ValuePrintingTests, ansiColorsDerivation) { Value vDerivation; vDerivation.mkString("derivation"); BindingsBuilder builder(state, state.allocBindings(10)); builder.insert(state.s.type, &vDerivation); Value vAttrs; vAttrs.mkAttrs(builder.finish()); test(vAttrs, ANSI_GREEN "«derivation»" ANSI_NORMAL, PrintOptions { .ansiColors = true, .force = true, .derivationPaths = true }); test(vAttrs, "{ type = " ANSI_MAGENTA "\"derivation\"" ANSI_NORMAL "; }", PrintOptions { .ansiColors = true, .force = true }); } TEST_F(ValuePrintingTests, ansiColorsError) { Value throw_ = state.getBuiltin("throw"); Value message; message.mkString("uh oh!"); Value vError; vError.mkApp(&throw_, &message); test(vError, ANSI_RED "«error: uh oh!»" ANSI_NORMAL, PrintOptions { .ansiColors = true, .force = true, }); } TEST_F(ValuePrintingTests, ansiColorsDerivationError) { Value throw_ = state.getBuiltin("throw"); Value message; message.mkString("uh oh!"); Value vError; vError.mkApp(&throw_, &message); Value vDerivation; vDerivation.mkString("derivation"); BindingsBuilder builder(state, state.allocBindings(10)); builder.insert(state.s.type, &vDerivation); builder.insert(state.s.drvPath, &vError); Value vAttrs; vAttrs.mkAttrs(builder.finish()); test(vAttrs, "{ drvPath = " ANSI_RED "«error: uh oh!»" ANSI_NORMAL "; type = " ANSI_MAGENTA "\"derivation\"" ANSI_NORMAL "; }", PrintOptions { .ansiColors = true, .force = true }); test(vAttrs, ANSI_RED "«error: uh oh!»" ANSI_NORMAL, PrintOptions { .ansiColors = true, .force = true, .derivationPaths = true, }); } TEST_F(ValuePrintingTests, ansiColorsAssert) { ExprAssert expr(noPos, std::make_unique(state.symbols.create("false")), std::make_unique(1)); expr.bindVars(state, state.staticBaseEnv); Value v; state.mkThunk_(v, expr); test(v, ANSI_RED "«error: assertion 'false' failed»" ANSI_NORMAL, PrintOptions { .ansiColors = true, .force = true }); } TEST_F(ValuePrintingTests, ansiColorsList) { Value vOne; vOne.mkInt(1); Value vTwo; vTwo.mkInt(2); Value vList; state.mkList(vList, 5); vList.bigList.elems[0] = &vOne; vList.bigList.elems[1] = &vTwo; vList.bigList.size = 3; test(vList, "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_CYAN "2" ANSI_NORMAL " " ANSI_MAGENTA "«nullptr»" ANSI_NORMAL " ]", PrintOptions { .ansiColors = true }); } TEST_F(ValuePrintingTests, ansiColorsLambda) { Env env { .up = nullptr, .values = { } }; PosTable::Origin origin = state.positions.addOrigin(std::monostate(), 1); auto posIdx = state.positions.add(origin, 0); ExprLambda eLambda(posIdx, createSymbol("a"), std::make_unique(), std::make_unique(0)); Value vLambda; vLambda.mkLambda(&env, &eLambda); test(vLambda, ANSI_BLUE "«lambda @ «none»:1:1»" ANSI_NORMAL, PrintOptions { .ansiColors = true, .force = true }); eLambda.setName(createSymbol("puppy")); test(vLambda, ANSI_BLUE "«lambda puppy @ «none»:1:1»" ANSI_NORMAL, PrintOptions { .ansiColors = true, .force = true }); } TEST_F(ValuePrintingTests, ansiColorsPrimOp) { PrimOp primOp{ .name = "puppy" }; Value v; v.mkPrimOp(&primOp); test(v, ANSI_BLUE "«primop puppy»" ANSI_NORMAL, PrintOptions { .ansiColors = true }); } TEST_F(ValuePrintingTests, ansiColorsPrimOpApp) { PrimOp primOp{ .name = "puppy" }; Value vPrimOp; vPrimOp.mkPrimOp(&primOp); Value v; v.mkPrimOpApp(&vPrimOp, nullptr); test(v, ANSI_BLUE "«partially applied primop puppy»" ANSI_NORMAL, PrintOptions { .ansiColors = true }); } TEST_F(ValuePrintingTests, ansiColorsThunk) { Value v; ExprInt e(0); v.mkThunk(nullptr, e); test(v, ANSI_MAGENTA "«thunk»" ANSI_NORMAL, PrintOptions { .ansiColors = true }); } TEST_F(ValuePrintingTests, ansiColorsBlackhole) { Value v; v.mkBlackhole(); test(v, ANSI_RED "«potential infinite recursion»" ANSI_NORMAL, PrintOptions { .ansiColors = true }); } TEST_F(ValuePrintingTests, ansiColorsAttrsRepeated) { Value vZero; vZero.mkInt(0); BindingsBuilder innerBuilder(state, state.allocBindings(1)); innerBuilder.insert(state.symbols.create("x"), &vZero); Value vInner; vInner.mkAttrs(innerBuilder.finish()); BindingsBuilder builder(state, state.allocBindings(10)); builder.insert(state.symbols.create("a"), &vInner); builder.insert(state.symbols.create("b"), &vInner); Value vAttrs; vAttrs.mkAttrs(builder.finish()); test(vAttrs, "{ a = { x = " ANSI_CYAN "0" ANSI_NORMAL "; }; b = " ANSI_MAGENTA "«repeated»" ANSI_NORMAL "; }", PrintOptions { .ansiColors = true }); } TEST_F(ValuePrintingTests, ansiColorsListRepeated) { Value vZero; vZero.mkInt(0); BindingsBuilder innerBuilder(state, state.allocBindings(1)); innerBuilder.insert(state.symbols.create("x"), &vZero); Value vInner; vInner.mkAttrs(innerBuilder.finish()); Value vList; state.mkList(vList, 3); vList.bigList.elems[0] = &vInner; vList.bigList.elems[1] = &vInner; vList.bigList.size = 2; test(vList, "[ { x = " ANSI_CYAN "0" ANSI_NORMAL "; } " ANSI_MAGENTA "«repeated»" ANSI_NORMAL " ]", PrintOptions { .ansiColors = true }); } TEST_F(ValuePrintingTests, listRepeated) { Value vZero; vZero.mkInt(0); BindingsBuilder innerBuilder(state, state.allocBindings(1)); innerBuilder.insert(state.symbols.create("x"), &vZero); Value vInner; vInner.mkAttrs(innerBuilder.finish()); Value vList; state.mkList(vList, 3); vList.bigList.elems[0] = &vInner; vList.bigList.elems[1] = &vInner; vList.bigList.size = 2; test(vList, "[ { x = 0; } «repeated» ]", PrintOptions { }); test(vList, "[ { x = 0; } { x = 0; } ]", PrintOptions { .trackRepeated = false }); } TEST_F(ValuePrintingTests, ansiColorsAttrsElided) { Value vOne; vOne.mkInt(1); Value vTwo; vTwo.mkInt(2); BindingsBuilder builder(state, state.allocBindings(10)); builder.insert(state.symbols.create("one"), &vOne); builder.insert(state.symbols.create("two"), &vTwo); Value vAttrs; vAttrs.mkAttrs(builder.finish()); test(vAttrs, "{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«1 attribute elided»" ANSI_NORMAL " }", PrintOptions { .ansiColors = true, .maxAttrs = 1 }); Value vThree; vThree.mkInt(3); builder.insert(state.symbols.create("three"), &vThree); vAttrs.mkAttrs(builder.finish()); test(vAttrs, "{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«2 attributes elided»" ANSI_NORMAL " }", PrintOptions { .ansiColors = true, .maxAttrs = 1 }); } TEST_F(ValuePrintingTests, ansiColorsListElided) { BindingsBuilder emptyBuilder(state, state.allocBindings(1)); Value vOne; vOne.mkInt(1); Value vTwo; vTwo.mkInt(2); Value vList; state.mkList(vList, 4); vList.bigList.elems[0] = &vOne; vList.bigList.elems[1] = &vTwo; vList.bigList.size = 2; test(vList, "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«1 item elided»" ANSI_NORMAL " ]", PrintOptions { .ansiColors = true, .maxListItems = 1 }); Value vThree; vThree.mkInt(3); vList.bigList.elems[2] = &vThree; vList.bigList.size = 3; test(vList, "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«2 items elided»" ANSI_NORMAL " ]", PrintOptions { .ansiColors = true, .maxListItems = 1 }); } } // namespace nix