Files
acp/cleanup_test.go
T
Samuel Cui ac02c71d3f feat!: stream caller-owned items and own the result queue
Reshape the public API around one push engine and restore the af05f05c
surface as a compatibility shell over it.

- NewStream takes caller-owned items (Source/Targets), reports every accepted
  item through one results callback, and Wait returns the run's terminal error.
  Run, BatchSource and the item callbacks are gone.
- The engine owns the result path: WithResultBuffer is the depth of the result
  queue, WithResultBatch is how many results one delivery carries, and
  WithResultFlushInterval is how long a successful result may wait. A result
  that carries any error is delivered immediately as its own batch.
- Restore the af05f05c API byte-compatibly in the same package: New, Copyer,
  Wait/WaitErr, AccurateJob, WildcardJob, the report events and the JSON report
  round-trip through encoding/json alone.
- One descriptor serves a whole item; the stored hash is read through the
  descriptor that reads the content and the computed hash is published through
  it before it closes.
- Make every handoff escapable by a hard stop: the read buffer, a chunk send,
  an event send and the result buffer.
- Fix the recorded sweep findings: report indent and round-trip, cmd/acp exit
  status, acp-rewrite final paths, mmap EOF/close/slice, disk-usage
  reservations, ErrTargetIO identity, the fatal-panic safety net, option
  validation, and the FreeBSD/Windows build paths.
- Cover the contract with semantic tests, add Makefile check/race/vet/cross
  targets, and document the withdrawn v0.1.0/v0.2.0 migration in README.
2026-09-19 15:18:13 +08:00

56 lines
1.6 KiB
Go

package acp
import (
"errors"
"os"
"path/filepath"
"testing"
)
func TestCleanupRemovesTargetAfterMetadataFailure(t *testing.T) {
// Use a dangling symlink so metadata restoration fails while removal remains possible.
root := t.TempDir()
sourcePath := writeSourceFile(t, root, "source", []byte("fixture"))
info, err := os.Stat(sourcePath)
if err != nil {
t.Fatal(err)
}
stat, err := newStat(sourcePath, info)
if err != nil {
t.Fatal(err)
}
target := filepath.Join(root, "target")
if err := os.Symlink(filepath.Join(root, "missing"), target); err != nil {
t.Skipf("create test symlink: %v", err)
}
// The results stage must discard the failed target before publishing its final result.
copyer := newTestStream(t)
item := newFixtureItem(sourcePath, target)
fixture := newStreamFixture(item)
copyer.onResults = fixture.onResults
job := &baseJob{
copyer: copyer,
item: item,
path: sourcePath,
stat: stat,
targets: []string{target},
successTargets: []string{target},
}
copyed := make(chan *baseJob, 1)
copyed <- job
close(copyed)
runResults(copyer, copyed)
if _, err := os.Lstat(target); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("stat failed target error = %v, want %v", err, os.ErrNotExist)
}
result, terminalErr := item.terminal(t)
if terminalErr != nil {
t.Fatalf("a metadata failure is a target outcome, not an item failure: %v", terminalErr)
}
if len(result.Targets) != 1 || !errors.Is(result.Targets[0].Err, os.ErrNotExist) {
t.Fatalf("target outcome = %#v, want %v", result.Targets, os.ErrNotExist)
}
}