diff --git a/.custom-gcl.yml b/.custom-gcl.yml new file mode 100644 index 0000000..e5bd9ce --- /dev/null +++ b/.custom-gcl.yml @@ -0,0 +1,5 @@ +version: v2.13.1 +plugins: + - module: "go.uber.org/nilaway" + import: "go.uber.org/nilaway/cmd/gclplugin" + version: "v0.0.0-20260808063849-8649a03c818a" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ef11c8..9018396 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,9 @@ jobs: go mod tidy git diff --exit-code -- go.mod go.sum + - name: Run golangci-lint + run: make lint + test: name: Go tests (${{ matrix.os }}) runs-on: ${{ matrix.os }} diff --git a/.gitignore b/.gitignore index f9d654e..94b81a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .kata.local.toml # roborev snapshots /.roborev/ +/custom-gcl diff --git a/.golangci.nilaway.yml b/.golangci.nilaway.yml new file mode 100644 index 0000000..e72f73f --- /dev/null +++ b/.golangci.nilaway.yml @@ -0,0 +1,19 @@ +version: "2" +run: + tests: false +linters: + default: none + enable: + - nilaway + settings: + custom: + nilaway: + type: "module" + description: Static analysis tool to detect potential nil panics in Go code. + settings: + include-pkgs: "go.kenn.io/kit" + exclusions: + rules: + - path: "_test\\.go" + linters: + - nilaway diff --git a/.golangci.yml b/.golangci.yml index 3ef7188..d92e03c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,7 @@ version: "2" run: tests: true + go: "1.27" linters: default: none enable: @@ -33,6 +34,10 @@ linters: alias: gittest - pkg: go.kenn.io/kit/git/worktree alias: gitworktree + testifylint: + disable: + - float-compare + - require-error exclusions: generated: lax presets: @@ -47,6 +52,10 @@ linters: - linters: - modernize text: "omitzero:" + - linters: + - staticcheck + path: ^(agenthook/|git/managed/untrusted_tree\.go$) + text: "ST1005:" paths: - third_party$ - builtin$ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..abe25fe --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +GOLANGCI_LINT_VERSION ?= v2.13.1 +CUSTOM_GCL_DIR ?= . +CUSTOM_GCL := $(CUSTOM_GCL_DIR)/custom-gcl + +.PHONY: lint lint-golangci nilaway nilaway-golangci-build +lint: lint-golangci nilaway + +lint-golangci: + go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) run ./... + +nilaway-golangci-build: + go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) custom \ + --destination $(CUSTOM_GCL_DIR) --name custom-gcl --version $(GOLANGCI_LINT_VERSION) + +nilaway: nilaway-golangci-build + $(CUSTOM_GCL) run --config .golangci.nilaway.yml ./... diff --git a/agenthook/hermes.go b/agenthook/hermes.go index fae5a83..fde87ef 100644 --- a/agenthook/hermes.go +++ b/agenthook/hermes.go @@ -135,7 +135,7 @@ func removeOwnedHermesHookNodes( event := hooks.Content[i] entries := hooks.Content[i+1] resolvedEntries := resolveYAMLAlias(entries) - if resolvedEntries.Kind != yaml.SequenceNode { + if resolvedEntries == nil || resolvedEntries.Kind != yaml.SequenceNode { if isHermesEventName(event.Value) { return fmt.Errorf( "Hermes config %s event %q must be an array", path, event.Value, diff --git a/agenthook/json.go b/agenthook/json.go index 215c37d..754e2ed 100644 --- a/agenthook/json.go +++ b/agenthook/json.go @@ -34,6 +34,9 @@ func planNestedJSONConfig( } } if !uninstall { + if hooksObject == nil { + return nil, false, fmt.Errorf("agent hook config %s has no hooks object", path) + } for _, hook := range hooks { entry := map[string]any{} if hook.matcher != "" { diff --git a/agenthook/json_direct.go b/agenthook/json_direct.go index 429a165..3ed1082 100644 --- a/agenthook/json_direct.go +++ b/agenthook/json_direct.go @@ -43,6 +43,9 @@ func planDirectJSONConfig( } } if !uninstall { + if hooksObject == nil { + return nil, false, fmt.Errorf("agent hook config %s has no hooks object", path) + } if spec.requireVersion { if _, exists := root["version"]; !exists { root["version"] = 1 diff --git a/backup/auxiliary.go b/backup/auxiliary.go index 07b2b01..e87bf13 100644 --- a/backup/auxiliary.go +++ b/backup/auxiliary.go @@ -169,6 +169,9 @@ func captureAuxiliaryArtifacts( } return nil, fmt.Errorf("backup: preparing auxiliary artifact %q: %w", artifact.Name, err) } + if prepared == nil { + return nil, fmt.Errorf("backup: preparing auxiliary artifact %q returned no result", artifact.Name) + } id := prepared.ID() if _, err := appender.AddPrepared(ctx, prepared); err != nil { return nil, err diff --git a/backup/auxiliary_test.go b/backup/auxiliary_test.go index cdbf111..618a6a1 100644 --- a/backup/auxiliary_test.go +++ b/backup/auxiliary_test.go @@ -7,8 +7,8 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) @@ -48,7 +48,7 @@ func TestValidateAuxiliaryArtifactsRejectsAmbiguousAuthority(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() - assert.ErrorContains(t, validateAuxiliaryArtifacts(test.artifacts), test.want) + Assert.ErrorContains(t, validateAuxiliaryArtifacts(test.artifacts), test.want) }) } } @@ -90,11 +90,11 @@ func TestValidateManifestAuxiliaryRequiresSortedBoundedIdentity(t *testing.T) { want: "invalid size", }, } - require.NoError(t, validateManifestAuxiliary([]ManifestAuxiliary{valid})) + Require.NoError(t, validateManifestAuxiliary([]ManifestAuxiliary{valid})) for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() - assert.ErrorContains(t, validateManifestAuxiliary(test.artifacts), test.want) + Assert.ErrorContains(t, validateManifestAuxiliary(test.artifacts), test.want) }) } } @@ -106,14 +106,15 @@ func TestRestoreAuxiliaryRejectsOversizedFooterBeforePayloadRead(t *testing.T) { name = "unreadable payload" } t.Run(name, func(t *testing.T) { + require := Require.New(t) repo := initTestRepo(t) known := map[pack.BlobID]IndexEntry{} appender := NewPackAppender(repo, known, pack.DefaultZstdLevel, nil, testPackExt) content := bytes.Repeat([]byte("oversized auxiliary payload"), 4096) id, _, err := appender.Add(content) - require.NoError(t, err) + require.NoError(err) _, _, err = appender.Finish() - require.NoError(t, err) + require.NoError(err) if corruptPayload { corruptStoredBlob(t, repo, known, id) } @@ -125,8 +126,8 @@ func TestRestoreAuxiliaryRejectsOversizedFooterBeforePayloadRead(t *testing.T) { restored, err := state.restoreAuxiliary(context.Background(), manifest) - require.ErrorContains(t, err, "is 110592 bytes but manifest records 1") - assert.Nil(t, restored) + require.ErrorContains(err, "is 110592 bytes but manifest records 1") + Assert.Nil(t, restored) }) } } diff --git a/backup/create_shared.go b/backup/create_shared.go index 7c4d186..896cf0d 100644 --- a/backup/create_shared.go +++ b/backup/create_shared.go @@ -122,6 +122,9 @@ func preparePortableMetadata( } return pack.BlobID{}, 0, fmt.Errorf("backup: preparing portable metadata: %w", err) } + if prepared == nil { + return pack.BlobID{}, 0, fmt.Errorf("backup: preparing portable metadata returned no result") + } metadataID := prepared.ID() if _, err := appender.AddPrepared(ctx, prepared); err != nil { return pack.BlobID{}, 0, err diff --git a/backup/pagehash.go b/backup/pagehash.go index a9da3f8..940a23e 100644 --- a/backup/pagehash.go +++ b/backup/pagehash.go @@ -226,7 +226,7 @@ func MaterializeHashMap( fetch func(pack.BlobID) ([]byte, error), chain []pack.BlobID, ) (*PageHashMap, error) { - var deltas []*PageHashDelta + deltas := make([]*PageHashDelta, 0) for i, id := range chain { data, err := fetch(id) if err != nil { @@ -258,6 +258,9 @@ func MaterializeHashMap( err, ) } + if d == nil { + return nil, fmt.Errorf("backup: hash-map chain blob %d (%s) decoded to no delta", i, id) + } deltas = append(deltas, d) } return nil, fmt.Errorf("backup: hash-map chain of %d blobs has no keyframe", len(chain)) diff --git a/backup/pagemap.go b/backup/pagemap.go index b65507e..74f0efb 100644 --- a/backup/pagemap.go +++ b/backup/pagemap.go @@ -241,7 +241,7 @@ func ApplyPageMapDelta(base, delta *PageMap) (*PageMap, error) { // MaterializePageMap walks a newest-to-oldest blob chain to a keyframe and // replays the deltas oldest-first. func MaterializePageMap(fetch func(pack.BlobID) ([]byte, error), chain []pack.BlobID) (*PageMap, error) { - var deltas []*PageMap + deltas := make([]*PageMap, 0) for i, id := range chain { data, err := fetch(id) if err != nil { @@ -260,6 +260,9 @@ func MaterializePageMap(fetch func(pack.BlobID) ([]byte, error), chain []pack.Bl } return m, nil } + if m == nil { + return nil, fmt.Errorf("backup: page-map chain blob %d (%s) decoded to no delta", i, id) + } deltas = append(deltas, m) } return nil, fmt.Errorf("backup: page-map chain of %d blobs has no keyframe", len(chain)) diff --git a/backup/restore.go b/backup/restore.go index 0ae1604..f68c2dd 100644 --- a/backup/restore.go +++ b/backup/restore.go @@ -286,6 +286,9 @@ func Restore(ctx context.Context, r *Repo, app App, opts RestoreOptions) (res *R // publishRestoredDB touches an Overwrite target's existing database. var tmpRel string if m.Metadata == nil { + if pm == nil { + return nil, fmt.Errorf("backup: snapshot has no page map") + } tmpRel, err = st.restoreDB(ctx, app.DBFileName(), pm, hm) } else { tmpRel, res.DBBytes, err = st.restorePortableMetadata( diff --git a/backup/restore_packed_test.go b/backup/restore_packed_test.go index 064e8b6..54868cc 100644 --- a/backup/restore_packed_test.go +++ b/backup/restore_packed_test.go @@ -12,8 +12,8 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" "go.kenn.io/kit/packstore" @@ -64,23 +64,25 @@ func createPackedRestoreFixture(t *testing.T) (*Repo, App, *Manifest, string) { app := packedExtensionApp{App: newTestApp()} dbPath, attachmentsDir, dataDir, _ := seedBackupFixture(t) m, err := Create(ctx, r, app, createOpts(dbPath, attachmentsDir, dataDir, t.TempDir())) - require.NoError(t, err) + Require.NoError(t, err) return r, app, m, attachmentsDir } func TestRestoreWithoutPackedTargetRemainsFullyLoose(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) r, app, m, sourceContent := createPackedRestoreFixture(t) target := filepath.Join(t.TempDir(), "restore") res, err := Restore(context.Background(), r, app, RestoreOptions{TargetDir: target}) - require.NoError(t, err) - assert.Zero(t, res.PackedAttachmentBlobs) - assert.Equal(t, m.Attachments.Blobs, res.LooseAttachmentBlobs) - assert.Empty(t, res.PackFallbacks) - assert.Zero(t, res.AttachmentPacks) + require.NoError(err) + assert.Zero(res.PackedAttachmentBlobs) + assert.Equal(m.Attachments.Blobs, res.LooseAttachmentBlobs) + assert.Empty(res.PackFallbacks) + assert.Zero(res.AttachmentPacks) _, err = os.Stat(filepath.Join(target, "content", "packs")) - require.ErrorIs(t, err, os.ErrNotExist) - assert.Equal(t, snapshotDirHashes(t, sourceContent), snapshotDirHashes(t, filepath.Join(target, "content"))) + require.ErrorIs(err, os.ErrNotExist) + assert.Equal(snapshotDirHashes(t, sourceContent), snapshotDirHashes(t, filepath.Join(target, "content"))) } func TestRestorePackedTargetRejectsInvalidRestoreLeaseBeforePackPublication(t *testing.T) { @@ -116,8 +118,8 @@ func TestRestorePackedTargetRejectsInvalidRestoreLeaseBeforePackPublication(t *t ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() lease, err := maintenanceCoordinator.AcquireMutation(ctx) - require.NoError(t, err, "rejected maintenance lease must be released") - require.NoError(t, lease.Release()) + Require.NoError(t, err, "rejected maintenance lease must be released") + Require.NoError(t, lease.Release()) }}, } for _, tt := range tests { @@ -137,10 +139,10 @@ func TestRestorePackedTargetRejectsInvalidRestoreLeaseBeforePackPublication(t *t _, err := Restore(context.Background(), r, app, RestoreOptions{ TargetDir: target, PackedContent: packed, }) - require.ErrorIs(t, err, tt.wantErr) - assert.False(t, catalogOpened) + Require.ErrorIs(t, err, tt.wantErr) + Assert.False(t, catalogOpened) _, err = os.Stat(filepath.Join(target, "content", "packs")) - require.ErrorIs(t, err, os.ErrNotExist) + Require.ErrorIs(t, err, os.ErrNotExist) if tt.after != nil { tt.after(t) } @@ -149,33 +151,35 @@ func TestRestorePackedTargetRejectsInvalidRestoreLeaseBeforePackPublication(t *t } func TestRestorePackedTargetPublishesThenCommitsBeforeProof(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) r, app, m, _ := createPackedRestoreFixture(t) target := filepath.Join(t.TempDir(), "restore") committed := false packed := testPackedTarget{limits: packstore.DefaultLimits()} packed.open = func(ctx context.Context, db *sql.DB) (packstore.RestoreCatalog, error) { var notes int - require.NoError(t, db.QueryRowContext(ctx, "SELECT COUNT(*) FROM notes").Scan(¬es)) - require.Positive(t, notes) + require.NoError(db.QueryRowContext(ctx, "SELECT COUNT(*) FROM notes").Scan(¬es)) + require.Positive(notes) return restoreCatalogFunc(func(commitCtx context.Context, records []packstore.PackRecord, adoptions []packstore.Adoption) error { - require.NotEmpty(t, records) - require.Len(t, adoptions, int(m.Attachments.Blobs)) + require.NotEmpty(records) + require.Len(adoptions, int(m.Attachments.Blobs)) for _, record := range records { _, err := os.Stat(filepath.Join(target, "content", "packs", record.PackID[:2], record.PackID+packstore.PackExt)) - require.NoError(t, err, "pack must be durable before authority is granted") + require.NoError(err, "pack must be durable before authority is granted") } tx, err := db.BeginTx(commitCtx, nil) - require.NoError(t, err) + require.NoError(err) _, err = tx.ExecContext(commitCtx, "CREATE TABLE restored_pack_authority (packed_blobs INTEGER NOT NULL)") - require.NoError(t, err) + require.NoError(err) _, err = tx.ExecContext(commitCtx, "INSERT INTO restored_pack_authority VALUES (?)", len(adoptions)) - require.NoError(t, err) - require.NoError(t, tx.Commit()) + require.NoError(err) + require.NoError(tx.Commit()) committed = true return nil }), nil } - proofApp := proofObservingApp{App: app, beforeStats: func() { require.True(t, committed) }} + proofApp := proofObservingApp{App: app, beforeStats: func() { require.True(committed) }} sawAttachmentStart := false res, err := Restore(context.Background(), r, proofApp, RestoreOptions{ @@ -184,24 +188,24 @@ func TestRestorePackedTargetPublishesThenCommitsBeforeProof(t *testing.T) { if event.Stage == ProgressStageAttachments && event.Done == 0 && !sawAttachmentStart { sawAttachmentStart = true _, statErr := os.Stat(filepath.Join(target, "content", "packs")) - require.ErrorIs(t, statErr, os.ErrNotExist, + require.ErrorIs(statErr, os.ErrNotExist, "attachment progress must begin before pack preparation publishes files") } }, }) - require.NoError(t, err) - assert.True(t, committed) - assert.True(t, sawAttachmentStart) - assert.Equal(t, m.Attachments.Blobs, res.PackedAttachmentBlobs) - assert.Zero(t, res.LooseAttachmentBlobs) - assert.Positive(t, res.AttachmentPacks) - assert.Equal(t, res.AttachmentBlobs, res.PackedAttachmentBlobs+res.LooseAttachmentBlobs) + require.NoError(err) + assert.True(committed) + assert.True(sawAttachmentStart) + assert.Equal(m.Attachments.Blobs, res.PackedAttachmentBlobs) + assert.Zero(res.LooseAttachmentBlobs) + assert.Positive(res.AttachmentPacks) + assert.Equal(res.AttachmentBlobs, res.PackedAttachmentBlobs+res.LooseAttachmentBlobs) published, err := sql.Open("sqlite3", res.DBPath) - require.NoError(t, err) + require.NoError(err) t.Cleanup(func() { _ = published.Close() }) var publishedPacked int64 - require.NoError(t, published.QueryRow("SELECT packed_blobs FROM restored_pack_authority").Scan(&publishedPacked)) - assert.Equal(t, m.Attachments.Blobs, publishedPacked) + require.NoError(published.QueryRow("SELECT packed_blobs FROM restored_pack_authority").Scan(&publishedPacked)) + assert.Equal(m.Attachments.Blobs, publishedPacked) } type leaseResult struct { @@ -232,24 +236,26 @@ func (c *maintenanceQueueContext) Err() error { func assertMaintenanceBlocked(t *testing.T, acquired <-chan leaseResult, where string) { t.Helper() - assert.Empty(t, acquired, where) + Assert.Empty(t, acquired, where) } func requireMaintenanceLease(t *testing.T, acquired <-chan leaseResult) *packstore.Lease { t.Helper() select { case result := <-acquired: - require.NoError(t, result.err) - require.NotNil(t, result.lease) + Require.NoError(t, result.err) + Require.NotNil(t, result.lease) return result.lease case <-time.After(time.Second): - require.Fail(t, "maintenance did not acquire after restore lease release") + Require.Fail(t, "maintenance did not acquire after restore lease release") return nil } } // Not parallel: this test injects the package-global directory sync hook. func TestRestorePackedTargetHoldsLeaseThroughCatalogProofPublicationAndFinalSync(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) r, app, _, _ := createPackedRestoreFixture(t) target := filepath.Join(t.TempDir(), "restore") coordinator := packstore.NewCoordinator() @@ -291,12 +297,12 @@ func TestRestorePackedTargetHoldsLeaseThroughCatalogProofPublicationAndFinalSync _, err := Restore(context.Background(), r, restoreApp, RestoreOptions{ TargetDir: target, PackedContent: packed, }) - require.NoError(t, err) - assert.True(t, openChecked) - assert.True(t, replaceChecked) - assert.True(t, proofChecked) - assert.True(t, finalSyncChecked) - require.NoError(t, requireMaintenanceLease(t, maintenance).Release()) + require.NoError(err) + assert.True(openChecked) + assert.True(replaceChecked) + assert.True(proofChecked) + assert.True(finalSyncChecked) + require.NoError(requireMaintenanceLease(t, maintenance).Release()) } // Not parallel: this test injects the package-global directory sync hook. @@ -326,12 +332,12 @@ func TestRestorePackedTargetReleasesLeaseAfterPostPublicationFailure(t *testing. _, err := Restore(context.Background(), r, app, RestoreOptions{ TargetDir: target, PackedContent: packed, }) - require.ErrorIs(t, err, publicationErr) + Require.ErrorIs(t, err, publicationErr) acquireCtx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() maintenance, acquireErr := coordinator.AcquireMaintenance(acquireCtx) - require.NoError(t, acquireErr) - require.NoError(t, maintenance.Release()) + Require.NoError(t, acquireErr) + Require.NoError(t, maintenance.Release()) } func TestRestorePackedTargetJoinsReleaseErrorWithPrimaryError(t *testing.T) { @@ -349,7 +355,7 @@ func TestRestorePackedTargetJoinsReleaseErrorWithPrimaryError(t *testing.T) { }, open: func(context.Context, *sql.DB) (packstore.RestoreCatalog, error) { return restoreCatalogFunc(func(context.Context, []packstore.PackRecord, []packstore.Adoption) error { - require.NoError(t, restoreLease.Release()) + Require.NoError(t, restoreLease.Release()) return primaryErr }), nil }, @@ -358,9 +364,9 @@ func TestRestorePackedTargetJoinsReleaseErrorWithPrimaryError(t *testing.T) { _, err := Restore(context.Background(), r, app, RestoreOptions{ TargetDir: target, PackedContent: packed, }) - require.ErrorIs(t, err, primaryErr) - require.ErrorIs(t, err, packstore.ErrLeaseReleased) - assert.ErrorContains(t, err, "releasing packed restore lease") + Require.ErrorIs(t, err, primaryErr) + Require.ErrorIs(t, err, packstore.ErrLeaseReleased) + Assert.ErrorContains(t, err, "releasing packed restore lease") } type proofObservingApp struct { @@ -396,6 +402,7 @@ func (a proofObservingApp) RestoredStats(ctx context.Context, db *sql.DB) (json. } func TestRestorePackedTargetFallsBackDeclinedEntriesLoose(t *testing.T) { + assert := Assert.New(t) r, app, m, sourceContent := createPackedRestoreFixture(t) target := filepath.Join(t.TempDir(), "restore") limits := packstore.DefaultLimits() @@ -409,12 +416,12 @@ func TestRestorePackedTargetFallsBackDeclinedEntriesLoose(t *testing.T) { }} res, err := Restore(context.Background(), r, app, RestoreOptions{TargetDir: target, PackedContent: packed}) - require.NoError(t, err) - assert.Positive(t, res.PackedAttachmentBlobs) - assert.Positive(t, res.LooseAttachmentBlobs) - assert.Equal(t, m.Attachments.Blobs, res.PackedAttachmentBlobs+res.LooseAttachmentBlobs) - assert.Len(t, adopted, int(res.PackedAttachmentBlobs)) - assert.NotEmpty(t, res.PackFallbacks) + Require.NoError(t, err) + assert.Positive(res.PackedAttachmentBlobs) + assert.Positive(res.LooseAttachmentBlobs) + assert.Equal(m.Attachments.Blobs, res.PackedAttachmentBlobs+res.LooseAttachmentBlobs) + assert.Len(adopted, int(res.PackedAttachmentBlobs)) + assert.NotEmpty(res.PackFallbacks) sourceHashes := snapshotDirHashes(t, sourceContent) wantLoose := map[string][32]byte{} for _, fallback := range res.PackFallbacks { @@ -423,7 +430,7 @@ func TestRestorePackedTargetFallsBackDeclinedEntriesLoose(t *testing.T) { wantLoose[rel] = sourceHashes[rel] } } - assert.Equal(t, wantLoose, looseRestoredHashes(t, target, res.PackFallbacks)) + assert.Equal(wantLoose, looseRestoredHashes(t, target, res.PackFallbacks)) } func looseRestoredHashes(t *testing.T, target string, fallbacks []packstore.ImportFallback) map[string][32]byte { @@ -451,7 +458,7 @@ func TestRestorePackedTargetCatalogFailureDoesNotPublishDatabase(t *testing.T) { _, err := Restore(context.Background(), r, app, RestoreOptions{ TargetDir: target, Overwrite: true, PackedContent: packed, }) - require.ErrorContains(t, err, "catalog rejected restore") + Require.ErrorContains(t, err, "catalog rejected restore") checkIntact() } @@ -471,7 +478,7 @@ func stagedCatalogPath(t *testing.T, ctx context.Context, db *sql.DB) string { t.Helper() var sequence int var name, filename string - require.NoError(t, db.QueryRowContext(ctx, "PRAGMA database_list").Scan(&sequence, &name, &filename)) + Require.NoError(t, db.QueryRowContext(ctx, "PRAGMA database_list").Scan(&sequence, &name, &filename)) return filename } @@ -492,12 +499,12 @@ func TestRestorePackedTargetSyncsClosedStagedCatalogBeforeProofAndPublication(t }} originalSync := syncStagedCatalogFile syncStagedCatalogFile = func(file *os.File) error { - require.Error(t, catalogDB.PingContext(context.Background()), "SQLite must be closed before file sync") + Require.Error(t, catalogDB.PingContext(context.Background()), "SQLite must be closed before file sync") openedInfo, err := file.Stat() - require.NoError(t, err) + Require.NoError(t, err) stagedInfo, err := os.Stat(stagedPath) - require.NoError(t, err) - require.True(t, os.SameFile(openedInfo, stagedInfo), "sync handle must name the staged database") + Require.NoError(t, err) + Require.True(t, os.SameFile(openedInfo, stagedInfo), "sync handle must name the staged database") syncedFile = file synced = true return originalSync(file) @@ -509,16 +516,16 @@ func TestRestorePackedTargetSyncsClosedStagedCatalogBeforeProofAndPublication(t return err } proofApp := proofObservingApp{App: app, beforeStats: func() { - require.True(t, synced, "staged database must be synced before proof") - require.True(t, closed, "synced staged database handle must be closed before proof") + Require.True(t, synced, "staged database must be synced before proof") + Require.True(t, closed, "synced staged database handle must be closed before proof") _, err := syncedFile.Stat() - require.Error(t, err) + Require.Error(t, err) }} _, err := Restore(context.Background(), r, proofApp, RestoreOptions{TargetDir: target, PackedContent: packed}) - require.NoError(t, err) - assert.True(t, synced) - assert.True(t, closed) + Require.NoError(t, err) + Assert.True(t, synced) + Assert.True(t, closed) } // Not parallel: this test injects package-global staged catalog file hooks. @@ -537,13 +544,15 @@ func TestRestorePackedTargetCatalogReplacementFailureDoesNotSyncStagedCatalog(t }} _, err := Restore(context.Background(), r, app, RestoreOptions{TargetDir: target, Overwrite: true, PackedContent: packed}) - require.ErrorIs(t, err, replaceErr) - assert.False(t, syncCalled) + Require.ErrorIs(t, err, replaceErr) + Assert.False(t, syncCalled) checkIntact() } // Not parallel: this test injects package-global staged catalog file hooks. func TestRestorePackedTargetStagedCatalogSyncFailurePreventsProofAndPublication(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) installStagedCatalogFileHooks(t) r, app, _, _ := createPackedRestoreFixture(t) target, checkIntact := seedLiveOverwriteTarget(t) @@ -567,11 +576,11 @@ func TestRestorePackedTargetStagedCatalogSyncFailurePreventsProofAndPublication( }} _, err := Restore(context.Background(), r, proofApp, RestoreOptions{TargetDir: target, Overwrite: true, PackedContent: packed}) - require.ErrorIs(t, err, syncErr) - assert.False(t, proofCalled) - assert.True(t, closed) + require.ErrorIs(err, syncErr) + assert.False(proofCalled) + assert.True(closed) _, statErr := syncedFile.Stat() - require.Error(t, statErr) + require.Error(statErr) checkIntact() } @@ -591,8 +600,8 @@ func TestRestorePackedTargetJoinsStagedCatalogSyncAndCloseFailures(t *testing.T) }} _, err := Restore(context.Background(), r, app, RestoreOptions{TargetDir: target, Overwrite: true, PackedContent: packed}) - require.ErrorIs(t, err, syncErr) - require.ErrorIs(t, err, closeErr) + Require.ErrorIs(t, err, syncErr) + Require.ErrorIs(t, err, closeErr) checkIntact() } @@ -605,7 +614,7 @@ func TestRestorePackedTargetStagedCatalogCloseFailurePreventsProofAndPublication var closedFile *os.File closeStagedCatalogFile = func(file *os.File) error { closedFile = file - require.NoError(t, file.Close()) + Require.NoError(t, file.Close()) return closeErr } proofCalled := false @@ -615,10 +624,10 @@ func TestRestorePackedTargetStagedCatalogCloseFailurePreventsProofAndPublication }} _, err := Restore(context.Background(), r, proofApp, RestoreOptions{TargetDir: target, Overwrite: true, PackedContent: packed}) - require.ErrorIs(t, err, closeErr) - assert.False(t, proofCalled) + Require.ErrorIs(t, err, closeErr) + Assert.False(t, proofCalled) _, statErr := closedFile.Stat() - require.Error(t, statErr) + Require.Error(t, statErr) checkIntact() } @@ -630,25 +639,27 @@ func TestRestorePackedTargetRejectsNonRegularStagedCatalogBeforeOpen(t *testing. }{ {name: "symlink", plant: func(t *testing.T, staged string) { victim := filepath.Join(t.TempDir(), "victim.db") - require.NoError(t, os.WriteFile(victim, []byte("victim"), 0o600)) - require.NoError(t, os.Remove(staged)) + Require.NoError(t, os.WriteFile(victim, []byte("victim"), 0o600)) + Require.NoError(t, os.Remove(staged)) if err := os.Symlink(victim, staged); err != nil { t.Skip("symlinks not supported on this platform") } }}, {name: "directory", plant: func(t *testing.T, staged string) { - require.NoError(t, os.Remove(staged)) - require.NoError(t, os.Mkdir(staged, 0o700)) + Require.NoError(t, os.Remove(staged)) + Require.NoError(t, os.Mkdir(staged, 0o700)) }}, } { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) installStagedCatalogFileHooks(t) target := t.TempDir() staged := filepath.Join(target, "staged.db") - require.NoError(t, os.WriteFile(staged, []byte("staged"), 0o600)) + require.NoError(os.WriteFile(staged, []byte("staged"), 0o600)) root, err := os.OpenRoot(target) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, root.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(root.Close()) }) openCalled := false syncCalled := false openStagedCatalogFile = func(*os.Root, string) (*os.File, error) { @@ -662,15 +673,17 @@ func TestRestorePackedTargetRejectsNonRegularStagedCatalogBeforeOpen(t *testing. tt.plant(t, staged) state := restoreState{root: root, dbRead: "staged.db"} err = state.syncAndCloseStagedCatalog() - require.ErrorContains(t, err, "regular file") - assert.False(t, openCalled) - assert.False(t, syncCalled) + require.ErrorContains(err, "regular file") + assert.False(openCalled) + assert.False(syncCalled) }) } } // Not parallel: this test injects package-global staged catalog file hooks. func TestRestorePackedTargetRejectsOpenedStagedCatalogIdentityMismatchAndJoinsCloseFailure(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) installStagedCatalogFileHooks(t) r, app, _, _ := createPackedRestoreFixture(t) target, checkIntact := seedLiveOverwriteTarget(t) @@ -693,15 +706,17 @@ func TestRestorePackedTargetRejectsOpenedStagedCatalogIdentityMismatchAndJoinsCl }} _, err := Restore(context.Background(), r, app, RestoreOptions{TargetDir: target, Overwrite: true, PackedContent: packed}) - require.ErrorContains(t, err, "identity") - require.ErrorIs(t, err, closeErr) - assert.False(t, syncCalled) - assert.True(t, closed) + require.ErrorContains(err, "identity") + require.ErrorIs(err, closeErr) + assert.False(syncCalled) + assert.True(closed) checkIntact() } // Not parallel: this test injects package-global staged catalog file hooks. func TestRestorePackedTargetRejectsStagedCatalogReplacedDuringSync(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) installStagedCatalogFileHooks(t) r, app, _, _ := createPackedRestoreFixture(t) target, checkIntact := seedLiveOverwriteTarget(t) @@ -720,10 +735,10 @@ func TestRestorePackedTargetRejectsStagedCatalogReplacedDuringSync(t *testing.T) if err := originalSync(file); err != nil { return err } - require.NoError(t, root.Rename(stagedName, stagedName+".aside")) + require.NoError(root.Rename(stagedName, stagedName+".aside")) replacement, err := root.OpenFile(stagedName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600) - require.NoError(t, err) - require.NoError(t, replacement.Close()) + require.NoError(err) + require.NoError(replacement.Close()) return nil } originalClose := closeStagedCatalogFile @@ -739,11 +754,11 @@ func TestRestorePackedTargetRejectsStagedCatalogReplacedDuringSync(t *testing.T) }} _, err := Restore(context.Background(), r, proofApp, RestoreOptions{TargetDir: target, Overwrite: true, PackedContent: packed}) - require.ErrorContains(t, err, "identity") - assert.False(t, proofCalled) - assert.True(t, closed) + require.ErrorContains(err, "identity") + assert.False(proofCalled) + assert.True(closed) _, statErr := syncedFile.Stat() - require.Error(t, statErr) + require.Error(statErr) checkIntact() } @@ -761,8 +776,8 @@ func TestRestorePackedTargetProofFailureKeepsVisibleDatabase(t *testing.T) { _, err := Restore(context.Background(), r, proofObservingApp{App: app, badStats: true}, RestoreOptions{ TargetDir: target, Overwrite: true, PackedContent: packed, }) - require.ErrorContains(t, err, "do not match manifest stats") - assert.True(t, committed) + Require.ErrorContains(t, err, "do not match manifest stats") + Assert.True(t, committed) checkIntact() } @@ -772,8 +787,8 @@ func TestRestorePackedTargetOverwriteKeepsOldDatabaseUntilPublish(t *testing.T) packed := testPackedTarget{limits: packstore.DefaultLimits(), open: func(context.Context, *sql.DB) (packstore.RestoreCatalog, error) { return restoreCatalogFunc(func(context.Context, []packstore.PackRecord, []packstore.Adoption) error { got, err := os.ReadFile(filepath.Join(target, "app.db")) - require.NoError(t, err) - require.Equal(t, []byte("live database bytes"), got) + Require.NoError(t, err) + Require.Equal(t, []byte("live database bytes"), got) return nil }), nil }} @@ -781,26 +796,27 @@ func TestRestorePackedTargetOverwriteKeepsOldDatabaseUntilPublish(t *testing.T) _, err := Restore(context.Background(), r, app, RestoreOptions{ TargetDir: target, Overwrite: true, PackedContent: packed, }) - require.NoError(t, err) + Require.NoError(t, err) got, err := os.ReadFile(filepath.Join(target, "app.db")) - require.NoError(t, err) - assert.NotEqual(t, []byte("live database bytes"), got) + Require.NoError(t, err) + Assert.NotEqual(t, []byte("live database bytes"), got) } func TestRestorePackedTargetCorruptSelectedSourceKeepsVisibleDatabase(t *testing.T) { + require := Require.New(t) r, app, m, _ := createPackedRestoreFixture(t) known, err := r.LoadBlobIndex() - require.NoError(t, err) + require.NoError(err) refs, _, err := LoadListRefs(r, known, m.Attachments.Lists, nil, packstore.PackExt) - require.NoError(t, err) + require.NoError(err) id, err := pack.ParseBlobID(refs[0].Hash) - require.NoError(t, err) + require.NoError(err) ie := known[id] sourcePack := r.packPath(ie.PackID, packstore.PackExt) data, err := os.ReadFile(sourcePack) - require.NoError(t, err) + require.NoError(err) data[ie.Offset+ie.StoredLen/2] ^= 1 - require.NoError(t, os.WriteFile(sourcePack, data, 0o600)) + require.NoError(os.WriteFile(sourcePack, data, 0o600)) target, checkIntact := seedLiveOverwriteTarget(t) packed := testPackedTarget{limits: packstore.DefaultLimits(), open: func(context.Context, *sql.DB) (packstore.RestoreCatalog, error) { return restoreCatalogFunc(func(context.Context, []packstore.PackRecord, []packstore.Adoption) error { return nil }), nil @@ -809,64 +825,67 @@ func TestRestorePackedTargetCorruptSelectedSourceKeepsVisibleDatabase(t *testing _, err = Restore(context.Background(), r, app, RestoreOptions{ TargetDir: target, Overwrite: true, PackedContent: packed, }) - require.Error(t, err) + require.Error(err) checkIntact() } func TestRestorePackedTargetIncompatibleExtensionRestoresFullyLooseAndClearsAuthority(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) ctx := context.Background() r := initTestRepo(t) app := newTestApp() dbPath, sourceContent, dataDir, _ := seedBackupFixture(t) m, err := Create(ctx, r, app, createOpts(dbPath, sourceContent, dataDir, t.TempDir())) - require.NoError(t, err) + require.NoError(err) target := filepath.Join(t.TempDir(), "restore") commits := 0 packed := testPackedTarget{limits: packstore.DefaultLimits(), open: func(context.Context, *sql.DB) (packstore.RestoreCatalog, error) { return restoreCatalogFunc(func(_ context.Context, records []packstore.PackRecord, adoptions []packstore.Adoption) error { commits++ - assert.Empty(t, records) - assert.Empty(t, adoptions) + assert.Empty(records) + assert.Empty(adoptions) return nil }), nil }} res, err := Restore(ctx, r, app, RestoreOptions{TargetDir: target, PackedContent: packed}) - require.NoError(t, err) - assert.Equal(t, 1, commits) - assert.Zero(t, res.PackedAttachmentBlobs) - assert.Equal(t, m.Attachments.Blobs, res.LooseAttachmentBlobs) - assert.Equal(t, m.Attachments.Blobs, res.AttachmentBlobs) - assert.Equal(t, m.Attachments.BlobBytes, res.AttachmentBytes) - assert.NotEmpty(t, res.PackFallbacks) + require.NoError(err) + assert.Equal(1, commits) + assert.Zero(res.PackedAttachmentBlobs) + assert.Equal(m.Attachments.Blobs, res.LooseAttachmentBlobs) + assert.Equal(m.Attachments.Blobs, res.AttachmentBlobs) + assert.Equal(m.Attachments.BlobBytes, res.AttachmentBytes) + assert.NotEmpty(res.PackFallbacks) for _, fallback := range res.PackFallbacks { - assert.Equal(t, packstore.FallbackPackEncoding, fallback.Reason) + assert.Equal(packstore.FallbackPackEncoding, fallback.Reason) } _, err = os.Stat(filepath.Join(target, "content", "packs")) - require.ErrorIs(t, err, os.ErrNotExist) - assert.Equal(t, snapshotDirHashes(t, sourceContent), snapshotDirHashes(t, filepath.Join(target, "content"))) + require.ErrorIs(err, os.ErrNotExist) + assert.Equal(snapshotDirHashes(t, sourceContent), snapshotDirHashes(t, filepath.Join(target, "content"))) } func TestRestorePackedTargetUnsupportedEncodingStillRequiresLooseVerification(t *testing.T) { + require := Require.New(t) r, app, m, sourceContent := createPackedRestoreFixture(t) known, err := r.LoadBlobIndex() - require.NoError(t, err) + require.NoError(err) refs, _, err := LoadListRefs(r, known, m.Attachments.Lists, nil, packstore.PackExt) - require.NoError(t, err) + require.NoError(err) attachmentIDs := make(map[pack.BlobID]struct{}, len(refs)) appender := NewPackAppender(r, map[pack.BlobID]IndexEntry{}, pack.DefaultZstdLevel, nil, packstore.PackExt) for _, ref := range refs { id, parseErr := pack.ParseBlobID(ref.Hash) - require.NoError(t, parseErr) + require.NoError(parseErr) attachmentIDs[id] = struct{}{} raw, readErr := os.ReadFile(filepath.Join(sourceContent, ref.Hash[:2], ref.Hash)) - require.NoError(t, readErr) + require.NoError(readErr) _, _, addErr := appender.Add(raw) - require.NoError(t, addErr) + require.NoError(addErr) } packIDs, replacementEntries, err := appender.Finish() - require.NoError(t, err) - require.Len(t, packIDs, 1) + require.NoError(err) + require.Len(packIDs, 1) combined := append([]IndexEntry(nil), replacementEntries...) for id, entry := range known { if _, replaced := attachmentIDs[id]; !replaced { @@ -874,18 +893,18 @@ func TestRestorePackedTargetUnsupportedEncodingStillRequiresLooseVerification(t } } indexFiles, err := os.ReadDir(r.Path("indexes")) - require.NoError(t, err) + require.NoError(err) for _, file := range indexFiles { - require.NoError(t, os.Remove(r.Path("indexes", file.Name()))) + require.NoError(os.Remove(r.Path("indexes", file.Name()))) } _, err = r.WriteIndex(combined) - require.NoError(t, err) + require.NoError(err) sourcePack := r.packPath(packIDs[0], packstore.PackExt) data, err := os.ReadFile(sourcePack) - require.NoError(t, err) - require.GreaterOrEqual(t, len(data), 5) + require.NoError(err) + require.GreaterOrEqual(len(data), 5) data[4] = 0x7f // recognizable pack magic with an unsupported version - require.NoError(t, os.WriteFile(sourcePack, data, 0o600)) + require.NoError(os.WriteFile(sourcePack, data, 0o600)) target, checkIntact := seedLiveOverwriteTarget(t) packed := testPackedTarget{limits: packstore.DefaultLimits(), open: func(context.Context, *sql.DB) (packstore.RestoreCatalog, error) { return nil, errors.New("catalog must not open when loose verification fails") @@ -894,12 +913,14 @@ func TestRestorePackedTargetUnsupportedEncodingStillRequiresLooseVerification(t _, err = Restore(context.Background(), r, app, RestoreOptions{ TargetDir: target, Overwrite: true, PackedContent: packed, }) - require.ErrorContains(t, err, "opening pack") - assert.NotContains(t, err.Error(), "preparing packed attachment restore") + require.ErrorContains(err, "opening pack") + Assert.NotContains(t, err.Error(), "preparing packed attachment restore") checkIntact() } func TestRestorePackedTargetZeroBlobLimitRestoresFullyLoose(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) r, app, m, sourceContent := createPackedRestoreFixture(t) target := filepath.Join(t.TempDir(), "restore") limits := packstore.DefaultLimits() @@ -907,21 +928,21 @@ func TestRestorePackedTargetZeroBlobLimitRestoresFullyLoose(t *testing.T) { committed := false packed := testPackedTarget{limits: limits, open: func(context.Context, *sql.DB) (packstore.RestoreCatalog, error) { return restoreCatalogFunc(func(_ context.Context, records []packstore.PackRecord, adoptions []packstore.Adoption) error { - assert.Empty(t, records) - assert.Empty(t, adoptions) + assert.Empty(records) + assert.Empty(adoptions) committed = true return nil }), nil }} res, err := Restore(context.Background(), r, app, RestoreOptions{TargetDir: target, PackedContent: packed}) - require.NoError(t, err) - assert.True(t, committed) - assert.Zero(t, res.PackedAttachmentBlobs) - assert.Equal(t, m.Attachments.Blobs, res.LooseAttachmentBlobs) - assert.Equal(t, snapshotDirHashes(t, sourceContent), snapshotDirHashes(t, filepath.Join(target, "content"))) + require.NoError(err) + assert.True(committed) + assert.Zero(res.PackedAttachmentBlobs) + assert.Equal(m.Attachments.Blobs, res.LooseAttachmentBlobs) + assert.Equal(snapshotDirHashes(t, sourceContent), snapshotDirHashes(t, filepath.Join(target, "content"))) _, err = os.Stat(filepath.Join(target, "content", "packs")) - require.ErrorIs(t, err, os.ErrNotExist) + require.ErrorIs(err, os.ErrNotExist) } func TestRestorePackedTargetRejectsNegativeBlobLimitBeforePublishingContent(t *testing.T) { @@ -934,19 +955,21 @@ func TestRestorePackedTargetRejectsNegativeBlobLimitBeforePublishingContent(t *t }} _, err := Restore(context.Background(), r, app, RestoreOptions{TargetDir: target, PackedContent: packed}) - require.ErrorContains(t, err, "invalid limits") + Require.ErrorContains(t, err, "invalid limits") _, err = os.Stat(filepath.Join(target, "content", "packs")) - require.ErrorIs(t, err, os.ErrNotExist) + Require.ErrorIs(t, err, os.ErrNotExist) } func TestRestorePackedTargetRejectsPortablePackSubtreeAliasesBeforePublication(t *testing.T) { for _, reserved := range []string{"packs", "PACKS", `PaCkS\shard`} { t.Run(strings.ReplaceAll(reserved, `\`, "-"), func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) r, app, m, _ := createPackedRestoreFixture(t) known, err := r.LoadBlobIndex() - require.NoError(t, err) + require.NoError(err) refs, _, err := LoadListRefs(r, known, m.Attachments.Lists, nil, packstore.PackExt) - require.NoError(t, err) + require.NoError(err) var declined ContentRef for _, ref := range refs { if ref.Size > declined.Size { @@ -954,16 +977,16 @@ func TestRestorePackedTargetRejectsPortablePackSubtreeAliasesBeforePublication(t } } id, err := pack.ParseBlobID(declined.Hash) - require.NoError(t, err) + require.NoError(err) packID := known[id].PackID sourcePack := r.packPath(packID, packstore.PackExt) sourceBytes, err := os.ReadFile(sourcePack) - require.NoError(t, err) + require.NoError(err) target, checkIntact := seedLiveOverwriteTarget(t) finalPack := filepath.Join(target, "content", "packs", packID[:2], packID+packstore.PackExt) - require.NoError(t, os.MkdirAll(filepath.Dir(finalPack), 0o700)) - require.NoError(t, os.WriteFile(finalPack, sourceBytes, 0o600)) + require.NoError(os.MkdirAll(filepath.Dir(finalPack), 0o700)) + require.NoError(os.WriteFile(finalPack, sourceBytes, 0o600)) catalogCalled := false limits := packstore.DefaultLimits() limits.BlobBytes = 25 @@ -982,22 +1005,24 @@ func TestRestorePackedTargetRejectsPortablePackSubtreeAliasesBeforePublication(t _, err = Restore(context.Background(), r, restoreApp, RestoreOptions{ TargetDir: target, Overwrite: true, PackedContent: packed, }) - require.ErrorContains(t, err, "reserved packed-content subtree") - assert.False(t, catalogCalled) + require.ErrorContains(err, "reserved packed-content subtree") + assert.False(catalogCalled) got, err := os.ReadFile(finalPack) - require.NoError(t, err) - assert.Equal(t, sourceBytes, got) + require.NoError(err) + assert.Equal(sourceBytes, got) checkIntact() }) } } func TestRestoreWithoutPackedTargetAllowsHistoricalPackNamedPath(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) r, app, m, _ := createPackedRestoreFixture(t) known, err := r.LoadBlobIndex() - require.NoError(t, err) + require.NoError(err) refs, _, err := LoadListRefs(r, known, m.Attachments.Lists, nil, packstore.PackExt) - require.NoError(t, err) + require.NoError(err) ref := refs[0] rel := filepath.Join("packs", "historical", ref.Hash) restoreApp := contentPathOverrideApp{App: app, override: func(paths map[string][]string) { @@ -1006,22 +1031,23 @@ func TestRestoreWithoutPackedTargetAllowsHistoricalPackNamedPath(t *testing.T) { target := filepath.Join(t.TempDir(), "restore") res, err := Restore(context.Background(), r, restoreApp, RestoreOptions{TargetDir: target}) - require.NoError(t, err) - assert.Equal(t, m.Attachments.Blobs, res.LooseAttachmentBlobs) + require.NoError(err) + assert.Equal(m.Attachments.Blobs, res.LooseAttachmentBlobs) got, err := os.ReadFile(filepath.Join(target, "content", rel)) - require.NoError(t, err) - assert.Equal(t, ref.Size, int64(len(got))) + require.NoError(err) + assert.Equal(ref.Size, int64(len(got))) } func TestRestorePackedTargetCatalogWriteFailureCleansOnlyStagedSidecars(t *testing.T) { + require := Require.New(t) r, app, _, _ := createPackedRestoreFixture(t) target, checkIntact := seedLiveOverwriteTarget(t) packed := testPackedTarget{limits: packstore.DefaultLimits(), open: func(_ context.Context, db *sql.DB) (packstore.RestoreCatalog, error) { return restoreCatalogFunc(func(ctx context.Context, _ []packstore.PackRecord, _ []packstore.Adoption) error { var mode string - require.NoError(t, db.QueryRowContext(ctx, "PRAGMA journal_mode=PERSIST").Scan(&mode)) + require.NoError(db.QueryRowContext(ctx, "PRAGMA journal_mode=PERSIST").Scan(&mode)) _, err := db.ExecContext(ctx, "CREATE TABLE staged_sidecar_probe (id INTEGER)") - require.NoError(t, err) + require.NoError(err) return errors.New("catalog failed after write") }), nil }} @@ -1029,33 +1055,34 @@ func TestRestorePackedTargetCatalogWriteFailureCleansOnlyStagedSidecars(t *testi _, err := Restore(context.Background(), r, app, RestoreOptions{ TargetDir: target, Overwrite: true, PackedContent: packed, }) - require.ErrorContains(t, err, "catalog failed after write") + require.ErrorContains(err, "catalog failed after write") checkIntact() entries, err := os.ReadDir(target) - require.NoError(t, err) + require.NoError(err) for _, entry := range entries { - assert.False(t, strings.HasPrefix(entry.Name(), "app.db.restore-"), entry.Name()) + Assert.False(t, strings.HasPrefix(entry.Name(), "app.db.restore-"), entry.Name()) } _, err = os.Stat(filepath.Join(target, "app.db-wal")) - require.NoError(t, err, "the visible database sidecar must not be cleaned") + require.NoError(err, "the visible database sidecar must not be cleaned") } // Not parallel: this test injects the package-global directory sync hook. func TestRestorePackedTargetLooseDurabilityFailurePreventsCatalogAuthority(t *testing.T) { + require := Require.New(t) r, app, m, _ := createPackedRestoreFixture(t) known, err := r.LoadBlobIndex() - require.NoError(t, err) + require.NoError(err) refs, _, err := LoadListRefs(r, known, m.Attachments.Lists, nil, packstore.PackExt) - require.NoError(t, err) + require.NoError(err) id, err := pack.ParseBlobID(refs[0].Hash) - require.NoError(t, err) + require.NoError(err) packID := known[id].PackID sourceBytes, err := os.ReadFile(r.packPath(packID, packstore.PackExt)) - require.NoError(t, err) + require.NoError(err) target, checkIntact := seedLiveOverwriteTarget(t) finalPack := filepath.Join(target, "content", "packs", packID[:2], packID+packstore.PackExt) - require.NoError(t, os.MkdirAll(filepath.Dir(finalPack), 0o700)) - require.NoError(t, os.WriteFile(finalPack, sourceBytes, 0o600)) + require.NoError(os.MkdirAll(filepath.Dir(finalPack), 0o700)) + require.NoError(os.WriteFile(finalPack, sourceBytes, 0o600)) catalogCalled := false limits := packstore.DefaultLimits() limits.BlobBytes = 25 @@ -1075,7 +1102,7 @@ func TestRestorePackedTargetLooseDurabilityFailurePreventsCatalogAuthority(t *te _, err = Restore(context.Background(), r, app, RestoreOptions{ TargetDir: target, Overwrite: true, PackedContent: packed, }) - require.ErrorContains(t, err, "injected content durability failure") - assert.False(t, catalogCalled) + require.ErrorContains(err, "injected content durability failure") + Assert.False(t, catalogCalled) checkIntact() } diff --git a/backup/stream_test.go b/backup/stream_test.go index 0d8a911..6f40170 100644 --- a/backup/stream_test.go +++ b/backup/stream_test.go @@ -6,33 +6,35 @@ import ( "maps" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) func TestRepoOpenBlobStreamsVerifiedContent(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) r := initTestRepo(t) known := map[pack.BlobID]IndexEntry{} appender := NewPackAppender(r, known, pack.DefaultZstdLevel, nil, testPackExt) content := []byte("repository streaming content") id, _, err := appender.Add(content) - require.NoError(t, err) + require.NoError(err) _, _, err = appender.Finish() - require.NoError(t, err) + require.NoError(err) stream, err := r.OpenBlob(context.Background(), known, id, nil, testPackExt) - require.NoError(t, err) - assert.Equal(t, int64(len(content)), stream.Size()) + require.NoError(err) + assert.Equal(int64(len(content)), stream.Size()) prefix := make([]byte, 4) _, err = io.ReadFull(stream, prefix) - require.NoError(t, err) - assert.False(t, stream.Verified()) + require.NoError(err) + assert.False(stream.Verified()) rest, err := io.ReadAll(stream) - require.NoError(t, err) - assert.Equal(t, content, append(prefix, rest...)) - assert.True(t, stream.Verified()) - require.NoError(t, stream.Close()) + require.NoError(err) + assert.Equal(content, append(prefix, rest...)) + assert.True(stream.Verified()) + require.NoError(stream.Close()) } func TestRepoOpenBlobRejectsIndexMismatchBeforeStreaming(t *testing.T) { @@ -40,9 +42,9 @@ func TestRepoOpenBlobRejectsIndexMismatchBeforeStreaming(t *testing.T) { known := map[pack.BlobID]IndexEntry{} appender := NewPackAppender(r, known, pack.DefaultZstdLevel, nil, testPackExt) id, _, err := appender.Add([]byte("indexed content")) - require.NoError(t, err) + Require.NoError(t, err) _, _, err = appender.Finish() - require.NoError(t, err) + Require.NoError(t, err) for _, tc := range []struct { name string forge func(*IndexEntry) @@ -58,25 +60,26 @@ func TestRepoOpenBlobRejectsIndexMismatchBeforeStreaming(t *testing.T) { forgedKnown[id] = forged stream, err := r.OpenBlob(context.Background(), forgedKnown, id, nil, testPackExt) - require.ErrorContains(t, err, "index metadata disagrees") - assert.Nil(t, stream) + Require.ErrorContains(t, err, "index metadata disagrees") + Assert.Nil(t, stream) }) } } func TestRepoOpenBlobEarlyCloseIsUnverified(t *testing.T) { + require := Require.New(t) r := initTestRepo(t) known := map[pack.BlobID]IndexEntry{} appender := NewPackAppender(r, known, pack.DefaultZstdLevel, nil, testPackExt) id, _, err := appender.Add([]byte("early close")) - require.NoError(t, err) + require.NoError(err) _, _, err = appender.Finish() - require.NoError(t, err) + require.NoError(err) stream, err := r.OpenBlob(context.Background(), known, id, nil, testPackExt) - require.NoError(t, err) + require.NoError(err) _, err = stream.Read(make([]byte, 1)) - require.NoError(t, err) - require.ErrorIs(t, stream.Close(), pack.ErrVerificationIncomplete) - require.ErrorIs(t, stream.Close(), pack.ErrVerificationIncomplete) + require.NoError(err) + require.ErrorIs(stream.Close(), pack.ErrVerificationIncomplete) + require.ErrorIs(stream.Close(), pack.ErrVerificationIncomplete) } diff --git a/backup/streaming_lifecycle_test.go b/backup/streaming_lifecycle_test.go index d6c4838..777d59b 100644 --- a/backup/streaming_lifecycle_test.go +++ b/backup/streaming_lifecycle_test.go @@ -11,8 +11,8 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/packstore" "go.kenn.io/kit/packstore/packstoretest" @@ -40,10 +40,10 @@ func addLifecycleCandidate(t *testing.T, catalog *packstoretest.MemoryCatalog, h func readLifecycleBlob(t *testing.T, store *packstore.Store, hash packstore.Hash) []byte { t.Helper() reader, size, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) + Require.NoError(t, err) content, readErr := io.ReadAll(reader) - require.NoError(t, errors.Join(readErr, reader.Close())) - assert.Equal(t, size, int64(len(content))) + Require.NoError(t, errors.Join(readErr, reader.Close())) + Assert.Equal(t, size, int64(len(content))) return content } @@ -51,7 +51,8 @@ func readLifecycleBlob(t *testing.T, store *packstore.Store, hash packstore.Hash // focused package tests intentionally split apart: mixed reads, sparse repack, // unpack, backup capture and verification, then loose and pack-native restore. func TestStreamingLifecycleGate(t *testing.T) { - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) ctx := context.Background() dbPath, contentDir, dataDir, _ := seedBackupFixture(t) layout, err := packstore.NewLayout(contentDir, packstore.LayoutOptions{ @@ -100,9 +101,9 @@ func TestStreamingLifecycleGate(t *testing.T) { packed, err := maintainer.Pack(ctx, packstore.PackOptions{}) require.NoError(err) - assert.Equal(t, 5, packed.BlobsPacked) + assert.Equal(5, packed.BlobsPacked) for hash, want := range expected { - assert.Equal(t, want, readLifecycleBlob(t, maintainer.Store(), hash)) + assert.Equal(want, readLifecycleBlob(t, maintainer.Store(), hash)) } mixed, err := loose.WriteBytes(ctx, []byte("mixed loose member"), packstore.WriteOptions{ @@ -110,7 +111,7 @@ func TestStreamingLifecycleGate(t *testing.T) { }) require.NoError(err) addLifecycleCandidate(t, catalog, mixed.Hash, mixed.Path, mixed.Size) - assert.Equal(t, []byte("mixed loose member"), readLifecycleBlob(t, maintainer.Store(), mixed.Hash)) + assert.Equal([]byte("mixed loose member"), readLifecycleBlob(t, maintainer.Store(), mixed.Hash)) for hash := range catalog.Snapshot().Members { if _, keep := expected[hash]; !keep && hash != mixed.Hash { @@ -122,14 +123,14 @@ func TestStreamingLifecycleGate(t *testing.T) { Selection: packstore.RepackSelection{MinAge: time.Hour, MinDeadStored: 1}, }) require.NoError(err) - assert.Equal(t, 2, repacked.BlobsRepacked) + assert.Equal(2, repacked.BlobsRepacked) unpacked, err := maintainer.Unpack(ctx) require.NoError(err) - assert.Equal(t, 2, unpacked.BlobsRestored) - assert.Empty(t, catalog.Snapshot().Entries) + assert.Equal(2, unpacked.BlobsRestored) + assert.Empty(catalog.Snapshot().Entries) for hash, want := range expected { - assert.Equal(t, want, readLifecycleBlob(t, maintainer.Store(), hash)) + assert.Equal(want, readLifecycleBlob(t, maintainer.Store(), hash)) } repo := initTestRepo(t) @@ -142,7 +143,7 @@ func TestStreamingLifecycleGate(t *testing.T) { require.NoError(err) verified, err := Verify(ctx, repo, app, VerifyOptions{SnapshotID: manifest.SnapshotID}) require.NoError(err) - assert.Empty(t, verified.Problems) + assert.Empty(verified.Problems) looseTarget := filepath.Join(t.TempDir(), "loose-restore") _, err = Restore(ctx, repo, app, RestoreOptions{TargetDir: looseTarget}) @@ -151,7 +152,7 @@ func TestStreamingLifecycleGate(t *testing.T) { path := filepath.Join(looseTarget, "content", hash.String()[:2], hash.String()) got, readErr := os.ReadFile(path) require.NoError(readErr) - assert.Equal(t, want, got) + assert.Equal(want, got) } packedTarget := filepath.Join(t.TempDir(), "packed-restore") @@ -172,7 +173,7 @@ func TestStreamingLifecycleGate(t *testing.T) { } result, err := Restore(ctx, repo, app, RestoreOptions{TargetDir: packedTarget, PackedContent: target}) require.NoError(err) - assert.Equal(t, manifest.Attachments.Blobs, result.PackedAttachmentBlobs) + assert.Equal(manifest.Attachments.Blobs, result.PackedAttachmentBlobs) packedLayout, err := packstore.NewLayout(filepath.Join(packedTarget, "content"), packstore.LayoutOptions{ Staging: packstore.StagingSameDirectory, }) @@ -181,9 +182,9 @@ func TestStreamingLifecycleGate(t *testing.T) { require.NoError(err) t.Cleanup(func() { require.NoError(restoredStore.Close()) }) for hash, want := range expected { - assert.Equal(t, want, readLifecycleBlob(t, restoredStore, hash)) + assert.Equal(want, readLifecycleBlob(t, restoredStore, hash)) } verified, err = Verify(ctx, repo, app, VerifyOptions{All: true}) require.NoError(err) - assert.Empty(t, verified.Problems) + assert.Empty(verified.Problems) } diff --git a/backup/verify.go b/backup/verify.go index 549c195..db89c3f 100644 --- a/backup/verify.go +++ b/backup/verify.go @@ -681,6 +681,9 @@ func (s *verifyState) queuePageRunChecks(m *Manifest, pm *PageMap, hm *PageHashM // rather than recording them, so drain workers can call it without holding // the state lock. func pageRunProblems(id pack.BlobID, raw []byte, c pageRunCheck) []string { + if raw == nil { + raw = []byte{} + } length := uint64(c.run.PageCount) * uint64(c.pageSize) blobLen := uint64(len(raw)) if c.run.BlobOffset > blobLen || length > blobLen-c.run.BlobOffset { diff --git a/daemon/process_identity_linux_test.go b/daemon/process_identity_linux_test.go index 8f371fe..9e1443e 100644 --- a/daemon/process_identity_linux_test.go +++ b/daemon/process_identity_linux_test.go @@ -8,49 +8,51 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) func TestReadLinuxProcessIdentityUsesInspectableTargetNamespace(t *testing.T) { + require := Require.New(t) proc := t.TempDir() - require.NoError(t, os.MkdirAll(filepath.Join(proc, "sys/kernel/random"), 0o755)) - require.NoError(t, os.MkdirAll(filepath.Join(proc, "42/ns"), 0o755)) - require.NoError(t, os.WriteFile( + require.NoError(os.MkdirAll(filepath.Join(proc, "sys/kernel/random"), 0o755)) + require.NoError(os.MkdirAll(filepath.Join(proc, "42/ns"), 0o755)) + require.NoError(os.WriteFile( filepath.Join(proc, "sys/kernel/random/boot_id"), []byte("b08745a1-625b-4f8b-8ab9-0123456789ab\n"), 0o644, )) - require.NoError(t, os.WriteFile( + require.NoError(os.WriteFile( filepath.Join(proc, "42/stat"), []byte(linuxStatFixture(42, "kwt daemon (worker)", "202")), 0o644, )) - require.NoError(t, os.Symlink("pid:[4026532448]", filepath.Join(proc, "42/ns/pid"))) + require.NoError(os.Symlink("pid:[4026532448]", filepath.Join(proc, "42/ns/pid"))) identity, ok := readLinuxProcessIdentity(proc, 42) - require.True(t, ok) - assert.Equal(t, ProcessIdentity("linux-v1:b08745a1-625b-4f8b-8ab9-0123456789ab:4026532448:202"), identity) + require.True(ok) + Assert.Equal(t, ProcessIdentity("linux-v1:b08745a1-625b-4f8b-8ab9-0123456789ab:4026532448:202"), identity) } func TestReadLinuxProcessIdentityRejectsMalformedTargetNamespace(t *testing.T) { + require := Require.New(t) proc := t.TempDir() - require.NoError(t, os.MkdirAll(filepath.Join(proc, "sys/kernel/random"), 0o755)) - require.NoError(t, os.MkdirAll(filepath.Join(proc, "42/ns"), 0o755)) - require.NoError(t, os.WriteFile( + require.NoError(os.MkdirAll(filepath.Join(proc, "sys/kernel/random"), 0o755)) + require.NoError(os.MkdirAll(filepath.Join(proc, "42/ns"), 0o755)) + require.NoError(os.WriteFile( filepath.Join(proc, "sys/kernel/random/boot_id"), []byte("b08745a1-625b-4f8b-8ab9-0123456789ab\n"), 0o644, )) - require.NoError(t, os.WriteFile( + require.NoError(os.WriteFile( filepath.Join(proc, "42/stat"), []byte(linuxStatFixture(42, "daemon", "202")), 0o644, )) - require.NoError(t, os.Symlink("pid:not-an-inode", filepath.Join(proc, "42/ns/pid"))) + require.NoError(os.Symlink("pid:not-an-inode", filepath.Join(proc, "42/ns/pid"))) _, ok := readLinuxProcessIdentity(proc, 42) - assert.False(t, ok) + Assert.False(t, ok) } func TestLinuxProcessIdentityCompatibilityRejectsMalformedValues(t *testing.T) { @@ -67,14 +69,14 @@ func TestLinuxProcessIdentityCompatibilityRejectsMalformedValues(t *testing.T) { "linux-v1:b08745a1-625b-4f8b-8ab9-0123456789ab:4026532448:202:extra", } for _, identity := range tests { - assert.False(t, processIdentityCompatible(identity), string(identity)) - assert.Equal(t, ProcessIdentityUnknown, CompareProcessIdentity(os.Getpid(), identity), string(identity)) + Assert.False(t, processIdentityCompatible(identity), string(identity)) + Assert.Equal(t, ProcessIdentityUnknown, CompareProcessIdentity(os.Getpid(), identity), string(identity)) } } func TestParseLinuxProcessStartTicksRejectsMalformedStat(t *testing.T) { _, err := parseLinuxProcessStartTicks([]byte("42 malformed")) - require.Error(t, err) + Require.Error(t, err) } func linuxStatFixture(pid int, command, startTicks string) string { diff --git a/daemon/process_identity_test.go b/daemon/process_identity_test.go index f9212b1..a5c5328 100644 --- a/daemon/process_identity_test.go +++ b/daemon/process_identity_test.go @@ -7,41 +7,42 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/daemon" ) func TestProcessIdentityMatchesTheSameLiveProcess(t *testing.T) { identity, ok := daemon.ReadProcessIdentity(os.Getpid()) - require.True(t, ok) - require.NotEmpty(t, identity) - assert.Equal(t, daemon.ProcessIdentityMatch, + Require.True(t, ok) + Require.NotEmpty(t, identity) + Assert.Equal(t, daemon.ProcessIdentityMatch, daemon.CompareProcessIdentity(os.Getpid(), identity)) } func TestProcessIdentityRejectsAMismatchedIdentity(t *testing.T) { + require := Require.New(t) identity, ok := daemon.ReadProcessIdentity(os.Getpid()) - require.True(t, ok) + require.True(ok) var mismatched daemon.ProcessIdentity if runtime.GOOS == "linux" { fields := strings.Split(string(identity), ":") - require.Len(t, fields, 4) + require.Len(fields, 4) startTicks, err := strconv.ParseUint(fields[3], 10, 64) - require.NoError(t, err) + require.NoError(err) fields[3] = strconv.FormatUint(startTicks+1, 10) mismatched = daemon.ProcessIdentity(strings.Join(fields, ":")) } else { created, err := strconv.ParseUint(string(identity), 10, 64) - require.NoError(t, err) + require.NoError(err) mismatched = daemon.ProcessIdentity(strconv.FormatUint(created+1, 10)) } - assert.Equal(t, daemon.ProcessIdentityMismatch, + Assert.Equal(t, daemon.ProcessIdentityMismatch, daemon.CompareProcessIdentity(os.Getpid(), mismatched)) } func TestProcessIdentityTreatsMalformedIdentityAsUnknown(t *testing.T) { - assert.Equal(t, daemon.ProcessIdentityUnknown, + Assert.Equal(t, daemon.ProcessIdentityUnknown, daemon.CompareProcessIdentity(os.Getpid(), "malformed")) } @@ -50,11 +51,11 @@ func TestRuntimeProcessIdentityTreatsUnsupportedVersionAsUnknown(t *testing.T) { PID: os.Getpid(), ProcessIdentityV2: "future-v1:12345", } - assert.Equal(t, daemon.ProcessIdentityUnknown, + Assert.Equal(t, daemon.ProcessIdentityUnknown, daemon.CompareRuntimeProcessIdentity(rec)) } func TestProcessIdentityTreatsMissingIdentityAsUnknown(t *testing.T) { - assert.Equal(t, daemon.ProcessIdentityUnknown, + Assert.Equal(t, daemon.ProcessIdentityUnknown, daemon.CompareProcessIdentity(os.Getpid(), "")) } diff --git a/daemon/runtime_identity_linux_test.go b/daemon/runtime_identity_linux_test.go index 50dfd34..b7bba97 100644 --- a/daemon/runtime_identity_linux_test.go +++ b/daemon/runtime_identity_linux_test.go @@ -6,27 +6,29 @@ import ( "encoding/json" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/daemon" ) func TestLinuxRuntimeRecordUsesVersionedIdentityWithoutExposingItToOldClients(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) rec := daemon.NewRuntimeRecord("tool", "v1", daemon.Endpoint{ Network: daemon.NetworkTCP, Address: "127.0.0.1:1234", }) - require.NotEmpty(t, rec.ProcessIdentityV2) - assert.Empty(t, rec.ProcessIdentity) - assert.Equal(t, daemon.ProcessIdentityMatch, daemon.CompareRuntimeProcessIdentity(rec)) + require.NotEmpty(rec.ProcessIdentityV2) + assert.Empty(rec.ProcessIdentity) + assert.Equal(daemon.ProcessIdentityMatch, daemon.CompareRuntimeProcessIdentity(rec)) body, err := json.Marshal(rec) - require.NoError(t, err) + require.NoError(err) var legacy struct { ProcessIdentity daemon.ProcessIdentity `json:"process_identity"` } - require.NoError(t, json.Unmarshal(body, &legacy)) - assert.Empty(t, legacy.ProcessIdentity) + require.NoError(json.Unmarshal(body, &legacy)) + assert.Empty(legacy.ProcessIdentity) } func TestLinuxLegacyWallClockIdentityFailsClosed(t *testing.T) { @@ -34,5 +36,5 @@ func TestLinuxLegacyWallClockIdentityFailsClosed(t *testing.T) { PID: 1, ProcessIdentity: "123456789", } - assert.Equal(t, daemon.ProcessIdentityUnknown, daemon.CompareRuntimeProcessIdentity(rec)) + Assert.Equal(t, daemon.ProcessIdentityUnknown, daemon.CompareRuntimeProcessIdentity(rec)) } diff --git a/git/cmd/gitcmd.go b/git/cmd/gitcmd.go index 7f44e58..432d050 100644 --- a/git/cmd/gitcmd.go +++ b/git/cmd/gitcmd.go @@ -427,8 +427,7 @@ func (e *GitError) Unwrap() error { // ExitCode returns git's process exit code when available. func (e *GitError) ExitCode() (int, bool) { - var exitErr *exec.ExitError - if errors.As(e.Err, &exitErr) { + if exitErr, ok := errors.AsType[*exec.ExitError](e.Err); ok { return exitErr.ExitCode(), true } return 0, false diff --git a/git/cmd/gitcmd_test.go b/git/cmd/gitcmd_test.go index e0c595f..d76cae3 100644 --- a/git/cmd/gitcmd_test.go +++ b/git/cmd/gitcmd_test.go @@ -12,8 +12,8 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) func TestRunnerCommandUsesDefensiveEnvironment(t *testing.T) { @@ -45,8 +45,8 @@ func TestRunnerCommandUsesDefensiveEnvironment(t *testing.T) { } func TestRunnerPreservesInheritedCommandScopeConfig(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) runner := New() runner.StripEnv = false runner.DisableSafeDirectoryForward = true @@ -71,8 +71,8 @@ func TestRunnerPreservesInheritedCommandScopeConfig(t *testing.T) { } func TestRunnerPreservesInheritedSafeDirectoryReset(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) globalConfig := filepath.Join(t.TempDir(), "gitconfig") require.NoError(os.WriteFile( globalConfig, @@ -100,8 +100,8 @@ func TestRunnerPreservesInheritedSafeDirectoryReset(t *testing.T) { } func TestRunnerReplaysInheritedSafeDirectoryAfterLowerScopeReset(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) globalConfig := filepath.Join(t.TempDir(), "gitconfig") require.NoError(os.WriteFile( globalConfig, @@ -137,25 +137,27 @@ func TestRunnerReplaysInheritedSafeDirectoryAfterLowerScopeReset(t *testing.T) { } func TestEnvValueForGOOSHonorsPlatformKeyCasing(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) env := []string{ "git_config_count=1", "GIT_CONFIG_COUNT=2", } value, ok := envValueForGOOS(env, "GIT_CONFIG_COUNT", "windows") - require.True(t, ok) - assert.Equal(t, "2", value) + require.True(ok) + assert.Equal("2", value) value, ok = envValueForGOOS( []string{"git_config_count=1"}, "GIT_CONFIG_COUNT", "windows", ) - require.True(t, ok) - assert.Equal(t, "1", value) + require.True(ok) + assert.Equal("1", value) _, ok = envValueForGOOS( []string{"git_config_count=1"}, "GIT_CONFIG_COUNT", "linux", ) - assert.False(t, ok) + assert.False(ok) } func TestNullGlobalConfigPathIsReadableEmptyFile(t *testing.T) { @@ -183,7 +185,7 @@ func TestNullGlobalConfigPathIsReadableEmptyFile(t *testing.T) { func safeDirectoryTestEnv(t *testing.T, globalConfig string) []string { t.Helper() emptySystemConfig := filepath.Join(t.TempDir(), "system-gitconfig") - require.NoError(t, os.WriteFile(emptySystemConfig, nil, 0o600)) + Require.NoError(t, os.WriteFile(emptySystemConfig, nil, 0o600)) return append(os.Environ(), "GIT_CONFIG_GLOBAL="+globalConfig, "GIT_CONFIG_SYSTEM="+emptySystemConfig, @@ -193,26 +195,26 @@ func safeDirectoryTestEnv(t *testing.T, globalConfig string) []string { func TestReadSafeDirectories(t *testing.T) { globalConfig := filepath.Join(t.TempDir(), "gitconfig") - require.NoError(t, os.WriteFile(globalConfig, []byte("[safe]\n\tdirectory = *\n\tdirectory = /srv/repo\n"), 0o600)) + Require.NoError(t, os.WriteFile(globalConfig, []byte("[safe]\n\tdirectory = *\n\tdirectory = /srv/repo\n"), 0o600)) got := readSafeDirectories(context.Background(), safeDirectoryTestEnv(t, globalConfig), "") - assert.Equal(t, []string{"*", "/srv/repo"}, got) + Assert.Equal(t, []string{"*", "/srv/repo"}, got) } func TestReadSafeDirectoriesUnset(t *testing.T) { globalConfig := filepath.Join(t.TempDir(), "gitconfig") - require.NoError(t, os.WriteFile(globalConfig, nil, 0o600)) + Require.NoError(t, os.WriteFile(globalConfig, nil, 0o600)) - assert.Empty(t, readSafeDirectories(context.Background(), safeDirectoryTestEnv(t, globalConfig), "")) + Assert.Empty(t, readSafeDirectories(context.Background(), safeDirectoryTestEnv(t, globalConfig), "")) } func TestReadSafeDirectoriesSystemScope(t *testing.T) { dir := t.TempDir() systemConfig := filepath.Join(dir, "system-gitconfig") - require.NoError(t, os.WriteFile(systemConfig, []byte("[safe]\n\tdirectory = /etc/repo\n"), 0o600)) + Require.NoError(t, os.WriteFile(systemConfig, []byte("[safe]\n\tdirectory = /etc/repo\n"), 0o600)) globalConfig := filepath.Join(dir, "gitconfig") - require.NoError(t, os.WriteFile(globalConfig, []byte("[safe]\n\tdirectory = /home/repo\n"), 0o600)) + Require.NoError(t, os.WriteFile(globalConfig, []byte("[safe]\n\tdirectory = /home/repo\n"), 0o600)) env := append(os.Environ(), "GIT_CONFIG_GLOBAL="+globalConfig, "GIT_CONFIG_SYSTEM="+systemConfig, @@ -221,7 +223,7 @@ func TestReadSafeDirectoriesSystemScope(t *testing.T) { got := readSafeDirectories(context.Background(), env, "") - assert.Equal(t, []string{"/etc/repo", "/home/repo"}, got, "system entries must come before global entries") + Assert.Equal(t, []string{"/etc/repo", "/home/repo"}, got, "system entries must come before global entries") } func TestReadSafeDirectoriesHonorsNoSystem(t *testing.T) { @@ -231,9 +233,9 @@ func TestReadSafeDirectoriesHonorsNoSystem(t *testing.T) { // "safe.directory = *" baked into CI runner images) get forwarded. dir := t.TempDir() systemConfig := filepath.Join(dir, "system-gitconfig") - require.NoError(t, os.WriteFile(systemConfig, []byte("[safe]\n\tdirectory = /etc/repo\n"), 0o600)) + Require.NoError(t, os.WriteFile(systemConfig, []byte("[safe]\n\tdirectory = /etc/repo\n"), 0o600)) globalConfig := filepath.Join(dir, "gitconfig") - require.NoError(t, os.WriteFile(globalConfig, []byte("[safe]\n\tdirectory = /home/repo\n"), 0o600)) + Require.NoError(t, os.WriteFile(globalConfig, []byte("[safe]\n\tdirectory = /home/repo\n"), 0o600)) env := append(os.Environ(), "GIT_CONFIG_GLOBAL="+globalConfig, "GIT_CONFIG_SYSTEM="+systemConfig, @@ -242,7 +244,7 @@ func TestReadSafeDirectoriesHonorsNoSystem(t *testing.T) { got := readSafeDirectories(context.Background(), env, "") - assert.Equal(t, []string{"/home/repo"}, got) + Assert.Equal(t, []string{"/home/repo"}, got) } func TestReadSafeDirectoriesBoundsProbeRuntime(t *testing.T) { @@ -258,13 +260,13 @@ func TestReadSafeDirectoriesBoundsProbeRuntime(t *testing.T) { start := time.Now() got := readSafeDirectories(context.Background(), env, "") - assert.Empty(t, got) - assert.Less(t, time.Since(start), time.Second, "safe.directory probes are best-effort and must not stall git commands") + Assert.Empty(t, got) + Assert.Less(t, time.Since(start), time.Second, "safe.directory probes are best-effort and must not stall git commands") } func TestReadSafeDirectoriesConditionalInclude(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) // Regression test: the probes must run in the command's directory with // --includes so includeIf "gitdir:..." entries resolve for the repository // the command targets, not for the calling process's working directory. @@ -296,16 +298,16 @@ func TestReadSafeDirectoriesConditionalInclude(t *testing.T) { func TestCommandEnvForwardsSafeDirectory(t *testing.T) { globalConfig := filepath.Join(t.TempDir(), "gitconfig") - require.NoError(t, os.WriteFile(globalConfig, []byte("[safe]\n\tdirectory = *\n"), 0o600)) + Require.NoError(t, os.WriteFile(globalConfig, []byte("[safe]\n\tdirectory = *\n"), 0o600)) runner := New() runner.Env = safeDirectoryTestEnv(t, globalConfig) cmd := runner.Command(context.Background(), "", "status") - assert.Equal(t, "*", gitConfigValue(strings.Join(cmd.Env, "\n"), "safe.directory")) + Assert.Equal(t, "*", gitConfigValue(strings.Join(cmd.Env, "\n"), "safe.directory")) // The sanitized environment must still hide the user's global config from // everything except the forwarded safe.directory entries. - assert.Contains(t, cmd.Env, "GIT_CONFIG_GLOBAL="+nullGlobalConfigPath()) + Assert.Contains(t, cmd.Env, "GIT_CONFIG_GLOBAL="+nullGlobalConfigPath()) } func TestCommandEnvForwardsSafeDirectoryForRunnerLiterals(t *testing.T) { @@ -313,7 +315,7 @@ func TestCommandEnvForwardsSafeDirectoryForRunnerLiterals(t *testing.T) { // literal instead of using New(); a zero DisableSafeDirectoryForward // keeps isolation flags from silently dropping the user's trust entries. globalConfig := filepath.Join(t.TempDir(), "gitconfig") - require.NoError(t, os.WriteFile(globalConfig, []byte("[safe]\n\tdirectory = /srv/repo\n"), 0o600)) + Require.NoError(t, os.WriteFile(globalConfig, []byte("[safe]\n\tdirectory = /srv/repo\n"), 0o600)) runner := Runner{ Env: safeDirectoryTestEnv(t, globalConfig), @@ -323,12 +325,12 @@ func TestCommandEnvForwardsSafeDirectoryForRunnerLiterals(t *testing.T) { } cmd := runner.Command(context.Background(), "", "status") - assert.Equal(t, "/srv/repo", gitConfigValue(strings.Join(cmd.Env, "\n"), "safe.directory")) + Assert.Equal(t, "/srv/repo", gitConfigValue(strings.Join(cmd.Env, "\n"), "safe.directory")) } func TestCommandEnvReadsSafeDirectoryFromRunnerEnv(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) // The forwarded entries must come from the runner's configured Env, not // from the process environment, and one runner's entries must not leak // into a runner with a different environment. @@ -352,19 +354,19 @@ func TestCommandEnvReadsSafeDirectoryFromRunnerEnv(t *testing.T) { func TestCommandEnvSkipsSafeDirectoryWhenDisabled(t *testing.T) { globalConfig := filepath.Join(t.TempDir(), "gitconfig") - require.NoError(t, os.WriteFile(globalConfig, []byte("[safe]\n\tdirectory = *\n"), 0o600)) + Require.NoError(t, os.WriteFile(globalConfig, []byte("[safe]\n\tdirectory = *\n"), 0o600)) runner := New() runner.Env = safeDirectoryTestEnv(t, globalConfig) runner.DisableSafeDirectoryForward = true cmd := runner.Command(context.Background(), "", "status") - assert.Empty(t, gitConfigValue(strings.Join(cmd.Env, "\n"), "safe.directory")) + Assert.Empty(t, gitConfigValue(strings.Join(cmd.Env, "\n"), "safe.directory")) } func TestCredentialResponseIsPrivateDataAndCleanupIsIdempotent(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) path, cleanup, err := (basicAuth{ username: "alice", @@ -408,8 +410,8 @@ func TestCredentialResponseRejectsProtocolDelimitersBeforeCreatingFile(t *testin for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) tempDir := t.TempDir() t.Setenv("TMPDIR", tempDir) t.Setenv("TMP", tempDir) @@ -433,7 +435,7 @@ func TestCredentialResponseRejectsProtocolDelimitersBeforeCreatingFile(t *testin } func TestWithBasicAuthKeepsSecretOutOfCommandEnvironment(t *testing.T) { - assert := assert.New(t) + assert := Assert.New(t) env := captureGitEnv(t, New().WithBasicAuth("alice", "secret-token")) for _, secret := range []string{ @@ -451,8 +453,8 @@ func TestWithBasicAuthKeepsSecretOutOfCommandEnvironment(t *testing.T) { } func TestWithBasicAuthRoundTripsCredentialProtocol(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) username := `al ice'$\"` password := `sec ret'\"$\\;|&()` request := "protocol=https\nhost=example.invalid\n\n" @@ -474,14 +476,15 @@ func TestWithBasicAuthStoreAndEraseDoNotDiscloseCredentials(t *testing.T) { stdout, stderr, err := New().WithBasicAuth("alice", "secret-token").Run( context.Background(), "", strings.NewReader(request), "credential", operation, ) - require.NoError(t, err, string(stderr)) - assert.Empty(t, stdout) - assert.Empty(t, stderr) + Require.NoError(t, err, string(stderr)) + Assert.Empty(t, stdout) + Assert.Empty(t, stderr) }) } } func TestWithBasicAuthRejectsCredentialProtocolInjection(t *testing.T) { + assert := Assert.New(t) password := "secret-token\nusername=mallory" request := "protocol=https\nhost=example.invalid\n\n" @@ -489,10 +492,10 @@ func TestWithBasicAuthRejectsCredentialProtocolInjection(t *testing.T) { context.Background(), "", strings.NewReader(request), "credential", "fill", ) - require.Error(t, err) - assert.Empty(t, stdout) - assert.NotContains(t, string(stderr), password) - assert.NotContains(t, err.Error(), password) + Require.Error(t, err) + assert.Empty(stdout) + assert.NotContains(string(stderr), password) + assert.NotContains(err.Error(), password) } func TestWithBasicAuthRejectsCommand(t *testing.T) { @@ -521,11 +524,12 @@ func TestWithBasicAuthRemovesCredentialResponseAfterRun(t *testing.T) { captureGitEnv(t, New().WithBasicAuth("alice", "secret-token")) responses, err := filepath.Glob(filepath.Join(tempDir, "gitcmd-credential-response-*")) - require.NoError(t, err) - assert.Empty(t, responses) + Require.NoError(t, err) + Assert.Empty(t, responses) } func TestWithBasicAuthRemovesCredentialResponseAfterGitFailure(t *testing.T) { + require := Require.New(t) tempDir := t.TempDir() t.Setenv("TMPDIR", tempDir) t.Setenv("TMP", tempDir) @@ -538,18 +542,18 @@ func TestWithBasicAuthRemovesCredentialResponseAfterGitFailure(t *testing.T) { gitPath += ".bat" script = "@exit /b 1\r\n" } - require.NoError(t, os.WriteFile(gitPath, []byte(script), 0o700)) + require.NoError(os.WriteFile(gitPath, []byte(script), 0o700)) pathEnv := binDir + string(os.PathListSeparator) + os.Getenv("PATH") t.Setenv("PATH", pathEnv) runner := New().WithBasicAuth("alice", "secret-token") runner.Env = []string{"PATH=" + pathEnv} _, _, err := runner.Run(context.Background(), "", nil, "version") - require.Error(t, err) + require.Error(err) responses, globErr := filepath.Glob(filepath.Join(tempDir, "gitcmd-credential-response-*")) - require.NoError(t, globErr) - assert.Empty(t, responses) + require.NoError(globErr) + Assert.Empty(t, responses) } func captureGitEnv(t *testing.T, runner Runner) string { @@ -588,7 +592,7 @@ func buildSleepingGit(t *testing.T) string { } exePath := filepath.Join(binDir, exeName) srcPath := filepath.Join(t.TempDir(), "main.go") - require.NoError(t, os.WriteFile(srcPath, []byte(`package main + Require.NoError(t, os.WriteFile(srcPath, []byte(`package main import "time" @@ -598,7 +602,7 @@ func main() { `), 0o600)) cmd := exec.Command("go", "build", "-o", exePath, srcPath) out, err := cmd.CombinedOutput() - require.NoError(t, err, string(out)) + Require.NoError(t, err, string(out)) return binDir } diff --git a/git/managed/lifecycle.go b/git/managed/lifecycle.go index 9fd6639..0cea5f1 100644 --- a/git/managed/lifecycle.go +++ b/git/managed/lifecycle.go @@ -532,7 +532,7 @@ func worktreeHasInitializedSubmodules( err, strings.TrimSpace(string(out)), ) } - for _, line := range bytes.Split(out, []byte{'\n'}) { + for line := range bytes.SplitSeq(out, []byte{'\n'}) { if len(line) != 0 && line[0] != '-' { return true, nil } @@ -989,8 +989,7 @@ func runLifecycleHook( if ctxErr := ctx.Err(); ctxErr != nil { return fmt.Errorf("run lifecycle hook %s: %w", script, ctxErr) } - var exitErr *exec.ExitError - if errors.As(err, &exitErr) { + if exitErr, ok := errors.AsType[*exec.ExitError](err); ok { return &HookError{ Script: script, ExitCode: exitErr.ExitCode(), @@ -1002,16 +1001,6 @@ func runLifecycleHook( return nil } -// rollbackCreatedWorktree best-effort unwinds a worktree this call just -// created after its setup hook failed. The branch is deleted only when -// this call created it; the original hook error is what the caller -// surfaces. -func rollbackCreatedWorktree( - ctx context.Context, root, path, branch string, deleteBranch bool, -) { - _, _ = rollbackCreatedWorktreeWithResult(ctx, root, path, branch, deleteBranch) -} - func rollbackCreatedWorktreeWithResult( ctx context.Context, root, path, branch string, deleteBranch bool, ) (RollbackResult, error) { diff --git a/git/managed/lifecycle_mr.go b/git/managed/lifecycle_mr.go index 70f7d01..dc25a41 100644 --- a/git/managed/lifecycle_mr.go +++ b/git/managed/lifecycle_mr.go @@ -586,9 +586,9 @@ func looksLikeSCPRemote(value string) bool { prefix, _, found := strings.Cut(value, ":") return found && prefix != "" && !strings.ContainsAny(prefix, `/\`) && - !(len(prefix) == 1 && - ((prefix[0] >= 'a' && prefix[0] <= 'z') || - (prefix[0] >= 'A' && prefix[0] <= 'Z'))) + (len(prefix) != 1 || + (prefix[0] < 'a' || prefix[0] > 'z') && + (prefix[0] < 'A' || prefix[0] > 'Z')) } func resolveMergeRequestOID(ctx context.Context, root, ref string) (string, error) { diff --git a/git/managed/lifecycle_mr_test.go b/git/managed/lifecycle_mr_test.go index 29bebfe..28d515a 100644 --- a/git/managed/lifecycle_mr_test.go +++ b/git/managed/lifecycle_mr_test.go @@ -357,7 +357,7 @@ func TestCreateWorktreeFromMergeRequestPreservesCancellation(t *testing.T) { require.ErrorIs(err, context.Canceled) var changeErr *ChangeRequestError - assert.False(errors.As(err, &changeErr)) + assert.NotErrorAs(err, &changeErr) }) } } diff --git a/git/managed/lifecycle_test.go b/git/managed/lifecycle_test.go index 01e9784..c2a5e71 100644 --- a/git/managed/lifecycle_test.go +++ b/git/managed/lifecycle_test.go @@ -243,7 +243,7 @@ func TestCreateWorktreeOnDiskUsesExecutionPolicy(t *testing.T) { }, }) require.NoError(err) - assert.Greater(gitRuns, 0) + assert.Positive(gitRuns) assert.Equal(1, hookRuns) } @@ -1059,7 +1059,7 @@ func TestLifecycleHookPreservesContextCancellation(t *testing.T) { require.Error(err) assert.ErrorIs(err, context.Canceled) var hookErr *HookError - assert.False(errors.As(err, &hookErr)) + assert.NotErrorAs(err, &hookErr) } func TestLifecycleHookExitHelper(t *testing.T) { diff --git a/git/remote/remote_test.go b/git/remote/remote_test.go index 6da0104..8b00769 100644 --- a/git/remote/remote_test.go +++ b/git/remote/remote_test.go @@ -4,7 +4,7 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/assert" + Assert "github.com/stretchr/testify/assert" ) func TestClonePathRejectsTraversalAndSeparators(t *testing.T) { @@ -52,17 +52,15 @@ func TestValidateRemoteIdentity(t *testing.T) { } func TestCloneURLIdentityNormalizesHostAndPreservesRepoCase(t *testing.T) { - assert.Equal(t, - "example.com/Acme/Widget", + assert := Assert.New(t) + assert.Equal("example.com/Acme/Widget", CloneURLIdentity("https://EXAMPLE.com:443/Acme/Widget.git"), ) - assert.Equal(t, - "2001:db8::1/Acme/Widget", + assert.Equal("2001:db8::1/Acme/Widget", CloneURLIdentity("https://[2001:db8::1]:443/Acme/Widget.git"), ) - assert.NotEqual(t, - CloneURLIdentity("ssh://example.com/Acme/Widget.git"), + assert.NotEqual(CloneURLIdentity("ssh://example.com/Acme/Widget.git"), CloneURLIdentity("ssh://example.com:443/Acme/Widget.git"), ) - assert.Equal(t, "/tmp/Widget.git", CloneURLIdentity(" /tmp/Widget.git ")) + assert.Equal("/tmp/Widget.git", CloneURLIdentity(" /tmp/Widget.git ")) } diff --git a/git/worktree/porcelain_test.go b/git/worktree/porcelain_test.go index 4a92038..905a399 100644 --- a/git/worktree/porcelain_test.go +++ b/git/worktree/porcelain_test.go @@ -3,11 +3,12 @@ package gitworktree import ( "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) func TestParsePorcelain(t *testing.T) { + assert := Assert.New(t) output := "" + "worktree /repo\nHEAD abc123\nbranch refs/heads/main\n\n" + "worktree /repo/.wt/feature\nHEAD def456\nbranch refs/heads/feature/x\n\n" + @@ -16,34 +17,36 @@ func TestParsePorcelain(t *testing.T) { "prunable gitdir file points to non-existent location\n" entries := ParsePorcelain(output) - require.Len(t, entries, 4) + Require.Len(t, entries, 4) - assert.Equal(t, PorcelainEntry{Path: "/repo", Head: "abc123", Branch: "main"}, entries[0]) - assert.Equal(t, PorcelainEntry{Path: "/repo/.wt/feature", Head: "def456", Branch: "feature/x"}, entries[1]) - assert.Equal(t, PorcelainEntry{Path: "/bare", Bare: true}, entries[2]) - assert.Equal(t, PorcelainEntry{ + assert.Equal(PorcelainEntry{Path: "/repo", Head: "abc123", Branch: "main"}, entries[0]) + assert.Equal(PorcelainEntry{Path: "/repo/.wt/feature", Head: "def456", Branch: "feature/x"}, entries[1]) + assert.Equal(PorcelainEntry{Path: "/bare", Bare: true}, entries[2]) + assert.Equal(PorcelainEntry{ Path: "/repo/.wt/detached", Head: "9f9f9f9f9f9f9f", Detached: true, Prunable: true, PrunableReason: "gitdir file points to non-existent location", }, entries[3]) } func TestParsePorcelainIgnoresUnknownFieldsAndIncompleteBlocks(t *testing.T) { + assert := Assert.New(t) entries := ParsePorcelain("HEAD ignored\n\nworktree /repo\nlocked reason\ncustom value\n") - require.Len(t, entries, 1) - assert.Equal(t, "/repo", entries[0].Path) - assert.True(t, entries[0].Locked) - assert.Equal(t, "reason", entries[0].LockedReason) + Require.Len(t, entries, 1) + assert.Equal("/repo", entries[0].Path) + assert.True(entries[0].Locked) + assert.Equal("reason", entries[0].LockedReason) } func TestParsePorcelainHandlesCRLFAndQuotedPaths(t *testing.T) { + assert := Assert.New(t) entries := ParsePorcelain( "worktree /repo\r\nHEAD abc123\r\nbranch refs/heads/main\r\n\r\n" + "worktree \"/repo/quoted\\tpath\"\r\nHEAD def456\r\ndetached\r\n", ) - require.Len(t, entries, 2) - assert.Equal(t, "/repo", entries[0].Path) - assert.Equal(t, "/repo/quoted\tpath", entries[1].Path) - assert.True(t, entries[1].Detached) + Require.Len(t, entries, 2) + assert.Equal("/repo", entries[0].Path) + assert.Equal("/repo/quoted\tpath", entries[1].Path) + assert.True(entries[1].Detached) } diff --git a/git/worktree/worktree_test.go b/git/worktree/worktree_test.go index 3c93f84..f2016e7 100644 --- a/git/worktree/worktree_test.go +++ b/git/worktree/worktree_test.go @@ -2,11 +2,12 @@ package gitworktree import ( "context" - "errors" "os" "path/filepath" "testing" + "github.com/stretchr/testify/require" + gittest "go.kenn.io/kit/git/test" ) @@ -43,14 +44,9 @@ func TestCreateCaptureAndApplyPatch(t *testing.T) { if string(got) != "changed\n" { t.Fatalf("base.txt = %q, want changed", got) } - if err := CheckPatch(ctx, repo.Root, patch); err == nil { - t.Fatal("patch should conflict after being applied") - } else { - var conflict *PatchConflictError - if !errors.As(err, &conflict) { - t.Fatalf("err = %T %v, want PatchConflictError", err, err) - } - } + err = CheckPatch(ctx, repo.Root, patch) + require.Error(t, err, "patch should conflict after being applied") + require.ErrorAs(t, err, new(*PatchConflictError)) } func TestGitmodulesFileProtocolDetection(t *testing.T) { diff --git a/go.mod b/go.mod index a56d9e5..ae1d1d4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module go.kenn.io/kit -go 1.26.6 +go 1.27.0 require ( github.com/BurntSushi/toml v1.6.0 @@ -112,8 +112,9 @@ require ( go.opentelemetry.io/otel/sdk/log v0.20.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/nilaway v0.0.0-20260528182042-490362de4fb6 // indirect + go.uber.org/nilaway v0.0.0-20260808063849-8649a03c818a // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect + golang.org/x/exp/typeparams v0.0.0-20260611194520-c48552f49976 // indirect golang.org/x/net v0.56.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260706201446-f0a921348800 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect diff --git a/go.sum b/go.sum index 259eaa4..dc8b775 100644 --- a/go.sum +++ b/go.sum @@ -456,8 +456,8 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/nilaway v0.0.0-20260528182042-490362de4fb6 h1:zPMeq2W25bus839tj1hJc6L1et7usbCgI6PrhdeRNy8= -go.uber.org/nilaway v0.0.0-20260528182042-490362de4fb6/go.mod h1:z2TyDbbirBXLiioI5nUG3MnE9hB8+4F6ha93I6C4cmg= +go.uber.org/nilaway v0.0.0-20260808063849-8649a03c818a h1:Ro4T0lIfQIWQWiEXvYHRpNs02BaphW77st7M7qM0eec= +go.uber.org/nilaway v0.0.0-20260808063849-8649a03c818a/go.mod h1:i9Hi8GeZFLBtUCXP73ZNSHKZ4K2JtxT0pLmo3sbJzlo= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= @@ -482,6 +482,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp/typeparams v0.0.0-20260611194520-c48552f49976 h1:GTD/WuaexTazIG/SxLOz4rEKZPDVilmVVC2nz4xhwfE= +golang.org/x/exp/typeparams v0.0.0-20260611194520-c48552f49976/go.mod h1:PqrXSW65cXDZH0k4IeUbhmg/bcAZDbzNz3byBpKCsXo= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/openssh/manager.go b/openssh/manager.go index 2afdca8..7e9742f 100644 --- a/openssh/manager.go +++ b/openssh/manager.go @@ -271,6 +271,9 @@ func (m *PersistentManager) connect( return 0, err } entry := m.host(identity, true) + if entry == nil { + return 0, fmt.Errorf("OpenSSH host state for %q was not created", identity) + } for { entry.mu.Lock() @@ -526,8 +529,7 @@ func (m *PersistentManager) finishStart( entry.state = StateConnected entry.message = "" } else { - var drainErr *masterDrainError - if errors.As(err, &drainErr) { + if _, ok := errors.AsType[*masterDrainError](err); ok { entry.state = StateStopping stopping = true } else { @@ -615,8 +617,7 @@ func (m *PersistentManager) finishTeardown( entry.message = "" entry.lastActive = time.Now() } else { - var drainErr *masterDrainError - if errors.As(err, &drainErr) { + if _, ok := errors.AsType[*masterDrainError](err); ok { entry.state = StateStopping entry.message = err.Error() stopping = true @@ -1018,8 +1019,7 @@ func runSSHCommand( if contextErr := ctx.Err(); contextErr != nil { return -1, errors.Join(contextErr, err) } - var exitErr *exec.ExitError - if errors.As(err, &exitErr) { + if exitErr, ok := errors.AsType[*exec.ExitError](err); ok { return exitErr.ExitCode(), err } return -1, err diff --git a/openssh/manager_unix_test.go b/openssh/manager_unix_test.go index c18dc9e..5332f33 100644 --- a/openssh/manager_unix_test.go +++ b/openssh/manager_unix_test.go @@ -13,8 +13,8 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) type fakeSSH struct { @@ -46,8 +46,8 @@ func newFakeSSH() *fakeSSH { func newSocketDir(t *testing.T) string { t.Helper() directory, err := os.MkdirTemp("", "kit-ssh-") - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, os.RemoveAll(directory)) }) + Require.NoError(t, err) + t.Cleanup(func() { Require.NoError(t, os.RemoveAll(directory)) }) return directory } @@ -59,7 +59,7 @@ func newTestManager(t *testing.T, directory string, fake *fakeSSH) *PersistentMa EstablishPollInterval: time.Millisecond, EstablishTimeout: time.Second, }) - require.NoError(t, err) + Require.NoError(t, err) return manager } @@ -71,7 +71,7 @@ func newConnectedTestManager( fake := newFakeSSH() manager := newTestManager(t, newSocketDir(t), fake) generation, err := manager.Connect(context.Background(), identity, testTarget(destination)) - require.NoError(t, err) + Require.NoError(t, err) t.Cleanup(func() { fake.closeAll() }) return fake, manager, generation } @@ -283,19 +283,19 @@ func (f *fakeSSH) setCheckGate(started chan<- struct{}, release <-chan struct{}) func TestPersistentManagerRejectsSymlinkedDirectoryBeforeSSH(t *testing.T) { target := t.TempDir() directory := filepath.Join(t.TempDir(), "control") - require.NoError(t, os.Symlink(target, directory)) + Require.NoError(t, os.Symlink(target, directory)) fake := newFakeSSH() manager := newTestManager(t, directory, fake) _, err := manager.Connect(context.Background(), "studio", testTarget("wes@studio")) - require.Error(t, err) - assert.Empty(t, fake.callsSnapshot()) + Require.Error(t, err) + Assert.Empty(t, fake.callsSnapshot()) } func TestPersistentManagerAdoptsOwnedMuxSocket(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) directory := newSocketDir(t) fake := newFakeSSH() manager := newTestManager(t, directory, fake) @@ -310,8 +310,8 @@ func TestPersistentManagerAdoptsOwnedMuxSocket(t *testing.T) { } func TestPersistentManagerRemovesOnlyPositivelyStaleSocket(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake, manager, _ := newConnectedTestManager(t, "studio", "wes@studio") path := manager.SocketPath("studio", testTarget("wes@studio")) require.NoError(fake.closeSocket(path, false)) @@ -324,8 +324,8 @@ func TestPersistentManagerRemovesOnlyPositivelyStaleSocket(t *testing.T) { } func TestPersistentManagerPreservesOccupiedSocket(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) directory := newSocketDir(t) fake := newFakeSSH() manager := newTestManager(t, directory, fake) @@ -342,8 +342,8 @@ func TestPersistentManagerPreservesOccupiedSocket(t *testing.T) { } func TestPersistentManagerPreservesIndeterminateSocket(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) directory := newSocketDir(t) fake := newFakeSSH() manager := newTestManager(t, directory, fake) @@ -362,8 +362,8 @@ func TestPersistentManagerPreservesIndeterminateSocket(t *testing.T) { } func TestPersistentManagerChangesDestinationByTeardownThenConnect(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake, manager, _ := newConnectedTestManager(t, "studio", "wes@old") oldCalls := len(fake.callsSnapshot()) @@ -376,8 +376,8 @@ func TestPersistentManagerChangesDestinationByTeardownThenConnect(t *testing.T) } func TestPersistentManagerBindsRunnerToConnectionGeneration(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) defaultRunner := newFakeSSH() boundRunner := newFakeSSH() manager := newTestManager(t, newSocketDir(t), defaultRunner) @@ -401,8 +401,8 @@ func TestPersistentManagerBindsRunnerToConnectionGeneration(t *testing.T) { } func TestPersistentManagerUsesOldRunnerForReplacementTeardown(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) defaultRunner := newFakeSSH() oldRunner := newFakeSSH() newRunner := newFakeSSH() @@ -426,8 +426,8 @@ func TestPersistentManagerUsesOldRunnerForReplacementTeardown(t *testing.T) { } func TestPersistentManagerArgumentsRemainBoundToOriginalTarget(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) _, manager, oldGeneration := newConnectedTestManager(t, "studio", "wes@old") oldArguments, err := manager.ConnectionArguments("studio", oldGeneration) require.NoError(err) @@ -448,8 +448,8 @@ func TestPersistentManagerArgumentsRemainBoundToOriginalTarget(t *testing.T) { } func TestPersistentManagerKeepsOldDestinationWhenReplacementTeardownFails(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake, manager, _ := newConnectedTestManager(t, "studio", "wes@old") sentinel := errors.New("exit failed") fake.setExitResult(255, sentinel) @@ -463,8 +463,8 @@ func TestPersistentManagerKeepsOldDestinationWhenReplacementTeardownFails(t *tes } func TestDisconnectWaitsForSocketDrainAfterExitReturns(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake, manager, _ := newConnectedTestManager(t, "studio", "wes@studio") path := manager.SocketPath("studio", testTarget("wes@studio")) exitReturned := make(chan struct{}) @@ -499,8 +499,8 @@ func TestDisconnectWaitsForSocketDrainAfterExitReturns(t *testing.T) { } func TestDisconnectBoundsSocketDrainAndPreservesListener(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake := newFakeSSH() manager, err := NewPersistentManager(newSocketDir(t), PersistentConfig{ RunSSH: fake.run, @@ -560,8 +560,8 @@ func TestDisconnectBoundsSocketDrainAndPreservesListener(t *testing.T) { } func TestDisconnectBoundsExitCommand(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake := newFakeSSH() manager, err := NewPersistentManager(newSocketDir(t), PersistentConfig{ RunSSH: fake.run, @@ -588,8 +588,8 @@ func TestDisconnectBoundsExitCommand(t *testing.T) { } func TestPersistentManagerReportsTypedSpawnFailure(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake := newFakeSSH() sentinel := errors.New("permission denied") fake.spawnExitCode, fake.spawnErr = 255, sentinel @@ -611,8 +611,8 @@ func TestPersistentManagerReportsTypedSpawnFailure(t *testing.T) { } func TestPersistentManagerFailedStartCleansCreatedSocket(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake := newFakeSSH() fake.spawnSocketOnError = true fake.spawnExitCode = 255 @@ -629,8 +629,8 @@ func TestPersistentManagerFailedStartCleansCreatedSocket(t *testing.T) { } func TestPersistentManagerFailedStartCleansLateSocket(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake := newFakeSSH() fake.spawnWithoutSocket = true directory := newSocketDir(t) @@ -663,8 +663,8 @@ func TestPersistentManagerFailedStartCleansLateSocket(t *testing.T) { } func TestFailedStartDrainTimeoutQuarantinesSocket(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake := newFakeSSH() fake.spawnSocketOnError = true fake.spawnExitCode = 255 @@ -722,8 +722,8 @@ stoppingObserved: } func TestPersistentManagerFailedCleanupBlocksTargetReplacement(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake := newFakeSSH() fake.spawnSocketOnError = true fake.spawnExitCode = 255 @@ -761,8 +761,8 @@ func TestPersistentManagerFailedCleanupBlocksTargetReplacement(t *testing.T) { } func TestStaleGenerationCannotTouchOrMarkReplacement(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) _, manager, oldGeneration := newConnectedTestManager(t, "studio", "wes@old") newGeneration, err := manager.Connect(context.Background(), "studio", testTarget("wes@new")) @@ -777,25 +777,25 @@ func TestStaleGenerationCannotTouchOrMarkReplacement(t *testing.T) { func TestDisconnectUnknownIdentityDoesNotTouchFilesystem(t *testing.T) { directory := filepath.Join(t.TempDir(), "does-not-exist") manager, err := NewPersistentManager(directory, PersistentConfig{}) - require.NoError(t, err) + Require.NoError(t, err) - require.NoError(t, manager.Disconnect(context.Background(), "unknown")) + Require.NoError(t, manager.Disconnect(context.Background(), "unknown")) - assert.NoDirExists(t, directory) + Assert.NoDirExists(t, directory) } func TestPersistentManagerRejectsEmptySocketDirectory(t *testing.T) { manager, err := NewPersistentManager("", PersistentConfig{}) - assert.Nil(t, manager) + Assert.Nil(t, manager) var pathErr *PathError - require.ErrorAs(t, err, &pathErr) - assert.Equal(t, "empty control directory", pathErr.Reason) + Require.ErrorAs(t, err, &pathErr) + Assert.Equal(t, "empty control directory", pathErr.Reason) } func TestPersistentManagerSkipsIdleCandidateRefreshedAfterScan(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) _, manager, generation := newConnectedTestManager(t, "studio", "wes@studio") entry := manager.host("studio", false) entry.mu.Lock() @@ -813,8 +813,8 @@ func TestPersistentManagerSkipsIdleCandidateRefreshedAfterScan(t *testing.T) { } func TestPersistentManagerProbeFailureDoesNotRefreshActivity(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) _, manager, generation := newConnectedTestManager( t, "studio", "wes@studio", ) @@ -835,8 +835,8 @@ func TestPersistentManagerProbeFailureDoesNotRefreshActivity(t *testing.T) { } func TestIdleScanStopsReservingConnectionsAfterCancellation(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake := newFakeSSH() manager := newTestManager(t, newSocketDir(t), fake) firstGeneration, err := manager.Connect( @@ -879,8 +879,8 @@ func TestIdleScanStopsReservingConnectionsAfterCancellation(t *testing.T) { } func TestIdleTeardownFailureEmitsErrorAndPreservesConnection(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) events := make(chan Event, 8) fake := newFakeSSH() manager, err := NewPersistentManager(newSocketDir(t), PersistentConfig{ @@ -935,8 +935,8 @@ connected: } func TestEstablishTimeoutUsesBoundedCleanupAndKeepsOriginalError(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) cleanupDeadline := make(chan bool, 1) fake := newFakeSSH() fake.onCheck = func(ctx context.Context) (int, error) { @@ -981,19 +981,19 @@ func TestCanceledEstablishKeepsCancellationWhenCleanupFails(t *testing.T) { CleanupTimeout: 10 * time.Millisecond, MaximumControlPathBytes: 1_000, }) - require.NoError(t, err) + Require.NoError(t, err) ctx, cancel := context.WithCancel(context.Background()) cancel() _, err = manager.Connect(ctx, "studio", testTarget("wes@studio")) - require.ErrorIs(t, err, context.Canceled) - require.ErrorIs(t, err, cleanupSentinel) + Require.ErrorIs(t, err, context.Canceled) + Require.ErrorIs(t, err, cleanupSentinel) } func TestDisconnectRejectsSocketReplacedAfterExit(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake, manager, _ := newConnectedTestManager(t, "studio", "wes@studio") path := manager.SocketPath("studio", testTarget("wes@studio")) fake.onExit = func(context.Context) (int, error) { @@ -1011,7 +1011,8 @@ func TestDisconnectRejectsSocketReplacedAfterExit(t *testing.T) { } func TestEventsCarryGenerationAndSuppressStaleState(t *testing.T) { - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) events := make(chan Event, 8) fake := newFakeSSH() manager, err := NewPersistentManager(newSocketDir(t), PersistentConfig{ @@ -1022,17 +1023,17 @@ func TestEventsCarryGenerationAndSuppressStaleState(t *testing.T) { events <- event }, }) - require.NoError(t, err) + require.NoError(err) generation, err := manager.Connect(context.Background(), "studio", testTarget("wes@studio")) - require.NoError(t, err) + require.NoError(err) var received []Event for len(received) == 0 || received[len(received)-1].State != StateConnected { select { case event := <-events: received = append(received, event) case <-time.After(time.Second): - require.Fail(t, "connected event was not delivered") + require.Fail("connected event was not delivered") } } for _, event := range received { @@ -1049,8 +1050,8 @@ func TestEventsCarryGenerationAndSuppressStaleState(t *testing.T) { } func TestEventQueueCoalescesWhileCallbackIsBlocked(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) callbackStarted := make(chan struct{}) releaseCallback := make(chan struct{}) t.Cleanup(func() { close(releaseCallback) }) @@ -1110,8 +1111,8 @@ func TestEventQueueCoalescesWhileCallbackIsBlocked(t *testing.T) { } func TestConnectEmitsConnectedEventWhenProbeFailureRecovers(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) events := make(chan Event, 8) fake := newFakeSSH() manager, err := NewPersistentManager(newSocketDir(t), PersistentConfig{ @@ -1160,8 +1161,8 @@ initiallyConnected: } func TestPersistentManagerEventCallbackCanDisconnectReentrantly(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake := newFakeSSH() callbackResult := make(chan error, 1) var manager *PersistentManager @@ -1195,8 +1196,8 @@ func TestPersistentManagerEventCallbackCanDisconnectReentrantly(t *testing.T) { } func TestPersistentManagerAcceptsExplicitConnectionOptions(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) fake := newFakeSSH() options := DefaultConnectionOptions() options.TCPKeepAlive = false @@ -1216,8 +1217,8 @@ func TestPersistentManagerAcceptsExplicitConnectionOptions(t *testing.T) { } func TestPersistentManagerResolvesSocketDirectoryAtConstruction(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) originalDirectory, err := os.Getwd() require.NoError(err) t.Cleanup(func() { require.NoError(os.Chdir(originalDirectory)) }) diff --git a/openssh/persistent_platform_other_test.go b/openssh/persistent_platform_other_test.go index f63e2b1..f1d0085 100644 --- a/openssh/persistent_platform_other_test.go +++ b/openssh/persistent_platform_other_test.go @@ -6,21 +6,22 @@ import ( "context" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) func TestPersistentManagerIsUnsupported(t *testing.T) { + require := Require.New(t) manager, err := NewPersistentManager(t.TempDir(), PersistentConfig{}) - require.NoError(t, err) + require.NoError(err) _, err = manager.Connect(context.Background(), "studio", testTarget("wes@studio")) - require.ErrorIs(t, err, ErrPersistentUnsupported) + require.ErrorIs(err, ErrPersistentUnsupported) _, err = manager.IsAlive(context.Background(), "studio", Generation(1)) - require.ErrorIs(t, err, ErrPersistentUnsupported) + require.ErrorIs(err, ErrPersistentUnsupported) arguments, err := ClientArguments("") - require.NoError(t, err) - assert.Equal(t, []string{ + require.NoError(err) + Assert.Equal(t, []string{ "-o", "ControlMaster=no", "-o", "ControlPersist=no", "-S", "none", diff --git a/pack/reader.go b/pack/reader.go index 455469c..5b02ec6 100644 --- a/pack/reader.go +++ b/pack/reader.go @@ -196,6 +196,9 @@ func readFooterRegion(f *os.File, size int64, enc bool, id string, } if enc { + if crypter == nil { + return nil, 0, ErrEncrypted + } footerOffset, storedLen, err := parseEncryptedTrailer(fixedTail, uint64(size)) //nolint:gosec // size >= 0 if err != nil { return nil, 0, err diff --git a/pack/reader_test.go b/pack/reader_test.go index 7c2123e..0105f26 100644 --- a/pack/reader_test.go +++ b/pack/reader_test.go @@ -8,8 +8,8 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) // buildTestPack writes a pack with the given blobs and returns its final path @@ -19,14 +19,14 @@ func buildTestPack(t *testing.T, blobs [][]byte, t.Helper() dir := t.TempDir() w, err := NewWriter(dir, WriterOptions{Crypter: crypter}) - require.NoError(t, err) + Require.NoError(t, err) for _, b := range blobs { _, err := w.Append(b) - require.NoError(t, err) + Require.NoError(t, err) } final := filepath.Join(dir, w.ID()+".mvpack") entries, err := w.Seal(final) - require.NoError(t, err) + Require.NoError(t, err) return final, entries } @@ -34,7 +34,7 @@ func testBlobs(t *testing.T) [][]byte { t.Helper() random := make([]byte, 32*1024) _, err := rand.Read(random) - require.NoError(t, err) + Require.NoError(t, err) return [][]byte{ bytes.Repeat([]byte("compressible text "), 2000), random, @@ -44,8 +44,8 @@ func testBlobs(t *testing.T) [][]byte { } func TestReaderRoundTripPlain(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) blobs := testBlobs(t) path, wrote := buildTestPack(t, blobs, nil) @@ -67,8 +67,8 @@ func TestReaderIDIgnoresExtension(t *testing.T) { // OpenReader derives the pack ID from the filename minus its extension, so // any extension works: the same sealed pack copied under a ".mvpack" name // and a ".kpack" name must both open and report the same ID. - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) path, _ := buildTestPack(t, testBlobs(t), nil) id := strings.TrimSuffix(filepath.Base(path), ".mvpack") data, err := os.ReadFile(path) @@ -85,32 +85,33 @@ func TestReaderIDIgnoresExtension(t *testing.T) { } func TestReaderHeaderValidation(t *testing.T) { + require := Require.New(t) path, _ := buildTestPack(t, testBlobs(t), nil) data, err := os.ReadFile(path) - require.NoError(t, err) + require.NoError(err) writeVariant := func(mutate func([]byte)) string { v := append([]byte(nil), data...) mutate(v) p := filepath.Join(t.TempDir(), NewPackID()+".mvpack") - require.NoError(t, os.WriteFile(p, v, 0o600)) + require.NoError(os.WriteFile(p, v, 0o600)) return p } _, err = OpenReader(writeVariant(func(b []byte) { b[0] = 'X' }), nil) - require.ErrorIs(t, err, ErrBadMagic) + require.ErrorIs(err, ErrBadMagic) _, err = OpenReader(writeVariant(func(b []byte) { b[4] = 99 }), nil) - require.ErrorIs(t, err, ErrUnsupportedVersion) + require.ErrorIs(err, ErrUnsupportedVersion) _, err = OpenReader(writeVariant(func(b []byte) { b[5] = byte(packEncrypted | 1<<7) }), nil) - require.ErrorIs(t, err, ErrCorrupt) - require.ErrorContains(t, err, "unknown pack flags 0x81") + require.ErrorIs(err, ErrCorrupt) + require.ErrorContains(err, "unknown pack flags 0x81") } func TestReaderBlobCorruption(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) path, entries := buildTestPack(t, testBlobs(t), nil) data, err := os.ReadFile(path) require.NoError(err) @@ -136,8 +137,8 @@ func TestReaderBlobCorruption(t *testing.T) { func TestReaderRoundTripLargePack(t *testing.T) { // Build a several-MB pack (much larger than the footer itself) and // confirm opening and reading it back is still correct. - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) var blobs [][]byte for range 8 { @@ -173,7 +174,7 @@ func TestReaderRejectsForgedHugeRawLen(t *testing.T) { // entries alike: maxStoredLen exceeds MaxRawLen by the compression/seal // allowances, so an uncompressed entry could otherwise claim a raw length // just past the documented blob limit. - require := require.New(t) + require := Require.New(t) compressible := bytes.Repeat([]byte("forge me some zstd bytes "), 4096) path, entries := buildTestPack(t, [][]byte{compressible}, nil) @@ -202,7 +203,7 @@ func TestReaderRejectsForgedHugeRawLen(t *testing.T) { // entries that never passed footer parsing: the MaxRawLen bound applies to // uncompressed frames too, not only the zstd preallocation path. func TestDecodeFrameRejectsOversizedRawLen(t *testing.T) { - require := require.New(t) + require := Require.New(t) for _, compressed := range []bool{true, false} { _, err := decodeFrame([]byte("stored"), compressed, MaxRawLen+1) require.ErrorIs(err, ErrCorrupt, "compressed=%v", compressed) @@ -214,7 +215,7 @@ func TestReaderRejectsEncryptedFlagInPlainPack(t *testing.T) { // An entry flagged BlobEncrypted inside a pack whose trailer is plain is // structurally corrupt: the pack-level flag and the entry-level flag // disagree about whether the blob was sealed. - require := require.New(t) + require := Require.New(t) blobs := [][]byte{[]byte("first"), []byte("second")} path, entries := buildTestPack(t, blobs, nil) @@ -237,7 +238,7 @@ func TestReaderRejectsEncryptedFlagInPlainPack(t *testing.T) { } func TestReaderBlobHashMismatch(t *testing.T) { - require := require.New(t) + require := Require.New(t) // A stored frame whose bytes are internally consistent (CRC recomputed to // match) but whose content does not hash to the entry's BlobID must fail // with ErrBlobMismatch. Build it by lying to the footer: swap two entries' diff --git a/pack/stream.go b/pack/stream.go index da28c6e..5d5236c 100644 --- a/pack/stream.go +++ b/pack/stream.go @@ -5,7 +5,6 @@ import ( "crypto/sha256" "errors" "fmt" - "hash" "hash/crc32" "io" "os" @@ -210,10 +209,9 @@ func PrepareBlob( rawCRC := crc32.New(crc32cTable) rawHash := sha256.New() writers := []io.Writer{rawFile, rawCRC, rawHash} - var compressedCRC hash.Hash32 + compressedCRC := crc32.New(crc32cTable) var encoder *zstd.Encoder if compressedFile != nil { - compressedCRC = crc32.New(crc32cTable) encoder, err = zstd.NewWriter(io.MultiWriter(compressedFile, compressedCRC), zstd.WithEncoderLevel(zstd.EncoderLevelFromZstd(normalizeZstdLevel(zstdLevel))), zstd.WithEncoderConcurrency(1), diff --git a/pack/stream_test.go b/pack/stream_test.go index b886f8b..07df74e 100644 --- a/pack/stream_test.go +++ b/pack/stream_test.go @@ -12,8 +12,8 @@ import ( "sync" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) func TestAppendStreamRoundTrip(t *testing.T) { @@ -40,10 +40,12 @@ func TestAppendStreamRoundTrip(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) t.Parallel() dir := t.TempDir() writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) t.Cleanup(func() { _ = writer.Abort() }) id := ComputeBlobID(tt.content) @@ -52,37 +54,37 @@ func TestAppendStreamRoundTrip(t *testing.T) { ScratchDir: dir, ScratchBytes: uint64(len(tt.content))*3 + 1024, }) - require.NoError(t, err) - assert.Equal(t, tt.compressed, entry.Flags&BlobCompressed != 0) - assert.Equal(t, id, entry.ID) + require.NoError(err) + assert.Equal(tt.compressed, entry.Flags&BlobCompressed != 0) + assert.Equal(id, entry.ID) matches, err := filepath.Glob(filepath.Join(dir, "pack-prepared-*")) - require.NoError(t, err) - assert.Empty(t, matches) + require.NoError(err) + assert.Empty(matches) final := filepath.Join(dir, writer.ID()+".pack") _, err = writer.Seal(final) - require.NoError(t, err) + require.NoError(err) reader, err := OpenReader(final, nil) - require.NoError(t, err) + require.NoError(err) if tt.compressed { window, windowErr := reader.streamingWindow(reader.Entries()[0]) - require.NoError(t, windowErr) - assert.LessOrEqual(t, window, uint64(streamMaxWindowSize)) + require.NoError(windowErr) + assert.LessOrEqual(window, uint64(streamMaxWindowSize)) } stream, err := reader.OpenBlob(context.Background(), reader.Entries()[0]) - require.NoError(t, err) + require.NoError(err) got, err := io.ReadAll(stream) - require.NoError(t, err) - assert.Equal(t, tt.content, got) - assert.True(t, stream.Verified()) - require.NoError(t, stream.Verify()) - require.NoError(t, stream.Close()) + require.NoError(err) + assert.Equal(tt.content, got) + assert.True(stream.Verified()) + require.NoError(stream.Verify()) + require.NoError(stream.Close()) buffered, err := reader.ReadBlob(reader.Entries()[0]) - require.NoError(t, err) - assert.Equal(t, tt.content, buffered) - require.NoError(t, reader.Close()) + require.NoError(err) + assert.Equal(tt.content, buffered) + require.NoError(reader.Close()) }) } } @@ -102,28 +104,30 @@ func TestAppendStreamAboveLegacyCeiling(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) dir := t.TempDir() writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) t.Cleanup(func() { _ = writer.Abort() }) source := io.LimitReader(tt.source(), int64(size)) entry, err := writer.AppendStream(context.Background(), source, size, AppendStreamOptions{ ScratchDir: dir, ScratchBytes: size*2 + 64<<20, }) - require.NoError(t, err) - assert.Equal(t, tt.compressed, entry.Flags&BlobCompressed != 0) + require.NoError(err) + assert.Equal(tt.compressed, entry.Flags&BlobCompressed != 0) final := filepath.Join(dir, writer.ID()+".pack") _, err = writer.Seal(final) - require.NoError(t, err) + require.NoError(err) reader, err := OpenReader(final, nil) - require.NoError(t, err) + require.NoError(err) stream, err := reader.OpenBlob(context.Background(), reader.Entries()[0]) - require.NoError(t, err) - require.NoError(t, stream.Verify()) - assert.True(t, stream.Verified()) - require.NoError(t, stream.Close()) - require.NoError(t, reader.Close()) + require.NoError(err) + require.NoError(stream.Verify()) + assert.True(stream.Verified()) + require.NoError(stream.Close()) + require.NoError(reader.Close()) }) } } @@ -135,8 +139,8 @@ func largeStreamTestBytes(t *testing.T, fallback int64) int64 { return fallback } size, err := strconv.ParseInt(value, 10, 64) - require.NoError(t, err) - require.Positive(t, size) + Require.NoError(t, err) + Require.Positive(t, size) return size } @@ -160,21 +164,22 @@ func (r *noiseReader) Read(p []byte) (int, error) { } func TestAppendStreamSourceFailuresLeaveWriterUsable(t *testing.T) { + require := Require.New(t) t.Parallel() dir := t.TempDir() writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) t.Cleanup(func() { _ = writer.Abort() }) _, err = writer.AppendStream(context.Background(), strings.NewReader("short"), 6, AppendStreamOptions{ScratchDir: dir}) - require.ErrorIs(t, err, ErrTruncated) + require.ErrorIs(err, ErrTruncated) _, err = writer.AppendStream(context.Background(), strings.NewReader("trailing"), 5, AppendStreamOptions{ScratchDir: dir}) - require.ErrorIs(t, err, ErrCorrupt) + require.ErrorIs(err, ErrCorrupt) content := []byte("valid after source failures") entry, err := writer.AppendStream(context.Background(), bytes.NewReader(content), uint64(len(content)), AppendStreamOptions{ScratchDir: dir}) - require.NoError(t, err) - assert.Equal(t, ComputeBlobID(content), entry.ID) + require.NoError(err) + Assert.Equal(t, ComputeBlobID(content), entry.ID) } func TestPrepareBlobCancellationCleansScratch(t *testing.T) { @@ -183,10 +188,10 @@ func TestPrepareBlobCancellationCleansScratch(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) source := &cancelingReader{remaining: 1 << 20, cancel: cancel} _, err := PrepareBlob(ctx, source, 1<<20, DefaultZstdLevel, AppendStreamOptions{ScratchDir: dir}) - require.ErrorIs(t, err, context.Canceled) + Require.ErrorIs(t, err, context.Canceled) matches, err := filepath.Glob(filepath.Join(dir, "pack-prepared-*")) - require.NoError(t, err) - assert.Empty(t, matches) + Require.NoError(t, err) + Assert.Empty(t, matches) } func TestPrepareBlobPreservesSourceError(t *testing.T) { @@ -194,10 +199,10 @@ func TestPrepareBlobPreservesSourceError(t *testing.T) { dir := t.TempDir() sourceErr := errors.New("source failed") _, err := PrepareBlob(context.Background(), &failingReader{err: sourceErr}, 1<<20, DefaultZstdLevel, AppendStreamOptions{ScratchDir: dir}) - require.ErrorIs(t, err, sourceErr) + Require.ErrorIs(t, err, sourceErr) matches, err := filepath.Glob(filepath.Join(dir, "pack-prepared-*")) - require.NoError(t, err) - assert.Empty(t, matches) + Require.NoError(t, err) + Assert.Empty(t, matches) } type failingReader struct { @@ -236,6 +241,8 @@ func (r *cancelingReader) Read(p []byte) (int, error) { } func TestPrepareBlobLimitsAndIdentity(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) t.Parallel() dir := t.TempDir() content := bytes.Repeat([]byte("x"), 1<<16) @@ -245,78 +252,82 @@ func TestPrepareBlobLimitsAndIdentity(t *testing.T) { ScratchDir: dir, ScratchBytes: uint64(len(content)), }) var limitErr *StreamLimitError - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, StreamLimitScratchBytes, limitErr.Dimension) + require.ErrorAs(err, &limitErr) + assert.Equal(StreamLimitScratchBytes, limitErr.Dimension) _, err = PrepareBlob(context.Background(), bytes.NewReader(content), uint64(len(content)), DefaultZstdLevel, AppendStreamOptions{ ExpectedID: &wrong, ScratchDir: dir, }) - require.ErrorIs(t, err, ErrBlobMismatch) + require.ErrorIs(err, ErrBlobMismatch) matches, globErr := filepath.Glob(filepath.Join(dir, "pack-prepared-*")) - require.NoError(t, globErr) - assert.Empty(t, matches) + require.NoError(globErr) + assert.Empty(matches) _, err = PrepareBlob(context.Background(), strings.NewReader(""), MaxRawLen+1, DefaultZstdLevel, AppendStreamOptions{ScratchDir: dir}) - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, StreamLimitRawBytes, limitErr.Dimension) + require.ErrorAs(err, &limitErr) + assert.Equal(StreamLimitRawBytes, limitErr.Dimension) small := []byte("small") prepared, err := PrepareBlob(context.Background(), bytes.NewReader(small), uint64(len(small)), DefaultZstdLevel, AppendStreamOptions{ ScratchDir: dir, ScratchBytes: uint64(len(small)), }) - require.NoError(t, err) - require.NoError(t, prepared.Close()) + require.NoError(err) + require.NoError(prepared.Close()) } func TestPreparedBlobCloseIsIdempotent(t *testing.T) { + require := Require.New(t) t.Parallel() dir := t.TempDir() content := []byte("discard prepared content") prepared, err := PrepareBlob(context.Background(), bytes.NewReader(content), uint64(len(content)), DefaultZstdLevel, AppendStreamOptions{ScratchDir: dir}) - require.NoError(t, err) - require.NoError(t, prepared.Close()) - require.NoError(t, prepared.Close()) + require.NoError(err) + require.NoError(prepared.Close()) + require.NoError(prepared.Close()) matches, err := filepath.Glob(filepath.Join(dir, "pack-prepared-*")) - require.NoError(t, err) - assert.Empty(t, matches) + require.NoError(err) + Assert.Empty(t, matches) } func TestAppendPreparedZeroByteWriteFailureDoesNotPoisonWriter(t *testing.T) { + require := Require.New(t) t.Parallel() dir := t.TempDir() content := bytes.Repeat([]byte("prepared"), 1<<14) prepared, err := PrepareBlob(context.Background(), bytes.NewReader(content), uint64(len(content)), DefaultZstdLevel, AppendStreamOptions{ScratchDir: dir}) - require.NoError(t, err) + require.NoError(err) writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) t.Cleanup(func() { _ = writer.Abort() }) - require.NoError(t, writer.f.Close()) + require.NoError(writer.f.Close()) _, err = writer.AppendPrepared(context.Background(), prepared) - require.Error(t, err) - assert.NoError(t, writer.err) + require.Error(err) + Assert.NoError(t, writer.err) } func TestAppendPreparedCancellationBeforeCopyLeavesWriterUsable(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) t.Parallel() dir := t.TempDir() content := bytes.Repeat([]byte("prepared cancellation"), 1<<12) prepared, err := PrepareBlob(context.Background(), bytes.NewReader(content), uint64(len(content)), DefaultZstdLevel, AppendStreamOptions{ScratchDir: dir}) - require.NoError(t, err) + require.NoError(err) writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) t.Cleanup(func() { _ = writer.Abort() }) ctx, cancel := context.WithCancel(context.Background()) cancelBetweenChecks := &cancelAfterFirstErrContext{Context: ctx, cancel: cancel} _, err = writer.AppendPrepared(cancelBetweenChecks, prepared) - require.ErrorIs(t, err, context.Canceled) - assert.Equal(t, int64(headerSize), writer.StoredSize()) + require.ErrorIs(err, context.Canceled) + assert.Equal(int64(headerSize), writer.StoredSize()) later := []byte("later append") entry, err := writer.Append(later) - require.NoError(t, err) - assert.Equal(t, ComputeBlobID(later), entry.ID) + require.NoError(err) + assert.Equal(ComputeBlobID(later), entry.ID) } type cancelAfterFirstErrContext struct { @@ -332,324 +343,341 @@ func (c *cancelAfterFirstErrContext) Err() error { } func TestAppendPreparedScratchCorruptionPoisonsWriter(t *testing.T) { + require := Require.New(t) t.Parallel() dir := t.TempDir() content := bytes.Repeat([]byte("prepared corruption"), 1<<12) prepared, err := PrepareBlob(context.Background(), bytes.NewReader(content), uint64(len(content)), DefaultZstdLevel, AppendStreamOptions{ScratchDir: dir}) - require.NoError(t, err) + require.NoError(err) _, err = prepared.f.WriteAt([]byte{0xff}, 0) - require.NoError(t, err) + require.NoError(err) writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) t.Cleanup(func() { _ = writer.Abort() }) _, firstErr := writer.AppendPrepared(context.Background(), prepared) - require.ErrorIs(t, firstErr, ErrCorrupt) + require.ErrorIs(firstErr, ErrCorrupt) _, nextErr := writer.Append([]byte("later")) - assert.EqualError(t, nextErr, firstErr.Error()) + Assert.EqualError(t, nextErr, firstErr.Error()) } func TestBlobReaderTerminalVerificationAndParentLifetime(t *testing.T) { + require := Require.New(t) t.Parallel() dir := t.TempDir() content := []byte("terminal verification content") writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) entry, err := writer.Append(content) - require.NoError(t, err) + require.NoError(err) final := filepath.Join(dir, writer.ID()+".pack") _, err = writer.Seal(final) - require.NoError(t, err) + require.NoError(err) reader, err := OpenReader(final, nil) - require.NoError(t, err) + require.NoError(err) stream, err := reader.OpenBlob(context.Background(), entry) - require.NoError(t, err) - require.ErrorIs(t, reader.Close(), ErrStreamsActive) + require.NoError(err) + require.ErrorIs(reader.Close(), ErrStreamsActive) buf := make([]byte, 4) _, err = stream.Read(buf) - require.NoError(t, err) - require.ErrorIs(t, stream.Close(), ErrVerificationIncomplete) - require.NoError(t, reader.Close()) + require.NoError(err) + require.ErrorIs(stream.Close(), ErrVerificationIncomplete) + require.NoError(reader.Close()) } func TestBlobReaderRejectsEntryOutsideVerifiedFooter(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) t.Parallel() dir := t.TempDir() secret := []byte("secret entry bytes") public := []byte("public entry bytes") writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) secretEntry, err := writer.Append(secret) - require.NoError(t, err) + require.NoError(err) publicEntry, err := writer.Append(public) - require.NoError(t, err) + require.NoError(err) final := filepath.Join(dir, writer.ID()+".pack") _, err = writer.Seal(final) - require.NoError(t, err) + require.NoError(err) reader, err := OpenReader(final, nil) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, reader.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(reader.Close()) }) listed := reader.Entries() listed[0] = publicEntry - assert.Equal(t, secretEntry, reader.Entries()[0], "returned footer entries must not mutate reader authority") + assert.Equal(secretEntry, reader.Entries()[0], "returned footer entries must not mutate reader authority") forged := publicEntry forged.Offset = secretEntry.Offset forged.StoredLen = secretEntry.StoredLen forged.RawLen = secretEntry.RawLen stream, err := reader.OpenBlob(context.Background(), forged) - require.ErrorIs(t, err, ErrCorrupt) - require.ErrorContains(t, err, "does not match verified footer") - assert.Nil(t, stream) + require.ErrorIs(err, ErrCorrupt) + require.ErrorContains(err, "does not match verified footer") + assert.Nil(stream) } func TestBlobReaderCancellationIsTerminal(t *testing.T) { + require := Require.New(t) t.Parallel() dir := t.TempDir() content := bytes.Repeat([]byte("cancel stream"), 1<<14) writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) entry, err := writer.Append(content) - require.NoError(t, err) + require.NoError(err) final := filepath.Join(dir, writer.ID()+".pack") _, err = writer.Seal(final) - require.NoError(t, err) + require.NoError(err) reader, err := OpenReader(final, nil) - require.NoError(t, err) + require.NoError(err) ctx, cancel := context.WithCancel(context.Background()) stream, err := reader.OpenBlob(ctx, entry) - require.NoError(t, err) + require.NoError(err) buf := make([]byte, 32) _, err = stream.Read(buf) - require.NoError(t, err) + require.NoError(err) cancel() _, err = stream.Read(buf) - require.ErrorIs(t, err, context.Canceled) - require.ErrorIs(t, stream.Verify(), context.Canceled) - require.ErrorIs(t, stream.Close(), context.Canceled) - require.NoError(t, reader.Close()) + require.ErrorIs(err, context.Canceled) + require.ErrorIs(stream.Verify(), context.Canceled) + require.ErrorIs(stream.Close(), context.Canceled) + require.NoError(reader.Close()) } func TestBlobReaderReportsTerminalIntegrityErrors(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) t.Parallel() dir := t.TempDir() content := []byte("content delivered before terminal verification") writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) entry, err := writer.Append(content) - require.NoError(t, err) + require.NoError(err) final := filepath.Join(dir, writer.ID()+".pack") _, err = writer.Seal(final) - require.NoError(t, err) + require.NoError(err) f, err := os.OpenFile(final, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) _, err = f.WriteAt([]byte{'X'}, int64(entry.Offset)) - require.NoError(t, err) - require.NoError(t, f.Close()) + require.NoError(err) + require.NoError(f.Close()) reader, err := OpenReader(final, nil) - require.NoError(t, err) + require.NoError(err) stream, err := reader.OpenBlob(context.Background(), reader.Entries()[0]) - require.NoError(t, err) + require.NoError(err) got, err := io.ReadAll(stream) - require.ErrorIs(t, err, ErrCorrupt) - assert.Len(t, got, len(content)) - assert.False(t, stream.Verified()) - require.ErrorIs(t, stream.Close(), ErrVerificationIncomplete) - require.NoError(t, reader.Close()) + require.ErrorIs(err, ErrCorrupt) + assert.Len(got, len(content)) + assert.False(stream.Verified()) + require.ErrorIs(stream.Close(), ErrVerificationIncomplete) + require.NoError(reader.Close()) } func TestBlobReaderReportsCompressedDecodeFailure(t *testing.T) { + require := Require.New(t) t.Parallel() dir := t.TempDir() content := bytes.Repeat([]byte("compressed corruption "), 1<<15) writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) entry, err := writer.AppendStream(context.Background(), bytes.NewReader(content), uint64(len(content)), AppendStreamOptions{ScratchDir: dir}) - require.NoError(t, err) - require.NotZero(t, entry.Flags&BlobCompressed) + require.NoError(err) + require.NotZero(entry.Flags & BlobCompressed) final := filepath.Join(dir, writer.ID()+".pack") _, err = writer.Seal(final) - require.NoError(t, err) + require.NoError(err) f, err := os.OpenFile(final, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) corruptAt := int64(entry.Offset + entry.StoredLen/2) //nolint:gosec // test frame is small var original [1]byte _, err = f.ReadAt(original[:], corruptAt) - require.NoError(t, err) + require.NoError(err) original[0] ^= 0xff _, err = f.WriteAt(original[:], corruptAt) - require.NoError(t, err) - require.NoError(t, f.Close()) + require.NoError(err) + require.NoError(f.Close()) reader, err := OpenReader(final, nil) - require.NoError(t, err) + require.NoError(err) stream, err := reader.OpenBlob(context.Background(), reader.Entries()[0]) - require.NoError(t, err) + require.NoError(err) _, err = io.Copy(io.Discard, stream) - require.ErrorIs(t, err, ErrCorrupt) - require.ErrorIs(t, stream.Close(), ErrVerificationIncomplete) - require.NoError(t, reader.Close()) + require.ErrorIs(err, ErrCorrupt) + require.ErrorIs(stream.Close(), ErrVerificationIncomplete) + require.NoError(reader.Close()) } func TestBlobReaderDetectsHashMismatch(t *testing.T) { + require := Require.New(t) t.Parallel() dir := t.TempDir() content := []byte("hash checked at eof") writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) entry, err := writer.Append(content) - require.NoError(t, err) + require.NoError(err) final := filepath.Join(dir, writer.ID()+".pack") _, err = writer.Seal(final) - require.NoError(t, err) + require.NoError(err) forged := entry forged.ID = ComputeBlobID([]byte("different")) data, err := os.ReadFile(final) - require.NoError(t, err) + require.NoError(err) footerStart := int(entry.Offset + entry.StoredLen) rebuilt := append([]byte{}, data[:footerStart]...) rebuilt = append(rebuilt, appendPlainTrailer(encodeFooterRegion([]Entry{forged}))...) - require.NoError(t, os.WriteFile(final, rebuilt, 0o600)) + require.NoError(os.WriteFile(final, rebuilt, 0o600)) reader, err := OpenReader(final, nil) - require.NoError(t, err) + require.NoError(err) stream, err := reader.OpenBlob(context.Background(), reader.Entries()[0]) - require.NoError(t, err) + require.NoError(err) got, err := io.ReadAll(stream) - assert.Equal(t, content, got) - require.ErrorIs(t, err, ErrBlobMismatch) - require.ErrorIs(t, stream.Verify(), ErrBlobMismatch) - require.ErrorIs(t, stream.Close(), ErrBlobMismatch) + Assert.Equal(t, content, got) + require.ErrorIs(err, ErrBlobMismatch) + require.ErrorIs(stream.Verify(), ErrBlobMismatch) + require.ErrorIs(stream.Close(), ErrBlobMismatch) _, repeatedErr := stream.Read(make([]byte, 1)) - require.EqualError(t, repeatedErr, err.Error()) - require.EqualError(t, stream.Verify(), err.Error()) - require.NoError(t, reader.Close()) + require.EqualError(repeatedErr, err.Error()) + require.EqualError(stream.Verify(), err.Error()) + require.NoError(reader.Close()) } func TestStreamingEncryptedV1IsUnsupported(t *testing.T) { + require := Require.New(t) t.Parallel() key := [32]byte{1} crypter, err := NewCrypter(key) - require.NoError(t, err) + require.NoError(err) dir := t.TempDir() writer, err := NewWriter(dir, WriterOptions{Crypter: crypter}) - require.NoError(t, err) + require.NoError(err) t.Cleanup(func() { _ = writer.Abort() }) _, err = writer.AppendStream(context.Background(), strings.NewReader("secret"), 6, AppendStreamOptions{}) - require.ErrorIs(t, err, ErrStreamUnsupported) + require.ErrorIs(err, ErrStreamUnsupported) entry, err := writer.Append([]byte("buffered secret")) - require.NoError(t, err) + require.NoError(err) final := filepath.Join(dir, writer.ID()+".pack") _, err = writer.Seal(final) - require.NoError(t, err) + require.NoError(err) reader, err := OpenReader(final, crypter) - require.NoError(t, err) + require.NoError(err) _, err = reader.OpenBlob(context.Background(), entry) - require.ErrorIs(t, err, ErrStreamUnsupported) - require.NoError(t, reader.Close()) + require.ErrorIs(err, ErrStreamUnsupported) + require.NoError(reader.Close()) } func TestOpenReaderWithOptionsEnforcesLimits(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) t.Parallel() dir := t.TempDir() writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) _, err = writer.Append([]byte("bounded content")) - require.NoError(t, err) + require.NoError(err) _, err = writer.Append([]byte("second")) - require.NoError(t, err) + require.NoError(err) final := filepath.Join(dir, writer.ID()+".pack") _, err = writer.Seal(final) - require.NoError(t, err) + require.NoError(err) _, err = OpenReaderWithOptions(final, nil, ReaderOptions{Limits: ReaderLimits{RawBytes: 1}}) var limitErr *StreamLimitError - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, StreamLimitRawBytes, limitErr.Dimension) + require.ErrorAs(err, &limitErr) + assert.Equal(StreamLimitRawBytes, limitErr.Dimension) info, err := os.Stat(final) - require.NoError(t, err) + require.NoError(err) _, err = OpenReaderWithOptions(final, nil, ReaderOptions{Limits: ReaderLimits{ContainerBytes: uint64(info.Size() - 1)}}) //nolint:gosec - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, StreamLimitContainerBytes, limitErr.Dimension) + require.ErrorAs(err, &limitErr) + assert.Equal(StreamLimitContainerBytes, limitErr.Dimension) _, err = OpenReaderWithOptions(final, nil, ReaderOptions{Limits: ReaderLimits{Entries: 1}}) - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, StreamLimitEntryCount, limitErr.Dimension) + require.ErrorAs(err, &limitErr) + assert.Equal(StreamLimitEntryCount, limitErr.Dimension) _, err = OpenReaderWithOptions(final, nil, ReaderOptions{Limits: ReaderLimits{FooterBytes: 1}}) - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, StreamLimitFooterBytes, limitErr.Dimension) + require.ErrorAs(err, &limitErr) + assert.Equal(StreamLimitFooterBytes, limitErr.Dimension) _, err = OpenReaderWithOptions(final, nil, ReaderOptions{Limits: ReaderLimits{StoredBytes: 1}}) - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, StreamLimitStoredBytes, limitErr.Dimension) + require.ErrorAs(err, &limitErr) + assert.Equal(StreamLimitStoredBytes, limitErr.Dimension) } func TestBlobReaderEnforcesZstdWindowLimit(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) t.Parallel() dir := t.TempDir() content := bytes.Repeat([]byte("legacy-single-segment"), 1<<16) writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) entry, err := writer.Append(content) - require.NoError(t, err) - require.NotZero(t, entry.Flags&BlobCompressed) + require.NoError(err) + require.NotZero(entry.Flags & BlobCompressed) final := filepath.Join(dir, writer.ID()+".pack") _, err = writer.Seal(final) - require.NoError(t, err) + require.NoError(err) reader, err := OpenReaderWithOptions(final, nil, ReaderOptions{Limits: ReaderLimits{WindowBytes: 64 << 10}}) - require.NoError(t, err) + require.NoError(err) _, err = reader.OpenBlob(context.Background(), reader.Entries()[0]) var limitErr *StreamLimitError - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, StreamLimitWindowBytes, limitErr.Dimension) - assert.Greater(t, limitErr.Actual, limitErr.Limit) - require.NoError(t, reader.Close()) + require.ErrorAs(err, &limitErr) + assert.Equal(StreamLimitWindowBytes, limitErr.Dimension) + assert.Greater(limitErr.Actual, limitErr.Limit) + require.NoError(reader.Close()) } func TestBlobReaderReadsFrozenV1Fixture(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) t.Parallel() path := filepath.Join("..", "packstore", "testdata", "msgvault-v1", "01kx758hcw5gnkdz233217fd9a.mvpack") reader, err := OpenReader(path, nil) - require.NoError(t, err) + require.NoError(err) compressed := false for _, entry := range reader.Entries() { compressed = compressed || entry.Flags&BlobCompressed != 0 stream, openErr := reader.OpenBlob(context.Background(), entry) - require.NoError(t, openErr) - require.NoError(t, stream.Verify()) - assert.True(t, stream.Verified()) - require.NoError(t, stream.Close()) + require.NoError(openErr) + require.NoError(stream.Verify()) + assert.True(stream.Verified()) + require.NoError(stream.Close()) } - assert.True(t, compressed) - require.NoError(t, reader.Close()) + assert.True(compressed) + require.NoError(reader.Close()) } func TestOpenBlobHonorsCancellation(t *testing.T) { + require := Require.New(t) t.Parallel() dir := t.TempDir() writer, err := NewWriter(dir, WriterOptions{}) - require.NoError(t, err) + require.NoError(err) entry, err := writer.Append([]byte("cancelled")) - require.NoError(t, err) + require.NoError(err) final := filepath.Join(dir, writer.ID()+".pack") _, err = writer.Seal(final) - require.NoError(t, err) + require.NoError(err) reader, err := OpenReader(final, nil) - require.NoError(t, err) + require.NoError(err) defer func() { _ = reader.Close() }() ctx, cancel := context.WithCancel(context.Background()) cancel() _, err = reader.OpenBlob(ctx, entry) - require.ErrorIs(t, err, context.Canceled) + require.ErrorIs(err, context.Canceled) } diff --git a/packstore/filesystem_backend_test.go b/packstore/filesystem_backend_test.go index a162fe4..c82b60b 100644 --- a/packstore/filesystem_backend_test.go +++ b/packstore/filesystem_backend_test.go @@ -15,14 +15,14 @@ import ( "testing" "github.com/klauspost/compress/zstd" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) func TestFilesystemBackendPublishesAndInventoriesCanonicalObjects(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) ctx := context.Background() backend := attachedFilesystemBackend(t, "archive", "epoch-1") looseContent := []byte("inventory loose content") @@ -106,8 +106,8 @@ func TestFilesystemBackendLoosePublicationReadsOnlyOneByteBeyondExpectedSize(t * } for _, tt := range operations { t.Run(tt.name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) backend := attachedFilesystemBackend(t, "archive", "epoch-1") source := &countingPackReader{reader: bytes.NewReader(sourceBytes)} @@ -122,31 +122,34 @@ func TestFilesystemBackendLoosePublicationReadsOnlyOneByteBeyondExpectedSize(t * } func TestFilesystemBackendInventoryRejectsCanonicalSymlink(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) if runtime.GOOS == "windows" { t.Skip("symlink creation requires privileges on Windows") } backend := attachedFilesystemBackend(t, "archive", "epoch-1") hash := hashForTest([]byte("unsafe inventory entry")) path := backend.Layout().LoosePath(hash) - require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + require.NoError(os.MkdirAll(filepath.Dir(path), 0o700)) target := filepath.Join(t.TempDir(), "target") - require.NoError(t, os.WriteFile(target, []byte("not authoritative"), 0o600)) - require.NoError(t, os.Symlink(target, path)) + require.NoError(os.WriteFile(target, []byte("not authoritative"), 0o600)) + require.NoError(os.Symlink(target, path)) relative, err := filepath.Rel(backend.Layout().Root(), path) - require.NoError(t, err) + require.NoError(err) page, err := backend.Inventory(context.Background(), "") - require.NoError(t, err) - assert.Empty(t, page.Objects) - assert.Equal(t, []string{filepath.ToSlash(relative)}, page.Unknown) + require.NoError(err) + assert.Empty(page.Objects) + assert.Equal([]string{filepath.ToSlash(relative)}, page.Unknown) } func TestFilesystemBackendInventoryFollowsConfiguredSymlinkRoot(t *testing.T) { + assert := Assert.New(t) if runtime.GOOS == "windows" { t.Skip("symlink creation requires privileges on Windows") } - require := require.New(t) + require := Require.New(t) actualRoot := t.TempDir() linkedRoot := filepath.Join(t.TempDir(), "store") require.NoError(os.Symlink(actualRoot, linkedRoot)) @@ -159,7 +162,7 @@ func TestFilesystemBackendInventoryFollowsConfiguredSymlinkRoot(t *testing.T) { t.Cleanup(func() { require.NoError(backend.Close()) }) empty, err := backend.NamespaceEmpty(context.Background()) require.NoError(err) - assert.True(t, empty) + assert.True(empty) content := []byte("symlink-root inventory content") hash := hashForTest(content) require.NoError(os.MkdirAll(filepath.Dir(layout.LoosePath(hash)), 0o700)) @@ -170,60 +173,63 @@ func TestFilesystemBackendInventoryFollowsConfiguredSymlinkRoot(t *testing.T) { empty, err = backend.NamespaceEmpty(context.Background()) require.NoError(err) - assert.Equal(t, []InventoryObject{{ + assert.Equal([]InventoryObject{{ Ref: ObjectRef{LooseHash: hash, LooseEncoding: LooseEncodingRaw}, StoredSize: int64(len(content)), }}, page.Objects) - assert.Empty(t, page.Unknown) - assert.False(t, empty) + assert.Empty(page.Unknown) + assert.False(empty) } func TestFilesystemWalkPropagatesMissingEntryAfterRootResolution(t *testing.T) { layout := layoutForStoreTest(t) backend, err := NewFilesystemBackend(layout, FilesystemBackendOptions{}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, backend.Close()) }) + Require.NoError(t, err) + t.Cleanup(func() { Require.NoError(t, backend.Close()) }) originalWalk := walkFilesystemTree walkFilesystemTree = func(string, fs.WalkDirFunc) error { return fs.ErrNotExist } t.Cleanup(func() { walkFilesystemTree = originalWalk }) _, err = backend.Inventory(context.Background(), "") - require.ErrorIs(t, err, fs.ErrNotExist) + Require.ErrorIs(t, err, fs.ErrNotExist) _, err = backend.NamespaceEmpty(context.Background()) - require.ErrorIs(t, err, fs.ErrNotExist) + Require.ErrorIs(t, err, fs.ErrNotExist) } func TestFilesystemWalkTreatsInitiallyMissingRootAsEmpty(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout, err := NewLayout(filepath.Join(t.TempDir(), "missing"), LayoutOptions{ Staging: StagingStoreDirectory, StagingDir: "tmp", }) - require.NoError(t, err) + require.NoError(err) backend, err := NewFilesystemBackend(layout, FilesystemBackendOptions{}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, backend.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(backend.Close()) }) page, err := backend.Inventory(context.Background(), "") - require.NoError(t, err) + require.NoError(err) empty, err := backend.NamespaceEmpty(context.Background()) - require.NoError(t, err) + require.NoError(err) - assert.Empty(t, page.Objects) - assert.Empty(t, page.Unknown) - assert.True(t, empty) + assert.Empty(page.Objects) + assert.Empty(page.Unknown) + assert.True(empty) } func TestFilesystemBackendSeekablePackHonorsCancellation(t *testing.T) { + require := Require.New(t) backend := attachedFilesystemBackend(t, "archive", "epoch-1") packPath, packID, entries := buildBackendPackSource( t, bytes.Repeat([]byte("cancel seekable pack"), 128<<10), ) source, err := os.Open(packPath) - require.NoError(t, err) + require.NoError(err) _, err = backend.PublishPack(context.Background(), packID, source, PublishOptions{}) - require.NoError(t, errors.Join(err, source.Close())) - require.Len(t, entries, 1) + require.NoError(errors.Join(err, source.Close())) + require.Len(entries, 1) indexed, err := indexEntryFromPack(entries[0], packID) - require.NoError(t, err) + require.NoError(err) store, err := NewMultiStore( staticLocationResolver{resolution: Resolution{ Member: true, @@ -234,8 +240,8 @@ func TestFilesystemBackendSeekablePackHonorsCancellation(t *testing.T) { staticBackendRegistry{"archive": backend}, MultiStoreOptions{}, ) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, store.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(store.Close()) }) ctx, cancel := context.WithCancel(context.Background()) cancel() @@ -244,17 +250,18 @@ func TestFilesystemBackendSeekablePackHonorsCancellation(t *testing.T) { _ = reader.Close() } - require.ErrorIs(t, err, context.Canceled) + require.ErrorIs(err, context.Canceled) } func TestVerifyFilesystemPackHashHonorsCancellation(t *testing.T) { + require := Require.New(t) packPath, packID, _ := buildBackendPackSource( t, bytes.Repeat([]byte("cancel canonical pack hash"), 4096), ) file, err := os.Open(packPath) - require.NoError(t, err) + require.NoError(err) info, err := file.Stat() - require.NoError(t, err) + require.NoError(err) ctx, cancel := context.WithCancel(context.Background()) cancel() @@ -262,8 +269,8 @@ func TestVerifyFilesystemPackHashHonorsCancellation(t *testing.T) { ctx, file, packID, DefaultLimits(), info.Size(), [sha256.Size]byte{}, ) - require.ErrorIs(t, err, context.Canceled) - assert.NotErrorIs(t, err, ErrContentMismatch) + require.ErrorIs(err, context.Canceled) + Assert.NotErrorIs(t, err, ErrContentMismatch) } func TestClassifyFilesystemPackVerificationPreservesCancellation(t *testing.T) { @@ -277,13 +284,14 @@ func TestClassifyFilesystemPackVerificationPreservesCancellation(t *testing.T) { t.Run(test.name, func(t *testing.T) { got := classifyFilesystemPackVerificationError(test.err) - require.ErrorIs(t, got, test.err) - assert.NotErrorIs(t, got, ErrPhysicalCorrupt) + Require.ErrorIs(t, got, test.err) + Assert.NotErrorIs(t, got, ErrPhysicalCorrupt) }) } } func TestFilesystemBackendDurablePackPublicationSyncsFreshHierarchy(t *testing.T) { + require := Require.New(t) backend := attachedFilesystemBackend(t, "archive", "epoch-1") packPath, packID, _ := buildBackendPackSource(t, []byte("durable pack hierarchy")) originalSyncDir := syncFilesystemRootDir @@ -294,18 +302,18 @@ func TestFilesystemBackendDurablePackPublicationSyncsFreshHierarchy(t *testing.T } t.Cleanup(func() { syncFilesystemRootDir = originalSyncDir }) source, err := os.Open(packPath) - require.NoError(t, err) + require.NoError(err) _, err = backend.PublishPack( context.Background(), packID, source, PublishOptions{Durability: DurablePublication}, ) - require.NoError(t, errors.Join(err, source.Close())) + require.NoError(errors.Join(err, source.Close())) resolvedRoot, err := filepath.EvalSymlinks(backend.Layout().Root()) - require.NoError(t, err) + require.NoError(err) packsDir := filepath.Join(resolvedRoot, "packs") - assert.Equal(t, []string{ + Assert.Equal(t, []string{ resolvedRoot, packsDir, packsDir, @@ -332,8 +340,8 @@ func TestFilesystemBackendPackPublicationSyncsStagingOnlyWhenDurable(t *testing. {name: "durable", durability: DurablePublication, wantSync: true, wantCalls: 1}, } { t.Run(test.name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) syncCalls = 0 backend := attachedFilesystemBackend(t, "archive", "epoch-1") packPath, packID, _ := buildBackendPackSource(t, []byte(test.name+" staging sync")) @@ -359,18 +367,18 @@ func TestFilesystemBackendPublishPackRejectsInvalidDurabilityBeforeWrite(t *test backend := attachedFilesystemBackend(t, "archive", "epoch-1") packPath, packID, _ := buildBackendPackSource(t, []byte("invalid durability")) source, err := os.Open(packPath) - require.NoError(t, err) + Require.NoError(t, err) _, err = backend.PublishPack( context.Background(), packID, source, PublishOptions{Durability: Durability(99)}, ) - require.ErrorIs(t, errors.Join(err, source.Close()), ErrInvalidPolicy) - assert.NoFileExists(t, backend.Layout().PackPath(packID)) + Require.ErrorIs(t, errors.Join(err, source.Close()), ErrInvalidPolicy) + Assert.NoFileExists(t, backend.Layout().PackPath(packID)) } func TestFilesystemBackendRejectsDifferentPackAtExistingIdentity(t *testing.T) { - require := require.New(t) + require := Require.New(t) ctx := context.Background() backend := attachedFilesystemBackend(t, "archive", "epoch-1") firstPath, firstID, _ := buildBackendPackSource(t, []byte("first pack content")) @@ -387,6 +395,7 @@ func TestFilesystemBackendRejectsDifferentPackAtExistingIdentity(t *testing.T) { } func TestFilesystemBackendPublishPackRejectsDecoderWindow(t *testing.T) { + require := Require.New(t) content := bytes.Repeat([]byte("filesystem window policy "), 1<<15) packPath, packID := buildEncodedBackendPackSource( t, @@ -400,17 +409,17 @@ func TestFilesystemBackendPublishPackRejectsDecoderWindow(t *testing.T) { backend, err := NewFilesystemBackend(layout, FilesystemBackendOptions{ Limits: limits, }) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, backend.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(backend.Close()) }) owner := Ownership{ Format: OwnershipFormatV1, Vault: "test-vault", Store: "archive", Epoch: "epoch-1", } - require.NoError(t, backend.ReplaceOwnership(context.Background(), owner, nil)) + require.NoError(backend.ReplaceOwnership(context.Background(), owner, nil)) source, err := os.Open(packPath) - require.NoError(t, err) + require.NoError(err) _, err = backend.PublishPack( context.Background(), @@ -418,12 +427,12 @@ func TestFilesystemBackendPublishPackRejectsDecoderWindow(t *testing.T) { source, PublishOptions{}, ) - require.NoError(t, source.Close()) + require.NoError(source.Close()) - require.ErrorIs(t, err, ErrBlobTooLarge) + require.ErrorIs(err, ErrBlobTooLarge) var limit *LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, LimitBlobWindowBytes, limit.Dimension) + require.ErrorAs(err, &limit) + Assert.Equal(t, LimitBlobWindowBytes, limit.Dimension) } func TestFilesystemBackendPublishPackRejectsDecodedLengthMismatch(t *testing.T) { @@ -437,6 +446,7 @@ func TestFilesystemBackendPublishPackRejectsDecodedLengthMismatch(t *testing.T) } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + require := Require.New(t) packPath, packID := buildEncodedBackendPackSource( t, content, @@ -445,7 +455,7 @@ func TestFilesystemBackendPublishPackRejectsDecodedLengthMismatch(t *testing.T) ) backend := attachedFilesystemBackend(t, "archive", "epoch-1") source, err := os.Open(packPath) - require.NoError(t, err) + require.NoError(err) _, err = backend.PublishPack( context.Background(), @@ -453,30 +463,32 @@ func TestFilesystemBackendPublishPackRejectsDecodedLengthMismatch(t *testing.T) source, PublishOptions{}, ) - require.NoError(t, source.Close()) + require.NoError(source.Close()) - require.ErrorIs(t, err, pack.ErrCorrupt) - require.ErrorIs(t, err, ErrPhysicalCorrupt) + require.ErrorIs(err, pack.ErrCorrupt) + require.ErrorIs(err, ErrPhysicalCorrupt) }) } } func TestFilesystemBackendPublishPackRejectsKnownConfiguredLimitBeforeRead(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) limits := DefaultLimits() limits.PackBytes = 8 layout := layoutForStoreTest(t) backend, err := NewFilesystemBackend(layout, FilesystemBackendOptions{ Limits: limits, }) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, backend.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(backend.Close()) }) owner := Ownership{ Format: OwnershipFormatV1, Vault: "test-vault", Store: "archive", Epoch: "epoch-1", } - require.NoError(t, backend.ReplaceOwnership(context.Background(), owner, nil)) + require.NoError(backend.ReplaceOwnership(context.Background(), owner, nil)) packID := pack.NewPackID() source := &countingPackReader{ reader: bytes.NewReader(bytes.Repeat([]byte("x"), 9)), @@ -489,30 +501,32 @@ func TestFilesystemBackendPublishPackRejectsKnownConfiguredLimitBeforeRead(t *te PublishOptions{ExpectedSize: 9, SizeKnown: true, MaxBytes: 100}, ) - require.ErrorIs(t, err, ErrBlobTooLarge) + require.ErrorIs(err, ErrBlobTooLarge) var limit *LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, LimitPackContainerBytes, limit.Dimension) - assert.Zero(t, source.reads) - assert.NoFileExists(t, layout.PackPath(packID)) + require.ErrorAs(err, &limit) + assert.Equal(LimitPackContainerBytes, limit.Dimension) + assert.Zero(source.reads) + assert.NoFileExists(layout.PackPath(packID)) } func TestFilesystemBackendPublishPackCapsCallerLimitBeforeCanonicalWrite(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) limits := DefaultLimits() limits.PackBytes = 8 layout := layoutForStoreTest(t) backend, err := NewFilesystemBackend(layout, FilesystemBackendOptions{ Limits: limits, }) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, backend.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(backend.Close()) }) owner := Ownership{ Format: OwnershipFormatV1, Vault: "test-vault", Store: "archive", Epoch: "epoch-1", } - require.NoError(t, backend.ReplaceOwnership(context.Background(), owner, nil)) + require.NoError(backend.ReplaceOwnership(context.Background(), owner, nil)) packID := pack.NewPackID() _, err = backend.PublishPack( @@ -522,17 +536,17 @@ func TestFilesystemBackendPublishPackCapsCallerLimitBeforeCanonicalWrite(t *test PublishOptions{MaxBytes: 100}, ) - require.ErrorIs(t, err, ErrBlobTooLarge) + require.ErrorIs(err, ErrBlobTooLarge) var limit *LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, LimitPackContainerBytes, limit.Dimension) - assert.NoFileExists(t, layout.PackPath(packID)) + require.ErrorAs(err, &limit) + assert.Equal(LimitPackContainerBytes, limit.Dimension) + assert.NoFileExists(layout.PackPath(packID)) } func TestFilesystemBackendPublishPackRejectsExactSizeMismatchBeforeCanonicalWrite(t *testing.T) { packPath, packID, _ := buildBackendPackSource(t, []byte("exact size publication")) info, err := os.Stat(packPath) - require.NoError(t, err) + Require.NoError(t, err) for _, tt := range []struct { name string expectedSize int64 @@ -541,9 +555,10 @@ func TestFilesystemBackendPublishPackRejectsExactSizeMismatchBeforeCanonicalWrit {name: "source is overlong", expectedSize: info.Size() - 1}, } { t.Run(tt.name, func(t *testing.T) { + require := Require.New(t) backend := attachedFilesystemBackend(t, "archive", "epoch-1") source, err := os.Open(packPath) - require.NoError(t, err) + require.NoError(err) _, err = backend.PublishPack( context.Background(), @@ -551,10 +566,10 @@ func TestFilesystemBackendPublishPackRejectsExactSizeMismatchBeforeCanonicalWrit source, PublishOptions{ExpectedSize: tt.expectedSize, SizeKnown: true}, ) - require.NoError(t, source.Close()) + require.NoError(source.Close()) - require.ErrorIs(t, err, ErrContentMismatch) - assert.NoFileExists(t, backend.Layout().PackPath(packID)) + require.ErrorIs(err, ErrContentMismatch) + Assert.NoFileExists(t, backend.Layout().PackPath(packID)) }) } } @@ -570,60 +585,63 @@ func TestFilesystemBackendPublishPackRejectsMalformedBeforeCanonicalWrite(t *tes PublishOptions{}, ) - require.ErrorIs(t, err, pack.ErrBadMagic) - require.ErrorIs(t, err, ErrPhysicalCorrupt) - assert.NoFileExists(t, backend.Layout().PackPath(packID)) + Require.ErrorIs(t, err, pack.ErrBadMagic) + Require.ErrorIs(t, err, ErrPhysicalCorrupt) + Assert.NoFileExists(t, backend.Layout().PackPath(packID)) } func TestFilesystemBackendPublishPackRejectsDuplicateBlobIDs(t *testing.T) { + require := Require.New(t) backend := attachedFilesystemBackend(t, "archive", "epoch-1") writer, err := pack.NewWriter(t.TempDir(), pack.WriterOptions{}) - require.NoError(t, err) + require.NoError(err) content := []byte("duplicate publication entry") _, err = writer.Append(content) - require.NoError(t, err) + require.NoError(err) _, err = writer.Append(content) - require.NoError(t, err) + require.NoError(err) packID := writer.ID() path := filepath.Join(t.TempDir(), packID+PackExt) _, err = writer.Seal(path) - require.NoError(t, err) + require.NoError(err) source, err := os.Open(path) - require.NoError(t, err) + require.NoError(err) _, err = backend.PublishPack(context.Background(), packID, source, PublishOptions{}) - require.NoError(t, source.Close()) + require.NoError(source.Close()) - require.ErrorIs(t, err, pack.ErrCorrupt) - assert.NoFileExists(t, backend.Layout().PackPath(packID)) + require.ErrorIs(err, pack.ErrCorrupt) + Assert.NoFileExists(t, backend.Layout().PackPath(packID)) } func TestFilesystemBackendPublishPackRejectsForgedBlobLimitBeforeCanonicalWrite(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) limits := DefaultLimits() limits.BlobBytes = 16 packPath, packID := buildEncodedBackendPackSource(t, []byte("x"), 17, 0) layout := layoutForStoreTest(t) backend, err := NewFilesystemBackend(layout, FilesystemBackendOptions{Limits: limits}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, backend.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(backend.Close()) }) owner := Ownership{ Format: OwnershipFormatV1, Vault: "test-vault", Store: "archive", Epoch: "epoch-1", } - require.NoError(t, backend.ReplaceOwnership(context.Background(), owner, nil)) + require.NoError(backend.ReplaceOwnership(context.Background(), owner, nil)) source, err := os.Open(packPath) - require.NoError(t, err) + require.NoError(err) _, err = backend.PublishPack(context.Background(), packID, source, PublishOptions{}) - require.NoError(t, source.Close()) + require.NoError(source.Close()) - require.ErrorIs(t, err, ErrBlobTooLarge) + require.ErrorIs(err, ErrBlobTooLarge) var limit *LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, LimitBlobRawBytes, limit.Dimension) - assert.NoFileExists(t, layout.PackPath(packID)) + require.ErrorAs(err, &limit) + assert.Equal(LimitBlobRawBytes, limit.Dimension) + assert.NoFileExists(layout.PackPath(packID)) } func TestFilesystemBackendPublishPackEnforcesZeroBlobLimit(t *testing.T) { @@ -637,56 +655,60 @@ func TestFilesystemBackendPublishPackEnforcesZeroBlobLimit(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) limits := DefaultLimits() limits.BlobBytes = 0 packPath, packID, _ := buildBackendPackSource(t, tt.content) layout := layoutForStoreTest(t) backend, err := NewFilesystemBackend(layout, FilesystemBackendOptions{Limits: limits}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, backend.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(backend.Close()) }) owner := Ownership{ Format: OwnershipFormatV1, Vault: "test-vault", Store: "archive", Epoch: "epoch-1", } - require.NoError(t, backend.ReplaceOwnership(context.Background(), owner, nil)) + require.NoError(backend.ReplaceOwnership(context.Background(), owner, nil)) source, err := os.Open(packPath) - require.NoError(t, err) + require.NoError(err) _, err = backend.PublishPack(context.Background(), packID, source, PublishOptions{}) - require.NoError(t, source.Close()) + require.NoError(source.Close()) if !tt.wantLimit { - require.NoError(t, err) - assert.FileExists(t, layout.PackPath(packID)) + require.NoError(err) + assert.FileExists(layout.PackPath(packID)) return } - require.ErrorIs(t, err, ErrBlobTooLarge) + require.ErrorIs(err, ErrBlobTooLarge) var limit *LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, LimitBlobRawBytes, limit.Dimension) - assert.Equal(t, uint64(1), limit.Actual) - assert.Zero(t, limit.Limit) - assert.NoFileExists(t, layout.PackPath(packID)) + require.ErrorAs(err, &limit) + assert.Equal(LimitBlobRawBytes, limit.Dimension) + assert.Equal(uint64(1), limit.Actual) + assert.Zero(limit.Limit) + assert.NoFileExists(layout.PackPath(packID)) }) } } func TestFilesystemBackendPublishPackPreflightsAllEntryLimitsBeforeIntegrity(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) staging := t.TempDir() writer, err := pack.NewWriter(staging, pack.WriterOptions{}) - require.NoError(t, err) + require.NoError(err) _, err = writer.Append(nil) - require.NoError(t, err) + require.NoError(err) _, err = writer.Append([]byte("x")) - require.NoError(t, err) + require.NoError(err) packID := writer.ID() packPath := filepath.Join(staging, packID+PackExt) _, err = writer.Seal(packPath) - require.NoError(t, err) + require.NoError(err) packBytes, err := os.ReadFile(packPath) - require.NoError(t, err) + require.NoError(err) trailerOffset := len(packBytes) - plainPackTrailerSize footerLen := int(binary.LittleEndian.Uint32(packBytes[trailerOffset:])) footerOffset := trailerOffset - footerLen @@ -696,35 +718,35 @@ func TestFilesystemBackendPublishPackPreflightsAllEntryLimitsBeforeIntegrity(t * _, _ = footerDigest.Write(packBytes[footerOffset:trailerOffset]) _, _ = footerDigest.Write(packBytes[trailerOffset : trailerOffset+4]) copy(packBytes[trailerOffset+4:trailerOffset+36], footerDigest.Sum(nil)) - require.NoError(t, os.WriteFile(packPath, packBytes, 0o600)) + require.NoError(os.WriteFile(packPath, packBytes, 0o600)) limits := DefaultLimits() limits.BlobBytes = 0 layout := layoutForStoreTest(t) backend, err := NewFilesystemBackend(layout, FilesystemBackendOptions{Limits: limits}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, backend.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(backend.Close()) }) owner := Ownership{ Format: OwnershipFormatV1, Vault: "test-vault", Store: "archive", Epoch: "epoch-1", } - require.NoError(t, backend.ReplaceOwnership(context.Background(), owner, nil)) + require.NoError(backend.ReplaceOwnership(context.Background(), owner, nil)) source, err := os.Open(packPath) - require.NoError(t, err) + require.NoError(err) _, err = backend.PublishPack(context.Background(), packID, source, PublishOptions{}) - require.NoError(t, source.Close()) + require.NoError(source.Close()) - require.ErrorIs(t, err, ErrBlobTooLarge) - require.NotErrorIs(t, err, pack.ErrBlobMismatch) + require.ErrorIs(err, ErrBlobTooLarge) + require.NotErrorIs(err, pack.ErrBlobMismatch) var limit *LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, LimitBlobRawBytes, limit.Dimension) - assert.Equal(t, uint64(1), limit.Actual) - assert.Zero(t, limit.Limit) - assert.NoFileExists(t, layout.PackPath(packID)) + require.ErrorAs(err, &limit) + assert.Equal(LimitBlobRawBytes, limit.Dimension) + assert.Equal(uint64(1), limit.Actual) + assert.Zero(limit.Limit) + assert.NoFileExists(layout.PackPath(packID)) } func TestCopyBoundedContextAcceptsMaxInt64Limit(t *testing.T) { @@ -737,14 +759,14 @@ func TestCopyBoundedContextAcceptsMaxInt64Limit(t *testing.T) { math.MaxInt64, ) - require.NoError(t, err) - assert.Equal(t, int64(len("bounded content")), written) - assert.Equal(t, "bounded content", destination.String()) + Require.NoError(t, err) + Assert.Equal(t, int64(len("bounded content")), written) + Assert.Equal(t, "bounded content", destination.String()) } func TestFilesystemBackendUsesAndRetiresExactLooseRepresentation(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) ctx := context.Background() backend := attachedFilesystemBackend(t, "archive", "epoch-1") content := bytes.Repeat([]byte("compressible authority "), 4096) @@ -802,17 +824,19 @@ func TestFilesystemBackendRejectsAmbiguousLooseLocation(t *testing.T) { bytes.NewReader(content), PublishOptions{ExpectedSize: int64(len(content)), SizeKnown: true}, ) - require.NoError(t, err) + Require.NoError(t, err) stream, _, err := backend.OpenLoose(ctx, hash, LooseLocation{}) if stream != nil { t.Cleanup(func() { _ = stream.Close() }) } - require.ErrorIs(t, err, ErrInvalidPolicy) + Require.ErrorIs(t, err, ErrInvalidPolicy) } func TestFilesystemBackendRepairLooseOverwritesCorruptCanonical(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) ctx := context.Background() backend := attachedFilesystemBackend(t, "archive", "epoch-1") content := []byte("trusted repair content") @@ -821,8 +845,8 @@ func TestFilesystemBackendRepairLooseOverwritesCorruptCanonical(t *testing.T) { ctx, hash, bytes.NewReader(content), PublishOptions{ExpectedSize: int64(len(content)), SizeKnown: true}, ) - require.NoError(t, err) - require.NoError(t, os.WriteFile( + require.NoError(err) + require.NoError(os.WriteFile( backend.Layout().LoosePath(hash), []byte("corrupt"), 0o600, )) @@ -830,15 +854,15 @@ func TestFilesystemBackendRepairLooseOverwritesCorruptCanonical(t *testing.T) { ctx, hash, bytes.NewReader(content), PublishOptions{ExpectedSize: int64(len(content)), SizeKnown: true}, ) - require.NoError(t, err) - assert.NotEqual(t, published.Generation, repaired.Generation) - assert.False(t, repaired.Created) + require.NoError(err) + assert.NotEqual(published.Generation, repaired.Generation) + assert.False(repaired.Created) stream, _, err := backend.OpenLoose(ctx, hash, repaired.Location) - require.NoError(t, err) + require.NoError(err) got, err := io.ReadAll(stream) - require.NoError(t, err) - require.NoError(t, stream.Close()) - assert.Equal(t, content, got) + require.NoError(err) + require.NoError(stream.Close()) + assert.Equal(content, got) } func TestFilesystemBackendClassifiesTerminalStreamIntegrityErrors(t *testing.T) { @@ -873,7 +897,7 @@ func TestFilesystemBackendClassifiesTerminalStreamIntegrityErrors(t *testing.T) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.invoke(&physicalVerifiedStream{stream: tt.stream}) - require.ErrorIs(t, err, ErrPhysicalCorrupt) + Require.ErrorIs(t, err, ErrPhysicalCorrupt) }) } } @@ -885,11 +909,12 @@ func TestFilesystemBackendDoesNotClassifyIncompleteStreamCloseAsCorrupt(t *testi err := stream.Close() - require.ErrorIs(t, err, pack.ErrVerificationIncomplete) - require.NotErrorIs(t, err, ErrPhysicalCorrupt) + Require.ErrorIs(t, err, pack.ErrVerificationIncomplete) + Require.NotErrorIs(t, err, ErrPhysicalCorrupt) } func TestFilesystemBackendPreservesClosedStreamLifecycleErrors(t *testing.T) { + require := Require.New(t) ctx := context.Background() backend := attachedFilesystemBackend(t, "archive", "epoch-1") content := []byte("early closed physical stream") @@ -900,17 +925,17 @@ func TestFilesystemBackendPreservesClosedStreamLifecycleErrors(t *testing.T) { bytes.NewReader(content), PublishOptions{ExpectedSize: int64(len(content)), SizeKnown: true}, ) - require.NoError(t, err) + require.NoError(err) stream, _, err := backend.OpenLoose(ctx, hash, receipt.Location) - require.NoError(t, err) - require.ErrorIs(t, stream.Close(), pack.ErrVerificationIncomplete) + require.NoError(err) + require.ErrorIs(stream.Close(), pack.ErrVerificationIncomplete) _, readErr := stream.Read(make([]byte, 1)) verifyErr := stream.Verify() for _, err := range []error{readErr, verifyErr} { - require.ErrorIs(t, err, os.ErrClosed) - require.NotErrorIs(t, err, ErrStoreUnavailable) - require.NotErrorIs(t, err, ErrPhysicalCorrupt) + require.ErrorIs(err, os.ErrClosed) + require.NotErrorIs(err, ErrStoreUnavailable) + require.NotErrorIs(err, ErrPhysicalCorrupt) } } @@ -930,7 +955,7 @@ func TestMultiStoreFallsBackFromUnavailableFilesystemLooseObject(t *testing.T) { bytes.NewReader(content), PublishOptions{ExpectedSize: int64(len(content)), SizeKnown: true}, ) - require.NoError(t, err) + Require.NoError(t, err) operations := []struct { name string @@ -941,10 +966,10 @@ func TestMultiStoreFallsBackFromUnavailableFilesystemLooseObject(t *testing.T) { read: func(t *testing.T, store *Store) []byte { t.Helper() stream, size, err := store.OpenStream(ctx, hash) - require.NoError(t, err) - require.Equal(t, int64(len(content)), size) + Require.NoError(t, err) + Require.Equal(t, int64(len(content)), size) data, err := io.ReadAll(stream) - require.NoError(t, errors.Join(err, stream.Close())) + Require.NoError(t, errors.Join(err, stream.Close())) return data }, }, @@ -953,10 +978,10 @@ func TestMultiStoreFallsBackFromUnavailableFilesystemLooseObject(t *testing.T) { read: func(t *testing.T, store *Store) []byte { t.Helper() reader, size, err := store.Open(ctx, hash) - require.NoError(t, err) - require.Equal(t, int64(len(content)), size) + Require.NoError(t, err) + Require.Equal(t, int64(len(content)), size) data, err := io.ReadAll(reader) - require.NoError(t, errors.Join(err, reader.Close())) + Require.NoError(t, errors.Join(err, reader.Close())) return data }, }, @@ -965,14 +990,15 @@ func TestMultiStoreFallsBackFromUnavailableFilesystemLooseObject(t *testing.T) { read: func(t *testing.T, store *Store) []byte { t.Helper() data, size, err := store.ReadBounded(ctx, hash, int64(len(content))) - require.NoError(t, err) - require.Equal(t, int64(len(content)), size) + Require.NoError(t, err) + Require.Equal(t, int64(len(content)), size) return data }, }, } for _, operation := range operations { t.Run(operation.name, func(t *testing.T) { + require := Require.New(t) store, err := NewMultiStore( staticLocationResolver{resolution: Resolution{ Member: true, @@ -984,15 +1010,15 @@ func TestMultiStoreFallsBackFromUnavailableFilesystemLooseObject(t *testing.T) { staticBackendRegistry{"primary": primary, "secondary": secondary}, MultiStoreOptions{}, ) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, store.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(store.Close()) }) - assert.Equal(t, content, operation.read(t, store)) + Assert.Equal(t, content, operation.read(t, store)) _, _, err = primary.OpenLoose(ctx, hash, receipt.Location) - require.ErrorIs(t, err, ErrStoreUnavailable) - require.NotErrorIs(t, err, ErrPhysicalMissing) - require.NotErrorIs(t, err, ErrPhysicalCorrupt) - require.ErrorIs(t, err, openErr) + require.ErrorIs(err, ErrStoreUnavailable) + require.NotErrorIs(err, ErrPhysicalMissing) + require.NotErrorIs(err, ErrPhysicalCorrupt) + require.ErrorIs(err, openErr) }) } } @@ -1011,10 +1037,10 @@ func TestMultiStoreFallsBackFromPackFooterCorruption(t *testing.T) { read: func(t *testing.T, store *Store, hash Hash) []byte { t.Helper() stream, size, err := store.OpenStream(ctx, hash) - require.NoError(t, err) - require.Equal(t, int64(len(content)), size) + Require.NoError(t, err) + Require.Equal(t, int64(len(content)), size) data, err := io.ReadAll(stream) - require.NoError(t, errors.Join(err, stream.Close())) + Require.NoError(t, errors.Join(err, stream.Close())) return data }, primaryError: func(backend *FilesystemBackend, entry IndexEntry) error { @@ -1030,10 +1056,10 @@ func TestMultiStoreFallsBackFromPackFooterCorruption(t *testing.T) { read: func(t *testing.T, store *Store, hash Hash) []byte { t.Helper() reader, size, err := store.Open(ctx, hash) - require.NoError(t, err) - require.Equal(t, int64(len(content)), size) + Require.NoError(t, err) + Require.Equal(t, int64(len(content)), size) data, err := io.ReadAll(reader) - require.NoError(t, errors.Join(err, reader.Close())) + Require.NoError(t, errors.Join(err, reader.Close())) return data }, primaryError: func(backend *FilesystemBackend, entry IndexEntry) error { @@ -1049,8 +1075,8 @@ func TestMultiStoreFallsBackFromPackFooterCorruption(t *testing.T) { read: func(t *testing.T, store *Store, hash Hash) []byte { t.Helper() data, size, err := store.ReadBounded(ctx, hash, int64(len(content))) - require.NoError(t, err) - require.Equal(t, int64(len(content)), size) + Require.NoError(t, err) + Require.Equal(t, int64(len(content)), size) return data }, primaryError: func(backend *FilesystemBackend, entry IndexEntry) error { @@ -1084,27 +1110,29 @@ func TestMultiStoreFallsBackFromPackFooterCorruption(t *testing.T) { for _, condition := range conditions { t.Run(condition.name, func(t *testing.T) { + require := Require.New(t) primary := attachedFilesystemBackend(t, "primary", "primary-1") secondary := attachedFilesystemBackend(t, "secondary", "secondary-1") primaryPath, primaryID, primaryEntries := buildBackendPackSource(t, condition.primaryData) primarySource, err := os.Open(primaryPath) - require.NoError(t, err) + require.NoError(err) _, err = primary.PublishPack(ctx, primaryID, primarySource, PublishOptions{}) - require.NoError(t, errors.Join(err, primarySource.Close())) + require.NoError(errors.Join(err, primarySource.Close())) primaryEntry, err := indexEntryFromPack(primaryEntries[0], primaryID) - require.NoError(t, err) + require.NoError(err) primaryEntry = condition.entry(primaryEntry) secondaryPath, secondaryID, secondaryEntries := buildBackendPackSource(t, content) secondarySource, err := os.Open(secondaryPath) - require.NoError(t, err) + require.NoError(err) _, err = secondary.PublishPack(ctx, secondaryID, secondarySource, PublishOptions{}) - require.NoError(t, errors.Join(err, secondarySource.Close())) + require.NoError(errors.Join(err, secondarySource.Close())) secondaryEntry, err := indexEntryFromPack(secondaryEntries[0], secondaryID) - require.NoError(t, err) + require.NoError(err) for _, operation := range operations { t.Run(operation.name, func(t *testing.T) { + require := Require.New(t) store, err := NewMultiStore( staticLocationResolver{resolution: Resolution{ Member: true, @@ -1116,13 +1144,13 @@ func TestMultiStoreFallsBackFromPackFooterCorruption(t *testing.T) { staticBackendRegistry{"primary": primary, "secondary": secondary}, MultiStoreOptions{}, ) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, store.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(store.Close()) }) - assert.Equal(t, content, operation.read(t, store, hash)) + Assert.Equal(t, content, operation.read(t, store, hash)) err = operation.primaryError(primary, primaryEntry) - require.ErrorIs(t, err, ErrPhysicalCorrupt) - require.NotErrorIs(t, err, ErrPhysicalMissing) + require.ErrorIs(err, ErrPhysicalCorrupt) + require.NotErrorIs(err, ErrPhysicalMissing) }) } }) @@ -1165,26 +1193,28 @@ func TestMultiStoreFallsBackFromFilesystemPackRepresentationLimits(t *testing.T) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) published := attachedFilesystemBackend(t, "primary", "primary-1") packPath, packID, entries := buildBackendPackSource( t, content, []byte("second footer entry"), ) packSource, err := os.Open(packPath) - require.NoError(t, err) + require.NoError(err) _, err = published.PublishPack(ctx, packID, packSource, PublishOptions{}) - require.NoError(t, errors.Join(err, packSource.Close())) + require.NoError(errors.Join(err, packSource.Close())) info, err := os.Stat(packPath) - require.NoError(t, err) + require.NoError(err) indexed, err := indexEntryFromPack(entries[0], packID) - require.NoError(t, err) + require.NoError(err) limits := tt.limit(DefaultLimits(), info.Size()) primary, err := NewFilesystemBackend( published.Layout(), FilesystemBackendOptions{Limits: limits}, ) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, primary.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(primary.Close()) }) healthy := &recordingReadBackend{content: content} store, err := NewMultiStore( staticLocationResolver{resolution: Resolution{ @@ -1197,27 +1227,28 @@ func TestMultiStoreFallsBackFromFilesystemPackRepresentationLimits(t *testing.T) staticBackendRegistry{"primary": primary, "healthy": healthy}, MultiStoreOptions{}, ) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, store.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(store.Close()) }) stream, size, err := store.OpenStream(ctx, indexed.Hash) - require.NoError(t, err) + require.NoError(err) got, err := io.ReadAll(stream) - require.NoError(t, errors.Join(err, stream.Close())) - assert.Equal(t, content, got) - assert.Equal(t, int64(len(content)), size) + require.NoError(errors.Join(err, stream.Close())) + assert.Equal(content, got) + assert.Equal(int64(len(content)), size) _, _, err = primary.OpenPack(ctx, indexed.Hash, indexed) - require.ErrorIs(t, err, ErrPhysicalCorrupt) - require.ErrorIs(t, err, ErrBlobTooLarge) + require.ErrorIs(err, ErrPhysicalCorrupt) + require.ErrorIs(err, ErrBlobTooLarge) var limit *LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, tt.dimension, limit.Dimension) + require.ErrorAs(err, &limit) + assert.Equal(tt.dimension, limit.Dimension) }) } } func TestFilesystemBackendClassifiesLateLooseIntegrityFailure(t *testing.T) { + require := Require.New(t) ctx := context.Background() backend := attachedFilesystemBackend(t, "archive", "epoch-1") content := []byte("trusted loose content") @@ -1228,23 +1259,23 @@ func TestFilesystemBackendClassifiesLateLooseIntegrityFailure(t *testing.T) { bytes.NewReader(content), PublishOptions{ExpectedSize: int64(len(content)), SizeKnown: true}, ) - require.NoError(t, err) - require.NoError(t, os.WriteFile( + require.NoError(err) + require.NoError(os.WriteFile( backend.Layout().LoosePath(hash), bytes.Repeat([]byte("x"), len(content)), 0o600, )) stream, _, err := backend.OpenLoose(ctx, hash, receipt.Location) - require.NoError(t, err) + require.NoError(err) err = stream.Verify() - require.ErrorIs(t, err, ErrPhysicalCorrupt) - require.ErrorIs(t, stream.Close(), ErrPhysicalCorrupt) + require.ErrorIs(err, ErrPhysicalCorrupt) + require.ErrorIs(stream.Close(), ErrPhysicalCorrupt) } func TestFilesystemOwnershipRejectsNoncanonicalMarker(t *testing.T) { - require := require.New(t) + require := Require.New(t) ctx := context.Background() backend := attachedFilesystemBackend(t, "archive", "epoch-1") noncanonical := []byte(`{"epoch":"epoch-1","store":"archive","vault":"test-vault","format":1}` + "\n") @@ -1280,15 +1311,15 @@ func buildBackendPackSource( t.Helper() root := t.TempDir() writer, err := pack.NewWriter(root, pack.WriterOptions{}) - require.NoError(t, err) + Require.NoError(t, err) for _, content := range contents { _, err = writer.Append(content) - require.NoError(t, err) + Require.NoError(t, err) } packID := writer.ID() path := filepath.Join(root, packID+PackExt) entries, err := writer.Seal(path) - require.NoError(t, err) + Require.NoError(t, err) return path, packID, entries } @@ -1305,24 +1336,24 @@ func buildEncodedBackendPackSource( options = append(options, zstd.WithWindowSize(windowBytes)) } encoder, err := zstd.NewWriter(&frame, options...) - require.NoError(t, err) + Require.NoError(t, err) _, err = encoder.Write(content) - require.NoError(t, err) - require.NoError(t, encoder.Close()) + Require.NoError(t, err) + Require.NoError(t, encoder.Close()) staging := t.TempDir() writer, err := pack.NewWriter(staging, pack.WriterOptions{}) - require.NoError(t, err) + Require.NoError(t, err) _, err = writer.AppendEncoded( pack.ComputeBlobID(content), frame.Bytes(), rawLen, true, ) - require.NoError(t, err) + Require.NoError(t, err) packID := writer.ID() path := filepath.Join(staging, packID+PackExt) _, err = writer.Seal(path) - require.NoError(t, err) + Require.NoError(t, err) return path, packID } diff --git a/packstore/filesystem_retire_symlink_unix_test.go b/packstore/filesystem_retire_symlink_unix_test.go index fba1452..f146601 100644 --- a/packstore/filesystem_retire_symlink_unix_test.go +++ b/packstore/filesystem_retire_symlink_unix_test.go @@ -9,83 +9,88 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) func TestFilesystemBackendRetireRejectsSymlinkedLooseShard(t *testing.T) { + require := Require.New(t) backend := attachedFilesystemBackend(t, "archive", "epoch-1") content := []byte("outside loose object") hash := hashForTest(content) external := t.TempDir() externalPath := filepath.Join(external, hash.String()) - require.NoError(t, os.WriteFile(externalPath, content, 0o600)) - require.NoError(t, os.Symlink(external, filepath.Join(backend.Layout().Root(), hash.String()[:2]))) + require.NoError(os.WriteFile(externalPath, content, 0o600)) + require.NoError(os.Symlink(external, filepath.Join(backend.Layout().Root(), hash.String()[:2]))) err := backend.Retire(context.Background(), ObjectRef{ LooseHash: hash, LooseEncoding: LooseEncodingRaw, }) - require.ErrorContains(t, err, "unsafe filesystem directory") + require.ErrorContains(err, "unsafe filesystem directory") got, readErr := os.ReadFile(externalPath) - require.NoError(t, readErr) - assert.Equal(t, content, got) + require.NoError(readErr) + Assert.Equal(t, content, got) } func TestFilesystemBackendRetireRejectsSymlinkedPackShard(t *testing.T) { + require := Require.New(t) backend := attachedFilesystemBackend(t, "archive", "epoch-1") packID := pack.NewPackID() external := t.TempDir() externalPath := filepath.Join(external, packID+PackExt) - require.NoError(t, os.WriteFile(externalPath, []byte("outside pack"), 0o600)) - require.NoError(t, os.Mkdir(filepath.Join(backend.Layout().Root(), "packs"), 0o700)) - require.NoError(t, os.Symlink(external, filepath.Join( + require.NoError(os.WriteFile(externalPath, []byte("outside pack"), 0o600)) + require.NoError(os.Mkdir(filepath.Join(backend.Layout().Root(), "packs"), 0o700)) + require.NoError(os.Symlink(external, filepath.Join( backend.Layout().Root(), "packs", packID[:2], ))) err := backend.Retire(context.Background(), ObjectRef{PackID: packID}) - require.ErrorContains(t, err, "unsafe filesystem directory") + require.ErrorContains(err, "unsafe filesystem directory") got, readErr := os.ReadFile(externalPath) - require.NoError(t, readErr) - assert.Equal(t, []byte("outside pack"), got) + require.NoError(readErr) + Assert.Equal(t, []byte("outside pack"), got) } func TestFilesystemBackendPublishPackRejectsSymlinkedPackDirectory(t *testing.T) { + require := Require.New(t) backend := attachedFilesystemBackend(t, "archive", "epoch-1") external := t.TempDir() - require.NoError(t, os.Symlink(external, filepath.Join(backend.Layout().Root(), "packs"))) + require.NoError(os.Symlink(external, filepath.Join(backend.Layout().Root(), "packs"))) path, packID, _ := buildBackendPackSource(t, []byte("confined pack publication")) source, err := os.Open(path) - require.NoError(t, err) + require.NoError(err) _, err = backend.PublishPack(context.Background(), packID, source, PublishOptions{}) - require.NoError(t, source.Close()) + require.NoError(source.Close()) - require.ErrorContains(t, err, "unsafe filesystem directory") + require.ErrorContains(err, "unsafe filesystem directory") entries, readErr := os.ReadDir(external) - require.NoError(t, readErr) - assert.Empty(t, entries) + require.NoError(readErr) + Assert.Empty(t, entries) } func TestFilesystemBackendPublishLooseStaysBoundToOwnedSymlinkRoot(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) base := t.TempDir() ownedRoot := filepath.Join(base, "owned") foreignRoot := filepath.Join(base, "foreign") - require.NoError(t, os.Mkdir(ownedRoot, 0o700)) - require.NoError(t, os.Mkdir(foreignRoot, 0o700)) + require.NoError(os.Mkdir(ownedRoot, 0o700)) + require.NoError(os.Mkdir(foreignRoot, 0o700)) link := filepath.Join(base, "store") - require.NoError(t, os.Symlink(ownedRoot, link)) + require.NoError(os.Symlink(ownedRoot, link)) linkedLayout, err := NewLayout(link, LayoutOptions{Staging: StagingSameDirectory}) - require.NoError(t, err) + require.NoError(err) backend, err := NewFilesystemBackend(linkedLayout, FilesystemBackendOptions{}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, backend.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(backend.Close()) }) owner := Ownership{ Format: OwnershipFormatV1, Vault: "test-vault", Store: "archive", Epoch: "epoch-1", } - require.NoError(t, backend.ReplaceOwnership(context.Background(), owner, nil)) + require.NoError(backend.ReplaceOwnership(context.Background(), owner, nil)) content := []byte("pinned ownership namespace") hash := hashForTest(content) @@ -97,13 +102,13 @@ func TestFilesystemBackendPublishLooseStaysBoundToOwnedSymlinkRoot(t *testing.T) PublishOptions{ExpectedSize: int64(len(content)), SizeKnown: true}, ) - require.NoError(t, err) + require.NoError(err) ownedLayout, err := NewLayout(ownedRoot, LayoutOptions{Staging: StagingSameDirectory}) - require.NoError(t, err) + require.NoError(err) foreignLayout, err := NewLayout(foreignRoot, LayoutOptions{Staging: StagingSameDirectory}) - require.NoError(t, err) - assert.FileExists(t, ownedLayout.LoosePath(hash)) - assert.NoFileExists(t, foreignLayout.LoosePath(hash)) + require.NoError(err) + assert.FileExists(ownedLayout.LoosePath(hash)) + assert.NoFileExists(foreignLayout.LoosePath(hash)) } type swapRootReader struct { diff --git a/packstore/filesystem_root.go b/packstore/filesystem_root.go index bcd7964..0d9dddd 100644 --- a/packstore/filesystem_root.go +++ b/packstore/filesystem_root.go @@ -85,6 +85,12 @@ func rootDirNoSymlinks(root *os.Root, rel string, create, durable bool) (*os.Roo } return nil, err } + if before == nil { + if parentOwned { + _ = parent.Close() + } + return nil, fmt.Errorf("packstore: filesystem directory %q has no identity", part) + } if before.Mode()&os.ModeSymlink != 0 || !before.IsDir() { if parentOwned { _ = parent.Close() @@ -100,7 +106,7 @@ func rootDirNoSymlinks(root *os.Root, rel string, create, durable bool) (*os.Roo } held, heldErr := child.Stat(".") after, afterErr := parent.Lstat(part) - if heldErr != nil || afterErr != nil || after.Mode()&os.ModeSymlink != 0 || + if heldErr != nil || afterErr != nil || held == nil || after == nil || after.Mode()&os.ModeSymlink != 0 || !after.IsDir() || !os.SameFile(before, held) || !os.SameFile(held, after) { closeErr := child.Close() if parentOwned { diff --git a/packstore/identity_pin_unix_hardlink.go b/packstore/identity_pin_unix_hardlink.go index c91904b..937cc8a 100644 --- a/packstore/identity_pin_unix_hardlink.go +++ b/packstore/identity_pin_unix_hardlink.go @@ -106,6 +106,9 @@ func openHardlinkIdentityPin(path string) (identityPin, fs.FileInfo, error) { if identityErr := errors.Join(pinErr, sourceErr); identityErr != nil { return nil, nil, errors.Join(identityErr, pin.Close()) } + if pinIdentity == nil || sourceIdentity == nil { + return nil, nil, errors.Join(fmt.Errorf("packstore: hard-link identity pin for %s has incomplete state", path), pin.Close()) + } if !os.SameFile(pinIdentity, sourceIdentity) { return nil, nil, errors.Join(errIdentityChanged, pin.Close()) } diff --git a/packstore/identity_pin_unix_hardlink_test.go b/packstore/identity_pin_unix_hardlink_test.go index b66e029..10b07a7 100644 --- a/packstore/identity_pin_unix_hardlink_test.go +++ b/packstore/identity_pin_unix_hardlink_test.go @@ -7,39 +7,42 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) func TestHardlinkIdentityPinCloseCleansOwnedPathWithoutCapturedIdentity(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) dir := filepath.Join(t.TempDir(), "exclusive-pin") - require.NoError(t, os.Mkdir(dir, 0o700)) + require.NoError(os.Mkdir(dir, 0o700)) path := filepath.Join(dir, "pinned") - require.NoError(t, os.WriteFile(path, []byte("owned pin"), 0o600)) + require.NoError(os.WriteFile(path, []byte("owned pin"), 0o600)) pin := &hardlinkIdentityPin{path: path, dir: dir} var closeErr error - require.NotPanics(t, func() { + require.NotPanics(func() { closeErr = pin.Close() }) - require.NoError(t, closeErr) - assert.NoFileExists(t, path) - assert.NoDirExists(t, dir) + require.NoError(closeErr) + assert.NoFileExists(path) + assert.NoDirExists(dir) } func TestHardlinkIdentityPinStatRejectsPrivatePathReplacement(t *testing.T) { + require := Require.New(t) dir := filepath.Join(t.TempDir(), "exclusive-pin") - require.NoError(t, os.Mkdir(dir, 0o700)) + require.NoError(os.Mkdir(dir, 0o700)) path := filepath.Join(dir, "pinned") - require.NoError(t, os.WriteFile(path, []byte("verified pin"), 0o600)) + require.NoError(os.WriteFile(path, []byte("verified pin"), 0o600)) identity, err := os.Lstat(path) - require.NoError(t, err) - require.NoError(t, os.Link(path, filepath.Join(dir, "held")), "keep the verified inode allocated") + require.NoError(err) + require.NoError(os.Link(path, filepath.Join(dir, "held")), "keep the verified inode allocated") pin := &hardlinkIdentityPin{path: path, dir: dir, identity: identity} - require.NoError(t, os.Remove(path)) - require.NoError(t, os.WriteFile(path, []byte("replacement"), 0o600)) + require.NoError(os.Remove(path)) + require.NoError(os.WriteFile(path, []byte("replacement"), 0o600)) _, err = pin.Stat() - require.ErrorIs(t, err, errIdentityChanged) + require.ErrorIs(err, errIdentityChanged) } diff --git a/packstore/import_test.go b/packstore/import_test.go index 526277c..fcd79f3 100644 --- a/packstore/import_test.go +++ b/packstore/import_test.go @@ -15,8 +15,8 @@ import ( "time" "github.com/klauspost/compress/zstd" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) @@ -59,6 +59,8 @@ func (c *recordingRestoreCatalog) ReplaceRestoredPacks( } func TestPrepareImportPublishesBeforeCatalogAuthority(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected")) @@ -66,28 +68,30 @@ func TestPrepareImportPublishesBeforeCatalogAuthority(t *testing.T) { PackID: packID, SourcePath: source, Selections: importSelections(t, entries), }}, ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()}) - require.NoError(t, err) - assert.Equal(t, []Hash{hashFromEntry(t, entries[0])}, prepared.PackedHashes()) + require.NoError(err) + assert.Equal([]Hash{hashFromEntry(t, entries[0])}, prepared.PackedHashes()) _, err = target.Stat(importPackPath("content", packID)) - require.NoError(t, err) + require.NoError(err) catalog := &recordingRestoreCatalog{} - assert.Equal(t, 0, catalog.calls) + assert.Equal(0, catalog.calls) assertNoImportStaging(t, target) } func TestPrepareImportReusesByteIdenticalDestination(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected")) input := []ImportPack{{PackID: packID, SourcePath: source, Selections: importSelections(t, entries)}} opts := ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()} first, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) + require.NoError(err) second, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) - assert.Equal(t, first.PackedHashes(), second.PackedHashes()) - assert.Equal(t, first.Stats(), second.Stats()) + require.NoError(err) + assert.Equal(first.PackedHashes(), second.PackedHashes()) + assert.Equal(first.Stats(), second.Stats()) } func TestPrepareImportReuseRequiresDurableDestinationDirectory(t *testing.T) { @@ -96,7 +100,7 @@ func TestPrepareImportReuseRequiresDurableDestinationDirectory(t *testing.T) { input := []ImportPack{{PackID: packID, SourcePath: source, Selections: importSelections(t, entries)}} opts := ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()} _, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) + Require.NoError(t, err) originalSync := syncImportRootDir syncErr := errors.New("reused pack directory sync failed") finalParent := path.Dir(importPackPath("content", packID)) @@ -110,12 +114,14 @@ func TestPrepareImportReuseRequiresDurableDestinationDirectory(t *testing.T) { prepared, err := PrepareImport(context.Background(), target, "content", input, opts) - assert.Nil(t, prepared) - require.ErrorIs(t, err, syncErr) + Assert.Nil(t, prepared) + Require.ErrorIs(t, err, syncErr) assertNoImportStaging(t, target) } func TestPrepareImportFallsBackWhenHardLinksUnavailable(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) originalLink := importRootLink originalUnsupported := importLinkUnsupported importRootLink = func(*os.Root, string, string) error { return errors.New("hard links unavailable") } @@ -134,17 +140,19 @@ func TestPrepareImportFallsBackWhenHardLinksUnavailable(t *testing.T) { PackID: packID, SourcePath: source, Selections: importSelections(t, entries), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - require.NoError(t, err) - assert.Empty(t, prepared.PackedHashes()) - assert.Equal(t, ImportStats{Fallbacks: []ImportFallback{{ + require.NoError(err) + assert.Empty(prepared.PackedHashes()) + assert.Equal(ImportStats{Fallbacks: []ImportFallback{{ PackID: packID, Reason: FallbackPackPublication, }}}, prepared.Stats()) _, err = target.Stat(importPackPath("content", packID)) - require.ErrorIs(t, err, os.ErrNotExist) + require.ErrorIs(err, os.ErrNotExist) assertNoImportStaging(t, target) } func TestPrepareImportConcurrentLinkUnsupportedNeverPublishes(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) originalLink := importRootLink originalUnsupported := importLinkUnsupported importRootLink = func(*os.Root, string, string) error { return errors.New("hard links unavailable") } @@ -172,14 +180,14 @@ func TestPrepareImportConcurrentLinkUnsupportedNeverPublishes(t *testing.T) { workers.Wait() close(results) for result := range results { - require.NoError(t, result.err) - assert.Empty(t, result.prepared.PackedHashes()) - assert.Equal(t, ImportStats{Fallbacks: []ImportFallback{{ + require.NoError(result.err) + assert.Empty(result.prepared.PackedHashes()) + assert.Equal(ImportStats{Fallbacks: []ImportFallback{{ PackID: packID, Reason: FallbackPackPublication, }}}, result.prepared.Stats()) } _, err := target.Stat(importPackPath("content", packID)) - require.ErrorIs(t, err, os.ErrNotExist) + require.ErrorIs(err, os.ErrNotExist) assertNoImportStaging(t, target) } @@ -204,23 +212,25 @@ func TestPrepareImportConcurrentSameIDReusesAtomicWinner(t *testing.T) { workers.Wait() close(results) for result := range results { - require.NoError(t, result.err) - assert.Equal(t, []Hash{hash}, result.prepared.PackedHashes()) + Require.NoError(t, result.err) + Assert.Equal(t, []Hash{hash}, result.prepared.PackedHashes()) } _, err := target.Stat(importPackPath("content", packID)) - require.NoError(t, err) + Require.NoError(t, err) assertNoImportStaging(t, target) } func TestPrepareImportLinkFallbackRefusesPreexistingDestination(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected")) input := []ImportPack{{PackID: packID, SourcePath: source, Selections: importSelections(t, entries)}} opts := ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()} _, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) + require.NoError(err) final := filepath.Join(target.Name(), filepath.FromSlash(importPackPath("content", packID))) - require.NoError(t, os.WriteFile(final, []byte("preexisting collision"), 0o600)) + require.NoError(os.WriteFile(final, []byte("preexisting collision"), 0o600)) originalLink := importRootLink originalUnsupported := importLinkUnsupported importRootLink = func(*os.Root, string, string) error { return errors.New("hard links unavailable") } @@ -232,21 +242,23 @@ func TestPrepareImportLinkFallbackRefusesPreexistingDestination(t *testing.T) { prepared, err := PrepareImport(context.Background(), target, "content", input, opts) - assert.Nil(t, prepared) - require.ErrorContains(t, err, "collision") + assert.Nil(prepared) + require.ErrorContains(err, "collision") data, readErr := os.ReadFile(final) - require.NoError(t, readErr) - assert.Equal(t, []byte("preexisting collision"), data) + require.NoError(readErr) + assert.Equal([]byte("preexisting collision"), data) } func TestPrepareImportLinkUnsupportedPlantedFileIsNeverReplaced(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected")) planted := []byte("planted during publication") originalLink := importRootLink originalUnsupported := importLinkUnsupported importRootLink = func(root *os.Root, _, final string) error { - require.NoError(t, root.WriteFile(final, planted, 0o600)) + require.NoError(root.WriteFile(final, planted, 0o600)) return errors.New("hard links unavailable") } importLinkUnsupported = func(error) bool { return true } @@ -259,18 +271,19 @@ func TestPrepareImportLinkUnsupportedPlantedFileIsNeverReplaced(t *testing.T) { PackID: packID, SourcePath: source, Selections: importSelections(t, entries), }}, ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()}) - assert.Nil(t, prepared) - require.ErrorContains(t, err, "collision") + assert.Nil(prepared) + require.ErrorContains(err, "collision") data, readErr := target.ReadFile(importPackPath("content", packID)) - require.NoError(t, readErr) - assert.Equal(t, planted, data) + require.NoError(readErr) + assert.Equal(planted, data) } func TestPrepareImportBoundsSourceGrowthAfterPreflight(t *testing.T) { + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected")) info, err := os.Stat(source) - require.NoError(t, err) + require.NoError(err) limits := DefaultLimits() limits.PackBytes = info.Size() originalAfterOpen := importAfterSourceOpen @@ -287,10 +300,10 @@ func TestPrepareImportBoundsSourceGrowthAfterPreflight(t *testing.T) { PackID: packID, SourcePath: source, Selections: importSelections(t, entries), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - assert.Nil(t, prepared) - require.ErrorContains(t, err, "source mutation") + Assert.Nil(t, prepared) + require.ErrorContains(err, "source mutation") _, statErr := target.Stat(importPackPath("content", packID)) - require.ErrorIs(t, statErr, os.ErrNotExist) + require.ErrorIs(statErr, os.ErrNotExist) assertNoImportStaging(t, target) } @@ -300,9 +313,9 @@ func TestImportBoundedWriterNeverWritesBeyondLimit(t *testing.T) { n, err := writer.Write([]byte("ten bytes!")) - assert.Equal(t, 4, n) - require.ErrorIs(t, err, errImportSourceExceedsLimit) - assert.Equal(t, "ten ", destination.String()) + Assert.Equal(t, 4, n) + Require.ErrorIs(t, err, errImportSourceExceedsLimit) + Assert.Equal(t, "ten ", destination.String()) } func TestPrepareImportVerifiesEligibleSelectedPayloadOnce(t *testing.T) { @@ -324,49 +337,51 @@ func TestPrepareImportVerifiesEligibleSelectedPayloadOnce(t *testing.T) { PackID: packID, SourcePath: source, Selections: importSelections(t, entries), }}, ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()}) - require.NoError(t, err) - assert.Equal(t, 1, calls) + Require.NoError(t, err) + Assert.Equal(t, 1, calls) } func TestPrepareImportAcceptsLegacyZstdWindowWithinLimit(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("legacy bounded window "), 128) encoder, err := zstd.NewWriter(nil, zstd.WithEncoderConcurrency(1), zstd.WithWindowSize(1<<20), zstd.WithSingleSegment(false)) - require.NoError(t, err) + require.NoError(err) encoded := encoder.EncodeAll(content, nil) encoder.Close() dir := t.TempDir() writer, err := pack.NewWriter(dir, pack.WriterOptions{}) - require.NoError(t, err) + require.NoError(err) id := pack.ComputeBlobID(content) _, err = writer.AppendEncoded(id, encoded, uint64(len(content)), true) - require.NoError(t, err) + require.NoError(err) source := filepath.Join(dir, writer.ID()+PackExt) entries, err := writer.Seal(source) - require.NoError(t, err) + require.NoError(err) reader, err := OpenMaintenancePack(source, DefaultLimits()) - require.NoError(t, err) + require.NoError(err) decoded, err := reader.ReadBlob(hashFromEntry(t, entries[0])) - require.NoError(t, err) - assert.Equal(t, content, decoded) - require.NoError(t, reader.Close()) + require.NoError(err) + assert.Equal(content, decoded) + require.NoError(reader.Close()) target := openImportTarget(t) prepared, err := PrepareImport(context.Background(), target, "content", []ImportPack{{ PackID: writer.ID(), SourcePath: source, Selections: importSelections(t, entries), }}, ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()}) - require.NoError(t, err) - assert.Equal(t, []Hash{hashFromEntry(t, entries[0])}, prepared.PackedHashes()) + require.NoError(err) + assert.Equal([]Hash{hashFromEntry(t, entries[0])}, prepared.PackedHashes()) } func TestPrepareImportSurfacesStagingDirectorySyncFailure(t *testing.T) { target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected")) - require.NoError(t, target.MkdirAll(path.Dir(importPackPath("content", packID)), 0o700)) + Require.NoError(t, target.MkdirAll(path.Dir(importPackPath("content", packID)), 0o700)) originalSync := syncImportRootDir syncErr := errors.New("staging parent sync failed") syncImportRootDir = func(_ *os.Root, name string) error { @@ -381,59 +396,64 @@ func TestPrepareImportSurfacesStagingDirectorySyncFailure(t *testing.T) { PackID: packID, SourcePath: source, Selections: importSelections(t, entries), }}, ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()}) - assert.Nil(t, prepared) - require.ErrorIs(t, err, syncErr) + Assert.Nil(t, prepared) + Require.ErrorIs(t, err, syncErr) assertNoImportStaging(t, target) } func TestPrepareImportPartialFailureLeavesOnlyEarlierVerifiedOrphan(t *testing.T) { + require := Require.New(t) target := openImportTarget(t) firstSource, firstID, firstEntries := buildImportTestPack(t, []byte("first selected")) secondSource, secondID, secondEntries := buildImportTestPack(t, []byte("second selected")) second, err := os.OpenFile(secondSource, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) _, err = second.WriteAt([]byte{0xff}, int64(secondEntries[0].Offset)) //nolint:gosec // test pack is small - require.NoError(t, err) - require.NoError(t, second.Close()) + require.NoError(err) + require.NoError(second.Close()) prepared, err := PrepareImport(context.Background(), target, "content", []ImportPack{ {PackID: firstID, SourcePath: firstSource, Selections: importSelections(t, firstEntries)}, {PackID: secondID, SourcePath: secondSource, Selections: importSelections(t, secondEntries)}, }, ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()}) - assert.Nil(t, prepared) - require.ErrorIs(t, err, pack.ErrCorrupt) + Assert.Nil(t, prepared) + require.ErrorIs(err, pack.ErrCorrupt) _, firstErr := target.Stat(importPackPath("content", firstID)) - require.NoError(t, firstErr) + require.NoError(firstErr) _, secondErr := target.Stat(importPackPath("content", secondID)) - require.ErrorIs(t, secondErr, os.ErrNotExist) + require.ErrorIs(secondErr, os.ErrNotExist) assertNoImportStaging(t, target) } func TestPrepareImportRefusesPackIDCollision(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected")) input := []ImportPack{{PackID: packID, SourcePath: source, Selections: importSelections(t, entries)}} opts := ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()} _, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) + require.NoError(err) final := filepath.Join(target.Name(), filepath.FromSlash(importPackPath("content", packID))) - require.NoError(t, os.WriteFile(final, []byte("different bytes"), 0o600)) + require.NoError(os.WriteFile(final, []byte("different bytes"), 0o600)) prepared, err := PrepareImport(context.Background(), target, "content", input, opts) - assert.Nil(t, prepared) - require.ErrorContains(t, err, "collision") + assert.Nil(prepared) + require.ErrorContains(err, "collision") data, readErr := os.ReadFile(final) - require.NoError(t, readErr) - assert.Equal(t, []byte("different bytes"), data) + require.NoError(readErr) + assert.Equal([]byte("different bytes"), data) } func TestPrepareImportRejectsOverlappingFullFooterEntries(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected"), []byte("unselected")) data, err := os.ReadFile(source) - require.NoError(t, err) + require.NoError(err) trailerStart := len(data) - plainPackTrailerSize footerLen := int(binary.LittleEndian.Uint32(data[trailerStart:])) footerStart := trailerStart - footerLen @@ -441,16 +461,16 @@ func TestPrepareImportRejectsOverlappingFullFooterEntries(t *testing.T) { binary.LittleEndian.PutUint64(data[secondOffset:], entries[0].Offset) digest := sha256.Sum256(data[footerStart : trailerStart+4]) copy(data[trailerStart+4:trailerStart+36], digest[:]) - require.NoError(t, os.WriteFile(source, data, 0o600)) + require.NoError(os.WriteFile(source, data, 0o600)) prepared, err := PrepareImport(context.Background(), target, "content", []ImportPack{{ PackID: packID, SourcePath: source, Selections: importSelections(t, entries[:1]), }}, ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()}) - assert.Nil(t, prepared) - require.ErrorIs(t, err, pack.ErrCorrupt) + assert.Nil(prepared) + require.ErrorIs(err, pack.ErrCorrupt) _, statErr := target.Stat(importPackPath("content", packID)) - assert.ErrorIs(t, statErr, os.ErrNotExist) + assert.ErrorIs(statErr, os.ErrNotExist) } func TestImportFooterStoredBytesIgnoresZeroLengthSpans(t *testing.T) { @@ -467,8 +487,8 @@ func TestImportFooterStoredBytesIgnoresZeroLengthSpans(t *testing.T) { {Offset: test.offset, StoredLen: 0, RawLen: 0}, }) - require.NoError(t, err) - assert.Equal(t, int64(10), stored) + Require.NoError(t, err) + Assert.Equal(t, int64(10), stored) }) } } @@ -505,9 +525,9 @@ func TestPrepareImportAllowsZeroLengthFooterEntries(t *testing.T) { PackID: packID, SourcePath: packPath, Selections: importSelections(t, entries), }}, ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()}) - require.NoError(t, err) - assert.Len(t, prepared.PackedHashes(), len(entries)) - assert.Equal(t, ImportStats{PackedPacks: 1, PackedBlobs: len(entries)}, prepared.Stats()) + Require.NoError(t, err) + Assert.Len(t, prepared.PackedHashes(), len(entries)) + Assert.Equal(t, ImportStats{PackedPacks: 1, PackedBlobs: len(entries)}, prepared.Stats()) }) } } @@ -518,120 +538,131 @@ func TestPreparedImportRejectsOverflowingFullFooterTotals(t *testing.T) { entries: []pack.Entry{{Offset: pack.MinEntryOffset, StoredLen: ^uint64(0)}}, }, time.Now()) - assert.ErrorIs(t, err, pack.ErrCorrupt) + Assert.ErrorIs(t, err, pack.ErrCorrupt) } func TestPreparedImportCatalogFailureLeavesPublishedOrphan(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected")) prepared, err := PrepareImport(context.Background(), target, "content", []ImportPack{{ PackID: packID, SourcePath: source, Selections: importSelections(t, entries), }}, ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()}) - require.NoError(t, err) + require.NoError(err) catalogErr := errors.New("transaction failed") catalog := &recordingRestoreCatalog{err: catalogErr} err = prepared.Commit(context.Background(), catalog) - require.ErrorIs(t, err, catalogErr) - require.ErrorContains(t, err, "catalog") - assert.Equal(t, 1, catalog.calls) + require.ErrorIs(err, catalogErr) + require.ErrorContains(err, "catalog") + assert.Equal(1, catalog.calls) _, statErr := target.Stat(importPackPath("content", packID)) - assert.NoError(t, statErr) + assert.NoError(statErr) } func TestPreparedImportRecordsFullFooterTotalsForSelectedSubset(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected"), []byte("unselected sibling")) createdAt := time.Now().UTC().Truncate(time.Second) prepared, err := PrepareImport(context.Background(), target, "content", []ImportPack{{ PackID: packID, SourcePath: source, Selections: importSelections(t, entries[:1]), }}, ImportOptions{Limits: DefaultLimits(), CreatedAt: createdAt}) - require.NoError(t, err) + require.NoError(err) catalog := &recordingRestoreCatalog{} - require.NoError(t, prepared.Commit(context.Background(), catalog)) + require.NoError(prepared.Commit(context.Background(), catalog)) - require.Len(t, catalog.records, 1) - assert.Equal(t, PackRecord{ + require.Len(catalog.records, 1) + assert.Equal(PackRecord{ PackID: packID, EntryCount: 2, StoredBytes: int64(entries[0].StoredLen + entries[1].StoredLen), //nolint:gosec // test pack is small CreatedAt: createdAt, }, catalog.records[0]) - require.Len(t, catalog.adoptions, 1) - assert.Equal(t, hashFromEntry(t, entries[0]), catalog.adoptions[0].Entry.Hash) - assert.Equal(t, entries[0].CRC32C, catalog.adoptions[0].Entry.CRC32C) - assert.Equal(t, []string{entries[0].ID.String()}, catalog.adoptions[0].OriginalHashes) - assert.Equal(t, 1, catalog.calls) + require.Len(catalog.adoptions, 1) + assert.Equal(hashFromEntry(t, entries[0]), catalog.adoptions[0].Entry.Hash) + assert.Equal(entries[0].CRC32C, catalog.adoptions[0].Entry.CRC32C) + assert.Equal([]string{entries[0].ID.String()}, catalog.adoptions[0].OriginalHashes) + assert.Equal(1, catalog.calls) } func TestPreparedImportCommitValidatesInputsAndAllowsIdempotentRetry(t *testing.T) { + require := Require.New(t) var nilPrepared *PreparedImport - require.ErrorContains(t, nilPrepared.Commit(context.Background(), &recordingRestoreCatalog{}), "nil") + require.ErrorContains(nilPrepared.Commit(context.Background(), &recordingRestoreCatalog{}), "nil") target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected")) prepared, err := PrepareImport(context.Background(), target, "content", []ImportPack{{ PackID: packID, SourcePath: source, Selections: importSelections(t, entries), }}, ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()}) - require.NoError(t, err) - require.ErrorContains(t, prepared.Commit(context.Background(), nil), "nil") + require.NoError(err) + require.ErrorContains(prepared.Commit(context.Background(), nil), "nil") canceled, cancel := context.WithCancel(context.Background()) cancel() - require.ErrorIs(t, prepared.Commit(canceled, &recordingRestoreCatalog{}), context.Canceled) + require.ErrorIs(prepared.Commit(canceled, &recordingRestoreCatalog{}), context.Canceled) catalog := &recordingRestoreCatalog{} - require.NoError(t, prepared.Commit(context.Background(), catalog)) - require.NoError(t, prepared.Commit(context.Background(), catalog)) - assert.Equal(t, 2, catalog.calls) + require.NoError(prepared.Commit(context.Background(), catalog)) + require.NoError(prepared.Commit(context.Background(), catalog)) + Assert.Equal(t, 2, catalog.calls) } func TestPreparedImportRetryAcrossMaintainerOrphanDisposition(t *testing.T) { t.Run("adopted orphan is reusable", func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected")) input := []ImportPack{{PackID: packID, SourcePath: source, Selections: importSelections(t, entries)}} opts := ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()} _, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) + require.NoError(err) catalog := newMaintenanceCatalog() hash := hashFromEntry(t, entries[0]) catalog.members[hash] = Reference{Hash: hash, OriginalHashes: []string{hash.String()}} stats := runImportMaintainer(t, target, catalog, DefaultLimits()) - assert.Equal(t, 1, stats.PacksAdopted) + assert.Equal(1, stats.PacksAdopted) retried, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) - assert.Equal(t, []Hash{hash}, retried.PackedHashes()) + require.NoError(err) + assert.Equal([]Hash{hash}, retried.PackedHashes()) }) t.Run("removed orphan is recopied", func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected")) input := []ImportPack{{PackID: packID, SourcePath: source, Selections: importSelections(t, entries)}} opts := ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()} _, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) + require.NoError(err) stats := runImportMaintainer(t, target, newMaintenanceCatalog(), DefaultLimits()) - assert.Equal(t, 1, stats.PacksRemoved) + assert.Equal(1, stats.PacksRemoved) _, err = target.Stat(importPackPath("content", packID)) - require.ErrorIs(t, err, os.ErrNotExist) + require.ErrorIs(err, os.ErrNotExist) retried, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) - assert.Equal(t, []Hash{hashFromEntry(t, entries[0])}, retried.PackedHashes()) + require.NoError(err) + assert.Equal([]Hash{hashFromEntry(t, entries[0])}, retried.PackedHashes()) _, err = target.Stat(importPackPath("content", packID)) - assert.NoError(t, err) + assert.NoError(err) }) t.Run("oversized retained orphan is reusable with compatible target limits", func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected content larger than maintenance ceiling")) input := []ImportPack{{PackID: packID, SourcePath: source, Selections: importSelections(t, entries)}} opts := ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()} _, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) + require.NoError(err) catalog := newMaintenanceCatalog() hash := hashFromEntry(t, entries[0]) catalog.members[hash] = Reference{Hash: hash, OriginalHashes: []string{hash.String()}} @@ -640,36 +671,38 @@ func TestPreparedImportRetryAcrossMaintainerOrphanDisposition(t *testing.T) { stats := runImportMaintainer(t, target, catalog, maintenanceLimits) - assert.Equal(t, 1, stats.PacksDeferredOversized) + assert.Equal(1, stats.PacksDeferredOversized) retried, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) - assert.Equal(t, []Hash{hash}, retried.PackedHashes()) + require.NoError(err) + assert.Equal([]Hash{hash}, retried.PackedHashes()) }) t.Run("damaged retained orphan fails current selection verification", func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected")) input := []ImportPack{{PackID: packID, SourcePath: source, Selections: importSelections(t, entries)}} opts := ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()} _, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) + require.NoError(err) final, err := target.OpenFile(importPackPath("content", packID), os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) _, err = final.WriteAt([]byte{0xff}, int64(entries[0].Offset)) //nolint:gosec // test pack is small - require.NoError(t, err) - require.NoError(t, final.Close()) + require.NoError(err) + require.NoError(final.Close()) catalog := newMaintenanceCatalog() hash := hashFromEntry(t, entries[0]) catalog.members[hash] = Reference{Hash: hash, OriginalHashes: []string{hash.String()}} stats := runImportMaintainer(t, target, catalog, DefaultLimits()) - assert.Equal(t, 1, stats.PacksQuarantined) + assert.Equal(1, stats.PacksQuarantined) _, err = target.Stat(importPackPath("content", packID)) - require.NoError(t, err) + require.NoError(err) prepared, err := PrepareImport(context.Background(), target, "content", input, opts) - assert.Nil(t, prepared) - assert.ErrorContains(t, err, "collision") + assert.Nil(prepared) + assert.ErrorContains(err, "collision") }) } @@ -678,12 +711,12 @@ func runImportMaintainer(t *testing.T, target *os.Root, catalog *maintenanceCata layout, err := NewLayout(filepath.Join(target.Name(), "content"), LayoutOptions{ Staging: StagingStoreDirectory, StagingDir: ".staging", }) - require.NoError(t, err) + Require.NoError(t, err) maintainer, err := NewMaintainer(catalog, layout, MaintainerOptions{Limits: limits}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, maintainer.Close()) }) + Require.NoError(t, err) + t.Cleanup(func() { Require.NoError(t, maintainer.Close()) }) stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) + Require.NoError(t, err) return stats } @@ -698,16 +731,18 @@ func TestPrepareImportUsesConfiguredLimits(t *testing.T) { PackID: packID, SourcePath: path, Selections: importSelections(t, entries), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - require.NoError(t, err) - assert.ElementsMatch(t, []Hash{hashFromEntry(t, entries[0]), hashFromEntry(t, entries[1])}, prepared.PackedHashes()) - assert.Equal(t, ImportStats{PackedPacks: 1, PackedBlobs: 2}, prepared.Stats()) + Require.NoError(t, err) + Assert.ElementsMatch(t, []Hash{hashFromEntry(t, entries[0]), hashFromEntry(t, entries[1])}, prepared.PackedHashes()) + Assert.Equal(t, ImportStats{PackedPacks: 1, PackedBlobs: 2}, prepared.Stats()) } func TestPrepareImportFallsBackWholePackForContainerLimit(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) path, packID, entries := buildImportTestPack(t, []byte("first"), []byte("second")) info, err := os.Stat(path) - require.NoError(t, err) + require.NoError(err) limits := DefaultLimits() limits.PackBytes = info.Size() - 1 @@ -715,9 +750,9 @@ func TestPrepareImportFallsBackWholePackForContainerLimit(t *testing.T) { PackID: packID, SourcePath: path, Selections: importSelections(t, entries), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - require.NoError(t, err) - assert.Empty(t, prepared.PackedHashes()) - assert.Equal(t, ImportStats{ + require.NoError(err) + assert.Empty(prepared.PackedHashes()) + assert.Equal(ImportStats{ Fallbacks: []ImportFallback{{PackID: packID, Reason: FallbackPackContainerLimit}}, }, prepared.Stats()) } @@ -725,7 +760,7 @@ func TestPrepareImportFallsBackWholePackForContainerLimit(t *testing.T) { func TestPrepareImportRejectsOversizedNonPackInsteadOfFallingBack(t *testing.T) { target := openImportTarget(t) path := filepath.Join(t.TempDir(), "not-a-pack") - require.NoError(t, os.WriteFile(path, make([]byte, 1024), 0o600)) + Require.NoError(t, os.WriteFile(path, make([]byte, 1024), 0o600)) _, packID, entries := buildImportTestPack(t, []byte("selected")) limits := DefaultLimits() limits.PackBytes = 512 @@ -734,22 +769,24 @@ func TestPrepareImportRejectsOversizedNonPackInsteadOfFallingBack(t *testing.T) PackID: packID, SourcePath: path, Selections: importSelections(t, entries), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - assert.Nil(t, prepared) - assert.ErrorIs(t, err, pack.ErrBadMagic) + Assert.Nil(t, prepared) + Assert.ErrorIs(t, err, pack.ErrBadMagic) } func TestPrepareImportRejectsForgedOversizedFooterLength(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) path, packID, entries := buildImportTestPack(t, []byte("selected")) info, err := os.Stat(path) - require.NoError(t, err) + require.NoError(err) f, err := os.OpenFile(path, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) var forged [4]byte binary.LittleEndian.PutUint32(forged[:], uint32(info.Size()+1)) _, err = f.WriteAt(forged[:], info.Size()-plainPackTrailerSize) - require.NoError(t, err) - require.NoError(t, f.Close()) + require.NoError(err) + require.NoError(f.Close()) limits := DefaultLimits() limits.FooterBytes = 1 @@ -757,20 +794,22 @@ func TestPrepareImportRejectsForgedOversizedFooterLength(t *testing.T) { PackID: packID, SourcePath: path, Selections: importSelections(t, entries), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - assert.Nil(t, prepared) - assert.ErrorIs(t, err, pack.ErrTruncated) + assert.Nil(prepared) + assert.ErrorIs(err, pack.ErrTruncated) } func TestPrepareImportRejectsForgedOversizedFooterCount(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) path, packID, entries := buildImportTestPack(t, []byte("selected")) data, err := os.ReadFile(path) - require.NoError(t, err) + require.NoError(err) trailerStart := len(data) - plainPackTrailerSize footerLen := int(binary.LittleEndian.Uint32(data[trailerStart:])) footerStart := trailerStart - footerLen binary.LittleEndian.PutUint32(data[footerStart:], 2) - require.NoError(t, os.WriteFile(path, data, 0o600)) + require.NoError(os.WriteFile(path, data, 0o600)) limits := DefaultLimits() limits.PackEntries = 1 @@ -778,15 +817,15 @@ func TestPrepareImportRejectsForgedOversizedFooterCount(t *testing.T) { PackID: packID, SourcePath: path, Selections: importSelections(t, entries), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - assert.Nil(t, prepared) - assert.ErrorIs(t, err, pack.ErrCorrupt) + assert.Nil(prepared) + assert.ErrorIs(err, pack.ErrCorrupt) } func TestPrepareImportRejectsMetadataMismatchBehindContainerLimit(t *testing.T) { target := openImportTarget(t) path, packID, entries := buildImportTestPack(t, []byte("selected")) info, err := os.Stat(path) - require.NoError(t, err) + Require.NoError(t, err) selections := importSelections(t, entries) selections[0].Offset++ limits := DefaultLimits() @@ -796,13 +835,15 @@ func TestPrepareImportRejectsMetadataMismatchBehindContainerLimit(t *testing.T) PackID: packID, SourcePath: path, Selections: selections, }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - assert.Nil(t, prepared) - assert.ErrorIs(t, err, pack.ErrCorrupt) + Assert.Nil(t, prepared) + Assert.ErrorIs(t, err, pack.ErrCorrupt) } func TestPrepareImportFallsBackWholePackForValidFooterLimits(t *testing.T) { for _, dimension := range []string{"footer bytes", "entry count"} { t.Run(dimension, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) path, packID, entries := buildImportTestPack(t, []byte("first"), []byte("second")) limits := DefaultLimits() @@ -810,9 +851,9 @@ func TestPrepareImportFallsBackWholePackForValidFooterLimits(t *testing.T) { switch dimension { case "footer bytes": info, err := os.Stat(path) - require.NoError(t, err) + require.NoError(err) data, err := os.ReadFile(path) - require.NoError(t, err) + require.NoError(err) footerLen := int64(binary.LittleEndian.Uint32(data[info.Size()-plainPackTrailerSize:])) limits.FooterBytes = footerLen - 1 reason = FallbackPackFooterLimit @@ -825,9 +866,9 @@ func TestPrepareImportFallsBackWholePackForValidFooterLimits(t *testing.T) { PackID: packID, SourcePath: path, Selections: importSelections(t, entries), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - require.NoError(t, err) - assert.Empty(t, prepared.PackedHashes()) - assert.Equal(t, ImportStats{ + require.NoError(err) + assert.Empty(prepared.PackedHashes()) + assert.Equal(ImportStats{ Fallbacks: []ImportFallback{{PackID: packID, Reason: reason}}, }, prepared.Stats()) }) @@ -837,6 +878,8 @@ func TestPrepareImportFallsBackWholePackForValidFooterLimits(t *testing.T) { func TestPrepareImportRejectsLimitedVerificationBudgetBeforeScratch(t *testing.T) { for _, dimension := range []string{"footer bytes", "entry count"} { t.Run(dimension, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) originalFooterBytes := importVerifyMaxFooterBytes originalEntries := importVerifyMaxEntries t.Cleanup(func() { @@ -847,9 +890,9 @@ func TestPrepareImportRejectsLimitedVerificationBudgetBeforeScratch(t *testing.T target := openImportTarget(t) packPath, packID, entries := buildImportTestPack(t, []byte("first"), []byte("second")) info, err := os.Stat(packPath) - require.NoError(t, err) + require.NoError(err) data, err := os.ReadFile(packPath) - require.NoError(t, err) + require.NoError(err) footerLen := uint64(binary.LittleEndian.Uint32(data[info.Size()-plainPackTrailerSize:])) //nolint:gosec // test pack size is positive limits := DefaultLimits() limits.PackBytes = info.Size() - 1 @@ -870,30 +913,31 @@ func TestPrepareImportRejectsLimitedVerificationBudgetBeforeScratch(t *testing.T PackID: packID, SourcePath: packPath, Selections: importSelections(t, entries[:1]), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - assert.Nil(t, prepared) - require.ErrorIs(t, err, ErrBlobTooLarge) + assert.Nil(prepared) + require.ErrorIs(err, ErrBlobTooLarge) var limitErr *LimitError - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, wantDimension, limitErr.Dimension) - assert.Equal(t, wantActual, limitErr.Actual) - assert.Equal(t, wantLimit, limitErr.Limit) + require.ErrorAs(err, &limitErr) + assert.Equal(wantDimension, limitErr.Dimension) + assert.Equal(wantActual, limitErr.Actual) + assert.Equal(wantLimit, limitErr.Limit) assertNoImportVerificationScratch(t, target) }) } } func TestPrepareImportLimitedVerificationRejectsTruncatedFooterBeforeBudget(t *testing.T) { + require := Require.New(t) target := openImportTarget(t) packPath, packID, entries := buildImportTestPack(t, []byte("selected")) info, err := os.Stat(packPath) - require.NoError(t, err) + require.NoError(err) f, err := os.OpenFile(packPath, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) var forged [4]byte binary.LittleEndian.PutUint32(forged[:], uint32(importVerifyMaxFooterBytes+1)) //nolint:gosec // test ceiling fits uint32 _, err = f.WriteAt(forged[:], info.Size()-plainPackTrailerSize) - require.NoError(t, err) - require.NoError(t, f.Close()) + require.NoError(err) + require.NoError(f.Close()) limits := DefaultLimits() limits.PackBytes = info.Size() - 1 @@ -901,17 +945,19 @@ func TestPrepareImportLimitedVerificationRejectsTruncatedFooterBeforeBudget(t *t PackID: packID, SourcePath: packPath, Selections: importSelections(t, entries), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - assert.Nil(t, prepared) - require.ErrorIs(t, err, pack.ErrTruncated) - require.NotErrorIs(t, err, ErrBlobTooLarge) + Assert.Nil(t, prepared) + require.ErrorIs(err, pack.ErrTruncated) + require.NotErrorIs(err, ErrBlobTooLarge) assertNoImportVerificationScratch(t, target) } func TestPrepareImportLimitedVerificationAllowsSparseOversizedContainer(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) packPath, packID, entries := buildImportTestPack(t, []byte("selected")) data, err := os.ReadFile(packPath) - require.NoError(t, err) + require.NoError(err) trailerStart := len(data) - plainPackTrailerSize footerLen := int(binary.LittleEndian.Uint32(data[trailerStart:])) footerStart := trailerStart - footerLen @@ -919,11 +965,11 @@ func TestPrepareImportLimitedVerificationAllowsSparseOversizedContainer(t *testi const packLimit = int64(1 << 20) newSize := packLimit + 1 f, err := os.OpenFile(packPath, os.O_RDWR, 0) - require.NoError(t, err) - require.NoError(t, f.Truncate(newSize)) + require.NoError(err) + require.NoError(f.Truncate(newSize)) _, err = f.WriteAt(suffix, newSize-int64(len(suffix))) - require.NoError(t, err) - require.NoError(t, f.Close()) + require.NoError(err) + require.NoError(f.Close()) limits := DefaultLimits() limits.PackBytes = packLimit @@ -931,9 +977,9 @@ func TestPrepareImportLimitedVerificationAllowsSparseOversizedContainer(t *testi PackID: packID, SourcePath: packPath, Selections: importSelections(t, entries), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - require.NoError(t, err) - assert.Empty(t, prepared.PackedHashes()) - assert.Equal(t, ImportStats{ + require.NoError(err) + assert.Empty(prepared.PackedHashes()) + assert.Equal(ImportStats{ Fallbacks: []ImportFallback{{PackID: packID, Reason: FallbackPackContainerLimit}}, }, prepared.Stats()) assertNoImportVerificationScratch(t, target) @@ -949,21 +995,23 @@ func TestPrepareImportFallsBackWholePackForRecognizableUnsupportedEncoding(t *te {name: "flags", offset: 5, value: 1}, } { t.Run(test.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) path, packID, entries := buildImportTestPack(t, []byte("first"), []byte("second")) f, err := os.OpenFile(path, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) _, err = f.WriteAt([]byte{test.value}, test.offset) - require.NoError(t, err) - require.NoError(t, f.Close()) + require.NoError(err) + require.NoError(f.Close()) prepared, err := PrepareImport(context.Background(), target, "content", []ImportPack{{ PackID: packID, SourcePath: path, Selections: importSelections(t, entries), }}, ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()}) - require.NoError(t, err) - assert.Empty(t, prepared.PackedHashes()) - assert.Equal(t, ImportStats{ + require.NoError(err) + assert.Empty(prepared.PackedHashes()) + assert.Equal(ImportStats{ Fallbacks: []ImportFallback{{PackID: packID, Reason: FallbackPackEncoding}}, }, prepared.Stats()) }) @@ -981,9 +1029,9 @@ func TestPrepareImportFallsBackOnlyOversizedSelectedEntry(t *testing.T) { PackID: packID, SourcePath: path, Selections: importSelections(t, entries), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - require.NoError(t, err) - assert.Equal(t, []Hash{hashFromEntry(t, entries[0])}, prepared.PackedHashes()) - assert.Equal(t, ImportStats{ + Require.NoError(t, err) + Assert.Equal(t, []Hash{hashFromEntry(t, entries[0])}, prepared.PackedHashes()) + Assert.Equal(t, ImportStats{ PackedPacks: 1, PackedBlobs: 1, Fallbacks: []ImportFallback{{ @@ -997,22 +1045,24 @@ func TestPrepareImportFallsBackOnlyOversizedSelectedEntry(t *testing.T) { func TestPrepareImportLimitFallbackStillVerifiesSelectedPayload(t *testing.T) { for _, dimension := range []string{"container bytes", "footer bytes", "entry count"} { t.Run(dimension, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) path, packID, entries := buildImportTestPack(t, []byte("selected content"), []byte("sibling")) f, err := os.OpenFile(path, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) _, err = f.WriteAt([]byte{0xff}, int64(entries[0].Offset)) - require.NoError(t, err) - require.NoError(t, f.Close()) + require.NoError(err) + require.NoError(f.Close()) limits := DefaultLimits() switch dimension { case "container bytes": info, err := os.Stat(path) - require.NoError(t, err) + require.NoError(err) limits.PackBytes = info.Size() - 1 case "footer bytes": data, err := os.ReadFile(path) - require.NoError(t, err) + require.NoError(err) trailerStart := len(data) - plainPackTrailerSize limits.FooterBytes = int64(binary.LittleEndian.Uint32(data[trailerStart:])) - 1 case "entry count": @@ -1023,8 +1073,8 @@ func TestPrepareImportLimitFallbackStillVerifiesSelectedPayload(t *testing.T) { PackID: packID, SourcePath: path, Selections: importSelections(t, entries[:1]), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - assert.Nil(t, prepared) - assert.ErrorIs(t, err, pack.ErrCorrupt) + assert.Nil(prepared) + assert.ErrorIs(err, pack.ErrCorrupt) }) } } @@ -1036,6 +1086,7 @@ func TestPrepareImportLimitFallbackRejectsOverlappingFooterSpans(t *testing.T) { for _, dimension := range []string{"container bytes", "footer bytes", "entry count"} { t.Run(dimension, func(t *testing.T) { + require := Require.New(t) target := openImportTarget(t) packPath, packID, entries := buildImportTestPack(t, []byte("selected content"), []byte("overlapping sibling")) mutateImportFooterEntry(t, packPath, 1, func(entry []byte) { @@ -1045,11 +1096,11 @@ func TestPrepareImportLimitFallbackRejectsOverlappingFooterSpans(t *testing.T) { switch dimension { case "container bytes": info, err := os.Stat(packPath) - require.NoError(t, err) + require.NoError(err) limits.PackBytes = info.Size() - 1 case "footer bytes": data, err := os.ReadFile(packPath) - require.NoError(t, err) + require.NoError(err) trailerStart := len(data) - plainPackTrailerSize limits.FooterBytes = int64(binary.LittleEndian.Uint32(data[trailerStart:])) - 1 case "entry count": @@ -1060,8 +1111,8 @@ func TestPrepareImportLimitFallbackRejectsOverlappingFooterSpans(t *testing.T) { PackID: packID, SourcePath: packPath, Selections: importSelections(t, entries[:1]), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - assert.Nil(t, prepared) - require.ErrorIs(t, err, pack.ErrCorrupt) + Assert.Nil(t, prepared) + require.ErrorIs(err, pack.ErrCorrupt) assertNoImportVerificationScratch(t, target) }) } @@ -1094,9 +1145,9 @@ func TestPrepareImportLimitFallbackAllowsEmptyFooterSpans(t *testing.T) { PackID: packID, SourcePath: packPath, Selections: importSelections(t, entries[:1]), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - require.NoError(t, err) - assert.Empty(t, prepared.PackedHashes()) - assert.Equal(t, ImportStats{ + Require.NoError(t, err) + Assert.Empty(t, prepared.PackedHashes()) + Assert.Equal(t, ImportStats{ Fallbacks: []ImportFallback{{PackID: packID, Reason: FallbackPackEntryCountLimit}}, }, prepared.Stats()) assertNoImportVerificationScratch(t, target) @@ -1105,16 +1156,18 @@ func TestPrepareImportLimitFallbackAllowsEmptyFooterSpans(t *testing.T) { } func TestPrepareImportLimitFallbackSkipsOversizedSelectedPayload(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) content := []byte("oversized selected content") path, packID, entries := buildImportTestPack(t, content) f, err := os.OpenFile(path, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) _, err = f.WriteAt([]byte{0xff}, int64(entries[0].Offset)) - require.NoError(t, err) - require.NoError(t, f.Close()) + require.NoError(err) + require.NoError(f.Close()) info, err := os.Stat(path) - require.NoError(t, err) + require.NoError(err) limits := DefaultLimits() limits.PackBytes = info.Size() - 1 limits.BlobBytes = int64(len(content) - 1) @@ -1123,9 +1176,9 @@ func TestPrepareImportLimitFallbackSkipsOversizedSelectedPayload(t *testing.T) { PackID: packID, SourcePath: path, Selections: importSelections(t, entries), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - require.NoError(t, err) - assert.Empty(t, prepared.PackedHashes()) - assert.Equal(t, ImportStats{ + require.NoError(err) + assert.Empty(prepared.PackedHashes()) + assert.Equal(ImportStats{ Fallbacks: []ImportFallback{{PackID: packID, Reason: FallbackPackContainerLimit}}, }, prepared.Stats()) } @@ -1145,24 +1198,25 @@ func TestPrepareImportStreamingVerifierCleansScratch(t *testing.T) { PackID: packID, SourcePath: path, Selections: importSelections(t, entries[:1]), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - require.NoError(t, err) + Require.NoError(t, err) assertNoImportVerificationScratch(t, target) }) t.Run("cross-run duplicate", func(t *testing.T) { + require := Require.New(t) target := openImportTarget(t) dir := t.TempDir() writer, err := pack.NewWriter(dir, pack.WriterOptions{}) - require.NoError(t, err) + require.NoError(err) entry, err := writer.Append([]byte("duplicate")) - require.NoError(t, err) + require.NoError(err) _, err = writer.Append([]byte("middle")) - require.NoError(t, err) + require.NoError(err) _, err = writer.Append([]byte("duplicate")) - require.NoError(t, err) + require.NoError(err) packPath := filepath.Join(dir, writer.ID()+PackExt) _, err = writer.Seal(packPath) - require.NoError(t, err) + require.NoError(err) limits := DefaultLimits() limits.PackEntries = 1 @@ -1170,8 +1224,8 @@ func TestPrepareImportStreamingVerifierCleansScratch(t *testing.T) { PackID: writer.ID(), SourcePath: packPath, Selections: importSelections(t, []pack.Entry{entry}), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - assert.Nil(t, prepared) - require.ErrorIs(t, err, pack.ErrCorrupt) + Assert.Nil(t, prepared) + require.ErrorIs(err, pack.ErrCorrupt) assertNoImportVerificationScratch(t, target) }) } @@ -1193,9 +1247,9 @@ func TestPrepareImportStreamingSpanVerifierCancelsDuringMerge(t *testing.T) { PackID: packID, SourcePath: packPath, Selections: importSelections(t, entries[:1]), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - assert.Nil(t, prepared) - require.ErrorIs(t, err, context.Canceled) - assert.Equal(t, ctx.cancelAt, ctx.calls) + Assert.Nil(t, prepared) + Require.ErrorIs(t, err, context.Canceled) + Assert.Equal(t, ctx.cancelAt, ctx.calls) assertNoImportVerificationScratch(t, target) } @@ -1215,48 +1269,50 @@ func TestPrepareImportStreamingSpanVerifierRejectsWithinRunOverlap(t *testing.T) PackID: packID, SourcePath: packPath, Selections: importSelections(t, entries[:1]), }}, ImportOptions{Limits: limits, CreatedAt: time.Now()}) - assert.Nil(t, prepared) - require.ErrorIs(t, err, pack.ErrCorrupt) + Assert.Nil(t, prepared) + Require.ErrorIs(t, err, pack.ErrCorrupt) assertNoImportVerificationScratch(t, target) } func TestPrepareImportRejectsCorruptSourceInsteadOfFallingBack(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) path, packID, entries := buildImportTestPack(t, []byte("selected content"), []byte("unselected content")) f, err := os.OpenFile(path, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) _, err = f.WriteAt([]byte{0xff}, int64(entries[0].Offset)) - require.NoError(t, err) - require.NoError(t, f.Close()) + require.NoError(err) + require.NoError(f.Close()) prepared, err := PrepareImport(context.Background(), target, "content", []ImportPack{{ PackID: packID, SourcePath: path, Selections: importSelections(t, entries[:1]), }}, ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()}) - assert.Nil(t, prepared) - assert.ErrorIs(t, err, pack.ErrCorrupt) + assert.Nil(prepared) + assert.ErrorIs(err, pack.ErrCorrupt) } func buildImportTestPack(t *testing.T, contents ...[]byte) (string, string, []pack.Entry) { t.Helper() dir := t.TempDir() writer, err := pack.NewWriter(dir, pack.WriterOptions{}) - require.NoError(t, err) + Require.NoError(t, err) for _, content := range contents { _, err = writer.Append(content) - require.NoError(t, err) + Require.NoError(t, err) } path := filepath.Join(dir, writer.ID()+PackExt) entries, err := writer.Seal(path) - require.NoError(t, err) + Require.NoError(t, err) return path, writer.ID(), entries } func openImportTarget(t *testing.T) *os.Root { t.Helper() target, err := os.OpenRoot(t.TempDir()) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, target.Close()) }) + Require.NoError(t, err) + t.Cleanup(func() { Require.NoError(t, target.Close()) }) return target } @@ -1278,14 +1334,14 @@ func importSelections(t *testing.T, entries []pack.Entry) []ImportSelection { func hashFromEntry(t *testing.T, entry pack.Entry) Hash { t.Helper() hash, err := ParseHash(entry.ID.String()) - require.NoError(t, err) + Require.NoError(t, err) return hash } func mutateImportFooterEntry(t *testing.T, packPath string, index int, mutate func([]byte)) { t.Helper() data, err := os.ReadFile(packPath) - require.NoError(t, err) + Require.NoError(t, err) trailerStart := len(data) - plainPackTrailerSize footerLen := int(binary.LittleEndian.Uint32(data[trailerStart:])) footerStart := trailerStart - footerLen @@ -1293,23 +1349,23 @@ func mutateImportFooterEntry(t *testing.T, packPath string, index int, mutate fu mutate(data[entryStart : entryStart+plainPackEntrySize]) digest := sha256.Sum256(data[footerStart : trailerStart+4]) copy(data[trailerStart+4:trailerStart+36], digest[:]) - require.NoError(t, os.WriteFile(packPath, data, 0o600)) + Require.NoError(t, os.WriteFile(packPath, data, 0o600)) } func assertNoImportVerificationScratch(t *testing.T, target *os.Root) { t.Helper() entries, err := os.ReadDir(target.Name()) - require.NoError(t, err) + Require.NoError(t, err) for _, entry := range entries { - assert.False(t, strings.HasPrefix(entry.Name(), importVerifyScratchPrefix), entry.Name()) + Assert.False(t, strings.HasPrefix(entry.Name(), importVerifyScratchPrefix), entry.Name()) } } func assertNoImportStaging(t *testing.T, target *os.Root) { t.Helper() packEntries, err := os.ReadDir(filepath.Join(target.Name(), "content", "packs")) - require.NoError(t, err) + Require.NoError(t, err) for _, entry := range packEntries { - assert.False(t, strings.HasSuffix(entry.Name(), ".staging"), entry.Name()) + Assert.False(t, strings.HasSuffix(entry.Name(), ".staging"), entry.Name()) } } diff --git a/packstore/import_windows_test.go b/packstore/import_windows_test.go index 367b709..b944c6f 100644 --- a/packstore/import_windows_test.go +++ b/packstore/import_windows_test.go @@ -9,11 +9,13 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) func TestPrepareImportWindowsPublishesWithClosedHandlesAndReopens(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected")) @@ -21,15 +23,15 @@ func TestPrepareImportWindowsPublishesWithClosedHandlesAndReopens(t *testing.T) PackID: packID, SourcePath: source, Selections: importSelections(t, entries), }}, ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()}) - require.NoError(t, err) - assert.Equal(t, []Hash{hashFromEntry(t, entries[0])}, prepared.PackedHashes()) + require.NoError(err) + assert.Equal([]Hash{hashFromEntry(t, entries[0])}, prepared.PackedHashes()) finalName := importPackPath("content", packID) renamedName := finalName + ".renamed" - require.NoError(t, target.Rename(finalName, renamedName)) + require.NoError(target.Rename(finalName, renamedName)) final, err := target.Open(renamedName) - require.NoError(t, err) - assert.NoError(t, final.Close()) - require.NoError(t, target.Rename(renamedName, finalName)) + require.NoError(err) + assert.NoError(final.Close()) + require.NoError(target.Rename(renamedName, finalName)) } func TestPrepareImportWindowsReusesByteIdenticalDestination(t *testing.T) { @@ -38,28 +40,30 @@ func TestPrepareImportWindowsReusesByteIdenticalDestination(t *testing.T) { input := []ImportPack{{PackID: packID, SourcePath: source, Selections: importSelections(t, entries)}} opts := ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()} requirePrepared, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) + Require.NoError(t, err) reused, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) - assert.Equal(t, requirePrepared.PackedHashes(), reused.PackedHashes()) + Require.NoError(t, err) + Assert.Equal(t, requirePrepared.PackedHashes(), reused.PackedHashes()) } func TestPrepareImportWindowsRefusesCollisionWithoutReplacing(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) target := openImportTarget(t) source, packID, entries := buildImportTestPack(t, []byte("selected")) input := []ImportPack{{PackID: packID, SourcePath: source, Selections: importSelections(t, entries)}} opts := ImportOptions{Limits: DefaultLimits(), CreatedAt: time.Now()} _, err := PrepareImport(context.Background(), target, "content", input, opts) - require.NoError(t, err) + require.NoError(err) final := filepath.Join(target.Name(), filepath.FromSlash(importPackPath("content", packID))) - require.NoError(t, os.WriteFile(final, []byte("collision"), 0o600)) + require.NoError(os.WriteFile(final, []byte("collision"), 0o600)) _, err = PrepareImport(context.Background(), target, "content", input, opts) - assert.ErrorContains(t, err, "collision") + assert.ErrorContains(err, "collision") data, readErr := os.ReadFile(final) - require.NoError(t, readErr) - assert.Equal(t, []byte("collision"), data) + require.NoError(readErr) + assert.Equal([]byte("collision"), data) } diff --git a/packstore/location_errors_test.go b/packstore/location_errors_test.go index b68c194..8fdfde5 100644 --- a/packstore/location_errors_test.go +++ b/packstore/location_errors_test.go @@ -7,8 +7,8 @@ import ( "io/fs" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) @@ -37,12 +37,13 @@ func TestClassifyPhysicalErrorPreservesControlErrors(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) err := classifyPhysicalError(tt.input) - require.ErrorIs(t, err, tt.input) - assert.Equal(t, tt.unavailable, errors.Is(err, ErrStoreUnavailable)) - assert.Equal(t, tt.missing, errors.Is(err, ErrPhysicalMissing)) - assert.Equal(t, tt.corrupt, errors.Is(err, ErrPhysicalCorrupt)) + Require.ErrorIs(t, err, tt.input) + assert.Equal(tt.unavailable, errors.Is(err, ErrStoreUnavailable)) + assert.Equal(tt.missing, errors.Is(err, ErrPhysicalMissing)) + assert.Equal(tt.corrupt, errors.Is(err, ErrPhysicalCorrupt)) }) } } @@ -67,8 +68,8 @@ func TestClassifyIntegrityError(t *testing.T) { t.Run(tt.name, func(t *testing.T) { err := ClassifyIntegrityError(tt.input) - assert.Equal(t, tt.corrupt, errors.Is(err, ErrPhysicalCorrupt)) - require.ErrorIs(t, err, tt.input) + Assert.Equal(t, tt.corrupt, errors.Is(err, ErrPhysicalCorrupt)) + Require.ErrorIs(t, err, tt.input) }) } } @@ -109,8 +110,8 @@ func TestClassifyRepresentationLimitError(t *testing.T) { t.Run(tt.name, func(t *testing.T) { err := ClassifyRepresentationLimitError(tt.input) - assert.Equal(t, tt.corrupt, errors.Is(err, ErrPhysicalCorrupt)) - require.ErrorIs(t, err, tt.input) + Assert.Equal(t, tt.corrupt, errors.Is(err, ErrPhysicalCorrupt)) + Require.ErrorIs(t, err, tt.input) }) } } diff --git a/packstore/location_health_test.go b/packstore/location_health_test.go index 95af99d..599def7 100644 --- a/packstore/location_health_test.go +++ b/packstore/location_health_test.go @@ -3,8 +3,8 @@ package packstore import ( "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) func TestLocationHealthPrefersTransientlyUnavailableOverKnownDamage(t *testing.T) { @@ -23,10 +23,12 @@ func TestLocationHealthPrefersTransientlyUnavailableOverKnownDamage(t *testing.T ordered := health.Order(hash, []ReadLocation{corrupt, unavailable}) - assert.Equal(t, []ReadLocation{unavailable, corrupt}, ordered) + Assert.Equal(t, []ReadLocation{unavailable, corrupt}, ordered) } func TestLooseLocationHealthIsScopedToContentHash(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) health := NewHealth() firstHash := hashForTest([]byte("first same-sized blob")) secondHash := hashForTest([]byte("other same-sized blob")) @@ -45,11 +47,11 @@ func TestLooseLocationHealthIsScopedToContentHash(t *testing.T) { health.Observe(firstHash, primary, ErrPhysicalCorrupt) ordered := health.Order(secondHash, []ReadLocation{primary, secondary}) - require.Len(t, ordered, 2) - assert.Equal(t, primary, ordered[0]) + require.Len(ordered, 2) + assert.Equal(primary, ordered[0]) health.Clear(secondHash, primary) ordered = health.Order(firstHash, []ReadLocation{primary, secondary}) - require.Len(t, ordered, 2) - assert.Equal(t, secondary, ordered[0]) + require.Len(ordered, 2) + assert.Equal(secondary, ordered[0]) } diff --git a/packstore/location_resolver_test.go b/packstore/location_resolver_test.go index 5a34836..ba69c1f 100644 --- a/packstore/location_resolver_test.go +++ b/packstore/location_resolver_test.go @@ -7,14 +7,14 @@ import ( "io" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) func TestMultiStoreSelectsFirstHealthyCandidate(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("multi-location content") hash := hashForTest(content) primary := &recordingReadBackend{content: content} @@ -48,8 +48,8 @@ func TestMultiStoreSelectsFirstHealthyCandidate(t *testing.T) { } func TestMultiStoreOpenFailureFallsThroughBeforePayload(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("secondary content") hash := hashForTest(content) primary := &recordingReadBackend{ @@ -84,6 +84,8 @@ func TestMultiStoreOpenFailureFallsThroughBeforePayload(t *testing.T) { } func TestMultiStoreRefreshesChangedResolutionAfterMissingLocation(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := []byte("migrated content") hash := hashForTest(content) oldLocation := ReadLocation{ @@ -111,21 +113,23 @@ func TestMultiStoreRefreshesChangedResolutionAfterMissingLocation(t *testing.T) staticBackendRegistry{"old": oldBackend, "new": newBackend}, MultiStoreOptions{}, ) - require.NoError(t, err) + require.NoError(err) stream, _, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) + require.NoError(err) got, err := io.ReadAll(stream) - require.NoError(t, err) - require.NoError(t, stream.Close()) + require.NoError(err) + require.NoError(stream.Close()) - assert.Equal(t, content, got) - assert.Equal(t, 2, resolver.calls) - assert.Equal(t, 1, oldBackend.opens) - assert.Equal(t, 1, newBackend.opens) + assert.Equal(content, got) + assert.Equal(2, resolver.calls) + assert.Equal(1, oldBackend.opens) + assert.Equal(1, newBackend.opens) } func TestMultiStoreRefreshesAfterMixedCorruptAndMissingLocations(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := []byte("migrated content after mixed failures") hash := hashForTest(content) corruptLocation := ReadLocation{ @@ -156,24 +160,24 @@ func TestMultiStoreRefreshesAfterMixedCorruptAndMissingLocations(t *testing.T) { }, MultiStoreOptions{}, ) - require.NoError(t, err) + require.NoError(err) stream, _, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) + require.NoError(err) got, err := io.ReadAll(stream) - require.NoError(t, err) - require.NoError(t, stream.Close()) - - assert.Equal(t, content, got) - assert.Equal(t, 2, resolver.calls) - assert.Equal(t, 1, corruptBackend.opens) - assert.Equal(t, 1, missingBackend.opens) - assert.Equal(t, 1, newBackend.opens) + require.NoError(err) + require.NoError(stream.Close()) + + assert.Equal(content, got) + assert.Equal(2, resolver.calls) + assert.Equal(1, corruptBackend.opens) + assert.Equal(1, missingBackend.opens) + assert.Equal(1, newBackend.opens) } func TestMultiStoreNextReadDemotesCorruptGeneration(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("redundant content") hash := hashForTest(content) primary := &recordingReadBackend{ @@ -217,8 +221,8 @@ func TestMultiStoreNextReadDemotesCorruptGeneration(t *testing.T) { } func TestMultiStoreGenerationChangeClearsDemotion(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("repaired content") hash := hashForTest(content) primary := &recordingReadBackend{ @@ -268,8 +272,8 @@ func TestMultiStoreGenerationChangeClearsDemotion(t *testing.T) { } func TestMultiStoreExhaustedPrecedence(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) hash := hashForTest([]byte("exhausted content")) resolution := Resolution{Member: true} backends := staticBackendRegistry{} @@ -315,8 +319,8 @@ func TestMultiStoreExhaustedPrecedence(t *testing.T) { } func TestMultiStoreOpenReturnsVerifiedSeekableContent(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("seekable multi-location content") hash := hashForTest(content) store, err := NewMultiStore( @@ -347,8 +351,8 @@ func TestMultiStoreOpenReturnsVerifiedSeekableContent(t *testing.T) { } func TestMultiStoreReadBoundedVerifiesWithinLimit(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("bounded multi-location content") hash := hashForTest(content) store, err := NewMultiStore( @@ -402,8 +406,8 @@ func TestMultiStoreReadBoundedPreflightsCatalogStoredSize(t *testing.T) { } for name, location := range locations { t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) backend := &recordingReadBackend{content: content} store, err := NewMultiStore( staticLocationResolver{resolution: Resolution{ @@ -429,6 +433,8 @@ func TestMultiStoreReadBoundedPreflightsCatalogStoredSize(t *testing.T) { } func TestMultiStoreReadBoundedFallsBackAfterStoredSizeLimit(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := []byte("stored representation fallback") hash := hashForTest(content) maxBytes := int64(len(content)) @@ -457,20 +463,20 @@ func TestMultiStoreReadBoundedFallsBackAfterStoredSizeLimit(t *testing.T) { staticBackendRegistry{"oversized": oversized, "healthy": healthy}, MultiStoreOptions{}, ) - require.NoError(t, err) + require.NoError(err) got, size, err := store.ReadBounded(context.Background(), hash, maxBytes) - require.NoError(t, err) - assert.Equal(t, content, got) - assert.Equal(t, maxBytes, size) - assert.Zero(t, oversized.opens) - assert.Equal(t, 1, healthy.opens) + require.NoError(err) + assert.Equal(content, got) + assert.Equal(maxBytes, size) + assert.Zero(oversized.opens) + assert.Equal(1, healthy.opens) } func TestMultiStoreRejectsMismatchedPackedCandidate(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("requested content") hash := hashForTest(content) otherHash := hashForTest([]byte("different content")) @@ -501,8 +507,8 @@ func TestMultiStoreRejectsMismatchedPackedCandidate(t *testing.T) { } func TestMultiStoreOpenRejectsNilBackendStream(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) hash := hashForTest([]byte("nil backend stream")) store, err := NewMultiStore( staticLocationResolver{resolution: Resolution{ @@ -526,8 +532,8 @@ func TestMultiStoreOpenRejectsNilBackendStream(t *testing.T) { } func TestMultiStoreRejectsUnknownLooseEncoding(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) hash := hashForTest([]byte("unknown encoding")) backend := &recordingReadBackend{content: []byte("unknown encoding")} store, err := NewMultiStore( @@ -566,20 +572,20 @@ func TestMultiStoreRejectsAmbiguousLooseLocation(t *testing.T) { staticBackendRegistry{"archive": backend}, MultiStoreOptions{}, ) - require.NoError(t, err) + Require.NoError(t, err) stream, _, err := store.OpenStream(context.Background(), hash) if stream != nil { t.Cleanup(func() { _ = stream.Close() }) } - require.ErrorIs(t, err, ErrInvalidPolicy) - assert.Zero(t, backend.opens) + Require.ErrorIs(t, err, ErrInvalidPolicy) + Assert.Zero(t, backend.opens) } func TestMultiStoreRejectsBackendSizeMismatch(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("size mismatch") hash := hashForTest(content) store, err := NewMultiStore( diff --git a/packstore/loose.go b/packstore/loose.go index 8a7960d..8a8113e 100644 --- a/packstore/loose.go +++ b/packstore/loose.go @@ -420,6 +420,9 @@ func (s *filesystemLooseStore) publish( if identity.Encoding == LooseEncodingZstd { selected = compressed } + if selected == nil { + return identity, fmt.Errorf("packstore: no loose staging file selected") + } if opts.Durability == DurablePublication { if err := syncLooseFile(selected.file); err != nil { return identity, fmt.Errorf("packstore: sync loose staging file: %w", err) @@ -681,7 +684,7 @@ func (f *stagedLooseFile) cleanup() (bool, error) { } func (f *stagedLooseFile) close() error { - if f.closed { + if f == nil || f.closed { return nil } f.closed = true @@ -1065,10 +1068,16 @@ func (v *looseVerifiedIdentity) close() error { } func sameLooseFileState(expected, actual fs.FileInfo) bool { + if expected == nil || actual == nil { + return false + } return os.SameFile(expected, actual) && expected.Size() == actual.Size() } func validateRegularNoFollow(path string, info fs.FileInfo) error { + if info == nil { + return fmt.Errorf("%w: %s has no file identity", ErrContentMismatch, path) + } if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() { return fmt.Errorf("%w: %s is not an independent regular file", ErrContentMismatch, path) } diff --git a/packstore/loose_existing_aba_linux_test.go b/packstore/loose_existing_aba_linux_test.go index ebdb40b..1acf5bb 100644 --- a/packstore/loose_existing_aba_linux_test.go +++ b/packstore/loose_existing_aba_linux_test.go @@ -11,8 +11,8 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) func TestLooseWriteFullHashRejectsEqualSizeReplacementAfterVerification(t *testing.T) { @@ -47,6 +47,8 @@ func TestLooseWriteFullHashRejectsEqualSizeReplacementAfterVerification(t *testi }, } { t.Run(encoding.name+"/"+api.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) opts := WriteOptions{ Durability: AtomicPublication, @@ -57,23 +59,23 @@ func TestLooseWriteFullHashRejectsEqualSizeReplacementAfterVerification(t *testi Compression: encoding.compression, } created, err := store.WriteBytes(context.Background(), content, opts) - require.NoError(t, err) - require.Equal(t, encoding.want, created.Encoding) + require.NoError(err) + require.Equal(encoding.want, created.Encoding) physical, err := os.ReadFile(created.Path) - require.NoError(t, err) + require.NoError(err) replacement := bytes.Repeat([]byte{0xa5}, len(physical)) replacementOutcome := installEqualSizeReplacementAtFinalSnapshot(t, created.Path, replacement) result, err := api.write(context.Background(), store, content, opts) - require.Error(t, err) - assert.True(t, errors.Is(err, ErrContentMismatch) || errors.Is(err, errIdentityChanged), err) - assert.False(t, result.Created) - assert.True(t, replacementOutcome.installed) - assert.True(t, replacementOutcome.pinLiveAtReplacement) - assert.True(t, replacementOutcome.replacementIdentityChanged) - assert.Equal(t, replacement, mustReadFile(t, created.Path)) + require.Error(err) + assert.True(errors.Is(err, ErrContentMismatch) || errors.Is(err, errIdentityChanged), err) + assert.False(result.Created) + assert.True(replacementOutcome.installed) + assert.True(replacementOutcome.pinLiveAtReplacement) + assert.True(replacementOutcome.replacementIdentityChanged) + assert.Equal(replacement, mustReadFile(t, created.Path)) }) } } @@ -94,6 +96,8 @@ func TestLooseDurableTypeAndSizeRejectsEqualSizeReplacementAfterSync(t *testing. }, } { t.Run(encoding.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) createOpts := WriteOptions{ Durability: AtomicPublication, @@ -104,10 +108,10 @@ func TestLooseDurableTypeAndSizeRejectsEqualSizeReplacementAfterSync(t *testing. Compression: encoding.compression, } created, err := store.WriteBytes(context.Background(), content, createOpts) - require.NoError(t, err) - require.Equal(t, encoding.want, created.Encoding) + require.NoError(err) + require.Equal(encoding.want, created.Encoding) physical, err := os.ReadFile(created.Path) - require.NoError(t, err) + require.NoError(err) replacement := bytes.Repeat([]byte{0x5a}, len(physical)) replacementOutcome := installEqualSizeReplacementAtFinalSnapshot(t, created.Path, replacement) @@ -120,13 +124,13 @@ func TestLooseDurableTypeAndSizeRejectsEqualSizeReplacementAfterSync(t *testing. Compression: encoding.compression, }) - require.Error(t, err) - assert.True(t, errors.Is(err, ErrContentMismatch) || errors.Is(err, errIdentityChanged), err) - assert.False(t, result.Created) - assert.True(t, replacementOutcome.installed) - assert.True(t, replacementOutcome.pinLiveAtReplacement) - assert.True(t, replacementOutcome.replacementIdentityChanged) - assert.Equal(t, replacement, mustReadFile(t, created.Path)) + require.Error(err) + assert.True(errors.Is(err, ErrContentMismatch) || errors.Is(err, errIdentityChanged), err) + assert.False(result.Created) + assert.True(replacementOutcome.installed) + assert.True(replacementOutcome.pinLiveAtReplacement) + assert.True(replacementOutcome.replacementIdentityChanged) + assert.Equal(replacement, mustReadFile(t, created.Path)) }) } } @@ -177,10 +181,10 @@ func installEqualSizeReplacementAtFinalSnapshot(t *testing.T, path string, repla return info, nil } outcome.installed = true - require.NoError(t, os.Remove(gotPath)) - require.NoError(t, os.WriteFile(gotPath, replacement, 0o600)) + Require.NoError(t, os.Remove(gotPath)) + Require.NoError(t, os.WriteFile(gotPath, replacement, 0o600)) replacementInfo, snapshotErr := originalSnapshot(gotPath) - require.NoError(t, snapshotErr) + Require.NoError(t, snapshotErr) outcome.pinLiveAtReplacement = pinLive outcome.replacementIdentityChanged = !os.SameFile(originalIdentity, replacementInfo) if !pinLive { diff --git a/packstore/loose_publish_supported_test.go b/packstore/loose_publish_supported_test.go index d204f4a..04e4f2d 100644 --- a/packstore/loose_publish_supported_test.go +++ b/packstore/loose_publish_supported_test.go @@ -8,25 +8,27 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) func TestLoosePublicationFallbackNeverReplacesExistingDestination(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) dir := t.TempDir() staging := filepath.Join(dir, "staging") final := filepath.Join(dir, "final") stagedContent := []byte("new staged content") existingContent := []byte("existing canonical content") - require.NoError(t, os.WriteFile(staging, stagedContent, 0o600)) - require.NoError(t, os.WriteFile(final, existingContent, 0o600)) + require.NoError(os.WriteFile(staging, stagedContent, 0o600)) + require.NoError(os.WriteFile(final, existingContent, 0o600)) originalLink := linkLoosePublicationFile linkLoosePublicationFile = func(string, string) error { return fs.ErrInvalid } t.Cleanup(func() { linkLoosePublicationFile = originalLink }) err := publishLooseFileNoReplace(staging, final) - require.ErrorIs(t, err, fs.ErrExist) - assert.Equal(t, stagedContent, mustReadFile(t, staging)) - assert.Equal(t, existingContent, mustReadFile(t, final)) + require.ErrorIs(err, fs.ErrExist) + assert.Equal(stagedContent, mustReadFile(t, staging)) + assert.Equal(existingContent, mustReadFile(t, final)) } diff --git a/packstore/loose_remove_aba_unix_test.go b/packstore/loose_remove_aba_unix_test.go index 32e87a0..39436a9 100644 --- a/packstore/loose_remove_aba_unix_test.go +++ b/packstore/loose_remove_aba_unix_test.go @@ -8,35 +8,38 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) func TestLooseRemovePreservesExactSizeReplacementAtClaimBoundary(t *testing.T) { + require := Require.New(t) layout := layoutForStoreTest(t) store, err := NewLooseStore(layout) - require.NoError(t, err) + require.NoError(err) content := []byte("original exact-size loose source") written, err := store.WriteBytes(context.Background(), content, WriteOptions{ Durability: AtomicPublication, Dedup: VerifyFullHash, }) - require.NoError(t, err) + require.NoError(err) replacement := []byte("foreign! exact-size loose source") - require.Len(t, replacement, len(content)) + require.Len(replacement, len(content)) installExactSizeRemovalReplacement(t, written.Path, replacement) err = store.Remove(written.Hash, BestEffortRemoval) - require.ErrorIs(t, err, errIdentityChanged) - assert.Equal(t, replacement, mustReadFile(t, written.Path)) + require.ErrorIs(err, errIdentityChanged) + Assert.Equal(t, replacement, mustReadFile(t, written.Path)) assertNoLooseRemovalClaims(t, written.Path) } func TestPackSweepPreservesExactSizeReplacementAtClaimBoundary(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := []byte("original exact-size sweep source") entry := buildStoreTestPack(t, layout, content) - require.Equal(t, entry.Hash, writeMaintenanceLoose(t, layout, content)) + require.Equal(entry.Hash, writeMaintenanceLoose(t, layout, content)) catalog := newMaintenanceCatalog() catalog.members[entry.Hash] = Reference{Hash: entry.Hash} catalog.entries[entry.Hash] = entry @@ -45,15 +48,15 @@ func TestPackSweepPreservesExactSizeReplacementAtClaimBoundary(t *testing.T) { } path := layout.LoosePath(entry.Hash) replacement := []byte("foreign! exact-size sweep source") - require.Len(t, replacement, len(content)) + require.Len(replacement, len(content)) installExactSizeRemovalReplacement(t, path, replacement) maintainer := newMaintainerForTest(t, catalog, layout, DefaultLimits()) stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.ErrorIs(t, err, errIdentityChanged) - assert.Zero(t, stats.LooseSwept) - assert.Equal(t, replacement, mustReadFile(t, path)) + require.ErrorIs(err, errIdentityChanged) + assert.Zero(stats.LooseSwept) + assert.Equal(replacement, mustReadFile(t, path)) assertNoLooseRemovalClaims(t, path) } @@ -66,9 +69,9 @@ func installExactSizeRemovalReplacement(t *testing.T, path string, replacement [ return } triggered = true - require.NoError(t, os.Remove(path)) - require.NoError(t, os.WriteFile(path, replacement, 0o600)) + Require.NoError(t, os.Remove(path)) + Require.NoError(t, os.WriteFile(path, replacement, 0o600)) } t.Cleanup(func() { beforeLooseRemovalClaim = originalHook }) - t.Cleanup(func() { assert.True(t, triggered, "removal reached the exact-size replacement boundary") }) + t.Cleanup(func() { Assert.True(t, triggered, "removal reached the exact-size replacement boundary") }) } diff --git a/packstore/loose_remove_race_test.go b/packstore/loose_remove_race_test.go index 9924bb8..7a11d71 100644 --- a/packstore/loose_remove_race_test.go +++ b/packstore/loose_remove_race_test.go @@ -9,8 +9,8 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) func TestLooseRemovePreservesReplacementAtClaimBoundary(t *testing.T) { @@ -22,11 +22,11 @@ func TestLooseRemovePreservesReplacementAtClaimBoundary(t *testing.T) { name: "regular file", replace: func(t *testing.T, path string) func(*testing.T, string) { replacement := []byte("foreign regular replacement") - require.NoError(t, os.WriteFile(path, replacement, 0o600)) + Require.NoError(t, os.WriteFile(path, replacement, 0o600)) return func(t *testing.T, path string) { got, err := os.ReadFile(path) - require.NoError(t, err) - assert.Equal(t, replacement, got) + Require.NoError(t, err) + Assert.Equal(t, replacement, got) } }, }, @@ -34,15 +34,15 @@ func TestLooseRemovePreservesReplacementAtClaimBoundary(t *testing.T) { name: "symlink", replace: func(t *testing.T, path string) func(*testing.T, string) { target := filepath.Join(t.TempDir(), "target") - require.NoError(t, os.WriteFile(target, []byte("target remains"), 0o600)) - require.NoError(t, os.Symlink(target, path)) + Require.NoError(t, os.WriteFile(target, []byte("target remains"), 0o600)) + Require.NoError(t, os.Symlink(target, path)) return func(t *testing.T, path string) { info, err := os.Lstat(path) - require.NoError(t, err) - assert.NotZero(t, info.Mode()&os.ModeSymlink) + Require.NoError(t, err) + Assert.NotZero(t, info.Mode()&os.ModeSymlink) gotTarget, err := os.Readlink(path) - require.NoError(t, err) - assert.Equal(t, target, gotTarget) + Require.NoError(t, err) + Assert.Equal(t, target, gotTarget) } }, }, @@ -56,29 +56,31 @@ func TestLooseRemovePreservesReplacementAtClaimBoundary(t *testing.T) { Durability: AtomicPublication, Dedup: VerifyFullHash, }) - require.NoError(t, err) + Require.NoError(t, err) held := installLooseRemovalReplacement(t, written.Path, tt.replace) err = store.Remove(written.Hash, BestEffortRemoval) - require.ErrorIs(t, err, errIdentityChanged) - assert.FileExists(t, held, "the displaced intended object remains outside the cleanup path") + Require.ErrorIs(t, err, errIdentityChanged) + Assert.FileExists(t, held, "the displaced intended object remains outside the cleanup path") assertNoLooseRemovalClaims(t, written.Path) }) } } func TestLooseRemoveDoesNotClobberNewerOccupantWhileRestoringForeignClaim(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := []byte("intended loose object") store := newLooseStoreForTest(t, StagingSameDirectory) written, err := store.WriteBytes(context.Background(), content, WriteOptions{ Durability: AtomicPublication, Dedup: VerifyFullHash, }) - require.NoError(t, err) + require.NoError(err) foreign := []byte("foreign replacement claimed for removal") _ = installLooseRemovalReplacement(t, written.Path, func(t *testing.T, path string) func(*testing.T, string) { - require.NoError(t, os.WriteFile(path, foreign, 0o600)) + Require.NoError(t, os.WriteFile(path, foreign, 0o600)) return func(*testing.T, string) {} }) newer := []byte("newer occupant must win") @@ -99,59 +101,63 @@ func TestLooseRemoveDoesNotClobberNewerOccupantWhileRestoringForeignClaim(t *tes err = store.Remove(written.Hash, BestEffortRemoval) - require.ErrorIs(t, err, errIdentityChanged) - require.ErrorIs(t, err, fs.ErrExist) - assert.Equal(t, newer, mustReadFile(t, written.Path)) + require.ErrorIs(err, errIdentityChanged) + require.ErrorIs(err, fs.ErrExist) + assert.Equal(newer, mustReadFile(t, written.Path)) claims, globErr := filepath.Glob(filepath.Join( filepath.Dir(written.Path), "."+filepath.Base(written.Path)+".remove-*", )) - require.NoError(t, globErr) - require.Len(t, claims, 1, "the un-restorable foreign entry remains preserved") - assert.Equal(t, foreign, mustReadFile(t, filepath.Join(claims[0], "claimed"))) + require.NoError(globErr) + require.Len(claims, 1, "the un-restorable foreign entry remains preserved") + assert.Equal(foreign, mustReadFile(t, filepath.Join(claims[0], "claimed"))) } func TestLooseRemovePublishesCompleteRegularRestoreAtomically(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) written, err := store.WriteBytes(context.Background(), []byte("atomic restore source"), WriteOptions{ Durability: AtomicPublication, Dedup: VerifyFullHash, }) - require.NoError(t, err) + require.NoError(err) foreign := bytes.Repeat([]byte("complete foreign restoration\n"), 4096) _ = installLooseRemovalReplacement(t, written.Path, func(t *testing.T, path string) func(*testing.T, string) { - require.NoError(t, os.WriteFile(path, foreign, 0o600)) + Require.NoError(t, os.WriteFile(path, foreign, 0o600)) return func(t *testing.T, path string) { - assert.Equal(t, foreign, mustReadFile(t, path)) + Assert.Equal(t, foreign, mustReadFile(t, path)) } }) originalBeforePublish := beforeLooseRemovalRestorePublish publishReached := false beforeLooseRemovalRestorePublish = func(stagingPath, canonicalPath string) { publishReached = true - assert.Equal(t, foreign, mustReadFile(t, stagingPath), "private staging is complete before publication") + assert.Equal(foreign, mustReadFile(t, stagingPath), "private staging is complete before publication") _, statErr := os.Lstat(canonicalPath) - require.ErrorIs(t, statErr, fs.ErrNotExist, "readers cannot observe restoration while it is being copied") + require.ErrorIs(statErr, fs.ErrNotExist, "readers cannot observe restoration while it is being copied") } t.Cleanup(func() { beforeLooseRemovalRestorePublish = originalBeforePublish }) err = store.Remove(written.Hash, BestEffortRemoval) - require.ErrorIs(t, err, errIdentityChanged) - assert.True(t, publishReached) - assert.Equal(t, foreign, mustReadFile(t, written.Path)) + require.ErrorIs(err, errIdentityChanged) + assert.True(publishReached) + assert.Equal(foreign, mustReadFile(t, written.Path)) assertNoLooseRemovalClaims(t, written.Path) } func TestLooseRemoveRestoresConcurrentClaimWrites(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) written, err := store.WriteBytes(context.Background(), []byte("concurrent restore source"), WriteOptions{ Durability: AtomicPublication, Dedup: VerifyFullHash, }) - require.NoError(t, err) + require.NoError(err) foreign := []byte("foreign replacement before restore") _ = installLooseRemovalReplacement(t, written.Path, func(t *testing.T, path string) func(*testing.T, string) { - require.NoError(t, os.WriteFile(path, foreign, 0o600)) + Require.NoError(t, os.WriteFile(path, foreign, 0o600)) return func(*testing.T, string) {} }) updated := []byte("foreign replacement after writer update") @@ -165,26 +171,26 @@ func TestLooseRemoveRestoresConcurrentClaimWrites(t *testing.T) { os.O_WRONLY|os.O_TRUNC, 0, ) - require.NoError(t, openErr) + require.NoError(openErr) writerIdentity, openErr = writer.Stat() - require.NoError(t, openErr) + require.NoError(openErr) _, writeErr := writer.Write(updated) - require.NoError(t, writeErr) - require.NoError(t, writer.Sync()) - require.NoError(t, writer.Close()) + require.NoError(writeErr) + require.NoError(writer.Sync()) + require.NoError(writer.Close()) _, statErr := os.Lstat(canonicalPath) - require.ErrorIs(t, statErr, fs.ErrNotExist) + require.ErrorIs(statErr, fs.ErrNotExist) } t.Cleanup(func() { beforeLooseRemovalRestorePublish = originalBeforePublish }) err = store.Remove(written.Hash, BestEffortRemoval) - require.ErrorIs(t, err, errIdentityChanged) - assert.True(t, writerRan) - assert.Equal(t, updated, mustReadFile(t, written.Path)) + require.ErrorIs(err, errIdentityChanged) + assert.True(writerRan) + assert.Equal(updated, mustReadFile(t, written.Path)) canonicalIdentity, statErr := os.Stat(written.Path) - require.NoError(t, statErr) - assert.True(t, os.SameFile(writerIdentity, canonicalIdentity), "restoration keeps the writer's exact inode") + require.NoError(statErr) + assert.True(os.SameFile(writerIdentity, canonicalIdentity), "restoration keeps the writer's exact inode") assertNoLooseRemovalClaims(t, written.Path) } @@ -197,37 +203,39 @@ func TestLooseRemoveTreatsPostPublicationReplacementAsLaterAction(t *testing.T) { name: "regular file", replace: func(t *testing.T, path string) func(*testing.T, string) { - require.NoError(t, os.WriteFile(path, []byte("published regular foreign entry"), 0o600)) + Require.NoError(t, os.WriteFile(path, []byte("published regular foreign entry"), 0o600)) return func(*testing.T, string) {} }, check: func(t *testing.T, path string) { - assert.Equal(t, []byte("published regular foreign entry"), mustReadFile(t, path)) + Assert.Equal(t, []byte("published regular foreign entry"), mustReadFile(t, path)) }, }, { name: "symlink", replace: func(t *testing.T, path string) func(*testing.T, string) { target := filepath.Join(t.TempDir(), "foreign-target") - require.NoError(t, os.WriteFile(target, []byte("target"), 0o600)) - require.NoError(t, os.Symlink(target, path)) + Require.NoError(t, os.WriteFile(target, []byte("target"), 0o600)) + Require.NoError(t, os.Symlink(target, path)) return func(*testing.T, string) {} }, check: func(t *testing.T, path string) { info, err := os.Lstat(path) - require.NoError(t, err) - assert.NotZero(t, info.Mode()&os.ModeSymlink) + Require.NoError(t, err) + Assert.NotZero(t, info.Mode()&os.ModeSymlink) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) written, err := store.WriteBytes(context.Background(), []byte("post-publication source"), WriteOptions{ Durability: AtomicPublication, Dedup: VerifyFullHash, }) - require.NoError(t, err) + require.NoError(err) _ = installLooseRemovalReplacement(t, written.Path, tt.replace) newer := []byte("later external occupant") originalAfterPublish := afterLooseRemovalRestorePublish @@ -235,35 +243,37 @@ func TestLooseRemoveTreatsPostPublicationReplacementAsLaterAction(t *testing.T) afterLooseRemovalRestorePublish = func(path string) { published = true tt.check(t, path) - require.NoError(t, os.Remove(path)) - require.NoError(t, os.WriteFile(path, newer, 0o600)) + require.NoError(os.Remove(path)) + require.NoError(os.WriteFile(path, newer, 0o600)) } t.Cleanup(func() { afterLooseRemovalRestorePublish = originalAfterPublish }) err = store.Remove(written.Hash, BestEffortRemoval) - require.ErrorIs(t, err, errIdentityChanged) - assert.True(t, published) - assert.Equal(t, newer, mustReadFile(t, written.Path)) + require.ErrorIs(err, errIdentityChanged) + assert.True(published) + assert.Equal(newer, mustReadFile(t, written.Path)) assertNoLooseRemovalClaims(t, written.Path) }) } } func TestLooseRemoveDoesNotClobberPreexistingAsideName(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) written, err := store.WriteBytes(context.Background(), []byte("aside collision source"), WriteOptions{ Durability: AtomicPublication, Dedup: VerifyFullHash, }) - require.NoError(t, err) + require.NoError(err) originalCreate := createLooseRemovalAside var collision string createLooseRemovalAside = func(path string) error { if collision == "" { collision = path - require.NoError(t, os.Mkdir(path, 0o700)) - require.NoError(t, os.WriteFile(filepath.Join(path, "owner"), []byte("preexisting"), 0o600)) + require.NoError(os.Mkdir(path, 0o700)) + require.NoError(os.WriteFile(filepath.Join(path, "owner"), []byte("preexisting"), 0o600)) return fs.ErrExist } return originalCreate(path) @@ -272,19 +282,21 @@ func TestLooseRemoveDoesNotClobberPreexistingAsideName(t *testing.T) { err = store.Remove(written.Hash, BestEffortRemoval) - require.NoError(t, err) - assert.NoFileExists(t, written.Path) - require.NotEmpty(t, collision) - assert.Equal(t, []byte("preexisting"), mustReadFile(t, filepath.Join(collision, "owner"))) + require.NoError(err) + assert.NoFileExists(written.Path) + require.NotEmpty(collision) + assert.Equal([]byte("preexisting"), mustReadFile(t, filepath.Join(collision, "owner"))) } func TestLooseRemovePreservesUnsupportedForeignReplacementInAside(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) written, err := store.WriteBytes(context.Background(), []byte("unsupported replacement source"), WriteOptions{ Durability: AtomicPublication, Dedup: VerifyFullHash, }) - require.NoError(t, err) + require.NoError(err) held := written.Path + ".held" originalHook := beforeLooseRemovalClaim triggered := false @@ -293,31 +305,33 @@ func TestLooseRemovePreservesUnsupportedForeignReplacementInAside(t *testing.T) return } triggered = true - require.NoError(t, os.Rename(path, held)) - require.NoError(t, os.Mkdir(path, 0o700)) - require.NoError(t, os.WriteFile(filepath.Join(path, "owner"), []byte("foreign directory"), 0o600)) + require.NoError(os.Rename(path, held)) + require.NoError(os.Mkdir(path, 0o700)) + require.NoError(os.WriteFile(filepath.Join(path, "owner"), []byte("foreign directory"), 0o600)) } t.Cleanup(func() { beforeLooseRemovalClaim = originalHook }) err = store.Remove(written.Hash, BestEffortRemoval) - require.ErrorIs(t, err, errIdentityChanged) - require.ErrorContains(t, err, "unsupported mode") - assert.FileExists(t, held) - assert.NoDirExists(t, written.Path, "unsupported entries are never recreated unsafely") + require.ErrorIs(err, errIdentityChanged) + require.ErrorContains(err, "unsupported mode") + assert.FileExists(held) + assert.NoDirExists(written.Path, "unsupported entries are never recreated unsafely") claims, globErr := filepath.Glob(filepath.Join( filepath.Dir(written.Path), "."+filepath.Base(written.Path)+".remove-*", )) - require.NoError(t, globErr) - require.Len(t, claims, 1) - assert.Equal(t, []byte("foreign directory"), mustReadFile(t, filepath.Join(claims[0], "claimed", "owner"))) + require.NoError(globErr) + require.Len(claims, 1) + assert.Equal([]byte("foreign directory"), mustReadFile(t, filepath.Join(claims[0], "claimed", "owner"))) } func TestPackSweepPreservesReplacementAtClaimBoundary(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := []byte("redundant indexed loose object") entry := buildStoreTestPack(t, layout, content) - require.Equal(t, entry.Hash, writeMaintenanceLoose(t, layout, content)) + require.Equal(entry.Hash, writeMaintenanceLoose(t, layout, content)) catalog := newMaintenanceCatalog() catalog.members[entry.Hash] = Reference{Hash: entry.Hash} catalog.entries[entry.Hash] = entry @@ -325,55 +339,58 @@ func TestPackSweepPreservesReplacementAtClaimBoundary(t *testing.T) { path := layout.LoosePath(entry.Hash) replacement := []byte("foreign indexed replacement") held := installLooseRemovalReplacement(t, path, func(t *testing.T, path string) func(*testing.T, string) { - require.NoError(t, os.WriteFile(path, replacement, 0o600)) + Require.NoError(t, os.WriteFile(path, replacement, 0o600)) return func(t *testing.T, path string) { got, err := os.ReadFile(path) - require.NoError(t, err) - assert.Equal(t, replacement, got) + Require.NoError(t, err) + Assert.Equal(t, replacement, got) } }) maintainer := newMaintainerForTest(t, catalog, layout, DefaultLimits()) stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.ErrorIs(t, err, errIdentityChanged) - assert.Zero(t, stats.LooseSwept) - assert.FileExists(t, held) - assert.Equal(t, replacement, mustReadFile(t, path)) + require.ErrorIs(err, errIdentityChanged) + assert.Zero(stats.LooseSwept) + assert.FileExists(held) + assert.Equal(replacement, mustReadFile(t, path)) assertNoLooseRemovalClaims(t, path) } func TestPackOrphanSweepPreservesReplacementAtClaimBoundary(t *testing.T) { + assert := Assert.New(t) layout := layoutForStoreTest(t) hash := writeMaintenanceLoose(t, layout, []byte("orphan loose object")) path := layout.LoosePath(hash) replacement := []byte("foreign orphan replacement") held := installLooseRemovalReplacement(t, path, func(t *testing.T, path string) func(*testing.T, string) { - require.NoError(t, os.WriteFile(path, replacement, 0o600)) + Require.NoError(t, os.WriteFile(path, replacement, 0o600)) return func(t *testing.T, path string) { got, err := os.ReadFile(path) - require.NoError(t, err) - assert.Equal(t, replacement, got) + Require.NoError(t, err) + Assert.Equal(t, replacement, got) } }) maintainer := newMaintainerForTest(t, newMaintenanceCatalog(), layout, DefaultLimits()) stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.ErrorIs(t, err, errIdentityChanged) - assert.Zero(t, stats.LooseOrphansRemoved) - assert.FileExists(t, held) - assert.Equal(t, replacement, mustReadFile(t, path)) + Require.ErrorIs(t, err, errIdentityChanged) + assert.Zero(stats.LooseOrphansRemoved) + assert.FileExists(held) + assert.Equal(replacement, mustReadFile(t, path)) assertNoLooseRemovalClaims(t, path) } func TestPackReportsNoncanonicalSourceClaimFailureAfterCatalogCommit(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := bytes.Repeat([]byte("noncanonical packed source\n"), 32) hash := hashForTest(content) path := filepath.Join(layout.Root(), "legacy", hash.String()) - require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) - require.NoError(t, os.WriteFile(path, content, 0o600)) + require.NoError(os.MkdirAll(filepath.Dir(path), 0o700)) + require.NoError(os.WriteFile(path, content, 0o600)) catalog := newMaintenanceCatalog() addMaintenanceCandidate(catalog, Candidate{Hash: hash, Paths: []string{path}, Size: int64(len(content))}) claimErr := errors.New("injected packed source claim failure") @@ -389,12 +406,12 @@ func TestPackReportsNoncanonicalSourceClaimFailureAfterCatalogCommit(t *testing. stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.ErrorIs(t, err, claimErr) - assert.Equal(t, 1, stats.PacksSealed) - assert.Equal(t, 1, stats.BlobsPacked) - assert.FileExists(t, path, "failed cleanup remains retry-visible") + require.ErrorIs(err, claimErr) + assert.Equal(1, stats.PacksSealed) + assert.Equal(1, stats.BlobsPacked) + assert.FileExists(path, "failed cleanup remains retry-visible") got, _ := readStoreTest(t, maintainer.store, hash) - assert.Equal(t, content, got, "the committed pack remains authoritative") + assert.Equal(content, got, "the committed pack remains authoritative") } func installLooseRemovalReplacement( @@ -412,12 +429,12 @@ func installLooseRemovalReplacement( return } triggered = true - require.NoError(t, os.Rename(path, held)) + Require.NoError(t, os.Rename(path, held)) check = replace(t, path) } t.Cleanup(func() { beforeLooseRemovalClaim = originalHook }) t.Cleanup(func() { - require.True(t, triggered, "cleanup reached the deterministic claim boundary") + Require.True(t, triggered, "cleanup reached the deterministic claim boundary") if check != nil { check(t, path) } @@ -430,6 +447,6 @@ func assertNoLooseRemovalClaims(t *testing.T, canonical string) { matches, err := filepath.Glob(filepath.Join( filepath.Dir(canonical), "."+filepath.Base(canonical)+".remove-*", )) - require.NoError(t, err) - assert.Empty(t, matches) + Require.NoError(t, err) + Assert.Empty(t, matches) } diff --git a/packstore/loose_remove_windows_test.go b/packstore/loose_remove_windows_test.go index 7040c2d..b52e585 100644 --- a/packstore/loose_remove_windows_test.go +++ b/packstore/loose_remove_windows_test.go @@ -9,40 +9,42 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) func TestWindowsLooseRemovalUnlinksActiveStream(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) loose, err := NewLooseStore(layout) - require.NoError(t, err) + require.NoError(err) content := bytes.Repeat([]byte("active Windows loose reader\n"), 128) written, err := loose.WriteBytes(context.Background(), content, WriteOptions{ Durability: AtomicPublication, Dedup: VerifyFullHash, }) - require.NoError(t, err) + require.NoError(err) store := newStoreForTest(t, &mapResolver{locations: map[Hash]Location{ written.Hash: {Member: true}, }}, layout) stream, size, err := store.OpenStream(context.Background(), written.Hash) - require.NoError(t, err) - require.Equal(t, int64(len(content)), size) - t.Cleanup(func() { require.NoError(t, stream.Close()) }) + require.NoError(err) + require.Equal(int64(len(content)), size) + t.Cleanup(func() { require.NoError(stream.Close()) }) prefix := make([]byte, 37) _, err = io.ReadFull(stream, prefix) - require.NoError(t, err) + require.NoError(err) err = loose.Remove(written.Hash, BestEffortRemoval) - require.NoError(t, err) - assert.NoFileExists(t, written.Path) + require.NoError(err) + assert.NoFileExists(written.Path) assertNoLooseRemovalClaims(t, written.Path) remainder, err := io.ReadAll(stream) - require.NoError(t, err) - assert.Equal(t, content, append(prefix, remainder...)) - require.NoError(t, stream.Verify()) + require.NoError(err) + assert.Equal(content, append(prefix, remainder...)) + require.NoError(stream.Verify()) } func TestWindowsLooseCleanupRemovesClaimDirectories(t *testing.T) { @@ -52,20 +54,22 @@ func TestWindowsLooseCleanupRemovesClaimDirectories(t *testing.T) { Durability: AtomicPublication, Dedup: VerifyFullHash, }) - require.NoError(t, err) + Require.NoError(t, err) err = store.Remove(written.Hash, BestEffortRemoval) - require.NoError(t, err) - assert.NoFileExists(t, written.Path) + Require.NoError(t, err) + Assert.NoFileExists(t, written.Path) assertNoLooseRemovalClaims(t, written.Path) }) t.Run("redundant sweep", func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := []byte("Windows redundant loose sweep") entry := buildStoreTestPack(t, layout, content) - require.Equal(t, entry.Hash, writeMaintenanceLoose(t, layout, content)) + require.Equal(entry.Hash, writeMaintenanceLoose(t, layout, content)) catalog := newMaintenanceCatalog() catalog.members[entry.Hash] = Reference{Hash: entry.Hash} catalog.entries[entry.Hash] = entry @@ -76,9 +80,9 @@ func TestWindowsLooseCleanupRemovesClaimDirectories(t *testing.T) { stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) - assert.Equal(t, 1, stats.LooseSwept) - assert.NoFileExists(t, layout.LoosePath(entry.Hash)) + require.NoError(err) + assert.Equal(1, stats.LooseSwept) + assert.NoFileExists(layout.LoosePath(entry.Hash)) assertNoLooseRemovalClaims(t, layout.LoosePath(entry.Hash)) }) @@ -92,9 +96,9 @@ func TestWindowsLooseCleanupRemovesClaimDirectories(t *testing.T) { stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) - assert.Equal(t, 1, stats.BlobsPacked) - assert.NoFileExists(t, layout.LoosePath(hash)) + Require.NoError(t, err) + Assert.Equal(t, 1, stats.BlobsPacked) + Assert.NoFileExists(t, layout.LoosePath(hash)) assertNoLooseRemovalClaims(t, layout.LoosePath(hash)) }) } diff --git a/packstore/loose_repair_reconcile.go b/packstore/loose_repair_reconcile.go index 6cc5ec1..473d9e1 100644 --- a/packstore/loose_repair_reconcile.go +++ b/packstore/loose_repair_reconcile.go @@ -75,13 +75,13 @@ func reconcileLooseRepairReplacement( backupState, err := inspectLooseRepairPath(backup, verified) if err != nil { return looseRepairPublishResult{ - KeepStaging: stagingState.matches, - SyncShard: true, - SyncStaging: stagingState.matches, - }, errors.Join( - replaceErr, - fmt.Errorf("inspect repair backup path: %w", err), - ) + KeepStaging: stagingState.matches, + SyncShard: true, + SyncStaging: stagingState.matches, + }, errors.Join( + replaceErr, + fmt.Errorf("inspect repair backup path: %w", err), + ) } if finalState.matches { @@ -109,14 +109,14 @@ func reconcileLooseRepairReplacement( ) } return looseRepairPublishResult{ - KeepStaging: true, - SyncShard: backupState.exists || finalAfter.exists, - SyncStaging: true, - }, errors.Join( - replaceErr, - fmt.Errorf("restore verified repair staging: %w", linkErr), - inspectErr, - ) + KeepStaging: true, + SyncShard: backupState.exists || finalAfter.exists, + SyncStaging: true, + }, errors.Join( + replaceErr, + fmt.Errorf("restore verified repair staging: %w", linkErr), + inspectErr, + ) } return looseRepairPublishResult{Created: true, SyncShard: true}, errors.Join( replaceErr, diff --git a/packstore/loose_test.go b/packstore/loose_test.go index 5e1a5aa..346d127 100644 --- a/packstore/loose_test.go +++ b/packstore/loose_test.go @@ -15,16 +15,16 @@ import ( "time" "github.com/klauspost/compress/zstd" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) var errInjectedPrimary = errors.New("injected primary failure") func TestLooseWriteStreamsAndChecksExpectedMetadata(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("streamed-content-"), 32*1024) hash := hashForTest(content) size := int64(len(content)) @@ -50,8 +50,8 @@ func TestLooseWriteStreamsAndChecksExpectedMetadata(t *testing.T) { } func TestLooseWriteBytesComputesIdentityBeforeSameDirectoryStaging(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("in-memory-content-"), 4096) store := newLooseStoreForTest(t, StagingSameDirectory) @@ -71,7 +71,7 @@ func TestLooseWriteBytesComputesIdentityBeforeSameDirectoryStaging(t *testing.T) } func TestLooseWriteBytesChecksExpectedMetadata(t *testing.T) { - require := require.New(t) + require := Require.New(t) content := []byte("actual") store := newLooseStoreForTest(t, StagingSameDirectory) @@ -92,7 +92,7 @@ func TestLooseWriteBytesChecksExpectedMetadata(t *testing.T) { } func TestLooseWriteRejectsHashAndSizeMismatch(t *testing.T) { - require := require.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) content := []byte("actual") wrongHash := hashForTest([]byte("other")) @@ -108,11 +108,11 @@ func TestLooseWriteRejectsHashAndSizeMismatch(t *testing.T) { ExpectedSize: int64(len(content) + 1), SizeKnown: true, }) require.ErrorIs(err, ErrContentMismatch) - assert.NoFileExists(t, store.layout.LoosePath(actualHash)) + Assert.NoFileExists(t, store.layout.LoosePath(actualHash)) } func TestLooseWriteCancellationAndLimitCleanStaging(t *testing.T) { - require := require.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingStoreDirectory) stagingDir := filepath.Join(store.layout.Root(), "tmp") @@ -122,13 +122,13 @@ func TestLooseWriteCancellationAndLimitCleanStaging(t *testing.T) { Durability: AtomicPublication, Dedup: VerifyFullHash, }) require.ErrorIs(err, context.Canceled) - assert.Empty(t, matchingFiles(t, stagingDir, ".staging-")) + Assert.Empty(t, matchingFiles(t, stagingDir, ".staging-")) _, err = store.Write(context.Background(), bytes.NewReader([]byte("too large")), WriteOptions{ Durability: AtomicPublication, Dedup: VerifyFullHash, MaxBytes: 3, }) require.ErrorIs(err, ErrContentMismatch) - assert.Empty(t, matchingFiles(t, stagingDir, ".staging-")) + Assert.Empty(t, matchingFiles(t, stagingDir, ".staging-")) } func TestLooseWriteCompressionCancellationCleansStaging(t *testing.T) { @@ -136,7 +136,7 @@ func TestLooseWriteCompressionCancellationCleansStaging(t *testing.T) { for _, staging := range []StagingMode{StagingSameDirectory, StagingStoreDirectory} { for _, cancelAt := range []string{"source read", "zstd write"} { t.Run(cancelAt+"/"+stagingName(staging), func(t *testing.T) { - require := require.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, staging) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -187,7 +187,7 @@ func TestLooseWriteCompressionFailureCleansStaging(t *testing.T) { Compression: LooseCompressionOptions{Enabled: true}, }) - require.ErrorIs(t, err, writeErr) + Require.ErrorIs(t, err, writeErr) assertNoLooseWriteResidue(t, store, hashForTest(content)) } @@ -210,8 +210,8 @@ func TestLooseWritePublicationFailureCleansStaging(t *testing.T) { }, }) - require.ErrorIs(t, err, publishErr) - assert.Equal(t, hashForTest(content), result.Hash) + Require.ErrorIs(t, err, publishErr) + Assert.Equal(t, hashForTest(content), result.Hash) assertNoLooseWriteResidue(t, store, result.Hash) }) } @@ -228,6 +228,8 @@ func TestLooseWriteFallsBackWhenHardLinksAreUnsupported(t *testing.T) { name = "compressed" } t.Run(name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingStoreDirectory) originalLink := linkLoosePublicationFile linkCalls := 0 @@ -244,11 +246,11 @@ func TestLooseWriteFallsBackWhenHardLinksAreUnsupported(t *testing.T) { Compression: compression, }) - require.NoError(t, err) - assert.True(t, result.Created) - assert.FileExists(t, result.Path) - assert.Equal(t, 1, linkCalls) - assert.Empty(t, matchingFiles(t, store.layout.LooseStagingDir(result.Hash), ".staging-")) + require.NoError(err) + assert.True(result.Created) + assert.FileExists(result.Path) + assert.Equal(1, linkCalls) + assert.Empty(matchingFiles(t, store.layout.LooseStagingDir(result.Hash), ".staging-")) deduplicated, err := store.WriteBytes(context.Background(), content, WriteOptions{ Durability: AtomicPublication, @@ -256,16 +258,17 @@ func TestLooseWriteFallsBackWhenHardLinksAreUnsupported(t *testing.T) { ExpectedHash: result.Hash, Compression: compression, }) - require.NoError(t, err) - assert.False(t, deduplicated.Created) - assert.Equal(t, result.Path, deduplicated.Path) - assert.Equal(t, 1, linkCalls, "deduplication does not attempt publication") + require.NoError(err) + assert.False(deduplicated.Created) + assert.Equal(result.Path, deduplicated.Path) + assert.Equal(1, linkCalls, "deduplication does not attempt publication") }) } } func TestLooseWriteCompressedDurabilitySyncsSelectedFileAndShard(t *testing.T) { - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("durable compressed content\n"), 4096) store := newLooseStoreForTest(t, StagingStoreDirectory) originalFileSync := syncLooseFile @@ -293,16 +296,16 @@ func TestLooseWriteCompressedDurabilitySyncsSelectedFileAndShard(t *testing.T) { require.NoError(err) require.Equal(LooseEncodingZstd, result.Encoding) - assert.Equal(t, 1, fileSyncs, "only the selected staging file is synced") - assert.Contains(t, syncedDirs, filepath.Clean(filepath.Dir(result.Path))) - assert.NoFileExists(t, store.layout.LoosePath(result.Hash)) - assert.Empty(t, matchingFiles(t, store.layout.LooseStagingDir(result.Hash), ".staging-")) - assert.FileExists(t, result.Path) + assert.Equal(1, fileSyncs, "only the selected staging file is synced") + assert.Contains(syncedDirs, filepath.Clean(filepath.Dir(result.Path))) + assert.NoFileExists(store.layout.LoosePath(result.Hash)) + assert.Empty(matchingFiles(t, store.layout.LooseStagingDir(result.Hash), ".staging-")) + assert.FileExists(result.Path) } func TestLooseWriteDurableStoreStagingSyncsAllUnlinks(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("durable staging unlink\n"), 4096) store := newLooseStoreForTest(t, StagingStoreDirectory) stagingDir := store.layout.LooseStagingDir(hashForTest(content)) @@ -329,7 +332,7 @@ func TestLooseWriteDurableStoreStagingSyncsAllUnlinks(t *testing.T) { } func TestLooseWriteDurableStoreStagingSyncsCancellationCleanup(t *testing.T) { - assert := assert.New(t) + assert := Assert.New(t) content := bytes.Repeat([]byte("durable cancelled staging\n"), 4096) store := newLooseStoreForTest(t, StagingStoreDirectory) stagingDir := store.layout.LooseStagingDir(hashForTest(content)) @@ -351,12 +354,12 @@ func TestLooseWriteDurableStoreStagingSyncsCancellationCleanup(t *testing.T) { Compression: LooseCompressionOptions{Enabled: true}, }) - require.ErrorIs(t, err, context.Canceled) + Require.ErrorIs(t, err, context.Canceled) assert.Equal(1, syncs) } func TestLooseWriteDurableStoreStagingSyncsCreationFailureCleanup(t *testing.T) { - assert := assert.New(t) + assert := Assert.New(t) content := []byte("staging creation cleanup") store := newLooseStoreForTest(t, StagingStoreDirectory) stagingDir := store.layout.LooseStagingDir(hashForTest(content)) @@ -381,11 +384,12 @@ func TestLooseWriteDurableStoreStagingSyncsCreationFailureCleanup(t *testing.T) Dedup: VerifyFullHash, }) - require.ErrorIs(t, err, chmodErr) + Require.ErrorIs(t, err, chmodErr) assert.Equal(1, syncs) } func TestLooseWriteDurableStagingSyncFailureIsReturned(t *testing.T) { + assert := Assert.New(t) content := bytes.Repeat([]byte("staging sync failure\n"), 4096) store := newLooseStoreForTest(t, StagingStoreDirectory) syncErr := errors.New("injected staging directory sync failure") @@ -399,10 +403,10 @@ func TestLooseWriteDurableStagingSyncFailureIsReturned(t *testing.T) { Compression: LooseCompressionOptions{Enabled: true}, }) - require.ErrorIs(t, err, syncErr) - assert.True(t, result.Created) - assert.FileExists(t, result.Path) - assert.Empty(t, matchingFiles(t, store.layout.LooseStagingDir(result.Hash), ".staging-")) + Require.ErrorIs(t, err, syncErr) + assert.True(result.Created) + assert.FileExists(result.Path) + assert.Empty(matchingFiles(t, store.layout.LooseStagingDir(result.Hash), ".staging-")) } func TestLooseWriteJoinsStagingCloseFailureOnEarlyReturns(t *testing.T) { @@ -479,9 +483,9 @@ func TestLooseWriteJoinsStagingCloseFailureOnEarlyReturns(t *testing.T) { _, err := store.Write(ctx, src, tt.opts(content)) - require.Error(t, err) - assert.True(t, tt.wantPrimary(err), "primary failure must remain in the returned error: %v", err) - require.ErrorIs(t, err, cleanupErr) + Require.Error(t, err) + Assert.True(t, tt.wantPrimary(err), "primary failure must remain in the returned error: %v", err) + Require.ErrorIs(t, err, cleanupErr) }) } } @@ -502,12 +506,12 @@ func TestLooseWriteJoinsStagingRemoveFailure(t *testing.T) { Compression: LooseCompressionOptions{Enabled: true}, }) - require.ErrorIs(t, err, primaryErr) - require.ErrorIs(t, err, cleanupErr) + Require.ErrorIs(t, err, primaryErr) + Require.ErrorIs(t, err, cleanupErr) } func TestLooseWriteRejectsExistingObjectAboveLimit(t *testing.T) { - require := require.New(t) + require := Require.New(t) content := []byte("existing object exceeds limit") store := newLooseStoreForTest(t, StagingSameDirectory) existing, err := store.WriteBytes(context.Background(), content, WriteOptions{ @@ -529,8 +533,8 @@ func TestLooseWriteRejectsExistingObjectAboveLimit(t *testing.T) { } func TestLooseWriteDeduplicatedRawResultReportsPhysicalMetadata(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("existing raw physical metadata") store := newLooseStoreForTest(t, StagingSameDirectory) created, err := store.WriteBytes(context.Background(), content, WriteOptions{ @@ -552,7 +556,7 @@ func TestLooseWriteDeduplicatedRawResultReportsPhysicalMetadata(t *testing.T) { } func TestLooseWriteReturnsIdentityAfterCompleteStaging(t *testing.T) { - require := require.New(t) + require := Require.New(t) content := []byte("identity survives publication failure") store := newLooseStoreForTest(t, StagingStoreDirectory) hash := hashForTest(content) @@ -563,14 +567,14 @@ func TestLooseWriteReturnsIdentityAfterCompleteStaging(t *testing.T) { Dedup: VerifyFullHash, }) require.Error(err) - assert.Equal(t, hash, result.Hash) - assert.Equal(t, int64(len(content)), result.Size) + Assert.Equal(t, hash, result.Hash) + Assert.Equal(t, int64(len(content)), result.Size) } func TestLooseWriteRequiresExplicitPolicies(t *testing.T) { store := newLooseStoreForTest(t, StagingStoreDirectory) _, err := store.Write(context.Background(), bytes.NewReader(nil), WriteOptions{}) - require.ErrorIs(t, err, ErrInvalidPolicy) + Require.ErrorIs(t, err, ErrInvalidPolicy) } func TestLooseWriteValidatesCompressionPolicy(t *testing.T) { @@ -608,10 +612,10 @@ func TestLooseWriteValidatesCompressionPolicy(t *testing.T) { }) if tt.wantErr { - require.ErrorIs(t, err, ErrInvalidPolicy) + Require.ErrorIs(t, err, ErrInvalidPolicy) return } - require.NoError(t, err) + Require.NoError(t, err) }) } } @@ -696,8 +700,8 @@ func TestLooseWriteCompressionPolicy(t *testing.T) { }, } { t.Run(write.name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) result, err := write.run(store) @@ -752,6 +756,7 @@ func TestLooseWriteCompressionExactSavingsBoundaryIncludesHeader(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) result, err := store.WriteBytes(context.Background(), content, WriteOptions{ @@ -763,23 +768,23 @@ func TestLooseWriteCompressionExactSavingsBoundaryIncludesHeader(t *testing.T) { }, }) - require.NoError(t, err) - assert.Equal(t, tt.want, result.Encoding) - assert.Equal(t, tt.storedSize, result.StoredSize) + Require.NoError(t, err) + assert.Equal(tt.want, result.Encoding) + assert.Equal(tt.storedSize, result.StoredSize) if tt.want == LooseEncodingZstd { - assert.FileExists(t, store.layout.CompressedLoosePath(result.Hash)) - assert.NoFileExists(t, store.layout.LoosePath(result.Hash)) + assert.FileExists(store.layout.CompressedLoosePath(result.Hash)) + assert.NoFileExists(store.layout.LoosePath(result.Hash)) } else { - assert.FileExists(t, store.layout.LoosePath(result.Hash)) - assert.NoFileExists(t, store.layout.CompressedLoosePath(result.Hash)) + assert.FileExists(store.layout.LoosePath(result.Hash)) + assert.NoFileExists(store.layout.CompressedLoosePath(result.Hash)) } }) } } func TestLooseWriteCompressionStreamsSourceWithPooledBuffer(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("bounded compressed source\n"), 64*1024) store := newLooseStoreForTest(t, StagingSameDirectory) @@ -801,8 +806,8 @@ func TestLooseWriteCompressionStreamsSourceWithPooledBuffer(t *testing.T) { } func TestLooseWriteSupportsEmptyAndStoreDirectoryStaging(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingStoreDirectory) result, err := store.Write(context.Background(), bytes.NewReader(nil), WriteOptions{ Durability: DurablePublication, Dedup: VerifyTypeAndSize, @@ -815,7 +820,7 @@ func TestLooseWriteSupportsEmptyAndStoreDirectoryStaging(t *testing.T) { } func TestLooseDurableWriteSurfacesExistingFileSyncFailure(t *testing.T) { - require := require.New(t) + require := Require.New(t) content := []byte("durable sync failure") store := newLooseStoreForTest(t, StagingSameDirectory) created, err := store.WriteBytes(context.Background(), content, WriteOptions{ @@ -833,11 +838,11 @@ func TestLooseDurableWriteSurfacesExistingFileSyncFailure(t *testing.T) { Dedup: VerifyFullHash, }) require.ErrorIs(err, syncErr) - assert.FileExists(t, created.Path) + Assert.FileExists(t, created.Path) } func TestLooseDurableWriteRetriesRootSyncAfterDirectoryResidue(t *testing.T) { - require := require.New(t) + require := Require.New(t) content := []byte("retry parent directory durability") store := newLooseStoreForTest(t, StagingSameDirectory) syncErr := errors.New("injected root sync failure") @@ -859,11 +864,11 @@ func TestLooseDurableWriteRetriesRootSyncAfterDirectoryResidue(t *testing.T) { require.ErrorIs(err, syncErr) _, err = store.WriteBytes(context.Background(), content, opts) require.NoError(err) - assert.Equal(t, 2, rootSyncs, "existing directory residue must not suppress the parent sync retry") + Assert.Equal(t, 2, rootSyncs, "existing directory residue must not suppress the parent sync retry") } func TestLooseDurableWriteSyncsRootForExistingObject(t *testing.T) { - require := require.New(t) + require := Require.New(t) content := []byte("upgrade existing object durability") store := newLooseStoreForTest(t, StagingSameDirectory) _, err := store.WriteBytes(context.Background(), content, WriteOptions{ @@ -884,12 +889,12 @@ func TestLooseDurableWriteSyncsRootForExistingObject(t *testing.T) { Dedup: VerifyFullHash, }) require.NoError(err) - assert.Contains(t, synced, filepath.Clean(store.layout.Root())) + Assert.Contains(t, synced, filepath.Clean(store.layout.Root())) } func TestLooseWriteDedupPrefersCompressedRepresentation(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("preferred compressed representation\n"), 256) store := newLooseStoreForTest(t, StagingSameDirectory) created, err := store.WriteBytes(context.Background(), content, WriteOptions{ @@ -920,7 +925,7 @@ func TestLooseWriteDedupPrefersCompressedRepresentation(t *testing.T) { } func TestLooseWriteDedupRejectsCorruptPreferredRepresentation(t *testing.T) { - require := require.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("do not fall back from corrupt preferred content\n"), 256) store := newLooseStoreForTest(t, StagingSameDirectory) created, err := store.WriteBytes(context.Background(), content, WriteOptions{ @@ -943,12 +948,13 @@ func TestLooseWriteDedupRejectsCorruptPreferredRepresentation(t *testing.T) { }) require.ErrorIs(err, ErrContentMismatch) - assert.Equal(t, corrupt, mustReadFile(t, created.Path), "ordinary writes must not replace a corrupt preferred copy") - assert.Equal(t, content, mustReadFile(t, rawPath), "a valid alternate copy must not mask preferred corruption") + Assert.Equal(t, corrupt, mustReadFile(t, created.Path), "ordinary writes must not replace a corrupt preferred copy") + Assert.Equal(t, content, mustReadFile(t, rawPath), "a valid alternate copy must not mask preferred corruption") } func TestLooseWriteDedupRejectsOverlongCompressedPayloadAfterOneExtraByte(t *testing.T) { - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) expected := []byte("short logical content") extra := bytes.Repeat([]byte("extra decoded content\n"), 4096) overlong := append(bytes.Clone(expected), extra...) @@ -990,18 +996,18 @@ func TestLooseWriteDedupRejectsOverlongCompressedPayloadAfterOneExtraByte(t *tes require.ErrorIs(err, ErrContentMismatch) require.NotErrorIs(err, overreadErr) - assert.False(t, overreadAttempted, "verification must stop after one decoded byte beyond the expected size") - assert.Equal(t, physical.Bytes(), mustReadFile(t, path), "ordinary dedup must preserve the corrupt preferred copy") - assert.NoFileExists(t, store.layout.LoosePath(hash)) + assert.False(overreadAttempted, "verification must stop after one decoded byte beyond the expected size") + assert.Equal(physical.Bytes(), mustReadFile(t, path), "ordinary dedup must preserve the corrupt preferred copy") + assert.NoFileExists(store.layout.LoosePath(hash)) } func TestLooseFullHashRejectsConcatenatedCompressedFrames(t *testing.T) { content := bytes.Repeat([]byte("one logical object across two physical frames\n"), 64) half := len(content) / 2 secondFrameEncoder, err := zstd.NewWriter(nil, zstd.WithEncoderConcurrency(1)) - require.NoError(t, err) + Require.NoError(t, err) secondFrame := secondFrameEncoder.EncodeAll(content[half:], nil) - require.NoError(t, secondFrameEncoder.Close()) + Require.NoError(t, secondFrameEncoder.Close()) newFixture := func(t *testing.T) (*LooseStore, []byte) { t.Helper() @@ -1019,18 +1025,20 @@ func TestLooseFullHashRejectsConcatenatedCompressedFrames(t *testing.T) { } t.Run("loose verify", func(t *testing.T) { + assert := Assert.New(t) store, before := newFixture(t) hash := hashForTest(content) result, exists, err := store.Verify(hash, int64(len(content)), VerifyFullHash, AtomicPublication) - require.ErrorIs(t, err, ErrContentMismatch) - assert.False(t, exists) - assert.False(t, result.Created) - assert.Equal(t, before, mustReadFile(t, store.layout.CompressedLoosePath(hash))) + Require.ErrorIs(t, err, ErrContentMismatch) + assert.False(exists) + assert.False(result.Created) + assert.Equal(before, mustReadFile(t, store.layout.CompressedLoosePath(hash))) }) t.Run("write dedup", func(t *testing.T) { + assert := Assert.New(t) store, before := newFixture(t) hash := hashForTest(content) @@ -1039,14 +1047,16 @@ func TestLooseFullHashRejectsConcatenatedCompressedFrames(t *testing.T) { Dedup: VerifyFullHash, }) - require.ErrorIs(t, err, ErrContentMismatch) - assert.Equal(t, hash, result.Hash) - assert.False(t, result.Created) - assert.Equal(t, before, mustReadFile(t, store.layout.CompressedLoosePath(hash))) - assert.NoFileExists(t, store.layout.LoosePath(hash)) + Require.ErrorIs(t, err, ErrContentMismatch) + assert.Equal(hash, result.Hash) + assert.False(result.Created) + assert.Equal(before, mustReadFile(t, store.layout.CompressedLoosePath(hash))) + assert.NoFileExists(store.layout.LoosePath(hash)) }) t.Run("stream verify", func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) loose, before := newFixture(t) hash := hashForTest(content) store := newStoreForTest(t, &mapResolver{locations: map[Hash]Location{ @@ -1054,17 +1064,17 @@ func TestLooseFullHashRejectsConcatenatedCompressedFrames(t *testing.T) { }}, loose.layout) stream, size, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) - assert.Equal(t, int64(len(content)), size) - require.ErrorIs(t, stream.Verify(), ErrContentMismatch) - require.ErrorIs(t, stream.Close(), ErrContentMismatch) - assert.False(t, stream.Verified()) - assert.Equal(t, before, mustReadFile(t, loose.layout.CompressedLoosePath(hash))) + require.NoError(err) + assert.Equal(int64(len(content)), size) + require.ErrorIs(stream.Verify(), ErrContentMismatch) + require.ErrorIs(stream.Close(), ErrContentMismatch) + assert.False(stream.Verified()) + assert.Equal(before, mustReadFile(t, loose.layout.CompressedLoosePath(hash))) }) } func TestLooseWriteDedupVerificationPolicies(t *testing.T) { - require := require.New(t) + require := Require.New(t) content := []byte("right") hash := hashForTest(content) store := newLooseStoreForTest(t, StagingSameDirectory) @@ -1077,10 +1087,10 @@ func TestLooseWriteDedupVerificationPolicies(t *testing.T) { ExpectedHash: hash, ExpectedSize: int64(len(content)), SizeKnown: true, }) require.NoError(err) - assert.False(t, result.Created) + Assert.False(t, result.Created) stored, err := os.ReadFile(path) require.NoError(err) - assert.Equal(t, []byte("wrong"), stored, "structural dedup deliberately does not detect same-size bit rot") + Assert.Equal(t, []byte("wrong"), stored, "structural dedup deliberately does not detect same-size bit rot") _, err = store.Write(context.Background(), bytes.NewReader(content), WriteOptions{ Durability: AtomicPublication, Dedup: VerifyFullHash, @@ -1090,8 +1100,8 @@ func TestLooseWriteDedupVerificationPolicies(t *testing.T) { } func TestLooseRepairRawRestoresCorruptCanonicalContent(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("verified raw repair content") corrupt := []byte("corrupt raw replacement!!!!") require.Len(corrupt, len(content)) @@ -1128,8 +1138,8 @@ func TestLooseRepairRawRestoresCorruptCanonicalContent(t *testing.T) { } func TestLooseRepairCompressedRestoresCorruptCanonicalContent(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("verified compressed repair content\n"), 1024) store := newLooseStoreForTest(t, StagingSameDirectory) created, err := store.WriteBytes(context.Background(), content, WriteOptions{ @@ -1181,8 +1191,8 @@ func TestLooseRepairReconcilesDualCopiesToSelectedRepresentation(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) created, err := store.WriteBytes(context.Background(), content, WriteOptions{ Durability: AtomicPublication, @@ -1218,8 +1228,8 @@ func TestLooseRepairReconcilesDualCopiesToSelectedRepresentation(t *testing.T) { } func TestLooseRepairMismatchPreservesAllCanonicalCopies(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) expected := []byte("expected repair bytes") wrong := []byte("different repair byte") require.Len(wrong, len(expected)) @@ -1248,8 +1258,8 @@ func TestLooseRepairMismatchPreservesAllCanonicalCopies(t *testing.T) { } func TestLooseRepairPublicationFailurePreservesAllCanonicalCopies(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("repair publication failure") store := newLooseStoreForTest(t, StagingSameDirectory) hash := hashForTest(content) @@ -1279,8 +1289,8 @@ func TestLooseRepairPublicationFailurePreservesAllCanonicalCopies(t *testing.T) } func TestLooseRepairPublicationFailurePreservesLastVerifiedStagingCopy(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("last verified repair staging copy") store := newLooseStoreForTest(t, StagingStoreDirectory) hash := hashForTest(content) @@ -1321,8 +1331,8 @@ func TestLooseRepairDurableBackupRestorationSyncsShardBeforeStagingCleanup(t *te {name: "sync failure remains visible", syncErr: errors.New("injected restored shard sync failure")}, } { t.Run(tt.name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("replacement disappears during partial Windows repair") oldCanonical := []byte("restored old canonical backup") store := newLooseStoreForTest(t, StagingStoreDirectory) @@ -1396,8 +1406,8 @@ func TestLooseRepairDurableKeepStagingSyncsPreservedEntryBeforeReturn(t *testing {name: "sync failure remains visible", syncErr: errors.New("injected preserved staging sync failure")}, } { t.Run(tt.name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("preserved durable repair staging") store := newLooseStoreForTest(t, StagingStoreDirectory) hash := hashForTest(content) @@ -1460,8 +1470,8 @@ func TestLooseRepairDurableKeepStagingSyncsPreservedEntryBeforeReturn(t *testing } func TestLooseRepairReplacementAPIErrorReturnsPublishedReceipt(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("replacement reached canonical despite API error") store := newLooseStoreForTest(t, StagingStoreDirectory) hash := hashForTest(content) @@ -1489,8 +1499,8 @@ func TestLooseRepairReplacementAPIErrorReturnsPublishedReceipt(t *testing.T) { } func TestLooseRepairVerifiesSelectedStagingRepresentationBeforeReplacement(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("fully verify staged repair content\n"), 1024) store := newLooseStoreForTest(t, StagingSameDirectory) hash := hashForTest(content) @@ -1519,8 +1529,8 @@ func TestLooseRepairVerifiesSelectedStagingRepresentationBeforeReplacement(t *te } func TestLooseRepairRejectsSelectedPathSwapAfterVerification(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("verified repair staging identity") store := newLooseStoreForTest(t, StagingSameDirectory) hash := hashForTest(content) @@ -1561,19 +1571,19 @@ func TestLooseRepairRejectsSameInodeMutationAfterVerification(t *testing.T) { { name: "overwrite", mutate: func(t *testing.T, path string) { - require.NoError(t, os.WriteFile(path, []byte("mutated repair staging content!"), 0o600)) + Require.NoError(t, os.WriteFile(path, []byte("mutated repair staging content!"), 0o600)) }, }, { name: "truncate", mutate: func(t *testing.T, path string) { - require.NoError(t, os.Truncate(path, 5)) + Require.NoError(t, os.Truncate(path, 5)) }, }, } { t.Run(tt.name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) hash := hashForTest(content) canonical := store.layout.LoosePath(hash) @@ -1607,8 +1617,8 @@ func TestLooseRepairCancellationDuringStagingPreservesCanonicalEvidence(t *testi content := bytes.Repeat([]byte("cancel repair staging\n"), 4096) for _, staging := range []StagingMode{StagingSameDirectory, StagingStoreDirectory} { t.Run(stagingName(staging), func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, staging) hash := hashForTest(content) canonical := store.layout.LoosePath(hash) @@ -1631,8 +1641,8 @@ func TestLooseRepairCancellationDuringStagingPreservesCanonicalEvidence(t *testi } func TestLooseRepairCancellationDuringVerificationPreservesCanonicalEvidence(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("cancel repair verification\n"), 4096) store := newLooseStoreForTest(t, StagingStoreDirectory) hash := hashForTest(content) @@ -1667,8 +1677,8 @@ func TestLooseRepairCancellationDuringVerificationPreservesCanonicalEvidence(t * } func TestLooseRepairCancellationWhileWaitingForStripeSkipsVerification(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("cancel repair stripe wait\n"), 4096) store := newLooseStoreForTest(t, StagingStoreDirectory) hash := hashForTest(content) @@ -1734,8 +1744,8 @@ func TestLooseRepairCancellationWhileWaitingForStripeSkipsVerification(t *testin } func TestLooseRepairDurablePublicationSyncsReplacementAndReconciliation(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("durable repair content") store := newLooseStoreForTest(t, StagingStoreDirectory) hash := hashForTest(content) @@ -1785,8 +1795,8 @@ func TestLooseRepairDurablePublicationSyncsReplacementAndReconciliation(t *testi } func TestLooseRepairAlternateRemovalFailureReturnsPublishedReceipt(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("alternate cleanup receipt") store := newLooseStoreForTest(t, StagingStoreDirectory) hash := hashForTest(content) @@ -1821,8 +1831,8 @@ func TestLooseRepairAlternateRemovalFailureReturnsPublishedReceipt(t *testing.T) } func TestLooseRepairShardSyncFailureReturnsPublishedReceipt(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("shard durability receipt") store := newLooseStoreForTest(t, StagingStoreDirectory) hash := hashForTest(content) @@ -1856,8 +1866,8 @@ func TestLooseRepairShardSyncFailureReturnsPublishedReceipt(t *testing.T) { } func TestLooseRepairStagingSyncFailureReturnsPublishedReceipt(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("staging durability receipt") store := newLooseStoreForTest(t, StagingStoreDirectory) hash := hashForTest(content) @@ -1906,8 +1916,8 @@ func TestLooseRepairKeepsActiveReadersStableAcrossRepresentations(t *testing.T) {name: "zstd to raw", initialEncoding: LooseEncodingZstd, repairEncoding: LooseEncodingRaw}, } { t.Run(tt.name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingStoreDirectory) writeCompression := LooseCompressionOptions{Enabled: tt.initialEncoding == LooseEncodingZstd} created, err := store.WriteBytes(context.Background(), content, WriteOptions{ @@ -1998,14 +2008,15 @@ func TestLooseRepairValidatesRequiredIdentityAndPolicyBeforeReading(t *testing.T _, err := store.Repair(context.Background(), reader, tt.expected, tt.opts) - require.ErrorIs(t, err, tt.wantErr) - assert.Zero(t, reader.reads, "invalid repair input is rejected before consuming replacement bytes") + Require.ErrorIs(t, err, tt.wantErr) + Assert.Zero(t, reader.reads, "invalid repair input is rejected before consuming replacement bytes") }) } } func TestLooseVerifyChecksCanonicalObject(t *testing.T) { - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := []byte("verify existing object") store := newLooseStoreForTest(t, StagingSameDirectory) created, err := store.WriteBytes(context.Background(), content, WriteOptions{ @@ -2016,13 +2027,13 @@ func TestLooseVerifyChecksCanonicalObject(t *testing.T) { result, exists, err := store.Verify(created.Hash, created.Size, VerifyFullHash, AtomicPublication) require.NoError(err) - assert.True(t, exists) - assert.Equal(t, created.Hash, result.Hash) + assert.True(exists) + assert.Equal(created.Hash, result.Hash) missing := hashForTest([]byte("missing")) _, exists, err = store.Verify(missing, 0, VerifyFullHash, AtomicPublication) require.NoError(err) - assert.False(t, exists) + assert.False(exists) _, _, err = store.Verify("", 0, VerifyFullHash, AtomicPublication) require.ErrorIs(err, ErrInvalidHash) } @@ -2042,8 +2053,8 @@ func TestLooseVerifyRejectsSameInodeGrowthAfterFullHash(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) created, err := store.WriteBytes(context.Background(), content, WriteOptions{ Durability: AtomicPublication, @@ -2095,8 +2106,8 @@ func TestLooseVerifyRejectsSameInodeGrowthAfterFullHash(t *testing.T) { } func TestLooseWriteBytesRejectsCompressedSameInodeGrowthDuringDedup(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("compressed dedup growth\n"), 256) store := newLooseStoreForTest(t, StagingSameDirectory) opts := WriteOptions{ @@ -2129,14 +2140,14 @@ func TestLooseWriteBytesRejectsCompressedSameInodeGrowthDuringDedup(t *testing.T func appendLooseTestMutation(t *testing.T, path string) { t.Helper() file, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0) - require.NoError(t, err) + Require.NoError(t, err) _, err = file.Write([]byte("trailing mutation")) - require.NoError(t, err) - require.NoError(t, file.Close()) + Require.NoError(t, err) + Require.NoError(t, file.Close()) } func TestLooseDurableVerifyRejectsIdentitySwap(t *testing.T) { - require := require.New(t) + require := Require.New(t) content := []byte("durable identity must remain stable") store := newLooseStoreForTest(t, StagingSameDirectory) created, err := store.WriteBytes(context.Background(), content, WriteOptions{ @@ -2161,12 +2172,12 @@ func TestLooseDurableVerifyRejectsIdentitySwap(t *testing.T) { _, _, err = store.Verify(created.Hash, created.Size, VerifyFullHash, DurablePublication) require.ErrorIs(err, errIdentityChanged) - assert.FileExists(t, created.Path) - assert.FileExists(t, displaced) + Assert.FileExists(t, created.Path) + Assert.FileExists(t, displaced) } func TestLooseWriteFullHashRejectsChangedFileWithRestoredTimestamp(t *testing.T) { - require := require.New(t) + require := Require.New(t) content := []byte("right") store := newLooseStoreForTest(t, StagingSameDirectory) opts := WriteOptions{Durability: AtomicPublication, Dedup: VerifyFullHash} @@ -2228,6 +2239,8 @@ func TestLooseWriteFullHashHonorsCancellationWhileVerifyingExisting(t *testing.T } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) opts := WriteOptions{ Durability: AtomicPublication, @@ -2238,11 +2251,11 @@ func TestLooseWriteFullHashHonorsCancellationWhileVerifyingExisting(t *testing.T Compression: tt.compression, } created, err := store.WriteBytes(context.Background(), content, opts) - require.NoError(t, err) + require.NoError(err) if tt.compression.Enabled { - require.Equal(t, LooseEncodingZstd, created.Encoding) + require.Equal(LooseEncodingZstd, created.Encoding) } else { - require.Equal(t, LooseEncodingRaw, created.Encoding) + require.Equal(LooseEncodingRaw, created.Encoding) } ctx, cancel := context.WithCancel(context.Background()) @@ -2274,10 +2287,10 @@ func TestLooseWriteFullHashHonorsCancellationWhileVerifyingExisting(t *testing.T err = tt.write(ctx, store, content, opts) - require.ErrorIs(t, err, context.Canceled) - assert.Positive(t, readBytes) - assert.LessOrEqual(t, readBytes, tt.maxRead) - assert.FileExists(t, created.Path) + require.ErrorIs(err, context.Canceled) + assert.Positive(readBytes) + assert.LessOrEqual(readBytes, tt.maxRead) + assert.FileExists(created.Path) }) } } @@ -2322,6 +2335,8 @@ func TestLooseWriteFullHashHonorsCancellationDuringFinalIdentityCheck(t *testing } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingSameDirectory) opts := WriteOptions{ Durability: AtomicPublication, @@ -2332,7 +2347,7 @@ func TestLooseWriteFullHashHonorsCancellationDuringFinalIdentityCheck(t *testing Compression: tt.compression, } created, err := store.WriteBytes(context.Background(), content, opts) - require.NoError(t, err) + require.NoError(err) ctx, cancel := context.WithCancel(context.Background()) originalSnapshot := snapshotLoosePathIdentity @@ -2353,9 +2368,9 @@ func TestLooseWriteFullHashHonorsCancellationDuringFinalIdentityCheck(t *testing err = tt.write(ctx, store, content, opts) - require.ErrorIs(t, err, context.Canceled) - assert.True(t, canceledDuringFinalSnapshot) - assert.FileExists(t, created.Path) + require.ErrorIs(err, context.Canceled) + assert.True(canceledDuringFinalSnapshot) + assert.FileExists(created.Path) }) } } @@ -2391,13 +2406,13 @@ func TestLooseWriteMaxIntLimitDoesNotOverflow(t *testing.T) { result, err := store.Write(context.Background(), bytes.NewReader(content), WriteOptions{ Durability: AtomicPublication, Dedup: VerifyFullHash, MaxBytes: math.MaxInt64, }) - require.NoError(t, err) - assert.Equal(t, int64(len(content)), result.Size) + Require.NoError(t, err) + Assert.Equal(t, int64(len(content)), result.Size) } func TestLooseStoreDefersMissingRootCreationUntilWritePolicyIsKnown(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) root := filepath.Join(t.TempDir(), "missing", "store") layout, err := NewLayout(root, LayoutOptions{Staging: StagingStoreDirectory, StagingDir: "tmp"}) require.NoError(err) @@ -2412,7 +2427,7 @@ func TestLooseStoreDefersMissingRootCreationUntilWritePolicyIsKnown(t *testing.T } func TestLooseWriteConcurrentDedup(t *testing.T) { - require := require.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("concurrent"), 1024) hash := hashForTest(content) store := newLooseStoreForTest(t, StagingSameDirectory) @@ -2436,16 +2451,16 @@ func TestLooseWriteConcurrentDedup(t *testing.T) { require.NoError(err) } for result := range results { - assert.Equal(t, hash, result.Hash) + Assert.Equal(t, hash, result.Hash) } stored, err := os.ReadFile(store.layout.LoosePath(hash)) require.NoError(err) - assert.Equal(t, content, stored) + Assert.Equal(t, content, stored) } func TestLooseWriteConcurrentRawAndCompressedPublishOneRepresentation(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("cross-representation publication\n"), 4096) hash := hashForTest(content) layout, err := NewLayout(t.TempDir(), LayoutOptions{Staging: StagingSameDirectory}) @@ -2521,8 +2536,8 @@ func TestLooseWriteConcurrentRawAndCompressedPublishOneRepresentation(t *testing } func TestLooseWriteCancelledWhileQueuedForStripeReturnsWithoutDedupOrPublish(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("cancel queued publication\n"), 4096) hash := hashForTest(content) layout, err := NewLayout(t.TempDir(), LayoutOptions{Staging: StagingSameDirectory}) @@ -2607,7 +2622,7 @@ func TestLooseWriteCancelledWhileQueuedForStripeReturnsWithoutDedupOrPublish(t * } func TestLooseWriteRejectsSymlinkDestination(t *testing.T) { - require := require.New(t) + require := Require.New(t) content := []byte("content") hash := hashForTest(content) store := newLooseStoreForTest(t, StagingSameDirectory) @@ -2623,25 +2638,25 @@ func TestLooseWriteRejectsSymlinkDestination(t *testing.T) { require.Error(err) info, statErr := os.Lstat(path) require.NoError(statErr) - assert.NotZero(t, info.Mode()&os.ModeSymlink) + Assert.NotZero(t, info.Mode()&os.ModeSymlink) } func TestRemoveLooseUsesExplicitDurability(t *testing.T) { - require := require.New(t) + require := Require.New(t) store := newLooseStoreForTest(t, StagingStoreDirectory) result, err := store.Write(context.Background(), bytes.NewReader([]byte("remove")), WriteOptions{ Durability: DurablePublication, Dedup: VerifyFullHash, }) require.NoError(err) require.NoError(store.Remove(result.Hash, BestEffortRemoval)) - assert.NoFileExists(t, result.Path) + Assert.NoFileExists(t, result.Path) require.NoError(store.Remove(result.Hash, DurableRemoval), "missing durable removal is idempotent") require.ErrorIs(store.Remove(result.Hash, 0), ErrInvalidPolicy) } func TestLooseRemoveRemovesRawAndCompressedRepresentationsDurably(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) store := newLooseStoreForTest(t, StagingStoreDirectory) content := bytes.Repeat([]byte("remove both representations\n"), 16) result, err := store.WriteBytes(context.Background(), content, WriteOptions{ @@ -2669,8 +2684,8 @@ func TestLooseRemoveRemovesRawAndCompressedRepresentationsDurably(t *testing.T) } func TestLooseRemoveRejectsSymlinksAndPreservesUnknownExtensions(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) store := newLooseStoreForTest(t, StagingStoreDirectory) hash := hashForTest([]byte("remove canonical names only")) rawPath := store.layout.LoosePath(hash) @@ -2699,13 +2714,13 @@ func BenchmarkLooseWriteBytesDuplicate(b *testing.B) { store := newLooseStoreForTest(b, StagingSameDirectory) opts := WriteOptions{Durability: AtomicPublication, Dedup: VerifyFullHash} _, err := store.WriteBytes(context.Background(), content, opts) - require.NoError(b, err) + Require.NoError(b, err) b.SetBytes(int64(len(content))) b.ReportAllocs() b.ResetTimer() for range b.N { _, err := store.WriteBytes(context.Background(), content, opts) - require.NoError(b, err) + Require.NoError(b, err) } } @@ -2716,9 +2731,9 @@ func newLooseStoreForTest(t testing.TB, staging StagingMode) *LooseStore { opts.StagingDir = "tmp" } layout, err := NewLayout(t.TempDir(), opts) - require.NoError(t, err) + Require.NoError(t, err) store, err := NewLooseStore(layout) - require.NoError(t, err) + Require.NoError(t, err) return store } @@ -2756,14 +2771,14 @@ func deterministicLooseNoise(size int) []byte { func matchingFiles(t *testing.T, dir, pattern string) []string { t.Helper() matches, err := filepath.Glob(filepath.Join(dir, pattern+"*")) - require.NoError(t, err) + Require.NoError(t, err) return matches } func mustReadFile(t *testing.T, path string) []byte { t.Helper() content, err := os.ReadFile(path) - require.NoError(t, err) + Require.NoError(t, err) return content } @@ -2771,19 +2786,19 @@ func readRepairedLoose(t *testing.T, loose *LooseStore, result WriteResult) []by t.Helper() resolver := &repairResolver{hash: result.Hash} store, err := NewStore(resolver, loose.layout, StoreOptions{}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, store.Close()) }) + Require.NoError(t, err) + t.Cleanup(func() { Require.NoError(t, store.Close()) }) content, size, err := store.ReadBounded(context.Background(), result.Hash, result.Size) - require.NoError(t, err) - assert.Equal(t, result.Size, size) + Require.NoError(t, err) + Assert.Equal(t, result.Size, size) return content } func assertNoLooseWriteResidue(t *testing.T, store *LooseStore, hash Hash) { t.Helper() - assert.NoFileExists(t, store.layout.LoosePath(hash)) - assert.NoFileExists(t, store.layout.CompressedLoosePath(hash)) - assert.Empty(t, matchingFiles(t, store.layout.LooseStagingDir(hash), ".staging-")) + Assert.NoFileExists(t, store.layout.LoosePath(hash)) + Assert.NoFileExists(t, store.layout.CompressedLoosePath(hash)) + Assert.Empty(t, matchingFiles(t, store.layout.LooseStagingDir(hash), ".staging-")) } func stagingName(staging StagingMode) string { @@ -2799,7 +2814,7 @@ func receiveLooseEncoding(t *testing.T, values <-chan LooseEncoding) LooseEncodi case value := <-values: return value case <-time.After(5 * time.Second): - require.FailNow(t, "timed out waiting for loose writer to reach publication barrier") + Require.FailNow(t, "timed out waiting for loose writer to reach publication barrier") return 0 } } @@ -2809,7 +2824,7 @@ func receiveLooseSignal(t *testing.T, signal <-chan struct{}, description string select { case <-signal: case <-time.After(5 * time.Second): - require.FailNow(t, "timed out waiting for "+description) + Require.FailNow(t, "timed out waiting for "+description) } } diff --git a/packstore/maintenance_stream_test.go b/packstore/maintenance_stream_test.go index e1cb449..409658c 100644 --- a/packstore/maintenance_stream_test.go +++ b/packstore/maintenance_stream_test.go @@ -7,24 +7,26 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) func TestPackAndUnpackStreamAboveFormerCeiling(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) if testing.Short() { t.Skip("streams an object above the former 64 MiB maintenance ceiling") } size := largeStoreStreamTestBytes(t, 64<<20+1) layout := layoutForStoreTest(t) loose, err := NewLooseStore(layout) - require.NoError(t, err) + require.NoError(err) written, err := loose.Write(context.Background(), io.LimitReader(streamZeroReader{}, size), WriteOptions{ Durability: AtomicPublication, Dedup: VerifyFullHash, MaxBytes: size, }) - require.NoError(t, err) - require.Equal(t, size, written.Size) + require.NoError(err) + require.Equal(size, written.Size) catalog := newMaintenanceCatalog() catalog.addLoose(written.Hash, written.Path) @@ -33,39 +35,41 @@ func TestPackAndUnpackStreamAboveFormerCeiling(t *testing.T) { maintainer := newMaintainerForTest(t, catalog, layout, limits) packed, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) - assert.Equal(t, 1, packed.BlobsPacked) - assert.Equal(t, size, packed.BytesPacked) - assert.NoFileExists(t, written.Path) + require.NoError(err) + assert.Equal(1, packed.BlobsPacked) + assert.Equal(size, packed.BytesPacked) + assert.NoFileExists(written.Path) unpacked, err := maintainer.Unpack(context.Background()) - require.NoError(t, err) - assert.Equal(t, 1, unpacked.BlobsRestored) - assert.Equal(t, size, unpacked.BytesRestored) + require.NoError(err) + assert.Equal(1, unpacked.BlobsRestored) + assert.Equal(size, unpacked.BytesRestored) info, err := os.Stat(written.Path) - require.NoError(t, err) - assert.Equal(t, size, info.Size()) + require.NoError(err) + assert.Equal(size, info.Size()) err = verifyLoosePath(context.Background(), written.Path, written.Hash, size) - require.NoError(t, err) + require.NoError(err) } func TestPackCompressedLooseStreamAboveFormerCeiling(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) if testing.Short() { t.Skip("streams a compressed object above the former 64 MiB maintenance ceiling") } size := largeStoreStreamTestBytes(t, 64<<20+1) layout := layoutForStoreTest(t) loose, err := NewLooseStore(layout) - require.NoError(t, err) + require.NoError(err) written, err := loose.Write(context.Background(), io.LimitReader(streamZeroReader{}, size), WriteOptions{ Durability: AtomicPublication, Dedup: VerifyFullHash, MaxBytes: size, Compression: LooseCompressionOptions{Enabled: true}, }) - require.NoError(t, err) - require.Equal(t, LooseEncodingZstd, written.Encoding) - require.Less(t, written.StoredSize, written.Size) + require.NoError(err) + require.Equal(LooseEncodingZstd, written.Encoding) + require.Less(written.StoredSize, written.Size) catalog := newMaintenanceCatalog() addMaintenanceCandidate(catalog, Candidate{ @@ -77,37 +81,39 @@ func TestPackCompressedLooseStreamAboveFormerCeiling(t *testing.T) { stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) - assert.Equal(t, 1, stats.BlobsPacked) - assert.Equal(t, size, stats.BytesPacked) - assert.NoFileExists(t, written.Path) + require.NoError(err) + assert.Equal(1, stats.BlobsPacked) + assert.Equal(size, stats.BytesPacked) + assert.NoFileExists(written.Path) stream, gotSize, err := maintainer.store.OpenStream(context.Background(), written.Hash) - require.NoError(t, err) - assert.Equal(t, size, gotSize) - require.NoError(t, stream.Verify()) - require.NoError(t, stream.Close()) + require.NoError(err) + assert.Equal(size, gotSize) + require.NoError(stream.Verify()) + require.NoError(stream.Close()) } func TestRepackStreamsAboveFormerCeiling(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) if testing.Short() { t.Skip("streams an object above the former 64 MiB maintenance ceiling") } size := largeStoreStreamTestBytes(t, 64<<20+1) layout := layoutForStoreTest(t) - require.NoError(t, os.MkdirAll(layout.PacksDir(), 0o700)) + require.NoError(os.MkdirAll(layout.PacksDir(), 0o700)) writer, err := pack.NewWriter(layout.PacksDir(), pack.WriterOptions{}) - require.NoError(t, err) + require.NoError(err) live, err := writer.AppendStream(context.Background(), io.LimitReader(streamZeroReader{}, size), uint64(size), pack.AppendStreamOptions{ ScratchDir: layout.PacksDir(), }) - require.NoError(t, err) + require.NoError(err) _, err = writer.Append([]byte("dead-one")) - require.NoError(t, err) + require.NoError(err) _, err = writer.Append([]byte("dead-two")) - require.NoError(t, err) + require.NoError(err) packID := writer.ID() entries, err := writer.Seal(layout.PackPath(packID)) - require.NoError(t, err) + require.NoError(err) indexed := indexFromPack(packID, live) catalog := newMaintenanceCatalog() @@ -125,13 +131,13 @@ func TestRepackStreamsAboveFormerCeiling(t *testing.T) { stats, err := maintainer.Repack(context.Background(), RepackOptions{ Now: time.Now(), Selection: RepackSelection{MinAge: time.Nanosecond, MinDeadStored: 1}, }) - require.NoError(t, err) - assert.Equal(t, 1, stats.BlobsRepacked) - assert.Equal(t, size, stats.BytesRepacked) - assert.NoFileExists(t, layout.PackPath(packID)) + require.NoError(err) + assert.Equal(1, stats.BlobsRepacked) + assert.Equal(size, stats.BytesRepacked) + assert.NoFileExists(layout.PackPath(packID)) stream, gotSize, err := maintainer.store.OpenStream(context.Background(), indexed.Hash) - require.NoError(t, err) - assert.Equal(t, size, gotSize) - require.NoError(t, stream.Verify()) - require.NoError(t, stream.Close()) + require.NoError(err) + assert.Equal(size, gotSize) + require.NoError(stream.Verify()) + require.NoError(stream.Close()) } diff --git a/packstore/ownership_test.go b/packstore/ownership_test.go index 5c7a4f1..69ef7a4 100644 --- a/packstore/ownership_test.go +++ b/packstore/ownership_test.go @@ -9,31 +9,33 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) func TestFilesystemNamespaceInspectionRejectsUnmarkedContent(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) backend, err := NewFilesystemBackend(layout, FilesystemBackendOptions{}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, backend.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(backend.Close()) }) empty, err := backend.NamespaceEmpty(t.Context()) - require.NoError(t, err) - assert.True(t, empty) - require.NoError(t, os.WriteFile( + require.NoError(err) + assert.True(empty) + require.NoError(os.WriteFile( filepath.Join(layout.Root(), "operator-note"), []byte("keep"), 0o600, )) empty, err = backend.NamespaceEmpty(t.Context()) - require.NoError(t, err) - assert.False(t, empty) + require.NoError(err) + assert.False(empty) } func TestFilesystemOwnershipCreateReattachAndTakeover(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) ctx := context.Background() layout := layoutForStoreTest(t) initial := Ownership{ @@ -85,7 +87,7 @@ func TestFilesystemOwnershipCreateReattachAndTakeover(t *testing.T) { } func TestFilesystemOwnershipMismatchFencesDestructiveWork(t *testing.T) { - require := require.New(t) + require := Require.New(t) ctx := context.Background() layout := layoutForStoreTest(t) initial := Ownership{ @@ -134,7 +136,7 @@ func TestMarshalOwnershipRejectsUnreadableMarkerSize(t *testing.T) { _, err := MarshalOwnership(value) - require.ErrorContains(t, err, "ownership marker size") + Require.ErrorContains(t, err, "ownership marker size") } func assertOwnershipMismatch( @@ -145,9 +147,9 @@ func assertOwnershipMismatch( ) { t.Helper() var mismatch *OwnershipMismatchError - require.ErrorAs(t, err, &mismatch) - require.NotNil(t, mismatch) - assert.Equal(t, expected, mismatch.Expected) - assert.Equal(t, actual, mismatch.Actual) - assert.ErrorIs(t, err, ErrStoreFenced) + Require.ErrorAs(t, err, &mismatch) + Require.NotNil(t, mismatch) + Assert.Equal(t, expected, mismatch.Expected) + Assert.Equal(t, actual, mismatch.Actual) + Assert.ErrorIs(t, err, ErrStoreFenced) } diff --git a/packstore/pack.go b/packstore/pack.go index 8434fee..dc1df88 100644 --- a/packstore/pack.go +++ b/packstore/pack.go @@ -339,6 +339,9 @@ func openLooseIdentityPin(path string) (identityPin, fs.FileInfo, error) { } return nil, nil, err } + if pin == nil || pinned == nil { + return nil, nil, errors.Join(fmt.Errorf("packstore: identity pin for %s has incomplete state", path), closeIdentityPin(pin)) + } if err := validateRegularNoFollow(path, pinned); err != nil { return nil, nil, errors.Join(err, pin.Close()) } @@ -368,10 +371,9 @@ func mergeLogicalCandidates(candidates []Candidate) []candidateGroup { if !exists { index = len(groups) byHash[candidate.Hash] = index - groups = append(groups, candidateGroup{Candidate: Candidate{ + groups = append(groups, candidateGroup{ Hash: candidate.Hash, - Size: candidate.Size, - }}) + Size: candidate.Size}) pathSets = append(pathSets, make(map[string]struct{}, len(candidate.Paths))) aliasSets = append(aliasSets, make(map[string]struct{}, len(candidate.OriginalHashes))) } else if groups[index].Size != candidate.Size { @@ -484,6 +486,17 @@ func (m *Maintainer) packCandidates( stats.BlobsMissing++ continue } + if prepared == nil || sourcePin == nil { + var closePreparedErr error + if prepared != nil { + closePreparedErr = prepared.Close() + } + return errors.Join( + fmt.Errorf("packstore: prepared candidate %s has incomplete state", candidate.Hash), + closePreparedErr, + closeIdentityPin(sourcePin), + ) + } if err := checkPlainOutput(m.limits, uint64(pack.MinEntryOffset), prepared.StoredLen(), 1); err != nil { if closeErr := errors.Join(prepared.Close(), sourcePin.Close()); closeErr != nil { return closeErr @@ -623,6 +636,12 @@ func (m *Maintainer) prepareCandidate( corrupt = errors.Join(corrupt, err) continue } + if selected == nil && prepared == nil { + return nil, "", nil, false, errors.Join( + fmt.Errorf("packstore: preparing candidate %s returned no result", candidate.Hash), + closeIdentityPin(pin), + ) + } if selected == nil { selected = prepared selectedPath = path @@ -642,6 +661,9 @@ func (m *Maintainer) pinOpenCandidate( if err != nil { return nil, err } + if pin == nil || pinned == nil { + return nil, errors.Join(fmt.Errorf("packstore: identity pin for %s has incomplete state", path), closeIdentityPin(pin)) + } if !os.SameFile(expected, pinned) { return nil, errors.Join(errIdentityChanged, pin.Close()) } diff --git a/packstore/pack_test.go b/packstore/pack_test.go index 5fa5993..201c214 100644 --- a/packstore/pack_test.go +++ b/packstore/pack_test.go @@ -14,14 +14,14 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) func TestPackRepairsThenPacksAndSweepsLooseContent(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() content := []byte("pack this loose content") @@ -52,8 +52,8 @@ func TestPackRepairsThenPacksAndSweepsLooseContent(t *testing.T) { } func TestPackMixedLooseRepresentationsUsesLogicalIdentity(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() contents := [][]byte{ @@ -111,8 +111,8 @@ func TestPackMixedLooseRepresentationsUsesLogicalIdentity(t *testing.T) { } func TestPackTreatsNoncanonicalHashZstdPathAsLegacyRaw(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) content := []byte("legacy raw bytes with a compressed-looking filename") hash := hashForTest(content) @@ -136,8 +136,8 @@ func TestPackTreatsNoncanonicalHashZstdPathAsLegacyRaw(t *testing.T) { } func TestPackClassifiesWindowsCaseVariantCanonicalPathAsCompressed(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) content := bytes.Repeat([]byte("case-insensitive canonical compressed path\n"), 32) hash := hashForTest(content) @@ -168,14 +168,14 @@ func TestCanonicalLoosePathEqualForOS(t *testing.T) { caseVariant := filepath.Join("ROOT", "AB", "ABCDEF.ZST") noncanonical := filepath.Join("root", "legacy", "abcdef.zst") - assert.True(t, canonicalLoosePathEqualForOS("windows", canonical, caseVariant)) - assert.False(t, canonicalLoosePathEqualForOS("linux", canonical, caseVariant)) - assert.False(t, canonicalLoosePathEqualForOS("windows", canonical, noncanonical)) + Assert.True(t, canonicalLoosePathEqualForOS("windows", canonical, caseVariant)) + Assert.False(t, canonicalLoosePathEqualForOS("linux", canonical, caseVariant)) + Assert.False(t, canonicalLoosePathEqualForOS("windows", canonical, noncanonical)) } func TestPackMergesDuplicateCandidateFallbackPathsAndAliases(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) content := bytes.Repeat([]byte("duplicate candidate fallback\n"), 64) hash := hashForTest(content) @@ -213,8 +213,8 @@ func TestPackMergesDuplicateCandidateFallbackPathsAndAliases(t *testing.T) { } func TestPackMergesAliasesAfterFirstSuccessfulDuplicateCandidate(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) content := bytes.Repeat([]byte("duplicate alias union\n"), 32) hash := writeMaintenanceLoose(t, layout, content) @@ -239,8 +239,8 @@ func TestPackMergesAliasesAfterFirstSuccessfulDuplicateCandidate(t *testing.T) { } func TestPackRejectsContradictoryDuplicateCandidateSizes(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) content := []byte("contradictory candidate metadata") hash := writeMaintenanceLoose(t, layout, content) @@ -265,8 +265,8 @@ func TestPackRejectsContradictoryDuplicateCandidateSizes(t *testing.T) { } func TestPackRejectsCorruptCompressedCandidate(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() content := bytes.Repeat([]byte("verify compressed candidate\n"), 64) @@ -289,8 +289,8 @@ func TestPackRejectsCorruptCompressedCandidate(t *testing.T) { } func TestPackPreservesCompressedSourceReplacementAfterCatalogCommit(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() content := bytes.Repeat([]byte("source replacement race\n"), 128) @@ -320,19 +320,21 @@ func TestPackPreservesCompressedSourceReplacementAfterCatalogCommit(t *testing.T } func TestSweepLooseSkipsPackedAuthorityWhenNoCanonicalLooseCandidateExists(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) base := newMaintenanceCatalog() var first Hash for index := range 64 { hash, err := ParseHash(fmt.Sprintf("%064x", index+1)) - require.NoError(t, err) + require.NoError(err) if index == 0 { first = hash } base.members[hash] = Reference{Hash: hash} base.entries[hash] = IndexEntry{Hash: hash, PackID: pack.NewPackID()} } - require.NoError(t, os.MkdirAll(layout.LoosePath(first), 0o700)) + require.NoError(os.MkdirAll(layout.LoosePath(first), 0o700)) symlink := layout.CompressedLoosePath(first) if err := os.Symlink("elsewhere", symlink); err != nil { t.Logf("symlink fixture unavailable: %v", err) @@ -343,20 +345,22 @@ func TestSweepLooseSkipsPackedAuthorityWhenNoCanonicalLooseCandidateExists(t *te err := maintainer.sweepLoose(context.Background(), base.members, true, &stats) - require.NoError(t, err) - assert.Zero(t, catalog.resolveCalls, "an absent loose namespace must not open packed authority") - assert.Equal(t, PackStats{}, stats) - assert.DirExists(t, layout.LoosePath(first), "non-regular canonical-looking entries stay untouched") + require.NoError(err) + assert.Zero(catalog.resolveCalls, "an absent loose namespace must not open packed authority") + assert.Equal(PackStats{}, stats) + assert.DirExists(layout.LoosePath(first), "non-regular canonical-looking entries stay untouched") } func TestSweepLooseVerifiesPackedAuthorityForCanonicalLooseCandidate(t *testing.T) { for _, encoding := range []LooseEncoding{LooseEncodingRaw, LooseEncodingZstd} { t.Run(fmt.Sprint(encoding), func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := bytes.Repeat([]byte("redundant packed authority\n"), 32) entry := buildStoreTestPack(t, layout, content) if encoding == LooseEncodingRaw { - require.Equal(t, entry.Hash, writeMaintenanceLoose(t, layout, content)) + require.Equal(entry.Hash, writeMaintenanceLoose(t, layout, content)) } else { writeCompressedLooseFixture(t, layout, entry.Hash, int64(len(content)), content, nil) } @@ -372,20 +376,22 @@ func TestSweepLooseVerifiesPackedAuthorityForCanonicalLooseCandidate(t *testing. err := maintainer.sweepLoose(context.Background(), base.members, true, &stats) - require.NoError(t, err) - assert.Equal(t, 1, catalog.resolveCalls, "a loose candidate requires packed authority verification") - assert.Equal(t, 1, stats.LooseSwept) - assert.NoFileExists(t, layout.LoosePath(entry.Hash)) - assert.NoFileExists(t, layout.CompressedLoosePath(entry.Hash)) + require.NoError(err) + assert.Equal(1, catalog.resolveCalls, "a loose candidate requires packed authority verification") + assert.Equal(1, stats.LooseSwept) + assert.NoFileExists(layout.LoosePath(entry.Hash)) + assert.NoFileExists(layout.CompressedLoosePath(entry.Hash)) }) } } func TestSweepLooseDoesNotReportValidSourceCorruptWhenRemovalPinIsUnavailable(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := []byte("valid redundant loose source without removal authority") entry := buildStoreTestPack(t, layout, content) - require.Equal(t, entry.Hash, writeMaintenanceLoose(t, layout, content)) + require.Equal(entry.Hash, writeMaintenanceLoose(t, layout, content)) catalog := newMaintenanceCatalog() catalog.members[entry.Hash] = Reference{Hash: entry.Hash} catalog.entries[entry.Hash] = entry @@ -400,13 +406,15 @@ func TestSweepLooseDoesNotReportValidSourceCorruptWhenRemovalPinIsUnavailable(t err := maintainer.sweepLoose(context.Background(), catalog.members, true, &stats) - require.NoError(t, err) - assert.Zero(t, stats.BlobsCorrupt) - assert.Zero(t, stats.LooseSwept) - assert.FileExists(t, layout.LoosePath(entry.Hash)) + require.NoError(err) + assert.Zero(stats.BlobsCorrupt) + assert.Zero(stats.LooseSwept) + assert.FileExists(layout.LoosePath(entry.Hash)) } func TestPackDoesNotRequireRemovalAuthorityForReadableCandidate(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := []byte("pack readable source without removal authority") hash := writeMaintenanceLoose(t, layout, content) @@ -419,18 +427,19 @@ func TestPackDoesNotRequireRemovalAuthorityForReadableCandidate(t *testing.T) { stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) - assert.Equal(t, 1, stats.BlobsPacked) - assert.Zero(t, stats.BlobsCorrupt) - assert.FileExists(t, layout.LoosePath(hash), "unavailable cleanup preserves the redundant loose source") + require.NoError(err) + assert.Equal(1, stats.BlobsPacked) + assert.Zero(stats.BlobsCorrupt) + assert.FileExists(layout.LoosePath(hash), "unavailable cleanup preserves the redundant loose source") location, err := catalog.Resolve(context.Background(), hash) - require.NoError(t, err) - require.NotNil(t, location.Pack) + require.NoError(err) + require.NotNil(location.Pack) got, _ := readStoreTest(t, maintainer.store, hash) - assert.Equal(t, content, got) + assert.Equal(content, got) } func TestPackSourcePinLimitRotatesWithoutLeakingPins(t *testing.T) { + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() var order []Hash @@ -456,11 +465,11 @@ func TestPackSourcePinLimitRotatesWithoutLeakingPins(t *testing.T) { stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) - assert.Equal(t, 10, stats.BlobsPacked) - assert.Equal(t, 4, stats.PacksSealed) - assert.Equal(t, opened, closed) - assert.Equal(t, 10, opened) + Require.NoError(t, err) + assert.Equal(10, stats.BlobsPacked) + assert.Equal(4, stats.PacksSealed) + assert.Equal(opened, closed) + assert.Equal(10, opened) } func TestPackedSourcePinLimitForSoftLimit(t *testing.T) { @@ -475,39 +484,40 @@ func TestPackedSourcePinLimitForSoftLimit(t *testing.T) { {name: "target-derived ceiling", soft: 10_000, want: 4_096}, } { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, packedSourcePinLimitForSoftLimit(tt.soft)) + Assert.Equal(t, tt.want, packedSourcePinLimitForSoftLimit(tt.soft)) }) } } func TestPackedSourcePinLimitForReportedSoftLimitDistinguishesZeroFromInvalid(t *testing.T) { - assert.Equal(t, 1, packedSourcePinLimitForReportedSoftLimit(0, false), + Assert.Equal(t, 1, packedSourcePinLimitForReportedSoftLimit(0, false), "zero is a valid soft limit with no descriptors available for source pins") - assert.Equal(t, fallbackPackedSourcePins, packedSourcePinLimitForReportedSoftLimit(^uint64(0), true), + Assert.Equal(t, fallbackPackedSourcePins, packedSourcePinLimitForReportedSoftLimit(^uint64(0), true), "a signed negative or infinity sentinel remains an invalid report after conversion") - assert.Equal(t, 64, packedSourcePinLimitForReportedSoftLimit(256, false)) + Assert.Equal(t, 64, packedSourcePinLimitForReportedSoftLimit(256, false)) } func TestNormalizePackedSourceSoftLimitRecognizesPortableSentinels(t *testing.T) { + assert := Assert.New(t) soft, invalid := normalizePackedSourceSoftLimit(uint64(0)) - assert.Equal(t, uint64(0), soft) - assert.False(t, invalid) + assert.Equal(uint64(0), soft) + assert.False(invalid) soft, invalid = normalizePackedSourceSoftLimit(int64(-1)) - assert.Equal(t, ^uint64(0), soft) - assert.True(t, invalid) + assert.Equal(^uint64(0), soft) + assert.True(invalid) soft, invalid = normalizePackedSourceSoftLimit(^uint64(0)) - assert.Equal(t, ^uint64(0), soft) - assert.True(t, invalid) + assert.Equal(^uint64(0), soft) + assert.True(invalid) soft, invalid = normalizePackedSourceSoftLimit(int64(^uint64(0) >> 1)) - assert.Equal(t, uint64(^uint64(0)>>1), soft) - assert.True(t, invalid) + assert.Equal(uint64(^uint64(0)>>1), soft) + assert.True(invalid) soft, invalid = normalizePackedSourceSoftLimit(int64(256)) - assert.Equal(t, uint64(256), soft) - assert.False(t, invalid) + assert.Equal(uint64(256), soft) + assert.False(invalid) } func TestPackTargetDerivedSourcePinLimitKeepsThousandTinySourcesTogether(t *testing.T) { @@ -529,12 +539,13 @@ func TestPackTargetDerivedSourcePinLimitKeepsThousandTinySourcesTogether(t *test stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) - assert.Equal(t, 1_000, stats.BlobsPacked) - assert.Equal(t, 1, stats.PacksSealed, "the normal resource cap must not fragment ordinary tiny-object packs") + Require.NoError(t, err) + Assert.Equal(t, 1_000, stats.BlobsPacked) + Assert.Equal(t, 1, stats.PacksSealed, "the normal resource cap must not fragment ordinary tiny-object packs") } func TestPackClosesSourcePinsWhenRecordPackFails(t *testing.T) { + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() content := []byte("source pin closes after catalog failure") @@ -556,10 +567,10 @@ func TestPackClosesSourcePinsWhenRecordPackFails(t *testing.T) { _, err := maintainer.Pack(context.Background(), PackOptions{}) - require.ErrorIs(t, err, recordErr) - assert.Equal(t, 1, opened) - assert.Equal(t, opened, closed) - assert.FileExists(t, layout.LoosePath(hash)) + Require.ErrorIs(t, err, recordErr) + assert.Equal(1, opened) + assert.Equal(opened, closed) + assert.FileExists(layout.LoosePath(hash)) } func TestPackReportsSourcePinCloseFailure(t *testing.T) { @@ -581,15 +592,15 @@ func TestPackReportsSourcePinCloseFailure(t *testing.T) { stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.ErrorIs(t, err, closeErr) - assert.Equal(t, 1, stats.BlobsPacked, "catalog commit remains authoritative despite cleanup failure") + Require.ErrorIs(t, err, closeErr) + Assert.Equal(t, 1, stats.BlobsPacked, "catalog commit remains authoritative despite cleanup failure") got, _ := readStoreTest(t, maintainer.store, hash) - assert.Equal(t, content, got) + Assert.Equal(t, content, got) } func TestPackCancellationDuringCompressedCandidateCleansScratch(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) content := bytes.Repeat([]byte("cancel compressed candidate\n"), 4096) hash := hashForTest(content) @@ -643,8 +654,8 @@ func TestPackCancellationDuringCompressedCandidateCleansScratch(t *testing.T) { } func TestPackCancellationBetweenCandidatePathsClosesSelectedPin(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) content := bytes.Repeat([]byte("cancel between candidate paths\n"), 128) hash := writeMaintenanceLoose(t, layout, content) @@ -688,8 +699,8 @@ func TestPackCancellationBetweenCandidatePathsClosesSelectedPin(t *testing.T) { } func TestPackPreservesDualCopiesWhenNeitherVerifies(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() content := []byte("expected dual-copy logical bytes") @@ -714,8 +725,8 @@ func TestPackPreservesDualCopiesWhenNeitherVerifies(t *testing.T) { } func TestPackPreservesSoleValidAndDiagnosticCopiesUntilAdoption(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() content := bytes.Repeat([]byte("sole valid loose representation\n"), 32) @@ -748,8 +759,8 @@ func TestPackPreservesSoleValidAndDiagnosticCopiesUntilAdoption(t *testing.T) { } func TestPackSweepsBothVerifiedLooseRepresentations(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) content := bytes.Repeat([]byte("redundant packed content\n"), 32) entry := buildStoreTestPack(t, layout, content) @@ -770,8 +781,8 @@ func TestPackSweepsBothVerifiedLooseRepresentations(t *testing.T) { } func TestPackSweepReturnsRemovalFailure(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) content := []byte("report redundant loose removal failure") entry := buildStoreTestPack(t, layout, content) @@ -801,8 +812,8 @@ func TestPackSweepReturnsRemovalFailure(t *testing.T) { } func TestPackRepacksCompressedOnlyAuthorityWhenIndexedPackIsCorrupt(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) content := bytes.Repeat([]byte("compressed-only recovery authority\n"), 32) entry := buildStoreTestPack(t, layout, content) @@ -833,8 +844,8 @@ func TestPackRepacksCompressedOnlyAuthorityWhenIndexedPackIsCorrupt(t *testing.T } func TestPackReconcilePreservesCompressedOnlyAuthorityOverOrphanPack(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) content := bytes.Repeat([]byte("compressed authority over orphan pack\n"), 32) entry := buildStoreTestPack(t, layout, content) @@ -853,10 +864,12 @@ func TestPackReconcilePreservesCompressedOnlyAuthorityOverOrphanPack(t *testing. } func TestPackReconcileDoesNotTreatRawAsAuthoritativeAfterPreferredCompressedCorruption(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := bytes.Repeat([]byte("orphan pack remains readable authority\n"), 32) entry := buildStoreTestPack(t, layout, content) - require.Equal(t, entry.Hash, writeMaintenanceLoose(t, layout, content)) + require.Equal(entry.Hash, writeMaintenanceLoose(t, layout, content)) writeCompressedLooseFixture( t, layout, @@ -871,23 +884,25 @@ func TestPackReconcileDoesNotTreatRawAsAuthoritativeAfterPreferredCompressedCorr stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) - assert.Zero(t, stats.PacksRemoved) - assert.Equal(t, 1, stats.PacksAdopted) - assert.FileExists(t, layout.PackPath(entry.PackID)) + require.NoError(err) + assert.Zero(stats.PacksRemoved) + assert.Equal(1, stats.PacksAdopted) + assert.FileExists(layout.PackPath(entry.PackID)) got, _ := readStoreTest(t, maintainer.store, entry.Hash) - assert.Equal(t, content, got) + assert.Equal(content, got) } func TestMaintenancePreflightsCompressedStoredSizeBeforeDecode(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := []byte("small maintenance object") hash := hashForTest(content) path := layout.CompressedLoosePath(hash) - require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + require.NoError(os.MkdirAll(filepath.Dir(path), 0o700)) header := encodeCompressedLooseHeader(uint64(len(content))) physical := append(header[:], bytes.Repeat([]byte("oversized stored payload"), 4)...) - require.NoError(t, os.WriteFile(path, physical, 0o600)) + require.NoError(os.WriteFile(path, physical, 0o600)) originalReader := newLooseZstdReader decoderCalls := 0 newLooseZstdReader = func(src io.Reader) (looseZstdReader, error) { @@ -900,22 +915,24 @@ func TestMaintenancePreflightsCompressedStoredSizeBeforeDecode(t *testing.T) { _, err := verifyLoosePathIdentity(context.Background(), path, hash, limit, LooseEncodingZstd) var limitErr *LimitError - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, LimitBlobStoredBytes, limitErr.Dimension) - assert.Equal(t, uint64(len(physical)), limitErr.Actual) - assert.Equal(t, uint64(limit), limitErr.Limit) - assert.Zero(t, decoderCalls) + require.ErrorAs(err, &limitErr) + assert.Equal(LimitBlobStoredBytes, limitErr.Dimension) + assert.Equal(uint64(len(physical)), limitErr.Actual) + assert.Equal(uint64(limit), limitErr.Limit) + assert.Zero(decoderCalls) } func TestPackDefersCompressedCandidateAboveStoredLimitBeforeDecode(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := []byte("small pack candidate") hash := hashForTest(content) path := layout.CompressedLoosePath(hash) - require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + require.NoError(os.MkdirAll(filepath.Dir(path), 0o700)) header := encodeCompressedLooseHeader(uint64(len(content))) physical := append(header[:], bytes.Repeat([]byte("oversized stored payload"), 4)...) - require.NoError(t, os.WriteFile(path, physical, 0o600)) + require.NoError(os.WriteFile(path, physical, 0o600)) catalog := newMaintenanceCatalog() addMaintenanceCandidate(catalog, Candidate{ Hash: hash, Paths: []string{path}, Size: int64(len(content)), @@ -933,17 +950,17 @@ func TestPackDefersCompressedCandidateAboveStoredLimitBeforeDecode(t *testing.T) stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) - assert.Equal(t, 1, stats.BlobsDeferredOversized) - assert.Zero(t, stats.BlobsCorrupt) - assert.Zero(t, stats.BlobsPacked) - assert.Zero(t, decoderCalls) - assert.FileExists(t, path) + require.NoError(err) + assert.Equal(1, stats.BlobsDeferredOversized) + assert.Zero(stats.BlobsCorrupt) + assert.Zero(stats.BlobsPacked) + assert.Zero(decoderCalls) + assert.FileExists(path) } func TestPackOrphanSweepRecognizesOnlyCanonicalLooseNames(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) rawHash := writeMaintenanceLoose(t, layout, []byte("raw orphan")) compressedContent := []byte("compressed orphan") @@ -973,7 +990,7 @@ func TestPackOrphanSweepRecognizesOnlyCanonicalLooseNames(t *testing.T) { } func TestPackDefersOversizedBlobWithoutFailingRun(t *testing.T) { - assert := assert.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() content := bytes.Repeat([]byte("x"), 9) @@ -984,15 +1001,15 @@ func TestPackDefersOversizedBlobWithoutFailingRun(t *testing.T) { maintainer := newMaintainerForTest(t, catalog, layout, limits) stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) + Require.NoError(t, err) assert.Equal(1, stats.BlobsDeferredOversized) assert.Zero(stats.PacksSealed) assert.FileExists(layout.LoosePath(hash)) } func TestPackIncompleteReferenceInventoryPreservesLooseOrphans(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() catalog.referencesComplete = false @@ -1012,8 +1029,8 @@ func TestPackIncompleteReferenceInventoryPreservesLooseOrphans(t *testing.T) { } func TestPackIncompleteReferenceInventoryDefersOrphanPackReconciliation(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() catalog.referencesComplete = false @@ -1058,8 +1075,8 @@ func TestPackRotatesBeforeExceedingMaintenanceOutputLimits(t *testing.T) { }}, } { t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() for _, content := range [][]byte{[]byte("aaaaaaaa"), []byte("bbbbbbbb"), []byte("cccccccc")} { @@ -1084,8 +1101,8 @@ func TestPackRotatesBeforeExceedingMaintenanceOutputLimits(t *testing.T) { } func TestPackDefersBlobThatCannotFitAnEmptyOutputPack(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() content := []byte("eight888") @@ -1112,13 +1129,13 @@ func TestPackSoftBudgetStopsAfterCommittedBlob(t *testing.T) { maintainer := newMaintainerForTest(t, catalog, layout, DefaultLimits()) stats, err := maintainer.Pack(context.Background(), PackOptions{MaxBytes: 1}) - require.NoError(t, err) - assert.True(t, stats.BudgetExhausted) - assert.Equal(t, 1, stats.BlobsPacked) + Require.NoError(t, err) + Assert.True(t, stats.BudgetExhausted) + Assert.Equal(t, 1, stats.BlobsPacked) } func TestPackPreservesCatalogCandidateOrder(t *testing.T) { - require := require.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() contents := [][]byte{[]byte("first by locality"), []byte("second by locality"), []byte("third by locality")} @@ -1154,8 +1171,8 @@ func TestPackPreservesCatalogCandidateOrder(t *testing.T) { } func TestPackCommitFailureLeavesRecoverableOrphanAndLooseSource(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() content := []byte("survive catalog commit failure") @@ -1191,14 +1208,14 @@ func TestRepairDropsDanglingRecordsAndUnreferencedMappings(t *testing.T) { maintainer := newMaintainerForTest(t, catalog, layout, DefaultLimits()) stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) - assert.Equal(t, 1, stats.RecordsDropped) - assert.Equal(t, int64(1), stats.MappingsPruned) + Require.NoError(t, err) + Assert.Equal(t, 1, stats.RecordsDropped) + Assert.Equal(t, int64(1), stats.MappingsPruned) } func TestRepairRepacksValidRawCopyWhenIndexedPackAndPreferredCompressedAreCorrupt(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() content := []byte("recover from corrupt indexed pack") @@ -1243,8 +1260,8 @@ func TestRepairRepacksValidRawCopyWhenIndexedPackAndPreferredCompressedAreCorrup } func TestRepairCommitFailureRetainsCorruptPackedMappingWhenOnlyRawAlternateIsValid(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() content := []byte("retain corrupt packed authority until raw recovery commits") @@ -1284,11 +1301,13 @@ func TestRepairCommitFailureRetainsCorruptPackedMappingWhenOnlyRawAlternateIsVal } func TestRepairUsesVerifiedLooseSizeWhenPackedMetadataIsCorrupt(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() content := []byte("derive recovery size from verified loose bytes") entry := buildStoreTestPack(t, layout, content) - require.Equal(t, entry.Hash, writeMaintenanceLoose(t, layout, content)) + require.Equal(entry.Hash, writeMaintenanceLoose(t, layout, content)) catalog.addLoose(entry.Hash, layout.LoosePath(entry.Hash)) entry.RawLen++ catalog.entries[entry.Hash] = entry @@ -1299,14 +1318,14 @@ func TestRepairUsesVerifiedLooseSizeWhenPackedMetadataIsCorrupt(t *testing.T) { stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) - assert.Equal(t, 1, stats.BlobsPacked) + require.NoError(err) + assert.Equal(1, stats.BlobsPacked) location, err := catalog.Resolve(context.Background(), entry.Hash) - require.NoError(t, err) - require.NotNil(t, location.Pack) - assert.NotEqual(t, entry.PackID, location.Pack.PackID) + require.NoError(err) + require.NotNil(location.Pack) + assert.NotEqual(entry.PackID, location.Pack.PackID) got, _ := readStoreTest(t, maintainer.store, entry.Hash) - assert.Equal(t, content, got) + assert.Equal(content, got) } func TestReconcileAdoptsOnlyFullyVerifiedOrphanPack(t *testing.T) { @@ -1316,8 +1335,8 @@ func TestReconcileAdoptsOnlyFullyVerifiedOrphanPack(t *testing.T) { name = "damaged" } t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() content := []byte("orphan recovery content") @@ -1358,11 +1377,11 @@ func TestPackHonorsCancellationBeforeMutation(t *testing.T) { cancel() _, err := maintainer.Pack(ctx, PackOptions{}) - require.ErrorIs(t, err, context.Canceled) + Require.ErrorIs(t, err, context.Canceled) } func TestPackDurablyCreatesPacksDirectory(t *testing.T) { - require := require.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) catalog := newMaintenanceCatalog() maintainer := newMaintainerForTest(t, catalog, layout, DefaultLimits()) @@ -1382,8 +1401,8 @@ func TestPackDurablyCreatesPacksDirectory(t *testing.T) { func newMaintainerForTest(t testing.TB, catalog Catalog, layout Layout, limits Limits) *Maintainer { t.Helper() maintainer, err := NewMaintainer(catalog, layout, MaintainerOptions{Limits: limits}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, maintainer.Close()) }) + Require.NoError(t, err) + t.Cleanup(func() { Require.NoError(t, maintainer.Close()) }) return maintainer } @@ -1391,8 +1410,8 @@ func writeMaintenanceLoose(t *testing.T, layout Layout, content []byte) Hash { t.Helper() hash := hashForTest(content) path := layout.LoosePath(hash) - require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) - require.NoError(t, os.WriteFile(path, content, 0o600)) + Require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + Require.NoError(t, os.WriteFile(path, content, 0o600)) return hash } diff --git a/packstore/pack_verification_windows_test.go b/packstore/pack_verification_windows_test.go index 0bb2828..bdc79ed 100644 --- a/packstore/pack_verification_windows_test.go +++ b/packstore/pack_verification_windows_test.go @@ -9,14 +9,16 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "golang.org/x/sys/windows" ) const windowsFileDeleteChild = 0x40 func TestWindowsPackingReadableNonDeletableLooseCandidate(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := []byte("readable packing candidate without deletion permission") hash := writeMaintenanceLoose(t, layout, content) @@ -26,33 +28,35 @@ func TestWindowsPackingReadableNonDeletableLooseCandidate(t *testing.T) { restoreWindowsFileDACL := denyWindowsFileDeletion(t, loosePath) t.Cleanup(restoreWindowsFileDACL) readable, _, err := openLooseFile(loosePath) - require.NoError(t, err, "fixture remains readable") - require.NoError(t, readable.Close()) + require.NoError(err, "fixture remains readable") + require.NoError(readable.Close()) deletePin, _, deleteErr := openLooseIdentityPin(loosePath) if deletePin != nil { - require.NoError(t, deletePin.Close()) + require.NoError(deletePin.Close()) } - require.Error(t, deleteErr, "fixture must deny deletion-capable identity handles") + require.Error(deleteErr, "fixture must deny deletion-capable identity handles") maintainer := newMaintainerForTest(t, catalog, layout, DefaultLimits()) stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) - assert.Equal(t, 1, stats.BlobsPacked) - assert.Zero(t, stats.BlobsCorrupt) + require.NoError(err) + assert.Equal(1, stats.BlobsPacked) + assert.Zero(stats.BlobsCorrupt) location, err := catalog.Resolve(context.Background(), hash) - require.NoError(t, err) - require.NotNil(t, location.Pack) - assert.FileExists(t, loosePath) + require.NoError(err) + require.NotNil(location.Pack) + assert.FileExists(loosePath) got, _ := readStoreTest(t, maintainer.store, hash) - assert.Equal(t, content, got) + assert.Equal(content, got) } func TestWindowsRecoveryPacksReadableNonDeletableLooseAuthority(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := []byte("readable loose authority without deletion permission") entry := buildStoreTestPack(t, layout, content) - require.Equal(t, entry.Hash, writeMaintenanceLoose(t, layout, content)) + require.Equal(entry.Hash, writeMaintenanceLoose(t, layout, content)) catalog := newMaintenanceCatalog() catalog.entries[entry.Hash] = entry catalog.members[entry.Hash] = Reference{Hash: entry.Hash, OriginalHashes: []string{entry.Hash.String()}} @@ -60,48 +64,48 @@ func TestWindowsRecoveryPacksReadableNonDeletableLooseAuthority(t *testing.T) { PackID: entry.PackID, EntryCount: 1, StoredBytes: entry.StoredLen, CreatedAt: time.Now(), } packFile, err := os.OpenFile(layout.PackPath(entry.PackID), os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) var damaged [1]byte _, err = packFile.ReadAt(damaged[:], entry.Offset) - require.NoError(t, err) + require.NoError(err) damaged[0] ^= 0xff _, err = packFile.WriteAt(damaged[:], entry.Offset) - require.NoError(t, err) - require.NoError(t, packFile.Close()) + require.NoError(err) + require.NoError(packFile.Close()) loosePath := layout.LoosePath(entry.Hash) restoreWindowsFileDACL := denyWindowsFileDeletion(t, loosePath) t.Cleanup(restoreWindowsFileDACL) readable, _, err := openLooseFile(loosePath) - require.NoError(t, err, "fixture remains readable") - require.NoError(t, readable.Close()) + require.NoError(err, "fixture remains readable") + require.NoError(readable.Close()) deletePin, _, deleteErr := openLooseIdentityPin(loosePath) if deletePin != nil { - require.NoError(t, deletePin.Close()) + require.NoError(deletePin.Close()) } - require.Error(t, deleteErr, "fixture must deny deletion-capable identity handles") + require.Error(deleteErr, "fixture must deny deletion-capable identity handles") maintainer := newMaintainerForTest(t, catalog, layout, DefaultLimits()) stats, err := maintainer.Pack(context.Background(), PackOptions{}) - require.NoError(t, err) - assert.Equal(t, 1, stats.BlobsPacked) - assert.Zero(t, stats.BlobsCorrupt) + require.NoError(err) + assert.Equal(1, stats.BlobsPacked) + assert.Zero(stats.BlobsCorrupt) entries, _ := catalog.snapshot() - require.Contains(t, entries, entry.Hash) - assert.NotEqual(t, entry.PackID, entries[entry.Hash].PackID) - assert.FileExists(t, loosePath) + require.Contains(entries, entry.Hash) + assert.NotEqual(entry.PackID, entries[entry.Hash].PackID) + assert.FileExists(loosePath) got, _ := readStoreTest(t, maintainer.store, entry.Hash) - assert.Equal(t, content, got) + assert.Equal(content, got) } func denyWindowsFileDeletion(t *testing.T, path string) func() { t.Helper() file, err := openWindowsNoFollow(path, windows.READ_CONTROL|windows.WRITE_DAC) - require.NoError(t, err) + Require.NoError(t, err) parent, err := openWindowsNoFollow(filepath.Dir(path), windows.READ_CONTROL|windows.WRITE_DAC) - require.NoError(t, err) + Require.NoError(t, err) user, err := windows.GetCurrentProcessToken().GetTokenUser() - require.NoError(t, err) + Require.NoError(t, err) trustee := windows.TRUSTEE{ TrusteeForm: windows.TRUSTEE_IS_SID, TrusteeType: windows.TRUSTEE_IS_USER, @@ -121,8 +125,8 @@ func denyWindowsFileDeletion(t *testing.T, path string) func() { Trustee: trustee, }, }, nil) - require.NoError(t, err) - require.NoError(t, windows.SetSecurityInfo( + Require.NoError(t, err) + Require.NoError(t, windows.SetSecurityInfo( windows.Handle(file.Fd()), windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, @@ -145,8 +149,8 @@ func denyWindowsFileDeletion(t *testing.T, path string) func() { Trustee: trustee, }, }, nil) - require.NoError(t, err) - require.NoError(t, windows.SetSecurityInfo( + Require.NoError(t, err) + Require.NoError(t, windows.SetSecurityInfo( windows.Handle(parent.Fd()), windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, @@ -162,8 +166,8 @@ func denyWindowsFileDeletion(t *testing.T, path string) func() { Inheritance: windows.NO_INHERITANCE, Trustee: trustee, }}, nil) - require.NoError(t, aclErr) - require.NoError(t, windows.SetSecurityInfo( + Require.NoError(t, aclErr) + Require.NoError(t, windows.SetSecurityInfo( windows.Handle(file.Fd()), windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, @@ -172,7 +176,7 @@ func denyWindowsFileDeletion(t *testing.T, path string) func() { fullControl, nil, )) - require.NoError(t, windows.SetSecurityInfo( + Require.NoError(t, windows.SetSecurityInfo( windows.Handle(parent.Fd()), windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, @@ -181,7 +185,7 @@ func denyWindowsFileDeletion(t *testing.T, path string) func() { fullControl, nil, )) - require.NoError(t, file.Close()) - require.NoError(t, parent.Close()) + Require.NoError(t, file.Close()) + Require.NoError(t, parent.Close()) } } diff --git a/packstore/preflight_test.go b/packstore/preflight_test.go index b10a2a7..871529f 100644 --- a/packstore/preflight_test.go +++ b/packstore/preflight_test.go @@ -11,13 +11,13 @@ import ( "testing" "github.com/klauspost/compress/zstd" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) func TestMaintenanceReadAllowsMinimumZstdWindowForSmallBlob(t *testing.T) { - require := require.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("small bounded frame "), 16) encoder, err := zstd.NewWriter(nil, zstd.WithEncoderConcurrency(1), @@ -49,7 +49,7 @@ func TestMaintenanceReadAllowsMinimumZstdWindowForSmallBlob(t *testing.T) { } func TestMaintenanceReadRejectsZstdWindowAboveBlobLimit(t *testing.T) { - require := require.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("bounded window "), 128) encoder, err := zstd.NewWriter(nil, zstd.WithEncoderConcurrency(1), @@ -85,8 +85,8 @@ func TestMaintenanceReadRejectsZstdWindowAboveBlobLimit(t *testing.T) { func TestReadBoundedEnforcesLooseAndPackedBlobLimits(t *testing.T) { for _, storage := range []string{"loose", "packed"} { t.Run(storage, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := []byte("bounded content") hash := hashForTest(content) @@ -112,7 +112,7 @@ func TestReadBoundedEnforcesLooseAndPackedBlobLimits(t *testing.T) { func TestReadBoundedEnforcesConfiguredBlobCeiling(t *testing.T) { for _, storage := range []string{"loose", "packed"} { t.Run(storage, func(t *testing.T) { - require := require.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := []byte("ninebytes") hash := hashForTest(content) @@ -141,12 +141,12 @@ func TestPreflightRejectsContainerFooterAndDuplicateIDs(t *testing.T) { t.Run("container", func(t *testing.T) { layout := layoutForStoreTest(t) entry := buildStoreTestPack(t, layout, []byte("container")) - require.NoError(t, os.Truncate(layout.PackPath(entry.PackID), limits.PackBytes+1)) + Require.NoError(t, os.Truncate(layout.PackPath(entry.PackID), limits.PackBytes+1)) _, err := OpenMaintenancePack(layout.PackPath(entry.PackID), limits) - assert.ErrorIs(t, err, ErrBlobTooLarge) + Assert.ErrorIs(t, err, ErrBlobTooLarge) }) t.Run("footer", func(t *testing.T) { - require := require.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) entry := buildStoreTestPack(t, layout, []byte("footer")) path := layout.PackPath(entry.PackID) @@ -160,10 +160,10 @@ func TestPreflightRejectsContainerFooterAndDuplicateIDs(t *testing.T) { require.NoError(err) require.NoError(f.Close()) _, err = OpenMaintenancePack(path, limits) - assert.ErrorIs(t, err, ErrBlobTooLarge) + Assert.ErrorIs(t, err, ErrBlobTooLarge) }) t.Run("duplicate id", func(t *testing.T) { - require := require.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) writer, err := pack.NewWriter(t.TempDir(), pack.WriterOptions{}) require.NoError(err) @@ -176,14 +176,14 @@ func TestPreflightRejectsContainerFooterAndDuplicateIDs(t *testing.T) { _, err = writer.Seal(path) require.NoError(err) _, err = OpenMaintenancePack(path, limits) - assert.ErrorIs(t, err, pack.ErrCorrupt) + Assert.ErrorIs(t, err, pack.ErrCorrupt) }) } func TestPreflightEnforcesPackFormatLengthsIndependently(t *testing.T) { t.Run("footer length", func(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) entry := buildStoreTestPack(t, layout, []byte("format footer ceiling")) path := layout.PackPath(entry.PackID) @@ -209,7 +209,7 @@ func TestPreflightEnforcesPackFormatLengthsIndependently(t *testing.T) { }) t.Run("stored frame length", func(t *testing.T) { - require := require.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) entry := buildStoreTestPack(t, layout, []byte("format stored ceiling")) path := layout.PackPath(entry.PackID) @@ -238,7 +238,7 @@ func TestPreflightEnforcesPackFormatLengthsIndependently(t *testing.T) { } func TestPreflightRejectsSymlinkToValidPack(t *testing.T) { - require := require.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) entry := buildStoreTestPack(t, layout, []byte("symlink target")) link := filepath.Join(t.TempDir(), "pack-link") @@ -259,7 +259,7 @@ func TestMaintenanceAllocationsRespectPlatformInt(t *testing.T) { t.Cleanup(func() { maxPlatformInt = originalMax }) t.Run("verified loose content", func(t *testing.T) { - require := require.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := []byte("ninebytes") hash := writeMaintenanceLoose(t, layout, content) @@ -269,8 +269,8 @@ func TestMaintenanceAllocationsRespectPlatformInt(t *testing.T) { }) t.Run("pack footer", func(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) entry := buildStoreTestPack(t, layout, []byte("footer allocation")) @@ -288,23 +288,24 @@ func TestMaintenanceAllocationsRespectPlatformInt(t *testing.T) { func TestVerifyLooseFileBoundsSnapshotAndHonorsCancellation(t *testing.T) { t.Run("growth", func(t *testing.T) { + require := Require.New(t) layout := layoutForStoreTest(t) content := []byte("snapshotted content") hash := writeMaintenanceLoose(t, layout, content) path := layout.LoosePath(hash) info, err := snapshotPathIdentity(path) - require.NoError(t, err) + require.NoError(err) appendFile, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0) - require.NoError(t, err) + require.NoError(err) _, err = appendFile.Write([]byte("growth")) - require.NoError(t, err) - require.NoError(t, appendFile.Close()) + require.NoError(err) + require.NoError(appendFile.Close()) f, err := openNoFollow(path, false) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, f.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(f.Close()) }) err = verifyLooseFile(context.Background(), f, info, hash) - require.ErrorIs(t, err, ErrContentMismatch) + require.ErrorIs(err, ErrContentMismatch) }) t.Run("cancellation", func(t *testing.T) { @@ -313,21 +314,21 @@ func TestVerifyLooseFileBoundsSnapshotAndHonorsCancellation(t *testing.T) { hash := writeMaintenanceLoose(t, layout, content) path := layout.LoosePath(hash) info, err := snapshotPathIdentity(path) - require.NoError(t, err) + Require.NoError(t, err) f, err := openNoFollow(path, false) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, f.Close()) }) + Require.NoError(t, err) + t.Cleanup(func() { Require.NoError(t, f.Close()) }) ctx, cancel := context.WithCancel(context.Background()) cancel() err = verifyLooseFile(ctx, f, info, hash) - require.ErrorIs(t, err, context.Canceled) + Require.ErrorIs(t, err, context.Canceled) }) } func TestLimitErrorIsTyped(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) err := newLimitError(LimitBlobRawBytes, 11, 10) require.ErrorIs(err, ErrBlobTooLarge) var limitErr *LimitError diff --git a/packstore/repack.go b/packstore/repack.go index 799cb91..9214ca0 100644 --- a/packstore/repack.go +++ b/packstore/repack.go @@ -298,6 +298,9 @@ func (m *Maintainer) rewriteSource(ctx context.Context, oldPackID string, entrie } return result, fmt.Errorf("packstore: read source %s blob %s: %w", oldPackID, indexed.Hash, err) } + if prepared == nil { + return result, fmt.Errorf("packstore: prepare source %s blob %s returned no result", oldPackID, indexed.Hash) + } if err := checkPlainOutput(m.limits, uint64(pack.MinEntryOffset), prepared.StoredLen(), 1); err != nil { _ = prepared.Close() return result, err diff --git a/packstore/s3store/inventory_test.go b/packstore/s3store/inventory_test.go index d8a003e..54f1467 100644 --- a/packstore/s3store/inventory_test.go +++ b/packstore/s3store/inventory_test.go @@ -10,14 +10,14 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/packstore" ) func TestProbeDisarmsCleanupAfterExplicitDelete(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) state, backend := newProbeHTTPBackend(t, false) report, err := backend.Probe(context.Background()) @@ -39,26 +39,28 @@ func TestProbeDisarmsCleanupAfterExplicitDelete(t *testing.T) { } func TestProbeRejectsAcknowledgedDeleteThatLeavesObject(t *testing.T) { + assert := Assert.New(t) state, backend := newProbeHTTPBackend(t, false) state.ignoreDelete = true report, err := backend.Probe(context.Background()) - require.Error(t, err) - assert.False(t, report.Delete) - assert.Equal(t, 2, state.deletes, "failed verification must leave cleanup armed") - assert.NotNil(t, state.object) + Require.Error(t, err) + assert.False(report.Delete) + assert.Equal(2, state.deletes, "failed verification must leave cleanup armed") + assert.NotNil(state.object) } func TestProbeCleanupUsesFreshDeadline(t *testing.T) { + assert := Assert.New(t) state, backend := newProbeHTTPBackend(t, true) _, err := backend.Probe(context.Background()) - require.Error(t, err) - assert.True(t, state.cleanupOwnershipHadDeadline) - assert.True(t, state.cleanupDeleteHadDeadline) - assert.Equal(t, 1, state.deletes) + Require.Error(t, err) + assert.True(state.cleanupOwnershipHadDeadline) + assert.True(state.cleanupDeleteHadDeadline) + assert.Equal(1, state.deletes) } func TestProbeRejectsIgnoredConditionalWrites(t *testing.T) { @@ -86,7 +88,7 @@ func TestProbeRejectsIgnoredConditionalWrites(t *testing.T) { _, err := backend.Probe(context.Background()) - require.Error(t, err) + Require.Error(t, err) }) } } @@ -97,23 +99,25 @@ func TestProbeRejectsAppliedStaleConditionalReplacement(t *testing.T) { _, err := backend.Probe(context.Background()) - require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) + Require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) } func TestReadProbeBodyBoundsAndValidatesResponse(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) expected := []byte("probe") got, err := readProbeBody(bytes.NewReader(expected), nil, expected) - require.NoError(t, err) - assert.Equal(t, expected, got) + require.NoError(err) + assert.Equal(expected, got) oversized := bytes.NewReader(bytes.Repeat([]byte("x"), 64)) _, err = readProbeBody(oversized, nil, expected) - require.ErrorContains(t, err, "exceeds expected length") - assert.Equal(t, 64-len(expected)-1, oversized.Len()) + require.ErrorContains(err, "exceeds expected length") + assert.Equal(64-len(expected)-1, oversized.Len()) contentLength := int64(len(expected) + 1) _, err = readProbeBody(bytes.NewReader(expected), &contentLength, expected) - require.ErrorContains(t, err, "response length") + require.ErrorContains(err, "response length") } type probeHTTPState struct { @@ -148,7 +152,7 @@ func newProbeHTTPBackend(t *testing.T, failFirstProbeRead bool) (*probeHTTPState Epoch: "epoch-1", } marker, err := packstore.MarshalOwnership(owner) - require.NoError(t, err) + Require.NoError(t, err) state := &probeHTTPState{ t: t, marker: marker, diff --git a/packstore/s3store/ownership.go b/packstore/s3store/ownership.go index 726b642..b414f9f 100644 --- a/packstore/s3store/ownership.go +++ b/packstore/s3store/ownership.go @@ -150,8 +150,7 @@ func classifyError(operation string, err error) error { } func statusCode(err error) int { - var response *smithyhttp.ResponseError - if errors.As(err, &response) { + if response, ok := errors.AsType[*smithyhttp.ResponseError](err); ok { return response.HTTPStatusCode() } return 0 diff --git a/packstore/s3store/publication_test.go b/packstore/s3store/publication_test.go index 5ecbdeb..26b5fe7 100644 --- a/packstore/s3store/publication_test.go +++ b/packstore/s3store/publication_test.go @@ -13,8 +13,8 @@ import ( "testing" "github.com/klauspost/compress/zstd" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" "go.kenn.io/kit/packstore" ) @@ -36,8 +36,8 @@ func TestVerifyPackObjectRejectsMismatchedContentLengthBeforeRead(t *testing.T) 1, ) - require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) - assert.Zero(t, body.read) + Require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) + Assert.Zero(t, body.read) } func TestVerifyPackObjectBoundsReadWhenContentLengthMissing(t *testing.T) { @@ -56,8 +56,8 @@ func TestVerifyPackObjectBoundsReadWhenContentLengthMissing(t *testing.T) { expectedSize, ) - require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) - assert.LessOrEqual(t, body.read, expectedSize+1) + Require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) + Assert.LessOrEqual(t, body.read, expectedSize+1) } func TestVerifyRawObjectBoundsReadWhenContentLengthMissing(t *testing.T) { @@ -77,8 +77,8 @@ func TestVerifyRawObjectBoundsReadWhenContentLengthMissing(t *testing.T) { expectedSize, ) - require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) - assert.LessOrEqual(t, body.read, expectedSize+1) + Require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) + Assert.LessOrEqual(t, body.read, expectedSize+1) } func TestVerifyPackObjectEnforcesConfiguredBlobLimit(t *testing.T) { @@ -102,10 +102,10 @@ func TestVerifyPackObjectEnforcesConfiguredBlobLimit(t *testing.T) { int64(len(packBytes)), ) - require.ErrorIs(t, err, packstore.ErrBlobTooLarge) + Require.ErrorIs(t, err, packstore.ErrBlobTooLarge) var limit *packstore.LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, packstore.LimitBlobRawBytes, limit.Dimension) + Require.ErrorAs(t, err, &limit) + Assert.Equal(t, packstore.LimitBlobRawBytes, limit.Dimension) } func TestVerifyPackObjectEnforcesConfiguredDecoderWindowLimit(t *testing.T) { @@ -134,10 +134,10 @@ func TestVerifyPackObjectEnforcesConfiguredDecoderWindowLimit(t *testing.T) { int64(len(packBytes)), ) - require.ErrorIs(t, err, packstore.ErrBlobTooLarge) + Require.ErrorIs(t, err, packstore.ErrBlobTooLarge) var limit *packstore.LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, packstore.LimitBlobWindowBytes, limit.Dimension) + Require.ErrorAs(t, err, &limit) + Assert.Equal(t, packstore.LimitBlobWindowBytes, limit.Dimension) } func TestVerifyPackObjectRejectsDecodedLengthMismatch(t *testing.T) { @@ -171,13 +171,15 @@ func TestVerifyPackObjectRejectsDecodedLengthMismatch(t *testing.T) { int64(len(packBytes)), ) - require.ErrorIs(t, err, pack.ErrCorrupt) - require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) + Require.ErrorIs(t, err, pack.ErrCorrupt) + Require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) }) } } func TestPublishPackRejectsKnownConfiguredLimitBeforeMultipart(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) limits := packstore.DefaultLimits() limits.PackBytes = 8 owner := packstore.Ownership{ @@ -187,7 +189,7 @@ func TestPublishPackRejectsKnownConfiguredLimitBeforeMultipart(t *testing.T) { Epoch: "epoch-1", } marker, err := packstore.MarshalOwnership(owner) - require.NoError(t, err) + require.NoError(err) var requests int backend := newHTTPBackend(limits, func(request *http.Request) (*http.Response, error) { requests++ @@ -220,15 +222,17 @@ func TestPublishPackRejectsKnownConfiguredLimitBeforeMultipart(t *testing.T) { }, ) - require.ErrorIs(t, err, packstore.ErrBlobTooLarge) + require.ErrorIs(err, packstore.ErrBlobTooLarge) var limit *packstore.LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, packstore.LimitPackContainerBytes, limit.Dimension) - assert.Zero(t, source.read) - assert.Equal(t, 1, requests) + require.ErrorAs(err, &limit) + assert.Equal(packstore.LimitPackContainerBytes, limit.Dimension) + assert.Zero(source.read) + assert.Equal(1, requests) } func TestPublishPackCapsCallerLimitBeforeMultipart(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) limits := packstore.DefaultLimits() limits.PackBytes = 8 owner := packstore.Ownership{ @@ -238,7 +242,7 @@ func TestPublishPackCapsCallerLimitBeforeMultipart(t *testing.T) { Epoch: "epoch-1", } marker, err := packstore.MarshalOwnership(owner) - require.NoError(t, err) + require.NoError(err) var creates, uploads, completes, aborts int backend := newHTTPBackend(limits, func(request *http.Request) (*http.Response, error) { query := request.URL.Query() @@ -283,14 +287,14 @@ func TestPublishPackCapsCallerLimitBeforeMultipart(t *testing.T) { packstore.PublishOptions{MaxBytes: 100}, ) - require.ErrorIs(t, err, packstore.ErrBlobTooLarge) + require.ErrorIs(err, packstore.ErrBlobTooLarge) var limit *packstore.LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, packstore.LimitPackContainerBytes, limit.Dimension) - assert.Zero(t, creates) - assert.Zero(t, uploads) - assert.Zero(t, completes) - assert.Zero(t, aborts) + require.ErrorAs(err, &limit) + assert.Equal(packstore.LimitPackContainerBytes, limit.Dimension) + assert.Zero(creates) + assert.Zero(uploads) + assert.Zero(completes) + assert.Zero(aborts) } func TestPublishPackRejectsExactSizeMismatchBeforeMultipart(t *testing.T) { @@ -301,7 +305,7 @@ func TestPublishPackRejectsExactSizeMismatchBeforeMultipart(t *testing.T) { Epoch: "epoch-1", } marker, err := packstore.MarshalOwnership(owner) - require.NoError(t, err) + Require.NoError(t, err) for _, tt := range []struct { name string expectedSize int64 @@ -310,6 +314,7 @@ func TestPublishPackRejectsExactSizeMismatchBeforeMultipart(t *testing.T) { {name: "source is overlong", expectedSize: 4}, } { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) var creates, uploads, completes, aborts int backend := newHTTPBackend(packstore.DefaultLimits(), func(request *http.Request) (*http.Response, error) { query := request.URL.Query() @@ -354,11 +359,11 @@ func TestPublishPackRejectsExactSizeMismatchBeforeMultipart(t *testing.T) { packstore.PublishOptions{ExpectedSize: tt.expectedSize, SizeKnown: true}, ) - require.ErrorIs(t, err, packstore.ErrContentMismatch) - assert.Zero(t, creates) - assert.Zero(t, uploads) - assert.Zero(t, completes) - assert.Zero(t, aborts) + Require.ErrorIs(t, err, packstore.ErrContentMismatch) + assert.Zero(creates) + assert.Zero(uploads) + assert.Zero(completes) + assert.Zero(aborts) }) } } @@ -371,7 +376,7 @@ func TestPublishPackValidatesEveryEntryBeforeMultipart(t *testing.T) { Epoch: "epoch-1", } marker, err := packstore.MarshalOwnership(owner) - require.NoError(t, err) + Require.NoError(t, err) forgedLimits := packstore.DefaultLimits() forgedLimits.BlobBytes = 16 forgedID, forgedBytes := makeEncodedPack(t, []byte("x"), 17, 0) @@ -404,6 +409,8 @@ func TestPublishPackValidatesEveryEntryBeforeMultipart(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) var gets, multipartCreates, multipartCompletes int backend := newHTTPBackend(tt.limits, func(request *http.Request) (*http.Response, error) { query := request.URL.Query() @@ -454,19 +461,21 @@ func TestPublishPackValidatesEveryEntryBeforeMultipart(t *testing.T) { packstore.PublishOptions{}, ) - require.ErrorIs(t, err, tt.wantErr) + require.ErrorIs(err, tt.wantErr) if tt.limit != "" { var limit *packstore.LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, tt.limit, limit.Dimension) + require.ErrorAs(err, &limit) + assert.Equal(tt.limit, limit.Dimension) } - assert.Zero(t, multipartCreates) - assert.Zero(t, multipartCompletes) + assert.Zero(multipartCreates) + assert.Zero(multipartCompletes) }) } } func TestPublishPackRejectsInvalidDurabilityBeforeStaging(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) owner := packstore.Ownership{ Format: packstore.OwnershipFormatV1, Vault: "test-vault", @@ -474,7 +483,7 @@ func TestPublishPackRejectsInvalidDurabilityBeforeStaging(t *testing.T) { Epoch: "epoch-1", } marker, err := packstore.MarshalOwnership(owner) - require.NoError(t, err) + require.NoError(err) packID, packBytes, _ := makePack(t, []byte("invalid pack durability")) multipartCreates := 0 backend := newHTTPBackend(packstore.DefaultLimits(), func(request *http.Request) (*http.Response, error) { @@ -501,9 +510,9 @@ func TestPublishPackRejectsInvalidDurabilityBeforeStaging(t *testing.T) { packstore.PublishOptions{Durability: packstore.Durability(99)}, ) - require.ErrorIs(t, err, packstore.ErrInvalidPolicy) - assert.Zero(t, source.read) - assert.Zero(t, multipartCreates) + require.ErrorIs(err, packstore.ErrInvalidPolicy) + assert.Zero(source.read) + assert.Zero(multipartCreates) } func TestPublishLooseRejectsInvalidOptionsBeforeMultipart(t *testing.T) { @@ -514,7 +523,7 @@ func TestPublishLooseRejectsInvalidOptionsBeforeMultipart(t *testing.T) { Epoch: "epoch-1", } marker, err := packstore.MarshalOwnership(owner) - require.NoError(t, err) + Require.NoError(t, err) content := []byte("hello") hash := hashOf(content) for _, tt := range []struct { @@ -556,8 +565,8 @@ func TestPublishLooseRejectsInvalidOptionsBeforeMultipart(t *testing.T) { context.Background(), hash, bytes.NewReader(content), tt.opts, ) - require.ErrorIs(t, err, packstore.ErrInvalidPolicy) - assert.Zero(t, multipartCreates) + Require.ErrorIs(t, err, packstore.ErrInvalidPolicy) + Assert.Zero(t, multipartCreates) }) } } @@ -570,7 +579,7 @@ func TestRepairLooseAcceptsMaxInt64Limit(t *testing.T) { Epoch: "epoch-1", } marker, err := packstore.MarshalOwnership(owner) - require.NoError(t, err) + Require.NoError(t, err) content := []byte("hello") hash := hashOf(content) var gets int @@ -608,11 +617,12 @@ func TestRepairLooseAcceptsMaxInt64Limit(t *testing.T) { }, ) - require.NoError(t, err) - assert.Equal(t, int64(len(content)), receipt.Location.LogicalSize) + Require.NoError(t, err) + Assert.Equal(t, int64(len(content)), receipt.Location.LogicalSize) } func TestMultipartPublishBoundsPartBufferByPublicationLimit(t *testing.T) { + assert := Assert.New(t) var uploaded int64 backend := newHTTPBackend(packstore.DefaultLimits(), func(request *http.Request) (*http.Response, error) { query := request.URL.Query() @@ -647,10 +657,10 @@ func TestMultipartPublishBoundsPartBufferByPublicationLimit(t *testing.T) { multipartPublishOptions{maxBytes: 2}, ) - require.NoError(t, err) - assert.Equal(t, int64(2), result.size) - assert.Equal(t, int64(2), uploaded) - assert.Equal(t, 3, source.maxRequest) + Require.NoError(t, err) + assert.Equal(int64(2), result.size) + assert.Equal(int64(2), uploaded) + assert.Equal(3, source.maxRequest) } func TestMultipartPublishAbortsDeduplicatedUpload(t *testing.T) { @@ -688,12 +698,14 @@ func TestMultipartPublishAbortsDeduplicatedUpload(t *testing.T) { multipartPublishOptions{maxBytes: 5}, ) - require.NoError(t, err) - assert.False(t, result.created) - assert.Equal(t, 1, aborts) + Require.NoError(t, err) + Assert.False(t, result.created) + Assert.Equal(t, 1, aborts) } func TestRepairLooseCancelStopsBeforePut(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) ctx, cancel := context.WithCancel(context.Background()) source := &cancelAfterFirstRead{ cancel: cancel, @@ -706,7 +718,7 @@ func TestRepairLooseCancelStopsBeforePut(t *testing.T) { Epoch: "epoch-1", } marker, err := packstore.MarshalOwnership(owner) - require.NoError(t, err) + require.NoError(err) var puts int backend := newHTTPBackend(packstore.DefaultLimits(), func(request *http.Request) (*http.Response, error) { switch request.Method { @@ -733,14 +745,14 @@ func TestRepairLooseCancelStopsBeforePut(t *testing.T) { packstore.PublishOptions{ExpectedSize: 5, SizeKnown: true}, ) - require.ErrorIs(t, err, context.Canceled) - assert.Equal(t, 1, source.reads) - assert.Zero(t, puts) + require.ErrorIs(err, context.Canceled) + assert.Equal(1, source.reads) + assert.Zero(puts) } func TestRepairLooseReadsOnlyOneByteBeyondExpectedSize(t *testing.T) { - assert := assert.New(t) - require := require.New(t) + assert := Assert.New(t) + require := Require.New(t) expected := []byte("known") sourceBytes := append(bytes.Clone(expected), bytes.Repeat([]byte("x"), 1<<20)...) source := &countingReadCloser{reader: bytes.NewReader(sourceBytes)} @@ -787,6 +799,7 @@ func TestRepairLooseReadsOnlyOneByteBeyondExpectedSize(t *testing.T) { } func TestMultipartPublishCancelAbortsWithBoundedContext(t *testing.T) { + assert := Assert.New(t) ctx, cancel := context.WithCancel(context.Background()) source := &cancelAfterFirstRead{ cancel: cancel, @@ -809,9 +822,9 @@ func TestMultipartPublishCancelAbortsWithBoundedContext(t *testing.T) { completes++ return xmlResponse(request, http.StatusOK, ""), nil case request.Method == http.MethodDelete && query.Get("uploadId") == "upload-1": - require.NoError(t, request.Context().Err()) + Require.NoError(t, request.Context().Err()) _, hasDeadline := request.Context().Deadline() - assert.True(t, hasDeadline) + assert.True(hasDeadline) aborts++ return xmlResponse(request, http.StatusNoContent, ""), nil default: @@ -824,14 +837,15 @@ func TestMultipartPublishCancelAbortsWithBoundedContext(t *testing.T) { ctx, "packs/test", source, multipartPublishOptions{maxBytes: 5}, ) - require.ErrorIs(t, err, context.Canceled) - assert.Equal(t, 1, source.reads) - assert.Zero(t, uploads) - assert.Zero(t, completes) - assert.Equal(t, 1, aborts) + Require.ErrorIs(t, err, context.Canceled) + assert.Equal(1, source.reads) + assert.Zero(uploads) + assert.Zero(completes) + assert.Equal(1, aborts) } func TestMultipartPublishReadsOnlyOneByteBeyondLimit(t *testing.T) { + assert := Assert.New(t) source := &countingReadCloser{reader: strings.NewReader("0123456789abcdefghij")} var uploads, aborts int backend := newHTTPBackend(packstore.DefaultLimits(), func(request *http.Request) (*http.Response, error) { @@ -863,10 +877,10 @@ func TestMultipartPublishReadsOnlyOneByteBeyondLimit(t *testing.T) { multipartPublishOptions{maxBytes: 7}, ) - require.ErrorIs(t, err, packstore.ErrBlobTooLarge) - assert.Equal(t, int64(8), source.read) - assert.Equal(t, 1, uploads) - assert.Equal(t, 1, aborts) + Require.ErrorIs(t, err, packstore.ErrBlobTooLarge) + assert.Equal(int64(8), source.read) + assert.Equal(1, uploads) + assert.Equal(1, aborts) } type countingReadCloser struct { @@ -923,26 +937,26 @@ func makeEncodedPack( options = append(options, zstd.WithWindowSize(windowBytes)) } encoder, err := zstd.NewWriter(&frame, options...) - require.NoError(t, err) + Require.NoError(t, err) _, err = encoder.Write(content) - require.NoError(t, err) - require.NoError(t, encoder.Close()) + Require.NoError(t, err) + Require.NoError(t, encoder.Close()) staging := t.TempDir() writer, err := pack.NewWriter(staging, pack.WriterOptions{}) - require.NoError(t, err) + Require.NoError(t, err) _, err = writer.AppendEncoded( pack.ComputeBlobID(content), frame.Bytes(), rawLen, true, ) - require.NoError(t, err) + Require.NoError(t, err) packID := writer.ID() packPath := filepath.Join(staging, packID+".pack") _, err = writer.Seal(packPath) - require.NoError(t, err) + Require.NoError(t, err) packBytes, err := os.ReadFile(packPath) - require.NoError(t, err) + Require.NoError(t, err) return packID, packBytes } diff --git a/packstore/s3store/reader_test.go b/packstore/s3store/reader_test.go index 92a135a..479dc88 100644 --- a/packstore/s3store/reader_test.go +++ b/packstore/s3store/reader_test.go @@ -12,13 +12,15 @@ import ( "strconv" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" "go.kenn.io/kit/packstore" ) func TestDownloadPackRangesRejectsOversizedObjectBeforeGET(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) limits := packstore.DefaultLimits() limits.PackBytes = 10 var getRequests int @@ -43,12 +45,12 @@ func TestDownloadPackRangesRejectsOversizedObjectBeforeGET(t *testing.T) { "0123456789abcdef0123456789abcdef", ) - require.ErrorIs(t, err, packstore.ErrBlobTooLarge) - require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) + require.ErrorIs(err, packstore.ErrBlobTooLarge) + require.ErrorIs(err, packstore.ErrPhysicalCorrupt) var limit *packstore.LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, packstore.LimitPackContainerBytes, limit.Dimension) - assert.Zero(t, getRequests) + require.ErrorAs(err, &limit) + assert.Equal(packstore.LimitPackContainerBytes, limit.Dimension) + assert.Zero(getRequests) } func TestDownloadPackRangesPreservesCancellation(t *testing.T) { @@ -77,8 +79,8 @@ func TestDownloadPackRangesPreservesCancellation(t *testing.T) { "0123456789abcdef0123456789abcdef", ) - require.ErrorIs(t, err, terminal) - require.NotErrorIs(t, err, packstore.ErrPhysicalCorrupt) + Require.ErrorIs(t, err, terminal) + Require.NotErrorIs(t, err, packstore.ErrPhysicalCorrupt) }) } } @@ -88,7 +90,7 @@ func TestPackReaderOptionsUseConfiguredLimits(t *testing.T) { BlobBytes: 4096, PackBytes: 8192, FooterBytes: 2048, PackEntries: 32, }} - assert.Equal(t, pack.ReaderOptions{Limits: pack.ReaderLimits{ + Assert.Equal(t, pack.ReaderOptions{Limits: pack.ReaderLimits{ ContainerBytes: 8192, FooterBytes: 2048, Entries: 32, @@ -114,14 +116,15 @@ func TestOpenRequiresAttachedOwnership(t *testing.T) { StoredSize: int64(len(content)), }, ) - require.ErrorIs(t, err, packstore.ErrStoreFenced) + Require.ErrorIs(t, err, packstore.ErrStoreFenced) _, _, err = backend.OpenPack(context.Background(), entry.Hash, entry) - require.ErrorIs(t, err, packstore.ErrStoreFenced) - assert.Zero(t, requests) + Require.ErrorIs(t, err, packstore.ErrStoreFenced) + Assert.Zero(t, requests) } func TestOpenPackEnforcesConfiguredBlobLimit(t *testing.T) { + require := Require.New(t) content := []byte("blob exceeds configured S3 reader limit") _, packBytes, indexed := makePack(t, content) limits := packstore.DefaultLimits() @@ -154,14 +157,16 @@ func TestOpenPackEnforcesConfiguredBlobLimit(t *testing.T) { indexed, ) - require.ErrorIs(t, err, packstore.ErrBlobTooLarge) - require.NotErrorIs(t, err, packstore.ErrPhysicalCorrupt) + require.ErrorIs(err, packstore.ErrBlobTooLarge) + require.NotErrorIs(err, packstore.ErrPhysicalCorrupt) var limit *packstore.LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, packstore.LimitBlobRawBytes, limit.Dimension) + require.ErrorAs(err, &limit) + Assert.Equal(t, packstore.LimitBlobRawBytes, limit.Dimension) } func TestOpenPackIgnoresUnselectedEntryBlobLimit(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) selected := []byte("selected blob") unselected := bytes.Repeat([]byte("unrelated oversized footer entry"), 32) _, packBytes, entries := makePackEntries(t, selected, unselected) @@ -187,14 +192,16 @@ func TestOpenPackIgnoresUnselectedEntryBlobLimit(t *testing.T) { attachTestBackend(backend) stream, size, err := backend.OpenPack(context.Background(), entries[0].Hash, entries[0]) - require.NoError(t, err) + require.NoError(err) got, err := io.ReadAll(stream) - require.NoError(t, errors.Join(err, stream.Close())) - assert.Equal(t, selected, got) - assert.Equal(t, int64(len(selected)), size) + require.NoError(errors.Join(err, stream.Close())) + assert.Equal(selected, got) + assert.Equal(int64(len(selected)), size) } func TestS3DuplicatePackEntriesFallBackToHealthyCandidate(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := []byte("duplicate S3 footer fallback") _, packBytes, entries := makePackEntries(t, content, content) corrupt := newHTTPBackend(packstore.DefaultLimits(), func(request *http.Request) (*http.Response, error) { @@ -227,29 +234,31 @@ func TestS3DuplicatePackEntriesFallBackToHealthyCandidate(t *testing.T) { staticReadBackendRegistry{"corrupt": corrupt, "healthy": healthy}, packstore.MultiStoreOptions{}, ) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, store.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(store.Close()) }) stream, size, err := store.OpenStream(context.Background(), entries[0].Hash) - require.NoError(t, err) + require.NoError(err) got, err := io.ReadAll(stream) - require.NoError(t, errors.Join(err, stream.Close())) - assert.Equal(t, content, got) - assert.Equal(t, int64(len(content)), size) - assert.Equal(t, 1, healthy.opens) + require.NoError(errors.Join(err, stream.Close())) + assert.Equal(content, got) + assert.Equal(int64(len(content)), size) + assert.Equal(1, healthy.opens) _, _, err = corrupt.OpenPack(context.Background(), entries[0].Hash, entries[0]) - require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) - require.ErrorIs(t, err, pack.ErrCorrupt) + require.ErrorIs(err, packstore.ErrPhysicalCorrupt) + require.ErrorIs(err, pack.ErrCorrupt) } func TestOversizedS3ReplicaFallsBackToHealthyCandidate(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := []byte("healthy replica content") _, packBytes, indexed := makePack(t, content) limits := packstore.DefaultLimits() limits.PackBytes = int64(len(packBytes) - 1) oversized := newHTTPBackend(limits, func(request *http.Request) (*http.Response, error) { - require.Equal(t, http.MethodHead, request.Method) + require.Equal(http.MethodHead, request.Method) header := make(http.Header) header.Set("Content-Length", strconv.Itoa(len(packBytes))) return &http.Response{ @@ -274,17 +283,17 @@ func TestOversizedS3ReplicaFallsBackToHealthyCandidate(t *testing.T) { }, packstore.MultiStoreOptions{}, ) - require.NoError(t, err) + require.NoError(err) stream, size, err := store.OpenStream(context.Background(), indexed.Hash) - require.NoError(t, err) + require.NoError(err) got, err := io.ReadAll(stream) - require.NoError(t, err) - require.NoError(t, stream.Close()) + require.NoError(err) + require.NoError(stream.Close()) - assert.Equal(t, int64(len(content)), size) - assert.Equal(t, content, got) - assert.Equal(t, 1, healthy.opens) + assert.Equal(int64(len(content)), size) + assert.Equal(content, got) + assert.Equal(1, healthy.opens) } func TestS3PackRepresentationLimitsFallBackToHealthyCandidate(t *testing.T) { @@ -315,6 +324,8 @@ func TestS3PackRepresentationLimitsFallBackToHealthyCandidate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) limited := newHTTPBackend(tt.limit(packstore.DefaultLimits()), func(request *http.Request) (*http.Response, error) { header := make(http.Header) header.Set("Content-Length", strconv.Itoa(len(packBytes))) @@ -348,22 +359,22 @@ func TestS3PackRepresentationLimitsFallBackToHealthyCandidate(t *testing.T) { staticReadBackendRegistry{"limited": limited, "healthy": healthy}, packstore.MultiStoreOptions{}, ) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, store.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(store.Close()) }) stream, size, err := store.OpenStream(context.Background(), entries[0].Hash) - require.NoError(t, err) + require.NoError(err) got, err := io.ReadAll(stream) - require.NoError(t, errors.Join(err, stream.Close())) - assert.Equal(t, content, got) - assert.Equal(t, int64(len(content)), size) + require.NoError(errors.Join(err, stream.Close())) + assert.Equal(content, got) + assert.Equal(int64(len(content)), size) _, _, err = limited.OpenPack(context.Background(), entries[0].Hash, entries[0]) - require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) - require.ErrorIs(t, err, packstore.ErrBlobTooLarge) + require.ErrorIs(err, packstore.ErrPhysicalCorrupt) + require.ErrorIs(err, packstore.ErrBlobTooLarge) var limit *packstore.LimitError - require.ErrorAs(t, err, &limit) - assert.Equal(t, tt.dimension, limit.Dimension) + require.ErrorAs(err, &limit) + assert.Equal(tt.dimension, limit.Dimension) }) } } @@ -373,8 +384,8 @@ func TestPackBodyClassifiesTerminalReadCorruption(t *testing.T) { _, err := io.ReadAll(body) - require.ErrorIs(t, err, pack.ErrCorrupt) - require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) + Require.ErrorIs(t, err, pack.ErrCorrupt) + Require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) } func TestPackBodyClassifiesTerminalVerifyCorruption(t *testing.T) { @@ -382,8 +393,8 @@ func TestPackBodyClassifiesTerminalVerifyCorruption(t *testing.T) { err := body.Verify() - require.ErrorIs(t, err, pack.ErrCorrupt) - require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) + Require.ErrorIs(t, err, pack.ErrCorrupt) + Require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) } func TestPackBodyDoesNotClassifyIncompleteCloseAsCorrupt(t *testing.T) { @@ -391,8 +402,8 @@ func TestPackBodyDoesNotClassifyIncompleteCloseAsCorrupt(t *testing.T) { err := body.Close() - require.ErrorIs(t, err, pack.ErrVerificationIncomplete) - require.NotErrorIs(t, err, packstore.ErrPhysicalCorrupt) + Require.ErrorIs(t, err, pack.ErrVerificationIncomplete) + Require.NotErrorIs(t, err, packstore.ErrPhysicalCorrupt) } func TestPackBodyPreservesVerifiedEOF(t *testing.T) { @@ -400,12 +411,14 @@ func TestPackBodyPreservesVerifiedEOF(t *testing.T) { got, err := io.ReadAll(body) - require.NoError(t, err) - assert.Equal(t, []byte("terminal S3 pack integrity"), got) - assert.True(t, body.Verified()) + Require.NoError(t, err) + Assert.Equal(t, []byte("terminal S3 pack integrity"), got) + Assert.True(t, body.Verified()) } func TestS3TerminalCorruptionDemotesGeneration(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) body, indexed := newPackBody(t, true) primary := packstore.ReadLocation{ StoreID: "primary", Generation: "primary-1", Pack: &indexed, @@ -416,13 +429,13 @@ func TestS3TerminalCorruptionDemotesGeneration(t *testing.T) { health := packstore.NewHealth() err := body.Verify() - require.ErrorIs(t, err, packstore.ErrPhysicalCorrupt) + require.ErrorIs(err, packstore.ErrPhysicalCorrupt) health.Observe(indexed.Hash, primary, err) ordered := health.Order(indexed.Hash, []packstore.ReadLocation{primary, secondary}) - require.Len(t, ordered, 2) - assert.Equal(t, secondary, ordered[0]) - assert.Equal(t, primary, ordered[1]) + require.Len(ordered, 2) + assert.Equal(secondary, ordered[0]) + assert.Equal(primary, ordered[1]) } func newPackBody( @@ -436,11 +449,11 @@ func newPackBody( packBytes[indexed.Offset] ^= 0xff } path := filepath.Join(t.TempDir(), packID+".pack") - require.NoError(t, os.WriteFile(path, packBytes, 0o600)) + Require.NoError(t, os.WriteFile(path, packBytes, 0o600)) reader, err := pack.OpenReader(path, nil) - require.NoError(t, err) + Require.NoError(t, err) blob, err := reader.OpenBlob(context.Background(), reader.Entries()[0]) - require.NoError(t, err) + Require.NoError(t, err) t.Cleanup(func() { _ = blob.Close() _ = reader.Close() diff --git a/packstore/seekable_temp_windows_test.go b/packstore/seekable_temp_windows_test.go index e84aa91..01d8305 100644 --- a/packstore/seekable_temp_windows_test.go +++ b/packstore/seekable_temp_windows_test.go @@ -9,12 +9,14 @@ import ( "testing" "unsafe" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "golang.org/x/sys/windows" ) func TestStoreOpenWindowsTemporaryRejectsWritersAndPreservesReplacement(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("verified seekable Windows content "), 1024) layout := layoutForStoreTest(t) hash := hashForTest(content) @@ -24,14 +26,14 @@ func TestStoreOpenWindowsTemporaryRejectsWritersAndPreservesReplacement(t *testi }}, layout) reader, _, err := store.Open(context.Background(), hash) - require.NoError(t, err) + require.NoError(err) named, ok := reader.(interface{ Name() string }) - require.True(t, ok) + require.True(ok) temporaryPath := named.Name() t.Cleanup(func() { _ = os.Remove(temporaryPath) }) name, err := windows.UTF16PtrFromString(temporaryPath) - require.NoError(t, err) + require.NoError(err) readerHandle, openErr := windows.CreateFile( name, windows.GENERIC_READ, @@ -42,9 +44,9 @@ func TestStoreOpenWindowsTemporaryRejectsWritersAndPreservesReplacement(t *testi 0, ) if openErr == nil { - require.NoError(t, windows.CloseHandle(readerHandle)) + require.NoError(windows.CloseHandle(readerHandle)) } - require.ErrorIs(t, openErr, windows.ERROR_SHARING_VIOLATION) + require.ErrorIs(openErr, windows.ERROR_SHARING_VIOLATION) writer, openErr := windows.CreateFile( name, @@ -56,23 +58,23 @@ func TestStoreOpenWindowsTemporaryRejectsWritersAndPreservesReplacement(t *testi 0, ) if openErr == nil { - require.NoError(t, windows.CloseHandle(writer)) + require.NoError(windows.CloseHandle(writer)) } - require.ErrorIs(t, openErr, windows.ERROR_SHARING_VIOLATION) + require.ErrorIs(openErr, windows.ERROR_SHARING_VIOLATION) handleSource, ok := reader.(interface{ Fd() uintptr }) - require.True(t, ok) + require.True(ok) assertWindowsSeekableTempDACL(t, windows.Handle(handleSource.Fd())) displaced := temporaryPath + ".displaced" - require.NoError(t, os.Rename(temporaryPath, displaced)) + require.NoError(os.Rename(temporaryPath, displaced)) t.Cleanup(func() { _ = os.Remove(displaced) }) replacement := []byte("unrelated replacement remains") - require.NoError(t, os.WriteFile(temporaryPath, replacement, 0o600)) + require.NoError(os.WriteFile(temporaryPath, replacement, 0o600)) - require.NoError(t, reader.Close()) + require.NoError(reader.Close()) - assert.NoFileExists(t, displaced, "closing the reader deletes only its renamed temporary file") - assert.Equal(t, replacement, mustReadFile(t, temporaryPath)) + assert.NoFileExists(displaced, "closing the reader deletes only its renamed temporary file") + assert.Equal(replacement, mustReadFile(t, temporaryPath)) } func assertWindowsSeekableTempDACL(t *testing.T, handle windows.Handle) { @@ -82,27 +84,27 @@ func assertWindowsSeekableTempDACL(t *testing.T, handle windows.Handle) { windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION, ) - require.NoError(t, err) + Require.NoError(t, err) control, _, err := descriptor.Control() - require.NoError(t, err) - assert.NotZero(t, control&windows.SE_DACL_PROTECTED) + Require.NoError(t, err) + Assert.NotZero(t, control&windows.SE_DACL_PROTECTED) dacl, _, err := descriptor.DACL() - require.NoError(t, err) - require.NotNil(t, dacl) + Require.NoError(t, err) + Require.NotNil(t, dacl) user, err := windows.GetCurrentProcessToken().GetTokenUser() - require.NoError(t, err) + Require.NoError(t, err) system, err := windows.CreateWellKnownSid(windows.WinLocalSystemSid) - require.NoError(t, err) + Require.NoError(t, err) admins, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid) - require.NoError(t, err) + Require.NoError(t, err) allowed := []*windows.SID{user.User.Sid, system, admins} - require.Positive(t, dacl.AceCount) + Require.Positive(t, dacl.AceCount) for index := uint16(0); index < dacl.AceCount; index++ { var ace *windows.ACCESS_ALLOWED_ACE - require.NoError(t, windows.GetAce(dacl, uint32(index), &ace)) - require.Equal(t, uint8(windows.ACCESS_ALLOWED_ACE_TYPE), ace.Header.AceType) + Require.NoError(t, windows.GetAce(dacl, uint32(index), &ace)) + Require.Equal(t, uint8(windows.ACCESS_ALLOWED_ACE_TYPE), ace.Header.AceType) sid := (*windows.SID)(unsafe.Pointer(&ace.SidStart)) - assert.Condition(t, func() bool { + Assert.Condition(t, func() bool { for _, trusted := range allowed { if sid.Equals(trusted) { return true diff --git a/packstore/store.go b/packstore/store.go index 468ee6d..cb9221d 100644 --- a/packstore/store.go +++ b/packstore/store.go @@ -491,6 +491,9 @@ func (s *Store) openLooseObjectAt( if err != nil { return nil, markPhysicalSourceNotFound(err) } + if info == nil { + return nil, errors.Join(fmt.Errorf("packstore: compressed loose object has no file identity"), f.Close()) + } header := make([]byte, compressedLooseHeaderSize) if _, readErr := io.ReadFull(f, header); readErr != nil { return nil, errors.Join( @@ -514,6 +517,9 @@ func (s *Store) openLooseObjectAt( if err != nil { return nil, markPhysicalSourceNotFound(err) } + if info == nil { + return nil, errors.Join(fmt.Errorf("packstore: raw loose object has no file identity"), f.Close()) + } return &looseObject{ file: f, encoding: LooseEncodingRaw, logicalSize: info.Size(), storedSize: info.Size(), @@ -532,6 +538,9 @@ func openLooseFile(path string) (*os.File, fs.FileInfo, error) { if err != nil { return nil, nil, errors.Join(err, f.Close()) } + if info == nil { + return nil, nil, errors.Join(fmt.Errorf("packstore: %s has no file identity", path), f.Close()) + } if err := validateRegularNoFollow(path, info); err != nil { return nil, nil, errors.Join(err, f.Close()) } diff --git a/packstore/store_test.go b/packstore/store_test.go index 014583e..e2bc7f7 100644 --- a/packstore/store_test.go +++ b/packstore/store_test.go @@ -14,14 +14,14 @@ import ( "sync" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) func TestStoreReadsOnlyCatalogMembersFromLooseAndPackedStorage(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) loose := []byte("loose bytes") looseHash := hashForTest(loose) @@ -88,14 +88,16 @@ func TestNewStorePreservesSingleFilesystemFailureShape(t *testing.T) { for _, tt := range reads { t.Run(tt.name, func(t *testing.T) { err := tt.read() - require.ErrorIs(t, err, fs.ErrNotExist) + Require.ErrorIs(t, err, fs.ErrNotExist) var exhausted *ExhaustedError - assert.NotErrorAs(t, err, &exhausted) + Assert.NotErrorAs(t, err, &exhausted) }) } } func TestNewStoreLargePackedOpenDoesNotRequireTemporaryStorage(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("large packed compatibility content\n"), 1<<16) layout := layoutForStoreTest(t) entry := buildStoreTestPack(t, layout, content) @@ -110,15 +112,17 @@ func TestNewStoreLargePackedOpenDoesNotRequireTemporaryStorage(t *testing.T) { t.Cleanup(func() { createSeekableLooseTemp = originalCreate }) reader, size, err := store.Open(context.Background(), entry.Hash) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, reader.Close()) }) - assert.Equal(t, int64(len(content)), size) + require.NoError(err) + t.Cleanup(func() { require.NoError(reader.Close()) }) + assert.Equal(int64(len(content)), size) actual, err := io.ReadAll(reader) - require.NoError(t, err) - assert.Equal(t, content, actual) + require.NoError(err) + assert.Equal(content, actual) } func TestStoreOpenReadsAndSeeksCompressedLooseContent(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("seekable compressed content "), 1024) layout := layoutForStoreTest(t) hash := hashForTest(content) @@ -128,36 +132,37 @@ func TestStoreOpenReadsAndSeeksCompressedLooseContent(t *testing.T) { }}, layout) reader, size, err := store.Open(context.Background(), hash) - require.NoError(t, err) - assert.Equal(t, int64(len(content)), size) + require.NoError(err) + assert.Equal(int64(len(content)), size) named, ok := reader.(interface{ Name() string }) - require.True(t, ok, "compressed compatibility reader must expose its private temporary path") + require.True(ok, "compressed compatibility reader must expose its private temporary path") temporaryPath := named.Name() if runtime.GOOS == "windows" { - assert.FileExists(t, temporaryPath) + assert.FileExists(temporaryPath) } else { - assert.NoFileExists(t, temporaryPath, "Unix compatibility temps are unlinked before exposure") + assert.NoFileExists(temporaryPath, "Unix compatibility temps are unlinked before exposure") } statter, ok := reader.(interface{ Stat() (fs.FileInfo, error) }) - require.True(t, ok) + require.True(ok) temporaryInfo, err := statter.Stat() - require.NoError(t, err) + require.NoError(err) if runtime.GOOS != "windows" { - assert.Equal(t, fs.FileMode(0o600), temporaryInfo.Mode().Perm()) + assert.Equal(fs.FileMode(0o600), temporaryInfo.Mode().Perm()) } offset, err := reader.Seek(9, io.SeekStart) - require.NoError(t, err) - assert.Equal(t, int64(9), offset) + require.NoError(err) + assert.Equal(int64(9), offset) got, err := io.ReadAll(reader) - require.NoError(t, err) - assert.Equal(t, content[9:], got) - require.NoError(t, reader.Close()) - assert.NoFileExists(t, temporaryPath) - require.NoError(t, reader.Close()) + require.NoError(err) + assert.Equal(content[9:], got) + require.NoError(reader.Close()) + assert.NoFileExists(temporaryPath) + require.NoError(reader.Close()) } func TestStoreOpenClosePreservesTemporaryPathReplacement(t *testing.T) { + require := Require.New(t) content := bytes.Repeat([]byte("seekable replacement-safe content "), 1024) layout := layoutForStoreTest(t) hash := hashForTest(content) @@ -167,19 +172,21 @@ func TestStoreOpenClosePreservesTemporaryPathReplacement(t *testing.T) { }}, layout) reader, _, err := store.Open(context.Background(), hash) - require.NoError(t, err) + require.NoError(err) named, ok := reader.(interface{ Name() string }) - require.True(t, ok) + require.True(ok) temporaryPath := named.Name() replacement := []byte("unrelated temporary path replacement") replaceSeekableTemporaryPath(t, temporaryPath, replacement) - require.NoError(t, reader.Close()) + require.NoError(reader.Close()) - assert.Equal(t, replacement, mustReadFile(t, temporaryPath)) + Assert.Equal(t, replacement, mustReadFile(t, temporaryPath)) } func TestStoreOpenRejectsCorruptCompressedLooseAndCleansTemporaryFile(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("verify before seekable exposure "), 1024) layout := layoutForStoreTest(t) hash := hashForTest(content) @@ -191,18 +198,19 @@ func TestStoreOpenRejectsCorruptCompressedLooseAndCleansTemporaryFile(t *testing }}, layout) pattern := filepath.Join(os.TempDir(), "packstore-loose-open-*") before, err := filepath.Glob(pattern) - require.NoError(t, err) + require.NoError(err) reader, size, err := store.Open(context.Background(), hash) - require.ErrorIs(t, err, ErrContentMismatch) - assert.Nil(t, reader) - assert.Zero(t, size) + require.ErrorIs(err, ErrContentMismatch) + assert.Nil(reader) + assert.Zero(size) after, globErr := filepath.Glob(pattern) - require.NoError(t, globErr) - assert.ElementsMatch(t, before, after, "failed compatibility opens must remove private temporary files") + require.NoError(globErr) + assert.ElementsMatch(before, after, "failed compatibility opens must remove private temporary files") } func TestStoreOpenTemporaryWriteFailureDoesNotDrainCompressedSource(t *testing.T) { + assert := Assert.New(t) content := bytes.Repeat([]byte("do not drain after temporary write failure\n"), 4096) layout := layoutForStoreTest(t) hash := hashForTest(content) @@ -238,10 +246,10 @@ func TestStoreOpenTemporaryWriteFailureDoesNotDrainCompressedSource(t *testing.T t.Cleanup(func() { copySeekableLoose = originalCopy }) reader, _, err := store.Open(context.Background(), hash) - require.ErrorIs(t, err, writeErr) - assert.Nil(t, reader) - assert.LessOrEqual(t, decodedBytes, int64(looseCopyBufferBytes)) - assert.NoFileExists(t, temporaryPath) + Require.ErrorIs(t, err, writeErr) + assert.Nil(reader) + assert.LessOrEqual(decodedBytes, int64(looseCopyBufferBytes)) + assert.NoFileExists(temporaryPath) } func TestStoreOpenFailurePreservesTemporaryPathReplacement(t *testing.T) { @@ -274,26 +282,27 @@ func TestStoreOpenFailurePreservesTemporaryPathReplacement(t *testing.T) { reader, _, err := store.Open(context.Background(), hash) - require.ErrorIs(t, err, writeErr) - assert.Nil(t, reader) - assert.Equal(t, replacement, mustReadFile(t, temporaryPath)) + Require.ErrorIs(t, err, writeErr) + Assert.Nil(t, reader) + Assert.Equal(t, replacement, mustReadFile(t, temporaryPath)) } func replaceSeekableTemporaryPath(t *testing.T, path string, replacement []byte) { t.Helper() if runtime.GOOS == "windows" { displaced := path + ".displaced" - require.NoError(t, os.Rename(path, displaced)) + Require.NoError(t, os.Rename(path, displaced)) t.Cleanup(func() { _ = os.Remove(displaced) }) } else { removeErr := os.Remove(path) - require.True(t, removeErr == nil || errors.Is(removeErr, fs.ErrNotExist), removeErr) + Require.True(t, removeErr == nil || errors.Is(removeErr, fs.ErrNotExist), removeErr) } - require.NoError(t, os.WriteFile(path, replacement, 0o600)) + Require.NoError(t, os.WriteFile(path, replacement, 0o600)) t.Cleanup(func() { _ = os.Remove(path) }) } func TestStoreOpenDoesNotRetryTemporaryNotExist(t *testing.T) { + assert := Assert.New(t) content := []byte("temporary creation failure is not migration") layout := layoutForStoreTest(t) hash := hashForTest(content) @@ -311,10 +320,10 @@ func TestStoreOpenDoesNotRetryTemporaryNotExist(t *testing.T) { t.Cleanup(func() { createSeekableLooseTemp = originalCreate }) reader, size, err := store.Open(context.Background(), hash) - require.ErrorIs(t, err, stagingErr) - assert.Nil(t, reader) - assert.Zero(t, size) - assert.Equal(t, 1, resolver.calls) + Require.ErrorIs(t, err, stagingErr) + assert.Nil(reader) + assert.Zero(size) + assert.Equal(1, resolver.calls) } type countingLooseZstdReader struct { @@ -329,6 +338,8 @@ func (r *countingLooseZstdReader) Read(p []byte) (int, error) { } func TestReadBoundedCompressedLooseParityAndHeaderPreflight(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("bounded compressed content "), 1024) layout := layoutForStoreTest(t) hash := hashForTest(content) @@ -338,47 +349,51 @@ func TestReadBoundedCompressedLooseParityAndHeaderPreflight(t *testing.T) { }}, layout) got, size, err := store.ReadBounded(context.Background(), hash, int64(len(content))) - require.NoError(t, err) - assert.Equal(t, content, got) - assert.Equal(t, int64(len(content)), size) + require.NoError(err) + assert.Equal(content, got) + assert.Equal(int64(len(content)), size) _, _, err = store.ReadBounded(context.Background(), hash, int64(len(content)-1)) var limitErr *LimitError - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, LimitBlobRawBytes, limitErr.Dimension) - assert.Equal(t, uint64(len(content)), limitErr.Actual) + require.ErrorAs(err, &limitErr) + assert.Equal(LimitBlobRawBytes, limitErr.Dimension) + assert.Equal(uint64(len(content)), limitErr.Actual) } func TestReadBoundedPreflightsCompressedHeaderBeforeDecode(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := []byte("preflight identity") layout := layoutForStoreTest(t) hash := hashForTest(content) path := layout.CompressedLoosePath(hash) - require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + require.NoError(os.MkdirAll(filepath.Dir(path), 0o700)) header := encodeCompressedLooseHeader(1024) - require.NoError(t, os.WriteFile(path, append(header[:], []byte("not zstd")...), 0o600)) + require.NoError(os.WriteFile(path, append(header[:], []byte("not zstd")...), 0o600)) store := newStoreForTest(t, &mapResolver{locations: map[Hash]Location{ hash: {Member: true}, }}, layout) data, size, err := store.ReadBounded(context.Background(), hash, 16) var limitErr *LimitError - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, LimitBlobRawBytes, limitErr.Dimension) - assert.Equal(t, uint64(1024), limitErr.Actual) - assert.Nil(t, data) - assert.Zero(t, size) + require.ErrorAs(err, &limitErr) + assert.Equal(LimitBlobRawBytes, limitErr.Dimension) + assert.Equal(uint64(1024), limitErr.Actual) + assert.Nil(data) + assert.Zero(size) } func TestReadBoundedPreflightsCompressedStoredSizeBeforeDecode(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := []byte("small logical content") layout := layoutForStoreTest(t) hash := hashForTest(content) path := layout.CompressedLoosePath(hash) - require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + require.NoError(os.MkdirAll(filepath.Dir(path), 0o700)) header := encodeCompressedLooseHeader(uint64(len(content))) physical := append(header[:], bytes.Repeat([]byte("oversized stored payload"), 4)...) - require.NoError(t, os.WriteFile(path, physical, 0o600)) + require.NoError(os.WriteFile(path, physical, 0o600)) store := newStoreForTest(t, &mapResolver{locations: map[Hash]Location{ hash: {Member: true}, }}, layout) @@ -394,46 +409,48 @@ func TestReadBoundedPreflightsCompressedStoredSizeBeforeDecode(t *testing.T) { data, size, err := store.ReadBounded(context.Background(), hash, limit) var limitErr *LimitError - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, LimitBlobStoredBytes, limitErr.Dimension) - assert.Equal(t, uint64(len(physical)), limitErr.Actual) - assert.Equal(t, uint64(limit), limitErr.Limit) - assert.Zero(t, decoderCalls) - assert.Nil(t, data) - assert.Zero(t, size) + require.ErrorAs(err, &limitErr) + assert.Equal(LimitBlobStoredBytes, limitErr.Dimension) + assert.Equal(uint64(len(physical)), limitErr.Actual) + assert.Equal(uint64(limit), limitErr.Limit) + assert.Zero(decoderCalls) + assert.Nil(data) + assert.Zero(size) } func TestReadBoundedPreflightsPlatformIntBeforeAllocation(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := []byte("platform allocation preflight") layout := layoutForStoreTest(t) hash := hashForTest(content) path := layout.CompressedLoosePath(hash) - require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + require.NoError(os.MkdirAll(filepath.Dir(path), 0o700)) header := encodeCompressedLooseHeader(math.MaxInt64) - require.NoError(t, os.WriteFile(path, append(header[:], []byte("not zstd")...), 0o600)) + require.NoError(os.WriteFile(path, append(header[:], []byte("not zstd")...), 0o600)) limits := DefaultLimits() limits.BlobBytes = math.MaxInt64 store, err := NewStore(&mapResolver{locations: map[Hash]Location{hash: {Member: true}}}, layout, StoreOptions{ Limits: limits, }) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, store.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(store.Close()) }) originalMax := maxPlatformInt maxPlatformInt = 1024 t.Cleanup(func() { maxPlatformInt = originalMax }) data, size, err := store.ReadBounded(context.Background(), hash, math.MaxInt64) var limitErr *LimitError - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, LimitBlobRawBytes, limitErr.Dimension) - assert.Equal(t, uint64(math.MaxInt64), limitErr.Actual) - assert.Equal(t, uint64(1024), limitErr.Limit) - assert.Nil(t, data) - assert.Zero(t, size) + require.ErrorAs(err, &limitErr) + assert.Equal(LimitBlobRawBytes, limitErr.Dimension) + assert.Equal(uint64(math.MaxInt64), limitErr.Actual) + assert.Equal(uint64(1024), limitErr.Limit) + assert.Nil(data) + assert.Zero(size) } func TestReadBoundedRejectsCorruptLooseContent(t *testing.T) { - require := require.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := []byte("expected loose bytes") hash := hashForTest(content) @@ -455,17 +472,17 @@ func TestReadBoundedRejectsCorruptLooseContent(t *testing.T) { func TestStoreConstructorsRejectZeroLayout(t *testing.T) { t.Run("store", func(t *testing.T) { _, err := NewStore(&mapResolver{}, Layout{}, StoreOptions{}) - require.ErrorContains(t, err, "invalid empty layout") + Require.ErrorContains(t, err, "invalid empty layout") }) t.Run("maintainer", func(t *testing.T) { _, err := NewMaintainer(newMaintenanceCatalog(), Layout{}, MaintainerOptions{}) - require.ErrorContains(t, err, "invalid empty layout") + Require.ErrorContains(t, err, "invalid empty layout") }) } func TestStoreRetriesLooseToPackAndPackToLooseRacesOnce(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) content := []byte("migration race") hash := hashForTest(content) @@ -501,11 +518,11 @@ func TestStoreRejectsForgedPackIndexMetadata(t *testing.T) { }}, layout) _, _, err := store.Open(context.Background(), entry.Hash) - require.ErrorContains(t, err, "metadata mismatch") + Require.ErrorContains(t, err, "metadata mismatch") } func TestStoreSharesBoundedAndOrdinaryCacheSlotsAndEvicts(t *testing.T) { - require := require.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) resolver := &mapResolver{locations: map[Hash]Location{}} store := newStoreForTest(t, resolver, layout) @@ -521,14 +538,14 @@ func TestStoreSharesBoundedAndOrdinaryCacheSlotsAndEvicts(t *testing.T) { require.NoError(err) require.LessOrEqual(len(store.packReaders), maxOpenReaders) } - assert.Len(t, store.order, maxOpenReaders) + Assert.Len(t, store.order, maxOpenReaders) require.NoError(store.Close()) - assert.Empty(t, store.order) + Assert.Empty(t, store.order) } func TestStoreReaderModeConversionPreservesOneCacheSlot(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) layout := layoutForStoreTest(t) entry := buildStoreTestPack(t, layout, []byte("one logical cache slot")) store := newStoreForTest(t, &mapResolver{locations: map[Hash]Location{ @@ -551,7 +568,7 @@ func TestStoreReaderModeConversionPreservesOneCacheSlot(t *testing.T) { } func TestStoreConcurrentOrdinaryAndBoundedReads(t *testing.T) { - require := require.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) content := bytes.Repeat([]byte("concurrent packed read"), 4096) entry := buildStoreTestPack(t, layout, content) @@ -612,15 +629,15 @@ func (r *sequenceResolver) Resolve(_ context.Context, _ Hash) (Location, error) func layoutForStoreTest(t *testing.T) Layout { t.Helper() layout, err := NewLayout(t.TempDir(), LayoutOptions{Staging: StagingStoreDirectory, StagingDir: "tmp"}) - require.NoError(t, err) + Require.NoError(t, err) return layout } func newStoreForTest(t *testing.T, resolver Resolver, layout Layout) *Store { t.Helper() store, err := NewStore(resolver, layout, StoreOptions{}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, store.Close()) }) + Require.NoError(t, err) + t.Cleanup(func() { Require.NoError(t, store.Close()) }) return store } @@ -628,17 +645,17 @@ func buildStoreTestPack(t *testing.T, layout Layout, content []byte) IndexEntry t.Helper() staging := t.TempDir() w, err := pack.NewWriter(staging, pack.WriterOptions{}) - require.NoError(t, err) + Require.NoError(t, err) _, err = w.Append(content) - require.NoError(t, err) + Require.NoError(t, err) packID := w.ID() - require.NoError(t, os.MkdirAll(filepath.Dir(layout.PackPath(packID)), 0o700)) + Require.NoError(t, os.MkdirAll(filepath.Dir(layout.PackPath(packID)), 0o700)) entries, err := w.Seal(layout.PackPath(packID)) - require.NoError(t, err) - require.Len(t, entries, 1) + Require.NoError(t, err) + Require.Len(t, entries, 1) entry := entries[0] hash, err := ParseHash(entry.ID.String()) - require.NoError(t, err) + Require.NoError(t, err) return IndexEntry{ Hash: hash, PackID: packID, Offset: int64(entry.Offset), StoredLen: int64(entry.StoredLen), RawLen: int64(entry.RawLen), @@ -649,9 +666,9 @@ func buildStoreTestPack(t *testing.T, layout Layout, content []byte) IndexEntry func readStoreTest(t *testing.T, store *Store, hash Hash) ([]byte, int64) { t.Helper() r, size, err := store.Open(context.Background(), hash) - require.NoError(t, err) + Require.NoError(t, err) data, err := io.ReadAll(r) - require.NoError(t, err) - require.NoError(t, r.Close()) + Require.NoError(t, err) + Require.NoError(t, r.Close()) return data, size } diff --git a/packstore/stream_test.go b/packstore/stream_test.go index 93dbb2d..bfa8907 100644 --- a/packstore/stream_test.go +++ b/packstore/stream_test.go @@ -14,8 +14,8 @@ import ( "testing" "github.com/klauspost/compress/zstd" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) @@ -23,72 +23,77 @@ func TestStoreOpenStreamLoosePackedParity(t *testing.T) { content := bytes.Repeat([]byte("stream parity "), 1<<14) for _, representation := range []string{"loose", "compressed", "packed"} { t.Run(representation, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store, hash := streamStoreForTest(t, representation, content) stream, size, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) - assert.Equal(t, int64(len(content)), size) + require.NoError(err) + assert.Equal(int64(len(content)), size) prefix := make([]byte, 17) _, err = io.ReadFull(stream, prefix) - require.NoError(t, err) - assert.False(t, stream.Verified()) + require.NoError(err) + assert.False(stream.Verified()) rest, err := io.ReadAll(stream) - require.NoError(t, err) - assert.Equal(t, content, append(prefix, rest...)) - assert.True(t, stream.Verified()) - require.NoError(t, stream.Verify()) - require.NoError(t, stream.Close()) - require.NoError(t, stream.Close()) + require.NoError(err) + assert.Equal(content, append(prefix, rest...)) + assert.True(stream.Verified()) + require.NoError(stream.Verify()) + require.NoError(stream.Close()) + require.NoError(stream.Close()) }) } } func TestStoreStreamsLooseObjectAboveMaintenanceLimit(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("oversized loose content "), 16) layout := layoutForStoreTest(t) hash := hashForTest(content) - require.NoError(t, os.MkdirAll(filepath.Dir(layout.LoosePath(hash)), 0o700)) - require.NoError(t, os.WriteFile(layout.LoosePath(hash), content, 0o600)) + require.NoError(os.MkdirAll(filepath.Dir(layout.LoosePath(hash)), 0o700)) + require.NoError(os.WriteFile(layout.LoosePath(hash), content, 0o600)) limits := DefaultLimits() limits.BlobBytes = int64(len(content) - 1) store, err := NewStore(&mapResolver{locations: map[Hash]Location{ hash: {Member: true}, }}, layout, StoreOptions{Limits: limits}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, store.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(store.Close()) }) stream, size, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) - assert.Equal(t, int64(len(content)), size) + require.NoError(err) + assert.Equal(int64(len(content)), size) got, err := io.ReadAll(stream) - require.NoError(t, err) - assert.Equal(t, content, got) - require.NoError(t, stream.Close()) + require.NoError(err) + assert.Equal(content, got) + require.NoError(stream.Close()) var copied bytes.Buffer written, err := store.CopyVerified(context.Background(), hash, &copied) - require.NoError(t, err) - assert.Equal(t, int64(len(content)), written) - assert.Equal(t, content, copied.Bytes()) + require.NoError(err) + assert.Equal(int64(len(content)), written) + assert.Equal(content, copied.Bytes()) _, _, err = store.ReadBounded(context.Background(), hash, limits.BlobBytes) var limitErr *LimitError - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, LimitBlobRawBytes, limitErr.Dimension) + require.ErrorAs(err, &limitErr) + assert.Equal(LimitBlobRawBytes, limitErr.Dimension) } func TestStoreOpenStreamEarlyCloseLoosePackedParity(t *testing.T) { content := []byte("early close content") for _, representation := range []string{"loose", "compressed", "packed"} { t.Run(representation, func(t *testing.T) { + require := Require.New(t) store, hash := streamStoreForTest(t, representation, content) stream, _, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) + require.NoError(err) buf := make([]byte, 2) _, err = stream.Read(buf) - require.NoError(t, err) - require.ErrorIs(t, stream.Close(), pack.ErrVerificationIncomplete) - require.ErrorIs(t, stream.Close(), pack.ErrVerificationIncomplete) - assert.False(t, stream.Verified()) + require.NoError(err) + require.ErrorIs(stream.Close(), pack.ErrVerificationIncomplete) + require.ErrorIs(stream.Close(), pack.ErrVerificationIncomplete) + Assert.False(t, stream.Verified()) assertPackedLeases(t, store, 0) }) } @@ -119,20 +124,21 @@ func TestStoreRejectsUnknownPackFlags(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + require := Require.New(t) layout := layoutForStoreTest(t) entry := buildStoreTestPack(t, layout, []byte("unknown pack flags")) f, err := os.OpenFile(layout.PackPath(entry.PackID), os.O_WRONLY, 0) - require.NoError(t, err) + require.NoError(err) _, err = f.WriteAt([]byte{0x80}, 5) - require.NoError(t, err) - require.NoError(t, f.Close()) + require.NoError(err) + require.NoError(f.Close()) store := newStoreForTest(t, &mapResolver{locations: map[Hash]Location{ entry.Hash: {Member: true, Pack: &entry}, }}, layout) err = tt.read(store, entry.Hash) - require.ErrorIs(t, err, pack.ErrCorrupt) - require.ErrorContains(t, err, "unknown pack flags 0x80") + require.ErrorIs(err, pack.ErrCorrupt) + require.ErrorContains(err, "unknown pack flags 0x80") }) } } @@ -162,23 +168,24 @@ func TestStoreRejectsUnknownFlagsOnUnselectedEntry(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + require := Require.New(t) layout := layoutForStoreTest(t) staging := t.TempDir() writer, err := pack.NewWriter(staging, pack.WriterOptions{}) - require.NoError(t, err) + require.NoError(err) selected, err := writer.Append([]byte("selected entry")) - require.NoError(t, err) + require.NoError(err) _, err = writer.Append([]byte("unselected entry")) - require.NoError(t, err) + require.NoError(err) packID := writer.ID() path := layout.PackPath(packID) - require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + require.NoError(os.MkdirAll(filepath.Dir(path), 0o700)) _, err = writer.Seal(path) - require.NoError(t, err) + require.NoError(err) mutateImportFooterEntry(t, path, 1, func(entry []byte) { entry[56] |= 0x80 }) hash, err := ParseHash(selected.ID.String()) - require.NoError(t, err) + require.NoError(err) indexed := IndexEntry{ Hash: hash, PackID: packID, Offset: int64(selected.Offset), StoredLen: int64(selected.StoredLen), RawLen: int64(selected.RawLen), @@ -189,8 +196,8 @@ func TestStoreRejectsUnknownFlagsOnUnselectedEntry(t *testing.T) { }}, layout) err = tt.read(store, hash) - require.ErrorIs(t, err, pack.ErrCorrupt) - require.ErrorContains(t, err, "entry 1 has unknown flags 0x80") + require.ErrorIs(err, pack.ErrCorrupt) + require.ErrorContains(err, "entry 1 has unknown flags 0x80") }) } } @@ -202,9 +209,9 @@ func TestStoreCopyVerifiedLoosePackedParity(t *testing.T) { store, hash := streamStoreForTest(t, representation, content) var dst bytes.Buffer written, err := store.CopyVerified(context.Background(), hash, &dst) - require.NoError(t, err) - assert.Equal(t, int64(len(content)), written) - assert.Equal(t, content, dst.Bytes()) + Require.NoError(t, err) + Assert.Equal(t, int64(len(content)), written) + Assert.Equal(t, content, dst.Bytes()) assertPackedLeases(t, store, 0) }) } @@ -216,8 +223,8 @@ func TestStoreCopyVerifiedDestinationFailureReleasesSource(t *testing.T) { destinationErr := errors.New("destination failed") dst := &failAfterWriter{remaining: 32, err: destinationErr} written, err := store.CopyVerified(context.Background(), hash, dst) - require.ErrorIs(t, err, destinationErr) - assert.Equal(t, int64(32), written) + Require.ErrorIs(t, err, destinationErr) + Assert.Equal(t, int64(32), written) assertPackedLeases(t, store, 0) } @@ -250,22 +257,24 @@ func TestStoreOpenStreamTerminalIntegrityErrors(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) hash := hashForTest(content) var entry IndexEntry if tt.representation == "loose" { - require.NoError(t, os.MkdirAll(filepath.Dir(layout.LoosePath(hash)), 0o700)) + require.NoError(os.MkdirAll(filepath.Dir(layout.LoosePath(hash)), 0o700)) corrupt := append([]byte(nil), content...) corrupt[0] ^= 0xff - require.NoError(t, os.WriteFile(layout.LoosePath(hash), corrupt, 0o600)) + require.NoError(os.WriteFile(layout.LoosePath(hash), corrupt, 0o600)) } else { entry = buildStoreTestPack(t, layout, content) hash = entry.Hash f, err := os.OpenFile(layout.PackPath(entry.PackID), os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) _, err = f.WriteAt([]byte{'X'}, entry.Offset) - require.NoError(t, err) - require.NoError(t, f.Close()) + require.NoError(err) + require.NoError(f.Close()) } location := Location{Member: true} if tt.representation == "packed" { @@ -273,35 +282,38 @@ func TestStoreOpenStreamTerminalIntegrityErrors(t *testing.T) { } store := newStoreForTest(t, &mapResolver{locations: map[Hash]Location{hash: location}}, layout) stream, _, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) + require.NoError(err) got, err := io.ReadAll(stream) - require.ErrorIs(t, err, tt.want) - assert.Len(t, got, len(content)) - assert.False(t, stream.Verified()) - require.ErrorIs(t, stream.Verify(), tt.want) - require.ErrorIs(t, stream.Close(), tt.want) + require.ErrorIs(err, tt.want) + assert.Len(got, len(content)) + assert.False(stream.Verified()) + require.ErrorIs(stream.Verify(), tt.want) + require.ErrorIs(stream.Close(), tt.want) assertPackedLeases(t, store, 0) }) } } func TestStoreOpenStreamCancellationReleasesPackedLease(t *testing.T) { + require := Require.New(t) content := bytes.Repeat([]byte("cancel packed stream "), 4096) store, hash := streamStoreForTest(t, "packed", content) ctx, cancel := context.WithCancel(context.Background()) stream, _, err := store.OpenStream(ctx, hash) - require.NoError(t, err) + require.NoError(err) buf := make([]byte, 32) _, err = stream.Read(buf) - require.NoError(t, err) + require.NoError(err) cancel() _, err = stream.Read(buf) - require.ErrorIs(t, err, context.Canceled) + require.ErrorIs(err, context.Canceled) assertPackedLeases(t, store, 0) - require.ErrorIs(t, stream.Close(), context.Canceled) + require.ErrorIs(stream.Close(), context.Canceled) } func TestStoreOpenStreamCancellationClosesCompressedLoose(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("cancel compressed stream "), 4096) store, hash := streamStoreForTest(t, "compressed", content) originalReader := newLooseZstdReader @@ -316,19 +328,19 @@ func TestStoreOpenStreamCancellationClosesCompressedLoose(t *testing.T) { t.Cleanup(func() { newLooseZstdReader = originalReader }) ctx, cancel := context.WithCancel(context.Background()) stream, _, err := store.OpenStream(ctx, hash) - require.NoError(t, err) + require.NoError(err) physical := stream.(*looseVerifiedStream).object.file buf := make([]byte, 32) _, err = stream.Read(buf) - require.NoError(t, err) + require.NoError(err) cancel() _, err = stream.Read(buf) - require.ErrorIs(t, err, context.Canceled) - require.ErrorIs(t, stream.Close(), context.Canceled) - assert.False(t, stream.Verified()) - assert.Equal(t, 1, closeCalls) + require.ErrorIs(err, context.Canceled) + require.ErrorIs(stream.Close(), context.Canceled) + assert.False(stream.Verified()) + assert.Equal(1, closeCalls) _, err = physical.Read(make([]byte, 1)) - require.ErrorIs(t, err, os.ErrClosed) + require.ErrorIs(err, os.ErrClosed) } func TestStoreOpenStreamChecksCancellationBetweenCompressedPayloadReads(t *testing.T) { @@ -344,12 +356,12 @@ func TestStoreOpenStreamChecksCancellationBetweenCompressedPayloadReads(t *testi } t.Cleanup(func() { newLooseZstdReader = originalReader }) stream, _, err := store.OpenStream(ctx, hash) - require.NoError(t, err) + Require.NoError(t, err) _, err = stream.Read(make([]byte, 1)) - require.ErrorIs(t, err, context.Canceled) - require.ErrorIs(t, stream.Close(), context.Canceled) + Require.ErrorIs(t, err, context.Canceled) + Require.ErrorIs(t, stream.Close(), context.Canceled) } var errCompressedSourceCancellationMissed = errors.New("compressed source missed cancellation") @@ -387,8 +399,8 @@ func TestStoreOpenStreamRejectsCompressedLooseIntegrityFailures(t *testing.T) { content := bytes.Repeat([]byte("compressed integrity "), 1024) var emptyFrame bytes.Buffer emptyEncoder, err := zstd.NewWriter(&emptyFrame, zstd.WithEncoderConcurrency(1)) - require.NoError(t, err) - require.NoError(t, emptyEncoder.Close()) + Require.NoError(t, err) + Require.NoError(t, emptyEncoder.Close()) emptySkippableFrame := []byte{0x50, 0x2a, 0x4d, 0x18, 0, 0, 0, 0} tests := []struct { name string @@ -446,6 +458,8 @@ func TestStoreOpenStreamRejectsCompressedLooseIntegrityFailures(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) layout := layoutForStoreTest(t) hash := hashForTest(content) writeCompressedLooseFixture(t, layout, hash, tt.logicalSize, tt.decoded, tt.mutate) @@ -454,17 +468,19 @@ func TestStoreOpenStreamRejectsCompressedLooseIntegrityFailures(t *testing.T) { }}, layout) stream, size, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) - assert.Equal(t, tt.logicalSize, size) + require.NoError(err) + assert.Equal(tt.logicalSize, size) err = stream.Verify() - require.ErrorIs(t, err, ErrContentMismatch) - require.ErrorIs(t, stream.Close(), ErrContentMismatch) - assert.False(t, stream.Verified()) + require.ErrorIs(err, ErrContentMismatch) + require.ErrorIs(stream.Close(), ErrContentMismatch) + assert.False(stream.Verified()) }) } } func TestStoreOpenStreamRejectsCompressedLooseGrowthAfterOpen(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("compressed growth after open "), 1024) layout := layoutForStoreTest(t) hash := hashForTest(content) @@ -473,22 +489,23 @@ func TestStoreOpenStreamRejectsCompressedLooseGrowthAfterOpen(t *testing.T) { hash: {Member: true}, }}, layout) stream, size, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) - assert.Equal(t, int64(len(content)), size) + require.NoError(err) + assert.Equal(int64(len(content)), size) appendFile, err := os.OpenFile(layout.CompressedLoosePath(hash), os.O_APPEND|os.O_WRONLY, 0) - require.NoError(t, err) + require.NoError(err) _, err = appendFile.Write([]byte("trailing physical mutation")) - require.NoError(t, err) - require.NoError(t, appendFile.Close()) + require.NoError(err) + require.NoError(appendFile.Close()) err = stream.Verify() - require.ErrorIs(t, err, ErrContentMismatch) - require.ErrorIs(t, stream.Close(), ErrContentMismatch) - assert.False(t, stream.Verified()) + require.ErrorIs(err, ErrContentMismatch) + require.ErrorIs(stream.Close(), ErrContentMismatch) + assert.False(stream.Verified()) } func TestStoreOpenStreamRejectsSkippableFrameAfterSingleSegmentRawBlock(t *testing.T) { + require := Require.New(t) content := []byte("12345678") frame := []byte{ 0x28, 0xb5, 0x2f, 0xfd, // zstd magic @@ -508,10 +525,10 @@ func TestStoreOpenStreamRejectsSkippableFrameAfterSingleSegmentRawBlock(t *testi }}, layout) stream, size, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) - assert.Equal(t, int64(len(content)), size) - require.ErrorIs(t, stream.Verify(), ErrContentMismatch) - require.ErrorIs(t, stream.Close(), ErrContentMismatch) + require.NoError(err) + Assert.Equal(t, int64(len(content)), size) + require.ErrorIs(stream.Verify(), ErrContentMismatch) + require.ErrorIs(stream.Close(), ErrContentMismatch) } func TestSingleZstdFrameReaderLeavesConcatenatedFrameUnread(t *testing.T) { @@ -531,12 +548,12 @@ func TestSingleZstdFrameReaderLeavesConcatenatedFrameUnread(t *testing.T) { encode := func(t *testing.T, opts ...zstd.EOption) []byte { t.Helper() encoder, err := zstd.NewWriter(nil, opts...) - require.NoError(t, err) + Require.NoError(t, err) frame := encoder.EncodeAll(bytes.Repeat([]byte("compressible block content "), 128), nil) - require.NoError(t, encoder.Close()) + Require.NoError(t, encoder.Close()) var header zstd.Header - require.NoError(t, header.Decode(frame)) - require.True(t, header.FirstBlock.Compressed) + Require.NoError(t, header.Decode(frame)) + Require.True(t, header.FirstBlock.Compressed) return frame } tests := []struct { @@ -561,9 +578,9 @@ func TestSingleZstdFrameReaderLeavesConcatenatedFrameUnread(t *testing.T) { source := &io.LimitedReader{R: bytes.NewReader(physical), N: int64(len(physical))} got, err := io.ReadAll(newSingleZstdFrameReader(source)) - require.NoError(t, err) - assert.Equal(t, tt.frame, got) - assert.Equal(t, int64(len(skippable)), source.N) + Require.NoError(t, err) + Assert.Equal(t, tt.frame, got) + Assert.Equal(t, int64(len(skippable)), source.N) }) } } @@ -587,44 +604,46 @@ func TestSingleZstdFrameReaderLeavesTrailingFrameAfterMaximalHeader(t *testing.T got, err := io.ReadAll(newSingleZstdFrameReader(source)) - require.NoError(t, err) - assert.Equal(t, frame, got) - assert.Equal(t, int64(len(skippable)), source.N) + Require.NoError(t, err) + Assert.Equal(t, frame, got) + Assert.Equal(t, int64(len(skippable)), source.N) } func TestStoreOpenStreamRejectsMalformedCompressedLooseHeader(t *testing.T) { + require := Require.New(t) content := []byte("malformed compressed header") layout := layoutForStoreTest(t) hash := hashForTest(content) path := layout.CompressedLoosePath(hash) - require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) - require.NoError(t, os.WriteFile(path, []byte("short header"), 0o600)) - require.NoError(t, os.WriteFile(layout.LoosePath(hash), content, 0o600)) + require.NoError(os.MkdirAll(filepath.Dir(path), 0o700)) + require.NoError(os.WriteFile(path, []byte("short header"), 0o600)) + require.NoError(os.WriteFile(layout.LoosePath(hash), content, 0o600)) store := newStoreForTest(t, &mapResolver{locations: map[Hash]Location{ hash: {Member: true}, }}, layout) stream, _, err := store.OpenStream(context.Background(), hash) - require.ErrorIs(t, err, ErrContentMismatch) - assert.Nil(t, stream) + require.ErrorIs(err, ErrContentMismatch) + Assert.Nil(t, stream) } func TestStoreOpenStreamPrefersCompressedLooseWithoutCorruptFallback(t *testing.T) { + require := Require.New(t) content := []byte("preferred compressed loose content") layout := layoutForStoreTest(t) hash := hashForTest(content) rawPath := layout.LoosePath(hash) - require.NoError(t, os.MkdirAll(filepath.Dir(rawPath), 0o700)) - require.NoError(t, os.WriteFile(rawPath, content, 0o600)) + require.NoError(os.MkdirAll(filepath.Dir(rawPath), 0o700)) + require.NoError(os.WriteFile(rawPath, content, 0o600)) writeCompressedLooseFixture(t, layout, hash, int64(len(content)), bytes.Repeat([]byte{'x'}, len(content)), nil) store := newStoreForTest(t, &mapResolver{locations: map[Hash]Location{ hash: {Member: true}, }}, layout) stream, _, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) - require.ErrorIs(t, stream.Verify(), ErrContentMismatch) - require.ErrorIs(t, stream.Close(), ErrContentMismatch) + require.NoError(err) + require.ErrorIs(stream.Verify(), ErrContentMismatch) + require.ErrorIs(stream.Close(), ErrContentMismatch) } func TestStoreOpenStreamRetriesAuthorityMoves(t *testing.T) { @@ -635,55 +654,57 @@ func TestStoreOpenStreamRetriesAuthorityMoves(t *testing.T) { layout := layoutForStoreTest(t) entry := buildStoreTestPack(t, layout, content) loosePath := layout.LoosePath(hash) - require.NoError(t, os.MkdirAll(filepath.Dir(loosePath), 0o700)) - require.NoError(t, os.WriteFile(loosePath, content, 0o600)) + Require.NoError(t, os.MkdirAll(filepath.Dir(loosePath), 0o700)) + Require.NoError(t, os.WriteFile(loosePath, content, 0o600)) resolver := &sequenceResolver{locations: []Location{{Member: true}, {Member: true, Pack: &entry}}} - resolver.beforeFirstReturn = func() { require.NoError(t, os.Remove(loosePath)) } + resolver.beforeFirstReturn = func() { Require.NoError(t, os.Remove(loosePath)) } store := newStoreForTest(t, resolver, layout) assertStreamContent(t, store, hash, content) - assert.Equal(t, 2, resolver.calls) + Assert.Equal(t, 2, resolver.calls) }) t.Run("pack to loose", func(t *testing.T) { + require := Require.New(t) layout := layoutForStoreTest(t) entry := buildStoreTestPack(t, layout, content) loosePath := layout.LoosePath(hash) - require.NoError(t, os.MkdirAll(filepath.Dir(loosePath), 0o700)) - require.NoError(t, os.WriteFile(loosePath, content, 0o600)) - require.NoError(t, os.Remove(layout.PackPath(entry.PackID))) + require.NoError(os.MkdirAll(filepath.Dir(loosePath), 0o700)) + require.NoError(os.WriteFile(loosePath, content, 0o600)) + require.NoError(os.Remove(layout.PackPath(entry.PackID))) resolver := &sequenceResolver{locations: []Location{{Member: true, Pack: &entry}, {Member: true}}} store := newStoreForTest(t, resolver, layout) assertStreamContent(t, store, hash, content) - assert.Equal(t, 2, resolver.calls) + Assert.Equal(t, 2, resolver.calls) }) t.Run("pack to pack", func(t *testing.T) { layout := layoutForStoreTest(t) first := buildStoreTestPack(t, layout, content) second := buildStoreTestPack(t, layout, content) - require.NotEqual(t, first.PackID, second.PackID) + Require.NotEqual(t, first.PackID, second.PackID) resolver := &sequenceResolver{locations: []Location{{Member: true, Pack: &first}, {Member: true, Pack: &second}}} - resolver.beforeFirstReturn = func() { require.NoError(t, os.Remove(layout.PackPath(first.PackID))) } + resolver.beforeFirstReturn = func() { Require.NoError(t, os.Remove(layout.PackPath(first.PackID))) } store := newStoreForTest(t, resolver, layout) assertStreamContent(t, store, hash, content) - assert.Equal(t, 2, resolver.calls) + Assert.Equal(t, 2, resolver.calls) }) } func TestStoreConcurrentPackedStreamsShareLeasedReader(t *testing.T) { + require := Require.New(t) content := bytes.Repeat([]byte("shared packed stream "), 1<<14) store, hash := streamStoreForTest(t, "packed", content) const streams = 16 readers := make([]VerifiedReadCloser, streams) for i := range readers { reader, _, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) + require.NoError(err) readers[i] = reader } store.mu.Lock() - require.Len(t, store.packReaders, 1) + require.Len(store.packReaders, 1) for _, slot := range store.packReaders { - assert.Equal(t, streams, slot.leases) + Assert.Equal(t, streams, slot.leases) } store.mu.Unlock() @@ -695,12 +716,13 @@ func TestStoreConcurrentPackedStreamsShareLeasedReader(t *testing.T) { wg.Wait() close(errs) for err := range errs { - require.NoError(t, err) + require.NoError(err) } assertPackedLeases(t, store, 0) } func TestStoreEvictionAndClosePreserveActiveStreams(t *testing.T) { + require := Require.New(t) layout := layoutForStoreTest(t) firstContent := bytes.Repeat([]byte("first stream "), 4096) secondContent := bytes.Repeat([]byte("second stream "), 4096) @@ -710,56 +732,60 @@ func TestStoreEvictionAndClosePreserveActiveStreams(t *testing.T) { first.Hash: {Member: true, Pack: &first}, second.Hash: {Member: true, Pack: &second}, }} store, err := NewStore(resolver, layout, StoreOptions{Limits: DefaultLimits(), ReaderSlots: 1}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, store.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(store.Close()) }) firstStream, _, err := store.OpenStream(context.Background(), first.Hash) - require.NoError(t, err) + require.NoError(err) secondStream, _, err := store.OpenStream(context.Background(), second.Hash) - require.NoError(t, err) - require.Len(t, store.packReaders, 1) - require.NoError(t, secondStream.Verify()) - require.NoError(t, store.Close()) - assert.Empty(t, store.packReaders) - require.NoError(t, firstStream.Verify()) - require.NoError(t, firstStream.Close()) + require.NoError(err) + require.Len(store.packReaders, 1) + require.NoError(secondStream.Verify()) + require.NoError(store.Close()) + Assert.Empty(t, store.packReaders) + require.NoError(firstStream.Verify()) + require.NoError(firstStream.Close()) } func TestStoreRetirePackKeepsActiveStreamReadable(t *testing.T) { + require := Require.New(t) content := bytes.Repeat([]byte("retired active stream "), 4096) store, hash := streamStoreForTest(t, "packed", content) location := store.resolver.(*mapResolver).locations[hash] - require.NotNil(t, location.Pack) + require.NotNil(location.Pack) stream, _, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) - require.NoError(t, store.RetirePack(location.Pack.PackID)) + require.NoError(err) + require.NoError(store.RetirePack(location.Pack.PackID)) _, err = os.Stat(store.layout.PackPath(location.Pack.PackID)) - require.ErrorIs(t, err, fs.ErrNotExist) - require.NoError(t, stream.Verify()) - require.NoError(t, stream.Close()) + require.ErrorIs(err, fs.ErrNotExist) + require.NoError(stream.Verify()) + require.NoError(stream.Close()) } func TestStoreRetirePackErrorsAreTyped(t *testing.T) { + require := Require.New(t) content := []byte("typed retirement") store, hash := streamStoreForTest(t, "packed", content) location := store.resolver.(*mapResolver).locations[hash] path := store.layout.PackPath(location.Pack.PackID) stream, _, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) + require.NoError(err) orphan := path + ".open" - require.NoError(t, os.Rename(path, orphan)) - require.NoError(t, os.Mkdir(path, 0o700)) - require.NoError(t, os.WriteFile(filepath.Join(path, "child"), []byte("x"), 0o600)) + require.NoError(os.Rename(path, orphan)) + require.NoError(os.Mkdir(path, 0o700)) + require.NoError(os.WriteFile(filepath.Join(path, "child"), []byte("x"), 0o600)) err = store.RetirePack(location.Pack.PackID) - require.ErrorIs(t, err, ErrPackRetirementDeferred) + require.ErrorIs(err, ErrPackRetirementDeferred) var retireErr *PackRetirementError - require.ErrorAs(t, err, &retireErr) - assert.Equal(t, location.Pack.PackID, retireErr.PackID) - require.NoError(t, stream.Verify()) - require.NoError(t, stream.Close()) + require.ErrorAs(err, &retireErr) + Assert.Equal(t, location.Pack.PackID, retireErr.PackID) + require.NoError(stream.Verify()) + require.NoError(stream.Close()) } func TestStoreOpenStreamPreservesBufferedContractAndAppliesPolicy(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := []byte("container policy") layout := layoutForStoreTest(t) entry := buildStoreTestPack(t, layout, content) @@ -768,82 +794,85 @@ func TestStoreOpenStreamPreservesBufferedContractAndAppliesPolicy(t *testing.T) store, err := NewStore(&mapResolver{locations: map[Hash]Location{ entry.Hash: {Member: true, Pack: &entry}, }}, layout, StoreOptions{Limits: limits}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, store.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(store.Close()) }) buffered, _, err := store.Open(context.Background(), entry.Hash) - require.NoError(t, err) - require.NoError(t, buffered.Close()) + require.NoError(err) + require.NoError(buffered.Close()) _, _, err = store.OpenStream(context.Background(), entry.Hash) var limitErr *LimitError - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, LimitPackContainerBytes, limitErr.Dimension) + require.ErrorAs(err, &limitErr) + assert.Equal(LimitPackContainerBytes, limitErr.Dimension) strictStore, err := NewStore(&mapResolver{locations: map[Hash]Location{ entry.Hash: {Member: true, Pack: &entry}, }}, layout, StoreOptions{Limits: limits}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, strictStore.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(strictStore.Close()) }) _, _, err = strictStore.OpenStream(context.Background(), entry.Hash) - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, LimitPackContainerBytes, limitErr.Dimension) + require.ErrorAs(err, &limitErr) + assert.Equal(LimitPackContainerBytes, limitErr.Dimension) } func TestStoreOpenStreamRejectsNonMemberBeforePhysicalRead(t *testing.T) { content := []byte("physical but unauthorized") layout := layoutForStoreTest(t) hash := hashForTest(content) - require.NoError(t, os.MkdirAll(filepath.Dir(layout.LoosePath(hash)), 0o700)) - require.NoError(t, os.WriteFile(layout.LoosePath(hash), content, 0o600)) + Require.NoError(t, os.MkdirAll(filepath.Dir(layout.LoosePath(hash)), 0o700)) + Require.NoError(t, os.WriteFile(layout.LoosePath(hash), content, 0o600)) store := newStoreForTest(t, &mapResolver{locations: map[Hash]Location{hash: {}}}, layout) _, _, err := store.OpenStream(context.Background(), hash) - require.ErrorIs(t, err, fs.ErrNotExist) + Require.ErrorIs(t, err, fs.ErrNotExist) } func TestStoreOpenWindowPolicyAppliesOnlyToStreaming(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) content := bytes.Repeat([]byte("window policy "), 1<<18) var frame bytes.Buffer encoder, err := zstd.NewWriter(&frame, zstd.WithWindowSize(8<<20), zstd.WithEncoderConcurrency(1)) - require.NoError(t, err) + require.NoError(err) _, err = encoder.Write(content) - require.NoError(t, err) - require.NoError(t, encoder.Close()) + require.NoError(err) + require.NoError(encoder.Close()) layout := layoutForStoreTest(t) staging := t.TempDir() w, err := pack.NewWriter(staging, pack.WriterOptions{}) - require.NoError(t, err) + require.NoError(err) id := pack.ComputeBlobID(content) entry, err := w.AppendEncoded(id, frame.Bytes(), uint64(len(content)), true) - require.NoError(t, err) + require.NoError(err) packID := w.ID() - require.NoError(t, os.MkdirAll(filepath.Dir(layout.PackPath(packID)), 0o700)) + require.NoError(os.MkdirAll(filepath.Dir(layout.PackPath(packID)), 0o700)) _, err = w.Seal(layout.PackPath(packID)) - require.NoError(t, err) + require.NoError(err) hash, err := ParseHash(id.String()) - require.NoError(t, err) + require.NoError(err) indexed := IndexEntry{Hash: hash, PackID: packID, Offset: int64(entry.Offset), StoredLen: int64(entry.StoredLen), RawLen: int64(entry.RawLen), Flags: uint8(entry.Flags), CRC32C: entry.CRC32C} limits := DefaultLimits() limits.BlobBytes = 4 << 20 store, err := NewStore(&mapResolver{locations: map[Hash]Location{ hash: {Member: true, Pack: &indexed}, }}, layout, StoreOptions{Limits: limits}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, store.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(store.Close()) }) buffered, size, err := store.Open(context.Background(), hash) - require.NoError(t, err) - assert.Equal(t, int64(len(content)), size) + require.NoError(err) + assert.Equal(int64(len(content)), size) actual, err := io.ReadAll(buffered) - require.NoError(t, err) - require.NoError(t, buffered.Close()) - assert.Equal(t, content, actual) + require.NoError(err) + require.NoError(buffered.Close()) + assert.Equal(content, actual) _, _, err = store.OpenStream(context.Background(), hash) var limitErr *LimitError - require.ErrorAs(t, err, &limitErr) - assert.Equal(t, LimitBlobWindowBytes, limitErr.Dimension) + require.ErrorAs(err, &limitErr) + assert.Equal(LimitBlobWindowBytes, limitErr.Dimension) } func TestStoreStreamsPackedObjectAboveDefaultCeiling(t *testing.T) { + require := Require.New(t) if testing.Short() { t.Skip("writes a blob above the default 64 MiB policy ceiling") } @@ -851,30 +880,30 @@ func TestStoreStreamsPackedObjectAboveDefaultCeiling(t *testing.T) { layout := layoutForStoreTest(t) staging := t.TempDir() w, err := pack.NewWriter(staging, pack.WriterOptions{}) - require.NoError(t, err) + require.NoError(err) entry, err := w.AppendStream(context.Background(), io.LimitReader(streamZeroReader{}, size), uint64(size), pack.AppendStreamOptions{ ScratchDir: staging, ScratchBytes: uint64(size)*2 + 64<<20, //nolint:gosec // helper requires positive size }) - require.NoError(t, err) + require.NoError(err) packID := w.ID() - require.NoError(t, os.MkdirAll(filepath.Dir(layout.PackPath(packID)), 0o700)) + require.NoError(os.MkdirAll(filepath.Dir(layout.PackPath(packID)), 0o700)) _, err = w.Seal(layout.PackPath(packID)) - require.NoError(t, err) + require.NoError(err) hash, err := ParseHash(entry.ID.String()) - require.NoError(t, err) + require.NoError(err) indexed := IndexEntry{Hash: hash, PackID: packID, Offset: int64(entry.Offset), StoredLen: int64(entry.StoredLen), RawLen: int64(entry.RawLen), Flags: uint8(entry.Flags), CRC32C: entry.CRC32C} limits := DefaultLimits() limits.BlobBytes = size store, err := NewStore(&mapResolver{locations: map[Hash]Location{ hash: {Member: true, Pack: &indexed}, }}, layout, StoreOptions{Limits: limits}) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, store.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(store.Close()) }) stream, gotSize, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) - assert.Equal(t, size, gotSize) - require.NoError(t, stream.Verify()) - require.NoError(t, stream.Close()) + require.NoError(err) + Assert.Equal(t, size, gotSize) + require.NoError(stream.Verify()) + require.NoError(stream.Close()) } func largeStoreStreamTestBytes(t *testing.T, fallback int64) int64 { @@ -884,8 +913,8 @@ func largeStoreStreamTestBytes(t *testing.T, fallback int64) int64 { return fallback } size, err := strconv.ParseInt(value, 10, 64) - require.NoError(t, err) - require.Positive(t, size) + Require.NoError(t, err) + Require.Positive(t, size) return size } @@ -903,8 +932,8 @@ func streamStoreForTest(t *testing.T, representation string, content []byte) (*S location := Location{Member: true} switch representation { case "loose": - require.NoError(t, os.MkdirAll(filepath.Dir(layout.LoosePath(hash)), 0o700)) - require.NoError(t, os.WriteFile(layout.LoosePath(hash), content, 0o600)) + Require.NoError(t, os.MkdirAll(filepath.Dir(layout.LoosePath(hash)), 0o700)) + Require.NoError(t, os.WriteFile(layout.LoosePath(hash), content, 0o600)) case "compressed": writeCompressedLooseFixture(t, layout, hash, int64(len(content)), content, nil) case "packed": @@ -912,7 +941,7 @@ func streamStoreForTest(t *testing.T, representation string, content []byte) (*S hash = entry.Hash location.Pack = &entry default: - require.FailNow(t, "unknown representation", representation) + Require.FailNow(t, "unknown representation", representation) } return newStoreForTest(t, &mapResolver{locations: map[Hash]Location{hash: location}}, layout), hash } @@ -926,34 +955,34 @@ func writeCompressedLooseFixture( mutate func([]byte) []byte, ) { t.Helper() - require.GreaterOrEqual(t, logicalSize, int64(0)) + Require.GreaterOrEqual(t, logicalSize, int64(0)) header := encodeCompressedLooseHeader(uint64(logicalSize)) var physical bytes.Buffer _, err := physical.Write(header[:]) - require.NoError(t, err) + Require.NoError(t, err) encoder, err := zstd.NewWriter(&physical, zstd.WithEncoderConcurrency(1)) - require.NoError(t, err) + Require.NoError(t, err) _, err = encoder.Write(decoded) - require.NoError(t, err) - require.NoError(t, encoder.Close()) + Require.NoError(t, err) + Require.NoError(t, encoder.Close()) data := physical.Bytes() if mutate != nil { data = mutate(bytes.Clone(data)) } path := layout.CompressedLoosePath(hash) - require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) - require.NoError(t, os.WriteFile(path, data, 0o600)) + Require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + Require.NoError(t, os.WriteFile(path, data, 0o600)) } func assertStreamContent(t *testing.T, store *Store, hash Hash, want []byte) { t.Helper() stream, size, err := store.OpenStream(context.Background(), hash) - require.NoError(t, err) + Require.NoError(t, err) got, err := io.ReadAll(stream) - require.NoError(t, err) - assert.Equal(t, int64(len(want)), size) - assert.Equal(t, want, got) - require.NoError(t, stream.Close()) + Require.NoError(t, err) + Assert.Equal(t, int64(len(want)), size) + Assert.Equal(t, want, got) + Require.NoError(t, stream.Close()) } func assertPackedLeases(t *testing.T, store *Store, want int) { @@ -961,6 +990,6 @@ func assertPackedLeases(t *testing.T, store *Store, want int) { store.mu.Lock() defer store.mu.Unlock() for _, slot := range store.packReaders { - assert.Equal(t, want, slot.leases) + Assert.Equal(t, want, slot.leases) } } diff --git a/packstore/stream_windows_test.go b/packstore/stream_windows_test.go index aa7810e..a310664 100644 --- a/packstore/stream_windows_test.go +++ b/packstore/stream_windows_test.go @@ -6,29 +6,30 @@ import ( "context" "testing" - "github.com/stretchr/testify/require" + Require "github.com/stretchr/testify/require" "golang.org/x/sys/windows" ) func TestRetirePackWindowsSharingViolationIsRetryable(t *testing.T) { + require := Require.New(t) layout := layoutForStoreTest(t) entry := buildStoreTestPack(t, layout, []byte("windows sharing retirement")) store := newStoreForTest(t, &mapResolver{locations: map[Hash]Location{ entry.Hash: {Member: true, Pack: &entry}, }}, layout) stream, _, err := store.OpenStream(context.Background(), entry.Hash) - require.NoError(t, err) - require.NoError(t, stream.Verify()) - require.NoError(t, stream.Close()) + require.NoError(err) + require.NoError(stream.Verify()) + require.NoError(stream.Close()) name, err := windows.UTF16PtrFromString(layout.PackPath(entry.PackID)) - require.NoError(t, err) + require.NoError(err) handle, err := windows.CreateFile(name, windows.GENERIC_READ, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0) - require.NoError(t, err) + require.NoError(err) err = store.RetirePack(entry.PackID) - require.ErrorIs(t, err, ErrPackRetirementDeferred) - require.NoError(t, windows.CloseHandle(handle)) - require.NoError(t, store.RetirePack(entry.PackID)) + require.ErrorIs(err, ErrPackRetirementDeferred) + require.NoError(windows.CloseHandle(handle)) + require.NoError(store.RetirePack(entry.PackID)) } diff --git a/prek.toml b/prek.toml index 0c4012b..b01060d 100644 --- a/prek.toml +++ b/prek.toml @@ -52,7 +52,7 @@ priority = 0 id = "golangci-lint" name = "golangci-lint" language = "system" -entry = "golangci-lint run" +entry = "make lint-golangci" files = "^(go\\.mod|go\\.sum|.*\\.go)$" pass_filenames = false require_serial = true @@ -72,7 +72,7 @@ priority = 15 id = "nilaway" name = "nilaway" language = "system" -entry = "go tool nilaway -include-pkgs=go.kenn.io/kit ./..." +entry = "make nilaway" stages = ["pre-push"] files = "^(go\\.mod|go\\.sum|.*\\.go)$" pass_filenames = false diff --git a/safefileio/private_dir_windows_test.go b/safefileio/private_dir_windows_test.go index 991713c..f20c4a2 100644 --- a/safefileio/private_dir_windows_test.go +++ b/safefileio/private_dir_windows_test.go @@ -7,53 +7,55 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "golang.org/x/sys/windows" ) func TestEnsurePrivateDirCreatesOwnedDirectory(t *testing.T) { + require := Require.New(t) dir := filepath.Join(t.TempDir(), "runtime") - require.NoError(t, EnsurePrivateDir(dir)) + require.NoError(EnsurePrivateDir(dir)) ownerSID, err := currentWindowsOwnerSID() - require.NoError(t, err) + require.NoError(err) descriptor, err := windows.GetNamedSecurityInfo( dir, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION, ) - require.NoError(t, err) + require.NoError(err) owner, _, err := descriptor.Owner() - require.NoError(t, err) - require.NotNil(t, owner) - require.True(t, owner.Equals(ownerSID)) + require.NoError(err) + require.NotNil(owner) + require.True(owner.Equals(ownerSID)) } func TestValidatePrivateDirAcceptsPrivateDir(t *testing.T) { dir := filepath.Join(t.TempDir(), "runtime") - require.NoError(t, EnsurePrivateDir(dir)) + Require.NoError(t, EnsurePrivateDir(dir)) - require.NoError(t, ValidatePrivateDir(dir)) + Require.NoError(t, ValidatePrivateDir(dir)) } func TestValidatePrivateDirRejectsBroadDACL(t *testing.T) { + require := Require.New(t) dir := filepath.Join(t.TempDir(), "runtime") - require.NoError(t, EnsurePrivateDir(dir)) + require.NoError(EnsurePrivateDir(dir)) handle, err := openWindowsDir(dir) - require.NoError(t, err) + require.NoError(err) defer func() { _ = windows.CloseHandle(handle) }() userSID, err := currentWindowsUserSID() - require.NoError(t, err) + require.NoError(err) world, err := windows.CreateWellKnownSid(windows.WinWorldSid) - require.NoError(t, err) + require.NoError(err) acl, err := windows.ACLFromEntries([]windows.EXPLICIT_ACCESS{ allowFullControl(userSID, windows.TRUSTEE_IS_USER), allowFullControl(world, windows.TRUSTEE_IS_WELL_KNOWN_GROUP), }, nil) - require.NoError(t, err) - require.NoError(t, windows.SetSecurityInfo( + require.NoError(err) + require.NoError(windows.SetSecurityInfo( handle, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, @@ -63,48 +65,49 @@ func TestValidatePrivateDirRejectsBroadDACL(t *testing.T) { nil, )) - require.Error(t, ValidatePrivateDir(dir)) + require.Error(ValidatePrivateDir(dir)) } func TestEnsurePrivateDirRejectsEmptyPath(t *testing.T) { - require.Error(t, EnsurePrivateDir("")) + Require.Error(t, EnsurePrivateDir("")) } func TestEnsurePrivateDirRejectsSymlink(t *testing.T) { base := t.TempDir() target := filepath.Join(base, "target") link := filepath.Join(base, "link") - require.NoError(t, os.Mkdir(target, 0o700)) + Require.NoError(t, os.Mkdir(target, 0o700)) if err := os.Symlink(target, link); err != nil { t.Skipf("symlink unavailable: %v", err) } - require.Error(t, EnsurePrivateDir(link)) + Require.Error(t, EnsurePrivateDir(link)) } func TestOpenCurrentUserFileRejectsEmptyPath(t *testing.T) { file, err := OpenCurrentUserFile("") - require.Error(t, err) - require.Nil(t, file) + Require.Error(t, err) + Require.Nil(t, file) } func TestOpenCurrentUserFileAcceptsCurrentTokenOwner(t *testing.T) { path := filepath.Join(t.TempDir(), "record.json") - require.NoError(t, os.WriteFile(path, []byte("{}"), 0o600)) + Require.NoError(t, os.WriteFile(path, []byte("{}"), 0o600)) file, err := OpenCurrentUserFile(path) - require.NoError(t, err) - require.NoError(t, file.Close()) + Require.NoError(t, err) + Require.NoError(t, file.Close()) } func TestValidatePrivateCurrentUserFileRejectsBroadDACL(t *testing.T) { + require := Require.New(t) path := filepath.Join(t.TempDir(), "record.json") - require.NoError(t, os.WriteFile(path, []byte("{}"), 0o600)) + require.NoError(os.WriteFile(path, []byte("{}"), 0o600)) file, err := os.OpenFile(path, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) defer func() { _ = file.Close() }() path16, err := windows.UTF16PtrFromString(path) - require.NoError(t, err) + require.NoError(err) handle, err := windows.CreateFile( path16, windows.READ_CONTROL|windows.WRITE_DAC, @@ -114,20 +117,20 @@ func TestValidatePrivateCurrentUserFileRejectsBroadDACL(t *testing.T) { windows.FILE_FLAG_OPEN_REPARSE_POINT, 0, ) - require.NoError(t, err) + require.NoError(err) defer func() { _ = windows.CloseHandle(handle) }() userSID, err := currentWindowsUserSID() - require.NoError(t, err) + require.NoError(err) ownerSID, err := currentWindowsOwnerSID() - require.NoError(t, err) + require.NoError(err) world, err := windows.CreateWellKnownSid(windows.WinWorldSid) - require.NoError(t, err) + require.NoError(err) acl, err := windows.ACLFromEntries([]windows.EXPLICIT_ACCESS{ allowFullControl(userSID, windows.TRUSTEE_IS_USER), allowFullControl(world, windows.TRUSTEE_IS_WELL_KNOWN_GROUP), }, nil) - require.NoError(t, err) - require.NoError(t, windows.SetSecurityInfo( + require.NoError(err) + require.NoError(windows.SetSecurityInfo( handle, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, @@ -136,22 +139,23 @@ func TestValidatePrivateCurrentUserFileRejectsBroadDACL(t *testing.T) { acl, nil, )) - require.Error(t, verifyWindowsDirDACL(path, handle, userSID, ownerSID)) + require.Error(verifyWindowsDirDACL(path, handle, userSID, ownerSID)) - require.Error(t, ValidatePrivateCurrentUserFile(file)) - require.Error(t, verifyWindowsDirDACL(path, handle, userSID, ownerSID)) + require.Error(ValidatePrivateCurrentUserFile(file)) + require.Error(verifyWindowsDirDACL(path, handle, userSID, ownerSID)) } func TestValidatePrivateCurrentUserFileRejectsUnprotectedPrivateDACL(t *testing.T) { + require := Require.New(t) dir := filepath.Join(t.TempDir(), "private") - require.NoError(t, EnsurePrivateDir(dir)) + require.NoError(EnsurePrivateDir(dir)) path := filepath.Join(dir, "record.json") - require.NoError(t, os.WriteFile(path, []byte("{}"), 0o600)) + require.NoError(os.WriteFile(path, []byte("{}"), 0o600)) file, err := os.OpenFile(path, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) defer func() { _ = file.Close() }() path16, err := windows.UTF16PtrFromString(path) - require.NoError(t, err) + require.NoError(err) handle, err := windows.CreateFile( path16, windows.READ_CONTROL|windows.WRITE_DAC, @@ -161,15 +165,15 @@ func TestValidatePrivateCurrentUserFileRejectsUnprotectedPrivateDACL(t *testing. windows.FILE_FLAG_OPEN_REPARSE_POINT, 0, ) - require.NoError(t, err) + require.NoError(err) defer func() { _ = windows.CloseHandle(handle) }() userSID, err := currentWindowsUserSID() - require.NoError(t, err) + require.NoError(err) acl, err := windows.ACLFromEntries([]windows.EXPLICIT_ACCESS{ allowFullControl(userSID, windows.TRUSTEE_IS_USER), }, nil) - require.NoError(t, err) - require.NoError(t, windows.SetSecurityInfo( + require.NoError(err) + require.NoError(windows.SetSecurityInfo( handle, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION|windows.UNPROTECTED_DACL_SECURITY_INFORMATION, @@ -179,19 +183,20 @@ func TestValidatePrivateCurrentUserFileRejectsUnprotectedPrivateDACL(t *testing. nil, )) - require.Error(t, ValidatePrivateCurrentUserFile(file)) + require.Error(ValidatePrivateCurrentUserFile(file)) } func TestValidatePrivateCurrentUserFileAcceptsProtectedPrivateDACL(t *testing.T) { + require := Require.New(t) dir := filepath.Join(t.TempDir(), "private") - require.NoError(t, EnsurePrivateDir(dir)) + require.NoError(EnsurePrivateDir(dir)) path := filepath.Join(dir, "record.json") - require.NoError(t, os.WriteFile(path, []byte("{}"), 0o600)) + require.NoError(os.WriteFile(path, []byte("{}"), 0o600)) file, err := os.OpenFile(path, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) defer func() { _ = file.Close() }() path16, err := windows.UTF16PtrFromString(path) - require.NoError(t, err) + require.NoError(err) handle, err := windows.CreateFile( path16, windows.READ_CONTROL|windows.WRITE_DAC, @@ -201,18 +206,18 @@ func TestValidatePrivateCurrentUserFileAcceptsProtectedPrivateDACL(t *testing.T) windows.FILE_FLAG_OPEN_REPARSE_POINT, 0, ) - require.NoError(t, err) + require.NoError(err) defer func() { _ = windows.CloseHandle(handle) }() userSID, err := currentWindowsUserSID() - require.NoError(t, err) - require.NoError(t, restrictWindowsDir(handle, userSID)) + require.NoError(err) + require.NoError(restrictWindowsDir(handle, userSID)) - require.NoError(t, ValidatePrivateCurrentUserFile(file)) + require.NoError(ValidatePrivateCurrentUserFile(file)) } func TestWindowsOwnerMatchesCurrentUserAndTokenOwner(t *testing.T) { - require := require.New(t) - assert := assert.New(t) + require := Require.New(t) + assert := Assert.New(t) userSID, err := windows.CreateWellKnownSid(windows.WinBuiltinUsersSid) require.NoError(err) ownerSID, err := windows.CreateWellKnownSid(windows.WinBuiltinGuestsSid) @@ -230,7 +235,7 @@ func TestWindowsOwnerMatchesCurrentUserAndTokenOwner(t *testing.T) { } func TestVerifyWindowsDirectoryOwner(t *testing.T) { - require := require.New(t) + require := Require.New(t) userSID, err := windows.CreateWellKnownSid(windows.WinBuiltinUsersSid) require.NoError(err) ownerSID, err := windows.CreateWellKnownSid(windows.WinBuiltinGuestsSid) @@ -265,7 +270,7 @@ func TestVerifyWindowsDirectoryOwner(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert := assert.New(t) + assert := Assert.New(t) err := verifyWindowsDirectoryOwner("runtime", tt.owner, userSID, ownerSID) if tt.wantError != "" { assert.EqualError(err, tt.wantError) @@ -277,16 +282,17 @@ func TestVerifyWindowsDirectoryOwner(t *testing.T) { } func TestCurrentUserIDIsPerUser(t *testing.T) { + require := Require.New(t) id, err := CurrentUserID() - require.NoError(t, err) - require.NotEmpty(t, id) - require.NotEqual(t, "user", id) - require.Contains(t, id, "sid-") + require.NoError(err) + require.NotEmpty(id) + require.NotEqual("user", id) + require.Contains(id, "sid-") } func TestCurrentWindowsOwnerSIDIsAvailable(t *testing.T) { ownerSID, err := currentWindowsOwnerSID() - require.NoError(t, err) - require.NotNil(t, ownerSID) - require.NotEmpty(t, ownerSID.String()) + Require.NoError(t, err) + Require.NotNil(t, ownerSID) + Require.NotEmpty(t, ownerSID.String()) } diff --git a/safefileio/private_file_darwin_test.go b/safefileio/private_file_darwin_test.go index 6afa90f..5233065 100644 --- a/safefileio/private_file_darwin_test.go +++ b/safefileio/private_file_darwin_test.go @@ -6,27 +6,28 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/safefileio" ) func TestValidatePrivateCurrentUserFileRejectsExtendedACL(t *testing.T) { + require := Require.New(t) path := filepath.Join(t.TempDir(), "record.json") - require.NoError(t, os.WriteFile(path, []byte("{}"), 0o600)) + require.NoError(os.WriteFile(path, []byte("{}"), 0o600)) output, err := exec.Command( "chmod", "+a", "everyone allow read", path, ).CombinedOutput() - require.NoError(t, err, string(output)) + require.NoError(err, string(output)) file, err := os.OpenFile(path, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) defer func() { _ = file.Close() }() - require.Error(t, safefileio.ValidatePrivateCurrentUserFile(file)) + require.Error(safefileio.ValidatePrivateCurrentUserFile(file)) listing, err := exec.Command("ls", "-lde", path).CombinedOutput() - require.NoError(t, err, string(listing)) - assert.Contains(t, string(listing), "everyone allow read") + require.NoError(err, string(listing)) + Assert.Contains(t, string(listing), "everyone allow read") } diff --git a/safefileio/private_file_supported_unix_external_test.go b/safefileio/private_file_supported_unix_external_test.go index afb417a..193a8ea 100644 --- a/safefileio/private_file_supported_unix_external_test.go +++ b/safefileio/private_file_supported_unix_external_test.go @@ -7,30 +7,31 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/require" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/safefileio" ) func TestValidatePrivateCurrentUserFileRejectsPublicMode(t *testing.T) { + require := Require.New(t) path := filepath.Join(t.TempDir(), "record.json") - require.NoError(t, os.WriteFile(path, []byte("{}"), 0o600)) - require.NoError(t, os.Chmod(path, 0o666)) + require.NoError(os.WriteFile(path, []byte("{}"), 0o600)) + require.NoError(os.Chmod(path, 0o666)) file, err := os.OpenFile(path, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) defer func() { _ = file.Close() }() - require.Error(t, safefileio.ValidatePrivateCurrentUserFile(file)) + require.Error(safefileio.ValidatePrivateCurrentUserFile(file)) info, err := file.Stat() - require.NoError(t, err) - require.Equal(t, os.FileMode(0o666), info.Mode().Perm()) + require.NoError(err) + require.Equal(os.FileMode(0o666), info.Mode().Perm()) } func TestValidatePrivateCurrentUserFileAcceptsPrivateMode(t *testing.T) { path := filepath.Join(t.TempDir(), "record.json") - require.NoError(t, os.WriteFile(path, []byte("{}"), 0o600)) + Require.NoError(t, os.WriteFile(path, []byte("{}"), 0o600)) file, err := os.OpenFile(path, os.O_RDWR, 0) - require.NoError(t, err) + Require.NoError(t, err) defer func() { _ = file.Close() }() - require.NoError(t, safefileio.ValidatePrivateCurrentUserFile(file)) + Require.NoError(t, safefileio.ValidatePrivateCurrentUserFile(file)) } diff --git a/safefileio/private_file_supported_unix_test.go b/safefileio/private_file_supported_unix_test.go index 8f542e0..a417e5a 100644 --- a/safefileio/private_file_supported_unix_test.go +++ b/safefileio/private_file_supported_unix_test.go @@ -7,16 +7,17 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/require" + Require "github.com/stretchr/testify/require" ) func TestVerifyPrivateFileModeRejectsPublicMode(t *testing.T) { + require := Require.New(t) path := filepath.Join(t.TempDir(), "record.json") - require.NoError(t, os.WriteFile(path, []byte("{}"), 0o600)) - require.NoError(t, os.Chmod(path, 0o666)) + require.NoError(os.WriteFile(path, []byte("{}"), 0o600)) + require.NoError(os.Chmod(path, 0o666)) file, err := os.OpenFile(path, os.O_RDWR, 0) - require.NoError(t, err) + require.NoError(err) defer func() { _ = file.Close() }() - require.ErrorContains(t, verifyPrivateFileMode(file), "mode is 0666, not 0600") + require.ErrorContains(verifyPrivateFileMode(file), "mode is 0666, not 0600") } diff --git a/tools/benchgate/main_test.go b/tools/benchgate/main_test.go index 627b2b3..ce443c0 100644 --- a/tools/benchgate/main_test.go +++ b/tools/benchgate/main_test.go @@ -7,16 +7,16 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "golang.org/x/perf/benchfmt" ) func parseString(t *testing.T, input string) benchmarkSamples { t.Helper() result, syntax, err := parseBench(benchfmt.NewReader(strings.NewReader(input), "test")) - require.NoError(t, err) - require.Empty(t, syntax) + Require.NoError(t, err) + Require.Empty(t, syntax) return result } @@ -24,9 +24,9 @@ func TestParseBenchSeparatesPackagesAndSamples(t *testing.T) { result := parseString(t, "pkg: example/a\nBenchmarkRead-8 5 1000000 ns/op 10000 B/op 10 allocs/op\n"+ "BenchmarkRead-8 5 1100000 ns/op 11000 B/op 11 allocs/op\n"+ "pkg: example/b\nBenchmarkRead-8 5 2000000 ns/op 20000 B/op 20 allocs/op\n") - require.Len(t, result, 2) - assert.InDeltaSlice(t, []float64{0.001, 0.0011}, result["example/a.Read-8"]["sec/op"], 1e-12) - assert.Equal(t, []float64{20}, result["example/b.Read-8"]["allocs/op"]) + Require.Len(t, result, 2) + Assert.InDeltaSlice(t, []float64{0.001, 0.0011}, result["example/a.Read-8"]["sec/op"], 1e-12) + Assert.Equal(t, []float64{20}, result["example/b.Read-8"]["allocs/op"]) } func TestCompareGatesDeterministicAndSignificantRegressions(t *testing.T) { @@ -39,44 +39,47 @@ func TestCompareGatesDeterministicAndSignificantRegressions(t *testing.T) { "B/op": {20_000}, "allocs/op": {20}, }} _, violations, issues := compare(old, next, defaultGates(2, 1.2, 1.25, 100_000, 8, 4096)) - assert.Empty(t, issues) - require.Len(t, violations, 3) - assert.ElementsMatch(t, []string{"allocs/op", "B/op", "sec/op"}, + Assert.Empty(t, issues) + Require.Len(t, violations, 3) + Assert.ElementsMatch(t, []string{"allocs/op", "B/op", "sec/op"}, []string{violations[0].unit, violations[1].unit, violations[2].unit}) } func TestCompareReportsBenchmarkSetChanges(t *testing.T) { + assert := Assert.New(t) old := benchmarkSamples{"Old-8": {"sec/op": {0.001}}} next := benchmarkSamples{"New-8": {"sec/op": {0.001}}} report, violations, issues := compare(old, next, defaultGates(2, 1.2, 1.25, 100_000, 8, 4096)) - assert.Empty(t, violations) - require.Len(t, issues, 1) - assert.Contains(t, issues[0], "Old-8: benchmark missing from candidate") - assert.Contains(t, strings.Join(report, "\n"), "new benchmark; no baseline") - assert.Contains(t, strings.Join(report, "\n"), "missing from candidate") + assert.Empty(violations) + Require.Len(t, issues, 1) + assert.Contains(issues[0], "Old-8: benchmark missing from candidate") + assert.Contains(strings.Join(report, "\n"), "new benchmark; no baseline") + assert.Contains(strings.Join(report, "\n"), "missing from candidate") } func TestRunRejectsRemovedBenchmark(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) dir := t.TempDir() baseline := filepath.Join(dir, "old.txt") candidate := filepath.Join(dir, "new.txt") line := "BenchmarkOld-8 5 1000000 ns/op 10000 B/op 10 allocs/op\n" - require.NoError(t, os.WriteFile(baseline, []byte(strings.Repeat(line, 5)), 0o600)) - require.NoError(t, os.WriteFile(candidate, []byte( + require.NoError(os.WriteFile(baseline, []byte(strings.Repeat(line, 5)), 0o600)) + require.NoError(os.WriteFile(candidate, []byte( strings.Repeat(strings.ReplaceAll(line, "Old", "New"), 5)), 0o600)) out, code := run(baseline, candidate, defaultGates(2, 1.2, 1.25, 100_000, 8, 4096)) - assert.Equal(t, 2, code) - assert.Contains(t, out, "Old-8: benchmark missing from candidate") + assert.Equal(2, code) + assert.Contains(out, "Old-8: benchmark missing from candidate") } func TestCompareMissingCandidateMetricIsConfigurationError(t *testing.T) { old := benchmarkSamples{"Read-8": {"B/op": {10_000}, "allocs/op": {10}}} next := benchmarkSamples{"Read-8": {"allocs/op": {10}}} _, violations, issues := compare(old, next, defaultGates(2, 1.2, 1.25, 100_000, 8, 4096)) - assert.Empty(t, violations) - require.Len(t, issues, 1) - assert.Contains(t, issues[0], "B/op missing from candidate") + Assert.Empty(t, violations) + Require.Len(t, issues, 1) + Assert.Contains(t, issues[0], "B/op missing from candidate") } func TestCompareIncompleteBaselineIsConfigurationError(t *testing.T) { @@ -86,40 +89,42 @@ func TestCompareIncompleteBaselineIsConfigurationError(t *testing.T) { "sec/op": {0.001, 0.001, 0.001, 0.001, 0.001}, "B/op": {10_000}, }} _, violations, issues := compare(old, next, defaultGates(2, 1.2, 1.25, 100_000, 8, 4096)) - assert.Empty(t, violations) - require.Len(t, issues, 1) - assert.Contains(t, issues[0], "B/op missing from baseline") + Assert.Empty(t, violations) + Require.Len(t, issues, 1) + Assert.Contains(t, issues[0], "B/op missing from baseline") }) t.Run("too few timing samples", func(t *testing.T) { old := benchmarkSamples{"Read-8": {"sec/op": {0.000001}}} next := benchmarkSamples{"Read-8": {"sec/op": {0.001, 0.001, 0.001, 0.001, 0.001}}} _, violations, issues := compare(old, next, defaultGates(2, 1.2, 1.25, 100_000, 8, 4096)) - assert.Empty(t, violations) - require.Len(t, issues, 1) - assert.Contains(t, issues[0], "needs at least 5 baseline samples") + Assert.Empty(t, violations) + Require.Len(t, issues, 1) + Assert.Contains(t, issues[0], "needs at least 5 baseline samples") }) } func TestRunRejectsMalformedBaseline(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) dir := t.TempDir() baseline := filepath.Join(dir, "old.txt") candidate := filepath.Join(dir, "new.txt") - require.NoError(t, os.WriteFile(baseline, []byte( + require.NoError(os.WriteFile(baseline, []byte( "BenchmarkRead-8 not-a-valid-result\n"), 0o600)) line := "BenchmarkRead-8 5 1000000 ns/op 10000 B/op 10 allocs/op\n" - require.NoError(t, os.WriteFile(candidate, []byte(strings.Repeat(line, 5)), 0o600)) + require.NoError(os.WriteFile(candidate, []byte(strings.Repeat(line, 5)), 0o600)) out, code := run(baseline, candidate, defaultGates(2, 1.2, 1.25, 100_000, 8, 4096)) - assert.Equal(t, 2, code) - assert.Contains(t, out, "baseline syntax") + assert.Equal(2, code) + assert.Contains(out, "baseline syntax") } func TestRunExitCodes(t *testing.T) { dir := t.TempDir() write := func(name, content string) string { path := filepath.Join(dir, name) - require.NoError(t, os.WriteFile(path, []byte(content), 0o600)) + Require.NoError(t, os.WriteFile(path, []byte(content), 0o600)) return path } lines := func(ns, bytes, allocs int) string { @@ -130,7 +135,7 @@ func TestRunExitCodes(t *testing.T) { passing := write("pass.txt", lines(1_100_000, 11_000, 11)) regressed := write("fail.txt", lines(1_100_000, 30_000, 30)) _, code := run(baseline, passing, defaultGates(2, 1.2, 1.25, 100_000, 8, 4096)) - assert.Zero(t, code) + Assert.Zero(t, code) _, code = run(baseline, regressed, defaultGates(2, 1.2, 1.25, 100_000, 8, 4096)) - assert.Equal(t, 1, code) + Assert.Equal(t, 1, code) } diff --git a/tools/packstreamfixture/main_test.go b/tools/packstreamfixture/main_test.go index 7f2a30e..bd7be4a 100644 --- a/tools/packstreamfixture/main_test.go +++ b/tools/packstreamfixture/main_test.go @@ -4,19 +4,21 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) func TestWriteFixtureContainsRawAndCompressedEntries(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) path := filepath.Join(t.TempDir(), "stream-v1.pack") - require.NoError(t, writeFixture(path)) + require.NoError(writeFixture(path)) reader, err := pack.OpenReader(path, nil) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, reader.Close()) }) + require.NoError(err) + t.Cleanup(func() { require.NoError(reader.Close()) }) entries := reader.Entries() - require.Len(t, entries, 2) - assert.Zero(t, entries[0].Flags&pack.BlobCompressed) - assert.NotZero(t, entries[1].Flags&pack.BlobCompressed) + require.Len(entries, 2) + assert.Zero(entries[0].Flags & pack.BlobCompressed) + assert.NotZero(entries[1].Flags & pack.BlobCompressed) } diff --git a/tools/packv1reader/main_test.go b/tools/packv1reader/main_test.go index 08500f8..afd07a6 100644 --- a/tools/packv1reader/main_test.go +++ b/tools/packv1reader/main_test.go @@ -5,20 +5,21 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/require" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/pack" ) func TestReadFixture(t *testing.T) { + require := Require.New(t) dir := t.TempDir() writer, err := pack.NewWriter(dir, pack.WriterOptions{}) - require.NoError(t, err) + require.NoError(err) _, err = writer.Append([]byte("raw")) - require.NoError(t, err) + require.NoError(err) _, err = writer.Append(bytes.Repeat([]byte("compressed"), 4096)) - require.NoError(t, err) + require.NoError(err) path := filepath.Join(dir, "fixture.pack") _, err = writer.Seal(path) - require.NoError(t, err) - require.NoError(t, readFixture(path)) + require.NoError(err) + require.NoError(readFixture(path)) } diff --git a/vector/flow.go b/vector/flow.go index b40c551..0ab7f3c 100644 --- a/vector/flow.go +++ b/vector/flow.go @@ -423,8 +423,7 @@ func applyFillBatch[K, G comparable]( if errors.Is(batch.err, context.Canceled) || errors.Is(batch.err, context.DeadlineExceeded) { return fillBatchContextError(batch.err, batch.refs, states) } - var invalid *InvalidVectorError - if errors.As(batch.err, &invalid) { + if invalid, ok := errors.AsType[*InvalidVectorError](batch.err); ok { return applyAttributedInvalidFillBatch(ctx, store, gen, enc, o, batch, invalid, states, orderedSaves, stale, stats) } @@ -701,6 +700,9 @@ func Search[K, G comparable]( if err != nil { return nil, fmt.Errorf("embed query for generation %v: %w", gen, err) } + if len(vectors) != 1 { + return nil, fmt.Errorf("embed query for generation %v returned %d vectors, want 1", gen, len(vectors)) + } hits, err := store.QueryGeneration(ctx, gen, vectors[0], perGen) if err != nil { return nil, fmt.Errorf("query generation %v: %w", gen, err) diff --git a/vector/flow_batch_error_test.go b/vector/flow_batch_error_test.go index de69041..513cc1f 100644 --- a/vector/flow_batch_error_test.go +++ b/vector/flow_batch_error_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" "go.kenn.io/kit/vector" ) @@ -33,11 +33,13 @@ func TestFillSharedErrorClassifierFailsClosedWithoutProbes(t *testing.T) { {name: "nil classifier"}, {name: "classifier false", wantClassifiers: 1, classifier: func(err error) bool { var got *fillProviderError - require.ErrorAs(t, err, &got) + Require.ErrorAs(t, err, &got) return false }}, } { t.Run(tc.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newMemStore() store.content = map[int64]string{1: "one", 2: "two", 3: "three"} var calls, classifiers, hooks int @@ -61,13 +63,13 @@ func TestFillSharedErrorClassifierFailsClosedWithoutProbes(t *testing.T) { return true }, }) - require.Error(t, err) + require.Error(err) var got *fillProviderError - require.ErrorAs(t, err, &got) - assert.Same(t, providerErr, got) - assert.Equal(t, 1, calls) - assert.Equal(t, tc.wantClassifiers, classifiers) - assert.Zero(t, hooks) + require.ErrorAs(err, &got) + assert.Same(providerErr, got) + assert.Equal(1, calls) + assert.Equal(tc.wantClassifiers, classifiers) + assert.Zero(hooks) }) } } @@ -81,6 +83,8 @@ func TestFillSharedErrorRejectedFirstProbeStopsDiagnosis(t *testing.T) { {name: "false hook", hook: func(int64, error) bool { return false }}, } { t.Run(tc.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newMemStore() store.content = map[int64]string{1: "poison one", 2: "poison two"} var calls, classifiers, hooks int @@ -104,17 +108,18 @@ func TestFillSharedErrorRejectedFirstProbeStopsDiagnosis(t *testing.T) { }, OnEncodeError: hook, }) - require.Error(t, err) + require.Error(err) var providerErr *fillProviderError - require.ErrorAs(t, err, &providerErr) - assert.Equal(t, 2, calls, "one shared call plus the first probe") - assert.Equal(t, 1, classifiers) - assert.Equal(t, map[bool]int{true: 1, false: 0}[tc.hook != nil], hooks) + require.ErrorAs(err, &providerErr) + assert.Equal(2, calls, "one shared call plus the first probe") + assert.Equal(1, classifiers) + assert.Equal(map[bool]int{true: 1, false: 0}[tc.hook != nil], hooks) }) } } func TestFillSharedErrorAllowsTwoPoisonDocuments(t *testing.T) { + assert := Assert.New(t) store := newMemStore() store.content = map[int64]string{1: "poison one", 2: "poison two"} var calls int @@ -132,14 +137,15 @@ func TestFillSharedErrorAllowsTwoPoisonDocuments(t *testing.T) { return true }, }) - require.NoError(t, err) - assert.Equal(t, 3, calls) - assert.Equal(t, map[int64]int{1: 1, 2: 1}, hooks) - assert.Equal(t, 2, stats.Skipped) - assert.Zero(t, stats.Documents) + Require.NoError(t, err) + assert.Equal(3, calls) + assert.Equal(map[int64]int{1: 1, 2: 1}, hooks) + assert.Equal(2, stats.Skipped) + assert.Zero(stats.Documents) } func TestFillSharedInvalidVectorRejectedWithoutProbe(t *testing.T) { + assert := Assert.New(t) store := newMemStore() store.content = map[int64]string{1: "good", 2: "bad", 3: "later"} var calls, classifiers, hooks int @@ -156,17 +162,17 @@ func TestFillSharedInvalidVectorRejectedWithoutProbe(t *testing.T) { }, OnEncodeError: func(doc int64, err error) bool { hooks++ - assert.Equal(t, int64(2), doc) + assert.Equal(int64(2), doc) var invalid *vector.InvalidVectorError - require.ErrorAs(t, err, &invalid) - assert.Equal(t, 0, invalid.Chunk) + Require.ErrorAs(t, err, &invalid) + assert.Equal(0, invalid.Chunk) return false }, }) - require.Error(t, err) - assert.Equal(t, 1, calls) - assert.Zero(t, classifiers) - assert.Equal(t, 1, hooks) + Require.Error(t, err) + assert.Equal(1, calls) + assert.Zero(classifiers) + assert.Equal(1, hooks) } func TestFillSharedInvalidVectorNilHookRejectsWithoutProbe(t *testing.T) { @@ -182,12 +188,13 @@ func TestFillSharedInvalidVectorNilHookRejectsWithoutProbe(t *testing.T) { Batch: vector.BatchOptions{BatchSize: 2}, ShouldIsolateBatchError: func(error) bool { classifiers++; return true }, }) - require.Error(t, err) - assert.Equal(t, 1, calls) - assert.Zero(t, classifiers) + Require.Error(t, err) + Assert.Equal(t, 1, calls) + Assert.Zero(t, classifiers) } func TestFillSharedInvalidVectorRecoversOnlyOtherSlices(t *testing.T) { + assert := Assert.New(t) store := newMemStore() store.content = map[int64]string{1: "good", 2: "bad", 3: "later"} var calls [][]string @@ -211,14 +218,16 @@ func TestFillSharedInvalidVectorRecoversOnlyOtherSlices(t *testing.T) { return true }, }) - require.NoError(t, err) - assert.Equal(t, [][]string{{"good", "bad", "later"}, {"good"}, {"later"}}, calls) - assert.Equal(t, map[int64]int{2: 1}, hooks) - assert.Equal(t, 2, stats.Documents) - assert.Equal(t, 1, stats.Skipped) + Require.NoError(t, err) + assert.Equal([][]string{{"good", "bad", "later"}, {"good"}, {"later"}}, calls) + assert.Equal(map[int64]int{2: 1}, hooks) + assert.Equal(2, stats.Documents) + assert.Equal(1, stats.Skipped) } func TestFillSharedInvalidRecoveryFailureUsesProbeRules(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newMemStore() store.content = map[int64]string{1: "bad", 2: "neighbor"} var calls, classifiers int @@ -239,15 +248,17 @@ func TestFillSharedInvalidRecoveryFailureUsesProbeRules(t *testing.T) { return doc == 1 }, }) - require.Error(t, err) + require.Error(err) var providerErr *fillProviderError - require.ErrorAs(t, err, &providerErr) - assert.Equal(t, 2, calls) - assert.Zero(t, classifiers, "recovery failures are never reclassified") - assert.Equal(t, map[int64]int{1: 1, 2: 1}, hooks) + require.ErrorAs(err, &providerErr) + assert.Equal(2, calls) + assert.Zero(classifiers, "recovery failures are never reclassified") + assert.Equal(map[int64]int{1: 1, 2: 1}, hooks) } func TestFillSharedInvalidVectorOutOfRangeIsFatal(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newMemStore() store.content = map[int64]string{1: "one", 2: "two"} var calls, classifiers, hooks int @@ -261,15 +272,17 @@ func TestFillSharedInvalidVectorOutOfRangeIsFatal(t *testing.T) { ShouldIsolateBatchError: func(error) bool { classifiers++; return true }, OnEncodeError: func(int64, error) bool { hooks++; return true }, }) - require.ErrorContains(t, err, "invalid vector chunk 2 outside batch of 2 chunks") + require.ErrorContains(err, "invalid vector chunk 2 outside batch of 2 chunks") var invalid *vector.InvalidVectorError - require.ErrorAs(t, err, &invalid) - assert.Equal(t, 1, calls) - assert.Zero(t, classifiers) - assert.Zero(t, hooks) + require.ErrorAs(err, &invalid) + assert.Equal(1, calls) + assert.Zero(classifiers) + assert.Zero(hooks) } func TestFillSharedInvalidVectorPreservesCompanionCauses(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newMemStore() store.content = map[int64]string{1: "good", 2: "bad"} providerErr := &fillProviderError{code: 422} @@ -285,28 +298,29 @@ func TestFillSharedInvalidVectorPreservesCompanionCauses(t *testing.T) { ScanBatch: 2, Batch: vector.BatchOptions{BatchSize: 2}, OnEncodeError: func(doc int64, err error) bool { - assert.Equal(t, int64(2), doc) + assert.Equal(int64(2), doc) var invalid *vector.InvalidVectorError - require.ErrorAs(t, err, &invalid) - assert.Equal(t, 0, invalid.Chunk) + require.ErrorAs(err, &invalid) + assert.Equal(0, invalid.Chunk) var gotProvider *fillProviderError - assert.ErrorAs(t, err, &gotProvider) - assert.Same(t, providerErr, gotProvider) - assert.ErrorIs(t, err, sentinel) + assert.ErrorAs(err, &gotProvider) + assert.Same(providerErr, gotProvider) + assert.ErrorIs(err, sentinel) return false }, }) - require.Error(t, err) + require.Error(err) var invalid *vector.InvalidVectorError - require.ErrorAs(t, err, &invalid) - assert.Equal(t, 0, invalid.Chunk) + require.ErrorAs(err, &invalid) + assert.Equal(0, invalid.Chunk) var gotProvider *fillProviderError - assert.ErrorAs(t, err, &gotProvider) - assert.Same(t, providerErr, gotProvider) - assert.ErrorIs(t, err, sentinel) + assert.ErrorAs(err, &gotProvider) + assert.Same(providerErr, gotProvider) + assert.ErrorIs(err, sentinel) } func TestFillRejectedProbeBackpressuresAndCancelsWorkers(t *testing.T) { + require := Require.New(t) store := newMemStore() store.content = map[int64]string{ 1: "one", 2: "two", 3: "three", 4: "four", 5: "five", 6: "six", @@ -368,40 +382,41 @@ func TestFillRejectedProbeBackpressuresAndCancelsWorkers(t *testing.T) { select { case <-fillReturned: case <-time.After(5 * time.Second): - assert.Fail(t, "Fill goroutine did not stop during cleanup") + Assert.Fail(t, "Fill goroutine did not stop during cleanup") } }) select { case <-probeStarted: case <-time.After(5 * time.Second): - require.FailNow(t, "collector did not start the first probe") + require.FailNow("collector did not start the first probe") } select { case <-thirdStarted: - require.FailNow(t, "failed-result worker started a third job during collection") + require.FailNow("failed-result worker started a third job during collection") case <-time.After(100 * time.Millisecond): } release() select { case err := <-done: - require.Error(t, err) + require.Error(err) case <-time.After(5 * time.Second): - require.FailNow(t, "Fill did not abort after hook rejection") + require.FailNow("Fill did not abort after hook rejection") } select { case <-secondCanceled: case <-time.After(5 * time.Second): - require.FailNow(t, "in-flight worker did not observe cancellation") + require.FailNow("in-flight worker did not observe cancellation") } select { case <-thirdStarted: - assert.Fail(t, "third job started after collection rejected the failure") + Assert.Fail(t, "third job started after collection rejected the failure") default: } } func TestFillLateSharedFailureFiltersDecidedDocument(t *testing.T) { + assert := Assert.New(t) store := newMemStore() store.content = map[int64]string{1: "abc", 2: "d"} releaseShared := make(chan struct{}) @@ -430,17 +445,17 @@ func TestFillLateSharedFailureFiltersDecidedDocument(t *testing.T) { }, OnEncodeError: func(doc int64, _ error) bool { hookCalls.Add(1) - assert.Equal(t, int64(1), doc) + assert.Equal(int64(1), doc) close(releaseShared) return true }, }) - require.NoError(t, err) - assert.Equal(t, int32(1), hookCalls.Load()) - assert.Equal(t, int32(1), classifierCalls.Load()) - assert.Equal(t, 1, stats.Skipped) - assert.Equal(t, 1, stats.Documents) - assert.True(t, store.embedded[2][7]) + Require.NoError(t, err) + assert.Equal(int32(1), hookCalls.Load()) + assert.Equal(int32(1), classifierCalls.Load()) + assert.Equal(1, stats.Skipped) + assert.Equal(1, stats.Documents) + assert.True(store.embedded[2][7]) } func TestFillWrappedProbeDeadlineAbortsWithoutHook(t *testing.T) { @@ -460,9 +475,9 @@ func TestFillWrappedProbeDeadlineAbortsWithoutHook(t *testing.T) { ShouldIsolateBatchError: func(error) bool { return true }, OnEncodeError: func(int64, error) bool { hooks++; return true }, }) - require.ErrorIs(t, err, context.DeadlineExceeded) - assert.Equal(t, 2, calls) - assert.Zero(t, hooks) + Require.ErrorIs(t, err, context.DeadlineExceeded) + Assert.Equal(t, 2, calls) + Assert.Zero(t, hooks) } func TestFillBatchClassifierExclusions(t *testing.T) { @@ -488,6 +503,8 @@ func TestFillBatchClassifierExclusions(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) store := newMemStore() store.content = tc.content var classifiers, hooks int @@ -500,12 +517,12 @@ func TestFillBatchClassifierExclusions(t *testing.T) { OnEncodeError: func(int64, error) bool { hooks++; return true }, }) if errors.Is(tc.encodeErr, context.Canceled) { - require.ErrorIs(t, err, context.Canceled) + require.ErrorIs(err, context.Canceled) } else { - require.NoError(t, err) + require.NoError(err) } - assert.Zero(t, classifiers) - assert.Equal(t, tc.wantHook, hooks) + assert.Zero(classifiers) + assert.Equal(tc.wantHook, hooks) }) } } diff --git a/vector/flow_internal_test.go b/vector/flow_internal_test.go index 8461bf1..e9427dd 100644 --- a/vector/flow_internal_test.go +++ b/vector/flow_internal_test.go @@ -5,8 +5,8 @@ import ( "errors" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + Assert "github.com/stretchr/testify/assert" + Require "github.com/stretchr/testify/require" ) type internalFillProviderError struct{} @@ -34,6 +34,8 @@ func (noOpFillStore) QueryGeneration(context.Context, int, Vector, int) ([]Hit[i } func TestApplyFillBatchProbeInvalidVectorAddsSliceAndLocalOffsets(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) refs := []fillChunkRef{ {doc: 0, chunk: 3, value: Chunk{Index: 3, Text: "d"}}, {doc: 0, chunk: 4, value: Chunk{Index: 4, Text: "e"}}, @@ -59,10 +61,10 @@ func TestApplyFillBatchProbeInvalidVectorAddsSliceAndLocalOffsets(t *testing.T) enc := func(_ context.Context, texts []string) ([][]float32, error) { calls++ if calls == 1 { - assert.Equal(t, []string{"d", "e", "z"}, texts) + assert.Equal([]string{"d", "e", "z"}, texts) return nil, &internalFillProviderError{} } - assert.Equal(t, []string{"d", "e"}, texts) + assert.Equal([]string{"d", "e"}, texts) return [][]float32{{1}, {0}}, nil } batch := encodeFillBatch(context.Background(), enc, refs) @@ -71,24 +73,26 @@ func TestApplyFillBatchProbeInvalidVectorAddsSliceAndLocalOffsets(t *testing.T) FillOptions[int64]{ ShouldIsolateBatchError: func(err error) bool { var providerErr *internalFillProviderError - require.ErrorAs(t, err, &providerErr) + require.ErrorAs(err, &providerErr) return true }, OnEncodeError: func(doc int64, err error) bool { - assert.Equal(t, int64(10), doc) - require.ErrorAs(t, err, &got) + assert.Equal(int64(10), doc) + require.ErrorAs(err, &got) return false }, }, enc, batch, states, true, map[int64]struct{}{}, &FillStats{}) - require.Error(t, err) - require.NotNil(t, got) - assert.Equal(t, 4, got.Chunk, "slice start 3 plus local invalid index 1") - assert.Equal(t, 2, calls) - assert.True(t, errors.As(err, &got)) + require.Error(err) + require.NotNil(got) + assert.Equal(4, got.Chunk, "slice start 3 plus local invalid index 1") + assert.Equal(2, calls) + assert.ErrorAs(err, &got) } func TestApplyFillBatchProbeInvalidVectorPreservesCompanionCauses(t *testing.T) { + assert := Assert.New(t) + require := Require.New(t) refs := []fillChunkRef{ {doc: 0, chunk: 3, value: Chunk{Index: 3, Text: "d"}}, {doc: 0, chunk: 4, value: Chunk{Index: 4, Text: "e"}}, @@ -116,10 +120,10 @@ func TestApplyFillBatchProbeInvalidVectorPreservesCompanionCauses(t *testing.T) enc := func(_ context.Context, texts []string) ([][]float32, error) { calls++ if calls == 1 { - assert.Equal(t, []string{"d", "e", "z"}, texts) + assert.Equal([]string{"d", "e", "z"}, texts) return nil, &internalFillProviderError{} } - assert.Equal(t, []string{"d", "e"}, texts) + assert.Equal([]string{"d", "e"}, texts) return nil, errors.Join( &InvalidVectorError{Chunk: 1, Component: -1, Reason: "zero norm"}, companion, @@ -132,27 +136,27 @@ func TestApplyFillBatchProbeInvalidVectorPreservesCompanionCauses(t *testing.T) FillOptions[int64]{ ShouldIsolateBatchError: func(err error) bool { var providerErr *internalFillProviderError - require.ErrorAs(t, err, &providerErr) + require.ErrorAs(err, &providerErr) return true }, OnEncodeError: func(doc int64, err error) bool { - assert.Equal(t, int64(10), doc) - require.ErrorAs(t, err, &gotInvalid) - assert.Equal(t, 4, gotInvalid.Chunk) + assert.Equal(int64(10), doc) + require.ErrorAs(err, &gotInvalid) + assert.Equal(4, gotInvalid.Chunk) var gotCompanion *internalFillCompanionError - assert.ErrorAs(t, err, &gotCompanion) - assert.Same(t, companion, gotCompanion) - assert.ErrorIs(t, err, sentinel) + assert.ErrorAs(err, &gotCompanion) + assert.Same(companion, gotCompanion) + assert.ErrorIs(err, sentinel) return false }, }, enc, batch, states, true, map[int64]struct{}{}, &FillStats{}) - require.Error(t, err) - require.NotNil(t, gotInvalid) - assert.Equal(t, 4, gotInvalid.Chunk) - assert.Equal(t, 2, calls) + require.Error(err) + require.NotNil(gotInvalid) + assert.Equal(4, gotInvalid.Chunk) + assert.Equal(2, calls) var gotCompanion *internalFillCompanionError - assert.ErrorAs(t, err, &gotCompanion) - assert.Same(t, companion, gotCompanion) - assert.ErrorIs(t, err, sentinel) + assert.ErrorAs(err, &gotCompanion) + assert.Same(companion, gotCompanion) + assert.ErrorIs(err, sentinel) } diff --git a/vector/flow_test.go b/vector/flow_test.go index 2f4f7c6..a3140c6 100644 --- a/vector/flow_test.go +++ b/vector/flow_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "maps" "math" "slices" "sort" @@ -240,9 +241,7 @@ func TestFillCrossDocumentBatchingMatchesLegacyAcrossConfigurations(t *testing.T } baseline := newMemStore() - for doc, content := range contents { - baseline.content[doc] = content - } + maps.Copy(baseline.content, contents) baselineStats, err := vector.Fill(ctx, baseline, 7, textEncoder(), vector.FillOptions[int64]{ ScanBatch: 5, Split: vector.SplitOptions{MaxRunes: 3, Overlap: 1}, @@ -257,9 +256,7 @@ func TestFillCrossDocumentBatchingMatchesLegacyAcrossConfigurations(t *testing.T scanBatch, batchSize, batchConcurrency, fillConcurrency) t.Run(name, func(t *testing.T) { store := newMemStore() - for doc, content := range contents { - store.content[doc] = content - } + maps.Copy(store.content, contents) stats, err := vector.Fill(ctx, store, 7, textEncoder(), vector.FillOptions[int64]{ ScanBatch: scanBatch,