corso/src/internal/kopia/conn_test.go
ashmrtn bf29443ad4
Use interface for test helper functions (#4114)
Take an interface as the parameter for test helper functions. This allows them to be used with *testing.B (benchmark) as well as *testing.T (test).

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Test Plan

- [x] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2023-08-25 17:28:09 +00:00

528 lines
13 KiB
Go

package kopia
import (
"context"
"math"
"testing"
"time"
"github.com/alcionai/clues"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/repo/blob"
"github.com/kopia/kopia/snapshot"
"github.com/kopia/kopia/snapshot/policy"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/control/repository"
"github.com/alcionai/corso/src/pkg/storage"
storeTD "github.com/alcionai/corso/src/pkg/storage/testdata"
)
func openKopiaRepo(
t tester.TestT,
ctx context.Context, //revive:disable-line:context-as-argument
) (*conn, error) {
st := storeTD.NewPrefixedS3Storage(t)
k := NewConn(st)
if err := k.Initialize(ctx, repository.Options{}, repository.Retention{}); err != nil {
return nil, err
}
return k, nil
}
// ---------------
// unit tests
// ---------------
type WrapperUnitSuite struct {
tester.Suite
}
func TestWrapperUnitSuite(t *testing.T) {
suite.Run(t, &WrapperUnitSuite{Suite: tester.NewUnitSuite(t)})
}
func (suite *WrapperUnitSuite) TestCloseWithoutOpenDoesNotCrash() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
k := conn{}
assert.NotPanics(t, func() {
k.Close(ctx)
})
}
// ---------------
// integration tests that use kopia
// ---------------
type WrapperIntegrationSuite struct {
tester.Suite
}
func TestWrapperIntegrationSuite(t *testing.T) {
suite.Run(t, &WrapperIntegrationSuite{
Suite: tester.NewIntegrationSuite(
t,
[][]string{storeTD.AWSStorageCredEnvs},
),
})
}
func (suite *WrapperIntegrationSuite) TestRepoExistsError() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
st := storeTD.NewPrefixedS3Storage(t)
k := NewConn(st)
err := k.Initialize(ctx, repository.Options{}, repository.Retention{})
require.NoError(t, err, clues.ToCore(err))
err = k.Close(ctx)
require.NoError(t, err, clues.ToCore(err))
err = k.Initialize(ctx, repository.Options{}, repository.Retention{})
assert.Error(t, err, clues.ToCore(err))
assert.ErrorIs(t, err, ErrorRepoAlreadyExists)
}
func (suite *WrapperIntegrationSuite) TestBadProviderErrors() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
st := storeTD.NewPrefixedS3Storage(t)
st.Provider = storage.ProviderUnknown
k := NewConn(st)
err := k.Initialize(ctx, repository.Options{}, repository.Retention{})
assert.Error(t, err, clues.ToCore(err))
}
func (suite *WrapperIntegrationSuite) TestConnectWithoutInitErrors() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
st := storeTD.NewPrefixedS3Storage(t)
k := NewConn(st)
err := k.Connect(ctx, repository.Options{})
assert.Error(t, err, clues.ToCore(err))
}
func (suite *WrapperIntegrationSuite) TestCloseTwiceDoesNotCrash() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
k, err := openKopiaRepo(t, ctx)
require.NoError(t, err, clues.ToCore(err))
err = k.Close(ctx)
assert.NoError(t, err, clues.ToCore(err))
assert.Nil(t, k.Repository)
err = k.Close(ctx)
assert.NoError(t, err, clues.ToCore(err))
}
func (suite *WrapperIntegrationSuite) TestCloseAfterWrap() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
k, err := openKopiaRepo(t, ctx)
require.NoError(t, err, clues.ToCore(err))
err = k.wrap()
require.NoError(t, err, clues.ToCore(err))
assert.Equal(t, 2, k.refCount)
err = k.Close(ctx)
require.NoError(t, err, clues.ToCore(err))
assert.NotNil(t, k.Repository)
assert.Equal(t, 1, k.refCount)
err = k.Close(ctx)
require.NoError(t, err, clues.ToCore(err))
assert.Nil(t, k.Repository)
assert.Equal(t, 0, k.refCount)
}
func (suite *WrapperIntegrationSuite) TestOpenAfterClose() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
k, err := openKopiaRepo(t, ctx)
require.NoError(t, err, clues.ToCore(err))
err = k.Close(ctx)
assert.NoError(t, err, clues.ToCore(err))
err = k.wrap()
assert.Error(t, err, clues.ToCore(err))
}
func (suite *WrapperIntegrationSuite) TestBadCompressorType() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
k, err := openKopiaRepo(t, ctx)
require.NoError(t, err, clues.ToCore(err))
defer func() {
err := k.Close(ctx)
assert.NoError(t, err, clues.ToCore(err))
}()
err = k.Compression(ctx, "not-a-compressor")
assert.Error(t, err, clues.ToCore(err))
}
func (suite *WrapperIntegrationSuite) TestGetPolicyOrDefault_GetsDefault() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
k, err := openKopiaRepo(t, ctx)
require.NoError(t, err, clues.ToCore(err))
defer func() {
err := k.Close(ctx)
assert.NoError(t, err, clues.ToCore(err))
}()
si := snapshot.SourceInfo{
Host: corsoHost,
UserName: corsoUser,
Path: "test-path-root",
}
p, err := k.getPolicyOrEmpty(ctx, si)
require.NoError(t, err, clues.ToCore(err))
assert.Equal(t, policy.Policy{}, *p)
}
func (suite *WrapperIntegrationSuite) TestSetCompressor() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
compressor := "pgzip"
k, err := openKopiaRepo(t, ctx)
require.NoError(t, err, clues.ToCore(err))
defer func() {
err := k.Close(ctx)
assert.NoError(t, err, clues.ToCore(err))
}()
err = k.Compression(ctx, compressor)
assert.NoError(t, err, clues.ToCore(err))
// Check the policy was actually created and has the right compressor.
p, err := k.getPolicyOrEmpty(ctx, policy.GlobalPolicySourceInfo)
require.NoError(t, err, clues.ToCore(err))
assert.Equal(t, compressor, string(p.CompressionPolicy.CompressorName))
// Check the global policy will be the effective policy in future snapshots
// for some source info.
si := snapshot.SourceInfo{
Host: corsoHost,
UserName: corsoUser,
Path: "test-path-root",
}
policyTree, err := policy.TreeForSource(ctx, k, si)
require.NoError(t, err, clues.ToCore(err))
assert.Equal(
t,
compressor,
string(policyTree.EffectivePolicy().CompressionPolicy.CompressorName))
}
func (suite *WrapperIntegrationSuite) TestConfigDefaultsSetOnInitAndNotOnConnect() {
newCompressor := "pgzip"
newRetentionDaily := policy.OptionalInt(42)
newRetention := policy.RetentionPolicy{KeepDaily: &newRetentionDaily}
newSchedInterval := time.Second * 42
table := []struct {
name string
checkInitFunc func(*testing.T, *policy.Policy)
checkFunc func(*testing.T, *policy.Policy)
mutator func(context.Context, *policy.Policy) error
}{
{
name: "Compression",
checkInitFunc: func(t *testing.T, p *policy.Policy) {
t.Helper()
require.Equal(t, defaultCompressor, string(p.CompressionPolicy.CompressorName))
},
checkFunc: func(t *testing.T, p *policy.Policy) {
t.Helper()
require.Equal(t, newCompressor, string(p.CompressionPolicy.CompressorName))
},
mutator: func(innerCtx context.Context, p *policy.Policy) error {
_, res := updateCompressionOnPolicy(newCompressor, p)
return res
},
},
{
name: "Retention",
checkInitFunc: func(t *testing.T, p *policy.Policy) {
t.Helper()
require.Equal(
t,
defaultRetention,
p.RetentionPolicy,
)
assert.Equal(
t,
math.MaxInt,
p.RetentionPolicy.EffectiveKeepLatest().OrDefault(42),
)
},
checkFunc: func(t *testing.T, p *policy.Policy) {
t.Helper()
require.Equal(
t,
newRetention,
p.RetentionPolicy,
)
assert.Equal(
t,
42,
p.RetentionPolicy.EffectiveKeepLatest().OrDefault(42),
)
},
mutator: func(innerCtx context.Context, p *policy.Policy) error {
updateRetentionOnPolicy(newRetention, p)
return nil
},
},
{
name: "Scheduling",
checkInitFunc: func(t *testing.T, p *policy.Policy) {
t.Helper()
require.Equal(t, defaultSchedulingInterval, p.SchedulingPolicy.Interval())
},
checkFunc: func(t *testing.T, p *policy.Policy) {
t.Helper()
require.Equal(t, newSchedInterval, p.SchedulingPolicy.Interval())
},
mutator: func(innerCtx context.Context, p *policy.Policy) error {
updateSchedulingOnPolicy(newSchedInterval, p)
return nil
},
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
k, err := openKopiaRepo(t, ctx)
require.NoError(t, err, clues.ToCore(err))
p, err := k.getPolicyOrEmpty(ctx, policy.GlobalPolicySourceInfo)
require.NoError(t, err, clues.ToCore(err))
test.checkInitFunc(t, p)
err = test.mutator(ctx, p)
require.NoError(t, err, clues.ToCore(err))
err = k.writeGlobalPolicy(ctx, "TestDefaultPolicyConfigSet", p)
require.NoError(t, err, clues.ToCore(err))
err = k.Close(ctx)
require.NoError(t, err, clues.ToCore(err))
err = k.Connect(ctx, repository.Options{})
require.NoError(t, err, clues.ToCore(err))
defer func() {
err := k.Close(ctx)
assert.NoError(t, err, clues.ToCore(err))
}()
p, err = k.getPolicyOrEmpty(ctx, policy.GlobalPolicySourceInfo)
require.NoError(t, err, clues.ToCore(err))
test.checkFunc(t, p)
})
}
}
func (suite *WrapperIntegrationSuite) TestInitAndConnWithTempDirectory() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
k, err := openKopiaRepo(t, ctx)
require.NoError(t, err, clues.ToCore(err))
err = k.Close(ctx)
require.NoError(t, err, clues.ToCore(err))
// Re-open with Connect.
err = k.Connect(ctx, repository.Options{})
require.NoError(t, err, clues.ToCore(err))
err = k.Close(ctx)
assert.NoError(t, err, clues.ToCore(err))
}
func (suite *WrapperIntegrationSuite) TestSetUserAndHost() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
opts := repository.Options{
User: "foo",
Host: "bar",
}
st := storeTD.NewPrefixedS3Storage(t)
k := NewConn(st)
err := k.Initialize(ctx, opts, repository.Retention{})
require.NoError(t, err, clues.ToCore(err))
kopiaOpts := k.ClientOptions()
require.Equal(t, opts.User, kopiaOpts.Username)
require.Equal(t, opts.Host, kopiaOpts.Hostname)
err = k.Close(ctx)
require.NoError(t, err, clues.ToCore(err))
// Re-open with Connect and a different user/hostname.
opts.User = "hello"
opts.Host = "world"
err = k.Connect(ctx, opts)
require.NoError(t, err, clues.ToCore(err))
kopiaOpts = k.ClientOptions()
require.Equal(t, opts.User, kopiaOpts.Username)
require.Equal(t, opts.Host, kopiaOpts.Hostname)
err = k.Close(ctx)
require.NoError(t, err, clues.ToCore(err))
// Make sure not setting the values uses the kopia defaults.
opts.User = ""
opts.Host = ""
err = k.Connect(ctx, opts)
require.NoError(t, err, clues.ToCore(err))
kopiaOpts = k.ClientOptions()
assert.NotEmpty(t, kopiaOpts.Username)
assert.NotEqual(t, "hello", kopiaOpts.Username)
assert.NotEmpty(t, kopiaOpts.Hostname)
assert.NotEqual(t, "world", kopiaOpts.Hostname)
err = k.Close(ctx)
assert.NoError(t, err, clues.ToCore(err))
}
// ---------------
// integration tests that require object locking to be enabled on the bucket.
// ---------------
type ConnRetentionIntegrationSuite struct {
tester.Suite
}
func TestConnRetentionIntegrationSuite(t *testing.T) {
suite.Run(t, &ConnRetentionIntegrationSuite{
Suite: tester.NewRetentionSuite(
t,
[][]string{storeTD.AWSStorageCredEnvs},
),
})
}
// Test that providing retention doesn't change anything but retention values
// from the default values that kopia uses.
func (suite *ConnRetentionIntegrationSuite) TestInitWithAndWithoutRetention() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
st1 := storeTD.NewPrefixedS3Storage(t)
k1 := NewConn(st1)
err := k1.Initialize(ctx, repository.Options{}, repository.Retention{})
require.NoError(t, err, "initializing repo 1: %v", clues.ToCore(err))
st2 := storeTD.NewPrefixedS3Storage(t)
k2 := NewConn(st2)
err = k2.Initialize(
ctx,
repository.Options{},
repository.Retention{
Mode: ptr.To(repository.GovernanceRetention),
Duration: ptr.To(time.Hour * 48),
Extend: ptr.To(true),
})
require.NoError(t, err, "initializing repo 2: %v", clues.ToCore(err))
dr1, ok := k1.Repository.(repo.DirectRepository)
require.True(t, ok, "getting direct repo 1")
dr2, ok := k2.Repository.(repo.DirectRepository)
require.True(t, ok, "getting direct repo 2")
format1 := dr1.FormatManager().ScrubbedContentFormat()
format2 := dr2.FormatManager().ScrubbedContentFormat()
assert.Equal(t, format1, format2)
blobCfg1, err := dr1.FormatManager().BlobCfgBlob()
require.NoError(t, err, "getting blob config 1: %v", clues.ToCore(err))
blobCfg2, err := dr2.FormatManager().BlobCfgBlob()
require.NoError(t, err, "getting retention config 2: %v", clues.ToCore(err))
assert.NotEqual(t, blobCfg1, blobCfg2)
// Check to make sure retention not enabled unexpectedly.
checkRetentionParams(t, ctx, k1, blob.RetentionMode(""), 0, assert.False)
// Some checks to make sure retention was fully initialized as expected.
checkRetentionParams(t, ctx, k2, blob.Governance, time.Hour*48, assert.True)
}