// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // The pure pipeline against the goldens: fixture/pipeline.json holds the // recorded answers over the fixtures next to it — the inventory, the plans, // the keys, the prompts, the normalized results, the stream, the config. // Each result is compared as DumpFile writes it, so a key out of place fails // as surely as a wrong value: the dump, the shared files and the prompts are // made of these. A deliberate change edits the goldens in the same commit. import std; import Crafter.Review; namespace fs = std::filesystem; using namespace Crafter::Review; namespace { std::int32_t Failures = 0; std::int32_t Checks = 0; void Check(bool cond, std::string_view what) { ++Checks; if (!cond) { std::println(std::cerr, "FAIL: {}", what); ++Failures; } } // Where two texts part, for the failure message. std::string Parting(std::string_view got, std::string_view want) { std::size_t at = 0; while (at < got.size() && at < want.size() && got[at] == want[at]) ++at; auto window = [&](std::string_view text) { return Dyn::Repr(text.substr(at > 40 ? at - 40 : 0, 120)); }; return std::format("at byte {}: got {} want {}", at, window(got), window(want)); } void Same(std::string_view got, std::string_view want, std::string_view what) { Check(got == want, got == want ? std::string(what) : std::format("{}: {}", what, Parting(got, want))); } void SameJson(const Json& got, const Json& want, std::string_view what) { Same(DumpFile(got), DumpFile(want), what); } std::vector Strings(const Json& array) { std::vector out; if (array.is_null()) return out; for (const Json& item : array) out.push_back(item.get()); return out; } Json ToJson(const std::vector& values) { Json out = Json::array(); for (const std::string& value : values) out.push_back(value); return out; } Json Optional(const std::optional& value) { return value ? Json(*value) : Json(); } std::optional OptionalString(const Json& value) { if (value.is_null()) return std::nullopt; return value.get(); } Json CommitCodeJson(const Plan::CommitCode& cc) { Json body = Json::object(); Json code = Json::object(); Json docs = Json::object(); Json message = Json::object(); Json upto = Json::object(); for (const auto& [k, v] : cc.body) body[std::to_string(k)] = v; for (const auto& [k, v] : cc.code) code[std::to_string(k)] = v; for (const auto& [k, v] : cc.docs) docs[std::to_string(k)] = ToJson(v); for (const auto& [k, v] : cc.message) message[std::to_string(k)] = v; for (const auto& [k, v] : cc.upto) upto[std::to_string(k)] = v; return {{"body", body}, {"code", code}, {"docs", docs}, {"msg", message}, {"range", cc.range}, {"upto", upto}, {"base", cc.base}}; } void CheckSeries(const std::string& name, const Json& golden) { const std::string dir = golden.at("dir").get(); const Inventory::Built built = Inventory::Build("commits", dir); SameJson(built.out, golden.at("inventory"), std::format("{}: inventory", name)); SameJson(ToJson(built.lines), golden.at("lines"), std::format("{}: lines", name)); Same(Artifact::ArtifactHash("commits", dir), golden.at("hash").get(), std::format("{}: artifact hash", name)); const Plan::CommitCode cc = Plan::CommitCodeOf(Artifact::PatchTexts(dir)); SameJson(CommitCodeJson(cc), golden.at("commitCode"), std::format("{}: commit_code", name)); const Json& units = built.out.at("units"); for (auto it = golden.at("codeKeyParts").begin(); it != golden.at("codeKeyParts").end(); ++it) { const auto [ident, deps] = Plan::CodeKeyParts(it.key(), cc); SameJson(Json::array({ident, ToJson(deps)}), it.value(), std::format("{}: code_key_parts {}", name, it.key())); } std::map blocksByLens; std::map wholeByLens; const std::vector exclude = {"**/*.test.*", "docs/**", "tests/**"}; for (const Plan::Lens& lens : Plan::Lenses) { const Plan::LensPlan plan = Plan::PlanLens(lens.file, units, built.lines, {}, exclude); Json blocks = Json::array(); for (const Json& block : plan.blocks) blocks.push_back(block); blocksByLens[std::string(lens.file)] = blocks; wholeByLens[std::string(lens.file)] = plan.whole; } for (auto it = golden.at("keys").begin(); it != golden.at("keys").end(); ++it) { const std::vector tag = Dyn::Split(it.key(), "|"); const std::string& lensFile = tag[0]; const std::string environment = tag[1] == "None" ? std::string() : tag[1]; const std::vector ids = tag[2].empty() ? std::vector() : Dyn::Split(tag[2], ","); Json keys = Json::object(); for (const Json& job : blocksByLens[lensFile]) { if (job.at("kind") != "block") continue; const std::string unit = job.at("unit").get(); const Json* found = nullptr; for (const Json& u : units) { if (u.at("id") == unit) found = &u; } keys[unit] = Optional(Plan::BlockReuseKey(job, lensFile, Plan::UnitText(built.lines, *found), cc, ids, std::string(64, 'c'), 1, environment)); } SameJson(keys, it.value(), std::format("{}: block keys {}", name, it.key())); const Json& whole = wholeByLens[lensFile]; const Json wholeKey = whole.is_null() ? Json() : Optional(Plan::WholeCodeKey(whole, lensFile, cc, ids, std::string(64, 'c'), 1, environment)); SameJson(wholeKey, golden.at("wholeKeys").at(it.key()), std::format("{}: whole key {}", name, it.key())); } if (!golden.contains("plans")) return; Same(Artifact::PatchHead(dir).value_or("None"), golden.at("head").is_null() ? "None" : golden.at("head").get(), std::format("{}: patch_head", name)); SameJson(ToJson(Artifact::PatchFiles(dir)), golden.at("patchFiles"), std::format("{}: patch_files", name)); for (const auto& [k, text] : Artifact::PatchTexts(dir)) { const Plan::PatchParts parts = Plan::PartsOf(text); SameJson(Json::array({parts.hasBody, parts.code, parts.base, ToJson(parts.docs), parts.message}), golden.at("patchParts").at(std::to_string(k)), std::format("{}: patch_parts {}", name, k)); } for (std::size_t i = 0; i < units.size(); ++i) { const Json& want = golden.at("units").at(i); const std::string id = units[i].at("id").get(); const std::string text = Plan::UnitText(built.lines, units[i]); Same(Plan::UnitKind(units[i]), want.at("kind").get(), std::format("{}: unit_kind {}", name, id)); Check(Plan::IsDocUnit(id) == want.at("doc").get(), std::format("{}: is_doc_unit {}", name, id)); SameJson(Optional(Plan::UnitPath(id)), want.at("path"), std::format("{}: unit_path {}", name, id)); Same(text, want.at("text").get(), std::format("{}: unit_text {}", name, id)); Same(Plan::CodeNorm(text), want.at("norm").get(), std::format("{}: code_norm {}", name, id)); } std::vector all; std::vector statics; for (const Plan::Lens& lens : Plan::Lenses) { all.emplace_back(lens.file); if (lens.file != Plan::FunctionalLens) statics.emplace_back(lens.file); } const std::vector functional = {std::string(Plan::FunctionalLens)}; SameJson(ToJson(Plan::NotReviewed(units, built.lines, all)), golden.at("notReviewed").at("all"), std::format("{}: not_reviewed all", name)); SameJson(ToJson(Plan::NotReviewed(units, built.lines, statics)), golden.at("notReviewed").at("static"), std::format("{}: not_reviewed static", name)); SameJson(ToJson(Plan::NotReviewed(units, built.lines, functional, exclude)), golden.at("notReviewed").at("functional"), std::format("{}: not_reviewed functional", name)); std::vector jobs; for (const Plan::Lens& lens : Plan::Lenses) { for (const bool excluded : {false, true}) { const Plan::LensPlan plan = Plan::PlanLens(lens.file, units, built.lines, {}, excluded ? std::span(exclude) : std::span()); Json blocks = Json::array(); for (const Json& block : plan.blocks) blocks.push_back(block); const std::string key = std::format("{}{}", lens.file, excluded ? ":exclude" : ""); SameJson(Json{{"whole", plan.whole}, {"blocks", blocks}}, golden.at("plans").at(key), std::format("{}: plan_lens {}", name, key)); if (!excluded) { if (!plan.whole.is_null()) jobs.push_back(plan.whole); jobs.insert(jobs.end(), plan.blocks.begin(), plan.blocks.end()); } } } SameJson(Plan::Estimate(jobs, 123457, {{"repo-better-solution", 4100}, {"repo-functional", 9000}}), golden.at("estimate"), std::format("{}: estimate", name)); } void CheckPlan(const Json& g) { for (const Json& entry : g.at("unitIds")) { const std::string id = entry.at("id").get(); Same(Plan::UnitKindOf(id), entry.at("kind").get(), std::format("unit_kind {}", Dyn::Repr(id))); Check(Plan::IsDocUnit(id) == entry.at("doc").get(), std::format("is_doc_unit {}", Dyn::Repr(id))); SameJson(Optional(Plan::UnitPath(id)), entry.at("path"), std::format("unit_path {}", Dyn::Repr(id))); } for (const Json& entry : g.at("excludedBy")) { const std::vector globs = Strings(entry.at(1)); SameJson(Optional(Plan::ExcludedBy(entry.at(0).get(), globs)), entry.at(2), std::format("excluded_by {}", DumpCompactSorted(entry))); } for (const Json& entry : g.at("gates")) { const Json& args = entry.at(0); const std::vector only = args.contains("only") ? Strings(args.at("only")) : std::vector(); const std::vector without = args.contains("without") ? Strings(args.at("without")) : std::vector(); const std::vector off = args.contains("off") ? Strings(args.at("off")) : std::vector(); Json got = Json::array(); for (const Plan::Gate& gate : Plan::GatesFor(only, without, off)) { Json lenses = Json::array(); for (const std::string_view lens : gate.lenses) lenses.push_back(lens); got.push_back(Json::array({gate.title, lenses})); } SameJson(got, entry.at(1), std::format("gates_for {}", DumpCompactSorted(args))); } for (const Json& entry : g.at("codeNorm")) { Same(Plan::CodeNorm(entry.at(0).get()), entry.at(1).get(), std::format("code_norm {}", Dyn::Repr(entry.at(0).get()))); } SameJson(Optional(Plan::ReuseKey({{"kind", "block"}, {"unit", "U"}}, "repo-bug-prover.md", "T", {"d", "b"}, {"w2", "w1"}, "cs", 1)), g.at("reuseKey"), "reuse_key"); for (const Json& entry : g.at("readsOf")) Check(Plan::ReadsOf(entry.at(0)) == entry.at(1).get(), std::format("reads_of {}", DumpCompactSorted(entry.at(0)))); for (const Json& entry : g.at("readIn")) { Check(Plan::ReadIn(entry.at(0), entry.at(1).get(), entry.at(2).get()) == entry.at(3).get(), std::format("read_in {}", DumpCompactSorted(entry))); } SameJson(Plan::Rebind(g.at("rebind").at(0), "p2:hello.py#3"), g.at("rebind").at(1), "rebind"); } void CheckPrompts(const Json& g, const Json& series) { const Json& p = g.at("prompts"); Same(Prompt::SepRun, p.at("separators").at(0).get(), "SEP_RUN"); Same(Prompt::SepLens, p.at("separators").at(1).get(), "SEP_LENS"); Same(Prompt::SepTail, p.at("separators").at(2).get(), "SEP_TAIL"); Same(Prompt::GenericVocab, p.at("genericVocab").get(), "GENERIC_VOCAB"); Same(Prompt::WholeFirstRepo, p.at("wholeText").at("first-repo").get(), "WHOLE_TEXT first-repo"); Same(Prompt::WholeFunctional, p.at("wholeText").at("functional").get(), "WHOLE_TEXT functional"); Same(Prompt::FunctionalBlockHint, p.at("blockHint").get(), "FUNCTIONAL_BLOCK_HINT"); Same(Prompt::ShotRules, p.at("shotRules").get(), "SHOT_RULES"); Same(Prompt::HintOutside, p.at("kindHints").at("outside").get(), "KIND_HINTS_REPO outside"); Same(Prompt::HintAbsent, p.at("kindHints").at("absent").get(), "KIND_HINTS_REPO absent"); Same(Prompt::SystemText(), p.at("systemText").get(), "system_text"); for (auto it = p.at("charterSha").begin(); it != p.at("charterSha").end(); ++it) { Same(Plan::Sha(Prompt::Charter(it.key())), it.value().get(), std::format("charter sha {}", it.key())); } for (const Json& entry : p.at("runLayer")) { const Json& s = series.at(entry.at("series").get()); const Json& args = entry.at("args"); const std::vector trees = Strings(args.at("trees")); const std::vector context = Strings(args.at("context_paths")); const std::vector lines = Strings(s.at("lines")); Same(Prompt::RunLayer(args.at("artifact_rel").get(), args.at("base").get(), args.at("date").get(), trees, s.at("hash").get(), lines, s.at("inventory").at("units"), context, args.at("repo_name").get()), entry.at("text").get(), std::format("run_layer {} {}", entry.at("series").get(), DumpCompactSorted(args))); } for (const Json& entry : p.at("appBlock")) { Same(Prompt::AppBlock(entry.at("functional"), OptionalString(entry.at("fixture")), OptionalString(entry.at("totp")), entry.at("slots").get()), entry.at("text").get(), std::format("app_block {}", DumpCompactSorted(entry.at("functional")))); } for (const Json& entry : p.at("stackText")) { Same(Prompt::StackText(entry.at("stack"), OptionalString(entry.at("totp"))), entry.at("text").get(), "stack_text"); } for (const Json& entry : p.at("dbBlock")) { const std::optional bypass = entry.at("bypass").is_null() ? std::nullopt : std::optional(entry.at("bypass").get()); Same(Prompt::DbBlock(entry.at("supabase"), "/app/bin/crafter-review sql", OptionalString(entry.at("user")), entry.at("withApp").get(), OptionalString(entry.at("checked")), bypass), entry.at("text").get(), std::format("db_block {}", DumpCompactSorted(entry.at("supabase")))); } for (const Json& entry : p.at("lensLayer")) { const std::string lensFile = entry.at("lensFile").get(); Same(Prompt::LensLayer(lensFile, Prompt::Charter(lensFile), "DISPOSITIONS BLOCK", OptionalString(entry.at("app")).value_or(""), OptionalString(entry.at("summary")).value_or(""), OptionalString(entry.at("db")).value_or("")), entry.at("text").get(), std::format("lens_layer {}", lensFile)); } for (const Json& entry : p.at("tails")) { const Json& s = series.at(entry.at("series").get()); const Json& plan = s.at("plans").at(entry.at("plan").get()); const Json* job = plan.at("whole").is_object() && plan.at("whole").at("label") == entry.at("label") ? &plan.at("whole") : nullptr; for (const Json& block : plan.at("blocks")) { if (block.at("label") == entry.at("label")) job = █ } const std::vector lines = Strings(s.at("lines")); Same(Prompt::Tail(*job, s.at("inventory").at("units"), lines, OptionalString(entry.at("summary")).value_or("")), entry.at("text").get(), std::format("tail {} {}", entry.at("series").get(), entry.at("label").get())); } for (const Json& entry : p.at("userPrompt")) { Same(Prompt::UserPrompt(entry.at("tail").get(), entry.at("job"), OptionalString(entry.at("stack")).value_or("")), entry.at("text").get(), std::format("user_prompt {}", DumpCompactSorted(entry.at("job")))); } Same(Prompt::SystemPrompt("S", "R", "L"), p.at("systemPrompt").get(), "system_prompt"); } void CheckDispositions(const Json& g) { const Json& d = g.at("dispositions"); const std::vector entries(d.at("entries").begin(), d.at("entries").end()); const std::vector rows = Dispositions::Classify(entries, "H1", d.at("dir").get()); SameJson(Json(rows), d.at("rows"), "dispositions classify"); const std::vector inForce = Dispositions::InForce(entries, rows); SameJson(Json(inForce), d.at("inForce"), "dispositions in_force"); for (auto it = d.at("blocks").begin(); it != d.at("blocks").end(); ++it) { Same(Dispositions::Block(inForce, rows, it.key(), "H1"), it.value().get(), std::format("dispositions block {}", it.key())); } Same(Dispositions::Block({}, {}, Plan::FunctionalLens, "H9"), d.at("empty").get(), "dispositions block (none)"); const std::vector two(entries.begin() + 1, entries.begin() + 3); SameJson(Json(Dispositions::Classify(two, "H9", fs::path(d.at("dir").get()) / "0001-a.patch")), d.at("rowsFile"), "dispositions classify (a file)"); } void CheckNormalize(const Json& g) { std::int32_t n = 0; for (const Json& entry : g.at("normalize")) { Json v = entry.at("v"); const std::vector inForce(entry.at("inForce").begin(), entry.at("inForce").end()); const std::vector allIds = Strings(entry.at("allIds")); const Json result = Normalize::NormalizeOutput(v, inForce, entry.at("lensFile").get(), entry.at("hash").get(), entry.at("inventory"), allIds); SameJson(result, entry.at("result"), std::format("normalize case {}", n)); SameJson(v, entry.at("vAfter"), std::format("normalize case {}: the output's notes after", n)); ++n; } for (const Json& entry : g.at("resolveId")) { SameJson(Normalize::ResolveId(entry.at(0), Strings(entry.at(1))), entry.at(2), std::format("resolve_id {}", DumpCompactSorted(entry.at(0)))); } n = 0; for (const Json& entry : g.at("merge")) { const std::vector parts(entry.at("parts").begin(), entry.at("parts").end()); SameJson(Normalize::MergeParts(entry.at("lens").get(), entry.at("lensFile").get(), parts, Strings(entry.at("missing")), entry.at("stopped")), entry.at("result"), std::format("merge_parts case {}", n++)); } n = 0; for (const Json& entry : g.at("matrix")) { const std::vector results(entry.at("results").begin(), entry.at("results").end()); SameJson(Normalize::Matrix(entry.at("inventory"), results, Strings(entry.at("notReviewed"))), entry.at("result"), std::format("matrix case {}", n++)); } for (const Json& entry : g.at("dedupe")) { const std::vector items(entry.at(0).begin(), entry.at(0).end()); SameJson(Json(Normalize::Dedupe(items)), entry.at(1), "dedupe"); } } void CheckStream(const Json& g) { const Json& s = g.at("stream"); std::map parsed; for (auto it = s.at("streams").begin(); it != s.at("streams").end(); ++it) { parsed[it.key()] = Stream::ParseStream(it.value().get()); SameJson(parsed[it.key()], s.at("parsed").at(it.key()), std::format("parse_stream {}", it.key())); const std::optional structured = Stream::StructuredOutput(parsed[it.key()].at("result")); SameJson(structured ? *structured : Json(), s.at("structured").at(it.key()), std::format("structured_output {}", it.key())); } for (const Json& entry : s.at("classify")) { const std::optional code = entry.at(1).is_null() ? std::nullopt : std::optional(entry.at(1).get()); const auto [status, reason] = Stream::Classify(parsed.at(entry.at(0).get()), code, entry.at(2).get(), entry.at(3).get()); SameJson(Json::array({status, reason}), entry.at(4), std::format("classify {} {}", entry.at(0).get(), DumpCompactSorted(entry.at(2)))); } for (const Json& entry : s.at("windows")) SameJson(Stream::Windows(entry.at(0)), entry.at(1), std::format("windows {}", DumpCompactSorted(entry.at(0)))); for (const Json& entry : s.at("structuredOf")) { const std::optional structured = Stream::StructuredOutput(entry.at(0)); SameJson(structured ? *structured : Json(), entry.at(1), std::format("structured_output {}", DumpCompactSorted(entry.at(0)))); } for (const Json& entry : g.at("patterns")) { const std::string text = entry.at(0).get(); Check(Stream::LimitText(text) == entry.at(1).get(), std::format("LIMIT_RE {}", Dyn::Repr(text))); Check(Stream::AuthText(text) == entry.at(2).get(), std::format("AUTH_RE {}", Dyn::Repr(text))); Check(Stream::CreditText(text) == entry.at(3).get(), std::format("CREDIT_RE {}", Dyn::Repr(text))); Check(Stream::IsSqlCall(text) == entry.at(4).get(), std::format("SQL_CALL {}", Dyn::Repr(text))); Check(Stream::HasAnsweredLine(text) == entry.at(5).get(), std::format("ANSWERED_LINE {}", Dyn::Repr(text))); } } void CheckState(const Json& g) { const Json& s = g.at("state"); std::int32_t n = 0; for (const Json& entry : s.at("merge")) { SameJson(State::MergeState(entry.at("disk"), entry.at("sets"), Strings(entry.at("drops")), entry.at("read")), entry.at("result"), std::format("merge_state case {}", n++)); } n = 0; for (const Json& entry : s.at("usage")) { const std::vector records(entry.at(0).begin(), entry.at(0).end()); SameJson(State::UsageTotals(records), entry.at(1), std::format("usage_totals case {}", n++)); } for (const Json& entry : s.at("describe")) Same(State::Describe(entry.at(0)), entry.at(1).get(), std::format("_describe {}", DumpCompactSorted(entry.at(0)))); n = 0; for (const Json& entry : s.at("summary")) Same(State::Summary(entry.at(0)), entry.at(1).get(), std::format("summary case {}", n++)); } std::string Spelled(std::string text, const fs::path& repo) { text = Dyn::Replace(text, repo.string(), ""); return Dyn::Replace(text, std::format("`{} slots`", Invocation()), "` slots`"); } void CheckConfig(const Json& g) { const Json& c = g.at("config"); const fs::path scratch = fs::temp_directory_path() / std::format("crafter-review-goldens-{}", Platform::PublishedPid()); for (const Json& entry : c.at("cases")) { const std::string name = entry.at("name").get(); const fs::path repo = scratch / name; fs::remove_all(repo); fs::create_directories(repo); if (!entry.at("body").is_null()) { fs::create_directories(repo / ".review-gates"); std::ofstream(repo / ".review-gates" / "config.json", std::ios::binary) << entry.at("body").get(); } const Config::Loaded loaded = Config::Load(repo); std::vector problems; for (const std::string& problem : loaded.problems) problems.push_back(Spelled(problem, repo)); SameJson(ToJson(problems), entry.at("problems"), std::format("config {}: problems", name)); SameJson(Json::parse(Spelled(DumpFile(Config::Redacted(loaded.config)), repo)), entry.at("config"), std::format("config {}: redacted", name)); const Json& path = Dyn::Get(loaded.config, "_path"); SameJson(path.is_null() ? Json() : Json(Spelled(path.get(), repo)), entry.at("path"), std::format("config {}: _path", name)); Check(Dyn::Has(loaded.config, "_path") == entry.at("hasPath").get(), std::format("config {}: _path present", name)); std::vector warnings; for (const Json& warning : Dyn::Or(Dyn::Get(loaded.config, "_warnings"), Dyn::EmptyList())) warnings.push_back(Spelled(warning.get(), repo)); SameJson(ToJson(warnings), entry.at("warnings"), std::format("config {}: warnings", name)); SameJson(ToJson(Config::LensesOff(loaded.config)), entry.at("lensesOff"), std::format("config {}: lenses_off", name)); SameJson(Config::Environments(loaded.config), entry.at("environments"), std::format("config {}: environments", name)); } { const fs::path repo = scratch / "missing"; fs::create_directories(repo); std::vector problems; for (const std::string& problem : Config::Load(repo, repo / "none.json").problems) problems.push_back(Spelled(problem, repo)); SameJson(ToJson(problems), c.at("missing"), "config --config to a missing file"); } fs::remove_all(scratch); SameJson(Config::Defaults(), c.at("defaults"), "config DEFAULTS"); SameJson(Config::Example(), c.at("example"), "config example()"); SameJson(ToJson(Config::Validate(Config::Example())), c.at("exampleProblems"), "config validate(example())"); for (const Json& entry : c.at("denied")) Check(Tools::Denied(entry.at(0).get()) == entry.at(1).get(), std::format("denied {}", Dyn::Repr(entry.at(0).get()))); const Config::Slots slots = Config::SlotStacks({{"slots", {{"dir", "slots"}}}}, c.at("slotStacks").at("dir").get()); SameJson(slots.stacks, c.at("slotStacks").at("stacks"), "slot_stacks stacks"); SameJson(ToJson(slots.problems), c.at("slotStacks").at("problems"), "slot_stacks problems"); const std::array oneThree = {1, 3}; const std::array one = {1}; const std::array two = {2}; SameJson(ToJson(Config::SlotCommands({{"slots", {{"start", "up {n} {n}"}}}}, "start", oneThree)), c.at("slotCommands").at(0), "slot_commands start"); SameJson(ToJson(Config::SlotCommands({{"slots", Json::object()}}, "stop", one)), c.at("slotCommands").at(1), "slot_commands none"); SameJson(ToJson(Config::SlotCommands(Json::object(), "start", two)), c.at("slotCommands").at(2), "slot_commands no slots"); for (const Json& entry : c.at("checkUrl")) SameJson(Optional(PgWire::CheckUrl(entry.at(0))), entry.at(1), std::format("check_url {}", DumpCompactSorted(entry.at(0)))); for (const Json& entry : c.at("redact")) Same(PgWire::Redact(entry.at(0).get()), entry.at(1).get(), std::format("redact {}", entry.at(0).get())); for (const Json& entry : c.at("where")) Same(PgWire::Where(entry.at(0).get()), entry.at(1).get(), std::format("where {}", entry.at(0).get())); } } int main() { SetHome(fs::current_path()); const Json g = Dyn::LoadJsonFile(fs::current_path() / "tests" / "Goldens" / "fixture" / "pipeline.json"); Same(Schema::SchemaJson("block"), g.at("schema").at("block").get(), "schema block"); Same(Schema::SchemaJson("whole"), g.at("schema").at("whole").get(), "schema whole"); for (auto it = g.at("series").begin(); it != g.at("series").end(); ++it) CheckSeries(it.key(), it.value()); CheckPlan(g); CheckPrompts(g, g.at("series")); CheckDispositions(g); CheckNormalize(g); CheckStream(g); CheckState(g); CheckConfig(g); std::println("{} checks against the goldens", Checks); if (Failures) std::println(std::cerr, "{} failed", Failures); return Failures ? 1 : 0; }