2021-05-06 17:56:57 +01:00
|
|
|
// 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"
|
|
|
|
|
2021-05-26 21:19:29 +01:00
|
|
|
"storj.io/storj/cmd/uplinkng/ulext"
|
2021-05-06 17:56:57 +01:00
|
|
|
"storj.io/storj/cmd/uplinkng/ulfs"
|
|
|
|
"storj.io/storj/cmd/uplinkng/ulloc"
|
|
|
|
)
|
|
|
|
|
2021-05-26 21:19:29 +01:00
|
|
|
// Commands is an alias to refer to a function that builds clingy commands.
|
|
|
|
type Commands = func(clingy.Commands, ulext.External)
|
|
|
|
|
2021-05-06 17:56:57 +01:00
|
|
|
// Setup returns some State that can be run multiple times with different command
|
|
|
|
// line arguments.
|
2021-05-26 21:19:29 +01:00
|
|
|
func Setup(cmds Commands, opts ...ExecuteOption) State {
|
2021-05-06 17:56:57 +01:00
|
|
|
return State{
|
|
|
|
cmds: cmds,
|
|
|
|
opts: opts,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// State represents some state and environment for a command to execute in.
|
|
|
|
type State struct {
|
2021-05-26 21:19:29 +01:00
|
|
|
cmds Commands
|
2021-05-06 17:56:57 +01:00
|
|
|
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 ran bool
|
|
|
|
|
2021-05-12 17:16:56 +01:00
|
|
|
tfs := newTestFilesystem()
|
|
|
|
|
2021-05-06 17:56:57 +01:00
|
|
|
ok, err := clingy.Environment{
|
|
|
|
Name: "uplink-test",
|
|
|
|
Args: args,
|
|
|
|
|
|
|
|
Stdin: &stdin,
|
|
|
|
Stdout: &stdout,
|
|
|
|
Stderr: &stderr,
|
|
|
|
|
2021-05-25 00:11:50 +01:00
|
|
|
Wrap: func(ctx clingy.Context, cmd clingy.Command) error {
|
2021-05-06 17:56:57 +01:00
|
|
|
for _, opt := range st.opts {
|
2021-05-12 17:16:56 +01:00
|
|
|
opt.fn(t, ctx, tfs)
|
2021-05-06 17:56:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(tfs.stdin) > 0 {
|
|
|
|
_, _ = stdin.WriteString(tfs.stdin)
|
|
|
|
}
|
|
|
|
|
|
|
|
ran = true
|
2021-05-12 17:16:56 +01:00
|
|
|
return cmd.Execute(ctx)
|
2021-05-06 17:56:57 +01:00
|
|
|
},
|
2021-05-26 21:19:29 +01:00
|
|
|
}.Run(context.Background(), func(cmds clingy.Commands) {
|
|
|
|
st.cmds(cmds, newExternal(tfs, nil))
|
|
|
|
})
|
2021-05-06 17:56:57 +01:00
|
|
|
|
|
|
|
if ok && err == nil {
|
|
|
|
require.True(t, ran, "no command was executed: %q", args)
|
|
|
|
}
|
2021-05-12 17:16:56 +01:00
|
|
|
|
2021-05-06 17:56:57 +01:00
|
|
|
return Result{
|
2021-10-02 00:47:53 +01:00
|
|
|
Stdout: stdout.String(),
|
|
|
|
Stderr: stderr.String(),
|
|
|
|
Ok: ok,
|
|
|
|
Err: err,
|
|
|
|
Files: tfs.Files(),
|
|
|
|
Pending: tfs.Pending(),
|
2021-05-06 17:56:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExecuteOption allows one to control the environment that a command executes in.
|
|
|
|
type ExecuteOption struct {
|
2021-05-12 17:16:56 +01:00
|
|
|
fn func(t *testing.T, ctx clingy.Context, tfs *testFilesystem)
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithFilesystem lets one do arbitrary setup on the filesystem in a callback.
|
|
|
|
func WithFilesystem(cb func(t *testing.T, ctx clingy.Context, fs ulfs.Filesystem)) ExecuteOption {
|
|
|
|
return ExecuteOption{func(t *testing.T, ctx clingy.Context, tfs *testFilesystem) {
|
|
|
|
cb(t, ctx, tfs)
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithBucket ensures the bucket exists.
|
|
|
|
func WithBucket(name string) ExecuteOption {
|
|
|
|
return ExecuteOption{func(_ *testing.T, _ clingy.Context, tfs *testFilesystem) {
|
|
|
|
tfs.ensureBucket(name)
|
|
|
|
}}
|
2021-05-06 17:56:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// WithStdin sets the command to execute with the provided string as standard input.
|
|
|
|
func WithStdin(stdin string) ExecuteOption {
|
2021-05-12 17:16:56 +01:00
|
|
|
return ExecuteOption{func(_ *testing.T, _ clingy.Context, tfs *testFilesystem) {
|
2021-05-06 17:56:57 +01:00
|
|
|
tfs.stdin = stdin
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithFile sets the command to execute with a file created at the given location.
|
2021-05-12 17:16:56 +01:00
|
|
|
func WithFile(location string, contents ...string) ExecuteOption {
|
|
|
|
contents = append([]string(nil), contents...)
|
|
|
|
return ExecuteOption{func(t *testing.T, ctx clingy.Context, tfs *testFilesystem) {
|
2021-05-06 17:56:57 +01:00
|
|
|
loc, err := ulloc.Parse(location)
|
2021-05-12 17:16:56 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-05-06 17:56:57 +01:00
|
|
|
if bucket, _, ok := loc.RemoteParts(); ok {
|
|
|
|
tfs.ensureBucket(bucket)
|
|
|
|
}
|
2021-05-12 17:16:56 +01:00
|
|
|
|
2021-05-06 17:56:57 +01:00
|
|
|
wh, err := tfs.Create(ctx, loc)
|
2021-05-12 17:16:56 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
defer func() { _ = wh.Abort() }()
|
|
|
|
|
|
|
|
for _, content := range contents {
|
|
|
|
_, err := wh.Write([]byte(content))
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
if len(contents) == 0 {
|
|
|
|
_, err := wh.Write([]byte(location))
|
|
|
|
require.NoError(t, err)
|
2021-05-06 17:56:57 +01:00
|
|
|
}
|
2021-05-12 17:16:56 +01:00
|
|
|
|
|
|
|
require.NoError(t, wh.Commit())
|
2021-05-06 17:56:57 +01:00
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithPendingFile sets the command to execute with a pending upload happening to
|
|
|
|
// the provided location.
|
|
|
|
func WithPendingFile(location string) ExecuteOption {
|
2021-05-12 17:16:56 +01:00
|
|
|
return ExecuteOption{func(t *testing.T, ctx clingy.Context, tfs *testFilesystem) {
|
2021-05-06 17:56:57 +01:00
|
|
|
loc, err := ulloc.Parse(location)
|
2021-05-12 17:16:56 +01:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-05-06 17:56:57 +01:00
|
|
|
if bucket, _, ok := loc.RemoteParts(); ok {
|
|
|
|
tfs.ensureBucket(bucket)
|
2021-10-02 00:47:53 +01:00
|
|
|
} else {
|
|
|
|
t.Fatalf("Invalid pending local file: %s", loc)
|
2021-05-06 17:56:57 +01:00
|
|
|
}
|
2021-05-12 17:16:56 +01:00
|
|
|
|
2021-05-06 17:56:57 +01:00
|
|
|
_, err = tfs.Create(ctx, loc)
|
2021-05-12 17:16:56 +01:00
|
|
|
require.NoError(t, err)
|
2021-05-06 17:56:57 +01:00
|
|
|
}}
|
|
|
|
}
|