Skip to content

testsuite: don't run transport-agnostic archiver tests over rest:// (#9324)#9837

Merged
ThomasWaldmann merged 3 commits into
borgbackup:masterfrom
ThomasWaldmann:testsuite-9324-skip-redundant-remote-tests
Jul 1, 2026
Merged

testsuite: don't run transport-agnostic archiver tests over rest:// (#9324)#9837
ThomasWaldmann merged 3 commits into
borgbackup:masterfrom
ThomasWaldmann:testsuite-9324-skip-redundant-remote-tests

Conversation

@ThomasWaldmann

@ThomasWaldmann ThomasWaldmann commented Jun 30, 2026

Copy link
Copy Markdown
Member

What

Stop running the remote (rest://) archiver variant for tests whose behavior does not depend on the repository transport.

Why

Profiling the testsuite (-n auto, no coverage) showed every one of the 40 slowest tests was a remote_archiver variant. The remote variant spawns a borgstore-server-rest subprocess and does synchronous HTTP-over-stdio round trips per object, so a remote run costs several times its local twin — e.g. test_prune_repository_example: ~2.8s local vs ~12.2s remote (4.4x).

Most of those tests exercise pure command logic that operates on metadata or only formats output (prune scheduling, diff/list/info rendering, rename, delete/undelete, recreate, key handling, debug, return codes, fuse mount, lock_cmds, repo-create/repo-delete, repo-space, tar). That logic is identical regardless of transport — none of it calls a store method whose implementation actually differs per backend — and the wire-transport-specific behavior is already covered by the create/extract/check/compact/transfer tests.

What changed

  • 20 archiver test files switched from kinds="local,remote,binary" (or "local,remote") to kinds="local,binary" (or "local").
  • generate_archiver_tests()'s docstring now states the rule directly: only keep remote where a backend genuinely branches its implementation per transport (not just "moves real archive data"), and that branch isn't already exercised by another remote-tested file.
  • Added check_cmd_remote_test.py: check_cmd_test.py itself dropped remote (its tests only exercise generic store list/load/delete, same as prune/list/etc.), but Repository.check()'s repository-only mode relies on store.hash(), which the REST backend computes server-side (nothing downloaded) instead of the default download-then-hash. No existing test actually exercised a plain --repository-only check catching pack corruption via that hash comparison (the existing corruption tests all go through --verify-data, which decrypts client-side instead), so this closes a real coverage gap rather than just moving it.

remote is now kept only where the store I/O pattern itself genuinely differs by backend:

  • create / extract: Repository.get()'s store.load(offset=, size=) (partial reads — HTTP Range requests for REST vs. seek/read elsewhere) and store.store()'s content-hash upload header.
  • check (new check_cmd_remote_test.py): store.hash() computed server-side.
  • compact: its own store-level defrag/pack-rewrite path.
  • transfer: the only command that opens two live repository/store connections at once (source + destination).
  • Plus the already-local,remote checks_test.py (shared setup/teardown for the check suite).

Commands whose remote testing turned out to be redundant on closer look (all confirmed by reading the actual command implementation, not just assumption):

  • tar cmds (export-tar/import-tar): route through the exact same Archive/Repository.get()/put() machinery as extract/create, so their own remote run is redundant — same category as recreate.
  • repo-create / repo-delete: store.create()/store.destroy() are trivial 1:1 RPC forwards in the REST backend (no content-dependent divergence); still incidentally exercised over the wire by every remaining remote test file's own repo-create setup step, and by checks_test.py's repo-delete.
  • repo-space: grepped borg's own code for .quota(it's never called. repo_space_cmd.py only does generic store_list/store_store/store_delete on config/space-reserve.*, despite quota() being a genuinely backend-specific method in borgstore that borg simply doesn't use.
  • lock_cmds: Lock only does generic list/store/delete on locks/*; its one host-specific check (process_alive() across hosts) isn't exercised differently by this test infra regardless of "kind" anyway (client hostid is the same host either way).

Effect

Two independent measurements, both -n auto, no coverage, -k "not binary":

The 374 → 178 → 128 invocation counts and the original 242.7s → 154.2s wall-clock come from the PR author's machine (see the numbers earlier in this description). On top of that, this update was verified with a same-machine, apples-to-apples before/after (12-core machine, checked out the pre-PR commit into the working tree, ran the full archiver suite, then restored the branch and re-ran):

before this PR (pre-#9837, same machine) after this update
archiver suite wall-clock (-n auto) 219.7s (1 failed, 913 passed, 55 skipped) 96.0s (683 passed, 39 skipped)
remote_archiver invocations (archiver suite) 374 128 (−66%)

The one failure in the "before" run (test_fuse_allow_damaged_files, a FUSE-daemonization timeout) is an unrelated, known-flaky test (stale/slow FUSE mount teardown), not caused by this change — it did not reproduce in the "after" run. Test counts differ between the two runs because kind-pruning also removes the parametrized [remote_archiver] variants themselves (not just their execution time), so "after" collects fewer test IDs overall.

Notes for review

The categorization is per-file (the existing granularity). Previously-open borderline calls, now resolved:

  • recreate / tar — both rewrite/stream chunk data; recreate was already dropped from remote (covered by create+extract); tar is now dropped too, for the same reason (redundant with create+extract's coverage of the actual wire-specific Repository.get()/put() behavior).
  • check_cmd was the single biggest time sink and was originally kept whole on remote "since it is borg's integrity command." On closer inspection, only its store.hash()-based repository-only corruption check is actually backend-specific; the rest of check_cmd_test.py's 17 tests are transport-agnostic (metadata presence/absence, --verify-data decryption-based corruption which never touches store.hash()). Dropped remote from check_cmd_test.py and added one small dedicated check_cmd_remote_test.py test for the hash-comparison path instead — which also fixed a real gap, since no prior test exercised plain --repository-only corruption detection under any transport.
  • The two remaining get_kind() == "remote" branches in the touched files (test_repo_list_from_borg1, test_migrate_lock_alive) were already pytest.skip("only works locally"), so nothing was left dead.

Refs #9324.

🤖 Generated with Claude Code

…orgbackup#9324)

The remote (rest://) archiver variant spawns a borgstore-server-rest
subprocess and does synchronous HTTP-over-stdio round trips per object,
so a remote test run costs several times its local twin (e.g.
test_prune_repository_example: ~2.8s local vs ~12.2s remote). Profiling
the suite showed every one of the 40 slowest tests was a remote_archiver
variant.

Most of those tests exercise pure command logic that operates on
metadata or only formats output (prune scheduling, diff/list/info
rendering, rename, delete/undelete, recreate, key handling, debug,
return codes, fuse mount) - this behaves identically regardless of
transport, and the data path over rest is already covered by the
create/extract/check/compact/repo/lock/transfer tests.

Switch those files to kinds="local,binary" and document the policy for
picking kinds in generate_archiver_tests().

Effect (local, -n auto, no coverage): remote_archiver invocations
374 -> 178; full suite 242s -> 154s (-36%), no test failures. The win
on the overloaded macOS CI runners (cold subprocess spawns + coverage)
should be relatively larger.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.17%. Comparing base (1206808) to head (dbadb32).
⚠️ Report is 17 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9837      +/-   ##
==========================================
+ Coverage   85.14%   85.17%   +0.02%     
==========================================
  Files          93       93              
  Lines       15337    15346       +9     
  Branches     2306     2308       +2     
==========================================
+ Hits        13059    13071      +12     
+ Misses       1586     1581       -5     
- Partials      692      694       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

ThomasWaldmann and others added 2 commits July 1, 2026 19:38
check_cmd_test.py's remaining tests only exercise the generic store
list/load/delete calls also covered by other remote-tested files
(create, extract, ...), so drop remote from its kinds like borgbackup#9324 did
elsewhere.

Repository.check() does have one transport-specific path though: the
REST backend computes pack hashes server-side (nothing downloaded),
unlike other backends. No existing test actually exercised a plain
--repository-only check catching a corrupted pack via that hash
comparison (existing corruption tests all go through --verify-data,
which decrypts client-side instead). Add that as its own local+remote
test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… tests

lock_cmds, repo-create, repo-delete, repo-space and tar all only exercise
the generic store list/load/store/delete forwarding that every backend
implements identically:

- lock_cmds: Lock only does list/store/delete on locks/*, and its one
  host-specific check (process_alive across hosts) isn't exercised
  differently by this test infra regardless of "kind" anyway.
- repo-create/repo-delete: store.create()/destroy() are trivial 1:1 RPC
  forwards in the REST backend; still exercised over the wire incidentally
  via every remaining remote test file's setup (repo-create) and via
  checks_test.py (repo-delete), which stays on remote.
- repo-space: grepped borg's own code - it never calls store.quota() at
  all, only generic store_list/store/delete on config/space-reserve.*.
- tar cmds: do_export_tar/do_import_tar route through the same
  Archive/Repository.get()/put() machinery as extract/create, so this
  is redundant with their remote coverage (same category as recreate).

create, extract, check and transfer keep "remote" because they exercise
backends that genuinely branch per transport (partial reads/content-hash
upload headers, server-side hashing, two simultaneous store connections).
Rewrote the kinds-picking policy comment in generate_archiver_tests() to
state that rule directly instead of "moves real archive data".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ThomasWaldmann ThomasWaldmann merged commit 5453014 into borgbackup:master Jul 1, 2026
19 checks passed
@ThomasWaldmann ThomasWaldmann deleted the testsuite-9324-skip-redundant-remote-tests branch July 1, 2026 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant