forked from lix-project/lix
Previously, errors while printing values in `nix repl` would be printed
in `«error: ...»` brackets rather than displayed normally:
```
nix-repl> legacyPackages.aarch64-darwin.pythonPackages.APScheduler
«error: Package ‘python-2.7.18.7’ in /nix/store/6s0m1qc31zw3l3kq0q4wd5cp3lqpkq0q-source/pkgs/development/interpreters/python/cpython/2.7/default.nix:335 is marked as insecure, refusing to evaluate.»
```
Now, errors will be displayed normally if they're emitted at the
top-level of an expression:
```
nix-repl> legacyPackages.aarch64-darwin.pythonPackages.APScheduler
error:
… in the condition of the assert statement
at /nix/store/6s0m1qc31zw3l3kq0q4wd5cp3lqpkq0q-source/lib/customisation.nix:268:17:
267| in commonAttrs // {
268| drvPath = assert condition; drv.drvPath;
| ^
269| outPath = assert condition; drv.outPath;
… in the left operand of the OR (||) operator
at /nix/store/6s0m1qc31zw3l3kq0q4wd5cp3lqpkq0q-source/pkgs/development/interpreters/python/passthrufun.nix:28:45:
27| if lib.isDerivation value then
28| lib.extendDerivation (valid value || throw "${name} should use `buildPythonPackage` or `toPythonModule` if it is to be part of the Python packages set.") {} value
| ^
29| else
(stack trace truncated; use '--show-trace' to show the full trace)
error: Package ‘python-2.7.18.7’ in /nix/store/6s0m1qc31zw3l3kq0q4wd5cp3lqpkq0q-source/pkgs/development/interpreters/python/cpython/2.7/default.nix:335 is marked as insecure, refusing to evaluate.
```
Errors emitted in nested structures (like e.g. when printing `nixpkgs`)
will still be printed in brackets.
Change-Id: I25aeddf08c017582718cb9772a677bf51b9fc2ad
192 lines
5.9 KiB
C++
192 lines
5.9 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <boost/algorithm/string/replace.hpp>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <unistd.h>
|
|
|
|
#include "escape-string.hh"
|
|
#include "test-session.hh"
|
|
#include "tests/characterization.hh"
|
|
#include "tests/cli-literate-parser.hh"
|
|
#include "tests/terminal-code-eater.hh"
|
|
#include "util.hh"
|
|
|
|
using namespace std::string_literals;
|
|
|
|
namespace nix {
|
|
|
|
static constexpr const std::string_view REPL_PROMPT = "nix-repl> ";
|
|
|
|
// ASCII ENQ character
|
|
static constexpr const std::string_view AUTOMATION_PROMPT = "\x05";
|
|
|
|
static std::string_view trimOutLog(std::string_view outLog)
|
|
{
|
|
const std::string trailer = "\n"s + AUTOMATION_PROMPT;
|
|
if (outLog.ends_with(trailer)) {
|
|
outLog.remove_suffix(trailer.length());
|
|
}
|
|
return outLog;
|
|
}
|
|
|
|
class ReplSessionTest : public CharacterizationTest
|
|
{
|
|
Path unitTestData = getUnitTestData();
|
|
|
|
public:
|
|
Path goldenMaster(std::string_view testStem) const override
|
|
{
|
|
return unitTestData + "/" + testStem;
|
|
}
|
|
|
|
void runReplTest(const std::string content, std::vector<std::string> extraArgs = {}) const
|
|
{
|
|
auto parsed = cli_literate_parser::parse(
|
|
content, cli_literate_parser::Config{.prompt = std::string(REPL_PROMPT), .indent = 2}
|
|
);
|
|
parsed.interpolatePwd(unitTestData);
|
|
|
|
// FIXME: why does this need two --quiets
|
|
// show-trace is on by default due to test configuration, but is not a
|
|
// standard
|
|
Strings args{
|
|
"--quiet",
|
|
"repl",
|
|
"--quiet",
|
|
"--option",
|
|
"show-trace",
|
|
"false",
|
|
"--offline",
|
|
"--extra-experimental-features",
|
|
"repl-automation",
|
|
};
|
|
args.insert(args.end(), extraArgs.begin(), extraArgs.end());
|
|
args.insert(args.end(), parsed.args.begin(), parsed.args.end());
|
|
|
|
auto nixBin = canonPath(getEnvNonEmpty("NIX_BIN_DIR").value_or(NIX_BIN_DIR));
|
|
|
|
auto process = RunningProcess::start(nixBin + "/nix", args);
|
|
auto session = TestSession(std::string(AUTOMATION_PROMPT), std::move(process));
|
|
|
|
for (auto & event : parsed.syntax) {
|
|
std::visit(
|
|
overloaded{
|
|
[&](const cli_literate_parser::Command & e) {
|
|
ASSERT_TRUE(session.waitForPrompt());
|
|
if (e.text == ":quit") {
|
|
// If we quit the repl explicitly, we won't have a
|
|
// prompt when we're done.
|
|
parsed.shouldStart = false;
|
|
}
|
|
session.runCommand(e.text);
|
|
},
|
|
[&](const auto & e) {},
|
|
},
|
|
event
|
|
);
|
|
}
|
|
if (parsed.shouldStart) {
|
|
ASSERT_TRUE(session.waitForPrompt());
|
|
}
|
|
session.close();
|
|
|
|
auto replacedOutLog =
|
|
boost::algorithm::replace_all_copy(session.outLog, unitTestData, "$TEST_DATA");
|
|
auto cleanedOutLog = trimOutLog(replacedOutLog);
|
|
|
|
auto parsedOutLog = cli_literate_parser::parse(
|
|
std::string(cleanedOutLog),
|
|
cli_literate_parser::Config{.prompt = std::string(AUTOMATION_PROMPT), .indent = 0}
|
|
);
|
|
|
|
auto expected = parsed.tidyOutputForComparison();
|
|
auto actual = parsedOutLog.tidyOutputForComparison();
|
|
|
|
ASSERT_EQ(expected, actual);
|
|
}
|
|
|
|
void runReplTestPath(const std::string_view & nameBase, std::vector<std::string> extraArgs)
|
|
{
|
|
auto nixPath = goldenMaster(nameBase + ".nix");
|
|
if (pathExists(nixPath)) {
|
|
extraArgs.push_back("-f");
|
|
extraArgs.push_back(nixPath);
|
|
}
|
|
readTest(nameBase + ".test", [this, extraArgs](std::string input) {
|
|
runReplTest(input, extraArgs);
|
|
});
|
|
}
|
|
|
|
void runReplTestPath(const std::string_view & nameBase)
|
|
{
|
|
runReplTestPath(nameBase, {});
|
|
}
|
|
};
|
|
|
|
TEST_F(ReplSessionTest, round_trip)
|
|
{
|
|
writeTest("basic.test", [this]() {
|
|
const std::string content = readFile(goldenMaster("basic.test"));
|
|
auto parsed = cli_literate_parser::parse(
|
|
content, cli_literate_parser::Config{.prompt = std::string(REPL_PROMPT)}
|
|
);
|
|
|
|
std::ostringstream out{};
|
|
for (auto & node : parsed.syntax) {
|
|
cli_literate_parser::unparseNode(out, node, true);
|
|
}
|
|
return out.str();
|
|
});
|
|
}
|
|
|
|
TEST_F(ReplSessionTest, tidy)
|
|
{
|
|
writeTest("basic.ast", [this]() {
|
|
const std::string content = readFile(goldenMaster("basic.test"));
|
|
auto parsed = cli_literate_parser::parse(
|
|
content, cli_literate_parser::Config{.prompt = std::string(REPL_PROMPT)}
|
|
);
|
|
std::ostringstream out{};
|
|
for (auto & node : parsed.syntax) {
|
|
out << debugNode(node) << "\n";
|
|
}
|
|
return out.str();
|
|
});
|
|
writeTest("basic_tidied.ast", [this]() {
|
|
const std::string content = readFile(goldenMaster("basic.test"));
|
|
auto parsed = cli_literate_parser::parse(
|
|
content, cli_literate_parser::Config{.prompt = std::string(REPL_PROMPT)}
|
|
);
|
|
auto tidied = parsed.tidyOutputForComparison();
|
|
std::ostringstream out{};
|
|
for (auto & node : tidied) {
|
|
out << debugNode(node) << "\n";
|
|
}
|
|
return out.str();
|
|
});
|
|
}
|
|
|
|
#define REPL_TEST(name) \
|
|
TEST_F(ReplSessionTest, name) \
|
|
{ \
|
|
runReplTestPath(#name); \
|
|
}
|
|
|
|
REPL_TEST(basic_repl);
|
|
REPL_TEST(no_nested_debuggers);
|
|
REPL_TEST(regression_9917);
|
|
REPL_TEST(regression_9918);
|
|
REPL_TEST(regression_l145);
|
|
REPL_TEST(repl_overlays);
|
|
REPL_TEST(repl_overlays_compose);
|
|
REPL_TEST(repl_overlays_destructure_without_dotdotdot_errors);
|
|
REPL_TEST(repl_overlays_destructure_without_formals_ok);
|
|
REPL_TEST(repl_overlays_error);
|
|
REPL_TEST(repl_printing);
|
|
REPL_TEST(stack_vars);
|
|
REPL_TEST(errors);
|
|
|
|
}; // namespace nix
|