Files
tai/taicpp/tests/tui_spec.cpp
T
retoorandClaude Sonnet 5.5 bbdc927215 tui: grr terminal client (Grr* classes), control socket, entry points, spec test, parity docs
Port of the grr client of tai.py: client, SSE stream, session, store, router, shell, views,
chat, panes, file viewer and the unix control socket; GrrTui::run is the --tui entry.
Verified against tai.py --tui on a real grr server over a pty: 236 steps, screen text and
per-cell styles identical except clocks, spinner frames and server-made ids.
docs/tui.md holds the symbol table and gaps, docs/wiring/tui.md the integrator hooks.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 06:15:05 +01:00

104 lines
5.6 KiB
C++

// tui_spec: drift checks of grrtui.md against the C++ client (mirrors test_tai.py
// GrrSpecTests). grrtui.md is the specification; it must list exactly what the code
// has. Run from taicpp/; the doc is ../grrtui.md (override with GRRTUI_MD).
#include "GrrConst.h"
#include "GrrControl.h"
#include "GrrShell.h"
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <regex>
#include <sstream>
using namespace detz::tui;
static int failures = 0;
static void check(bool ok, const std::string& what) {
if (!ok) {
failures++;
std::cout << "FAIL " << what << "\n";
}
}
static std::string read_doc() {
const char* env = std::getenv("GRRTUI_MD");
std::ifstream in(env ? env : "../grrtui.md");
std::stringstream ss;
ss << in.rdbuf();
return ss.str();
}
// text of "## Heading" up to the next heading of level 1 to 3
static std::string section(const std::string& doc, const std::string& heading) {
size_t start = doc.find("\n" + heading + "\n");
if (start == std::string::npos) return std::string();
std::string rest = doc.substr(start + heading.size() + 2);
static const std::regex next("\n#{1,3} ");
std::smatch m;
if (std::regex_search(rest, m, next)) return rest.substr(0, static_cast<size_t>(m.position(0)));
return rest;
}
int main() {
std::string doc = read_doc();
check(!doc.empty(), "grrtui.md is readable");
if (doc.empty()) return 1;
// route table: | `pattern` | `ViewName` |
{
std::string text = section(doc, "## Screens");
std::vector<std::pair<std::string, std::string>> rows;
static const std::regex row("(^|\n)\\| `([^`]+)` \\| `([^`]+)` \\|");
for (std::sregex_iterator it(text.begin(), text.end(), row), end; it != end; ++it) rows.emplace_back((*it)[2], (*it)[3]);
check(rows == GrrConst::routes(), "the route table matches the code");
check(!rows.empty(), "the route table is not empty");
// every route's view can be built
}
// key table: | `key` | action |
{
std::string text = section(doc, "## Keys");
std::vector<std::pair<std::string, std::string>> rows;
static const std::regex row("(^|\n)\\| `([^`]+)` \\| (.+?) \\|(?=\n|$)");
for (std::sregex_iterator it(text.begin(), text.end(), row), end; it != end; ++it) rows.emplace_back((*it)[2], (*it)[3]);
check(rows == GrrConst::keys(), "the key table matches the code");
}
// caps: every GRR_* number is documented with its value, and nothing else is
{
std::vector<std::pair<std::string, double>> caps = {
{"GRR_HTTP_TIMEOUT", GRR_HTTP_TIMEOUT}, {"GRR_SSE_TIMEOUT", GRR_SSE_TIMEOUT}, {"GRR_SSE_RETRY", GRR_SSE_RETRY},
{"GRR_SSE_LINE_MAX", static_cast<double>(GRR_SSE_LINE_MAX)}, {"GRR_BODY_MAX", static_cast<double>(GRR_BODY_MAX)},
{"GRR_SIDE_W", GRR_SIDE_W}, {"GRR_SIDE_MIN_COLS", GRR_SIDE_MIN_COLS}, {"GRR_PANE_MIN_COLS", GRR_PANE_MIN_COLS},
{"GRR_PANE_MIN_W", GRR_PANE_MIN_W}, {"GRR_PANE_MAX_W", GRR_PANE_MAX_W}, {"GRR_MESSAGE_KEEP", GRR_MESSAGE_KEEP},
{"GRR_TOAST_SECS", GRR_TOAST_SECS}, {"GRR_BANNER_SECS", GRR_BANNER_SECS}, {"GRR_TOAST_MAX", GRR_TOAST_MAX},
{"GRR_COMPOSER_MAX_ROWS", GRR_COMPOSER_MAX_ROWS}, {"GRR_VIEWER_MAX_BYTES", static_cast<double>(GRR_VIEWER_MAX_BYTES)},
{"GRR_DRAFT_KEEP", GRR_DRAFT_KEEP}, {"GRR_PROMPT_QUIET", GRR_PROMPT_QUIET}, {"GRR_PROMPT_ASKS", GRR_PROMPT_ASKS},
{"GRR_PROMPT_MAX", GRR_PROMPT_MAX}, {"GRR_LIST_MORE", GRR_LIST_MORE}, {"GRR_MENU_MAX_H", GRR_MENU_MAX_H},
{"GRR_CONTROL_JOBS", GRR_CONTROL_JOBS}, {"GRR_CONTROL_LINE", GRR_CONTROL_LINE}, {"GRR_CONTROL_WAIT", GRR_CONTROL_WAIT},
{"GRR_CONTROL_WAIT_MAX", GRR_CONTROL_WAIT_MAX}, {"GRR_CONTROL_KEYS_MAX", GRR_CONTROL_KEYS_MAX},
};
std::string text = section(doc, "## Caps");
std::map<std::string, double> documented;
static const std::regex row("(^|\n)\\| `(GRR_[A-Z_]+)` \\| ([0-9.]+) \\|");
for (std::sregex_iterator it(text.begin(), text.end(), row), end; it != end; ++it) documented[(*it)[2]] = std::stod((*it)[3]);
check(documented.size() == caps.size(), "the caps table lists every cap (" + std::to_string(documented.size()) + " documented, " + std::to_string(caps.size()) + " in code)");
for (auto& c : caps) {
auto d = documented.find(c.first);
check(d != documented.end(), c.first + " is documented");
if (d != documented.end()) check(std::fabs(d->second - c.second) < 1e-9, c.first + " has the documented value");
}
// the control class carries the same numbers
check(GrrControl::JOBS == GRR_CONTROL_JOBS && GrrControl::LINE == static_cast<size_t>(GRR_CONTROL_LINE) && GrrControl::WAIT == GRR_CONTROL_WAIT &&
GrrControl::WAIT_MAX == GRR_CONTROL_WAIT_MAX && GrrControl::KEYS_MAX == static_cast<size_t>(GRR_CONTROL_KEYS_MAX),
"GrrControl uses the documented caps");
}
// shapes and pass-through events are named in the doc
for (auto& shape : GrrConst::shapes()) check(doc.find("`" + shape + "`") != std::string::npos, "shape " + shape + " is named");
for (auto& name : GrrShell::pass_events()) check(doc.find("`" + name + "`") != std::string::npos, "event " + name + " is named");
// the flags are documented
check(doc.find("--tui-local") != std::string::npos, "--tui-local is documented");
check(doc.find("GRR_URL") != std::string::npos, "GRR_URL is documented");
std::cout << (failures ? "FAILED " + std::to_string(failures) : std::string("ok")) << "\n";
return failures ? 1 : 0;
}