mirror of
https://github.com/kopia/kopia.git
synced 2026-05-19 04:04:56 -04:00
* fix(repository): fixed handling of content.Info Previously content.Info was an interface which was implemented by: * index.InfoStruct * index.indexEntryInfoV1 * index.indexEntryInfoV2 The last 2 implementations were relying on memory-mapped files which in rare cases could be closed while Kopia was still processing them leading to #2599. This changes fixes the bug and strictly separates content.Info (which is now always a struct) from the other two (which were renamed as index.InfoReader and only used inside repo/content/...). In addition to being safer, this _should_ reduce memory allocations. * reduce the size of content.Info with proper alignment. * pr feedback * renamed index.InfoStruct to index.Info
136 lines
3.7 KiB
Go
136 lines
3.7 KiB
Go
package content
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/kopia/kopia/internal/faketime"
|
|
"github.com/kopia/kopia/internal/gather"
|
|
"github.com/kopia/kopia/internal/testlogging"
|
|
"github.com/kopia/kopia/internal/testutil"
|
|
"github.com/kopia/kopia/repo/blob"
|
|
"github.com/kopia/kopia/repo/content/index"
|
|
)
|
|
|
|
func TestCommittedContentIndexCache_Disk(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ta := faketime.NewClockTimeWithOffset(0)
|
|
|
|
testCache(t, &diskCommittedContentIndexCache{testutil.TempDirectory(t), ta.NowFunc(), func() int { return 3 }, testlogging.Printf(t.Logf, ""), DefaultIndexCacheSweepAge}, ta)
|
|
}
|
|
|
|
func TestCommittedContentIndexCache_Memory(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCache(t, &memoryCommittedContentIndexCache{
|
|
contents: map[blob.ID]index.Index{},
|
|
v1PerContentOverhead: func() int { return 3 },
|
|
}, nil)
|
|
}
|
|
|
|
//nolint:thelper
|
|
func testCache(t *testing.T, cache committedContentIndexCache, fakeTime *faketime.ClockTimeWithOffset) {
|
|
ctx := testlogging.Context(t)
|
|
|
|
has, err := cache.hasIndexBlobID(ctx, "ndx1")
|
|
require.NoError(t, err)
|
|
|
|
if has {
|
|
t.Fatal("hasIndexBlobID invalid response, expected false")
|
|
}
|
|
|
|
if _, err = cache.openIndex(ctx, "ndx1"); err == nil {
|
|
t.Fatal("openIndex unexpectedly succeeded")
|
|
}
|
|
|
|
require.NoError(t, cache.addContentToCache(ctx, "ndx1", mustBuildIndex(t, index.Builder{
|
|
mustParseID(t, "c1"): Info{PackBlobID: "p1234", ContentID: mustParseID(t, "c1")},
|
|
mustParseID(t, "c2"): Info{PackBlobID: "p1234", ContentID: mustParseID(t, "c2")},
|
|
})))
|
|
|
|
has, err = cache.hasIndexBlobID(ctx, "ndx1")
|
|
require.NoError(t, err)
|
|
|
|
if !has {
|
|
t.Fatal("hasIndexBlobID invalid response, expected true")
|
|
}
|
|
|
|
require.NoError(t, cache.addContentToCache(ctx, "ndx2", mustBuildIndex(t, index.Builder{
|
|
mustParseID(t, "c3"): Info{PackBlobID: "p2345", ContentID: mustParseID(t, "c3")},
|
|
mustParseID(t, "c4"): Info{PackBlobID: "p2345", ContentID: mustParseID(t, "c4")},
|
|
})))
|
|
|
|
require.NoError(t, cache.addContentToCache(ctx, "ndx2", mustBuildIndex(t, index.Builder{
|
|
mustParseID(t, "c3"): Info{PackBlobID: "p2345", ContentID: mustParseID(t, "c3")},
|
|
mustParseID(t, "c4"): Info{PackBlobID: "p2345", ContentID: mustParseID(t, "c4")},
|
|
})))
|
|
|
|
ndx1, err := cache.openIndex(ctx, "ndx1")
|
|
require.NoError(t, err)
|
|
|
|
ndx2, err := cache.openIndex(ctx, "ndx2")
|
|
require.NoError(t, err)
|
|
|
|
i, err := ndx1.GetInfo(mustParseID(t, "c1"))
|
|
require.NoError(t, err)
|
|
|
|
if got, want := i.GetPackBlobID(), blob.ID("p1234"); got != want {
|
|
t.Fatalf("unexpected pack blob ID: %v, want %v", got, want)
|
|
}
|
|
|
|
require.NoError(t, ndx1.Close())
|
|
|
|
i, err = ndx2.GetInfo(mustParseID(t, "c3"))
|
|
require.NoError(t, err)
|
|
|
|
if got, want := i.GetPackBlobID(), blob.ID("p2345"); got != want {
|
|
t.Fatalf("unexpected pack blob ID: %v, want %v", got, want)
|
|
}
|
|
|
|
// expire leaving only ndx2, this will actually not do anything for disk cache
|
|
// because it relies on passage of time for safety, we'll re-do it in a sec.
|
|
require.NoError(t, cache.expireUnused(ctx, []blob.ID{"ndx2"}))
|
|
|
|
if fakeTime != nil {
|
|
fakeTime.Advance(2 * time.Hour)
|
|
}
|
|
|
|
// expire leaving only ndx2
|
|
require.NoError(t, cache.expireUnused(ctx, []blob.ID{"ndx2"}))
|
|
|
|
has, err = cache.hasIndexBlobID(ctx, "ndx1")
|
|
require.NoError(t, err)
|
|
|
|
if has {
|
|
t.Fatal("hasIndexBlobID invalid response, expected false")
|
|
}
|
|
|
|
if _, err = cache.openIndex(ctx, "ndx1"); err == nil {
|
|
t.Fatal("openIndex unexpectedly succeeded")
|
|
}
|
|
}
|
|
|
|
func mustBuildIndex(t *testing.T, b index.Builder) gather.Bytes {
|
|
t.Helper()
|
|
|
|
var buf bytes.Buffer
|
|
if err := b.Build(&buf, index.Version2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return gather.FromSlice(buf.Bytes())
|
|
}
|
|
|
|
func mustParseID(t *testing.T, s string) ID {
|
|
t.Helper()
|
|
|
|
id, err := index.ParseID(s)
|
|
require.NoError(t, err)
|
|
|
|
return id
|
|
}
|