diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index c9720462..c4b4390c 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -209,14 +209,16 @@ and is what `internal/scanner/README.md#loader` cites for its figures. loaders. `GOWORK` matters most: inside a workspace a sibling module resolves to its working copy, and missing it synthesizes the sibling empty rather than failing. See `internal/scanner/README.md#loader`. - - `SkipCompiledDependencies` — opt out (default false) of taking dependency - types from the compiler's export data under the go/packages loader. Unset is - the default since v0.36.4 and costs no meaning: a dependency's annotations are - read back after the load, and a declaration the spec needs is fetched at the - lookup that wants it. Set it for cost alone — `go list -export` compiles the - closure, so a cold cache is an order of magnitude slower and writes ~229 MB. - Code that fails to build needs no opt-out: that load is retried from source, - which is what keeps go-swagger#2874 fixed. See + - `CompiledDependencies` — opt in (default false) to taking dependency types + from the compiler's export data under the go/packages loader. Costs no + meaning: a dependency's annotations are read back after the load, and a + declaration the spec needs is fetched at the lookup that wants it. Set it for + cost alone, and only where the build cache is warm — `go list -export` + compiles the closure, so a cold cache is an order of magnitude slower and + writes ~229 MB, which is why the default protects the cold case. Code that + fails to build is safe either way: that load is retried from source, which is + what keeps go-swagger#2874 fixed. Asking for it under a loader that cannot + honour it raises a `scan.compiled-dependencies` Warning. See `internal/scanner/README.md#compiled-dependencies`. - Toolchain-free loading (experimental; see `internal/scanner/README.md#loader`): - `ToolchainFreeLoader` — scan through `internal/packages` instead of diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml index 65393bfc..4430a9c4 100644 --- a/.github/workflows/go-test.yml +++ b/.github/workflows/go-test.yml @@ -15,6 +15,16 @@ jobs: test: uses: go-openapi/ci-workflows/.github/workflows/go-test-monorepo.yml@81c95ab8bb6a3e0aeccc90a7c9e5fc5b398b1244 # v0.6.0 with: + # The suites emit the same document under any loader -- TestLoaderAgreement holds that over + # the whole corpus -- so which one runs is a question about what the run costs, and this + # harness is the shape compiled dependencies were made for: several hundred scans of small + # trees over one dependency closure, compiled once and read back by every scan after the + # first. Measured over the repository, that is 4m06s of CPU against 5m34s, and a runner with + # two cores spends most of the difference on the wall. + # + # A build tag rather than CODESCAN_TEST_LOADER because the shared workflow forwards flags to + # `go test` and not the environment. See internal/scantest for the knob itself. + extra-flags: -tags=testloader_compiled # From v0.5.0 the shared workflow skips the matrix when nothing relevant changed, and # what it watches is go sources. Ours are not the whole input: the integration tests # compare against captured specs under testdata/integration/golden, and a corpus that diff --git a/.github/workflows/loader-conformance.yml b/.github/workflows/loader-conformance.yml new file mode 100644 index 00000000..1125b8b1 --- /dev/null +++ b/.github/workflows/loader-conformance.yml @@ -0,0 +1,62 @@ +# Runs the whole suite once per loader, so a divergence between them is caught as a divergence +# rather than as a puzzle. +# +# The loaders are meant to produce the same document by different means, and the agreement A/B +# asserts exactly that over the fixture corpus. It does not cover the SUITE: a test can depend on how +# its graph was loaded without saying so, which is how one measuring the compiled-dependency +# read-back economy came to fail under the toolchain-free loader, and how four sites wrapped twice in +# the run-wide loader setting passed every ordinary run. +# +# Deliberately none of what the shared go-test workflow does. No OS matrix, no -race, no coverage: on +# this question those add wall clock and variance and answer nothing, and windows in particular +# drowns the signal. One platform, one toolchain, plain `go test`. What it buys is a green tick per +# loader and three durations that can be read side by side. +name: loader conformance + +on: + push: + branches: + - master + + pull_request: + + # On demand, for comparing the three after a change to how a scan loads anything. + workflow_dispatch: + +permissions: + contents: read + +jobs: + loaders: + name: ${{ matrix.loader }} loader + runs-on: ubuntu-latest + # Every loader reports, rather than the first failure hiding the other two: which ones diverge is + # the whole of what this job has to say. + strategy: + fail-fast: false + matrix: + loader: + - source + - compiled + - own + steps: + - + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version: stable + check-latest: true + cache: true + cache-dependency-path: '**/go.sum' + - + name: Run the whole workspace under the ${{ matrix.loader }} loader + # The environment variable rather than the build tag the shared workflow has to use: a + # reusable workflow does not inherit the caller's environment, and this job is our own. It + # also means the variable is exercised somewhere, which the tags otherwise leave untested. + # + # -count=1 because a cached result reports no work and no duration, and the duration is half + # the point. + env: + CODESCAN_TEST_LOADER: ${{ matrix.loader }} + run: go test work -count=1 -timeout=30m ./... diff --git a/.gitignore b/.gitignore index 508f9030..b415a0b8 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,12 @@ internal/exportdata/exportdata.zip /genspec-wasi /loader-benchmark +# and from "go build ./..." run inside a command's own module, which drops the +# binary beside its main package rather than at the repo root +/cmd/genspec/genspec +/cmd/genspec-tui/genspec-tui +/cmd/genspec-wasi/genspec-wasi + # benchmark corpora: the archive is committed, the tree it unpacks to is not internal/benchmarks/testdata/corpus/ diff --git a/cmd/genspec-tui/internal/ux/diagnostics_test.go b/cmd/genspec-tui/internal/ux/diagnostics_test.go index 3ad91de2..0f1e852d 100644 --- a/cmd/genspec-tui/internal/ux/diagnostics_test.go +++ b/cmd/genspec-tui/internal/ux/diagnostics_test.go @@ -253,11 +253,11 @@ func TestDoScanCollectsDiagnostics(t *testing.T) { "types.go": badMaximumFixture, }) - res := scan.Do(&codescan.Options{ + res := scan.Do(testutils.ApplyLoader(&codescan.Options{ WorkDir: dir, Packages: []string{"."}, ScanModels: true, - }, &scan.Profiling{}) + }), &scan.Profiling{}) if res.Err != nil { t.Fatalf("scan should not hard-fail on soft diagnostics: %v", res.Err) diff --git a/cmd/genspec-tui/internal/ux/escape_test.go b/cmd/genspec-tui/internal/ux/escape_test.go index d5a8f82c..9d55c600 100644 --- a/cmd/genspec-tui/internal/ux/escape_test.go +++ b/cmd/genspec-tui/internal/ux/escape_test.go @@ -110,12 +110,12 @@ func TestSpecRender_EscapesControlCharacters(t *testing.T) { "}\n" require.NoError(t, os.WriteFile(filepath.Join(dir, "api.go"), []byte(src), 0o600)) - res := scan.Do(&codescan.Options{ + res := scan.Do(testutils.ApplyLoader(&codescan.Options{ WorkDir: dir, Packages: []string{"./..."}, GOWORK: "off", ScanModels: true, - }, nil) + }), nil) require.NoError(t, res.Err) require.Contains(t, res.JSON, "PWNED", "the crafted text never reached the document, so nothing is being tested") diff --git a/cmd/genspec-tui/internal/ux/fixtures_test.go b/cmd/genspec-tui/internal/ux/fixtures_test.go index 30d8bb42..1d9bfb78 100644 --- a/cmd/genspec-tui/internal/ux/fixtures_test.go +++ b/cmd/genspec-tui/internal/ux/fixtures_test.go @@ -8,6 +8,7 @@ import ( "github.com/go-openapi/codescan" "github.com/go-openapi/codescan/cmd/genspec-tui/internal/index" + "github.com/go-openapi/codescan/cmd/genspec-tui/internal/ux/testutils" "github.com/go-openapi/codescan/internal/parsers/grammar" "github.com/go-openapi/codescan/internal/scanner" ) @@ -31,7 +32,7 @@ func testModel(t *testing.T, opts ...modelOpt) *Model { func testModelIn(t *testing.T, dir string, opts ...modelOpt) *Model { t.Helper() - m := New(Startup{Options: &codescan.Options{WorkDir: dir, Packages: []string{"./..."}}}) + m := New(Startup{Options: testutils.ApplyLoader(&codescan.Options{WorkDir: dir, Packages: []string{"./..."}})}) t.Cleanup(m.Close) m.width, m.height = 80, 24 diff --git a/cmd/genspec-tui/internal/ux/humanize/humanize.go b/cmd/genspec-tui/internal/ux/humanize/humanize.go index 0000de79..7285d6ca 100644 --- a/cmd/genspec-tui/internal/ux/humanize/humanize.go +++ b/cmd/genspec-tui/internal/ux/humanize/humanize.go @@ -62,9 +62,7 @@ func SignedBytes(n int64) string { // Negating MinInt64 overflows. Adding one before the negation keeps it in range, at the cost of a single byte at a // magnitude no heap difference can reach. - // - //nolint:gosec // -(n+1) is non-negative for every n < 0, which is the only branch this is reached on - return "-" + Bytes(uint64(-(n+1))+1) + return "-" + Bytes(uint64(-(n+1))+1) //nolint:gosec // -(n+1) is non-negative for every n < 0, which is the only branch this is reached on } // SignedCount renders a count difference in short form: "+410 k", "-1.2 M", "+37". diff --git a/cmd/genspec-tui/internal/ux/integration_test.go b/cmd/genspec-tui/internal/ux/integration_test.go index 61f16dc6..2c1178ba 100644 --- a/cmd/genspec-tui/internal/ux/integration_test.go +++ b/cmd/genspec-tui/internal/ux/integration_test.go @@ -22,6 +22,7 @@ import ( "github.com/go-openapi/codescan" "github.com/go-openapi/codescan/cmd/genspec-tui/internal/ux/scan" + "github.com/go-openapi/codescan/cmd/genspec-tui/internal/ux/testutils" "github.com/go-openapi/codescan/internal/parsers/grammar" "github.com/go-openapi/codescan/internal/scanner" "github.com/go-openapi/testify/v2/assert" @@ -762,11 +763,11 @@ func scanPetstore(t *testing.T) *Model { t.Helper() petstoreOnce.Do(func() { - petstoreRes = scan.Do(&codescan.Options{ + petstoreRes = scan.Do(testutils.ApplyLoader(&codescan.Options{ WorkDir: fixturesDir(t), Packages: []string{"./goparsing/petstore/..."}, ScanModels: true, - }, &scan.Profiling{}) + }), &scan.Profiling{}) }) res := petstoreRes require.NoError(t, res.Err, "the petstore fixture must scan cleanly") diff --git a/cmd/genspec-tui/internal/ux/options/options.go b/cmd/genspec-tui/internal/ux/options/options.go index 9bc39212..c41dd356 100644 --- a/cmd/genspec-tui/internal/ux/options/options.go +++ b/cmd/genspec-tui/internal/ux/options/options.go @@ -149,8 +149,8 @@ func New(cfg *codescan.Options) Overlay { &cfg.StubStdlib, &dep{&cfg.ToolchainFreeLoader, true, "ToolchainFreeLoader"}, }, { - groupLoading, "SkipCompiledDependencies", "read dep source, not the build cache", - &cfg.SkipCompiledDependencies, &dep{&cfg.ToolchainFreeLoader, false, "ToolchainFreeLoader"}, + groupLoading, "CompiledDependencies", "use the build cache, not dep source", + &cfg.CompiledDependencies, &dep{&cfg.ToolchainFreeLoader, false, "ToolchainFreeLoader"}, }, }, } diff --git a/cmd/genspec-tui/internal/ux/options/options_test.go b/cmd/genspec-tui/internal/ux/options/options_test.go index 2e103ff5..22202a94 100644 --- a/cmd/genspec-tui/internal/ux/options/options_test.go +++ b/cmd/genspec-tui/internal/ux/options/options_test.go @@ -30,7 +30,7 @@ var deliberatelyOmitted = map[string]string{ //nolint:gochecknoglobals // table func newOverlay(t *testing.T, height int) (*Overlay, *codescan.Options) { t.Helper() - cfg := &codescan.Options{WorkDir: t.TempDir(), Packages: []string{"./..."}} + cfg := testutils.ApplyLoader(&codescan.Options{WorkDir: t.TempDir(), Packages: []string{"./..."}}) o := New(cfg) o.SetSize(100, height) diff --git a/cmd/genspec-tui/internal/ux/runstats_test.go b/cmd/genspec-tui/internal/ux/runstats_test.go index 8be8c8bc..5de82120 100644 --- a/cmd/genspec-tui/internal/ux/runstats_test.go +++ b/cmd/genspec-tui/internal/ux/runstats_test.go @@ -84,10 +84,10 @@ func TestRunStats_TakesTheCostFromTheScan(t *testing.T) { func TestRunStats_ProfiledRunNamesWhoSpentIt(t *testing.T) { dir := t.TempDir() - res := scan.Do(&codescan.Options{ + res := scan.Do(testutils.ApplyLoader(&codescan.Options{ WorkDir: fixturesDir(t), Packages: []string{"./goparsing/classification/..."}, - }, &scan.Profiling{Enabled: true, Dir: dir}) + }), &scan.Profiling{Enabled: true, Dir: dir}) require.NoError(t, res.Err) report := res.Profile diff --git a/cmd/genspec-tui/internal/ux/scan/scan_test.go b/cmd/genspec-tui/internal/ux/scan/scan_test.go index b9aed210..50c2ca61 100644 --- a/cmd/genspec-tui/internal/ux/scan/scan_test.go +++ b/cmd/genspec-tui/internal/ux/scan/scan_test.go @@ -8,6 +8,7 @@ package scan import ( "testing" + "github.com/go-openapi/codescan/cmd/genspec-tui/internal/ux/testutils" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -19,7 +20,7 @@ import ( func TestDoLeavesTheCallersOptionsAlone(t *testing.T) { t.Parallel() - cfg := &codescan.Options{WorkDir: t.TempDir(), Packages: []string{"./..."}} + cfg := testutils.ApplyLoader(&codescan.Options{WorkDir: t.TempDir(), Packages: []string{"./..."}}) _ = Do(cfg, nil) @@ -32,7 +33,7 @@ func TestDoLeavesTheCallersOptionsAlone(t *testing.T) { func TestRunSnapshotsTheOptionsBeforeItRuns(t *testing.T) { t.Parallel() - cfg := &codescan.Options{WorkDir: "/nonexistent-at-launch", Packages: []string{"./..."}} + cfg := testutils.ApplyLoader(&codescan.Options{WorkDir: "/nonexistent-at-launch", Packages: []string{"./..."}}) cmd := Run(cfg, nil) diff --git a/cmd/genspec-tui/internal/ux/source_test.go b/cmd/genspec-tui/internal/ux/source_test.go index 5d69bc97..6c54482a 100644 --- a/cmd/genspec-tui/internal/ux/source_test.go +++ b/cmd/genspec-tui/internal/ux/source_test.go @@ -500,10 +500,10 @@ func classificationScan(t *testing.T) scan.ResultMsg { t.Helper() classificationOnce.Do(func() { - classificationRes = scan.Do(&codescan.Options{ + classificationRes = scan.Do(testutils.ApplyLoader(&codescan.Options{ WorkDir: fixturesDir(t), Packages: []string{"./goparsing/classification/..."}, - }, &scan.Profiling{}) + }), &scan.Profiling{}) }) require.NoError(t, classificationRes.Err) require.NotEmpty(t, classificationRes.Diags, "the corpus must produce diagnostics") diff --git a/cmd/genspec-tui/internal/ux/startup_test.go b/cmd/genspec-tui/internal/ux/startup_test.go index 4f3fcc87..362748f4 100644 --- a/cmd/genspec-tui/internal/ux/startup_test.go +++ b/cmd/genspec-tui/internal/ux/startup_test.go @@ -13,13 +13,14 @@ import ( "github.com/go-openapi/codescan" "github.com/go-openapi/codescan/cmd/genspec-tui/internal/ux/scan" + "github.com/go-openapi/codescan/cmd/genspec-tui/internal/ux/testutils" ) // A session that behaves unlike the command line says it should is a session whose configuration file nobody knows // about. It is said once, on the status line, and expires like every other notice. func TestStartup_AnnouncesTheConfigurationFile(t *testing.T) { m := New(Startup{ - Options: &codescan.Options{WorkDir: t.TempDir(), Packages: []string{"./..."}}, + Options: testutils.ApplyLoader(&codescan.Options{WorkDir: t.TempDir(), Packages: []string{"./..."}}), ConfigPath: "/proj/.codescan.yaml", ConfigSet: []string{"workdir", "scan-models"}, }) @@ -33,7 +34,7 @@ func TestStartup_AnnouncesTheConfigurationFile(t *testing.T) { func TestStartup_SaysWhenTheFileDecidedNothing(t *testing.T) { m := New(Startup{ - Options: &codescan.Options{WorkDir: t.TempDir()}, + Options: testutils.ApplyLoader(&codescan.Options{WorkDir: t.TempDir()}), ConfigPath: "/proj/.codescan.yaml", }) t.Cleanup(m.Close) @@ -47,7 +48,7 @@ func TestStartup_SaysWhenTheFileDecidedNothing(t *testing.T) { // One setting is one setting. func TestStartup_CountsOneSettingSingly(t *testing.T) { m := New(Startup{ - Options: &codescan.Options{WorkDir: t.TempDir()}, + Options: testutils.ApplyLoader(&codescan.Options{WorkDir: t.TempDir()}), ConfigPath: "/proj/.codescan.yaml", ConfigSet: []string{"workdir"}, }) @@ -61,7 +62,7 @@ func TestStartup_CountsOneSettingSingly(t *testing.T) { // The ordinary case: no file, nothing said. func TestStartup_SaysNothingWithoutAFile(t *testing.T) { - m := New(Startup{Options: &codescan.Options{WorkDir: t.TempDir()}}) + m := New(Startup{Options: testutils.ApplyLoader(&codescan.Options{WorkDir: t.TempDir()})}) t.Cleanup(m.Close) assert.Nil(t, m.announceConfig()) @@ -72,7 +73,7 @@ func TestStartup_SaysNothingWithoutAFile(t *testing.T) { func TestStartup_CarriesProfilingToTheScans(t *testing.T) { dir := t.TempDir() m := New(Startup{ - Options: &codescan.Options{WorkDir: dir}, + Options: testutils.ApplyLoader(&codescan.Options{WorkDir: dir}), Profiling: &scan.Profiling{Enabled: true, Dir: dir}, }) t.Cleanup(m.Close) diff --git a/cmd/genspec-tui/internal/ux/testutils/utils.go b/cmd/genspec-tui/internal/ux/testutils/utils.go index 051e945a..3b8f7c89 100644 --- a/cmd/genspec-tui/internal/ux/testutils/utils.go +++ b/cmd/genspec-tui/internal/ux/testutils/utils.go @@ -11,6 +11,9 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/go-openapi/testify/v2/require" + + "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/internal/testloader" ) // StripANSI removes the SGR escape sequences lipgloss emits. @@ -41,3 +44,32 @@ func WriteTempGo(t *testing.T, content string) string { return path } + +// ApplyLoader writes the loader this run was asked for onto opts, and returns it for chaining. +// +// The TUI scans through the same library as everything else, so its tests answer to the same +// run-wide setting; see internal/testloader for what selects it and why. +// +// A test that pins a loader must not route through here - there is no telling a field left at its +// zero value from one set on purpose - which is why this refuses one rather than writing over it. +func ApplyLoader(opts *codescan.Options) *codescan.Options { + if opts == nil || opts.FS != nil { + return opts + } + if opts.CompiledDependencies || opts.ToolchainFreeLoader { + panic("testutils: these options already pin a loader, so they must not be routed through " + + "ApplyLoader; drop the call and leave the pin, or drop the pin and keep the call") + } + + switch selected := testloader.Selected(); selected { + case testloader.LoaderCompiled: + opts.CompiledDependencies = true + case testloader.LoaderOwn: + opts.ToolchainFreeLoader = true + case testloader.LoaderSource: + default: + panic("testutils: no rule for loader " + string(selected)) + } + + return opts +} diff --git a/cmd/genspec-tui/internal/ux/watcher/watcher.go b/cmd/genspec-tui/internal/ux/watcher/watcher.go index 23ffd284..c13f190c 100644 --- a/cmd/genspec-tui/internal/ux/watcher/watcher.go +++ b/cmd/genspec-tui/internal/ux/watcher/watcher.go @@ -43,7 +43,8 @@ func (w *Watcher) Events() <-chan struct{} { return w.events } func (w *Watcher) addRecursive(root string) { _ = filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { if err != nil || !d.IsDir() { - return nil //nolint:nilerr // skip unreadable entries, keep walking + //nolint:nilerr // skip unreadable entries, keep walking + return nil } if name := d.Name(); path != root && (strings.HasPrefix(name, ".") || name == "vendor" || name == "node_modules") { return filepath.SkipDir diff --git a/cmd/genspec-tui/main_test.go b/cmd/genspec-tui/main_test.go index 17105644..2aa82cb1 100644 --- a/cmd/genspec-tui/main_test.go +++ b/cmd/genspec-tui/main_test.go @@ -11,6 +11,7 @@ import ( "github.com/go-openapi/codescan/cmd/internal/cliconf" "github.com/go-openapi/codescan/cmd/internal/cliopts" + "github.com/go-openapi/codescan/internal/testloader" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" ) @@ -126,7 +127,11 @@ func exec(t *testing.T, argv ...string) (stdout, stderr string, err error) { t.Helper() var out, errs bytes.Buffer - err = run(argv, &out, &errs) + const maxExtraArgs = 4 + argsWithLoader := make([]string, 0, len(argv)+maxExtraArgs) + argsWithLoader = append(argsWithLoader, testloader.Flags()...) + argsWithLoader = append(argsWithLoader, argv...) + err = run(argsWithLoader, &out, &errs) return out.String(), errs.String(), err } diff --git a/cmd/genspec-wasi/README.md b/cmd/genspec-wasi/README.md index 18938195..3e0def08 100644 --- a/cmd/genspec-wasi/README.md +++ b/cmd/genspec-wasi/README.md @@ -44,10 +44,10 @@ go run ./cmd/genspec-wasi -workdir ./testdata ./goparsing/petstore/... | *(one flag per boolean option)* | | every boolean knob on `codescan.Options` has a flag, named after the field in kebab-case — `-prune-unused-models`, `-ref-aliases`, `-clean-go-doc` … Run `genspec-wasi -h` for the list. The one exception is `ToolchainFreeLoader`, which `-loader` states more fully. | | `-build-tags` | | comma-separated build tags to apply while loading | | `-goos` / `-goarch` | this machine's | the platform the **scanned code** is built for | -| `-loader` | `auto` | `go` runs `go list`; `own` needs no toolchain; `auto` picks `own` wherever the build cannot exec | +| `-loader` | `auto` | here `auto` and `own` are the same thing: a WebAssembly guest can start no subprocess, so `go list` can never run. `go` is refused up front for that reason, rather than failing deep inside the go command | | `-export-data` | | directory or `.zip` of precomputed dependency types (see below) | | `-stub-stdlib` | `false` | synthesize standard-library types instead of reading GOROOT | -| `-skip-compiled-dependencies` | `false` | read every dependency from source rather than taking its types from the compiler's export data. The spec is the same either way; this is much slower on a warm cache and much faster on a cold one, since export data has to be compiled before it exists. **Native builds only** — it needs `-loader=go`, which WebAssembly cannot run | +| `-compiled-dependencies` | `false` | take dependency types from the compiler's export data rather than reading every dependency from source. The spec is the same either way; this is much faster on a warm build cache and much slower on a cold one, since export data has to be compiled before it exists. **Native builds only** — it needs `-loader=go`, which WebAssembly cannot run | | `-format` | `spec` | `spec` writes the document alone; `json` wraps it with diagnostics and provenance (see below) | | `-output` | `-` | where to write the specification | | `-indent` | `true` | indent the emitted JSON | diff --git a/cmd/genspec/README.md b/cmd/genspec/README.md index fb869a4e..a0bd838e 100644 --- a/cmd/genspec/README.md +++ b/cmd/genspec/README.md @@ -148,7 +148,7 @@ specific answer. - **what to build it as** — `-goos`, `-goarch`, `-goflags`, `-gowork`, `-goexperiment`. Each changes what gets compiled, and so what the document says; each is a flag rather than inherited state, so a scan is reproducible -- **how to load it** — `-loader`, `-stub-stdlib`, `-skip-compiled-dependencies` +- **how to load it** — `-loader`, `-stub-stdlib`, `-compiled-dependencies` - **what to emit** — `-scan-models`, `-prune-unused-models`, the alias and `allOf` knobs, `-skip-extensions`, the naming knobs, the doc-comment knobs diff --git a/cmd/genspec/main_test.go b/cmd/genspec/main_test.go index c7c4765c..5d2bdfcc 100644 --- a/cmd/genspec/main_test.go +++ b/cmd/genspec/main_test.go @@ -16,6 +16,7 @@ import ( "github.com/go-openapi/codescan/cmd/genspec/internal/clitest/fixtures" "github.com/go-openapi/codescan/cmd/genspec/internal/sentinel" "github.com/go-openapi/codescan/cmd/internal/cliconf" + "github.com/go-openapi/codescan/internal/testloader" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" ) @@ -132,9 +133,13 @@ func TestRun(t *testing.T) { t.Parallel() _, stderr, err := exec(t, "-workdir", fixtures.Petstore(t), "-color=never", "-no-config", "./...") - require.NoError(t, err) - assert.Empty(t, stderr) + + if loaderHasSomethingToSay() { + assert.Contains(t, stderr, "finished hints=1") + } else { + assert.Empty(t, stderr) + } }) t.Run("should remain silent when quiet", func(t *testing.T) { @@ -344,7 +349,11 @@ tui: _, stderr, err := exec(t, "-config", path, "-color=never", "./...") require.NoError(t, err) - assert.Empty(t, stderr, "reading a configuration file is not news") + if loaderHasSomethingToSay() { + assert.Contains(t, stderr, "finished hints=1") + } else { + assert.Empty(t, stderr, "reading a configuration file is not news") + } }) t.Run("should reject config pointing to another config", func(t *testing.T) { @@ -384,6 +393,17 @@ func TestConfigFileWalkUp(t *testing.T) { /* exec helpers */ /********************************/ +// loaderHasSomethingToSay reports whether the loader this run uses reports a hint of its own on a +// clean scan, which the silence assertions have to allow for. +// +// Both non-default loaders do, for unrelated reasons: compiled dependencies announce that the +// request was met, and the toolchain-free loader says it did not run the cgo tool. Neither is a +// finding about the scanned code, which is what those assertions are really about -- so the +// exception is the loader having spoken, not any particular thing it said. +func loaderHasSomethingToSay() bool { + return testloader.Selected() != testloader.LoaderSource +} + // exec runs the command as the process would, and reports what it wrote and what it returned. // // NOTE: this does not simulate the exit code returned to the shell: you have to check exitStatus() for that. @@ -391,7 +411,11 @@ func exec(t *testing.T, argv ...string) (stdout, stderr string, err error) { t.Helper() var out, errs bytes.Buffer - err = run(argv, &out, &errs) + const maxExtraArgs = 4 + argsWithLoader := make([]string, 0, len(argv)+maxExtraArgs) + argsWithLoader = append(argsWithLoader, testloader.Flags()...) + argsWithLoader = append(argsWithLoader, argv...) + err = run(argsWithLoader, &out, &errs) return out.String(), errs.String(), err } diff --git a/cmd/internal/cliopts/cliopts.go b/cmd/internal/cliopts/cliopts.go index ade5f2ed..20f2c960 100644 --- a/cmd/internal/cliopts/cliopts.go +++ b/cmd/internal/cliopts/cliopts.go @@ -117,11 +117,12 @@ var boolOptions = []option[bool]{ //nolint:gochecknoglobals // the flag table, r func(o *Options) *bool { return &o.StubStdlib }, }, { - sectionLoad, "skip-compiled-dependencies", false, - "read every dependency from source rather than taking its types from the compiler's export\n" + - "data: the spec is the same either way, but this is much slower on a warm cache and much\n" + - "faster on a cold one. Native builds only -- it needs -loader=go, which WebAssembly cannot run", - func(o *Options) *bool { return &o.SkipCompiledDependencies }, + sectionLoad, "compiled-dependencies", false, + "take dependency types from the compiler's export data rather than reading every dependency\n" + + "from source: the spec is the same either way, but this is much faster on a warm build\n" + + "cache and much slower on a cold one, where it also writes hundreds of MB. Native builds\n" + + "only -- it needs -loader=go, which WebAssembly cannot run", + func(o *Options) *bool { return &o.CompiledDependencies }, }, } diff --git a/cmd/internal/cliopts/loader.go b/cmd/internal/cliopts/loader.go index 9e80d275..b26dfe0f 100644 --- a/cmd/internal/cliopts/loader.go +++ b/cmd/internal/cliopts/loader.go @@ -18,7 +18,14 @@ const ( loaderOwn = "own" // codescan's own loader: no toolchain, no subprocess loaderAuto = "auto" // own where there is no exec, go otherwise - loaderHelp = `package loader: "go" runs go list, "own" needs no toolchain, "auto" picks own where there is no exec` + // Spelled out rather than left as "auto picks own where there is no exec", which takes two hops to + // answer the question a reader actually has -- what do I get if I pass nothing? The string is + // printed by the WebAssembly build as well, so it has to be true of both without naming either as + // the normal case. + loaderHelp = `package loader: "auto" (the default) runs go list wherever a subprocess can be` + "\n" + + `started, which is every native build, and uses "own" only where one cannot -- a` + "\n" + + `WebAssembly build. "go" always runs go list; "own" always uses codescan's own loader,` + "\n" + + `which needs neither a toolchain nor a subprocess` ) // resolveLoader reports whether to use codescan's own loader. diff --git a/docs/doc-site/getting-started/usage-as-a-headless-cli.md b/docs/doc-site/getting-started/usage-as-a-headless-cli.md index 323413cd..2d3c8dc6 100644 --- a/docs/doc-site/getting-started/usage-as-a-headless-cli.md +++ b/docs/doc-site/getting-started/usage-as-a-headless-cli.md @@ -188,7 +188,7 @@ file uses as its sections: `-goexperiment`. Each decides what compiles, and so what the document says; each is a flag rather than inherited state, so a scan is reproducible - **how it is read** (`load`) — `-loader`, `-stub-stdlib`, - `-skip-compiled-dependencies` + `-compiled-dependencies` - **what is emitted** (`emit`) — `-scan-models`, `-prune-unused-models`, the alias and `allOf` knobs, `-skip-extensions`, the naming and doc-comment knobs diff --git a/docs/doc-site/getting-started/usage-as-a-tui.md b/docs/doc-site/getting-started/usage-as-a-tui.md index 085c3d6c..1d71fb9f 100644 --- a/docs/doc-site/getting-started/usage-as-a-tui.md +++ b/docs/doc-site/getting-started/usage-as-a-tui.md @@ -78,8 +78,9 @@ A second group settles what gets built, and how it is loaded — the environment | `-goflags` | — | default go command flags, as `GOFLAGS` — `-build-tags` wins over a `-tags` given here | | `-gowork` | search upwards | workspace selection, as `GOWORK`: `off` to ignore a `go.work`, or the path to one | | `-goexperiment` | — | toolchain experiments, as `GOEXPERIMENT` | -| `-loader` | `auto` | `go` runs `go list`; `own` uses codescan's own loader and needs no toolchain; `auto` picks `own` wherever the build cannot exec (experimental) | +| `-loader` | `auto` | `auto` runs `go list`, which is what every native build does, and picks `own` only where no subprocess can be started; `go` always runs `go list`; `own` always uses codescan's own loader, which needs no toolchain (experimental) | | `-stub-stdlib` | `false` | synthesize the standard library instead of reading GOROOT (needs `-loader=own`) | +| `-compiled-dependencies` | `false` | take dependency types from the compiler's export data instead of reading every dependency from source (needs `-loader=go`). Worth having here more than anywhere: a session rescans on every save, so the build cache is warm after the first one | Everything the flags set is also a **live toggle**. Press `o` for the options popup, `space` to flip a row, `Esc` to apply — the spec re-renders on close, which diff --git a/docs/doc-site/maintainers/performance.md b/docs/doc-site/maintainers/performance.md index f9fccd29..5a810fc0 100644 --- a/docs/doc-site/maintainers/performance.md +++ b/docs/doc-site/maintainers/performance.md @@ -41,7 +41,7 @@ xychart-beta title "Time for one scan of kubeapi (warm build cache, seconds)" x-axis ["v0.33.3", "v0.35.1", "current"] y-axis "seconds" 0 --> 8 - bar [7.087, 1.758, 0.970] + bar [7.087, 1.758, 1.506] ``` ```mermaid @@ -49,17 +49,26 @@ xychart-beta title "Memory: total allocated (bars) and peak RSS (line), MB" x-axis ["v0.33.3", "v0.35.1", "current"] y-axis "MB" 0 --> 4800 - bar [4555, 1217, 447] - line [1154, 807, 306] + bar [4555, 1217, 1215] + line [1154, 807, 751] ``` -End to end: **7.3× faster and 10× less allocated**, for the identical document. +End to end, out of the box: **4.7× faster and 3.7× less allocated**, for the +identical document. Either of the loader options in the next section takes it +further — compiled dependencies reach 0.970 s and 447 MB, which is **7.3× faster +and 10× less allocated** than v0.33.3. + +All three bars are the *same* configuration, so the series measures six months of +work and nothing else. That matters because the largest single step available +today is not on this chart at all: it is a flag, and mixing it in here would +credit the calendar for it. The two charts do not tell the same story, and the difference is the interesting part. The step from v0.33.3 to v0.35.1 removes 3.3 GB of *allocation* but only 346 MB of *peak* — the old regexp-based parser allocated and discarded, so the -high-water mark stayed near the floor that the loaded package graph sets. The -step to current lowers that floor instead. +high-water mark stayed near the floor that the loaded package graph sets. Nothing +since has moved that floor by default (−56 MB); moving it is what asking for a +different loader does, and it is worth −395 to −501 MB. ### What each step changed @@ -74,7 +83,9 @@ in reading annotations, and it appears where route and operation bodies do. **v0.35.1 → current — the package loader.** Resolving and type-checking the package graph is the bulk of a scan, and it is now possible to ask for a loader -that reads less of it. That is the next section. +that reads less of it. In the default configuration this stretch bought speed +only — a quarter-second on both corpora, allocation-neutral to within 2 MB. The +memory is all in the asking. That is the next section. ## How each loader option fares @@ -112,19 +123,20 @@ xychart-beta | configuration | option | warm | cold | allocated | peak RSS | build cache it writes | |---|---|---|---|---|---|---| -| source dependencies | `SkipCompiledDependencies` | 1.506 s | 2.208 s | 1215 MB | 751 MB | 7.7 MB | +| source dependencies | *the default* | 1.506 s | 2.208 s | 1215 MB | 751 MB | 7.7 MB | | pure-Go loader | `ToolchainFreeLoader` | 1.331 s | **1.359 s** | 643 MB | 412 MB | **4 KB** | -| compiled dependencies | *the default* | **0.970 s** | 14.511 s | **447 MB** | **306 MB** | 231 MB | +| compiled dependencies | `CompiledDependencies` | **0.970 s** | 14.511 s | **447 MB** | **306 MB** | 231 MB | **No configuration wins both cache states**, which is the whole reason there is a choice to make: - **Compiled dependencies** take dependency types from the compiler instead of - reading their source — 19 packages read from source rather than 296. Fastest and + reading their source — 31 packages read from source rather than 296. Fastest and smallest by a wide margin on a warm cache. On a cold one it must *compile* the - closure before it can read it, so it is 6× slower than reading source and writes - 231 MB of build cache. It is the default; `SkipCompiledDependencies` opts out, - and a CI job regenerating a spec from a clean checkout is what that is for. + closure before it can read it, so it is more than 6× slower than reading + source, and it writes 231 MB of build cache. Opt in where the cache is warm by + construction; it is off by default because a CI job regenerating a spec from a + clean checkout is not. - **The pure-Go loader** never invokes the go command, so there is no metadata to populate and nothing to compile: it writes 4 KB of build cache and **its cold time equals its warm time**. It is the only choice whose cost is predictable, diff --git a/docs/doc-site/usage/options-reference.md b/docs/doc-site/usage/options-reference.md index a30259fc..b21a913a 100644 --- a/docs/doc-site/usage/options-reference.md +++ b/docs/doc-site/usage/options-reference.md @@ -81,17 +81,18 @@ cache, so cold costs what warm costs: about level with the standard loader on a warm cache, roughly 30% faster on a cold one, and the only choice whose cost does not depend on cache state. Usually the right pick for CI. -**Compiled dependencies** — since v0.36.4 the standard loader takes dependency types -from the compiler's export data instead of reading their source. It produces the -same document either way — whatever the spec needs out of a dependency is read at -the moment it is needed — and on a warm build cache it is the fastest by a wide -margin, and several times smaller. +**Compiled dependencies** (`CompiledDependencies`) — the standard loader taking +dependency types from the compiler's export data instead of reading their source. It +produces the same document either way — whatever the spec needs out of a dependency +is read at the moment it is needed — and on a warm build cache it is the fastest by +a wide margin, and several times smaller. It must *compile* the dependency closure rather than type-check it, so on a cold -cache it is an order of magnitude slower and writes a large build cache. That is -what `SkipCompiledDependencies` is for, and CI regenerating a spec from a clean -checkout is the case to reach for it. Code that does not compile is **not** a -reason: such a load is retried from source automatically. +cache it is an order of magnitude slower and writes a large build cache. Reach for +it where the cache is warm by construction — your own machine, a watch loop, a +pipeline that restores its cache — and leave it off where a clean checkout is the +norm, which is what a CI runner usually is. Code that does not compile is **not** a +reason to avoid it: such a load is retried from source automatically. Two further options drop the `GOROOT` requirement altogether, for environments with no Go installation at all — a WASI guest, a browser. `StubStdlib` synthesizes the @@ -119,7 +120,7 @@ whose harness also takes an extra corpus of your own to measure alongside them. | `FS` | `fs.FS` | `nil` | — | — | Read source through a virtual filesystem — an in-memory tree, an uploaded archive, an `embed.FS` — instead of the real one. Implies `ToolchainFreeLoader`, since `go list` can only read the real filesystem. **`FS` is the whole world the scan can read**: dependencies and GOROOT come through it too, absolute paths map by dropping the leading separator, and anything unreachable is synthesized — a valid but quietly thinner spec, announced by `scan.synthesized-import` and `scan.degraded-load`. **Experimental.** | | `StubStdlib` | `bool` | `false` | `-stub-stdlib` | `load` | Synthesize the standard library from the names the code selects, rather than reading GOROOT. Toolchain-free loader only. Identity recognition still works (`time.Time`, `json.RawMessage` are matched on package and name), but a synthesized type has no fields and no method set — so `json.RawMessage` stops rendering as a byte array and nothing is seen to implement `encoding.TextMarshaler`. Trades fidelity for reach, quietly; prefer a full graph where GOROOT is available. **Experimental.** | | `ExportData` | `fs.FS` | `nil` | `-export-data` *(genspec-wasi)* | — | Serve dependencies from pre-computed export data (one `.export` file per package) under the toolchain-free loader. Unlike `StubStdlib` this costs no fidelity, the types being the ones the compiler computed — but it is valid only for the toolchain that produced it, and an uncovered package falls back to source, then to synthesis. The module under scan is never read this way, and neither is a dependency whose source carries annotations or one the spec later needs a declaration from. **Experimental.** | -| `SkipCompiledDependencies` | `bool` | `false` | `-skip-compiled-dependencies` | `load` | Read every dependency from source, instead of taking its types from the compiler's export data under the go/packages loader. Unset (the default since v0.36.4) costs no meaning: a dependency whose source carries annotations is read back after the load, and one that merely *declares* a type the spec carries is read at the lookup that wants it — so a `swagger:strfmt` written in a library still counts, and a model declared in an unannotated dependency keeps its doc comment and its fields. Set it for cost alone: the default is markedly faster on a warm build cache and markedly slower on a cold one, since `go list -export` compiles the closure before it can read it. A closure that does not compile is handled without this — the load falls back to source and raises `scan.compiled-dependencies`. | +| `CompiledDependencies` | `bool` | `false` | `-compiled-dependencies` | `load` | Take dependency types from the compiler's export data instead of reading every dependency from source, under the go/packages loader. It costs no meaning: a dependency whose source carries annotations is read back after the load, and one that merely *declares* a type the spec carries is read at the lookup that wants it — so a `swagger:strfmt` written in a library still counts, and a model declared in an unannotated dependency keeps its doc comment and its fields. Set it for cost alone, and only where the build cache is warm: it is markedly faster warm and markedly slower cold, since `go list -export` compiles the closure before it can read it. A closure that does not compile is handled either way — the load falls back to source and raises `scan.compiled-dependencies`. | {{% notice style="note" %}} The virtual-filesystem and export-data options exist to make a scan possible diff --git a/docs/doc-site/usage/setting-options.md b/docs/doc-site/usage/setting-options.md index 77586593..399f98df 100644 --- a/docs/doc-site/usage/setting-options.md +++ b/docs/doc-site/usage/setting-options.md @@ -51,9 +51,10 @@ Two shapes do not follow from a field name: Naming none scans `./...`; - **`-loader`** takes `go`, `own` or `auto` where the field (`ToolchainFreeLoader`) is a boolean: the useful default is the third answer, - "pick `own` wherever the build cannot exec". `auto` is what lets the same - source build for a WASI guest, which has no process model and so can never run - `go list`. + "whichever one can run here". On a native build that is `go list`, so a stock + run loads exactly as it always did; `auto` picks `own` only where no subprocess + can be started. That is what lets the same source build for a WASI guest, which + has no process model and so can never run `go list`. **Commands may add some extra flags that do not affect the scanning itself**. diff --git a/docs/examples/concepts/decorators/decorators_test.go b/docs/examples/concepts/decorators/decorators_test.go index 8fded88b..21f5de02 100644 --- a/docs/examples/concepts/decorators/decorators_test.go +++ b/docs/examples/concepts/decorators/decorators_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,11 +25,11 @@ func examplesRoot(t *testing.T) string { func scanDecorators(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/decorators"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/concepts/enums/enums_test.go b/docs/examples/concepts/enums/enums_test.go index 03bb8126..605395b8 100644 --- a/docs/examples/concepts/enums/enums_test.go +++ b/docs/examples/concepts/enums/enums_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,11 +25,11 @@ func examplesRoot(t *testing.T) string { func scanEnums(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/enums"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/concepts/examples/examples_test.go b/docs/examples/concepts/examples/examples_test.go index ceddc5c5..24c2da9f 100644 --- a/docs/examples/concepts/examples/examples_test.go +++ b/docs/examples/concepts/examples/examples_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,11 +25,11 @@ func examplesRoot(t *testing.T) string { func scanExamples(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/examples"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/concepts/maps/maps_test.go b/docs/examples/concepts/maps/maps_test.go index 7b1f3b27..6d1b9390 100644 --- a/docs/examples/concepts/maps/maps_test.go +++ b/docs/examples/concepts/maps/maps_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,11 +25,11 @@ func examplesRoot(t *testing.T) string { func scanMaps(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/maps"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/concepts/meta/meta_test.go b/docs/examples/concepts/meta/meta_test.go index b11d75c4..2d0466a6 100644 --- a/docs/examples/concepts/meta/meta_test.go +++ b/docs/examples/concepts/meta/meta_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,10 +25,10 @@ func examplesRoot(t *testing.T) string { func scanMeta(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/meta"}, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/concepts/models/models_test.go b/docs/examples/concepts/models/models_test.go index a17a5094..7192a522 100644 --- a/docs/examples/concepts/models/models_test.go +++ b/docs/examples/concepts/models/models_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -27,11 +28,11 @@ func examplesRoot(t *testing.T) string { func scanModels(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/models"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/concepts/omit/omit_test.go b/docs/examples/concepts/omit/omit_test.go index 40148e79..55f70cdd 100644 --- a/docs/examples/concepts/omit/omit_test.go +++ b/docs/examples/concepts/omit/omit_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -45,10 +46,10 @@ func goldenJSON(t *testing.T, feature string, v any) { // // Regenerate with: UPDATE_GOLDEN=1 go test ./... func TestOmitFragments(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/omit"}, - }) + })) require.NoError(t, err) require.NotNil(t, doc) diff --git a/docs/examples/concepts/polymorphism-nested/nested_test.go b/docs/examples/concepts/polymorphism-nested/nested_test.go index 97d18321..4845a2f5 100644 --- a/docs/examples/concepts/polymorphism-nested/nested_test.go +++ b/docs/examples/concepts/polymorphism-nested/nested_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -27,10 +28,10 @@ func examplesRoot(t *testing.T) string { // response), so every level below it has to be discovered. func scanNested(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/polymorphism-nested"}, - }) + })) require.NoError(t, err) require.NotNil(t, doc) diff --git a/docs/examples/concepts/polymorphism/polymorphism_test.go b/docs/examples/concepts/polymorphism/polymorphism_test.go index 20f4af4c..faa3aed2 100644 --- a/docs/examples/concepts/polymorphism/polymorphism_test.go +++ b/docs/examples/concepts/polymorphism/polymorphism_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -26,11 +27,11 @@ func examplesRoot(t *testing.T) string { func scanPolymorphism(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/polymorphism"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc @@ -47,7 +48,7 @@ func scanReachableOnly(t *testing.T) (*spec.Swagger, []string) { const discoveredSubtype codescan.Code = "scan.discovered-subtype" var hints []string - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/polymorphism"}, OnDiagnostic: func(d codescan.Diagnostic) { @@ -55,7 +56,7 @@ func scanReachableOnly(t *testing.T) (*spec.Swagger, []string) { hints = append(hints, string(d.Code)+": "+d.Message) } }, - }) + })) require.NoError(t, err) require.NotNil(t, doc) sort.Strings(hints) diff --git a/docs/examples/concepts/refoverride/refoverride_test.go b/docs/examples/concepts/refoverride/refoverride_test.go index 968b9b5c..dc376fbc 100644 --- a/docs/examples/concepts/refoverride/refoverride_test.go +++ b/docs/examples/concepts/refoverride/refoverride_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,11 +25,11 @@ func examplesRoot(t *testing.T) string { func scanRefOverride(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/refoverride"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/concepts/routes/routes_test.go b/docs/examples/concepts/routes/routes_test.go index 89ec0f51..9e8099eb 100644 --- a/docs/examples/concepts/routes/routes_test.go +++ b/docs/examples/concepts/routes/routes_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,11 +25,11 @@ func examplesRoot(t *testing.T) string { func scanRoutes(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/routes"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/concepts/security/security_test.go b/docs/examples/concepts/security/security_test.go index 2e7fb33b..4d742fb0 100644 --- a/docs/examples/concepts/security/security_test.go +++ b/docs/examples/concepts/security/security_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -43,10 +44,10 @@ func goldenRaw(t *testing.T, feature string, v any) { // // Regenerate with: UPDATE_GOLDEN=1 go test ./... func TestSecurityInCode(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/security"}, - }) + })) require.NoError(t, err) require.NotNil(t, doc) @@ -95,12 +96,12 @@ func TestSecurityByOverlay(t *testing.T) { var base spec.Swagger require.NoError(t, json.Unmarshal([]byte(baseSpec), &base)) - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/routes"}, ScanModels: true, InputSpec: &base, - }) + })) require.NoError(t, err) require.Contains(t, doc.SecurityDefinitions, "api_key", "schemes come from the base, not the code") require.Len(t, doc.Security, 1, "default requirement comes from the base") diff --git a/docs/examples/concepts/sharedparams/sharedparams_test.go b/docs/examples/concepts/sharedparams/sharedparams_test.go index cc6fe627..bd62ed1a 100644 --- a/docs/examples/concepts/sharedparams/sharedparams_test.go +++ b/docs/examples/concepts/sharedparams/sharedparams_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,10 +25,10 @@ func examplesRoot(t *testing.T) string { func scanSharedParams(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/sharedparams"}, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/concepts/validations/validations_test.go b/docs/examples/concepts/validations/validations_test.go index f67492dd..af3f7f3a 100644 --- a/docs/examples/concepts/validations/validations_test.go +++ b/docs/examples/concepts/validations/validations_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,11 +25,11 @@ func examplesRoot(t *testing.T) string { func scanValidations(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./concepts/validations"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/internal/loadertest/loadertest.go b/docs/examples/internal/loadertest/loadertest.go new file mode 100644 index 00000000..f094a05d --- /dev/null +++ b/docs/examples/internal/loadertest/loadertest.go @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +// Package loadertest lets the runnable examples answer to the run-wide loader setting. +// +// The examples are documentation that happens to be tested, so their own source says nothing about +// how a package graph is fetched - a reader copying one wants the ordinary defaults. Their TESTS are +// part of the same run as everything else, though, and pay the same cost, so the scan each one makes +// goes through here. +// +// See internal/testloader for what selects the loader and why. +package loadertest + +import ( + "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/internal/testloader" +) + +// Apply writes the loader this run was asked for onto opts, and returns it for chaining. +// +// A test that pins a loader must not route through here - there is no telling a field left at its +// zero value from one set on purpose - which is why this refuses one rather than writing over it. +func Apply(opts *codescan.Options) *codescan.Options { + if opts == nil || opts.FS != nil { + return opts + } + if opts.CompiledDependencies || opts.ToolchainFreeLoader { + panic("loadertest: these options already pin a loader, so they must not be routed through " + + "Apply; drop the call and leave the pin, or drop the pin and keep the call") + } + + switch selected := testloader.Selected(); selected { + case testloader.LoaderCompiled: + opts.CompiledDependencies = true + case testloader.LoaderOwn: + opts.ToolchainFreeLoader = true + case testloader.LoaderSource: + default: + panic("loadertest: no rule for loader " + string(selected)) + } + + return opts +} diff --git a/docs/examples/shaping/afterdecl/afterdecl_test.go b/docs/examples/shaping/afterdecl/afterdecl_test.go index 6a65ec83..307cf287 100644 --- a/docs/examples/shaping/afterdecl/afterdecl_test.go +++ b/docs/examples/shaping/afterdecl/afterdecl_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -26,12 +27,12 @@ func examplesRoot(t *testing.T) string { // either off (the control) or on. func scanAfterDecl(t *testing.T, on bool) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/afterdecl"}, ScanModels: true, AfterDeclComments: on, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/aliases/aliases_test.go b/docs/examples/shaping/aliases/aliases_test.go index 4f60902e..d318d96c 100644 --- a/docs/examples/shaping/aliases/aliases_test.go +++ b/docs/examples/shaping/aliases/aliases_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,11 +25,11 @@ func examplesRoot(t *testing.T) string { func scanAliases(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/aliases"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/buildtags/buildtags_test.go b/docs/examples/shaping/buildtags/buildtags_test.go index dcee66f4..103fabe6 100644 --- a/docs/examples/shaping/buildtags/buildtags_test.go +++ b/docs/examples/shaping/buildtags/buildtags_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,12 +25,12 @@ func examplesRoot(t *testing.T) string { func scan(t *testing.T, tags string) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/buildtags"}, ScanModels: true, BuildTags: tags, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/descref/descref_test.go b/docs/examples/shaping/descref/descref_test.go index 3d7f3ee2..449b926b 100644 --- a/docs/examples/shaping/descref/descref_test.go +++ b/docs/examples/shaping/descref/descref_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/require" ) @@ -23,12 +24,12 @@ func examplesRoot(t *testing.T) string { func scan(t *testing.T, descWithRef bool) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/descref"}, ScanModels: true, DescWithRef: descWithRef, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/discovery/discovery_test.go b/docs/examples/shaping/discovery/discovery_test.go index 04502e69..0593c4ef 100644 --- a/docs/examples/shaping/discovery/discovery_test.go +++ b/docs/examples/shaping/discovery/discovery_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,11 +25,11 @@ func examplesRoot(t *testing.T) string { func scanDiscovery(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/discovery"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/embedallof/embedallof_test.go b/docs/examples/shaping/embedallof/embedallof_test.go index d0af90ab..8bc84703 100644 --- a/docs/examples/shaping/embedallof/embedallof_test.go +++ b/docs/examples/shaping/embedallof/embedallof_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -25,12 +26,12 @@ func examplesRoot(t *testing.T) string { // scanEmbedAllOf scans the witness tree with DefaultAllOfForEmbeds off or on. func scanEmbedAllOf(t *testing.T, on bool) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/embedallof/..."}, ScanModels: true, DefaultAllOfForEmbeds: on, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/extensions/extensions_test.go b/docs/examples/shaping/extensions/extensions_test.go index 4b15881c..e02ceed9 100644 --- a/docs/examples/shaping/extensions/extensions_test.go +++ b/docs/examples/shaping/extensions/extensions_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -25,12 +26,12 @@ func examplesRoot(t *testing.T) string { func scan(t *testing.T, skipExtensions bool) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/extensions"}, ScanModels: true, SkipExtensions: skipExtensions, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc @@ -38,13 +39,13 @@ func scan(t *testing.T, skipExtensions bool) *spec.Swagger { func scanEmitXGoType(t *testing.T, skipExtensions bool) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/extensions"}, ScanModels: true, EmitXGoType: true, SkipExtensions: skipExtensions, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/formats/formats_test.go b/docs/examples/shaping/formats/formats_test.go index 7c795c17..c587d54d 100644 --- a/docs/examples/shaping/formats/formats_test.go +++ b/docs/examples/shaping/formats/formats_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,11 +25,11 @@ func examplesRoot(t *testing.T) string { func scanFormats(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/formats"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/genericenvelopes/genericenvelopes_test.go b/docs/examples/shaping/genericenvelopes/genericenvelopes_test.go index f48390ec..f9e07412 100644 --- a/docs/examples/shaping/genericenvelopes/genericenvelopes_test.go +++ b/docs/examples/shaping/genericenvelopes/genericenvelopes_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,11 +25,11 @@ func examplesRoot(t *testing.T) string { func scanGenericEnvelopes(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/genericenvelopes"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/godoclinks/godoclinks_test.go b/docs/examples/shaping/godoclinks/godoclinks_test.go index 3317a474..1fe50817 100644 --- a/docs/examples/shaping/godoclinks/godoclinks_test.go +++ b/docs/examples/shaping/godoclinks/godoclinks_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -26,12 +27,12 @@ func examplesRoot(t *testing.T) string { // subpackage) with CleanGoDoc off or on. func scanGodocLinks(t *testing.T, on bool) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/godoclinks/..."}, ScanModels: true, CleanGoDoc: on, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/inlineresponses/inlineresponses_test.go b/docs/examples/shaping/inlineresponses/inlineresponses_test.go index 3053ca21..64471482 100644 --- a/docs/examples/shaping/inlineresponses/inlineresponses_test.go +++ b/docs/examples/shaping/inlineresponses/inlineresponses_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,11 +25,11 @@ func examplesRoot(t *testing.T) string { func scanInlineResponses(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/inlineresponses"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/interfacenames/interfacenames_test.go b/docs/examples/shaping/interfacenames/interfacenames_test.go index b4cc0350..83b51c4f 100644 --- a/docs/examples/shaping/interfacenames/interfacenames_test.go +++ b/docs/examples/shaping/interfacenames/interfacenames_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -25,12 +26,12 @@ func examplesRoot(t *testing.T) string { // scanInterfaceNames scans the witness with SkipJSONifyInterfaceMethods off or on. func scanInterfaceNames(t *testing.T, skip bool) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/interfacenames/..."}, ScanModels: true, SkipJSONifyInterfaceMethods: skip, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/markdowndesc/markdowndesc_test.go b/docs/examples/shaping/markdowndesc/markdowndesc_test.go index 5e4b78c8..e83b39f5 100644 --- a/docs/examples/shaping/markdowndesc/markdowndesc_test.go +++ b/docs/examples/shaping/markdowndesc/markdowndesc_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -27,11 +28,11 @@ func examplesRoot(t *testing.T) string { // marker is annotation syntax, opt-in per annotation). func scanMarkdownDesc(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/markdowndesc/..."}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/nameconflicts/nameconflicts_test.go b/docs/examples/shaping/nameconflicts/nameconflicts_test.go index 4b0beb9e..71d81646 100644 --- a/docs/examples/shaping/nameconflicts/nameconflicts_test.go +++ b/docs/examples/shaping/nameconflicts/nameconflicts_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -27,11 +28,11 @@ func examplesRoot(t *testing.T) string { // reachable model set, not of discovery order. func scanTree(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/nameconflicts/..."}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/naming-from-tags/naming_test.go b/docs/examples/shaping/naming-from-tags/naming_test.go index 89a8db53..527ce84f 100644 --- a/docs/examples/shaping/naming-from-tags/naming_test.go +++ b/docs/examples/shaping/naming-from-tags/naming_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,12 +25,12 @@ func examplesRoot(t *testing.T) string { func scan(t *testing.T, nameTags []string) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/naming-from-tags"}, ScanModels: true, NameFromTags: nameTags, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/nullable/nullable_test.go b/docs/examples/shaping/nullable/nullable_test.go index 5dbcf4b0..ac6c86aa 100644 --- a/docs/examples/shaping/nullable/nullable_test.go +++ b/docs/examples/shaping/nullable/nullable_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,12 +25,12 @@ func examplesRoot(t *testing.T) string { func scan(t *testing.T, xNullable bool) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/nullable"}, ScanModels: true, SetXNullableForPointers: xNullable, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/overlay/overlay_test.go b/docs/examples/shaping/overlay/overlay_test.go index bc14f698..98630611 100644 --- a/docs/examples/shaping/overlay/overlay_test.go +++ b/docs/examples/shaping/overlay/overlay_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -60,12 +61,12 @@ func TestOverlay(t *testing.T) { require.NoError(t, json.Unmarshal([]byte(baseSpec), &base)) writeGolden(t, "base", &base) - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/overlay"}, ScanModels: true, InputSpec: &base, - }) + })) require.NoError(t, err) require.NotNil(t, doc) writeGolden(t, "merged", doc) diff --git a/docs/examples/shaping/overrides/overrides_test.go b/docs/examples/shaping/overrides/overrides_test.go index 3afc5ada..7a43c73e 100644 --- a/docs/examples/shaping/overrides/overrides_test.go +++ b/docs/examples/shaping/overrides/overrides_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -33,7 +34,7 @@ func examplesRoot(t *testing.T) string { // collects the scan diagnostics. func scanOverrides(t *testing.T, emitRefSiblings bool, diags *[]codescan.Diagnostic) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/overrides"}, ScanModels: true, @@ -43,7 +44,7 @@ func scanOverrides(t *testing.T, emitRefSiblings bool, diags *[]codescan.Diagnos *diags = append(*diags, d) } }, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc @@ -134,13 +135,13 @@ func TestOverrides(t *testing.T) { // the swagger:title context error (OAS2 responses and headers have no title). func TestOverrides_Responses(t *testing.T) { var diags []codescan.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/overrides"}, OnDiagnostic: func(d codescan.Diagnostic) { diags = append(diags, d) }, - }) + })) require.NoError(t, err) require.NotNil(t, doc) diff --git a/docs/examples/shaping/singleline/singleline_test.go b/docs/examples/shaping/singleline/singleline_test.go index b108bac8..d64f3de6 100644 --- a/docs/examples/shaping/singleline/singleline_test.go +++ b/docs/examples/shaping/singleline/singleline_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,12 +25,12 @@ func examplesRoot(t *testing.T) string { func scan(t *testing.T, singleLineAsDesc bool) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/singleline"}, ScanModels: true, SingleLineCommentAsDescription: singleLineAsDesc, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/docs/examples/shaping/streams/streams_test.go b/docs/examples/shaping/streams/streams_test.go index 076a5a85..996fde8b 100644 --- a/docs/examples/shaping/streams/streams_test.go +++ b/docs/examples/shaping/streams/streams_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/docs/examples/internal/loadertest" "github.com/go-openapi/spec" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" @@ -24,11 +25,11 @@ func examplesRoot(t *testing.T) string { func scanStreams(t *testing.T) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := codescan.Run(loadertest.Apply(&codescan.Options{ WorkDir: examplesRoot(t), Packages: []string{"./shaping/streams"}, ScanModels: true, - }) + })) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/internal/benchmarks/README.md b/internal/benchmarks/README.md index cf4c2ff3..babdc676 100644 --- a/internal/benchmarks/README.md +++ b/internal/benchmarks/README.md @@ -39,21 +39,21 @@ Three configurations of the working tree, the same document out of each. | configuration | wall | peak RSS | allocated | |---|---|---|---| -| **dockerctl** — source dependencies | 0.972 s | 679 MB | 1103 MB | +| **dockerctl** — source dependencies *(the default)* | 0.972 s | 679 MB | 1103 MB | | pure-Go loader (`ToolchainFreeLoader`) | 1.134 s | **403 MB** | **589 MB** | -| compiled dependencies *(the default)* | **0.438 s** | **170 MB** | **237 MB** | -| **kubeapi** — source dependencies | 1.506 s | 751 MB | 1215 MB | +| compiled dependencies | **0.438 s** | **170 MB** | **237 MB** | +| **kubeapi** — source dependencies *(the default)* | 1.506 s | 751 MB | 1215 MB | | pure-Go loader (`ToolchainFreeLoader`) | 1.331 s | 412 MB | 643 MB | -| compiled dependencies *(the default)* | **0.970 s** | **306 MB** | **447 MB** | +| compiled dependencies | **0.970 s** | **306 MB** | **447 MB** | ### Cold build cache (n=1, private empty `GOCACHE`) | configuration | wall | build cache it writes | |---|---|---| -| **dockerctl** — source dependencies | 1.490 s | 7.5 MB | +| **dockerctl** — source dependencies *(the default)* | 1.490 s | 7.5 MB | | pure-Go loader | **1.198 s** | **4 KB** | | compiled dependencies | 9.361 s | 216 MB | -| **kubeapi** — source dependencies | 2.208 s | 7.7 MB | +| **kubeapi** — source dependencies *(the default)* | 2.208 s | 7.7 MB | | pure-Go loader | **1.359 s** | **4 KB** | | compiled dependencies | 14.511 s | 231 MB | @@ -63,10 +63,11 @@ Memory does not depend on cache state and is omitted here; it matches the warm f **Compiled dependencies win warm and lose cold, by a lot.** Taking dependency types from the compiler instead of their source is 35–55% faster and 2.5–4× smaller on a warm cache. On a cold one -it is **6× slower** than reading source, because `go list -export` must *compile* the closure rather -than type-check it, and it materialises up to 231 MB of build cache doing so. It is the default -since v0.36.4, and `SkipCompiledDependencies` is the opt-out; CI regenerating a spec from a clean -checkout is the case that wants it. +it is **more than 6× slower** than reading source (6.3× and 6.6× on the two corpora), because +`go list -export` must *compile* the closure rather +than type-check it, and it materialises up to 231 MB of build cache doing so. `CompiledDependencies` +opts in, and a build cache that is warm by construction — a developer's machine, a watch loop, a +pipeline that restores its cache — is the case that wants it. **The pure-Go loader is the only one whose cost is predictable.** It writes 4 KB of build cache and its cold time equals its warm time, because it never invokes the go command — there is no metadata @@ -83,6 +84,15 @@ corpus-dependent: *slower* than the standard loader warm on the smaller client, larger server. Scale decides whether the wall-clock deficit survives; on the larger corpus it does not. +**Which is why the default is the standard loader reading dependencies from source.** No +configuration wins everywhere, so the default is the one with no bad case: it is never an order of +magnitude off, it writes 7 MB of build cache rather than 231, and it does not care whether the cache +was there. The two better answers are both conditional, and each is one flag away — compiled +dependencies where the cache is warm by construction, and the pure-Go loader where memory is the +binding constraint or the cost has to be predictable. The pure-Go loader is the better trade of the +two on these figures and would be the natural default; it remains experimental, which is the only +reason it is not. + **Where the memory goes.** The load ladders (`bench_loaders_test.go`) report the shape behind those figures — how much of the closure ends up as parsed syntax: @@ -124,11 +134,13 @@ own throwaway module so it links the released library rather than this checkout. |---|---|---|---|---| | v0.33.3 | 1.593 s | 1316 MB | 7.087 s | 4555 MB | | v0.35.1 | 1.226 s | 1103 MB | 1.758 s | 1217 MB | -| current, source dependencies | 0.972 s | 1103 MB | 1.506 s | 1215 MB | -| current, compiled dependencies *(default)* | **0.438 s** | **237 MB** | **0.970 s** | **447 MB** | +| current, source dependencies *(default)* | 0.972 s | 1103 MB | 1.506 s | 1215 MB | +| current, compiled dependencies | **0.438 s** | **237 MB** | **0.970 s** | **447 MB** | -End to end, six months moved the larger corpus from 7.1 s / 4555 MB to 0.97 s / 447 MB — **7.3× -faster, 10× less allocated** — for the identical 222 definitions and 260 paths. +End to end, six months moved the larger corpus from 7.1 s / 4555 MB to 1.51 s / 1215 MB in the +default configuration — **4.7× faster, 3.7× less allocated** — and to 0.97 s / 447 MB with compiled +dependencies asked for, which is **7.3× faster, 10× less allocated**. Every row emits the identical +222 definitions and 260 paths. ### The two halves moved for different reasons, on different corpora @@ -198,7 +210,7 @@ comes from *asking* for a loader that prunes. ### What a scan is made of today -Per phase, in the default configuration, warm: +Per phase, warm, with compiled dependencies — the configuration the loader work was aimed at: | corpus | Load | Build | Build's share | |---|---|---|---| @@ -292,6 +304,6 @@ with the commands above. too. - **A version probe refuses options younger than itself.** `options_baseline.go` accepts the same flags as the current build and *exits* rather than ignoring them — a probe that silently accepted - `-compiled-deps` would report a default-configuration measurement under a label saying otherwise, - the one measurement error that cannot be spotted in the output. + `-compiled-deps` would report a source-dependency measurement under a label saying otherwise, the + one measurement error that cannot be spotted in the output. - **Peak RSS is read from `/proc`**, so that column reads zero off Linux. diff --git a/internal/benchmarks/loader-benchmark/options_current.go b/internal/benchmarks/loader-benchmark/options_current.go index 216ba881..827652cd 100644 --- a/internal/benchmarks/loader-benchmark/options_current.go +++ b/internal/benchmarks/loader-benchmark/options_current.go @@ -32,11 +32,10 @@ func (e *extraOptions) register() { // apply sets the configuration this run measures. // -// -compiled-deps keeps its meaning across both builds — "take dependency types from compiled export -// data" — even though the field behind it inverted when that became the default. So the flag selects a -// configuration rather than reporting the library's default, and a run without it measures the opt-out -// rather than what a caller gets out of the box. +// -compiled-deps keeps its meaning across every build measured — "take dependency types from compiled +// export data" — whichever way the field behind it was spelled at that release. So the flag selects a +// configuration rather than reporting the library's default. func (e *extraOptions) apply(opts *codescan.Options) { - opts.SkipCompiledDependencies = !e.compiledDependencies + opts.CompiledDependencies = e.compiledDependencies opts.ToolchainFreeLoader = e.toolchainFreeLoader } diff --git a/internal/benchmarks/loader-benchmark/run.sh b/internal/benchmarks/loader-benchmark/run.sh index 177e7831..3719d711 100755 --- a/internal/benchmarks/loader-benchmark/run.sh +++ b/internal/benchmarks/loader-benchmark/run.sh @@ -87,9 +87,9 @@ done current="$work/probe-current" # The working tree, in the configurations worth telling apart. `current` is the source-loading scan, -# which is what SkipCompiledDependencies selects today; `current+compiled-deps` is what a plain run -# does since v0.36.4. Rows are labelled by configuration rather than by default, so they stay -# comparable across a release that moves the default. +# which is what a plain run does; `current+compiled-deps` opts into export data. Rows are labelled by +# configuration rather than by default, so they stay comparable across a release that moves the +# default -- and one has moved. configs+=( "current:$current:" "current+toolchain-free:$current:-toolchain-free" diff --git a/internal/builders/operations/operations_go119_test.go b/internal/builders/operations/operations_go119_test.go index fa6f9c84..b249583a 100644 --- a/internal/builders/operations/operations_go119_test.go +++ b/internal/builders/operations/operations_go119_test.go @@ -15,12 +15,12 @@ import ( ) func TestIndentedYAMLBlock(t *testing.T) { - sctx, err := scanner.NewScanCtx(&scanner.Options{ + sctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{ "./goparsing/go119", }, WorkDir: scantest.FixturesDir(), - }) + })) require.NoError(t, err) var ops spec.Paths @@ -76,3 +76,7 @@ curl -u "${LOGIN}:${PASSWORD}" -d '{"key2": "value2"}' -X POST "https://{host} assert.MapContainsT(t, sample2, "source") assert.Equal(t, expectedSource, sample2["source"]) } + +func applyLoader(opts *scanner.Options) *scanner.Options { + return scantest.ApplyLoader(opts) +} diff --git a/internal/builders/operations/operations_test.go b/internal/builders/operations/operations_test.go index 9b23e0e7..bc54375b 100644 --- a/internal/builders/operations/operations_test.go +++ b/internal/builders/operations/operations_test.go @@ -16,7 +16,7 @@ import ( ) func TestOperationsParser(t *testing.T) { - sctx, err := scanner.NewScanCtx(&scanner.Options{ + sctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{ "./goparsing/classification", "./goparsing/classification/models", @@ -24,7 +24,7 @@ func TestOperationsParser(t *testing.T) { "./goparsing/classification/operations_annotation", }, WorkDir: scantest.FixturesDir(), - }) + })) require.NoError(t, err) var ops spec.Paths for apiPath := range sctx.Operations() { diff --git a/internal/builders/parameters/parameters_test.go b/internal/builders/parameters/parameters_test.go index a698271e..14a3e22a 100644 --- a/internal/builders/parameters/parameters_test.go +++ b/internal/builders/parameters/parameters_test.go @@ -121,7 +121,7 @@ func TestParamsParser_OptionVariants(t *testing.T) { } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - sctx, err := scanner.NewScanCtx(&scanner.Options{ + sctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{ "./goparsing/classification", "./goparsing/classification/models", @@ -130,7 +130,7 @@ func TestParamsParser_OptionVariants(t *testing.T) { WorkDir: scantest.FixturesDir(), SkipExtensions: tc.skipExt, DescWithRef: tc.descRef, - }) + })) require.NoError(t, err) operations := make(map[string]*oaispec.Operation) paramNames := []string{ @@ -574,12 +574,12 @@ func assertSomeAliasOperationParams(t *testing.T, operations map[string]*oaispec } func TestParamsParser_TransparentAliases(t *testing.T) { - sctx, err := scanner.NewScanCtx(&scanner.Options{ + sctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{"./goparsing/transparentalias"}, WorkDir: scantest.FixturesDir(), TransparentAliases: true, ScanModels: true, - }) + })) require.NoError(t, err) td := getParameter(sctx, "TransparentAliasParams") @@ -678,3 +678,7 @@ func TestGo118ParameterParser_Issue2011(t *testing.T) { sch := param.Schema require.NotNil(t, sch) } + +func applyLoader(opts *scanner.Options) *scanner.Options { + return scantest.ApplyLoader(opts) +} diff --git a/internal/builders/responses/responses_test.go b/internal/builders/responses/responses_test.go index 9ae59175..b9adcb4b 100644 --- a/internal/builders/responses/responses_test.go +++ b/internal/builders/responses/responses_test.go @@ -39,7 +39,7 @@ func TestParseResponses_OptionVariants(t *testing.T) { const petRef = "#/definitions/github.com/go-openapi/codescan/testdata/goparsing/classification/transitive/mods/pet" for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - sctx, err := scanner.NewScanCtx(&scanner.Options{ + sctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{ "./goparsing/classification", "./goparsing/classification/models", @@ -48,7 +48,7 @@ func TestParseResponses_OptionVariants(t *testing.T) { WorkDir: scantest.FixturesDir(), SkipExtensions: tc.skipExt, DescWithRef: tc.descRef, - }) + })) require.NoError(t, err) responses := make(map[string]spec.Response) responseNames := []string{ @@ -358,12 +358,12 @@ func assertSomeResponseHeaders(t *testing.T, responses map[string]spec.Response) } func TestParseResponses_TransparentAliases(t *testing.T) { - sctx, err := scanner.NewScanCtx(&scanner.Options{ + sctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{"./goparsing/transparentalias"}, WorkDir: scantest.FixturesDir(), TransparentAliases: true, ScanModels: true, - }) + })) require.NoError(t, err) td := getResponse(sctx, "TransparentAliasResponse") @@ -429,10 +429,10 @@ func TestParseResponses_Issue2011(t *testing.T) { } func TestParseResponses_Issue2145(t *testing.T) { - sctx, err := scanner.NewScanCtx(&scanner.Options{ + sctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{"./goparsing/product/..."}, WorkDir: scantest.FixturesDir(), - }) + })) require.NoError(t, err) responses := make(map[string]spec.Response) td := getResponse(sctx, "GetProductsResponse") @@ -481,3 +481,7 @@ func TestGo118ParseResponses_Issue2011(t *testing.T) { assert.Empty(t, resp.Schema.Ref.String()) assert.Empty(t, resp.Schema.Properties) } + +func applyLoader(opts *scanner.Options) *scanner.Options { + return scantest.ApplyLoader(opts) +} diff --git a/internal/builders/routes/routes_test.go b/internal/builders/routes/routes_test.go index 8e88bc4b..f57ecd20 100644 --- a/internal/builders/routes/routes_test.go +++ b/internal/builders/routes/routes_test.go @@ -139,7 +139,7 @@ func TestRoutesParser(t *testing.T) { } func TestRoutesParserBody(t *testing.T) { - sctx, err := scanner.NewScanCtx(&scanner.Options{ + sctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{ "./goparsing/classification", "./goparsing/classification/models", @@ -147,7 +147,7 @@ func TestRoutesParserBody(t *testing.T) { "./goparsing/classification/operations_body", }, WorkDir: scantest.FixturesDir(), - }) + })) require.NoError(t, err) var ops oaispec.Paths for apiPath := range sctx.Routes() { @@ -419,3 +419,7 @@ func assertOperationBody(t *testing.T, op *oaispec.Operation, id, summary, descr assert.Empty(t, rsp.Ref.String()) assert.EqualT(t, "#/definitions/validationError", rsp.Schema.Ref.String()) } + +func applyLoader(opts *scanner.Options) *scanner.Options { + return scantest.ApplyLoader(opts) +} diff --git a/internal/builders/schema/schema_test.go b/internal/builders/schema/schema_test.go index 80def15c..c2a985e1 100644 --- a/internal/builders/schema/schema_test.go +++ b/internal/builders/schema/schema_test.go @@ -761,14 +761,14 @@ func TestAliasedModels(t *testing.T) { func TestAliasedTopLevelModels(t *testing.T) { t.Run("with options: no scan models, with aliases as ref", func(t *testing.T) { t.Run("with goparsing/spec", func(t *testing.T) { - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{ "./goparsing/spec", }, WorkDir: scantest.FixturesDir(), ScanModels: false, RefAliases: true, - }) + })) require.NoError(t, err) t.Run("should find User definition in source", func(t *testing.T) { @@ -834,14 +834,14 @@ func TestAliasedTopLevelModels(t *testing.T) { t.Run("with options: no scan models, without aliases as ref", func(t *testing.T) { t.Run("with goparsing/spec", func(t *testing.T) { - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{ "./goparsing/spec", }, WorkDir: scantest.FixturesDir(), ScanModels: false, RefAliases: false, - }) + })) require.NoError(t, err) t.Run("should find User definition in source", func(t *testing.T) { @@ -940,7 +940,11 @@ func TestPointersAreNullableByDefaultWhenSetXNullableForPointersIsSet(t *testing packagePattern := "./enhancements/pointers-nullable-by-default" packagePath := fixturesModule + "/enhancements/pointers-nullable-by-default" - ctx, err := scanner.NewScanCtx(&scanner.Options{Packages: []string{packagePattern}, WorkDir: scantest.FixturesDir(), SetXNullableForPointers: true}) + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ + Packages: []string{packagePattern}, + WorkDir: scantest.FixturesDir(), + SetXNullableForPointers: true, + })) require.NoError(t, err) assertModel(ctx, packagePath, "Item") @@ -986,7 +990,10 @@ func TestPointersAreNotNullableByDefaultWhenSetXNullableForPointersIsNotSet(t *t packagePattern := "./enhancements/pointers-nullable-by-default" packagePath := fixturesModule + "/enhancements/pointers-nullable-by-default" - ctx, err := scanner.NewScanCtx(&scanner.Options{Packages: []string{packagePattern}, WorkDir: scantest.FixturesDir()}) + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ + Packages: []string{packagePattern}, + WorkDir: scantest.FixturesDir(), + })) require.NoError(t, err) assertModel(ctx, packagePath, "Item") @@ -1305,13 +1312,13 @@ func TestBuilder_DiagnosticsOnInvalidNumeric(t *testing.T) { packagePath := fixturesModule + "/enhancements/diagnostics" var collected []grammar.Diagnostic - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{packagePattern}, WorkDir: scantest.FixturesDir(), OnDiagnostic: func(d grammar.Diagnostic) { collected = append(collected, d) }, - }) + })) require.NoError(t, err) decl, _ := ctx.FindDecl(packagePath, "BadMaximum") @@ -1375,10 +1382,10 @@ func TestBuilder_DiagnosticsOnAmbiguousEmbed(t *testing.T) { build := func(t *testing.T, name string) *Builder { t.Helper() - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{packagePattern}, WorkDir: scantest.FixturesDir(), - }) + })) require.NoError(t, err) decl, _ := ctx.FindDecl(packagePath, name) @@ -1437,10 +1444,10 @@ func TestBuilder_DiagnosticsOnAmbiguousEmbed(t *testing.T) { func TestEmbeddedDescriptionAndTags(t *testing.T) { packagePattern := "./" + fixtureMinimal3125 packagePath := fixturesModule + "/" + fixtureMinimal3125 - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{packagePattern}, WorkDir: scantest.FixturesDir(), - }) + })) require.NoError(t, err) decl, _ := ctx.FindDecl(packagePath, "Item") require.NotNil(t, decl) @@ -1508,12 +1515,12 @@ func TestEmbeddedDescriptionAndTags_OptionVariants(t *testing.T) { } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{"./" + fixtureMinimal3125}, WorkDir: scantest.FixturesDir(), SkipExtensions: tc.skipExt, DescWithRef: tc.descRef, - }) + })) require.NoError(t, err) decl, _ := ctx.FindDecl(fixturesModule+"/"+fixtureMinimal3125, "Item") require.NotNil(t, decl) @@ -1565,11 +1572,11 @@ func TestEmbeddedDescriptionAndTags_OptionVariants(t *testing.T) { func TestEmbeddedDescriptionAndTags_SkipExtensions(t *testing.T) { packagePattern := "./" + fixtureMinimal3125 packagePath := fixturesModule + "/" + fixtureMinimal3125 - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{packagePattern}, WorkDir: scantest.FixturesDir(), SkipExtensions: true, - }) + })) require.NoError(t, err) decl, _ := ctx.FindDecl(packagePath, "Item") require.NotNil(t, decl) @@ -1615,12 +1622,12 @@ func TestEmbeddedDescriptionAndTags_SkipAllOfCompounding(t *testing.T) { packagePath := fixturesModule + "/" + fixtureMinimal3125 var diags []grammar.Diagnostic - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{"./" + fixtureMinimal3125}, WorkDir: scantest.FixturesDir(), SkipAllOfCompounding: true, OnDiagnostic: func(d grammar.Diagnostic) { diags = append(diags, d) }, - }) + })) require.NoError(t, err) decl, _ := ctx.FindDecl(packagePath, "Item") require.NotNil(t, decl) @@ -1702,12 +1709,12 @@ func TestEmbeddedDescriptionAndTags_EmitRefSiblings(t *testing.T) { packagePath := fixturesModule + "/" + fixtureMinimal3125 var diags []grammar.Diagnostic - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{"./" + fixtureMinimal3125}, WorkDir: scantest.FixturesDir(), EmitRefSiblings: true, OnDiagnostic: func(d grammar.Diagnostic) { diags = append(diags, d) }, - }) + })) require.NoError(t, err) decl, _ := ctx.FindDecl(packagePath, "Item") require.NotNil(t, decl) @@ -1755,13 +1762,13 @@ func TestEmbeddedDescriptionAndTags_EmitRefSiblings_Skip(t *testing.T) { packagePath := fixturesModule + "/" + fixtureMinimal3125 var diags []grammar.Diagnostic - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{"./" + fixtureMinimal3125}, WorkDir: scantest.FixturesDir(), EmitRefSiblings: true, SkipAllOfCompounding: true, OnDiagnostic: func(d grammar.Diagnostic) { diags = append(diags, d) }, - }) + })) require.NoError(t, err) decl, _ := ctx.FindDecl(packagePath, "Item") require.NotNil(t, decl) @@ -1815,7 +1822,7 @@ func TestEmbeddedDescriptionAndTags_EmitRefSiblings_Skip(t *testing.T) { func TestParamsShape_DescWithRef_BothModes(t *testing.T) { getPetField := func(t *testing.T, descWithRef bool) oaispec.Schema { t.Helper() - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{ "./goparsing/classification", "./goparsing/classification/models", @@ -1824,7 +1831,7 @@ func TestParamsShape_DescWithRef_BothModes(t *testing.T) { WorkDir: scantest.FixturesDir(), SkipExtensions: true, DescWithRef: descWithRef, - }) + })) require.NoError(t, err) decl, ok := ctx.FindDecl(fixturesModule+"/goparsing/classification/models", "NoModel") require.True(t, ok) @@ -1900,11 +1907,11 @@ func TestIssue2540(t *testing.T) { }` packagePattern := "./bugs/2540/foo" packagePath := fixturesModule + "/bugs/2540/foo" - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{packagePattern}, WorkDir: scantest.FixturesDir(), SkipExtensions: true, - }) + })) require.NoError(t, err) decl, _ := ctx.FindDecl(packagePath, "Book") @@ -1944,3 +1951,7 @@ func TestBuildFromDeclWithoutSource(t *testing.T) { require.Error(t, err) assert.ErrorIs(t, err, ErrSchema) } + +func applyLoader(opts *scanner.Options) *scanner.Options { + return scantest.ApplyLoader(opts) +} diff --git a/internal/builders/spec/panic_guard_test.go b/internal/builders/spec/panic_guard_test.go index 71c05ec1..64aba18a 100644 --- a/internal/builders/spec/panic_guard_test.go +++ b/internal/builders/spec/panic_guard_test.go @@ -4,11 +4,14 @@ package spec import ( + "cmp" "go/token" + "slices" "testing" "github.com/go-openapi/codescan/internal/parsers/grammar" "github.com/go-openapi/codescan/internal/scanner" + "github.com/go-openapi/codescan/internal/scantest" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" ) @@ -23,11 +26,11 @@ import ( // The spec.Builder build loops wrap every per-decl step in this guard. func TestBuilder_guard_RecoversPanicWithLocatedDiagnostic(t *testing.T) { var diags []grammar.Diagnostic - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{"./enhancements/emit-x-go-type/..."}, WorkDir: "../../../testdata", OnDiagnostic: func(d grammar.Diagnostic) { diags = append(diags, d) }, - }) + })) require.NoError(t, err) b := NewBuilder(nil, ctx, false) @@ -39,13 +42,21 @@ func TestBuilder_guard_RecoversPanicWithLocatedDiagnostic(t *testing.T) { assert.Contains(t, gerr.Error(), "widget.go:42", "the aborting error carries the source location") assert.Contains(t, gerr.Error(), "model Widget") - require.Len(t, diags, 1) - assert.Equal(t, grammar.CodeInternalPanic, diags[0].Code) - assert.Equal(t, grammar.SeverityError, diags[0].Severity) - assert.Equal(t, pos, diags[0].Pos, "the diagnostic is anchored at the offending decl") - assert.Contains(t, diags[0].Message, "boom") + require.NotEmpty(t, diags) + diagsAtThisPoint := len(diags) + mostSevere := slices.MaxFunc(diags, func(a, b grammar.Diagnostic) int { // depending on the loader mode, some hint diagnostics may be added + return cmp.Compare(int(b.Severity), int(a.Severity)) + }) + assert.Equal(t, grammar.CodeInternalPanic, mostSevere.Code) + assert.Equal(t, grammar.SeverityError, mostSevere.Severity) + assert.Equal(t, pos, mostSevere.Pos, "the diagnostic is anchored at the offending decl") + assert.Contains(t, mostSevere.Message, "boom") - // A non-panicking step passes through with no diagnostic. + // A non-panicking step passes through with no additional diagnostic. require.NoError(t, b.guard(pos, "ok", func() error { return nil })) - assert.Len(t, diags, 1, "a clean step emits no diagnostic") + assert.Len(t, diags, diagsAtThisPoint, "a clean step emits no additional diagnostic") +} + +func applyLoader(opts *scanner.Options) *scanner.Options { + return scantest.ApplyLoader(opts) } diff --git a/internal/builders/spec/subtypes_test.go b/internal/builders/spec/subtypes_test.go index 879d070f..7158bd30 100644 --- a/internal/builders/spec/subtypes_test.go +++ b/internal/builders/spec/subtypes_test.go @@ -28,10 +28,10 @@ const ( // — that independence is what makes the no-`-m` pull possible at all. func newSubtypesBuilder(t *testing.T) *Builder { t.Helper() - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{"./enhancements/discriminated-subtypes/..."}, WorkDir: "../../../testdata", - }) + })) require.NoError(t, err) return NewBuilder(nil, ctx, false) @@ -176,10 +176,10 @@ func TestDiscriminatedSubtypesOf(t *testing.T) { // TestSubtypeIndex_Nested locks the multi-level hierarchy in the index: an INTERFACE that embeds a discriminated // interface is a subtype too, and is itself a base for the structs below it. func TestSubtypeIndex_Nested(t *testing.T) { - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{"./enhancements/discriminated-subtypes-nested/..."}, WorkDir: "../../../testdata", - }) + })) require.NoError(t, err) idx := NewBuilder(nil, ctx, false).subtypes() diff --git a/internal/integration/annotation_noise_test.go b/internal/integration/annotation_noise_test.go index c14d25e8..5602e099 100644 --- a/internal/integration/annotation_noise_test.go +++ b/internal/integration/annotation_noise_test.go @@ -25,7 +25,7 @@ import ( // embedded type's own declaration, and the message says so. func TestAnnotationNoise(t *testing.T) { var diags []codescan.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/annotation-noise/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/builder_conformance_test.go b/internal/integration/builder_conformance_test.go index 58de66ef..6e818dcc 100644 --- a/internal/integration/builder_conformance_test.go +++ b/internal/integration/builder_conformance_test.go @@ -42,7 +42,7 @@ import ( // behaviour is pinned by its own witnesses elsewhere. This suite only asks whether the four agree. func TestBuilderConformance(t *testing.T) { var diags []codescan.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/builder-conformance/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/classification_test.go b/internal/integration/classification_test.go index c8d2d026..51b3c3b4 100644 --- a/internal/integration/classification_test.go +++ b/internal/integration/classification_test.go @@ -31,7 +31,7 @@ func TestClassification_Spec(t *testing.T) { } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{ "./goparsing/classification", "./goparsing/classification/models", diff --git a/internal/integration/compiled_deps_announce_test.go b/internal/integration/compiled_deps_announce_test.go index 514d9806..641686d7 100644 --- a/internal/integration/compiled_deps_announce_test.go +++ b/internal/integration/compiled_deps_announce_test.go @@ -15,18 +15,17 @@ import ( "github.com/go-openapi/testify/v2/require" ) -// Taking dependency types from compiled export data is what an ordinary scan now does, so it is not -// something to announce. +// Taking dependency types from compiled export data is something a caller asks for, so a scan says +// whether the request was met: a Hint when the load took the shortcut, a Warning when the resolved +// loader could not. // -// While it was opt-in, a scan said so once: a Hint describing the deviation the caller had chosen, and -// a Warning when the option was asked for under a loader that cannot honour it. Both were reasonable -// then and neither is now — the first fires on every scan, and nobody asks for a default. +// Only a caller who asked hears any of it. An ordinary scan reads every dependency from source, which +// is not a deviation from anything and not worth a line on every run. // -// So scan.compiled-dependencies is reserved for the one thing that can still cost the output: a lookup -// that wanted a declaration and found no source to read it from. That is reportSourcelessLookup, and -// TestExportOnly_ReportedWhereItCosts covers it. Reaching the code any other way is noise, which is -// what this pins. -func TestCompiledDependencies_DefaultSaysNothing(t *testing.T) { +// The remaining use of scan.compiled-dependencies is the one thing that can still cost the output: a +// lookup that wanted a declaration and found no source to read it from. That is reportSourcelessLookup, +// covered by TestExportOnly_ReportedWhereItCosts. +func TestCompiledDependencies_AnnouncesWhetherItHappened(t *testing.T) { t.Parallel() collect := func(t *testing.T, apply func(*codescan.Options)) []codescan.Diagnostic { @@ -54,37 +53,56 @@ func TestCompiledDependencies_DefaultSaysNothing(t *testing.T) { t.Parallel() assert.Empty(t, collect(t, func(*codescan.Options) {}), - "every dependency's source is reachable here, so nothing was lost and there is nothing to say") + "nothing was asked for and every dependency was read as usual, so there is nothing to say") }) - t.Run("opting out is silent too", func(t *testing.T) { + t.Run("the default is silent under the toolchain-free loader too", func(t *testing.T) { t.Parallel() - assert.Empty(t, collect(t, func(o *codescan.Options) { o.SkipCompiledDependencies = true })) + assert.Empty(t, collect(t, func(o *codescan.Options) { o.ToolchainFreeLoader = true }), + "a loader that could not have honoured the option has nothing to report when nobody asked") }) - // A loader that cannot honour the default is not a loader that has anything to report. It resolves - // imports itself and already decides per dependency whether to read its source, so it arrives at the - // same place by its own route. - t.Run("the toolchain-free loader is silent too", func(t *testing.T) { + t.Run("asking for it, and getting it", func(t *testing.T) { t.Parallel() - assert.Empty(t, collect(t, func(o *codescan.Options) { o.ToolchainFreeLoader = true })) + seen := collect(t, func(o *codescan.Options) { o.CompiledDependencies = true }) + require.Len(t, seen, 1) + assert.Equal(t, codescan.SeverityHint, seen[0].Severity) + assert.Contains(t, seen[0].Message, "dependency types come from compiled export data") + }) + + // Asked for and not delivered. The toolchain-free loader resolves imports itself and already decides + // per dependency whether to read its source, so it arrives at the same document by its own route — + // but the caller chose this for the speed-up and did not get it, and nothing else would say so. + t.Run("asking for it under a loader that cannot", func(t *testing.T) { + t.Parallel() + + seen := collect(t, func(o *codescan.Options) { + o.CompiledDependencies = true + o.ToolchainFreeLoader = true + }) + require.Len(t, seen, 1) + assert.Equal(t, codescan.SeverityWarning, seen[0].Severity) + assert.Contains(t, seen[0].Message, "is ignored under the") }) // The tree here is deliberately not a working one and must not be read as a model for using FS: an // io/fs is the WHOLE world a scan may read, and a module rooted on its own reaches neither GOROOT // nor the module cache, so every dependency including the standard library synthesizes. // - // It earns its place because FS forces the toolchain-free loader without the caller naming one, and - // a scan whose dependencies all synthesized is exactly where a stale announcement would resurface. - t.Run("a virtual filesystem forces the other loader, still silent", func(t *testing.T) { + // It earns its place because FS forces the toolchain-free loader without the caller naming one, so + // the warning has to be driven by the loader that RAN rather than by the one that was asked for. + t.Run("a virtual filesystem forces the other loader, and is warned about", func(t *testing.T) { t.Parallel() - assert.Empty(t, collect(t, func(o *codescan.Options) { + seen := collect(t, func(o *codescan.Options) { + o.CompiledDependencies = true o.FS = os.DirFS(scantest.FixturesDir()) o.WorkDir = "." o.Packages = []string{"./goparsing/petstore/..."} - })) + }) + require.Len(t, seen, 1) + assert.Equal(t, codescan.SeverityWarning, seen[0].Severity) }) } diff --git a/internal/integration/compiled_deps_test.go b/internal/integration/compiled_deps_test.go index 708469ce..310f6dbd 100644 --- a/internal/integration/compiled_deps_test.go +++ b/internal/integration/compiled_deps_test.go @@ -13,8 +13,8 @@ import ( "github.com/go-openapi/testify/v2/require" ) -// Taking dependency types from the compiler buys a large amount of time, and since v0.36.4 it is what an -// ordinary scan does. +// Taking dependency types from the compiler buys a large amount of time on a warm build cache, which +// is what CompiledDependencies is for. // // It used to cost dependency source along with it, and this test pinned the loss. It no longer does: // a dependency whose source carries annotations is parsed for them after the load, so the case that @@ -28,10 +28,10 @@ func TestCompiledDependencies_KeepsDependencyAnnotations(t *testing.T) { scan := func(compiled bool) (format string, sourceless []string) { doc, err := codescan.Run(&codescan.Options{ - Packages: []string{"./petstore/..."}, - WorkDir: scantest.FixturesDir() + "/goparsing", - ScanModels: true, - SkipCompiledDependencies: !compiled, + Packages: []string{"./petstore/..."}, + WorkDir: scantest.FixturesDir() + "/goparsing", + ScanModels: true, + CompiledDependencies: compiled, OnDiagnostic: func(d codescan.Diagnostic) { if strings.Contains(d.Message, "could not be read") { sourceless = append(sourceless, d.Message) diff --git a/internal/integration/coverage_additional_properties_field_test.go b/internal/integration/coverage_additional_properties_field_test.go index 1d718736..40c3bfca 100644 --- a/internal/integration/coverage_additional_properties_field_test.go +++ b/internal/integration/coverage_additional_properties_field_test.go @@ -18,7 +18,7 @@ import ( // allOf-sibling form on a referenced-model field, and the lowest-priority contradiction. func TestCoverage_AdditionalProperties_Field(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/additional-properties-field/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_additional_properties_simpleschema_test.go b/internal/integration/coverage_additional_properties_simpleschema_test.go index 960633c8..fe59f942 100644 --- a/internal/integration/coverage_additional_properties_simpleschema_test.go +++ b/internal/integration/coverage_additional_properties_simpleschema_test.go @@ -23,7 +23,7 @@ import ( // UnsupportedSimpleSchemaString in the param/header/items dispatchers. func TestCoverage_AdditionalProperties_SimpleSchemaSafeguard(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/additional-properties-simpleschema/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_additional_properties_test.go b/internal/integration/coverage_additional_properties_test.go index 91067e0d..a8c41c5b 100644 --- a/internal/integration/coverage_additional_properties_test.go +++ b/internal/integration/coverage_additional_properties_test.go @@ -18,7 +18,7 @@ import ( // map-override case, coexistence with maxProperties, and the lowest-priority precedence rule. func TestCoverage_AdditionalProperties(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/additional-properties/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_after_decl_comments_test.go b/internal/integration/coverage_after_decl_comments_test.go index 64347789..886a3379 100644 --- a/internal/integration/coverage_after_decl_comments_test.go +++ b/internal/integration/coverage_after_decl_comments_test.go @@ -15,7 +15,7 @@ import ( func runAfterDeclComments(t *testing.T, on bool) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/after-decl-comments/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_alias_embed_inline_test.go b/internal/integration/coverage_alias_embed_inline_test.go index 8a2d73de..952e9ec9 100644 --- a/internal/integration/coverage_alias_embed_inline_test.go +++ b/internal/integration/coverage_alias_embed_inline_test.go @@ -20,7 +20,7 @@ import ( // The fixture exercises the same Base struct embedded direct, via alias, via pointer, and via // interface — all four produce structurally equivalent inline shapes. func TestCoverage_AliasEmbedInline(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-calibration-embed/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -70,7 +70,7 @@ func TestCoverage_AliasEmbedInline(t *testing.T) { // Together with TestCoverage_AliasEmbedInline this asserts the bidirectional contract: annotation // is the sole gate of allOf composition. func TestCoverage_AliasEmbedAllOfOptIn(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-calibration-embed/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_alias_field_test.go b/internal/integration/coverage_alias_field_test.go index 07ab19e7..35e837a3 100644 --- a/internal/integration/coverage_alias_field_test.go +++ b/internal/integration/coverage_alias_field_test.go @@ -35,7 +35,7 @@ import ( // - EnvelopeAnnotatedAlias.viaAliasModeled (ANNOTATED) → // $ref: BaseAliasModeled (alias keeps identity); has own def func TestCoverage_AliasField_Default(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-calibration-embed/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -68,7 +68,7 @@ func TestCoverage_AliasField_Default(t *testing.T) { // decl's own definition shape (chain `$ref` instead of structural copy), not the field-site `$ref` // target. func TestCoverage_AliasField_Ref(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-calibration-embed/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -98,7 +98,7 @@ func TestCoverage_AliasField_Ref(t *testing.T) { // Both Envelope.viaAlias and EnvelopeAnnotatedAlias.viaAliasModeled resolve to {$ref: Base} because // Transparent supersedes the annotation gate at use sites. func TestCoverage_AliasField_Transparent(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-calibration-embed/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_alias_findmodel_witness_test.go b/internal/integration/coverage_alias_findmodel_witness_test.go index 9e31c511..b2c1ad63 100644 --- a/internal/integration/coverage_alias_findmodel_witness_test.go +++ b/internal/integration/coverage_alias_findmodel_witness_test.go @@ -20,7 +20,7 @@ import ( // definitions via the implicit ExtraModels side effect. // Both paths produce identical output; the golden is the witness. func TestCoverage_AliasFindModelWitness(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-findmodel-witness/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_alias_parameters_calibration_test.go b/internal/integration/coverage_alias_parameters_calibration_test.go index 182c35b1..66aa740c 100644 --- a/internal/integration/coverage_alias_parameters_calibration_test.go +++ b/internal/integration/coverage_alias_parameters_calibration_test.go @@ -53,7 +53,7 @@ func findBodyParam(t *testing.T, doc *oaispec.Swagger, path, verb, name string) // See [§alias-handling](../builders/parameters/README.md#alias-handling) for the contract. func TestCoverage_AliasParametersCalibration_Default(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-parameters-calibration/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -102,7 +102,7 @@ func TestCoverage_AliasParametersCalibration_Default(t *testing.T) { } func TestCoverage_AliasParametersCalibration_Ref(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-parameters-calibration/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -136,7 +136,7 @@ func TestCoverage_AliasParametersCalibration_Ref(t *testing.T) { } func TestCoverage_AliasParametersCalibration_Transparent(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-parameters-calibration/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_alias_predeclared_ref_test.go b/internal/integration/coverage_alias_predeclared_ref_test.go index 2377d1cd..28af964a 100644 --- a/internal/integration/coverage_alias_predeclared_ref_test.go +++ b/internal/integration/coverage_alias_predeclared_ref_test.go @@ -23,7 +23,7 @@ import ( // through `applyStdlibSpecials`, so `error` produces its canonical `{type: string, x-go-type: // error}` shape inline instead of crashing the scan. func TestCoverage_AliasPredeclaredRef(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-calibration-stdlib/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -96,7 +96,7 @@ func TestCoverage_AliasPredeclaredRef(t *testing.T) { // With the recognizer call, all four produce their canonical shape inline, matching // TransparentAliases mode. func TestCoverage_AliasStdlibDefault(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-calibration-stdlib/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_alias_response_shapes_test.go b/internal/integration/coverage_alias_response_shapes_test.go index 34e5aa97..e191c7df 100644 --- a/internal/integration/coverage_alias_response_shapes_test.go +++ b/internal/integration/coverage_alias_response_shapes_test.go @@ -22,7 +22,7 @@ import ( // primitives ({string, ""}); pre-Q4 they emitted empty headers because the response builder's // buildFieldAlias fell through to makeRef even on header targets. func TestCoverage_AliasResponseShapes_Default(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-response-shapes/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -59,7 +59,7 @@ func TestCoverage_AliasResponseShapes_Default(t *testing.T) { // // The Q4 gate's "in != body OR !RefAliases" branch covers both legs. func TestCoverage_AliasResponseShapes_Ref(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-response-shapes/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -79,7 +79,7 @@ func TestCoverage_AliasResponseShapes_Ref(t *testing.T) { // alias-of-struct case on a header surfaces empty (the struct can't reduce to a SimpleSchema // primitive — same as Q2's post-fix behaviour). func TestCoverage_AliasResponseShapes_Transparent(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-response-shapes/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_alias_responses_calibration_test.go b/internal/integration/coverage_alias_responses_calibration_test.go index 739c381a..d4a18647 100644 --- a/internal/integration/coverage_alias_responses_calibration_test.go +++ b/internal/integration/coverage_alias_responses_calibration_test.go @@ -28,7 +28,7 @@ import ( // See [§alias-handling](../builders/responses/README.md#alias-handling) for the contract. func TestCoverage_AliasResponsesCalibration_Default(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-responses-calibration/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -79,7 +79,7 @@ func TestCoverage_AliasResponsesCalibration_Default(t *testing.T) { } func TestCoverage_AliasResponsesCalibration_Ref(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-responses-calibration/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -116,7 +116,7 @@ func TestCoverage_AliasResponsesCalibration_Ref(t *testing.T) { } func TestCoverage_AliasResponsesCalibration_Transparent(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-responses-calibration/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1026_test.go b/internal/integration/coverage_bug_1026_test.go index 8d2e8d1e..a80d3872 100644 --- a/internal/integration/coverage_bug_1026_test.go +++ b/internal/integration/coverage_bug_1026_test.go @@ -19,7 +19,7 @@ import ( // 📖 Need doc: document InfoExtensions / Extensions for arbitrary x-* vendor extensions on info / // the spec root. func TestCoverage_Bug1026(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1026/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1063_test.go b/internal/integration/coverage_bug_1063_test.go index f966b0c9..51f8b020 100644 --- a/internal/integration/coverage_bug_1063_test.go +++ b/internal/integration/coverage_bug_1063_test.go @@ -18,7 +18,7 @@ import ( // 📖 Need doc: readOnly is a spec-level marker; excluding the field from request bodies is the // responsibility of downstream code generation / validation, not the spec scanner. func TestCoverage_Bug1063(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1063/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1078_test.go b/internal/integration/coverage_bug_1078_test.go index ef2cf679..25c45473 100644 --- a/internal/integration/coverage_bug_1078_test.go +++ b/internal/integration/coverage_bug_1078_test.go @@ -15,7 +15,7 @@ import ( // TestCoverage_Bug1078 locks the fix for go-swagger issue #1078 ("fails to generate JSON schema for // time fields"): a time.Time field is documented as {type: string, format: date-time}. func TestCoverage_Bug1078(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1078/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1079_test.go b/internal/integration/coverage_bug_1079_test.go index b3aa76c9..d9fb215e 100644 --- a/internal/integration/coverage_bug_1079_test.go +++ b/internal/integration/coverage_bug_1079_test.go @@ -21,7 +21,7 @@ import ( // 📖 Need doc: clarify -m semantics (annotated models + referenced types, not every struct in the // package). func TestCoverage_Bug1079(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1079/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1088_test.go b/internal/integration/coverage_bug_1088_test.go index ca8a016a..86bc94be 100644 --- a/internal/integration/coverage_bug_1088_test.go +++ b/internal/integration/coverage_bug_1088_test.go @@ -31,7 +31,7 @@ import ( func TestCoverage_Bug1088(t *testing.T) { var diagnostics []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1088/..."}, WorkDir: scantest.FixturesDir(), OnDiagnostic: func(d grammar.Diagnostic) { diff --git a/internal/integration/coverage_bug_1092_test.go b/internal/integration/coverage_bug_1092_test.go index bdf5fc7e..0470969b 100644 --- a/internal/integration/coverage_bug_1092_test.go +++ b/internal/integration/coverage_bug_1092_test.go @@ -18,7 +18,7 @@ import ( // — the prose stays in the description and the real Version field is read; no YAML "mapping // values" error. func TestCoverage_Bug1092(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1092/..."}, WorkDir: scantest.FixturesDir(), }) require.NoError(t, err, "colon-bearing meta prose must not raise a YAML mapping error (go-swagger#1092)") diff --git a/internal/integration/coverage_bug_1096_test.go b/internal/integration/coverage_bug_1096_test.go index 9f9d9032..39d05e33 100644 --- a/internal/integration/coverage_bug_1096_test.go +++ b/internal/integration/coverage_bug_1096_test.go @@ -21,7 +21,7 @@ import ( // Guarded by the cgo build tag so it is skipped where cgo is unavailable (the fixture is likewise // cgo-tagged). func TestCoverage_Bug1096(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1096/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1109_test.go b/internal/integration/coverage_bug_1109_test.go index 78cb9149..993db540 100644 --- a/internal/integration/coverage_bug_1109_test.go +++ b/internal/integration/coverage_bug_1109_test.go @@ -32,7 +32,7 @@ import ( // swagger:response-vs-model name resolution. func TestCoverage_Bug1109(t *testing.T) { t.Run("OP form `200: order` resolves via definition-fallback with -m", func(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1109/responseref/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -45,7 +45,7 @@ func TestCoverage_Bug1109(t *testing.T) { }) t.Run("casualjim form `200: body:order` with -m", func(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1109/bodyref/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1115_test.go b/internal/integration/coverage_bug_1115_test.go index e1e9f315..731898a5 100644 --- a/internal/integration/coverage_bug_1115_test.go +++ b/internal/integration/coverage_bug_1115_test.go @@ -19,7 +19,7 @@ import ( // The grammar2 scanner handles the StarExpr — the pointer is dereferenced and the alias emits a // $ref like the non-pointer form. func TestCoverage_Bug1115(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1115/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1117_test.go b/internal/integration/coverage_bug_1117_test.go index f884dd02..ac74dff8 100644 --- a/internal/integration/coverage_bug_1117_test.go +++ b/internal/integration/coverage_bug_1117_test.go @@ -18,7 +18,7 @@ import ( // // 📖 Need doc: array-body response recipe (in:body Body []T). func TestCoverage_Bug1117(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1117/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1118_test.go b/internal/integration/coverage_bug_1118_test.go index dd501eaa..2d9274e7 100644 --- a/internal/integration/coverage_bug_1118_test.go +++ b/internal/integration/coverage_bug_1118_test.go @@ -24,7 +24,7 @@ import ( // // 📖 Need doc: document the title/description heuristic (see #2626). func TestCoverage_Bug1118(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1118/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1121_test.go b/internal/integration/coverage_bug_1121_test.go index 940ce712..686175dd 100644 --- a/internal/integration/coverage_bug_1121_test.go +++ b/internal/integration/coverage_bug_1121_test.go @@ -18,7 +18,7 @@ import ( // // 📖 Need doc: document the swagger:meta Tags: block for tag descriptions. func TestCoverage_Bug1121(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1121/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1133_test.go b/internal/integration/coverage_bug_1133_test.go index 4fbb8f36..93a9395d 100644 --- a/internal/integration/coverage_bug_1133_test.go +++ b/internal/integration/coverage_bug_1133_test.go @@ -18,7 +18,7 @@ import ( // // It is warned and skipped; the annotated model is still emitted. func TestCoverage_Bug1133(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1133/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1267_test.go b/internal/integration/coverage_bug_1267_test.go index 480b26d2..eeebf28d 100644 --- a/internal/integration/coverage_bug_1267_test.go +++ b/internal/integration/coverage_bug_1267_test.go @@ -16,7 +16,7 @@ import ( // is described with produces: application/octet-stream and a body field marked swagger:strfmt // binary -> {type:string, format:binary}. func TestCoverage_Bug1267(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1267/..."}, WorkDir: scantest.FixturesDir(), }) require.NoError(t, err) diff --git a/internal/integration/coverage_bug_1279_test.go b/internal/integration/coverage_bug_1279_test.go index 3bcdd670..3fac508b 100644 --- a/internal/integration/coverage_bug_1279_test.go +++ b/internal/integration/coverage_bug_1279_test.go @@ -18,7 +18,7 @@ import ( // // 📖 Need doc: document the inline Parameters: block in swagger:route. func TestCoverage_Bug1279(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1279/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1395_test.go b/internal/integration/coverage_bug_1395_test.go index ce18d04e..960650e1 100644 --- a/internal/integration/coverage_bug_1395_test.go +++ b/internal/integration/coverage_bug_1395_test.go @@ -20,7 +20,7 @@ import ( // meta-Security parsing fixes (cf. #2403). // The OP's "SecurityDefinition" (singular) was a keyword typo. func TestCoverage_Bug1395(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1395/..."}, WorkDir: scantest.FixturesDir(), }) require.NoError(t, err) diff --git a/internal/integration/coverage_bug_1402_test.go b/internal/integration/coverage_bug_1402_test.go index dbe88418..64282dd8 100644 --- a/internal/integration/coverage_bug_1402_test.go +++ b/internal/integration/coverage_bug_1402_test.go @@ -16,7 +16,7 @@ import ( // map[string]interface{}"): the field renders as {type:object, additionalProperties:{}} — an OPEN // value schema (any), not one wrongly constrained to object. func TestCoverage_Bug1402(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1402/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, }) require.NoError(t, err) diff --git a/internal/integration/coverage_bug_1416_test.go b/internal/integration/coverage_bug_1416_test.go index 815a182a..71726595 100644 --- a/internal/integration/coverage_bug_1416_test.go +++ b/internal/integration/coverage_bug_1416_test.go @@ -19,7 +19,7 @@ import ( // The body param is now clean — {in: body, schema: {$ref}}, no top-level $ref — and links to // the operation. func TestCoverage_Bug1416(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1416/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1436_test.go b/internal/integration/coverage_bug_1436_test.go index 33a22f5e..ad281a7d 100644 --- a/internal/integration/coverage_bug_1436_test.go +++ b/internal/integration/coverage_bug_1436_test.go @@ -19,7 +19,7 @@ import ( // Works-as-designed; the "prune unreferenced under -m" middle ground is forthcoming-features §12. func TestCoverage_Bug1436(t *testing.T) { run := func(m bool) map[string]struct{} { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1436/..."}, WorkDir: scantest.FixturesDir(), ScanModels: m, }) require.NoError(t, err) diff --git a/internal/integration/coverage_bug_1459_test.go b/internal/integration/coverage_bug_1459_test.go index a17bbff3..30f4583a 100644 --- a/internal/integration/coverage_bug_1459_test.go +++ b/internal/integration/coverage_bug_1459_test.go @@ -19,7 +19,7 @@ import ( // The misleading additionalProp1/2/3 keys are only the Swagger UI's fallback when no example is // provided. func TestCoverage_Bug1459(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1459/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, }) require.NoError(t, err) diff --git a/internal/integration/coverage_bug_1483_test.go b/internal/integration/coverage_bug_1483_test.go index 16c2d2bf..3faf7955 100644 --- a/internal/integration/coverage_bug_1483_test.go +++ b/internal/integration/coverage_bug_1483_test.go @@ -16,7 +16,7 @@ import ( // map[string]string"): a map[string]string field in a body parameter renders as {type:object, // additionalProperties:{type:string}} — no "items doesn't support maps" error. func TestCoverage_Bug1483(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1483/..."}, WorkDir: scantest.FixturesDir(), }) require.NoError(t, err) diff --git a/internal/integration/coverage_bug_1499_test.go b/internal/integration/coverage_bug_1499_test.go index 57d96726..aadc4869 100644 --- a/internal/integration/coverage_bug_1499_test.go +++ b/internal/integration/coverage_bug_1499_test.go @@ -24,7 +24,7 @@ import ( // The `[]`-prefixed form collapses a Go struct slice to a simple array-of-scalar query parameter, // mirroring the schema builder's array layers. func TestCoverage_Bug1499(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1499/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1512_test.go b/internal/integration/coverage_bug_1512_test.go index 06c5d9dd..bada568a 100644 --- a/internal/integration/coverage_bug_1512_test.go +++ b/internal/integration/coverage_bug_1512_test.go @@ -23,7 +23,7 @@ import ( // 📖 Need doc: the uint* formats are intentional; use swagger:strfmt int64 to emit a conformant // string-encoded int64. func TestCoverage_Bug1512(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1512/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1542_test.go b/internal/integration/coverage_bug_1542_test.go index 85963c90..898c9450 100644 --- a/internal/integration/coverage_bug_1542_test.go +++ b/internal/integration/coverage_bug_1542_test.go @@ -19,7 +19,7 @@ import ( // 📖 Need doc: complex (map/object) examples must be written as valid JSON on the example: line. // (Comma-list / array coercion remains the §2.1 enhancement.) func TestCoverage_Bug1542(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1542/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1587_test.go b/internal/integration/coverage_bug_1587_test.go index cc45e9ab..0f1aaab8 100644 --- a/internal/integration/coverage_bug_1587_test.go +++ b/internal/integration/coverage_bug_1587_test.go @@ -17,7 +17,7 @@ import ( // tail (josev2) differs from its package name (jose) used to fail with "no import found for jose". // go/packages resolves it by the real package name, so the $ref and its definition are emitted. func TestCoverage_Bug1587(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1587/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1595_test.go b/internal/integration/coverage_bug_1595_test.go index 95890a51..e220e6a7 100644 --- a/internal/integration/coverage_bug_1595_test.go +++ b/internal/integration/coverage_bug_1595_test.go @@ -23,7 +23,7 @@ import ( // Both the flush-left block style and the idiomatic `*`-decorated style must parse like their // // equivalent — the path with a 200 response — without erroring. func TestCoverage_Bug1595(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1595/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1609_test.go b/internal/integration/coverage_bug_1609_test.go index 0a9015f5..0e5731bb 100644 --- a/internal/integration/coverage_bug_1609_test.go +++ b/internal/integration/coverage_bug_1609_test.go @@ -21,7 +21,7 @@ import ( // 📖 Need doc: a bare `// x-example: …` line is swallowed as the description; the supported // form is an Extensions: block on the parameter / header. func TestCoverage_Bug1609(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1609/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1613_test.go b/internal/integration/coverage_bug_1613_test.go index 17fea385..4019c93a 100644 --- a/internal/integration/coverage_bug_1613_test.go +++ b/internal/integration/coverage_bug_1613_test.go @@ -18,7 +18,7 @@ import ( // // It now emits schema {type: string} (the primitive-body work, #2942). func TestCoverage_Bug1613(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1613/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1635_test.go b/internal/integration/coverage_bug_1635_test.go index 88aa5f52..d2656532 100644 --- a/internal/integration/coverage_bug_1635_test.go +++ b/internal/integration/coverage_bug_1635_test.go @@ -23,7 +23,7 @@ import ( // one body parameter per promoted field (invalid OAS2, which allows at most one body parameter per // operation). func TestCoverage_Bug1635(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1635/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1665_test.go b/internal/integration/coverage_bug_1665_test.go index 39f88796..b00a138b 100644 --- a/internal/integration/coverage_bug_1665_test.go +++ b/internal/integration/coverage_bug_1665_test.go @@ -16,7 +16,7 @@ import ( // annotation across multiple lines"): multiple swagger:parameters lines on one type are all honored // — the shared param binds to operations listed across every line. func TestCoverage_Bug1665(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1665/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1708_test.go b/internal/integration/coverage_bug_1708_test.go index b5b1fe66..ee2fa8bc 100644 --- a/internal/integration/coverage_bug_1708_test.go +++ b/internal/integration/coverage_bug_1708_test.go @@ -16,7 +16,7 @@ import ( // property type", follow-up of #619): a body response whose fields are pointers to other models no // longer errors with "missing property type" — the fields resolve to $refs in an object schema. func TestCoverage_Bug1708(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1708/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1711_test.go b/internal/integration/coverage_bug_1711_test.go index 4dfeae68..2032428a 100644 --- a/internal/integration/coverage_bug_1711_test.go +++ b/internal/integration/coverage_bug_1711_test.go @@ -19,7 +19,7 @@ import ( // 📖 Need doc: the param description comes from the field comment; x-go-name is a separate vendor // extension, not concatenated into the description. func TestCoverage_Bug1711(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1711/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1713_test.go b/internal/integration/coverage_bug_1713_test.go index 890db760..8a655d31 100644 --- a/internal/integration/coverage_bug_1713_test.go +++ b/internal/integration/coverage_bug_1713_test.go @@ -19,7 +19,7 @@ import ( // (The struct-based swagger:response example-by-mime form is the separate forthcoming feature §10 // / #2871.) func TestCoverage_Bug1713(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1713/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1726_test.go b/internal/integration/coverage_bug_1726_test.go index 60e6df5e..36a70b21 100644 --- a/internal/integration/coverage_bug_1726_test.go +++ b/internal/integration/coverage_bug_1726_test.go @@ -22,7 +22,7 @@ import ( // `* item` / `+ item` are normalised to `- item` (matching gofmt's own rewrite of doc-comment // bullets), so the asserted descriptions carry the dash form. func TestCoverage_Bug1726(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1726/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1737_test.go b/internal/integration/coverage_bug_1737_test.go index 3b67995c..371b2fe4 100644 --- a/internal/integration/coverage_bug_1737_test.go +++ b/internal/integration/coverage_bug_1737_test.go @@ -21,7 +21,7 @@ import ( // description survives alongside the reference. func TestCoverage_Bug1737(t *testing.T) { t.Run("default drops the description (bare $ref)", func(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1737/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -35,7 +35,7 @@ func TestCoverage_Bug1737(t *testing.T) { }) t.Run("DescWithRef preserves it via allOf", func(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1737/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1742_test.go b/internal/integration/coverage_bug_1742_test.go index 9a7c091c..808f331c 100644 --- a/internal/integration/coverage_bug_1742_test.go +++ b/internal/integration/coverage_bug_1742_test.go @@ -21,7 +21,7 @@ import ( // params. // That engine is gone; grammar2 scans a route-with-params (here, cross-package) without panicking. func TestCoverage_Bug1742(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1742/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1828_test.go b/internal/integration/coverage_bug_1828_test.go index a2f8b621..42c28310 100644 --- a/internal/integration/coverage_bug_1828_test.go +++ b/internal/integration/coverage_bug_1828_test.go @@ -19,7 +19,7 @@ import ( // The legacy engine emitted an invalid `$ref: '#/responses/'` (empty name); grammar2 parses the // inline description correctly. func TestCoverage_Bug1828(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1828/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1852_test.go b/internal/integration/coverage_bug_1852_test.go index d029d71e..aae59f2d 100644 --- a/internal/integration/coverage_bug_1852_test.go +++ b/internal/integration/coverage_bug_1852_test.go @@ -20,7 +20,7 @@ import ( // The legacy engine omitted it entirely, so editor.swagger.io rejected the spec ("missingProperty: // description"); codescan now always emits the key (empty string when no doc comment is present). func TestCoverage_Bug1852(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1852/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1867_test.go b/internal/integration/coverage_bug_1867_test.go index 752815a9..d34e84f9 100644 --- a/internal/integration/coverage_bug_1867_test.go +++ b/internal/integration/coverage_bug_1867_test.go @@ -16,7 +16,7 @@ import ( // swagger:route (with a body parameter) and swagger:operation — the reporter found // swagger:operation silently failed and swagger:route could not specify the JSON body. func TestCoverage_Bug1867(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1867/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1881_test.go b/internal/integration/coverage_bug_1881_test.go index 0a8e3637..15c9d25f 100644 --- a/internal/integration/coverage_bug_1881_test.go +++ b/internal/integration/coverage_bug_1881_test.go @@ -16,7 +16,7 @@ import ( // swagger:response whose body is a slice of a model produces a valid `{type: array, items: {$ref}}` // schema. func TestCoverage_Bug1881(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1881/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1887_test.go b/internal/integration/coverage_bug_1887_test.go index e90ac0bb..eb60e9a1 100644 --- a/internal/integration/coverage_bug_1887_test.go +++ b/internal/integration/coverage_bug_1887_test.go @@ -15,7 +15,7 @@ import ( // TestCoverage_Bug1887 locks go-swagger issue #1887 ("file type support"): the `swagger:file` // marker on a formData parameter field emits the Swagger 2.0 `type: file` parameter. func TestCoverage_Bug1887(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1887/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1891_test.go b/internal/integration/coverage_bug_1891_test.go index 0c2f6de2..cf069934 100644 --- a/internal/integration/coverage_bug_1891_test.go +++ b/internal/integration/coverage_bug_1891_test.go @@ -16,7 +16,7 @@ import ( // grouped `type ( ... )` block, plus a swagger:route declared inside a function body, are both // discovered and produce a valid spec. func TestCoverage_Bug1891(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1891/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1913_test.go b/internal/integration/coverage_bug_1913_test.go index f3558c91..6d4914ed 100644 --- a/internal/integration/coverage_bug_1913_test.go +++ b/internal/integration/coverage_bug_1913_test.go @@ -22,7 +22,7 @@ import ( // keeps locking the -m shape; the discovery half lives in // coverage_discriminated_subtypes_test.go. func TestCoverage_Bug1913(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1913/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1925_test.go b/internal/integration/coverage_bug_1925_test.go index 7309b250..90f3e552 100644 --- a/internal/integration/coverage_bug_1925_test.go +++ b/internal/integration/coverage_bug_1925_test.go @@ -16,7 +16,7 @@ import ( // parameter typed `[]map[string]interface{}` no longer aborts spec generation — it produces a // valid array-of-object schema with open additionalProperties. func TestCoverage_Bug1925(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1925/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1934_test.go b/internal/integration/coverage_bug_1934_test.go index adf81796..38f6cd20 100644 --- a/internal/integration/coverage_bug_1934_test.go +++ b/internal/integration/coverage_bug_1934_test.go @@ -17,7 +17,7 @@ import ( // discovered — the model resolves as the route's response body $ref, and the params type // contributes the query parameter. func TestCoverage_Bug1934(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1934/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_1955_test.go b/internal/integration/coverage_bug_1955_test.go index f3629b2a..54c5eb1c 100644 --- a/internal/integration/coverage_bug_1955_test.go +++ b/internal/integration/coverage_bug_1955_test.go @@ -16,7 +16,7 @@ import ( // swagger:parameters struct defined in a DIFFERENT package — they are matched by operation id // across all scanned packages (the swagger:operation analog of #1742). func TestCoverage_Bug1955(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1955/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1958_test.go b/internal/integration/coverage_bug_1958_test.go index 934b3d5d..8145a1df 100644 --- a/internal/integration/coverage_bug_1958_test.go +++ b/internal/integration/coverage_bug_1958_test.go @@ -17,7 +17,7 @@ import ( // share a name with a known keyword (here, `responses:` inside x-amazon-apigateway-integration), // which must NOT be confused with the operation's own responses. func TestCoverage_Bug1958(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1958/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_1992_test.go b/internal/integration/coverage_bug_1992_test.go index fa59a88b..24bad58d 100644 --- a/internal/integration/coverage_bug_1992_test.go +++ b/internal/integration/coverage_bug_1992_test.go @@ -17,7 +17,7 @@ import ( // scope, but the OAS2 idiom for server-assigned fields — `read only: true` → `readOnly: true` // — is supported and is the recommended mechanism. func TestCoverage_Bug1992(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/1992/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2002_test.go b/internal/integration/coverage_bug_2002_test.go index d8babea6..b29105d7 100644 --- a/internal/integration/coverage_bug_2002_test.go +++ b/internal/integration/coverage_bug_2002_test.go @@ -19,7 +19,7 @@ import ( // The reporter's "unsupported type 'invalid type'" came from the old GO111MODULE=off go/loader; the // module-aware go/packages loader resolves the cross-package type. func TestCoverage_Bug2002(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2002/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2013_test.go b/internal/integration/coverage_bug_2013_test.go index 9f979307..a762fe81 100644 --- a/internal/integration/coverage_bug_2013_test.go +++ b/internal/integration/coverage_bug_2013_test.go @@ -16,7 +16,7 @@ import ( // buildEmbedded): an enum on a promoted (embedded) field is parsed without panicking and the enum // values are carried onto the composed model. func TestCoverage_Bug2013(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2013/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2020_test.go b/internal/integration/coverage_bug_2020_test.go index 595e0eae..b40435b9 100644 --- a/internal/integration/coverage_bug_2020_test.go +++ b/internal/integration/coverage_bug_2020_test.go @@ -20,7 +20,7 @@ import ( // — it is dropped to a query param — but that narrow edge is independent of the // grouped-declaration detection this locks.) func TestCoverage_Bug2020(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2020/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2038_test.go b/internal/integration/coverage_bug_2038_test.go index a3a2b632..c88dc698 100644 --- a/internal/integration/coverage_bug_2038_test.go +++ b/internal/integration/coverage_bug_2038_test.go @@ -22,7 +22,7 @@ import ( // tag and flattens both forms, so the generated spec for Tagged does not match the actual JSON. // The untagged form (correct promotion) is the green guard rail. func TestCoverage_Bug2038(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2038/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2062_test.go b/internal/integration/coverage_bug_2062_test.go index 2cd217b4..e7d545c4 100644 --- a/internal/integration/coverage_bug_2062_test.go +++ b/internal/integration/coverage_bug_2062_test.go @@ -17,7 +17,7 @@ import ( // swagger:meta concept in OpenAPI 2.0, not a per-operation field — that part of the ask is N/A by // spec.) func TestCoverage_Bug2062(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2062/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2064_test.go b/internal/integration/coverage_bug_2064_test.go index 13202cd3..70c6f9f1 100644 --- a/internal/integration/coverage_bug_2064_test.go +++ b/internal/integration/coverage_bug_2064_test.go @@ -17,7 +17,7 @@ import ( // // They are carried on the parameter object alongside the body schema. func TestCoverage_Bug2064(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2064/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2106_test.go b/internal/integration/coverage_bug_2106_test.go index 4178ed50..71be7a37 100644 --- a/internal/integration/coverage_bug_2106_test.go +++ b/internal/integration/coverage_bug_2106_test.go @@ -16,7 +16,7 @@ import ( // an array"): a field-level `Extensions:` block emits its x-* vendor extensions on BOTH scalar and // array fields. func TestCoverage_Bug2106(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2106/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2119_test.go b/internal/integration/coverage_bug_2119_test.go index 3298752e..07a1829a 100644 --- a/internal/integration/coverage_bug_2119_test.go +++ b/internal/integration/coverage_bug_2119_test.go @@ -17,14 +17,14 @@ import ( // vendor extensions (which clash across same-named types). func TestCoverage_Bug2119(t *testing.T) { // Default: x-go-name / x-go-package are present. - on, err := codescan.Run(&codescan.Options{ + on, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2119/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, }) require.NoError(t, err) assert.Equal(t, "Colour", on.Definitions["Widget"].Properties["colour"].Extensions["x-go-name"]) // SkipExtensions: x-go-* are gone. - off, err := codescan.Run(&codescan.Options{ + off, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2119/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, SkipExtensions: true, }) diff --git a/internal/integration/coverage_bug_2125_test.go b/internal/integration/coverage_bug_2125_test.go index 24109487..4c9d9667 100644 --- a/internal/integration/coverage_bug_2125_test.go +++ b/internal/integration/coverage_bug_2125_test.go @@ -18,7 +18,7 @@ import ( // kept in the info description and does NOT derail field parsing — the real `Version:` field is // read correctly. func TestCoverage_Bug2125(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2125/..."}, WorkDir: scantest.FixturesDir(), }) require.NoError(t, err) diff --git a/internal/integration/coverage_bug_2133_test.go b/internal/integration/coverage_bug_2133_test.go index 57e21726..7a5d9017 100644 --- a/internal/integration/coverage_bug_2133_test.go +++ b/internal/integration/coverage_bug_2133_test.go @@ -16,7 +16,7 @@ import ( // parameter is emitted as a simple {type:string, in:path} with NO sibling `schema` (the // double-emission the reporter saw would fail validation). func TestCoverage_Bug2133(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2133/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2160_test.go b/internal/integration/coverage_bug_2160_test.go index bc047d7d..5bdfb3c7 100644 --- a/internal/integration/coverage_bug_2160_test.go +++ b/internal/integration/coverage_bug_2160_test.go @@ -17,7 +17,7 @@ import ( // (not zero-valued). (The multi-line YAML-list example syntax is kept as a raw string — the // example-coercion gap tracked in forthcoming-features §2.1.) func TestCoverage_Bug2160(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2160/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2184_test.go b/internal/integration/coverage_bug_2184_test.go index e9450839..b865274e 100644 --- a/internal/integration/coverage_bug_2184_test.go +++ b/internal/integration/coverage_bug_2184_test.go @@ -16,7 +16,7 @@ import ( // indicated type when using swagger:type on struct"): a struct type carrying `swagger:type int64`, // used as a parameter field, makes the parameter an integer — not a $ref to the struct. func TestCoverage_Bug2184(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2184/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2218_test.go b/internal/integration/coverage_bug_2218_test.go index 625f45d8..5c05ca62 100644 --- a/internal/integration/coverage_bug_2218_test.go +++ b/internal/integration/coverage_bug_2218_test.go @@ -16,7 +16,7 @@ import ( // swagger:parameters struct is bound to its route when the struct's operation id matches the // route's operation id — the query parameter appears on the operation. func TestCoverage_Bug2218(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2218/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2228_test.go b/internal/integration/coverage_bug_2228_test.go index ced5a8e0..d94b5126 100644 --- a/internal/integration/coverage_bug_2228_test.go +++ b/internal/integration/coverage_bug_2228_test.go @@ -16,7 +16,7 @@ import ( // the auto-derived summary (first prose line) is suppressed by simply omitting that line — a // route with only `responses:` has no summary. func TestCoverage_Bug2228(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2228/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2230_test.go b/internal/integration/coverage_bug_2230_test.go index ee1ac9e4..93834299 100644 --- a/internal/integration/coverage_bug_2230_test.go +++ b/internal/integration/coverage_bug_2230_test.go @@ -19,7 +19,7 @@ import ( // To constrain or example the RawMessage, annotate it (swagger:type / swagger:strfmt); the raw // bytes are not forced to an int array. func TestCoverage_Bug2230(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2230/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2232_test.go b/internal/integration/coverage_bug_2232_test.go index aa2218ab..d1cadd14 100644 --- a/internal/integration/coverage_bug_2232_test.go +++ b/internal/integration/coverage_bug_2232_test.go @@ -17,7 +17,7 @@ import ( // spaced tags — its tags are space-delimited tokens — so the YAML form is the supported route // to it.) func TestCoverage_Bug2232(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2232/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2233_test.go b/internal/integration/coverage_bug_2233_test.go index 2ab713fa..cbe6d041 100644 --- a/internal/integration/coverage_bug_2233_test.go +++ b/internal/integration/coverage_bug_2233_test.go @@ -17,7 +17,7 @@ import ( // defined in ANOTHER package resolves — the response and its body model are emitted (no "$refs // must reference a valid location"). func TestCoverage_Bug2233(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2233/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2245_test.go b/internal/integration/coverage_bug_2245_test.go index a00b2999..5b0c24b6 100644 --- a/internal/integration/coverage_bug_2245_test.go +++ b/internal/integration/coverage_bug_2245_test.go @@ -16,7 +16,7 @@ import ( // application/xml"): a `produces: [application/xml]` in the swagger:operation YAML body sets the // operation's produced media type. func TestCoverage_Bug2245(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2245/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2248_test.go b/internal/integration/coverage_bug_2248_test.go index bc37d9d1..b51e3e14 100644 --- a/internal/integration/coverage_bug_2248_test.go +++ b/internal/integration/coverage_bug_2248_test.go @@ -16,7 +16,7 @@ import ( // response header typed time.Duration resolves to {type:integer, format:int64} — it carries a // type, so the spec is valid (the reporter saw "headers..type in body is required"). func TestCoverage_Bug2248(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2248/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2251_test.go b/internal/integration/coverage_bug_2251_test.go index 99bd3bd4..ccb0d47e 100644 --- a/internal/integration/coverage_bug_2251_test.go +++ b/internal/integration/coverage_bug_2251_test.go @@ -26,7 +26,7 @@ import ( // branch; see forthcoming §18.) func TestCoverage_Bug2251(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2251/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2286_test.go b/internal/integration/coverage_bug_2286_test.go index 6ccafca1..7467a091 100644 --- a/internal/integration/coverage_bug_2286_test.go +++ b/internal/integration/coverage_bug_2286_test.go @@ -16,7 +16,7 @@ import ( // swagger:model directly as a route response (`200: model`) is accepted and yields a valid response // with a $ref to that model — the lenient shorthand for `200: body:model`. func TestCoverage_Bug2286(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2286/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2294_test.go b/internal/integration/coverage_bug_2294_test.go index 448f7880..47397705 100644 --- a/internal/integration/coverage_bug_2294_test.go +++ b/internal/integration/coverage_bug_2294_test.go @@ -22,7 +22,7 @@ import ( // The fix lives in the meta-Security parser (internal/parsers/security), the same parser as // #2403/#2479. func TestCoverage_Bug2294(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2294/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2296_test.go b/internal/integration/coverage_bug_2296_test.go index 005b4cfb..b2b3b9fd 100644 --- a/internal/integration/coverage_bug_2296_test.go +++ b/internal/integration/coverage_bug_2296_test.go @@ -16,7 +16,7 @@ import ( // struct that itself has an anonymous-struct field no longer panics during schema build; the // promoted anonymous-struct property is emitted as a nested object. func TestCoverage_Bug2296(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2296/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2305_test.go b/internal/integration/coverage_bug_2305_test.go index 98a13af9..421f78ef 100644 --- a/internal/integration/coverage_bug_2305_test.go +++ b/internal/integration/coverage_bug_2305_test.go @@ -16,7 +16,7 @@ import ( // parameter with an enum produces a clean, valid parameter — the enum is present, with no illegal // `schema` and no duplicated description. func TestCoverage_Bug2305(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2305/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2311_test.go b/internal/integration/coverage_bug_2311_test.go index 75ac6885..eb6ffc40 100644 --- a/internal/integration/coverage_bug_2311_test.go +++ b/internal/integration/coverage_bug_2311_test.go @@ -16,7 +16,7 @@ import ( // `swagger:ignore` on a struct FIELD drops that property from the model (the docs only mentioned // types/whole declarations). func TestCoverage_Bug2311(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2311/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2317_test.go b/internal/integration/coverage_bug_2317_test.go index b3bbd056..71a85ca3 100644 --- a/internal/integration/coverage_bug_2317_test.go +++ b/internal/integration/coverage_bug_2317_test.go @@ -16,7 +16,7 @@ import ( // `x-nullable: true` Extensions block on a pointer field is applied — the field becomes // allOf[$ref] carrying x-nullable. func TestCoverage_Bug2317(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2317/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2353_test.go b/internal/integration/coverage_bug_2353_test.go index e22f86f1..b3fa7202 100644 --- a/internal/integration/coverage_bug_2353_test.go +++ b/internal/integration/coverage_bug_2353_test.go @@ -17,7 +17,7 @@ import ( // body param is a clean {in:body, schema: {$ref}} with no forbidden sibling `type`, and the path // param is a simple {in:path, type:string}. func TestCoverage_Bug2353(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2353/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2384_test.go b/internal/integration/coverage_bug_2384_test.go index 8b98f15d..afc23b33 100644 --- a/internal/integration/coverage_bug_2384_test.go +++ b/internal/integration/coverage_bug_2384_test.go @@ -18,7 +18,7 @@ import ( // multi-line output came from go-swagger's spec->code generator, not from codescan's code->spec // scan.) func TestCoverage_Bug2384(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2384/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2396_test.go b/internal/integration/coverage_bug_2396_test.go index d87a0f8f..4d9eae66 100644 --- a/internal/integration/coverage_bug_2396_test.go +++ b/internal/integration/coverage_bug_2396_test.go @@ -22,7 +22,7 @@ import ( // The scanner currently glues them onto the first/last value (["[issues", "pulls", "projects]"]). // The unbracketed form (which already trims correctly) is the green guard rail. func TestCoverage_Bug2396(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2396/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2403_test.go b/internal/integration/coverage_bug_2403_test.go index a5f7a0a3..a3f24ee1 100644 --- a/internal/integration/coverage_bug_2403_test.go +++ b/internal/integration/coverage_bug_2403_test.go @@ -22,7 +22,7 @@ import ( // scope `"[]"`. The fix lives in internal/parsers/security (stripSequenceMarker // + parseScopes). func TestCoverage_Bug2403(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2403/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2407_test.go b/internal/integration/coverage_bug_2407_test.go index b4bb66db..51cfbe86 100644 --- a/internal/integration/coverage_bug_2407_test.go +++ b/internal/integration/coverage_bug_2407_test.go @@ -15,7 +15,7 @@ import ( // TestCoverage_Bug2407 locks go-swagger issue #2407 ("how add example to yml"): an `example: [1, 2, // 3]` on an array-typed body response is emitted on the response schema (previously absent). func TestCoverage_Bug2407(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2407/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2409_test.go b/internal/integration/coverage_bug_2409_test.go index 719eea60..057d6722 100644 --- a/internal/integration/coverage_bug_2409_test.go +++ b/internal/integration/coverage_bug_2409_test.go @@ -17,7 +17,7 @@ import ( // specific x-go-type IMPORT semantics is a separate design question — poison-queue #2924 — but // the extension mechanism itself works for any x-* key.) func TestCoverage_Bug2409(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2409/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2417_test.go b/internal/integration/coverage_bug_2417_test.go index 7cb2390d..79088330 100644 --- a/internal/integration/coverage_bug_2417_test.go +++ b/internal/integration/coverage_bug_2417_test.go @@ -28,7 +28,7 @@ import ( // - color.SamePackageAlias embedded -> "hue" promoted // - named a.AnotherPackageAlias -> $ref func TestCoverage_Bug2417(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2417/b/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2419_test.go b/internal/integration/coverage_bug_2419_test.go index 9a328ff1..8306d53f 100644 --- a/internal/integration/coverage_bug_2419_test.go +++ b/internal/integration/coverage_bug_2419_test.go @@ -16,7 +16,7 @@ import ( // field"): a `swagger:type string` on a field whose type is an external/imported struct overrides // it to a plain string (instead of a $ref to the external type's definition). func TestCoverage_Bug2419(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2419/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2441_test.go b/internal/integration/coverage_bug_2441_test.go index 56e7ced4..703b6a85 100644 --- a/internal/integration/coverage_bug_2441_test.go +++ b/internal/integration/coverage_bug_2441_test.go @@ -17,7 +17,7 @@ import ( // → format binary. (OpenAPI 2.0 `type: file` is formData-only; a raw body uses `{type: string, // format: binary}`.) func TestCoverage_Bug2441(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2441/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2479_test.go b/internal/integration/coverage_bug_2479_test.go index 2ecde161..ec95dd0c 100644 --- a/internal/integration/coverage_bug_2479_test.go +++ b/internal/integration/coverage_bug_2479_test.go @@ -25,7 +25,7 @@ import ( // The fix makes security.Parse return a non-nil empty list for the explicit `[]` form, which // go-openapi/spec marshals as `"security": []` rather than omitting it. func TestCoverage_Bug2479(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2479/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2483_test.go b/internal/integration/coverage_bug_2483_test.go index de5a5038..d0afe8f2 100644 --- a/internal/integration/coverage_bug_2483_test.go +++ b/internal/integration/coverage_bug_2483_test.go @@ -19,7 +19,7 @@ import ( // // The arms are now emitted exactly once. func TestCoverage_Bug2483(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2483/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2520_test.go b/internal/integration/coverage_bug_2520_test.go index 14ffa293..e9ba3ef8 100644 --- a/internal/integration/coverage_bug_2520_test.go +++ b/internal/integration/coverage_bug_2520_test.go @@ -18,7 +18,7 @@ import ( // type level and cannot infer the custom-marshaled wire shape; declaring it requires swagger:type / // swagger:strfmt on the wrapper type. func TestCoverage_Bug2520(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2520/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2547_test.go b/internal/integration/coverage_bug_2547_test.go index ef675a33..a1e3f9de 100644 --- a/internal/integration/coverage_bug_2547_test.go +++ b/internal/integration/coverage_bug_2547_test.go @@ -18,7 +18,7 @@ import ( // `example: ""` is the empty string; the scanner currently keeps the quotes verbatim (`""` -> the // 2-char string `""`, `"Foo"` -> `"Foo"` with quotes), so this is currently RED. func TestCoverage_Bug2547(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2547/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2549_test.go b/internal/integration/coverage_bug_2549_test.go index c1ccf4a9..91f4fa8f 100644 --- a/internal/integration/coverage_bug_2549_test.go +++ b/internal/integration/coverage_bug_2549_test.go @@ -19,7 +19,7 @@ import ( // (The example value is carried as the string "210000"; numeric coercion of example values is the // separate forthcoming-features §2.1 / #1268 concern.) func TestCoverage_Bug2549(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2549/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2575_test.go b/internal/integration/coverage_bug_2575_test.go index 80455064..683e47fb 100644 --- a/internal/integration/coverage_bug_2575_test.go +++ b/internal/integration/coverage_bug_2575_test.go @@ -15,7 +15,7 @@ import ( // TestCoverage_Bug2575 locks go-swagger issue #2575 ("custom request headers"): a // swagger:parameters field marked `in: header` emits a header parameter. func TestCoverage_Bug2575(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2575/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2588_test.go b/internal/integration/coverage_bug_2588_test.go index e3255dbd..712e683a 100644 --- a/internal/integration/coverage_bug_2588_test.go +++ b/internal/integration/coverage_bug_2588_test.go @@ -16,7 +16,7 @@ import ( // a struct embedding a named type whose underlying type is an interface (type B A; type C struct{ B // }) no longer panics with "interface conversion: ast.Expr is *ast.Ident, not *ast.InterfaceType". func TestCoverage_Bug2588(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2588/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2592_test.go b/internal/integration/coverage_bug_2592_test.go index e6e14e0d..0b893c13 100644 --- a/internal/integration/coverage_bug_2592_test.go +++ b/internal/integration/coverage_bug_2592_test.go @@ -20,7 +20,7 @@ import ( // same embeds yields that allOf directly on the response schema. (Embedding in the response WRAPPER // instead turns the field into a header — the embeds must live in the body.) func TestCoverage_Bug2592(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2592/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, }) require.NoError(t, err) diff --git a/internal/integration/coverage_bug_2599_test.go b/internal/integration/coverage_bug_2599_test.go index ceb6aaeb..5727af7d 100644 --- a/internal/integration/coverage_bug_2599_test.go +++ b/internal/integration/coverage_bug_2599_test.go @@ -18,7 +18,7 @@ import ( // // Same mechanism answers #2404/#2419. func TestCoverage_Bug2599(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2599/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2638_test.go b/internal/integration/coverage_bug_2638_test.go index edd0d563..b05ce547 100644 --- a/internal/integration/coverage_bug_2638_test.go +++ b/internal/integration/coverage_bug_2638_test.go @@ -15,7 +15,7 @@ import ( // TestCoverage_Bug2638 locks the fix for go-swagger issue #2638: a field group with multiple names // on one line (`R, G, B, A uint8`) emits one property per name, not a single mislabeled property. func TestCoverage_Bug2638(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2638/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2651_test.go b/internal/integration/coverage_bug_2651_test.go index 629bcb36..c8d9c3f1 100644 --- a/internal/integration/coverage_bug_2651_test.go +++ b/internal/integration/coverage_bug_2651_test.go @@ -21,7 +21,7 @@ import ( // After the fix the bound body is its own `in: body` parameter and the inline path param carries // only its simple schema (no Schema/$ref). func TestCoverage_Bug2651(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2651/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2652_test.go b/internal/integration/coverage_bug_2652_test.go index 3d3981f2..e0d5f4a6 100644 --- a/internal/integration/coverage_bug_2652_test.go +++ b/internal/integration/coverage_bug_2652_test.go @@ -24,7 +24,7 @@ import ( // Previously it was carried as a raw string ("{...}") because the override arm has no type to // coerce against. func TestCoverage_Bug2652(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2652/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2655_test.go b/internal/integration/coverage_bug_2655_test.go index 391481bf..aedf31c3 100644 --- a/internal/integration/coverage_bug_2655_test.go +++ b/internal/integration/coverage_bug_2655_test.go @@ -21,7 +21,7 @@ import ( // list nesting survives the raw-block lexer because `Tags:` preserves per-line indentation (like // extensions/securityDefinitions). func TestCoverage_Bug2655(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2655/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2663_test.go b/internal/integration/coverage_bug_2663_test.go index 5d126718..7222be10 100644 --- a/internal/integration/coverage_bug_2663_test.go +++ b/internal/integration/coverage_bug_2663_test.go @@ -30,7 +30,7 @@ import ( // // 📖 Need doc: document the one-field/named-type body-parameter idiom. func TestCoverage_Bug2663(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2663/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2687_test.go b/internal/integration/coverage_bug_2687_test.go index 6ad1c441..1f659b9a 100644 --- a/internal/integration/coverage_bug_2687_test.go +++ b/internal/integration/coverage_bug_2687_test.go @@ -22,7 +22,7 @@ import ( // (alongside Go `//go:`/`//nolint:` directives) and drops them from the prose surface, so the // descriptions stay clean. func TestCoverage_Bug2687(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2687/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2701_test.go b/internal/integration/coverage_bug_2701_test.go index 51272e9a..ec747ccf 100644 --- a/internal/integration/coverage_bug_2701_test.go +++ b/internal/integration/coverage_bug_2701_test.go @@ -21,7 +21,7 @@ import ( // The test also guards the exportedness contract: only exported fields are promoted (including ones // reached through an unexported embed), and unexported fields never surface. func TestCoverage_Bug2701(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2701/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2746_test.go b/internal/integration/coverage_bug_2746_test.go index fa2a78f5..1f98ab2e 100644 --- a/internal/integration/coverage_bug_2746_test.go +++ b/internal/integration/coverage_bug_2746_test.go @@ -18,7 +18,7 @@ import ( // // The format now applies to the array's items. func TestCoverage_Bug2746(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2746/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2761_test.go b/internal/integration/coverage_bug_2761_test.go index 0fed78c2..56a0feba 100644 --- a/internal/integration/coverage_bug_2761_test.go +++ b/internal/integration/coverage_bug_2761_test.go @@ -23,7 +23,7 @@ import ( // 📖 Need doc: an allOf $ref requires the embedded base to be a swagger:model, not a // swagger:response. func TestCoverage_Bug2761(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2761/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2783_test.go b/internal/integration/coverage_bug_2783_test.go index 9204d6a7..f97fedfd 100644 --- a/internal/integration/coverage_bug_2783_test.go +++ b/internal/integration/coverage_bug_2783_test.go @@ -20,7 +20,7 @@ import ( // Now each keeps its own identity: two distinct Test definitions (deep-keyed by package while the // leaf collides) plus TestResponseBody — three definitions, deterministic. func TestCoverage_Bug2783(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2783/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2799_test.go b/internal/integration/coverage_bug_2799_test.go index 16ceb3d0..451b694b 100644 --- a/internal/integration/coverage_bug_2799_test.go +++ b/internal/integration/coverage_bug_2799_test.go @@ -22,7 +22,7 @@ import ( // (Distinct from #3211, which is about markdown *table* rows losing their leading pipe — still // open.) func TestCoverage_Bug2799(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2799/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2801_test.go b/internal/integration/coverage_bug_2801_test.go index 26bd95c4..a2ca6190 100644 --- a/internal/integration/coverage_bug_2801_test.go +++ b/internal/integration/coverage_bug_2801_test.go @@ -19,7 +19,7 @@ import ( // // The v0.30 empty-spec is gone. func TestCoverage_Bug2801(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2801/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2802_test.go b/internal/integration/coverage_bug_2802_test.go index 1909a7f3..341b309e 100644 --- a/internal/integration/coverage_bug_2802_test.go +++ b/internal/integration/coverage_bug_2802_test.go @@ -21,7 +21,7 @@ import ( // The uninstantiated generic's free type parameter `T` is skipped with a warning (it has no spec // representation), which is acceptable — the point is the scan completes. func TestCoverage_Bug2802(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2802/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2804_test.go b/internal/integration/coverage_bug_2804_test.go index f04740e1..4db3896f 100644 --- a/internal/integration/coverage_bug_2804_test.go +++ b/internal/integration/coverage_bug_2804_test.go @@ -34,7 +34,7 @@ import ( func TestCoverage_Bug2804(t *testing.T) { var diagnostics []grammar.Diagnostic - spec, err := codescan.Run(&codescan.Options{ + spec, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2804/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2837_test.go b/internal/integration/coverage_bug_2837_test.go index 1f1f6f24..2bf9a4fc 100644 --- a/internal/integration/coverage_bug_2837_test.go +++ b/internal/integration/coverage_bug_2837_test.go @@ -21,7 +21,7 @@ import ( // Both the authored `+` form and the gofmt-canonical `-` form now parse to the same parameter. func TestCoverage_Bug2837(t *testing.T) { scan := func(pkg string) *oaispec.Swagger { - doc, err := codescan.Run(&codescan.Options{Packages: []string{pkg}, WorkDir: scantest.FixturesDir()}) + doc, err := runScan(&codescan.Options{Packages: []string{pkg}, WorkDir: scantest.FixturesDir()}) require.NoError(t, err) require.NotNil(t, doc) return doc diff --git a/internal/integration/coverage_bug_2871_test.go b/internal/integration/coverage_bug_2871_test.go index cc10dbb9..39543261 100644 --- a/internal/integration/coverage_bug_2871_test.go +++ b/internal/integration/coverage_bug_2871_test.go @@ -17,7 +17,7 @@ import ( // type) ARE supported — multiple operations may reuse one example-bearing model while each // response code carries its own example, different from the model's. func TestCoverage_Bug2871(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2871/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, }) require.NoError(t, err) diff --git a/internal/integration/coverage_bug_2872_test.go b/internal/integration/coverage_bug_2872_test.go index 54da5a88..baf50f95 100644 --- a/internal/integration/coverage_bug_2872_test.go +++ b/internal/integration/coverage_bug_2872_test.go @@ -20,7 +20,7 @@ import ( // objects (schema, operation, tag); supporting those is a separate feature beyond this meta-focused // fix. func TestCoverage_Bug2872(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2872/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2875_test.go b/internal/integration/coverage_bug_2875_test.go index 5640bc29..38180570 100644 --- a/internal/integration/coverage_bug_2875_test.go +++ b/internal/integration/coverage_bug_2875_test.go @@ -17,7 +17,7 @@ import ( // type's properties; it now emits a proper `allOf: [{$ref: …}]` to the embedded model's // definition. func TestCoverage_Bug2875(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2875/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2899_test.go b/internal/integration/coverage_bug_2899_test.go index a3266882..c160fa2b 100644 --- a/internal/integration/coverage_bug_2899_test.go +++ b/internal/integration/coverage_bug_2899_test.go @@ -19,7 +19,7 @@ import ( // The quoted source example (`example: "123456"`) emits the quote-stripped value `123456`: // surrounding quotes are delimiters (quirk F8, fixed alongside go-swagger#2547). func TestCoverage_Bug2899(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2899/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2907_test.go b/internal/integration/coverage_bug_2907_test.go index 2d292f45..b8c13a16 100644 --- a/internal/integration/coverage_bug_2907_test.go +++ b/internal/integration/coverage_bug_2907_test.go @@ -19,7 +19,7 @@ import ( // The cross-package model is now resolved, so the response schema is an array of `$ref` to the // emitted definition. func TestCoverage_Bug2907(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2907/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2909_test.go b/internal/integration/coverage_bug_2909_test.go index 882f667c..6a82da5b 100644 --- a/internal/integration/coverage_bug_2909_test.go +++ b/internal/integration/coverage_bug_2909_test.go @@ -21,7 +21,7 @@ import ( // (OpenAPI 2.0 path templating cannot express it). func TestCoverage_Bug2909(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2909/..."}, WorkDir: scantest.FixturesDir(), OnDiagnostic: func(d grammar.Diagnostic) { diff --git a/internal/integration/coverage_bug_2917_test.go b/internal/integration/coverage_bug_2917_test.go index 5adcfc5f..2ca5bd1b 100644 --- a/internal/integration/coverage_bug_2917_test.go +++ b/internal/integration/coverage_bug_2917_test.go @@ -19,7 +19,7 @@ import ( // The current regex only matches `swagger:` at start-of-line or after whitespace/slash, so the // embedded `_swagger:extendee` is not treated as an annotation. func TestCoverage_Bug2917(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2917/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2922_test.go b/internal/integration/coverage_bug_2922_test.go index f1aa9b4c..98f1e77b 100644 --- a/internal/integration/coverage_bug_2922_test.go +++ b/internal/integration/coverage_bug_2922_test.go @@ -38,7 +38,7 @@ func TestCoverage_Bug2922(t *testing.T) { const enumMapping = "FIRST TestEnumFirst" t.Run("default keeps the mapping in the description (backward compat)", func(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2922/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -66,7 +66,7 @@ func TestCoverage_Bug2922(t *testing.T) { }) t.Run("SkipEnumDescriptions leaves the description as authored prose", func(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2922/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2942_test.go b/internal/integration/coverage_bug_2942_test.go index 0f98433a..033fbcb5 100644 --- a/internal/integration/coverage_bug_2942_test.go +++ b/internal/integration/coverage_bug_2942_test.go @@ -21,7 +21,7 @@ import ( // {schema: {type: string}} instead of no schema (Typed routed to the body // schema rather than a discarded header). func TestCoverage_Bug2942(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2942/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_2961_test.go b/internal/integration/coverage_bug_2961_test.go index 044f2d11..7b3090b7 100644 --- a/internal/integration/coverage_bug_2961_test.go +++ b/internal/integration/coverage_bug_2961_test.go @@ -17,7 +17,7 @@ import ( // // The values are now coerced to numbers against the integer schema type. func TestCoverage_Bug2961(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2961/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_2985_test.go b/internal/integration/coverage_bug_2985_test.go index abe0b563..83755416 100644 --- a/internal/integration/coverage_bug_2985_test.go +++ b/internal/integration/coverage_bug_2985_test.go @@ -17,7 +17,7 @@ import ( // maxItems), so on an object swagger:model they emit property-count validation instead of leaking // into the model description. func TestCoverage_Bug2985(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/2985/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_3005_test.go b/internal/integration/coverage_bug_3005_test.go index 4ad8c5cc..38e8c5b4 100644 --- a/internal/integration/coverage_bug_3005_test.go +++ b/internal/integration/coverage_bug_3005_test.go @@ -19,7 +19,7 @@ import ( // marker supplies the additionalProperties value schema. field1 stays a named property alongside // it. func TestCoverage_Bug3005(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3005/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_3007_test.go b/internal/integration/coverage_bug_3007_test.go index 3f1e0238..8dafa335 100644 --- a/internal/integration/coverage_bug_3007_test.go +++ b/internal/integration/coverage_bug_3007_test.go @@ -22,7 +22,7 @@ import ( // go-swagger#2687: the lexer drops `+marker`-style directive lines from prose, so the description // is clean. func TestCoverage_Bug3007(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3007/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_3013_test.go b/internal/integration/coverage_bug_3013_test.go index 8f837d1f..74d89956 100644 --- a/internal/integration/coverage_bug_3013_test.go +++ b/internal/integration/coverage_bug_3013_test.go @@ -16,7 +16,7 @@ import ( // swagger:response whose body is a top-level array type now lands on the response body schema (it // was previously dropped — the response decl comment only contributed the description). func TestCoverage_Bug3013(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3013/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_301_test.go b/internal/integration/coverage_bug_301_test.go index cc1ad12f..ddda9b7a 100644 --- a/internal/integration/coverage_bug_301_test.go +++ b/internal/integration/coverage_bug_301_test.go @@ -18,7 +18,7 @@ import ( // The reporter's "model not appearing" was the -m (ScanModels) requirement — an unreferenced // model only shows up under -m, which is asserted here as the documented behaviour. func TestCoverage_Bug301(t *testing.T) { - withModels, err := codescan.Run(&codescan.Options{ + withModels, err := runScan(&codescan.Options{ Packages: []string{"./bugs/301/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -37,7 +37,7 @@ func TestCoverage_Bug301(t *testing.T) { user.Properties["friends"].Items.Schema.Ref.String(), "self-referential array") // The documented confusion: without -m an unreferenced model is absent. - withoutModels, err := codescan.Run(&codescan.Options{ + withoutModels, err := runScan(&codescan.Options{ Packages: []string{"./bugs/301/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_3035_test.go b/internal/integration/coverage_bug_3035_test.go index 1a8b065f..5a4c6f69 100644 --- a/internal/integration/coverage_bug_3035_test.go +++ b/internal/integration/coverage_bug_3035_test.go @@ -29,7 +29,7 @@ import ( // // We lock the current behaviour; surfacing it would be a separate enhancement. func TestCoverage_Bug3035(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3035/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_3069_test.go b/internal/integration/coverage_bug_3069_test.go index b5ea2688..0c4875b8 100644 --- a/internal/integration/coverage_bug_3069_test.go +++ b/internal/integration/coverage_bug_3069_test.go @@ -19,7 +19,7 @@ import ( // ComplexType -> string makes a []ComplexType field render as an array of strings, matching the // actual wire format. (📖 Need doc: document this override for custom-marshalled types.) func TestCoverage_Bug3069(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3069/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_3100_test.go b/internal/integration/coverage_bug_3100_test.go index d4a9d61d..48c0382b 100644 --- a/internal/integration/coverage_bug_3100_test.go +++ b/internal/integration/coverage_bug_3100_test.go @@ -19,7 +19,7 @@ import ( // round-trip its `in` field verbatim; the scanner used to only recognise `form` and dropped `in` // entirely from the spec. func TestCoverage_Bug3100(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3100/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_3107_test.go b/internal/integration/coverage_bug_3107_test.go index 5d143c9f..25808925 100644 --- a/internal/integration/coverage_bug_3107_test.go +++ b/internal/integration/coverage_bug_3107_test.go @@ -23,7 +23,7 @@ import ( // body, so the test also exercises type resolution through the import graph (the v0.30.5 trigger: // the model package was resolved by reference, not directly scanned). func TestCoverage_Bug3107(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3107/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_3117_test.go b/internal/integration/coverage_bug_3117_test.go index 93723fe3..e5611416 100644 --- a/internal/integration/coverage_bug_3117_test.go +++ b/internal/integration/coverage_bug_3117_test.go @@ -18,7 +18,7 @@ import ( // // The body param schema is a clean $ref. func TestCoverage_Bug3117(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3117/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_3138_test.go b/internal/integration/coverage_bug_3138_test.go index f74c7158..2cf47ed4 100644 --- a/internal/integration/coverage_bug_3138_test.go +++ b/internal/integration/coverage_bug_3138_test.go @@ -27,7 +27,7 @@ import ( // Operation-level `deprecated: true` keeps using the native OAS2 field. func TestCoverage_Bug3138(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3138/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_3213_test.go b/internal/integration/coverage_bug_3213_test.go index 541cd894..b9cf5df5 100644 --- a/internal/integration/coverage_bug_3213_test.go +++ b/internal/integration/coverage_bug_3213_test.go @@ -20,7 +20,7 @@ import ( // distinct titles, which a GenDecl-only reader could not produce. // A grouped `swagger:enum` referenced by one of them also has its values parsed. func TestCoverage_Bug3213(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3213/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_3214_test.go b/internal/integration/coverage_bug_3214_test.go index c313a38f..27b89622 100644 --- a/internal/integration/coverage_bug_3214_test.go +++ b/internal/integration/coverage_bug_3214_test.go @@ -28,7 +28,7 @@ import ( // This test asserts the parsed shape directly and captures the full output in a golden as a // regression lock. func TestCoverage_Bug3214(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3214/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_334_test.go b/internal/integration/coverage_bug_334_test.go index e6e784d5..0ee6d9a1 100644 --- a/internal/integration/coverage_bug_334_test.go +++ b/internal/integration/coverage_bug_334_test.go @@ -20,7 +20,7 @@ import ( // title/description are dropped — they are only captured when the annotation comes last. // Asserted explicitly below so a future change is conscious. func TestCoverage_Bug334(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/334/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_3412_test.go b/internal/integration/coverage_bug_3412_test.go index dc60976d..e8a70bb3 100644 --- a/internal/integration/coverage_bug_3412_test.go +++ b/internal/integration/coverage_bug_3412_test.go @@ -21,7 +21,7 @@ import ( // not merely the scan. A float enum covers the FLOAT branch, and a non-decimal enum covers the // base-detected integer forms. func TestCoverage_Bug3412(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3412/negative/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -133,7 +133,7 @@ func TestCoverage_Bug3412(t *testing.T) { // Values come from the type-checker, which evaluated all of them exactly; the schema type still // comes from the declared Go type. func TestCoverage_Bug3412_ConstForms(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3412/constforms/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -220,7 +220,7 @@ func TestCoverage_Bug3412_ConstForms(t *testing.T) { // inline. Each is a distinct path, and a per-property assertion in one of them says nothing about // the others; the golden is what makes a regression in any single target visible. func TestCoverage_Bug3412_SimpleSchema(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3412/simpleschema/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -237,7 +237,7 @@ func TestCoverage_Bug3412_SimpleSchema(t *testing.T) { // type without the annotation keeps it, since the ordinary path resolves the declaration's // right-hand side instead. Annotating a type as an enum must not cost the author their format. func TestCoverage_Bug3412_StrfmtEnum(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/3412/strfmtenum/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_413_test.go b/internal/integration/coverage_bug_413_test.go index 1bad2daa..b7a817c6 100644 --- a/internal/integration/coverage_bug_413_test.go +++ b/internal/integration/coverage_bug_413_test.go @@ -19,7 +19,7 @@ import ( // // It now resolves cleanly — the embedded fields are promoted as parameters. func TestCoverage_Bug413(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/413/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_bug_439_test.go b/internal/integration/coverage_bug_439_test.go index be949e56..859a9e35 100644 --- a/internal/integration/coverage_bug_439_test.go +++ b/internal/integration/coverage_bug_439_test.go @@ -16,7 +16,7 @@ import ( // schema"): an unrelated, unannotated type with fields the scanner can't model (chan, time.Time) is // left untouched — only the discovered body type is emitted, with no "Expr unsupported" error. func TestCoverage_Bug439(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{Packages: []string{"./bugs/439/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true}) + doc, err := runScan(&codescan.Options{Packages: []string{"./bugs/439/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true}) require.NoError(t, err, "unrelated declarations must not break the scan (go-swagger#439)") _, hasBody := doc.Definitions["BodyParamType"] _, hasUnrelated := doc.Definitions["Unrelated"] diff --git a/internal/integration/coverage_bug_613_test.go b/internal/integration/coverage_bug_613_test.go index 8c869a63..df955a1a 100644 --- a/internal/integration/coverage_bug_613_test.go +++ b/internal/integration/coverage_bug_613_test.go @@ -16,7 +16,7 @@ import ( // referenced struct (here via a []*Ulimit field) is implicitly converted into its own definition // and referenced by $ref — no unhelpful error. func TestCoverage_Bug613(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{Packages: []string{"./bugs/613/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true}) + doc, err := runScan(&codescan.Options{Packages: []string{"./bugs/613/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true}) require.NoError(t, err) ul := doc.Definitions["Resources"].Properties["ulimits"] require.NotNil(t, ul.Items) diff --git a/internal/integration/coverage_bug_622_test.go b/internal/integration/coverage_bug_622_test.go index 26333f23..d49312dd 100644 --- a/internal/integration/coverage_bug_622_test.go +++ b/internal/integration/coverage_bug_622_test.go @@ -16,7 +16,7 @@ import ( // response body field is emitted on the response schema. (The reporter's `exampleValue:` was // non-standard; `example:` is the keyword.) func TestCoverage_Bug622(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{Packages: []string{"./bugs/622/..."}, WorkDir: scantest.FixturesDir()}) + doc, err := runScan(&codescan.Options{Packages: []string{"./bugs/622/..."}, WorkDir: scantest.FixturesDir()}) require.NoError(t, err) name := doc.Responses["personResponse"].Schema.Properties["name"] assert.Equal(t, "Bob", name.Example, "example on a response body field is emitted (go-swagger#622)") diff --git a/internal/integration/coverage_bug_796_test.go b/internal/integration/coverage_bug_796_test.go index 7d9335cc..ce67d98e 100644 --- a/internal/integration/coverage_bug_796_test.go +++ b/internal/integration/coverage_bug_796_test.go @@ -24,7 +24,7 @@ import ( // (pingResponse) is emitted; the parameter struct is rendered as an operation parameter (with a // clean description and a real `in` field) and the interface is dropped entirely. func TestCoverage_Bug796(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/796/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_834_test.go b/internal/integration/coverage_bug_834_test.go index 7d671ddd..e917c2db 100644 --- a/internal/integration/coverage_bug_834_test.go +++ b/internal/integration/coverage_bug_834_test.go @@ -16,7 +16,7 @@ import ( // / example on a field are applied as schema fields, not concatenated into the property // description. (The non-json naming-tag ask is the separate forthcoming-features §7 / #1391 item.) func TestCoverage_Bug834(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{Packages: []string{"./bugs/834/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true}) + doc, err := runScan(&codescan.Options{Packages: []string{"./bugs/834/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true}) require.NoError(t, err) code := doc.Definitions["Widget"].Properties["code"] assert.Equal(t, "the code", code.Description, "description is just the prose, not the annotations") diff --git a/internal/integration/coverage_bug_91_test.go b/internal/integration/coverage_bug_91_test.go index 97139b32..98c7a191 100644 --- a/internal/integration/coverage_bug_91_test.go +++ b/internal/integration/coverage_bug_91_test.go @@ -15,7 +15,7 @@ import ( // TestCoverage_Bug91 locks go-swagger issue #91 ("decouple from source"): a query parameter can be // declared INLINE in the swagger:operation YAML body — no Go wrapper struct required. func TestCoverage_Bug91(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{Packages: []string{"./bugs/91/..."}, WorkDir: scantest.FixturesDir()}) + doc, err := runScan(&codescan.Options{Packages: []string{"./bugs/91/..."}, WorkDir: scantest.FixturesDir()}) require.NoError(t, err) op := doc.Paths.Paths["/search"].Get require.NotNil(t, op) diff --git a/internal/integration/coverage_bug_958_test.go b/internal/integration/coverage_bug_958_test.go index 74b58575..5e436242 100644 --- a/internal/integration/coverage_bug_958_test.go +++ b/internal/integration/coverage_bug_958_test.go @@ -20,7 +20,7 @@ import ( // The fix carries both for defined-type fields too; since a $ref cannot carry sibling keywords, // they ride the override arm of an allOf compound. func TestCoverage_Bug958(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/958/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_bug_977_test.go b/internal/integration/coverage_bug_977_test.go index 9dbdf1a3..41a4807c 100644 --- a/internal/integration/coverage_bug_977_test.go +++ b/internal/integration/coverage_bug_977_test.go @@ -16,7 +16,7 @@ import ( // by a named string type (type Role string) renders as {type:object, additionalProperties:} // — its underlying string kind makes it a valid object-key. func TestCoverage_Bug977(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{Packages: []string{"./bugs/977/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true}) + doc, err := runScan(&codescan.Options{Packages: []string{"./bugs/977/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true}) require.NoError(t, err) perms := doc.Definitions["Acl"].Properties["perms"] assert.Equal(t, []string{"object"}, []string(perms.Type)) diff --git a/internal/integration/coverage_bug_989_test.go b/internal/integration/coverage_bug_989_test.go index f9609b12..b65ca7fa 100644 --- a/internal/integration/coverage_bug_989_test.go +++ b/internal/integration/coverage_bug_989_test.go @@ -21,7 +21,7 @@ import ( // an inline description value there is NOT captured (the 403 gets an empty description). // To add a description, use swagger:operation or a dedicated swagger:response object. func TestCoverage_Bug989(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./bugs/989/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_default_allof_embeds_test.go b/internal/integration/coverage_default_allof_embeds_test.go index 8358d886..12b69b17 100644 --- a/internal/integration/coverage_default_allof_embeds_test.go +++ b/internal/integration/coverage_default_allof_embeds_test.go @@ -15,7 +15,7 @@ import ( func runDefaultAllOfEmbeds(t *testing.T, on bool) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/default-allof-embeds/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_description_title_override_test.go b/internal/integration/coverage_description_title_override_test.go index a9ea6e39..5e25efea 100644 --- a/internal/integration/coverage_description_title_override_test.go +++ b/internal/integration/coverage_description_title_override_test.go @@ -20,7 +20,7 @@ import ( // scan.empty-override. func TestCoverage_DescriptionTitleOverride_Schema(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/description-title-override/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -101,7 +101,7 @@ func TestCoverage_DescriptionTitleOverride_Schema(t *testing.T) { // siblings of the $ref ({$ref, title, description}) rather than dropped — the override flows // through the same machinery a prose description would. func TestCoverage_DescriptionTitleOverride_RefSiblings(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/description-title-override/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -126,7 +126,7 @@ func TestCoverage_DescriptionTitleOverride_RefSiblings(t *testing.T) { // description override still applies. func TestCoverage_DescriptionTitleOverride_Responses(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/description-title-override-responses/..."}, WorkDir: scantest.FixturesDir(), OnDiagnostic: func(d grammar.Diagnostic) { diff --git a/internal/integration/coverage_diagnostics_dedup_test.go b/internal/integration/coverage_diagnostics_dedup_test.go index 023214c5..9595f6df 100644 --- a/internal/integration/coverage_diagnostics_dedup_test.go +++ b/internal/integration/coverage_diagnostics_dedup_test.go @@ -22,7 +22,7 @@ import ( func TestCoverage_DiagnosticsDeduped(t *testing.T) { counts := map[string]int{} emitted := 0 - _, err := codescan.Run(&codescan.Options{ + _, err := runScan(&codescan.Options{ Packages: []string{"./goparsing/classification/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_diagnostics_from_logger_test.go b/internal/integration/coverage_diagnostics_from_logger_test.go index 1ace74a6..c5663484 100644 --- a/internal/integration/coverage_diagnostics_from_logger_test.go +++ b/internal/integration/coverage_diagnostics_from_logger_test.go @@ -21,7 +21,7 @@ import ( // (never to stderr). func TestDiagnostics_UnsupportedGoType(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/unsupported-go-type/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -60,7 +60,7 @@ func TestDiagnostics_UnsupportedGoType(t *testing.T) { // Hint per dropped route through OnDiagnostic. func TestDiagnostics_IgnoredByTag(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./goparsing/petstore/..."}, WorkDir: scantest.FixturesDir(), ExcludeTags: []string{"orders"}, diff --git a/internal/integration/coverage_discriminated_nested_test.go b/internal/integration/coverage_discriminated_nested_test.go index b5b40fb1..eb94f2a2 100644 --- a/internal/integration/coverage_discriminated_nested_test.go +++ b/internal/integration/coverage_discriminated_nested_test.go @@ -21,7 +21,7 @@ const discriminatedNestedPkg = "./enhancements/discriminated-subtypes-nested/... func runNested(t *testing.T, scanModels, prune bool) (*oaispec.Swagger, map[grammar.Code][]grammar.Diagnostic) { t.Helper() byCode := map[grammar.Code][]grammar.Diagnostic{} - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{discriminatedNestedPkg}, WorkDir: scantest.FixturesDir(), ScanModels: scanModels, diff --git a/internal/integration/coverage_discriminated_subtypes_test.go b/internal/integration/coverage_discriminated_subtypes_test.go index 68c8d587..43617996 100644 --- a/internal/integration/coverage_discriminated_subtypes_test.go +++ b/internal/integration/coverage_discriminated_subtypes_test.go @@ -20,7 +20,7 @@ const discriminatedSubtypesPkg = "./enhancements/discriminated-subtypes/..." func runSubtypes(t *testing.T, scanModels, prune bool) (*oaispec.Swagger, map[grammar.Code][]grammar.Diagnostic) { t.Helper() byCode := map[grammar.Code][]grammar.Diagnostic{} - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{discriminatedSubtypesPkg}, WorkDir: scantest.FixturesDir(), ScanModels: scanModels, diff --git a/internal/integration/coverage_embed_inheritance_test.go b/internal/integration/coverage_embed_inheritance_test.go index a07f2403..00e8e488 100644 --- a/internal/integration/coverage_embed_inheritance_test.go +++ b/internal/integration/coverage_embed_inheritance_test.go @@ -19,7 +19,7 @@ import ( // // Exportedness stays per-field — the unexported promoted member never surfaces. func TestCoverage_EmbedInheritance(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/embed-inheritance/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_embed_override_test.go b/internal/integration/coverage_embed_override_test.go index 15c03920..4f58c615 100644 --- a/internal/integration/coverage_embed_override_test.go +++ b/internal/integration/coverage_embed_override_test.go @@ -18,7 +18,7 @@ const embedOverridePkg = "./enhancements/default-allof-embeds-override/..." // runEmbedOverride scans the override fixture in one of the two embed renderings. func runEmbedOverride(t *testing.T, defaultAllOf bool) *oaispec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{embedOverridePkg}, WorkDir: scantest.FixturesDir(), DefaultAllOfForEmbeds: defaultAllOf, diff --git a/internal/integration/coverage_emit_x_go_type_test.go b/internal/integration/coverage_emit_x_go_type_test.go index 6e44846a..24e384be 100644 --- a/internal/integration/coverage_emit_x_go_type_test.go +++ b/internal/integration/coverage_emit_x_go_type_test.go @@ -20,7 +20,7 @@ func TestCoverage_EmitXGoType(t *testing.T) { const pkgType = "github.com/go-openapi/codescan/testdata/enhancements/emit-x-go-type.Widget" // Default: x-go-name / x-go-package are present, x-go-type is not. - off, err := codescan.Run(&codescan.Options{ + off, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/emit-x-go-type/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, }) require.NoError(t, err) @@ -29,7 +29,7 @@ func TestCoverage_EmitXGoType(t *testing.T) { assert.Nil(t, w.Extensions["x-go-type"], "x-go-type is opt-in (off by default)") // EmitXGoType: x-go-type records the fully-qualified Go type. - on, err := codescan.Run(&codescan.Options{ + on, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/emit-x-go-type/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, EmitXGoType: true, }) @@ -37,7 +37,7 @@ func TestCoverage_EmitXGoType(t *testing.T) { assert.Equal(t, pkgType, on.Definitions["Widget"].Extensions["x-go-type"], "EmitXGoType stamps the qualified Go type (go-swagger#2924)") // SkipExtensions wins: no vendor extension is emitted even with EmitXGoType. - none, err := codescan.Run(&codescan.Options{ + none, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/emit-x-go-type/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, EmitXGoType: true, SkipExtensions: true, }) diff --git a/internal/integration/coverage_enhancements_test.go b/internal/integration/coverage_enhancements_test.go index 475a4b93..9618e4d3 100644 --- a/internal/integration/coverage_enhancements_test.go +++ b/internal/integration/coverage_enhancements_test.go @@ -21,7 +21,7 @@ import ( // refactor. func TestCoverage_EmbeddedTypes(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/embedded-types/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -33,7 +33,7 @@ func TestCoverage_EmbeddedTypes(t *testing.T) { } func TestCoverage_AllOfEdges(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/allof-edges/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -45,7 +45,7 @@ func TestCoverage_AllOfEdges(t *testing.T) { } func TestCoverage_StrfmtArrays(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/strfmt-arrays/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -57,7 +57,7 @@ func TestCoverage_StrfmtArrays(t *testing.T) { } func TestCoverage_DefaultsExamples(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/defaults-examples/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -69,7 +69,7 @@ func TestCoverage_DefaultsExamples(t *testing.T) { } func TestCoverage_InterfaceMethods(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/interface-methods/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -81,7 +81,7 @@ func TestCoverage_InterfaceMethods(t *testing.T) { } func TestCoverage_InterfaceMethods_XNullable(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/interface-methods/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -97,7 +97,7 @@ func TestCoverage_InterfaceMethods_XNullable(t *testing.T) { // buildFieldAlias take the non-transparent expansion path: each alias resolves to the underlying // type and the target is emitted inline rather than as a $ref. func TestCoverage_AliasExpand(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-expand/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -144,7 +144,7 @@ func TestCoverage_AliasExpand(t *testing.T) { // response aliases resolve to $ref via makeRef, and the alias-of-alias chain resolves through the // non-transparent switch. func TestCoverage_AliasRef(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-expand/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -162,7 +162,7 @@ func TestCoverage_AliasRef(t *testing.T) { // Under RefAliases=true the scanner takes the responseBuilder.buildAlias refAliases switch, which // is not covered by any other test. func TestCoverage_AliasResponseRef(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/alias-response/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -175,7 +175,7 @@ func TestCoverage_AliasResponseRef(t *testing.T) { } func TestCoverage_ResponseEdges(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/response-edges/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -188,7 +188,7 @@ func TestCoverage_ResponseEdges(t *testing.T) { func TestCoverage_NamedBasic(t *testing.T) { var diags []string - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/named-basic/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -224,7 +224,7 @@ func TestCoverage_NamedBasic(t *testing.T) { // // Pins today's broken behavior so the gap is visible until it's fixed. func TestCoverage_WrapperDeclTypeOverride(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/wrapper-decl-type-override/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -262,7 +262,7 @@ func TestCoverage_WrapperDeclTypeOverride(t *testing.T) { // A field-level `swagger:type` is now consumed by scanFieldDoc and applied in applyFieldCarrier // after buildFromType (Gap C — closed). func TestCoverage_RawMessageOverride(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/raw-message-override/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -342,7 +342,7 @@ func TestCoverage_RawMessageOverride(t *testing.T) { // // Covers buildNamedSlice, buildNamedArray and buildNamedStruct fallthrough branches. func TestCoverage_SwaggerTypeArray(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/swagger-type-array/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -354,7 +354,7 @@ func TestCoverage_SwaggerTypeArray(t *testing.T) { } func TestCoverage_RefAliasChain(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/ref-alias-chain/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -392,7 +392,7 @@ func TestCoverage_RefAliasChain(t *testing.T) { } func TestCoverage_EnumDocs(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/enum-docs/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -417,7 +417,7 @@ func TestCoverage_EnumDocs(t *testing.T) { // The golden snapshot becomes the v1-behavior contract the v2 migration either preserves or // consciously diverges from. func TestCoverage_EnumOverrides(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/enum-overrides/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -429,7 +429,7 @@ func TestCoverage_EnumOverrides(t *testing.T) { } func TestCoverage_TextMarshal(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/text-marshal/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -446,7 +446,7 @@ func TestCoverage_TextMarshal(t *testing.T) { // underlying shape, not as a $ref to the generic declaration (whose own schema is empty because // type parameters are filtered as UnsupportedBuiltinType). func TestCoverage_GenericInstantiation(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/generic-instantiation/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -458,7 +458,7 @@ func TestCoverage_GenericInstantiation(t *testing.T) { } func TestCoverage_AllHTTPMethods(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/all-http-methods/..."}, WorkDir: scantest.FixturesDir(), }) @@ -479,7 +479,7 @@ func TestCoverage_AllHTTPMethods(t *testing.T) { // This exercises the default branch of typeIndex.detectNodes. func TestCoverage_UnknownAnnotation(t *testing.T) { var diags []codescan.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/unknown-annotation/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -507,7 +507,7 @@ func TestCoverage_UnknownAnnotation(t *testing.T) { } func TestCoverage_NamedStructTags(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/named-struct-tags/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -519,7 +519,7 @@ func TestCoverage_NamedStructTags(t *testing.T) { } func TestCoverage_NamedStructTagsRef(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/named-struct-tags-ref/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -531,7 +531,7 @@ func TestCoverage_NamedStructTagsRef(t *testing.T) { } func TestCoverage_TopLevelKinds(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/top-level-kinds/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -572,7 +572,7 @@ func TestCoverage_InputOverlay(t *testing.T) { }, } - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/embedded-types/..."}, WorkDir: scantest.FixturesDir(), InputSpec: input, @@ -591,7 +591,7 @@ func TestCoverage_InputOverlay(t *testing.T) { // The pre-fix golden shows the buggy state (LocalItem missing from definitions). // The fix commit regenerates the golden to show LocalItem appearing, witnessing the resolution. func TestCoverage_ParametersMapPostDecl(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/parameters-map-postdecl/..."}, WorkDir: scantest.FixturesDir(), }) @@ -610,7 +610,7 @@ func TestCoverage_ParametersMapPostDecl(t *testing.T) { // See M6.5-PRE plan. func TestCoverage_RoutesParamsPath(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-path/..."}, WorkDir: scantest.FixturesDir(), }) @@ -621,7 +621,7 @@ func TestCoverage_RoutesParamsPath(t *testing.T) { } func TestCoverage_RoutesParamsQueryValidations(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-query-validations/..."}, WorkDir: scantest.FixturesDir(), }) @@ -632,7 +632,7 @@ func TestCoverage_RoutesParamsQueryValidations(t *testing.T) { } func TestCoverage_RoutesParamsBodyRef(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-body-ref/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -644,7 +644,7 @@ func TestCoverage_RoutesParamsBodyRef(t *testing.T) { } func TestCoverage_RoutesResponsesTaggedBody(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-responses-tagged-body/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -656,7 +656,7 @@ func TestCoverage_RoutesResponsesTaggedBody(t *testing.T) { } func TestCoverage_RoutesParamsQueryString(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-query-string/..."}, WorkDir: scantest.FixturesDir(), }) @@ -666,7 +666,7 @@ func TestCoverage_RoutesParamsQueryString(t *testing.T) { } func TestCoverage_RoutesParamsQueryNumber(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-query-number/..."}, WorkDir: scantest.FixturesDir(), }) @@ -676,7 +676,7 @@ func TestCoverage_RoutesParamsQueryNumber(t *testing.T) { } func TestCoverage_RoutesParamsQueryBoolean(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-query-boolean/..."}, WorkDir: scantest.FixturesDir(), }) @@ -686,7 +686,7 @@ func TestCoverage_RoutesParamsQueryBoolean(t *testing.T) { } func TestCoverage_RoutesParamsQueryArray(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-query-array/..."}, WorkDir: scantest.FixturesDir(), }) @@ -696,7 +696,7 @@ func TestCoverage_RoutesParamsQueryArray(t *testing.T) { } func TestCoverage_RoutesParamsHeaderString(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-header-string/..."}, WorkDir: scantest.FixturesDir(), }) @@ -706,7 +706,7 @@ func TestCoverage_RoutesParamsHeaderString(t *testing.T) { } func TestCoverage_RoutesParamsFormString(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-form-string/..."}, WorkDir: scantest.FixturesDir(), }) @@ -716,7 +716,7 @@ func TestCoverage_RoutesParamsFormString(t *testing.T) { } func TestCoverage_RoutesParamsBodyArray(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-body-array/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -727,7 +727,7 @@ func TestCoverage_RoutesParamsBodyArray(t *testing.T) { } func TestCoverage_RoutesParamsBodyArrayNested(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-body-array-nested/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -738,7 +738,7 @@ func TestCoverage_RoutesParamsBodyArrayNested(t *testing.T) { } func TestCoverage_RoutesParamsBodyWithSchemaValidations(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-body-with-schema-validations/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -749,7 +749,7 @@ func TestCoverage_RoutesParamsBodyWithSchemaValidations(t *testing.T) { } func TestCoverage_RoutesParamsMultiple(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-multiple/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -760,7 +760,7 @@ func TestCoverage_RoutesParamsMultiple(t *testing.T) { } func TestCoverage_RoutesParamsUnknownKey(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-unknown-key/..."}, WorkDir: scantest.FixturesDir(), }) @@ -770,7 +770,7 @@ func TestCoverage_RoutesParamsUnknownKey(t *testing.T) { } func TestCoverage_RoutesParamsEmptyChunk(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-params-empty-chunk/..."}, WorkDir: scantest.FixturesDir(), }) @@ -780,7 +780,7 @@ func TestCoverage_RoutesParamsEmptyChunk(t *testing.T) { } func TestCoverage_RoutesResponsesPositional(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-responses-positional/..."}, WorkDir: scantest.FixturesDir(), }) @@ -790,7 +790,7 @@ func TestCoverage_RoutesResponsesPositional(t *testing.T) { } func TestCoverage_RoutesResponsesTaggedResponse(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-responses-tagged-response/..."}, WorkDir: scantest.FixturesDir(), }) @@ -800,7 +800,7 @@ func TestCoverage_RoutesResponsesTaggedResponse(t *testing.T) { } func TestCoverage_RoutesResponsesMixedBodies(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-responses-mixed-bodies/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -811,7 +811,7 @@ func TestCoverage_RoutesResponsesMixedBodies(t *testing.T) { } func TestCoverage_RoutesResponsesDescriptionOnly(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-responses-description-only/..."}, WorkDir: scantest.FixturesDir(), }) @@ -821,7 +821,7 @@ func TestCoverage_RoutesResponsesDescriptionOnly(t *testing.T) { } func TestCoverage_RoutesResponsesDefault(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-responses-default/..."}, WorkDir: scantest.FixturesDir(), }) @@ -831,7 +831,7 @@ func TestCoverage_RoutesResponsesDefault(t *testing.T) { } func TestCoverage_RoutesResponsesArray(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-responses-array/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -842,7 +842,7 @@ func TestCoverage_RoutesResponsesArray(t *testing.T) { } func TestCoverage_RoutesResponsesEmptyValue(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-responses-empty-value/..."}, WorkDir: scantest.FixturesDir(), }) @@ -852,7 +852,7 @@ func TestCoverage_RoutesResponsesEmptyValue(t *testing.T) { } func TestCoverage_RoutesResponsesDefinitionFallback(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-responses-definition-fallback/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -863,7 +863,7 @@ func TestCoverage_RoutesResponsesDefinitionFallback(t *testing.T) { } func TestCoverage_RoutesResponsesMultipleCodes(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-responses-multiple-codes/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -874,7 +874,7 @@ func TestCoverage_RoutesResponsesMultipleCodes(t *testing.T) { } func TestCoverage_RoutesFullPetstoreShape(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-full-petstore-shape/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -885,7 +885,7 @@ func TestCoverage_RoutesFullPetstoreShape(t *testing.T) { } func TestCoverage_RoutesMultiMethodSamePath(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-multi-method-same-path/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -896,7 +896,7 @@ func TestCoverage_RoutesMultiMethodSamePath(t *testing.T) { } func TestCoverage_RoutesResponsesSpaceBodyQuirk(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-responses-space-body-quirk/..."}, WorkDir: scantest.FixturesDir(), }) @@ -906,7 +906,7 @@ func TestCoverage_RoutesResponsesSpaceBodyQuirk(t *testing.T) { } func TestCoverage_RoutesResponsesRefNotFound(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-responses-ref-not-found/..."}, WorkDir: scantest.FixturesDir(), }) @@ -916,7 +916,7 @@ func TestCoverage_RoutesResponsesRefNotFound(t *testing.T) { } func TestCoverage_RoutesDescriptionDashList(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-description-dash-list/..."}, WorkDir: scantest.FixturesDir(), }) @@ -926,7 +926,7 @@ func TestCoverage_RoutesDescriptionDashList(t *testing.T) { } func TestCoverage_RoutesDescriptionYAMLFenceAbsorb(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-description-yaml-fence-absorb/..."}, WorkDir: scantest.FixturesDir(), }) @@ -936,7 +936,7 @@ func TestCoverage_RoutesDescriptionYAMLFenceAbsorb(t *testing.T) { } func TestCoverage_RoutesListsFlexForms(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/routes-lists-flex-forms/..."}, WorkDir: scantest.FixturesDir(), }) @@ -946,7 +946,7 @@ func TestCoverage_RoutesListsFlexForms(t *testing.T) { } func TestCoverage_MetaListsFlexForms(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/meta-lists-flex-forms/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_external_docs_objects_test.go b/internal/integration/coverage_external_docs_objects_test.go index 8aa11c70..ad972061 100644 --- a/internal/integration/coverage_external_docs_objects_test.go +++ b/internal/integration/coverage_external_docs_objects_test.go @@ -18,7 +18,7 @@ import ( // a simple-schema query parameter. func TestCoverage_ExternalDocsObjects(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/external-docs-objects/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_external_docs_test.go b/internal/integration/coverage_external_docs_test.go index 23c08ab9..8590aec5 100644 --- a/internal/integration/coverage_external_docs_test.go +++ b/internal/integration/coverage_external_docs_test.go @@ -18,7 +18,7 @@ import ( // - an empty block, which must NOT emit a useless `externalDocs: {}`. func TestCoverage_ExternalDocs(t *testing.T) { t.Run("aliased url-only", func(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/external-docs/aliased/..."}, WorkDir: scantest.FixturesDir(), }) @@ -29,7 +29,7 @@ func TestCoverage_ExternalDocs(t *testing.T) { }) t.Run("empty block skipped", func(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/external-docs/empty/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_godoc_links_test.go b/internal/integration/coverage_godoc_links_test.go index 463d2572..d2cf4439 100644 --- a/internal/integration/coverage_godoc_links_test.go +++ b/internal/integration/coverage_godoc_links_test.go @@ -15,7 +15,7 @@ import ( func runGodocLinks(t *testing.T, on bool) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/godoc-links/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_godoc_links_walk_test.go b/internal/integration/coverage_godoc_links_walk_test.go index c4e20f4f..d7233c95 100644 --- a/internal/integration/coverage_godoc_links_walk_test.go +++ b/internal/integration/coverage_godoc_links_walk_test.go @@ -23,7 +23,7 @@ import ( // The godoc-links fixture is models-only, so it only ever exercised the definitions arm of that // walk. This one puts a doc-link in each of the other positions. func TestCoverage_GodocLinksWalk(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/godoc-links-walk/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -120,7 +120,7 @@ func TestCoverage_GodocLinksWalk(t *testing.T) { // // Assertions rather than a golden: this pins one branch, not a document shape. func TestCoverage_GodocLinksWalk_PrunedTarget(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/godoc-links-walk/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_header_extensions_test.go b/internal/integration/coverage_header_extensions_test.go index 2b8deeab..2b6f9792 100644 --- a/internal/integration/coverage_header_extensions_test.go +++ b/internal/integration/coverage_header_extensions_test.go @@ -18,7 +18,7 @@ import ( // Pre-M2, user-authored `Extensions:` blocks on header fields were silently dropped; post-M2 they // land on the header's Extensions map with grammar-typed values. func TestCoverage_HeaderExtensions(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/header-extensions/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_header_named_basic_test.go b/internal/integration/coverage_header_named_basic_test.go index ad0c97b5..872a4293 100644 --- a/internal/integration/coverage_header_named_basic_test.go +++ b/internal/integration/coverage_header_named_basic_test.go @@ -18,7 +18,7 @@ import ( // Pre-fix, a header parameter typed as a named string emitted a `$ref` (invalid under OAS v2 // SimpleSchema); post-fix it inlines as `{type: string}`. func TestCoverage_HeaderNamedBasic(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/header-named-basic/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_in_case_insensitive_test.go b/internal/integration/coverage_in_case_insensitive_test.go index 79f319bc..a73f0f21 100644 --- a/internal/integration/coverage_in_case_insensitive_test.go +++ b/internal/integration/coverage_in_case_insensitive_test.go @@ -29,7 +29,7 @@ import ( // mixedCaseResponse: shaped as a body response with Payload // schema. func TestCoverage_InCaseInsensitive(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/in-case-insensitive/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_inner_markdown_test.go b/internal/integration/coverage_inner_markdown_test.go index 8647127e..c00089aa 100644 --- a/internal/integration/coverage_inner_markdown_test.go +++ b/internal/integration/coverage_inner_markdown_test.go @@ -19,7 +19,7 @@ import ( // // Reframes go-swagger#3211. func TestCoverage_InnerMarkdown(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/inner-markdown/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_interface_name_verbatim_test.go b/internal/integration/coverage_interface_name_verbatim_test.go index 93afa1f0..9c10f4f0 100644 --- a/internal/integration/coverage_interface_name_verbatim_test.go +++ b/internal/integration/coverage_interface_name_verbatim_test.go @@ -22,7 +22,7 @@ import ( // See observed-quirks Q9 (RESOLVED, Stream M) and internal/builders/schema/README.md // §interface-naming. func TestCoverage_InterfaceNameVerbatim(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/interface-name-verbatim/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_keyword_xpkg_test.go b/internal/integration/coverage_keyword_xpkg_test.go index 74b83bb1..221e63b4 100644 --- a/internal/integration/coverage_keyword_xpkg_test.go +++ b/internal/integration/coverage_keyword_xpkg_test.go @@ -20,7 +20,7 @@ import ( // This matches the leaf resolution the name-identity engine gave routes/responses. func TestKeywordCrossPackageLeaf(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/keyword-xpkg-leaf/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_malformed_test.go b/internal/integration/coverage_malformed_test.go index de2bc048..8ffc93be 100644 --- a/internal/integration/coverage_malformed_test.go +++ b/internal/integration/coverage_malformed_test.go @@ -17,7 +17,7 @@ import ( // No goldens are produced — these tests exist purely to pin the error surface. func TestMalformed_DefaultInt(t *testing.T) { - _, err := codescan.Run(&codescan.Options{ + _, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/malformed/default-int/..."}, WorkDir: scantest.FixturesDir(), }) @@ -25,7 +25,7 @@ func TestMalformed_DefaultInt(t *testing.T) { } func TestMalformed_ExampleInt(t *testing.T) { - _, err := codescan.Run(&codescan.Options{ + _, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/malformed/example-int/..."}, WorkDir: scantest.FixturesDir(), }) @@ -39,7 +39,7 @@ func TestMalformed_ExampleInt(t *testing.T) { // diagnostic at grammar parse time and drop, but Run still succeeds. // The fixture's bad key ends up absent from the captured golden. func TestMalformed_MetaBadExtensionKey(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/malformed/meta-bad-ext-key/..."}, WorkDir: scantest.FixturesDir(), }) @@ -53,7 +53,7 @@ func TestMalformed_MetaBadExtensionKey(t *testing.T) { // // Same diagnose-and-drop shift, here under the InfoExtensions: keyword. func TestMalformed_InfoBadExtensionKey(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/malformed/info-bad-ext-key/..."}, WorkDir: scantest.FixturesDir(), }) @@ -64,7 +64,7 @@ func TestMalformed_InfoBadExtensionKey(t *testing.T) { } func TestMalformed_BadContact(t *testing.T) { - _, err := codescan.Run(&codescan.Options{ + _, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/malformed/bad-contact/..."}, WorkDir: scantest.FixturesDir(), }) @@ -80,7 +80,7 @@ func TestMalformed_BadContact(t *testing.T) { // The fixture's malformed response line ends up absent from the captured golden — the witness IS // the dropped output. func TestMalformed_DuplicateBodyTag(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/malformed/duplicate-body-tag/..."}, WorkDir: scantest.FixturesDir(), }) @@ -95,7 +95,7 @@ func TestMalformed_DuplicateBodyTag(t *testing.T) { // Unknown tag prefixes emit a diagnostic and drop the response line; the rest of the route builds // normally. func TestMalformed_BadResponseTag(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/malformed/bad-response-tag/..."}, WorkDir: scantest.FixturesDir(), }) @@ -106,7 +106,7 @@ func TestMalformed_BadResponseTag(t *testing.T) { } func TestMalformed_BadSecurityDefinitions(t *testing.T) { - _, err := codescan.Run(&codescan.Options{ + _, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/malformed/bad-sec-defs/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_meta_securitydefs_duplicate_keys_test.go b/internal/integration/coverage_meta_securitydefs_duplicate_keys_test.go index 320262ae..449a2b7b 100644 --- a/internal/integration/coverage_meta_securitydefs_duplicate_keys_test.go +++ b/internal/integration/coverage_meta_securitydefs_duplicate_keys_test.go @@ -33,7 +33,7 @@ import ( // Diagnostic emission on duplicates is deferred to the yaml-library swap (see the // forthcoming-features roadmap). func TestCoverage_MetaSecurityDefsDuplicateKeys(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/meta-securitydefs-duplicate-keys/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_meta_tos_schemes_terminator_test.go b/internal/integration/coverage_meta_tos_schemes_terminator_test.go index f207c47c..b9cb9bd9 100644 --- a/internal/integration/coverage_meta_tos_schemes_terminator_test.go +++ b/internal/integration/coverage_meta_tos_schemes_terminator_test.go @@ -25,7 +25,7 @@ import ( // "termsOfService": "http://example.com/terms/" // "schemes": ["http", "https"] func TestCoverage_MetaTOSSchemesTerminator(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/meta-tos-schemes-terminator/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_name_from_tags_test.go b/internal/integration/coverage_name_from_tags_test.go index 95e6dfdc..a30aa097 100644 --- a/internal/integration/coverage_name_from_tags_test.go +++ b/internal/integration/coverage_name_from_tags_test.go @@ -28,7 +28,7 @@ import ( func TestCoverage_NameFromTags(t *testing.T) { run := func(t *testing.T, nameTags []string) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/name-from-tags/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_name_identity_test.go b/internal/integration/coverage_name_identity_test.go index 87142696..aebb6770 100644 --- a/internal/integration/coverage_name_identity_test.go +++ b/internal/integration/coverage_name_identity_test.go @@ -44,7 +44,7 @@ func nameIdentityDoc(t *testing.T, pkg string) *oaispec.Swagger { func nameIdentityDocDiags(t *testing.T, pkg string) (*oaispec.Swagger, []grammar.Diagnostic) { t.Helper() var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{pkg}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -239,7 +239,7 @@ func TestNameIdentity_Hierarchical(t *testing.T) { t.Run("nested when EmitHierarchicalNames is set", func(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{pkg}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_object_keywords_context_test.go b/internal/integration/coverage_object_keywords_context_test.go index bea8cbe4..2d515a12 100644 --- a/internal/integration/coverage_object_keywords_context_test.go +++ b/internal/integration/coverage_object_keywords_context_test.go @@ -21,7 +21,7 @@ import ( // - dropped + CodeUnsupportedInSimpleSchema on a SimpleSchema param. func TestCoverage_ObjectKeywordsContext(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/object-keywords-context/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_param_name_keyword_test.go b/internal/integration/coverage_param_name_keyword_test.go index d3439737..b9a4444b 100644 --- a/internal/integration/coverage_param_name_keyword_test.go +++ b/internal/integration/coverage_param_name_keyword_test.go @@ -18,7 +18,7 @@ import ( // structural keyword — is stripped from the parameter description rather than leaking into it as // prose (it previously had no handler and fell through to Block.Prose()). func TestCoverage_ParamNameKeyword(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/param-name-keyword/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_path_regex_stripping_test.go b/internal/integration/coverage_path_regex_stripping_test.go index 281ac7c6..0f7f3596 100644 --- a/internal/integration/coverage_path_regex_stripping_test.go +++ b/internal/integration/coverage_path_regex_stripping_test.go @@ -18,7 +18,7 @@ import ( // no warning). func TestCoverage_PathRegexStripping(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/path-regex-stripping/..."}, WorkDir: scantest.FixturesDir(), OnDiagnostic: func(d grammar.Diagnostic) { diff --git a/internal/integration/coverage_pattern_properties_test.go b/internal/integration/coverage_pattern_properties_test.go index b91869d5..a1af0b92 100644 --- a/internal/integration/coverage_pattern_properties_test.go +++ b/internal/integration/coverage_pattern_properties_test.go @@ -19,7 +19,7 @@ import ( // raises a CodeInvalidAnnotation diagnostic (never dropped silently). func TestCoverage_PatternProperties(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/pattern-properties/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_pattern_properties_typed_test.go b/internal/integration/coverage_pattern_properties_typed_test.go index e19d4002..8ee9c831 100644 --- a/internal/integration/coverage_pattern_properties_typed_test.go +++ b/internal/integration/coverage_pattern_properties_typed_test.go @@ -18,7 +18,7 @@ import ( // lowest-priority precedence rule. func TestCoverage_PatternProperties_Typed(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/pattern-properties-typed/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_primitive_response_test.go b/internal/integration/coverage_primitive_response_test.go index f671dafb..2cee9830 100644 --- a/internal/integration/coverage_primitive_response_test.go +++ b/internal/integration/coverage_primitive_response_test.go @@ -20,7 +20,7 @@ import ( // and a model-ref control that must still resolve to a $ref. func TestCoverage_PrimitiveResponse(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/primitive-response/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_provenance_test.go b/internal/integration/coverage_provenance_test.go index a373da7d..a25fa0dd 100644 --- a/internal/integration/coverage_provenance_test.go +++ b/internal/integration/coverage_provenance_test.go @@ -25,7 +25,7 @@ import ( // TestCoverage_ProvenanceAnchorKinds; the geometry safety net by TestCoverage_ProvenanceGeometry. func TestCoverage_ProvenanceDefinitions(t *testing.T) { byPointer := map[string]scanner.Provenance{} - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/named-basic"}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -76,7 +76,7 @@ func TestCoverage_ProvenanceDefinitions(t *testing.T) { // dangled after the rename. func TestCoverage_ProvenanceCollisionRename(t *testing.T) { byPointer := map[string]scanner.Provenance{} - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/name-identity-mixed/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -126,7 +126,7 @@ func TestCoverage_ProvenanceCollisionRename(t *testing.T) { // TestCoverage_ProvenanceOffByDefault confirms the callback is opt-in: a scan without OnProvenance // set produces the same spec and records nothing. func TestCoverage_ProvenanceOffByDefault(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/named-basic"}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -187,7 +187,7 @@ func TestCoverage_ProvenanceGeometry(t *testing.T) { for _, pkg := range fixtures { t.Run(pkg, func(t *testing.T) { var recorded []scanner.Provenance - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{pkg}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -262,7 +262,7 @@ func TestCoverage_ProvenanceAnchorKinds(t *testing.T) { // lands on `// default: 1.5` rather than the field declaration. func TestCoverage_ProvenanceValidations(t *testing.T) { byPointer := map[string]scanner.Provenance{} - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/defaults-examples"}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -301,7 +301,7 @@ func TestCoverage_ProvenanceValidations(t *testing.T) { // sibling), so this is the only way to reach them. func TestCoverage_ProvenanceMetaAndRoutes(t *testing.T) { byPointer := map[string]scanner.Provenance{} - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./goparsing/petstore/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -346,7 +346,7 @@ func TestCoverage_ProvenanceMetaAndRoutes(t *testing.T) { // `over` is an array, not an object). func TestCoverage_ProvenanceSchemaShapes(t *testing.T) { byPointer := map[string]scanner.Provenance{} - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/provenance-schema-shapes"}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -396,7 +396,7 @@ func TestCoverage_ProvenanceSchemaShapes(t *testing.T) { // // The fixture's regex deliberately contains both '/' and '~'. func TestCoverage_ProvenancePatternPropertyPointer(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/provenance-schema-shapes"}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -438,7 +438,7 @@ func TestCoverage_ProvenancePatternPropertyPointer(t *testing.T) { // its own definition instead). func TestCoverage_ProvenanceParamsResponses(t *testing.T) { byPointer := map[string]scanner.Provenance{} - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/provenance-params-responses"}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -476,7 +476,7 @@ func TestCoverage_ProvenanceParamsResponses(t *testing.T) { func scanAndResolve(t *testing.T, pkg string) map[string]scanner.Provenance { t.Helper() seen := map[string]scanner.Provenance{} - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{pkg}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_prune_unused_test.go b/internal/integration/coverage_prune_unused_test.go index 9a99d0a0..eacf0298 100644 --- a/internal/integration/coverage_prune_unused_test.go +++ b/internal/integration/coverage_prune_unused_test.go @@ -22,7 +22,7 @@ const pruneUnusedPkg = "./enhancements/prune-unused/..." func runPrune(t *testing.T, scanModels, prune bool, input *oaispec.Swagger) (*oaispec.Swagger, map[grammar.Code][]grammar.Diagnostic) { t.Helper() byCode := map[grammar.Code][]grammar.Diagnostic{} - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{pruneUnusedPkg}, WorkDir: scantest.FixturesDir(), InputSpec: input, @@ -146,7 +146,7 @@ func TestPruneUnused_InputSpecPinned(t *testing.T) { // references a pruned node, and every emitted anchor resolves in the rendered spec. func TestPruneUnused_Provenance(t *testing.T) { var recorded []scanner.Provenance - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{pruneUnusedPkg}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_quirk_alias_deprecated_test.go b/internal/integration/coverage_quirk_alias_deprecated_test.go index 45891500..2ee9d78d 100644 --- a/internal/integration/coverage_quirk_alias_deprecated_test.go +++ b/internal/integration/coverage_quirk_alias_deprecated_test.go @@ -22,7 +22,7 @@ import ( // swagger:model). func TestQuirk_AliasDeprecated(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./quirks/alias-deprecated/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_quirk_alias_model_test.go b/internal/integration/coverage_quirk_alias_model_test.go index df44fd39..584202c8 100644 --- a/internal/integration/coverage_quirk_alias_model_test.go +++ b/internal/integration/coverage_quirk_alias_model_test.go @@ -34,7 +34,7 @@ func TestQuirk_AliasModelNoHang(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - doc, err := codescan.Run(tc.opts) + doc, err := runScan(tc.opts) require.NoError(t, err, "the model-annotated alias must scan, not hang (F9)") require.NotNil(t, doc) diff --git a/internal/integration/coverage_quirk_body_response_description_test.go b/internal/integration/coverage_quirk_body_response_description_test.go index 18cea3af..92f2e75e 100644 --- a/internal/integration/coverage_quirk_body_response_description_test.go +++ b/internal/integration/coverage_quirk_body_response_description_test.go @@ -24,7 +24,7 @@ import ( // 500: body:string → "Internal Server Error" (primitive → status phrase) // default: body:string → "Default response" (no phrase → placeholder) func TestQuirk_BodyResponseDescription(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./quirks/body-response-description/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_quirk_gofmt_meta_test.go b/internal/integration/coverage_quirk_gofmt_meta_test.go index 7077eb51..61ebfa44 100644 --- a/internal/integration/coverage_quirk_gofmt_meta_test.go +++ b/internal/integration/coverage_quirk_gofmt_meta_test.go @@ -27,7 +27,7 @@ import ( // This closes the residual behind #2959-class meta failures: write the natural column-0 form, run // gofmt, still scans. func TestQuirk_GofmtMetaYAML(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./quirks/gofmt-meta/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_quirk_gofmt_operation_test.go b/internal/integration/coverage_quirk_gofmt_operation_test.go index 6f79d60f..d8fe3aef 100644 --- a/internal/integration/coverage_quirk_gofmt_operation_test.go +++ b/internal/integration/coverage_quirk_gofmt_operation_test.go @@ -26,7 +26,7 @@ import ( // whose body interleaves 1-space prose keys with tab-prefixed value blocks. // The dedent now expands leading tabs to spaces before stripping the common indent. func TestQuirk_GofmtOperationYAML(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./quirks/gofmt-operation/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_quirk_meta_placement_test.go b/internal/integration/coverage_quirk_meta_placement_test.go index 2fd15d69..1ae69b21 100644 --- a/internal/integration/coverage_quirk_meta_placement_test.go +++ b/internal/integration/coverage_quirk_meta_placement_test.go @@ -40,7 +40,7 @@ func TestQuirk_MetaPlacement(t *testing.T) { // Wrong answer: the authored block is ignored, and an ordinary sentence about the package is // parsed as the meta block in its place. t.Run("below a package doc", func(t *testing.T) { - doc, err := codescan.Run(scanQuirkMetaPlacement(t, "below-package-doc")) + doc, err := runScan(scanQuirkMetaPlacement(t, "below-package-doc")) require.NoError(t, err, "a meta block below the package clause must scan") require.NotNil(t, doc) require.NotNil(t, doc.Info) @@ -61,7 +61,7 @@ func TestQuirk_MetaPlacement(t *testing.T) { // No answer at all: the file's doc is nil, and the anchor recorded for the info node // dereferenced it. t.Run("with no package doc", func(t *testing.T) { - doc, err := codescan.Run(scanQuirkMetaPlacement(t, "no-package-doc")) + doc, err := runScan(scanQuirkMetaPlacement(t, "no-package-doc")) require.NoError(t, err, "a file with no package doc must scan") require.NotNil(t, doc) require.NotNil(t, doc.Info) diff --git a/internal/integration/coverage_quirk_model_override_simpleschema_test.go b/internal/integration/coverage_quirk_model_override_simpleschema_test.go index 22f3df92..c0e39001 100644 --- a/internal/integration/coverage_quirk_model_override_simpleschema_test.go +++ b/internal/integration/coverage_quirk_model_override_simpleschema_test.go @@ -22,7 +22,7 @@ import ( // The buildNamedType gate is therefore (isModel && !simpleSchema): a regression would dissolve the // schema to {} via the simple-schema $ref safety net, losing the enum/format. func TestQuirk_ModelOverrideSimpleSchema(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./quirks/model-override-simpleschema/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_quirk_model_override_test.go b/internal/integration/coverage_quirk_model_override_test.go index f3791836..021b531c 100644 --- a/internal/integration/coverage_quirk_model_override_test.go +++ b/internal/integration/coverage_quirk_model_override_test.go @@ -24,7 +24,7 @@ import ( // still pin the current (no-enum) behaviour. func TestQuirk_ModelOverrideMatrix(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./quirks/model-override-matrix/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_quirk_name_field_test.go b/internal/integration/coverage_quirk_name_field_test.go index 4565947c..cf1e9ac8 100644 --- a/internal/integration/coverage_quirk_name_field_test.go +++ b/internal/integration/coverage_quirk_name_field_test.go @@ -22,7 +22,7 @@ import ( // The Go field name is still recorded as x-go-name when it differs from the emitted JSON name, so // the override does not lose traceability. func TestQuirk_NameOnField(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./quirks/name-on-field/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_quirk_name_keyword_universal_test.go b/internal/integration/coverage_quirk_name_keyword_universal_test.go index 1eff8db7..81a916e5 100644 --- a/internal/integration/coverage_quirk_name_keyword_universal_test.go +++ b/internal/integration/coverage_quirk_name_keyword_universal_test.go @@ -24,7 +24,7 @@ import ( // Precedence: name: keyword > swagger:name > json tag > Go field name. func TestQuirk_NameKeywordUniversal(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./quirks/name-keyword-universal/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_quirk_ref_example_coercion_test.go b/internal/integration/coverage_quirk_ref_example_coercion_test.go index de9a9b96..64d947c8 100644 --- a/internal/integration/coverage_quirk_ref_example_coercion_test.go +++ b/internal/integration/coverage_quirk_ref_example_coercion_test.go @@ -17,7 +17,7 @@ import ( // allOf override arm — matching the direct-field path — instead of riding the arm as a raw // string. func TestQuirk_RefExampleCoercion(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./quirks/ref-example-coercion/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_quirk_type_matrix_test.go b/internal/integration/coverage_quirk_type_matrix_test.go index d839a545..5c8ad00f 100644 --- a/internal/integration/coverage_quirk_type_matrix_test.go +++ b/internal/integration/coverage_quirk_type_matrix_test.go @@ -26,7 +26,7 @@ import ( // strfmt-iff-format-compatible rule. func TestQuirk_TypeMatrix(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./quirks/swagger-type-matrix/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_ref_siblings_test.go b/internal/integration/coverage_ref_siblings_test.go index 00193614..c6be07ff 100644 --- a/internal/integration/coverage_ref_siblings_test.go +++ b/internal/integration/coverage_ref_siblings_test.go @@ -24,7 +24,7 @@ import ( // never entered. func TestCoverage_RefSiblingValidations(t *testing.T) { var diags []codescan.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/ref-sibling-validations/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_response_bodyfield_example_test.go b/internal/integration/coverage_response_bodyfield_example_test.go index 29cd75c7..99eb8a8f 100644 --- a/internal/integration/coverage_response_bodyfield_example_test.go +++ b/internal/integration/coverage_response_bodyfield_example_test.go @@ -16,7 +16,7 @@ import ( // the body-field keywords land on the body schema rather than the discarded header // (go-swagger#3013, #2942 family). func TestCoverage_ResponseBodyFieldExample(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/response-bodyfield-example/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_response_examples_test.go b/internal/integration/coverage_response_examples_test.go index e2e81038..c722b23b 100644 --- a/internal/integration/coverage_response_examples_test.go +++ b/internal/integration/coverage_response_examples_test.go @@ -19,7 +19,7 @@ import ( // coverage_bug_2871); this verifies the Go-struct `swagger:response` path, where the `examples:` // block in the decl comment is parsed into the OAS2 Response.examples field. func TestCoverage_ResponseExamplesByMime(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/response-examples-by-mime/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_response_file_types_test.go b/internal/integration/coverage_response_file_types_test.go index 1d902993..66da490e 100644 --- a/internal/integration/coverage_response_file_types_test.go +++ b/internal/integration/coverage_response_file_types_test.go @@ -31,7 +31,7 @@ import ( // for (1). func TestCoverage_ResponseFileTypes(t *testing.T) { var got []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/response-file-types/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_response_header_ref_leak_test.go b/internal/integration/coverage_response_header_ref_leak_test.go index 2d7b961b..f91bcec7 100644 --- a/internal/integration/coverage_response_header_ref_leak_test.go +++ b/internal/integration/coverage_response_header_ref_leak_test.go @@ -35,7 +35,7 @@ import ( // set; diagnostic still fires for the underlying ref attempt. func TestCoverage_ResponseHeaderRefLeak(t *testing.T) { var got []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/response-header-ref-leak/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_response_implicit_header_test.go b/internal/integration/coverage_response_implicit_header_test.go index 9b1cac36..5b918a5b 100644 --- a/internal/integration/coverage_response_implicit_header_test.go +++ b/internal/integration/coverage_response_implicit_header_test.go @@ -31,7 +31,7 @@ import ( // Schema presence, primitive types — locks down side-by-side. func TestCoverage_ResponseImplicitHeader(t *testing.T) { var got []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/response-implicit-header/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_response_name_keyword_test.go b/internal/integration/coverage_response_name_keyword_test.go index e6999488..24c4feae 100644 --- a/internal/integration/coverage_response_name_keyword_test.go +++ b/internal/integration/coverage_response_name_keyword_test.go @@ -19,7 +19,7 @@ import ( // // Mirrors the parameter-side TestCoverage_ParamNameKeyword. func TestCoverage_ResponseNameKeyword(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/response-name-keyword/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_response_toplevel_example_test.go b/internal/integration/coverage_response_toplevel_example_test.go index a00e80d1..4d796ec8 100644 --- a/internal/integration/coverage_response_toplevel_example_test.go +++ b/internal/integration/coverage_response_toplevel_example_test.go @@ -17,7 +17,7 @@ import ( // // Both land on the response body schema (go-swagger#3013). func TestCoverage_ResponseTopLevelExample(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/response-toplevel-example/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_security_yaml_idiom_test.go b/internal/integration/coverage_security_yaml_idiom_test.go index 3b847f0a..afbeb6f2 100644 --- a/internal/integration/coverage_security_yaml_idiom_test.go +++ b/internal/integration/coverage_security_yaml_idiom_test.go @@ -31,7 +31,7 @@ import ( // (explicit empty opt-out) and #2294 (multi-key AND grouping): the positive demonstration that // those forms are just YAML. func TestCoverage_SecurityYAMLIdiom(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/security-yaml-idiom/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/coverage_shared_parameters_test.go b/internal/integration/coverage_shared_parameters_test.go index 2392cf5f..39107284 100644 --- a/internal/integration/coverage_shared_parameters_test.go +++ b/internal/integration/coverage_shared_parameters_test.go @@ -23,7 +23,7 @@ import ( // Register-only: the entries appear in the top-level map regardless of whether any operation // references them yet (the $ref wiring is a later phase). func TestCoverage_SharedParameters_TopLevel(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/shared-parameters/..."}, WorkDir: scantest.FixturesDir(), }) @@ -65,7 +65,7 @@ func paramRefs(op *spec.Operation) []string { // `swagger:parameters * opid` definition convenience and the standalone `swagger:parameters opid // name` reference marker on a func. func TestCoverage_SharedParameters_Refs(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/shared-parameters/..."}, WorkDir: scantest.FixturesDir(), }) @@ -104,7 +104,7 @@ func TestCoverage_SharedParameters_Refs(t *testing.T) { // Application is exact-path (no hierarchy), and path-item parameters co-exist with operation-level // ones (the operation one wins at resolution — co-presence, not removal). func TestCoverage_SharedParameters_PathItem(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/shared-parameters-pathitem/..."}, WorkDir: scantest.FixturesDir(), }) @@ -166,7 +166,7 @@ func TestCoverage_SharedParameters_PathItem(t *testing.T) { // Before P5 the `*` failed the name regex and the response was silently dropped, leaving the route // ref dangling. func TestCoverage_SharedResponses(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/shared-parameters/..."}, WorkDir: scantest.FixturesDir(), }) @@ -195,7 +195,7 @@ func TestCoverage_SharedResponses(t *testing.T) { // warning. func TestCoverage_SharedParameters_OverridesAndDedup(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/shared-parameters-overrides/..."}, WorkDir: scantest.FixturesDir(), OnDiagnostic: func(d grammar.Diagnostic) { @@ -248,7 +248,7 @@ func TestCoverage_SharedParameters_OverridesAndDedup(t *testing.T) { // scan.dangling-{parameter,response}-ref warning rather than emitting an invalid reference. func TestCoverage_SharedParameters_YAMLRefs(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/shared-parameters-yaml/..."}, WorkDir: scantest.FixturesDir(), OnDiagnostic: func(d grammar.Diagnostic) { @@ -296,7 +296,7 @@ func TestCoverage_SharedParameters_YAMLRefs(t *testing.T) { // Independent namespaces: #/definitions/Status coexists with #/parameters/Status. func TestCoverage_SharedParameters_Conflict(t *testing.T) { var diags []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/shared-parameters-conflict/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -344,7 +344,7 @@ func TestCoverage_SharedParameters_Conflict(t *testing.T) { func runSharedPrune(t *testing.T, prune bool) (*spec.Swagger, []grammar.Diagnostic) { t.Helper() var pruned []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/shared-parameters-prune/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -416,7 +416,7 @@ func TestCoverage_SharedParameters_Prune_On(t *testing.T) { func runSharedPruneCascade(t *testing.T, prune bool) (*spec.Swagger, []grammar.Diagnostic) { t.Helper() var pruned []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/shared-parameters-prune-cascade/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_simple_schema_readonly_test.go b/internal/integration/coverage_simple_schema_readonly_test.go index d93661b2..1a18908e 100644 --- a/internal/integration/coverage_simple_schema_readonly_test.go +++ b/internal/integration/coverage_simple_schema_readonly_test.go @@ -29,7 +29,7 @@ import ( // The observable signal is the gate diagnostic itself. func TestCoverage_SimpleSchemaReadOnlyGate(t *testing.T) { var got []grammar.Diagnostic - _, err := codescan.Run(&codescan.Options{ + _, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/simple-schema-readonly/..."}, WorkDir: scantest.FixturesDir(), OnDiagnostic: func(d grammar.Diagnostic) { diff --git a/internal/integration/coverage_simple_schema_test.go b/internal/integration/coverage_simple_schema_test.go index 96a2fba7..c55b55d0 100644 --- a/internal/integration/coverage_simple_schema_test.go +++ b/internal/integration/coverage_simple_schema_test.go @@ -33,7 +33,7 @@ import ( // - OnDiagnostic callback fires with the code in both cases func TestCoverage_SimpleSchemaViolation(t *testing.T) { var got []grammar.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/simple-schema-violation/..."}, WorkDir: scantest.FixturesDir(), OnDiagnostic: func(d grammar.Diagnostic) { diff --git a/internal/integration/coverage_single_line_description_test.go b/internal/integration/coverage_single_line_description_test.go index 8aa25024..299ef627 100644 --- a/internal/integration/coverage_single_line_description_test.go +++ b/internal/integration/coverage_single_line_description_test.go @@ -20,7 +20,7 @@ func TestCoverage_SingleLineCommentAsDescription(t *testing.T) { pkgs := []string{"./enhancements/single-line-description/..."} // Default: a single-line comment ending in a period is the title. - def, err := codescan.Run(&codescan.Options{ + def, err := runScan(&codescan.Options{ Packages: pkgs, WorkDir: scantest.FixturesDir(), ScanModels: true, }) require.NoError(t, err) @@ -29,7 +29,7 @@ func TestCoverage_SingleLineCommentAsDescription(t *testing.T) { assert.Empty(t, widget.Description) // Option on: the single-line comment becomes the description instead. - on, err := codescan.Run(&codescan.Options{ + on, err := runScan(&codescan.Options{ Packages: pkgs, WorkDir: scantest.FixturesDir(), ScanModels: true, SingleLineCommentAsDescription: true, }) diff --git a/internal/integration/coverage_skip_jsonify_interface_test.go b/internal/integration/coverage_skip_jsonify_interface_test.go index 5c5d7806..4cb5dd62 100644 --- a/internal/integration/coverage_skip_jsonify_interface_test.go +++ b/internal/integration/coverage_skip_jsonify_interface_test.go @@ -15,7 +15,7 @@ import ( func runSkipJSONifyInterface(t *testing.T, skip bool) *spec.Swagger { t.Helper() - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/interface-no-mangle/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/coverage_swagger_omit_test.go b/internal/integration/coverage_swagger_omit_test.go index 11d8266e..72560661 100644 --- a/internal/integration/coverage_swagger_omit_test.go +++ b/internal/integration/coverage_swagger_omit_test.go @@ -20,7 +20,7 @@ const swaggerOmitPkg = "./enhancements/swagger-omit/..." func runOmit(t *testing.T, defaultAllOf bool) (*oaispec.Swagger, map[grammar.Code][]grammar.Diagnostic) { t.Helper() byCode := map[grammar.Code][]grammar.Diagnostic{} - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{swaggerOmitPkg}, WorkDir: scantest.FixturesDir(), DefaultAllOfForEmbeds: defaultAllOf, diff --git a/internal/integration/coverage_toolchain_independence_test.go b/internal/integration/coverage_toolchain_independence_test.go index c1f2e7bf..0fc1149f 100644 --- a/internal/integration/coverage_toolchain_independence_test.go +++ b/internal/integration/coverage_toolchain_independence_test.go @@ -60,7 +60,7 @@ func TestToolchainIndependence_FullScan(t *testing.T) { }) t.Run("symptom: TextMarshaler fields still render as strings", func(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/text-marshal/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/default_example_typing_test.go b/internal/integration/default_example_typing_test.go index 65d4dfbf..b29b56aa 100644 --- a/internal/integration/default_example_typing_test.go +++ b/internal/integration/default_example_typing_test.go @@ -29,7 +29,7 @@ import ( // same dispatch arms, so a divergence between them is itself a defect. func TestDefaultExampleTyping(t *testing.T) { var diags []string - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/default-example-typing/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/dependency_declarations_test.go b/internal/integration/dependency_declarations_test.go index 64dfed06..6a97511b 100644 --- a/internal/integration/dependency_declarations_test.go +++ b/internal/integration/dependency_declarations_test.go @@ -123,10 +123,10 @@ func scanTarget(tb testing.TB, target string, compiled bool) (*oaispec.Swagger, var notices []string doc, err := codescan.Run(&codescan.Options{ - Packages: []string{target}, - WorkDir: scantest.FixturesDir(), - ScanModels: true, - SkipCompiledDependencies: !compiled, + Packages: []string{target}, + WorkDir: scantest.FixturesDir(), + ScanModels: true, + CompiledDependencies: compiled, OnDiagnostic: func(d codescan.Diagnostic) { if strings.Contains(d.Message, "could not be read") || d.Code == "scan.sourceless-type" { notices = append(notices, d.Message) diff --git a/internal/integration/embed_basic_underlying_test.go b/internal/integration/embed_basic_underlying_test.go index 5c938b24..e24386d3 100644 --- a/internal/integration/embed_basic_underlying_test.go +++ b/internal/integration/embed_basic_underlying_test.go @@ -39,7 +39,7 @@ import ( func TestEmbedBasicUnderlying(t *testing.T) { wire := loadEmbedWireGolden(t) - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/embed-basic-underlying/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/go_swagger_spec_test.go b/internal/integration/go_swagger_spec_test.go index 5097217f..cb8566d5 100644 --- a/internal/integration/go_swagger_spec_test.go +++ b/internal/integration/go_swagger_spec_test.go @@ -24,7 +24,7 @@ func TestGoSwagger_GenerateJSONSpec(t *testing.T) { WorkDir: filepath.Join(scantest.FixturesDir(), "goparsing", "spec"), Packages: []string{"./..."}, } - swspec, err := codescan.Run(&opts) + swspec, err := runScan(&opts) require.NoError(t, err) scantest.CompareOrDumpJSON(t, swspec, "api_spec_go111.json") @@ -36,7 +36,7 @@ func TestGoSwagger_GenerateJSONSpec_RefAliases(t *testing.T) { Packages: []string{"./..."}, RefAliases: true, } - swspec, err := codescan.Run(&opts) + swspec, err := runScan(&opts) require.NoError(t, err) scantest.CompareOrDumpJSON(t, swspec, "api_spec_go111_ref.json") @@ -48,7 +48,7 @@ func TestGoSwagger_GenerateJSONSpec_TransparentAliases(t *testing.T) { Packages: []string{"./..."}, TransparentAliases: true, } - swspec, err := codescan.Run(&opts) + swspec, err := runScan(&opts) require.NoError(t, err) scantest.CompareOrDumpJSON(t, swspec, "api_spec_go111_transparent.json") diff --git a/internal/integration/helpers_test.go b/internal/integration/helpers_test.go index 98e58f7e..64fab1ec 100644 --- a/internal/integration/helpers_test.go +++ b/internal/integration/helpers_test.go @@ -11,9 +11,28 @@ import ( "github.com/go-openapi/testify/v2/assert" + "github.com/go-openapi/codescan" + "github.com/go-openapi/codescan/internal/scantest" oaispec "github.com/go-openapi/spec" ) +// runScan is codescan.Run under whichever loader the suite was asked to run with. +// +// Almost every test here is about what a scan EMITS, and the emitted document is the same however +// the package graph was fetched - held over the whole corpus by TestLoaderAgreement. What differs is +// the cost, and this suite is where the cost lands: several hundred scans of small trees over one +// dependency closure. Reading that closure from source each time is roughly three times the CPU of +// compiling it once and reading it back, which a runner with two cores pays for on the wall. +// +// So the configuration is a property of the RUN rather than of each test, and lives in one place. +// See scantest.LoaderEnv for how to select it. +// +// The tests that are about loading itself do not come through here: they set their own options and +// call codescan.Run directly, because a test that pins a loader cannot have one applied over it. +func runScan(opts *codescan.Options) (*oaispec.Swagger, error) { + return codescan.Run(scantest.ApplyLoader(opts)) +} + // enableSpecOutput toggles YAML dumping of generated specs for debugging. const enableSpecOutput = false diff --git a/internal/integration/json_tag_fidelity_test.go b/internal/integration/json_tag_fidelity_test.go index 9d29d1e3..7dbcef3a 100644 --- a/internal/integration/json_tag_fidelity_test.go +++ b/internal/integration/json_tag_fidelity_test.go @@ -35,7 +35,7 @@ func TestJSONTagFidelity(t *testing.T) { wire := loadWireGolden(t) var diags []string - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/json-tag-fidelity/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/loader_agreement_test.go b/internal/integration/loader_agreement_test.go index cfd4b44a..1c7a0467 100644 --- a/internal/integration/loader_agreement_test.go +++ b/internal/integration/loader_agreement_test.go @@ -73,11 +73,10 @@ type abConfig struct { // abBaseline is the reference every configuration is compared against: every dependency read from // source, nothing taken from a compiler's word for it. // -// Deliberately not the default Options. Since v0.36.4 the default takes dependency types from export -// data, and a test asking "does this shortcut change the document?" has to hold the no-shortcut scan -// as its reference — otherwise the shortcut is on both sides of the comparison and the question -// answers itself. -func abBaseline(o *codescan.Options) { o.SkipCompiledDependencies = true } +// That is what plain Options do, which is the point of the default: a test asking "does this shortcut +// change the document?" has to hold the no-shortcut scan as its reference, or the shortcut sits on +// both sides of the comparison and the question answers itself. +func abBaseline(*codescan.Options) {} // abConfigs returns the configurations to compare against [abBaseline]. func abConfigs(tb testing.TB) []abConfig { @@ -92,12 +91,11 @@ func abConfigs(tb testing.TB) []abConfig { apply: func(o *codescan.Options) { o.ToolchainFreeLoader = true }, }, { - // Axis B, and since v0.36.4 the default. go/packages takes dependency types from `go list - // -export` wholesale — a LoadMode is one value for the whole load — and the annotated ones - // are read back afterwards, which is how it reaches the per-dependency outcome the - // toolchain-free route decides during it. + // Axis B. go/packages takes dependency types from `go list -export` wholesale — a LoadMode + // is one value for the whole load — and the annotated ones are read back afterwards, which + // is how it reaches the per-dependency outcome the toolchain-free route decides during it. name: "compiled-dependencies", - apply: func(*codescan.Options) {}, + apply: func(o *codescan.Options) { o.CompiledDependencies = true }, }, } diff --git a/internal/integration/loader_echo_test.go b/internal/integration/loader_echo_test.go new file mode 100644 index 00000000..69908c1f --- /dev/null +++ b/internal/integration/loader_echo_test.go @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package integration_test + +import ( + "testing" + + "github.com/go-openapi/codescan/internal/testloader" +) + +// TestLoaderInUse records which loader the suite ran under. +// +// This package is the bulk of the run and the one whose cost moves most with that choice, so a run +// that comes in unexpectedly slow can be read rather than guessed at. `go test -json` captures a +// passing test's log, so the line survives in the report CI keeps even where the console hides it. +func TestLoaderInUse(t *testing.T) { + t.Skip(testloader.Describe("integration")) +} diff --git a/internal/integration/math_big_test.go b/internal/integration/math_big_test.go index ad0ab524..e874f90d 100644 --- a/internal/integration/math_big_test.go +++ b/internal/integration/math_big_test.go @@ -19,7 +19,7 @@ import ( // MarshalText the same type also carries — so it is an `integer`, while big.Float and big.Rat have // only MarshalText and therefore travel quoted ("3.5", "5/3"), so they are `string`. func TestMathBig(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/math-big/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/opaque_streams_test.go b/internal/integration/opaque_streams_test.go index b782066a..ba4bf352 100644 --- a/internal/integration/opaque_streams_test.go +++ b/internal/integration/opaque_streams_test.go @@ -23,7 +23,7 @@ import ( // `byte` is the base64-encoded string OAS 2.0 defines for exactly that. It is not a claim about // the content — a stream is opaque, and this is the standard way of saying so. func TestOpaqueStreams(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/opaque-streams/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -64,7 +64,7 @@ func TestOpaqueStreams(t *testing.T) { }) t.Run("SkipExtensions suppresses the x-go-type stamp", func(t *testing.T) { - bare, err := codescan.Run(&codescan.Options{ + bare, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/opaque-streams/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/petstore_test.go b/internal/integration/petstore_test.go index 32aeadee..74a45c7e 100644 --- a/internal/integration/petstore_test.go +++ b/internal/integration/petstore_test.go @@ -19,7 +19,7 @@ import ( const epsilon = 1e-9 func TestAppScanner_NewSpec(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./goparsing/petstore/..."}, WorkDir: scantest.FixturesDir(), }) @@ -40,7 +40,7 @@ func TestAppScanner_NewSpec(t *testing.T) { // excluded. // The bookings fixture is the reporter's own repro. func TestAppScanner_Definitions(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./goparsing/bookings/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/response_named_nonstruct_test.go b/internal/integration/response_named_nonstruct_test.go index acb1dd25..57219966 100644 --- a/internal/integration/response_named_nonstruct_test.go +++ b/internal/integration/response_named_nonstruct_test.go @@ -27,7 +27,7 @@ import ( // the $ref machinery and publishes it as a definition — which is what the response-toplevel-example // and response-edges witnesses exist to prevent. func TestResponseNamedNonStruct(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/response-named-nonstruct/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/response_specials_test.go b/internal/integration/response_specials_test.go index 7bdcab79..35531d79 100644 --- a/internal/integration/response_specials_test.go +++ b/internal/integration/response_specials_test.go @@ -19,7 +19,7 @@ import ( // Putting them after it lost every type that is a struct underneath: time.Time went to the struct // arm to have its fields read as response headers, exports none, and the response carried no schema. func TestResponseSpecials(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/response-specials/..."}, WorkDir: scantest.FixturesDir(), }) diff --git a/internal/integration/route_name_shapes_test.go b/internal/integration/route_name_shapes_test.go index 22c34748..5453dd54 100644 --- a/internal/integration/route_name_shapes_test.go +++ b/internal/integration/route_name_shapes_test.go @@ -24,7 +24,7 @@ import ( // there was nothing to report and the path just never appeared. func TestRouteNameShapes(t *testing.T) { var diags []codescan.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/route-name-shapes/..."}, WorkDir: scantest.FixturesDir(), OnDiagnostic: func(d codescan.Diagnostic) { diff --git a/internal/integration/schema_aliased_test.go b/internal/integration/schema_aliased_test.go index ee3d79a2..c36379c2 100644 --- a/internal/integration/schema_aliased_test.go +++ b/internal/integration/schema_aliased_test.go @@ -21,7 +21,7 @@ func TestAliasedSchemas(t *testing.T) { var sp *oaispec.Swagger t.Run("end-to-end source scan should succeed", func(t *testing.T) { var err error - sp, err = codescan.Run(&codescan.Options{ + sp, err = runScan(&codescan.Options{ WorkDir: fixturesPath, BuildTags: "testscanner", // fixture code is excluded from normal build ScanModels: true, diff --git a/internal/integration/schema_special_test.go b/internal/integration/schema_special_test.go index e236160d..bee0ee1e 100644 --- a/internal/integration/schema_special_test.go +++ b/internal/integration/schema_special_test.go @@ -22,7 +22,7 @@ func TestSpecialSchemas(t *testing.T) { t.Run("end-to-end source scan should succeed", func(t *testing.T) { var err error - sp, err = codescan.Run(&codescan.Options{ + sp, err = runScan(&codescan.Options{ WorkDir: fixturesPath, BuildTags: "testscanner", // fixture code is excluded from normal build ScanModels: true, diff --git a/internal/integration/schema_uuid_go127_test.go b/internal/integration/schema_uuid_go127_test.go index 6173a51d..64d088ad 100644 --- a/internal/integration/schema_uuid_go127_test.go +++ b/internal/integration/schema_uuid_go127_test.go @@ -33,7 +33,7 @@ func TestStdlibUUID(t *testing.T) { t.Run("end-to-end source scan should succeed", func(t *testing.T) { var err error - sp, err = codescan.Run(&codescan.Options{ + sp, err = runScan(&codescan.Options{ WorkDir: fixturesPath, ScanModels: true, OnDiagnostic: func(d codescan.Diagnostic) { diff --git a/internal/integration/simple_schema_untyped_test.go b/internal/integration/simple_schema_untyped_test.go index 932bcb30..d7fc8887 100644 --- a/internal/integration/simple_schema_untyped_test.go +++ b/internal/integration/simple_schema_untyped_test.go @@ -21,7 +21,7 @@ import ( // fallback is codescan choosing rather than the Go type saying. func TestSimpleSchemaUntyped(t *testing.T) { var diags []codescan.Diagnostic - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/simple-schema-untyped/..."}, WorkDir: scantest.FixturesDir(), OnDiagnostic: func(d codescan.Diagnostic) { diags = append(diags, d) }, @@ -100,7 +100,7 @@ type spec2Param struct { // The x-go-type stamp is an extension like any other and must disappear under SkipExtensions, while // the type it accompanies must not: the type is what makes the spec valid. func TestSimpleSchemaUntyped_SkipExtensions(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/simple-schema-untyped/..."}, WorkDir: scantest.FixturesDir(), SkipExtensions: true, diff --git a/internal/integration/strfmt_symmetry_harness_test.go b/internal/integration/strfmt_symmetry_harness_test.go index fb6a609a..335f9b33 100644 --- a/internal/integration/strfmt_symmetry_harness_test.go +++ b/internal/integration/strfmt_symmetry_harness_test.go @@ -167,7 +167,7 @@ func (l symmetryLedger) run(t *testing.T) { for _, mode := range aliasModes() { t.Run(mode.name, func(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./" + l.pkg + "/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/type_override_symmetry_test.go b/internal/integration/type_override_symmetry_test.go index 1d036404..59feaad1 100644 --- a/internal/integration/type_override_symmetry_test.go +++ b/internal/integration/type_override_symmetry_test.go @@ -68,7 +68,7 @@ func TestTypeOverrideSymmetry(t *testing.T) { // each pair must agree, which is the point: the gate lives on the shared path, so the alias and the // named declaration reach it alike. func TestTypeOverrideSimpleSchema(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/type-override-symmetry/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -101,7 +101,7 @@ func TestTypeOverrideSimpleSchema(t *testing.T) { // legacy annotation raises, so both spellings pass through the SAME location gate — a formData // parameter and a response body, nowhere else. func TestTypeOverrideFileSynonym(t *testing.T) { - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/type-override-symmetry/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, @@ -141,7 +141,7 @@ func TestTypeOverrideFileSynonym(t *testing.T) { // NAMED enum type does work, because the named type survives the alias, and must stay silent. func TestAliasEnumIsUnfixableButLoud(t *testing.T) { var diags []string - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{"./enhancements/type-override-symmetry/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, diff --git a/internal/integration/wasi_test.go b/internal/integration/wasi_test.go index c30f2a5d..96387e75 100644 --- a/internal/integration/wasi_test.go +++ b/internal/integration/wasi_test.go @@ -57,7 +57,7 @@ func TestWASIArtifactMatchesNativeScan(t *testing.T) { require.NoError(t, json.Unmarshal(out, &fromGuest)) // The control: the same scan, in this process, through the stock loader. - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{pattern}, WorkDir: fixtures, ScanModels: true, @@ -174,7 +174,7 @@ func TestWASIArtifactIsSelfContained(t *testing.T) { var fromGuest map[string]any require.NoError(t, json.Unmarshal(out, &fromGuest)) - doc, err := codescan.Run(&codescan.Options{ + doc, err := runScan(&codescan.Options{ Packages: []string{pattern}, WorkDir: fixtures, ScanModels: true, diff --git a/internal/packages/list/resolve.go b/internal/packages/list/resolve.go index 8e708522..d3f1045b 100644 --- a/internal/packages/list/resolve.go +++ b/internal/packages/list/resolve.go @@ -43,6 +43,17 @@ type Resolver struct { modPath string // the main module's path vendor string // /vendor when it is authoritative (see vendorMode), else "" srcRoot string // GOROOT/src + // stdVendor is GOROOT/src/vendor, the standard library's OWN vendor tree, when it has one. + // + // A second vendor root, because std is the second module loaded from its own tree: everything else arrives + // pre-flattened, either from the module cache or from the main module's vendor directory, and `go mod vendor` + // is what flattens it. A dependency's own vendor directory is never consulted - see modload/import.go, "everything + // must be in the main modules or the main module's or workspace's vendor directory". + // + // std is the exception because std never goes through modload at all. cmd/go resolves its imports with the older + // vendoredImportPath walk, which climbs from the importing package looking for a vendor directory and stops at the + // source root - so a package in GOROOT/src reaches GOROOT/src/vendor and nothing outside it does. + stdVendor string // modDirs maps a required module path to the directory holding its source: the module cache for an ordinary // requirement, an arbitrary directory for a `replace` target. @@ -187,7 +198,28 @@ func (r *Resolver) ResolvePatterns(patterns []string) ([]Target, error) { return out, nil } +// ResolveImportFrom maps an import path onto a directory, for an import made from fromDir. +// +// The importing directory decides one thing only, and it is the thing [Resolver.ResolveImport] cannot know: whether +// the standard library's own vendor tree is in scope. It is in scope for a package inside GOROOT/src and for nothing +// else, which is what keeps a user package importing golang.org/x/crypto from silently picking up the copy pinned +// inside the Go installation. +// +// Vendor first, as the go command has it: for an importer under the source root the vendored copy IS the package, +// and the flat GOROOT/src lookup would miss it anyway. +func (r *Resolver) ResolveImportFrom(importPath, fromDir string) (dir, pkgPath string, ok bool) { + if d, p, found := r.resolveStdVendor(importPath, fromDir); found { + return d, p, true + } + + return r.ResolveImport(importPath) +} + // ResolveImport maps an import path onto a directory. +// +// It answers for an import from anywhere in the main module, which is every caller that has no importer to name. Use +// [Resolver.ResolveImportFrom] where the importer is known: it is the only way the standard library's own vendor tree +// can be reached. func (r *Resolver) ResolveImport(importPath string) (dir, pkgPath string, ok bool) { // Standard library: GOROOT/src is laid out by import path. if !strings.Contains(firstSegment(importPath), ".") { @@ -255,11 +287,50 @@ func (r *Resolver) InMainModule(importPath string) bool { return importPath == r.modPath || strings.HasPrefix(importPath, r.modPath+"/") } +// UnderGoroot reports whether an import made from dir can see the standard library's own vendor tree. +// +// Exported for the loader's stub memo, which must not give one importer's answer to another. +func (r *Resolver) UnderGoroot(dir string) bool { return r.underSrcRoot(dir) } + +// resolveStdVendor maps an import made from inside GOROOT onto the standard library's vendored copy of it. +// +// The package path carries the `vendor/` prefix, which is what `go list` reports for these and therefore what the +// go/packages loader reports too - the two must agree on identity or a type recognized under one name goes unrecognized +// under the other. +func (r *Resolver) resolveStdVendor(importPath, fromDir string) (dir, pkgPath string, ok bool) { + if r.stdVendor == "" || !r.underSrcRoot(fromDir) { + return "", "", false + } + d := r.vfs.Join(r.stdVendor, importPath) + if !r.vfs.IsDir(d) { + return "", "", false + } + + return d, "vendor/" + importPath, true +} + +// underSrcRoot reports whether dir is GOROOT/src or sits inside it. +// +// Compared as a path rather than as a string, so that a directory merely NAMED like the source root does not qualify. +func (r *Resolver) underSrcRoot(dir string) bool { + if dir == "" || r.srcRoot == "" { + return false + } + if dir == r.srcRoot { + return true + } + + return strings.HasPrefix(dir, r.srcRoot+string(filepath.Separator)) || strings.HasPrefix(dir, r.srcRoot+"/") +} + func (r *Resolver) init() error { if r.dir == "" { r.dir = "." } r.srcRoot = r.vfs.Join(r.ctx.GOROOT, "src") + if v := r.vfs.Join(r.srcRoot, "vendor"); r.isVendorTree(v) { + r.stdVendor = v + } r.nearestMod = map[string]moduleAt{} r.ws = r.findWorkspace(r.gowork) @@ -308,6 +379,15 @@ func (r *Resolver) vendorMode(dir string) bool { if r.modFlag == "mod" { return false } + + return r.isVendorTree(dir) +} + +// isVendorTree reports whether dir is a vendor directory `go mod vendor` wrote, by the same test the go command uses. +// +// Split out of [Resolver.vendorMode] for the standard library's own tree, which -mod does not speak for: that flag +// chooses how the MAIN module resolves its dependencies, and says nothing about the Go installation. +func (r *Resolver) isVendorTree(dir string) bool { _, err := r.vfs.ReadFile(r.vfs.Join(dir, "modules.txt")) return err == nil diff --git a/internal/packages/list/stdvendor_test.go b/internal/packages/list/stdvendor_test.go new file mode 100644 index 00000000..3b107927 --- /dev/null +++ b/internal/packages/list/stdvendor_test.go @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package list + +import ( + "go/build" + "path/filepath" + "testing" + + "github.com/go-openapi/codescan/internal/packages/vfs" + "github.com/go-openapi/testify/v2/assert" + "github.com/go-openapi/testify/v2/require" +) + +// The standard library vendors a handful of golang.org/x packages, and crypto/tls imports one of +// them by its real path rather than by a std one. +// +// Nothing else in the resolver can find it: the stdlib branch is gated on an import path whose first +// segment has no dot, and every other branch answers for the module under scan. +const ( + stdVendored = "golang.org/x/crypto/chacha20poly1305" + stdImporter = "crypto/tls" +) + +// newGorootResolver builds a resolver over the real Go installation, which is the only place a +// standard library with a vendor directory can be found. +func newGorootResolver(t *testing.T) *Resolver { + t.Helper() + + ctx := build.Default + r, err := NewResolver(Config{Dir: t.TempDir(), Context: &ctx, FS: vfs.New(nil)}) + require.NoError(t, err) + require.NotEmpty(t, r.srcRoot) + + if !r.vfs.IsDir(filepath.Join(r.srcRoot, "vendor", stdVendored)) { + t.Skipf("this Go installation does not vendor %s into its standard library", stdVendored) + } + + return r +} + +// TestResolveImportFrom_StdVendor covers the second vendor root: the standard library's own. +// +// go mod vendor flattens the whole module graph into the MAIN module's vendor directory, and a +// dependency's own is never read - so one vendor root answers for module mode entirely. std is the +// exception, because std never goes through modload: the go command resolves its imports with the +// older walk that climbs from the importing package to the source root, which is how a package in +// GOROOT/src reaches GOROOT/src/vendor and nothing outside it does. +func TestResolveImportFrom_StdVendor(t *testing.T) { + t.Parallel() + + r := newGorootResolver(t) + + t.Run("an importer inside GOROOT reaches the vendored copy", func(t *testing.T) { + dir, pkgPath, ok := r.ResolveImportFrom(stdVendored, filepath.Join(r.srcRoot, stdImporter)) + require.True(t, ok, "crypto/tls imports this, so it has to resolve") + assert.Equal(t, filepath.Join(r.srcRoot, "vendor", stdVendored), dir) + + // The path go list reports, so that the two loaders agree on identity: a type recognized under + // one name and not the other is the whole failure mode this prefix exists to avoid. + assert.Equal(t, "vendor/"+stdVendored, pkgPath) + }) + + // The bound is the point. Without it the Go installation's pinned copy would answer for anybody + // whose own module graph came up short, which is a build the go command refuses outright. + t.Run("an importer outside GOROOT does not", func(t *testing.T) { + _, _, ok := r.ResolveImportFrom(stdVendored, t.TempDir()) + assert.False(t, ok) + }) + + t.Run("and neither does one that names no importer", func(t *testing.T) { + _, _, ok := r.ResolveImport(stdVendored) + assert.False(t, ok) + }) + + // A std import from a std importer still resolves the ordinary way: the vendor lookup is tried + // first and has nothing to say about it. + t.Run("a std import is unaffected", func(t *testing.T) { + dir, pkgPath, ok := r.ResolveImportFrom("encoding/json", filepath.Join(r.srcRoot, stdImporter)) + require.True(t, ok) + assert.Equal(t, filepath.Join(r.srcRoot, "encoding/json"), dir) + assert.Equal(t, "encoding/json", pkgPath) + }) +} diff --git a/internal/packages/loader.go b/internal/packages/loader.go index a875f2ec..45905c2b 100644 --- a/internal/packages/loader.go +++ b/internal/packages/loader.go @@ -430,7 +430,7 @@ func (ld *loadState) loadDir(dir, pkgPath string, root bool) (*Package, error) { // Fabricate whatever this package selects through imports that will not resolve. // This way, the checker has something to bind those selectors to. - ld.synthesizeFrom(syntax) + ld.synthesizeFrom(syntax, dir) // Only the two maps codescan reads. // [types.Info] is pure output — every map handed in is one the checker then fills for every node it visits. @@ -442,7 +442,7 @@ func (ld *loadState) loadDir(dir, pkgPath string, root bool) (*Package, error) { Defs: map[*ast.Ident]types.Object{}, } conf := types.Config{ - Importer: &importer{ld: ld, from: pkg}, + Importer: &importer{ld: ld, from: pkg, fromDir: dir}, // Function bodies are checked for the packages under scan. // // An annotated type may be declared inside one (see the swagger:response in classification/operations/responses.go), @@ -530,6 +530,11 @@ func inAnyFile(pos string, files []string) bool { type importer struct { ld *loadState from *Package + // fromDir is where the importing package's source lives. + // + // Carried because it decides whether the standard library's own vendor tree is in scope: a package inside GOROOT/src + // resolves golang.org/x/crypto/... to the copy vendored beside it, and a package anywhere else must not. + fromDir string } func (i *importer) Import(importPath string) (*types.Package, error) { @@ -557,7 +562,7 @@ func (i *importer) Import(importPath string) (*types.Package, error) { } } - dir, pkgPath, ok := i.ld.res.ResolveImport(importPath) + dir, pkgPath, ok := i.ld.res.ResolveImportFrom(importPath, i.fromDir) if ok { dep, err := i.ld.loadDir(dir, pkgPath, false) if err == nil && dep != nil && dep.Types != nil { diff --git a/internal/packages/options.go b/internal/packages/options.go index 41e543f0..856f7ef7 100644 --- a/internal/packages/options.go +++ b/internal/packages/options.go @@ -166,7 +166,7 @@ func WithStubbedStdlib() Option { // It stays an option here while being the default above, because the loader is the mechanism and the policy is the // scanner's. What the scanner adds on top is a retry: `go list -export` BUILDS what it is asked about, so a scanned // package that does not compile fails the load outright, and it is reloaded without this option rather than allowed -// to abort. See Options.SkipCompiledDependencies. +// to abort. See Options.CompiledDependencies. func WithCompiledDependencies() Option { return func(o *options) { o.compiledDeps = true } } diff --git a/internal/packages/synthesize.go b/internal/packages/synthesize.go index 30bdf592..7ccc2f47 100644 --- a/internal/packages/synthesize.go +++ b/internal/packages/synthesize.go @@ -24,34 +24,49 @@ import ( // This is not enough for anything structural: a synthesized type has no fields to walk and no methods, // so drilling into it, or asking whether it implements an interface, both fail. -// willStub reports whether an import path will be synthesized rather than loaded. -func (ld *loadState) willStub(path string) bool { +// willStub reports whether an import path will be synthesized rather than loaded, for an import made from fromDir. +// +// The importing directory is part of the question, not context around it: the standard library's vendored packages +// resolve for an importer inside GOROOT/src and for no other, so the same path has two honest answers. Memoizing on +// the path alone gave whichever was asked first to everybody, which is how a resolvable import came to be announced +// as synthesized. +func (ld *loadState) willStub(path, fromDir string) bool { if path == "unsafe" { return false } - if known, ok := ld.stubbable[path]; ok { + key := stubKey(path, ld.res.UnderGoroot(fromDir)) + if known, ok := ld.stubbable[key]; ok { return known } if ld.hasExportData(path) { - ld.stubbable[path] = false + ld.stubbable[key] = false return false } - _, _, resolved := ld.res.ResolveImport(path) - ld.stubbable[path] = !resolved + _, _, resolved := ld.res.ResolveImportFrom(path, fromDir) + ld.stubbable[key] = !resolved return !resolved } +// stubKey distinguishes the two answers an import path can honestly have. +func stubKey(path string, fromGoroot bool) string { + if fromGoroot { + return "goroot\x00" + path + } + + return path +} + // synthesizeFrom records the names a package selects through its unresolvable imports, so that the synthesized packages // hold them before this package is type-checked against them. // // Names are collected per importing package, which is why this runs before each Check rather than once up front: the // set of packages is discovered lazily, as imports are followed. -func (ld *loadState) synthesizeFrom(files []*ast.File) { +func (ld *loadState) synthesizeFrom(files []*ast.File, fromDir string) { for _, f := range files { - aliases := ld.stubbedImports(f) + aliases := ld.stubbedImports(f, fromDir) if len(aliases) == 0 { continue } @@ -75,11 +90,11 @@ func (ld *loadState) synthesizeFrom(files []*ast.File) { } // stubbedImports maps the local name of each to-be-synthesized import onto its path. -func (ld *loadState) stubbedImports(f *ast.File) map[string]string { +func (ld *loadState) stubbedImports(f *ast.File, fromDir string) map[string]string { var aliases map[string]string for _, spec := range f.Imports { path, err := strconv.Unquote(spec.Path.Value) - if err != nil || !ld.willStub(path) { + if err != nil || !ld.willStub(path, fromDir) { continue } diff --git a/internal/parsers/grammar/diagnostic.go b/internal/parsers/grammar/diagnostic.go index 2a6a4172..6e6ff6b1 100644 --- a/internal/parsers/grammar/diagnostic.go +++ b/internal/parsers/grammar/diagnostic.go @@ -217,16 +217,22 @@ const ( // Carries the position of the import that triggered it, once per import path. CodeSynthesizedImport Code = "scan.synthesized-import" - // CodeCompiledDependencies fires once when a scan takes dependency types from compiled export data - // rather than from their source. - // - // Announced rather than left to be discovered, because what changes is cost and cost alone: export - // data carries types and not comments, so a dependency is read only when the scan needs what its - // source says — its own annotations, found by scanning for the marker, or a declaration the spec - // turns out to want, fetched at that lookup. Worth saying because the trade is real in the other - // direction: the closure has to be compiled first, so a cold build cache pays for this heavily. - // - // Informational (Hint); emitted once per scan, not per package. + // CodeCompiledDependencies reports on a scan that was asked to take dependency types from compiled + // export data rather than from their source. + // + // Only a caller who asked hears it, and it says whether the request was met. A Hint when the load + // took the shortcut: what changes is cost and cost alone, since export data carries types and not + // comments, so a dependency is read only when the scan needs what its source says — its own + // annotations, found by scanning for the marker, or a declaration the spec turns out to want, + // fetched at that lookup. Worth saying because the trade is real in the other direction: the + // closure has to be compiled first, so a cold build cache pays for this heavily. + // + // A Warning instead where the resolved loader could not honour the request, since the caller chose + // it for the speed-up and did not get it. A Hint again, later and separately, where the fast path + // was abandoned because the scanned tree does not build, and once more for a lookup that wanted a + // declaration from a package with no source to read it from. + // + // Emitted once per scan for the load itself, not per package. CodeCompiledDependencies Code = "scan.compiled-dependencies" // CodeSourcelessType fires when a type is rendered from what its type alone says, because the diff --git a/internal/scanner/README.md b/internal/scanner/README.md index 923be2b3..a6331ea1 100644 --- a/internal/scanner/README.md +++ b/internal/scanner/README.md @@ -604,14 +604,16 @@ Three ways to get a package graph. The short version, before the mechanism: build cache, so **cold costs what warm costs**: about level with the standard loader warm, ~30% faster cold, and the only option whose cost is predictable. Usually the right pick for CI. -- **Compiled dependencies** — what the standard loader does since v0.36.4: dependency - types come from compiled export data instead of their source. Warm, it is 35–55% - faster and 2.5–4× smaller, and it produces the same document, because a +- **Compiled dependencies** (`CompiledDependencies`) — the standard loader, taking + dependency types from compiled export data instead of their source. Warm, it is + 35–55% faster and 2.5–4× smaller, and it produces the same document, because a dependency's declarations are fetched on demand when the spec needs them — see [§compiled-dependencies](#compiled-dependencies). It must **compile** the closure, not merely type-check it, so on a cold cache it - is *an order of magnitude slower* and writes a large build cache. - `SkipCompiledDependencies` opts out, and CI is what it is for. + is *an order of magnitude slower* and writes a large build cache. Opt in where the + cache is warm by construction — a developer's machine, a watch loop, a pipeline + that restores it. Off by default because a CI runner is cold by construction, and + that is the case a default should protect. Two knobs below drop the GOROOT requirement entirely, for environments that have no Go installation at all (a WASI guest, a browser): `StubStdlib`, which pays for the @@ -652,10 +654,11 @@ Two consequences of that override are worth stating, because both have bitten: - an option only one strategy can honour is **dropped, not refused**, when the other one runs. Compiled dependencies are the case — the go command is what takes dependency types from export data, so under `FS` nothing does. This is why the - scan no longer announces where its dependency types came from: announcing it from - the request is how a toolchain-free scan came to be told its types were compiled - while it read every one of them from source, and announcing it from - `Loader.Strategy()` is a line on every scan once it is the default. + announcement is driven by `Loader.Strategy()` and not by the request: announcing it + from the request is how a toolchain-free scan came to be told its types were + compiled while it read every one of them from source. A request the resolved + loader cannot meet is a Warning rather than silence, since the caller chose it for + the speed-up and did not get it. - `FS` is **the whole world the scan can read**, not just the tree being scanned — see [§virtual-filesystem](#virtual-filesystem). @@ -930,27 +933,26 @@ ordinary scan never walks a file twice. Both halves are needed, and together they close every known divergence: the loader agreement A/B over the whole fixture corpus is empty for all three configurations (`internal/integration/loader_agreement_test.go`). That A/B holds the **source** -scan as its reference rather than the default one — since the default now takes the -shortcut, a test asking whether the shortcut changes anything would otherwise have -it on both sides. +scan as its reference, which is what plain `Options` give — a test asking whether +the shortcut changes anything must not have it on both sides. #### Code that does not build -Closing the fidelity gap is what let this become the default in v0.36.4, and it left -one thing to handle that has nothing to do with fidelity. +Closing the fidelity gap is what makes this option safe to reach for at all, and it +left one thing to handle that has nothing to do with fidelity. `go list -export` **builds** the packages it is asked about. So a scanned package that does not compile comes back as a package that could not be loaded at all — a `ListError`, which aborts the scan — where an ordinary load reports a type error on a package whose definitions are still perfectly usable. That is go-swagger#2874 exactly: one non-building package sinking a whole `./...` scan. Scanning a tree -mid-edit is the ordinary case, so the default cannot be allowed to reintroduce it. +mid-edit is the ordinary case, so opting into a shortcut must not reintroduce it. The fast path is therefore abandoned rather than allowed to change the answer: `loadPackages` retries from source when the compiled load reports a `ListError`, and says so with a `scan.compiled-dependencies` Hint. The retry costs a second load, and only on a tree that was not going to build; a healthy tree pays nothing. -A caller who sees the Hint every run wants `SkipCompiledDependencies`, which skips +A caller who sees the Hint every run wants `CompiledDependencies` unset, which skips the wasted first attempt. Note that dependencies need no such treatment — go/packages already type-checks a diff --git a/internal/scanner/declaration_test.go b/internal/scanner/declaration_test.go index ebcc0d6d..cdf11f5d 100644 --- a/internal/scanner/declaration_test.go +++ b/internal/scanner/declaration_test.go @@ -53,10 +53,10 @@ func TestEntityDecl(t *testing.T) { t.Run("alias type returns types.Alias", func(t *testing.T) { // Load the spec fixture which has type aliases (Customer = User). - specCtx, err := NewScanCtx(&Options{ + specCtx, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{"./goparsing/spec"}, WorkDir: "../../testdata", - }) + })) require.NoError(t, err) decl, ok := specCtx.FindDecl( diff --git a/internal/scanner/expression_types_test.go b/internal/scanner/expression_types_test.go index 89b4740c..09869311 100644 --- a/internal/scanner/expression_types_test.go +++ b/internal/scanner/expression_types_test.go @@ -85,11 +85,11 @@ func TestSpecIsBuiltWithoutExpressionTypes(t *testing.T) { func buildCorpus(t *testing.T, pkgs []string, dropExpressionTypes bool) string { t.Helper() - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: pkgs, WorkDir: scantest.FixturesDir(), ScanModels: true, - }) + })) require.NoError(t, err) if dropExpressionTypes { @@ -104,3 +104,7 @@ func buildCorpus(t *testing.T, pkgs []string, dropExpressionTypes bool) string { return string(out) } + +func applyLoader(opts *scanner.Options) *scanner.Options { + return scantest.ApplyLoader(opts) +} diff --git a/internal/scanner/index_test.go b/internal/scanner/index_test.go index a2e8f953..94fd1931 100644 --- a/internal/scanner/index_test.go +++ b/internal/scanner/index_test.go @@ -318,7 +318,7 @@ func TestDetectNodes_StructConflict(t *testing.T) { } func TestNewTypeIndex_ExcludeDeps(t *testing.T) { - sctx, err := NewScanCtx(&Options{ + sctx, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{ "./goparsing/classification", "./goparsing/classification/models", @@ -326,7 +326,7 @@ func TestNewTypeIndex_ExcludeDeps(t *testing.T) { }, WorkDir: "../../testdata", ExcludeDeps: true, - }) + })) require.NoError(t, err) require.NotNil(t, sctx) require.NotNil(t, sctx.app) @@ -339,7 +339,7 @@ func TestNewTypeIndex_ExcludeDeps(t *testing.T) { func TestNewTypeIndex_IncludeExcludePkgs(t *testing.T) { t.Run("include only models package", func(t *testing.T) { - sctx, err := NewScanCtx(&Options{ + sctx, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{ "./goparsing/classification", "./goparsing/classification/models", @@ -348,7 +348,7 @@ func TestNewTypeIndex_IncludeExcludePkgs(t *testing.T) { WorkDir: "../../testdata", ExcludeDeps: true, Include: []string{"models"}, - }) + })) require.NoError(t, err) // Only the models package should have been processed for swagger annotations. @@ -358,7 +358,7 @@ func TestNewTypeIndex_IncludeExcludePkgs(t *testing.T) { }) t.Run("exclude operations package", func(t *testing.T) { - sctx, err := NewScanCtx(&Options{ + sctx, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{ "./goparsing/classification", "./goparsing/classification/models", @@ -367,7 +367,7 @@ func TestNewTypeIndex_IncludeExcludePkgs(t *testing.T) { WorkDir: "../../testdata", ExcludeDeps: true, Exclude: []string{"operations$"}, - }) + })) require.NoError(t, err) // Routes and parameters come from operations, so excluding it should eliminate them. @@ -378,7 +378,7 @@ func TestNewTypeIndex_IncludeExcludePkgs(t *testing.T) { func TestNewTypeIndex_IncludeExcludeTags(t *testing.T) { t.Run("include tag filters routes", func(t *testing.T) { - sctx, err := NewScanCtx(&Options{ + sctx, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{ "./goparsing/classification", "./goparsing/classification/operations", @@ -386,7 +386,7 @@ func TestNewTypeIndex_IncludeExcludeTags(t *testing.T) { WorkDir: "../../testdata", ExcludeDeps: true, IncludeTags: []string{"orders"}, - }) + })) require.NoError(t, err) // Only routes tagged "orders" should be included. @@ -396,7 +396,7 @@ func TestNewTypeIndex_IncludeExcludeTags(t *testing.T) { }) t.Run("exclude tag filters routes", func(t *testing.T) { - sctx, err := NewScanCtx(&Options{ + sctx, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{ "./goparsing/classification", "./goparsing/classification/operations", @@ -404,7 +404,7 @@ func TestNewTypeIndex_IncludeExcludeTags(t *testing.T) { WorkDir: "../../testdata", ExcludeDeps: true, ExcludeTags: []string{"orders"}, - }) + })) require.NoError(t, err) // No route should have the "orders" tag. @@ -417,10 +417,10 @@ func TestNewTypeIndex_IncludeExcludeTags(t *testing.T) { } func TestCollectOperationPathAnnotations(t *testing.T) { - sctx, err := NewScanCtx(&Options{ + sctx, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{"./goparsing/classification/operations_annotation"}, WorkDir: "../../testdata", - }) + })) require.NoError(t, err) // operations_annotation has swagger:operation annotations. @@ -433,12 +433,12 @@ func TestCollectOperationPathAnnotations(t *testing.T) { func TestCollectOperationPathAnnotations_TagFiltering(t *testing.T) { t.Run("include tag filters operations", func(t *testing.T) { - sctx, err := NewScanCtx(&Options{ + sctx, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{"./goparsing/classification/operations_annotation"}, WorkDir: "../../testdata", ExcludeDeps: true, IncludeTags: []string{"Events"}, - }) + })) require.NoError(t, err) var count int @@ -452,12 +452,12 @@ func TestCollectOperationPathAnnotations_TagFiltering(t *testing.T) { }) t.Run("exclude tag filters operations", func(t *testing.T) { - sctx, err := NewScanCtx(&Options{ + sctx, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{"./goparsing/classification/operations_annotation"}, WorkDir: "../../testdata", ExcludeDeps: true, ExcludeTags: []string{"pets"}, - }) + })) require.NoError(t, err) for op := range sctx.Operations() { @@ -473,50 +473,50 @@ func TestNewTypeIndex_ErrorPropagation(t *testing.T) { // The invalid_model_param fixture has swagger:model and swagger:parameters on the same struct, // which triggers a struct conflict error in detectNodes, propagated through processFile → // processPackage → build → NewTypeIndex → NewScanCtx. - _, err := NewScanCtx(&Options{ + _, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{"./goparsing/invalid_model_param"}, WorkDir: "../../testdata", - }) + })) require.Error(t, err) assert.True(t, errors.Is(err, ErrScanner), "expected ErrScanner, got: %v", err) }) t.Run("model and response conflict propagates error", func(t *testing.T) { - _, err := NewScanCtx(&Options{ + _, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{"./goparsing/invalid_model_response"}, WorkDir: "../../testdata", - }) + })) require.Error(t, err) assert.True(t, errors.Is(err, ErrScanner), "expected ErrScanner, got: %v", err) }) t.Run("param and model conflict propagates error", func(t *testing.T) { - _, err := NewScanCtx(&Options{ + _, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{"./goparsing/invalid_param_model"}, WorkDir: "../../testdata", - }) + })) require.Error(t, err) assert.True(t, errors.Is(err, ErrScanner), "expected ErrScanner, got: %v", err) }) t.Run("response and model conflict propagates error", func(t *testing.T) { - _, err := NewScanCtx(&Options{ + _, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{"./goparsing/invalid_response_model"}, WorkDir: "../../testdata", - }) + })) require.Error(t, err) assert.True(t, errors.Is(err, ErrScanner), "expected ErrScanner, got: %v", err) }) t.Run("duplicate package is de-duplicated", func(t *testing.T) { // Load the same package twice — the second occurrence should be skipped (line 109 in build). - sctx, err := NewScanCtx(&Options{ + sctx, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{ "./goparsing/petstore/enums", "./goparsing/petstore/enums", // duplicate }, WorkDir: "../../testdata", - }) + })) require.NoError(t, err) require.NotNil(t, sctx) }) @@ -525,13 +525,13 @@ func TestNewTypeIndex_ErrorPropagation(t *testing.T) { // Load a valid package alongside an invalid one that shares imports. // The invalid_model_param package is a main package; loading it should trigger the conflict error // even if other packages are fine. - _, err := NewScanCtx(&Options{ + _, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{ "./goparsing/petstore/enums", "./goparsing/invalid_model_param", }, WorkDir: "../../testdata", - }) + })) require.Error(t, err) assert.True(t, errors.Is(err, ErrScanner)) }) diff --git a/internal/scanner/loader_echo_test.go b/internal/scanner/loader_echo_test.go new file mode 100644 index 00000000..e27a9462 --- /dev/null +++ b/internal/scanner/loader_echo_test.go @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package scanner + +import ( + "testing" + + "github.com/go-openapi/codescan/internal/testloader" +) + +// TestLoaderInUse records which loader the suite ran under. +// +// Worth having here in particular: this suite was silently left on the default while the setting was +// believed to cover it, and an echo would have said so at a glance. +// +// Skipped rather than logged, because gotestsum prints a skip's message and swallows a passing +// test's log. See the integration suite's twin. +func TestLoaderInUse(t *testing.T) { + t.Skip(testloader.Describe("scanner")) +} diff --git a/internal/scanner/loader_test.go b/internal/scanner/loader_test.go new file mode 100644 index 00000000..9ed017e4 --- /dev/null +++ b/internal/scanner/loader_test.go @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package scanner + +import "github.com/go-openapi/codescan/internal/testloader" + +// withTestLoader writes the loader this run was asked for onto opts. +// +// The same policy scantest.ApplyLoader applies, reached directly because these tests are IN-package: +// scantest imports this package, so importing scantest back would be a cycle. internal/testloader +// imports nothing, which is what makes it reachable from both sides. +// +// Tests that pin a loader themselves do not call this - there is no telling a field left at its zero +// value from one set to it on purpose. +func withTestLoader(opts *Options) *Options { + if opts == nil || opts.FS != nil { + return opts + } + if opts.CompiledDependencies || opts.ToolchainFreeLoader { + panic("scanner: these options already pin a loader, so they must not be routed through " + + "withTestLoader; drop the call and leave the pin, or drop the pin and keep the call") + } + + switch selected := testloader.Selected(); selected { + case testloader.LoaderCompiled: + opts.CompiledDependencies = true + case testloader.LoaderOwn: + opts.ToolchainFreeLoader = true + case testloader.LoaderSource: + default: + panic("scanner: no rule for loader " + string(selected)) + } + + return opts +} diff --git a/internal/scanner/options.go b/internal/scanner/options.go index f11fd79f..7001c6e5 100644 --- a/internal/scanner/options.go +++ b/internal/scanner/options.go @@ -145,14 +145,14 @@ type Options struct { // Experimental: see ToolchainFreeLoader. StubStdlib bool - // SkipCompiledDependencies reads every dependency from source, instead of taking its types from the - // compiler's export data. + // CompiledDependencies takes dependency types from the compiler's export data, instead of reading + // every dependency from source. // - // By default a scan under the go/packages loader takes dependency types from export data, because - // parsing and type-checking dependencies is most of what a load does and the compiler already did - // it. Set this to opt out and load the graph the way every release before v0.36.4 did. + // Unset, a scan parses and type-checks the whole dependency graph, which is most of what a load + // does. Set, that work is skipped for anything outside the scanned module: the compiler already did + // it, and `go list -export` hands the result over. // - // Leaving it unset costs no meaning. Export data carries the full exported type surface — fields, + // Setting it costs no meaning. Export data carries the full exported type surface — fields, // method sets, interface identity — but no syntax and no comments, and a scan wants two different // things out of a dependency's source. What a dependency SAYS about its own types is found by // scanning its files for the annotation marker after the load, which is what keeps go-openapi's own @@ -164,22 +164,28 @@ type Options struct { // // # When to set it // - // Cost, and only cost. It is roughly 2.3x faster on a warm build cache and an order of magnitude - // slower on a cold one, since export data has to be compiled before it exists — around 229 MB of - // build cache on a large tree. CI regenerating a spec from a clean checkout is the case this exists - // for, and it is the reason to reach for it. + // Cost, and only cost — but the cost swings both ways, which is why this is a choice and not a + // default. On a warm build cache it is roughly 2.3x faster and holds a third of the memory. On a + // cold one it is an order of magnitude slower, since export data has to be compiled before it + // exists, and it writes around 229 MB of build cache on a large tree. // - // A closure that does not compile is NOT a reason: go/packages falls back to type-checking such a - // package from source, and the spec comes out the same. + // So it pays where the cache is warm by construction: a developer's own machine, a watch loop, a + // pipeline that restores its cache. It loses badly on a clean checkout, which is what a CI runner + // usually is — and that is the case the default protects. Under a memory bound rather than a time + // one, ToolchainFreeLoader is the better answer than either: leaner than a source load and, unlike + // this, indifferent to the state of the cache. + // + // A closure that does not compile is NOT a reason to avoid it: the load is retried from source and + // the spec comes out the same. // // The option applies to the go/packages loader alone. The toolchain-free loader resolves imports // itself and already decides per dependency whether to read its source, so it neither needs this // nor honours it; ExportData below is the same idea supplied by hand for that loader. // - // So there is nothing here to opt out of wherever go/packages cannot run. A WebAssembly build has + // So there is nothing here to ask for wherever go/packages cannot run. A WebAssembly build has // no process model and therefore no `go list`, and setting FS forces the same loader for the same // reason — in both, dependency types come from source or from ExportData regardless of this field. - SkipCompiledDependencies bool + CompiledDependencies bool // ExportData serves DEPENDENCIES from pre-computed export data instead of reading their source, // under the toolchain-free loader. diff --git a/internal/scanner/parameter_ref_test.go b/internal/scanner/parameter_ref_test.go index fadb796f..00446348 100644 --- a/internal/scanner/parameter_ref_test.go +++ b/internal/scanner/parameter_ref_test.go @@ -33,10 +33,10 @@ func parametersBlock(t *testing.T, sctx *ScanCtx, ref *ParameterRef) *grammar.Pa // Struct-hosted markers (definitions) must NOT appear among the refs. func TestParameterRefDiscovery(t *testing.T) { t.Run("operation reference on a route func", func(t *testing.T) { - sctx, err := NewScanCtx(&Options{ + sctx, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{"./enhancements/shared-parameters"}, WorkDir: "../../testdata", - }) + })) require.NoError(t, err) refs := slices.Collect(sctx.ParameterRefs()) @@ -51,10 +51,10 @@ func TestParameterRefDiscovery(t *testing.T) { }) t.Run("path reference on a standalone func", func(t *testing.T) { - sctx, err := NewScanCtx(&Options{ + sctx, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{"./enhancements/shared-parameters-pathitem"}, WorkDir: "../../testdata", - }) + })) require.NoError(t, err) refs := slices.Collect(sctx.ParameterRefs()) @@ -69,10 +69,10 @@ func TestParameterRefDiscovery(t *testing.T) { }) t.Run("no reference markers when all swagger:parameters are struct definitions", func(t *testing.T) { - sctx, err := NewScanCtx(&Options{ + sctx, err := NewScanCtx(withTestLoader(&Options{ Packages: []string{"./goparsing/classification/operations"}, WorkDir: "../../testdata", - }) + })) require.NoError(t, err) assert.Empty(t, slices.Collect(sctx.ParameterRefs())) @@ -96,7 +96,7 @@ func TestSharedParametersFixturesScanClean(t *testing.T) { } for _, pkg := range pkgs { t.Run(pkg, func(t *testing.T) { - _, err := NewScanCtx(&Options{Packages: []string{pkg}, WorkDir: "../../testdata"}) + _, err := NewScanCtx(withTestLoader(&Options{Packages: []string{pkg}, WorkDir: "../../testdata"})) assert.NoError(t, err) }) } diff --git a/internal/scanner/read_back_test.go b/internal/scanner/read_back_test.go index 9dfa3531..3423ca97 100644 --- a/internal/scanner/read_back_test.go +++ b/internal/scanner/read_back_test.go @@ -18,19 +18,24 @@ const makeplans = "github.com/go-swagger/scan-repo-boundary/makeplans" // A dependency taken types-only is read back when a declaration is wanted from it, and only then. // -// That "only then" is what makes the default affordable. Reading every dependency back up front would answer the -// same lookups and give away most of the saving — measured on a generated server, a third of the wall clock and a -// third of the peak RSS — because the closure a scan does not look at is where the saving lives. So the cost is one -// parse per declaration wanted, and this pins the "one" from both sides. +// That "only then" is what makes the shortcut worth choosing. Reading every dependency back up front would answer +// the same lookups and give away most of the saving — measured on a generated server, a third of the wall clock and +// a third of the peak RSS — because the closure a scan does not look at is where the saving lives. So the cost is +// one parse per declaration wanted, and this pins the "one" from both sides. // -// No option is set: taking dependency types from export data is what an ordinary scan does. +// CompiledDependencies is what creates the economy being measured: an ordinary scan reads every dependency, so there +// is nothing to read back and nothing here to count. func TestReadBackOnDemand_PaysPerDeclarationWanted(t *testing.T) { t.Parallel() + // Pinned, and deliberately NOT routed through the run-wide loader setting: the economy measured + // here exists only under compiled dependencies. The toolchain-free loader reads every dependency + // during the load, so there is no types-only package left to read back and nothing to count. ctx, err := scanner.NewScanCtx(&scanner.Options{ - Packages: []string{"./goparsing/bookings/..."}, - WorkDir: scantest.FixturesDir(), - ScanModels: true, + Packages: []string{"./goparsing/bookings/..."}, + WorkDir: scantest.FixturesDir(), + ScanModels: true, + CompiledDependencies: true, }) require.NoError(t, err) @@ -59,11 +64,11 @@ func TestReadBackOnDemand_PaysPerDeclarationWanted(t *testing.T) { func TestReadBackOnDemand_MissingDeclaration(t *testing.T) { t.Parallel() - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(applyLoader(&scanner.Options{ Packages: []string{"./goparsing/bookings/..."}, WorkDir: scantest.FixturesDir(), ScanModels: true, - }) + })) require.NoError(t, err) _, found := ctx.FindDecl(makeplans, "NoSuchType") diff --git a/internal/scanner/scan_context.go b/internal/scanner/scan_context.go index 1045120e..62c4f43f 100644 --- a/internal/scanner/scan_context.go +++ b/internal/scanner/scan_context.go @@ -137,14 +137,21 @@ func loadPackages(opts *Options, exportOnly map[string]string) ([]*packages.Pack cfg.BuildFlags = []string{"-tags", opts.BuildTags} } - if opts.SkipCompiledDependencies { + if !opts.CompiledDependencies { return loadWith(cfg, opts, loaderOpts) } pkgs, loader, err := loadWith(cfg, opts, append(loaderOpts, ownpackages.WithCompiledDependencies())) - if err != nil || !anyListError(pkgs) { + if err != nil { return pkgs, loader, err } + if !anyListError(pkgs) { + // Announced from the resolved strategy, not from the request. Only one strategy can take dependency types from + // the compiler, and Options.FS forces the other one whatever was asked for. + reportCompiledDependencies(opts, loader.Strategy()) + + return pkgs, loader, nil + } // Taking dependency types from export data means `go list -export`, and that BUILDS what it is asked about rather // than merely type-checking it. So a scanned package that does not compile comes back as a package that could not @@ -188,7 +195,7 @@ func anyListError(pkgs []*packages.Package) bool { // reportCompiledFallback says the compiled fast path was abandoned, and why. // // Worth a word rather than silence: the scan is about to cost roughly twice what it should, and the reason is in the -// scanned tree rather than in codescan. A caller who sees this on every run wants SkipCompiledDependencies, which +// scanned tree rather than in codescan. A caller who sees this on every run wants CompiledDependencies unset, which // skips the wasted first load. func reportCompiledFallback(opts *Options, pkgs []*packages.Package) { if opts.OnDiagnostic == nil { @@ -206,7 +213,7 @@ func reportCompiledFallback(opts *Options, pkgs []*packages.Package) { opts.OnDiagnostic(grammar.Hintf(token.Position{}, grammar.CodeCompiledDependencies, "reading every dependency from source instead: taking their types from compiled export data needs the "+ - "scanned code to build, and it does not (%s). Set SkipCompiledDependencies to skip this first attempt", + "scanned code to build, and it does not (%s). Unset CompiledDependencies to skip this first attempt", culprit)) } @@ -222,18 +229,45 @@ func loaderStrategy(opts *Options) ownpackages.Strategy { return ownpackages.StrategyGoPackages } -// Where compiled dependencies stopped being announced. +// reportCompiledDependencies announces which dependencies are read and which are not. +// +// Only a caller who asked for this hears anything. The pair says whether the request was met: a Hint +// when the load took the shortcut, a Warning when it could not. // -// A Hint used to fire once per scan saying dependency types had come from export data, and a Warning -// fired when the option was asked for under a loader that cannot honour it. Both made sense while this -// was opt-in: the first described a deviation the caller had chosen, the second an intent the load -// could not meet. +// This does not announce a loss. Dependency source going unread wholesale is what it used to mean — +// strfmt being the case that mattered, since its `swagger:strfmt` marks are what turn a +// strfmt.DateTime field into a date-time. A dependency whose source carries annotations is now read +// back after the load, and one that is merely asked for a declaration is read back at the lookup. // -// Neither survives the option becoming the default. Announcing the default on every scan is noise, and -// nobody asks for something they did not set, so there is no unmet intent left to report. +// So this says what the load did rather than what it cost. What it can still cost — source that is +// not there to read at all — is announced where it lands, by the lookup that wanted a declaration and +// did not find one; see reportSourcelessLookup. // -// What could still cost something is announced where it lands rather than up front: the lookup that -// wanted a declaration and found no source to read it from. See reportSourcelessLookup. +// It takes the RESOLVED strategy rather than reading the request off Options, because the two can +// disagree: only the go/packages strategy can ask the compiler for dependency types, and a virtual +// filesystem forces the other one whatever the caller asked for. Announcing from the request meant +// telling a toolchain-free scan that its dependency types came from export data while it was reading +// every one of them from source — a diagnostic contradicting the load it describes. +func reportCompiledDependencies(opts *Options, strategy ownpackages.Strategy) { + if opts.OnDiagnostic == nil { + return + } + + // Asked for and not delivered. Worth more than silence: the caller chose this for the speed-up and + // did not get it, and nothing else in the output would say so. + if strategy != ownpackages.StrategyGoPackages { + opts.OnDiagnostic(grammar.Warnf(token.Position{}, grammar.CodeCompiledDependencies, + "CompiledDependencies is ignored under the %s loader, which resolves imports itself and "+ + "already decides per dependency whether to read its source; every dependency here is "+ + "loaded as usual", strategy)) + + return + } + + opts.OnDiagnostic(grammar.Hintf(token.Position{}, grammar.CodeCompiledDependencies, + "dependency types come from compiled export data: a dependency is read only if its source carries "+ + "swagger annotations, or if the spec later needs a declaration out of it")) +} // exportOnlyCollector records "types without source" notices instead of announcing them. // diff --git a/internal/scanner/scan_context_test.go b/internal/scanner/scan_context_test.go index a5b6327b..59be2641 100644 --- a/internal/scanner/scan_context_test.go +++ b/internal/scanner/scan_context_test.go @@ -555,14 +555,14 @@ func TestNewScanCtx_NonBuildingCode_FallsBackToSource(t *testing.T) { "type Gadget struct {\n\tName string `json:\"name\"`\n}\n\n"+ "var _ int = \"not an int\"\n"), 0o600)) - scan := func(skip bool) []grammar.Diagnostic { + scan := func(compiled bool) []grammar.Diagnostic { var diags []grammar.Diagnostic sctx, err := NewScanCtx(&Options{ - Packages: []string{"./..."}, - WorkDir: dir, - ScanModels: true, - SkipCompiledDependencies: skip, - OnDiagnostic: func(d grammar.Diagnostic) { diags = append(diags, d) }, + Packages: []string{"./..."}, + WorkDir: dir, + ScanModels: true, + CompiledDependencies: compiled, + OnDiagnostic: func(d grammar.Diagnostic) { diags = append(diags, d) }, }) require.NoError(t, err) require.NotNil(t, sctx) @@ -583,11 +583,11 @@ func TestNewScanCtx_NonBuildingCode_FallsBackToSource(t *testing.T) { return n } - assert.Equal(t, 1, fellBack(scan(false)), + assert.Equal(t, 1, fellBack(scan(true)), "the compiled load could not build this tree, so it was abandoned and said so") - assert.Zero(t, fellBack(scan(true)), - "opting out skips the attempt entirely, so there is nothing to fall back from") + assert.Zero(t, fellBack(scan(false)), + "the default never attempts the compiled load, so there is nothing to fall back from") } // TestSameSourceFile covers the comparison that joins a position out of compiled export data to the diff --git a/internal/scanner/written_rhs_test.go b/internal/scanner/written_rhs_test.go index 98307370..798eef47 100644 --- a/internal/scanner/written_rhs_test.go +++ b/internal/scanner/written_rhs_test.go @@ -36,7 +36,7 @@ func TestWrittenRHS_AgreesWithTheTypeChecker(t *testing.T) { "./enhancements/...", "./bugs/...", } { - sctx, err := NewScanCtx(&Options{Packages: []string{root}, WorkDir: "../../testdata"}) + sctx, err := NewScanCtx(withTestLoader(&Options{Packages: []string{root}, WorkDir: "../../testdata"})) if err != nil { continue } diff --git a/internal/scantest/doc.go b/internal/scantest/doc.go index a98b7ddf..e29d45bb 100644 --- a/internal/scantest/doc.go +++ b/internal/scantest/doc.go @@ -2,4 +2,29 @@ // SPDX-License-Identifier: Apache-2.0 // Package scantest exposes utilities for testing the codescan packages. +// +// # Choosing the loader a test run uses +// +// The suites emit the same document however the package graph was fetched, so which loader runs is +// a question about what the run costs. [ApplyLoader] is the one place that decides, reading +// [LoaderEnv] or the build tag the binary was compiled with: +// +// go test ./... # the shipped default, source dependencies +// CODESCAN_TEST_LOADER=compiled go test ./... # dependency types from the build cache +// CODESCAN_TEST_LOADER=own go test ./... # codescan's own loader, no go command +// go test -tags=testloader_compiled ./... # the same, selected at build time +// +// The environment variable is for a person at a terminal. The build tags exist because CI reaches +// `go test` through a shared workflow that forwards flags and not the environment. +// +// It is worth having because the harness is the shape compiled dependencies were made for: several +// hundred scans of small trees over one dependency closure, so the closure is compiled once and +// every scan after the first reads it back. Over the whole repository that is a quarter of the CPU, +// and a runner with two cores spends most of that difference on the wall. +// +// Two kinds of test do not come through here. Those that pin a loader themselves — the agreement +// A/B, the virtual-filesystem and export-data suites — call codescan.Run directly, because there is +// no way to tell a field left at its zero value from one set to it on purpose. And the runnable +// examples under docs/ show a caller how to call the API, so they use the real defaults and nothing +// else. package scantest diff --git a/internal/scantest/load.go b/internal/scantest/load.go index 85fa7eb3..015d1caf 100644 --- a/internal/scantest/load.go +++ b/internal/scantest/load.go @@ -36,11 +36,11 @@ func LoadPetstorePkgsCtx(t testing.TB, enableDebug bool) *scanner.ScanCtx { return petstoreCtx } - ctx, err := scanner.NewScanCtx(&scanner.Options{ + ctx, err := scanner.NewScanCtx(ApplyLoader(&scanner.Options{ Packages: []string{"./goparsing/petstore/..."}, WorkDir: FixturesDir(), Debug: enableDebug, - }) + })) require.NoError(t, err) petstoreCtx = ctx @@ -55,12 +55,12 @@ func LoadGo118ClassificationPkgsCtx(t *testing.T) *scanner.ScanCtx { return go118ClassificationCtx } - sctx, err := scanner.NewScanCtx(&scanner.Options{ + sctx, err := scanner.NewScanCtx(ApplyLoader(&scanner.Options{ Packages: []string{ "./goparsing/go118", }, WorkDir: FixturesDir(), - }) + })) require.NoError(t, err) go118ClassificationCtx = sctx @@ -74,14 +74,14 @@ func LoadClassificationPkgsCtx(t *testing.T) *scanner.ScanCtx { return classificationCtx } - sctx, err := scanner.NewScanCtx(&scanner.Options{ + sctx, err := scanner.NewScanCtx(ApplyLoader(&scanner.Options{ Packages: []string{ "./goparsing/classification", "./goparsing/classification/models", "./goparsing/classification/operations", }, WorkDir: FixturesDir(), - }) + })) require.NoError(t, err) classificationCtx = sctx diff --git a/internal/scantest/loader.go b/internal/scantest/loader.go new file mode 100644 index 00000000..a4c47cc4 --- /dev/null +++ b/internal/scantest/loader.go @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package scantest + +import ( + "github.com/go-openapi/codescan/internal/scanner" + "github.com/go-openapi/codescan/internal/testloader" +) + +// Re-exported so a caller that already imports scantest needs nothing else. +// +// The policy itself lives in internal/testloader, which imports nothing: the scanner's own tests are +// in-package and this package imports the scanner, so a policy declared here could not be read from +// where most of the scans are. +const ( + LoaderSource = testloader.LoaderSource + LoaderCompiled = testloader.LoaderCompiled + LoaderOwn = testloader.LoaderOwn + + LoaderEnv = testloader.Env +) + +// TestLoader reports which loader the suites run under. See [testloader.Selected]. +func TestLoader() testloader.Loader { return testloader.Selected() } + +// ApplyLoader writes the selected loader onto opts and returns it, for chaining onto a call. +// +// It is deliberately a WRITE rather than a default: a test that has an opinion about how its graph +// is loaded must not route through here at all, because there is no way to tell a field left at its +// zero value from one set to it on purpose. Those tests set their options directly, and say so. +// +// Options.FS is the one thing it will not fight: a virtual filesystem forces the toolchain-free +// loader whatever anyone asks for, so asking for something else here would only produce a +// diagnostic about an intent that could not be met. +func ApplyLoader(opts *scanner.Options) *scanner.Options { + return applyLoader(opts) +} + +// A test that pins a loader must not also be routed through the run-wide setting: there is no way to +// tell a field left at its zero value from one set on purpose, so the setting would silently write +// over the pin. Under one tag that merely wastes the pin; under another it inverts what the test +// measures, and the failure surfaces as an arithmetic assertion far from the cause. +// +// Panicking names the cause at the call site instead, under every tag including the default, so a +// sweep that routes one of these is caught by an ordinary run rather than by the one tag that breaks. +// +// applyLoader is the shared body, so packages that cannot import scantest can hold a copy of the +// three lines rather than of the reasoning. +func applyLoader(opts *scanner.Options) *scanner.Options { + if opts == nil || opts.FS != nil { + return opts + } + if opts.CompiledDependencies || opts.ToolchainFreeLoader { + panic("scantest: these options already pin a loader, so they must not be routed through " + + "ApplyLoader; drop the call and leave the pin, or drop the pin and keep the call") + } + + switch selected := testloader.Selected(); selected { + case testloader.LoaderCompiled: + opts.CompiledDependencies = true + case testloader.LoaderOwn: + opts.ToolchainFreeLoader = true + case testloader.LoaderSource: + // The shipped default: nothing to set. + default: + panic("scantest: no rule for loader " + string(selected)) + } + + return opts +} diff --git a/internal/testloader/compiled.go b/internal/testloader/compiled.go new file mode 100644 index 00000000..0aab6325 --- /dev/null +++ b/internal/testloader/compiled.go @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +//go:build testloader_compiled + +package testloader + +// defaultLoader is what the suites run under when nothing selects otherwise. +// +// Selected by `-tags=testloader_compiled`, which is how a CI lane asks for it: the shared test +// workflow forwards flags to `go test` and does not forward the environment. +const defaultLoader = LoaderCompiled diff --git a/internal/testloader/default.go b/internal/testloader/default.go new file mode 100644 index 00000000..0c40f1c2 --- /dev/null +++ b/internal/testloader/default.go @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +//go:build !testloader_compiled && !testloader_own + +package testloader + +// defaultLoader is what the suites run under when nothing selects otherwise. +// +// The shipped default, so an ordinary `go test ./...` exercises the configuration a caller gets. +// Anything else has to be asked for, by the environment or by a build tag. +const defaultLoader = LoaderSource diff --git a/internal/testloader/own.go b/internal/testloader/own.go new file mode 100644 index 00000000..b6af50a3 --- /dev/null +++ b/internal/testloader/own.go @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +//go:build testloader_own + +package testloader + +// defaultLoader is what the suites run under when nothing selects otherwise. +// +// Selected by `-tags=testloader_own`, for a lane that exercises the toolchain-free loader over the +// whole corpus rather than over the agreement A/B alone. +const defaultLoader = LoaderOwn diff --git a/internal/testloader/testloader.go b/internal/testloader/testloader.go new file mode 100644 index 00000000..74f04288 --- /dev/null +++ b/internal/testloader/testloader.go @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +// Package testloader carries the one setting that decides which loader a test run uses. +// +// It depends on nothing. That is the whole reason it exists as a package of its own: the scanner's +// own tests are in-package, and scantest imports the scanner, so a policy living there could never +// be read from where most of the scans are. +// +// See internal/scantest for how it is applied, and for what it buys. +package testloader + +import ( + "fmt" + "os" +) + +// Loader names one of the ways a scan can get its package graph. +// +// The suites emit the same document under all three, so which one runs is a question about what the +// run COSTS and nothing else. +type Loader string + +const ( + // LoaderSource reads every dependency from source, which is what a caller gets by default. + LoaderSource Loader = "source" + + // LoaderCompiled takes dependency types from the compiler's export data. + LoaderCompiled Loader = "compiled" + + // LoaderOwn resolves the graph with codescan's own loader, invoking no go command. + LoaderOwn Loader = "own" +) + +// Env is the variable that selects the loader for a test run. +// +// Unset, the suites run under whatever the build selected - LoaderSource unless the +// testloader_compiled or testloader_own build tag was given. The variable is the knob for a person +// at a terminal; the build tags are the knob for CI, which reaches `go test` through a shared +// workflow that forwards flags and not the environment. +const Env = "CODESCAN_TEST_LOADER" + +// Selected reports which loader the suites run under. +// +// An unrecognised value stops the run rather than being ignored: a typo would otherwise silently +// exercise the default while the operator believed otherwise, which has already cost a CI round. +func Selected() Loader { + switch v := Loader(os.Getenv(Env)); v { + case "": + return defaultLoader + case LoaderSource, LoaderCompiled, LoaderOwn: + return v + default: + panic(fmt.Sprintf("testloader: %s=%q is not one of %q, %q, %q", + Env, string(v), LoaderSource, LoaderCompiled, LoaderOwn)) + } +} + +// Flags is the loader as command-line flags, for the commands that build their options from flags +// rather than from a literal. +// +// Derived from [Selected] rather than from the build tag alone, so that the two knobs cannot +// disagree: a test branching on Selected while the command it ran was configured by the tag reads +// one loader and exercises another, which is a failure that accuses the command of the wrong thing. +// CODESCAN_TEST_LOADER is how the loader-conformance workflow selects, so this is not hypothetical. +// +// -loader is stated even for the shipped default: "auto" resolves to go list on every native build, +// but saying so pins the intent rather than leaning on that resolution. +func Flags() []string { + switch selected := Selected(); selected { + case LoaderCompiled: + return []string{"-loader", "go", "-compiled-dependencies"} + case LoaderOwn: + return []string{"-loader", "own"} + case LoaderSource: + return []string{"-loader", "go"} + default: + panic("testloader: no flags for loader " + string(selected)) + } +} + +// Describe is a one-line statement of which loader is in force. +// +// A run is otherwise indistinguishable from a run configured differently: the tag that selects the +// loader is set in a workflow file, far from any test, and two rounds of CI debugging went on +// inferring exactly that from wall-clock time. +// +// Returned rather than printed, and reported from an ordinary test rather than from TestMain, +// because TestMain also runs under `go test -list`: the fuzz matrix builds itself by treating every +// listed line as a target name, and a stray line there becomes a fuzz test that does not exist. +func Describe(suite string) string { + return fmt.Sprintf("codescan %s suite: loader=%s (set %s or -tags=testloader_)", + suite, Selected(), Env) +}