From cf0ce785b7de1acd9ae2cbfca23347efa7d7ff89 Mon Sep 17 00:00:00 2001 From: rootile Date: Wed, 17 Jun 2026 12:02:20 +0200 Subject: [PATCH] tests: remove forgotten cli-literate-parser When removing the f1 repl characterization test suit, these files were kept on accident, despite them being dead code now Change-Id: I13b2c5a96004787c26fe7209e4cb3aeedd8ada15 --- .../tests/cli-literate-parser.cc | 436 ------------------ .../tests/cli-literate-parser.hh | 202 -------- tests/unit/meson.build | 1 - 3 files changed, 639 deletions(-) delete mode 100644 tests/unit/libutil-support/tests/cli-literate-parser.cc delete mode 100644 tests/unit/libutil-support/tests/cli-literate-parser.hh diff --git a/tests/unit/libutil-support/tests/cli-literate-parser.cc b/tests/unit/libutil-support/tests/cli-literate-parser.cc deleted file mode 100644 index ebd5eb809..000000000 --- a/tests/unit/libutil-support/tests/cli-literate-parser.cc +++ /dev/null @@ -1,436 +0,0 @@ -#include "cli-literate-parser.hh" -#include "lix/libutil/escape-string.hh" -#include "lix/libutil/types.hh" -#include -#include -#include -#include -#include -#include - -#include "cli-literate-parser.hh" -#include "lix/libutil/escape-string.hh" -#include "lix/libutil/fmt.hh" -#include "lix/libutil/shlex.hh" -#include "lix/libutil/types.hh" -#include "lix/libutil/strings.hh" - -static constexpr const bool DEBUG_PARSER = false; - -using namespace std::string_literals; -using namespace boost::algorithm; - -namespace nix { - -namespace cli_literate_parser { - -struct Parser -{ - Parser(const std::string input, Config config) - : input(input) - , rest(this->input) - , prompt(config.prompt) - , indentString(config.indent, ' ') - , lastWasOutput(false) - , syntax{} - { - assert(!prompt.empty()); - } - - const std::string input; - std::string_view rest; - const std::string prompt; - const std::string indentString; - - /** Last line was output, so we consider a blank to be part of the output */ - bool lastWasOutput; - - /** - * Nodes of syntax being built. - */ - std::vector syntax; - - auto dbg(std::string_view state) -> void - { - std::cout << state << ": "; - escapeString( - std::cout, - rest, - { - .maxLength = 40, - .outputAnsiColors = true, - .escapeNonPrinting = true, - } - ); - std::cout << std::endl; - } - - template - auto pushNode(T node) -> void - { - if constexpr (DEBUG_PARSER) { - std::cout << debugNode(node); - } - syntax.emplace_back(node); - } - - auto parseLiteral(const char c) -> bool - { - if (rest.starts_with(c)) { - rest.remove_prefix(1); - return true; - } else { - return false; - } - } - - auto parseLiteral(const std::string_view & literal) -> bool - { - if (rest.starts_with(literal)) { - rest.remove_prefix(literal.length()); - return true; - } else { - return false; - } - } - - auto parseBool() -> bool - { - auto result = false; - if (parseLiteral("true")) { - result = true; - } else if (parseLiteral("false")) { - result = false; - } else { - throw ParseError("true or false", std::string(rest)); - } - auto untilNewline = parseUntilNewline(); - if (!untilNewline.empty()) { - throw ParseError("nothing after true or false", untilNewline); - } - return result; - } - - auto parseUntilNewline() -> std::string - { - auto pos = rest.find('\n'); - if (pos == std::string_view::npos) { - throw ParseError("text and then newline", std::string(rest)); - } else { - // `parseOutput()` sets this to true anyways. - lastWasOutput = false; - auto result = std::string(rest, 0, pos); - rest.remove_prefix(pos + 1); - return result; - } - } - - auto parseIndent() -> bool - { - if constexpr (DEBUG_PARSER) { - dbg("indent"); - } - if (indentString.empty()) { - return true; - } - - if (parseLiteral(indentString)) { - pushNode(Indent(indentString)); - return true; - } else { - if constexpr (DEBUG_PARSER) { - dbg("indent failed"); - } - return false; - } - } - - auto parseCommand() -> void - { - if constexpr (DEBUG_PARSER) { - dbg("command"); - } - auto untilNewline = parseUntilNewline(); - pushNode(Command(untilNewline)); - } - - auto parsePrompt() -> void - { - if constexpr (DEBUG_PARSER) { - dbg("prompt"); - } - if (parseLiteral(prompt)) { - pushNode(Prompt(prompt)); - if (rest.empty()) { - return; - } - parseCommand(); - } else { - parseOutput(); - } - } - - auto parseOutput() -> void - { - if constexpr (DEBUG_PARSER) { - dbg("output"); - } - auto untilNewline = parseUntilNewline(); - pushNode(Output(untilNewline)); - lastWasOutput = true; - } - - auto parseAtSign() -> void - { - if constexpr (DEBUG_PARSER) { - dbg("@ symbol"); - } - if (!parseLiteral('@')) { - parseOutputOrCommentary(); - } - - if (parseLiteral("args ")) { - parseArgs(); - } else if (parseLiteral("should-start ")) { - if constexpr (DEBUG_PARSER) { - dbg("@should-start"); - } - auto shouldStart = parseBool(); - pushNode(ShouldStart{shouldStart}); - } - } - - auto parseArgs() -> void - { - if constexpr (DEBUG_PARSER) { - dbg("@args"); - } - auto untilNewline = parseUntilNewline(); - pushNode(Args(untilNewline)); - } - - auto parseOutputOrCommentary() -> void - { - if constexpr (DEBUG_PARSER) { - dbg("output/commentary"); - } - auto oldLastWasOutput = lastWasOutput; - auto untilNewline = parseUntilNewline(); - - auto trimmed = trim_right_copy(untilNewline); - - if (oldLastWasOutput && trimmed.empty()) { - pushNode(Output{trimmed}); - } else { - pushNode(Commentary{untilNewline}); - } - } - - auto parseStartOfLine() -> void - { - if constexpr (DEBUG_PARSER) { - dbg("start of line"); - } - if (parseIndent()) { - parsePrompt(); - } else { - parseAtSign(); - } - } - - auto parse() && -> ParseResult - { - // Begin the recursive descent parser at the start of a new line. - while (!rest.empty()) { - parseStartOfLine(); - } - return std::move(*this).intoParseResult(); - } - - auto intoParseResult() && -> ParseResult - { - // Do another pass over the nodes to produce auxiliary results like parsed - // command line arguments. - std::vector args; - std::vector newSyntax; - auto shouldStart = true; - - for (auto & node : syntax) { - std::visit( - overloaded{ - [&](Args & e) { - auto split = shell_split(e.text); - args.insert(args.end(), split.begin(), split.end()); - }, - [&](ShouldStart & e) { shouldStart = e.shouldStart; }, - [&](auto & e) {}, - }, - node - ); - - newSyntax.push_back(node); - } - - return ParseResult{ - .syntax = std::move(newSyntax), - .args = std::move(args), - .shouldStart = shouldStart, - }; - } -}; - -template -auto tidySyntax(View syntax) -> std::vector -{ - // Note: Setting `lastWasCommand` lets us trim blank lines at the start and - // end of the output stream. - auto lastWasCommand = true; - std::vector newSyntax; - - for (auto & node : syntax) { - // Only compare `Command` and `Output` nodes. - if (std::visit([&](auto && e) { return !e.shouldCompare(); }, node)) { - continue; - } - - // Remove blank lines before and after commands. This lets us keep nice - // whitespace in the test files. - auto shouldKeep = std::visit( - overloaded{ - [&](Command & e) { - lastWasCommand = true; - auto trimmed = trim_right_copy(e.text); - if (trimmed.empty()) { - return false; - } else { - e.text = trimmed; - return true; - } - }, - [&](Output & e) { - std::string trimmed = trim_right_copy(e.text); - if (lastWasCommand && trimmed.empty()) { - // NB: Keep `lastWasCommand` true in this branch so we - // can keep pruning empty output lines. - return false; - } else { - e.text = trimmed; - lastWasCommand = false; - return true; - } - }, - [&](auto & e) { - lastWasCommand = false; - return false; - }, - }, - node - ); - - if (shouldKeep) { - newSyntax.push_back(node); - } - } - - return newSyntax; -} - -auto ParseResult::tidyOutputForComparison() -> std::vector -{ - auto reversed = tidySyntax(std::ranges::reverse_view(syntax)); - auto unreversed = tidySyntax(std::ranges::reverse_view(reversed)); - return unreversed; -} - -void ParseResult::interpolatePwd(std::string_view pwd) -{ - std::vector newArgs; - for (auto & arg : args) { - newArgs.push_back(replaceStrings(arg, "${PWD}", pwd)); - } - args = std::move(newArgs); -} - -const char * ParseError::what() const noexcept -{ - if (what_) { - return what_->c_str(); - } else { - auto escaped = escapeString(rest, {.maxLength = 256, .escapeNonPrinting = true}); - auto hint = HintFmt("Parse error: Expected %1%, got:\n%2%", expected, Uncolored(escaped)); - what_ = hint.str(); - return what_->c_str(); - } -} - -auto parse(const std::string input, Config config) -> ParseResult -{ - return Parser(input, config).parse(); -} - -std::ostream & operator<<(std::ostream & output, const Args & node) -{ - return output << "@args " << node.text; -} - -std::ostream & operator<<(std::ostream & output, const ShouldStart & node) -{ - return output << "@should-start " << (node.shouldStart ? "true" : "false"); -} - -std::ostream & operator<<(std::ostream & output, const TextNode & rhs) -{ - return output << rhs.text; -} - -void unparseNode(std::ostream & output, const Node & node, bool withNewline) -{ - std::visit( - [&](const auto & n) { output << n << (withNewline && n.emitNewlineAfter() ? "\n" : ""); }, - node - ); -} - -template -std::string gtestFormat(T & value) -{ - std::ostringstream formatted; - unparseNode(formatted, value, true); - auto str = formatted.str(); - // Needs to be the literal string `\n` and not a newline character to - // trigger gtest diff printing. Yes seriously. - boost::algorithm::replace_all(str, "\n", "\\n"); - return str; -} - -void PrintTo(const std::vector & nodes, std::ostream * output) -{ - for (auto & node : nodes) { - *output << gtestFormat(node); - } -} - -std::string debugNode(const Node & node) -{ - std::ostringstream output; - output << std::visit([](const auto & n) { return n.kind(); }, node) << ": "; - std::ostringstream contents; - unparseNode(contents, node, false); - escapeString(output, contents.str(), {.escapeNonPrinting = true}); - return output.str(); -} - -auto ParseResult::debugPrint(std::ostream & output) -> void -{ - ::nix::cli_literate_parser::debugPrint(output, syntax); -} - -void debugPrint(std::ostream & output, std::vector & nodes) -{ - for (auto & node : nodes) { - output << debugNode(node) << std::endl; - } -} - -} // namespace cli_literate_parser -} // namespace nix diff --git a/tests/unit/libutil-support/tests/cli-literate-parser.hh b/tests/unit/libutil-support/tests/cli-literate-parser.hh deleted file mode 100644 index 55ea5eb81..000000000 --- a/tests/unit/libutil-support/tests/cli-literate-parser.hh +++ /dev/null @@ -1,202 +0,0 @@ -#pragma once -///@file - -#include "lix/libutil/error.hh" -#include -#include -#include -#include -#include -#include -#include - -namespace nix { -namespace cli_literate_parser { - -// ------------------------- NODES ------------------------- -// -// To update golden test files while preserving commentary output and other `@` -// directives, we need to keep commentary output around after parsing. - -struct BaseNode { - virtual ~BaseNode() = default; - - virtual auto shouldCompare() const -> bool { return false; } - - virtual auto kind() const -> std::string = 0; - virtual auto emitNewlineAfter() const -> bool = 0; - - auto operator<=>(const BaseNode & rhs) const = default; -}; - -/** - * A node containing text. The text should be identical to how the node was - * written in the input file. - */ -struct TextNode : BaseNode { - std::string text; - - explicit TextNode(std::string text) : text(text) {} - auto operator<=>(const TextNode & rhs) const = default; -}; - -std::ostream &operator<<(std::ostream &output, const TextNode &node); - -// clang-format off -#define DECLARE_TEXT_NODE(NAME, NEEDS_NEWLINE, SHOULD_COMPARE) \ - struct NAME : TextNode { \ - using TextNode::TextNode; \ - ~NAME() override = default; \ - \ - auto kind() const -> std::string override { return #NAME; } \ - auto emitNewlineAfter() const -> bool override { return NEEDS_NEWLINE; } \ - auto shouldCompare() const -> bool override { return SHOULD_COMPARE; } \ - auto operator<=>(const NAME & rhs) const = default; \ - }; -// clang-format on - -/* name, needsNewline, shouldCompare */ -DECLARE_TEXT_NODE(Prompt, false, false) -DECLARE_TEXT_NODE(Command, true, true) -DECLARE_TEXT_NODE(Output, true, true) -DECLARE_TEXT_NODE(Commentary, true, false) -DECLARE_TEXT_NODE(Args, true, false) -DECLARE_TEXT_NODE(Indent, false, false) - -#undef DECLARE_TEXT_NODE - -struct ShouldStart : BaseNode { - bool shouldStart; - - ShouldStart(bool shouldStart) : shouldStart(shouldStart) {} - ~ShouldStart() override = default; - auto emitNewlineAfter() const -> bool override { return true; } - auto kind() const -> std::string override { return "should-start"; } - - auto operator<=>(const ShouldStart &rhs) const = default; -}; -std::ostream &operator<<(std::ostream &output, const ShouldStart &node); - -/** - * Any syntax node, including those that are cosmetic. - */ -using Node = std::variant; - -/** Unparses a node into the exact text that would have created it, including a - * newline at the end if present, if withNewline is set */ -void unparseNode(std::ostream &output, const Node &node, - bool withNewline = true); - -std::string debugNode(const Node &node); -void debugPrint(std::ostream &output, std::vector &nodes); - -/** - * Override gtest printing for lists of nodes. - */ -void PrintTo(std::vector const &nodes, std::ostream *output); - -/** - * The result of parsing a test file. - */ -struct ParseResult { - /** - * A set of nodes that can be used to reproduce the input file. This is used - * to implement updating the test files. - */ - std::vector syntax; - - /** - * Extra CLI arguments. - */ - std::vector args; - - /** - * Should the program start successfully? - */ - bool shouldStart = false; - - /** - * Replace `$PWD` with the given value in `args`. - */ - void interpolatePwd(std::string_view pwd); - - /** - * Tidy `syntax` to remove unnecessary nodes. - */ - auto tidyOutputForComparison() -> std::vector; - - auto debugPrint(std::ostream &output) -> void; -}; - -/** - * A parse error. - */ -struct ParseError : BaseException { - std::string expected; - std::string rest; - - ParseError(std::string expected, std::string rest) - : expected(expected), rest(rest) {} - - const char *what() const noexcept override; - -private: - /** - * Cached formatted contents of `what()`. - */ - mutable std::optional what_; -}; - -struct Config { - /** - * The prompt string to look for. - */ - std::string prompt; - /** - * The number of spaces of indent for commands and output. - */ - size_t indent = 2; -}; - -/* - * A recursive descent parser for literate test cases for CLIs. - * - * FIXME: implement merging of these, so you can auto update cases that have - * comments. - * - * Syntax: - * ``` - * ( COMMENTARY - * | INDENT PROMPT COMMAND - * | INDENT OUTPUT - * | @args ARGS - * | @should-start ( true | false )) * - * ``` - * - * e.g. - * ``` - * commentary commentary commentary - * @args --foo - * @should-start false - * nix-repl> :t 1 - * an integer - * ``` - * - * Yields something like: - * ``` - * Commentary "commentary commentary commentary" - * Args "--foo" - * ShouldStart false - * Command ":t 1" - * Output "an integer" - * ``` - * - * Note: one Output line is generated for each line of the sources, because - * this is effectively necessary to be able to align them in the future to - * auto-update tests. - */ -auto parse(std::string input, Config config) -> ParseResult; - -}; // namespace cli_literate_parser -}; // namespace nix diff --git a/tests/unit/meson.build b/tests/unit/meson.build index 1cb2ce143..38b642728 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -20,7 +20,6 @@ default_test_env = { } libutil_test_support_sources = files( - 'libutil-support/tests/cli-literate-parser.cc', 'libutil-support/tests/hash.cc', 'libutil-support/tests/terminal-code-eater.cc', 'libutil-support/tests/test-data.cc',