storj/cmd/uplinkng/ultest/setup.go
Jeff Wendling 98be54b9a3 cmd/uplinkng: refactor into some focused packages
the directory was starting to get pretty large and
it was making it hard to pick concise names for
types and variables. this moves the location
stuff into a cmd/uplinkng/ulloc package, the
filesystem stuff into a cmd/uplinkng/ulfs package,
and the testing stuff into a cmd/uplinkng/ultest
package.

this should make the remaining stuff in cmd/uplinkng
only the business logic of how to implement the
commands, rather than also including a bunch of
helper utilities and scaffolding.

Change-Id: Id0901625ebfff9b1cf2dae52366aceb3b6c8f5b6
2021-06-14 15:23:41 -04:00

155 lines
3.7 KiB
Go

// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
package ultest
import (
"bytes"
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/zeebo/clingy"
"github.com/zeebo/errs"
"storj.io/storj/cmd/uplinkng/ulfs"
"storj.io/storj/cmd/uplinkng/ulloc"
)
// Setup returns some State that can be run multiple times with different command
// line arguments.
func Setup(cmds func(clingy.Commands, clingy.Flags), opts ...ExecuteOption) State {
return State{
cmds: cmds,
opts: opts,
}
}
// State represents some state and environment for a command to execute in.
type State struct {
cmds func(clingy.Commands, clingy.Flags)
opts []ExecuteOption
}
// With appends the provided options and returns a new State.
func (st State) With(opts ...ExecuteOption) State {
st.opts = append([]ExecuteOption(nil), st.opts...)
st.opts = append(st.opts, opts...)
return st
}
// Succeed is the same as Run followed by result.RequireSuccess.
func (st State) Succeed(t *testing.T, args ...string) Result {
result := st.Run(t, args...)
result.RequireSuccess(t)
return result
}
// Fail is the same as Run followed by result.RequireFailure.
func (st State) Fail(t *testing.T, args ...string) Result {
result := st.Run(t, args...)
result.RequireFailure(t)
return result
}
// Run executes the command specified by the args and returns a Result.
func (st State) Run(t *testing.T, args ...string) Result {
var stdout bytes.Buffer
var stderr bytes.Buffer
var stdin bytes.Buffer
var ops []Operation
var ran bool
ok, err := clingy.Environment{
Name: "uplink-test",
Args: args,
Stdin: &stdin,
Stdout: &stdout,
Stderr: &stderr,
Wrap: func(ctx clingy.Context, cmd clingy.Cmd) error {
tfs := newTestFilesystem()
for _, opt := range st.opts {
if err := opt.fn(ctx, tfs); err != nil {
return errs.Wrap(err)
}
}
tfs.ops = nil
if len(tfs.stdin) > 0 {
_, _ = stdin.WriteString(tfs.stdin)
}
if setter, ok := cmd.(interface {
SetTestFilesystem(ulfs.Filesystem)
}); ok {
setter.SetTestFilesystem(tfs)
}
ran = true
err := cmd.Execute(ctx)
ops = tfs.ops
return err
},
}.Run(context.Background(), st.cmds)
if ok && err == nil {
require.True(t, ran, "no command was executed: %q", args)
}
return Result{
Stdout: stdout.String(),
Stderr: stderr.String(),
Ok: ok,
Err: err,
Operations: ops,
}
}
// ExecuteOption allows one to control the environment that a command executes in.
type ExecuteOption struct {
fn func(ctx clingy.Context, tfs *testFilesystem) error
}
// WithStdin sets the command to execute with the provided string as standard input.
func WithStdin(stdin string) ExecuteOption {
return ExecuteOption{func(_ clingy.Context, tfs *testFilesystem) error {
tfs.stdin = stdin
return nil
}}
}
// WithFile sets the command to execute with a file created at the given location.
func WithFile(location string) ExecuteOption {
return ExecuteOption{func(ctx clingy.Context, tfs *testFilesystem) error {
loc, err := ulloc.Parse(location)
if err != nil {
return err
}
if bucket, _, ok := loc.RemoteParts(); ok {
tfs.ensureBucket(bucket)
}
wh, err := tfs.Create(ctx, loc)
if err != nil {
return err
}
return wh.Commit()
}}
}
// WithPendingFile sets the command to execute with a pending upload happening to
// the provided location.
func WithPendingFile(location string) ExecuteOption {
return ExecuteOption{func(ctx clingy.Context, tfs *testFilesystem) error {
loc, err := ulloc.Parse(location)
if err != nil {
return err
}
if bucket, _, ok := loc.RemoteParts(); ok {
tfs.ensureBucket(bucket)
}
_, err = tfs.Create(ctx, loc)
return err
}}
}