// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // The semantics of :Text, checked against reference vectors: every vector in // fixture/vectors.json is a recorded answer (the file names the Unicode // version it holds). // // The driver's output is decided by these rules: a unit's excerpt is cut by // Strip() with Unicode whitespace, a key by Length() in code points, a peak // by Round() half to even, an exclusion by FnMatch()'s character classes, a // stack key by UrlSplit() and Lower(). The nearest C++ equivalent would agree // on ASCII and drift exactly where nobody looks. // // Also :Json's three dump layouts — DumpFile(), DumpSpaced() and // DumpCompactSorted() — and its handling of a duplicate key. import std; import Crafter.Review; namespace fs = std::filesystem; using namespace Crafter::Review; namespace { std::int32_t Failures = 0; void Check(bool cond, std::string_view msg) { if (!cond) { std::println(std::cerr, "FAIL: {}", msg); ++Failures; } } std::vector Strings(const Json& array) { std::vector out; for (const Json& item : array) out.push_back(item.get()); return out; } std::string Show(const std::vector& pieces) { Json array = Json::array(); for (const std::string& piece : pieces) array.push_back(piece); return Dyn::ReprValue(array); } std::string ShowOptional(const std::optional& value) { return value ? Dyn::Repr(*value) : std::string("None"); } bool SameOptional(const std::optional& value, const Json& expected) { if (expected.is_null()) return !value.has_value(); return value && *value == expected.get(); } bool IsError(const Json& value) { return value.is_object() && value.contains("error"); } } int main() { const fs::path path = fs::current_path() / "tests" / "Text" / "fixture" / "vectors.json"; const Json vectors = Json::parse(Dyn::ReadText(path)); for (const Json& v : vectors["strip"]) { const std::string input = v[0]; Check(Dyn::Strip(input) == v[1].get(), std::format("strip({}) = {}", Dyn::Repr(input), Dyn::Repr(Dyn::Strip(input)))); } for (const Json& v : vectors["split"]) { const std::string input = v[0]; Check(Dyn::Split(input) == Strings(v[1]), std::format("split({}) = {}", Dyn::Repr(input), Show(Dyn::Split(input)))); } for (const Json& v : vectors["splitlines"]) { const std::string input = v[0]; Check(Dyn::SplitLines(input) == Strings(v[1]), std::format("splitlines({}) = {}", Dyn::Repr(input), Show(Dyn::SplitLines(input)))); } for (const Json& v : vectors["splitlinesKeep"]) { const std::string input = v[0]; Check(Dyn::SplitLines(input, true) == Strings(v[1]), std::format("splitlines({}, keepends) = {}", Dyn::Repr(input), Show(Dyn::SplitLines(input, true)))); } for (const Json& v : vectors["lower"]) { const std::string input = v[0]; Check(Dyn::Lower(input) == v[1].get(), std::format("lower({}) = {}", Dyn::Repr(input), Dyn::Repr(Dyn::Lower(input)))); } for (const Json& v : vectors["upper"]) { const std::string input = v[0]; Check(Dyn::Upper(input) == v[1].get(), std::format("upper({}) = {}", Dyn::Repr(input), Dyn::Repr(Dyn::Upper(input)))); } for (const Json& v : vectors["floatRepr"]) { const double value = v[0].get(); Check(Dyn::FloatRepr(value) == v[1].get(), std::format("repr({}) = {}", v[1].get(), Dyn::FloatRepr(value))); } for (const Json& v : vectors["round"]) { const double value = v[0].get(); const std::int32_t digits = v[1].get(); const std::string got = digits == 0 ? std::to_string(Dyn::RoundToInt(value)) : Dyn::FloatRepr(Dyn::Round(value, digits)); Check(got == v[2].get(), std::format("Round({}, {}) = {}, expected {}", Dyn::FloatRepr(value), digits, got, v[2].get())); } for (const Json& v : vectors["int"]) { const std::string input = v[0]; const std::optional got = Dyn::ParseInt(input); if (IsError(v[1])) { Check(!got && Dyn::IntError(input) == v[1]["message"].get(), std::format("ParseInt({}) fails with the recorded message ({})", Dyn::Repr(input), Dyn::IntError(input))); } else { Check(got && *got == v[1].get(), std::format("int({}) = {}", Dyn::Repr(input), got ? std::to_string(*got) : std::string("error"))); } } for (const Json& v : vectors["float"]) { const std::string input = v[0]; const std::optional got = Dyn::ParseFloat(input); if (IsError(v[1])) { Check(!got && Dyn::FloatError(input) == v[1]["message"].get(), std::format("ParseFloat({}) fails with the recorded message", Dyn::Repr(input))); } else { Check(got && Dyn::FloatRepr(*got) == v[1].get(), std::format("float({}) = {}", Dyn::Repr(input), got ? Dyn::FloatRepr(*got) : std::string("error"))); } } for (const Json& v : vectors["repr"]) { const std::string input = v[0]; Check(Dyn::Repr(input) == v[1].get(), std::format("repr of {} = {}", v[1].get(), Dyn::Repr(input))); } for (const char* key : { "reprList", "reprDict" }) { for (const Json& v : vectors[key]) { Check(Dyn::ReprValue(v[0]) == v[1].get(), std::format("ReprValue = {}, expected {}", Dyn::ReprValue(v[0]), v[1].get())); } } for (const Json& v : vectors["fnmatch"]) { const std::string name = v[0]; const std::string pattern = v[1]; Check(Dyn::FnMatch(name, pattern) == v[2].get(), std::format("fnmatch({}, {}) should be {}", Dyn::Repr(name), Dyn::Repr(pattern), v[2].get())); } for (const Json& v : vectors["urlsplit"]) { const std::string url = v[0]; const Json& expected = v[1]; if (IsError(expected)) { std::string message = "(no error)"; try { Dyn::UrlSplit(url); } catch (const std::invalid_argument& e) { message = e.what(); } Check(message == expected["message"].get(), std::format("UrlSplit({}) fails with {}, expected {}", Dyn::Repr(url), Dyn::Repr(message), Dyn::Repr(expected["message"].get()))); continue; } const Dyn::SplitUrl got = Dyn::UrlSplit(url); Check(got.scheme == expected["scheme"].get() && got.netloc == expected["netloc"].get() && got.path == expected["path"].get() && got.query == expected["query"].get() && got.fragment == expected["fragment"].get(), std::format("urlsplit({}) parts: {} {} {} {} {}", Dyn::Repr(url), got.scheme, got.netloc, got.path, got.query, got.fragment)); Check(SameOptional(got.Hostname(), expected["hostname"]), std::format("urlsplit({}).hostname = {}", Dyn::Repr(url), ShowOptional(got.Hostname()))); Check(SameOptional(got.Username(), expected["username"]), std::format("urlsplit({}).username = {}", Dyn::Repr(url), ShowOptional(got.Username()))); Check(SameOptional(got.Password(), expected["password"]), std::format("urlsplit({}).password = {}", Dyn::Repr(url), ShowOptional(got.Password()))); const Json& port = expected["port"]; if (IsError(port)) { std::string message = "(no error)"; try { got.Port(); } catch (const std::invalid_argument& e) { message = e.what(); } Check(message == port["message"].get(), std::format("urlsplit({}).port fails with {}", Dyn::Repr(url), Dyn::Repr(message))); } else if (port.is_null()) { Check(!got.Port(), std::format("urlsplit({}).port is None", Dyn::Repr(url))); } else { Check(got.Port() == port.get(), std::format("urlsplit({}).port", Dyn::Repr(url))); } } for (const Json& v : vectors["unquote"]) { const std::string input = v[0]; Check(Dyn::Unquote(input) == v[1].get(), std::format("unquote({}) = {}", Dyn::Repr(input), Dyn::Repr(Dyn::Unquote(input)))); } for (const Json& v : vectors["natural"]) { const std::string name = v[0]; Dyn::NaturalKey expected; for (const Json& part : v[1]) { if (part.is_string()) { expected.parts.emplace_back(part.get()); } else { expected.parts.emplace_back(part.get()); } } Check(Dyn::Natural(name) == expected, std::format("natural key of {}", Dyn::Repr(name))); } for (const Json& v : vectors["paths"]) { const std::string op = v[0][0]; const std::string a = v[0][1]; std::string got; if (op == "normpath") got = Dyn::NormPath(a); if (op == "abspath") got = Dyn::AbsPath(a); if (op == "basename") got = Dyn::BaseName(a); if (op == "dirname") got = Dyn::DirName(a); if (op == "relpath") got = Dyn::RelPath(a, v[0][2].get()); if (op == "join") got = Dyn::JoinPath(a, v[0][2].get()); Check(got == v[1].get(), std::format("{}({}) = {}, expected {}", op, Dyn::Repr(a), Dyn::Repr(got), Dyn::Repr(v[1].get()))); } for (const Json& v : vectors["jsonIndent1"]) { Check(DumpFile(v[0]) == v[1].get(), std::format("DumpFile:\n{}\nexpected:\n{}", DumpFile(v[0]), v[1].get())); } for (const Json& v : vectors["jsonDefault"]) { Check(DumpSpaced(v[0]) == v[1].get(), std::format("DumpSpaced: {} / expected {}", DumpSpaced(v[0]), v[1].get())); } for (const Json& v : vectors["jsonSorted"]) { Check(DumpCompactSorted(v[0]) == v[1].get(), std::format("DumpCompactSorted: {} / expected {}", DumpCompactSorted(v[0]), v[1].get())); } for (const Json& v : vectors["jsonDuplicate"]) { const Json parsed = Json::parse(v[0].get()); Json pairs = Json::array(); for (auto it = parsed.begin(); it != parsed.end(); ++it) pairs.push_back(Json::array({ it.key(), it.value() })); Check(pairs == v[1], std::format("a duplicate key keeps its first place and its last value: {}", pairs.dump())); } // Read the way open() reads: \r\n and a lone \r are \n. Check(Dyn::UniversalNewlines("a\r\nb\rc\n") == "a\nb\nc\n", "universal newlines"); Check(Dyn::Length("é😀a") == 3 && Dyn::Slice("é😀abc", 1, 3) == "😀a" && Dyn::Slice("abc", -2) == "bc", "len and slicing count code points"); Check(Dyn::DecodeReplace("a\xC3(b") == "a\xEF\xBF\xBD(b", "a bad byte becomes U+FFFD, the next character survives"); Check(!Dyn::IsUtf8("\xED\xA0\x80") && Dyn::IsUtf8("\xEF\xBF\xBD"), "a surrogate is not UTF-8; a real U+FFFD is"); Check(Dyn::IgnoreCaseLetter(U'K') == 'k' && Dyn::IgnoreCaseLetter(U'ſ') == 's' && Dyn::IgnoreCaseLetter(U'é') == '\0', "re.IGNORECASE folds"); Check(Dyn::ErrnoText(111) == "[Errno 111] Connection refused", "OSError text"); Check(Dyn::Count("abab", "ab") == 2 && Dyn::Count("aaa", "aa") == 1 && Dyn::Count("ab", "") == 3, "count: non-overlapping, empty counts len+1"); Check(Dyn::RSplit("a)b)c", ")", 1) == std::vector{ "a)b", "c" }, "rsplit with maxsplit"); std::println("{} vector groups, Unicode {}", vectors.size() - 1, vectors["unicode"].get()); return Failures == 0 ? 0 : 1; }