diff --git a/Makefile b/Makefile index e4a6d6e..19f37a4 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ # tool variables -GO=go +GO=GO111MODULE=on go DEP=dep GOMETALINTER=gometalinter @@ -9,23 +9,14 @@ Q=@ ## make rules -all: get build install - -get: - $(Q)$(DEP) ensure - build: $(Q)$(GO) build $(GO_BUILD_FLAGS) -install: - $(Q)$(GO) install - test: $(Q)$(GO) test lint: $(Q)$(GOMETALINTER) $(GOMETALINTER_FLAGS) . - .PHONY: all build get install test lint diff --git a/container.go b/container.go index d5879a0..34f4466 100644 --- a/container.go +++ b/container.go @@ -80,8 +80,7 @@ func ResetCustom(fn func() error) ResetFunc { // ContainerOpts is an option struct for creating a docker container // configuration. type ContainerOpts struct { - ForcePull bool - // AutoRemove is always set to true + ForcePull bool Config *container.Config HostConfig *container.HostConfig Name string @@ -116,6 +115,7 @@ type Container struct { // nolint: maligned cancel func() resetF ResetFunc closed bool + removed bool } // Creates a new container configuration with the given options. @@ -125,11 +125,9 @@ func newContainer(t testing.TB, c *client.Client, opts ContainerOpts) *Container opts.HealthCheckTimeout = 30 * time.Second } - // always autoremove if opts.HostConfig == nil { opts.HostConfig = &container.HostConfig{} } - opts.HostConfig.AutoRemove = true // set testingdock label opts.Config.Labels = createTestingLabel() @@ -175,7 +173,7 @@ func (c *Container) start(ctx context.Context) { // nolint: gocyclo } if len(images) == 0 || c.forcePull { - printf("(setup) %-25s - pulling image", c.ccfg.Image) + printf("(setup ) %-25s - pulling image", c.ccfg.Image) img, err := c.imagePull(ctx) if err != nil { c.t.Fatalf("image downloading failure of '%s': %s", c.ccfg.Image, err.Error()) @@ -209,10 +207,11 @@ func (c *Container) start(ctx context.Context) { // nolint: gocyclo c.t.Fatalf("container disconnect failure: %s", err.Error()) } printf("(cancel) %-25s (%s) - container disconnected from: %s", c.Name, c.ID, c.network.name) - if err := c.cli.ContainerRemove(ctx, c.ID, types.ContainerRemoveOptions{Force: true}); err != nil { - c.t.Fatalf("container removal failure: %s", err.Error()) + timeout := time.Second * 5 + if err := c.cli.ContainerStop(ctx, c.ID, &timeout); err != nil { + c.t.Fatalf("container stop failure: %s", err.Error()) } - printf("(cancel) %-25s (%s) - container removed", c.Name, c.ID) + printf("(cancel) %-25s (%s) - container stopped", c.Name, c.ID) } // start the container finally @@ -220,6 +219,9 @@ func (c *Container) start(ctx context.Context) { // nolint: gocyclo c.t.Fatalf("container start failure: %s", err.Error()) } + c.closed = false + c.removed = false + printf("(setup ) %-25s (%s) - container started", c.Name, c.ID) // start container logging @@ -303,6 +305,7 @@ func (c *Container) initialCleanup(ctx context.Context) { if err = c.cli.ContainerRemove(ctx, cont.ID, types.ContainerRemoveOptions{ Force: true, RemoveVolumes: true, + RemoveLinks: true, }); err != nil { c.t.Fatalf("container removal failure: %s", err.Error()) } @@ -343,6 +346,43 @@ func (c *Container) close() error { return nil } +// remove removes/cleans up the container +func (c *Container) remove() { + if !c.closed { + c.t.Fatalf("container removal failed, please close containers first") + } + + if SpawnSequential { + for _, cont := range c.children { + cont.remove() // nolint: errcheck + } + } else { + var wg sync.WaitGroup + + wg.Add(len(c.children)) + for _, cont := range c.children { + go func(cont *Container) { + defer wg.Done() + cont.remove() // nolint: errcheck + }(cont) + } + wg.Wait() + } + + if c.removed { + return + } + + if err := c.cli.ContainerRemove(context.TODO(), c.ID, types.ContainerRemoveOptions{ + Force: true, + RemoveVolumes: true, + }); err != nil { + c.t.Fatalf("container removal failure: %s", err.Error()) + } + c.removed = true + printf("(remove ) %-25s (%s) - container removed/cleaned up", c.Name, c.ID) +} + // After adds a child container (dependency, sort of) // to the current container configuration in the same network. func (c *Container) After(cc *Container) { @@ -357,6 +397,10 @@ func (c *Container) reset(ctx context.Context) { if err := c.resetF(ctx, c); err != nil { c.t.Fatalf("container reset failure: %s", err.Error()) } + + c.closed = false + c.removed = false + c.executeHealthCheck(ctx) for _, cc := range c.children { diff --git a/container_test.go b/container_test.go index 5a79152..3ce3723 100644 --- a/container_test.go +++ b/container_test.go @@ -5,17 +5,30 @@ import ( "database/sql" "testing" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" "github.com/docker/go-connections/nat" _ "github.com/lib/pq" - "github.com/m4ksio/testingdock" + "github.com/dapperlabs/testingdock" ) func TestContainer_Start(t *testing.T) { + // set up docker client + cli, err := client.NewClientWithOpts( + client.FromEnv, + client.WithAPIVersionNegotiation(), + ) + if err != nil { + t.Fatalf("error creating docker client: %s", err.Error()) + } + // create suite name := "TestContainer_Start" - s, ok := testingdock.GetOrCreateSuite(t, name, testingdock.SuiteOpts{}) + s, ok := testingdock.GetOrCreateSuite(t, name, testingdock.SuiteOpts{ + Client: cli, + }) if ok { t.Fatal("this suite should not exists yet") } @@ -39,6 +52,7 @@ func TestContainer_Start(t *testing.T) { ForcePull: false, Config: &container.Config{ Image: "postgres:9.6", + Env: []string{"POSTGRES_HOST_AUTH_METHOD=trust"}, }, HostConfig: &container.HostConfig{ PortBindings: nat.PortMap{ @@ -79,6 +93,7 @@ func TestContainer_Start(t *testing.T) { ForcePull: true, Config: &container.Config{ Image: "postgres:9.6", + Env: []string{"POSTGRES_HOST_AUTH_METHOD=trust"}, }, }) @@ -91,7 +106,10 @@ func TestContainer_Start(t *testing.T) { // start the network, this also starts the containers s.Start(context.TODO()) - defer s.Close() + defer func() { + s.Close() + s.Remove() + }() // test stuff within the database testQueries(t, db) @@ -99,6 +117,29 @@ func TestContainer_Start(t *testing.T) { s.Reset(context.TODO()) testQueries(t, db) + + if err = s.Close(); err != nil { + t.Fatalf("could not close containers: %s", err.Error()) + } + + list0, err := cli.ContainerList(context.TODO(), types.ContainerListOptions{All: true}) + if err != nil { + t.Fatalf("could not retreive container list: %s", err.Error()) + } + + if err = s.Remove(); err != nil { + t.Fatalf("could not remove containers: %s", err.Error()) + } + + list1, err := cli.ContainerList(context.TODO(), types.ContainerListOptions{All: true}) + if err != nil { + t.Fatalf("could not retreive container list: %s", err.Error()) + } + + if len(list0) != len(list1)+3 { + t.Fatalf("expected Remove to remove 3 containers from container list (len before %v, len after %v)", len(list0), + len(list1)) + } } func testQueries(t *testing.T, db *sql.DB) { diff --git a/go.mod b/go.mod index 97abff2..7f3e085 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/m4ksio/testingdock +module github.com/dapperlabs/testingdock go 1.13 @@ -21,6 +21,7 @@ require ( github.com/opencontainers/image-spec v1.0.1 // indirect github.com/opencontainers/runc v0.1.1 // indirect github.com/sirupsen/logrus v1.4.2 // indirect + github.com/stretchr/testify v1.4.0 golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect google.golang.org/grpc v1.26.0 // indirect gopkg.in/yaml.v2 v2.2.4 // indirect diff --git a/network.go b/network.go index 4de88da..1f50290 100644 --- a/network.go +++ b/network.go @@ -168,6 +168,22 @@ func (n *Network) close() error { return nil } +// remove removes all the containers in the network. +func (n *Network) remove() error { + var wg sync.WaitGroup + + wg.Add(len(n.children)) + for _, cont := range n.children { + go func(cont *Container) { + defer wg.Done() + cont.remove() // nolint: errcheck + }(cont) + } + wg.Wait() + + return nil +} + // After adds a child container to the current network configuration. // These containers then kind of "depend" on the network and will // be closed when the network closes. @@ -184,3 +200,7 @@ func (n *Network) reset(ctx context.Context) { } printf("(reset ) %-25s (%s) - network reseted in %s", n.name, n.id, time.Since(now)) } + +func (n *Network) ID() string { + return n.id +} diff --git a/network_test.go b/network_test.go index 19b3321..1fc7519 100644 --- a/network_test.go +++ b/network_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/m4ksio/testingdock" + "github.com/dapperlabs/testingdock" ) func TestNetwork_Start(t *testing.T) { @@ -20,4 +20,8 @@ func TestNetwork_Start(t *testing.T) { if err := s.Close(); err != nil { t.Fatalf("Failed to close a network: %s", err.Error()) } + + if err := s.Remove(); err != nil { + t.Fatalf("Failed to remove a network: %s", err.Error()) + } } diff --git a/suite.go b/suite.go index 3d33857..ac3fa4f 100644 --- a/suite.go +++ b/suite.go @@ -161,3 +161,12 @@ func (s *Suite) Close() error { return nil } + +// Remove removes all the containers in the network. +func (s *Suite) Remove() error { + if s.network != nil { + return s.network.remove() + } + + return nil +} diff --git a/suite_test.go b/suite_test.go index 6983c59..b73a6f9 100644 --- a/suite_test.go +++ b/suite_test.go @@ -6,7 +6,7 @@ import ( "os" "testing" - "github.com/m4ksio/testingdock" + "github.com/dapperlabs/testingdock" ) func TestMain(m *testing.M) {