From 440d925467988012269010422a30efd2282fbc28 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 14 Jun 2026 21:27:13 +0200 Subject: [PATCH 01/24] repository: default to fastest compression in tests --- internal/repository/prune_test.go | 2 +- internal/repository/testing.go | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/repository/prune_test.go b/internal/repository/prune_test.go index 78d039b4e..d67234a42 100644 --- a/internal/repository/prune_test.go +++ b/internal/repository/prune_test.go @@ -125,7 +125,7 @@ func TestPruneSmall(t *testing.T) { t.Logf("rand initialized with seed %d", seed) be := repository.TestBackend(t) - repo, _ := repository.TestRepositoryWithBackend(t, be, 0, repository.Options{PackSize: repository.MinPackSize}) + repo, _ := repository.TestRepositoryWithBackend(t, be, 0, repository.Options{PackSize: repository.MinPackSize, Compression: repository.CompressionOff}) const blobSize = 1000 * 1000 const numBlobsCreated = 55 diff --git a/internal/repository/testing.go b/internal/repository/testing.go index 944512a54..27dd98f2f 100644 --- a/internal/repository/testing.go +++ b/internal/repository/testing.go @@ -54,6 +54,10 @@ func TestRepositoryWithBackend(t testing.TB, be backend.Backend, version uint, o if be == nil { be = TestBackend(t) } + // Speed up tests by default + if opts.Compression == CompressionAuto { + opts.Compression = CompressionFastest + } repo, err := New(be, opts) if err != nil { @@ -126,7 +130,7 @@ func TestOpenLocal(t testing.TB, dir string) (*Repository, backend.Backend) { } func TestOpenBackend(t testing.TB, be backend.Backend) *Repository { - repo, err := New(be, Options{}) + repo, err := New(be, Options{Compression: CompressionFastest}) if err != nil { t.Fatal(err) } From 1174e97b309e37a33a2850cfc25fef1ad95b6e8f Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 14 Jun 2026 21:38:09 +0200 Subject: [PATCH 02/24] restic: speed up repository config initialization in tests Avoid redundant polynomial generation, which takes a millisecond or so. --- internal/repository/repository.go | 5 +---- internal/restic/config.go | 12 ++++++++---- internal/restic/config_test.go | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/internal/repository/repository.go b/internal/repository/repository.go index 35f5c78f5..889c76049 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -919,13 +919,10 @@ func (r *Repository) Init(ctx context.Context, version uint, password string, ch return err } - cfg, err := restic.CreateConfig(version) + cfg, err := restic.CreateConfig(version, chunkerPolynomial) if err != nil { return err } - if chunkerPolynomial != nil { - cfg.ChunkerPolynomial = *chunkerPolynomial - } return r.init(ctx, password, cfg) } diff --git a/internal/restic/config.go b/internal/restic/config.go index 8af09c908..4c6c97a08 100644 --- a/internal/restic/config.go +++ b/internal/restic/config.go @@ -28,15 +28,19 @@ const StableRepoVersion = 2 // CreateConfig creates a config file with a randomly selected polynomial and // ID. -func CreateConfig(version uint) (Config, error) { +func CreateConfig(version uint, pol *chunker.Pol) (Config, error) { var ( err error cfg Config ) - cfg.ChunkerPolynomial, err = chunker.RandomPolynomial() - if err != nil { - return Config{}, errors.Wrap(err, "chunker.RandomPolynomial") + if pol == nil { + cfg.ChunkerPolynomial, err = chunker.RandomPolynomial() + if err != nil { + return Config{}, errors.Wrap(err, "chunker.RandomPolynomial") + } + } else { + cfg.ChunkerPolynomial = *pol } cfg.ID = NewRandomID().String() diff --git a/internal/restic/config_test.go b/internal/restic/config_test.go index 5a7f6b0ae..54d3db2fc 100644 --- a/internal/restic/config_test.go +++ b/internal/restic/config_test.go @@ -43,7 +43,7 @@ func TestConfig(t *testing.T) { return restic.ID{}, nil } - cfg1, err := restic.CreateConfig(restic.MaxRepoVersion) + cfg1, err := restic.CreateConfig(restic.MaxRepoVersion, nil) rtest.OK(t, err) err = restic.SaveConfig(context.TODO(), saver{save}, cfg1) From c183fe2035e0d00434b81fc446a8423b558de901 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 14 Jun 2026 21:40:50 +0200 Subject: [PATCH 03/24] restore: speed up test data generation in newTestRepo --- internal/restorer/filerestorer_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/restorer/filerestorer_test.go b/internal/restorer/filerestorer_test.go index adb4e9cd1..5a423ea2a 100644 --- a/internal/restorer/filerestorer_test.go +++ b/internal/restorer/filerestorer_test.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "slices" + "strings" "testing" "github.com/restic/restic/internal/errors" @@ -107,9 +108,9 @@ func newTestRepo(content []TestFile) *TestRepo { filesPathToContent := make(map[string]string) for _, file := range content { - var content string + content := strings.Builder{} for _, blob := range file.blobs { - content += blob.data + content.WriteString(blob.data) // get the pack, create as necessary var pack Pack @@ -134,7 +135,7 @@ func newTestRepo(content []TestFile) *TestRepo { packs[blob.pack] = pack } - filesPathToContent[file.name] = content + filesPathToContent[file.name] = content.String() } blobs := make(map[restic.ID][]restic.PackBlob) From 69a3ba5b33233c40dac9341a0e7c25cb57db8eb5 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Mon, 15 Jun 2026 21:47:31 +0200 Subject: [PATCH 04/24] index: speed up oversized test data generation --- internal/repository/index/index_internal_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/repository/index/index_internal_test.go b/internal/repository/index/index_internal_test.go index 94a871ed3..c5cf2c6e6 100644 --- a/internal/repository/index/index_internal_test.go +++ b/internal/repository/index/index_internal_test.go @@ -13,11 +13,18 @@ func TestIndexOversized(t *testing.T) { // Add blobs up to indexMaxBlobs + pack.MaxHeaderEntries - 1 packID := idx.addToPacks(restic.NewRandomID()) + id := restic.NewRandomID() for i := uint(0); i < indexMaxBlobs+pack.MaxHeaderEntries-1; i++ { + // Directly modify ID to avoid benchmarking NewRandomID + id[0] = byte(i) + id[1] = byte(i >> 8) + id[2] = byte(i >> 16) + id[3] = byte(i >> 24) + idx.store(packID, pack.Blob{ BlobHandle: restic.BlobHandle{ Type: restic.DataBlob, - ID: restic.NewRandomID(), + ID: id, }, Length: 100, Offset: uint(i) * 100, From f9d953d4cdadaf16b464e0a85ef6031414ebba0b Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Mon, 15 Jun 2026 21:48:21 +0200 Subject: [PATCH 05/24] index: test ForAllIndex with generated repo --- .../repository/index/index_parallel_test.go | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/internal/repository/index/index_parallel_test.go b/internal/repository/index/index_parallel_test.go index 030fb9ea0..f69d92e84 100644 --- a/internal/repository/index/index_parallel_test.go +++ b/internal/repository/index/index_parallel_test.go @@ -2,26 +2,44 @@ package index_test import ( "context" - "path/filepath" "testing" "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/repository" + "github.com/restic/restic/internal/repository/crypto" "github.com/restic/restic/internal/repository/index" + "github.com/restic/restic/internal/repository/pack" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" ) -var repoFixture = filepath.Join("..", "testdata", "test-repo.tar.gz") - func TestRepositoryForAllIndexes(t *testing.T) { - repo, _ := repository.TestFromFixture(t, repoFixture) + originalFull := index.Full + defer func() { + index.Full = originalFull + }() + index.Full = func(*index.Index) bool { return true } + + repo, unpacked, _ := repository.TestRepositoryWithVersion(t, restic.StableRepoVersion) + + mi := index.NewMasterIndex() + for range 3 { + packID := restic.NewRandomID() + blob := pack.Blob{ + BlobHandle: restic.NewRandomBlobHandle(), + Length: uint(crypto.CiphertextLength(10)), + Offset: 0, + } + rtest.OK(t, mi.StorePack(context.TODO(), packID, pack.Blobs{blob}, unpacked)) + rtest.OK(t, mi.Flush(context.TODO(), unpacked)) + } expectedIndexIDs := restic.NewIDSet() rtest.OK(t, repo.List(context.TODO(), restic.IndexFile, func(id restic.ID, size int64) error { expectedIndexIDs.Insert(id) return nil })) + rtest.Assert(t, len(expectedIndexIDs) > 1, "test repo should have multiple indexes") // check that all expected indexes are loaded without errors indexIDs := restic.NewIDSet() From aae09856735df81185aedda698964c48ac13c9c8 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Mon, 15 Jun 2026 21:48:38 +0200 Subject: [PATCH 06/24] index: decrease depth of test repos --- internal/repository/index/master_index_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/repository/index/master_index_test.go b/internal/repository/index/master_index_test.go index 42b24320a..97fcb0ef3 100644 --- a/internal/repository/index/master_index_test.go +++ b/internal/repository/index/master_index_test.go @@ -410,7 +410,7 @@ func BenchmarkMasterIndexGC(b *testing.B) { var ( snapshotTime = time.Unix(1470492820, 207401672) - depth = 3 + depth = 2 ) func createFilledRepo(t testing.TB, snapshots int, version uint) (*repository.Repository, restic.Unpacked[restic.FileType]) { From 7e5eaf1ead1e3de98a2f327ec32190e769226a8a Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 14 Jun 2026 22:11:35 +0200 Subject: [PATCH 07/24] crypto: decrease parameters for calibrate test --- internal/repository/crypto/kdf_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/repository/crypto/kdf_test.go b/internal/repository/crypto/kdf_test.go index 5823eb889..83d5f81d0 100644 --- a/internal/repository/crypto/kdf_test.go +++ b/internal/repository/crypto/kdf_test.go @@ -6,7 +6,7 @@ import ( ) func TestCalibrate(t *testing.T) { - params, err := Calibrate(100*time.Millisecond, 50) + params, err := Calibrate(25*time.Millisecond, 50) if err != nil { t.Fatal(err) } From 37e8f50aeb2a26d04ab37e6ec27dba6dd431c7d1 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 14 Jun 2026 22:34:39 +0200 Subject: [PATCH 08/24] repository: decrease test data size for streamPack --- internal/repository/repository_internal_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/repository/repository_internal_test.go b/internal/repository/repository_internal_test.go index dbcecadf0..1b78f07cb 100644 --- a/internal/repository/repository_internal_test.go +++ b/internal/repository/repository_internal_test.go @@ -205,19 +205,17 @@ func testStreamPack(t *testing.T, version uint) { key := testKey(t) blobSizes := []int{ - 5522811, + 1522811, 10, 5231, 18812, 123123, - 13522811, 12301, 892242, 28616, 13351, 252287, 188883, - 3522811, 18883, } From 2148508050e5bf5004bbb0b3efce16624d99a17d Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 14 Jun 2026 22:51:29 +0200 Subject: [PATCH 09/24] repository/checker: speed up test repo creation data.TestCreateSnapshot is much faster than archiver.TestSnapshot --- internal/checker/checker_test.go | 3 +-- internal/repository/checker_test.go | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/checker/checker_test.go b/internal/checker/checker_test.go index 0de1c24cf..58dc7c744 100644 --- a/internal/checker/checker_test.go +++ b/internal/checker/checker_test.go @@ -12,7 +12,6 @@ import ( "testing" "time" - "github.com/restic/restic/internal/archiver" "github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/checker" "github.com/restic/restic/internal/data" @@ -310,7 +309,7 @@ func (b *errorOnceBackend) Load(ctx context.Context, h backend.Handle, length in func TestCheckerModifiedData(t *testing.T) { repo, _, be := repository.TestRepositoryWithVersion(t, 0) - sn := archiver.TestSnapshot(t, repo, ".", nil) + sn := data.TestCreateSnapshot(t, repo, time.Unix(1470492820, 207401672), 2) t.Logf("archived as %v", sn.ID().Str()) errBe := &errorBackend{Backend: be} diff --git a/internal/repository/checker_test.go b/internal/repository/checker_test.go index 7933dca67..10fe94970 100644 --- a/internal/repository/checker_test.go +++ b/internal/repository/checker_test.go @@ -8,10 +8,11 @@ import ( "path/filepath" "strings" "testing" + "time" "github.com/klauspost/compress/zstd" - "github.com/restic/restic/internal/archiver" "github.com/restic/restic/internal/backend" + "github.com/restic/restic/internal/data" "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/repository/pack" "github.com/restic/restic/internal/restic" @@ -153,7 +154,7 @@ func setupChecker(t *testing.T, wrap func(backend.Backend) backend.Backend) *Che t.Helper() // Write a snapshot into a fresh in-memory repository. repo, be := TestRepositoryWithBackend(t, nil, 0, Options{}) - _ = archiver.TestSnapshot(t, repo, ".", nil) + data.TestCreateSnapshot(t, repo, time.Unix(1470492820, 207401672), 2) // Re-open the same backend (now containing real pack files) through // the corruption wrapper so the checker reads corrupted data. From f91afb2fa85b041e42241b4eaf96d43a3bb74ffc Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 14 Jun 2026 22:52:09 +0200 Subject: [PATCH 10/24] repository: run TestPruneSmall in parallel to other tests --- internal/repository/prune_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/repository/prune_test.go b/internal/repository/prune_test.go index d67234a42..cf5664104 100644 --- a/internal/repository/prune_test.go +++ b/internal/repository/prune_test.go @@ -120,6 +120,7 @@ func TestPrune(t *testing.T) { 6.) The result should be less packfiles than before */ func TestPruneSmall(t *testing.T) { + t.Parallel() seed := time.Now().UnixNano() random := rand.New(rand.NewSource(seed)) t.Logf("rand initialized with seed %d", seed) From 18e5c446e05902649b5589f98809018fdd4d802e Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Mon, 15 Jun 2026 21:53:33 +0200 Subject: [PATCH 11/24] data/fs: faster comparison in tests --- internal/data/snapshot_policy_test.go | 6 ++++-- internal/fs/fs_reader_test.go | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/data/snapshot_policy_test.go b/internal/data/snapshot_policy_test.go index 7cb943361..b74bfd667 100644 --- a/internal/data/snapshot_policy_test.go +++ b/internal/data/snapshot_policy_test.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "reflect" "testing" "time" @@ -275,11 +276,12 @@ func TestApplyPolicy(t *testing.T) { cmpOpts := cmpopts.IgnoreUnexported(data.Snapshot{}) - if !cmp.Equal(want.Keep, keep, cmpOpts) { + // reflect.DeepEqual is faster than cmp.Equal + if !reflect.DeepEqual(want.Keep, keep) { t.Error(cmp.Diff(want.Keep, keep, cmpOpts)) } - if !cmp.Equal(want.Reasons, reasons, cmpOpts) { + if !reflect.DeepEqual(want.Reasons, reasons) { t.Error(cmp.Diff(want.Reasons, reasons, cmpOpts)) } }) diff --git a/internal/fs/fs_reader_test.go b/internal/fs/fs_reader_test.go index d56583646..a467a522f 100644 --- a/internal/fs/fs_reader_test.go +++ b/internal/fs/fs_reader_test.go @@ -6,6 +6,7 @@ import ( "io" "os" "path" + "slices" "sort" "strings" "testing" @@ -23,7 +24,8 @@ func verifyFileContentOpenFile(t testing.TB, fs FS, filename string, want []byte test.OK(t, err) test.OK(t, f.Close()) - if !cmp.Equal(want, buf) { + // slices.Equal is much faster than cmp.Equal + if !slices.Equal(want, buf) { t.Error(cmp.Diff(want, buf)) } } From c021b8cde0cfedfdc877459a025baa6fb4d12d06 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Mon, 15 Jun 2026 21:04:57 +0200 Subject: [PATCH 12/24] decrease test data sizes --- internal/archiver/archiver_test.go | 8 ++++---- internal/backend/cache/file_test.go | 2 +- internal/backend/test/tests.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/archiver/archiver_test.go b/internal/archiver/archiver_test.go index d54d9fb5d..76a9f2488 100644 --- a/internal/archiver/archiver_test.go +++ b/internal/archiver/archiver_test.go @@ -754,12 +754,12 @@ func TestArchiverSaveDir(t *testing.T) { }{ { src: TestDir{ - "targetfile": TestFile{Content: string(rtest.Random(888, 2*1024*1024+5000))}, + "targetfile": TestFile{Content: string(rtest.Random(888, 20*1024+5000))}, }, target: ".", want: TestDir{ "targetdir": TestDir{ - "targetfile": TestFile{Content: string(rtest.Random(888, 2*1024*1024+5000))}, + "targetfile": TestFile{Content: string(rtest.Random(888, 20*1024+5000))}, }, }, }, @@ -769,8 +769,8 @@ func TestArchiverSaveDir(t *testing.T) { "foo": TestFile{Content: "foo"}, "emptyfile": TestFile{Content: ""}, "bar": TestFile{Content: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}, - "largefile": TestFile{Content: string(rtest.Random(888, 2*1024*1024+5000))}, - "largerfile": TestFile{Content: string(rtest.Random(234, 5*1024*1024+5000))}, + "largefile": TestFile{Content: string(rtest.Random(888, 1*1024*1024+5000))}, + "largerfile": TestFile{Content: string(rtest.Random(234, 3*1024*1024+5000))}, }, }, target: "targetdir", diff --git a/internal/backend/cache/file_test.go b/internal/backend/cache/file_test.go index 949bbcd8c..d26c8cc6a 100644 --- a/internal/backend/cache/file_test.go +++ b/internal/backend/cache/file_test.go @@ -22,7 +22,7 @@ import ( func generateRandomFiles(t testing.TB, random *rand.Rand, tpe backend.FileType, c *Cache) map[string]struct{} { ids := make(map[string]struct{}) for i := 0; i < random.Intn(15)+10; i++ { - buf := rtest.Random(random.Int(), 1<<19) + buf := rtest.Random(random.Int(), 1<<15) id := restic.Hash(buf) h := backend.Handle{Type: tpe, Name: id.String()} diff --git a/internal/backend/test/tests.go b/internal/backend/test/tests.go index 00c678942..6aabaf832 100644 --- a/internal/backend/test/tests.go +++ b/internal/backend/test/tests.go @@ -143,7 +143,7 @@ func (s *Suite[C]) TestLoad(t *testing.T) { test.Assert(t, b.IsNotExist(err), "IsNotExist() did not recognize non-existing blob: %v", err) test.Assert(t, b.IsPermanentError(err), "IsPermanentError() did not recognize non-existing blob: %v", err) - length := random.Intn(1<<24) + 2000 + length := random.Intn(1<<20) + 2000 data := test.Random(23, length) id := restic.Hash(data) From b24d05bcb4271f2e3770ea0d1e4f4cdaa8e511bf Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Mon, 15 Jun 2026 21:05:56 +0200 Subject: [PATCH 13/24] skip key decryption in tests --- internal/checker/checker_test.go | 8 ++++++++ internal/repository/checker_test.go | 1 + internal/repository/key.go | 12 ++++++++++++ internal/repository/repository_test.go | 2 ++ internal/repository/testing.go | 10 ++++++++++ 5 files changed, 33 insertions(+) diff --git a/internal/checker/checker_test.go b/internal/checker/checker_test.go index 58dc7c744..bdc2f9f77 100644 --- a/internal/checker/checker_test.go +++ b/internal/checker/checker_test.go @@ -72,6 +72,7 @@ func assertOnlyMixedPackHints(t *testing.T, hints []error) { } func TestCheckRepo(t *testing.T) { + repository.TestInjectKey(t, restic.TestParseID("7bb3065bfb17da7430dc4dde4741d6db3dd83fdb0829500cf105755e067f879a"), `{"mac":{"k":"W1Y8bmQNJg6TAmuDt7lbpQ==","r":"r43DBmAdmwtQneoBTGAABQ=="},"encrypt":"JuZGBs6joRiLzqkyMWhmbZMLHe8+5oH6MDE5I6M8R/I="}`) repo, _ := repository.TestFromFixture(t, checkerTestData) chkr := checker.New(repo, false) @@ -89,6 +90,7 @@ func TestCheckRepo(t *testing.T) { } func TestMissingPack(t *testing.T) { + repository.TestInjectKey(t, restic.TestParseID("7bb3065bfb17da7430dc4dde4741d6db3dd83fdb0829500cf105755e067f879a"), `{"mac":{"k":"W1Y8bmQNJg6TAmuDt7lbpQ==","r":"r43DBmAdmwtQneoBTGAABQ=="},"encrypt":"JuZGBs6joRiLzqkyMWhmbZMLHe8+5oH6MDE5I6M8R/I="}`) repo, be := repository.TestFromFixture(t, checkerTestData) packID := restic.TestParseID("657f7fb64f6a854fff6fe9279998ee09034901eded4e6db9bcee0e59745bbce6") @@ -114,6 +116,7 @@ func TestMissingPack(t *testing.T) { } func TestUnreferencedPack(t *testing.T) { + repository.TestInjectKey(t, restic.TestParseID("7bb3065bfb17da7430dc4dde4741d6db3dd83fdb0829500cf105755e067f879a"), `{"mac":{"k":"W1Y8bmQNJg6TAmuDt7lbpQ==","r":"r43DBmAdmwtQneoBTGAABQ=="},"encrypt":"JuZGBs6joRiLzqkyMWhmbZMLHe8+5oH6MDE5I6M8R/I="}`) repo, be := repository.TestFromFixture(t, checkerTestData) // index 3f1a only references pack 60e0 @@ -141,6 +144,7 @@ func TestUnreferencedPack(t *testing.T) { } func TestUnreferencedBlobs(t *testing.T) { + repository.TestInjectKey(t, restic.TestParseID("7bb3065bfb17da7430dc4dde4741d6db3dd83fdb0829500cf105755e067f879a"), `{"mac":{"k":"W1Y8bmQNJg6TAmuDt7lbpQ==","r":"r43DBmAdmwtQneoBTGAABQ=="},"encrypt":"JuZGBs6joRiLzqkyMWhmbZMLHe8+5oH6MDE5I6M8R/I="}`) repo, be := repository.TestFromFixture(t, checkerTestData) snapshotID := restic.TestParseID("51d249d28815200d59e4be7b3f21a157b864dc343353df9d8e498220c2499b02") @@ -175,6 +179,7 @@ func TestUnreferencedBlobs(t *testing.T) { } func TestModifiedIndex(t *testing.T) { + repository.TestInjectKey(t, restic.TestParseID("7bb3065bfb17da7430dc4dde4741d6db3dd83fdb0829500cf105755e067f879a"), `{"mac":{"k":"W1Y8bmQNJg6TAmuDt7lbpQ==","r":"r43DBmAdmwtQneoBTGAABQ=="},"encrypt":"JuZGBs6joRiLzqkyMWhmbZMLHe8+5oH6MDE5I6M8R/I="}`) repo, be := repository.TestFromFixture(t, checkerTestData) done := make(chan struct{}) @@ -214,6 +219,7 @@ func TestModifiedIndex(t *testing.T) { var checkerDuplicateIndexTestData = filepath.Join("testdata", "duplicate-packs-in-index-test-repo.tar.gz") func TestDuplicatePacksInIndex(t *testing.T) { + repository.TestInjectKey(t, restic.TestParseID("b9883c60bed42db51be171ca52f055104b6ea7cfa2bc381c05b2b1f78231280c"), `{"mac":{"k":"maQ4ILA872XnDxHVEno94A==","r":"OptMBABwkgIsMQcHME8cBw=="},"encrypt":"janrR1efN7HyQ8kOZ9zhHixooZ/e+WelH0mT4v9WskQ="}`) repo, _ := repository.TestFromFixture(t, checkerDuplicateIndexTestData) chkr := checker.New(repo, false) @@ -413,6 +419,7 @@ func (r *loadTreesOnceRepository) LoadBlob(ctx context.Context, bh restic.BlobHa } func TestCheckerNoDuplicateTreeDecodes(t *testing.T) { + repository.TestInjectKey(t, restic.TestParseID("7bb3065bfb17da7430dc4dde4741d6db3dd83fdb0829500cf105755e067f879a"), `{"mac":{"k":"W1Y8bmQNJg6TAmuDt7lbpQ==","r":"r43DBmAdmwtQneoBTGAABQ=="},"encrypt":"JuZGBs6joRiLzqkyMWhmbZMLHe8+5oH6MDE5I6M8R/I="}`) repo, _ := repository.TestFromFixture(t, checkerTestData) checkRepo := &loadTreesOnceRepository{ Repository: repo, @@ -562,6 +569,7 @@ func TestCheckerBlobTypeConfusion(t *testing.T) { } func loadBenchRepository(t *testing.B) (*checker.Checker, restic.Repository) { + repository.TestInjectKey(t, restic.TestParseID("7bb3065bfb17da7430dc4dde4741d6db3dd83fdb0829500cf105755e067f879a"), `{"mac":{"k":"W1Y8bmQNJg6TAmuDt7lbpQ==","r":"r43DBmAdmwtQneoBTGAABQ=="},"encrypt":"JuZGBs6joRiLzqkyMWhmbZMLHe8+5oH6MDE5I6M8R/I="}`) repo, _ := repository.TestFromFixture(t, checkerTestData) chkr := checker.New(repo, false) diff --git a/internal/repository/checker_test.go b/internal/repository/checker_test.go index 10fe94970..f344443ed 100644 --- a/internal/repository/checker_test.go +++ b/internal/repository/checker_test.go @@ -34,6 +34,7 @@ func testWrapCheckPack(ctx context.Context, t *testing.T, repo *Repository, // TestGapInBlobs creates a gap in the blob list by omitting the first entry before passing it to checkPack func TestGapInBlobs(t *testing.T) { + TestInjectKey(t, restic.TestParseID("7bb3065bfb17da7430dc4dde4741d6db3dd83fdb0829500cf105755e067f879a"), `{"mac":{"k":"W1Y8bmQNJg6TAmuDt7lbpQ==","r":"r43DBmAdmwtQneoBTGAABQ=="},"encrypt":"JuZGBs6joRiLzqkyMWhmbZMLHe8+5oH6MDE5I6M8R/I="}`) repo, _ := TestFromFixture(t, checkerTestData) err := repo.LoadIndex(context.TODO(), restic.NoopTerminalCounterFactory) diff --git a/internal/repository/key.go b/internal/repository/key.go index e5d1b0724..94a4184e6 100644 --- a/internal/repository/key.go +++ b/internal/repository/key.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "os/user" + "sync" "time" "github.com/restic/restic/internal/errors" @@ -47,6 +48,9 @@ type Key struct { // calibrated on the first run of AddKey(). var params *crypto.Params +// testKeyInjection is used to speed up tests by skipping the key decryption step. +var testKeyInjection = sync.Map{} + const ( // KDFTimeout specifies the maximum runtime for the KDF. KDFTimeout = 500 * time.Millisecond @@ -63,6 +67,14 @@ func createMasterKey(ctx context.Context, s *Repository, password string) (*Key, // openKey tries do decrypt the key specified by name with the given password. func openKey(ctx context.Context, s *Repository, id restic.ID, password string) (*Key, error) { + if key, ok := testKeyInjection.Load(id); ok { + return &Key{ + master: key.(*crypto.Key), + user: key.(*crypto.Key), // not correct but good enough for testing + id: id, + }, nil + } + k, err := LoadKey(ctx, s, id) if err != nil { debug.Log("LoadKey(%v) returned error %v", id.String(), err) diff --git a/internal/repository/repository_test.go b/internal/repository/repository_test.go index b830458e7..331cea1c8 100644 --- a/internal/repository/repository_test.go +++ b/internal/repository/repository_test.go @@ -319,6 +319,7 @@ func benchmarkLoadUnpacked(b *testing.B, version uint) { var repoFixture = filepath.Join("testdata", "test-repo.tar.gz") func TestRepositoryLoadIndex(t *testing.T) { + repository.TestInjectKey(t, restic.TestParseID("7bb3065bfb17da7430dc4dde4741d6db3dd83fdb0829500cf105755e067f879a"), `{"mac":{"k":"W1Y8bmQNJg6TAmuDt7lbpQ==","r":"r43DBmAdmwtQneoBTGAABQ=="},"encrypt":"JuZGBs6joRiLzqkyMWhmbZMLHe8+5oH6MDE5I6M8R/I="}`) repo, _ := repository.TestFromFixture(t, repoFixture) rtest.OK(t, repo.LoadIndex(context.TODO(), restic.NoopTerminalCounterFactory)) @@ -372,6 +373,7 @@ func (be *damageOnceBackend) Load(ctx context.Context, h backend.Handle, length } func TestRepositoryLoadUnpackedRetryBroken(t *testing.T) { + repository.TestInjectKey(t, restic.TestParseID("7bb3065bfb17da7430dc4dde4741d6db3dd83fdb0829500cf105755e067f879a"), `{"mac":{"k":"W1Y8bmQNJg6TAmuDt7lbpQ==","r":"r43DBmAdmwtQneoBTGAABQ=="},"encrypt":"JuZGBs6joRiLzqkyMWhmbZMLHe8+5oH6MDE5I6M8R/I="}`) repodir := rtest.Env(t, repoFixture) be, err := local.Open(context.TODO(), local.Config{Path: repodir, Connections: 2}, t.Logf) diff --git a/internal/repository/testing.go b/internal/repository/testing.go index 27dd98f2f..976ae3b8a 100644 --- a/internal/repository/testing.go +++ b/internal/repository/testing.go @@ -2,6 +2,7 @@ package repository import ( "context" + "encoding/json" "fmt" "os" "sync" @@ -193,3 +194,12 @@ func TestCheckRepo(t testing.TB, repo *Repository) { t.Error(err) } } + +func TestInjectKey(t testing.TB, keyID restic.ID, key string) { + var k crypto.Key + err := json.Unmarshal([]byte(key), &k) + if err != nil { + t.Fatal(err) + } + testKeyInjection.Store(keyID, &k) +} From a3be3bd9e10a798e98ee813aa7e728d9b03997d5 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Mon, 15 Jun 2026 21:18:30 +0200 Subject: [PATCH 14/24] backend/test: gradually increase timeout for TestListCancel to speed up fast backends --- internal/backend/test/tests.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/internal/backend/test/tests.go b/internal/backend/test/tests.go index 6aabaf832..4999bddd9 100644 --- a/internal/backend/test/tests.go +++ b/internal/backend/test/tests.go @@ -426,10 +426,7 @@ func (s *Suite[C]) TestListCancel(t *testing.T) { } }) - t.Run("Timeout", func(t *testing.T) { - // rather large timeout, let's try to get at least one item - timeout := time.Second - + testTimeout := func(timeout time.Duration) error { ctxTimeout, cancel := context.WithTimeout(context.TODO(), timeout) defer cancel() @@ -449,11 +446,28 @@ func (s *Suite[C]) TestListCancel(t *testing.T) { }) if !errors.Is(err, context.DeadlineExceeded) { - t.Fatalf("expected error not found, want %#v, got %#v", context.DeadlineExceeded, err) + return errors.Errorf("expected error not found, want %#v, got %#v", context.DeadlineExceeded, err) } if i > 2 { - t.Fatalf("wrong number of files returned by List, want <= 2, got %v", i) + return errors.Errorf("wrong number of files returned by List, want <= 2, got %v", i) + } + + return nil + } + + t.Run("Timeout", func(t *testing.T) { + // try short timeouts first to speed up tests for fast backends + var err error + for _, timeout := range []time.Duration{10 * time.Millisecond, 100 * time.Millisecond, 1 * time.Second} { + err = testTimeout(timeout) + if err == nil { + break + } + } + // fails if last attempt also did not succeed + if err != nil { + t.Fatal(err) } }) From a1420ea6c76a1d3927b10060b18a50244b24a23b Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Mon, 15 Jun 2026 22:43:05 +0200 Subject: [PATCH 15/24] repository: use separate rand instance per test Use separate instances to prevent data races when executing tests in parallel. --- internal/repository/repository_test.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/repository/repository_test.go b/internal/repository/repository_test.go index 331cea1c8..f84d4214a 100644 --- a/internal/repository/repository_test.go +++ b/internal/repository/repository_test.go @@ -28,8 +28,6 @@ import ( var testSizes = []int{5, 23, 2<<18 + 23, 1 << 20} -var rnd = rand.New(rand.NewSource(time.Now().UnixNano())) - func TestSave(t *testing.T) { repository.TestAllVersions(t, testSavePassID) repository.TestAllVersions(t, testSaveCalculateID) @@ -45,6 +43,7 @@ func testSaveCalculateID(t *testing.T, version uint) { func testSave(t *testing.T, version uint, calculateID bool) { repo, _, _ := repository.TestRepositoryWithVersion(t, version) + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) for _, size := range testSizes { data := make([]byte, size) @@ -119,6 +118,7 @@ func testSavePackMerging(t *testing.T, targetPercentage int, expectedPacks int) // minimum pack size to speed up test PackSize: repository.MinPackSize, }) + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) var ids restic.IDs rtest.OK(t, repo.WithBlobUploader(context.TODO(), func(ctx context.Context, uploader restic.BlobSaverWithAsync) error { @@ -162,6 +162,7 @@ func benchmarkSaveAndEncrypt(t *testing.B, version uint) { size := 4 << 20 // 4MiB data := make([]byte, size) + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) _, err := io.ReadFull(rnd, data) rtest.OK(t, err) @@ -188,6 +189,7 @@ func testLoadBlob(t *testing.T, version uint) { repo, _, _ := repository.TestRepositoryWithVersion(t, version) length := 1000000 buf := crypto.NewBlobBuffer(length) + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) _, err := io.ReadFull(rnd, buf) rtest.OK(t, err) @@ -245,6 +247,7 @@ func benchmarkLoadBlob(b *testing.B, version uint) { repo, _, _ := repository.TestRepositoryWithVersion(b, version) length := 1000000 buf := crypto.NewBlobBuffer(length) + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) _, err := io.ReadFull(rnd, buf) rtest.OK(b, err) @@ -286,6 +289,7 @@ func benchmarkLoadUnpacked(b *testing.B, version uint) { repo, _, _ := repository.TestRepositoryWithVersion(b, version) length := 1000000 buf := crypto.NewBlobBuffer(length) + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) _, err := io.ReadFull(rnd, buf) rtest.OK(b, err) @@ -385,9 +389,10 @@ func TestRepositoryLoadUnpackedRetryBroken(t *testing.T) { // saveRandomDataBlobs generates random data blobs and saves them to the repository. func saveRandomDataBlobs(t testing.TB, repo restic.Repository, num int, sizeMax int) { + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) rtest.OK(t, repo.WithBlobUploader(context.TODO(), func(ctx context.Context, uploader restic.BlobSaverWithAsync) error { for i := 0; i < num; i++ { - size := rand.Int() % sizeMax + size := rnd.Int() % sizeMax buf := make([]byte, size) _, err := io.ReadFull(rnd, buf) From 072305c5b3cd02f14145bb30862458a8bd49f235 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Mon, 15 Jun 2026 22:31:11 +0200 Subject: [PATCH 16/24] repository: run version sub-tests of TestAllVersions in parallel TestRepositoryIncrementalIndex no longer modified index.Full as this modified global state and also had no effect on the test result. --- internal/repository/repository_test.go | 3 --- internal/repository/testing.go | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/repository/repository_test.go b/internal/repository/repository_test.go index f84d4214a..7df317752 100644 --- a/internal/repository/repository_test.go +++ b/internal/repository/repository_test.go @@ -412,8 +412,6 @@ func TestRepositoryIncrementalIndex(t *testing.T) { func testRepositoryIncrementalIndex(t *testing.T, version uint) { repo, _, _ := repository.TestRepositoryWithVersion(t, version) - index.Full = func(*index.Index) bool { return true } - // add a few rounds of packs for j := 0; j < 5; j++ { // add some packs and write index @@ -445,7 +443,6 @@ func testRepositoryIncrementalIndex(t *testing.T, version uint) { t.Errorf("pack %v listed in %d indexes\n", packID, len(ids)) } } - } func TestInvalidCompression(t *testing.T) { diff --git a/internal/repository/testing.go b/internal/repository/testing.go index 976ae3b8a..d44586e44 100644 --- a/internal/repository/testing.go +++ b/internal/repository/testing.go @@ -148,6 +148,7 @@ type VersionedTest func(t *testing.T, version uint) func TestAllVersions(t *testing.T, test VersionedTest) { for version := restic.MinRepoVersion; version <= restic.MaxRepoVersion; version++ { t.Run(fmt.Sprintf("v%d", version), func(t *testing.T) { + t.Parallel() test(t, uint(version)) }) } From f394723aa2430dc070556abfebb558d27436dc4f Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Mon, 15 Jun 2026 22:55:41 +0200 Subject: [PATCH 17/24] inject crypto keys for integration tests --- cmd/restic/cmd_repair_index_integration_test.go | 4 ++++ cmd/restic/cmd_restore_integration_test.go | 2 ++ cmd/restic/integration_test.go | 2 ++ 3 files changed, 8 insertions(+) diff --git a/cmd/restic/cmd_repair_index_integration_test.go b/cmd/restic/cmd_repair_index_integration_test.go index 1b10f88fd..52dc47315 100644 --- a/cmd/restic/cmd_repair_index_integration_test.go +++ b/cmd/restic/cmd_repair_index_integration_test.go @@ -11,6 +11,8 @@ import ( "github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/global" + "github.com/restic/restic/internal/repository" + "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" ) @@ -22,6 +24,7 @@ func testRunRebuildIndex(t testing.TB, gopts global.Options) { } func testRebuildIndex(t *testing.T, backendTestHook global.BackendWrapper) { + repository.TestInjectKey(t, restic.TestParseID("b9883c60bed42db51be171ca52f055104b6ea7cfa2bc381c05b2b1f78231280c"), `{"mac":{"k":"maQ4ILA872XnDxHVEno94A==","r":"OptMBABwkgIsMQcHME8cBw=="},"encrypt":"janrR1efN7HyQ8kOZ9zhHixooZ/e+WelH0mT4v9WskQ="}`) env, cleanup := withTestEnvironment(t) defer cleanup() @@ -109,6 +112,7 @@ func (b *appendOnlyBackend) Remove(_ context.Context, h backend.Handle) error { } func TestRebuildIndexFailsOnAppendOnly(t *testing.T) { + repository.TestInjectKey(t, restic.TestParseID("b9883c60bed42db51be171ca52f055104b6ea7cfa2bc381c05b2b1f78231280c"), `{"mac":{"k":"maQ4ILA872XnDxHVEno94A==","r":"OptMBABwkgIsMQcHME8cBw=="},"encrypt":"janrR1efN7HyQ8kOZ9zhHixooZ/e+WelH0mT4v9WskQ="}`) env, cleanup := withTestEnvironment(t) defer cleanup() diff --git a/cmd/restic/cmd_restore_integration_test.go b/cmd/restic/cmd_restore_integration_test.go index 47d611d8b..303b4e7ca 100644 --- a/cmd/restic/cmd_restore_integration_test.go +++ b/cmd/restic/cmd_restore_integration_test.go @@ -13,6 +13,7 @@ import ( "github.com/restic/restic/internal/data" "github.com/restic/restic/internal/global" + "github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" ) @@ -329,6 +330,7 @@ func TestRestoreLatest(t *testing.T) { } func TestRestoreWithPermissionFailure(t *testing.T) { + repository.TestInjectKey(t, restic.TestParseID("18493b1f93ad90b6bce7ed3afa93395a1f90e981f04145c03e5958cafa2ee33b"), `{"mac":{"k":"7ftgSq7jNM2HiGCyY9TYrg==","r":"o+1bB0wApwqoG7oAXOLyDw=="},"encrypt":"i57gVfyYp9HmXjzE0dSZyrkp2FN9LE75uWFjOuWze1M="}`) env, cleanup := withTestEnvironment(t) defer cleanup() diff --git a/cmd/restic/integration_test.go b/cmd/restic/integration_test.go index 3bda0dc7d..f9a066ba3 100644 --- a/cmd/restic/integration_test.go +++ b/cmd/restic/integration_test.go @@ -12,12 +12,14 @@ import ( "github.com/restic/restic/internal/data" "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/global" + "github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" "github.com/restic/restic/internal/ui/progress" ) func TestCheckRestoreNoLock(t *testing.T) { + repository.TestInjectKey(t, restic.TestParseID("a19acdab068765b022ffb81cb5aac83c5de4bf4fbce0d26e9ade8e636c6ae49f"), `{"mac":{"k":"TbkpCBdNYAvAwb+64r8VGw==","r":"Q5V1CnAvBQREgJAOQD40Bw=="},"encrypt":"SjCkTpms+XOUJR5LSsy2G+uO9ngG7H0L+IVwPV4u70A="}`) env, cleanup := withTestEnvironment(t) defer cleanup() From 52cde35aea13b493471084bce7f1a48d62cca9a9 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Tue, 16 Jun 2026 19:38:00 +0200 Subject: [PATCH 18/24] try deleting a directory before resetting the readonly status in tests --- internal/test/helpers.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/test/helpers.go b/internal/test/helpers.go index 1ca5c151a..48fbf1259 100644 --- a/internal/test/helpers.go +++ b/internal/test/helpers.go @@ -195,8 +195,12 @@ func resetReadOnly(t testing.TB, dir string) { // afterwards uses os.RemoveAll() to remove the path. func RemoveAll(t testing.TB, path string) { t.Helper() - resetReadOnly(t, path) - err := os.RemoveAll(path) + var err error + err = os.RemoveAll(path) + if err != nil { + resetReadOnly(t, path) + err = os.RemoveAll(path) + } if errors.Is(err, os.ErrNotExist) { err = nil } From 71064fac107195087714b2cf8cc3bb5c96bf8e77 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sat, 20 Jun 2026 19:34:44 +0200 Subject: [PATCH 19/24] mount: decrease sleep to speed up test --- cmd/restic/cmd_mount_integration_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/restic/cmd_mount_integration_test.go b/cmd/restic/cmd_mount_integration_test.go index 6aa3b8650..11aad54ed 100644 --- a/cmd/restic/cmd_mount_integration_test.go +++ b/cmd/restic/cmd_mount_integration_test.go @@ -22,8 +22,8 @@ import ( ) const ( - mountWait = 20 - mountSleep = 100 * time.Millisecond + mountWait = 400 + mountSleep = 5 * time.Millisecond mountTestSubdir = "snapshots" ) From 5683224a3cdaa9efd8ee27321331e2f797b105cf Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sat, 20 Jun 2026 19:42:54 +0200 Subject: [PATCH 20/24] layout: skip creation of pack subdirectories in integration tests Each time a local backend is created, the local backend also creates all shard subdirectories. For the integration tests this has the downside that this results in ~120 (number of test) * 256 (number of directories) = 30k directories that are created unnecessarily. This significantly slows down test execution and cleanup. --- cmd/restic/cmd_init_integration_test.go | 1 + cmd/restic/integration_helpers_test.go | 2 ++ internal/backend/layout/layout_default.go | 14 ++++++++++---- internal/backend/layout/testing.go | 9 +++++++++ 4 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 internal/backend/layout/testing.go diff --git a/cmd/restic/cmd_init_integration_test.go b/cmd/restic/cmd_init_integration_test.go index d2e96a613..2bfd9c7fb 100644 --- a/cmd/restic/cmd_init_integration_test.go +++ b/cmd/restic/cmd_init_integration_test.go @@ -25,6 +25,7 @@ func testRunInit(t testing.TB, gopts global.Options) { // create temporary junk files to verify that restic does not trip over them for _, path := range []string{"index", "snapshots", "keys", "locks", filepath.Join("data", "00")} { + rtest.OK(t, os.MkdirAll(filepath.Join(gopts.Repo, path), 0700)) rtest.OK(t, os.WriteFile(filepath.Join(gopts.Repo, path, "tmp12345"), []byte("junk file"), 0o600)) } } diff --git a/cmd/restic/integration_helpers_test.go b/cmd/restic/integration_helpers_test.go index 614fa916f..54ad884d6 100644 --- a/cmd/restic/integration_helpers_test.go +++ b/cmd/restic/integration_helpers_test.go @@ -14,6 +14,7 @@ import ( "github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/backend/all" + "github.com/restic/restic/internal/backend/layout" "github.com/restic/restic/internal/backend/retry" "github.com/restic/restic/internal/data" "github.com/restic/restic/internal/errors" @@ -192,6 +193,7 @@ func withTestEnvironment(t testing.TB) (env *testEnvironment, cleanup func()) { repository.TestUseLowSecurityKDFParameters(t) restic.TestDisableCheckPolynomial(t) retry.TestFastRetries(t) + layout.TestDisablePackSubdirs(t) tempdir, err := os.MkdirTemp(rtest.TestTempDir, "restic-test-") rtest.OK(t, err) diff --git a/internal/backend/layout/layout_default.go b/internal/backend/layout/layout_default.go index d2c4634d3..6566da5a7 100644 --- a/internal/backend/layout/layout_default.go +++ b/internal/backend/layout/layout_default.go @@ -6,6 +6,10 @@ import ( "github.com/restic/restic/internal/backend" ) +// disablePackSubdirs is used to disable the creation of pack subdirectories. +// Only used for testing. +var disablePackSubdirs = false + // DefaultLayout implements the default layout for local and sftp backends, as // described in the Design document. The `data` directory has one level of // subdirs, two characters each (taken from the first two characters of the @@ -66,10 +70,12 @@ func (l *DefaultLayout) Paths() (dirs []string) { dirs = append(dirs, l.join(l.path, p)) } - // also add subdirs - for i := 0; i < 256; i++ { - subdir := hex.EncodeToString([]byte{byte(i)}) - dirs = append(dirs, l.join(l.path, defaultLayoutPaths[backend.PackFile], subdir)) + if !disablePackSubdirs { + // also add subdirs + for i := 0; i < 256; i++ { + subdir := hex.EncodeToString([]byte{byte(i)}) + dirs = append(dirs, l.join(l.path, defaultLayoutPaths[backend.PackFile], subdir)) + } } return dirs diff --git a/internal/backend/layout/testing.go b/internal/backend/layout/testing.go new file mode 100644 index 000000000..c69932776 --- /dev/null +++ b/internal/backend/layout/testing.go @@ -0,0 +1,9 @@ +package layout + +import ( + "testing" +) + +func TestDisablePackSubdirs(t testing.TB) { + disablePackSubdirs = true +} From d5267f03e078c281ce066b0402e7908edcca1080 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sat, 20 Jun 2026 19:46:11 +0200 Subject: [PATCH 21/24] use fastest compression in integration tests --- cmd/restic/cmd_backup_integration_test.go | 4 ++++ cmd/restic/integration_helpers_test.go | 11 ++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cmd/restic/cmd_backup_integration_test.go b/cmd/restic/cmd_backup_integration_test.go index 840ef65b9..712476c3b 100644 --- a/cmd/restic/cmd_backup_integration_test.go +++ b/cmd/restic/cmd_backup_integration_test.go @@ -13,6 +13,7 @@ import ( "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/fs" "github.com/restic/restic/internal/global" + "github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" ) @@ -49,6 +50,9 @@ func testBackup(t *testing.T, useFsSnapshot bool) { env, cleanup := withTestEnvironment(t) defer cleanup() + // Use auto compression to ensure coverage in the integration tests + // All other tests use fastest compression for faster execution + env.gopts.Compression = repository.CompressionAuto testSetupBackupData(t, env) opts := BackupOptions{UseFsSnapshot: useFsSnapshot} diff --git a/cmd/restic/integration_helpers_test.go b/cmd/restic/integration_helpers_test.go index 54ad884d6..c9b287415 100644 --- a/cmd/restic/integration_helpers_test.go +++ b/cmd/restic/integration_helpers_test.go @@ -212,11 +212,12 @@ func withTestEnvironment(t testing.TB) (env *testEnvironment, cleanup func()) { rtest.OK(t, os.MkdirAll(env.repo, 0700)) env.gopts = global.Options{ - Repo: env.repo, - Quiet: true, - CacheDir: env.cache, - Password: rtest.TestPassword, - Extended: make(options.Options), + Repo: env.repo, + Quiet: true, + CacheDir: env.cache, + Password: rtest.TestPassword, + Extended: make(options.Options), + Compression: repository.CompressionFastest, // replace this hook with "nil" if listing a filetype more than once is necessary BackendTestHook: func(r backend.Backend) (backend.Backend, error) { return newOrderedListOnceBackend(r), nil }, From 8d191c19a50a4e8bc91217b15613e628b74725c9 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sat, 20 Jun 2026 21:55:46 +0200 Subject: [PATCH 22/24] archiver: decrease sleeps for TestFileChanged test Modern filesystems provide microsecond granularity or better. The old special case for macOS is removed as Go 1.25 (minimum for restic) requires at least macOS 12, which uses APFS instead of the old HFS+. --- internal/archiver/archiver_test.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/internal/archiver/archiver_test.go b/internal/archiver/archiver_test.go index 76a9f2488..e856fa2b9 100644 --- a/internal/archiver/archiver_test.go +++ b/internal/archiver/archiver_test.go @@ -572,13 +572,7 @@ func nodeFromFile(t testing.TB, localFs fs.FS, filename string) *data.Node { // sleep sleeps long enough to ensure a timestamp change. func sleep() { - d := 50 * time.Millisecond - if runtime.GOOS == "darwin" { - // On older Darwin instances, the file system only supports one second - // granularity. - d = 1500 * time.Millisecond - } - time.Sleep(d) + time.Sleep(5 * time.Millisecond) } func TestFileChanged(t *testing.T) { From c6a064b68bae3bfd294c6354924b0cee154590b6 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sat, 20 Jun 2026 22:04:06 +0200 Subject: [PATCH 23/24] mount: disable debug log for test I don't recall any recent problems with that test. --- cmd/restic/cmd_mount_integration_test.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cmd/restic/cmd_mount_integration_test.go b/cmd/restic/cmd_mount_integration_test.go index 11aad54ed..5aff2ef92 100644 --- a/cmd/restic/cmd_mount_integration_test.go +++ b/cmd/restic/cmd_mount_integration_test.go @@ -14,7 +14,6 @@ import ( systemFuse "github.com/anacrolix/fuse" "github.com/restic/restic/internal/data" - "github.com/restic/restic/internal/debug" "github.com/restic/restic/internal/global" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" @@ -274,11 +273,6 @@ func TestMountSameTimestamps(t *testing.T) { t.Skip("Skipping fuse tests") } - debugEnabled := debug.TestLogToStderr(t) - if debugEnabled { - defer debug.TestDisableLog(t) - } - env, cleanup := withTestEnvironment(t) // must list snapshots more than once env.gopts.BackendTestHook = nil From 8f00b335b4463b3337fdd1b1f353bfd4832fc610 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sat, 20 Jun 2026 22:30:13 +0200 Subject: [PATCH 24/24] rclone/fs: parallelize tests --- internal/backend/rclone/backend_test.go | 1 + internal/backend/rclone/internal_test.go | 1 + internal/fs/node_windows_test.go | 1 + 3 files changed, 3 insertions(+) diff --git a/internal/backend/rclone/backend_test.go b/internal/backend/rclone/backend_test.go index 742031585..bde6ac024 100644 --- a/internal/backend/rclone/backend_test.go +++ b/internal/backend/rclone/backend_test.go @@ -34,6 +34,7 @@ func findRclone(t testing.TB) { } func TestBackendRclone(t *testing.T) { + t.Parallel() defer func() { if t.Skipped() { rtest.SkipDisallowed(t, "restic/backend/rclone.TestBackendRclone") diff --git a/internal/backend/rclone/internal_test.go b/internal/backend/rclone/internal_test.go index 8da8dcd6f..f6b7336fb 100644 --- a/internal/backend/rclone/internal_test.go +++ b/internal/backend/rclone/internal_test.go @@ -12,6 +12,7 @@ import ( // restic should detect rclone exiting. func TestRcloneExit(t *testing.T) { + t.Parallel() dir := rtest.TempDir(t) cfg := NewConfig() cfg.Remote = dir diff --git a/internal/fs/node_windows_test.go b/internal/fs/node_windows_test.go index 5836dc382..174c637bd 100644 --- a/internal/fs/node_windows_test.go +++ b/internal/fs/node_windows_test.go @@ -652,6 +652,7 @@ func TestPrepareVolumeName(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + t.Parallel() isEASupported, err := checkAndStoreEASupport(tc.path) test.OK(t, err) test.Equals(t, tc.expectedEASupported, isEASupported)