mirror of
https://github.com/samuelncui/acp.git
synced 2026-07-21 06:52:18 +00:00
test: add library end-to-end coverage
This commit is contained in:
@@ -35,7 +35,7 @@ The current change set addresses these areas:
|
||||
- select the longest matching mountpoint with directory-boundary checks;
|
||||
- use `path/filepath` for host file-system paths;
|
||||
- centralize platform-aware path ordering in `comparePath`;
|
||||
- add semantic, race, cross-platform, and CLI end-to-end coverage.
|
||||
- add semantic, race, cross-platform, CLI end-to-end, and library end-to-end coverage.
|
||||
|
||||
The working tree may already contain staged or unstaged changes. Preserve them and do not reset, rewrite, or discard unrelated work.
|
||||
|
||||
|
||||
+24
-5
@@ -28,9 +28,9 @@ Run the suite with the race detector:
|
||||
go test -race ./...
|
||||
```
|
||||
|
||||
## End-to-end test
|
||||
## End-to-end tests
|
||||
|
||||
The end-to-end test builds the `acp` command, copies a directory tree through the CLI, and verifies:
|
||||
The command end-to-end test builds the `acp` command, copies a directory tree through the CLI, and verifies:
|
||||
|
||||
- regular and empty file contents;
|
||||
- nested destination paths;
|
||||
@@ -38,13 +38,32 @@ The end-to-end test builds the `acp` command, copies a directory tree through th
|
||||
- JSON report decoding;
|
||||
- SHA256 generation for every copied file.
|
||||
|
||||
Run only this test with:
|
||||
Run only the command test with:
|
||||
|
||||
```sh
|
||||
go test -run '^TestACPCopyE2E$' -v .
|
||||
go test -run '^TestACPCommandE2E$' -v .
|
||||
```
|
||||
|
||||
The test is skipped when `go test` is run with `-short`.
|
||||
The library end-to-end test uses the public Go API directly with two destinations and verifies:
|
||||
|
||||
- the complete index, prepare, copy, cleanup, and event pipeline;
|
||||
- regular, empty, and nested file contents at both destinations;
|
||||
- successful report status and destination sets;
|
||||
- file size and exact SHA256 values.
|
||||
|
||||
Run only the library test with:
|
||||
|
||||
```sh
|
||||
go test -run '^TestACPLibraryE2E$' -v .
|
||||
```
|
||||
|
||||
Run both end-to-end tests with:
|
||||
|
||||
```sh
|
||||
go test -run '^TestACP(Command|Library)E2E$' -v .
|
||||
```
|
||||
|
||||
Both tests are skipped when `go test` is run with `-short`.
|
||||
|
||||
## Focused tests
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/samuelncui/acp"
|
||||
)
|
||||
|
||||
func TestACPCopyE2E(t *testing.T) {
|
||||
func TestACPCommandE2E(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping end-to-end test in short mode")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package acp_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/samuelncui/acp"
|
||||
)
|
||||
|
||||
func TestACPLibraryE2E(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping end-to-end test in short mode")
|
||||
}
|
||||
|
||||
tempDir := t.TempDir()
|
||||
source := filepath.Join(tempDir, "source")
|
||||
targets := []string{
|
||||
filepath.Join(tempDir, "target-a"),
|
||||
filepath.Join(tempDir, "target-b"),
|
||||
}
|
||||
for _, target := range targets {
|
||||
if err := os.MkdirAll(target, 0o755); err != nil {
|
||||
t.Fatalf("create target directory %q: %v", target, err)
|
||||
}
|
||||
}
|
||||
|
||||
files := map[string][]byte{
|
||||
"plain.txt": []byte("library copy\n"),
|
||||
"empty.txt": nil,
|
||||
filepath.Join("nested", "data"): {5, 4, 3, 2, 1},
|
||||
}
|
||||
for relativePath, data := range files {
|
||||
path := filepath.Join(source, relativePath)
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
t.Fatalf("create source directory: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(path, data, 0o644); err != nil {
|
||||
t.Fatalf("write source file %q: %v", relativePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
handler, getter := acp.NewReportGetter()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
copyer, err := acp.New(
|
||||
ctx,
|
||||
acp.WildcardJob(acp.Source(source), acp.Target(targets...)),
|
||||
acp.WithHash(true),
|
||||
acp.Overwrite(true),
|
||||
acp.WithEventHandler(handler),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("create copyer: %v", err)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
copyer.Wait()
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-ctx.Done():
|
||||
t.Fatalf("wait for copyer: %v", ctx.Err())
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
copyRoot := filepath.Join(target, filepath.Base(source))
|
||||
for relativePath, want := range files {
|
||||
got, err := os.ReadFile(filepath.Join(copyRoot, relativePath))
|
||||
if err != nil {
|
||||
t.Fatalf("read copied file %q from %q: %v", relativePath, target, err)
|
||||
}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("copied file %q from %q = %v, want %v", relativePath, target, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
report := getter()
|
||||
if len(report.Errors) != 0 {
|
||||
t.Fatalf("report errors = %v", report.Errors)
|
||||
}
|
||||
if len(report.Jobs) != len(files) {
|
||||
t.Fatalf("report jobs = %d, want %d", len(report.Jobs), len(files))
|
||||
}
|
||||
|
||||
jobs := make(map[string]*acp.Job, len(report.Jobs))
|
||||
for _, job := range report.Jobs {
|
||||
jobs[job.FullPath] = job
|
||||
}
|
||||
for relativePath, data := range files {
|
||||
sourcePath := filepath.Join(source, relativePath)
|
||||
job, ok := jobs[sourcePath]
|
||||
if !ok {
|
||||
t.Fatalf("report job not found for %q", sourcePath)
|
||||
}
|
||||
if job.Status != acp.JobStatusFinished {
|
||||
t.Fatalf("job %q status = %q", sourcePath, job.Status)
|
||||
}
|
||||
if len(job.FailTargets) != 0 {
|
||||
t.Fatalf("job %q failures = %v", sourcePath, job.FailTargets)
|
||||
}
|
||||
if job.Size != int64(len(data)) {
|
||||
t.Fatalf("job %q size = %d, want %d", sourcePath, job.Size, len(data))
|
||||
}
|
||||
|
||||
wantTargets := make(map[string]struct{}, len(targets))
|
||||
for _, target := range targets {
|
||||
wantTargets[filepath.Join(target, filepath.Base(source), relativePath)] = struct{}{}
|
||||
}
|
||||
if len(job.SuccessTargets) != len(wantTargets) {
|
||||
t.Fatalf("job %q success targets = %v", sourcePath, job.SuccessTargets)
|
||||
}
|
||||
for _, target := range job.SuccessTargets {
|
||||
if _, ok := wantTargets[target]; !ok {
|
||||
t.Fatalf("job %q unexpected success target %q", sourcePath, target)
|
||||
}
|
||||
}
|
||||
|
||||
sum := sha256.Sum256(data)
|
||||
if want := hex.EncodeToString(sum[:]); job.SHA256 != want {
|
||||
t.Fatalf("job %q SHA256 = %q, want %q", sourcePath, job.SHA256, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user