mirror of https://github.com/docker/compose.git
Merge pull request #1281 from gtardif/compose_unittests
More Local Compose unit tests
This commit is contained in:
commit
f52bdc54d8
|
@ -33,12 +33,12 @@ import (
|
|||
)
|
||||
|
||||
// NewComposeService create a local implementation of the compose.Service API
|
||||
func NewComposeService(apiClient *client.Client) compose.Service {
|
||||
func NewComposeService(apiClient client.APIClient) compose.Service {
|
||||
return &composeService{apiClient: apiClient}
|
||||
}
|
||||
|
||||
type composeService struct {
|
||||
apiClient *client.Client
|
||||
apiClient client.APIClient
|
||||
}
|
||||
|
||||
func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
|
||||
|
|
|
@ -70,20 +70,6 @@ func (containers Containers) filter(predicate containerPredicate) Containers {
|
|||
return filtered
|
||||
}
|
||||
|
||||
// split return Containers with elements to match and those not to match predicate
|
||||
func (containers Containers) split(predicate containerPredicate) (Containers, Containers) {
|
||||
var right Containers
|
||||
var left Containers
|
||||
for _, c := range containers {
|
||||
if predicate(c) {
|
||||
right = append(right, c)
|
||||
} else {
|
||||
left = append(left, c)
|
||||
}
|
||||
}
|
||||
return right, left
|
||||
}
|
||||
|
||||
func (containers Containers) names() []string {
|
||||
var names []string
|
||||
for _, c := range containers {
|
||||
|
|
|
@ -64,10 +64,8 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt
|
|||
|
||||
var observedState Containers
|
||||
observedState, err = s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(project.Name),
|
||||
),
|
||||
All: true,
|
||||
Filters: filters.NewArgs(projectFilter(project.Name)),
|
||||
All: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -57,27 +57,23 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c
|
|||
}
|
||||
|
||||
err = InReverseDependencyOrder(ctx, options.Project, func(c context.Context, service types.ServiceConfig) error {
|
||||
serviceContainers, others := containers.split(isService(service.Name))
|
||||
serviceContainers := containers.filter(isService(service.Name))
|
||||
err := s.removeContainers(ctx, w, serviceContainers)
|
||||
containers = others
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if options.RemoveOrphans && len(containers) > 0 {
|
||||
err := s.removeContainers(ctx, w, containers)
|
||||
orphans := containers.filter(isNotService(options.Project.ServiceNames()...))
|
||||
if options.RemoveOrphans && len(orphans) > 0 {
|
||||
err := s.removeContainers(ctx, w, orphans)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
networks, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(projectName),
|
||||
),
|
||||
})
|
||||
networks, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(projectName))})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -137,13 +133,15 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
|
|||
return eg.Wait()
|
||||
}
|
||||
|
||||
func projectFilterListOpt(projectName string) moby.ContainerListOptions {
|
||||
return moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(projectFilter(projectName)),
|
||||
All: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *composeService) projectFromContainerLabels(ctx context.Context, projectName string) (*types.Project, error) {
|
||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(projectName),
|
||||
),
|
||||
All: true,
|
||||
})
|
||||
containers, err := s.apiClient.ContainerList(ctx, projectFilterListOpt(projectName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
apitypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
"github.com/docker/compose-cli/local/mocks"
|
||||
)
|
||||
|
||||
func TestDown(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
api := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = api
|
||||
|
||||
ctx := context.Background()
|
||||
api.EXPECT().ContainerList(ctx, projectFilterListOpt(testProject)).Return(
|
||||
[]apitypes.Container{testContainer("service1", "123"), testContainer("service1", "456"), testContainer("service2", "789"), testContainer("service_orphan", "321")}, nil).Times(2)
|
||||
|
||||
api.EXPECT().ContainerStop(ctx, "123", nil).Return(nil)
|
||||
api.EXPECT().ContainerStop(ctx, "456", nil).Return(nil)
|
||||
api.EXPECT().ContainerStop(ctx, "789", nil).Return(nil)
|
||||
|
||||
api.EXPECT().ContainerRemove(ctx, "123", apitypes.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
api.EXPECT().ContainerRemove(ctx, "456", apitypes.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
api.EXPECT().ContainerRemove(ctx, "789", apitypes.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
|
||||
api.EXPECT().NetworkList(ctx, apitypes.NetworkListOptions{Filters: filters.NewArgs(projectFilter(testProject))}).Return([]apitypes.NetworkResource{{ID: "myProject_default"}}, nil)
|
||||
|
||||
api.EXPECT().NetworkRemove(ctx, "myProject_default").Return(nil)
|
||||
|
||||
err := tested.Down(ctx, testProject, compose.DownOptions{})
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func TestDownRemoveOrphans(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
api := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = api
|
||||
|
||||
ctx := context.Background()
|
||||
api.EXPECT().ContainerList(ctx, projectFilterListOpt(testProject)).Return(
|
||||
[]apitypes.Container{testContainer("service1", "123"), testContainer("service2", "789"), testContainer("service_orphan", "321")}, nil).Times(2)
|
||||
|
||||
api.EXPECT().ContainerStop(ctx, "123", nil).Return(nil)
|
||||
api.EXPECT().ContainerStop(ctx, "789", nil).Return(nil)
|
||||
api.EXPECT().ContainerStop(ctx, "321", nil).Return(nil)
|
||||
|
||||
api.EXPECT().ContainerRemove(ctx, "123", apitypes.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
api.EXPECT().ContainerRemove(ctx, "789", apitypes.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
api.EXPECT().ContainerRemove(ctx, "321", apitypes.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
|
||||
api.EXPECT().NetworkList(ctx, apitypes.NetworkListOptions{Filters: filters.NewArgs(projectFilter(testProject))}).Return([]apitypes.NetworkResource{{ID: "myProject_default"}}, nil)
|
||||
|
||||
api.EXPECT().NetworkRemove(ctx, "myProject_default").Return(nil)
|
||||
|
||||
err := tested.Down(ctx, testProject, compose.DownOptions{RemoveOrphans: true})
|
||||
assert.NilError(t, err)
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
apitypes "github.com/docker/docker/api/types"
|
||||
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
"github.com/docker/compose-cli/local/mocks"
|
||||
)
|
||||
|
||||
const testProject = "testProject"
|
||||
|
||||
var tested = composeService{}
|
||||
|
||||
func TestKillAll(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
api := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = api
|
||||
|
||||
project := types.Project{Name: testProject, Services: []types.ServiceConfig{testService("service1"), testService("service2")}}
|
||||
|
||||
ctx := context.Background()
|
||||
api.EXPECT().ContainerList(ctx, projectFilterListOpt(testProject)).Return(
|
||||
[]apitypes.Container{testContainer("service1", "123"), testContainer("service1", "456"), testContainer("service2", "789")}, nil)
|
||||
api.EXPECT().ContainerKill(anyCancellableContext(), "123", "").Return(nil)
|
||||
api.EXPECT().ContainerKill(anyCancellableContext(), "456", "").Return(nil)
|
||||
api.EXPECT().ContainerKill(anyCancellableContext(), "789", "").Return(nil)
|
||||
|
||||
err := tested.Kill(ctx, &project, compose.KillOptions{})
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func TestKillSignal(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
api := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = api
|
||||
|
||||
project := types.Project{Name: testProject, Services: []types.ServiceConfig{testService("service1")}}
|
||||
|
||||
ctx := context.Background()
|
||||
api.EXPECT().ContainerList(ctx, projectFilterListOpt(testProject)).Return([]apitypes.Container{testContainer("service1", "123")}, nil)
|
||||
api.EXPECT().ContainerKill(anyCancellableContext(), "123", "SIGTERM").Return(nil)
|
||||
|
||||
err := tested.Kill(ctx, &project, compose.KillOptions{Signal: "SIGTERM"})
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func testService(name string) types.ServiceConfig {
|
||||
return types.ServiceConfig{Name: name}
|
||||
}
|
||||
|
||||
func testContainer(service string, id string) apitypes.Container {
|
||||
return apitypes.Container{
|
||||
ID: id,
|
||||
Names: []string{id},
|
||||
Labels: containerLabels(service),
|
||||
}
|
||||
}
|
||||
|
||||
func containerLabels(service string) map[string]string {
|
||||
return map[string]string{serviceLabel: service, configFilesLabel: "testdata/docker-compose.yml", workingDirLabel: "testdata", projectLabel: testProject}
|
||||
}
|
||||
|
||||
func anyCancellableContext() gomock.Matcher {
|
||||
ctxWithCancel, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
return gomock.AssignableToTypeOf(ctxWithCancel)
|
||||
}
|
|
@ -84,3 +84,23 @@ func combinedStatus(statuses []string) string {
|
|||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func groupContainerByLabel(containers []moby.Container, labelName string) (map[string][]moby.Container, []string, error) {
|
||||
containersByLabel := map[string][]moby.Container{}
|
||||
keys := []string{}
|
||||
for _, c := range containers {
|
||||
label, ok := c.Labels[labelName]
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("No label %q set on container %q of compose project", labelName, c.ID)
|
||||
}
|
||||
labelContainers, ok := containersByLabel[label]
|
||||
if !ok {
|
||||
labelContainers = []moby.Container{}
|
||||
keys = append(keys, label)
|
||||
}
|
||||
labelContainers = append(labelContainers, c)
|
||||
containersByLabel[label] = labelContainers
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return containersByLabel, keys, nil
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ package compose
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
|
@ -30,10 +29,8 @@ import (
|
|||
|
||||
func (s *composeService) Ps(ctx context.Context, projectName string, options compose.PsOptions) ([]compose.ContainerSummary, error) {
|
||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(projectName),
|
||||
),
|
||||
All: options.All,
|
||||
Filters: filters.NewArgs(projectFilter(projectName)),
|
||||
All: options.All,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -83,23 +80,3 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options com
|
|||
}
|
||||
return summary, eg.Wait()
|
||||
}
|
||||
|
||||
func groupContainerByLabel(containers []moby.Container, labelName string) (map[string][]moby.Container, []string, error) {
|
||||
containersByLabel := map[string][]moby.Container{}
|
||||
keys := []string{}
|
||||
for _, c := range containers {
|
||||
label, ok := c.Labels[labelName]
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("No label %q set on container %q of compose project", labelName, c.ID)
|
||||
}
|
||||
labelContainers, ok := containersByLabel[label]
|
||||
if !ok {
|
||||
labelContainers = []moby.Container{}
|
||||
keys = append(keys, label)
|
||||
}
|
||||
labelContainers = append(labelContainers, c)
|
||||
containersByLabel[label] = labelContainers
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return containersByLabel, keys, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
apitypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
"github.com/docker/compose-cli/local/mocks"
|
||||
)
|
||||
|
||||
func TestPs(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
api := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = api
|
||||
|
||||
ctx := context.Background()
|
||||
listOpts := apitypes.ContainerListOptions{Filters: filters.NewArgs(projectFilter(testProject)), All: false}
|
||||
c1, inspect1 := containerDetails("service1", "123", "Running", "healthy")
|
||||
c2, inspect2 := containerDetails("service1", "456", "Running", "")
|
||||
c2.Ports = []apitypes.Port{{PublicPort: 80, PrivatePort: 90, IP: "localhost"}}
|
||||
c3, inspect3 := containerDetails("service2", "789", "Running", "")
|
||||
api.EXPECT().ContainerList(ctx, listOpts).Return([]apitypes.Container{c1, c2, c3}, nil)
|
||||
api.EXPECT().ContainerInspect(anyCancellableContext(), "123").Return(inspect1, nil)
|
||||
api.EXPECT().ContainerInspect(anyCancellableContext(), "456").Return(inspect2, nil)
|
||||
api.EXPECT().ContainerInspect(anyCancellableContext(), "789").Return(inspect3, nil)
|
||||
|
||||
containers, err := tested.Ps(ctx, testProject, compose.PsOptions{})
|
||||
|
||||
expected := []compose.ContainerSummary{
|
||||
{ID: "123", Name: "123", Project: testProject, Service: "service1", State: "Running", Health: "healthy", Publishers: nil},
|
||||
{ID: "456", Name: "456", Project: testProject, Service: "service1", State: "Running", Health: "", Publishers: []compose.PortPublisher{{URL: "localhost:80", TargetPort: 90, PublishedPort: 80}}},
|
||||
{ID: "789", Name: "789", Project: testProject, Service: "service2", State: "Running", Health: "", Publishers: nil},
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, containers, expected)
|
||||
}
|
||||
|
||||
func TestPsAll(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
api := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = api
|
||||
|
||||
ctx := context.Background()
|
||||
listOpts := apitypes.ContainerListOptions{Filters: filters.NewArgs(projectFilter(testProject)), All: true}
|
||||
c1, inspect1 := containerDetails("service1", "123", "Running", "healthy")
|
||||
c2, inspect2 := containerDetails("service1", "456", "Stopped", "")
|
||||
api.EXPECT().ContainerList(ctx, listOpts).Return([]apitypes.Container{c1, c2}, nil)
|
||||
api.EXPECT().ContainerInspect(anyCancellableContext(), "123").Return(inspect1, nil)
|
||||
api.EXPECT().ContainerInspect(anyCancellableContext(), "456").Return(inspect2, nil)
|
||||
|
||||
containers, err := tested.Ps(ctx, testProject, compose.PsOptions{All: true})
|
||||
|
||||
expected := []compose.ContainerSummary{
|
||||
{ID: "123", Name: "123", Project: testProject, Service: "service1", State: "Running", Health: "healthy", Publishers: nil},
|
||||
{ID: "456", Name: "456", Project: testProject, Service: "service1", State: "Stopped", Health: "", Publishers: nil},
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, containers, expected)
|
||||
}
|
||||
|
||||
func containerDetails(service string, id string, status string, health string) (apitypes.Container, apitypes.ContainerJSON) {
|
||||
container := apitypes.Container{
|
||||
ID: id,
|
||||
Names: []string{"/" + id},
|
||||
Labels: containerLabels(service),
|
||||
State: status,
|
||||
}
|
||||
inspect := apitypes.ContainerJSON{ContainerJSONBase: &apitypes.ContainerJSONBase{State: &apitypes.ContainerState{Status: status, Health: &apitypes.Health{Status: health}}}}
|
||||
return container, inspect
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
services:
|
||||
service1:
|
||||
image: nginx
|
||||
service2:
|
||||
image: mysql
|
|
@ -27,7 +27,7 @@ import (
|
|||
func TestCascadeStop(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
|
||||
const projectName = "compose-e2e-logs"
|
||||
const projectName = "e2e-cascade-stop"
|
||||
|
||||
t.Run("abort-on-container-exit", func(t *testing.T) {
|
||||
res := c.RunDockerOrExitError("compose", "-f", "./fixtures/cascade-stop-test/compose.yaml", "--project-name", projectName, "up", "--abort-on-container-exit")
|
||||
|
|
|
@ -101,9 +101,9 @@ func TestLocalBackendRun(t *testing.T) {
|
|||
fields := strings.Fields(line)
|
||||
if fields[0] == nginxID {
|
||||
nginxFound = true
|
||||
assert.Equal(t, fields[1], "nginx")
|
||||
assert.Equal(t, fields[2], "/docker-entrypoint.sh")
|
||||
assert.Equal(t, fields[len(fields)-1], "0.0.0.0:85->80/tcp")
|
||||
assert.Equal(t, fields[1], "nginx", res.Combined())
|
||||
assert.Equal(t, fields[2], "/docker-entrypoint.sh", res.Combined())
|
||||
assert.Equal(t, fields[len(fields)-1], "0.0.0.0:85->80/tcp", res.Combined())
|
||||
}
|
||||
}
|
||||
assert.Assert(t, nginxFound, res.Stdout())
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue