Skip to content

Coverage uplift across config, fetcher, updater and multirepo#737

Open
rdimitrov wants to merge 9 commits into
rework-testing-ci-fixesfrom
config-coverage-followup
Open

Coverage uplift across config, fetcher, updater and multirepo#737
rdimitrov wants to merge 9 commits into
rework-testing-ci-fixesfrom
config-coverage-followup

Conversation

@rdimitrov

@rdimitrov rdimitrov commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Stacked on top of #734.

Pushes statement coverage up across the packages with the largest gaps, plus a couple of hygiene cleanups. Six commits, one per concern.

Coverage moves

Package Before After Why
metadata/config 28.6% 97.1% 4 SetDefaultFetcher* / SetRetryOptions methods (all 0%) now exercised
metadata/fetcher 57.1% 93.9% 6 constructor/setter methods covered + DownloadFile tests now hermetic via httptest.Server instead of jku.github.io
metadata/updater 64.5% 77.0% GetTargetInfo, DownloadTarget, FindCachedTarget covered
metadata/multirepo 14.0% 37.2% EnsurePathsExist (0% → 100%), New failure paths, initTUFClients (0% → 76%)

Commits

Commit What it does
chore(testutils): drop the unused helpers/updater.go Deletes dead code: UpdaterTestCase, CheckError, AssertFilesExist, AssertFilesExact, TrustedMetadataTestCase, RunTrustedMetadataTests had zero callers; the README pointed at functions that didn't even exist. Drop both.
test(config): cover the SetDefaultFetcher* and SetRetryOptions methods 4 table-driven tests for UpdaterConfig methods. 28.6% → 97.1%.
test(fetcher): cover the constructors and setters 6 setters/constructors that were 0%. 57.1% → 87.8%.
test(fetcher): make DownloadFile tests hermetic via httptest.Server Replaces jku.github.io/tuf-demo URLs with a local httptest.Server. Removes external CI dependency. Adds a BodyExceedsLimit case for the post-read LimitReader path. 87.8% → 93.9%.
test(updater): cover GetTargetInfo, DownloadTarget and FindCachedTarget Three new tables backed by simulator.TestRepository. The simulator has a couple of bugs in target URL routing (lastIndex panics on shallow paths; hash-prefix parser confuses nested paths) — sidestepped with doubly-nested paths and consistent_snapshot = false. Worth fixing in the simulator separately. 64.5% → 77.0%.
test(multirepo): cover EnsurePathsExist and New's construction failures Tables for EnsurePathsExist (mirrors the equivalent in metadata/config) and the New() construction error paths. 14.0% → 37.2%.

Not done (intentionally)

  • multirepo.Refresh / GetTopLevelTargets / GetTargetInfo / DownloadTarget — still 0%. They need a working multi-repo TUF fixture (multiple simulators wired together via a map.json). Worth a follow-up PR.
  • Fixing the two simulator URL-routing bugs flagged in the updater commit.
  • Race-detector runs, property-based tests, larger fuzz corpora — separate concerns.

Merge order

If #734 merges first, the base auto-updates to master and this can merge cleanly. If #734 lands as something different, this branch rebases easily — none of the commits depend on the testing-rework rename pattern.

🤖 Generated with Claude Code

@rdimitrov
rdimitrov requested a review from a team as a code owner June 3, 2026 14:17
@rdimitrov rdimitrov changed the title test(config): bring metadata/config coverage from 28.6% to 97.1% Coverage uplift across config, fetcher, updater and multirepo Jun 3, 2026
@rdimitrov
rdimitrov force-pushed the config-coverage-followup branch from bbb7f54 to 123dcd5 Compare June 3, 2026 14:58
@rdimitrov
rdimitrov requested a review from Copilot June 3, 2026 15:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR increases statement coverage across several core metadata packages by adding/expanding unit tests, making fetcher tests hermetic (no external network dependency), and removing dead test helper documentation/code in internal/testutils.

Changes:

  • Add table-driven tests to cover previously-uncovered config/fetcher/updater/multirepo branches.
  • Make metadata/fetcher download tests hermetic via httptest.Server, including an additional body-size limit case.
  • Remove unused updater-specific test helpers and update internal/testutils documentation accordingly.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
