510 lines
20 KiB
C++
510 lines
20 KiB
C++
// Differential test of the Mplex core classes (args, command parser, style, options, config,
|
|
// format, layout) against tai.py. The expected values are produced by tests/mplex_core_gen.py
|
|
// from the real Python classes (build/core_cases.txt). Run from taicpp/:
|
|
// python3 -I tests/mplex_core_gen.py && build/mplex_core_test
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <sstream>
|
|
|
|
#include "mplex_args.h"
|
|
#include "mplex_commands.h"
|
|
#include "mplex_config.h"
|
|
#include "mplex_format.h"
|
|
#include "mplex_keys.h"
|
|
#include "mplex_layout.h"
|
|
#include "mplex_options.h"
|
|
#include "mplex_pane.h"
|
|
#include "mplex_py.h"
|
|
#include "mplex_style.h"
|
|
|
|
using namespace detz::mplex;
|
|
|
|
namespace {
|
|
|
|
long failures = 0, checks = 0, known_deviations = 0;
|
|
|
|
// Documented deviation: MplexArgs::int_flag returns a 64 bit value; a Python int beyond that
|
|
// saturates (every consumer clamps it). Rewrites such expected values in the "ints" section.
|
|
std::string saturate_big_ints(const std::string& want) {
|
|
size_t cut = want.rfind('\x1e');
|
|
if (cut == std::string::npos) return want;
|
|
std::string head = want.substr(0, cut + 1), tail = want.substr(cut + 1), out;
|
|
for (size_t i = 0; i < tail.size();) {
|
|
size_t j = i;
|
|
if (tail[j] == '-') ++j;
|
|
size_t digits = j;
|
|
while (digits < tail.size() && tail[digits] >= '0' && tail[digits] <= '9') ++digits;
|
|
if (digits - j >= 19 && (j == 0 || tail[j - 1] != 'x') && i > 0 && tail[i - 1] == '=') {
|
|
++known_deviations;
|
|
out += tail[i] == '-' ? "-9223372036854775808" : "9223372036854775807";
|
|
i = digits;
|
|
} else {
|
|
out.push_back(tail[i]);
|
|
++i;
|
|
}
|
|
}
|
|
return head + out;
|
|
}
|
|
std::map<std::string, long> per_kind_checks, per_kind_failures;
|
|
|
|
void check(const std::string& kind, const std::string& label, const std::string& got, const std::string& want) {
|
|
++checks;
|
|
++per_kind_checks[kind];
|
|
if (got == want) return;
|
|
++failures;
|
|
++per_kind_failures[kind];
|
|
if (per_kind_failures[kind] <= 6) {
|
|
std::cerr << "MISMATCH [" << kind << "] " << label << "\n got : " << got << "\n want: " << want << "\n";
|
|
}
|
|
}
|
|
|
|
std::string unesc(const std::string& s) {
|
|
std::string out;
|
|
for (size_t i = 0; i < s.size(); ++i) {
|
|
if (s[i] != '\\' || i + 1 >= s.size()) {
|
|
out.push_back(s[i]);
|
|
continue;
|
|
}
|
|
char n = s[++i];
|
|
if (n == 'n') out.push_back('\n');
|
|
else if (n == 't') out.push_back('\t');
|
|
else if (n == 'r') out.push_back('\r');
|
|
else if (n == '\\') out.push_back('\\');
|
|
else if (n == 'x' && i + 2 < s.size()) {
|
|
out.push_back(static_cast<char>(std::stoi(s.substr(i + 1, 2), nullptr, 16)));
|
|
i += 2;
|
|
} else {
|
|
out.push_back('\\');
|
|
out.push_back(n);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::vector<std::string> fields_of(const std::string& line) {
|
|
std::vector<std::string> out;
|
|
size_t start = 0;
|
|
while (true) {
|
|
size_t tab = line.find('\t', start);
|
|
if (tab == std::string::npos) {
|
|
out.push_back(unesc(line.substr(start)));
|
|
break;
|
|
}
|
|
out.push_back(unesc(line.substr(start, tab - start)));
|
|
start = tab + 1;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::string enc_list(const std::vector<std::string>& items) {
|
|
std::string out = std::to_string(items.size());
|
|
for (const auto& item : items) out += "\x1f" + item;
|
|
return out;
|
|
}
|
|
|
|
std::vector<std::string> dec_list(const std::string& text) {
|
|
std::vector<std::string> out;
|
|
size_t start = 0;
|
|
bool first = true;
|
|
while (true) {
|
|
size_t sep = text.find('\x1f', start);
|
|
std::string piece = text.substr(start, sep == std::string::npos ? std::string::npos : sep - start);
|
|
if (first) first = false;
|
|
else out.push_back(piece);
|
|
if (sep == std::string::npos) break;
|
|
start = sep + 1;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::vector<std::string> split_on(const std::string& text, char sep) {
|
|
std::vector<std::string> out;
|
|
size_t start = 0;
|
|
while (true) {
|
|
size_t at = text.find(sep, start);
|
|
if (at == std::string::npos) {
|
|
out.push_back(text.substr(start));
|
|
break;
|
|
}
|
|
out.push_back(text.substr(start, at - start));
|
|
start = at + 1;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
template <typename F>
|
|
std::string outcome(F func) {
|
|
try {
|
|
return "OK:" + func();
|
|
} catch (const MplexError& exc) {
|
|
return std::string("ERR:") + exc.what();
|
|
} catch (const ValueError&) {
|
|
return "EXC:ValueError";
|
|
} catch (const std::out_of_range&) {
|
|
return "EXC:KeyError";
|
|
} catch (const std::exception& exc) {
|
|
return std::string("EXC:?") + exc.what();
|
|
}
|
|
}
|
|
|
|
std::string show_val(const OptValue& v) {
|
|
if (v.is_bool()) return v.b ? "bool:True" : "bool:False";
|
|
if (v.is_int()) return "int:" + std::to_string(v.i);
|
|
return "str:" + v.s;
|
|
}
|
|
|
|
std::string frag_or_dash(Color c) { return c ? color_sgr(c) : "-"; }
|
|
Color frag_color(const std::string& s) { return s == "-" ? 0 : color_from_sgr(s); }
|
|
|
|
// ---- args ------------------------------------------------------------------------------
|
|
std::string args_dump(const MplexArgs& args) {
|
|
std::vector<std::string> parts, gets, ints;
|
|
for (const auto& [key, values] : args.flags) {
|
|
std::vector<std::string> vals;
|
|
for (const auto& v : values) vals.push_back(v.is_true ? "T" : "S" + v.text);
|
|
parts.push_back(std::string(1, key) + ":" + enc_list(vals));
|
|
}
|
|
for (const auto& [key, values] : args.flags) {
|
|
(void)values;
|
|
auto got = args.get(key);
|
|
const auto& last = args.flags.at(key).back();
|
|
gets.push_back(std::string(1, key) + "=" + (last.is_true ? "True" : py::repr(*got)));
|
|
std::string all = "[";
|
|
bool first = true;
|
|
for (const auto& v : args.flags.at(key)) {
|
|
if (!first) all += ", ";
|
|
first = false;
|
|
all += v.is_true ? "True" : py::repr(v.text);
|
|
}
|
|
all += "]";
|
|
gets.push_back(std::string(1, key) + "*" + all);
|
|
try {
|
|
auto n = args.int_flag(key, 7);
|
|
ints.push_back(std::string(1, key) + "=" + std::to_string(*n));
|
|
} catch (const MplexError& exc) {
|
|
ints.push_back(std::string(1, key) + "=ERR " + exc.what());
|
|
}
|
|
}
|
|
return enc_list(parts) + "\x1e" + enc_list(args.positional) + "\x1e" + enc_list(gets) + "\x1e" + enc_list(ints);
|
|
}
|
|
|
|
// ---- config ------------------------------------------------------------------------------
|
|
std::string cfg_dump(const MplexConfig& config) {
|
|
std::vector<std::string> opts;
|
|
for (const auto& name : config.options.value_order) opts.push_back(name + "=" + show_val(config.options.values.at(name)));
|
|
std::vector<std::string> tabs;
|
|
for (const auto& [tname, table] : config.tables.items()) {
|
|
std::vector<std::string> rows;
|
|
for (const auto& [key, b] : table.items())
|
|
rows.push_back(key + "\x1d" + b.command + "\x1d" + (b.repeat ? "1" : "0") + "\x1d" + b.note);
|
|
tabs.push_back(tname + "\x1c" + enc_list(rows));
|
|
}
|
|
return enc_list(opts) + "\x1e" + enc_list(tabs) + "\x1e" + enc_list(config.errors) + "\x1e" + std::to_string(config.skipped) +
|
|
"\x1e" + config.path;
|
|
}
|
|
|
|
// ---- layout ------------------------------------------------------------------------------
|
|
using PanePtr = std::shared_ptr<MplexPane>;
|
|
|
|
std::string tree_dump(const MplexLayoutNode::Ptr& node) {
|
|
if (!node) return "None";
|
|
if (node->kind == "pane") {
|
|
const auto& p = node->pane;
|
|
return "P" + std::to_string(p->id) + "(" + std::to_string(node->x) + "," + std::to_string(node->y) + "," +
|
|
std::to_string(node->w) + "," + std::to_string(node->h) + ")[" + std::to_string(p->x) + "," + std::to_string(p->y) +
|
|
"," + std::to_string(p->w) + "," + std::to_string(p->h) + "]";
|
|
}
|
|
std::string out = node->kind + (node->reverse ? "r" : "") + "(" + std::to_string(node->x) + "," + std::to_string(node->y) +
|
|
"," + std::to_string(node->w) + "," + std::to_string(node->h) + "){";
|
|
for (size_t i = 0; i < node->children.size(); ++i) {
|
|
if (i) out += ",";
|
|
out += tree_dump(node->children[i]);
|
|
}
|
|
return out + "}";
|
|
}
|
|
|
|
void sync_panes(const MplexLayoutNode::Ptr& root) {
|
|
if (!root) return;
|
|
for (auto& leaf : root->leaves()) {
|
|
leaf->pane->x = leaf->x;
|
|
leaf->pane->y = leaf->y;
|
|
leaf->pane->w = leaf->w;
|
|
leaf->pane->h = leaf->h;
|
|
}
|
|
}
|
|
|
|
struct LayoutState {
|
|
std::vector<PanePtr> panes;
|
|
MplexLayoutNode::Ptr root;
|
|
int W = 80, H = 24;
|
|
int counter = 1;
|
|
};
|
|
|
|
PanePtr new_pane(int ident) { return std::make_shared<MplexPane>(ident, 1, 1, 0, "", "0"); }
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char** argv) {
|
|
const std::string path = argc > 1 ? argv[1] : "build/core_cases.txt";
|
|
setenv("TZ", "UTC", 1);
|
|
tzset();
|
|
setenv("VISUAL", "nvi", 1);
|
|
unsetenv("EDITOR");
|
|
std::ifstream in(path);
|
|
if (!in) {
|
|
std::cerr << "cannot open " << path << " (run tests/mplex_core_gen.py first)\n";
|
|
return 2;
|
|
}
|
|
std::map<std::string, std::string> vars;
|
|
auto resolver = [&](const std::string& name) -> std::optional<std::string> {
|
|
if (!name.empty() && name[0] == '(') return "SH<" + name.substr(1) + ">";
|
|
auto found = vars.find(name);
|
|
if (found == vars.end()) return std::nullopt;
|
|
return found->second;
|
|
};
|
|
MplexOptions g, s(&g), w(&s);
|
|
std::vector<MplexOptions*> levels = {&g, &s, &w};
|
|
LayoutState layout;
|
|
std::unique_ptr<MplexConfig> fresh;
|
|
std::string line;
|
|
long lineno = 0;
|
|
while (std::getline(in, line)) {
|
|
++lineno;
|
|
std::vector<std::string> f = fields_of(line);
|
|
const std::string& kind = f[0];
|
|
std::string label = "line " + std::to_string(lineno) + " " + (f.size() > 1 ? f[1] : "");
|
|
if (kind == "P") {
|
|
check(kind, label, outcome([&] {
|
|
auto commands = MplexCommandParser::parse(f[1]);
|
|
std::string out;
|
|
for (size_t i = 0; i < commands.size(); ++i) {
|
|
if (i) out += "\x1e";
|
|
for (size_t j = 0; j < commands[i].size(); ++j) {
|
|
if (j) out += "\x1f";
|
|
out += commands[i][j];
|
|
}
|
|
}
|
|
return out + "|" + std::to_string(commands.size());
|
|
}),
|
|
f[2]);
|
|
} else if (kind == "Q") {
|
|
check(kind, label, MplexCommandParser::quote(f[1]), f[2]);
|
|
} else if (kind == "A") {
|
|
check(kind, label + " " + f[5], outcome([&] {
|
|
return args_dump(MplexArgs::parse(f[1], f[2], dec_list(f[5]), std::stoi(f[3]), std::stoi(f[4])));
|
|
}),
|
|
saturate_big_ints(f[6]));
|
|
} else if (kind == "C") {
|
|
std::string want = f[3];
|
|
if (want.find(";-") != std::string::npos) { // documented deviation: negative "#-f0000" components
|
|
++known_deviations;
|
|
want = "ERR:bad colour: " + py::lower(py::strip(f[1]));
|
|
}
|
|
check(kind, label, outcome([&] {
|
|
Color c = MplexStyle::color(f[1], f[2] == "1");
|
|
return c ? "'" + color_sgr(c) + "'" : std::string("None");
|
|
}),
|
|
want);
|
|
} else if (kind == "S") {
|
|
auto decode_style = [](const std::string& text) {
|
|
auto parts = split_on(text, '\x1e');
|
|
auto pair = dec_list(parts[0]);
|
|
return Style{frag_color(pair[0]), frag_color(pair[1]), std::stoi(parts[1])};
|
|
};
|
|
Style base = decode_style(f[2]);
|
|
std::optional<Style> reset;
|
|
if (f[3] != "-") reset = decode_style(f[3]);
|
|
check(kind, label + " base=" + f[2], outcome([&] {
|
|
Style r = MplexStyle::parse(f[1], base, reset);
|
|
return frag_or_dash(r.fg) + "|" + frag_or_dash(r.bg) + "|" + std::to_string(r.attr);
|
|
}),
|
|
f[4]);
|
|
} else if (kind == "G") {
|
|
check(kind, label, MplexStyle::sgr(frag_color(f[1]), frag_color(f[2]), std::stoi(f[3])), f[4]);
|
|
} else if (kind == "O") {
|
|
check(kind, label + " " + f[2], outcome([&] { return show_val(MplexOptions::check(f[1], f[2])); }), f[3]);
|
|
} else if (kind == "D") {
|
|
std::vector<std::string> names, defaults;
|
|
for (const auto& d : MplexOptions::defs()) {
|
|
names.push_back(d.name);
|
|
defaults.push_back(show_val(d.def) + "~" + d.kind);
|
|
}
|
|
check(kind, "names", enc_list(names), f[1]);
|
|
check(kind, "defaults", enc_list(defaults), f[2]);
|
|
check(kind, "word separators", MplexOptions::WORD_SEPARATORS, f[3]);
|
|
} else if (kind == "OBEGIN") {
|
|
g = MplexOptions();
|
|
s = MplexOptions(&g);
|
|
w = MplexOptions(&s);
|
|
} else if (kind == "OOP") {
|
|
MplexOptions& obj = *levels[static_cast<size_t>(std::stoi(f[1]))];
|
|
const std::string& op = f[2];
|
|
const std::string& name = f[3];
|
|
std::string res;
|
|
if (op == "set" || op == "append") res = outcome([&] { return show_val(obj.set(name, f[4], op == "append")); });
|
|
else if (op == "get") res = outcome([&] { return show_val(obj.get(name)); });
|
|
else if (op == "toggle") res = outcome([&] { obj.toggle(name); return std::string(); });
|
|
else if (op == "unset") res = outcome([&] { obj.unset(name); return std::string(); });
|
|
else if (op == "is_set") res = std::string("OK:") + (obj.is_set(name) ? "True" : "False");
|
|
else res = outcome([&] { return MplexOptions::show(obj.get(name)); });
|
|
std::string dump;
|
|
for (size_t i = 0; i < levels.size(); ++i) {
|
|
std::vector<std::string> items;
|
|
for (const auto& n : levels[i]->value_order) items.push_back(n + "=" + show_val(levels[i]->values.at(n)));
|
|
if (i) dump += "\x1e";
|
|
dump += enc_list(items);
|
|
}
|
|
check(kind, label + " " + op, res, f[5]);
|
|
check("OOPDUMP", label + " " + op, dump, f[6]);
|
|
} else if (kind == "K") {
|
|
if (f[1] == "-") {
|
|
MplexConfig config;
|
|
check(kind, "fresh", cfg_dump(config), f[2]);
|
|
fresh = std::make_unique<MplexConfig>();
|
|
} else {
|
|
auto config = MplexConfig::load(f[1]);
|
|
check(kind, label, cfg_dump(*config), f[2]);
|
|
}
|
|
} else if (kind == "DEFAULTPATH") {
|
|
check(kind, label, MplexConfig::default_path("/home/x/.tai") + "|" + MplexConfig::default_path("rel"),
|
|
f[1] + "|" + f[2]);
|
|
} else if (kind == "LOOKUP") {
|
|
std::string got;
|
|
for (auto pair : std::vector<std::pair<std::string, std::string>>{{"prefix", "C-b"}, {"prefix", "nope"}, {"root", "C-b"},
|
|
{"nonexistent", "x"}, {"copy-mode", "Escape"}})
|
|
got += pair.first + "/" + pair.second + "=" + (fresh->lookup(pair.first, pair.second) ? "True" : "False") + ";";
|
|
check(kind, label, got, f[1]);
|
|
} else if (kind == "UNBIND") {
|
|
check(kind, label, fresh->unbind(f[1], f[2]) ? "1" : "0", f[3]);
|
|
} else if (kind == "V") {
|
|
vars[f[1]] = f[2];
|
|
} else if (kind == "F") {
|
|
check(kind, label, outcome([&] { return MplexFormat::expand(f[1], resolver); }), f[2]);
|
|
} else if (kind == "W") {
|
|
check(kind, label, outcome([&] { return MplexFormat::with_time(f[1], std::stod(f[2])); }), f[3]);
|
|
} else if (kind == "X") {
|
|
check(kind, label, MplexFormat::strip_styles(f[1]), f[2]);
|
|
} else if (kind == "TRUTHY") {
|
|
std::optional<std::string> v;
|
|
if (f[1] != "-") v = f[1].substr(1);
|
|
check(kind, label, MplexFormat::truthy(v) ? "1" : "0", f[2]);
|
|
} else if (kind == "MC") {
|
|
check(kind, label, std::to_string(MplexFormat::match_close(f[3], std::stol(f[4]), f[1][0], f[2][0])), f[5]);
|
|
} else if (kind == "SC") {
|
|
check(kind, label, enc_list(MplexFormat::split_commas(f[1])), f[2]);
|
|
} else if (kind == "LBEGIN") {
|
|
layout = LayoutState();
|
|
layout.panes = {new_pane(0)};
|
|
layout.root = std::make_shared<MplexLayoutNode>("pane", layout.panes[0], 80, 24);
|
|
MplexLayout::assign(layout.root, 0, 0, 80, 24);
|
|
sync_panes(layout.root);
|
|
} else if (kind == "LOP") {
|
|
const std::string& op = f[1];
|
|
auto& st = layout;
|
|
auto leaves_now = [&] { return st.root->leaves(); };
|
|
if (op == "init") {
|
|
check("LOP:init", label + " init", tree_dump(st.root), f[3]);
|
|
} else if (op == "split") {
|
|
auto parts = split_on(f[2], ' ');
|
|
size_t idx = std::stoul(parts[0]);
|
|
int size = parts[3] == "N" ? -1 : std::stoi(parts[3]);
|
|
auto leaves = leaves_now();
|
|
PanePtr np = new_pane(st.counter++);
|
|
std::string res = outcome([&] {
|
|
auto [nr, fresh_leaf] = MplexLayout::split(st.root, leaves[idx], parts[1], parts[2] == "1", np, size);
|
|
(void)fresh_leaf;
|
|
st.root = nr;
|
|
st.panes.push_back(np);
|
|
return tree_dump(st.root);
|
|
});
|
|
check("LOP:split", label + " split " + f[2], res, f[3]);
|
|
} else if (op == "remove") {
|
|
size_t idx = std::stoul(f[2]);
|
|
auto leaves = leaves_now();
|
|
PanePtr victim = leaves[idx]->pane;
|
|
st.root = MplexLayout::remove(st.root, leaves[idx]);
|
|
std::vector<PanePtr> kept;
|
|
for (auto& p : st.panes)
|
|
if (p != victim) kept.push_back(p);
|
|
st.panes = kept;
|
|
check("LOP:remove", label + " remove", tree_dump(st.root), f[3]);
|
|
} else if (op == "resize_by") {
|
|
auto parts = split_on(f[2], ' ');
|
|
auto leaves = leaves_now();
|
|
std::string res = outcome([&] {
|
|
MplexLayout::resize_by(leaves[std::stoul(parts[0])], parts[1], std::stoi(parts[2]), parts[3] == "1");
|
|
return tree_dump(st.root);
|
|
});
|
|
check("LOP:resize_by", label + " resize_by " + f[2], res, f[3]);
|
|
} else if (op == "resize_to") {
|
|
auto parts = split_on(f[2], ' ');
|
|
auto leaves = leaves_now();
|
|
std::string res = outcome([&] {
|
|
MplexLayout::resize_to(leaves[std::stoul(parts[0])], parts[1], std::stoi(parts[2]));
|
|
return tree_dump(st.root);
|
|
});
|
|
check("LOP:resize_to", label + " resize_to " + f[2], res, f[3]);
|
|
} else if (op == "assign") {
|
|
auto parts = split_on(f[2], ' ');
|
|
st.W = std::stoi(parts[0]);
|
|
st.H = std::stoi(parts[1]);
|
|
MplexLayout::assign(st.root, 0, 0, st.W, st.H);
|
|
sync_panes(st.root);
|
|
check("LOP:assign", label + " assign " + f[2], tree_dump(st.root), f[3]);
|
|
} else if (op == "active") {
|
|
auto values = dec_list(f[2]);
|
|
for (size_t i = 0; i < values.size() && i < st.panes.size(); ++i) st.panes[i]->last_active = std::stod(values[i]);
|
|
} else if (op == "neighbour") {
|
|
auto parts = split_on(f[2], ' ');
|
|
int id = std::stoi(parts[0]);
|
|
PanePtr current;
|
|
for (auto& p : st.panes)
|
|
if (p->id == id) current = p;
|
|
std::vector<PanePtr> panes;
|
|
for (auto& leaf : st.root->leaves()) panes.push_back(leaf->pane);
|
|
PanePtr got = MplexLayout::neighbour(panes, current, parts[1][0], std::stoi(parts[2]), std::stoi(parts[3]));
|
|
check("LOP:neighbour", label + " neighbour " + f[2], got ? std::to_string(got->id) : "-", f[3]);
|
|
} else if (op == "preset") {
|
|
auto parts = split_on(f[2], ' ');
|
|
int count = std::stoi(parts[1]);
|
|
std::vector<PanePtr> pset;
|
|
for (int i = 0; i < count; ++i) pset.push_back(new_pane(st.counter + i));
|
|
st.counter += count;
|
|
st.panes = pset;
|
|
st.root = MplexLayout::preset(parts[0], pset, std::stoi(parts[2]), std::stoi(parts[3]), std::stoi(parts[4]),
|
|
std::stoi(parts[5]));
|
|
check("LOP:preset", label + " preset " + f[2], tree_dump(st.root), f[3]);
|
|
}
|
|
} else if (kind == "LPRESET") {
|
|
auto parts = split_on(f[1], ' ');
|
|
int count = std::stoi(parts[1]);
|
|
std::vector<PanePtr> pset;
|
|
for (int i = 0; i < count; ++i) pset.push_back(new_pane(i));
|
|
auto root = MplexLayout::preset(parts[0], pset, std::stoi(parts[2]), std::stoi(parts[3]), std::stoi(parts[4]),
|
|
std::stoi(parts[5]));
|
|
check(kind, label + " pre " + f[1], tree_dump(root), f[2]);
|
|
MplexLayout::assign(root, 0, 0, std::stoi(parts[2]), std::stoi(parts[3]));
|
|
sync_panes(root);
|
|
check(kind, label + " post " + f[1], tree_dump(root), f[3]);
|
|
} else if (kind == "LSHARES") {
|
|
check(kind, label, enc_list([&] {
|
|
std::vector<std::string> out;
|
|
for (int v : MplexLayout::shares(std::stoi(f[1]), std::stoi(f[2]), f[3] == "1")) out.push_back(std::to_string(v));
|
|
return out;
|
|
}()),
|
|
f[4]);
|
|
} else {
|
|
std::cerr << "unknown case kind " << kind << " at line " << lineno << "\n";
|
|
++failures;
|
|
}
|
|
}
|
|
for (const auto& [kind, count] : per_kind_checks)
|
|
std::cout << kind << ": " << count << " checks, " << per_kind_failures[kind] << " failures\n";
|
|
std::cout << "known deviations applied (int64 saturation, negative #-hex colour): " << known_deviations << "\n";
|
|
std::cout << "TOTAL " << checks << " checks, " << failures << " failures\n";
|
|
return failures ? 1 : 0;
|
|
}
|