Merge pull request #1281 from gtardif/compose_unittests

More Local Compose unit tests
This commit is contained in:
Guillaume Tardif 2021-02-12 12:12:57 +01:00 committed by GitHub
commit f52bdc54d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 2129 additions and 64 deletions

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

94
local/compose/ps_test.go Normal file
View File

@ -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
}

View File

@ -0,0 +1,5 @@
services:
service1:
image: nginx
service2:
image: mysql

View File

@ -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")

View File

@ -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