From b090f2b4e166c7f0a031c6af059b8df912da92b5 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Sat, 30 May 2026 09:35:21 +0000 Subject: [PATCH] test(go): golden capture-parity guard for emitGoScopeCaptures (#1848 U1) Pins emitGoScopeCaptures output across all 89 go-* fixtures + a synthetic DAO shape as a committed golden (test/fixtures/go-captures-golden/expected-captures.json), so future drift in the Go scope-capture path fails CI instead of only the coarse perf tripwire. Match-grouped, order-independent sha256 canonicalization; regenerate intentionally with UPDATE_GOLDEN=1. Mirrors test/integration/pipeline-graph-golden.test.ts. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../go-captures-golden/expected-captures.json | 362 ++++++++++++++++++ .../go/go-captures-golden.test.ts | 169 ++++++++ 2 files changed, 531 insertions(+) create mode 100644 gitnexus/test/fixtures/go-captures-golden/expected-captures.json create mode 100644 gitnexus/test/unit/scope-resolution/go/go-captures-golden.test.ts diff --git a/gitnexus/test/fixtures/go-captures-golden/expected-captures.json b/gitnexus/test/fixtures/go-captures-golden/expected-captures.json new file mode 100644 index 000000000..77e5130f4 --- /dev/null +++ b/gitnexus/test/fixtures/go-captures-golden/expected-captures.json @@ -0,0 +1,362 @@ +{ + "go-aliased-package-import/internal/util/log.go": { + "captureGroups": 4, + "digest": "41e9e3d504509d3918723770b7c2d1afe5810484c2315fd6f30e600e05c3b2a5" + }, + "go-aliased-package-import/main.go": { + "captureGroups": 7, + "digest": "bce19f66617eb9db4b0f8ae4fbe25159226b79ee2e6fb84b5958963de4675472" + }, + "go-ambiguous/internal/models/handler.go": { + "captureGroups": 9, + "digest": "7738516d8dc80922530db1eec3663adee74f633d4cabfc243996ae4196909fc4" + }, + "go-ambiguous/internal/other/handler.go": { + "captureGroups": 9, + "digest": "6716caae0d8ee91d1cd6d5cd33c167d93a48d77ba4cf8c8c9b0dbfc8f98aa996" + }, + "go-ambiguous/internal/services/user.go": { + "captureGroups": 8, + "digest": "2692d14c00a6238eb7840d64c7fa73e91c85c13dddf2a584be398a2fab9a7648" + }, + "go-assignment-chain/cmd/main.go": { + "captureGroups": 50, + "digest": "4f74ab86ea15f343d6f82d8413998f7078ca979971067ffbae4c85bce52a41c9" + }, + "go-assignment-chain/models/repo.go": { + "captureGroups": 8, + "digest": "43736fad4b59031e430a18d5f7d20ea5a3fd0306c870d323faef0e27681691e4" + }, + "go-assignment-chain/models/user.go": { + "captureGroups": 8, + "digest": "d9ac10cc3e7a441758616566bcc9138d0e5860922369f674044e888864ce6780" + }, + "go-call-result-binding/cmd/main.go": { + "captureGroups": 16, + "digest": "80014d52ebc6e7a07fdbc1a97e0874617e0286b06d6a5839cffad2cdb2e9a1a0" + }, + "go-call-result-binding/models/user.go": { + "captureGroups": 10, + "digest": "0e997eb91cb5636e6615ff54dd93e5e1337dadab05b8d7bd8223f1c34df9ab1e" + }, + "go-calls/cmd/main.go": { + "captureGroups": 6, + "digest": "7a2555acc08958b7d31a58fac9c85228d4c75e36ece8d7e3fa9c6c9794dd5247" + }, + "go-calls/internal/onearg/log.go": { + "captureGroups": 6, + "digest": "a9c6f0559affaa76a28eff3e50cc3022236f9e4ba46818a20d31400be689faac" + }, + "go-calls/internal/zeroarg/log.go": { + "captureGroups": 5, + "digest": "e2511a692ba392bb121ce02dc4ccb3e48ec5e2a4a5dbd506cd70a6bf93037442" + }, + "go-chain-call/cmd/main.go": { + "captureGroups": 20, + "digest": "0118c2729a2607021c85b7af534d4d10947296349fa80c9913f8cbc3d04005fd" + }, + "go-chain-call/models/repo.go": { + "captureGroups": 10, + "digest": "aedabd86f2a5f79242559be20fcc92235d7803aab603964014900f4aa918d510" + }, + "go-chain-call/models/user.go": { + "captureGroups": 10, + "digest": "0e997eb91cb5636e6615ff54dd93e5e1337dadab05b8d7bd8223f1c34df9ab1e" + }, + "go-child-extends-parent/models/child.go": { + "captureGroups": 3, + "digest": "342eee3d77dff198b7e6141731c916d5826b924aa3da4418b3d6841f98c5d0bb" + }, + "go-child-extends-parent/models/parent.go": { + "captureGroups": 8, + "digest": "39c966e77ff29a77a26b02aba78522f03aee80ea35705712072524637826e9ff" + }, + "go-child-extends-parent/services/app.go": { + "captureGroups": 10, + "digest": "b9df4e73f171ea576808b3008b008bc8e1c8744b7faefbf82386cfdcdfb3071e" + }, + "go-cmd-helper/cmd/server/internal/config/config.go": { + "captureGroups": 5, + "digest": "d57f553c8c3cd3d3fdf3e77399740bb47952b6dc0e3f94ba851f92275d920b1f" + }, + "go-cmd-helper/cmd/server/main.go": { + "captureGroups": 7, + "digest": "e6f35bb4c7e068f9cda8088847446d02a3f13639f1423bb0a26e395edc43b141" + }, + "go-constructor-type-inference/cmd/main.go": { + "captureGroups": 15, + "digest": "14cf51d0863eb826a1465076e63f704598cae82726b13e6441144eb74ca50481" + }, + "go-constructor-type-inference/models/repo.go": { + "captureGroups": 8, + "digest": "43736fad4b59031e430a18d5f7d20ea5a3fd0306c870d323faef0e27681691e4" + }, + "go-constructor-type-inference/models/user.go": { + "captureGroups": 8, + "digest": "d9ac10cc3e7a441758616566bcc9138d0e5860922369f674044e888864ce6780" + }, + "go-deep-field-chain/cmd/main.go": { + "captureGroups": 13, + "digest": "f28677390371c2f8bccef6473e730300d8ee07d270d82e97892e5a664770ede9" + }, + "go-deep-field-chain/models/models.go": { + "captureGroups": 33, + "digest": "80107196afad2d229f5518e433328416c001e9b293efe3c1b8c9c08d4e050ec0" + }, + "go-field-types/cmd/main.go": { + "captureGroups": 9, + "digest": "5f5a502d81c17574a165f27160ca8a1c3371bf877a747eb97d0e73eb89eb4358" + }, + "go-field-types/models/models.go": { + "captureGroups": 22, + "digest": "e295f5dc526c60a3381fb059e12ee168328ca6def6966f6cc7fcc57c2c7d8822" + }, + "go-for-call-expr/cmd/main.go": { + "captureGroups": 27, + "digest": "864bd4e722283e1e401427a40efb1c98da48cc0c47ee6eedcdd0ce1dbded12f0" + }, + "go-for-call-expr/models/repo.go": { + "captureGroups": 14, + "digest": "1627ce670ab6dfb5ac233baa374dcd516bb0e31c7404daa13faecd16287616be" + }, + "go-for-call-expr/models/user.go": { + "captureGroups": 14, + "digest": "0ada592e34bfbb021eef45865deffb7fcac4f79c205d88b2700cf4eaaf2b2a4d" + }, + "go-inc-dec-write-access/main.go": { + "captureGroups": 21, + "digest": "8f996bfae2b96c38aee2c466501ca0f96ee49fe49854e617317cd309a311f163" + }, + "go-local-shadow/cmd/main.go": { + "captureGroups": 12, + "digest": "44ddef75c0e6a573ff3de922a039926801a87ba8191d14b75e92bd40b3e10133" + }, + "go-local-shadow/internal/utils/utils.go": { + "captureGroups": 6, + "digest": "970e265c1246ac54739d78ebd8abc749114e33931e107b00b9c28106d4d09972" + }, + "go-make-builtin/main.go": { + "captureGroups": 18, + "digest": "f89bee010abfed6ee199cbc07ca56f5ce35f6e8967bb0d6619416740f2f75c36" + }, + "go-make-builtin/models.go": { + "captureGroups": 15, + "digest": "7627584e8e3775d2bed1341010c3ee5e81c4ec0104607ccf751ef1374270f5df" + }, + "go-map-range/main.go": { + "captureGroups": 10, + "digest": "665cf8d7d0b70b0ecf088e25ea8509d368611429e7d18ce286fb44537360138e" + }, + "go-map-range/models/repo.go": { + "captureGroups": 9, + "digest": "0a65a8956b22f1079e3bcc7f4f85200e7fdcef18cd955a77c5f3433b21386173" + }, + "go-map-range/models/user.go": { + "captureGroups": 9, + "digest": "cc11974da05b96e917947973f0de3e485a5591f03b991b66fc50556e39009643" + }, + "go-member-calls/cmd/main.go": { + "captureGroups": 11, + "digest": "5619f3584b8848c89783a4e05237c1516c46b1e686836fd25b57cd58c2d1c76c" + }, + "go-member-calls/models/user.go": { + "captureGroups": 8, + "digest": "d9ac10cc3e7a441758616566bcc9138d0e5860922369f674044e888864ce6780" + }, + "go-method-chain-binding/cmd/main.go": { + "captureGroups": 21, + "digest": "44ea12bacafc97d43e5ce77fa1b1eaf7d26340a20c9849e30e640c8db4932b58" + }, + "go-method-chain-binding/models/user.go": { + "captureGroups": 24, + "digest": "038f9a0025fa364341b2e1fb334c7ac7c50292f1afc77cc460bb3d7825603f4d" + }, + "go-method-enrichment/animal.go": { + "captureGroups": 15, + "digest": "63833a10aa3e940b71b79ae24da39162d6d223255dbf2e58fa595551e9faadc1" + }, + "go-method-enrichment/app.go": { + "captureGroups": 15, + "digest": "acdbd60589872f9c3fb4a8c189c857629c6246c8b7b9f3fbfab5e31b3d80f8fd" + }, + "go-mixed-chain/cmd/main.go": { + "captureGroups": 20, + "digest": "ac3d1639382e5247c7613135cd0e3a6788d84c3b5de1a8d4f089f6b7986b0649" + }, + "go-mixed-chain/models/models.go": { + "captureGroups": 41, + "digest": "8428e0a9fcacd0f0ea7448f542e897768701a98a3e92660cecde5d745e4c24fa" + }, + "go-multi-assign/app.go": { + "captureGroups": 16, + "digest": "3a4243a05de378fb2853a74c8e3c3dadf8777178457b3463af58a92bab135f2d" + }, + "go-multi-assign/models.go": { + "captureGroups": 19, + "digest": "b4ed9ce71a440771980d27a7034802f707ae191baac9f4d1d98980c3a223b554" + }, + "go-multi-return-inference/cmd/main.go": { + "captureGroups": 36, + "digest": "601ca464d0f6d9588e616eb414cfee71913f7797e28d4d545ae5e4b18dc5117e" + }, + "go-multi-return-inference/models/repo.go": { + "captureGroups": 10, + "digest": "aedabd86f2a5f79242559be20fcc92235d7803aab603964014900f4aa918d510" + }, + "go-multi-return-inference/models/user.go": { + "captureGroups": 10, + "digest": "0e997eb91cb5636e6615ff54dd93e5e1337dadab05b8d7bd8223f1c34df9ab1e" + }, + "go-new-builtin/main.go": { + "captureGroups": 12, + "digest": "684af689b07fc9f270e872018fb2170070b84fff873791a215156d7e675b095f" + }, + "go-new-builtin/models.go": { + "captureGroups": 17, + "digest": "fbc349c4d2ba2d3a224c96e0ea3dbdb4eaa65850e5889205c42438ded1218f5a" + }, + "go-nullable-receiver/cmd/main.go": { + "captureGroups": 27, + "digest": "a5f635e8241cf718c54b36464756d1978cba7b00619840c6243fa46ed7525bcd" + }, + "go-nullable-receiver/models/repo.go": { + "captureGroups": 8, + "digest": "43736fad4b59031e430a18d5f7d20ea5a3fd0306c870d323faef0e27681691e4" + }, + "go-nullable-receiver/models/user.go": { + "captureGroups": 8, + "digest": "d9ac10cc3e7a441758616566bcc9138d0e5860922369f674044e888864ce6780" + }, + "go-parent-resolution/models/base.go": { + "captureGroups": 8, + "digest": "44b96fd5ae712621d9d66eea953462ade0a8e3271e962a7ae7534d596b82ba81" + }, + "go-parent-resolution/models/user.go": { + "captureGroups": 8, + "digest": "42fb5dfc9887acec38aca169a42618ae5e5a8cb5a98d2b4a3b84c4f6746bc24b" + }, + "go-pkg/cmd/main.go": { + "captureGroups": 14, + "digest": "73bbd5566b0edb9ba0068ea59c6e0198f7f6b5bca7dfd07214aaae8636eaee02" + }, + "go-pkg/internal/auth/service.go": { + "captureGroups": 17, + "digest": "2577136c6df4602c9de06d0c3495ea603b4d2de867bf1724b8367569ac53d832" + }, + "go-pkg/internal/models/admin.go": { + "captureGroups": 13, + "digest": "e9b23d1d0ac8556a288211ecff43571d7b1f6566266e8ed6dc1900511893cb84" + }, + "go-pkg/internal/models/repository.go": { + "captureGroups": 3, + "digest": "23a95d0be76cc6bfdaa1c0cdfb8421917bca77a91376c40bf2c4e9b5e0966313" + }, + "go-pkg/internal/models/user.go": { + "captureGroups": 13, + "digest": "c06c5b45d9c29281f30ad83720469181bcc8266453ee8ea8094059970a0815ab" + }, + "go-pointer-constructor-inference/cmd/main.go": { + "captureGroups": 15, + "digest": "1291eec1a2ccc521c9c8aad46bd3abfd5921920bcc7e1f6745b1e2ec3bb67fce" + }, + "go-pointer-constructor-inference/models/repo.go": { + "captureGroups": 10, + "digest": "aedabd86f2a5f79242559be20fcc92235d7803aab603964014900f4aa918d510" + }, + "go-pointer-constructor-inference/models/user.go": { + "captureGroups": 10, + "digest": "0e997eb91cb5636e6615ff54dd93e5e1337dadab05b8d7bd8223f1c34df9ab1e" + }, + "go-receiver-method-free-call/example.go": { + "captureGroups": 8, + "digest": "f21184b79f193586674f7163bd70c492e8d1ca7bce88ba97587fa6be48658bcc" + }, + "go-receiver-method-free-call/util.go": { + "captureGroups": 4, + "digest": "84d54b4a4d56fc3c0748d15f115586afe82b4b6766f1e69130f3527f54cdf22a" + }, + "go-receiver-resolution/cmd/main.go": { + "captureGroups": 13, + "digest": "3532de5d39d65476ac0399c87f8a07645c3b5f84ed4641f75b5e0bc10926b00f" + }, + "go-receiver-resolution/models/repo.go": { + "captureGroups": 8, + "digest": "43736fad4b59031e430a18d5f7d20ea5a3fd0306c870d323faef0e27681691e4" + }, + "go-receiver-resolution/models/user.go": { + "captureGroups": 8, + "digest": "d9ac10cc3e7a441758616566bcc9138d0e5860922369f674044e888864ce6780" + }, + "go-return-type-inference/cmd/main.go": { + "captureGroups": 39, + "digest": "b84ca4b69d9515078c1b2b365140dd91676ad4b7786fe9f352d38a278ad4ca72" + }, + "go-return-type-inference/models/repo.go": { + "captureGroups": 16, + "digest": "db56aaa73b8b9a3d2cba805c494816198417f81004961125d21a83ecb6a754b1" + }, + "go-return-type-inference/models/user.go": { + "captureGroups": 16, + "digest": "3dde52af7ec836a0574c3b97fed38a3c84de5593ecc47efc61041a9e7c5e0cad" + }, + "go-same-package-factory/main.go": { + "captureGroups": 14, + "digest": "977fa6ea452d0b558307276d510c775f331a9fc21bbbaac93c486e58fede1ad0" + }, + "go-same-package-factory/repo.go": { + "captureGroups": 8, + "digest": "46680b352bb89775342d9c535d1f3808d2a878cfeb26f58b8a094db1a0abec3b" + }, + "go-same-package-factory/user.go": { + "captureGroups": 8, + "digest": "108a5fc6d3fd36704d0ce02787466d48032f2febf2bf84f75ca0d54d3a52974f" + }, + "go-split-method-owner/main.go": { + "captureGroups": 9, + "digest": "972b12a6ab1b5646c86dd164cd36fcc616ca5d082de373f95c572be3f2705144" + }, + "go-split-method-owner/repo.go": { + "captureGroups": 8, + "digest": "46680b352bb89775342d9c535d1f3808d2a878cfeb26f58b8a094db1a0abec3b" + }, + "go-split-method-owner/save.go": { + "captureGroups": 6, + "digest": "00efd15483f889a19ffa8de4950bfe95e14b404b073667d222eb8db8d234cb69" + }, + "go-split-method-owner/user.go": { + "captureGroups": 3, + "digest": "3e5f53d45e44a627864ff3d60310d2c134ff9fa3c424497551437a4c53a514d5" + }, + "go-struct-literals/app.go": { + "captureGroups": 10, + "digest": "79019ba9ec2b101656a6d730fa7e0b47847c32d798ec3a28191e5fc151a513ec" + }, + "go-struct-literals/user.go": { + "captureGroups": 10, + "digest": "a5b426dc7c44adbe266027bdb86458b3a2e3aa09aa5697cf3c0692ab9f431955" + }, + "go-type-assertion/main.go": { + "captureGroups": 11, + "digest": "df8edeab1ead0201660381986f9ee85575cbb5461eb0ba4dc9ae33d761a1b607" + }, + "go-type-assertion/models.go": { + "captureGroups": 17, + "digest": "71c02619d03bc81d2ecc8ee398c0639cab2543940ec007a8457015925ba77faa" + }, + "go-variadic-resolution/cmd/main.go": { + "captureGroups": 6, + "digest": "df08cd1a32778a43233e296021a7a5e040e9e41ec593479a0b6dd2c28e955d2c" + }, + "go-variadic-resolution/internal/logger/logger.go": { + "captureGroups": 4, + "digest": "2e221a3c0b1abc280c91a735b8af8204d481de9b98ef9cabf2ecf32810fc7490" + }, + "go-write-access/main.go": { + "captureGroups": 20, + "digest": "71198584484ae954b34957a3ef2c152386cc7b9fc7b12d0b2c5af2b02f883feb" + }, + "synthetic:dao-20": { + "captureGroups": 481, + "digest": "c02d9baf1c673a9bb2199566453e55d067433507fd494f9e9b6a7a906b6919a9" + } +} diff --git a/gitnexus/test/unit/scope-resolution/go/go-captures-golden.test.ts b/gitnexus/test/unit/scope-resolution/go/go-captures-golden.test.ts new file mode 100644 index 000000000..707a26784 --- /dev/null +++ b/gitnexus/test/unit/scope-resolution/go/go-captures-golden.test.ts @@ -0,0 +1,169 @@ +/** + * Golden capture-parity test for `emitGoScopeCaptures` (issue #1848 follow-up). + * + * Pins the exact capture output of `emitGoScopeCaptures` across the whole + * `test/fixtures/lang-resolution/go-*` corpus plus a synthetic generated-DAO + * source, so any future drift in the Go scope-capture path fails CI rather than + * only being caught by a coarse perf tripwire or pipeline-level resolver tests. + * + * This is a FORWARD-DRIFT guard: it locks in the current (post-#1915, verified) + * output as the baseline. It does not independently re-prove the original + * pre-fix parity — that was established during PR #1915. + * + * Regenerate the golden intentionally with `UPDATE_GOLDEN=1` in the environment. + * + * Per fixture the snapshot stores `{ captureGroups, digest }`: + * - captureGroups: number of capture matches (makes a count change legible) + * - digest: sha256 of a match-grouped, order-independent canonicalization + * (see canonicalize* below). Nothing path/time/id-dependent leaks in. + * + * Pattern: mirrors test/integration/pipeline-graph-golden.test.ts. + */ +import { describe, it, expect } from 'vitest'; +import path from 'path'; +import fs from 'fs'; +import crypto from 'crypto'; +import { emitGoScopeCaptures } from '../../../../src/core/ingestion/languages/go/index.js'; +import type { CaptureMatch } from 'gitnexus-shared'; + +// This test lives at test/unit/scope-resolution/go/, so fixtures are THREE +// levels up (unlike pipeline-graph-golden.test.ts at test/integration/). +const FIXTURE_ROOT = path.resolve(__dirname, '..', '..', '..', 'fixtures', 'lang-resolution'); +const GOLDEN_DIR = path.resolve(__dirname, '..', '..', '..', 'fixtures', 'go-captures-golden'); +const GOLDEN_FILE = path.join(GOLDEN_DIR, 'expected-captures.json'); + +const UPDATE = process.env.UPDATE_GOLDEN === '1'; + +interface FixtureSnapshot { + captureGroups: number; + digest: string; +} +type Snapshot = Record; + +/** + * Canonicalize ONE match. A CaptureMatch is a Record (multiple + * captures per match), so we group by match to preserve match identity: + * build one `tag|text|startLine:startCol-endLine:endCol` string per capture, + * sort them within the match, and join. We deliberately do NOT flatten every + * capture into one global list — that would lose match boundaries. + */ +function canonicalizeMatch(match: CaptureMatch): string { + const parts: string[] = []; + for (const tag of Object.keys(match)) { + const cap = match[tag]!; + const r = cap.range; + parts.push(`${tag}|${cap.text}|${r.startLine}:${r.startCol}-${r.endLine}:${r.endCol}`); + } + parts.sort(); + return parts.join(';'); +} + +/** Order-independent digest of a full capture result (match-grouped). */ +function digestCaptures(matches: readonly CaptureMatch[]): string { + const matchStrings = matches.map(canonicalizeMatch).sort(); + return crypto.createHash('sha256').update(matchStrings.join('\n')).digest('hex'); +} + +function snapshotOf(src: string, filePath: string): FixtureSnapshot { + const matches = emitGoScopeCaptures(src, filePath); + return { captureGroups: matches.length, digest: digestCaptures(matches) }; +} + +/** All `.go` files under `lang-resolution/go-*`, as sorted repo-relative-ish keys. */ +function collectGoFixtures(): { key: string; absPath: string }[] { + const out: { key: string; absPath: string }[] = []; + for (const entry of fs.readdirSync(FIXTURE_ROOT, { withFileTypes: true })) { + if (!entry.isDirectory() || !entry.name.startsWith('go-')) continue; + const stack = [path.join(FIXTURE_ROOT, entry.name)]; + while (stack.length) { + const dir = stack.pop()!; + for (const c of fs.readdirSync(dir, { withFileTypes: true })) { + const p = path.join(dir, c.name); + if (c.isDirectory()) stack.push(p); + else if (c.name.endsWith('.go')) { + out.push({ key: path.relative(FIXTURE_ROOT, p).split(path.sep).join('/'), absPath: p }); + } + } + } + } + out.sort((a, b) => a.key.localeCompare(b.key)); + return out; +} + +/** Small deterministic generated-DAO source — the #1848 shape at correctness scale. */ +function generateDao(entityCount: number): string { + const lines = ['package generated', '']; + for (let i = 0; i < entityCount; i++) { + const n = String(i).padStart(4, '0'); + lines.push( + `type DefUserDao${n} struct {`, + '\tid int64', + '\tname string', + '}', + '', + `func (d *DefUserDao${n}) GetID() int64 { return d.id }`, + `func (d *DefUserDao${n}) SetName(name string) { d.name = name }`, + `func (d *DefUserDao${n}) Validate() error { return nil }`, + '', + ); + } + return lines.join('\n'); +} + +function buildSnapshot(): Snapshot { + const snap: Snapshot = {}; + for (const { key, absPath } of collectGoFixtures()) { + snap[key] = snapshotOf(fs.readFileSync(absPath, 'utf8'), absPath); + } + snap['synthetic:dao-20'] = snapshotOf(generateDao(20), 'zz_generated.def_userdao.go'); + // Stable key order for deterministic JSON serialization. + return Object.fromEntries(Object.keys(snap).sort().map((k) => [k, snap[k]!])); +} + +function formatGolden(snap: Snapshot): string { + return JSON.stringify(snap, null, 2) + '\n'; +} + +describe('Go scope captures — golden parity', () => { + it('matches the committed golden snapshot across all go-* fixtures + DAO shape', () => { + const snapshot = buildSnapshot(); + + if (UPDATE || !fs.existsSync(GOLDEN_FILE)) { + fs.mkdirSync(GOLDEN_DIR, { recursive: true }); + fs.writeFileSync(GOLDEN_FILE, formatGolden(snapshot), 'utf8'); + console.log( + `[go-captures-golden] ${UPDATE ? 'Regenerated' : 'Created'} golden at ${GOLDEN_FILE}`, + ); + return; + } + + const expected: Snapshot = JSON.parse(fs.readFileSync(GOLDEN_FILE, 'utf8')); + expect( + snapshot, + 'emitGoScopeCaptures output drifted from the committed golden. If this drift is intentional, ' + + 'regenerate with UPDATE_GOLDEN=1 npx vitest run test/unit/scope-resolution/go/go-captures-golden.test.ts', + ).toEqual(expected); + }); + + it('produces a deterministic digest across repeated runs', () => { + const src = generateDao(8); + expect(digestCaptures(emitGoScopeCaptures(src, 'a.go'))).toBe( + digestCaptures(emitGoScopeCaptures(src, 'a.go')), + ); + }); + + it('digest is independent of capture-match array order', () => { + const matches = emitGoScopeCaptures(generateDao(6), 'a.go'); + const reversed = [...matches].reverse(); + expect(digestCaptures(reversed)).toBe(digestCaptures(matches)); + }); + + it('records a capture-group count for every fixture and the DAO shape', () => { + const snapshot = buildSnapshot(); + const fixtureKeys = collectGoFixtures().map((f) => f.key); + // Every collected fixture is present in the snapshot. + for (const k of fixtureKeys) expect(snapshot[k]).toBeDefined(); + // The DAO shape (which has symbols) yields a non-empty capture set. + expect(snapshot['synthetic:dao-20']!.captureGroups).toBeGreaterThan(0); + }); +});