Add unit tests for libexpr (#5377)
* libexpr: fix builtins.split example The example was previously indicating that multiple whitespaces would be collapsed into a single captured whitespace. That isn't true and was likely a mistake when being documented initially. * Fix segfault on unitilized list when printing value Since lists are just chunks of memory the individual elements in the list might be unitilized when a programming error happens within Nix. In this case the values are null-initialized (at least with Boehm GC) and we can avoid a nullptr deref when printing them. I ran into this issue while ensuring that new expression tests would show the actual value on an assertion failure. This is unlikely to cause any runtime performance regressions as printing values is not really in the hot path (unless the repl is the primary use case). * Add operator<< for ValueTypes * Add libexpr tests This introduces tests for libexpr that evalulate various trivial Nix language expressions and primop invocations that should be good smoke tests wheter or not the implementation is behaving as expected.
This commit is contained in:
parent
b470218d9a
commit
059ae7f6c4
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -35,6 +35,7 @@ perl/Makefile.config
|
||||||
/src/libexpr/parser-tab.hh
|
/src/libexpr/parser-tab.hh
|
||||||
/src/libexpr/parser-tab.output
|
/src/libexpr/parser-tab.output
|
||||||
/src/libexpr/nix.tbl
|
/src/libexpr/nix.tbl
|
||||||
|
/src/libexpr/tests/libexpr-tests
|
||||||
|
|
||||||
# /src/libstore/
|
# /src/libstore/
|
||||||
*.gen.*
|
*.gen.*
|
||||||
|
|
1
Makefile
1
Makefile
|
@ -8,6 +8,7 @@ makefiles = \
|
||||||
src/libfetchers/local.mk \
|
src/libfetchers/local.mk \
|
||||||
src/libmain/local.mk \
|
src/libmain/local.mk \
|
||||||
src/libexpr/local.mk \
|
src/libexpr/local.mk \
|
||||||
|
src/libexpr/tests/local.mk \
|
||||||
src/libcmd/local.mk \
|
src/libcmd/local.mk \
|
||||||
src/nix/local.mk \
|
src/nix/local.mk \
|
||||||
src/resolve-system-dependencies/local.mk \
|
src/resolve-system-dependencies/local.mk \
|
||||||
|
|
|
@ -147,7 +147,10 @@ void Value::print(const SymbolTable & symbols, std::ostream & str,
|
||||||
else {
|
else {
|
||||||
str << "[ ";
|
str << "[ ";
|
||||||
for (auto v2 : listItems()) {
|
for (auto v2 : listItems()) {
|
||||||
|
if (v2)
|
||||||
v2->print(symbols, str, seen);
|
v2->print(symbols, str, seen);
|
||||||
|
else
|
||||||
|
str << "(nullptr)";
|
||||||
str << " ";
|
str << " ";
|
||||||
}
|
}
|
||||||
str << "]";
|
str << "]";
|
||||||
|
@ -184,6 +187,11 @@ void Value::print(const SymbolTable & symbols, std::ostream & str, bool showRepe
|
||||||
print(symbols, str, showRepeated ? nullptr : &seen);
|
print(symbols, str, showRepeated ? nullptr : &seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pretty print types for assertion errors
|
||||||
|
std::ostream & operator << (std::ostream & os, const ValueType t) {
|
||||||
|
os << showType(t);
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
std::string printValue(const EvalState & state, const Value & v)
|
std::string printValue(const EvalState & state, const Value & v)
|
||||||
{
|
{
|
||||||
|
|
|
@ -55,6 +55,7 @@ typedef std::map<Path, StorePath> SrcToStore;
|
||||||
|
|
||||||
std::ostream & printValue(const EvalState & state, std::ostream & str, const Value & v);
|
std::ostream & printValue(const EvalState & state, std::ostream & str, const Value & v);
|
||||||
std::string printValue(const EvalState & state, const Value & v);
|
std::string printValue(const EvalState & state, const Value & v);
|
||||||
|
std::ostream & operator << (std::ostream & os, const ValueType t);
|
||||||
|
|
||||||
|
|
||||||
typedef std::pair<std::string, std::string> SearchPathElem;
|
typedef std::pair<std::string, std::string> SearchPathElem;
|
||||||
|
|
68
src/libexpr/tests/json.cc
Normal file
68
src/libexpr/tests/json.cc
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
#include "libexprtests.hh"
|
||||||
|
#include "value-to-json.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
// Testing the conversion to JSON
|
||||||
|
|
||||||
|
class JSONValueTest : public LibExprTest {
|
||||||
|
protected:
|
||||||
|
std::string getJSONValue(Value& value) {
|
||||||
|
std::stringstream ss;
|
||||||
|
PathSet ps;
|
||||||
|
printValueAsJSON(state, true, value, noPos, ss, ps);
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(JSONValueTest, null) {
|
||||||
|
Value v;
|
||||||
|
v.mkNull();
|
||||||
|
ASSERT_EQ(getJSONValue(v), "null");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(JSONValueTest, BoolFalse) {
|
||||||
|
Value v;
|
||||||
|
v.mkBool(false);
|
||||||
|
ASSERT_EQ(getJSONValue(v),"false");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(JSONValueTest, BoolTrue) {
|
||||||
|
Value v;
|
||||||
|
v.mkBool(true);
|
||||||
|
ASSERT_EQ(getJSONValue(v), "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(JSONValueTest, IntPositive) {
|
||||||
|
Value v;
|
||||||
|
v.mkInt(100);
|
||||||
|
ASSERT_EQ(getJSONValue(v), "100");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(JSONValueTest, IntNegative) {
|
||||||
|
Value v;
|
||||||
|
v.mkInt(-100);
|
||||||
|
ASSERT_EQ(getJSONValue(v), "-100");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(JSONValueTest, String) {
|
||||||
|
Value v;
|
||||||
|
v.mkString("test");
|
||||||
|
ASSERT_EQ(getJSONValue(v), "\"test\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(JSONValueTest, StringQuotes) {
|
||||||
|
Value v;
|
||||||
|
|
||||||
|
v.mkString("test\"");
|
||||||
|
ASSERT_EQ(getJSONValue(v), "\"test\\\"\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The dummy store doesn't support writing files. Fails with this exception message:
|
||||||
|
// C++ exception with description "error: operation 'addToStoreFromDump' is
|
||||||
|
// not supported by store 'dummy'" thrown in the test body.
|
||||||
|
TEST_F(JSONValueTest, DISABLED_Path) {
|
||||||
|
Value v;
|
||||||
|
v.mkPath("test");
|
||||||
|
ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\"");
|
||||||
|
}
|
||||||
|
} /* namespace nix */
|
136
src/libexpr/tests/libexprtests.hh
Normal file
136
src/libexpr/tests/libexprtests.hh
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
#include "value.hh"
|
||||||
|
#include "nixexpr.hh"
|
||||||
|
#include "eval.hh"
|
||||||
|
#include "eval-inline.hh"
|
||||||
|
#include "store-api.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
class LibExprTest : public ::testing::Test {
|
||||||
|
public:
|
||||||
|
static void SetUpTestSuite() {
|
||||||
|
initGC();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
LibExprTest()
|
||||||
|
: store(openStore("dummy://"))
|
||||||
|
, state({}, store)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
Value eval(std::string input, bool forceValue = true) {
|
||||||
|
Value v;
|
||||||
|
Expr * e = state.parseExprFromString(input, "");
|
||||||
|
assert(e);
|
||||||
|
state.eval(e, v);
|
||||||
|
if (forceValue)
|
||||||
|
state.forceValue(v, noPos);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
Symbol createSymbol(const char * value) {
|
||||||
|
return state.symbols.create(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ref<Store> store;
|
||||||
|
EvalState state;
|
||||||
|
};
|
||||||
|
|
||||||
|
MATCHER(IsListType, "") {
|
||||||
|
return arg != nList;
|
||||||
|
}
|
||||||
|
|
||||||
|
MATCHER(IsList, "") {
|
||||||
|
return arg.type() == nList;
|
||||||
|
}
|
||||||
|
|
||||||
|
MATCHER(IsString, "") {
|
||||||
|
return arg.type() == nString;
|
||||||
|
}
|
||||||
|
|
||||||
|
MATCHER(IsNull, "") {
|
||||||
|
return arg.type() == nNull;
|
||||||
|
}
|
||||||
|
|
||||||
|
MATCHER(IsThunk, "") {
|
||||||
|
return arg.type() == nThunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
MATCHER(IsAttrs, "") {
|
||||||
|
return arg.type() == nAttrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
MATCHER_P(IsStringEq, s, fmt("The string is equal to \"%1%\"", s)) {
|
||||||
|
if (arg.type() != nString) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return std::string_view(arg.string.s) == s;
|
||||||
|
}
|
||||||
|
|
||||||
|
MATCHER_P(IsIntEq, v, fmt("The string is equal to \"%1%\"", v)) {
|
||||||
|
if (arg.type() != nInt) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return arg.integer == v;
|
||||||
|
}
|
||||||
|
|
||||||
|
MATCHER_P(IsFloatEq, v, fmt("The float is equal to \"%1%\"", v)) {
|
||||||
|
if (arg.type() != nFloat) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return arg.fpoint == v;
|
||||||
|
}
|
||||||
|
|
||||||
|
MATCHER(IsTrue, "") {
|
||||||
|
if (arg.type() != nBool) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return arg.boolean == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MATCHER(IsFalse, "") {
|
||||||
|
if (arg.type() != nBool) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return arg.boolean == false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MATCHER_P(IsPathEq, p, fmt("Is a path equal to \"%1%\"", p)) {
|
||||||
|
if (arg.type() != nPath) {
|
||||||
|
*result_listener << "Expected a path got " << arg.type();
|
||||||
|
return false;
|
||||||
|
} else if (std::string_view(arg.string.s) != p) {
|
||||||
|
*result_listener << "Expected a path that equals \"" << p << "\" but got: " << arg.string.s;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MATCHER_P(IsListOfSize, n, fmt("Is a list of size [%1%]", n)) {
|
||||||
|
if (arg.type() != nList) {
|
||||||
|
*result_listener << "Expected list got " << arg.type();
|
||||||
|
return false;
|
||||||
|
} else if (arg.listSize() != (size_t)n) {
|
||||||
|
*result_listener << "Expected as list of size " << n << " got " << arg.listSize();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MATCHER_P(IsAttrsOfSize, n, fmt("Is a set of size [%1%]", n)) {
|
||||||
|
if (arg.type() != nAttrs) {
|
||||||
|
*result_listener << "Expexted set got " << arg.type();
|
||||||
|
return false;
|
||||||
|
} else if (arg.attrs->size() != (size_t)n) {
|
||||||
|
*result_listener << "Expected a set with " << n << " attributes but got " << arg.attrs->size();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} /* namespace nix */
|
15
src/libexpr/tests/local.mk
Normal file
15
src/libexpr/tests/local.mk
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
check: libexpr-tests_RUN
|
||||||
|
|
||||||
|
programs += libexpr-tests
|
||||||
|
|
||||||
|
libexpr-tests_DIR := $(d)
|
||||||
|
|
||||||
|
libexpr-tests_INSTALL_DIR :=
|
||||||
|
|
||||||
|
libexpr-tests_SOURCES := $(wildcard $(d)/*.cc)
|
||||||
|
|
||||||
|
libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests
|
||||||
|
|
||||||
|
libexpr-tests_LIBS = libexpr libutil libstore
|
||||||
|
|
||||||
|
libexpr-tests_LDFLAGS := $(GTEST_LIBS) -lgmock
|
839
src/libexpr/tests/primops.cc
Normal file
839
src/libexpr/tests/primops.cc
Normal file
|
@ -0,0 +1,839 @@
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "libexprtests.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
class CaptureLogger : public Logger
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CaptureLogger() {}
|
||||||
|
|
||||||
|
std::string get() const {
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void log(Verbosity lvl, const FormatOrString & fs) override {
|
||||||
|
oss << fs.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, IsAttrsOfSize(3));
|
||||||
|
|
||||||
|
auto file = v.attrs->find(createSymbol("file"));
|
||||||
|
ASSERT_NE(file, nullptr);
|
||||||
|
// FIXME: The file when running these tests is the input string?!?
|
||||||
|
ASSERT_THAT(*file->value, IsStringEq(expr));
|
||||||
|
|
||||||
|
auto line = v.attrs->find(createSymbol("line"));
|
||||||
|
ASSERT_NE(line, nullptr);
|
||||||
|
ASSERT_THAT(*line->value, IsIntEq(1));
|
||||||
|
|
||||||
|
auto column = v.attrs->find(createSymbol("column"));
|
||||||
|
ASSERT_NE(column, nullptr);
|
||||||
|
ASSERT_THAT(*column->value, IsIntEq(33));
|
||||||
|
}
|
||||||
|
|
||||||
|
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("foo", "foo"),
|
||||||
|
CASE(1, "1"),
|
||||||
|
CASE([1 2 3], "1 2 3"),
|
||||||
|
CASE(.123, "0.123000"),
|
||||||
|
CASE(true, "1"),
|
||||||
|
CASE(false, ""),
|
||||||
|
CASE(null, ""),
|
||||||
|
CASE({ v = "bar"; __toString = self: self.v; }, "bar"),
|
||||||
|
CASE({ v = "bar"; __toString = self: self.v; outPath = "foo"; }, "bar"),
|
||||||
|
CASE({ outPath = "foo"; }, "foo"),
|
||||||
|
CASE(./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("/nix/store"));
|
||||||
|
}
|
||||||
|
|
||||||
|
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]));
|
||||||
|
}
|
||||||
|
} /* namespace nix */
|
196
src/libexpr/tests/trivial.cc
Normal file
196
src/libexpr/tests/trivial.cc
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
#include "libexprtests.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
// Testing of trivial expressions
|
||||||
|
class TrivialExpressionTest : public LibExprTest {};
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, true) {
|
||||||
|
auto v = eval("true");
|
||||||
|
ASSERT_THAT(v, IsTrue());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, false) {
|
||||||
|
auto v = eval("false");
|
||||||
|
ASSERT_THAT(v, IsFalse());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, null) {
|
||||||
|
auto v = eval("null");
|
||||||
|
ASSERT_THAT(v, IsNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, 1) {
|
||||||
|
auto v = eval("1");
|
||||||
|
ASSERT_THAT(v, IsIntEq(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, 1plus1) {
|
||||||
|
auto v = eval("1+1");
|
||||||
|
ASSERT_THAT(v, IsIntEq(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, minus1) {
|
||||||
|
auto v = eval("-1");
|
||||||
|
ASSERT_THAT(v, IsIntEq(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, 1minus1) {
|
||||||
|
auto v = eval("1-1");
|
||||||
|
ASSERT_THAT(v, IsIntEq(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, lambdaAdd) {
|
||||||
|
auto v = eval("let add = a: b: a + b; in add 1 2");
|
||||||
|
ASSERT_THAT(v, IsIntEq(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, list) {
|
||||||
|
auto v = eval("[]");
|
||||||
|
ASSERT_THAT(v, IsListOfSize(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, attrs) {
|
||||||
|
auto v = eval("{}");
|
||||||
|
ASSERT_THAT(v, IsAttrsOfSize(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, float) {
|
||||||
|
auto v = eval("1.234");
|
||||||
|
ASSERT_THAT(v, IsFloatEq(1.234));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, updateAttrs) {
|
||||||
|
auto v = eval("{ a = 1; } // { b = 2; a = 3; }");
|
||||||
|
ASSERT_THAT(v, IsAttrsOfSize(2));
|
||||||
|
auto a = v.attrs->find(createSymbol("a"));
|
||||||
|
ASSERT_NE(a, nullptr);
|
||||||
|
ASSERT_THAT(*a->value, IsIntEq(3));
|
||||||
|
|
||||||
|
auto b = v.attrs->find(createSymbol("b"));
|
||||||
|
ASSERT_NE(b, nullptr);
|
||||||
|
ASSERT_THAT(*b->value, IsIntEq(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, hasAttrOpFalse) {
|
||||||
|
auto v = eval("{} ? a");
|
||||||
|
ASSERT_THAT(v, IsFalse());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, hasAttrOpTrue) {
|
||||||
|
auto v = eval("{ a = 123; } ? a");
|
||||||
|
ASSERT_THAT(v, IsTrue());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, withFound) {
|
||||||
|
auto v = eval("with { a = 23; }; a");
|
||||||
|
ASSERT_THAT(v, IsIntEq(23));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, withNotFound) {
|
||||||
|
ASSERT_THROW(eval("with {}; a"), Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, withOverride) {
|
||||||
|
auto v = eval("with { a = 23; }; with { a = 42; }; a");
|
||||||
|
ASSERT_THAT(v, IsIntEq(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, letOverWith) {
|
||||||
|
auto v = eval("let a = 23; in with { a = 1; }; a");
|
||||||
|
ASSERT_THAT(v, IsIntEq(23));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, multipleLet) {
|
||||||
|
auto v = eval("let a = 23; in let a = 42; in a");
|
||||||
|
ASSERT_THAT(v, IsIntEq(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, defaultFunctionArgs) {
|
||||||
|
auto v = eval("({ a ? 123 }: a) {}");
|
||||||
|
ASSERT_THAT(v, IsIntEq(123));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, defaultFunctionArgsOverride) {
|
||||||
|
auto v = eval("({ a ? 123 }: a) { a = 5; }");
|
||||||
|
ASSERT_THAT(v, IsIntEq(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, defaultFunctionArgsCaptureBack) {
|
||||||
|
auto v = eval("({ a ? 123 }@args: args) {}");
|
||||||
|
ASSERT_THAT(v, IsAttrsOfSize(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, defaultFunctionArgsCaptureFront) {
|
||||||
|
auto v = eval("(args@{ a ? 123 }: args) {}");
|
||||||
|
ASSERT_THAT(v, IsAttrsOfSize(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, assertThrows) {
|
||||||
|
ASSERT_THROW(eval("let x = arg: assert arg == 1; 123; in x 2"), Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, assertPassed) {
|
||||||
|
auto v = eval("let x = arg: assert arg == 1; 123; in x 1");
|
||||||
|
ASSERT_THAT(v, IsIntEq(123));
|
||||||
|
}
|
||||||
|
|
||||||
|
class AttrSetMergeTrvialExpressionTest :
|
||||||
|
public TrivialExpressionTest,
|
||||||
|
public testing::WithParamInterface<const char*>
|
||||||
|
{};
|
||||||
|
|
||||||
|
TEST_P(AttrSetMergeTrvialExpressionTest, attrsetMergeLazy) {
|
||||||
|
// Usually Nix rejects duplicate keys in an attrset but it does allow
|
||||||
|
// so if it is an attribute set that contains disjoint sets of keys.
|
||||||
|
// The below is equivalent to `{a.b = 1; a.c = 2; }`.
|
||||||
|
// The attribute set `a` will be a Thunk at first as the attribuets
|
||||||
|
// have to be merged (or otherwise computed) and that is done in a lazy
|
||||||
|
// manner.
|
||||||
|
|
||||||
|
auto expr = GetParam();
|
||||||
|
auto v = eval(expr);
|
||||||
|
ASSERT_THAT(v, IsAttrsOfSize(1));
|
||||||
|
|
||||||
|
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, IsAttrsOfSize(2));
|
||||||
|
|
||||||
|
auto b = a->value->attrs->find(createSymbol("b"));
|
||||||
|
ASSERT_NE(b, nullptr);
|
||||||
|
ASSERT_THAT(*b->value, IsIntEq(1));
|
||||||
|
|
||||||
|
auto c = a->value->attrs->find(createSymbol("c"));
|
||||||
|
ASSERT_NE(c, nullptr);
|
||||||
|
ASSERT_THAT(*c->value, IsIntEq(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_SUITE_P(
|
||||||
|
attrsetMergeLazy,
|
||||||
|
AttrSetMergeTrvialExpressionTest,
|
||||||
|
testing::Values(
|
||||||
|
"{ a.b = 1; a.c = 2; }",
|
||||||
|
"{ a = { b = 1; }; a = { c = 2; }; }"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, functor) {
|
||||||
|
auto v = eval("{ __functor = self: arg: self.v + arg; v = 10; } 5");
|
||||||
|
ASSERT_THAT(v, IsIntEq(15));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, bindOr) {
|
||||||
|
auto v = eval("{ or = 1; }");
|
||||||
|
ASSERT_THAT(v, IsAttrsOfSize(1));
|
||||||
|
auto b = v.attrs->find(createSymbol("or"));
|
||||||
|
ASSERT_NE(b, nullptr);
|
||||||
|
ASSERT_THAT(*b->value, IsIntEq(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, orCantBeUsed) {
|
||||||
|
ASSERT_THROW(eval("let or = 1; in or"), Error);
|
||||||
|
}
|
||||||
|
} /* namespace nix */
|
Loading…
Reference in a new issue