Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
281 changes: 219 additions & 62 deletions cmd/nerdctl/compose/compose_ps_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,21 @@ import (

"gotest.tools/v3/assert"

"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"

"github.com/containerd/nerdctl/v2/pkg/tabutil"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)

func TestComposePs(t *testing.T) {
base := testutil.NewBase(t)
testCase := nerdtest.Setup()

testCase.NoParallel = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testCase.Require = nerdtest.Private
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


var dockerComposeYAML = fmt.Sprintf(`
services:
wordpress:
Expand Down Expand Up @@ -61,52 +70,119 @@ volumes:
wordpress:
db:
`, testutil.WordpressImage, testutil.MariaDBImage, testutil.CommonImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
projectName := comp.ProjectName()
t.Logf("projectName=%q", projectName)

base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
testCase.Setup = func(data test.Data, helpers test.Helpers) {
composePath := data.Temp().Save(dockerComposeYAML, "compose.yaml")
data.Labels().Set("composeYaml", composePath)

helpers.Ensure("compose", "-f", composePath, "up", "-d")

time.Sleep(2 * time.Second)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used to ensure the container starts? If so, you can use EnsureContainerStarted.

}

testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
if path := data.Labels().Get("composeYaml"); path != "" {
helpers.Anyhow("compose", "-f", path, "down", "-v")
}
}

assertHandler := func(expectedName, expectedImage string) test.Comparator {
return func(stdout string, t tig.T) {

assertHandler := func(expectedName, expectedImage string) func(stdout string) error {
return func(stdout string) error {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
if len(lines) < 2 {
return fmt.Errorf("expected at least 2 lines, got %d", len(lines))
}
assert.Assert(t, len(lines) >= 2)

tab := tabutil.NewReader("NAME\tIMAGE\tCOMMAND\tSERVICE\tSTATUS\tPORTS")
err := tab.ParseHeader(lines[0])
if err != nil {
return fmt.Errorf("failed to parse header: %v", err)
}
assert.NilError(t, tab.ParseHeader(lines[0]))

container, _ := tab.ReadRow(lines[1], "NAME")
assert.Equal(t, container, expectedName)

image, _ := tab.ReadRow(lines[1], "IMAGE")
assert.Equal(t, image, expectedImage)

return nil
}
}

testCase.SubTests = []*test.Case{

{
Description: "compose ps wordpress",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "wordpress")
},
Expected: test.Expects(0, nil,
assertHandler("wordpress_container", testutil.WordpressImage),
),
},

{
Description: "compose ps db",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "db")
},
Expected: test.Expects(0, nil,
assertHandler("db_container", testutil.MariaDBImage),
),
},

{
Description: "compose ps should not show alpine unless running",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps")
},
Expected: test.Expects(0, nil,
expect.DoesNotContain(testutil.CommonImage),
),
},

{
Description: "compose ps alpine -a",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "alpine", "-a")
},
Expected: test.Expects(0, nil,
assertHandler("alpine_container", testutil.CommonImage),
),
},

{
Description: "compose ps filter exited",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "-a", "--filter", "status=exited")
},
Expected: test.Expects(0, nil,
assertHandler("alpine_container", testutil.CommonImage),
),
},

{
Description: "compose ps services",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "--services", "-a")
},
Expected: test.Expects(0, nil,
expect.All(
expect.Contains("wordpress\n"),
expect.Contains("db\n"),
expect.Contains("alpine\n"),
),
),
},
}

time.Sleep(3 * time.Second)
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "wordpress").AssertOutWithFunc(assertHandler("wordpress_container", testutil.WordpressImage))
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "db").AssertOutWithFunc(assertHandler("db_container", testutil.MariaDBImage))
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps").AssertOutNotContains(testutil.CommonImage)
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "alpine", "-a").AssertOutWithFunc(assertHandler("alpine_container", testutil.CommonImage))
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "-a", "--filter", "status=exited").AssertOutWithFunc(assertHandler("alpine_container", testutil.CommonImage))
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "--services", "-a").AssertOutContainsAll("wordpress\n", "db\n", "alpine\n")
testCase.Run(t)
}

func TestComposePsJSON(t *testing.T) {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, this new line is unnecessary.

// docker parses unknown 'format' as a Go template and won't output an error
testutil.DockerIncompatible(t)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you re-write using nerdtest.Docker


base := testutil.NewBase(t)
testCase := nerdtest.Setup()

testCase.NoParallel = true
testCase.Require = nerdtest.Private
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.


var dockerComposeYAML = fmt.Sprintf(`
services:
wordpress:
Expand Down Expand Up @@ -135,50 +211,131 @@ volumes:
db:
`, testutil.WordpressImage, testutil.MariaDBImage)

comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
projectName := comp.ProjectName()
t.Logf("projectName=%q", projectName)
testCase.Setup = func(data test.Data, helpers test.Helpers) {

base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
composePath := data.Temp().Save(dockerComposeYAML, "compose.yaml")
data.Labels().Set("composeYaml", composePath)

helpers.Ensure("compose", "-f", composePath, "up", "-d")

time.Sleep(2 * time.Second)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use EnsureContainerStarted ?

}

testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
if path := data.Labels().Get("composeYaml"); path != "" {
helpers.Anyhow("compose", "-f", path, "down", "-v")
}
}

