// 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 #include #include #include #include #include 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(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> 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> 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> 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(GRR_SSE_LINE_MAX)}, {"GRR_BODY_MAX", static_cast(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(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 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(GRR_CONTROL_LINE) && GrrControl::WAIT == GRR_CONTROL_WAIT && GrrControl::WAIT_MAX == GRR_CONTROL_WAIT_MAX && GrrControl::KEYS_MAX == static_cast(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; }