From abfe9d0c3efc2204b0f88ff87a642c174a09ff09 Mon Sep 17 00:00:00 2001 From: rootile Date: Tue, 9 Jun 2026 19:50:40 +0200 Subject: [PATCH] tests(functional): remove old repl characterization test suite Change-Id: I2c13e0332298600cafa97b06113ca56f66668324 --- tests/functional/meson.build | 2 - .../repl_characterization/.gitignore | 1 - .../repl_characterization/data/basic.ast | 27 --- .../repl_characterization/data/basic.test | 16 -- .../data/basic_tidied.ast | 9 - .../data/basic_tidied.test | 10 - .../repl_characterization/meson.build | 41 ---- .../repl_characterization.cc | 179 ------------------ .../repl_characterization/test-session.cc | 177 ----------------- .../repl_characterization/test-session.hh | 88 --------- .../functional/test-repl-characterization.sh | 7 - 11 files changed, 557 deletions(-) delete mode 100644 tests/functional/repl_characterization/.gitignore delete mode 100644 tests/functional/repl_characterization/data/basic.ast delete mode 100644 tests/functional/repl_characterization/data/basic.test delete mode 100644 tests/functional/repl_characterization/data/basic_tidied.ast delete mode 100644 tests/functional/repl_characterization/data/basic_tidied.test delete mode 100644 tests/functional/repl_characterization/meson.build delete mode 100644 tests/functional/repl_characterization/repl_characterization.cc delete mode 100644 tests/functional/repl_characterization/test-session.cc delete mode 100644 tests/functional/repl_characterization/test-session.hh delete mode 100644 tests/functional/test-repl-characterization.sh diff --git a/tests/functional/meson.build b/tests/functional/meson.build index e06601a97..f938a1fba 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -159,5 +159,3 @@ foreach script : functional_tests_scripts depends : extra_deps, ) endforeach - -subdir('repl_characterization') diff --git a/tests/functional/repl_characterization/.gitignore b/tests/functional/repl_characterization/.gitignore deleted file mode 100644 index 4c6412c2f..000000000 --- a/tests/functional/repl_characterization/.gitignore +++ /dev/null @@ -1 +0,0 @@ -test-repl-characterization diff --git a/tests/functional/repl_characterization/data/basic.ast b/tests/functional/repl_characterization/data/basic.ast deleted file mode 100644 index e079af588..000000000 --- a/tests/functional/repl_characterization/data/basic.ast +++ /dev/null @@ -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" diff --git a/tests/functional/repl_characterization/data/basic.test b/tests/functional/repl_characterization/data/basic.test deleted file mode 100644 index ae5812a2d..000000000 --- a/tests/functional/repl_characterization/data/basic.test +++ /dev/null @@ -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 diff --git a/tests/functional/repl_characterization/data/basic_tidied.ast b/tests/functional/repl_characterization/data/basic_tidied.ast deleted file mode 100644 index d41d21a96..000000000 --- a/tests/functional/repl_characterization/data/basic_tidied.ast +++ /dev/null @@ -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" diff --git a/tests/functional/repl_characterization/data/basic_tidied.test b/tests/functional/repl_characterization/data/basic_tidied.test deleted file mode 100644 index 2c56c489b..000000000 --- a/tests/functional/repl_characterization/data/basic_tidied.test +++ /dev/null @@ -1,10 +0,0 @@ -command -output output one - -output output two -command two -output output output -output output output - -command three -meow output diff --git a/tests/functional/repl_characterization/meson.build b/tests/functional/repl_characterization/meson.build deleted file mode 100644 index dfafe59b8..000000000 --- a/tests/functional/repl_characterization/meson.build +++ /dev/null @@ -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', -) diff --git a/tests/functional/repl_characterization/repl_characterization.cc b/tests/functional/repl_characterization/repl_characterization.cc deleted file mode 100644 index 49a195fcb..000000000 --- a/tests/functional/repl_characterization/repl_characterization.cc +++ /dev/null @@ -1,179 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -#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 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 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 diff --git a/tests/functional/repl_characterization/test-session.cc b/tests/functional/repl_characterization/test-session.cc deleted file mode 100644 index 8e8e53ecc..000000000 --- a/tests/functional/repl_characterization/test-session.cc +++ /dev/null @@ -1,177 +0,0 @@ -#include -#include -#include - -#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 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 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 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); -} - -}; diff --git a/tests/functional/repl_characterization/test-session.hh b/tests/functional/repl_characterization/test-session.hh deleted file mode 100644 index 38c31f702..000000000 --- a/tests/functional/repl_characterization/test-session.hh +++ /dev/null @@ -1,88 +0,0 @@ -#pragma once -///@file - -#include -#include -#include -#include - -#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)>; - /** 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); -}; -}; diff --git a/tests/functional/test-repl-characterization.sh b/tests/functional/test-repl-characterization.sh deleted file mode 100644 index 78dcab5fc..000000000 --- a/tests/functional/test-repl-characterization.sh +++ /dev/null @@ -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