lix/tests/functional/repl_characterization/repl_characterization.cc

190 lines
5.8 KiB
C++
Raw Normal View History

#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, {});
}
void runDebuggerTest(const std::string_view & nameBase)
{
runReplTestPath(nameBase, {"--debugger"});
}
};
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(stack_vars);
}; // namespace nix