tests(functional): remove old repl characterization test suite

Change-Id: I2c13e0332298600cafa97b06113ca56f66668324
This commit is contained in:
rootile
2026-06-11 16:30:56 +02:00
parent 64b434b87b
commit abfe9d0c3e
11 changed files with 0 additions and 557 deletions
-2
View File
@@ -159,5 +159,3 @@ foreach script : functional_tests_scripts
depends : extra_deps,
)
endforeach
subdir('repl_characterization')
@@ -1 +0,0 @@
test-repl-characterization
@@ -1,27 +0,0 @@
Commentary: "meow meow meow"
Indent: " "
Prompt: "nix-repl> "
Command: "command"
Indent: " "
Output: "output output one"
Output: ""
Commentary: ""
Indent: " "
Output: "output output two"
Commentary: "meow meow"
Indent: " "
Prompt: "nix-repl> "
Command: "command two"
Indent: " "
Output: "output output output"
Commentary: "commentary"
Indent: " "
Output: "output output output"
Output: ""
Commentary: "the blank below should be chomped"
Indent: " "
Prompt: "nix-repl> "
Command: "command three"
Commentary: ""
Indent: " "
Output: "meow output"
@@ -1,16 +0,0 @@
meow meow meow
nix-repl> command
output output one
output output two
meow meow
nix-repl> command two
output output output
commentary
output output output
the blank below should be chomped
nix-repl> command three
meow output
@@ -1,9 +0,0 @@
Command: "command"
Output: "output output one"
Output: ""
Output: "output output two"
Command: "command two"
Output: "output output output"
Output: "output output output"
Command: "command three"
Output: "meow output"
@@ -1,10 +0,0 @@
command
output output one
output output two
command two
output output output
output output output
command three
meow output
@@ -1,41 +0,0 @@
repl_characterization_tester_sources = files(
'repl_characterization.cc',
'test-session.cc',
)
repl_characterization_tester = executable(
'test-repl-characterization',
repl_characterization_tester_sources,
dependencies : [
libasanoptions,
liblix,
liblixutil_test_support,
editline,
boost,
lowdown,
gtest,
],
cpp_args : [
f'-DNIX_BIN_DIR="@bindir@"',
],
build_by_default : false,
)
# TODO(Qyriad): better understand the repl characterization tests' preconditions
# so we can run this with the gtest protocol, without run-test.py.
test(
# test-repl-characterization.sh expects the tester executable to have this name,
# so this name it shall have.
'repl-characterization-tests',
python,
args : [
meson.project_source_root() / 'meson/run-test.py',
'test-repl-characterization.sh',
],
depends : [repl_characterization_tester],
env : {
'_NIX_TEST_UNIT_DATA': meson.current_build_dir() / 'data',
'MESON_BUILD_ROOT': meson.project_build_root(),
},
suite : 'installcheck',
)
@@ -1,179 +0,0 @@
#include <gtest/gtest.h>
#include <boost/algorithm/string/replace.hpp>
#include <optional>
#include <string>
#include <string_view>
#include <unistd.h>
#include "test-session.hh"
#include "tests/characterization.hh"
#include "tests/cli-literate-parser.hh"
#include "lix/libutil/strings.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();
// Remove references to the checkout path
auto replacedOutLog =
boost::algorithm::replace_all_copy(session.outLog, unitTestData, "$TEST_DATA");
// Remove references to the current version
replacedOutLog =
boost::algorithm::replace_all_copy(replacedOutLog, PACKAGE_VERSION, "$VERSION");
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); \
}
}; // namespace nix
@@ -1,177 +0,0 @@
#include <iostream>
#include <span>
#include <unistd.h>
#include "test-session.hh"
#include "lix/libutil/escape-char.hh"
#include "lix/libutil/processes.hh"
namespace nix {
static constexpr const bool DEBUG_REPL_PARSER = false;
RunningProcess RunningProcess::start(std::string executable, Strings args)
{
Pipe procStdin{};
Pipe procStdout{};
procStdin.create();
procStdout.create();
auto proc = runProgram2({
.program = executable,
.searchPath = false,
.args = args,
.redirections = {
{.dup = STDIN_FILENO, .from = procStdin.readSide.get()},
{.dup = STDOUT_FILENO, .from = procStdout.writeSide.get()},
{.dup = STDERR_FILENO, .from = STDOUT_FILENO},
},
});
auto [pid, _stdout] = proc.release();
procStdout.writeSide.close();
procStdin.readSide.close();
return RunningProcess{
.pid = std::move(pid),
.procStdin = std::move(procStdin),
.procStdout = std::move(procStdout),
};
}
[[gnu::unused]]
std::ostream &
operator<<(std::ostream & os, ReplOutputParser::State s)
{
switch (s) {
case ReplOutputParser::State::Prompt:
os << "prompt";
break;
case ReplOutputParser::State::Context:
os << "context";
break;
}
return os;
}
void ReplOutputParser::transition(State new_state, char responsible_char, bool wasPrompt)
{
if constexpr (DEBUG_REPL_PARSER) {
std::cerr << "transition " << new_state << " for " << MaybeHexEscapedChar{responsible_char}
<< (wasPrompt ? " [prompt]" : "") << "\n";
}
state = new_state;
pos_in_prompt = 0;
}
bool ReplOutputParser::feed(char c)
{
if (c == '\n') {
transition(State::Prompt, c);
return false;
}
switch (state) {
case State::Context:
break;
case State::Prompt:
if (pos_in_prompt == prompt.length() - 1 && prompt[pos_in_prompt] == c) {
transition(State::Context, c, true);
return true;
}
if (pos_in_prompt >= prompt.length() - 1 || prompt[pos_in_prompt] != c) {
transition(State::Context, c);
break;
}
pos_in_prompt++;
break;
}
return false;
}
bool TestSession::readOutThen(ReadOutThenCallback cb)
{
std::vector<char> buf(1024);
for (;;) {
ssize_t res = read(proc.procStdout.readSide.get(), buf.data(), buf.size());
if (res < 0) {
throw SysError("read");
}
if (res == 0) {
return false;
}
switch (cb(std::span(buf.data(), res))) {
case ReadOutThenCallbackResult::Stop:
return true;
case ReadOutThenCallbackResult::Continue:
continue;
}
}
}
bool TestSession::waitForPrompt()
{
bool notEof = readOutThen([&](std::span<const char> s) -> ReadOutThenCallbackResult {
bool foundPrompt = false;
for (auto ch : s) {
// foundPrompt = foundPrompt || outputParser.feed(buf[i]);
bool wasEaten = true;
eater.feed(ch, [&](char c) {
wasEaten = false;
foundPrompt = outputParser.feed(ch) || foundPrompt;
outLog.push_back(c);
});
if constexpr (DEBUG_REPL_PARSER) {
std::cerr << "raw " << MaybeHexEscapedChar{ch} << (wasEaten ? " [eaten]" : "") << "\n";
}
}
return foundPrompt ? ReadOutThenCallbackResult::Stop : ReadOutThenCallbackResult::Continue;
});
return notEof;
}
void TestSession::wait()
{
readOutThen([&](std::span<const char> s) {
for (auto ch : s) {
eater.feed(ch, [&](char c) {
outputParser.feed(c);
outLog.push_back(c);
});
}
// just keep reading till we hit eof
return ReadOutThenCallbackResult::Continue;
});
}
void TestSession::close()
{
proc.procStdin.close();
wait();
proc.procStdout.close();
}
void TestSession::runCommand(std::string command)
{
if constexpr (DEBUG_REPL_PARSER) {
std::cerr << "runCommand " << command << "\n";
}
command += "\n";
// We have to feed a newline into the output parser, since Lix might not
// give us a newline before a prompt in all cases (it might clear line
// first, e.g.)
outputParser.feed('\n');
// Echo is disabled, so we have to make our own
outLog.append(command);
writeFull(proc.procStdin.writeSide.get(), command, false);
}
};
@@ -1,88 +0,0 @@
#pragma once
///@file
#include <functional>
#include <sched.h>
#include <span>
#include <string>
#include "lix/libutil/file-descriptor.hh"
#include "lix/libutil/processes.hh"
#include "tests/terminal-code-eater.hh"
namespace nix {
struct RunningProcess
{
Pid pid;
Pipe procStdin;
Pipe procStdout;
static RunningProcess start(std::string executable, Strings args);
};
/** DFA that catches repl prompts */
class ReplOutputParser
{
public:
ReplOutputParser(std::string prompt) : prompt(prompt)
{
assert(!prompt.empty());
}
/** Feeds in a character and returns whether this is an open prompt */
bool feed(char c);
enum class State {
Prompt,
Context,
};
private:
State state = State::Prompt;
size_t pos_in_prompt = 0;
std::string const prompt;
void transition(State state, char responsible_char, bool wasPrompt = false);
};
struct TestSession
{
RunningProcess proc;
ReplOutputParser outputParser;
TerminalCodeEater eater;
std::string outLog;
std::string prompt;
TestSession(std::string prompt, RunningProcess && proc)
: proc(std::move(proc))
, outputParser(prompt)
, eater{}
, outLog{}
, prompt(prompt)
{
}
/** Waits for the prompt and then returns if a prompt was found */
bool waitForPrompt();
/** Feeds a line of input into the command */
void runCommand(std::string command);
/** Closes the session, closing standard input and waiting for standard
* output to close, capturing any remaining output. */
void close();
private:
/** Waits until the command closes its output */
void wait();
enum class ReadOutThenCallbackResult { Stop, Continue };
using ReadOutThenCallback = std::function<ReadOutThenCallbackResult(std::span<const char>)>;
/** Reads some chunks of output, calling the callback provided for each
* chunk and stopping if it returns Stop.
*
* @returns false if EOF, true if the callback requested we stop first.
* */
bool readOutThen(ReadOutThenCallback cb);
};
};
@@ -1,7 +0,0 @@
source common.sh
# You might think that this can trivially be moved into a makefile, however,
# there is various environmental initialization of this shell framework that
# seems load bearing and seemingly prevents the tests working inside the
# builder.
_NIX_TEST_UNIT_DATA=$(pwd)/repl_characterization/data ./repl_characterization/test-repl-characterization