tests(functional): remove old repl characterization test suite
Change-Id: I2c13e0332298600cafa97b06113ca56f66668324
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user