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