snapshot: added tests

This commit is contained in:
Jarek Kowalski committed 2018-10-21 22:46:38 -07:00
1 parent c999c0fba8
commit 3a4b581814
7 files changed
+427 -159

No files matched your search

+3
View File
@@ -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/...
+123
View File
@@ -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
View File
@@ -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
}
+3
View File
@@ -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
View File
@@ -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
+210
View File
@@ -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)
}
}
}