diff options
Diffstat (limited to 'tests/functional/repl_characterization/test-session.cc')
-rw-r--r-- | tests/functional/repl_characterization/test-session.cc | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/tests/functional/repl_characterization/test-session.cc b/tests/functional/repl_characterization/test-session.cc new file mode 100644 index 000000000..c35030fc7 --- /dev/null +++ b/tests/functional/repl_characterization/test-session.cc @@ -0,0 +1,151 @@ +#include <iostream> +#include <unistd.h> + +#include "test-session.hh" +#include "util.hh" +#include "tests/debug-char.hh" + +namespace nix { + +static constexpr const bool DEBUG_REPL_PARSER = false; + +RunningProcess RunningProcess::start(std::string executable, Strings args) +{ + args.push_front(executable); + + Pipe procStdin{}; + Pipe procStdout{}; + + procStdin.create(); + procStdout.create(); + + // This is separate from runProgram2 because we have different IO requirements + pid_t pid = startProcess([&]() { + if (dup2(procStdout.writeSide.get(), STDOUT_FILENO) == -1) + throw SysError("dupping stdout"); + if (dup2(procStdin.readSide.get(), STDIN_FILENO) == -1) + throw SysError("dupping stdin"); + procStdin.writeSide.close(); + procStdout.readSide.close(); + if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) + throw SysError("dupping stderr"); + execv(executable.c_str(), stringsToCharPtrs(args).data()); + throw SysError("exec did not happen"); + }); + + procStdout.writeSide.close(); + procStdin.readSide.close(); + + return RunningProcess{ + .pid = 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 " << DebugChar{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; +} + +/** Waits for the prompt and then returns if a prompt was found */ +bool TestSession::waitForPrompt() +{ + 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; + } + + bool foundPrompt = false; + for (ssize_t i = 0; i < res; ++i) { + // foundPrompt = foundPrompt || outputParser.feed(buf[i]); + bool wasEaten = true; + eater.feed(buf[i], [&](char c) { + wasEaten = false; + foundPrompt = outputParser.feed(buf[i]) || foundPrompt; + + outLog.push_back(c); + }); + + if constexpr (DEBUG_REPL_PARSER) { + std::cerr << "raw " << DebugChar{buf[i]} << (wasEaten ? " [eaten]" : "") << "\n"; + } + } + + if (foundPrompt) { + return true; + } + } +} + +void TestSession::close() +{ + proc.procStdin.close(); + 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 Nix 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); +} + +}; |