Files
tai/taicpp/tests/cli_test.cpp
T

75 lines
4.7 KiB
C++
Raw Normal View History

2026-10-24 06:41:22 +02:00
#include "../src/cli/cli.h"
#include <cassert>
#include <cstdlib>
2026-10-24 06:41:22 +02:00
#include <string>
#include <vector>
#include "../src/cli/arg_parser.h"
#include "../src/cli/help_formatter.h"
#include "../src/cli/text_wrap.h"
using namespace detz::cli;
static Options opts(std::vector<const char*> a) {
std::vector<char*> v{const_cast<char*>("detz")};
for (auto x : a) v.push_back(const_cast<char*>(x));
return Parser().parseOpts(static_cast<int>(v.size()), v.data());
}
static bool errHas(const Options& o, const std::string& m) { return o.error.find("tai: error: " + m) != std::string::npos && o.error.rfind("usage: tai", 0) == 0; }
static void argparseCases() {
assert(opts({"--vers"}).version);
assert(errHas(opts({"--ins"}), "ambiguous option: --ins could match --install, --install-telegram"));
assert(errHas(opts({"--profile"}), "argument --profile: expected one argument"));
assert(errHas(opts({"--yes=1"}), "argument --yes: ignored explicit argument '1'"));
assert(errHas(opts({"--cap-max-turns", "abc"}), "argument --cap-max-turns: invalid int value: 'abc'"));
assert(errHas(opts({"--debug", "zzz"}), "argument --debug: invalid choice: 'zzz' (choose from file, stdout, stderr)"));
assert(errHas(opts({"--bogus"}), "unrecognized arguments: --bogus"));
assert(opts({"-h", "--bogus"}).help && opts({"-h", "--bogus"}).error.empty());
assert(errHas(opts({"--cap-permission-mode", "zzz", "-h"}), "argument --cap-permission-mode: invalid choice"));
assert(opts({"--debug"}).debug.value() == "file");
assert(opts({"--debug=stderr"}).debug.value() == "stderr");
assert(opts({"--profile=x", "hello", "world"}).prompt == (std::vector<std::string>{"hello", "world"}));
assert(opts({"--cap", "--yes", "x"}).cap.value() == (std::vector<std::string>{"--yes", "x"}));
assert(!opts({"--cap", "--yes"}).yes);
assert(opts({"--mplex"}).mplex.has_value() && opts({"--mplex"}).mplex->empty());
assert(opts({"--", "--yes"}).prompt == (std::vector<std::string>{"--yes"}));
assert(opts({"--cap-max-turns", "-1"}).cap_max_turns.value() == -1);
assert(opts({"--mcp-port", " 1_0 "}).mcp_port.value() == 10);
assert(errHas(opts({"--model", "x"}), "unrecognized arguments: --model x") || errHas(opts({"--model", "x"}), "unrecognized arguments: --model"));
long long n = 0;
assert(ArgParser::pyInt("\xd9\xa3", n) && n == 3);
assert(!ArgParser::pyInt("3x", n) && !ArgParser::pyInt("_3", n) && !ArgParser::pyInt("", n));
assert(ArgParser::pyRepr("it's") == "\"it's\"" && ArgParser::pyRepr("a'\"") == "'a\\'\"'");
}
static void helpCases() {
// Usage, help and parser share one table: every option string of the table is in --help.
setenv("COLUMNS", "80", 1);
const std::string help = Parser::helpText();
for (const auto& f : FlagTable::all())
for (const auto& n : f.names) assert(help.find(n) != std::string::npos);
assert(help.rfind("usage: tai [-h] [--profile PROFILE]", 0) == 0);
// width 80: the wrapped help column starts at 24
assert(help.find(" --debug [SINK] extensive debug logging to <home>/debug.log (file),\n") != std::string::npos);
setenv("COLUMNS", "40", 1);
assert(HelpFormatter(HelpFormatter::terminalColumns()).formatUsage().find("\n [--yes] [--yolo] [--auto]\n") != std::string::npos);
// textwrap: breaks after hyphens inside words, breaks over-long words
assert(TextWrap::wrap("per-record context", 10) == (std::vector<std::string>{"per-record", "context"}));
assert(TextWrap::wrap("abcdefghij", 4) == (std::vector<std::string>{"abcd", "efgh", "ij"}));
unsetenv("COLUMNS");
}
2026-10-24 06:41:22 +02:00
int main() {
argparseCases();
helpCases();
2026-10-24 06:41:22 +02:00
detz::cli::Parser p;
// golden --help contains every flag
std::string h = detz::cli::Parser::helpText();
for (auto f : {"--profile","--yes","--yolo","--auto","--continue","--debug","--debug-format","--env","--no-opencode","--update-models","--eval-retrieval","--stats-rag","--version","--cap-manifest","--cap-speak","--cap-cwd","--cap-model","--cap-permission-mode","--cap-max-turns","--cap ","--serve-mcp","--mcp-port","--mcp-bind","--telegram","--scheduler","--install","--install-telegram","--uninstall-telegram","--plexer-wrap","--mplex","--demo","--demo-shot","--tui","--tui-local","--theme"})
assert(h.find(f) != std::string::npos);
// parse smoke
const char* av[] = {"detz","--yes","--debug","--cap","resolve","foo"};
auto c = p.parse(6, (char**)av);
assert(c.opts.yes && c.opts.debug.value()=="file" && c.opts.cap && c.opts.cap->size()==2);
const char* bv[] = {"detz","--version"};
assert(p.parse(2,(char**)bv).run()==0);
const char* cv[] = {"detz"};
assert(p.parse(1,(char**)cv).name=="--help");
return 0;
}