refactor(cli): An in-memory storage mock setup for CLI tests (#1697)

* refactor cli tests to allow the use of in-memory mock

* use in-memory repo for set-parameters cli tests

* move inmemory storage provider into test package

Co-authored-by: Shikhar Mall <shikhar@kasten.io>
This commit is contained in:
Shikhar Mall
2022-02-01 10:29:13 -08:00
committed by GitHub
parent 902348191f
commit aa5e4cfb33
19 changed files with 153 additions and 82 deletions

View File

@@ -89,7 +89,7 @@ type appServices interface {
type advancedAppServices interface {
appServices
storageProviderServices
StorageProviderServices
runConnectCommandWithStorage(ctx context.Context, co *connectOptions, st blob.Storage) error
runConnectCommandWithStorageAndPassword(ctx context.Context, co *connectOptions, st blob.Storage, password string) error
@@ -121,6 +121,7 @@ type App struct {
persistCredentials bool
disableInternalLog bool
AdvancedCommands string
cliStorageProviders []StorageProvider
currentAction string
onExitCallbacks []func()
@@ -274,6 +275,18 @@ type commandParent interface {
func NewApp() *App {
return &App{
progress: &cliProgress{},
cliStorageProviders: []StorageProvider{
{"from-config", "the provided configuration file", func() StorageFlags { return &storageFromConfigFlags{} }},
{"azure", "an Azure blob storage", func() StorageFlags { return &storageAzureFlags{} }},
{"b2", "a B2 bucket", func() StorageFlags { return &storageB2Flags{} }},
{"filesystem", "a filesystem", func() StorageFlags { return &storageFilesystemFlags{} }},
{"gcs", "a Google Cloud Storage bucket", func() StorageFlags { return &storageGCSFlags{} }},
{"rclone", "a rclone-based provided", func() StorageFlags { return &storageRcloneFlags{} }},
{"s3", "an S3 bucket", func() StorageFlags { return &storageS3Flags{} }},
{"sftp", "an SFTP storage", func() StorageFlags { return &storageSFTPFlags{} }},
{"webdav", "a WebDAV storage", func() StorageFlags { return &storageWebDAVFlags{} }},
},
// testability hooks
osExit: os.Exit,

View File

@@ -25,14 +25,14 @@ func (c *commandRepositoryConnect) setup(svc advancedAppServices, parent command
c.co.setup(cmd)
c.server.setup(svc, cmd, &c.co)
for _, prov := range cliStorageProviders() {
for _, prov := range svc.storageProviders() {
// Set up 'connect' subcommand
f := prov.newFlags()
cc := cmd.Command(prov.name, "Connect to repository in "+prov.description)
f.setup(svc, cc)
f := prov.NewFlags()
cc := cmd.Command(prov.Name, "Connect to repository in "+prov.Description)
f.Setup(svc, cc)
cc.Action(func(_ *kingpin.ParseContext) error {
ctx := svc.rootContext()
st, err := f.connect(ctx, false, 0)
st, err := f.Connect(ctx, false, 0)
if err != nil {
return errors.Wrap(err, "can't connect to storage")
}

View File

@@ -14,17 +14,17 @@ type storageFromConfigFlags struct {
connectFromConfigFile string
connectFromConfigToken string
sps storageProviderServices
sps StorageProviderServices
}
func (c *storageFromConfigFlags) setup(sps storageProviderServices, cmd *kingpin.CmdClause) {
func (c *storageFromConfigFlags) Setup(sps StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("file", "Path to the configuration file").StringVar(&c.connectFromConfigFile)
cmd.Flag("token", "Configuration token").StringVar(&c.connectFromConfigToken)
c.sps = sps
}
func (c *storageFromConfigFlags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
func (c *storageFromConfigFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
if isCreate {
return nil, errors.New("not supported")
}

View File

@@ -52,18 +52,18 @@ func (c *commandRepositoryCreate) setup(svc advancedAppServices, parent commandP
c.svc = svc
c.out.setup(svc)
for _, prov := range cliStorageProviders() {
if prov.name == "from-config" {
for _, prov := range svc.storageProviders() {
if prov.Name == "from-config" {
continue
}
// Set up 'create' subcommand
f := prov.newFlags()
cc := cmd.Command(prov.name, "Create repository in "+prov.description)
f.setup(svc, cc)
f := prov.NewFlags()
cc := cmd.Command(prov.Name, "Create repository in "+prov.Description)
f.Setup(svc, cc)
cc.Action(func(_ *kingpin.ParseContext) error {
ctx := svc.rootContext()
st, err := f.connect(ctx, true, c.createFormatVersion)
st, err := f.Connect(ctx, true, c.createFormatVersion)
if err != nil {
return errors.Wrap(err, "can't connect to storage")
}

View File

@@ -25,13 +25,13 @@ func (c *commandRepositoryRepair) setup(svc advancedAppServices, parent commandP
cmd.Flag("recover-format-block-prefixes", "Prefixes of file names").StringsVar(&c.repairCommandRecoverFormatBlobPrefixes)
cmd.Flag("dry-run", "Do not modify repository").Short('n').BoolVar(&c.repairDryRun)
for _, prov := range cliStorageProviders() {
f := prov.newFlags()
cc := cmd.Command(prov.name, "Repair repository in "+prov.description)
f.setup(svc, cc)
for _, prov := range svc.storageProviders() {
f := prov.NewFlags()
cc := cmd.Command(prov.Name, "Repair repository in "+prov.Description)
f.Setup(svc, cc)
cc.Action(func(_ *kingpin.ParseContext) error {
ctx := svc.rootContext()
st, err := f.connect(ctx, false, 0)
st, err := f.Connect(ctx, false, 0)
if err != nil {
return errors.Wrap(err, "can't connect to storage")
}

View File

@@ -3,16 +3,39 @@
import (
"testing"
"github.com/alecthomas/kingpin"
"github.com/stretchr/testify/require"
"github.com/kopia/kopia/cli"
"github.com/kopia/kopia/internal/blobtesting"
"github.com/kopia/kopia/internal/repotesting"
"github.com/kopia/kopia/repo/content"
"github.com/kopia/kopia/tests/testenv"
)
func (s *formatSpecificTestSuite) TestRepositorySetParameters(t *testing.T) {
env := testenv.NewCLITest(t, s.formatFlags, testenv.NewInProcRunner(t))
func (s *formatSpecificTestSuite) setupInMemoryRepo(t *testing.T) *testenv.CLITest {
t.Helper()
env.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", env.RepoDir)
runner := testenv.NewInProcRunner(t)
runner.CustomizeApp = func(a *cli.App, kp *kingpin.Application) {
a.AddStorageProvider(cli.StorageProvider{
Name: "in-memory",
Description: "in-memory storage backend",
NewFlags: func() cli.StorageFlags { return &storageInMemoryFlags{} },
})
}
env := testenv.NewCLITest(t, s.formatFlags, runner)
st := repotesting.NewReconnectableStorage(t, blobtesting.NewVersionedMapStorage(nil))
env.RunAndExpectSuccess(t, "repo", "create", "in-memory", "--uuid",
st.ConnectionInfo().Config.(*repotesting.ReconnectableStorageOptions).UUID)
return env
}
func (s *formatSpecificTestSuite) TestRepositorySetParameters(t *testing.T) {
env := s.setupInMemoryRepo(t)
out := env.RunAndExpectSuccess(t, "repository", "status")
// default values
@@ -40,9 +63,7 @@ func (s *formatSpecificTestSuite) TestRepositorySetParameters(t *testing.T) {
}
func (s *formatSpecificTestSuite) TestRepositorySetParametersUpgrade(t *testing.T) {
env := testenv.NewCLITest(t, s.formatFlags, testenv.NewInProcRunner(t))
env.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", env.RepoDir)
env := s.setupInMemoryRepo(t)
out := env.RunAndExpectSuccess(t, "repository", "status")
// default values

View File

@@ -50,14 +50,14 @@ func (c *commandRepositorySyncTo) setup(svc advancedAppServices, parent commandP
// needs to be 64-bit aligned on ARM
c.nextSyncOutputTime = new(timetrack.Throttle)
for _, prov := range cliStorageProviders() {
for _, prov := range svc.storageProviders() {
// Set up 'sync-to' subcommand
f := prov.newFlags()
cc := cmd.Command(prov.name, "Synchronize repository data to another repository in "+prov.description)
f.setup(svc, cc)
f := prov.NewFlags()
cc := cmd.Command(prov.Name, "Synchronize repository data to another repository in "+prov.Description)
f.Setup(svc, cc)
cc.Action(func(_ *kingpin.ParseContext) error {
ctx := svc.rootContext()
st, err := f.connect(ctx, false, 0)
st, err := f.Connect(ctx, false, 0)
if err != nil {
return errors.Wrap(err, "can't connect to storage")
}

View File

@@ -13,7 +13,7 @@ type storageAzureFlags struct {
azOptions azure.Options
}
func (c *storageAzureFlags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) {
func (c *storageAzureFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("container", "Name of the Azure blob container").Required().StringVar(&c.azOptions.Container)
cmd.Flag("storage-account", "Azure storage account name (overrides AZURE_STORAGE_ACCOUNT environment variable)").Required().Envar("AZURE_STORAGE_ACCOUNT").StringVar(&c.azOptions.StorageAccount)
cmd.Flag("storage-key", "Azure storage account key (overrides AZURE_STORAGE_KEY environment variable)").Envar("AZURE_STORAGE_KEY").StringVar(&c.azOptions.StorageKey)
@@ -24,7 +24,7 @@ func (c *storageAzureFlags) setup(_ storageProviderServices, cmd *kingpin.CmdCla
commonThrottlingFlags(cmd, &c.azOptions.Limits)
}
func (c *storageAzureFlags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
func (c *storageAzureFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
// nolint:wrapcheck
return azure.New(ctx, &c.azOptions)
}

View File

@@ -13,7 +13,7 @@ type storageB2Flags struct {
b2options b2.Options
}
func (c *storageB2Flags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) {
func (c *storageB2Flags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("bucket", "Name of the B2 bucket").Required().StringVar(&c.b2options.BucketName)
cmd.Flag("key-id", "Key ID (overrides B2_KEY_ID environment variable)").Required().Envar("B2_KEY_ID").StringVar(&c.b2options.KeyID)
cmd.Flag("key", "Secret key (overrides B2_KEY environment variable)").Required().Envar("B2_KEY").StringVar(&c.b2options.Key)
@@ -21,7 +21,7 @@ func (c *storageB2Flags) setup(_ storageProviderServices, cmd *kingpin.CmdClause
commonThrottlingFlags(cmd, &c.b2options.Limits)
}
func (c *storageB2Flags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
func (c *storageB2Flags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
// nolint:wrapcheck
return b2.New(ctx, &c.b2options)
}

View File

@@ -28,7 +28,7 @@ type storageFilesystemFlags struct {
connectFlat bool
}
func (c *storageFilesystemFlags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) {
func (c *storageFilesystemFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("path", "Path to the repository").Required().StringVar(&c.options.Path)
cmd.Flag("owner-uid", "User ID owning newly created files").PlaceHolder("USER").StringVar(&c.connectOwnerUID)
cmd.Flag("owner-gid", "Group ID owning newly created files").PlaceHolder("GROUP").StringVar(&c.connectOwnerGID)
@@ -40,7 +40,7 @@ func (c *storageFilesystemFlags) setup(_ storageProviderServices, cmd *kingpin.C
commonThrottlingFlags(cmd, &c.options.Limits)
}
func (c *storageFilesystemFlags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
func (c *storageFilesystemFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
fso := c.options
fso.Path = ospath.ResolveUserFriendlyPath(fso.Path, false)

View File

@@ -18,7 +18,7 @@ type storageGCSFlags struct {
embedCredentials bool
}
func (c *storageGCSFlags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) {
func (c *storageGCSFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("bucket", "Name of the Google Cloud Storage bucket").Required().StringVar(&c.options.BucketName)
cmd.Flag("prefix", "Prefix to use for objects in the bucket").StringVar(&c.options.Prefix)
cmd.Flag("read-only", "Use read-only GCS scope to prevent write access").BoolVar(&c.options.ReadOnly)
@@ -28,7 +28,7 @@ func (c *storageGCSFlags) setup(_ storageProviderServices, cmd *kingpin.CmdClaus
commonThrottlingFlags(cmd, &c.options.Limits)
}
func (c *storageGCSFlags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
func (c *storageGCSFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
if c.embedCredentials {
data, err := os.ReadFile(c.options.ServiceAccountCredentialsFile)
if err != nil {

View File

@@ -0,0 +1,29 @@
package cli_test
import (
"context"
"github.com/alecthomas/kingpin"
"github.com/kopia/kopia/cli"
"github.com/kopia/kopia/internal/repotesting"
"github.com/kopia/kopia/repo/blob"
)
// storageInMemoryFlags is in-memory storage initialization flags for cli
// setup.
type storageInMemoryFlags struct {
options repotesting.ReconnectableStorageOptions
}
func (c *storageInMemoryFlags) Setup(_ cli.StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("uuid", "UUID of the reconnectable in-memory storage").Required().StringVar(&c.options.UUID)
}
func (c *storageInMemoryFlags) Connect(ctx context.Context, isCreate bool, _ int) (blob.Storage, error) {
// nolint:wrapcheck
return blob.NewStorage(ctx, blob.ConnectionInfo{
Type: repotesting.ReconnectableStorageType,
Config: &c.options,
}, isCreate)
}

View File

@@ -9,37 +9,43 @@
"github.com/kopia/kopia/repo/blob/throttling"
)
type storageProviderServices interface {
// StorageProviderServices is implemented by the cli App that allows the cli
// and tests to mutate the default storage providers.
type StorageProviderServices interface {
AddStorageProvider(p StorageProvider)
setPasswordFromToken(pwd string)
storageProviders() []StorageProvider
}
type storageFlags interface {
setup(sps storageProviderServices, cmd *kingpin.CmdClause)
connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error)
// StorageFlags is implemented by cli storage providers which need to support a
// particular backend. This requires the common setup and connection methods
// implemented by all the cli storage providers.
type StorageFlags interface {
Setup(sps StorageProviderServices, cmd *kingpin.CmdClause)
Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error)
}
type storageProvider struct {
name string
description string
newFlags func() storageFlags
}
func cliStorageProviders() []storageProvider {
return []storageProvider{
{"from-config", "the provided configuration file", func() storageFlags { return &storageFromConfigFlags{} }},
{"azure", "an Azure blob storage", func() storageFlags { return &storageAzureFlags{} }},
{"b2", "a B2 bucket", func() storageFlags { return &storageB2Flags{} }},
{"filesystem", "a filesystem", func() storageFlags { return &storageFilesystemFlags{} }},
{"gcs", "a Google Cloud Storage bucket", func() storageFlags { return &storageGCSFlags{} }},
{"rclone", "a rclone-based provided", func() storageFlags { return &storageRcloneFlags{} }},
{"s3", "an S3 bucket", func() storageFlags { return &storageS3Flags{} }},
{"sftp", "an SFTP storage", func() storageFlags { return &storageSFTPFlags{} }},
{"webdav", "a WebDAV storage", func() storageFlags { return &storageWebDAVFlags{} }},
}
// StorageProvider is a CLI provider for storage options and allows the CLI to
// multiplex between various provider CLI flag constructors.
type StorageProvider struct {
Name string
Description string
NewFlags func() StorageFlags
}
func commonThrottlingFlags(cmd *kingpin.CmdClause, limits *throttling.Limits) {
cmd.Flag("max-download-speed", "Limit the download speed.").PlaceHolder("BYTES_PER_SEC").FloatVar(&limits.DownloadBytesPerSecond)
cmd.Flag("max-upload-speed", "Limit the upload speed.").PlaceHolder("BYTES_PER_SEC").FloatVar(&limits.UploadBytesPerSecond)
}
// AddStorageProvider adds a new StorageProvider at runtime after the App has
// been initialized with the default providers. This is used in tests which
// require custom storage providers to simulate various edge cases.
func (c *App) AddStorageProvider(p StorageProvider) {
c.cliStorageProviders = append(c.cliStorageProviders, p)
}
func (c *App) storageProviders() []StorageProvider {
return c.cliStorageProviders
}

View File

@@ -17,7 +17,7 @@ type storageRcloneFlags struct {
embedRCloneConfigFile string
}
func (c *storageRcloneFlags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) {
func (c *storageRcloneFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("remote-path", "Rclone remote:path").Required().StringVar(&c.opt.RemotePath)
cmd.Flag("flat", "Use flat directory structure").BoolVar(&c.connectFlat)
cmd.Flag("rclone-exe", "Path to rclone binary").StringVar(&c.opt.RCloneExe)
@@ -32,7 +32,7 @@ func (c *storageRcloneFlags) setup(_ storageProviderServices, cmd *kingpin.CmdCl
commonThrottlingFlags(cmd, &c.opt.Limits)
}
func (c *storageRcloneFlags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
func (c *storageRcloneFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
c.opt.DirectoryShards = initialDirectoryShards(c.connectFlat, formatVersion)
if c.embedRCloneConfigFile != "" {

View File

@@ -15,7 +15,7 @@ type storageS3Flags struct {
s3options s3.Options
}
func (c *storageS3Flags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) {
func (c *storageS3Flags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("bucket", "Name of the S3 bucket").Required().StringVar(&c.s3options.BucketName)
cmd.Flag("endpoint", "Endpoint to use").Default("s3.amazonaws.com").StringVar(&c.s3options.Endpoint)
cmd.Flag("region", "S3 Region").Default("").StringVar(&c.s3options.Region)
@@ -46,7 +46,7 @@ func (c *storageS3Flags) setup(_ storageProviderServices, cmd *kingpin.CmdClause
cmd.Flag("point-in-time", "Use a point-in-time view of the storage repository when supported").PlaceHolder(time.RFC3339).PreAction(pitPreAction).StringVar(&pointInTimeStr)
}
func (c *storageS3Flags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
func (c *storageS3Flags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
if isCreate && c.s3options.PointInTime != nil && !c.s3options.PointInTime.IsZero() {
return nil, errors.New("Cannot specify a 'point-in-time' option when creating a repository")
}

View File

@@ -18,7 +18,7 @@ type storageSFTPFlags struct {
embedCredentials bool
}
func (c *storageSFTPFlags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) {
func (c *storageSFTPFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("path", "Path to the repository in the SFTP/SSH server").Required().StringVar(&c.options.Path)
cmd.Flag("host", "SFTP/SSH server hostname").Required().StringVar(&c.options.Host)
cmd.Flag("port", "SFTP/SSH server port").Default("22").IntVar(&c.options.Port)
@@ -109,7 +109,7 @@ func (c *storageSFTPFlags) getOptions(formatVersion int) (*sftp.Options, error)
return &sftpo, nil
}
func (c *storageSFTPFlags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
func (c *storageSFTPFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
opt, err := c.getOptions(formatVersion)
if err != nil {
return nil, err

View File

@@ -15,7 +15,7 @@ type storageWebDAVFlags struct {
connectFlat bool
}
func (c *storageWebDAVFlags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) {
func (c *storageWebDAVFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("url", "URL of WebDAV server").Required().StringVar(&c.options.URL)
cmd.Flag("flat", "Use flat directory structure").BoolVar(&c.connectFlat)
cmd.Flag("webdav-username", "WebDAV username").Envar("KOPIA_WEBDAV_USERNAME").StringVar(&c.options.Username)
@@ -26,7 +26,7 @@ func (c *storageWebDAVFlags) setup(_ storageProviderServices, cmd *kingpin.CmdCl
commonThrottlingFlags(cmd, &c.options.Limits)
}
func (c *storageWebDAVFlags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
func (c *storageWebDAVFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
wo := c.options
if wo.Username != "" && wo.Password == "" {

View File

@@ -18,23 +18,25 @@
type reconnectableStorage struct {
blob.Storage
opt *reconnectableStorageOptions
opt *ReconnectableStorageOptions
}
const reconnectableStorageType = "reconnectable"
// ReconnectableStorageType is the unique storage type identifier for
// reconnectable storage backend.
const ReconnectableStorageType = "reconnectable"
// reconnectableStorageOptions provides options to reconnectable storage.
type reconnectableStorageOptions struct {
// ReconnectableStorageOptions provides options to reconnectable storage.
type ReconnectableStorageOptions struct {
UUID string
}
// newReconnectableStorage wraps the provided storage that may or may not be round-trippable
// NewReconnectableStorage wraps the provided storage that may or may not be round-trippable
// in a wrapper that globally caches storage instance and ensures its connection info is
// round-trippable.
func newReconnectableStorage(tb testing.TB, st blob.Storage) blob.Storage {
func NewReconnectableStorage(tb testing.TB, st blob.Storage) blob.Storage {
tb.Helper()
st2 := reconnectableStorage{st, &reconnectableStorageOptions{UUID: uuid.NewString()}}
st2 := reconnectableStorage{st, &ReconnectableStorageOptions{UUID: uuid.NewString()}}
reconnectableStorageByUUID.Store(st2.opt.UUID, st2)
tb.Cleanup(func() {
@@ -49,17 +51,17 @@ func newReconnectableStorage(tb testing.TB, st blob.Storage) blob.Storage {
func (s reconnectableStorage) ConnectionInfo() blob.ConnectionInfo {
return blob.ConnectionInfo{
Type: reconnectableStorageType,
Type: ReconnectableStorageType,
Config: s.opt,
}
}
func init() {
blob.AddSupportedStorage(
reconnectableStorageType,
func() interface{} { return &reconnectableStorageOptions{} },
ReconnectableStorageType,
func() interface{} { return &ReconnectableStorageOptions{} },
func(ctx context.Context, o interface{}, isCreate bool) (blob.Storage, error) {
opt, ok := o.(*reconnectableStorageOptions)
opt, ok := o.(*ReconnectableStorageOptions)
if !ok {
return nil, errors.Errorf("invalid options %T", o)
}

View File

@@ -84,7 +84,7 @@ func (e *Environment) setup(tb testing.TB, version content.FormatVersion, opts .
st = blobtesting.NewVersionedMapStorage(openOpt.TimeNowFunc)
}
st = newReconnectableStorage(tb, st)
st = NewReconnectableStorage(tb, st)
e.st = st
if e.Password == "" {