assertHandler := func(svc string, count int, fields ...string) test.Comparator {
return func(stdout string, t tig.T) {

assertHandler := func(svc string, count int, fields ...string) func(stdout string) error {
return func(stdout string) error {
// 1. check json output can be unmarshalled back to printables.
var printables []composeContainerPrintable
if err := json.Unmarshal([]byte(stdout), &printables); err != nil {
return fmt.Errorf("[service: %s]failed to unmarshal json output from `compose ps`: %s", svc, stdout)
}
// 1. check json output can be unmarshalled back to printables.
assert.NilError(t, json.Unmarshal([]byte(stdout), &printables))
// 2. check #printables matches expected count.
if len(printables) != count {
return fmt.Errorf("[service: %s]unmarshal generates %d printables, expected %d: %s", svc, len(printables), count, stdout)
}
assert.Equal(t, len(printables), count)
// 3. check marshalled json string has all expected substrings.
for _, field := range fields {
if !strings.Contains(stdout, field) {
return fmt.Errorf("[service: %s]marshalled json output doesn't have expected string (%s): %s", svc, field, stdout)
}
assert.Assert(t, strings.Contains(stdout, field),
fmt.Sprintf("[service: %s] expected %s in %s", svc, field, stdout))
}
return nil
}
}

// check other formats are not supported
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "--format", "yaml").AssertFail()
// check all services are up (can be marshalled and unmarshalled) and check Image field exists
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "--format", "json").
AssertOutWithFunc(assertHandler("all", 2, `"Service":"wordpress"`, `"Service":"db"`,
fmt.Sprintf(`"Image":"%s"`, testutil.WordpressImage), fmt.Sprintf(`"Image":"%s"`, testutil.MariaDBImage)))
// check wordpress is running
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "--format", "json", "wordpress").
AssertOutWithFunc(assertHandler("wordpress", 1, `"Service":"wordpress"`, `"State":"running"`, `"TargetPort":80`, `"PublishedPort":8080`))
// check wordpress is stopped
base.ComposeCmd("-f", comp.YAMLFullPath(), "stop", "wordpress").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "--format", "json", "wordpress", "-a").
AssertOutWithFunc(assertHandler("wordpress", 1, `"Service":"wordpress"`, `"State":"exited"`))
// check wordpress is removed
base.ComposeCmd("-f", comp.YAMLFullPath(), "rm", "-f", "wordpress").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "--format", "json", "wordpress").
AssertOutWithFunc(assertHandler("wordpress", 0))
testCase.SubTests = []*test.Case{

{ // check other formats are not supported
Description: "unsupported format should fail",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command(
"compose",
"-f", data.Labels().Get("composeYaml"),
"ps",
"--format", "yaml",
)
},
Expected: test.Expects(1, nil, nil),
},

{ // check all services are up (can be marshalled and unmarshalled) and check Image field exists
Description: "ps json all services",
NoParallel: true,
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "--format", "json")
},
Expected: test.Expects(0, nil,
assertHandler("all", 2,
`"Service":"wordpress"`,
`"Service":"db"`,
fmt.Sprintf(`"Image":"%s"`, testutil.WordpressImage),
fmt.Sprintf(`"Image":"%s"`, testutil.MariaDBImage),
),
),
},

{ // check wordpress is running
Description: "wordpress running",
NoParallel: true,
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"),
"ps", "--format", "json", "wordpress")
},
Expected: test.Expects(0, nil,
assertHandler("wordpress", 1,
`"Service":"wordpress"`,
`"State":"running"`,
`"TargetPort":80`,
`"PublishedPort":8080`,
)),
},

{
Description: "stop wordpress",
NoParallel: true,
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"),
"stop", "wordpress")
},
Expected: test.Expects(0, nil, nil),
},

{ // check wordpress is stopped
Description: "wordpress exited",
NoParallel: true,
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"),
"ps", "--format", "json", "wordpress", "-a")
},
Expected: test.Expects(0, nil,
assertHandler("wordpress", 1,
`"Service":"wordpress"`,
`"State":"exited"`,
)),
},

{
Description: "remove wordpress",
NoParallel: true,
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"),
"rm", "-f", "wordpress")
},
Expected: test.Expects(0, nil, nil),
},

{ // check wordpress is removed
Description: "wordpress removed",
NoParallel: true,
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"),
"ps", "--format", "json", "wordpress")
},
Expected: test.Expects(0, nil,
assertHandler("wordpress", 0),
),
},
}

testCase.Run(t)
}
Loading