mirror of
https://github.com/kopia/kopia.git
synced 2026-10-22 14:15:03 -04:00
snapshot: added tests
This commit is contained in:
1 parent
c999c0fba8
commit
3a4b581814
7 files changed
+427
-159
No files matched your search
@@ -56,6 +56,9 @@ dev-deps:
|
||||
test-with-coverage: install
|
||||
go test -count=1 -coverprofile=tmp.cov --coverpkg $(COVERAGE_PACKAGES) -timeout 90s github.com/kopia/kopia/...
|
||||
|
||||
test-with-coverage-pkgonly: install
|
||||
go test -count=1 -coverprofile=tmp.cov -timeout 90s github.com/kopia/kopia/...
|
||||
|
||||
test: install
|
||||
go test -count=1 -timeout 90s github.com/kopia/kopia/...
|
||||
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// Package repotesting contains test utilities for working with repositories.
|
||||
package repotesting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/kopia/kopia/repo"
|
||||
"github.com/kopia/kopia/repo/storage"
|
||||
"github.com/kopia/kopia/repo/storage/filesystem"
|
||||
)
|
||||
|
||||
const masterPassword = "foobarbazfoobarbaz"
|
||||
|
||||
// Environment encapsulates details of a test environment.
|
||||
type Environment struct {
|
||||
Repository *repo.Repository
|
||||
|
||||
configDir string
|
||||
storageDir string
|
||||
}
|
||||
|
||||
// Setup sets up a test environment.
|
||||
func (e *Environment) Setup(t *testing.T, opts ...func(*repo.NewRepositoryOptions)) *Environment {
|
||||
var err error
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
e.configDir, err = ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
e.storageDir, err = ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
opt := &repo.NewRepositoryOptions{
|
||||
MaxBlockSize: 400,
|
||||
Splitter: "FIXED",
|
||||
BlockFormat: "UNENCRYPTED_HMAC_SHA256",
|
||||
MetadataEncryptionAlgorithm: "NONE",
|
||||
ObjectHMACSecret: []byte{},
|
||||
}
|
||||
|
||||
for _, mod := range opts {
|
||||
mod(opt)
|
||||
}
|
||||
|
||||
st, err := filesystem.New(ctx, &filesystem.Options{
|
||||
Path: e.storageDir,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if err = repo.Initialize(ctx, st, opt, masterPassword); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
connOpts := repo.ConnectOptions{
|
||||
//TraceStorage: log.Printf,
|
||||
}
|
||||
|
||||
if err = repo.Connect(ctx, e.configFile(), st, masterPassword, connOpts); err != nil {
|
||||
t.Fatalf("can't connect: %v", err)
|
||||
}
|
||||
|
||||
e.Repository, err = repo.Open(ctx, e.configFile(), masterPassword, &repo.Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("can't open: %v", err)
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// Close closes testing environment
|
||||
func (e *Environment) Close(t *testing.T) {
|
||||
if err := e.Repository.Close(context.Background()); err != nil {
|
||||
t.Fatalf("unable to close: %v", err)
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(e.configDir); err != nil {
|
||||
t.Errorf("error removing config directory: %v", err)
|
||||
}
|
||||
if err := os.RemoveAll(e.storageDir); err != nil {
|
||||
t.Errorf("error removing storage directory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Environment) configFile() string {
|
||||
return filepath.Join(e.configDir, "kopia.config")
|
||||
}
|
||||
|
||||
// MustReopen closes and reopens the repository.
|
||||
func (e *Environment) MustReopen(t *testing.T) {
|
||||
err := e.Repository.Close(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("close error: %v", err)
|
||||
}
|
||||
|
||||
e.Repository, err = repo.Open(context.Background(), e.configFile(), masterPassword, &repo.Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyStorageBlockCount verifies that the underlying storage contains the specified number of blocks.
|
||||
func (e *Environment) VerifyStorageBlockCount(t *testing.T, want int) {
|
||||
var got int
|
||||
|
||||
_ = e.Repository.Storage.ListBlocks(context.Background(), "", func(_ storage.BlockMetadata) error {
|
||||
got++
|
||||
return nil
|
||||
})
|
||||
|
||||
if got != want {
|
||||
t.Errorf("got unexpected number of storage blocks: %v, wanted %v", got, want)
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -33,6 +33,7 @@ type NewRepositoryOptions struct {
|
||||
|
||||
BlockFormat string // identifier of object format
|
||||
ObjectHMACSecret []byte // force the use of particular object HMAC secret
|
||||
DisableHMAC bool
|
||||
ObjectEncryptionKey []byte // force the use of particular object encryption key
|
||||
|
||||
Splitter string // splitter used to break objects into storage blocks
|
||||
@@ -40,8 +41,6 @@ type NewRepositoryOptions struct {
|
||||
AvgBlockSize int // approximate size of storage block (used with dynamic splitter)
|
||||
MaxBlockSize int // maximum size of storage block
|
||||
|
||||
// test-only
|
||||
noHMAC bool // disable HMAC
|
||||
}
|
||||
|
||||
// Initialize creates initial repository data structures in the specified storage with given credentials.
|
||||
@@ -102,7 +101,7 @@ func repositoryObjectFormatFromOptions(opt *NewRepositoryOptions) *config.Reposi
|
||||
AvgBlockSize: applyDefaultInt(opt.AvgBlockSize, 16<<20), // 16MiB
|
||||
}
|
||||
|
||||
if opt.noHMAC {
|
||||
if opt.DisableHMAC {
|
||||
f.HMACSecret = nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package repository
|
||||
|
||||
// dummy package
|
||||
@@ -1,60 +1,23 @@
|
||||
package repo
|
||||
package repository_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
cryptorand "crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kopia/kopia/internal/config"
|
||||
"github.com/kopia/kopia/internal/repotesting"
|
||||
"github.com/kopia/kopia/repo"
|
||||
"github.com/kopia/kopia/repo/block"
|
||||
"github.com/kopia/kopia/repo/internal/storagetesting"
|
||||
"github.com/kopia/kopia/repo/object"
|
||||
"github.com/kopia/kopia/repo/storage"
|
||||
)
|
||||
|
||||
const masterPassword = "foobarbazfoobarbaz"
|
||||
|
||||
func setupTest(t *testing.T, mods ...func(o *NewRepositoryOptions)) (map[string][]byte, map[string]time.Time, *Repository) {
|
||||
return setupTestWithData(t, map[string][]byte{}, map[string]time.Time{}, mods...)
|
||||
}
|
||||
|
||||
func setupTestWithData(t *testing.T, data map[string][]byte, keyTime map[string]time.Time, mods ...func(o *NewRepositoryOptions)) (map[string][]byte, map[string]time.Time, *Repository) {
|
||||
st := storagetesting.NewMapStorage(data, keyTime, nil)
|
||||
|
||||
opt := &NewRepositoryOptions{
|
||||
MaxBlockSize: 400,
|
||||
Splitter: "FIXED",
|
||||
BlockFormat: "UNENCRYPTED_HMAC_SHA256",
|
||||
MetadataEncryptionAlgorithm: "NONE",
|
||||
noHMAC: true,
|
||||
}
|
||||
|
||||
for _, m := range mods {
|
||||
m(opt)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
Initialize(ctx, st, opt, masterPassword)
|
||||
|
||||
r, err := connect(ctx, st, &config.LocalConfig{}, masterPassword, &Options{
|
||||
//TraceStorage: log.Printf,
|
||||
}, block.CachingOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("can't connect: %v", err)
|
||||
}
|
||||
|
||||
return data, keyTime, r
|
||||
}
|
||||
|
||||
func TestWriters(t *testing.T) {
|
||||
cases := []struct {
|
||||
data []byte
|
||||
@@ -70,10 +33,10 @@ func TestWriters(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, c := range cases {
|
||||
data, _, repo := setupTest(t)
|
||||
|
||||
writer := repo.Objects.NewWriter(ctx, object.WriterOptions{})
|
||||
var env repotesting.Environment
|
||||
defer env.Setup(t).Close(t)
|
||||
|
||||
writer := env.Repository.Objects.NewWriter(ctx, object.WriterOptions{})
|
||||
writer.Write(c.data)
|
||||
|
||||
result, err := writer.Result()
|
||||
@@ -82,48 +45,27 @@ func TestWriters(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
repo.Objects.Flush(ctx)
|
||||
env.Repository.Objects.Flush(ctx)
|
||||
|
||||
if !objectIDsEqual(result, c.objectID) {
|
||||
t.Errorf("incorrect result for %v, expected: %v got: %v", c.data, c.objectID.String(), result.String())
|
||||
}
|
||||
|
||||
repo.Blocks.Flush(ctx)
|
||||
|
||||
if got, want := len(data), 3; got != want {
|
||||
// 1 format block + 1 data block + 1 pack index block
|
||||
t.Errorf("unexpected data written to the storage (%v), wanted %v", got, want)
|
||||
dumpBlockManagerData(data)
|
||||
}
|
||||
env.Repository.Blocks.Flush(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func dumpBlockManagerData(data map[string][]byte) {
|
||||
for k, v := range data {
|
||||
if k[0] == 'P' {
|
||||
gz, _ := gzip.NewReader(bytes.NewReader(v))
|
||||
var buf bytes.Buffer
|
||||
buf.ReadFrom(gz)
|
||||
|
||||
var dst bytes.Buffer
|
||||
json.Indent(&dst, buf.Bytes(), "", " ")
|
||||
|
||||
log.Debugf("data[%v] = %v", k, dst.String())
|
||||
} else {
|
||||
log.Debugf("data[%v] = %x", k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
func objectIDsEqual(o1 object.ID, o2 object.ID) bool {
|
||||
return reflect.DeepEqual(o1, o2)
|
||||
}
|
||||
|
||||
func TestWriterCompleteChunkInTwoWrites(t *testing.T) {
|
||||
var env repotesting.Environment
|
||||
defer env.Setup(t).Close(t)
|
||||
ctx := context.Background()
|
||||
_, _, repo := setupTest(t)
|
||||
|
||||
bytes := make([]byte, 100)
|
||||
writer := repo.Objects.NewWriter(ctx, object.WriterOptions{})
|
||||
writer := env.Repository.Objects.NewWriter(ctx, object.WriterOptions{})
|
||||
writer.Write(bytes[0:50])
|
||||
writer.Write(bytes[0:50])
|
||||
result, err := writer.Result()
|
||||
@@ -133,28 +75,29 @@ func TestWriterCompleteChunkInTwoWrites(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPackingSimple(t *testing.T) {
|
||||
data, keyTime, repo := setupTest(t, func(n *NewRepositoryOptions) {
|
||||
})
|
||||
var env repotesting.Environment
|
||||
defer env.Setup(t).Close(t)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
content1 := "hello, how do you do?"
|
||||
content2 := "hi, how are you?"
|
||||
content3 := "thank you!"
|
||||
|
||||
oid1a := writeObject(ctx, t, repo, []byte(content1), "packed-object-1a")
|
||||
oid1b := writeObject(ctx, t, repo, []byte(content1), "packed-object-1b")
|
||||
oid2a := writeObject(ctx, t, repo, []byte(content2), "packed-object-2a")
|
||||
oid2b := writeObject(ctx, t, repo, []byte(content2), "packed-object-2b")
|
||||
oid1a := writeObject(ctx, t, env.Repository, []byte(content1), "packed-object-1a")
|
||||
oid1b := writeObject(ctx, t, env.Repository, []byte(content1), "packed-object-1b")
|
||||
oid2a := writeObject(ctx, t, env.Repository, []byte(content2), "packed-object-2a")
|
||||
oid2b := writeObject(ctx, t, env.Repository, []byte(content2), "packed-object-2b")
|
||||
|
||||
oid3a := writeObject(ctx, t, repo, []byte(content3), "packed-object-3a")
|
||||
oid3b := writeObject(ctx, t, repo, []byte(content3), "packed-object-3b")
|
||||
verify(ctx, t, repo, oid1a, []byte(content1), "packed-object-1")
|
||||
verify(ctx, t, repo, oid2a, []byte(content2), "packed-object-2")
|
||||
oid2c := writeObject(ctx, t, repo, []byte(content2), "packed-object-2c")
|
||||
oid1c := writeObject(ctx, t, repo, []byte(content1), "packed-object-1c")
|
||||
oid3a := writeObject(ctx, t, env.Repository, []byte(content3), "packed-object-3a")
|
||||
oid3b := writeObject(ctx, t, env.Repository, []byte(content3), "packed-object-3b")
|
||||
verify(ctx, t, env.Repository, oid1a, []byte(content1), "packed-object-1")
|
||||
verify(ctx, t, env.Repository, oid2a, []byte(content2), "packed-object-2")
|
||||
oid2c := writeObject(ctx, t, env.Repository, []byte(content2), "packed-object-2c")
|
||||
oid1c := writeObject(ctx, t, env.Repository, []byte(content1), "packed-object-1c")
|
||||
|
||||
repo.Objects.Flush(ctx)
|
||||
repo.Blocks.Flush(ctx)
|
||||
env.Repository.Objects.Flush(ctx)
|
||||
env.Repository.Blocks.Flush(ctx)
|
||||
|
||||
if got, want := oid1a.String(), oid1b.String(); got != want {
|
||||
t.Errorf("oid1a(%q) != oid1b(%q)", got, want)
|
||||
@@ -172,94 +115,68 @@ func TestPackingSimple(t *testing.T) {
|
||||
t.Errorf("oid3a(%q) != oid3b(%q)", got, want)
|
||||
}
|
||||
|
||||
// format + index + data
|
||||
if got, want := len(data), 3; got != want {
|
||||
t.Errorf("got unexpected repository contents %v items, wanted %v", got, want)
|
||||
}
|
||||
repo.Close(ctx)
|
||||
env.VerifyStorageBlockCount(t, 3)
|
||||
|
||||
data, _, repo = setupTestWithData(t, data, keyTime, func(n *NewRepositoryOptions) {
|
||||
})
|
||||
env.MustReopen(t)
|
||||
|
||||
verify(ctx, t, repo, oid1a, []byte(content1), "packed-object-1")
|
||||
verify(ctx, t, repo, oid2a, []byte(content2), "packed-object-2")
|
||||
verify(ctx, t, repo, oid3a, []byte(content3), "packed-object-3")
|
||||
verify(ctx, t, env.Repository, oid1a, []byte(content1), "packed-object-1")
|
||||
verify(ctx, t, env.Repository, oid2a, []byte(content2), "packed-object-2")
|
||||
verify(ctx, t, env.Repository, oid3a, []byte(content3), "packed-object-3")
|
||||
|
||||
if err := repo.Blocks.CompactIndexes(ctx, block.CompactOptions{MinSmallBlocks: 1, MaxSmallBlocks: 1}); err != nil {
|
||||
if err := env.Repository.Blocks.CompactIndexes(ctx, block.CompactOptions{MinSmallBlocks: 1, MaxSmallBlocks: 1}); err != nil {
|
||||
t.Errorf("optimize error: %v", err)
|
||||
}
|
||||
data, _, repo = setupTestWithData(t, data, keyTime, func(n *NewRepositoryOptions) {
|
||||
})
|
||||
|
||||
verify(ctx, t, repo, oid1a, []byte(content1), "packed-object-1")
|
||||
verify(ctx, t, repo, oid2a, []byte(content2), "packed-object-2")
|
||||
verify(ctx, t, repo, oid3a, []byte(content3), "packed-object-3")
|
||||
env.MustReopen(t)
|
||||
|
||||
if err := repo.Blocks.CompactIndexes(ctx, block.CompactOptions{MinSmallBlocks: 1, MaxSmallBlocks: 1}); err != nil {
|
||||
verify(ctx, t, env.Repository, oid1a, []byte(content1), "packed-object-1")
|
||||
verify(ctx, t, env.Repository, oid2a, []byte(content2), "packed-object-2")
|
||||
verify(ctx, t, env.Repository, oid3a, []byte(content3), "packed-object-3")
|
||||
|
||||
if err := env.Repository.Blocks.CompactIndexes(ctx, block.CompactOptions{MinSmallBlocks: 1, MaxSmallBlocks: 1}); err != nil {
|
||||
t.Errorf("optimize error: %v", err)
|
||||
}
|
||||
_, _, repo = setupTestWithData(t, data, keyTime, func(n *NewRepositoryOptions) {
|
||||
})
|
||||
|
||||
verify(ctx, t, repo, oid1a, []byte(content1), "packed-object-1")
|
||||
verify(ctx, t, repo, oid2a, []byte(content2), "packed-object-2")
|
||||
verify(ctx, t, repo, oid3a, []byte(content3), "packed-object-3")
|
||||
env.MustReopen(t)
|
||||
|
||||
verify(ctx, t, env.Repository, oid1a, []byte(content1), "packed-object-1")
|
||||
verify(ctx, t, env.Repository, oid2a, []byte(content2), "packed-object-2")
|
||||
verify(ctx, t, env.Repository, oid3a, []byte(content3), "packed-object-3")
|
||||
}
|
||||
|
||||
func TestHMAC(t *testing.T) {
|
||||
content := bytes.Repeat([]byte{0xcd}, 50)
|
||||
|
||||
_, _, repo := setupTest(t)
|
||||
|
||||
var env repotesting.Environment
|
||||
defer env.Setup(t).Close(t)
|
||||
ctx := context.Background()
|
||||
|
||||
w := repo.Objects.NewWriter(ctx, object.WriterOptions{})
|
||||
content := bytes.Repeat([]byte{0xcd}, 50)
|
||||
|
||||
w := env.Repository.Objects.NewWriter(ctx, object.WriterOptions{})
|
||||
w.Write(content)
|
||||
result, err := w.Result()
|
||||
if result.String() != "367352007ee6ca9fa755ce8352347d092c17a24077fd33c62f655574a8cf906d" {
|
||||
t.Errorf("unexpected result: %v err: %v", result.String(), err)
|
||||
}
|
||||
}
|
||||
func TestMalformedStoredData(t *testing.T) {
|
||||
data, _, repo := setupTest(t)
|
||||
ctx := context.Background()
|
||||
|
||||
cases := [][]byte{
|
||||
[]byte("foo\nba"),
|
||||
[]byte("foo\nbar1"),
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
data["a76999788386641a3ec798554f1fe7e6"] = c
|
||||
objectID, err := object.ParseID("Da76999788386641a3ec798554f1fe7e6")
|
||||
if err != nil {
|
||||
t.Errorf("cannot parse object ID: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
reader, err := repo.Objects.Open(ctx, objectID)
|
||||
if err == nil || reader != nil {
|
||||
t.Errorf("expected error for %x", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReaderStoredBlockNotFound(t *testing.T) {
|
||||
_, _, repo := setupTest(t)
|
||||
var env repotesting.Environment
|
||||
defer env.Setup(t).Close(t)
|
||||
ctx := context.Background()
|
||||
|
||||
objectID, err := object.ParseID("Ddeadbeef")
|
||||
if err != nil {
|
||||
t.Errorf("cannot parse object ID: %v", err)
|
||||
}
|
||||
reader, err := repo.Objects.Open(ctx, objectID)
|
||||
reader, err := env.Repository.Objects.Open(ctx, objectID)
|
||||
if err != storage.ErrBlockNotFound || reader != nil {
|
||||
t.Errorf("unexpected result: reader: %v err: %v", reader, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndToEndReadAndSeek(t *testing.T) {
|
||||
_, _, repo := setupTest(t)
|
||||
var env repotesting.Environment
|
||||
defer env.Setup(t).Close(t)
|
||||
ctx := context.Background()
|
||||
|
||||
for _, size := range []int{1, 199, 200, 201, 9999, 512434} {
|
||||
@@ -267,7 +184,7 @@ func TestEndToEndReadAndSeek(t *testing.T) {
|
||||
randomData := make([]byte, size)
|
||||
cryptorand.Read(randomData)
|
||||
|
||||
writer := repo.Objects.NewWriter(ctx, object.WriterOptions{})
|
||||
writer := env.Repository.Objects.NewWriter(ctx, object.WriterOptions{})
|
||||
writer.Write(randomData)
|
||||
objectID, err := writer.Result()
|
||||
writer.Close()
|
||||
@@ -276,12 +193,12 @@ func TestEndToEndReadAndSeek(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
verify(ctx, t, repo, objectID, randomData, fmt.Sprintf("%v %v", objectID, size))
|
||||
verify(ctx, t, env.Repository, objectID, randomData, fmt.Sprintf("%v %v", objectID, size))
|
||||
}
|
||||
}
|
||||
|
||||
func writeObject(ctx context.Context, t *testing.T, repo *Repository, data []byte, testCaseID string) object.ID {
|
||||
w := repo.Objects.NewWriter(ctx, object.WriterOptions{})
|
||||
func writeObject(ctx context.Context, t *testing.T, rep *repo.Repository, data []byte, testCaseID string) object.ID {
|
||||
w := rep.Objects.NewWriter(ctx, object.WriterOptions{})
|
||||
if _, err := w.Write(data); err != nil {
|
||||
t.Fatalf("can't write object %q - write failed: %v", testCaseID, err)
|
||||
|
||||
@@ -294,9 +211,9 @@ func writeObject(ctx context.Context, t *testing.T, repo *Repository, data []byt
|
||||
return oid
|
||||
}
|
||||
|
||||
func verify(ctx context.Context, t *testing.T, repo *Repository, objectID object.ID, expectedData []byte, testCaseID string) {
|
||||
func verify(ctx context.Context, t *testing.T, rep *repo.Repository, objectID object.ID, expectedData []byte, testCaseID string) {
|
||||
t.Helper()
|
||||
reader, err := repo.Objects.Open(ctx, objectID)
|
||||
reader, err := rep.Objects.Open(ctx, objectID)
|
||||
if err != nil {
|
||||
t.Errorf("cannot get reader for %v (%v): %v %v", testCaseID, objectID, err, string(debug.Stack()))
|
||||
return
|
||||
@@ -328,24 +245,22 @@ func verify(ctx context.Context, t *testing.T, repo *Repository, objectID object
|
||||
|
||||
func TestFormats(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
makeFormat := func(objectFormat string) func(*NewRepositoryOptions) {
|
||||
return func(n *NewRepositoryOptions) {
|
||||
makeFormat := func(objectFormat string) func(*repo.NewRepositoryOptions) {
|
||||
return func(n *repo.NewRepositoryOptions) {
|
||||
n.BlockFormat = objectFormat
|
||||
n.ObjectHMACSecret = []byte("key")
|
||||
n.MaxBlockSize = 10000
|
||||
n.Splitter = "FIXED"
|
||||
n.noHMAC = false
|
||||
}
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
format func(*NewRepositoryOptions)
|
||||
format func(*repo.NewRepositoryOptions)
|
||||
oids map[string]object.ID
|
||||
}{
|
||||
{
|
||||
format: func(n *NewRepositoryOptions) {
|
||||
format: func(n *repo.NewRepositoryOptions) {
|
||||
n.MaxBlockSize = 10000
|
||||
n.noHMAC = true
|
||||
},
|
||||
oids: map[string]object.ID{
|
||||
"": "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad",
|
||||
@@ -367,11 +282,12 @@ func TestFormats(t *testing.T) {
|
||||
}
|
||||
|
||||
for caseIndex, c := range cases {
|
||||
_, _, repo := setupTest(t, c.format)
|
||||
var env repotesting.Environment
|
||||
defer env.Setup(t, c.format).Close(t)
|
||||
|
||||
for k, v := range c.oids {
|
||||
bytesToWrite := []byte(k)
|
||||
w := repo.Objects.NewWriter(ctx, object.WriterOptions{})
|
||||
w := env.Repository.Objects.NewWriter(ctx, object.WriterOptions{})
|
||||
w.Write(bytesToWrite)
|
||||
oid, err := w.Result()
|
||||
if err != nil {
|
||||
@@ -381,7 +297,7 @@ func TestFormats(t *testing.T) {
|
||||
t.Errorf("invalid oid for #%v\ngot:\n%#v\nexpected:\n%#v", caseIndex, oid.String(), v.String())
|
||||
}
|
||||
|
||||
rc, err := repo.Objects.Open(ctx, oid)
|
||||
rc, err := env.Repository.Objects.Open(ctx, oid)
|
||||
if err != nil {
|
||||
t.Errorf("open failed: %v", err)
|
||||
continue
|
||||
+19
-5
@@ -56,21 +56,35 @@ func ListSnapshots(ctx context.Context, rep *repo.Repository, si SourceInfo) ([]
|
||||
return LoadSnapshots(ctx, rep, manifest.EntryIDs(entries))
|
||||
}
|
||||
|
||||
// LoadSnapshot loads and parses a snapshot with a given ID.
|
||||
func LoadSnapshot(ctx context.Context, rep *repo.Repository, manifestID string) (*Manifest, error) {
|
||||
// loadSnapshot loads and parses a snapshot with a given ID.
|
||||
func loadSnapshot(ctx context.Context, rep *repo.Repository, manifestID string) (*Manifest, error) {
|
||||
sm := &Manifest{}
|
||||
if err := rep.Manifests.Get(ctx, manifestID, sm); err != nil {
|
||||
return nil, fmt.Errorf("unable to find manifest entries: %v", err)
|
||||
}
|
||||
|
||||
sm.ID = manifestID
|
||||
|
||||
return sm, nil
|
||||
}
|
||||
|
||||
// SaveSnapshot persists given snapshot manifest and returns manifest ID.
|
||||
func SaveSnapshot(ctx context.Context, rep *repo.Repository, manifest *Manifest) (string, error) {
|
||||
return rep.Manifests.Put(ctx, sourceInfoToLabels(manifest.Source), manifest)
|
||||
if manifest.Source.Host == "" {
|
||||
return "", fmt.Errorf("missing host")
|
||||
}
|
||||
if manifest.Source.UserName == "" {
|
||||
return "", fmt.Errorf("missing username")
|
||||
}
|
||||
if manifest.Source.Path == "" {
|
||||
return "", fmt.Errorf("missing path")
|
||||
}
|
||||
|
||||
id, err := rep.Manifests.Put(ctx, sourceInfoToLabels(manifest.Source), manifest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
manifest.ID = id
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// LoadSnapshots efficiently loads and parses a given list of snapshot IDs.
|
||||
@@ -83,7 +97,7 @@ func LoadSnapshots(ctx context.Context, rep *repo.Repository, names []string) ([
|
||||
go func(i int, n string) {
|
||||
defer func() { <-sem }()
|
||||
|
||||
m, err := LoadSnapshot(ctx, rep, n)
|
||||
m, err := loadSnapshot(ctx, rep, n)
|
||||
if err != nil {
|
||||
log.Warningf("unable to parse snapshot manifest %v: %v", n, err)
|
||||
return
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
package snapshot_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/kopia/kopia/internal/repotesting"
|
||||
"github.com/kopia/kopia/repo"
|
||||
"github.com/kopia/kopia/snapshot"
|
||||
)
|
||||
|
||||
func TestSnapshotsAPI(t *testing.T) {
|
||||
var env repotesting.Environment
|
||||
defer env.Setup(t).Close(t)
|
||||
|
||||
src1 := snapshot.SourceInfo{
|
||||
Host: "host-1",
|
||||
UserName: "user-1",
|
||||
Path: "/some/path",
|
||||
}
|
||||
|
||||
src2 := snapshot.SourceInfo{
|
||||
Host: "host-1",
|
||||
UserName: "user-1",
|
||||
Path: "/some/other/path",
|
||||
}
|
||||
|
||||
verifySnapshotManifestIDs(t, env.Repository, nil, nil)
|
||||
verifySnapshotManifestIDs(t, env.Repository, &src1, nil)
|
||||
verifySnapshotManifestIDs(t, env.Repository, &src2, nil)
|
||||
verifyListSnapshots(t, env.Repository, src1, []*snapshot.Manifest{})
|
||||
verifyListSnapshots(t, env.Repository, src2, []*snapshot.Manifest{})
|
||||
manifest1 := &snapshot.Manifest{
|
||||
Source: src1,
|
||||
Description: "some-description",
|
||||
}
|
||||
id1 := mustSaveSnapshot(t, env.Repository, manifest1)
|
||||
verifySnapshotManifestIDs(t, env.Repository, nil, []string{id1})
|
||||
verifySnapshotManifestIDs(t, env.Repository, &src1, []string{id1})
|
||||
verifySnapshotManifestIDs(t, env.Repository, &src2, nil)
|
||||
verifyListSnapshots(t, env.Repository, src1, []*snapshot.Manifest{manifest1})
|
||||
|
||||
manifest2 := &snapshot.Manifest{
|
||||
Source: src1,
|
||||
Description: "some-other-description",
|
||||
}
|
||||
id2 := mustSaveSnapshot(t, env.Repository, manifest2)
|
||||
if id1 == id2 {
|
||||
t.Errorf("expected different manifest IDs, got same: %v", id1)
|
||||
}
|
||||
verifySnapshotManifestIDs(t, env.Repository, nil, []string{id1, id2})
|
||||
verifySnapshotManifestIDs(t, env.Repository, &src1, []string{id1, id2})
|
||||
verifySnapshotManifestIDs(t, env.Repository, &src2, nil)
|
||||
|
||||
manifest3 := &snapshot.Manifest{
|
||||
Source: src2,
|
||||
Description: "some-other-description",
|
||||
}
|
||||
|
||||
id3 := mustSaveSnapshot(t, env.Repository, manifest3)
|
||||
verifySnapshotManifestIDs(t, env.Repository, nil, []string{id1, id2, id3})
|
||||
verifySnapshotManifestIDs(t, env.Repository, &src1, []string{id1, id2})
|
||||
verifySnapshotManifestIDs(t, env.Repository, &src2, []string{id3})
|
||||
verifySources(t, env.Repository, src1, src2)
|
||||
verifyLoadSnapshots(t, env.Repository, []string{id1, id2, id3}, []*snapshot.Manifest{manifest1, manifest2, manifest3})
|
||||
}
|
||||
|
||||
func verifySnapshotManifestIDs(t *testing.T, rep *repo.Repository, src *snapshot.SourceInfo, expected []string) []string {
|
||||
t.Helper()
|
||||
res, err := snapshot.ListSnapshotManifests(context.Background(), rep, src)
|
||||
if err != nil {
|
||||
t.Errorf("error listing snapshot manifests: %v", err)
|
||||
}
|
||||
sort.Strings(res)
|
||||
sort.Strings(expected)
|
||||
if !reflect.DeepEqual(res, expected) {
|
||||
t.Errorf("unexpected manifests: %v, wanted %v", res, expected)
|
||||
return expected
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func mustSaveSnapshot(t *testing.T, rep *repo.Repository, man *snapshot.Manifest) string {
|
||||
t.Helper()
|
||||
id, err := snapshot.SaveSnapshot(context.Background(), rep, man)
|
||||
if err != nil {
|
||||
t.Fatalf("error saving snapshot: %v", err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func verifySources(t *testing.T, rep *repo.Repository, sources ...snapshot.SourceInfo) {
|
||||
sources, err := snapshot.ListSources(context.Background(), rep)
|
||||
if err != nil {
|
||||
t.Errorf("error listing sources: %v", err)
|
||||
}
|
||||
|
||||
if got, want := sorted(sourcesToStrings(sources...)), sorted(sourcesToStrings(sources...)); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("unexpected sources: %v want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyListSnapshots(t *testing.T, rep *repo.Repository, src snapshot.SourceInfo, expected []*snapshot.Manifest) {
|
||||
t.Helper()
|
||||
got, err := snapshot.ListSnapshots(context.Background(), rep, src)
|
||||
if err != nil {
|
||||
t.Errorf("error loading manifests: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("unexpected manifests: %v, wanted %v", got, expected)
|
||||
for i, m := range got {
|
||||
t.Logf("got[%v]=%#v", i, m)
|
||||
}
|
||||
for i, m := range expected {
|
||||
t.Logf("want[%v]=%#v", i, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyLoadSnapshots(t *testing.T, rep *repo.Repository, ids []string, expected []*snapshot.Manifest) {
|
||||
got, err := snapshot.LoadSnapshots(context.Background(), rep, ids)
|
||||
if err != nil {
|
||||
t.Errorf("error loading manifests: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("unexpected manifests: %v, wanted %v", got, expected)
|
||||
for i, m := range got {
|
||||
t.Logf("got[%v]=%#v", i, m)
|
||||
}
|
||||
for i, m := range expected {
|
||||
t.Logf("want[%v]=%#v", i, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sorted(s []string) []string {
|
||||
res := append([]string(nil), s...)
|
||||
sort.Strings(res)
|
||||
return res
|
||||
}
|
||||
|
||||
func sourcesToStrings(sources ...snapshot.SourceInfo) []string {
|
||||
var res []string
|
||||
|
||||
for _, src := range sources {
|
||||
res = append(res, src.String())
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func TestParseSourceInfo(t *testing.T) {
|
||||
cwd, _ := os.Getwd()
|
||||
|
||||
cases := []struct {
|
||||
path string
|
||||
want snapshot.SourceInfo
|
||||
}{
|
||||
{"(global)", snapshot.SourceInfo{}},
|
||||
{"", snapshot.SourceInfo{UserName: "default-user", Host: "default-host", Path: cwd}},
|
||||
{".", snapshot.SourceInfo{UserName: "default-user", Host: "default-host", Path: cwd}},
|
||||
{"..", snapshot.SourceInfo{UserName: "default-user", Host: "default-host", Path: filepath.Clean(filepath.Join(cwd, ".."))}},
|
||||
{"foo@bar:/some/path", snapshot.SourceInfo{UserName: "foo", Host: "bar", Path: "/some/path"}},
|
||||
{"/some/path", snapshot.SourceInfo{UserName: "default-user", Host: "default-host", Path: "/some/path"}},
|
||||
{"/some/path/../other-path", snapshot.SourceInfo{UserName: "default-user", Host: "default-host", Path: "/some/other-path"}},
|
||||
{"@some-host", snapshot.SourceInfo{Host: "some-host"}},
|
||||
{"some-user@some-host", snapshot.SourceInfo{UserName: "some-user", Host: "some-host"}},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
got, err := snapshot.ParseSourceInfo(tc.path, "default-host", "default-user")
|
||||
if err != nil {
|
||||
t.Errorf("error parsing %q: %v", tc.path, err)
|
||||
continue
|
||||
}
|
||||
if got != tc.want {
|
||||
t.Errorf("unexpected parsed value of %q: %v, wanted %v", tc.path, got, tc.want)
|
||||
}
|
||||
got2, err := snapshot.ParseSourceInfo(got.String(), "default-host", "default-user")
|
||||
if err != nil {
|
||||
t.Errorf("error parsing %q: %v", tc.path, err)
|
||||
continue
|
||||
}
|
||||
if got != got2 {
|
||||
t.Errorf("unexpected parsed value of %q: %v, wanted %v", got.String(), got2, got)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestParseInvalidSourceInfo(t *testing.T) {
|
||||
cases := []string{
|
||||
"@",
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
si, err := snapshot.ParseSourceInfo(tc, "default-host", "default-user")
|
||||
if err == nil {
|
||||
t.Errorf("unexpected success when parsing %v: %v", tc, si)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user