Skip to content

test(b2): pin core edges found by mutation testing - #249

Merged
ffumero2003 merged 5 commits into
mainfrom
test-issue-204-b2-edge-assertions
Oct 30, 2026
Merged

ffumero2003 merged 5 commits into
mainfrom
test-issue-204-b2-edge-assertions

Conversation

@ffumero2003

@ffumero2003 ffumero2003 commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Context

Part of the test-integrity audit (#97). Mutation testing (Gremlins) found single-line changes to b2 production code that no test catches. This PR covers those items from #204's ### b2 section (slice C3).

Changes

Test-only. New tests in b2/accountinfo_edges_test.go and b2/core_edges_test.go:

  • Cache refresh after object-lock updates (baseline.go updateFileRetention and updateFileLegalHold): after an update, Info is fetched again instead of served from the stale cache.
  • Session-file size caps (accountinfo.go, write and read): a session of exactly 1,048,576 bytes is accepted and 1,048,577 bytes is rejected, on both write and read.
  • Delete on a folder object (b2.go Object.Delete): returns the non-regular-file error.
  • memoryBuffer.Close twice (buffer.go): idempotent, no panic.
  • Zero-progress wake (progress.go): no duplicate callback.
  • Vacuous "no secret in file" check removed from TestAccountInfoFileRoundTripAndPermissions: AuthData has no key field, so it could never fail. TestAccountInfoFileResumeAndClearOn401 already covers it.

Not covered here: three items need a small test hook in production code, which a test-only change can't add. They're filed as #246:

  • the temp-file cleanup error (accountinfo.go:135);
  • file.Sync() before rename (accountinfo.go:146);
  • returning a buffer to the pool (buffer.go:148).

Verification

Each mutation was applied alone; the new test failed, then passed after revert:

Mutation Failing output
Retention or legal-hold cache refresh: error check flipped, or refresh deleted (4 breaks) post-update marker="before" calls=2 (want 3)
Write cap > → >= / guard deleted Set 1048576-byte session: ... exceeds / Set 1048577-byte session error = <nil>
Read cap > → >= / guard deleted AuthData() for 1048576-byte session: ok=false / ... 1048577-byte session: ok=true
Folder-delete guard negated / deleted Delete(folder) error = <nil>, want non-regular-file rejection
Buffer Close guard flipped / deleted Close retained the buffer / nil-pointer panic on second Close
Zero-wake n > 0 flipped / deleted progress callbacks = [0 0] / [1 1 1]

With no B2 credentials set: go test -race -count=5 -shuffle=on ./b2/..., a 30-run -race check of the buffer and progress tests, go vet ./..., GOOS=windows go vet ./b2/... and make ci pass.

make ci (tail)

go build ./...
go vet ./...
go mod tidy -diff
go mod verify
all modules verified
0 issues.
go test -race -shuffle=on ./...
ok  	github.com/Backblaze/blazer/b2	16.517s
ok  	github.com/Backblaze/blazer/b2/sync	4.412s
ok  	github.com/Backblaze/blazer/base	7.827s
ok  	github.com/Backblaze/blazer/internal/b2types	3.555s
ok  	github.com/Backblaze/blazer/internal/b2wiretest	2.319s
ok  	github.com/Backblaze/blazer/internal/blog	3.337s
ok  	github.com/Backblaze/blazer/internal/retry	3.128s
ok  	github.com/Backblaze/blazer/x/b2sim	2.111s
ok  	github.com/Backblaze/blazer/x/consistent	2.967s
ok  	github.com/Backblaze/blazer/x/transport	4.032s
ok  	github.com/Backblaze/blazer/x/window	2.679s
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./...
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build ./...
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build ./...
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build ./...
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build ./...
... scripts/*_test.sh: all ok

Part of #204

@ffumero2003 ffumero2003 added this to the 0.10.0 milestone Oct 30, 2026
@ffumero2003 ffumero2003 added the skip-changelog No changelog entry needed; skips the changelog check label Oct 30, 2026
@ffumero2003 ffumero2003 self-assigned this Oct 30, 2026

@david-santacruz-backblaze david-santacruz-backblaze 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.

Two small things before approval, please check:

  • intent/b2-edge-assertions/ has only plan.md, while sibling slices (e.g. b2-mapping-edges) have intent.md, spec.md and plan.md. Could you add the other two, or drop plan.md and move the mutation table into the PR description?
  • AGENTS.md asks for the tail of make ci in the PR. Could you paste it? CI is green, so this is just for the record.

@ffumero2003

Copy link
Copy Markdown
Contributor Author

@david-santacruz-backblaze Thanks, both done:

  • Intent folder: removed (617e15b). The mutation table was already in the PR description, so intent/b2-edge-assertions/ is gone; test: tighten minor test assertions (checklist) #204 is a checklist, so the description is its home.
  • make ci tail: added to the description, after syncing with main (1a06382): 0 issues., the race tests across every package, the five cross-builds and the script checks all pass.

@ffumero2003
ffumero2003 merged commit 15b1d48 into main Oct 30, 2026
23 of 24 checks passed
@ffumero2003
ffumero2003 deleted the test-issue-204-b2-edge-assertions branch October 30, 2026 21:24
ffumero2003 added a commit that referenced this pull request Oct 30, 2026
#247 removed objectForDeleteOptionTest from b2_test.go while #249's
TestObjectDeleteRejectsFolderStatus still used it, so the b2 tests no
longer compile on main. Use the same setup as TestObjectDeleteAllowsCopyStatus.
ffumero2003 added a commit that referenced this pull request Oct 30, 2026
…ext canceled (#252)

* test(b2): reproduce writes after copy cancellation

Pin delayed source reads that resume after copyContext returns and Writer.ReadFrom has been cancelled and closed.

Part of #229

* fix(b2): stop a cancelled copy from writing after copyContext returns

A cancelled copy could return while its source Read was still blocked. When that Read resumed, the copy goroutine could write to a Writer that the caller had already closed, causing the slice-bounds panic seen in CI run 40925001975.

Guard destination writes and mark the guard stopped before returning on cancellation. Waiting for the copy goroutine is not an option because the source Read may block forever. Preserve the closer path that closes the body and waits for its copy to finish.

Closes #229

* test(b2): match the abandoned-copy error with errors.Is and document ReadFrom cancellation

Address the panel's nits on #229: the regression test compared the error
text, so wrapping errCopyAbandoned would break it while the guard still
works; and ReadFrom's godoc did not say that a Read already in progress on a
non-seekable source can outlive a cancelled call.

* docs(b2): state exactly what ReadFrom guarantees after a cancelled copy

The comment said nothing a pending Read returns is written, but the guard
only stops writes once copyContext returns: a Write already in progress, with
data read before the cancellation, can still complete. Say what the code
guarantees: after ReadFrom returns, nothing more from r reaches the Writer.

* test(b2): pin ReadFrom cancellation during blocked error cleanup

Hold cancel_large_file or WithCancelOnError callbacks behind channels after producer and worker upload failures. ReadFrom must return on cancellation before those channels are released, and the callback must receive the cancellation request result (#229).

* fix(b2): release the copy guard before upload error cleanup

Keep the existing Writer error path and release the copy guard only on terminal write errors. No buffer writes follow that hook, and the stopped check still gates every later write, so no source bytes can reach the destination after a cancelled copy returns.

Release the error-state mutex before cancel_large_file and errf as well, so a producer observing a worker failure does not wait behind cleanup. Close joins its existing worker wait group on error exits to preserve delivery of the cleanup result to WithCancelOnError callers.

Reject a separate deferred error-recording and cleanup mechanism: reusing setErr preserves first-error ownership and callback semantics without adding parallel state. The public API and ReadFrom godoc remain unchanged.

Closes #229

* test(b2): pin error mutex release and Close cleanup waiting

Hold the producer inside buffer allocation until a worker enters blocked cancellation cleanup, then release it into getErr while the copy guard is still held. This makes moving cleanup back under emux fail deterministically.

Gate the worker cancellation request separately and verify that its nonblocking error callback has completed when Close returns. Removing the deferred worker wait now fails without making the callback depend on Close.

Keep the existing cancellation cases and production code unchanged.

* docs(b2): document Writer.Close callback wait

* test(b2): make Writer.Close callback proof deterministic

* test(b2): pin Writer.ReadFrom upload error precedence

* fix(b2): preserve upload errors from Writer.ReadFrom

* test(b2): pin ReadFrom caller cancellation with recorded transport errors

* fix(b2): preserve caller cancellation cause in Writer.ReadFrom

* test(b2): pin caller custom causes in Writer.ReadFrom

* fix(b2): distinguish Writer cancellation from caller causes

* test(b2): start ReadFrom deadline after upload setup

* docs(b2): clarify Writer ReadFrom error identity

* test(b2): bound copy cancellation and Close cleanup waits

* docs(b2): clarify cancellation and breaking Close wait contract

* test(b2): acknowledge cancellation stub body close results

* test(b2): inline the folder-delete test setup removed by #247

#247 removed objectForDeleteOptionTest from b2_test.go while #249's
TestObjectDeleteRejectsFolderStatus still used it, so the b2 tests no
longer compile on main. Use the same setup as TestObjectDeleteAllowsCopyStatus.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changelog No changelog entry needed; skips the changelog check

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants