| package container // import "github.com/docker/docker/container" |
| |
| import ( |
| "math/rand" |
| "os" |
| "path/filepath" |
| "testing" |
| |
| "github.com/docker/docker/api/types" |
| containertypes "github.com/docker/docker/api/types/container" |
| "github.com/docker/docker/pkg/stringid" |
| "github.com/google/uuid" |
| "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()) |
| } |
| |
| func newContainer(t *testing.T) *Container { |
| var ( |
| id = uuid.New().String() |
| cRoot = filepath.Join(root, id) |
| ) |
| if err := os.MkdirAll(cRoot, 0755); err != nil { |
| t.Fatal(err) |
| } |
| c := NewBaseContainer(id, cRoot) |
| c.HostConfig = &containertypes.HostConfig{} |
| return c |
| } |
| |
| func TestViewSaveDelete(t *testing.T) { |
| db, err := NewViewDB() |
| if err != nil { |
| t.Fatal(err) |
| } |
| c := newContainer(t) |
| if err := c.CheckpointTo(db); err != nil { |
| t.Fatal(err) |
| } |
| if err := db.Delete(c); err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestViewAll(t *testing.T) { |
| var ( |
| db, _ = NewViewDB() |
| one = newContainer(t) |
| two = newContainer(t) |
| ) |
| one.Pid = 10 |
| if err := one.CheckpointTo(db); err != nil { |
| t.Fatal(err) |
| } |
| two.Pid = 20 |
| if err := two.CheckpointTo(db); err != nil { |
| t.Fatal(err) |
| } |
| |
| all, err := db.Snapshot().All() |
| if err != nil { |
| t.Fatal(err) |
| } |
| if l := len(all); l != 2 { |
| t.Fatalf("expected 2 items, got %d", l) |
| } |
| byID := make(map[string]Snapshot) |
| for i := range all { |
| byID[all[i].ID] = all[i] |
| } |
| if s, ok := byID[one.ID]; !ok || s.Pid != 10 { |
| t.Fatalf("expected something different with for id=%s: %v", one.ID, s) |
| } |
| if s, ok := byID[two.ID]; !ok || s.Pid != 20 { |
| t.Fatalf("expected something different with for id=%s: %v", two.ID, s) |
| } |
| } |
| |
| func TestViewGet(t *testing.T) { |
| var ( |
| db, _ = NewViewDB() |
| one = newContainer(t) |
| ) |
| one.ImageID = "some-image-123" |
| if err := one.CheckpointTo(db); err != nil { |
| t.Fatal(err) |
| } |
| s, err := db.Snapshot().Get(one.ID) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if s == nil || s.ImageID != "some-image-123" { |
| t.Fatalf("expected ImageID=some-image-123. Got: %v", s) |
| } |
| } |
| |
| func TestNames(t *testing.T) { |
| db, err := NewViewDB() |
| if err != nil { |
| t.Fatal(err) |
| } |
| assert.Check(t, db.ReserveName("name1", "containerid1")) |
| assert.Check(t, db.ReserveName("name1", "containerid1")) // idempotent |
| assert.Check(t, db.ReserveName("name2", "containerid2")) |
| assert.Check(t, is.Error(db.ReserveName("name2", "containerid3"), ErrNameReserved.Error())) |
| |
| // Releasing a name allows the name to point to something else later. |
| assert.Check(t, db.ReleaseName("name2")) |
| assert.Check(t, db.ReserveName("name2", "containerid3")) |
| |
| view := db.Snapshot() |
| |
| id, err := view.GetID("name1") |
| assert.Check(t, err) |
| assert.Check(t, is.Equal("containerid1", id)) |
| |
| id, err = view.GetID("name2") |
| assert.Check(t, err) |
| assert.Check(t, is.Equal("containerid3", id)) |
| |
| _, err = view.GetID("notreserved") |
| assert.Check(t, is.Error(err, ErrNameNotReserved.Error())) |
| |
| // Releasing and re-reserving a name doesn't affect the snapshot. |
| assert.Check(t, db.ReleaseName("name2")) |
| assert.Check(t, db.ReserveName("name2", "containerid4")) |
| |
| id, err = view.GetID("name1") |
| assert.Check(t, err) |
| assert.Check(t, is.Equal("containerid1", id)) |
| |
| id, err = view.GetID("name2") |
| assert.Check(t, err) |
| assert.Check(t, is.Equal("containerid3", id)) |
| |
| // GetAllNames |
| assert.Check(t, is.DeepEqual(map[string][]string{"containerid1": {"name1"}, "containerid3": {"name2"}}, view.GetAllNames())) |
| |
| assert.Check(t, db.ReserveName("name3", "containerid1")) |
| assert.Check(t, db.ReserveName("name4", "containerid1")) |
| |
| view = db.Snapshot() |
| assert.Check(t, is.DeepEqual(map[string][]string{"containerid1": {"name1", "name3", "name4"}, "containerid4": {"name2"}}, view.GetAllNames())) |
| |
| // Release containerid1's names with Delete even though no container exists |
| assert.Check(t, db.Delete(&Container{ID: "containerid1"})) |
| |
| // Reusing one of those names should work |
| assert.Check(t, db.ReserveName("name1", "containerid4")) |
| view = db.Snapshot() |
| assert.Check(t, is.DeepEqual(map[string][]string{"containerid4": {"name1", "name2"}}, view.GetAllNames())) |
| } |
| |
| // Test case for GitHub issue 35920 |
| func TestViewWithHealthCheck(t *testing.T) { |
| var ( |
| db, _ = NewViewDB() |
| one = newContainer(t) |
| ) |
| one.Health = &Health{ |
| Health: types.Health{ |
| Status: "starting", |
| }, |
| } |
| if err := one.CheckpointTo(db); err != nil { |
| t.Fatal(err) |
| } |
| s, err := db.Snapshot().Get(one.ID) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if s == nil || s.Health != "starting" { |
| t.Fatalf("expected Health=starting. Got: %+v", s) |
| } |
| } |
| |
| func TestTruncIndex(t *testing.T) { |
| db, err := NewViewDB() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Get on an empty index |
| if _, err := db.GetByPrefix("foobar"); err == nil { |
| t.Fatal("Get on an empty index should return an error") |
| } |
| |
| id := "99b36c2c326ccc11e726eee6ee78a0baf166ef96" |
| // Add an id |
| if err := db.Save(&Container{ID: id}); err != nil { |
| t.Fatal(err) |
| } |
| |
| type testacase struct { |
| name string |
| input string |
| expectedResult string |
| expectError bool |
| } |
| |
| for _, tc := range []testacase{ |
| { |
| name: "Get a non-existing id", |
| input: "abracadabra", |
| expectError: true, |
| }, |
| { |
| name: "Get an empty id", |
| input: "", |
| expectedResult: "", |
| expectError: true, |
| }, |
| { |
| name: "Get the exact id", |
| input: id, |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "The first letter should match", |
| input: id[:1], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "The first half should match", |
| input: id[:len(id)/2], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "The second half should NOT match", |
| input: id[len(id)/2:], |
| expectError: true, |
| }, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| assertIndexGet(t, db, tc.input, tc.expectedResult, tc.expectError) |
| }) |
| } |
| |
| id2 := id[:6] + "blabla" |
| // Add an id |
| if err := db.Save(&Container{ID: id2}); err != nil { |
| t.Fatal(err) |
| } |
| |
| for _, tc := range []testacase{ |
| { |
| name: "id should work", |
| input: id, |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "id2 should work", |
| input: id2, |
| expectedResult: id2, |
| expectError: false, |
| }, |
| { |
| name: "6 characters should conflict", |
| input: id[:6], |
| expectedResult: "", |
| expectError: true, |
| }, |
| { |
| name: "4 characters should conflict", |
| input: id[:4], |
| expectedResult: "", |
| expectError: true, |
| }, |
| { |
| name: "1 character should conflict", |
| input: id[:6], |
| expectedResult: "", |
| expectError: true, |
| }, |
| { |
| name: "7 characters of id should not conflict", |
| input: id[:7], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "7 characters of id2 should not conflict", |
| input: id2[:7], |
| expectedResult: id2, |
| expectError: false, |
| }, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| assertIndexGet(t, db, tc.input, tc.expectedResult, tc.expectError) |
| }) |
| } |
| |
| // Deleting id2 should remove conflicts |
| if err := db.Delete(&Container{ID: id2}); err != nil { |
| t.Fatal(err) |
| } |
| |
| for _, tc := range []testacase{ |
| { |
| name: "id2 should no longer work", |
| input: id2, |
| expectedResult: "", |
| expectError: true, |
| }, |
| { |
| name: "7 characters id2 should no longer work", |
| input: id2[:7], |
| expectedResult: "", |
| expectError: true, |
| }, |
| { |
| name: "11 characters id2 should no longer work", |
| input: id2[:11], |
| expectedResult: "", |
| expectError: true, |
| }, |
| { |
| name: "conflicts between id[:6] and id2 should be gone", |
| input: id[:6], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "conflicts between id[:4] and id2 should be gone", |
| input: id[:4], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "conflicts between id[:1] and id2 should be gone", |
| input: id[:1], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "non-conflicting 7 character substrings should still not conflict", |
| input: id[:7], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "non-conflicting 15 character substrings should still not conflict", |
| input: id[:15], |
| expectedResult: id, |
| expectError: false, |
| }, |
| { |
| name: "non-conflicting substrings should still not conflict", |
| input: id, |
| expectedResult: id, |
| expectError: false, |
| }, |
| } { |
| t.Run(tc.name, func(t *testing.T) { |
| assertIndexGet(t, db, tc.input, tc.expectedResult, tc.expectError) |
| }) |
| } |
| } |
| |
| func assertIndexGet(t *testing.T, snapshot *ViewDB, input, expectedResult string, expectError bool) { |
| if result, err := snapshot.GetByPrefix(input); err != nil && !expectError { |
| t.Fatalf("Unexpected error getting '%s': %s", input, err) |
| } else if err == nil && expectError { |
| t.Fatalf("Getting '%s' should return an error, not '%s'", input, result) |
| } else if result != expectedResult { |
| t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult) |
| } |
| } |
| |
| func BenchmarkDBAdd100(b *testing.B) { |
| var testSet []string |
| for i := 0; i < 100; i++ { |
| testSet = append(testSet, stringid.GenerateRandomID()) |
| } |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| db, err := NewViewDB() |
| if err != nil { |
| b.Fatal(err) |
| } |
| for _, id := range testSet { |
| if err := db.Save(&Container{ID: id}); err != nil { |
| b.Fatal(err) |
| } |
| } |
| } |
| } |
| |
| func BenchmarkDBGetByPrefix100(b *testing.B) { |
| var testSet []string |
| var testKeys []string |
| for i := 0; i < 100; i++ { |
| testSet = append(testSet, stringid.GenerateRandomID()) |
| } |
| db, err := NewViewDB() |
| if err != nil { |
| b.Fatal(err) |
| } |
| for _, id := range testSet { |
| if err := db.Save(&Container{ID: id}); err != nil { |
| b.Fatal(err) |
| } |
| l := rand.Intn(12) + 12 |
| testKeys = append(testKeys, id[:l]) |
| } |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| for _, id := range testKeys { |
| if res, err := db.GetByPrefix(id); err != nil { |
| b.Fatal(res, err) |
| } |
| } |
| } |
| } |
| |
| func BenchmarkDBGetByPrefix250(b *testing.B) { |
| var testSet []string |
| var testKeys []string |
| for i := 0; i < 250; i++ { |
| testSet = append(testSet, stringid.GenerateRandomID()) |
| } |
| db, err := NewViewDB() |
| if err != nil { |
| b.Fatal(err) |
| } |
| for _, id := range testSet { |
| if err := db.Save(&Container{ID: id}); err != nil { |
| b.Fatal(err) |
| } |
| l := rand.Intn(12) + 12 |
| testKeys = append(testKeys, id[:l]) |
| } |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| for _, id := range testKeys { |
| if res, err := db.GetByPrefix(id); err != nil { |
| b.Fatal(res, err) |
| } |
| } |
| } |
| } |
| |
| func BenchmarkDBGetByPrefix500(b *testing.B) { |
| var testSet []string |
| var testKeys []string |
| for i := 0; i < 500; i++ { |
| testSet = append(testSet, stringid.GenerateRandomID()) |
| } |
| db, err := NewViewDB() |
| if err != nil { |
| b.Fatal(err) |
| } |
| for _, id := range testSet { |
| if err := db.Save(&Container{ID: id}); err != nil { |
| b.Fatal(err) |
| } |
| l := rand.Intn(12) + 12 |
| testKeys = append(testKeys, id[:l]) |
| } |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| for _, id := range testKeys { |
| if res, err := db.GetByPrefix(id); err != nil { |
| b.Fatal(res, err) |
| } |
| } |
| } |
| } |