Add a CliSuite for cli unit tests

Makes writing unit tests for commands quite easier
This commit is contained in:
Djordje Lukic 2020-05-20 15:06:08 +02:00
parent fe36c49246
commit 8495500aa2
7 changed files with 96 additions and 130 deletions

View File

@ -1,5 +1,5 @@
# syntax = docker/dockerfile:experimental # syntax = docker/dockerfile:experimental
ARG GO_VERSION=1.14.3-alpine3.11 ARG GO_VERSION=1.14.3-alpine
FROM golang:${GO_VERSION} AS base FROM golang:${GO_VERSION} AS base
ARG TARGET_OS=unknown ARG TARGET_OS=unknown
@ -7,7 +7,7 @@ ARG TARGET_ARCH=unknown
ARG PWD=/api ARG PWD=/api
ENV GO111MODULE=on ENV GO111MODULE=on
RUN apk update && apk add docker make RUN apk update && apk add -U docker make
WORKDIR ${PWD} WORKDIR ${PWD}
ADD go.* ${PWD} ADD go.* ${PWD}

View File

@ -1,76 +1,23 @@
package context package context
import ( import (
"context"
"io/ioutil"
"os"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"gotest.tools/v3/golden" "gotest.tools/v3/golden"
apicontext "github.com/docker/api/context" "github.com/docker/api/tests/framework"
"github.com/docker/api/context/store"
) )
type ContextSuite struct { type ContextSuite struct {
suite.Suite framework.CliSuite
ctx context.Context
writer *os.File
reader *os.File
originalStdout *os.File
storeRoot string
}
func (sut *ContextSuite) BeforeTest(suiteName, testName string) {
ctx := context.Background()
ctx = apicontext.WithCurrentContext(ctx, "example")
dir, err := ioutil.TempDir("", "store")
require.Nil(sut.T(), err)
s, err := store.New(
store.WithRoot(dir),
)
require.Nil(sut.T(), err)
err = s.Create("example", store.TypedContext{
Type: "example",
})
require.Nil(sut.T(), err)
sut.storeRoot = dir
ctx = store.WithContextStore(ctx, s)
sut.ctx = ctx
sut.originalStdout = os.Stdout
r, w, err := os.Pipe()
require.Nil(sut.T(), err)
os.Stdout = w
sut.writer = w
sut.reader = r
}
func (sut *ContextSuite) getStdOut() string {
err := sut.writer.Close()
require.Nil(sut.T(), err)
out, _ := ioutil.ReadAll(sut.reader)
return string(out)
}
func (sut *ContextSuite) AfterTest(suiteName, testName string) {
os.Stdout = sut.originalStdout
err := os.RemoveAll(sut.storeRoot)
require.Nil(sut.T(), err)
} }
func (sut *ContextSuite) TestLs() { func (sut *ContextSuite) TestLs() {
err := runList(sut.ctx) err := runList(sut.Context())
require.Nil(sut.T(), err) require.Nil(sut.T(), err)
golden.Assert(sut.T(), sut.getStdOut(), "ls-out.golden") golden.Assert(sut.T(), sut.GetStdOut(), "ls-out.golden")
} }
func TestPs(t *testing.T) { func TestPs(t *testing.T) {

View File

@ -1,72 +1,18 @@
package cmd package cmd
import ( import (
"context"
"io/ioutil"
"os"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"gotest.tools/v3/golden" "gotest.tools/v3/golden"
apicontext "github.com/docker/api/context"
"github.com/docker/api/context/store"
_ "github.com/docker/api/example" _ "github.com/docker/api/example"
"github.com/docker/api/tests/framework"
) )
type PsSuite struct { type PsSuite struct {
suite.Suite framework.CliSuite
ctx context.Context
writer *os.File
reader *os.File
originalStdout *os.File
storeRoot string
}
func (sut *PsSuite) BeforeTest(suiteName, testName string) {
ctx := context.Background()
ctx = apicontext.WithCurrentContext(ctx, "example")
dir, err := ioutil.TempDir("", "store")
require.Nil(sut.T(), err)
s, err := store.New(
store.WithRoot(dir),
)
require.Nil(sut.T(), err)
err = s.Create("example", store.TypedContext{
Type: "example",
})
require.Nil(sut.T(), err)
sut.storeRoot = dir
ctx = store.WithContextStore(ctx, s)
sut.ctx = ctx
sut.originalStdout = os.Stdout
r, w, err := os.Pipe()
require.Nil(sut.T(), err)
os.Stdout = w
sut.writer = w
sut.reader = r
}
func (sut *PsSuite) getStdOut() string {
err := sut.writer.Close()
require.Nil(sut.T(), err)
out, _ := ioutil.ReadAll(sut.reader)
return string(out)
}
func (sut *PsSuite) AfterTest(suiteName, testName string) {
os.Stdout = sut.originalStdout
err := os.RemoveAll(sut.storeRoot)
require.Nil(sut.T(), err)
} }
func (sut *PsSuite) TestPs() { func (sut *PsSuite) TestPs() {
@ -74,10 +20,10 @@ func (sut *PsSuite) TestPs() {
quiet: false, quiet: false,
} }
err := runPs(sut.ctx, opts) err := runPs(sut.Context(), opts)
assert.Nil(sut.T(), err) require.Nil(sut.T(), err)
golden.Assert(sut.T(), sut.getStdOut(), "ps-out.golden") golden.Assert(sut.T(), sut.GetStdOut(), "ps-out.golden")
} }
func (sut *PsSuite) TestPsQuiet() { func (sut *PsSuite) TestPsQuiet() {
@ -85,10 +31,10 @@ func (sut *PsSuite) TestPsQuiet() {
quiet: true, quiet: true,
} }
err := runPs(sut.ctx, opts) err := runPs(sut.Context(), opts)
assert.Nil(sut.T(), err) require.Nil(sut.T(), err)
golden.Assert(sut.T(), sut.getStdOut(), "ps-out-quiet.golden") golden.Assert(sut.T(), sut.GetStdOut(), "ps-out-quiet.golden")
} }
func TestPs(t *testing.T) { func TestPs(t *testing.T) {

View File

@ -146,12 +146,12 @@ func read(meta string, getter func() interface{}) (*Metadata, error) {
} }
var um untypedMetadata var um untypedMetadata
if err := marshalTyped(bytes, &um); err != nil { if err := json.Unmarshal(bytes, &um); err != nil {
return nil, err return nil, err
} }
var uc untypedContext var uc untypedContext
if err := marshalTyped(um.Metadata, &uc); err != nil { if err := json.Unmarshal(um.Metadata, &uc); err != nil {
return nil, err return nil, err
} }
if uc.Type == "" { if uc.Type == "" {
@ -178,10 +178,6 @@ func read(meta string, getter func() interface{}) (*Metadata, error) {
}, nil }, nil
} }
func marshalTyped(in []byte, val interface{}) error {
return json.Unmarshal(in, val)
}
func parse(payload []byte, getter func() interface{}) (interface{}, error) { func parse(payload []byte, getter func() interface{}) (interface{}, error) {
if getter == nil { if getter == nil {
var res map[string]interface{} var res map[string]interface{}
@ -266,7 +262,7 @@ func (s *store) List() ([]*Metadata, error) {
// The default context is not stored in the store, it is in-memory only // The default context is not stored in the store, it is in-memory only
// so we need a special case for it. // so we need a special case for it.
dockerDefault, err := dockerGefaultContext() dockerDefault, err := dockerDefaultContext()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -31,7 +31,7 @@ type endpoint struct {
DefaultNamespace string DefaultNamespace string
} }
func dockerGefaultContext() (*Metadata, error) { func dockerDefaultContext() (*Metadata, error) {
cmd := exec.Command("docker", "context", "inspect", "default") cmd := exec.Command("docker", "context", "inspect", "default")
var stdout bytes.Buffer var stdout bytes.Buffer
cmd.Stdout = &stdout cmd.Stdout = &stdout

View File

@ -7,7 +7,7 @@ import (
) )
func TestDefaultContext(t *testing.T) { func TestDefaultContext(t *testing.T) {
s, err := dockerGefaultContext() s, err := dockerDefaultContext()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "default", s.Name) assert.Equal(t, "default", s.Name)
} }

View File

@ -0,0 +1,77 @@
package framework
import (
"context"
"io/ioutil"
"os"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
apicontext "github.com/docker/api/context"
"github.com/docker/api/context/store"
)
// CliSuite is a helper struct that creates a configured context
// and captures the output of a command. it should be used in the
// same way as testify.suite.Suite
type CliSuite struct {
suite.Suite
ctx context.Context
writer *os.File
reader *os.File
OriginalStdout *os.File
storeRoot string
}
// BeforeTest is called by testify.suite
func (sut *CliSuite) BeforeTest(suiteName, testName string) {
ctx := context.Background()
ctx = apicontext.WithCurrentContext(ctx, "example")
dir, err := ioutil.TempDir("", "store")
require.Nil(sut.T(), err)
s, err := store.New(
store.WithRoot(dir),
)
require.Nil(sut.T(), err)
err = s.Create("example", store.TypedContext{
Type: "example",
})
require.Nil(sut.T(), err)
sut.storeRoot = dir
ctx = store.WithContextStore(ctx, s)
sut.ctx = ctx
sut.OriginalStdout = os.Stdout
r, w, err := os.Pipe()
require.Nil(sut.T(), err)
os.Stdout = w
sut.writer = w
sut.reader = r
}
// Context returns a configured context
func (sut *CliSuite) Context() context.Context {
return sut.ctx
}
// GetStdOut returns the output of the command
func (sut *CliSuite) GetStdOut() string {
err := sut.writer.Close()
require.Nil(sut.T(), err)
out, _ := ioutil.ReadAll(sut.reader)
return string(out)
}
// AfterTest is called by testify.suite
func (sut *CliSuite) AfterTest(suiteName, testName string) {
os.Stdout = sut.OriginalStdout
err := os.RemoveAll(sut.storeRoot)
require.Nil(sut.T(), err)
}