mirror of
https://github.com/NixOS/nix
synced 2025-07-07 06:01:48 +02:00
Merge remote-tracking branch 'origin/master' into lfs
This commit is contained in:
commit
99705c6932
948 changed files with 10434 additions and 3982 deletions
|
@ -56,3 +56,5 @@ GENERATE_TREEVIEW = YES
|
|||
OPTIMIZE_OUTPUT_FOR_C = YES
|
||||
|
||||
USE_MDFILE_AS_MAINPAGE = doc/external-api/README.md
|
||||
|
||||
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,21 @@ 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/test \
|
||||
@src@/libexpr-test-support/test/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/test \
|
||||
@src@/libutil \
|
||||
@src@/nix-util-tests \
|
||||
@src@/nix-util-test-support/test \
|
||||
@src@/libutil-tests \
|
||||
@src@/libutil-test-support/test \
|
||||
@src@/nix \
|
||||
@src@/nix-env \
|
||||
@src@/nix-store
|
||||
|
@ -97,3 +97,6 @@ EXPAND_AS_DEFINED = \
|
|||
DECLARE_WORKER_SERIALISER \
|
||||
DECLARE_SERVE_SERIALISER \
|
||||
LENGTH_PREFIXED_PROTO_HELPER
|
||||
|
||||
WARN_IF_UNDOCUMENTED = NO
|
||||
QUIET = YES
|
||||
|
|
|
@ -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,15 +127,8 @@ ref<Store> EvalCommand::getEvalStore()
|
|||
ref<EvalState> EvalCommand::getEvalState()
|
||||
{
|
||||
if (!evalState) {
|
||||
evalState =
|
||||
#if HAVE_BOEHMGC
|
||||
std::allocate_shared<EvalState>(
|
||||
traceable_allocator<EvalState>(),
|
||||
#else
|
||||
std::make_shared<EvalState>(
|
||||
#endif
|
||||
lookupPath, getEvalStore(), fetchSettings, evalSettings, getStore())
|
||||
;
|
||||
evalState = std::allocate_shared<EvalState>(
|
||||
traceable_allocator<EvalState>(), lookupPath, getEvalStore(), fetchSettings, evalSettings, getStore());
|
||||
|
||||
evalState->repair = repair;
|
||||
|
||||
|
@ -149,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},
|
||||
});
|
||||
|
@ -238,46 +233,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;
|
||||
if (!profile)
|
||||
return;
|
||||
auto store = getStore().dynamic_pointer_cast<LocalFSStore>();
|
||||
if (!store) throw Error("'--profile' is not supported for this Nix store");
|
||||
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]);
|
||||
}
|
||||
|
@ -287,51 +284,85 @@ 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace nix {
|
|||
|
||||
extern std::string programPath;
|
||||
|
||||
extern char * * savedArgv;
|
||||
extern char ** savedArgv;
|
||||
|
||||
class EvalState;
|
||||
struct Pos;
|
||||
|
@ -24,7 +24,8 @@ 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
|
||||
{
|
||||
|
@ -112,7 +113,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 +125,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();
|
||||
|
||||
|
@ -272,10 +273,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 +286,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 +314,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 +352,6 @@ 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);
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
namespace fs { using namespace std::filesystem; }
|
||||
|
||||
fetchers::Settings fetchSettings;
|
||||
|
||||
static GlobalConfig::Register rFetchSettings(&fetchSettings);
|
||||
|
@ -119,8 +121,8 @@ MixEvalArgs::MixEvalArgs()
|
|||
.category = category,
|
||||
.labels = {"original-ref", "resolved-ref"},
|
||||
.handler = {[&](std::string _from, std::string _to) {
|
||||
auto from = parseFlakeRef(fetchSettings, _from, absPath("."));
|
||||
auto to = parseFlakeRef(fetchSettings, _to, absPath("."));
|
||||
auto from = parseFlakeRef(fetchSettings, _from, fs::current_path().string());
|
||||
auto to = parseFlakeRef(fetchSettings, _to, fs::current_path().string());
|
||||
fetchers::Attrs extraAttrs;
|
||||
if (to.subdir != "") extraAttrs["dir"] = to.subdir;
|
||||
fetchers::overrideRegistry(from.input, to.input, extraAttrs);
|
||||
|
@ -171,7 +173,9 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * bas
|
|||
{
|
||||
if (EvalSettings::isPseudoUrl(s)) {
|
||||
auto accessor = fetchers::downloadTarball(
|
||||
EvalSettings::resolvePseudoUrl(s)).accessor;
|
||||
state.store,
|
||||
state.fetchSettings,
|
||||
EvalSettings::resolvePseudoUrl(s));
|
||||
auto storePath = fetchToStore(*state.store, SourcePath(accessor), FetchMode::Copy);
|
||||
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
|
||||
}
|
||||
|
|
|
@ -104,12 +104,12 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
|
|||
|
||||
auto drvPath = attr->forceDerivation();
|
||||
|
||||
std::optional<NixInt> priority;
|
||||
std::optional<NixInt::Inner> priority;
|
||||
|
||||
if (attr->maybeGetAttr(state->sOutputSpecified)) {
|
||||
} else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
|
||||
if (auto aPriority = aMeta->maybeGetAttr("priority"))
|
||||
priority = aPriority->getInt();
|
||||
priority = aPriority->getInt().value;
|
||||
}
|
||||
|
||||
return {{
|
||||
|
|
|
@ -40,7 +40,7 @@ struct ExtraPathInfoValue : ExtraPathInfo
|
|||
/**
|
||||
* An optional priority for use with "build envs". See Package
|
||||
*/
|
||||
std::optional<NixInt> priority;
|
||||
std::optional<NixInt::Inner> priority;
|
||||
|
||||
/**
|
||||
* The attribute path associated with this value. The idea is
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
namespace fs { using namespace std::filesystem; }
|
||||
|
||||
void completeFlakeInputPath(
|
||||
AddCompletions & completions,
|
||||
ref<EvalState> evalState,
|
||||
|
@ -86,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 = {[&]() {
|
||||
|
@ -341,7 +343,7 @@ void completeFlakeRefWithFragment(
|
|||
auto flakeRefS = std::string(prefix.substr(0, hash));
|
||||
|
||||
// TODO: ideally this would use the command base directory instead of assuming ".".
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, expandTilde(flakeRefS), absPath("."));
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, expandTilde(flakeRefS), fs::current_path().string());
|
||||
|
||||
auto evalCache = openEvalCache(*evalState,
|
||||
std::make_shared<flake::LockedFlake>(lockFlake(
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "error.hh"
|
||||
#include "repl-interacter.hh"
|
||||
#include "repl.hh"
|
||||
|
||||
#include "ansicolor.hh"
|
||||
#include "shared.hh"
|
||||
#include "config-global.hh"
|
||||
#include "eval.hh"
|
||||
#include "eval-settings.hh"
|
||||
#include "attr-path.hh"
|
||||
|
@ -29,11 +29,6 @@
|
|||
#include "ref.hh"
|
||||
#include "value.hh"
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
#define GC_INCLUDE_NEW
|
||||
#include <gc/gc_cpp.h>
|
||||
#endif
|
||||
|
||||
#include "strings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
@ -62,9 +57,7 @@ enum class ProcessLineResult {
|
|||
struct NixRepl
|
||||
: AbstractNixRepl
|
||||
, detail::ReplCompleterMixin
|
||||
#if HAVE_BOEHMGC
|
||||
, gc
|
||||
#endif
|
||||
{
|
||||
size_t debugTraceIndex;
|
||||
|
||||
|
@ -77,10 +70,14 @@ struct NixRepl
|
|||
int displ;
|
||||
StringSet varNames;
|
||||
|
||||
RunNix * runNixPtr;
|
||||
|
||||
void runNix(Path program, const Strings & args, const std::optional<std::string> & input = {});
|
||||
|
||||
std::unique_ptr<ReplInteracter> interacter;
|
||||
|
||||
NixRepl(const LookupPath & lookupPath, nix::ref<Store> store,ref<EvalState> state,
|
||||
std::function<AnnotatedValues()> getValues);
|
||||
std::function<AnnotatedValues()> getValues, RunNix * runNix);
|
||||
virtual ~NixRepl() = default;
|
||||
|
||||
ReplExitStatus mainLoop() override;
|
||||
|
@ -125,32 +122,16 @@ std::string removeWhitespace(std::string s)
|
|||
|
||||
|
||||
NixRepl::NixRepl(const LookupPath & lookupPath, nix::ref<Store> store, ref<EvalState> state,
|
||||
std::function<NixRepl::AnnotatedValues()> getValues)
|
||||
std::function<NixRepl::AnnotatedValues()> getValues, RunNix * runNix = nullptr)
|
||||
: AbstractNixRepl(state)
|
||||
, debugTraceIndex(0)
|
||||
, getValues(getValues)
|
||||
, staticEnv(new StaticEnv(nullptr, state->staticBaseEnv.get()))
|
||||
, interacter(make_unique<ReadlineLikeInteracter>(getDataDir() + "/nix/repl-history"))
|
||||
, runNixPtr{runNix}
|
||||
, interacter(make_unique<ReadlineLikeInteracter>(getDataDir() + "/repl-history"))
|
||||
{
|
||||
}
|
||||
|
||||
void runNix(Path program, const Strings & args,
|
||||
const std::optional<std::string> & input = {})
|
||||
{
|
||||
auto subprocessEnv = getEnv();
|
||||
subprocessEnv["NIX_CONFIG"] = globalConfig.toKeyValue();
|
||||
//isInteractive avoid grabling interactive commands
|
||||
runProgram2(RunOptions {
|
||||
.program = settings.nixBinDir+ "/" + program,
|
||||
.args = args,
|
||||
.environment = subprocessEnv,
|
||||
.input = input,
|
||||
.isInteractive = true,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positions, const DebugTrace & dt)
|
||||
{
|
||||
if (dt.isError)
|
||||
|
@ -635,7 +616,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
|
|||
// When missing, trigger the normal exception
|
||||
// e.g. :doc builtins.foo
|
||||
// behaves like
|
||||
// nix-repl> builtins.foo
|
||||
// nix-repl> builtins.foo<tab>
|
||||
// error: attribute 'foo' missing
|
||||
evalString(arg, v);
|
||||
assert(false);
|
||||
|
@ -664,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) {
|
||||
|
@ -673,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
|
||||
|
@ -733,7 +714,14 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
|
|||
if (flakeRefS.empty())
|
||||
throw Error("cannot use ':load-flake' without a path specified. (Use '.' for the current working directory.)");
|
||||
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, flakeRefS, absPath("."), true);
|
||||
std::filesystem::path cwd;
|
||||
try {
|
||||
cwd = std::filesystem::current_path();
|
||||
} catch (std::filesystem::filesystem_error & e) {
|
||||
throw SysError("cannot determine current working directory");
|
||||
}
|
||||
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, flakeRefS, cwd.string(), true);
|
||||
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
||||
throw Error("cannot use ':load-flake' on locked flake reference '%s' (use --impure to override)", flakeRefS);
|
||||
|
||||
|
@ -833,9 +821,18 @@ void NixRepl::evalString(std::string s, Value & v)
|
|||
}
|
||||
|
||||
|
||||
void NixRepl::runNix(Path program, const Strings & args, const std::optional<std::string> & input)
|
||||
{
|
||||
if (runNixPtr)
|
||||
(*runNixPtr)(program, args, input);
|
||||
else
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<AbstractNixRepl> AbstractNixRepl::create(
|
||||
const LookupPath & lookupPath, nix::ref<Store> store, ref<EvalState> state,
|
||||
std::function<AnnotatedValues()> getValues)
|
||||
std::function<AnnotatedValues()> getValues, RunNix * runNix)
|
||||
{
|
||||
return std::make_unique<NixRepl>(
|
||||
lookupPath,
|
||||
|
|
|
@ -19,9 +19,19 @@ struct AbstractNixRepl
|
|||
|
||||
typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues;
|
||||
|
||||
using RunNix = void(Path program, const Strings & args, const std::optional<std::string> & input);
|
||||
|
||||
/**
|
||||
* @param runNix Function to run the nix CLI to support various
|
||||
* `:<something>` commands. Optional; if not provided,
|
||||
* everything else will still work fine, but those commands won't.
|
||||
*/
|
||||
static std::unique_ptr<AbstractNixRepl> create(
|
||||
const LookupPath & lookupPath, nix::ref<Store> store, ref<EvalState> state,
|
||||
std::function<AnnotatedValues()> getValues);
|
||||
const LookupPath & lookupPath,
|
||||
nix::ref<Store> store,
|
||||
ref<EvalState> state,
|
||||
std::function<AnnotatedValues()> getValues,
|
||||
RunNix * runNix = nullptr);
|
||||
|
||||
static ReplExitStatus runSimple(
|
||||
ref<EvalState> evalState,
|
||||
|
|
|
@ -75,6 +75,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',
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
#if HAVE_BOEHMGC
|
||||
# include <mutex>
|
||||
# define GC_INCLUDE_NEW 1
|
||||
# include "gc_cpp.h"
|
||||
#endif
|
||||
|
||||
nix_err nix_libexpr_init(nix_c_context * context)
|
||||
|
|
|
@ -14,12 +14,6 @@
|
|||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
# include "gc/gc.h"
|
||||
# define GC_INCLUDE_NEW 1
|
||||
# include "gc_cpp.h"
|
||||
#endif
|
||||
|
||||
void nix_set_string_return(nix_string_return * str, const char * c)
|
||||
{
|
||||
str->str = c;
|
||||
|
|
|
@ -14,12 +14,6 @@
|
|||
#include "nix_api_value.h"
|
||||
#include "value/context.hh"
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
# include "gc/gc.h"
|
||||
# define GC_INCLUDE_NEW 1
|
||||
# include "gc_cpp.h"
|
||||
#endif
|
||||
|
||||
// Internal helper functions to check [in] and [out] `Value *` parameters
|
||||
static const nix::Value & check_value_not_null(const nix_value * value)
|
||||
{
|
||||
|
@ -306,7 +300,7 @@ int64_t nix_get_int(nix_c_context * context, const nix_value * value)
|
|||
try {
|
||||
auto & v = check_value_in(value);
|
||||
assert(v.type() == nix::nInt);
|
||||
return v.integer();
|
||||
return v.integer().value;
|
||||
}
|
||||
NIXC_CATCH_ERRS_RES(0);
|
||||
}
|
||||
|
|
|
@ -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
|
23
src/libexpr-test-support/local.mk
Normal file
23
src/libexpr-test-support/local.mk
Normal file
|
@ -0,0 +1,23 @@
|
|||
libraries += libexpr-test-support
|
||||
|
||||
libexpr-test-support_NAME = libnixexpr-test-support
|
||||
|
||||
libexpr-test-support_DIR := $(d)
|
||||
|
||||
ifeq ($(INSTALL_UNIT_TESTS), yes)
|
||||
libexpr-test-support_INSTALL_DIR := $(checklibdir)
|
||||
else
|
||||
libexpr-test-support_INSTALL_DIR :=
|
||||
endif
|
||||
|
||||
libexpr-test-support_SOURCES := \
|
||||
$(wildcard $(d)/tests/*.cc) \
|
||||
$(wildcard $(d)/tests/value/*.cc)
|
||||
|
||||
libexpr-test-support_CXXFLAGS += $(libexpr-tests_EXTRA_INCLUDES)
|
||||
|
||||
libexpr-test-support_LIBS = \
|
||||
libstore-test-support libutil-test-support \
|
||||
libexpr libstore libutil
|
||||
|
||||
libexpr-test-support_LDFLAGS := $(THREAD_LDFLAGS) -lrapidcheck
|
77
src/libexpr-test-support/meson.build
Normal file
77
src/libexpr-test-support/meson.build
Normal file
|
@ -0,0 +1,77 @@
|
|||
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')
|
||||
|
||||
subdir('build-utils-meson/threads')
|
||||
|
||||
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/diagnostics')
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
})
|
150
src/libexpr-test-support/tests/libexpr.hh
Normal file
150
src/libexpr-test-support/tests/libexpr.hh
Normal file
|
@ -0,0 +1,150 @@
|
|||
#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;
|
||||
}
|
||||
|
||||
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
141
src/libexpr-tests/eval.cc
Normal file
141
src/libexpr-tests/eval.cc
Normal file
|
@ -0,0 +1,141 @@
|
|||
#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));
|
||||
}
|
||||
|
||||
} // 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 */
|
45
src/libexpr-tests/local.mk
Normal file
45
src/libexpr-tests/local.mk
Normal file
|
@ -0,0 +1,45 @@
|
|||
check: libexpr-tests_RUN
|
||||
|
||||
programs += libexpr-tests
|
||||
|
||||
libexpr-tests_NAME := libnixexpr-tests
|
||||
|
||||
libexpr-tests_ENV := _NIX_TEST_UNIT_DATA=$(d)/data GTEST_OUTPUT=xml:$$testresults/libexpr-tests.xml
|
||||
|
||||
libexpr-tests_DIR := $(d)
|
||||
|
||||
ifeq ($(INSTALL_UNIT_TESTS), yes)
|
||||
libexpr-tests_INSTALL_DIR := $(checkbindir)
|
||||
else
|
||||
libexpr-tests_INSTALL_DIR :=
|
||||
endif
|
||||
|
||||
libexpr-tests_SOURCES := \
|
||||
$(wildcard $(d)/*.cc) \
|
||||
$(wildcard $(d)/value/*.cc) \
|
||||
$(wildcard $(d)/flake/*.cc)
|
||||
|
||||
libexpr-tests_EXTRA_INCLUDES = \
|
||||
-I src/libexpr-test-support \
|
||||
-I src/libstore-test-support \
|
||||
-I src/libutil-test-support \
|
||||
$(INCLUDE_libexpr) \
|
||||
$(INCLUDE_libexprc) \
|
||||
$(INCLUDE_libfetchers) \
|
||||
$(INCLUDE_libstore) \
|
||||
$(INCLUDE_libstorec) \
|
||||
$(INCLUDE_libutil) \
|
||||
$(INCLUDE_libutilc)
|
||||
|
||||
libexpr-tests_CXXFLAGS += $(libexpr-tests_EXTRA_INCLUDES)
|
||||
|
||||
libexpr-tests_LIBS = \
|
||||
libexpr-test-support libstore-test-support libutil-test-support \
|
||||
libexpr libexprc libfetchers libstore libstorec libutil libutilc
|
||||
|
||||
libexpr-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS) -lgmock
|
||||
|
||||
ifdef HOST_WINDOWS
|
||||
# Increase the default reserved stack size to 65 MB so Nix doesn't run out of space
|
||||
libexpr-tests_LDFLAGS += -Wl,--stack,$(shell echo $$((65 * 1024 * 1024)))
|
||||
endif
|
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();
|
||||
}
|
93
src/libexpr-tests/meson.build
Normal file
93
src/libexpr-tests/meson.build
Normal file
|
@ -0,0 +1,93 @@
|
|||
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/threads')
|
||||
|
||||
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/diagnostics')
|
||||
|
||||
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
|
|
@ -69,7 +69,7 @@ struct AttrDb
|
|||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
Path cacheDir = getCacheDir() + "/nix/eval-cache-v5";
|
||||
Path cacheDir = getCacheDir() + "/eval-cache-v5";
|
||||
createDirs(cacheDir);
|
||||
|
||||
Path dbPath = cacheDir + "/" + fingerprint.to_string(HashFormat::Base16, false) + ".sqlite";
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -328,7 +328,7 @@ struct AttrDb
|
|||
case AttrType::Bool:
|
||||
return {{rowId, queryAttribute.getInt(2) != 0}};
|
||||
case AttrType::Int:
|
||||
return {{rowId, int_t{queryAttribute.getInt(2)}}};
|
||||
return {{rowId, int_t{NixInt{queryAttribute.getInt(2)}}}};
|
||||
case AttrType::ListOfStrings:
|
||||
return {{rowId, tokenizeString<std::vector<std::string>>(queryAttribute.getStr(2), "\t")}};
|
||||
case AttrType::Missing:
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -471,7 +471,7 @@ Value & AttrCursor::forceValue()
|
|||
else if (v.type() == nBool)
|
||||
cachedValue = {root->db->setBool(getKey(), v.boolean()), v.boolean()};
|
||||
else if (v.type() == nInt)
|
||||
cachedValue = {root->db->setInt(getKey(), v.integer()), int_t{v.integer()}};
|
||||
cachedValue = {root->db->setInt(getKey(), v.integer().value), int_t{v.integer()}};
|
||||
else if (v.type() == nAttrs)
|
||||
; // FIXME: do something?
|
||||
else
|
||||
|
|
|
@ -32,122 +32,6 @@ static void * oomHandler(size_t requested)
|
|||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
class BoehmGCStackAllocator : public StackAllocator
|
||||
{
|
||||
boost::coroutines2::protected_fixedsize_stack stack{
|
||||
// We allocate 8 MB, the default max stack size on NixOS.
|
||||
// A smaller stack might be quicker to allocate but reduces the stack
|
||||
// depth available for source filter expressions etc.
|
||||
std::max(boost::context::stack_traits::default_size(), static_cast<std::size_t>(8 * 1024 * 1024))};
|
||||
|
||||
// This is specific to boost::coroutines2::protected_fixedsize_stack.
|
||||
// The stack protection page is included in sctx.size, so we have to
|
||||
// subtract one page size from the stack size.
|
||||
std::size_t pfss_usable_stack_size(boost::context::stack_context & sctx)
|
||||
{
|
||||
return sctx.size - boost::context::stack_traits::page_size();
|
||||
}
|
||||
|
||||
public:
|
||||
boost::context::stack_context allocate() override
|
||||
{
|
||||
auto sctx = stack.allocate();
|
||||
|
||||
// Stacks generally start at a high address and grow to lower addresses.
|
||||
// Architectures that do the opposite are rare; in fact so rare that
|
||||
// boost_routine does not implement it.
|
||||
// So we subtract the stack size.
|
||||
GC_add_roots(static_cast<char *>(sctx.sp) - pfss_usable_stack_size(sctx), sctx.sp);
|
||||
return sctx;
|
||||
}
|
||||
|
||||
void deallocate(boost::context::stack_context sctx) override
|
||||
{
|
||||
GC_remove_roots(static_cast<char *>(sctx.sp) - pfss_usable_stack_size(sctx), sctx.sp);
|
||||
stack.deallocate(sctx);
|
||||
}
|
||||
};
|
||||
|
||||
static BoehmGCStackAllocator boehmGCStackAllocator;
|
||||
|
||||
/**
|
||||
* When a thread goes into a coroutine, we lose its original sp until
|
||||
* control flow returns to the thread.
|
||||
* While in the coroutine, the sp points outside the thread stack,
|
||||
* so we can detect this and push the entire thread stack instead,
|
||||
* as an approximation.
|
||||
* The coroutine's stack is covered by `BoehmGCStackAllocator`.
|
||||
* This is not an optimal solution, because the garbage is scanned when a
|
||||
* coroutine is active, for both the coroutine and the original thread stack.
|
||||
* However, the implementation is quite lean, and usually we don't have active
|
||||
* coroutines during evaluation, so this is acceptable.
|
||||
*/
|
||||
void fixupBoehmStackPointer(void ** sp_ptr, void * _pthread_id)
|
||||
{
|
||||
void *& sp = *sp_ptr;
|
||||
auto pthread_id = reinterpret_cast<pthread_t>(_pthread_id);
|
||||
# ifndef __APPLE__
|
||||
pthread_attr_t pattr;
|
||||
# endif
|
||||
size_t osStackSize;
|
||||
// The low address of the stack, which grows down.
|
||||
void * osStackLimit;
|
||||
void * osStackBase;
|
||||
|
||||
# ifdef __APPLE__
|
||||
osStackSize = pthread_get_stacksize_np(pthread_id);
|
||||
osStackLimit = pthread_get_stackaddr_np(pthread_id);
|
||||
# else
|
||||
if (pthread_attr_init(&pattr)) {
|
||||
throw Error("fixupBoehmStackPointer: pthread_attr_init failed");
|
||||
}
|
||||
# ifdef HAVE_PTHREAD_GETATTR_NP
|
||||
if (pthread_getattr_np(pthread_id, &pattr)) {
|
||||
throw Error("fixupBoehmStackPointer: pthread_getattr_np failed");
|
||||
}
|
||||
# elif HAVE_PTHREAD_ATTR_GET_NP
|
||||
if (!pthread_attr_init(&pattr)) {
|
||||
throw Error("fixupBoehmStackPointer: pthread_attr_init failed");
|
||||
}
|
||||
if (!pthread_attr_get_np(pthread_id, &pattr)) {
|
||||
throw Error("fixupBoehmStackPointer: pthread_attr_get_np failed");
|
||||
}
|
||||
# else
|
||||
# error "Need one of `pthread_attr_get_np` or `pthread_getattr_np`"
|
||||
# endif
|
||||
if (pthread_attr_getstack(&pattr, &osStackLimit, &osStackSize)) {
|
||||
throw Error("fixupBoehmStackPointer: pthread_attr_getstack failed");
|
||||
}
|
||||
if (pthread_attr_destroy(&pattr)) {
|
||||
throw Error("fixupBoehmStackPointer: pthread_attr_destroy failed");
|
||||
}
|
||||
# endif
|
||||
osStackBase = (char *) osStackLimit + osStackSize;
|
||||
// NOTE: We assume the stack grows down, as it does on all architectures we support.
|
||||
// Architectures that grow the stack up are rare.
|
||||
if (sp >= osStackBase || sp < osStackLimit) { // sp is outside the os stack
|
||||
sp = osStackLimit;
|
||||
}
|
||||
}
|
||||
|
||||
/* Disable GC while this object lives. Used by CoroutineContext.
|
||||
*
|
||||
* Boehm keeps a count of GC_disable() and GC_enable() calls,
|
||||
* and only enables GC when the count matches.
|
||||
*/
|
||||
class BoehmDisableGC
|
||||
{
|
||||
public:
|
||||
BoehmDisableGC()
|
||||
{
|
||||
GC_disable();
|
||||
};
|
||||
~BoehmDisableGC()
|
||||
{
|
||||
GC_enable();
|
||||
};
|
||||
};
|
||||
|
||||
static inline void initGCReal()
|
||||
{
|
||||
/* Initialise the Boehm garbage collector. */
|
||||
|
@ -168,24 +52,6 @@ static inline void initGCReal()
|
|||
|
||||
GC_set_oom_fn(oomHandler);
|
||||
|
||||
StackAllocator::defaultAllocator = &boehmGCStackAllocator;
|
||||
|
||||
// TODO: Remove __APPLE__ condition.
|
||||
// Comment suggests an implementation that works on darwin and windows
|
||||
// https://github.com/ivmai/bdwgc/issues/362#issuecomment-1936672196
|
||||
# if GC_VERSION_MAJOR >= 8 && GC_VERSION_MINOR >= 2 && GC_VERSION_MICRO >= 4 && !defined(__APPLE__)
|
||||
GC_set_sp_corrector(&fixupBoehmStackPointer);
|
||||
|
||||
if (!GC_get_sp_corrector()) {
|
||||
printTalkative("BoehmGC on this platform does not support sp_corrector; will disable GC inside coroutines");
|
||||
/* Used to disable GC when entering coroutines on macOS */
|
||||
create_coro_gc_hook = []() -> std::shared_ptr<void> { return std::make_shared<BoehmDisableGC>(); };
|
||||
}
|
||||
# else
|
||||
# warning \
|
||||
"BoehmGC version does not support GC while coroutine exists. GC will be disabled inside coroutines. Consider updating bdw-gc to 8.2.4 or later."
|
||||
# endif
|
||||
|
||||
/* Set the initial heap size to something fairly big (25% of
|
||||
physical RAM, up to a maximum of 384 MiB) so that in most cases
|
||||
we don't need to garbage collect at all. (Collection has a
|
||||
|
|
|
@ -3,6 +3,34 @@
|
|||
|
||||
#include <cstddef>
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
|
||||
# define GC_INCLUDE_NEW
|
||||
|
||||
# include <gc/gc.h>
|
||||
# include <gc/gc_cpp.h>
|
||||
# include <gc/gc_allocator.h>
|
||||
|
||||
#else
|
||||
|
||||
# include <memory>
|
||||
|
||||
/* Some dummy aliases for Boehm GC definitions to reduce the number of
|
||||
#ifdefs. */
|
||||
|
||||
template<typename T>
|
||||
using traceable_allocator = std::allocator<T>;
|
||||
|
||||
template<typename T>
|
||||
using gc_allocator = std::allocator<T>;
|
||||
|
||||
# define GC_MALLOC_ATOMIC std::malloc
|
||||
|
||||
struct gc
|
||||
{};
|
||||
|
||||
#endif
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "print.hh"
|
||||
#include "eval.hh"
|
||||
#include "eval-error.hh"
|
||||
#include "eval-settings.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -138,5 +139,12 @@ inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view e
|
|||
}
|
||||
}
|
||||
|
||||
[[gnu::always_inline]]
|
||||
inline CallDepth EvalState::addCallDepth(const PosIdx pos) {
|
||||
if (callDepth > settings.maxCallDepth)
|
||||
error<EvalError>("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow();
|
||||
|
||||
return CallDepth(callDepth);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ const std::string & EvalSettings::getCurrentSystem() const
|
|||
Path getNixDefExpr()
|
||||
{
|
||||
return settings.useXDGBaseDirectories
|
||||
? getStateDir() + "/nix/defexpr"
|
||||
? getStateDir() + "/defexpr"
|
||||
: getHome() + "/.nix-defexpr";
|
||||
}
|
||||
|
||||
|
|
|
@ -87,10 +87,19 @@ struct EvalSettings : Config
|
|||
If the respective paths are accessible, the default values are:
|
||||
|
||||
- `$HOME/.nix-defexpr/channels`
|
||||
|
||||
The [user channel link](@docroot@/command-ref/files/default-nix-expression.md#user-channel-link), pointing to the current state of [channels](@docroot@/command-ref/files/channels.md) for the current user.
|
||||
|
||||
- `nixpkgs=$NIX_STATE_DIR/profiles/per-user/root/channels/nixpkgs`
|
||||
|
||||
The current state of the `nixpkgs` channel for the `root` user.
|
||||
|
||||
- `$NIX_STATE_DIR/profiles/per-user/root/channels`
|
||||
|
||||
See [`NIX_STATE_DIR`](@docroot@/command-ref/env-common.md#env-NIX_STATE_DIR) for details.
|
||||
The current state of all channels for the `root` user.
|
||||
|
||||
These files are set up by the [Nix installer](@docroot@/installation/installing-binary.md).
|
||||
See [`NIX_STATE_DIR`](@docroot@/command-ref/env-common.md#env-NIX_STATE_DIR) for details on the environment variable.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
|
@ -185,7 +194,11 @@ struct EvalSettings : Config
|
|||
)"};
|
||||
|
||||
Setting<bool> useEvalCache{this, true, "eval-cache",
|
||||
"Whether to use the flake evaluation cache."};
|
||||
R"(
|
||||
Whether to use the flake evaluation cache.
|
||||
Certain commands won't have to evaluate when invoked for the second time with a particular version of a flake.
|
||||
Intermediate results are not cached.
|
||||
)"};
|
||||
|
||||
Setting<bool> ignoreExceptionsDuringTry{this, false, "ignore-try",
|
||||
R"(
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "eval.hh"
|
||||
#include "eval-gc.hh"
|
||||
#include "eval-settings.hh"
|
||||
#include "primops.hh"
|
||||
#include "print-options.hh"
|
||||
|
@ -39,16 +38,6 @@
|
|||
# include <sys/resource.h>
|
||||
#endif
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
|
||||
# define GC_INCLUDE_NEW
|
||||
|
||||
# include <gc/gc.h>
|
||||
# include <gc/gc_cpp.h>
|
||||
# include <gc/gc_allocator.h>
|
||||
|
||||
#endif
|
||||
|
||||
#include "strings-inline.hh"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
@ -58,24 +47,7 @@ namespace nix {
|
|||
static char * allocString(size_t size)
|
||||
{
|
||||
char * t;
|
||||
#if HAVE_BOEHMGC
|
||||
t = (char *) GC_MALLOC_ATOMIC(size);
|
||||
#else
|
||||
t = (char *) malloc(size);
|
||||
#endif
|
||||
if (!t) throw std::bad_alloc();
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
static char * dupString(const char * s)
|
||||
{
|
||||
char * t;
|
||||
#if HAVE_BOEHMGC
|
||||
t = GC_STRDUP(s);
|
||||
#else
|
||||
t = strdup(s);
|
||||
#endif
|
||||
if (!t) throw std::bad_alloc();
|
||||
return t;
|
||||
}
|
||||
|
@ -99,11 +71,7 @@ static const char * makeImmutableString(std::string_view s)
|
|||
|
||||
RootValue allocRootValue(Value * v)
|
||||
{
|
||||
#if HAVE_BOEHMGC
|
||||
return std::allocate_shared<Value *>(traceable_allocator<Value *>(), v);
|
||||
#else
|
||||
return std::make_shared<Value *>(v);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Pretty print types for assertion errors
|
||||
|
@ -571,7 +539,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;
|
||||
|
@ -603,19 +571,33 @@ 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 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);
|
||||
auto _level = addCallDepth(noPos);
|
||||
return getDoc(partiallyApplied);
|
||||
}
|
||||
catch (Error & e) {
|
||||
e.addTrace(nullptr, "while partially calling '%1%' to retrieve documentation", "__functor");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -836,9 +818,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;
|
||||
|
@ -1471,26 +1454,9 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v)
|
|||
v.mkLambda(&env, this);
|
||||
}
|
||||
|
||||
namespace {
|
||||
/** Increments a count on construction and decrements on destruction.
|
||||
*/
|
||||
class CallDepth {
|
||||
size_t & count;
|
||||
public:
|
||||
CallDepth(size_t & count) : count(count) {
|
||||
++count;
|
||||
}
|
||||
~CallDepth() {
|
||||
--count;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
|
||||
{
|
||||
if (callDepth > settings.maxCallDepth)
|
||||
error<EvalError>("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow();
|
||||
CallDepth _level(callDepth);
|
||||
auto _level = addCallDepth(pos);
|
||||
|
||||
auto trace = settings.traceFunctionCalls
|
||||
? std::make_unique<FunctionCallTrace>(positions[pos])
|
||||
|
@ -1834,11 +1800,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 {
|
||||
|
@ -1979,7 +1943,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
|||
NixStringContext context;
|
||||
std::vector<BackedStringView> s;
|
||||
size_t sSize = 0;
|
||||
NixInt n = 0;
|
||||
NixInt n{0};
|
||||
NixFloat nf = 0;
|
||||
|
||||
bool first = !forceString;
|
||||
|
@ -2023,17 +1987,22 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
|||
|
||||
if (firstType == nInt) {
|
||||
if (vTmp.type() == nInt) {
|
||||
n += vTmp.integer();
|
||||
auto newN = n + vTmp.integer();
|
||||
if (auto checked = newN.valueChecked(); checked.has_value()) {
|
||||
n = NixInt(*checked);
|
||||
} else {
|
||||
state.error<EvalError>("integer overflow in adding %1% + %2%", n, vTmp.integer()).atPos(i_pos).debugThrow();
|
||||
}
|
||||
} else if (vTmp.type() == nFloat) {
|
||||
// Upgrade the type from int to float;
|
||||
firstType = nFloat;
|
||||
nf = n;
|
||||
nf = n.value;
|
||||
nf += vTmp.fpoint();
|
||||
} else
|
||||
state.error<EvalError>("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow();
|
||||
} else if (firstType == nFloat) {
|
||||
if (vTmp.type() == nInt) {
|
||||
nf += vTmp.integer();
|
||||
nf += vTmp.integer().value;
|
||||
} else if (vTmp.type() == nFloat) {
|
||||
nf += vTmp.fpoint();
|
||||
} else
|
||||
|
@ -2158,7 +2127,7 @@ NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view err
|
|||
try {
|
||||
forceValue(v, pos);
|
||||
if (v.type() == nInt)
|
||||
return v.integer();
|
||||
return v.integer().value;
|
||||
else if (v.type() != nFloat)
|
||||
error<TypeError>(
|
||||
"expected a float but found %1%: %2%",
|
||||
|
@ -2345,7 +2314,7 @@ BackedStringView EvalState::coerceToString(
|
|||
shell scripting convenience, just like `null'. */
|
||||
if (v.type() == nBool && v.boolean()) return "1";
|
||||
if (v.type() == nBool && !v.boolean()) return "";
|
||||
if (v.type() == nInt) return std::to_string(v.integer());
|
||||
if (v.type() == nInt) return std::to_string(v.integer().value);
|
||||
if (v.type() == nFloat) return std::to_string(v.fpoint());
|
||||
if (v.type() == nNull) return "";
|
||||
|
||||
|
@ -2728,9 +2697,9 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
|
|||
|
||||
// Special case type-compatibility between float and int
|
||||
if (v1.type() == nInt && v2.type() == nFloat)
|
||||
return v1.integer() == v2.fpoint();
|
||||
return v1.integer().value == v2.fpoint();
|
||||
if (v1.type() == nFloat && v2.type() == nInt)
|
||||
return v1.fpoint() == v2.integer();
|
||||
return v1.fpoint() == v2.integer().value;
|
||||
|
||||
// All other types are not compatible with each other.
|
||||
if (v1.type() != v2.type()) return false;
|
||||
|
@ -2865,7 +2834,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"] = {
|
||||
|
@ -3083,7 +3054,9 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
|
|||
if (EvalSettings::isPseudoUrl(value)) {
|
||||
try {
|
||||
auto accessor = fetchers::downloadTarball(
|
||||
EvalSettings::resolvePseudoUrl(value)).accessor;
|
||||
store,
|
||||
fetchSettings,
|
||||
EvalSettings::resolvePseudoUrl(value));
|
||||
auto storePath = fetchToStore(*store, SourcePath(accessor), FetchMode::Copy);
|
||||
return finish(store->toRealPath(storePath));
|
||||
} catch (Error & e) {
|
||||
|
|
|
@ -41,6 +41,21 @@ namespace eval_cache {
|
|||
class EvalCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments a count on construction and decrements on destruction.
|
||||
*/
|
||||
class CallDepth {
|
||||
size_t & count;
|
||||
|
||||
public:
|
||||
CallDepth(size_t & count) : count(count) {
|
||||
++count;
|
||||
}
|
||||
~CallDepth() {
|
||||
--count;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Function that implements a primop.
|
||||
*/
|
||||
|
@ -124,11 +139,7 @@ struct Constant
|
|||
bool impureOnly = false;
|
||||
};
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::map<std::string, Value *, std::less<std::string>, traceable_allocator<std::pair<const std::string, Value *> > > ValMap;
|
||||
#else
|
||||
typedef std::map<std::string, Value *> ValMap;
|
||||
#endif
|
||||
typedef std::map<std::string, Value *, std::less<std::string>, traceable_allocator<std::pair<const std::string, Value *> > > ValMap;
|
||||
|
||||
typedef std::unordered_map<PosIdx, DocComment> DocCommentMap;
|
||||
|
||||
|
@ -314,21 +325,13 @@ private:
|
|||
/**
|
||||
* A cache from path names to parse trees.
|
||||
*/
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::unordered_map<SourcePath, Expr *, std::hash<SourcePath>, std::equal_to<SourcePath>, traceable_allocator<std::pair<const SourcePath, Expr *>>> FileParseCache;
|
||||
#else
|
||||
typedef std::unordered_map<SourcePath, Expr *> FileParseCache;
|
||||
#endif
|
||||
FileParseCache fileParseCache;
|
||||
|
||||
/**
|
||||
* A cache from path names to values.
|
||||
*/
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::unordered_map<SourcePath, Value, std::hash<SourcePath>, std::equal_to<SourcePath>, traceable_allocator<std::pair<const SourcePath, Value>>> FileEvalCache;
|
||||
#else
|
||||
typedef std::unordered_map<SourcePath, Value> FileEvalCache;
|
||||
#endif
|
||||
FileEvalCache fileEvalCache;
|
||||
|
||||
/**
|
||||
|
@ -625,6 +628,12 @@ public:
|
|||
const char * doc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the documentation for a value. This will evaluate the value if
|
||||
* it is a thunk, and it will partially apply __functor if applicable.
|
||||
*
|
||||
* @param v The value to get the documentation for.
|
||||
*/
|
||||
std::optional<Doc> getDoc(Value & v);
|
||||
|
||||
private:
|
||||
|
@ -649,6 +658,11 @@ private:
|
|||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Check that the call depth is within limits, and increment it, until the returned object is destroyed.
|
||||
*/
|
||||
inline CallDepth addCallDepth(const PosIdx pos);
|
||||
|
||||
/**
|
||||
* Do a deep equality test between two values. That is, list
|
||||
* elements and attributes are compared recursively.
|
||||
|
|
|
@ -2,28 +2,15 @@
|
|||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
|
||||
#include <gc/gc.h>
|
||||
#include <gc/gc_cpp.h>
|
||||
#include <gc/gc_allocator.h>
|
||||
|
||||
#endif
|
||||
#include "value.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct Value;
|
||||
|
||||
/**
|
||||
* A GC compatible vector that may used a reserved portion of `nItems` on the stack instead of allocating on the heap.
|
||||
*/
|
||||
#if HAVE_BOEHMGC
|
||||
template <typename T, size_t nItems>
|
||||
using SmallVector = boost::container::small_vector<T, nItems, traceable_allocator<T>>;
|
||||
#else
|
||||
template <typename T, size_t nItems>
|
||||
using SmallVector = boost::container::small_vector<T, nItems>;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A vector of value pointers. See `SmallVector`.
|
||||
|
@ -39,4 +26,4 @@ using SmallValueVector = SmallVector<Value *, nItems>;
|
|||
template <size_t nItems>
|
||||
using SmallTemporaryValueVector = SmallVector<Value, nItems>;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -246,8 +246,8 @@ NixInt PackageInfo::queryMetaInt(const std::string & name, NixInt def)
|
|||
if (v->type() == nString) {
|
||||
/* Backwards compatibility with before we had support for
|
||||
integer meta fields. */
|
||||
if (auto n = string2Int<NixInt>(v->c_str()))
|
||||
return *n;
|
||||
if (auto n = string2Int<NixInt::Inner>(v->c_str()))
|
||||
return NixInt{*n};
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,11 +83,7 @@ public:
|
|||
};
|
||||
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::list<PackageInfo, traceable_allocator<PackageInfo>> PackageInfos;
|
||||
#else
|
||||
typedef std::list<PackageInfo> PackageInfos;
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "value.hh"
|
||||
#include "eval.hh"
|
||||
|
||||
#include <limits>
|
||||
#include <variant>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
|
@ -101,8 +102,12 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
bool number_unsigned(number_unsigned_t val) override
|
||||
bool number_unsigned(number_unsigned_t val_) override
|
||||
{
|
||||
if (val_ > std::numeric_limits<NixInt::Inner>::max()) {
|
||||
throw Error("unsigned json number %1% outside of Nix integer range", val_);
|
||||
}
|
||||
NixInt::Inner val = val_;
|
||||
rs->value(state).mkInt(val);
|
||||
rs->add();
|
||||
return true;
|
||||
|
|
|
@ -138,7 +138,7 @@ or { return OR_KW; }
|
|||
{INT} { errno = 0;
|
||||
std::optional<int64_t> numMay = string2Int<int64_t>(yytext);
|
||||
if (numMay.has_value()) {
|
||||
yylval->n = *numMay;
|
||||
yylval->n = NixInt{*numMay};
|
||||
} else {
|
||||
throw ParseError(ErrorInfo{
|
||||
.msg = HintFmt("invalid integer '%1%'", yytext),
|
||||
|
|
|
@ -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 \
|
||||
|
@ -107,6 +111,7 @@ struct ExprInt : Expr
|
|||
{
|
||||
Value v;
|
||||
ExprInt(NixInt n) { v.mkInt(n); };
|
||||
ExprInt(NixInt::Inner n) { v.mkInt(n); };
|
||||
Value * maybeThunk(EvalState & state, Env & env) override;
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
@ -353,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
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -102,10 +96,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
|
||||
;
|
||||
|
||||
|
@ -350,7 +359,7 @@ string_parts_interpolated
|
|||
|
||||
path_start
|
||||
: PATH {
|
||||
Path path(absPath({$1.p, $1.l}, state->basePath.path.abs()));
|
||||
Path path(absPath(std::string_view{$1.p, $1.l}, state->basePath.path.abs()));
|
||||
/* add back in the trailing '/' to the first segment */
|
||||
if ($1.p[$1.l-1] == '/' && $1.l > 1)
|
||||
path += "/";
|
||||
|
@ -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;
|
||||
|
@ -171,6 +178,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 +268,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -587,9 +619,9 @@ struct CompareValues
|
|||
{
|
||||
try {
|
||||
if (v1->type() == nFloat && v2->type() == nInt)
|
||||
return v1->fpoint() < v2->integer();
|
||||
return v1->fpoint() < v2->integer().value;
|
||||
if (v1->type() == nInt && v2->type() == nFloat)
|
||||
return v1->integer() < v2->fpoint();
|
||||
return v1->integer().value < v2->fpoint();
|
||||
if (v1->type() != v2->type())
|
||||
state.error<EvalError>("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow();
|
||||
// Allow selecting a subset of enum values
|
||||
|
@ -631,11 +663,7 @@ struct CompareValues
|
|||
};
|
||||
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::list<Value *, gc_allocator<Value *>> ValueList;
|
||||
#else
|
||||
typedef std::list<Value *> ValueList;
|
||||
#endif
|
||||
|
||||
|
||||
static Bindings::const_iterator getAttr(
|
||||
|
@ -941,6 +969,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,
|
||||
});
|
||||
|
@ -2133,7 +2164,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({
|
||||
|
@ -2241,7 +2272,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({
|
||||
|
@ -2762,13 +2793,13 @@ static struct LazyPosAcessors {
|
|||
PrimOp primop_lineOfPos{
|
||||
.arity = 1,
|
||||
.fun = [] (EvalState & state, PosIdx pos, Value * * args, Value & v) {
|
||||
v.mkInt(state.positions[PosIdx(args[0]->integer())].line);
|
||||
v.mkInt(state.positions[PosIdx(args[0]->integer().value)].line);
|
||||
}
|
||||
};
|
||||
PrimOp primop_columnOfPos{
|
||||
.arity = 1,
|
||||
.fun = [] (EvalState & state, PosIdx pos, Value * * args, Value & v) {
|
||||
v.mkInt(state.positions[PosIdx(args[0]->integer())].column);
|
||||
v.mkInt(state.positions[PosIdx(args[0]->integer().value)].column);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3136,7 +3167,7 @@ static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * arg
|
|||
std::optional<ListBuilder> list;
|
||||
};
|
||||
|
||||
std::map<Symbol, Item> attrsSeen;
|
||||
std::map<Symbol, Item, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, Item>>> attrsSeen;
|
||||
|
||||
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.zipAttrsWith");
|
||||
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.zipAttrsWith");
|
||||
|
@ -3244,7 +3275,8 @@ static void elemAt(EvalState & state, const PosIdx pos, Value & list, int n, Val
|
|||
/* Return the n-1'th element of a list. */
|
||||
static void prim_elemAt(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
elemAt(state, pos, *args[0], state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.elemAt"), v);
|
||||
NixInt::Inner elem = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.elemAt").value;
|
||||
elemAt(state, pos, *args[0], elem, v);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_elemAt({
|
||||
|
@ -3538,10 +3570,12 @@ static RegisterPrimOp primop_all({
|
|||
|
||||
static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
auto len = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList");
|
||||
auto len_ = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList").value;
|
||||
|
||||
if (len < 0)
|
||||
state.error<EvalError>("cannot create list of size %1%", len).atPos(pos).debugThrow();
|
||||
if (len_ < 0)
|
||||
state.error<EvalError>("cannot create list of size %1%", len_).atPos(pos).debugThrow();
|
||||
|
||||
size_t len = size_t(len_);
|
||||
|
||||
// More strict than striclty (!) necessary, but acceptable
|
||||
// as evaluating map without accessing any values makes little sense.
|
||||
|
@ -3798,9 +3832,17 @@ static void prim_add(EvalState & state, const PosIdx pos, Value * * args, Value
|
|||
if (args[0]->type() == nFloat || args[1]->type() == nFloat)
|
||||
v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first argument of the addition")
|
||||
+ state.forceFloat(*args[1], pos, "while evaluating the second argument of the addition"));
|
||||
else
|
||||
v.mkInt( state.forceInt(*args[0], pos, "while evaluating the first argument of the addition")
|
||||
+ state.forceInt(*args[1], pos, "while evaluating the second argument of the addition"));
|
||||
else {
|
||||
auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument of the addition");
|
||||
auto i2 = state.forceInt(*args[1], pos, "while evaluating the second argument of the addition");
|
||||
|
||||
auto result_ = i1 + i2;
|
||||
if (auto result = result_.valueChecked(); result.has_value()) {
|
||||
v.mkInt(*result);
|
||||
} else {
|
||||
state.error<EvalError>("integer overflow in adding %1% + %2%", i1, i2).atPos(pos).debugThrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_add({
|
||||
|
@ -3819,9 +3861,18 @@ static void prim_sub(EvalState & state, const PosIdx pos, Value * * args, Value
|
|||
if (args[0]->type() == nFloat || args[1]->type() == nFloat)
|
||||
v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first argument of the subtraction")
|
||||
- state.forceFloat(*args[1], pos, "while evaluating the second argument of the subtraction"));
|
||||
else
|
||||
v.mkInt( state.forceInt(*args[0], pos, "while evaluating the first argument of the subtraction")
|
||||
- state.forceInt(*args[1], pos, "while evaluating the second argument of the subtraction"));
|
||||
else {
|
||||
auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument of the subtraction");
|
||||
auto i2 = state.forceInt(*args[1], pos, "while evaluating the second argument of the subtraction");
|
||||
|
||||
auto result_ = i1 - i2;
|
||||
|
||||
if (auto result = result_.valueChecked(); result.has_value()) {
|
||||
v.mkInt(*result);
|
||||
} else {
|
||||
state.error<EvalError>("integer overflow in subtracting %1% - %2%", i1, i2).atPos(pos).debugThrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_sub({
|
||||
|
@ -3840,9 +3891,18 @@ static void prim_mul(EvalState & state, const PosIdx pos, Value * * args, Value
|
|||
if (args[0]->type() == nFloat || args[1]->type() == nFloat)
|
||||
v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first of the multiplication")
|
||||
* state.forceFloat(*args[1], pos, "while evaluating the second argument of the multiplication"));
|
||||
else
|
||||
v.mkInt( state.forceInt(*args[0], pos, "while evaluating the first argument of the multiplication")
|
||||
* state.forceInt(*args[1], pos, "while evaluating the second argument of the multiplication"));
|
||||
else {
|
||||
auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument of the multiplication");
|
||||
auto i2 = state.forceInt(*args[1], pos, "while evaluating the second argument of the multiplication");
|
||||
|
||||
auto result_ = i1 * i2;
|
||||
|
||||
if (auto result = result_.valueChecked(); result.has_value()) {
|
||||
v.mkInt(*result);
|
||||
} else {
|
||||
state.error<EvalError>("integer overflow in multiplying %1% * %2%", i1, i2).atPos(pos).debugThrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_mul({
|
||||
|
@ -3869,10 +3929,12 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value
|
|||
NixInt i1 = state.forceInt(*args[0], pos, "while evaluating the first operand of the division");
|
||||
NixInt i2 = state.forceInt(*args[1], pos, "while evaluating the second operand of the division");
|
||||
/* Avoid division overflow as it might raise SIGFPE. */
|
||||
if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1)
|
||||
state.error<EvalError>("overflow in integer division").atPos(pos).debugThrow();
|
||||
|
||||
v.mkInt(i1 / i2);
|
||||
auto result_ = i1 / i2;
|
||||
if (auto result = result_.valueChecked(); result.has_value()) {
|
||||
v.mkInt(*result);
|
||||
} else {
|
||||
state.error<EvalError>("integer overflow in dividing %1% / %2%", i1, i2).atPos(pos).debugThrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3887,8 +3949,9 @@ static RegisterPrimOp primop_div({
|
|||
|
||||
static void prim_bitAnd(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
v.mkInt(state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitAnd")
|
||||
& state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitAnd"));
|
||||
auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitAnd");
|
||||
auto i2 = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitAnd");
|
||||
v.mkInt(i1.value & i2.value);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_bitAnd({
|
||||
|
@ -3902,8 +3965,10 @@ static RegisterPrimOp primop_bitAnd({
|
|||
|
||||
static void prim_bitOr(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
v.mkInt(state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitOr")
|
||||
| state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitOr"));
|
||||
auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitOr");
|
||||
auto i2 = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitOr");
|
||||
|
||||
v.mkInt(i1.value | i2.value);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_bitOr({
|
||||
|
@ -3917,8 +3982,10 @@ static RegisterPrimOp primop_bitOr({
|
|||
|
||||
static void prim_bitXor(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
v.mkInt(state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitXor")
|
||||
^ state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitXor"));
|
||||
auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitXor");
|
||||
auto i2 = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitXor");
|
||||
|
||||
v.mkInt(i1.value ^ i2.value);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_bitXor({
|
||||
|
@ -3998,13 +4065,19 @@ static RegisterPrimOp primop_toString({
|
|||
non-negative. */
|
||||
static void prim_substring(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
int start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring");
|
||||
NixInt::Inner start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring").value;
|
||||
|
||||
if (start < 0)
|
||||
state.error<EvalError>("negative start position in 'substring'").atPos(pos).debugThrow();
|
||||
|
||||
|
||||
int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring");
|
||||
NixInt::Inner len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring").value;
|
||||
|
||||
// Negative length may be idiomatically passed to builtins.substring to get
|
||||
// the tail of the string.
|
||||
if (len < 0) {
|
||||
len = std::numeric_limits<NixInt::Inner>::max();
|
||||
}
|
||||
|
||||
// Special-case on empty substring to avoid O(n) strlen
|
||||
// This allows for the use of empty substrings to efficently capture string context
|
||||
|
@ -4047,7 +4120,7 @@ static void prim_stringLength(EvalState & state, const PosIdx pos, Value * * arg
|
|||
{
|
||||
NixStringContext context;
|
||||
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.stringLength");
|
||||
v.mkInt(s->size());
|
||||
v.mkInt(NixInt::Inner(s->size()));
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_stringLength({
|
||||
|
@ -4230,7 +4303,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) {
|
||||
|
@ -4314,7 +4387,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;
|
||||
|
@ -4325,14 +4398,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);
|
||||
|
@ -4531,7 +4604,8 @@ static void prim_compareVersions(EvalState & state, const PosIdx pos, Value * *
|
|||
{
|
||||
auto version1 = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.compareVersions");
|
||||
auto version2 = state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.compareVersions");
|
||||
v.mkInt(compareVersions(version1, version2));
|
||||
auto result = compareVersions(version1, version2);
|
||||
v.mkInt(result < 0 ? -1 : result > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_compareVersions({
|
||||
|
@ -4814,7 +4888,10 @@ void EvalState::createBaseEnv()
|
|||
addConstant("__nixPath", v, {
|
||||
.type = nList,
|
||||
.doc = R"(
|
||||
The value of the [`nix-path` configuration setting](@docroot@/command-ref/conf-file.md#conf-nix-path): a list of search path entries used to resolve [lookup paths](@docroot@/language/constructs/lookup-path.md).
|
||||
A list of search path entries used to resolve [lookup paths](@docroot@/language/constructs/lookup-path.md).
|
||||
Its value is primarily determined by the [`nix-path` configuration setting](@docroot@/command-ref/conf-file.md#conf-nix-path), which are
|
||||
- Overridden by the [`NIX_PATH`](@docroot@/command-ref/env-common.md#env-NIX_PATH) environment variable or the `--nix-path` option
|
||||
- Extended by the [`-I` option](@docroot@/command-ref/opt-common.md#opt-I) or `--extra-nix-path`
|
||||
|
||||
> **Example**
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
|
@ -122,9 +124,15 @@ static void fetchTree(
|
|||
}
|
||||
else if (attr.value->type() == nBool)
|
||||
attrs.emplace(state.symbols[attr.name], Explicit<bool>{attr.value->boolean()});
|
||||
else if (attr.value->type() == nInt)
|
||||
attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer()));
|
||||
else if (state.symbols[attr.name] == "publicKeys") {
|
||||
else if (attr.value->type() == nInt) {
|
||||
auto intValue = attr.value->integer().value;
|
||||
|
||||
if (intValue < 0) {
|
||||
state.error<EvalError>("negative value given for fetchTree attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow();
|
||||
}
|
||||
|
||||
attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
|
||||
} else if (state.symbols[attr.name] == "publicKeys") {
|
||||
experimentalFeatureSettings.require(Xp::VerifiedFetches);
|
||||
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump());
|
||||
}
|
||||
|
@ -240,7 +248,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"`
|
||||
|
@ -501,7 +509,11 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
|
|||
// https://github.com/NixOS/nix/issues/4313
|
||||
auto storePath =
|
||||
unpack
|
||||
? fetchToStore(*state.store, fetchers::downloadTarball(*url).accessor, FetchMode::Copy, name)
|
||||
? fetchToStore(
|
||||
*state.store,
|
||||
fetchers::downloadTarball(state.store, state.fetchSettings, *url),
|
||||
FetchMode::Copy,
|
||||
name)
|
||||
: fetchers::downloadFile(state.store, *url, name).storePath;
|
||||
|
||||
if (expectedHash) {
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -22,7 +22,7 @@ json printValueAsJSON(EvalState & state, bool strict,
|
|||
switch (v.type()) {
|
||||
|
||||
case nInt:
|
||||
out = v.integer();
|
||||
out = v.integer().value;
|
||||
break;
|
||||
|
||||
case nBool:
|
||||
|
|
|
@ -4,14 +4,13 @@
|
|||
#include <cassert>
|
||||
#include <span>
|
||||
|
||||
#include "eval-gc.hh"
|
||||
#include "symbol-table.hh"
|
||||
#include "value/context.hh"
|
||||
#include "source-path.hh"
|
||||
#include "print-options.hh"
|
||||
#include "checked-arithmetic.hh"
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
#include <gc/gc_allocator.h>
|
||||
#endif
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
namespace nix {
|
||||
|
@ -73,8 +72,8 @@ class EvalState;
|
|||
class XMLWriter;
|
||||
class Printer;
|
||||
|
||||
typedef int64_t NixInt;
|
||||
typedef double NixFloat;
|
||||
using NixInt = checked::Checked<int64_t>;
|
||||
using NixFloat = double;
|
||||
|
||||
/**
|
||||
* External values must descend from ExternalValueBase, so that
|
||||
|
@ -304,6 +303,11 @@ public:
|
|||
return internalType != tUninitialized;
|
||||
}
|
||||
|
||||
inline void mkInt(NixInt::Inner n)
|
||||
{
|
||||
mkInt(NixInt{n});
|
||||
}
|
||||
|
||||
inline void mkInt(NixInt n)
|
||||
{
|
||||
finishValue(tInt, { .integer = n });
|
||||
|
@ -325,9 +329,9 @@ public:
|
|||
|
||||
void mkStringMove(const char * s, const NixStringContext & context);
|
||||
|
||||
inline void mkString(const Symbol & s)
|
||||
inline void mkString(const SymbolStr & s)
|
||||
{
|
||||
mkString(((const std::string &) s).c_str());
|
||||
mkString(s.c_str());
|
||||
}
|
||||
|
||||
void mkPath(const SourcePath & path);
|
||||
|
@ -492,15 +496,9 @@ void Value::mkBlackhole()
|
|||
}
|
||||
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::vector<Value *, traceable_allocator<Value *>> ValueVector;
|
||||
typedef std::unordered_map<Symbol, Value *, std::hash<Symbol>, std::equal_to<Symbol>, traceable_allocator<std::pair<const Symbol, Value *>>> ValueMap;
|
||||
typedef std::map<Symbol, ValueVector, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, ValueVector>>> ValueVectorMap;
|
||||
#else
|
||||
typedef std::vector<Value *> ValueVector;
|
||||
typedef std::unordered_map<Symbol, Value *> ValueMap;
|
||||
typedef std::map<Symbol, ValueVector> ValueVectorMap;
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
|
|
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->sync());
|
||||
auto accessor = repo->getAccessor(result, false, 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
|
37
src/libfetchers-tests/local.mk
Normal file
37
src/libfetchers-tests/local.mk
Normal file
|
@ -0,0 +1,37 @@
|
|||
check: libfetchers-tests_RUN
|
||||
|
||||
programs += libfetchers-tests
|
||||
|
||||
libfetchers-tests_NAME = libnixfetchers-tests
|
||||
|
||||
libfetchers-tests_ENV := _NIX_TEST_UNIT_DATA=$(d)/data GTEST_OUTPUT=xml:$$testresults/libfetchers-tests.xml
|
||||
|
||||
libfetchers-tests_DIR := $(d)
|
||||
|
||||
ifeq ($(INSTALL_UNIT_TESTS), yes)
|
||||
libfetchers-tests_INSTALL_DIR := $(checkbindir)
|
||||
else
|
||||
libfetchers-tests_INSTALL_DIR :=
|
||||
endif
|
||||
|
||||
libfetchers-tests_SOURCES := $(wildcard $(d)/*.cc)
|
||||
|
||||
libfetchers-tests_EXTRA_INCLUDES = \
|
||||
-I src/libstore-test-support \
|
||||
-I src/libutil-test-support \
|
||||
$(INCLUDE_libfetchers) \
|
||||
$(INCLUDE_libstore) \
|
||||
$(INCLUDE_libutil)
|
||||
|
||||
libfetchers-tests_CXXFLAGS += $(libfetchers-tests_EXTRA_INCLUDES)
|
||||
|
||||
libfetchers-tests_LIBS = \
|
||||
libstore-test-support libutil-test-support \
|
||||
libfetchers libstore libutil
|
||||
|
||||
libfetchers-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS) $(LIBGIT2_LIBS)
|
||||
|
||||
ifdef HOST_WINDOWS
|
||||
# Increase the default reserved stack size to 65 MB so Nix doesn't run out of space
|
||||
libfetchers-tests_LDFLAGS += -Wl,--stack,$(shell echo $$((65 * 1024 * 1024)))
|
||||
endif
|
74
src/libfetchers-tests/meson.build
Normal file
74
src/libfetchers-tests/meson.build
Normal file
|
@ -0,0 +1,74 @@
|
|||
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/threads')
|
||||
|
||||
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/diagnostics')
|
||||
|
||||
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() + "/nix/fetcher-cache-v2.sqlite";
|
||||
auto dbPath = getCacheDir() + "/fetcher-cache-v2.sqlite";
|
||||
createDirs(dirOf(dbPath));
|
||||
|
||||
state->db = SQLite(dbPath);
|
||||
|
|
|
@ -170,24 +170,6 @@ std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
|
|||
if (!scheme)
|
||||
throw Error("cannot fetch unsupported input '%s'", attrsToJSON(toAttrs()));
|
||||
|
||||
/* 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. */
|
||||
if (getNarHash()) {
|
||||
try {
|
||||
auto storePath = computeStorePath(*store);
|
||||
|
||||
store->ensurePath(storePath);
|
||||
|
||||
debug("using substituted/cached input '%s' in '%s'",
|
||||
to_string(), store->printStorePath(storePath));
|
||||
|
||||
return {std::move(storePath), *this};
|
||||
} catch (Error & e) {
|
||||
debug("substitution of input '%s' failed: %s", to_string(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
auto [storePath, input] = [&]() -> std::pair<StorePath, Input> {
|
||||
try {
|
||||
auto [accessor, final] = getAccessorUnchecked(store);
|
||||
|
|
|
@ -14,13 +14,17 @@
|
|||
#include <git2/describe.h>
|
||||
#include <git2/errors.h>
|
||||
#include <git2/global.h>
|
||||
#include <git2/indexer.h>
|
||||
#include <git2/object.h>
|
||||
#include <git2/odb.h>
|
||||
#include <git2/refs.h>
|
||||
#include <git2/remote.h>
|
||||
#include <git2/repository.h>
|
||||
#include <git2/revparse.h>
|
||||
#include <git2/status.h>
|
||||
#include <git2/submodule.h>
|
||||
#include <git2/sys/odb_backend.h>
|
||||
#include <git2/sys/mempack.h>
|
||||
#include <git2/tree.h>
|
||||
|
||||
#include <iostream>
|
||||
|
@ -77,6 +81,9 @@ typedef std::unique_ptr<git_status_list, Deleter<git_status_list_free>> StatusLi
|
|||
typedef std::unique_ptr<git_remote, Deleter<git_remote_free>> Remote;
|
||||
typedef std::unique_ptr<git_config, Deleter<git_config_free>> GitConfig;
|
||||
typedef std::unique_ptr<git_config_iterator, Deleter<git_config_iterator_free>> ConfigIterator;
|
||||
typedef std::unique_ptr<git_odb, Deleter<git_odb_free>> ObjectDb;
|
||||
typedef std::unique_ptr<git_packbuilder, Deleter<git_packbuilder_free>> PackBuilder;
|
||||
typedef std::unique_ptr<git_indexer, Deleter<git_indexer_free>> Indexer;
|
||||
|
||||
// A helper to ensure that we don't leak objects returned by libgit2.
|
||||
template<typename T>
|
||||
|
@ -160,24 +167,101 @@ static Object peelToTreeOrBlob(git_object * obj)
|
|||
return peelObject<Object>(obj, GIT_OBJECT_TREE);
|
||||
}
|
||||
|
||||
struct PackBuilderContext {
|
||||
std::exception_ptr exception;
|
||||
|
||||
void handleException(const char * activity, int errCode)
|
||||
{
|
||||
switch (errCode) {
|
||||
case GIT_OK:
|
||||
break;
|
||||
case GIT_EUSER:
|
||||
if (!exception)
|
||||
panic("PackBuilderContext::handleException: user error, but exception was not set");
|
||||
|
||||
std::rethrow_exception(exception);
|
||||
default:
|
||||
throw Error("%s: %i, %s", Uncolored(activity), errCode, git_error_last()->message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
/**
|
||||
* A `git_packbuilder_progress` implementation that aborts the pack building if needed.
|
||||
*/
|
||||
static int packBuilderProgressCheckInterrupt(int stage, uint32_t current, uint32_t total, void *payload)
|
||||
{
|
||||
PackBuilderContext & args = * (PackBuilderContext *) payload;
|
||||
try {
|
||||
checkInterrupt();
|
||||
return GIT_OK;
|
||||
} catch (const std::exception & e) {
|
||||
args.exception = std::current_exception();
|
||||
return GIT_EUSER;
|
||||
}
|
||||
};
|
||||
static git_packbuilder_progress PACKBUILDER_PROGRESS_CHECK_INTERRUPT = &packBuilderProgressCheckInterrupt;
|
||||
|
||||
} // 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) {
|
||||
if (e.code() == std::errc::file_exists) // Someone might race us to create the repository.
|
||||
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. */
|
||||
std::filesystem::path path;
|
||||
/**
|
||||
* libgit2 repository. Note that new objects are not written to disk,
|
||||
* because we are using a mempack backend. For writing to disk, see
|
||||
* `flush()`, which is also called by `GitFileSystemObjectSink::sync()`.
|
||||
*/
|
||||
Repository repo;
|
||||
/**
|
||||
* In-memory object store for efficient batched writing to packfiles.
|
||||
* Owned by `repo`.
|
||||
*/
|
||||
git_odb_backend * mempack_backend;
|
||||
|
||||
GitRepoImpl(std::filesystem::path _path, bool create, bool bare)
|
||||
: path(std::move(_path))
|
||||
{
|
||||
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()))
|
||||
throw Error("getting Git object database: %s", git_error_last()->message);
|
||||
|
||||
// mempack_backend will be owned by the repository, so we are not expected to free it ourselves.
|
||||
if (git_mempack_new(&mempack_backend))
|
||||
throw Error("creating mempack backend: %s", git_error_last()->message);
|
||||
|
||||
if (git_odb_add_backend(odb.get(), mempack_backend, 999))
|
||||
throw Error("adding mempack backend to Git object database: %s", git_error_last()->message);
|
||||
}
|
||||
|
||||
operator git_repository * ()
|
||||
|
@ -185,6 +269,62 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
|||
return repo.get();
|
||||
}
|
||||
|
||||
void flush() override {
|
||||
checkInterrupt();
|
||||
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
Finally _disposeBuf { [&] { git_buf_dispose(&buf); } };
|
||||
PackBuilder packBuilder;
|
||||
PackBuilderContext packBuilderContext;
|
||||
git_packbuilder_new(Setter(packBuilder), *this);
|
||||
git_packbuilder_set_callbacks(packBuilder.get(), PACKBUILDER_PROGRESS_CHECK_INTERRUPT, &packBuilderContext);
|
||||
git_packbuilder_set_threads(packBuilder.get(), 0 /* autodetect */);
|
||||
|
||||
packBuilderContext.handleException(
|
||||
"preparing packfile",
|
||||
git_mempack_write_thin_pack(mempack_backend, packBuilder.get())
|
||||
);
|
||||
checkInterrupt();
|
||||
packBuilderContext.handleException(
|
||||
"writing packfile",
|
||||
git_packbuilder_write_buf(&buf, packBuilder.get())
|
||||
);
|
||||
checkInterrupt();
|
||||
|
||||
std::string repo_path = std::string(git_repository_path(repo.get()));
|
||||
while (!repo_path.empty() && repo_path.back() == '/')
|
||||
repo_path.pop_back();
|
||||
std::string pack_dir_path = repo_path + "/objects/pack";
|
||||
|
||||
// TODO (performance): could the indexing be done in a separate thread?
|
||||
// we'd need a more streaming variation of
|
||||
// git_packbuilder_write_buf, or incur the cost of
|
||||
// copying parts of the buffer to a separate thread.
|
||||
// (synchronously on the git_packbuilder_write_buf thread)
|
||||
Indexer indexer;
|
||||
git_indexer_progress stats;
|
||||
if (git_indexer_new(Setter(indexer), pack_dir_path.c_str(), 0, nullptr, nullptr))
|
||||
throw Error("creating git packfile indexer: %s", git_error_last()->message);
|
||||
|
||||
// TODO: provide index callback for checkInterrupt() termination
|
||||
// though this is about an order of magnitude faster than the packbuilder
|
||||
// expect up to 1 sec latency due to uninterruptible git_indexer_append.
|
||||
constexpr size_t chunkSize = 128 * 1024;
|
||||
for (size_t offset = 0; offset < buf.size; offset += chunkSize) {
|
||||
if (git_indexer_append(indexer.get(), buf.ptr + offset, std::min(chunkSize, buf.size - offset), &stats))
|
||||
throw Error("appending to git packfile index: %s", git_error_last()->message);
|
||||
checkInterrupt();
|
||||
}
|
||||
|
||||
if (git_indexer_commit(indexer.get(), &stats))
|
||||
throw Error("committing git packfile index: %s", git_error_last()->message);
|
||||
|
||||
if (git_mempack_reset(mempack_backend))
|
||||
throw Error("resetting git mempack backend: %s", git_error_last()->message);
|
||||
|
||||
checkInterrupt();
|
||||
}
|
||||
|
||||
uint64_t getRevCount(const Hash & rev) override
|
||||
{
|
||||
std::unordered_set<git_oid> done;
|
||||
|
@ -461,7 +601,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 + ")";
|
||||
}
|
||||
|
@ -641,12 +787,16 @@ struct GitSourceAccessor : SourceAccessor
|
|||
return readBlob(path, true);
|
||||
}
|
||||
|
||||
Hash getSubmoduleRev(const CanonPath & path)
|
||||
/**
|
||||
* If `path` exists and is a submodule, return its
|
||||
* revision. Otherwise return nothing.
|
||||
*/
|
||||
std::optional<Hash> getSubmoduleRev(const CanonPath & path)
|
||||
{
|
||||
auto entry = need(path);
|
||||
auto entry = lookup(path);
|
||||
|
||||
if (git_tree_entry_type(entry) != GIT_OBJECT_COMMIT)
|
||||
throw Error("'%s' is not a submodule", showPath(path));
|
||||
if (!entry || git_tree_entry_type(entry) != GIT_OBJECT_COMMIT)
|
||||
return std::nullopt;
|
||||
|
||||
return toHash(*git_tree_entry_id(entry));
|
||||
}
|
||||
|
@ -870,8 +1020,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) });
|
||||
};
|
||||
|
@ -1045,12 +1211,14 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
|
|||
git_tree_entry_filemode(entry));
|
||||
}
|
||||
|
||||
Hash sync() override
|
||||
Hash flush() override
|
||||
{
|
||||
updateBuilders({});
|
||||
|
||||
auto [oid, _name] = popBuilder();
|
||||
|
||||
repo->flush();
|
||||
|
||||
return toHash(oid);
|
||||
}
|
||||
};
|
||||
|
@ -1123,8 +1291,10 @@ std::vector<std::tuple<GitRepoImpl::Submodule, Hash>> GitRepoImpl::getSubmodules
|
|||
auto rawAccessor = getRawAccessor(rev, false);
|
||||
|
||||
for (auto & submodule : parseSubmodules(pathTemp)) {
|
||||
auto rev = rawAccessor->getSubmoduleRev(submodule.path);
|
||||
result.push_back({std::move(submodule), rev});
|
||||
/* Filter out .gitmodules entries that don't exist or are not
|
||||
submodules. */
|
||||
if (auto rev = rawAccessor->getSubmoduleRev(submodule.path))
|
||||
result.push_back({std::move(submodule), *rev});
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -1132,7 +1302,7 @@ std::vector<std::tuple<GitRepoImpl::Submodule, Hash>> GitRepoImpl::getSubmodules
|
|||
|
||||
ref<GitRepo> getTarballCache()
|
||||
{
|
||||
static auto repoDir = std::filesystem::path(getCacheDir()) / "nix" / "tarball-cache";
|
||||
static auto repoDir = std::filesystem::path(getCacheDir()) / "tarball-cache";
|
||||
|
||||
return GitRepo::openRepo(repoDir, true, true);
|
||||
}
|
||||
|
|
|
@ -7,12 +7,16 @@ namespace nix {
|
|||
|
||||
namespace fetchers { struct PublicKey; }
|
||||
|
||||
/**
|
||||
* A sink that writes into a Git repository. Note that nothing may be written
|
||||
* until `flush()` is called.
|
||||
*/
|
||||
struct GitFileSystemObjectSink : ExtendedFileSystemObjectSink
|
||||
{
|
||||
/**
|
||||
* Flush builder and return a final Git hash.
|
||||
*/
|
||||
virtual Hash sync() = 0;
|
||||
virtual Hash flush() = 0;
|
||||
};
|
||||
|
||||
struct GitRepo
|
||||
|
@ -80,6 +84,8 @@ struct GitRepo
|
|||
|
||||
virtual ref<GitFileSystemObjectSink> getFileSystemObjectSink() = 0;
|
||||
|
||||
virtual void flush() = 0;
|
||||
|
||||
virtual void fetch(
|
||||
const std::string & url,
|
||||
const std::string & refspec,
|
||||
|
|
|
@ -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>
|
||||
|
@ -44,7 +44,7 @@ bool isCacheFileWithinTtl(time_t now, const struct stat & st)
|
|||
Path getCachePath(std::string_view key, bool shallow)
|
||||
{
|
||||
return getCacheDir()
|
||||
+ "/nix/gitv3/"
|
||||
+ "/gitv3/"
|
||||
+ hashString(HashAlgorithm::SHA256, key).to_string(HashFormat::Nix32, false)
|
||||
+ (shallow ? "-shallow" : "");
|
||||
}
|
||||
|
@ -590,9 +590,10 @@ struct GitInputScheme : InputScheme
|
|||
}
|
||||
|
||||
try {
|
||||
setWriteTime(localRefFile, now, now);
|
||||
if (!input.getRev())
|
||||
setWriteTime(localRefFile, now, now);
|
||||
} catch (Error & e) {
|
||||
warn("could not update mtime for file '%s': %s", localRefFile, e.msg());
|
||||
warn("could not update mtime for file '%s': %s", localRefFile, e.info().msg);
|
||||
}
|
||||
if (!originalRef && !storeCachedHead(repoInfo.url, ref))
|
||||
warn("could not update cached head '%s' for '%s'", ref, repoInfo.url);
|
||||
|
|
|
@ -261,11 +261,12 @@ struct GitArchiveInputScheme : InputScheme
|
|||
auto tarballCache = getTarballCache();
|
||||
auto parseSink = tarballCache->getFileSystemObjectSink();
|
||||
auto lastModified = unpackTarfileToSink(archive, *parseSink);
|
||||
auto tree = parseSink->flush();
|
||||
|
||||
act.reset();
|
||||
|
||||
TarballInfo tarballInfo {
|
||||
.treeHash = tarballCache->dereferenceSingletonDirectory(parseSink->sync()),
|
||||
.treeHash = tarballCache->dereferenceSingletonDirectory(tree),
|
||||
.lastModified = lastModified
|
||||
};
|
||||
|
||||
|
|
|
@ -263,7 +263,7 @@ struct MercurialInputScheme : InputScheme
|
|||
return makeResult(res->value, res->storePath);
|
||||
}
|
||||
|
||||
Path cacheDir = fmt("%s/nix/hg/%s", getCacheDir(), hashString(HashAlgorithm::SHA256, actualUrl).to_string(HashFormat::Nix32, false));
|
||||
Path cacheDir = fmt("%s/hg/%s", getCacheDir(), hashString(HashAlgorithm::SHA256, actualUrl).to_string(HashFormat::Nix32, false));
|
||||
|
||||
/* If this is a commit hash that we already have, we don't
|
||||
have to pull again. */
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -157,7 +157,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)};
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ static std::shared_ptr<Registry> getSystemRegistry(const Settings & settings)
|
|||
|
||||
Path getUserRegistryPath()
|
||||
{
|
||||
return getConfigDir() + "/nix/registry.json";
|
||||
return getConfigDir() + "/registry.json";
|
||||
}
|
||||
|
||||
std::shared_ptr<Registry> getUserRegistry(const Settings & settings)
|
||||
|
@ -159,7 +159,7 @@ static std::shared_ptr<Registry> getGlobalRegistry(const Settings & settings, re
|
|||
if (!hasPrefix(path, "/")) {
|
||||
auto storePath = downloadFile(store, path, "flake-registry.json").storePath;
|
||||
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
|
||||
store2->addPermRoot(storePath, getCacheDir() + "/nix/flake-registry.json");
|
||||
store2->addPermRoot(storePath, getCacheDir() + "/flake-registry.json");
|
||||
path = store->toRealPath(storePath);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
@ -102,7 +103,7 @@ DownloadFileResult downloadFile(
|
|||
};
|
||||
}
|
||||
|
||||
DownloadTarballResult downloadTarball(
|
||||
static DownloadTarballResult downloadTarball_(
|
||||
const std::string & url,
|
||||
const Headers & headers)
|
||||
{
|
||||
|
@ -170,6 +171,7 @@ DownloadTarballResult downloadTarball(
|
|||
auto tarballCache = getTarballCache();
|
||||
auto parseSink = tarballCache->getFileSystemObjectSink();
|
||||
auto lastModified = unpackTarfileToSink(archive, *parseSink);
|
||||
auto tree = parseSink->flush();
|
||||
|
||||
act.reset();
|
||||
|
||||
|
@ -184,7 +186,7 @@ DownloadTarballResult downloadTarball(
|
|||
} else {
|
||||
infoAttrs.insert_or_assign("etag", res->etag);
|
||||
infoAttrs.insert_or_assign("treeHash",
|
||||
tarballCache->dereferenceSingletonDirectory(parseSink->sync()).gitRev());
|
||||
tarballCache->dereferenceSingletonDirectory(tree).gitRev());
|
||||
infoAttrs.insert_or_assign("lastModified", uint64_t(lastModified));
|
||||
if (res->immutableUrl)
|
||||
infoAttrs.insert_or_assign("immutableUrl", *res->immutableUrl);
|
||||
|
@ -202,6 +204,22 @@ DownloadTarballResult downloadTarball(
|
|||
return attrsToResult(infoAttrs);
|
||||
}
|
||||
|
||||
ref<SourceAccessor> downloadTarball(
|
||||
ref<Store> store,
|
||||
const Settings & settings,
|
||||
const std::string & url)
|
||||
{
|
||||
/* Go through Input::getAccessor() to ensure that the resulting
|
||||
accessor has a fingerprint. */
|
||||
fetchers::Attrs attrs;
|
||||
attrs.insert_or_assign("type", "tarball");
|
||||
attrs.insert_or_assign("url", url);
|
||||
|
||||
auto input = Input::fromAttrs(settings, std::move(attrs));
|
||||
|
||||
return input.getAccessor(store).first;
|
||||
}
|
||||
|
||||
// An input scheme corresponding to a curl-downloadable resource.
|
||||
struct CurlInputScheme : InputScheme
|
||||
{
|
||||
|
@ -353,7 +371,7 @@ struct TarballInputScheme : CurlInputScheme
|
|||
{
|
||||
auto input(_input);
|
||||
|
||||
auto result = downloadTarball(getStrAttr(input.attrs, "url"), {});
|
||||
auto result = downloadTarball_(getStrAttr(input.attrs, "url"), {});
|
||||
|
||||
result.accessor->setPathDisplay("«" + input.to_string() + "»");
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ struct SourceAccessor;
|
|||
|
||||
namespace nix::fetchers {
|
||||
|
||||
struct Settings;
|
||||
|
||||
struct DownloadFileResult
|
||||
{
|
||||
StorePath storePath;
|
||||
|
@ -40,8 +42,9 @@ struct DownloadTarballResult
|
|||
* Download and import a tarball into the Git cache. The result is the
|
||||
* Git tree hash of the root directory.
|
||||
*/
|
||||
DownloadTarballResult downloadTarball(
|
||||
const std::string & url,
|
||||
const Headers & headers = {});
|
||||
ref<SourceAccessor> downloadTarball(
|
||||
ref<Store> store,
|
||||
const Settings & settings,
|
||||
const std::string & url);
|
||||
|
||||
}
|
||||
|
|
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);
|
||||
}
|
||||
|
||||
}
|
43
src/libflake-tests/local.mk
Normal file
43
src/libflake-tests/local.mk
Normal file
|
@ -0,0 +1,43 @@
|
|||
check: libflake-tests_RUN
|
||||
|
||||
programs += libflake-tests
|
||||
|
||||
libflake-tests_NAME := libnixflake-tests
|
||||
|
||||
libflake-tests_ENV := _NIX_TEST_UNIT_DATA=$(d)/data GTEST_OUTPUT=xml:$$testresults/libflake-tests.xml
|
||||
|
||||
libflake-tests_DIR := $(d)
|
||||
|
||||
ifeq ($(INSTALL_UNIT_TESTS), yes)
|
||||
libflake-tests_INSTALL_DIR := $(checkbindir)
|
||||
else
|
||||
libflake-tests_INSTALL_DIR :=
|
||||
endif
|
||||
|
||||
libflake-tests_SOURCES := \
|
||||
$(wildcard $(d)/*.cc) \
|
||||
$(wildcard $(d)/value/*.cc) \
|
||||
$(wildcard $(d)/flake/*.cc)
|
||||
|
||||
libflake-tests_EXTRA_INCLUDES = \
|
||||
-I src/libflake-test-support \
|
||||
-I src/libstore-test-support \
|
||||
-I src/libutil-test-support \
|
||||
$(INCLUDE_libflake) \
|
||||
$(INCLUDE_libexpr) \
|
||||
$(INCLUDE_libfetchers) \
|
||||
$(INCLUDE_libstore) \
|
||||
$(INCLUDE_libutil) \
|
||||
|
||||
libflake-tests_CXXFLAGS += $(libflake-tests_EXTRA_INCLUDES)
|
||||
|
||||
libflake-tests_LIBS = \
|
||||
libexpr-test-support libstore-test-support libutil-test-support \
|
||||
libflake libexpr libfetchers libstore libutil
|
||||
|
||||
libflake-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS) -lgmock
|
||||
|
||||
ifdef HOST_WINDOWS
|
||||
# Increase the default reserved stack size to 65 MB so Nix doesn't run out of space
|
||||
libflake-tests_LDFLAGS += -Wl,--stack,$(shell echo $$((65 * 1024 * 1024)))
|
||||
endif
|
75
src/libflake-tests/meson.build
Normal file
75
src/libflake-tests/meson.build
Normal file
|
@ -0,0 +1,75 @@
|
|||
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/threads')
|
||||
|
||||
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/diagnostics')
|
||||
|
||||
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