metadata/updater/updater_test.go Adds new tables for GetTargetInfo, DownloadTarget, and FindCachedTarget, plus a Windows skip helper for simulator routing limitations.
metadata/multirepo/multirepo_test.go Adds table tests for EnsurePathsExist and failure-path tests for New().
metadata/fetcher/fetcher_test.go Reworks DownloadFile tests to use httptest.Server and adds tests for constructors/setters and size-limit behavior.
metadata/config/config_test.go Adds tests covering UpdaterConfig fetcher configuration helpers and retry options forwarding.
internal/testutils/README.md Removes references to deleted updater helper utilities.
internal/testutils/helpers/updater.go Deletes unused updater-related helper/test harness code.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread metadata/updater/updater_test.go Outdated
Comment thread metadata/updater/updater_test.go Outdated
Comment thread metadata/fetcher/fetcher_test.go
rdimitrov and others added 9 commits June 3, 2026 18:18
The four mutator methods on UpdaterConfig that proxy to the underlying
DefaultFetcher (SetDefaultFetcherHTTPClient, SetDefaultFetcherTransport,
SetDefaultFetcherRetry, SetRetryOptions) were sitting at 0% statement
coverage. They share the same shape -- type-assert cfg.Fetcher to a
*fetcher.DefaultFetcher, return an error if it's not, otherwise proxy
the call -- so a small nonDefaultFetcher test double plus a
newCfgWithFetcher helper unlocks both branches for all four.

Adds four table-driven tests, one per method:

* TestSetDefaultFetcherHTTPClientTable -- 2 cases.
* TestSetDefaultFetcherTransportTable -- 2 cases.
* TestSetDefaultFetcherRetryTable -- 3 cases (zero counts included).
* TestSetRetryOptionsTable -- 3 cases (nil opts, max-tries opt, wrong
  fetcher type).

go test -cover ./metadata/config/ climbs from 28.6% to 97.1% of
statements. The remaining 2.9% is the inner df.SetTransport error
path, which only fires when the fetcher's internal httpClient isn't
a *http.Client -- the field is unexported so reaching it from outside
the fetcher package would require reflection. Left as-is.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
internal/testutils/helpers/updater.go defined UpdaterTestCase,
TrustedMetadataTestCase, CheckError, AssertFilesExist, AssertFilesExact
and RunTrustedMetadataTests, all of which have zero callers anywhere
in the repository. The actual updater and trustedmetadata test files
use simulator.TestRepository's built-in AssertFilesExist/Exact and
direct assert.ErrorIs/ErrorContains, not these helpers.

The README's "Updater utilities" section additionally listed
ContainsTargetWithPrefix, AssertTargetDownloaded and
AssertTargetNotDownloaded -- none of which are defined anywhere
in the helpers package either. Remove that stale section too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
NewFetcherWithHTTPClient, NewFetcherWithRoundTripper, SetHTTPClient,
SetTransport, SetRetry and SetRetryOptions were all at 0% statement
coverage. Add focused tests for each that exercise the observable
state changes (the test file is in the same package, so we can verify
the unexported client/retryOptions fields directly).

SetTransport gets a two-case table covering both branches: success
on a fetcher backed by *http.Client, and the type-assertion error
when the underlying client is a mock httpClient.

go test -cover ./metadata/fetcher/ climbs from 57.1% to 87.8%. The
remaining 12.2% sits inside DownloadFile and is the network-dependent
error paths (length mismatch via Content-Length, retry on HTTP errors);
those are best addressed in a separate change that swaps the live
jku.github.io URLs for a httptest.Server.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
The TestDownLoadFile + TestDownloadFile_NoHTTPClientSet table previously
hit jku.github.io/tuf-demo for the success and length-mismatch cases,
which made CI dependent on an external service and prevented us from
covering error-injection paths in DownloadFile that aren't reachable
against a live server.

Replace the live URLs with a tiny httptest.Server fixture
(newRootServer) that serves the sampleRootData fixture on /1.root.json
with a correct Content-Length, and 404s on every other path. Add a
TestDownLoadFile_BodyExceedsLimit case to cover the post-read
LimitReader path inside DownloadFile (server omits Content-Length but
returns 200 bytes when maxLength is 10).

