mirror of
https://github.com/NixOS/nix
synced 2025-07-07 06:01:48 +02:00
Merge remote-tracking branch 'origin/master' into relative-flakes
This commit is contained in:
commit
0b00bf7c09
869 changed files with 5358 additions and 7769 deletions
|
@ -41,7 +41,7 @@ INPUT = \
|
|||
@src@/src/libutil-c \
|
||||
@src@/src/libexpr-c \
|
||||
@src@/src/libstore-c \
|
||||
@src@/doc/external-api/README.md
|
||||
@src@/src/external-api-docs/README.md
|
||||
|
||||
FILE_PATTERNS = nix_api_*.h *.md
|
||||
|
||||
|
@ -55,4 +55,8 @@ EXCLUDE_PATTERNS = *_internal.h
|
|||
GENERATE_TREEVIEW = YES
|
||||
OPTIMIZE_OUTPUT_FOR_C = YES
|
||||
|
||||
USE_MDFILE_AS_MAINPAGE = doc/external-api/README.md
|
||||
USE_MDFILE_AS_MAINPAGE = @src@/src/external-api-docs/README.md
|
||||
|
||||
WARN_IF_UNDOCUMENTED = NO
|
||||
WARN_IF_INCOMPLETE_DOC = NO
|
||||
QUIET = YES
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
{ lib
|
||||
, mkMesonDerivation
|
||||
|
||||
, meson
|
||||
, ninja
|
||||
, doxygen
|
||||
|
||||
# Configuration Options
|
||||
|
@ -37,8 +35,6 @@ mkMesonDerivation (finalAttrs: {
|
|||
];
|
||||
|
||||
nativeBuildInputs = [
|
||||
meson
|
||||
ninja
|
||||
doxygen
|
||||
];
|
||||
|
||||
|
|
|
@ -41,21 +41,22 @@ INPUT = \
|
|||
@src@/libcmd \
|
||||
@src@/libexpr \
|
||||
@src@/libexpr/flake \
|
||||
@src@/nix-expr-tests \
|
||||
@src@/nix-expr-tests/value \
|
||||
@src@/nix-expr-test-support/test \
|
||||
@src@/nix-expr-test-support/test/value \
|
||||
@src@/libexpr-tests \
|
||||
@src@/libexpr-tests/value \
|
||||
@src@/libexpr-test-support/tests \
|
||||
@src@/libexpr-test-support/tests/value \
|
||||
@src@/libexpr/value \
|
||||
@src@/libfetchers \
|
||||
@src@/libmain \
|
||||
@src@/libstore \
|
||||
@src@/libstore/build \
|
||||
@src@/libstore/builtins \
|
||||
@src@/nix-store-tests \
|
||||
@src@/nix-store-test-support/test \
|
||||
@src@/libstore-tests \
|
||||
@src@/libstore-test-support/tests \
|
||||
@src@/libutil \
|
||||
@src@/nix-util-tests \
|
||||
@src@/nix-util-test-support/test \
|
||||
@src@/libutil/args \
|
||||
@src@/libutil-tests \
|
||||
@src@/libutil-test-support/tests \
|
||||
@src@/nix \
|
||||
@src@/nix-env \
|
||||
@src@/nix-store
|
||||
|
@ -83,7 +84,9 @@ EXPAND_ONLY_PREDEF = YES
|
|||
# RECURSIVE has no effect here.
|
||||
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
|
||||
|
||||
INCLUDE_PATH =
|
||||
INCLUDE_PATH = \
|
||||
@BUILD_ROOT@/src/libexpr/libnixexpr.so.p \
|
||||
@BUILD_ROOT@/src/nix/nix.p \
|
||||
|
||||
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
|
||||
# tag can be used to specify a list of macro names that should be expanded. The
|
||||
|
@ -96,4 +99,18 @@ EXPAND_AS_DEFINED = \
|
|||
DECLARE_COMMON_SERIALISER \
|
||||
DECLARE_WORKER_SERIALISER \
|
||||
DECLARE_SERVE_SERIALISER \
|
||||
LENGTH_PREFIXED_PROTO_HELPER
|
||||
LENGTH_PREFIXED_PROTO_HELPER \
|
||||
LENGTH_PREFIXED_PROTO_HELPER_X \
|
||||
WORKER_USE_LENGTH_PREFIX_SERIALISER \
|
||||
WORKER_USE_LENGTH_PREFIX_SERIALISER_COMMA \
|
||||
SERVE_USE_LENGTH_PREFIX_SERIALISER \
|
||||
SERVE_USE_LENGTH_PREFIX_SERIALISER_COMMA \
|
||||
COMMON_METHODS \
|
||||
JSON_IMPL \
|
||||
MakeBinOp
|
||||
|
||||
PREDEFINED = DOXYGEN_SKIP
|
||||
|
||||
WARN_IF_UNDOCUMENTED = NO
|
||||
WARN_IF_INCOMPLETE_DOC = NO
|
||||
QUIET = YES
|
||||
|
|
|
@ -12,6 +12,7 @@ doxygen_cfg = configure_file(
|
|||
configuration : {
|
||||
'PROJECT_NUMBER': meson.project_version(),
|
||||
'OUTPUT_DIRECTORY' : meson.current_build_dir(),
|
||||
'BUILD_ROOT' : meson.build_root(),
|
||||
'src' : fs.parent(fs.parent(meson.project_source_root())) / 'src',
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
{ lib
|
||||
, mkMesonDerivation
|
||||
|
||||
, meson
|
||||
, ninja
|
||||
, doxygen
|
||||
|
||||
# Configuration Options
|
||||
|
@ -32,8 +30,6 @@ mkMesonDerivation (finalAttrs: {
|
|||
];
|
||||
|
||||
nativeBuildInputs = [
|
||||
meson
|
||||
ninja
|
||||
doxygen
|
||||
];
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include <algorithm>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "command.hh"
|
||||
|
@ -9,8 +10,7 @@
|
|||
#include "profiles.hh"
|
||||
#include "repl.hh"
|
||||
#include "strings.hh"
|
||||
|
||||
extern char * * environ __attribute__((weak));
|
||||
#include "environment-variables.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -23,7 +23,8 @@ nix::Commands RegisterCommand::getCommandsFor(const std::vector<std::string> & p
|
|||
if (name.size() == prefix.size() + 1) {
|
||||
bool equal = true;
|
||||
for (size_t i = 0; i < prefix.size(); ++i)
|
||||
if (name[i] != prefix[i]) equal = false;
|
||||
if (name[i] != prefix[i])
|
||||
equal = false;
|
||||
if (equal)
|
||||
res.insert_or_assign(name[prefix.size()], command);
|
||||
}
|
||||
|
@ -42,16 +43,16 @@ void NixMultiCommand::run()
|
|||
std::set<std::string> subCommandTextLines;
|
||||
for (auto & [name, _] : commands)
|
||||
subCommandTextLines.insert(fmt("- `%s`", name));
|
||||
std::string markdownError = fmt("`nix %s` requires a sub-command. Available sub-commands:\n\n%s\n",
|
||||
commandName, concatStringsSep("\n", subCommandTextLines));
|
||||
std::string markdownError =
|
||||
fmt("`nix %s` requires a sub-command. Available sub-commands:\n\n%s\n",
|
||||
commandName,
|
||||
concatStringsSep("\n", subCommandTextLines));
|
||||
throw UsageError(renderMarkdownToTerminal(markdownError));
|
||||
}
|
||||
command->second->run();
|
||||
}
|
||||
|
||||
StoreCommand::StoreCommand()
|
||||
{
|
||||
}
|
||||
StoreCommand::StoreCommand() {}
|
||||
|
||||
ref<Store> StoreCommand::getStore()
|
||||
{
|
||||
|
@ -126,10 +127,8 @@ ref<Store> EvalCommand::getEvalStore()
|
|||
ref<EvalState> EvalCommand::getEvalState()
|
||||
{
|
||||
if (!evalState) {
|
||||
evalState =
|
||||
std::allocate_shared<EvalState>(
|
||||
traceable_allocator<EvalState>(),
|
||||
lookupPath, getEvalStore(), fetchSettings, evalSettings, getStore());
|
||||
evalState = std::allocate_shared<EvalState>(
|
||||
traceable_allocator<EvalState>(), lookupPath, getEvalStore(), fetchSettings, evalSettings, getStore());
|
||||
|
||||
evalState->repair = repair;
|
||||
|
||||
|
@ -144,7 +143,8 @@ MixOperateOnOptions::MixOperateOnOptions()
|
|||
{
|
||||
addFlag({
|
||||
.longName = "derivation",
|
||||
.description = "Operate on the [store derivation](@docroot@/glossary.md#gloss-store-derivation) rather than its outputs.",
|
||||
.description =
|
||||
"Operate on the [store derivation](@docroot@/glossary.md#gloss-store-derivation) rather than its outputs.",
|
||||
.category = installablesCategory,
|
||||
.handler = {&operateOn, OperateOn::Derivation},
|
||||
});
|
||||
|
@ -179,30 +179,34 @@ BuiltPathsCommand::BuiltPathsCommand(bool recursive)
|
|||
|
||||
void BuiltPathsCommand::run(ref<Store> store, Installables && installables)
|
||||
{
|
||||
BuiltPaths paths;
|
||||
BuiltPaths rootPaths, allPaths;
|
||||
|
||||
if (all) {
|
||||
if (installables.size())
|
||||
throw UsageError("'--all' does not expect arguments");
|
||||
// XXX: Only uses opaque paths, ignores all the realisations
|
||||
for (auto & p : store->queryAllValidPaths())
|
||||
paths.emplace_back(BuiltPath::Opaque{p});
|
||||
rootPaths.emplace_back(BuiltPath::Opaque{p});
|
||||
allPaths = rootPaths;
|
||||
} else {
|
||||
paths = Installable::toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables);
|
||||
rootPaths = Installable::toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables);
|
||||
allPaths = rootPaths;
|
||||
|
||||
if (recursive) {
|
||||
// XXX: This only computes the store path closure, ignoring
|
||||
// intermediate realisations
|
||||
StorePathSet pathsRoots, pathsClosure;
|
||||
for (auto & root : paths) {
|
||||
for (auto & root : rootPaths) {
|
||||
auto rootFromThis = root.outPaths();
|
||||
pathsRoots.insert(rootFromThis.begin(), rootFromThis.end());
|
||||
}
|
||||
store->computeFSClosure(pathsRoots, pathsClosure);
|
||||
for (auto & path : pathsClosure)
|
||||
paths.emplace_back(BuiltPath::Opaque{path});
|
||||
allPaths.emplace_back(BuiltPath::Opaque{path});
|
||||
}
|
||||
}
|
||||
|
||||
run(store, std::move(paths));
|
||||
run(store, std::move(allPaths), std::move(rootPaths));
|
||||
}
|
||||
|
||||
StorePathsCommand::StorePathsCommand(bool recursive)
|
||||
|
@ -210,10 +214,10 @@ StorePathsCommand::StorePathsCommand(bool recursive)
|
|||
{
|
||||
}
|
||||
|
||||
void StorePathsCommand::run(ref<Store> store, BuiltPaths && paths)
|
||||
void StorePathsCommand::run(ref<Store> store, BuiltPaths && allPaths, BuiltPaths && rootPaths)
|
||||
{
|
||||
StorePathSet storePaths;
|
||||
for (auto & builtPath : paths)
|
||||
for (auto & builtPath : allPaths)
|
||||
for (auto & p : builtPath.outPaths())
|
||||
storePaths.insert(p);
|
||||
|
||||
|
@ -233,46 +237,48 @@ void StorePathCommand::run(ref<Store> store, StorePaths && storePaths)
|
|||
|
||||
MixProfile::MixProfile()
|
||||
{
|
||||
addFlag({
|
||||
.longName = "profile",
|
||||
.description = "The profile to operate on.",
|
||||
.labels = {"path"},
|
||||
.handler = {&profile},
|
||||
.completer = completePath
|
||||
});
|
||||
addFlag(
|
||||
{.longName = "profile",
|
||||
.description = "The profile to operate on.",
|
||||
.labels = {"path"},
|
||||
.handler = {&profile},
|
||||
.completer = completePath});
|
||||
}
|
||||
|
||||
void MixProfile::updateProfile(const StorePath & storePath)
|
||||
{
|
||||
if (!profile) return;
|
||||
auto store = getStore().dynamic_pointer_cast<LocalFSStore>();
|
||||
if (!store) throw Error("'--profile' is not supported for this Nix store");
|
||||
if (!profile)
|
||||
return;
|
||||
auto store = getDstStore().dynamic_pointer_cast<LocalFSStore>();
|
||||
if (!store)
|
||||
throw Error("'--profile' is not supported for this Nix store");
|
||||
auto profile2 = absPath(*profile);
|
||||
switchLink(profile2,
|
||||
createGeneration(*store, profile2, storePath));
|
||||
switchLink(profile2, createGeneration(*store, profile2, storePath));
|
||||
}
|
||||
|
||||
void MixProfile::updateProfile(const BuiltPaths & buildables)
|
||||
{
|
||||
if (!profile) return;
|
||||
if (!profile)
|
||||
return;
|
||||
|
||||
StorePaths result;
|
||||
|
||||
for (auto & buildable : buildables) {
|
||||
std::visit(overloaded {
|
||||
[&](const BuiltPath::Opaque & bo) {
|
||||
result.push_back(bo.path);
|
||||
std::visit(
|
||||
overloaded{
|
||||
[&](const BuiltPath::Opaque & bo) { result.push_back(bo.path); },
|
||||
[&](const BuiltPath::Built & bfd) {
|
||||
for (auto & output : bfd.outputs) {
|
||||
result.push_back(output.second);
|
||||
}
|
||||
},
|
||||
},
|
||||
[&](const BuiltPath::Built & bfd) {
|
||||
for (auto & output : bfd.outputs) {
|
||||
result.push_back(output.second);
|
||||
}
|
||||
},
|
||||
}, buildable.raw());
|
||||
buildable.raw());
|
||||
}
|
||||
|
||||
if (result.size() != 1)
|
||||
throw UsageError("'--profile' requires that the arguments produce a single store path, but there are %d", result.size());
|
||||
throw UsageError(
|
||||
"'--profile' requires that the arguments produce a single store path, but there are %d", result.size());
|
||||
|
||||
updateProfile(result[0]);
|
||||
}
|
||||
|
@ -282,50 +288,111 @@ MixDefaultProfile::MixDefaultProfile()
|
|||
profile = getDefaultProfile();
|
||||
}
|
||||
|
||||
MixEnvironment::MixEnvironment() : ignoreEnvironment(false)
|
||||
MixEnvironment::MixEnvironment()
|
||||
: ignoreEnvironment(false)
|
||||
{
|
||||
addFlag({
|
||||
.longName = "ignore-environment",
|
||||
.longName = "ignore-env",
|
||||
.aliases = {"ignore-environment"},
|
||||
.shortName = 'i',
|
||||
.description = "Clear the entire environment (except those specified with `--keep`).",
|
||||
.description = "Clear the entire environment, except for those specified with `--keep-env-var`.",
|
||||
.category = environmentVariablesCategory,
|
||||
.handler = {&ignoreEnvironment, true},
|
||||
});
|
||||
|
||||
addFlag({
|
||||
.longName = "keep",
|
||||
.longName = "keep-env-var",
|
||||
.aliases = {"keep"},
|
||||
.shortName = 'k',
|
||||
.description = "Keep the environment variable *name*.",
|
||||
.description = "Keep the environment variable *name*, when using `--ignore-env`.",
|
||||
.category = environmentVariablesCategory,
|
||||
.labels = {"name"},
|
||||
.handler = {[&](std::string s) { keep.insert(s); }},
|
||||
.handler = {[&](std::string s) { keepVars.insert(s); }},
|
||||
});
|
||||
|
||||
addFlag({
|
||||
.longName = "unset",
|
||||
.longName = "unset-env-var",
|
||||
.aliases = {"unset"},
|
||||
.shortName = 'u',
|
||||
.description = "Unset the environment variable *name*.",
|
||||
.category = environmentVariablesCategory,
|
||||
.labels = {"name"},
|
||||
.handler = {[&](std::string s) { unset.insert(s); }},
|
||||
.handler = {[&](std::string name) {
|
||||
if (setVars.contains(name))
|
||||
throw UsageError("Cannot unset environment variable '%s' that is set with '%s'", name, "--set-env-var");
|
||||
|
||||
unsetVars.insert(name);
|
||||
}},
|
||||
});
|
||||
|
||||
addFlag({
|
||||
.longName = "set-env-var",
|
||||
.shortName = 's',
|
||||
.description = "Sets an environment variable *name* with *value*.",
|
||||
.category = environmentVariablesCategory,
|
||||
.labels = {"name", "value"},
|
||||
.handler = {[&](std::string name, std::string value) {
|
||||
if (unsetVars.contains(name))
|
||||
throw UsageError(
|
||||
"Cannot set environment variable '%s' that is unset with '%s'", name, "--unset-env-var");
|
||||
|
||||
if (setVars.contains(name))
|
||||
throw UsageError(
|
||||
"Duplicate definition of environment variable '%s' with '%s' is ambiguous", name, "--set-env-var");
|
||||
|
||||
setVars.insert_or_assign(name, value);
|
||||
}},
|
||||
});
|
||||
}
|
||||
|
||||
void MixEnvironment::setEnviron() {
|
||||
if (ignoreEnvironment) {
|
||||
if (!unset.empty())
|
||||
throw UsageError("--unset does not make sense with --ignore-environment");
|
||||
void MixEnvironment::setEnviron()
|
||||
{
|
||||
if (ignoreEnvironment && !unsetVars.empty())
|
||||
throw UsageError("--unset-env-var does not make sense with --ignore-env");
|
||||
|
||||
for (const auto & var : keep) {
|
||||
auto val = getenv(var.c_str());
|
||||
if (val) stringsEnv.emplace_back(fmt("%s=%s", var.c_str(), val));
|
||||
}
|
||||
if (!ignoreEnvironment && !keepVars.empty())
|
||||
throw UsageError("--keep-env-var does not make sense without --ignore-env");
|
||||
|
||||
vectorEnv = stringsToCharPtrs(stringsEnv);
|
||||
environ = vectorEnv.data();
|
||||
} else {
|
||||
if (!keep.empty())
|
||||
throw UsageError("--keep does not make sense without --ignore-environment");
|
||||
auto env = getEnv();
|
||||
|
||||
for (const auto & var : unset)
|
||||
unsetenv(var.c_str());
|
||||
if (ignoreEnvironment)
|
||||
std::erase_if(env, [&](const auto & var) { return !keepVars.contains(var.first); });
|
||||
|
||||
for (const auto & [name, value] : setVars)
|
||||
env[name] = value;
|
||||
|
||||
if (!unsetVars.empty())
|
||||
std::erase_if(env, [&](const auto & var) { return unsetVars.contains(var.first); });
|
||||
|
||||
replaceEnv(env);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void createOutLinks(const std::filesystem::path & outLink, const BuiltPaths & buildables, LocalFSStore & store)
|
||||
{
|
||||
for (const auto & [_i, buildable] : enumerate(buildables)) {
|
||||
auto i = _i;
|
||||
std::visit(
|
||||
overloaded{
|
||||
[&](const BuiltPath::Opaque & bo) {
|
||||
auto symlink = outLink;
|
||||
if (i)
|
||||
symlink += fmt("-%d", i);
|
||||
store.addPermRoot(bo.path, absPath(symlink.string()));
|
||||
},
|
||||
[&](const BuiltPath::Built & bfd) {
|
||||
for (auto & output : bfd.outputs) {
|
||||
auto symlink = outLink;
|
||||
if (i)
|
||||
symlink += fmt("-%d", i);
|
||||
if (output.first != "out")
|
||||
symlink += fmt("-%s", output.first);
|
||||
store.addPermRoot(output.second, absPath(symlink.string()));
|
||||
}
|
||||
},
|
||||
},
|
||||
buildable.raw());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,18 +13,20 @@ namespace nix {
|
|||
|
||||
extern std::string programPath;
|
||||
|
||||
extern char * * savedArgv;
|
||||
extern char ** savedArgv;
|
||||
|
||||
class EvalState;
|
||||
struct Pos;
|
||||
class Store;
|
||||
class LocalFSStore;
|
||||
|
||||
static constexpr Command::Category catHelp = -1;
|
||||
static constexpr Command::Category catSecondary = 100;
|
||||
static constexpr Command::Category catUtility = 101;
|
||||
static constexpr Command::Category catNixInstallation = 102;
|
||||
|
||||
static constexpr auto installablesCategory = "Options that change the interpretation of [installables](@docroot@/command-ref/new-cli/nix.md#installables)";
|
||||
static constexpr auto installablesCategory =
|
||||
"Options that change the interpretation of [installables](@docroot@/command-ref/new-cli/nix.md#installables)";
|
||||
|
||||
struct NixMultiCommand : MultiCommand, virtual Command
|
||||
{
|
||||
|
@ -45,7 +47,20 @@ struct StoreCommand : virtual Command
|
|||
{
|
||||
StoreCommand();
|
||||
void run() override;
|
||||
|
||||
/**
|
||||
* Return the default Nix store.
|
||||
*/
|
||||
ref<Store> getStore();
|
||||
|
||||
/**
|
||||
* Return the destination Nix store.
|
||||
*/
|
||||
virtual ref<Store> getDstStore()
|
||||
{
|
||||
return getStore();
|
||||
}
|
||||
|
||||
virtual ref<Store> createStore();
|
||||
/**
|
||||
* Main entry point, with a `Store` provided
|
||||
|
@ -68,7 +83,7 @@ struct CopyCommand : virtual StoreCommand
|
|||
|
||||
ref<Store> createStore() override;
|
||||
|
||||
ref<Store> getDstStore();
|
||||
ref<Store> getDstStore() override;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -112,7 +127,9 @@ struct MixFlakeOptions : virtual Args, EvalCommand
|
|||
* arguments) so that the completions for these flags can use them.
|
||||
*/
|
||||
virtual std::vector<FlakeRef> getFlakeRefsForCompletion()
|
||||
{ return {}; }
|
||||
{
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
struct SourceExprCommand : virtual Args, MixFlakeOptions
|
||||
|
@ -122,11 +139,9 @@ struct SourceExprCommand : virtual Args, MixFlakeOptions
|
|||
|
||||
SourceExprCommand();
|
||||
|
||||
Installables parseInstallables(
|
||||
ref<Store> store, std::vector<std::string> ss);
|
||||
Installables parseInstallables(ref<Store> store, std::vector<std::string> ss);
|
||||
|
||||
ref<Installable> parseInstallable(
|
||||
ref<Store> store, const std::string & installable);
|
||||
ref<Installable> parseInstallable(ref<Store> store, const std::string & installable);
|
||||
|
||||
virtual Strings getDefaultFlakeAttrPaths();
|
||||
|
||||
|
@ -238,7 +253,7 @@ public:
|
|||
|
||||
BuiltPathsCommand(bool recursive = false);
|
||||
|
||||
virtual void run(ref<Store> store, BuiltPaths && paths) = 0;
|
||||
virtual void run(ref<Store> store, BuiltPaths && allPaths, BuiltPaths && rootPaths) = 0;
|
||||
|
||||
void run(ref<Store> store, Installables && installables) override;
|
||||
|
||||
|
@ -251,7 +266,7 @@ struct StorePathsCommand : public BuiltPathsCommand
|
|||
|
||||
virtual void run(ref<Store> store, StorePaths && storePaths) = 0;
|
||||
|
||||
void run(ref<Store> store, BuiltPaths && paths) override;
|
||||
void run(ref<Store> store, BuiltPaths && allPaths, BuiltPaths && rootPaths) override;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -272,10 +287,10 @@ struct RegisterCommand
|
|||
typedef std::map<std::vector<std::string>, std::function<ref<Command>()>> Commands;
|
||||
static Commands * commands;
|
||||
|
||||
RegisterCommand(std::vector<std::string> && name,
|
||||
std::function<ref<Command>()> command)
|
||||
RegisterCommand(std::vector<std::string> && name, std::function<ref<Command>()> command)
|
||||
{
|
||||
if (!commands) commands = new Commands;
|
||||
if (!commands)
|
||||
commands = new Commands;
|
||||
commands->emplace(name, command);
|
||||
}
|
||||
|
||||
|
@ -285,13 +300,13 @@ struct RegisterCommand
|
|||
template<class T>
|
||||
static RegisterCommand registerCommand(const std::string & name)
|
||||
{
|
||||
return RegisterCommand({name}, [](){ return make_ref<T>(); });
|
||||
return RegisterCommand({name}, []() { return make_ref<T>(); });
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static RegisterCommand registerCommand2(std::vector<std::string> && name)
|
||||
{
|
||||
return RegisterCommand(std::move(name), [](){ return make_ref<T>(); });
|
||||
return RegisterCommand(std::move(name), []() { return make_ref<T>(); });
|
||||
}
|
||||
|
||||
struct MixProfile : virtual StoreCommand
|
||||
|
@ -313,19 +328,21 @@ struct MixDefaultProfile : MixProfile
|
|||
MixDefaultProfile();
|
||||
};
|
||||
|
||||
struct MixEnvironment : virtual Args {
|
||||
struct MixEnvironment : virtual Args
|
||||
{
|
||||
|
||||
StringSet keep, unset;
|
||||
Strings stringsEnv;
|
||||
std::vector<char*> vectorEnv;
|
||||
StringSet keepVars;
|
||||
StringSet unsetVars;
|
||||
std::map<std::string, std::string> setVars;
|
||||
bool ignoreEnvironment;
|
||||
|
||||
MixEnvironment();
|
||||
|
||||
/***
|
||||
* Modify global environ based on `ignoreEnvironment`, `keep`, and
|
||||
* `unset`. It's expected that exec will be called before this class
|
||||
* goes out of scope, otherwise `environ` will become invalid.
|
||||
* Modify global environ based on `ignoreEnvironment`, `keep`,
|
||||
* `unset`, and `added`. It's expected that exec will be called
|
||||
* before this class goes out of scope, otherwise `environ` will
|
||||
* become invalid.
|
||||
*/
|
||||
void setEnviron();
|
||||
};
|
||||
|
@ -349,9 +366,12 @@ void completeFlakeRefWithFragment(
|
|||
std::string showVersions(const std::set<std::string> & versions);
|
||||
|
||||
void printClosureDiff(
|
||||
ref<Store> store,
|
||||
const StorePath & beforePath,
|
||||
const StorePath & afterPath,
|
||||
std::string_view indent);
|
||||
ref<Store> store, const StorePath & beforePath, const StorePath & afterPath, std::string_view indent);
|
||||
|
||||
/**
|
||||
* Create symlinks prefixed by `outLink` to the store paths in
|
||||
* `buildables`.
|
||||
*/
|
||||
void createOutLinks(const std::filesystem::path & outLink, const BuiltPaths & buildables, LocalFSStore & store);
|
||||
|
||||
}
|
||||
|
|
|
@ -29,13 +29,13 @@ EvalSettings evalSettings {
|
|||
{
|
||||
{
|
||||
"flake",
|
||||
[](ref<Store> store, std::string_view rest) {
|
||||
[](EvalState & state, std::string_view rest) {
|
||||
experimentalFeatureSettings.require(Xp::Flakes);
|
||||
// FIXME `parseFlakeRef` should take a `std::string_view`.
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, std::string { rest }, {}, true, false);
|
||||
debug("fetching flake search path element '%s''", rest);
|
||||
auto storePath = flakeRef.resolve(store).fetchTree(store).first;
|
||||
return store->toRealPath(storePath);
|
||||
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first;
|
||||
return state.rootPath(state.store->toRealPath(storePath));
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -32,16 +32,6 @@ InstallableDerivedPath InstallableDerivedPath::parse(
|
|||
// store path.
|
||||
[&](const ExtendedOutputsSpec::Default &) -> DerivedPath {
|
||||
auto storePath = store->followLinksToStorePath(prefix);
|
||||
// Remove this prior to stabilizing the new CLI.
|
||||
if (storePath.isDerivation()) {
|
||||
auto oldDerivedPath = DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePathRef(storePath),
|
||||
.outputs = OutputsSpec::All { },
|
||||
};
|
||||
warn(
|
||||
"The interpretation of store paths arguments ending in `.drv` recently changed. If this command is now failing try again with '%s'",
|
||||
oldDerivedPath.to_string(*store));
|
||||
};
|
||||
return DerivedPath::Opaque {
|
||||
.path = std::move(storePath),
|
||||
};
|
||||
|
|
|
@ -26,7 +26,7 @@ struct ExtraPathInfoFlake : ExtraPathInfoValue
|
|||
Flake flake;
|
||||
|
||||
ExtraPathInfoFlake(Value && v, Flake && f)
|
||||
: ExtraPathInfoValue(std::move(v)), flake(f)
|
||||
: ExtraPathInfoValue(std::move(v)), flake(std::move(f))
|
||||
{ }
|
||||
};
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ struct ExtraPathInfoValue : ExtraPathInfo
|
|||
Value value;
|
||||
|
||||
ExtraPathInfoValue(Value && v)
|
||||
: value(v)
|
||||
: value(std::move(v))
|
||||
{ }
|
||||
|
||||
virtual ~ExtraPathInfoValue() = default;
|
||||
|
|
|
@ -88,7 +88,7 @@ MixFlakeOptions::MixFlakeOptions()
|
|||
|
||||
> **DEPRECATED**
|
||||
>
|
||||
> Use [`--no-use-registries`](#opt-no-use-registries) instead.
|
||||
> Use [`--no-use-registries`](@docroot@/command-ref/conf-file.md#conf-use-registries) instead.
|
||||
)",
|
||||
.category = category,
|
||||
.handler = {[&]() {
|
||||
|
@ -857,6 +857,7 @@ std::vector<FlakeRef> RawInstallablesCommand::getFlakeRefsForCompletion()
|
|||
{
|
||||
applyDefaultInstallables(rawInstallables);
|
||||
std::vector<FlakeRef> res;
|
||||
res.reserve(rawInstallables.size());
|
||||
for (auto i : rawInstallables)
|
||||
res.push_back(parseFlakeRefWithFragment(
|
||||
fetchSettings,
|
||||
|
@ -917,4 +918,12 @@ void BuiltPathsCommand::applyDefaultInstallables(std::vector<std::string> & rawI
|
|||
rawInstallables.push_back(".");
|
||||
}
|
||||
|
||||
BuiltPaths toBuiltPaths(const std::vector<BuiltPathWithResult> & builtPathsWithResult)
|
||||
{
|
||||
BuiltPaths res;
|
||||
for (auto & i : builtPathsWithResult)
|
||||
res.push_back(i.path);
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -86,6 +86,8 @@ struct BuiltPathWithResult
|
|||
std::optional<BuildResult> result;
|
||||
};
|
||||
|
||||
BuiltPaths toBuiltPaths(const std::vector<BuiltPathWithResult> & builtPathsWithResult);
|
||||
|
||||
/**
|
||||
* Shorthand, for less typing and helping us keep the choice of
|
||||
* collection in sync.
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
libraries += libcmd
|
||||
|
||||
libcmd_NAME = libnixcmd
|
||||
|
||||
libcmd_DIR := $(d)
|
||||
|
||||
libcmd_SOURCES := $(wildcard $(d)/*.cc)
|
||||
|
||||
libcmd_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) $(INCLUDE_libflake) $(INCLUDE_libmain)
|
||||
|
||||
libcmd_LDFLAGS = $(EDITLINE_LIBS) $(LOWDOWN_LIBS) $(THREAD_LDFLAGS)
|
||||
|
||||
libcmd_LIBS = libutil libstore libfetchers libflake libexpr libmain
|
||||
|
||||
$(eval $(call install-file-in, $(buildprefix)$(d)/nix-cmd.pc, $(libdir)/pkgconfig, 0644))
|
|
@ -30,8 +30,6 @@ deps_public_maybe_subproject = [
|
|||
]
|
||||
subdir('build-utils-meson/subprojects')
|
||||
|
||||
subdir('build-utils-meson/threads')
|
||||
|
||||
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
|
||||
deps_public += nlohmann_json
|
||||
|
||||
|
@ -72,7 +70,7 @@ add_project_arguments(
|
|||
language : 'cpp',
|
||||
)
|
||||
|
||||
subdir('build-utils-meson/diagnostics')
|
||||
subdir('build-utils-meson/common')
|
||||
|
||||
sources = files(
|
||||
'built-path.cc',
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
prefix=@prefix@
|
||||
libdir=@libdir@
|
||||
includedir=@includedir@
|
||||
|
||||
Name: Nix
|
||||
Description: Nix Package Manager
|
||||
Version: @PACKAGE_VERSION@
|
||||
Libs: -L${libdir} -lnixcmd
|
||||
Cflags: -I${includedir}/nix -std=c++2a
|
|
@ -1,11 +1,6 @@
|
|||
{ lib
|
||||
, stdenv
|
||||
, mkMesonDerivation
|
||||
, releaseTools
|
||||
|
||||
, meson
|
||||
, ninja
|
||||
, pkg-config
|
||||
, mkMesonLibrary
|
||||
|
||||
, nix-util
|
||||
, nix-store
|
||||
|
@ -38,7 +33,7 @@ let
|
|||
inherit (lib) fileset;
|
||||
in
|
||||
|
||||
mkMesonDerivation (finalAttrs: {
|
||||
mkMesonLibrary (finalAttrs: {
|
||||
pname = "nix-cmd";
|
||||
inherit version;
|
||||
|
||||
|
@ -54,14 +49,6 @@ mkMesonDerivation (finalAttrs: {
|
|||
(fileset.fileFilter (file: file.hasExt "hh") ./.)
|
||||
];
|
||||
|
||||
outputs = [ "out" "dev" ];
|
||||
|
||||
nativeBuildInputs = [
|
||||
meson
|
||||
ninja
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
({ inherit editline readline; }.${readlineFlavor})
|
||||
] ++ lib.optional enableMarkdown lowdown;
|
||||
|
@ -93,10 +80,6 @@ mkMesonDerivation (finalAttrs: {
|
|||
LDFLAGS = "-fuse-ld=gold";
|
||||
};
|
||||
|
||||
separateDebugInfo = !stdenv.hostPlatform.isStatic;
|
||||
|
||||
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
|
||||
|
||||
meta = {
|
||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||
};
|
||||
|
|
|
@ -645,7 +645,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
|
|||
|
||||
logger->cout(trim(renderMarkdownToTerminal(markdown)));
|
||||
} else if (fallbackPos) {
|
||||
std::stringstream ss;
|
||||
std::ostringstream ss;
|
||||
ss << "Attribute `" << fallbackName << "`\n\n";
|
||||
ss << " … defined at " << state->positions[fallbackPos] << "\n\n";
|
||||
if (fallbackDoc) {
|
||||
|
@ -654,7 +654,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
|
|||
ss << "No documentation found.\n\n";
|
||||
}
|
||||
|
||||
auto markdown = ss.str();
|
||||
auto markdown = toView(ss);
|
||||
logger->cout(trim(renderMarkdownToTerminal(markdown)));
|
||||
|
||||
} else
|
||||
|
@ -826,7 +826,7 @@ void NixRepl::runNix(Path program, const Strings & args, const std::optional<std
|
|||
if (runNixPtr)
|
||||
(*runNixPtr)(program, args, input);
|
||||
else
|
||||
throw Error("Cannot run '%s', no method of calling the Nix CLI provided", program);
|
||||
throw Error("Cannot run '%s' because no method of calling the Nix CLI was provided. This is a configuration problem pertaining to how this program was built. See Nix 2.25 release notes", program);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
libraries += libexprc
|
||||
|
||||
libexprc_NAME = libnixexprc
|
||||
|
||||
libexprc_DIR := $(d)
|
||||
|
||||
libexprc_SOURCES := \
|
||||
$(wildcard $(d)/*.cc) \
|
||||
|
||||
# Not just for this library itself, but also for downstream libraries using this library
|
||||
|
||||
INCLUDE_libexprc := -I $(d)
|
||||
libexprc_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libutilc) \
|
||||
$(INCLUDE_libfetchers) \
|
||||
$(INCLUDE_libstore) $(INCLUDE_libstorec) \
|
||||
$(INCLUDE_libexpr) $(INCLUDE_libexprc)
|
||||
|
||||
libexprc_LIBS = libutil libutilc libstore libstorec libfetchers libexpr
|
||||
|
||||
libexprc_LDFLAGS += $(THREAD_LDFLAGS)
|
||||
|
||||
$(eval $(call install-file-in, $(d)/nix-expr-c.pc, $(libdir)/pkgconfig, 0644))
|
||||
|
||||
libexprc_FORCE_INSTALL := 1
|
||||
|
|
@ -29,8 +29,6 @@ deps_public_maybe_subproject = [
|
|||
]
|
||||
subdir('build-utils-meson/subprojects')
|
||||
|
||||
subdir('build-utils-meson/threads')
|
||||
|
||||
# TODO rename, because it will conflict with downstream projects
|
||||
configdata.set_quoted('PACKAGE_VERSION', meson.project_version())
|
||||
|
||||
|
@ -55,7 +53,7 @@ add_project_arguments(
|
|||
language : 'cpp',
|
||||
)
|
||||
|
||||
subdir('build-utils-meson/diagnostics')
|
||||
subdir('build-utils-meson/common')
|
||||
|
||||
sources = files(
|
||||
'nix_api_expr.cc',
|
||||
|
@ -75,6 +73,7 @@ headers = [config_h] + files(
|
|||
headers += files('nix_api_expr_internal.h')
|
||||
|
||||
subdir('build-utils-meson/export-all-symbols')
|
||||
subdir('build-utils-meson/windows-version')
|
||||
|
||||
this_library = library(
|
||||
'nixexprc',
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
prefix=@prefix@
|
||||
libdir=@libdir@
|
||||
includedir=@includedir@
|
||||
|
||||
Name: Nix
|
||||
Description: Nix Language Evaluator - C API
|
||||
Version: @PACKAGE_VERSION@
|
||||
Requires: nix-store-c
|
||||
Libs: -L${libdir} -lnixexprc
|
||||
Cflags: -I${includedir}/nix
|
|
@ -67,7 +67,7 @@ nix_err nix_value_call_multi(nix_c_context * context, EvalState * state, nix_val
|
|||
if (context)
|
||||
context->last_err_code = NIX_OK;
|
||||
try {
|
||||
state->state.callFunction(fn->value, nargs, (nix::Value * *)args, value->value, nix::noPos);
|
||||
state->state.callFunction(fn->value, {(nix::Value * *) args, nargs}, value->value, nix::noPos);
|
||||
state->state.forceValue(value->value, nix::noPos);
|
||||
}
|
||||
NIXC_CATCH_ERRS
|
||||
|
|
|
@ -129,7 +129,7 @@ nix_err nix_value_call_multi(
|
|||
* @param[in] state The state of the evaluation.
|
||||
* @param[out] value The result of the function call.
|
||||
* @param[in] fn The Nix function to call.
|
||||
* @param[in] args The arguments to pass to the function.
|
||||
* @param[in] ... The arguments to pass to the function.
|
||||
*
|
||||
* @see nix_value_call_multi
|
||||
*/
|
||||
|
|
|
@ -77,8 +77,7 @@ typedef struct ExternalValue ExternalValue;
|
|||
*/
|
||||
typedef struct nix_realised_string nix_realised_string;
|
||||
|
||||
/** @defgroup primops
|
||||
* @brief Create your own primops
|
||||
/** @defgroup primops Adding primops
|
||||
* @{
|
||||
*/
|
||||
/** @brief Function pointer for primops
|
||||
|
@ -252,7 +251,7 @@ int64_t nix_get_int(nix_c_context * context, const nix_value * value);
|
|||
* @param[in] value Nix value to inspect
|
||||
* @return reference to external, NULL in case of error
|
||||
*/
|
||||
ExternalValue * nix_get_external(nix_c_context * context, nix_value *);
|
||||
ExternalValue * nix_get_external(nix_c_context * context, nix_value * value);
|
||||
|
||||
/** @brief Get the ix'th element of a list
|
||||
*
|
||||
|
@ -423,7 +422,7 @@ nix_list_builder_insert(nix_c_context * context, ListBuilder * list_builder, uns
|
|||
/** @brief Free a list builder
|
||||
*
|
||||
* Does not fail.
|
||||
* @param[in] builder the builder to free
|
||||
* @param[in] list_builder The builder to free.
|
||||
*/
|
||||
void nix_list_builder_free(ListBuilder * list_builder);
|
||||
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
{ lib
|
||||
, stdenv
|
||||
, mkMesonDerivation
|
||||
|
||||
, meson
|
||||
, ninja
|
||||
, pkg-config
|
||||
, mkMesonLibrary
|
||||
|
||||
, nix-store-c
|
||||
, nix-expr
|
||||
|
@ -18,7 +14,7 @@ let
|
|||
inherit (lib) fileset;
|
||||
in
|
||||
|
||||
mkMesonDerivation (finalAttrs: {
|
||||
mkMesonLibrary (finalAttrs: {
|
||||
pname = "nix-expr-c";
|
||||
inherit version;
|
||||
|
||||
|
@ -35,14 +31,6 @@ mkMesonDerivation (finalAttrs: {
|
|||
(fileset.fileFilter (file: file.hasExt "h") ./.)
|
||||
];
|
||||
|
||||
outputs = [ "out" "dev" ];
|
||||
|
||||
nativeBuildInputs = [
|
||||
meson
|
||||
ninja
|
||||
pkg-config
|
||||
];
|
||||
|
||||
propagatedBuildInputs = [
|
||||
nix-store-c
|
||||
nix-expr
|
||||
|
@ -63,10 +51,6 @@ mkMesonDerivation (finalAttrs: {
|
|||
LDFLAGS = "-fuse-ld=gold";
|
||||
};
|
||||
|
||||
separateDebugInfo = !stdenv.hostPlatform.isStatic;
|
||||
|
||||
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
|
||||
|
||||
meta = {
|
||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||
};
|
||||
|
|
1
src/libexpr-test-support/.version
Symbolic link
1
src/libexpr-test-support/.version
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../.version
|
1
src/libexpr-test-support/build-utils-meson
Symbolic link
1
src/libexpr-test-support/build-utils-meson
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../build-utils-meson
|
75
src/libexpr-test-support/meson.build
Normal file
75
src/libexpr-test-support/meson.build
Normal file
|
@ -0,0 +1,75 @@
|
|||
project('nix-expr-test-support', 'cpp',
|
||||
version : files('.version'),
|
||||
default_options : [
|
||||
'cpp_std=c++2a',
|
||||
# TODO(Qyriad): increase the warning level
|
||||
'warning_level=1',
|
||||
'debug=true',
|
||||
'optimization=2',
|
||||
'errorlogs=true', # Please print logs for tests that fail
|
||||
],
|
||||
meson_version : '>= 1.1',
|
||||
license : 'LGPL-2.1-or-later',
|
||||
)
|
||||
|
||||
cxx = meson.get_compiler('cpp')
|
||||
|
||||
subdir('build-utils-meson/deps-lists')
|
||||
|
||||
deps_private_maybe_subproject = [
|
||||
]
|
||||
deps_public_maybe_subproject = [
|
||||
dependency('nix-util'),
|
||||
dependency('nix-util-test-support'),
|
||||
dependency('nix-store'),
|
||||
dependency('nix-store-test-support'),
|
||||
dependency('nix-expr'),
|
||||
]
|
||||
subdir('build-utils-meson/subprojects')
|
||||
|
||||
rapidcheck = dependency('rapidcheck')
|
||||
deps_public += rapidcheck
|
||||
|
||||
add_project_arguments(
|
||||
# TODO(Qyriad): Yes this is how the autoconf+Make system did it.
|
||||
# It would be nice for our headers to be idempotent instead.
|
||||
'-include', 'config-util.hh',
|
||||
'-include', 'config-store.hh',
|
||||
'-include', 'config-expr.hh',
|
||||
language : 'cpp',
|
||||
)
|
||||
|
||||
subdir('build-utils-meson/common')
|
||||
|
||||
sources = files(
|
||||
'tests/value/context.cc',
|
||||
)
|
||||
|
||||
include_dirs = [include_directories('.')]
|
||||
|
||||
headers = files(
|
||||
'tests/libexpr.hh',
|
||||
'tests/nix_api_expr.hh',
|
||||
'tests/value/context.hh',
|
||||
)
|
||||
|
||||
subdir('build-utils-meson/export-all-symbols')
|
||||
subdir('build-utils-meson/windows-version')
|
||||
|
||||
this_library = library(
|
||||
'nix-expr-test-support',
|
||||
sources,
|
||||
dependencies : deps_public + deps_private + deps_other,
|
||||
include_directories : include_dirs,
|
||||
# TODO: Remove `-lrapidcheck` when https://github.com/emil-e/rapidcheck/pull/326
|
||||
# is available. See also ../libutil/build.meson
|
||||
link_args: linker_export_flags + ['-lrapidcheck'],
|
||||
prelink : true, # For C++ static initializers
|
||||
install : true,
|
||||
)
|
||||
|
||||
install_headers(headers, subdir : 'nix', preserve_path : true)
|
||||
|
||||
libraries_private = []
|
||||
|
||||
subdir('build-utils-meson/export')
|
60
src/libexpr-test-support/package.nix
Normal file
60
src/libexpr-test-support/package.nix
Normal file
|
@ -0,0 +1,60 @@
|
|||
{ lib
|
||||
, stdenv
|
||||
, mkMesonLibrary
|
||||
|
||||
, nix-store-test-support
|
||||
, nix-expr
|
||||
|
||||
, rapidcheck
|
||||
|
||||
# Configuration Options
|
||||
|
||||
, version
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) fileset;
|
||||
in
|
||||
|
||||
mkMesonLibrary (finalAttrs: {
|
||||
pname = "nix-util-test-support";
|
||||
inherit version;
|
||||
|
||||
workDir = ./.;
|
||||
fileset = fileset.unions [
|
||||
../../build-utils-meson
|
||||
./build-utils-meson
|
||||
../../.version
|
||||
./.version
|
||||
./meson.build
|
||||
# ./meson.options
|
||||
(fileset.fileFilter (file: file.hasExt "cc") ./.)
|
||||
(fileset.fileFilter (file: file.hasExt "hh") ./.)
|
||||
];
|
||||
|
||||
propagatedBuildInputs = [
|
||||
nix-store-test-support
|
||||
nix-expr
|
||||
rapidcheck
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
mesonFlags = [
|
||||
];
|
||||
|
||||
env = lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
|
||||
LDFLAGS = "-fuse-ld=gold";
|
||||
};
|
||||
|
||||
meta = {
|
||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||
};
|
||||
|
||||
})
|
156
src/libexpr-test-support/tests/libexpr.hh
Normal file
156
src/libexpr-test-support/tests/libexpr.hh
Normal file
|
@ -0,0 +1,156 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "fetch-settings.hh"
|
||||
#include "value.hh"
|
||||
#include "nixexpr.hh"
|
||||
#include "nixexpr.hh"
|
||||
#include "eval.hh"
|
||||
#include "eval-gc.hh"
|
||||
#include "eval-inline.hh"
|
||||
#include "eval-settings.hh"
|
||||
|
||||
#include "tests/libstore.hh"
|
||||
|
||||
namespace nix {
|
||||
class LibExprTest : public LibStoreTest {
|
||||
public:
|
||||
static void SetUpTestSuite() {
|
||||
LibStoreTest::SetUpTestSuite();
|
||||
initGC();
|
||||
}
|
||||
|
||||
protected:
|
||||
LibExprTest()
|
||||
: LibStoreTest()
|
||||
, state({}, store, fetchSettings, evalSettings, nullptr)
|
||||
{
|
||||
evalSettings.nixPath = {};
|
||||
}
|
||||
Value eval(std::string input, bool forceValue = true) {
|
||||
Value v;
|
||||
Expr * e = state.parseExprFromString(input, state.rootPath(CanonPath::root));
|
||||
assert(e);
|
||||
state.eval(e, v);
|
||||
if (forceValue)
|
||||
state.forceValue(v, noPos);
|
||||
return v;
|
||||
}
|
||||
|
||||
Value * maybeThunk(std::string input, bool forceValue = true) {
|
||||
Expr * e = state.parseExprFromString(input, state.rootPath(CanonPath::root));
|
||||
assert(e);
|
||||
return e->maybeThunk(state, state.baseEnv);
|
||||
}
|
||||
|
||||
Symbol createSymbol(const char * value) {
|
||||
return state.symbols.create(value);
|
||||
}
|
||||
|
||||
bool readOnlyMode = true;
|
||||
fetchers::Settings fetchSettings{};
|
||||
EvalSettings evalSettings{readOnlyMode};
|
||||
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.c_str()) == s;
|
||||
}
|
||||
|
||||
MATCHER_P(IsIntEq, v, fmt("The string is equal to \"%1%\"", v)) {
|
||||
if (arg.type() != nInt) {
|
||||
return false;
|
||||
}
|
||||
return arg.integer().value == 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 {
|
||||
auto path = arg.path();
|
||||
if (path.path != CanonPath(p)) {
|
||||
*result_listener << "Expected a path that equals \"" << p << "\" but got: " << path.path;
|
||||
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 << "Expected 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 */
|
31
src/libexpr-test-support/tests/nix_api_expr.hh
Normal file
31
src/libexpr-test-support/tests/nix_api_expr.hh
Normal file
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
///@file
|
||||
#include "nix_api_expr.h"
|
||||
#include "nix_api_value.h"
|
||||
#include "tests/nix_api_store.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace nixC {
|
||||
|
||||
class nix_api_expr_test : public nix_api_store_test
|
||||
{
|
||||
protected:
|
||||
|
||||
nix_api_expr_test()
|
||||
{
|
||||
nix_libexpr_init(ctx);
|
||||
state = nix_state_create(nullptr, nullptr, store);
|
||||
value = nix_alloc_value(nullptr, state);
|
||||
}
|
||||
~nix_api_expr_test()
|
||||
{
|
||||
nix_gc_decref(nullptr, value);
|
||||
nix_state_free(state);
|
||||
}
|
||||
|
||||
EvalState * state;
|
||||
nix_value * value;
|
||||
};
|
||||
|
||||
}
|
30
src/libexpr-test-support/tests/value/context.cc
Normal file
30
src/libexpr-test-support/tests/value/context.cc
Normal file
|
@ -0,0 +1,30 @@
|
|||
#include <rapidcheck.h>
|
||||
|
||||
#include "tests/path.hh"
|
||||
#include "tests/value/context.hh"
|
||||
|
||||
namespace rc {
|
||||
using namespace nix;
|
||||
|
||||
Gen<NixStringContextElem::DrvDeep> Arbitrary<NixStringContextElem::DrvDeep>::arbitrary()
|
||||
{
|
||||
return gen::just(NixStringContextElem::DrvDeep {
|
||||
.drvPath = *gen::arbitrary<StorePath>(),
|
||||
});
|
||||
}
|
||||
|
||||
Gen<NixStringContextElem> Arbitrary<NixStringContextElem>::arbitrary()
|
||||
{
|
||||
switch (*gen::inRange<uint8_t>(0, std::variant_size_v<NixStringContextElem::Raw>)) {
|
||||
case 0:
|
||||
return gen::just<NixStringContextElem>(*gen::arbitrary<NixStringContextElem::Opaque>());
|
||||
case 1:
|
||||
return gen::just<NixStringContextElem>(*gen::arbitrary<NixStringContextElem::DrvDeep>());
|
||||
case 2:
|
||||
return gen::just<NixStringContextElem>(*gen::arbitrary<NixStringContextElem::Built>());
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
31
src/libexpr-test-support/tests/value/context.hh
Normal file
31
src/libexpr-test-support/tests/value/context.hh
Normal file
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
///@file
|
||||
|
||||
#include <rapidcheck/gen/Arbitrary.h>
|
||||
|
||||
#include "value/context.hh"
|
||||
|
||||
namespace rc {
|
||||
using namespace nix;
|
||||
|
||||
template<>
|
||||
struct Arbitrary<NixStringContextElem::Opaque> {
|
||||
static Gen<NixStringContextElem::Opaque> arbitrary();
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Arbitrary<NixStringContextElem::Built> {
|
||||
static Gen<NixStringContextElem::Built> arbitrary();
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Arbitrary<NixStringContextElem::DrvDeep> {
|
||||
static Gen<NixStringContextElem::DrvDeep> arbitrary();
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Arbitrary<NixStringContextElem> {
|
||||
static Gen<NixStringContextElem> arbitrary();
|
||||
};
|
||||
|
||||
}
|
1
src/libexpr-tests/.version
Symbolic link
1
src/libexpr-tests/.version
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../.version
|
1
src/libexpr-tests/build-utils-meson
Symbolic link
1
src/libexpr-tests/build-utils-meson
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../build-utils-meson
|
0
src/libexpr-tests/data/.gitkeep
Normal file
0
src/libexpr-tests/data/.gitkeep
Normal file
68
src/libexpr-tests/derived-path.cc
Normal file
68
src/libexpr-tests/derived-path.cc
Normal file
|
@ -0,0 +1,68 @@
|
|||
#include <nlohmann/json.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <rapidcheck/gtest.h>
|
||||
|
||||
#include "tests/derived-path.hh"
|
||||
#include "tests/libexpr.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
// Testing of trivial expressions
|
||||
class DerivedPathExpressionTest : public LibExprTest {};
|
||||
|
||||
// FIXME: `RC_GTEST_FIXTURE_PROP` isn't calling `SetUpTestSuite` because it is
|
||||
// no a real fixture.
|
||||
//
|
||||
// See https://github.com/emil-e/rapidcheck/blob/master/doc/gtest.md#rc_gtest_fixture_propfixture-name-args
|
||||
TEST_F(DerivedPathExpressionTest, force_init)
|
||||
{
|
||||
}
|
||||
|
||||
#ifndef COVERAGE
|
||||
|
||||
RC_GTEST_FIXTURE_PROP(
|
||||
DerivedPathExpressionTest,
|
||||
prop_opaque_path_round_trip,
|
||||
(const SingleDerivedPath::Opaque & o))
|
||||
{
|
||||
auto * v = state.allocValue();
|
||||
state.mkStorePathString(o.path, *v);
|
||||
auto d = state.coerceToSingleDerivedPath(noPos, *v, "");
|
||||
RC_ASSERT(SingleDerivedPath { o } == d);
|
||||
}
|
||||
|
||||
// TODO use DerivedPath::Built for parameter once it supports a single output
|
||||
// path only.
|
||||
|
||||
RC_GTEST_FIXTURE_PROP(
|
||||
DerivedPathExpressionTest,
|
||||
prop_derived_path_built_placeholder_round_trip,
|
||||
(const SingleDerivedPath::Built & b))
|
||||
{
|
||||
/**
|
||||
* We set these in tests rather than the regular globals so we don't have
|
||||
* to worry about race conditions if the tests run concurrently.
|
||||
*/
|
||||
ExperimentalFeatureSettings mockXpSettings;
|
||||
mockXpSettings.set("experimental-features", "ca-derivations");
|
||||
|
||||
auto * v = state.allocValue();
|
||||
state.mkOutputString(*v, b, std::nullopt, mockXpSettings);
|
||||
auto [d, _] = state.coerceToSingleDerivedPathUnchecked(noPos, *v, "");
|
||||
RC_ASSERT(SingleDerivedPath { b } == d);
|
||||
}
|
||||
|
||||
RC_GTEST_FIXTURE_PROP(
|
||||
DerivedPathExpressionTest,
|
||||
prop_derived_path_built_out_path_round_trip,
|
||||
(const SingleDerivedPath::Built & b, const StorePath & outPath))
|
||||
{
|
||||
auto * v = state.allocValue();
|
||||
state.mkOutputString(*v, b, outPath);
|
||||
auto [d, _] = state.coerceToSingleDerivedPathUnchecked(noPos, *v, "");
|
||||
RC_ASSERT(SingleDerivedPath { b } == d);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} /* namespace nix */
|
1378
src/libexpr-tests/error_traces.cc
Normal file
1378
src/libexpr-tests/error_traces.cc
Normal file
File diff suppressed because it is too large
Load diff
164
src/libexpr-tests/eval.cc
Normal file
164
src/libexpr-tests/eval.cc
Normal file
|
@ -0,0 +1,164 @@
|
|||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "eval.hh"
|
||||
#include "tests/libexpr.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
TEST(nix_isAllowedURI, http_example_com) {
|
||||
Strings allowed;
|
||||
allowed.push_back("http://example.com");
|
||||
|
||||
ASSERT_TRUE(isAllowedURI("http://example.com", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("http://example.com/foo", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("http://example.com/foo/", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("/", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.co", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.como", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.org", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.org/foo", allowed));
|
||||
}
|
||||
|
||||
TEST(nix_isAllowedURI, http_example_com_foo) {
|
||||
Strings allowed;
|
||||
allowed.push_back("http://example.com/foo");
|
||||
|
||||
ASSERT_TRUE(isAllowedURI("http://example.com/foo", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("http://example.com/foo/", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("/foo", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.com", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.como", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.org/foo", allowed));
|
||||
// Broken?
|
||||
// ASSERT_TRUE(isAllowedURI("http://example.com/foo?ok=1", allowed));
|
||||
}
|
||||
|
||||
TEST(nix_isAllowedURI, http) {
|
||||
Strings allowed;
|
||||
allowed.push_back("http://");
|
||||
|
||||
ASSERT_TRUE(isAllowedURI("http://", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("http://example.com", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("http://example.com/foo", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("http://example.com/foo/", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("http://example.com", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("/", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("https://", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http:foo", allowed));
|
||||
}
|
||||
|
||||
TEST(nix_isAllowedURI, https) {
|
||||
Strings allowed;
|
||||
allowed.push_back("https://");
|
||||
|
||||
ASSERT_TRUE(isAllowedURI("https://example.com", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("https://example.com/foo", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.com", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.com/https:", allowed));
|
||||
}
|
||||
|
||||
TEST(nix_isAllowedURI, absolute_path) {
|
||||
Strings allowed;
|
||||
allowed.push_back("/var/evil"); // bad idea
|
||||
|
||||
ASSERT_TRUE(isAllowedURI("/var/evil", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("/var/evil/", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("/var/evil/foo", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("/var/evil/foo/", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("/", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("/var/evi", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("/var/evilo", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("/var/evilo/", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("/var/evilo/foo", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.com/var/evil", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.com//var/evil", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.com//var/evil/foo", allowed));
|
||||
}
|
||||
|
||||
TEST(nix_isAllowedURI, file_url) {
|
||||
Strings allowed;
|
||||
allowed.push_back("file:///var/evil"); // bad idea
|
||||
|
||||
ASSERT_TRUE(isAllowedURI("file:///var/evil", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("file:///var/evil/", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("file:///var/evil/foo", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("file:///var/evil/foo/", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("/", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("/var/evi", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("/var/evilo", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("/var/evilo/", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("/var/evilo/foo", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.com/var/evil", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.com//var/evil", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://example.com//var/evil/foo", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://var/evil", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http:///var/evil", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("http://var/evil/", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("file:///var/evi", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("file:///var/evilo", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("file:///var/evilo/", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("file:///var/evilo/foo", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("file:///", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("file://", allowed));
|
||||
}
|
||||
|
||||
TEST(nix_isAllowedURI, github_all) {
|
||||
Strings allowed;
|
||||
allowed.push_back("github:");
|
||||
ASSERT_TRUE(isAllowedURI("github:", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("github:foo/bar", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("github:foo/bar/feat-multi-bar", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("github:foo/bar?ref=refs/heads/feat-multi-bar", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("github://foo/bar", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("https://github:443/foo/bar/archive/master.tar.gz", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("file://github:foo/bar/archive/master.tar.gz", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("file:///github:foo/bar/archive/master.tar.gz", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("github", allowed));
|
||||
}
|
||||
|
||||
TEST(nix_isAllowedURI, github_org) {
|
||||
Strings allowed;
|
||||
allowed.push_back("github:foo");
|
||||
ASSERT_FALSE(isAllowedURI("github:", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("github:foo/bar", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("github:foo/bar/feat-multi-bar", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("github:foo/bar?ref=refs/heads/feat-multi-bar", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("github://foo/bar", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("https://github:443/foo/bar/archive/master.tar.gz", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("file://github:foo/bar/archive/master.tar.gz", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("file:///github:foo/bar/archive/master.tar.gz", allowed));
|
||||
}
|
||||
|
||||
TEST(nix_isAllowedURI, non_scheme_colon) {
|
||||
Strings allowed;
|
||||
allowed.push_back("https://foo/bar:");
|
||||
ASSERT_TRUE(isAllowedURI("https://foo/bar:", allowed));
|
||||
ASSERT_TRUE(isAllowedURI("https://foo/bar:/baz", allowed));
|
||||
ASSERT_FALSE(isAllowedURI("https://foo/bar:baz", allowed));
|
||||
}
|
||||
|
||||
class EvalStateTest : public LibExprTest {};
|
||||
|
||||
TEST_F(EvalStateTest, getBuiltins_ok) {
|
||||
auto evaled = maybeThunk("builtins");
|
||||
auto & builtins = state.getBuiltins();
|
||||
ASSERT_TRUE(builtins.type() == nAttrs);
|
||||
ASSERT_EQ(evaled, &builtins);
|
||||
}
|
||||
|
||||
TEST_F(EvalStateTest, getBuiltin_ok) {
|
||||
auto & builtin = state.getBuiltin("toString");
|
||||
ASSERT_TRUE(builtin.type() == nFunction);
|
||||
// FIXME
|
||||
// auto evaled = maybeThunk("builtins.toString");
|
||||
// ASSERT_EQ(evaled, &builtin);
|
||||
auto & builtin2 = state.getBuiltin("true");
|
||||
ASSERT_EQ(state.forceBool(builtin2, noPos, "in unit test"), true);
|
||||
}
|
||||
|
||||
TEST_F(EvalStateTest, getBuiltin_fail) {
|
||||
ASSERT_THROW(state.getBuiltin("nonexistent"), EvalError);
|
||||
}
|
||||
|
||||
} // namespace nix
|
68
src/libexpr-tests/json.cc
Normal file
68
src/libexpr-tests/json.cc
Normal file
|
@ -0,0 +1,68 @@
|
|||
#include "tests/libexpr.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;
|
||||
NixStringContext 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(state.rootPath(CanonPath("/test")));
|
||||
ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\"");
|
||||
}
|
||||
} /* namespace nix */
|
42
src/libexpr-tests/main.cc
Normal file
42
src/libexpr-tests/main.cc
Normal file
|
@ -0,0 +1,42 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <cstdlib>
|
||||
#include "globals.hh"
|
||||
#include "logging.hh"
|
||||
|
||||
using namespace nix;
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
if (argc > 1 && std::string_view(argv[1]) == "__build-remote") {
|
||||
printError("test-build-remote: not supported in libexpr unit tests");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Disable build hook. We won't be testing remote builds in these unit tests. If we do, fix the above build hook.
|
||||
settings.buildHook = {};
|
||||
|
||||
#if __linux__ // should match the conditional around sandboxBuildDir declaration.
|
||||
|
||||
// When building and testing nix within the host's Nix sandbox, our store dir will be located in the host's sandboxBuildDir, e.g.:
|
||||
// Host
|
||||
// storeDir = /nix/store
|
||||
// sandboxBuildDir = /build
|
||||
// This process
|
||||
// storeDir = /build/foo/bar/store
|
||||
// sandboxBuildDir = /build
|
||||
// However, we have a rule that the store dir must not be inside the storeDir, so we need to pick a different sandboxBuildDir.
|
||||
settings.sandboxBuildDir = "/test-build-dir-instead-of-usual-build-dir";
|
||||
#endif
|
||||
|
||||
#if __APPLE__
|
||||
// Avoid this error, when already running in a sandbox:
|
||||
// sandbox-exec: sandbox_apply: Operation not permitted
|
||||
settings.sandboxMode = smDisabled;
|
||||
setEnv("_NIX_TEST_NO_SANDBOX", "1");
|
||||
#endif
|
||||
|
||||
// For pipe operator tests in trivial.cc
|
||||
experimentalFeatureSettings.set("experimental-features", "pipe-operators");
|
||||
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
91
src/libexpr-tests/meson.build
Normal file
91
src/libexpr-tests/meson.build
Normal file
|
@ -0,0 +1,91 @@
|
|||
project('nix-expr-tests', 'cpp',
|
||||
version : files('.version'),
|
||||
default_options : [
|
||||
'cpp_std=c++2a',
|
||||
# TODO(Qyriad): increase the warning level
|
||||
'warning_level=1',
|
||||
'debug=true',
|
||||
'optimization=2',
|
||||
'errorlogs=true', # Please print logs for tests that fail
|
||||
],
|
||||
meson_version : '>= 1.1',
|
||||
license : 'LGPL-2.1-or-later',
|
||||
)
|
||||
|
||||
cxx = meson.get_compiler('cpp')
|
||||
|
||||
subdir('build-utils-meson/deps-lists')
|
||||
|
||||
deps_private_maybe_subproject = [
|
||||
dependency('nix-expr'),
|
||||
dependency('nix-expr-c'),
|
||||
dependency('nix-expr-test-support'),
|
||||
]
|
||||
deps_public_maybe_subproject = [
|
||||
]
|
||||
subdir('build-utils-meson/subprojects')
|
||||
|
||||
subdir('build-utils-meson/export-all-symbols')
|
||||
subdir('build-utils-meson/windows-version')
|
||||
|
||||
rapidcheck = dependency('rapidcheck')
|
||||
deps_private += rapidcheck
|
||||
|
||||
gtest = dependency('gtest')
|
||||
deps_private += gtest
|
||||
|
||||
gtest = dependency('gmock')
|
||||
deps_private += gtest
|
||||
|
||||
add_project_arguments(
|
||||
# TODO(Qyriad): Yes this is how the autoconf+Make system did it.
|
||||
# It would be nice for our headers to be idempotent instead.
|
||||
'-include', 'config-util.hh',
|
||||
'-include', 'config-store.hh',
|
||||
'-include', 'config-expr.hh',
|
||||
'-include', 'config-util.h',
|
||||
'-include', 'config-store.h',
|
||||
'-include', 'config-expr.h',
|
||||
language : 'cpp',
|
||||
)
|
||||
|
||||
subdir('build-utils-meson/common')
|
||||
|
||||
sources = files(
|
||||
'derived-path.cc',
|
||||
'error_traces.cc',
|
||||
'eval.cc',
|
||||
'json.cc',
|
||||
'main.cc',
|
||||
'nix_api_expr.cc',
|
||||
'nix_api_external.cc',
|
||||
'nix_api_value.cc',
|
||||
'primops.cc',
|
||||
'search-path.cc',
|
||||
'trivial.cc',
|
||||
'value/context.cc',
|
||||
'value/print.cc',
|
||||
'value/value.cc',
|
||||
)
|
||||
|
||||
include_dirs = [include_directories('.')]
|
||||
|
||||
|
||||
this_exe = executable(
|
||||
meson.project_name(),
|
||||
sources,
|
||||
dependencies : deps_private_subproject + deps_private + deps_other,
|
||||
include_directories : include_dirs,
|
||||
# TODO: -lrapidcheck, see ../libutil-support/build.meson
|
||||
link_args: linker_export_flags + ['-lrapidcheck'],
|
||||
install : true,
|
||||
)
|
||||
|
||||
test(
|
||||
meson.project_name(),
|
||||
this_exe,
|
||||
env : {
|
||||
'_NIX_TEST_UNIT_DATA': meson.current_source_dir() / 'data',
|
||||
},
|
||||
protocol : 'gtest',
|
||||
)
|
404
src/libexpr-tests/nix_api_expr.cc
Normal file
404
src/libexpr-tests/nix_api_expr.cc
Normal file
|
@ -0,0 +1,404 @@
|
|||
#include "nix_api_store.h"
|
||||
#include "nix_api_store_internal.h"
|
||||
#include "nix_api_util.h"
|
||||
#include "nix_api_util_internal.h"
|
||||
#include "nix_api_expr.h"
|
||||
#include "nix_api_value.h"
|
||||
|
||||
#include "tests/nix_api_expr.hh"
|
||||
#include "tests/string_callback.hh"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace nixC {
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_eval_from_string)
|
||||
{
|
||||
nix_expr_eval_from_string(nullptr, state, "builtins.nixVersion", ".", value);
|
||||
nix_value_force(nullptr, state, value);
|
||||
std::string result;
|
||||
nix_get_string(nullptr, value, OBSERVE_STRING(result));
|
||||
|
||||
ASSERT_STREQ(PACKAGE_VERSION, result.c_str());
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_eval_add_numbers)
|
||||
{
|
||||
nix_expr_eval_from_string(nullptr, state, "1 + 1", ".", value);
|
||||
nix_value_force(nullptr, state, value);
|
||||
auto result = nix_get_int(nullptr, value);
|
||||
|
||||
ASSERT_EQ(2, result);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_eval_drv)
|
||||
{
|
||||
auto expr = R"(derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; })";
|
||||
nix_expr_eval_from_string(nullptr, state, expr, ".", value);
|
||||
ASSERT_EQ(NIX_TYPE_ATTRS, nix_get_type(nullptr, value));
|
||||
|
||||
EvalState * stateFn = nix_state_create(nullptr, nullptr, store);
|
||||
nix_value * valueFn = nix_alloc_value(nullptr, state);
|
||||
nix_expr_eval_from_string(nullptr, stateFn, "builtins.toString", ".", valueFn);
|
||||
ASSERT_EQ(NIX_TYPE_FUNCTION, nix_get_type(nullptr, valueFn));
|
||||
|
||||
EvalState * stateResult = nix_state_create(nullptr, nullptr, store);
|
||||
nix_value * valueResult = nix_alloc_value(nullptr, stateResult);
|
||||
nix_value_call(ctx, stateResult, valueFn, value, valueResult);
|
||||
ASSERT_EQ(NIX_TYPE_STRING, nix_get_type(nullptr, valueResult));
|
||||
|
||||
std::string p;
|
||||
nix_get_string(nullptr, valueResult, OBSERVE_STRING(p));
|
||||
std::string pEnd = "-myname";
|
||||
ASSERT_EQ(pEnd, p.substr(p.size() - pEnd.size()));
|
||||
|
||||
// Clean up
|
||||
nix_gc_decref(nullptr, valueFn);
|
||||
nix_state_free(stateFn);
|
||||
|
||||
nix_gc_decref(nullptr, valueResult);
|
||||
nix_state_free(stateResult);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_build_drv)
|
||||
{
|
||||
auto expr = R"(derivation { name = "myname";
|
||||
system = builtins.currentSystem;
|
||||
builder = "/bin/sh";
|
||||
args = [ "-c" "echo foo > $out" ];
|
||||
})";
|
||||
nix_expr_eval_from_string(nullptr, state, expr, ".", value);
|
||||
|
||||
nix_value * drvPathValue = nix_get_attr_byname(nullptr, value, state, "drvPath");
|
||||
std::string drvPath;
|
||||
nix_get_string(nullptr, drvPathValue, OBSERVE_STRING(drvPath));
|
||||
|
||||
std::string p = drvPath;
|
||||
std::string pEnd = "-myname.drv";
|
||||
ASSERT_EQ(pEnd, p.substr(p.size() - pEnd.size()));
|
||||
|
||||
// NOTE: .drvPath should be usually be ignored. Output paths are more versatile.
|
||||
// See https://github.com/NixOS/nix/issues/6507
|
||||
// Use e.g. nix_string_realise to realise the output.
|
||||
StorePath * drvStorePath = nix_store_parse_path(ctx, store, drvPath.c_str());
|
||||
ASSERT_EQ(true, nix_store_is_valid_path(ctx, store, drvStorePath));
|
||||
|
||||
nix_value * outPathValue = nix_get_attr_byname(ctx, value, state, "outPath");
|
||||
std::string outPath;
|
||||
nix_get_string(ctx, outPathValue, OBSERVE_STRING(outPath));
|
||||
|
||||
p = outPath;
|
||||
pEnd = "-myname";
|
||||
ASSERT_EQ(pEnd, p.substr(p.size() - pEnd.size()));
|
||||
ASSERT_EQ(true, drvStorePath->path.isDerivation());
|
||||
|
||||
StorePath * outStorePath = nix_store_parse_path(ctx, store, outPath.c_str());
|
||||
ASSERT_EQ(false, nix_store_is_valid_path(ctx, store, outStorePath));
|
||||
|
||||
nix_store_realise(ctx, store, drvStorePath, nullptr, nullptr);
|
||||
auto is_valid_path = nix_store_is_valid_path(ctx, store, outStorePath);
|
||||
ASSERT_EQ(true, is_valid_path);
|
||||
|
||||
// Clean up
|
||||
nix_store_path_free(drvStorePath);
|
||||
nix_store_path_free(outStorePath);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_realise_context_bad_value)
|
||||
{
|
||||
auto expr = "true";
|
||||
nix_expr_eval_from_string(ctx, state, expr, ".", value);
|
||||
assert_ctx_ok();
|
||||
auto r = nix_string_realise(ctx, state, value, false);
|
||||
ASSERT_EQ(nullptr, r);
|
||||
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
|
||||
ASSERT_THAT(ctx->last_err, testing::Optional(testing::HasSubstr("cannot coerce")));
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_realise_context_bad_build)
|
||||
{
|
||||
auto expr = R"(
|
||||
derivation { name = "letsbuild";
|
||||
system = builtins.currentSystem;
|
||||
builder = "/bin/sh";
|
||||
args = [ "-c" "echo failing a build for testing purposes; exit 1;" ];
|
||||
}
|
||||
)";
|
||||
nix_expr_eval_from_string(ctx, state, expr, ".", value);
|
||||
assert_ctx_ok();
|
||||
auto r = nix_string_realise(ctx, state, value, false);
|
||||
ASSERT_EQ(nullptr, r);
|
||||
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
|
||||
ASSERT_THAT(ctx->last_err, testing::Optional(testing::HasSubstr("failed with exit code 1")));
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_realise_context)
|
||||
{
|
||||
// TODO (ca-derivations): add a content-addressed derivation output, which produces a placeholder
|
||||
auto expr = R"(
|
||||
''
|
||||
a derivation output: ${
|
||||
derivation { name = "letsbuild";
|
||||
system = builtins.currentSystem;
|
||||
builder = "/bin/sh";
|
||||
args = [ "-c" "echo foo > $out" ];
|
||||
}}
|
||||
a path: ${builtins.toFile "just-a-file" "ooh file good"}
|
||||
a derivation path by itself: ${
|
||||
builtins.unsafeDiscardOutputDependency
|
||||
(derivation {
|
||||
name = "not-actually-built-yet";
|
||||
system = builtins.currentSystem;
|
||||
builder = "/bin/sh";
|
||||
args = [ "-c" "echo foo > $out" ];
|
||||
}).drvPath}
|
||||
''
|
||||
)";
|
||||
nix_expr_eval_from_string(ctx, state, expr, ".", value);
|
||||
assert_ctx_ok();
|
||||
auto r = nix_string_realise(ctx, state, value, false);
|
||||
assert_ctx_ok();
|
||||
ASSERT_NE(nullptr, r);
|
||||
|
||||
auto s = std::string(nix_realised_string_get_buffer_start(r), nix_realised_string_get_buffer_size(r));
|
||||
|
||||
EXPECT_THAT(s, testing::StartsWith("a derivation output:"));
|
||||
EXPECT_THAT(s, testing::HasSubstr("-letsbuild\n"));
|
||||
EXPECT_THAT(s, testing::Not(testing::HasSubstr("-letsbuild.drv")));
|
||||
EXPECT_THAT(s, testing::HasSubstr("a path:"));
|
||||
EXPECT_THAT(s, testing::HasSubstr("-just-a-file"));
|
||||
EXPECT_THAT(s, testing::Not(testing::HasSubstr("-just-a-file.drv")));
|
||||
EXPECT_THAT(s, testing::Not(testing::HasSubstr("ooh file good")));
|
||||
EXPECT_THAT(s, testing::HasSubstr("a derivation path by itself:"));
|
||||
EXPECT_THAT(s, testing::EndsWith("-not-actually-built-yet.drv\n"));
|
||||
|
||||
std::vector<std::string> names;
|
||||
size_t n = nix_realised_string_get_store_path_count(r);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
const StorePath * p = nix_realised_string_get_store_path(r, i);
|
||||
ASSERT_NE(nullptr, p);
|
||||
std::string name;
|
||||
nix_store_path_name(p, OBSERVE_STRING(name));
|
||||
names.push_back(name);
|
||||
}
|
||||
std::sort(names.begin(), names.end());
|
||||
ASSERT_EQ(3, names.size());
|
||||
EXPECT_THAT(names[0], testing::StrEq("just-a-file"));
|
||||
EXPECT_THAT(names[1], testing::StrEq("letsbuild"));
|
||||
EXPECT_THAT(names[2], testing::StrEq("not-actually-built-yet.drv"));
|
||||
|
||||
nix_realised_string_free(r);
|
||||
}
|
||||
|
||||
const char * SAMPLE_USER_DATA = "whatever";
|
||||
|
||||
static void
|
||||
primop_square(void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
|
||||
{
|
||||
assert(context);
|
||||
assert(state);
|
||||
assert(user_data == SAMPLE_USER_DATA);
|
||||
auto i = nix_get_int(context, args[0]);
|
||||
nix_init_int(context, ret, i * i);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_primop)
|
||||
{
|
||||
PrimOp * primop =
|
||||
nix_alloc_primop(ctx, primop_square, 1, "square", nullptr, "square an integer", (void *) SAMPLE_USER_DATA);
|
||||
assert_ctx_ok();
|
||||
nix_value * primopValue = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_primop(ctx, primopValue, primop);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * three = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_int(ctx, three, 3);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * result = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_value_call(ctx, state, primopValue, three, result);
|
||||
assert_ctx_ok();
|
||||
|
||||
auto r = nix_get_int(ctx, result);
|
||||
ASSERT_EQ(9, r);
|
||||
}
|
||||
|
||||
static void
|
||||
primop_repeat(void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
|
||||
{
|
||||
assert(context);
|
||||
assert(state);
|
||||
assert(user_data == SAMPLE_USER_DATA);
|
||||
|
||||
// Get the string to repeat
|
||||
std::string s;
|
||||
if (nix_get_string(context, args[0], OBSERVE_STRING(s)) != NIX_OK)
|
||||
return;
|
||||
|
||||
// Get the number of times to repeat
|
||||
auto n = nix_get_int(context, args[1]);
|
||||
if (nix_err_code(context) != NIX_OK)
|
||||
return;
|
||||
|
||||
// Repeat the string
|
||||
std::string result;
|
||||
for (int i = 0; i < n; ++i)
|
||||
result += s;
|
||||
|
||||
nix_init_string(context, ret, result.c_str());
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_primop_arity_2_multiple_calls)
|
||||
{
|
||||
PrimOp * primop =
|
||||
nix_alloc_primop(ctx, primop_repeat, 2, "repeat", nullptr, "repeat a string", (void *) SAMPLE_USER_DATA);
|
||||
assert_ctx_ok();
|
||||
nix_value * primopValue = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_primop(ctx, primopValue, primop);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * hello = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_string(ctx, hello, "hello");
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * three = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_int(ctx, three, 3);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * partial = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_value_call(ctx, state, primopValue, hello, partial);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * result = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_value_call(ctx, state, partial, three, result);
|
||||
assert_ctx_ok();
|
||||
|
||||
std::string r;
|
||||
nix_get_string(ctx, result, OBSERVE_STRING(r));
|
||||
ASSERT_STREQ("hellohellohello", r.c_str());
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_primop_arity_2_single_call)
|
||||
{
|
||||
PrimOp * primop =
|
||||
nix_alloc_primop(ctx, primop_repeat, 2, "repeat", nullptr, "repeat a string", (void *) SAMPLE_USER_DATA);
|
||||
assert_ctx_ok();
|
||||
nix_value * primopValue = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_primop(ctx, primopValue, primop);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * hello = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_string(ctx, hello, "hello");
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * three = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_int(ctx, three, 3);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * result = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
NIX_VALUE_CALL(ctx, state, result, primopValue, hello, three);
|
||||
assert_ctx_ok();
|
||||
|
||||
std::string r;
|
||||
nix_get_string(ctx, result, OBSERVE_STRING(r));
|
||||
assert_ctx_ok();
|
||||
|
||||
ASSERT_STREQ("hellohellohello", r.c_str());
|
||||
}
|
||||
|
||||
static void
|
||||
primop_bad_no_return(void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
|
||||
{
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_primop_bad_no_return)
|
||||
{
|
||||
PrimOp * primop =
|
||||
nix_alloc_primop(ctx, primop_bad_no_return, 1, "badNoReturn", nullptr, "a broken primop", nullptr);
|
||||
assert_ctx_ok();
|
||||
nix_value * primopValue = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_primop(ctx, primopValue, primop);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * three = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_int(ctx, three, 3);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * result = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_value_call(ctx, state, primopValue, three, result);
|
||||
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
|
||||
ASSERT_THAT(
|
||||
ctx->last_err,
|
||||
testing::Optional(
|
||||
testing::HasSubstr("Implementation error in custom function: return value was not initialized")));
|
||||
ASSERT_THAT(ctx->last_err, testing::Optional(testing::HasSubstr("badNoReturn")));
|
||||
}
|
||||
|
||||
static void primop_bad_return_thunk(
|
||||
void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
|
||||
{
|
||||
nix_init_apply(context, ret, args[0], args[1]);
|
||||
}
|
||||
TEST_F(nix_api_expr_test, nix_expr_primop_bad_return_thunk)
|
||||
{
|
||||
PrimOp * primop =
|
||||
nix_alloc_primop(ctx, primop_bad_return_thunk, 2, "badReturnThunk", nullptr, "a broken primop", nullptr);
|
||||
assert_ctx_ok();
|
||||
nix_value * primopValue = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_primop(ctx, primopValue, primop);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * toString = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_expr_eval_from_string(ctx, state, "builtins.toString", ".", toString);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * four = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
nix_init_int(ctx, four, 4);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * result = nix_alloc_value(ctx, state);
|
||||
assert_ctx_ok();
|
||||
NIX_VALUE_CALL(ctx, state, result, primopValue, toString, four);
|
||||
|
||||
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
|
||||
ASSERT_THAT(
|
||||
ctx->last_err,
|
||||
testing::Optional(
|
||||
testing::HasSubstr("Implementation error in custom function: return value must not be a thunk")));
|
||||
ASSERT_THAT(ctx->last_err, testing::Optional(testing::HasSubstr("badReturnThunk")));
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_call_multi_no_args)
|
||||
{
|
||||
nix_value * n = nix_alloc_value(ctx, state);
|
||||
nix_init_int(ctx, n, 3);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * r = nix_alloc_value(ctx, state);
|
||||
nix_value_call_multi(ctx, state, n, 0, nullptr, r);
|
||||
assert_ctx_ok();
|
||||
|
||||
auto rInt = nix_get_int(ctx, r);
|
||||
assert_ctx_ok();
|
||||
ASSERT_EQ(3, rInt);
|
||||
}
|
||||
} // namespace nixC
|
68
src/libexpr-tests/nix_api_external.cc
Normal file
68
src/libexpr-tests/nix_api_external.cc
Normal file
|
@ -0,0 +1,68 @@
|
|||
#include "nix_api_store.h"
|
||||
#include "nix_api_store_internal.h"
|
||||
#include "nix_api_util.h"
|
||||
#include "nix_api_util_internal.h"
|
||||
#include "nix_api_expr.h"
|
||||
#include "nix_api_expr_internal.h"
|
||||
#include "nix_api_value.h"
|
||||
#include "nix_api_external.h"
|
||||
|
||||
#include "tests/nix_api_expr.hh"
|
||||
#include "tests/string_callback.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace nixC {
|
||||
|
||||
class MyExternalValueDesc : public NixCExternalValueDesc
|
||||
{
|
||||
public:
|
||||
MyExternalValueDesc(int x)
|
||||
: _x(x)
|
||||
{
|
||||
print = print_function;
|
||||
showType = show_type_function;
|
||||
typeOf = type_of_function;
|
||||
}
|
||||
|
||||
private:
|
||||
int _x;
|
||||
static void print_function(void * self, nix_printer * printer) {}
|
||||
|
||||
static void show_type_function(void * self, nix_string_return * res) {}
|
||||
|
||||
static void type_of_function(void * self, nix_string_return * res)
|
||||
{
|
||||
MyExternalValueDesc * obj = static_cast<MyExternalValueDesc *>(self);
|
||||
|
||||
std::string type_string = "nix-external<MyExternalValueDesc( ";
|
||||
type_string += std::to_string(obj->_x);
|
||||
type_string += " )>";
|
||||
res->str = &*type_string.begin();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_eval_external)
|
||||
{
|
||||
MyExternalValueDesc * external = new MyExternalValueDesc(42);
|
||||
ExternalValue * val = nix_create_external_value(ctx, external, external);
|
||||
nix_init_external(ctx, value, val);
|
||||
|
||||
EvalState * stateResult = nix_state_create(nullptr, nullptr, store);
|
||||
nix_value * valueResult = nix_alloc_value(nullptr, stateResult);
|
||||
|
||||
EvalState * stateFn = nix_state_create(nullptr, nullptr, store);
|
||||
nix_value * valueFn = nix_alloc_value(nullptr, stateFn);
|
||||
|
||||
nix_expr_eval_from_string(nullptr, state, "builtins.typeOf", ".", valueFn);
|
||||
|
||||
ASSERT_EQ(NIX_TYPE_EXTERNAL, nix_get_type(nullptr, value));
|
||||
|
||||
nix_value_call(ctx, state, valueFn, value, valueResult);
|
||||
|
||||
std::string string_value;
|
||||
nix_get_string(nullptr, valueResult, OBSERVE_STRING(string_value));
|
||||
ASSERT_STREQ("nix-external<MyExternalValueDesc( 42 )>", string_value.c_str());
|
||||
}
|
||||
|
||||
}
|
402
src/libexpr-tests/nix_api_value.cc
Normal file
402
src/libexpr-tests/nix_api_value.cc
Normal file
|
@ -0,0 +1,402 @@
|
|||
#include "nix_api_store.h"
|
||||
#include "nix_api_store_internal.h"
|
||||
#include "nix_api_util.h"
|
||||
#include "nix_api_util_internal.h"
|
||||
#include "nix_api_expr.h"
|
||||
#include "nix_api_value.h"
|
||||
#include "nix_api_expr_internal.h"
|
||||
|
||||
#include "tests/nix_api_expr.hh"
|
||||
#include "tests/string_callback.hh"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace nixC {
|
||||
|
||||
TEST_F(nix_api_expr_test, as_nix_value_ptr)
|
||||
{
|
||||
// nix_alloc_value casts nix::Value to nix_value
|
||||
// It should be obvious from the decl that that works, but if it doesn't,
|
||||
// the whole implementation would be utterly broken.
|
||||
ASSERT_EQ(sizeof(nix::Value), sizeof(nix_value));
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_get_int_invalid)
|
||||
{
|
||||
ASSERT_EQ(0, nix_get_int(ctx, nullptr));
|
||||
assert_ctx_err();
|
||||
ASSERT_EQ(0, nix_get_int(ctx, value));
|
||||
assert_ctx_err();
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_set_get_int)
|
||||
{
|
||||
int myInt = 1;
|
||||
nix_init_int(ctx, value, myInt);
|
||||
|
||||
ASSERT_EQ(myInt, nix_get_int(ctx, value));
|
||||
ASSERT_STREQ("an integer", nix_get_typename(ctx, value));
|
||||
ASSERT_EQ(NIX_TYPE_INT, nix_get_type(ctx, value));
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_set_get_float_invalid)
|
||||
{
|
||||
ASSERT_DOUBLE_EQ(0.0, nix_get_float(ctx, nullptr));
|
||||
assert_ctx_err();
|
||||
ASSERT_DOUBLE_EQ(0.0, nix_get_float(ctx, value));
|
||||
assert_ctx_err();
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_set_get_float)
|
||||
{
|
||||
double myDouble = 1.0;
|
||||
nix_init_float(ctx, value, myDouble);
|
||||
|
||||
ASSERT_DOUBLE_EQ(myDouble, nix_get_float(ctx, value));
|
||||
ASSERT_STREQ("a float", nix_get_typename(ctx, value));
|
||||
ASSERT_EQ(NIX_TYPE_FLOAT, nix_get_type(ctx, value));
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_set_get_bool_invalid)
|
||||
{
|
||||
ASSERT_EQ(false, nix_get_bool(ctx, nullptr));
|
||||
assert_ctx_err();
|
||||
ASSERT_EQ(false, nix_get_bool(ctx, value));
|
||||
assert_ctx_err();
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_set_get_bool)
|
||||
{
|
||||
bool myBool = true;
|
||||
nix_init_bool(ctx, value, myBool);
|
||||
|
||||
ASSERT_EQ(myBool, nix_get_bool(ctx, value));
|
||||
ASSERT_STREQ("a Boolean", nix_get_typename(ctx, value));
|
||||
ASSERT_EQ(NIX_TYPE_BOOL, nix_get_type(ctx, value));
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_set_get_string_invalid)
|
||||
{
|
||||
std::string string_value;
|
||||
ASSERT_EQ(NIX_ERR_UNKNOWN, nix_get_string(ctx, nullptr, OBSERVE_STRING(string_value)));
|
||||
assert_ctx_err();
|
||||
ASSERT_EQ(NIX_ERR_UNKNOWN, nix_get_string(ctx, value, OBSERVE_STRING(string_value)));
|
||||
assert_ctx_err();
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_set_get_string)
|
||||
{
|
||||
std::string string_value;
|
||||
const char * myString = "some string";
|
||||
nix_init_string(ctx, value, myString);
|
||||
|
||||
nix_get_string(ctx, value, OBSERVE_STRING(string_value));
|
||||
ASSERT_STREQ(myString, string_value.c_str());
|
||||
ASSERT_STREQ("a string", nix_get_typename(ctx, value));
|
||||
ASSERT_EQ(NIX_TYPE_STRING, nix_get_type(ctx, value));
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_set_get_null_invalid)
|
||||
{
|
||||
ASSERT_EQ(NULL, nix_get_typename(ctx, value));
|
||||
assert_ctx_err();
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_set_get_null)
|
||||
{
|
||||
nix_init_null(ctx, value);
|
||||
|
||||
ASSERT_STREQ("null", nix_get_typename(ctx, value));
|
||||
ASSERT_EQ(NIX_TYPE_NULL, nix_get_type(ctx, value));
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_set_get_path_invalid)
|
||||
{
|
||||
ASSERT_EQ(nullptr, nix_get_path_string(ctx, nullptr));
|
||||
assert_ctx_err();
|
||||
ASSERT_EQ(nullptr, nix_get_path_string(ctx, value));
|
||||
assert_ctx_err();
|
||||
}
|
||||
TEST_F(nix_api_expr_test, nix_value_set_get_path)
|
||||
{
|
||||
const char * p = "/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname";
|
||||
nix_init_path_string(ctx, state, value, p);
|
||||
|
||||
ASSERT_STREQ(p, nix_get_path_string(ctx, value));
|
||||
ASSERT_STREQ("a path", nix_get_typename(ctx, value));
|
||||
ASSERT_EQ(NIX_TYPE_PATH, nix_get_type(ctx, value));
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_build_and_init_list_invalid)
|
||||
{
|
||||
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, nullptr, state, 0));
|
||||
assert_ctx_err();
|
||||
ASSERT_EQ(0, nix_get_list_size(ctx, nullptr));
|
||||
assert_ctx_err();
|
||||
|
||||
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, value, state, 0));
|
||||
assert_ctx_err();
|
||||
ASSERT_EQ(0, nix_get_list_size(ctx, value));
|
||||
assert_ctx_err();
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_build_and_init_list)
|
||||
{
|
||||
int size = 10;
|
||||
ListBuilder * builder = nix_make_list_builder(ctx, state, size);
|
||||
|
||||
nix_value * intValue = nix_alloc_value(ctx, state);
|
||||
nix_value * intValue2 = nix_alloc_value(ctx, state);
|
||||
|
||||
// `init` and `insert` can be called in any order
|
||||
nix_init_int(ctx, intValue, 42);
|
||||
nix_list_builder_insert(ctx, builder, 0, intValue);
|
||||
nix_list_builder_insert(ctx, builder, 1, intValue2);
|
||||
nix_init_int(ctx, intValue2, 43);
|
||||
|
||||
nix_make_list(ctx, builder, value);
|
||||
nix_list_builder_free(builder);
|
||||
|
||||
ASSERT_EQ(42, nix_get_int(ctx, nix_get_list_byidx(ctx, value, state, 0)));
|
||||
ASSERT_EQ(43, nix_get_int(ctx, nix_get_list_byidx(ctx, value, state, 1)));
|
||||
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, value, state, 2));
|
||||
ASSERT_EQ(10, nix_get_list_size(ctx, value));
|
||||
|
||||
ASSERT_STREQ("a list", nix_get_typename(ctx, value));
|
||||
ASSERT_EQ(NIX_TYPE_LIST, nix_get_type(ctx, value));
|
||||
|
||||
// Clean up
|
||||
nix_gc_decref(ctx, intValue);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_build_and_init_attr_invalid)
|
||||
{
|
||||
ASSERT_EQ(nullptr, nix_get_attr_byname(ctx, nullptr, state, 0));
|
||||
assert_ctx_err();
|
||||
ASSERT_EQ(nullptr, nix_get_attr_byidx(ctx, nullptr, state, 0, nullptr));
|
||||
assert_ctx_err();
|
||||
ASSERT_EQ(nullptr, nix_get_attr_name_byidx(ctx, nullptr, state, 0));
|
||||
assert_ctx_err();
|
||||
ASSERT_EQ(0, nix_get_attrs_size(ctx, nullptr));
|
||||
assert_ctx_err();
|
||||
ASSERT_EQ(false, nix_has_attr_byname(ctx, nullptr, state, "no-value"));
|
||||
assert_ctx_err();
|
||||
|
||||
ASSERT_EQ(nullptr, nix_get_attr_byname(ctx, value, state, 0));
|
||||
assert_ctx_err();
|
||||
ASSERT_EQ(nullptr, nix_get_attr_byidx(ctx, value, state, 0, nullptr));
|
||||
assert_ctx_err();
|
||||
ASSERT_EQ(nullptr, nix_get_attr_name_byidx(ctx, value, state, 0));
|
||||
assert_ctx_err();
|
||||
ASSERT_EQ(0, nix_get_attrs_size(ctx, value));
|
||||
assert_ctx_err();
|
||||
ASSERT_EQ(false, nix_has_attr_byname(ctx, value, state, "no-value"));
|
||||
assert_ctx_err();
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_build_and_init_attr)
|
||||
{
|
||||
int size = 10;
|
||||
const char ** out_name = (const char **) malloc(sizeof(char *));
|
||||
|
||||
BindingsBuilder * builder = nix_make_bindings_builder(ctx, state, size);
|
||||
|
||||
nix_value * intValue = nix_alloc_value(ctx, state);
|
||||
nix_init_int(ctx, intValue, 42);
|
||||
|
||||
nix_value * stringValue = nix_alloc_value(ctx, state);
|
||||
nix_init_string(ctx, stringValue, "foo");
|
||||
|
||||
nix_bindings_builder_insert(ctx, builder, "a", intValue);
|
||||
nix_bindings_builder_insert(ctx, builder, "b", stringValue);
|
||||
nix_make_attrs(ctx, value, builder);
|
||||
nix_bindings_builder_free(builder);
|
||||
|
||||
ASSERT_EQ(2, nix_get_attrs_size(ctx, value));
|
||||
|
||||
nix_value * out_value = nix_get_attr_byname(ctx, value, state, "a");
|
||||
ASSERT_EQ(42, nix_get_int(ctx, out_value));
|
||||
nix_gc_decref(ctx, out_value);
|
||||
|
||||
out_value = nix_get_attr_byidx(ctx, value, state, 0, out_name);
|
||||
ASSERT_EQ(42, nix_get_int(ctx, out_value));
|
||||
ASSERT_STREQ("a", *out_name);
|
||||
nix_gc_decref(ctx, out_value);
|
||||
|
||||
ASSERT_STREQ("a", nix_get_attr_name_byidx(ctx, value, state, 0));
|
||||
|
||||
ASSERT_EQ(true, nix_has_attr_byname(ctx, value, state, "b"));
|
||||
ASSERT_EQ(false, nix_has_attr_byname(ctx, value, state, "no-value"));
|
||||
|
||||
out_value = nix_get_attr_byname(ctx, value, state, "b");
|
||||
std::string string_value;
|
||||
nix_get_string(ctx, out_value, OBSERVE_STRING(string_value));
|
||||
ASSERT_STREQ("foo", string_value.c_str());
|
||||
nix_gc_decref(nullptr, out_value);
|
||||
|
||||
out_value = nix_get_attr_byidx(ctx, value, state, 1, out_name);
|
||||
nix_get_string(ctx, out_value, OBSERVE_STRING(string_value));
|
||||
ASSERT_STREQ("foo", string_value.c_str());
|
||||
ASSERT_STREQ("b", *out_name);
|
||||
nix_gc_decref(nullptr, out_value);
|
||||
|
||||
ASSERT_STREQ("b", nix_get_attr_name_byidx(ctx, value, state, 1));
|
||||
|
||||
ASSERT_STREQ("a set", nix_get_typename(ctx, value));
|
||||
ASSERT_EQ(NIX_TYPE_ATTRS, nix_get_type(ctx, value));
|
||||
|
||||
// Clean up
|
||||
nix_gc_decref(ctx, intValue);
|
||||
nix_gc_decref(ctx, stringValue);
|
||||
free(out_name);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_init)
|
||||
{
|
||||
// Setup
|
||||
|
||||
// two = 2;
|
||||
// f = a: a * a;
|
||||
|
||||
nix_value * two = nix_alloc_value(ctx, state);
|
||||
nix_init_int(ctx, two, 2);
|
||||
|
||||
nix_value * f = nix_alloc_value(ctx, state);
|
||||
nix_expr_eval_from_string(
|
||||
ctx,
|
||||
state,
|
||||
R"(
|
||||
a: a * a
|
||||
)",
|
||||
"<test>",
|
||||
f);
|
||||
|
||||
// Test
|
||||
|
||||
// r = f two;
|
||||
|
||||
nix_value * r = nix_alloc_value(ctx, state);
|
||||
nix_init_apply(ctx, r, f, two);
|
||||
assert_ctx_ok();
|
||||
|
||||
ValueType t = nix_get_type(ctx, r);
|
||||
assert_ctx_ok();
|
||||
|
||||
ASSERT_EQ(t, NIX_TYPE_THUNK);
|
||||
|
||||
nix_value_force(ctx, state, r);
|
||||
|
||||
t = nix_get_type(ctx, r);
|
||||
assert_ctx_ok();
|
||||
|
||||
ASSERT_EQ(t, NIX_TYPE_INT);
|
||||
|
||||
int n = nix_get_int(ctx, r);
|
||||
assert_ctx_ok();
|
||||
|
||||
ASSERT_EQ(n, 4);
|
||||
|
||||
// Clean up
|
||||
nix_gc_decref(ctx, two);
|
||||
nix_gc_decref(ctx, f);
|
||||
nix_gc_decref(ctx, r);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_init_apply_error)
|
||||
{
|
||||
nix_value * some_string = nix_alloc_value(ctx, state);
|
||||
nix_init_string(ctx, some_string, "some string");
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * v = nix_alloc_value(ctx, state);
|
||||
nix_init_apply(ctx, v, some_string, some_string);
|
||||
assert_ctx_ok();
|
||||
|
||||
// All ok. Call has not been evaluated yet.
|
||||
|
||||
// Evaluate it
|
||||
nix_value_force(ctx, state, v);
|
||||
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
|
||||
ASSERT_THAT(ctx->last_err.value(), testing::HasSubstr("attempt to call something which is not a function but"));
|
||||
|
||||
// Clean up
|
||||
nix_gc_decref(ctx, some_string);
|
||||
nix_gc_decref(ctx, v);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_value_init_apply_lazy_arg)
|
||||
{
|
||||
// f is a lazy function: it does not evaluate its argument before returning its return value
|
||||
// g is a helper to produce e
|
||||
// e is a thunk that throws an exception
|
||||
//
|
||||
// r = f e
|
||||
// r should not throw an exception, because e is not evaluated
|
||||
|
||||
nix_value * f = nix_alloc_value(ctx, state);
|
||||
nix_expr_eval_from_string(
|
||||
ctx,
|
||||
state,
|
||||
R"(
|
||||
a: { foo = a; }
|
||||
)",
|
||||
"<test>",
|
||||
f);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value * e = nix_alloc_value(ctx, state);
|
||||
{
|
||||
nix_value * g = nix_alloc_value(ctx, state);
|
||||
nix_expr_eval_from_string(
|
||||
ctx,
|
||||
state,
|
||||
R"(
|
||||
_ignore: throw "error message for test case nix_value_init_apply_lazy_arg"
|
||||
)",
|
||||
"<test>",
|
||||
g);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_init_apply(ctx, e, g, g);
|
||||
assert_ctx_ok();
|
||||
nix_gc_decref(ctx, g);
|
||||
}
|
||||
|
||||
nix_value * r = nix_alloc_value(ctx, state);
|
||||
nix_init_apply(ctx, r, f, e);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_value_force(ctx, state, r);
|
||||
assert_ctx_ok();
|
||||
|
||||
auto n = nix_get_attrs_size(ctx, r);
|
||||
assert_ctx_ok();
|
||||
ASSERT_EQ(1, n);
|
||||
|
||||
// nix_get_attr_byname isn't lazy (it could have been) so it will throw the exception
|
||||
nix_value * foo = nix_get_attr_byname(ctx, r, state, "foo");
|
||||
ASSERT_EQ(nullptr, foo);
|
||||
ASSERT_THAT(ctx->last_err.value(), testing::HasSubstr("error message for test case nix_value_init_apply_lazy_arg"));
|
||||
|
||||
// Clean up
|
||||
nix_gc_decref(ctx, f);
|
||||
nix_gc_decref(ctx, e);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_copy_value)
|
||||
{
|
||||
nix_value * source = nix_alloc_value(ctx, state);
|
||||
|
||||
nix_init_int(ctx, source, 42);
|
||||
nix_copy_value(ctx, value, source);
|
||||
|
||||
ASSERT_EQ(42, nix_get_int(ctx, value));
|
||||
|
||||
// Clean up
|
||||
nix_gc_decref(ctx, source);
|
||||
}
|
||||
|
||||
}
|
83
src/libexpr-tests/package.nix
Normal file
83
src/libexpr-tests/package.nix
Normal file
|
@ -0,0 +1,83 @@
|
|||
{ lib
|
||||
, buildPackages
|
||||
, stdenv
|
||||
, mkMesonExecutable
|
||||
|
||||
, nix-expr
|
||||
, nix-expr-c
|
||||
, nix-expr-test-support
|
||||
|
||||
, rapidcheck
|
||||
, gtest
|
||||
, runCommand
|
||||
|
||||
# Configuration Options
|
||||
|
||||
, version
|
||||
, resolvePath
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) fileset;
|
||||
in
|
||||
|
||||
mkMesonExecutable (finalAttrs: {
|
||||
pname = "nix-expr-tests";
|
||||
inherit version;
|
||||
|
||||
workDir = ./.;
|
||||
fileset = fileset.unions [
|
||||
../../build-utils-meson
|
||||
./build-utils-meson
|
||||
../../.version
|
||||
./.version
|
||||
./meson.build
|
||||
# ./meson.options
|
||||
(fileset.fileFilter (file: file.hasExt "cc") ./.)
|
||||
(fileset.fileFilter (file: file.hasExt "hh") ./.)
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
nix-expr
|
||||
nix-expr-c
|
||||
nix-expr-test-support
|
||||
rapidcheck
|
||||
gtest
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
mesonFlags = [
|
||||
];
|
||||
|
||||
env = lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
|
||||
LDFLAGS = "-fuse-ld=gold";
|
||||
};
|
||||
|
||||
passthru = {
|
||||
tests = {
|
||||
run = runCommand "${finalAttrs.pname}-run" {
|
||||
meta.broken = !stdenv.hostPlatform.emulatorAvailable buildPackages;
|
||||
} (lib.optionalString stdenv.hostPlatform.isWindows ''
|
||||
export HOME="$PWD/home-dir"
|
||||
mkdir -p "$HOME"
|
||||
'' + ''
|
||||
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
|
||||
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
||||
touch $out
|
||||
'');
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||
mainProgram = finalAttrs.pname + stdenv.hostPlatform.extensions.executable;
|
||||
};
|
||||
|
||||
})
|
860
src/libexpr-tests/primops.cc
Normal file
860
src/libexpr-tests/primops.cc
Normal file
|
@ -0,0 +1,860 @@
|
|||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "eval-settings.hh"
|
||||
#include "memory-source-accessor.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<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");
|
||||
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) {
|
||||
state.corepkgsFS->addFile(CanonPath("foo.nix"), "\n\r\n\r{ y = \"x\"; }");
|
||||
|
||||
auto expr = "builtins.unsafeGetAttrPos \"y\" (import <nix/foo.nix>)";
|
||||
auto v = eval(expr);
|
||||
ASSERT_THAT(v, IsAttrsOfSize(3));
|
||||
|
||||
auto file = v.attrs()->find(createSymbol("file"));
|
||||
ASSERT_NE(file, nullptr);
|
||||
ASSERT_THAT(*file->value, IsString());
|
||||
auto s = baseNameOf(file->value->string_view());
|
||||
ASSERT_EQ(s, "foo.nix");
|
||||
|
||||
auto line = v.attrs()->find(createSymbol("line"));
|
||||
ASSERT_NE(line, nullptr);
|
||||
state.forceValue(*line->value, noPos);
|
||||
ASSERT_THAT(*line->value, IsIntEq(4));
|
||||
|
||||
auto column = v.attrs()->find(createSymbol("column"));
|
||||
ASSERT_NE(column, nullptr);
|
||||
state.forceValue(*column->value, noPos);
|
||||
ASSERT_THAT(*column->value, IsIntEq(3));
|
||||
}
|
||||
|
||||
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, hashStringInvalidHashAlgorithm) {
|
||||
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(evalSettings.getCurrentSystem()));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, derivation) {
|
||||
auto v = eval("derivation");
|
||||
ASSERT_EQ(v.type(), nFunction);
|
||||
ASSERT_TRUE(v.isLambda());
|
||||
ASSERT_NE(v.payload.lambda.fun, nullptr);
|
||||
ASSERT_TRUE(v.payload.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_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(v.string_view(), "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, match5) {
|
||||
// The regex "\\{}" is valid and matches the string "{}".
|
||||
// Caused a regression before when trying to switch from std::regex to boost::regex.
|
||||
// See https://github.com/NixOS/nix/pull/7762#issuecomment-1834303659
|
||||
auto v = eval("builtins.match \"\\\\{}\" \"{}\"");
|
||||
ASSERT_THAT(v, IsListOfSize(0));
|
||||
}
|
||||
|
||||
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 */
|
90
src/libexpr-tests/search-path.cc
Normal file
90
src/libexpr-tests/search-path.cc
Normal file
|
@ -0,0 +1,90 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "search-path.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
TEST(LookupPathElem, parse_justPath) {
|
||||
ASSERT_EQ(
|
||||
LookupPath::Elem::parse("foo"),
|
||||
(LookupPath::Elem {
|
||||
.prefix = LookupPath::Prefix { .s = "" },
|
||||
.path = LookupPath::Path { .s = "foo" },
|
||||
}));
|
||||
}
|
||||
|
||||
TEST(LookupPathElem, parse_emptyPrefix) {
|
||||
ASSERT_EQ(
|
||||
LookupPath::Elem::parse("=foo"),
|
||||
(LookupPath::Elem {
|
||||
.prefix = LookupPath::Prefix { .s = "" },
|
||||
.path = LookupPath::Path { .s = "foo" },
|
||||
}));
|
||||
}
|
||||
|
||||
TEST(LookupPathElem, parse_oneEq) {
|
||||
ASSERT_EQ(
|
||||
LookupPath::Elem::parse("foo=bar"),
|
||||
(LookupPath::Elem {
|
||||
.prefix = LookupPath::Prefix { .s = "foo" },
|
||||
.path = LookupPath::Path { .s = "bar" },
|
||||
}));
|
||||
}
|
||||
|
||||
TEST(LookupPathElem, parse_twoEqs) {
|
||||
ASSERT_EQ(
|
||||
LookupPath::Elem::parse("foo=bar=baz"),
|
||||
(LookupPath::Elem {
|
||||
.prefix = LookupPath::Prefix { .s = "foo" },
|
||||
.path = LookupPath::Path { .s = "bar=baz" },
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
TEST(LookupPathElem, suffixIfPotentialMatch_justPath) {
|
||||
LookupPath::Prefix prefix { .s = "" };
|
||||
ASSERT_EQ(prefix.suffixIfPotentialMatch("any/thing"), std::optional { "any/thing" });
|
||||
}
|
||||
|
||||
TEST(LookupPathElem, suffixIfPotentialMatch_misleadingPrefix1) {
|
||||
LookupPath::Prefix prefix { .s = "foo" };
|
||||
ASSERT_EQ(prefix.suffixIfPotentialMatch("fooX"), std::nullopt);
|
||||
}
|
||||
|
||||
TEST(LookupPathElem, suffixIfPotentialMatch_misleadingPrefix2) {
|
||||
LookupPath::Prefix prefix { .s = "foo" };
|
||||
ASSERT_EQ(prefix.suffixIfPotentialMatch("fooX/bar"), std::nullopt);
|
||||
}
|
||||
|
||||
TEST(LookupPathElem, suffixIfPotentialMatch_partialPrefix) {
|
||||
LookupPath::Prefix prefix { .s = "fooX" };
|
||||
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo"), std::nullopt);
|
||||
}
|
||||
|
||||
TEST(LookupPathElem, suffixIfPotentialMatch_exactPrefix) {
|
||||
LookupPath::Prefix prefix { .s = "foo" };
|
||||
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo"), std::optional { "" });
|
||||
}
|
||||
|
||||
TEST(LookupPathElem, suffixIfPotentialMatch_multiKey) {
|
||||
LookupPath::Prefix prefix { .s = "foo/bar" };
|
||||
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/bar/baz"), std::optional { "baz" });
|
||||
}
|
||||
|
||||
TEST(LookupPathElem, suffixIfPotentialMatch_trailingSlash) {
|
||||
LookupPath::Prefix prefix { .s = "foo" };
|
||||
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/"), std::optional { "" });
|
||||
}
|
||||
|
||||
TEST(LookupPathElem, suffixIfPotentialMatch_trailingDoubleSlash) {
|
||||
LookupPath::Prefix prefix { .s = "foo" };
|
||||
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo//"), std::optional { "/" });
|
||||
}
|
||||
|
||||
TEST(LookupPathElem, suffixIfPotentialMatch_trailingPath) {
|
||||
LookupPath::Prefix prefix { .s = "foo" };
|
||||
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/bar/baz"), std::optional { "bar/baz" });
|
||||
}
|
||||
|
||||
}
|
250
src/libexpr-tests/trivial.cc
Normal file
250
src/libexpr-tests/trivial.cc
Normal file
|
@ -0,0 +1,250 @@
|
|||
#include "tests/libexpr.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, forwardPipe) {
|
||||
auto v = eval("1 |> builtins.add 2 |> builtins.mul 3");
|
||||
ASSERT_THAT(v, IsIntEq(9));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, backwardPipe) {
|
||||
auto v = eval("builtins.add 1 <| builtins.mul 2 <| 3");
|
||||
ASSERT_THAT(v, IsIntEq(7));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, forwardPipeEvaluationOrder) {
|
||||
auto v = eval("1 |> null |> (x: 2)");
|
||||
ASSERT_THAT(v, IsIntEq(2));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, backwardPipeEvaluationOrder) {
|
||||
auto v = eval("(x: 1) <| null <| 2");
|
||||
ASSERT_THAT(v, IsIntEq(1));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, differentPipeOperatorsDoNotAssociate) {
|
||||
ASSERT_THROW(eval("(x: 1) <| 2 |> (x: 3)"), ParseError);
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, differentPipeOperatorsParensLeft) {
|
||||
auto v = eval("((x: 1) <| 2) |> (x: 3)");
|
||||
ASSERT_THAT(v, IsIntEq(3));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, differentPipeOperatorsParensRight) {
|
||||
auto v = eval("(x: 1) <| (2 |> (x: 3))");
|
||||
ASSERT_THAT(v, IsIntEq(1));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, forwardPipeLowestPrecedence) {
|
||||
auto v = eval("false -> true |> (x: !x)");
|
||||
ASSERT_THAT(v, IsFalse());
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, backwardPipeLowestPrecedence) {
|
||||
auto v = eval("(x: !x) <| false -> true");
|
||||
ASSERT_THAT(v, IsFalse());
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, forwardPipeStrongerThanElse) {
|
||||
auto v = eval("if true then 1 else 2 |> 3");
|
||||
ASSERT_THAT(v, IsIntEq(1));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, backwardPipeStrongerThanElse) {
|
||||
auto v = eval("if true then 1 else 2 <| 3");
|
||||
ASSERT_THAT(v, IsIntEq(1));
|
||||
}
|
||||
|
||||
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 */
|
132
src/libexpr-tests/value/context.cc
Normal file
132
src/libexpr-tests/value/context.cc
Normal file
|
@ -0,0 +1,132 @@
|
|||
#include <nlohmann/json.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <rapidcheck/gtest.h>
|
||||
|
||||
#include "tests/path.hh"
|
||||
#include "tests/libexpr.hh"
|
||||
#include "tests/value/context.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
// Test a few cases of invalid string context elements.
|
||||
|
||||
TEST(NixStringContextElemTest, empty_invalid) {
|
||||
EXPECT_THROW(
|
||||
NixStringContextElem::parse(""),
|
||||
BadNixStringContextElem);
|
||||
}
|
||||
|
||||
TEST(NixStringContextElemTest, single_bang_invalid) {
|
||||
EXPECT_THROW(
|
||||
NixStringContextElem::parse("!"),
|
||||
BadNixStringContextElem);
|
||||
}
|
||||
|
||||
TEST(NixStringContextElemTest, double_bang_invalid) {
|
||||
EXPECT_THROW(
|
||||
NixStringContextElem::parse("!!/"),
|
||||
BadStorePath);
|
||||
}
|
||||
|
||||
TEST(NixStringContextElemTest, eq_slash_invalid) {
|
||||
EXPECT_THROW(
|
||||
NixStringContextElem::parse("=/"),
|
||||
BadStorePath);
|
||||
}
|
||||
|
||||
TEST(NixStringContextElemTest, slash_invalid) {
|
||||
EXPECT_THROW(
|
||||
NixStringContextElem::parse("/"),
|
||||
BadStorePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Round trip (string <-> data structure) test for
|
||||
* `NixStringContextElem::Opaque`.
|
||||
*/
|
||||
TEST(NixStringContextElemTest, opaque) {
|
||||
std::string_view opaque = "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x";
|
||||
auto elem = NixStringContextElem::parse(opaque);
|
||||
auto * p = std::get_if<NixStringContextElem::Opaque>(&elem.raw);
|
||||
ASSERT_TRUE(p);
|
||||
ASSERT_EQ(p->path, StorePath { opaque });
|
||||
ASSERT_EQ(elem.to_string(), opaque);
|
||||
}
|
||||
|
||||
/**
|
||||
* Round trip (string <-> data structure) test for
|
||||
* `NixStringContextElem::DrvDeep`.
|
||||
*/
|
||||
TEST(NixStringContextElemTest, drvDeep) {
|
||||
std::string_view drvDeep = "=g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
|
||||
auto elem = NixStringContextElem::parse(drvDeep);
|
||||
auto * p = std::get_if<NixStringContextElem::DrvDeep>(&elem.raw);
|
||||
ASSERT_TRUE(p);
|
||||
ASSERT_EQ(p->drvPath, StorePath { drvDeep.substr(1) });
|
||||
ASSERT_EQ(elem.to_string(), drvDeep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Round trip (string <-> data structure) test for a simpler
|
||||
* `NixStringContextElem::Built`.
|
||||
*/
|
||||
TEST(NixStringContextElemTest, built_opaque) {
|
||||
std::string_view built = "!foo!g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
|
||||
auto elem = NixStringContextElem::parse(built);
|
||||
auto * p = std::get_if<NixStringContextElem::Built>(&elem.raw);
|
||||
ASSERT_TRUE(p);
|
||||
ASSERT_EQ(p->output, "foo");
|
||||
ASSERT_EQ(*p->drvPath, ((SingleDerivedPath) SingleDerivedPath::Opaque {
|
||||
.path = StorePath { built.substr(5) },
|
||||
}));
|
||||
ASSERT_EQ(elem.to_string(), built);
|
||||
}
|
||||
|
||||
/**
|
||||
* Round trip (string <-> data structure) test for a more complex,
|
||||
* inductive `NixStringContextElem::Built`.
|
||||
*/
|
||||
TEST(NixStringContextElemTest, built_built) {
|
||||
/**
|
||||
* We set these in tests rather than the regular globals so we don't have
|
||||
* to worry about race conditions if the tests run concurrently.
|
||||
*/
|
||||
ExperimentalFeatureSettings mockXpSettings;
|
||||
mockXpSettings.set("experimental-features", "dynamic-derivations ca-derivations");
|
||||
|
||||
std::string_view built = "!foo!bar!g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
|
||||
auto elem = NixStringContextElem::parse(built, mockXpSettings);
|
||||
auto * p = std::get_if<NixStringContextElem::Built>(&elem.raw);
|
||||
ASSERT_TRUE(p);
|
||||
ASSERT_EQ(p->output, "foo");
|
||||
auto * drvPath = std::get_if<SingleDerivedPath::Built>(&*p->drvPath);
|
||||
ASSERT_TRUE(drvPath);
|
||||
ASSERT_EQ(drvPath->output, "bar");
|
||||
ASSERT_EQ(*drvPath->drvPath, ((SingleDerivedPath) SingleDerivedPath::Opaque {
|
||||
.path = StorePath { built.substr(9) },
|
||||
}));
|
||||
ASSERT_EQ(elem.to_string(), built);
|
||||
}
|
||||
|
||||
/**
|
||||
* Without the right experimental features enabled, we cannot parse a
|
||||
* complex inductive string context element.
|
||||
*/
|
||||
TEST(NixStringContextElemTest, built_built_xp) {
|
||||
ASSERT_THROW(
|
||||
NixStringContextElem::parse("!foo!bar!g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv"), MissingExperimentalFeature);
|
||||
}
|
||||
|
||||
#ifndef COVERAGE
|
||||
|
||||
RC_GTEST_PROP(
|
||||
NixStringContextElemTest,
|
||||
prop_round_rip,
|
||||
(const NixStringContextElem & o))
|
||||
{
|
||||
RC_ASSERT(o == NixStringContextElem::parse(o.to_string()));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
788
src/libexpr-tests/value/print.cc
Normal file
788
src/libexpr-tests/value/print.cc
Normal file
|
@ -0,0 +1,788 @@
|
|||
#include "tests/libexpr.hh"
|
||||
|
||||
#include "value.hh"
|
||||
#include "print.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
using namespace testing;
|
||||
|
||||
struct ValuePrintingTests : LibExprTest
|
||||
{
|
||||
template<class... A>
|
||||
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);
|
||||
|
||||
auto list = state.buildList(3);
|
||||
list.elems[0] = &vOne;
|
||||
list.elems[1] = &vTwo;
|
||||
Value vList;
|
||||
vList.mkList(list);
|
||||
|
||||
test(vList, "[ 1 2 «nullptr» ]");
|
||||
}
|
||||
|
||||
TEST_F(ValuePrintingTests, vThunk)
|
||||
{
|
||||
Value vThunk;
|
||||
vThunk.mkThunk(nullptr, nullptr);
|
||||
|
||||
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);
|
||||
auto body = ExprInt(0);
|
||||
auto formals = Formals {};
|
||||
|
||||
ExprLambda eLambda(posIdx, createSymbol("a"), &formals, &body);
|
||||
|
||||
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 vOne;
|
||||
vOne.mkInt(1);
|
||||
|
||||
Value vTwo;
|
||||
vTwo.mkInt(2);
|
||||
|
||||
BindingsBuilder builderEmpty(state, state.allocBindings(0));
|
||||
Value vAttrsEmpty;
|
||||
vAttrsEmpty.mkAttrs(builderEmpty.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("nested"), &vAttrsEmpty);
|
||||
|
||||
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 = { nested = { ... }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 2 });
|
||||
test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 3 });
|
||||
test(vNested, "{ nested = { nested = { }; 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());
|
||||
|
||||
auto list = state.buildList(3);
|
||||
list.elems[0] = &vOne;
|
||||
list.elems[1] = &vTwo;
|
||||
list.elems[2] = &vNested;
|
||||
Value vList;
|
||||
vList.mkList(list);
|
||||
|
||||
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<class... A>
|
||||
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.sType, &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.sType, &vDerivation);
|
||||
builder.insert(state.sDrvPath, &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)
|
||||
{
|
||||
ExprVar eFalse(state.symbols.create("false"));
|
||||
eFalse.bindVars(state, state.staticBaseEnv);
|
||||
ExprInt eInt(1);
|
||||
|
||||
ExprAssert expr(noPos, &eFalse, &eInt);
|
||||
|
||||
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);
|
||||
|
||||
auto list = state.buildList(3);
|
||||
list.elems[0] = &vOne;
|
||||
list.elems[1] = &vTwo;
|
||||
Value vList;
|
||||
vList.mkList(list);
|
||||
|
||||
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);
|
||||
auto body = ExprInt(0);
|
||||
auto formals = Formals {};
|
||||
|
||||
ExprLambda eLambda(posIdx, createSymbol("a"), &formals, &body);
|
||||
|
||||
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;
|
||||
v.mkThunk(nullptr, nullptr);
|
||||
|
||||
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)
|
||||
{
|
||||
BindingsBuilder emptyBuilder(state, state.allocBindings(1));
|
||||
|
||||
Value vEmpty;
|
||||
vEmpty.mkAttrs(emptyBuilder.finish());
|
||||
|
||||
BindingsBuilder builder(state, state.allocBindings(10));
|
||||
builder.insert(state.symbols.create("a"), &vEmpty);
|
||||
builder.insert(state.symbols.create("b"), &vEmpty);
|
||||
|
||||
Value vAttrs;
|
||||
vAttrs.mkAttrs(builder.finish());
|
||||
|
||||
test(vAttrs,
|
||||
"{ a = { }; b = " ANSI_MAGENTA "«repeated»" ANSI_NORMAL "; }",
|
||||
PrintOptions {
|
||||
.ansiColors = true
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ValuePrintingTests, ansiColorsListRepeated)
|
||||
{
|
||||
BindingsBuilder emptyBuilder(state, state.allocBindings(1));
|
||||
|
||||
Value vEmpty;
|
||||
vEmpty.mkAttrs(emptyBuilder.finish());
|
||||
|
||||
auto list = state.buildList(2);
|
||||
list.elems[0] = &vEmpty;
|
||||
list.elems[1] = &vEmpty;
|
||||
Value vList;
|
||||
vList.mkList(list);
|
||||
|
||||
test(vList,
|
||||
"[ { } " ANSI_MAGENTA "«repeated»" ANSI_NORMAL " ]",
|
||||
PrintOptions {
|
||||
.ansiColors = true
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ValuePrintingTests, listRepeated)
|
||||
{
|
||||
BindingsBuilder emptyBuilder(state, state.allocBindings(1));
|
||||
|
||||
Value vEmpty;
|
||||
vEmpty.mkAttrs(emptyBuilder.finish());
|
||||
|
||||
auto list = state.buildList(2);
|
||||
list.elems[0] = &vEmpty;
|
||||
list.elems[1] = &vEmpty;
|
||||
Value vList;
|
||||
vList.mkList(list);
|
||||
|
||||
test(vList, "[ { } «repeated» ]", PrintOptions { });
|
||||
test(vList,
|
||||
"[ { } { } ]",
|
||||
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);
|
||||
|
||||
{
|
||||
auto list = state.buildList(2);
|
||||
list.elems[0] = &vOne;
|
||||
list.elems[1] = &vTwo;
|
||||
Value vList;
|
||||
vList.mkList(list);
|
||||
|
||||
test(vList,
|
||||
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«1 item elided»" ANSI_NORMAL " ]",
|
||||
PrintOptions {
|
||||
.ansiColors = true,
|
||||
.maxListItems = 1
|
||||
});
|
||||
}
|
||||
|
||||
Value vThree;
|
||||
vThree.mkInt(3);
|
||||
|
||||
{
|
||||
auto list = state.buildList(3);
|
||||
list.elems[0] = &vOne;
|
||||
list.elems[1] = &vTwo;
|
||||
list.elems[2] = &vThree;
|
||||
Value vList;
|
||||
vList.mkList(list);
|
||||
|
||||
test(vList,
|
||||
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«2 items elided»" ANSI_NORMAL " ]",
|
||||
PrintOptions {
|
||||
.ansiColors = true,
|
||||
.maxListItems = 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nix
|
25
src/libexpr-tests/value/value.cc
Normal file
25
src/libexpr-tests/value/value.cc
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "value.hh"
|
||||
|
||||
#include "tests/libstore.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class ValueTest : public LibStoreTest
|
||||
{};
|
||||
|
||||
TEST_F(ValueTest, unsetValue)
|
||||
{
|
||||
Value unsetValue;
|
||||
ASSERT_EQ(false, unsetValue.isValid());
|
||||
ASSERT_EQ(nThunk, unsetValue.type(true));
|
||||
ASSERT_DEATH(unsetValue.type(), "");
|
||||
}
|
||||
|
||||
TEST_F(ValueTest, vInt)
|
||||
{
|
||||
Value vInt;
|
||||
vInt.mkInt(42);
|
||||
ASSERT_EQ(true, vInt.isValid());
|
||||
}
|
||||
|
||||
} // namespace nix
|
|
@ -10,6 +10,9 @@ lockFileStr:
|
|||
# unlocked trees.
|
||||
overrides:
|
||||
|
||||
# This is `prim_fetchFinalTree`.
|
||||
fetchTreeFinal:
|
||||
|
||||
let
|
||||
|
||||
lockFile = builtins.fromJSON lockFileStr;
|
||||
|
@ -51,7 +54,8 @@ let
|
|||
}
|
||||
else
|
||||
# FIXME: remove obsolete node.info.
|
||||
fetchTree (node.info or {} // removeAttrs node.locked ["dir"]);
|
||||
# Note: lock file entries are always final.
|
||||
fetchTreeFinal (node.info or {} // removeAttrs node.locked ["dir"]);
|
||||
|
||||
subdir = overrides.${key}.dir or node.locked.dir or "";
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ struct AttrDb
|
|||
state->txn->commit();
|
||||
state->txn.reset();
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
ignoreExceptionInDestructor();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,7 @@ struct AttrDb
|
|||
try {
|
||||
return fun();
|
||||
} catch (SQLiteError &) {
|
||||
ignoreException();
|
||||
ignoreExceptionExceptInterrupt();
|
||||
failed = true;
|
||||
return 0;
|
||||
}
|
||||
|
@ -351,7 +351,7 @@ static std::shared_ptr<AttrDb> makeAttrDb(
|
|||
try {
|
||||
return std::make_shared<AttrDb>(cfg, fingerprint, symbols);
|
||||
} catch (SQLiteError &) {
|
||||
ignoreException();
|
||||
ignoreExceptionExceptInterrupt();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
#else
|
||||
|
||||
# include <memory>
|
||||
|
||||
/* Some dummy aliases for Boehm GC definitions to reduce the number of
|
||||
#ifdefs. */
|
||||
|
||||
|
@ -23,7 +25,6 @@ template<typename T>
|
|||
using gc_allocator = std::allocator<T>;
|
||||
|
||||
# define GC_MALLOC_ATOMIC std::malloc
|
||||
# define GC_STRDUP strdup
|
||||
|
||||
struct gc
|
||||
{};
|
||||
|
|
|
@ -87,11 +87,15 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
|
|||
{
|
||||
if (v.isThunk()) {
|
||||
Env * env = v.payload.thunk.env;
|
||||
assert(env || v.isBlackhole());
|
||||
Expr * expr = v.payload.thunk.expr;
|
||||
try {
|
||||
v.mkBlackhole();
|
||||
//checkInterrupt();
|
||||
expr->eval(*this, *env, v);
|
||||
if (env) [[likely]]
|
||||
expr->eval(*this, *env, v);
|
||||
else
|
||||
ExprBlackHole::throwInfiniteRecursionError(*this, v);
|
||||
} catch (...) {
|
||||
v.mkThunk(env, expr);
|
||||
tryFixupBlackHolePos(v, pos);
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
|
||||
#include "config.hh"
|
||||
#include "ref.hh"
|
||||
#include "source-path.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class Store;
|
||||
class EvalState;
|
||||
|
||||
struct EvalSettings : Config
|
||||
{
|
||||
|
@ -18,11 +19,8 @@ struct EvalSettings : Config
|
|||
*
|
||||
* The return value is (a) whether the entry was valid, and, if so,
|
||||
* what does it map to.
|
||||
*
|
||||
* @todo Return (`std::optional` of) `SourceAccssor` or something
|
||||
* more structured instead of mere `std::string`?
|
||||
*/
|
||||
using LookupPathHook = std::optional<std::string>(ref<Store> store, std::string_view);
|
||||
using LookupPathHook = std::optional<SourcePath>(EvalState & state, std::string_view);
|
||||
|
||||
/**
|
||||
* Map from "scheme" to a `LookupPathHook`.
|
||||
|
|
|
@ -53,15 +53,6 @@ static char * allocString(size_t size)
|
|||
}
|
||||
|
||||
|
||||
static char * dupString(const char * s)
|
||||
{
|
||||
char * t;
|
||||
t = GC_STRDUP(s);
|
||||
if (!t) throw std::bad_alloc();
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
// When there's no need to write to the string, we can optimize away empty
|
||||
// string allocations.
|
||||
// This function handles makeImmutableString(std::string_view()) by returning
|
||||
|
@ -457,7 +448,7 @@ void EvalState::addConstant(const std::string & name, Value * v, Constant info)
|
|||
/* Install value the base environment. */
|
||||
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
|
||||
baseEnv.values[baseEnvDispl++] = v;
|
||||
baseEnv.values[0]->payload.attrs->push_back(Attr(symbols.create(name2), v));
|
||||
getBuiltins().payload.attrs->push_back(Attr(symbols.create(name2), v));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -519,16 +510,32 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
|
|||
|
||||
Value * v = allocValue();
|
||||
v->mkPrimOp(new PrimOp(primOp));
|
||||
staticBaseEnv->vars.emplace_back(envName, baseEnvDispl);
|
||||
baseEnv.values[baseEnvDispl++] = v;
|
||||
baseEnv.values[0]->payload.attrs->push_back(Attr(symbols.create(primOp.name), v));
|
||||
|
||||
if (primOp.internal)
|
||||
internalPrimOps.emplace(primOp.name, v);
|
||||
else {
|
||||
staticBaseEnv->vars.emplace_back(envName, baseEnvDispl);
|
||||
baseEnv.values[baseEnvDispl++] = v;
|
||||
getBuiltins().payload.attrs->push_back(Attr(symbols.create(primOp.name), v));
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
Value & EvalState::getBuiltins()
|
||||
{
|
||||
return *baseEnv.values[0];
|
||||
}
|
||||
|
||||
|
||||
Value & EvalState::getBuiltin(const std::string & name)
|
||||
{
|
||||
return *baseEnv.values[0]->attrs()->find(symbols.create(name))->value;
|
||||
auto it = getBuiltins().attrs()->get(symbols.create(name));
|
||||
if (it)
|
||||
return *it->value;
|
||||
else
|
||||
error<EvalError>("builtin '%1%' not found", name).debugThrow();
|
||||
}
|
||||
|
||||
|
||||
|
@ -548,7 +555,7 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
|
|||
if (v.isLambda()) {
|
||||
auto exprLambda = v.payload.lambda.fun;
|
||||
|
||||
std::stringstream s(std::ios_base::out);
|
||||
std::ostringstream s;
|
||||
std::string name;
|
||||
auto pos = positions[exprLambda->getPos()];
|
||||
std::string docStr;
|
||||
|
@ -580,30 +587,25 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
|
|||
|
||||
s << docStr;
|
||||
|
||||
s << '\0'; // for making a c string below
|
||||
std::string ss = s.str();
|
||||
|
||||
return Doc {
|
||||
.pos = pos,
|
||||
.name = name,
|
||||
.arity = 0, // FIXME: figure out how deep by syntax only? It's not semantically useful though...
|
||||
.args = {},
|
||||
.doc =
|
||||
// FIXME: this leaks; make the field std::string?
|
||||
strdup(ss.data()),
|
||||
.doc = makeImmutableString(toView(s)), // NOTE: memory leak when compiled without GC
|
||||
};
|
||||
}
|
||||
if (isFunctor(v)) {
|
||||
try {
|
||||
Value & functor = *v.attrs()->find(sFunctor)->value;
|
||||
Value * vp = &v;
|
||||
Value * vp[] = {&v};
|
||||
Value partiallyApplied;
|
||||
// The first paramater is not user-provided, and may be
|
||||
// handled by code that is opaque to the user, like lib.const = x: y: y;
|
||||
// So preferably we show docs that are relevant to the
|
||||
// "partially applied" function returned by e.g. `const`.
|
||||
// We apply the first argument:
|
||||
callFunction(functor, 1, &vp, partiallyApplied, noPos);
|
||||
callFunction(functor, vp, partiallyApplied, noPos);
|
||||
auto _level = addCallDepth(noPos);
|
||||
return getDoc(partiallyApplied);
|
||||
}
|
||||
|
@ -832,9 +834,10 @@ static const char * * encodeContext(const NixStringContext & context)
|
|||
size_t n = 0;
|
||||
auto ctx = (const char * *)
|
||||
allocBytes((context.size() + 1) * sizeof(char *));
|
||||
for (auto & i : context)
|
||||
ctx[n++] = dupString(i.to_string().c_str());
|
||||
ctx[n] = 0;
|
||||
for (auto & i : context) {
|
||||
ctx[n++] = makeImmutableString({i.to_string()});
|
||||
}
|
||||
ctx[n] = nullptr;
|
||||
return ctx;
|
||||
} else
|
||||
return nullptr;
|
||||
|
@ -1467,7 +1470,7 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v)
|
|||
v.mkLambda(&env, this);
|
||||
}
|
||||
|
||||
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
|
||||
void EvalState::callFunction(Value & fun, std::span<Value *> args, Value & vRes, const PosIdx pos)
|
||||
{
|
||||
auto _level = addCallDepth(pos);
|
||||
|
||||
|
@ -1482,16 +1485,16 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
auto makeAppChain = [&]()
|
||||
{
|
||||
vRes = vCur;
|
||||
for (size_t i = 0; i < nrArgs; ++i) {
|
||||
for (auto arg : args) {
|
||||
auto fun2 = allocValue();
|
||||
*fun2 = vRes;
|
||||
vRes.mkPrimOpApp(fun2, args[i]);
|
||||
vRes.mkPrimOpApp(fun2, arg);
|
||||
}
|
||||
};
|
||||
|
||||
const Attr * functor;
|
||||
|
||||
while (nrArgs > 0) {
|
||||
while (args.size() > 0) {
|
||||
|
||||
if (vCur.isLambda()) {
|
||||
|
||||
|
@ -1594,15 +1597,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
throw;
|
||||
}
|
||||
|
||||
nrArgs--;
|
||||
args += 1;
|
||||
args = args.subspan(1);
|
||||
}
|
||||
|
||||
else if (vCur.isPrimOp()) {
|
||||
|
||||
size_t argsLeft = vCur.primOp()->arity;
|
||||
|
||||
if (nrArgs < argsLeft) {
|
||||
if (args.size() < argsLeft) {
|
||||
/* We don't have enough arguments, so create a tPrimOpApp chain. */
|
||||
makeAppChain();
|
||||
return;
|
||||
|
@ -1614,15 +1616,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
if (countCalls) primOpCalls[fn->name]++;
|
||||
|
||||
try {
|
||||
fn->fun(*this, vCur.determinePos(noPos), args, vCur);
|
||||
fn->fun(*this, vCur.determinePos(noPos), args.data(), vCur);
|
||||
} catch (Error & e) {
|
||||
if (fn->addTrace)
|
||||
addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name);
|
||||
throw;
|
||||
}
|
||||
|
||||
nrArgs -= argsLeft;
|
||||
args += argsLeft;
|
||||
args = args.subspan(argsLeft);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1638,7 +1639,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
auto arity = primOp->primOp()->arity;
|
||||
auto argsLeft = arity - argsDone;
|
||||
|
||||
if (nrArgs < argsLeft) {
|
||||
if (args.size() < argsLeft) {
|
||||
/* We still don't have enough arguments, so extend the tPrimOpApp chain. */
|
||||
makeAppChain();
|
||||
return;
|
||||
|
@ -1670,8 +1671,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
throw;
|
||||
}
|
||||
|
||||
nrArgs -= argsLeft;
|
||||
args += argsLeft;
|
||||
args = args.subspan(argsLeft);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1682,13 +1682,12 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
Value * args2[] = {allocValue(), args[0]};
|
||||
*args2[0] = vCur;
|
||||
try {
|
||||
callFunction(*functor->value, 2, args2, vCur, functor->pos);
|
||||
callFunction(*functor->value, args2, vCur, functor->pos);
|
||||
} catch (Error & e) {
|
||||
e.addTrace(positions[pos], "while calling a functor (an attribute set with a '__functor' attribute)");
|
||||
throw;
|
||||
}
|
||||
nrArgs--;
|
||||
args++;
|
||||
args = args.subspan(1);
|
||||
}
|
||||
|
||||
else
|
||||
|
@ -1731,7 +1730,7 @@ void ExprCall::eval(EvalState & state, Env & env, Value & v)
|
|||
for (size_t i = 0; i < args.size(); ++i)
|
||||
vArgs[i] = args[i]->maybeThunk(state, env);
|
||||
|
||||
state.callFunction(vFun, args.size(), vArgs.data(), v, pos);
|
||||
state.callFunction(vFun, vArgs, v, pos);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1743,7 +1742,7 @@ void EvalState::incrFunctionCall(ExprLambda * fun)
|
|||
}
|
||||
|
||||
|
||||
void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
|
||||
void EvalState::autoCallFunction(const Bindings & args, Value & fun, Value & res)
|
||||
{
|
||||
auto pos = fun.determinePos(noPos);
|
||||
|
||||
|
@ -1813,11 +1812,9 @@ void ExprIf::eval(EvalState & state, Env & env, Value & v)
|
|||
void ExprAssert::eval(EvalState & state, Env & env, Value & v)
|
||||
{
|
||||
if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
|
||||
auto exprStr = ({
|
||||
std::ostringstream out;
|
||||
cond->show(state.symbols, out);
|
||||
out.str();
|
||||
});
|
||||
std::ostringstream out;
|
||||
cond->show(state.symbols, out);
|
||||
auto exprStr = toView(out);
|
||||
|
||||
if (auto eq = dynamic_cast<ExprOpEq *>(cond)) {
|
||||
try {
|
||||
|
@ -2055,9 +2052,12 @@ void ExprPos::eval(EvalState & state, Env & env, Value & v)
|
|||
state.mkPos(v, pos);
|
||||
}
|
||||
|
||||
|
||||
void ExprBlackHole::eval(EvalState & state, Env & env, Value & v)
|
||||
void ExprBlackHole::eval(EvalState & state, [[maybe_unused]] Env & env, Value & v)
|
||||
{
|
||||
throwInfiniteRecursionError(state, v);
|
||||
}
|
||||
|
||||
[[gnu::noinline]] [[noreturn]] void ExprBlackHole::throwInfiniteRecursionError(EvalState & state, Value &v) {
|
||||
state.error<InfiniteRecursionError>("infinite recursion encountered")
|
||||
.atPos(v.determinePos(noPos))
|
||||
.debugThrow();
|
||||
|
@ -2849,7 +2849,9 @@ void EvalState::printStatistics()
|
|||
#endif
|
||||
#if HAVE_BOEHMGC
|
||||
{GC_is_incremental_mode() ? "gcNonIncremental" : "gc", gcFullOnlyTime},
|
||||
#ifndef _WIN32 // TODO implement
|
||||
{GC_is_incremental_mode() ? "gcNonIncrementalFraction" : "gcFraction", gcFullOnlyTime / cpuTime},
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
topObj["envs"] = {
|
||||
|
@ -3036,8 +3038,8 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_
|
|||
if (!rOpt) continue;
|
||||
auto r = *rOpt;
|
||||
|
||||
Path res = suffix == "" ? r : concatStrings(r, "/", suffix);
|
||||
if (pathExists(res)) return rootPath(CanonPath(canonPath(res)));
|
||||
auto res = (r / CanonPath(suffix)).resolveSymlinks();
|
||||
if (res.pathExists()) return res;
|
||||
}
|
||||
|
||||
if (hasPrefix(path, "nix/"))
|
||||
|
@ -3052,13 +3054,13 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_
|
|||
}
|
||||
|
||||
|
||||
std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Path & value0, bool initAccessControl)
|
||||
std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Path & value0, bool initAccessControl)
|
||||
{
|
||||
auto & value = value0.s;
|
||||
auto i = lookupPathResolved.find(value);
|
||||
if (i != lookupPathResolved.end()) return i->second;
|
||||
|
||||
auto finish = [&](std::string res) {
|
||||
auto finish = [&](SourcePath res) {
|
||||
debug("resolved search path element '%s' to '%s'", value, res);
|
||||
lookupPathResolved.emplace(value, res);
|
||||
return res;
|
||||
|
@ -3071,7 +3073,7 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
|
|||
fetchSettings,
|
||||
EvalSettings::resolvePseudoUrl(value));
|
||||
auto storePath = fetchToStore(*store, SourcePath(accessor), FetchMode::Copy);
|
||||
return finish(store->toRealPath(storePath));
|
||||
return finish(rootPath(store->toRealPath(storePath)));
|
||||
} catch (Error & e) {
|
||||
logWarning({
|
||||
.msg = HintFmt("Nix search path entry '%1%' cannot be downloaded, ignoring", value)
|
||||
|
@ -3083,29 +3085,29 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
|
|||
auto scheme = value.substr(0, colPos);
|
||||
auto rest = value.substr(colPos + 1);
|
||||
if (auto * hook = get(settings.lookupPathHooks, scheme)) {
|
||||
auto res = (*hook)(store, rest);
|
||||
auto res = (*hook)(*this, rest);
|
||||
if (res)
|
||||
return finish(std::move(*res));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto path = absPath(value);
|
||||
auto path = rootPath(value);
|
||||
|
||||
/* Allow access to paths in the search path. */
|
||||
if (initAccessControl) {
|
||||
allowPath(path);
|
||||
if (store->isInStore(path)) {
|
||||
allowPath(path.path.abs());
|
||||
if (store->isInStore(path.path.abs())) {
|
||||
try {
|
||||
StorePathSet closure;
|
||||
store->computeFSClosure(store->toStorePath(path).first, closure);
|
||||
store->computeFSClosure(store->toStorePath(path.path.abs()).first, closure);
|
||||
for (auto & p : closure)
|
||||
allowPath(p);
|
||||
} catch (InvalidPath &) { }
|
||||
}
|
||||
}
|
||||
|
||||
if (pathExists(path))
|
||||
if (path.pathExists())
|
||||
return finish(std::move(path));
|
||||
else {
|
||||
logWarning({
|
||||
|
@ -3116,7 +3118,6 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
|
|||
|
||||
debug("failed to resolve search path element '%s'", value);
|
||||
return std::nullopt;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ struct PrimOp
|
|||
const char * doc = nullptr;
|
||||
|
||||
/**
|
||||
* Add a trace item, `while calling the '<name>' builtin`
|
||||
* Add a trace item, while calling the `<name>` builtin.
|
||||
*
|
||||
* This is used to remove the redundant item for `builtins.addErrorContext`.
|
||||
*/
|
||||
|
@ -107,6 +107,11 @@ struct PrimOp
|
|||
*/
|
||||
std::optional<ExperimentalFeature> experimentalFeature;
|
||||
|
||||
/**
|
||||
* If true, this primop is not exposed to the user.
|
||||
*/
|
||||
bool internal = false;
|
||||
|
||||
/**
|
||||
* Validity check to be performed by functions that introduce primops,
|
||||
* such as RegisterPrimOp() and Value::mkPrimOp().
|
||||
|
@ -342,7 +347,7 @@ private:
|
|||
|
||||
LookupPath lookupPath;
|
||||
|
||||
std::map<std::string, std::optional<std::string>> lookupPathResolved;
|
||||
std::map<std::string, std::optional<SourcePath>> lookupPathResolved;
|
||||
|
||||
/**
|
||||
* Cache used by prim_match().
|
||||
|
@ -447,9 +452,9 @@ public:
|
|||
*
|
||||
* If the specified search path element is a URI, download it.
|
||||
*
|
||||
* If it is not found, return `std::nullopt`
|
||||
* If it is not found, return `std::nullopt`.
|
||||
*/
|
||||
std::optional<std::string> resolveLookupPathPath(
|
||||
std::optional<SourcePath> resolveLookupPathPath(
|
||||
const LookupPath::Path & elem,
|
||||
bool initAccessControl = false);
|
||||
|
||||
|
@ -591,6 +596,11 @@ public:
|
|||
*/
|
||||
std::shared_ptr<StaticEnv> staticBaseEnv; // !!! should be private
|
||||
|
||||
/**
|
||||
* Internal primops not exposed to the user.
|
||||
*/
|
||||
std::unordered_map<std::string, Value *, std::hash<std::string>, std::equal_to<std::string>, traceable_allocator<std::pair<const std::string, Value *>>> internalPrimOps;
|
||||
|
||||
/**
|
||||
* Name and documentation about every constant.
|
||||
*
|
||||
|
@ -613,8 +623,19 @@ private:
|
|||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Retrieve a specific builtin, equivalent to evaluating `builtins.${name}`.
|
||||
* @param name The attribute name of the builtin to retrieve.
|
||||
* @throws EvalError if the builtin does not exist.
|
||||
*/
|
||||
Value & getBuiltin(const std::string & name);
|
||||
|
||||
/**
|
||||
* Retrieve the `builtins` attrset, equivalent to evaluating the reference `builtins`.
|
||||
* Always returns an attribute set value.
|
||||
*/
|
||||
Value & getBuiltins();
|
||||
|
||||
struct Doc
|
||||
{
|
||||
Pos pos;
|
||||
|
@ -680,20 +701,19 @@ public:
|
|||
|
||||
bool isFunctor(Value & fun);
|
||||
|
||||
// FIXME: use std::span
|
||||
void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos);
|
||||
void callFunction(Value & fun, std::span<Value *> args, Value & vRes, const PosIdx pos);
|
||||
|
||||
void callFunction(Value & fun, Value & arg, Value & vRes, const PosIdx pos)
|
||||
{
|
||||
Value * args[] = {&arg};
|
||||
callFunction(fun, 1, args, vRes, pos);
|
||||
callFunction(fun, args, vRes, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically call a function for which each argument has a
|
||||
* default value or has a binding in the `args` map.
|
||||
*/
|
||||
void autoCallFunction(Bindings & args, Value & fun, Value & res);
|
||||
void autoCallFunction(const Bindings & args, Value & fun, Value & res);
|
||||
|
||||
/**
|
||||
* Allocation primitives.
|
||||
|
@ -799,7 +819,6 @@ public:
|
|||
bool callPathFilter(
|
||||
Value * filterFun,
|
||||
const SourcePath & path,
|
||||
std::string_view pathArg,
|
||||
PosIdx pos);
|
||||
|
||||
DocComment getDocCommentForPos(PosIdx pos);
|
||||
|
|
|
@ -374,11 +374,12 @@ static void getDerivations(EvalState & state, Value & vIn,
|
|||
bound to the attribute with the "lower" name should take
|
||||
precedence). */
|
||||
for (auto & i : v.attrs()->lexicographicOrder(state.symbols)) {
|
||||
std::string_view symbol{state.symbols[i->name]};
|
||||
try {
|
||||
debug("evaluating attribute '%1%'", state.symbols[i->name]);
|
||||
if (!std::regex_match(std::string(state.symbols[i->name]), attrRegex))
|
||||
debug("evaluating attribute '%1%'", symbol);
|
||||
if (!std::regex_match(symbol.begin(), symbol.end(), attrRegex))
|
||||
continue;
|
||||
std::string pathPrefix2 = addToPath(pathPrefix, state.symbols[i->name]);
|
||||
std::string pathPrefix2 = addToPath(pathPrefix, symbol);
|
||||
if (combineChannels)
|
||||
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
|
||||
else if (getDerivation(state, *i->value, pathPrefix2, drvs, done, ignoreAssertionFailures)) {
|
||||
|
@ -392,7 +393,7 @@ static void getDerivations(EvalState & state, Value & vIn,
|
|||
}
|
||||
}
|
||||
} catch (Error & e) {
|
||||
e.addTrace(state.positions[i->pos], "while evaluating the attribute '%s'", state.symbols[i->name]);
|
||||
e.addTrace(state.positions[i->pos], "while evaluating the attribute '%s'", symbol);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
libraries += libexpr
|
||||
|
||||
libexpr_NAME = libnixexpr
|
||||
|
||||
libexpr_DIR := $(d)
|
||||
|
||||
libexpr_SOURCES := \
|
||||
$(wildcard $(d)/*.cc) \
|
||||
$(wildcard $(d)/value/*.cc) \
|
||||
$(wildcard $(d)/primops/*.cc) \
|
||||
$(d)/lexer-tab.cc \
|
||||
$(d)/parser-tab.cc
|
||||
# Not just for this library itself, but also for downstream libraries using this library
|
||||
|
||||
INCLUDE_libexpr := -I $(d)
|
||||
|
||||
libexpr_CXXFLAGS += \
|
||||
$(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) \
|
||||
-DGC_THREADS
|
||||
|
||||
libexpr_LIBS = libutil libstore libfetchers
|
||||
|
||||
libexpr_LDFLAGS += -lboost_context $(THREAD_LDFLAGS)
|
||||
ifdef HOST_LINUX
|
||||
libexpr_LDFLAGS += -ldl
|
||||
endif
|
||||
|
||||
# The dependency on libgc must be propagated (i.e. meaning that
|
||||
# programs/libraries that use libexpr must explicitly pass -lgc),
|
||||
# because inline functions in libexpr's header files call libgc.
|
||||
libexpr_LDFLAGS_PROPAGATED = $(BDW_GC_LIBS)
|
||||
|
||||
libexpr_ORDER_AFTER := $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexer-tab.hh
|
||||
|
||||
$(d)/parser-tab.cc $(d)/parser-tab.hh: $(d)/parser.y
|
||||
$(trace-gen) bison -v -o $(libexpr_DIR)/parser-tab.cc $< -d
|
||||
|
||||
$(d)/lexer-tab.cc $(d)/lexer-tab.hh: $(d)/lexer.l
|
||||
$(trace-gen) flex --outfile $(libexpr_DIR)/lexer-tab.cc --header-file=$(libexpr_DIR)/lexer-tab.hh $<
|
||||
|
||||
clean-files += $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexer-tab.hh
|
||||
|
||||
$(eval $(call install-file-in, $(buildprefix)$(d)/nix-expr.pc, $(libdir)/pkgconfig, 0644))
|
||||
|
||||
$(foreach i, $(wildcard src/libexpr/value/*.hh), \
|
||||
$(eval $(call install-file-in, $(i), $(includedir)/nix/value, 0644)))
|
||||
|
||||
$(d)/primops.cc: $(d)/imported-drv-to-derivation.nix.gen.hh
|
||||
|
||||
$(d)/eval.cc: $(d)/primops/derivation.nix.gen.hh $(d)/fetchurl.nix.gen.hh $(d)/call-flake.nix.gen.hh
|
|
@ -27,8 +27,6 @@ deps_public_maybe_subproject = [
|
|||
]
|
||||
subdir('build-utils-meson/subprojects')
|
||||
|
||||
subdir('build-utils-meson/threads')
|
||||
|
||||
boost = dependency(
|
||||
'boost',
|
||||
modules : ['container', 'context'],
|
||||
|
@ -79,7 +77,7 @@ add_project_arguments(
|
|||
language : 'cpp',
|
||||
)
|
||||
|
||||
subdir('build-utils-meson/diagnostics')
|
||||
subdir('build-utils-meson/common')
|
||||
|
||||
parser_tab = custom_target(
|
||||
input : 'parser.y',
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
prefix=@prefix@
|
||||
libdir=@libdir@
|
||||
includedir=@includedir@
|
||||
|
||||
Name: Nix
|
||||
Description: Nix Package Manager
|
||||
Version: @PACKAGE_VERSION@
|
||||
Requires: nix-store bdw-gc
|
||||
Libs: -L${libdir} -lnixexpr
|
||||
Cflags: -I${includedir}/nix -std=c++2a
|
|
@ -663,4 +663,32 @@ std::string DocComment::getInnerText(const PosTable & positions) const {
|
|||
return docStr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ‘Cursed or’ handling.
|
||||
*
|
||||
* In parser.y, every use of expr_select in a production must call one of the
|
||||
* two below functions.
|
||||
*
|
||||
* To be removed by https://github.com/NixOS/nix/pull/11121
|
||||
*/
|
||||
|
||||
void ExprCall::resetCursedOr()
|
||||
{
|
||||
cursedOrEndPos.reset();
|
||||
}
|
||||
|
||||
void ExprCall::warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions)
|
||||
{
|
||||
if (cursedOrEndPos.has_value()) {
|
||||
std::ostringstream out;
|
||||
out << "at " << positions[pos] << ": "
|
||||
"This expression uses `or` as an identifier in a way that will change in a future Nix release.\n"
|
||||
"Wrap this entire expression in parentheses to preserve its current meaning:\n"
|
||||
" (" << positions[pos].getSnippetUpTo(positions[*cursedOrEndPos]).value_or("could not read expression") << ")\n"
|
||||
"Give feedback at https://github.com/NixOS/nix/pull/11121";
|
||||
warn(out.str());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -96,6 +96,10 @@ struct Expr
|
|||
virtual void setName(Symbol name);
|
||||
virtual void setDocComment(DocComment docComment) { };
|
||||
virtual PosIdx getPos() const { return noPos; }
|
||||
|
||||
// These are temporary methods to be used only in parser.y
|
||||
virtual void resetCursedOr() { };
|
||||
virtual void warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions) { };
|
||||
};
|
||||
|
||||
#define COMMON_METHODS \
|
||||
|
@ -202,7 +206,7 @@ struct ExprSelect : Expr
|
|||
/**
|
||||
* Evaluate the `a.b.c` part of `a.b.c.d`. This exists mostly for the purpose of :doc in the repl.
|
||||
*
|
||||
* @param[out] v The attribute set that should contain the last attribute name (if it exists).
|
||||
* @param[out] attrs The attribute set that should contain the last attribute name (if it exists).
|
||||
* @return The last attribute name in `attrPath`
|
||||
*
|
||||
* @note This does *not* evaluate the final attribute, and does not fail if that's the only attribute that does not exist.
|
||||
|
@ -354,10 +358,16 @@ struct ExprCall : Expr
|
|||
Expr * fun;
|
||||
std::vector<Expr *> args;
|
||||
PosIdx pos;
|
||||
std::optional<PosIdx> cursedOrEndPos; // used during parsing to warn about https://github.com/NixOS/nix/issues/11118
|
||||
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args)
|
||||
: fun(fun), args(args), pos(pos)
|
||||
: fun(fun), args(args), pos(pos), cursedOrEndPos({})
|
||||
{ }
|
||||
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args, PosIdx && cursedOrEndPos)
|
||||
: fun(fun), args(args), pos(pos), cursedOrEndPos(cursedOrEndPos)
|
||||
{ }
|
||||
PosIdx getPos() const override { return pos; }
|
||||
virtual void resetCursedOr() override;
|
||||
virtual void warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions) override;
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
|
@ -458,6 +468,7 @@ struct ExprBlackHole : Expr
|
|||
void show(const SymbolTable & symbols, std::ostream & str) const override {}
|
||||
void eval(EvalState & state, Env & env, Value & v) override;
|
||||
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override {}
|
||||
[[noreturn]] static void throwInfiniteRecursionError(EvalState & state, Value & v);
|
||||
};
|
||||
|
||||
extern ExprBlackHole eBlackHole;
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
{ lib
|
||||
, stdenv
|
||||
, mkMesonDerivation
|
||||
, releaseTools
|
||||
, mkMesonLibrary
|
||||
|
||||
, meson
|
||||
, ninja
|
||||
, pkg-config
|
||||
, bison
|
||||
, flex
|
||||
, cmake # for resolving toml11 dep
|
||||
|
@ -38,7 +34,7 @@ let
|
|||
inherit (lib) fileset;
|
||||
in
|
||||
|
||||
mkMesonDerivation (finalAttrs: {
|
||||
mkMesonLibrary (finalAttrs: {
|
||||
pname = "nix-expr";
|
||||
inherit version;
|
||||
|
||||
|
@ -55,15 +51,13 @@ mkMesonDerivation (finalAttrs: {
|
|||
(fileset.fileFilter (file: file.hasExt "hh") ./.)
|
||||
./lexer.l
|
||||
./parser.y
|
||||
(fileset.fileFilter (file: file.hasExt "nix") ./.)
|
||||
(fileset.difference
|
||||
(fileset.fileFilter (file: file.hasExt "nix") ./.)
|
||||
./package.nix
|
||||
)
|
||||
];
|
||||
|
||||
outputs = [ "out" "dev" ];
|
||||
|
||||
nativeBuildInputs = [
|
||||
meson
|
||||
ninja
|
||||
pkg-config
|
||||
bison
|
||||
flex
|
||||
cmake
|
||||
|
@ -77,6 +71,10 @@ mkMesonDerivation (finalAttrs: {
|
|||
nix-util
|
||||
nix-store
|
||||
nix-fetchers
|
||||
] ++ finalAttrs.passthru.externalPropagatedBuildInputs;
|
||||
|
||||
# Hack for sake of the dev shell
|
||||
passthru.externalPropagatedBuildInputs = [
|
||||
boost
|
||||
nlohmann_json
|
||||
] ++ lib.optional enableGC boehmgc;
|
||||
|
@ -102,10 +100,6 @@ mkMesonDerivation (finalAttrs: {
|
|||
LDFLAGS = "-fuse-ld=gold";
|
||||
};
|
||||
|
||||
separateDebugInfo = !stdenv.hostPlatform.isStatic;
|
||||
|
||||
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
|
||||
|
||||
meta = {
|
||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||
};
|
||||
|
|
|
@ -264,19 +264,28 @@ expr_op
|
|||
;
|
||||
|
||||
expr_app
|
||||
: expr_app expr_select { $$ = makeCall(CUR_POS, $1, $2); }
|
||||
| expr_select
|
||||
: expr_app expr_select { $$ = makeCall(CUR_POS, $1, $2); $2->warnIfCursedOr(state->symbols, state->positions); }
|
||||
| /* Once a ‘cursed or’ reaches this nonterminal, it is no longer cursed,
|
||||
because the uncursed parse would also produce an expr_app. But we need
|
||||
to remove the cursed status in order to prevent valid things like
|
||||
`f (g or)` from triggering the warning. */
|
||||
expr_select { $$ = $1; $$->resetCursedOr(); }
|
||||
;
|
||||
|
||||
expr_select
|
||||
: expr_simple '.' attrpath
|
||||
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), nullptr); delete $3; }
|
||||
| expr_simple '.' attrpath OR_KW expr_select
|
||||
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), $5); delete $3; }
|
||||
| /* Backwards compatibility: because Nixpkgs has a rarely used
|
||||
function named ‘or’, allow stuff like ‘map or [...]’. */
|
||||
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), $5); delete $3; $5->warnIfCursedOr(state->symbols, state->positions); }
|
||||
| /* Backwards compatibility: because Nixpkgs has a function named ‘or’,
|
||||
allow stuff like ‘map or [...]’. This production is problematic (see
|
||||
https://github.com/NixOS/nix/issues/11118) and will be refactored in the
|
||||
future by treating `or` as a regular identifier. The refactor will (in
|
||||
very rare cases, we think) change the meaning of expressions, so we mark
|
||||
the ExprCall with data (establishing that it is a ‘cursed or’) that can
|
||||
be used to emit a warning when an affected expression is parsed. */
|
||||
expr_simple OR_KW
|
||||
{ $$ = new ExprCall(CUR_POS, $1, {new ExprVar(CUR_POS, state->s.or_)}); }
|
||||
{ $$ = new ExprCall(CUR_POS, $1, {new ExprVar(CUR_POS, state->s.or_)}, state->positions.add(state->origin, @$.endOffset)); }
|
||||
| expr_simple
|
||||
;
|
||||
|
||||
|
@ -472,7 +481,7 @@ string_attr
|
|||
;
|
||||
|
||||
expr_list
|
||||
: expr_list expr_select { $$ = $1; $1->elems.push_back($2); /* !!! dangerous */ }
|
||||
: expr_list expr_select { $$ = $1; $1->elems.push_back($2); /* !!! dangerous */; $2->warnIfCursedOr(state->symbols, state->positions); }
|
||||
| { $$ = new ExprList; }
|
||||
;
|
||||
|
||||
|
|
|
@ -40,6 +40,13 @@ namespace nix {
|
|||
* Miscellaneous
|
||||
*************************************************************/
|
||||
|
||||
static inline Value * mkString(EvalState & state, const std::csub_match & match)
|
||||
{
|
||||
Value * v = state.allocValue();
|
||||
v->mkString({match.first, match.second});
|
||||
return v;
|
||||
}
|
||||
|
||||
StringMap EvalState::realiseContext(const NixStringContext & context, StorePathSet * maybePathsOut, bool isIFD)
|
||||
{
|
||||
std::vector<DerivedPath::Built> drvs;
|
||||
|
@ -84,6 +91,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context, StorePathS
|
|||
|
||||
/* Build/substitute the context. */
|
||||
std::vector<DerivedPath> buildReqs;
|
||||
buildReqs.reserve(drvs.size());
|
||||
for (auto & d : drvs) buildReqs.emplace_back(DerivedPath { d });
|
||||
buildStore->buildPaths(buildReqs, bmNormal, store);
|
||||
|
||||
|
@ -171,6 +179,78 @@ static void mkOutputString(
|
|||
o.second.path(*state.store, Derivation::nameFromPath(drvPath), o.first));
|
||||
}
|
||||
|
||||
/**
|
||||
* `import` will parse a derivation when it imports a `.drv` file from the store.
|
||||
*
|
||||
* @param state The evaluation state.
|
||||
* @param pos The position of the `import` call.
|
||||
* @param path The path to the `.drv` to import.
|
||||
* @param storePath The path to the `.drv` to import.
|
||||
* @param v Return value
|
||||
*/
|
||||
void derivationToValue(EvalState & state, const PosIdx pos, const SourcePath & path, const StorePath & storePath, Value & v) {
|
||||
auto path2 = path.path.abs();
|
||||
Derivation drv = state.store->readDerivation(storePath);
|
||||
auto attrs = state.buildBindings(3 + drv.outputs.size());
|
||||
attrs.alloc(state.sDrvPath).mkString(path2, {
|
||||
NixStringContextElem::DrvDeep { .drvPath = storePath },
|
||||
});
|
||||
attrs.alloc(state.sName).mkString(drv.env["name"]);
|
||||
|
||||
auto list = state.buildList(drv.outputs.size());
|
||||
for (const auto & [i, o] : enumerate(drv.outputs)) {
|
||||
mkOutputString(state, attrs, storePath, o);
|
||||
(list[i] = state.allocValue())->mkString(o.first);
|
||||
}
|
||||
attrs.alloc(state.sOutputs).mkList(list);
|
||||
|
||||
auto w = state.allocValue();
|
||||
w->mkAttrs(attrs);
|
||||
|
||||
if (!state.vImportedDrvToDerivation) {
|
||||
state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
|
||||
state.eval(state.parseExprFromString(
|
||||
#include "imported-drv-to-derivation.nix.gen.hh"
|
||||
, state.rootPath(CanonPath::root)), **state.vImportedDrvToDerivation);
|
||||
}
|
||||
|
||||
state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
|
||||
v.mkApp(*state.vImportedDrvToDerivation, w);
|
||||
state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a Nix file with an alternate base scope, as `builtins.scopedImport` does.
|
||||
*
|
||||
* @param state The evaluation state.
|
||||
* @param pos The position of the import call.
|
||||
* @param path The path to the file to import.
|
||||
* @param vScope The base scope to use for the import.
|
||||
* @param v Return value
|
||||
*/
|
||||
static void scopedImport(EvalState & state, const PosIdx pos, SourcePath & path, Value * vScope, Value & v) {
|
||||
state.forceAttrs(*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport");
|
||||
|
||||
Env * env = &state.allocEnv(vScope->attrs()->size());
|
||||
env->up = &state.baseEnv;
|
||||
|
||||
auto staticEnv = std::make_shared<StaticEnv>(nullptr, state.staticBaseEnv.get(), vScope->attrs()->size());
|
||||
|
||||
unsigned int displ = 0;
|
||||
for (auto & attr : *vScope->attrs()) {
|
||||
staticEnv->vars.emplace_back(attr.name, displ);
|
||||
env->values[displ++] = attr.value;
|
||||
}
|
||||
|
||||
// No need to call staticEnv.sort(), because
|
||||
// args[0]->attrs is already sorted.
|
||||
|
||||
printTalkative("evaluating file '%1%'", path);
|
||||
Expr * e = state.parseExprFromFile(resolveExprPath(path), staticEnv);
|
||||
|
||||
e->eval(state, *env, v);
|
||||
}
|
||||
|
||||
/* Load and evaluate an expression from path specified by the
|
||||
argument. */
|
||||
static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v)
|
||||
|
@ -189,60 +269,13 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
|
|||
};
|
||||
|
||||
if (auto storePath = isValidDerivationInStore()) {
|
||||
Derivation drv = state.store->readDerivation(*storePath);
|
||||
auto attrs = state.buildBindings(3 + drv.outputs.size());
|
||||
attrs.alloc(state.sDrvPath).mkString(path2, {
|
||||
NixStringContextElem::DrvDeep { .drvPath = *storePath },
|
||||
});
|
||||
attrs.alloc(state.sName).mkString(drv.env["name"]);
|
||||
|
||||
auto list = state.buildList(drv.outputs.size());
|
||||
for (const auto & [i, o] : enumerate(drv.outputs)) {
|
||||
mkOutputString(state, attrs, *storePath, o);
|
||||
(list[i] = state.allocValue())->mkString(o.first);
|
||||
}
|
||||
attrs.alloc(state.sOutputs).mkList(list);
|
||||
|
||||
auto w = state.allocValue();
|
||||
w->mkAttrs(attrs);
|
||||
|
||||
if (!state.vImportedDrvToDerivation) {
|
||||
state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
|
||||
state.eval(state.parseExprFromString(
|
||||
#include "imported-drv-to-derivation.nix.gen.hh"
|
||||
, state.rootPath(CanonPath::root)), **state.vImportedDrvToDerivation);
|
||||
}
|
||||
|
||||
state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
|
||||
v.mkApp(*state.vImportedDrvToDerivation, w);
|
||||
state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
|
||||
derivationToValue(state, pos, path, *storePath, v);
|
||||
}
|
||||
else if (vScope) {
|
||||
scopedImport(state, pos, path, vScope, v);
|
||||
}
|
||||
|
||||
else {
|
||||
if (!vScope)
|
||||
state.evalFile(path, v);
|
||||
else {
|
||||
state.forceAttrs(*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport");
|
||||
|
||||
Env * env = &state.allocEnv(vScope->attrs()->size());
|
||||
env->up = &state.baseEnv;
|
||||
|
||||
auto staticEnv = std::make_shared<StaticEnv>(nullptr, state.staticBaseEnv.get(), vScope->attrs()->size());
|
||||
|
||||
unsigned int displ = 0;
|
||||
for (auto & attr : *vScope->attrs()) {
|
||||
staticEnv->vars.emplace_back(attr.name, displ);
|
||||
env->values[displ++] = attr.value;
|
||||
}
|
||||
|
||||
// No need to call staticEnv.sort(), because
|
||||
// args[0]->attrs is already sorted.
|
||||
|
||||
printTalkative("evaluating file '%1%'", path);
|
||||
Expr * e = state.parseExprFromFile(resolveExprPath(path), staticEnv);
|
||||
|
||||
e->eval(state, *env, v);
|
||||
}
|
||||
state.evalFile(path, v);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -691,7 +724,7 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
|
|||
|
||||
/* Call the `operator' function with `e' as argument. */
|
||||
Value newElements;
|
||||
state.callFunction(*op->value, 1, &e, newElements, noPos);
|
||||
state.callFunction(*op->value, {&e, 1}, newElements, noPos);
|
||||
state.forceList(newElements, noPos, "while evaluating the return value of the `operator` passed to builtins.genericClosure");
|
||||
|
||||
/* Add the values returned by the operator to the work set. */
|
||||
|
@ -937,6 +970,9 @@ static RegisterPrimOp primop_tryEval({
|
|||
`let e = { x = throw ""; }; in
|
||||
(builtins.tryEval (builtins.deepSeq e e)).success` will be
|
||||
`false`.
|
||||
|
||||
`tryEval` intentionally does not return the error message, because that risks bringing non-determinism into the evaluation result, and it would become very difficult to improve error reporting without breaking existing expressions.
|
||||
Instead, use [`builtins.addErrorContext`](@docroot@/language/builtins.md#builtins-addErrorContext) to add context to the error message, and use a Nix unit testing tool for testing.
|
||||
)",
|
||||
.fun = prim_tryEval,
|
||||
});
|
||||
|
@ -1567,7 +1603,8 @@ static RegisterPrimOp primop_placeholder({
|
|||
*************************************************************/
|
||||
|
||||
|
||||
/* Convert the argument to a path. !!! obsolete? */
|
||||
/* Convert the argument to a path and then to a string (confusing,
|
||||
eh?). !!! obsolete? */
|
||||
static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
NixStringContext context;
|
||||
|
@ -2129,7 +2166,7 @@ static void prim_toXML(EvalState & state, const PosIdx pos, Value * * args, Valu
|
|||
std::ostringstream out;
|
||||
NixStringContext context;
|
||||
printValueAsXML(state, true, false, *args[0], out, context, pos);
|
||||
v.mkString(out.str(), context);
|
||||
v.mkString(toView(out), context);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_toXML({
|
||||
|
@ -2237,7 +2274,7 @@ static void prim_toJSON(EvalState & state, const PosIdx pos, Value * * args, Val
|
|||
std::ostringstream out;
|
||||
NixStringContext context;
|
||||
printValueAsJSON(state, true, *args[0], pos, out, context);
|
||||
v.mkString(out.str(), context);
|
||||
v.mkString(toView(out), context);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_toJSON({
|
||||
|
@ -2401,7 +2438,6 @@ static RegisterPrimOp primop_toFile({
|
|||
bool EvalState::callPathFilter(
|
||||
Value * filterFun,
|
||||
const SourcePath & path,
|
||||
std::string_view pathArg,
|
||||
PosIdx pos)
|
||||
{
|
||||
auto st = path.lstat();
|
||||
|
@ -2409,12 +2445,12 @@ bool EvalState::callPathFilter(
|
|||
/* Call the filter function. The first argument is the path, the
|
||||
second is a string indicating the type of the file. */
|
||||
Value arg1;
|
||||
arg1.mkString(pathArg);
|
||||
arg1.mkString(path.path.abs());
|
||||
|
||||
// assert that type is not "unknown"
|
||||
Value * args []{&arg1, fileTypeToString(*this, st.type)};
|
||||
Value res;
|
||||
callFunction(*filterFun, 2, args, res, pos);
|
||||
callFunction(*filterFun, args, res, pos);
|
||||
|
||||
return forceBool(res, pos, "while evaluating the return value of the path filter function");
|
||||
}
|
||||
|
@ -2452,7 +2488,7 @@ static void addPath(
|
|||
if (filterFun)
|
||||
filter = std::make_unique<PathFilter>([&](const Path & p) {
|
||||
auto p2 = CanonPath(p);
|
||||
return state.callPathFilter(filterFun, {path.accessor, p2}, p2.abs(), pos);
|
||||
return state.callPathFilter(filterFun, {path.accessor, p2}, pos);
|
||||
});
|
||||
|
||||
std::optional<StorePath> expectedStorePath;
|
||||
|
@ -2578,13 +2614,13 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
|
|||
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), HashAlgorithm::SHA256);
|
||||
else
|
||||
state.error<EvalError>(
|
||||
"unsupported argument '%1%' to 'addPath'",
|
||||
"unsupported argument '%1%' to 'builtins.path'",
|
||||
state.symbols[attr.name]
|
||||
).atPos(attr.pos).debugThrow();
|
||||
}
|
||||
if (!path)
|
||||
state.error<EvalError>(
|
||||
"missing required 'path' attribute in the first argument to builtins.path"
|
||||
"missing required 'path' attribute in the first argument to 'builtins.path'"
|
||||
).atPos(pos).debugThrow();
|
||||
if (name.empty())
|
||||
name = path->baseName();
|
||||
|
@ -3451,7 +3487,7 @@ static void prim_foldlStrict(EvalState & state, const PosIdx pos, Value * * args
|
|||
for (auto [n, elem] : enumerate(args[2]->listItems())) {
|
||||
Value * vs []{vCur, elem};
|
||||
vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue();
|
||||
state.callFunction(*args[0], 2, vs, *vCur, pos);
|
||||
state.callFunction(*args[0], vs, *vCur, pos);
|
||||
}
|
||||
state.forceValue(v, pos);
|
||||
} else {
|
||||
|
@ -3601,7 +3637,7 @@ static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value
|
|||
|
||||
Value * vs[] = {a, b};
|
||||
Value vBool;
|
||||
state.callFunction(*args[0], 2, vs, vBool, noPos);
|
||||
state.callFunction(*args[0], vs, vBool, noPos);
|
||||
return state.forceBool(vBool, pos, "while evaluating the return value of the sorting function passed to builtins.sort");
|
||||
};
|
||||
|
||||
|
@ -4268,7 +4304,7 @@ void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
|||
if (!match[i + 1].matched)
|
||||
v2 = &state.vNull;
|
||||
else
|
||||
(v2 = state.allocValue())->mkString(match[i + 1].str());
|
||||
v2 = mkString(state, match[i + 1]);
|
||||
v.mkList(list);
|
||||
|
||||
} catch (std::regex_error & e) {
|
||||
|
@ -4352,7 +4388,7 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
|||
auto match = *i;
|
||||
|
||||
// Add a string for non-matched characters.
|
||||
(list[idx++] = state.allocValue())->mkString(match.prefix().str());
|
||||
list[idx++] = mkString(state, match.prefix());
|
||||
|
||||
// Add a list for matched substrings.
|
||||
const size_t slen = match.size() - 1;
|
||||
|
@ -4363,14 +4399,14 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
|||
if (!match[si + 1].matched)
|
||||
v2 = &state.vNull;
|
||||
else
|
||||
(v2 = state.allocValue())->mkString(match[si + 1].str());
|
||||
v2 = mkString(state, match[si + 1]);
|
||||
}
|
||||
|
||||
(list[idx++] = state.allocValue())->mkList(list2);
|
||||
|
||||
// Add a string for non-matched suffix characters.
|
||||
if (idx == 2 * len)
|
||||
(list[idx++] = state.allocValue())->mkString(match.suffix().str());
|
||||
list[idx++] = mkString(state, match.suffix());
|
||||
}
|
||||
|
||||
assert(idx == 2 * len + 1);
|
||||
|
@ -4901,7 +4937,7 @@ void EvalState::createBaseEnv()
|
|||
|
||||
/* Now that we've added all primops, sort the `builtins' set,
|
||||
because attribute lookups expect it to be sorted. */
|
||||
baseEnv.values[0]->payload.attrs->sort();
|
||||
getBuiltins().payload.attrs->sort();
|
||||
|
||||
staticBaseEnv->sort();
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ static RegisterPrimOp primop_unsafeDiscardOutputDependency({
|
|||
|
||||
This is the opposite of [`builtins.addDrvOutputDependencies`](#builtins-addDrvOutputDependencies).
|
||||
|
||||
This is unsafe because it allows us to "forget" store objects we would have otherwise refered to with the string context,
|
||||
This is unsafe because it allows us to "forget" store objects we would have otherwise referred to with the string context,
|
||||
whereas Nix normally tracks all dependencies consistently.
|
||||
Safe operations "grow" but never "shrink" string contexts.
|
||||
[`builtins.addDrvOutputDependencies`] in contrast is safe because "derivation deep" string context element always refers to the underlying derivation (among many more things).
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include "value-to-json.hh"
|
||||
#include "fetch-to-store.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
|
@ -75,6 +77,7 @@ struct FetchTreeParams {
|
|||
bool emptyRevFallback = false;
|
||||
bool allowNameArgument = false;
|
||||
bool isFetchGit = false;
|
||||
bool isFinal = false;
|
||||
};
|
||||
|
||||
static void fetchTree(
|
||||
|
@ -192,6 +195,13 @@ static void fetchTree(
|
|||
|
||||
state.checkURI(input.toURLString());
|
||||
|
||||
if (params.isFinal) {
|
||||
input.attrs.insert_or_assign("__final", Explicit<bool>(true));
|
||||
} else {
|
||||
if (input.isFinal())
|
||||
throw Error("input '%s' is not allowed to use the '__final' attribute", input.to_string());
|
||||
}
|
||||
|
||||
auto [storePath, input2] = input.fetchToStore(state.store);
|
||||
|
||||
state.allowPath(storePath);
|
||||
|
@ -245,7 +255,7 @@ static RegisterPrimOp primop_fetchTree({
|
|||
The following source types and associated input attributes are supported.
|
||||
|
||||
<!-- TODO: It would be soooo much more predictable to work with (and
|
||||
document) if `fetchTree` was a curried call with the first paramter for
|
||||
document) if `fetchTree` was a curried call with the first parameter for
|
||||
`type` or an attribute like `builtins.fetchTree.git`! -->
|
||||
|
||||
- `"file"`
|
||||
|
@ -428,6 +438,18 @@ static RegisterPrimOp primop_fetchTree({
|
|||
.experimentalFeature = Xp::FetchTree,
|
||||
});
|
||||
|
||||
void prim_fetchFinalTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
fetchTree(state, pos, args, v, {.isFinal = true});
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_fetchFinalTree({
|
||||
.name = "fetchFinalTree",
|
||||
.args = {"input"},
|
||||
.fun = prim_fetchFinalTree,
|
||||
.internal = true,
|
||||
});
|
||||
|
||||
static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v,
|
||||
const std::string & who, bool unpack, std::string name)
|
||||
{
|
||||
|
|
|
@ -66,7 +66,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V
|
|||
attrs.alloc("_type").mkString("timestamp");
|
||||
std::ostringstream s;
|
||||
s << t;
|
||||
attrs.alloc("value").mkString(s.str());
|
||||
attrs.alloc("value").mkString(toView(s));
|
||||
v.mkAttrs(attrs);
|
||||
} else {
|
||||
throw std::runtime_error("Dates and times are not supported");
|
||||
|
|
|
@ -460,7 +460,7 @@ private:
|
|||
|
||||
std::ostringstream s;
|
||||
s << state.positions[v.payload.lambda.fun->pos];
|
||||
output << " @ " << filterANSIEscapes(s.str());
|
||||
output << " @ " << filterANSIEscapes(toView(s));
|
||||
}
|
||||
} else if (v.isPrimOp()) {
|
||||
if (v.primOp())
|
||||
|
|
|
@ -141,7 +141,9 @@ public:
|
|||
Value * * elems;
|
||||
ListBuilder(EvalState & state, size_t size);
|
||||
|
||||
ListBuilder(ListBuilder && x)
|
||||
// NOTE: Can be noexcept because we are just copying integral values and
|
||||
// raw pointers.
|
||||
ListBuilder(ListBuilder && x) noexcept
|
||||
: size(x.size)
|
||||
, inlineElems{x.inlineElems[0], x.inlineElems[1]}
|
||||
, elems(size <= 2 ? inlineElems : x.elems)
|
||||
|
|
|
@ -28,7 +28,7 @@ struct NixStringContextElem {
|
|||
/**
|
||||
* Plain opaque path to some store object.
|
||||
*
|
||||
* Encoded as just the path: ‘<path>’.
|
||||
* Encoded as just the path: `<path>`.
|
||||
*/
|
||||
using Opaque = SingleDerivedPath::Opaque;
|
||||
|
||||
|
@ -39,7 +39,7 @@ struct NixStringContextElem {
|
|||
* also all outputs of all derivations in that closure (including the
|
||||
* root derivation).
|
||||
*
|
||||
* Encoded in the form ‘=<drvPath>’.
|
||||
* Encoded in the form `=<drvPath>`.
|
||||
*/
|
||||
struct DrvDeep {
|
||||
StorePath drvPath;
|
||||
|
@ -50,7 +50,7 @@ struct NixStringContextElem {
|
|||
/**
|
||||
* Derivation output.
|
||||
*
|
||||
* Encoded in the form ‘!<output>!<drvPath>’.
|
||||
* Encoded in the form `!<output>!<drvPath>`.
|
||||
*/
|
||||
using Built = SingleDerivedPath::Built;
|
||||
|
||||
|
@ -68,9 +68,9 @@ struct NixStringContextElem {
|
|||
|
||||
/**
|
||||
* Decode a context string, one of:
|
||||
* - ‘<path>’
|
||||
* - ‘=<path>’
|
||||
* - ‘!<name>!<path>’
|
||||
* - `<path>`
|
||||
* - `=<path>`
|
||||
* - `!<name>!<path>`
|
||||
*
|
||||
* @param xpSettings Stop-gap to avoid globals during unit tests.
|
||||
*/
|
||||
|
|
1
src/libfetchers-tests/.version
Symbolic link
1
src/libfetchers-tests/.version
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../.version
|
1
src/libfetchers-tests/build-utils-meson
Symbolic link
1
src/libfetchers-tests/build-utils-meson
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../build-utils-meson
|
4
src/libfetchers-tests/data/public-key/defaultType.json
Normal file
4
src/libfetchers-tests/data/public-key/defaultType.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"key": "ABCDE",
|
||||
"type": "ssh-ed25519"
|
||||
}
|
3
src/libfetchers-tests/data/public-key/noRoundTrip.json
Normal file
3
src/libfetchers-tests/data/public-key/noRoundTrip.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"key": "ABCDE"
|
||||
}
|
4
src/libfetchers-tests/data/public-key/simple.json
Normal file
4
src/libfetchers-tests/data/public-key/simple.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"key": "ABCDE",
|
||||
"type": "ssh-rsa"
|
||||
}
|
112
src/libfetchers-tests/git-utils.cc
Normal file
112
src/libfetchers-tests/git-utils.cc
Normal file
|
@ -0,0 +1,112 @@
|
|||
#include "git-utils.hh"
|
||||
#include "file-system.hh"
|
||||
#include "gmock/gmock.h"
|
||||
#include <git2/global.h>
|
||||
#include <git2/repository.h>
|
||||
#include <git2/types.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include "fs-sink.hh"
|
||||
#include "serialise.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class GitUtilsTest : public ::testing::Test
|
||||
{
|
||||
// We use a single repository for all tests.
|
||||
Path tmpDir;
|
||||
std::unique_ptr<AutoDelete> delTmpDir;
|
||||
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
tmpDir = createTempDir();
|
||||
delTmpDir = std::make_unique<AutoDelete>(tmpDir, true);
|
||||
|
||||
// Create the repo with libgit2
|
||||
git_libgit2_init();
|
||||
git_repository * repo = nullptr;
|
||||
auto r = git_repository_init(&repo, tmpDir.c_str(), 0);
|
||||
ASSERT_EQ(r, 0);
|
||||
git_repository_free(repo);
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
// Destroy the AutoDelete, triggering removal
|
||||
// not AutoDelete::reset(), which would cancel the deletion.
|
||||
delTmpDir.reset();
|
||||
}
|
||||
|
||||
ref<GitRepo> openRepo()
|
||||
{
|
||||
return GitRepo::openRepo(tmpDir, true, false);
|
||||
}
|
||||
};
|
||||
|
||||
void writeString(CreateRegularFileSink & fileSink, std::string contents, bool executable)
|
||||
{
|
||||
if (executable)
|
||||
fileSink.isExecutable();
|
||||
fileSink.preallocateContents(contents.size());
|
||||
fileSink(contents);
|
||||
}
|
||||
|
||||
TEST_F(GitUtilsTest, sink_basic)
|
||||
{
|
||||
auto repo = openRepo();
|
||||
auto sink = repo->getFileSystemObjectSink();
|
||||
|
||||
// TODO/Question: It seems a little odd that we use the tarball-like convention of requiring a top-level directory
|
||||
// here
|
||||
// The sync method does not document this behavior, should probably renamed because it's not very
|
||||
// general, and I can't imagine that "non-conventional" archives or any other source to be handled by
|
||||
// this sink.
|
||||
|
||||
sink->createDirectory(CanonPath("foo-1.1"));
|
||||
|
||||
sink->createRegularFile(CanonPath("foo-1.1/hello"), [](CreateRegularFileSink & fileSink) {
|
||||
writeString(fileSink, "hello world", false);
|
||||
});
|
||||
sink->createRegularFile(CanonPath("foo-1.1/bye"), [](CreateRegularFileSink & fileSink) {
|
||||
writeString(fileSink, "thanks for all the fish", false);
|
||||
});
|
||||
sink->createSymlink(CanonPath("foo-1.1/bye-link"), "bye");
|
||||
sink->createDirectory(CanonPath("foo-1.1/empty"));
|
||||
sink->createDirectory(CanonPath("foo-1.1/links"));
|
||||
sink->createHardlink(CanonPath("foo-1.1/links/foo"), CanonPath("foo-1.1/hello"));
|
||||
|
||||
// sink->createHardlink("foo-1.1/links/foo-2", CanonPath("foo-1.1/hello"));
|
||||
|
||||
auto result = repo->dereferenceSingletonDirectory(sink->flush());
|
||||
auto accessor = repo->getAccessor(result, false);
|
||||
auto entries = accessor->readDirectory(CanonPath::root);
|
||||
ASSERT_EQ(entries.size(), 5);
|
||||
ASSERT_EQ(accessor->readFile(CanonPath("hello")), "hello world");
|
||||
ASSERT_EQ(accessor->readFile(CanonPath("bye")), "thanks for all the fish");
|
||||
ASSERT_EQ(accessor->readLink(CanonPath("bye-link")), "bye");
|
||||
ASSERT_EQ(accessor->readDirectory(CanonPath("empty")).size(), 0);
|
||||
ASSERT_EQ(accessor->readFile(CanonPath("links/foo")), "hello world");
|
||||
};
|
||||
|
||||
TEST_F(GitUtilsTest, sink_hardlink)
|
||||
{
|
||||
auto repo = openRepo();
|
||||
auto sink = repo->getFileSystemObjectSink();
|
||||
|
||||
sink->createDirectory(CanonPath("foo-1.1"));
|
||||
|
||||
sink->createRegularFile(CanonPath("foo-1.1/hello"), [](CreateRegularFileSink & fileSink) {
|
||||
writeString(fileSink, "hello world", false);
|
||||
});
|
||||
|
||||
try {
|
||||
sink->createHardlink(CanonPath("foo-1.1/link"), CanonPath("hello"));
|
||||
FAIL() << "Expected an exception";
|
||||
} catch (const nix::Error & e) {
|
||||
ASSERT_THAT(e.msg(), testing::HasSubstr("cannot find hard link target"));
|
||||
ASSERT_THAT(e.msg(), testing::HasSubstr("/hello"));
|
||||
ASSERT_THAT(e.msg(), testing::HasSubstr("foo-1.1/link"));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nix
|
72
src/libfetchers-tests/meson.build
Normal file
72
src/libfetchers-tests/meson.build
Normal file
|
@ -0,0 +1,72 @@
|
|||
project('nix-fetchers-tests', 'cpp',
|
||||
version : files('.version'),
|
||||
default_options : [
|
||||
'cpp_std=c++2a',
|
||||
# TODO(Qyriad): increase the warning level
|
||||
'warning_level=1',
|
||||
'debug=true',
|
||||
'optimization=2',
|
||||
'errorlogs=true', # Please print logs for tests that fail
|
||||
],
|
||||
meson_version : '>= 1.1',
|
||||
license : 'LGPL-2.1-or-later',
|
||||
)
|
||||
|
||||
cxx = meson.get_compiler('cpp')
|
||||
|
||||
subdir('build-utils-meson/deps-lists')
|
||||
|
||||
deps_private_maybe_subproject = [
|
||||
dependency('nix-store-test-support'),
|
||||
dependency('nix-fetchers'),
|
||||
]
|
||||
deps_public_maybe_subproject = [
|
||||
]
|
||||
subdir('build-utils-meson/subprojects')
|
||||
|
||||
subdir('build-utils-meson/export-all-symbols')
|
||||
subdir('build-utils-meson/windows-version')
|
||||
|
||||
rapidcheck = dependency('rapidcheck')
|
||||
deps_private += rapidcheck
|
||||
|
||||
gtest = dependency('gtest', main : true)
|
||||
deps_private += gtest
|
||||
|
||||
add_project_arguments(
|
||||
# TODO(Qyriad): Yes this is how the autoconf+Make system did it.
|
||||
# It would be nice for our headers to be idempotent instead.
|
||||
'-include', 'config-util.hh',
|
||||
'-include', 'config-store.hh',
|
||||
# '-include', 'config-fetchers.h',
|
||||
language : 'cpp',
|
||||
)
|
||||
|
||||
subdir('build-utils-meson/common')
|
||||
|
||||
sources = files(
|
||||
'public-key.cc',
|
||||
)
|
||||
|
||||
include_dirs = [include_directories('.')]
|
||||
|
||||
|
||||
this_exe = executable(
|
||||
meson.project_name(),
|
||||
sources,
|
||||
dependencies : deps_private_subproject + deps_private + deps_other,
|
||||
include_directories : include_dirs,
|
||||
# TODO: -lrapidcheck, see ../libutil-support/build.meson
|
||||
link_args: linker_export_flags + ['-lrapidcheck'],
|
||||
# get main from gtest
|
||||
install : true,
|
||||
)
|
||||
|
||||
test(
|
||||
meson.project_name(),
|
||||
this_exe,
|
||||
env : {
|
||||
'_NIX_TEST_UNIT_DATA': meson.current_source_dir() / 'data',
|
||||
},
|
||||
protocol : 'gtest',
|
||||
)
|
81
src/libfetchers-tests/package.nix
Normal file
81
src/libfetchers-tests/package.nix
Normal file
|
@ -0,0 +1,81 @@
|
|||
{ lib
|
||||
, buildPackages
|
||||
, stdenv
|
||||
, mkMesonExecutable
|
||||
|
||||
, nix-fetchers
|
||||
, nix-store-test-support
|
||||
|
||||
, rapidcheck
|
||||
, gtest
|
||||
, runCommand
|
||||
|
||||
# Configuration Options
|
||||
|
||||
, version
|
||||
, resolvePath
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) fileset;
|
||||
in
|
||||
|
||||
mkMesonExecutable (finalAttrs: {
|
||||
pname = "nix-fetchers-tests";
|
||||
inherit version;
|
||||
|
||||
workDir = ./.;
|
||||
fileset = fileset.unions [
|
||||
../../build-utils-meson
|
||||
./build-utils-meson
|
||||
../../.version
|
||||
./.version
|
||||
./meson.build
|
||||
# ./meson.options
|
||||
(fileset.fileFilter (file: file.hasExt "cc") ./.)
|
||||
(fileset.fileFilter (file: file.hasExt "hh") ./.)
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
nix-fetchers
|
||||
nix-store-test-support
|
||||
rapidcheck
|
||||
gtest
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
mesonFlags = [
|
||||
];
|
||||
|
||||
env = lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
|
||||
LDFLAGS = "-fuse-ld=gold";
|
||||
};
|
||||
|
||||
passthru = {
|
||||
tests = {
|
||||
run = runCommand "${finalAttrs.pname}-run" {
|
||||
meta.broken = !stdenv.hostPlatform.emulatorAvailable buildPackages;
|
||||
} (lib.optionalString stdenv.hostPlatform.isWindows ''
|
||||
export HOME="$PWD/home-dir"
|
||||
mkdir -p "$HOME"
|
||||
'' + ''
|
||||
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
|
||||
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
||||
touch $out
|
||||
'');
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||
mainProgram = finalAttrs.pname + stdenv.hostPlatform.extensions.executable;
|
||||
};
|
||||
|
||||
})
|
54
src/libfetchers-tests/public-key.cc
Normal file
54
src/libfetchers-tests/public-key.cc
Normal file
|
@ -0,0 +1,54 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include "fetchers.hh"
|
||||
#include "json-utils.hh"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "tests/characterization.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
class PublicKeyTest : public CharacterizationTest
|
||||
{
|
||||
std::filesystem::path unitTestData = getUnitTestData() / "public-key";
|
||||
|
||||
public:
|
||||
std::filesystem::path goldenMaster(std::string_view testStem) const override {
|
||||
return unitTestData / testStem;
|
||||
}
|
||||
};
|
||||
|
||||
#define TEST_JSON(FIXTURE, NAME, VAL) \
|
||||
TEST_F(FIXTURE, PublicKey_ ## NAME ## _from_json) { \
|
||||
readTest(#NAME ".json", [&](const auto & encoded_) { \
|
||||
fetchers::PublicKey expected { VAL }; \
|
||||
fetchers::PublicKey got = nlohmann::json::parse(encoded_); \
|
||||
ASSERT_EQ(got, expected); \
|
||||
}); \
|
||||
} \
|
||||
\
|
||||
TEST_F(FIXTURE, PublicKey_ ## NAME ## _to_json) { \
|
||||
writeTest(#NAME ".json", [&]() -> json { \
|
||||
return nlohmann::json(fetchers::PublicKey { VAL }); \
|
||||
}, [](const auto & file) { \
|
||||
return json::parse(readFile(file)); \
|
||||
}, [](const auto & file, const auto & got) { \
|
||||
return writeFile(file, got.dump(2) + "\n"); \
|
||||
}); \
|
||||
}
|
||||
|
||||
TEST_JSON(PublicKeyTest, simple, (fetchers::PublicKey { .type = "ssh-rsa", .key = "ABCDE" }))
|
||||
|
||||
TEST_JSON(PublicKeyTest, defaultType, fetchers::PublicKey { .key = "ABCDE" })
|
||||
|
||||
#undef TEST_JSON
|
||||
|
||||
TEST_F(PublicKeyTest, PublicKey_noRoundTrip_from_json) {
|
||||
readTest("noRoundTrip.json", [&](const auto & encoded_) {
|
||||
fetchers::PublicKey expected = { .type = "ssh-ed25519", .key = "ABCDE" };
|
||||
fetchers::PublicKey got = nlohmann::json::parse(encoded_);
|
||||
ASSERT_EQ(got, expected);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -36,7 +36,7 @@ struct CacheImpl : Cache
|
|||
{
|
||||
auto state(_state.lock());
|
||||
|
||||
auto dbPath = getCacheDir() + "/fetcher-cache-v2.sqlite";
|
||||
auto dbPath = getCacheDir() + "/fetcher-cache-v3.sqlite";
|
||||
createDirs(dirOf(dbPath));
|
||||
|
||||
state->db = SQLite(dbPath);
|
||||
|
|
|
@ -44,6 +44,8 @@ StorePath fetchToStore(
|
|||
: store.addToStore(
|
||||
name, path, method, HashAlgorithm::SHA256, {}, filter2, repair);
|
||||
|
||||
debug(mode == FetchMode::DryRun ? "hashed '%s'" : "copied '%s' to '%s'", path, store.printStorePath(storePath));
|
||||
|
||||
if (cacheKey && mode == FetchMode::Copy)
|
||||
fetchers::getCache()->upsert(*cacheKey, store, {}, storePath);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "source-path.hh"
|
||||
#include "fetch-to-store.hh"
|
||||
#include "json-utils.hh"
|
||||
#include "store-path-accessor.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
|
@ -100,7 +101,7 @@ Input Input::fromAttrs(const Settings & settings, Attrs && attrs)
|
|||
auto allowedAttrs = inputScheme->allowedAttrs();
|
||||
|
||||
for (auto & [name, _] : attrs)
|
||||
if (name != "type" && allowedAttrs.count(name) == 0)
|
||||
if (name != "type" && name != "__final" && allowedAttrs.count(name) == 0)
|
||||
throw Error("input attribute '%s' not supported by scheme '%s'", name, schemeName);
|
||||
|
||||
auto res = inputScheme->inputFromAttrs(settings, attrs);
|
||||
|
@ -145,6 +146,11 @@ bool Input::isLocked() const
|
|||
return scheme && scheme->isLocked(*this);
|
||||
}
|
||||
|
||||
bool Input::isFinal() const
|
||||
{
|
||||
return maybeGetBoolAttr(attrs, "__final").value_or(false);
|
||||
}
|
||||
|
||||
std::optional<std::string> Input::isRelative() const
|
||||
{
|
||||
assert(scheme);
|
||||
|
@ -178,16 +184,24 @@ std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
|
|||
|
||||
auto [storePath, input] = [&]() -> std::pair<StorePath, Input> {
|
||||
try {
|
||||
auto [accessor, final] = getAccessorUnchecked(store);
|
||||
auto [accessor, result] = getAccessorUnchecked(store);
|
||||
|
||||
auto storePath = nix::fetchToStore(*store, SourcePath(accessor), FetchMode::Copy, final.getName());
|
||||
auto storePath = nix::fetchToStore(*store, SourcePath(accessor), FetchMode::Copy, result.getName());
|
||||
|
||||
auto narHash = store->queryPathInfo(storePath)->narHash;
|
||||
final.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
|
||||
result.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
|
||||
|
||||
scheme->checkLocks(*this, final);
|
||||
// FIXME: we would like to mark inputs as final in
|
||||
// getAccessorUnchecked(), but then we can't add
|
||||
// narHash. Or maybe narHash should be excluded from the
|
||||
// concept of "final" inputs?
|
||||
result.attrs.insert_or_assign("__final", Explicit<bool>(true));
|
||||
|
||||
return {storePath, final};
|
||||
assert(result.isFinal());
|
||||
|
||||
checkLocks(*this, result);
|
||||
|
||||
return {storePath, result};
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while fetching the input '%s'", to_string());
|
||||
throw;
|
||||
|
@ -197,13 +211,40 @@ std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
|
|||
return {std::move(storePath), input};
|
||||
}
|
||||
|
||||
void InputScheme::checkLocks(const Input & specified, const Input & final) const
|
||||
void Input::checkLocks(Input specified, Input & result)
|
||||
{
|
||||
/* If the original input is final, then we just return the
|
||||
original attributes, dropping any new fields returned by the
|
||||
fetcher. However, any fields that are in both the specified and
|
||||
result input must be identical. */
|
||||
if (specified.isFinal()) {
|
||||
|
||||
/* Backwards compatibility hack: we had some lock files in the
|
||||
past that 'narHash' fields with incorrect base-64
|
||||
formatting (lacking the trailing '=', e.g. 'sha256-ri...Mw'
|
||||
instead of ''sha256-ri...Mw='). So fix that. */
|
||||
if (auto prevNarHash = specified.getNarHash())
|
||||
specified.attrs.insert_or_assign("narHash", prevNarHash->to_string(HashFormat::SRI, true));
|
||||
|
||||
for (auto & field : specified.attrs) {
|
||||
auto field2 = result.attrs.find(field.first);
|
||||
if (field2 != result.attrs.end() && field.second != field2->second)
|
||||
throw Error("mismatch in field '%s' of input '%s', got '%s'",
|
||||
field.first,
|
||||
attrsToJSON(specified.attrs),
|
||||
attrsToJSON(result.attrs));
|
||||
}
|
||||
|
||||
result.attrs = specified.attrs;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto prevNarHash = specified.getNarHash()) {
|
||||
if (final.getNarHash() != prevNarHash) {
|
||||
if (final.getNarHash())
|
||||
if (result.getNarHash() != prevNarHash) {
|
||||
if (result.getNarHash())
|
||||
throw Error((unsigned int) 102, "NAR hash mismatch in input '%s', expected '%s' but got '%s'",
|
||||
specified.to_string(), prevNarHash->to_string(HashFormat::SRI, true), final.getNarHash()->to_string(HashFormat::SRI, true));
|
||||
specified.to_string(), prevNarHash->to_string(HashFormat::SRI, true), result.getNarHash()->to_string(HashFormat::SRI, true));
|
||||
else
|
||||
throw Error((unsigned int) 102, "NAR hash mismatch in input '%s', expected '%s' but got none",
|
||||
specified.to_string(), prevNarHash->to_string(HashFormat::SRI, true));
|
||||
|
@ -211,32 +252,32 @@ void InputScheme::checkLocks(const Input & specified, const Input & final) const
|
|||
}
|
||||
|
||||
if (auto prevLastModified = specified.getLastModified()) {
|
||||
if (final.getLastModified() != prevLastModified)
|
||||
throw Error("'lastModified' attribute mismatch in input '%s', expected %d",
|
||||
final.to_string(), *prevLastModified);
|
||||
if (result.getLastModified() != prevLastModified)
|
||||
throw Error("'lastModified' attribute mismatch in input '%s', expected %d, got %d",
|
||||
result.to_string(), *prevLastModified, result.getLastModified().value_or(-1));
|
||||
}
|
||||
|
||||
if (auto prevRev = specified.getRev()) {
|
||||
if (final.getRev() != prevRev)
|
||||
if (result.getRev() != prevRev)
|
||||
throw Error("'rev' attribute mismatch in input '%s', expected %s",
|
||||
final.to_string(), prevRev->gitRev());
|
||||
result.to_string(), prevRev->gitRev());
|
||||
}
|
||||
|
||||
if (auto prevRevCount = specified.getRevCount()) {
|
||||
if (final.getRevCount() != prevRevCount)
|
||||
if (result.getRevCount() != prevRevCount)
|
||||
throw Error("'revCount' attribute mismatch in input '%s', expected %d",
|
||||
final.to_string(), *prevRevCount);
|
||||
result.to_string(), *prevRevCount);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> Input::getAccessor(ref<Store> store) const
|
||||
{
|
||||
try {
|
||||
auto [accessor, final] = getAccessorUnchecked(store);
|
||||
auto [accessor, result] = getAccessorUnchecked(store);
|
||||
|
||||
scheme->checkLocks(*this, final);
|
||||
checkLocks(*this, result);
|
||||
|
||||
return {accessor, std::move(final)};
|
||||
return {accessor, std::move(result)};
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while fetching the input '%s'", to_string());
|
||||
throw;
|
||||
|
@ -250,12 +291,42 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
|
|||
if (!scheme)
|
||||
throw Error("cannot fetch unsupported input '%s'", attrsToJSON(toAttrs()));
|
||||
|
||||
auto [accessor, final] = scheme->getAccessor(store, *this);
|
||||
/* The tree may already be in the Nix store, or it could be
|
||||
substituted (which is often faster than fetching from the
|
||||
original source). So check that. We only do this for final
|
||||
inputs, otherwise there is a risk that we don't return the
|
||||
same attributes (like `lastModified`) that the "real" fetcher
|
||||
would return.
|
||||
|
||||
FIXME: add a setting to disable this.
|
||||
FIXME: substituting may be slower than fetching normally,
|
||||
e.g. for fetchers like Git that are incremental!
|
||||
*/
|
||||
if (isFinal() && getNarHash()) {
|
||||
try {
|
||||
auto storePath = computeStorePath(*store);
|
||||
|
||||
store->ensurePath(storePath);
|
||||
|
||||
debug("using substituted/cached input '%s' in '%s'",
|
||||
to_string(), store->printStorePath(storePath));
|
||||
|
||||
auto accessor = makeStorePathAccessor(store, storePath);
|
||||
|
||||
accessor->fingerprint = scheme->getFingerprint(store, *this);
|
||||
|
||||
return {accessor, *this};
|
||||
} catch (Error & e) {
|
||||
debug("substitution of input '%s' failed: %s", to_string(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
auto [accessor, result] = scheme->getAccessor(store, *this);
|
||||
|
||||
assert(!accessor->fingerprint);
|
||||
accessor->fingerprint = scheme->getFingerprint(store, final);
|
||||
accessor->fingerprint = scheme->getFingerprint(store, result);
|
||||
|
||||
return {accessor, std::move(final)};
|
||||
return {accessor, std::move(result)};
|
||||
}
|
||||
|
||||
Input Input::applyOverrides(
|
||||
|
@ -410,7 +481,10 @@ namespace nlohmann {
|
|||
|
||||
using namespace nix;
|
||||
|
||||
fetchers::PublicKey adl_serializer<fetchers::PublicKey>::from_json(const json & json) {
|
||||
#ifndef DOXYGEN_SKIP
|
||||
|
||||
fetchers::PublicKey adl_serializer<fetchers::PublicKey>::from_json(const json & json)
|
||||
{
|
||||
fetchers::PublicKey res = { };
|
||||
if (auto type = optionalValueAt(json, "type"))
|
||||
res.type = getString(*type);
|
||||
|
@ -420,9 +494,12 @@ fetchers::PublicKey adl_serializer<fetchers::PublicKey>::from_json(const json &
|
|||
return res;
|
||||
}
|
||||
|
||||
void adl_serializer<fetchers::PublicKey>::to_json(json & json, fetchers::PublicKey p) {
|
||||
void adl_serializer<fetchers::PublicKey>::to_json(json & json, fetchers::PublicKey p)
|
||||
{
|
||||
json["type"] = p.type;
|
||||
json["key"] = p.key;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
@ -73,14 +73,15 @@ public:
|
|||
Attrs toAttrs() const;
|
||||
|
||||
/**
|
||||
* Check whether this is a "direct" input, that is, not
|
||||
* Return whether this is a "direct" input, that is, not
|
||||
* one that goes through a registry.
|
||||
*/
|
||||
bool isDirect() const;
|
||||
|
||||
/**
|
||||
* Check whether this is a "locked" input, that is,
|
||||
* one that contains a commit hash or content hash.
|
||||
* Return whether this is a "locked" input, that is, it has
|
||||
* attributes like a Git revision or NAR hash that uniquely
|
||||
* identify its contents.
|
||||
*/
|
||||
bool isLocked() const;
|
||||
|
||||
|
@ -90,6 +91,18 @@ public:
|
|||
*/
|
||||
std::optional<std::string> isRelative() const;
|
||||
|
||||
/**
|
||||
* Return whether this is a "final" input, meaning that fetching
|
||||
* it will not add, remove or change any attributes. (See
|
||||
* `checkLocks()` for the semantics.) Only "final" inputs can be
|
||||
* substituted from a binary cache.
|
||||
*
|
||||
* The "final" state is denoted by the presence of an attribute
|
||||
* `__final = true`. This attribute is currently undocumented and
|
||||
* for internal use only.
|
||||
*/
|
||||
bool isFinal() const;
|
||||
|
||||
bool operator ==(const Input & other) const noexcept;
|
||||
|
||||
bool contains(const Input & other) const;
|
||||
|
@ -100,6 +113,19 @@ public:
|
|||
*/
|
||||
std::pair<StorePath, Input> fetchToStore(ref<Store> store) const;
|
||||
|
||||
/**
|
||||
* Check the locking attributes in `result` against
|
||||
* `specified`. E.g. if `specified` has a `rev` attribute, then
|
||||
* `result` must have the same `rev` attribute. Throw an exception
|
||||
* if there is a mismatch.
|
||||
*
|
||||
* If `specified` is marked final (i.e. has the `__final`
|
||||
* attribute), then the intersection of attributes in `specified`
|
||||
* and `result` must be equal, and `final.attrs` is set to
|
||||
* `specified.attrs` (i.e. we discard any new attributes).
|
||||
*/
|
||||
static void checkLocks(Input specified, Input & result);
|
||||
|
||||
/**
|
||||
* Return a `SourceAccessor` that allows access to files in the
|
||||
* input without copying it to the store. Also return a possibly
|
||||
|
@ -145,6 +171,10 @@ public:
|
|||
/**
|
||||
* For locked inputs, return a string that uniquely specifies the
|
||||
* content of the input (typically a commit hash or content hash).
|
||||
*
|
||||
* Only known-equivalent inputs should return the same fingerprint.
|
||||
*
|
||||
* This is not a stable identifier between Nix versions, but not guaranteed to change either.
|
||||
*/
|
||||
std::optional<std::string> getFingerprint(ref<Store> store) const;
|
||||
};
|
||||
|
@ -216,32 +246,12 @@ struct InputScheme
|
|||
virtual bool isDirect(const Input & input) const
|
||||
{ return true; }
|
||||
|
||||
/**
|
||||
* A sufficiently unique string that can be used as a cache key to identify the `input`.
|
||||
*
|
||||
* Only known-equivalent inputs should return the same fingerprint.
|
||||
*
|
||||
* This is not a stable identifier between Nix versions, but not guaranteed to change either.
|
||||
*/
|
||||
virtual std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const
|
||||
{ return std::nullopt; }
|
||||
|
||||
/**
|
||||
* Return `true` if this input is considered "locked", i.e. it has
|
||||
* attributes like a Git revision or NAR hash that uniquely
|
||||
* identify its contents.
|
||||
*/
|
||||
virtual bool isLocked(const Input & input) const
|
||||
{ return false; }
|
||||
|
||||
/**
|
||||
* Check the locking attributes in `final` against
|
||||
* `specified`. E.g. if `specified` has a `rev` attribute, then
|
||||
* `final` must have the same `rev` attribute. Throw an exception
|
||||
* if there is a mismatch.
|
||||
*/
|
||||
virtual void checkLocks(const Input & specified, const Input & final) const;
|
||||
|
||||
virtual std::optional<std::string> isRelative(const Input & input) const
|
||||
{ return std::nullopt; }
|
||||
};
|
||||
|
|
|
@ -205,6 +205,31 @@ static git_packbuilder_progress PACKBUILDER_PROGRESS_CHECK_INTERRUPT = &packBuil
|
|||
|
||||
} // extern "C"
|
||||
|
||||
static void initRepoAtomically(std::filesystem::path &path, bool bare) {
|
||||
if (pathExists(path.string())) return;
|
||||
|
||||
Path tmpDir = createTempDir(os_string_to_string(PathViewNG { std::filesystem::path(path).parent_path() }));
|
||||
AutoDelete delTmpDir(tmpDir, true);
|
||||
Repository tmpRepo;
|
||||
|
||||
if (git_repository_init(Setter(tmpRepo), tmpDir.c_str(), bare))
|
||||
throw Error("creating Git repository %s: %s", path, git_error_last()->message);
|
||||
try {
|
||||
std::filesystem::rename(tmpDir, path);
|
||||
} catch (std::filesystem::filesystem_error & e) {
|
||||
// Someone may race us to create the repository.
|
||||
if (e.code() == std::errc::file_exists
|
||||
// `path` may be attempted to be deleted by s::f::rename, in which case the code is:
|
||||
|| e.code() == std::errc::directory_not_empty) {
|
||||
return;
|
||||
}
|
||||
else
|
||||
throw SysError("moving temporary git repository from %s to %s", tmpDir, path);
|
||||
}
|
||||
// we successfully moved the repository, so the temporary directory no longer exists.
|
||||
delTmpDir.cancel();
|
||||
}
|
||||
|
||||
struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
{
|
||||
/** Location of the repository on disk. */
|
||||
|
@ -226,13 +251,9 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
|||
{
|
||||
initLibGit2();
|
||||
|
||||
if (pathExists(path.string())) {
|
||||
if (git_repository_open(Setter(repo), path.string().c_str()))
|
||||
throw Error("opening Git repository '%s': %s", path, git_error_last()->message);
|
||||
} else {
|
||||
if (git_repository_init(Setter(repo), path.string().c_str(), bare))
|
||||
throw Error("creating Git repository '%s': %s", path, git_error_last()->message);
|
||||
}
|
||||
initRepoAtomically(path, bare);
|
||||
if (git_repository_open(Setter(repo), path.string().c_str()))
|
||||
throw Error("opening Git repository %s: %s", path, git_error_last()->message);
|
||||
|
||||
ObjectDb odb;
|
||||
if (git_repository_odb(Setter(odb), repo.get()))
|
||||
|
@ -583,7 +604,13 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
|||
std::string re = R"(Good "git" signature for \* with .* key SHA256:[)";
|
||||
for (const fetchers::PublicKey & k : publicKeys){
|
||||
// Calculate sha256 fingerprint from public key and escape the regex symbol '+' to match the key literally
|
||||
auto fingerprint = trim(hashString(HashAlgorithm::SHA256, base64Decode(k.key)).to_string(nix::HashFormat::Base64, false), "=");
|
||||
std::string keyDecoded;
|
||||
try {
|
||||
keyDecoded = base64Decode(k.key);
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while decoding public key '%s' used for git signature", k.key);
|
||||
}
|
||||
auto fingerprint = trim(hashString(HashAlgorithm::SHA256, keyDecoded).to_string(nix::HashFormat::Base64, false), "=");
|
||||
auto escaped_fingerprint = std::regex_replace(fingerprint, std::regex("\\+"), "\\+" );
|
||||
re += "(" + escaped_fingerprint + ")";
|
||||
}
|
||||
|
@ -954,8 +981,24 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
|
|||
|
||||
void pushBuilder(std::string name)
|
||||
{
|
||||
const git_tree_entry * entry;
|
||||
Tree prevTree = nullptr;
|
||||
|
||||
if (!pendingDirs.empty() &&
|
||||
(entry = git_treebuilder_get(pendingDirs.back().builder.get(), name.c_str())))
|
||||
{
|
||||
/* Clone a tree that we've already finished. This happens
|
||||
if a tarball has directory entries that are not
|
||||
contiguous. */
|
||||
if (git_tree_entry_type(entry) != GIT_OBJECT_TREE)
|
||||
throw Error("parent of '%s' is not a directory", name);
|
||||
|
||||
if (git_tree_entry_to_object((git_object * *) (git_tree * *) Setter(prevTree), *repo, entry))
|
||||
throw Error("looking up parent of '%s': %s", name, git_error_last()->message);
|
||||
}
|
||||
|
||||
git_treebuilder * b;
|
||||
if (git_treebuilder_new(&b, *repo, nullptr))
|
||||
if (git_treebuilder_new(&b, *repo, prevTree.get()))
|
||||
throw Error("creating a tree builder: %s", git_error_last()->message);
|
||||
pendingDirs.push_back({ .name = std::move(name), .builder = TreeBuilder(b) });
|
||||
};
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
#include "git-utils.hh"
|
||||
#include "logging.hh"
|
||||
#include "finally.hh"
|
||||
|
||||
#include "fetch-settings.hh"
|
||||
#include "json-utils.hh"
|
||||
|
||||
#include <regex>
|
||||
#include <string.h>
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
libraries += libfetchers
|
||||
|
||||
libfetchers_NAME = libnixfetchers
|
||||
|
||||
libfetchers_DIR := $(d)
|
||||
|
||||
libfetchers_SOURCES := $(wildcard $(d)/*.cc)
|
||||
|
||||
# Not just for this library itself, but also for downstream libraries using this library
|
||||
|
||||
INCLUDE_libfetchers := -I $(d)
|
||||
|
||||
libfetchers_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers)
|
||||
|
||||
libfetchers_LDFLAGS += $(THREAD_LDFLAGS) $(LIBGIT2_LIBS) -larchive
|
||||
|
||||
libfetchers_LIBS = libutil libstore
|
|
@ -26,8 +26,6 @@ deps_public_maybe_subproject = [
|
|||
]
|
||||
subdir('build-utils-meson/subprojects')
|
||||
|
||||
subdir('build-utils-meson/threads')
|
||||
|
||||
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
|
||||
deps_public += nlohmann_json
|
||||
|
||||
|
@ -43,7 +41,7 @@ add_project_arguments(
|
|||
language : 'cpp',
|
||||
)
|
||||
|
||||
subdir('build-utils-meson/diagnostics')
|
||||
subdir('build-utils-meson/common')
|
||||
|
||||
sources = files(
|
||||
'attrs.cc',
|
||||
|
@ -52,15 +50,15 @@ sources = files(
|
|||
'fetch-to-store.cc',
|
||||
'fetchers.cc',
|
||||
'filtering-source-accessor.cc',
|
||||
'git.cc',
|
||||
'git-utils.cc',
|
||||
'git.cc',
|
||||
'github.cc',
|
||||
'indirect.cc',
|
||||
'mercurial.cc',
|
||||
'mounted-source-accessor.cc',
|
||||
'path.cc',
|
||||
'store-path-accessor.cc',
|
||||
'registry.cc',
|
||||
'store-path-accessor.cc',
|
||||
'tarball.cc',
|
||||
)
|
||||
|
||||
|
@ -71,10 +69,10 @@ headers = files(
|
|||
'cache.hh',
|
||||
'fetch-settings.hh',
|
||||
'fetch-to-store.hh',
|
||||
'fetchers.hh',
|
||||
'filtering-source-accessor.hh',
|
||||
'git-utils.hh',
|
||||
'mounted-source-accessor.hh',
|
||||
'fetchers.hh',
|
||||
'registry.hh',
|
||||
'store-path-accessor.hh',
|
||||
'tarball.hh',
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
{ lib
|
||||
, stdenv
|
||||
, mkMesonDerivation
|
||||
, releaseTools
|
||||
|
||||
, meson
|
||||
, ninja
|
||||
, pkg-config
|
||||
, mkMesonLibrary
|
||||
|
||||
, nix-util
|
||||
, nix-store
|
||||
, nlohmann_json
|
||||
, libgit2
|
||||
, man
|
||||
|
||||
# Configuration Options
|
||||
|
||||
|
@ -22,7 +16,7 @@ let
|
|||
inherit (lib) fileset;
|
||||
in
|
||||
|
||||
mkMesonDerivation (finalAttrs: {
|
||||
mkMesonLibrary (finalAttrs: {
|
||||
pname = "nix-fetchers";
|
||||
inherit version;
|
||||
|
||||
|
@ -37,14 +31,6 @@ mkMesonDerivation (finalAttrs: {
|
|||
(fileset.fileFilter (file: file.hasExt "hh") ./.)
|
||||
];
|
||||
|
||||
outputs = [ "out" "dev" ];
|
||||
|
||||
nativeBuildInputs = [
|
||||
meson
|
||||
ninja
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
libgit2
|
||||
];
|
||||
|
@ -67,10 +53,6 @@ mkMesonDerivation (finalAttrs: {
|
|||
LDFLAGS = "-fuse-ld=gold";
|
||||
};
|
||||
|
||||
separateDebugInfo = !stdenv.hostPlatform.isStatic;
|
||||
|
||||
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
|
||||
|
||||
meta = {
|
||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||
};
|
||||
|
|
|
@ -72,6 +72,7 @@ struct PathInputScheme : InputScheme
|
|||
auto query = attrsToQuery(input.attrs);
|
||||
query.erase("path");
|
||||
query.erase("type");
|
||||
query.erase("__final");
|
||||
return ParsedURL {
|
||||
.scheme = "path",
|
||||
.path = getStrAttr(input.attrs, "path"),
|
||||
|
@ -140,7 +141,11 @@ struct PathInputScheme : InputScheme
|
|||
});
|
||||
storePath = store->addToStoreFromDump(*src, "source");
|
||||
}
|
||||
input.attrs.insert_or_assign("lastModified", uint64_t(mtime));
|
||||
|
||||
/* Trust the lastModified value supplied by the user, if
|
||||
any. It's not a "secure" attribute so we don't care. */
|
||||
if (!input.getLastModified())
|
||||
input.attrs.insert_or_assign("lastModified", uint64_t(mtime));
|
||||
|
||||
return {makeStorePathAccessor(store, *storePath), std::move(input)};
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ DownloadFileResult downloadFile(
|
|||
/* Cache metadata for all URLs in the redirect chain. */
|
||||
for (auto & url : res.urls) {
|
||||
key.second.insert_or_assign("url", url);
|
||||
assert(!res.urls.empty());
|
||||
infoAttrs.insert_or_assign("url", *res.urls.rbegin());
|
||||
getCache()->upsert(key, *store, infoAttrs, *storePath);
|
||||
}
|
||||
|
|
1
src/libflake-tests/.version
Symbolic link
1
src/libflake-tests/.version
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../.version
|
1
src/libflake-tests/build-utils-meson
Symbolic link
1
src/libflake-tests/build-utils-meson
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../build-utils-meson
|
0
src/libflake-tests/data/.gitkeep
Normal file
0
src/libflake-tests/data/.gitkeep
Normal file
24
src/libflake-tests/flakeref.cc
Normal file
24
src/libflake-tests/flakeref.cc
Normal file
|
@ -0,0 +1,24 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include "fetch-settings.hh"
|
||||
#include "flake/flakeref.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* ----------- tests for flake/flakeref.hh --------------------------------------------------*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* to_string
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST(to_string, doesntReencodeUrl) {
|
||||
fetchers::Settings fetchSettings;
|
||||
auto s = "http://localhost:8181/test/+3d.tar.gz";
|
||||
auto flakeref = parseFlakeRef(fetchSettings, s);
|
||||
auto parsed = flakeref.to_string();
|
||||
auto expected = "http://localhost:8181/test/%2B3d.tar.gz";
|
||||
|
||||
ASSERT_EQ(parsed, expected);
|
||||
}
|
||||
|
||||
}
|
73
src/libflake-tests/meson.build
Normal file
73
src/libflake-tests/meson.build
Normal file
|
@ -0,0 +1,73 @@
|
|||
project('nix-flake-tests', 'cpp',
|
||||
version : files('.version'),
|
||||
default_options : [
|
||||
'cpp_std=c++2a',
|
||||
# TODO(Qyriad): increase the warning level
|
||||
'warning_level=1',
|
||||
'debug=true',
|
||||
'optimization=2',
|
||||
'errorlogs=true', # Please print logs for tests that fail
|
||||
],
|
||||
meson_version : '>= 1.1',
|
||||
license : 'LGPL-2.1-or-later',
|
||||
)
|
||||
|
||||
cxx = meson.get_compiler('cpp')
|
||||
|
||||
subdir('build-utils-meson/deps-lists')
|
||||
|
||||
deps_private_maybe_subproject = [
|
||||
dependency('nix-expr-test-support'),
|
||||
dependency('nix-flake'),
|
||||
]
|
||||
deps_public_maybe_subproject = [
|
||||
]
|
||||
subdir('build-utils-meson/subprojects')
|
||||
|
||||
subdir('build-utils-meson/export-all-symbols')
|
||||
subdir('build-utils-meson/windows-version')
|
||||
|
||||
rapidcheck = dependency('rapidcheck')
|
||||
deps_private += rapidcheck
|
||||
|
||||
gtest = dependency('gtest', main : true)
|
||||
deps_private += gtest
|
||||
|
||||
add_project_arguments(
|
||||
# TODO(Qyriad): Yes this is how the autoconf+Make system did it.
|
||||
# It would be nice for our headers to be idempotent instead.
|
||||
'-include', 'config-util.hh',
|
||||
'-include', 'config-store.hh',
|
||||
'-include', 'config-expr.hh',
|
||||
language : 'cpp',
|
||||
)
|
||||
|
||||
subdir('build-utils-meson/common')
|
||||
|
||||
sources = files(
|
||||
'flakeref.cc',
|
||||
'url-name.cc',
|
||||
)
|
||||
|
||||
include_dirs = [include_directories('.')]
|
||||
|
||||
|
||||
this_exe = executable(
|
||||
meson.project_name(),
|
||||
sources,
|
||||
dependencies : deps_private_subproject + deps_private + deps_other,
|
||||
include_directories : include_dirs,
|
||||
# TODO: -lrapidcheck, see ../libutil-support/build.meson
|
||||
link_args: linker_export_flags + ['-lrapidcheck'],
|
||||
# get main from gtest
|
||||
install : true,
|
||||
)
|
||||
|
||||
test(
|
||||
meson.project_name(),
|
||||
this_exe,
|
||||
env : {
|
||||
'_NIX_TEST_UNIT_DATA': meson.current_source_dir() / 'data',
|
||||
},
|
||||
protocol : 'gtest',
|
||||
)
|
81
src/libflake-tests/package.nix
Normal file
81
src/libflake-tests/package.nix
Normal file
|
@ -0,0 +1,81 @@
|
|||
{ lib
|
||||
, buildPackages
|
||||
, stdenv
|
||||
, mkMesonExecutable
|
||||
|
||||
, nix-flake
|
||||
, nix-expr-test-support
|
||||
|
||||
, rapidcheck
|
||||
, gtest
|
||||
, runCommand
|
||||
|
||||
# Configuration Options
|
||||
|
||||
, version
|
||||
, resolvePath
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) fileset;
|
||||
in
|
||||
|
||||
mkMesonExecutable (finalAttrs: {
|
||||
pname = "nix-flake-tests";
|
||||
inherit version;
|
||||
|
||||
workDir = ./.;
|
||||
fileset = fileset.unions [
|
||||
../../build-utils-meson
|
||||
./build-utils-meson
|
||||
../../.version
|
||||
./.version
|
||||
./meson.build
|
||||
# ./meson.options
|
||||
(fileset.fileFilter (file: file.hasExt "cc") ./.)
|
||||
(fileset.fileFilter (file: file.hasExt "hh") ./.)
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
nix-flake
|
||||
nix-expr-test-support
|
||||
rapidcheck
|
||||
gtest
|
||||
];
|
||||
|
||||
preConfigure =
|
||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
||||
# Do the meson utils, without modification.
|
||||
''
|
||||
chmod u+w ./.version
|
||||
echo ${version} > ../../.version
|
||||
'';
|
||||
|
||||
mesonFlags = [
|
||||
];
|
||||
|
||||
env = lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
|
||||
LDFLAGS = "-fuse-ld=gold";
|
||||
};
|
||||
|
||||
passthru = {
|
||||
tests = {
|
||||
run = runCommand "${finalAttrs.pname}-run" {
|
||||
meta.broken = !stdenv.hostPlatform.emulatorAvailable buildPackages;
|
||||
} (lib.optionalString stdenv.hostPlatform.isWindows ''
|
||||
export HOME="$PWD/home-dir"
|
||||
mkdir -p "$HOME"
|
||||
'' + ''
|
||||
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
|
||||
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
||||
touch $out
|
||||
'');
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||
mainProgram = finalAttrs.pname + stdenv.hostPlatform.extensions.executable;
|
||||
};
|
||||
|
||||
})
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue