Files
moby/daemon/list_test.go
Cory Snider 778e5bfad3 api/types/filters: move to daemon/internal
Most of the code in the filters package relates to the unmarshaling,
validation and application of filters from client requests. None of this
is necessary or particularly useful for Go SDK users. Move the full-fat
filters package into daemon/internal and switch all the daemon code to
import that package so we are free to iterate upon the code without
worrying about source-code interface compatibility.

Signed-off-by: Cory Snider <csnider@mirantis.com>
2025-10-08 12:06:26 -04:00

230 lines
6.5 KiB
Go

package daemon
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
"time"
"github.com/google/uuid"
containertypes "github.com/moby/moby/api/types/container"
"github.com/moby/moby/v2/daemon/container"
"github.com/moby/moby/v2/daemon/internal/filters"
"github.com/moby/moby/v2/daemon/internal/image"
"github.com/moby/moby/v2/daemon/server/backend"
"github.com/opencontainers/go-digest"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
var root string
func TestMain(m *testing.M) {
var err error
root, err = os.MkdirTemp("", "docker-container-test-")
if err != nil {
panic(err)
}
defer os.RemoveAll(root)
os.Exit(m.Run())
}
// This sets up a container with a name so that name filters
// work against it. It takes in a pointer to Daemon so that
// minor operations are not repeated by the caller
func setupContainerWithName(t *testing.T, name string, daemon *Daemon) *container.Container {
t.Helper()
var (
id = uuid.New().String()
computedImageID = image.ID(digest.FromString(id))
cRoot = filepath.Join(root, id)
)
if err := os.MkdirAll(cRoot, 0o755); err != nil {
t.Fatal(err)
}
c := container.NewBaseContainer(id, cRoot)
// these are for passing includeContainerInList
if name[0] != '/' {
name = "/" + name
}
c.Name = name
c.State.Running = true
c.HostConfig = &containertypes.HostConfig{}
c.Created = time.Now()
// these are for passing the refreshImage reducer
c.ImageID = computedImageID
c.Config = &containertypes.Config{
Image: computedImageID.String(),
}
// this is done here to avoid requiring these
// operations n x number of containers in the
// calling function
daemon.containersReplica.Save(c)
daemon.reserveName(id, name)
return c
}
func containerListContainsName(containers []*containertypes.Summary, name string) bool {
for _, ctr := range containers {
for _, containerName := range ctr.Names {
if containerName == name {
return true
}
}
}
return false
}
func TestContainerList(t *testing.T) {
db, err := container.NewViewDB()
assert.NilError(t, err)
d := &Daemon{
containersReplica: db,
}
// test list with different number of containers
for _, num := range []int{0, 1, 2, 4, 8, 16, 32, 64, 100} {
t.Run(fmt.Sprintf("%d containers", num), func(t *testing.T) {
db, err := container.NewViewDB() // new DB to ignore prior containers
assert.NilError(t, err)
d = &Daemon{
containersReplica: db,
}
// create the containers
containers := make([]*container.Container, num)
for i := range num {
name := fmt.Sprintf("cont-%d", i)
containers[i] = setupContainerWithName(t, name, d)
// ensure container timestamps are separated enough so the
// sort used by d.Containers() can deterministically sort them.
if i > 0 {
containers[i].Created = containers[i-1].Created.Add(time.Millisecond)
}
}
// list them and verify correctness
containerList, err := d.Containers(context.Background(), &backend.ContainerListOptions{All: true})
assert.NilError(t, err)
assert.Assert(t, is.Len(containerList, num))
for i := range num {
// container list should be ordered in descending creation order
assert.Assert(t, is.Equal(containerList[i].Names[0], containers[num-1-i].Name))
}
})
}
}
func TestContainerList_InvalidFilter(t *testing.T) {
db, err := container.NewViewDB()
assert.NilError(t, err)
d := &Daemon{
containersReplica: db,
}
_, err = d.Containers(context.Background(), &backend.ContainerListOptions{
Filters: filters.NewArgs(filters.Arg("invalid", "foo")),
})
assert.Assert(t, is.Error(err, "invalid filter 'invalid'"))
}
func TestContainerList_NameFilter(t *testing.T) {
db, err := container.NewViewDB()
assert.NilError(t, err)
d := &Daemon{
containersReplica: db,
}
var (
one = setupContainerWithName(t, "a1", d)
two = setupContainerWithName(t, "a2", d)
three = setupContainerWithName(t, "b1", d)
)
// moby/moby #37453 - ^ regex not working due to prefix slash
// not being stripped
containerList, err := d.Containers(context.Background(), &backend.ContainerListOptions{
Filters: filters.NewArgs(filters.Arg("name", "^a")),
})
assert.NilError(t, err)
assert.Assert(t, is.Len(containerList, 2))
assert.Assert(t, containerListContainsName(containerList, one.Name))
assert.Assert(t, containerListContainsName(containerList, two.Name))
// Same as above but with slash prefix should produce the same result
containerListWithPrefix, err := d.Containers(context.Background(), &backend.ContainerListOptions{
Filters: filters.NewArgs(filters.Arg("name", "^/a")),
})
assert.NilError(t, err)
assert.Assert(t, is.Len(containerListWithPrefix, 2))
assert.Assert(t, containerListContainsName(containerListWithPrefix, one.Name))
assert.Assert(t, containerListContainsName(containerListWithPrefix, two.Name))
// Same as above but make sure it works for exact names
containerList, err = d.Containers(context.Background(), &backend.ContainerListOptions{
Filters: filters.NewArgs(filters.Arg("name", "b1")),
})
assert.NilError(t, err)
assert.Assert(t, is.Len(containerList, 1))
assert.Assert(t, containerListContainsName(containerList, three.Name))
// Same as above but with slash prefix should produce the same result
containerListWithPrefix, err = d.Containers(context.Background(), &backend.ContainerListOptions{
Filters: filters.NewArgs(filters.Arg("name", "/b1")),
})
assert.NilError(t, err)
assert.Assert(t, is.Len(containerListWithPrefix, 1))
assert.Assert(t, containerListContainsName(containerListWithPrefix, three.Name))
}
func TestContainerList_LimitFilter(t *testing.T) {
db, err := container.NewViewDB()
assert.NilError(t, err)
d := &Daemon{
containersReplica: db,
}
// start containers
num := 32
for i := range num {
name := fmt.Sprintf("cont-%d", i)
setupContainerWithName(t, name, d)
}
containers, err := db.Snapshot().All()
assert.NilError(t, err)
assert.Assert(t, is.Len(containers, num))
tests := []struct {
limit int
doc string
}{
{limit: 0, doc: "no limit"},
{limit: -1, doc: "negative limit doesn't limit"},
{limit: 1, doc: "limit 1 container"},
{limit: 20, doc: "limit less than num containers"},
{limit: 32, doc: "limit equal num containers"},
{limit: 40, doc: "limit greater than num containers"},
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
containerList, err := d.Containers(context.Background(), &backend.ContainerListOptions{Limit: tc.limit})
assert.NilError(t, err)
expectedListLen := num
if tc.limit > 0 {
expectedListLen = min(num, tc.limit)
}
assert.Assert(t, is.Len(containerList, expectedListLen))
})
}
}