Net coverage: fetcher 87.8% -> 93.9% of statements (DownloadFile
specifically 81.2% -> 90.6%). All tests are now offline-safe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
GetTargetInfo (40%), DownloadTarget (0%) and FindCachedTarget (0%)
weren't exercised by the table-driven updater suite. Add three
focused tables backed by simulator.TestRepository:

* TestGetTargetInfoTable -- known vs unknown target lookup.
* TestDownloadTargetTable -- happy path with cfg.RemoteTargetsURL,
  happy path with an explicit baseURL argument, and the
  no-base-URL-set rejection.
* TestFindCachedTargetTable -- post-download cache hit, missing
  file, corrupted (hash-mismatch) file, and the DisableLocalCache
  no-op.

The simulator's URL routing for targets has two latent bugs: its
lastIndex helper panics on target paths with fewer than two
separators, and the consistent-snapshot hash-prefix parser
mis-handles nested paths. We sidestep both by using "a/b/<name>"
paths and disabling consistent_snapshot in the download tests --
worth fixing in the simulator, but out of scope here.

Overall updater statement coverage climbs from 64.5% to 77.0%:
DownloadTarget 0% -> 59.5%, FindCachedTarget 0% -> 81.2%,
GetTargetInfo 40% -> exercised more thoroughly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
The multirepo package was sitting at 14% statement coverage because
the only methods exercised were NewConfig and validateRepoName -- the
construction error paths and the standalone EnsurePathsExist were
untested.

Add two new tables:

* TestEnsurePathsExistTable -- 5 cases mirroring the equivalent
  config_test.go table: happy path with fresh dirs, DisableLocalCache
  no-op, idempotency on already-existing dirs, os.ErrNotExist on
  empty paths, and the file-in-place-of-dir collision.

* TestNewFailureCases -- two cases that fire before any per-repo
  updater is built: invalid root bytes that fail updater.New
  internally, and sabotaged TrustedRoots that hit the "trusted root
  missing" branch in initTUFClients.

Net coverage: multirepo statement coverage climbs from 14.0% to 37.2%.
EnsurePathsExist 0% -> 100%. New 57.1% -> 85.7%. initTUFClients
0% -> 76.0%. Refresh, GetTopLevelTargets, GetTargetInfo and
DownloadTarget remain at 0% -- they require a working multi-repo
TUF fixture, which is a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
The new TestGetTargetInfoTable / TestDownloadTargetTable /
TestFindCachedTargetTable cases route through
simulator.RepositorySimulator.fetch, which keys off
filepath.Separator and the hardcoded "/targets/" prefix --
unreachable on windows where filepath.Separator is backslash.
The underlying production code still gets coverage from the
Linux and macOS runners.

Skip the three tests on runtime.GOOS == "windows" until the
simulator's target URL routing is fixed in a separate change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
…Tripper

The original test passed http.DefaultTransport -- already the effective
default Transport on http.DefaultClient -- so the assert.Same check
would still pass if NewFetcherWithRoundTripper stopped installing the
supplied transport. Reported by Copilot reviewer on #737.

Switch to a one-off recordingRoundTripper local type that we can
identity-compare against, and restore the original
http.DefaultClient.Transport after the test (the constructor mutates
the global as a side effect).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Two follow-ups from Copilot's review on #737:

* TestGetTargetInfoTable claimed to exercise GetTargetInfo's implicit
  Refresh, but each subtest called createAndRefresh() first, so the
  inner Refresh branch was never reached. Switch to constructing the
  updater directly with New(cfg) and let GetTargetInfo trigger the
  refresh itself.

* TestDownloadTargetTable picked which baseURL to pass by string-
  matching tc.name == "uses explicit baseURL argument", which is
  brittle (any rename of the subtest silently changes the routing).
  Replace with an explicit useExplicit bool on the case struct.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
@rdimitrov
rdimitrov force-pushed the config-coverage-followup branch from 123dcd5 to 0ea2003 Compare June 3, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants