mirror of
https://github.com/kopia/kopia.git
synced 2026-03-12 19:26:25 -04:00
- `repo.Repository` is now read-only and only has methods that can be supported over kopia server
- `repo.RepositoryWriter` has read-write methods that can be supported over kopia server
- `repo.DirectRepository` is read-only and contains all methods of `repo.Repository` plus some low-level methods for data inspection
- `repo.DirectRepositoryWriter` contains write methods for `repo.DirectRepository`
- `repo.Reader` removed and merged with `repo.Repository`
- `repo.Writer` became `repo.RepositoryWriter`
- `*repo.DirectRepository` struct became `repo.DirectRepository`
interface
Getting `{Direct}RepositoryWriter` requires using `NewWriter()` or `NewDirectWriter()` on a read-only repository and multiple simultaneous writers are supported at the same time, each writing to their own indexes and pack blobs.
`repo.Open` returns `repo.Repository` (which is also `repo.RepositoryWriter`).
* content: removed implicit flush on content manager close
* repo: added tests for WriteSession() and implicit flush behavior
* invalidate manifest manager after write session
* cli: disable maintenance in 'kopia server start'
Server will close the repository before completing.
* repo: unconditionally close RepositoryWriter in {Direct,}WriteSession
* repo: added panic in case somebody tries to create RepositoryWriter after closing repository
- used atomic to manage SharedManager.closed
* removed stale example
* linter: fixed spurious failures
Co-authored-by: Julio López <julio+gh@kasten.io>
228 lines
5.1 KiB
Go
228 lines
5.1 KiB
Go
// Package repotesting contains test utilities for working with repositories.
|
|
package repotesting
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/kopia/kopia/internal/testlogging"
|
|
"github.com/kopia/kopia/repo"
|
|
"github.com/kopia/kopia/repo/blob"
|
|
"github.com/kopia/kopia/repo/blob/filesystem"
|
|
"github.com/kopia/kopia/repo/content"
|
|
"github.com/kopia/kopia/repo/encryption"
|
|
"github.com/kopia/kopia/repo/object"
|
|
)
|
|
|
|
const masterPassword = "foobarbazfoobarbaz"
|
|
|
|
// Environment encapsulates details of a test environment.
|
|
type Environment struct {
|
|
Repository repo.Repository
|
|
RepositoryWriter repo.DirectRepositoryWriter
|
|
|
|
configDir string
|
|
storageDir string
|
|
connected bool
|
|
}
|
|
|
|
// Options used during Environment Setup.
|
|
type Options struct {
|
|
NewRepositoryOptions func(*repo.NewRepositoryOptions)
|
|
OpenOptions func(*repo.Options)
|
|
}
|
|
|
|
// Setup sets up a test environment.
|
|
func (e *Environment) Setup(t *testing.T, opts ...Options) *Environment {
|
|
t.Helper()
|
|
|
|
ctx := testlogging.Context(t)
|
|
e.configDir = t.TempDir()
|
|
e.storageDir = t.TempDir()
|
|
openOpt := &repo.Options{}
|
|
|
|
opt := &repo.NewRepositoryOptions{
|
|
BlockFormat: content.FormattingOptions{
|
|
HMACSecret: []byte{},
|
|
Hash: "HMAC-SHA256",
|
|
Encryption: encryption.DefaultAlgorithm,
|
|
},
|
|
ObjectFormat: object.Format{
|
|
Splitter: "FIXED-1M",
|
|
},
|
|
}
|
|
|
|
for _, mod := range opts {
|
|
if mod.NewRepositoryOptions != nil {
|
|
mod.NewRepositoryOptions(opt)
|
|
}
|
|
|
|
if mod.OpenOptions != nil {
|
|
mod.OpenOptions(openOpt)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
if err = repo.Connect(ctx, e.configFile(), st, masterPassword, nil); err != nil {
|
|
t.Fatalf("can't connect: %v", err)
|
|
}
|
|
|
|
e.connected = true
|
|
|
|
rep, err := repo.Open(ctx, e.configFile(), masterPassword, openOpt)
|
|
if err != nil {
|
|
t.Fatalf("can't open: %v", err)
|
|
}
|
|
|
|
e.Repository = rep
|
|
|
|
e.RepositoryWriter, err = rep.(repo.DirectRepository).NewDirectWriter(ctx, "test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return e
|
|
}
|
|
|
|
// Close closes testing environment.
|
|
func (e *Environment) Close(ctx context.Context, t *testing.T) {
|
|
t.Helper()
|
|
|
|
if err := e.RepositoryWriter.Close(ctx); err != nil {
|
|
t.Fatalf("unable to close: %v", err)
|
|
}
|
|
|
|
if e.connected {
|
|
if err := repo.Disconnect(ctx, e.configFile()); err != nil {
|
|
t.Errorf("error disconnecting: %v", err)
|
|
}
|
|
}
|
|
|
|
if err := os.Remove(e.configDir); err != nil {
|
|
// should be empty, assuming Disconnect was successful
|
|
t.Errorf("error removing config 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, openOpts ...func(*repo.Options)) {
|
|
t.Helper()
|
|
|
|
ctx := testlogging.Context(t)
|
|
|
|
err := e.RepositoryWriter.Close(ctx)
|
|
if err != nil {
|
|
t.Fatalf("close error: %v", err)
|
|
}
|
|
|
|
rep, err := repo.Open(ctx, e.configFile(), masterPassword, repoOptions(openOpts))
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
e.RepositoryWriter, err = rep.(repo.DirectRepository).NewDirectWriter(ctx, "test")
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
// MustOpenAnother opens another repository backend by the same storage.
|
|
func (e *Environment) MustOpenAnother(t *testing.T) repo.RepositoryWriter {
|
|
t.Helper()
|
|
|
|
ctx := testlogging.Context(t)
|
|
|
|
rep2, err := repo.Open(ctx, e.configFile(), masterPassword, &repo.Options{})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
rep2.Close(ctx)
|
|
})
|
|
|
|
w, err := rep2.NewWriter(ctx, "test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return w
|
|
}
|
|
|
|
// MustConnectOpenAnother opens another repository backend by the same storage,
|
|
// with independent config and cache options.
|
|
func (e *Environment) MustConnectOpenAnother(t *testing.T, openOpts ...func(*repo.Options)) repo.Repository {
|
|
t.Helper()
|
|
|
|
ctx := testlogging.Context(t)
|
|
|
|
st, err := filesystem.New(ctx, &filesystem.Options{
|
|
Path: e.storageDir,
|
|
})
|
|
if err != nil {
|
|
t.Fatal("err:", err)
|
|
}
|
|
|
|
config := filepath.Join(t.TempDir(), "kopia.config")
|
|
connOpts := &repo.ConnectOptions{
|
|
CachingOptions: content.CachingOptions{
|
|
CacheDirectory: t.TempDir(),
|
|
},
|
|
}
|
|
|
|
if err = repo.Connect(ctx, config, st, masterPassword, connOpts); err != nil {
|
|
t.Fatal("can't connect:", err)
|
|
}
|
|
|
|
rep, err := repo.Open(ctx, e.configFile(), masterPassword, repoOptions(openOpts))
|
|
if err != nil {
|
|
t.Fatal("can't open:", err)
|
|
}
|
|
|
|
return rep
|
|
}
|
|
|
|
// VerifyBlobCount verifies that the underlying storage contains the specified number of blobs.
|
|
func (e *Environment) VerifyBlobCount(t *testing.T, want int) {
|
|
t.Helper()
|
|
|
|
var got int
|
|
|
|
_ = e.RepositoryWriter.BlobReader().ListBlobs(testlogging.Context(t), "", func(_ blob.Metadata) error {
|
|
got++
|
|
return nil
|
|
})
|
|
|
|
if got != want {
|
|
t.Errorf("got unexpected number of BLOBs: %v, wanted %v", got, want)
|
|
}
|
|
}
|
|
|
|
func repoOptions(openOpts []func(*repo.Options)) *repo.Options {
|
|
openOpt := &repo.Options{}
|
|
|
|
for _, mod := range openOpts {
|
|
if mod != nil {
|
|
mod(openOpt)
|
|
}
|
|
}
|
|
|
|
return openOpt
|
|
}
|