gitlab.com/jfprevost/gitlab-runner-notlscheck@v11.11.4+incompatible/executors/docker/internal/volumes/cache_container_test.go (about) 1 package volumes 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "testing" 8 9 "github.com/docker/docker/api/types" 10 "github.com/docker/docker/api/types/container" 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/mock" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func TestNewCacheContainerManager(t *testing.T) { 17 logger := newDebugLoggerMock() 18 19 m := NewCacheContainerManager(context.Background(), logger, nil, nil, true) 20 assert.IsType(t, &cacheContainerManager{}, m) 21 } 22 23 func getCacheContainerManager() (*cacheContainerManager, *mockContainerClient) { 24 cClient := new(mockContainerClient) 25 26 m := &cacheContainerManager{ 27 logger: newDebugLoggerMock(), 28 containerClient: cClient, 29 failedContainerIDs: make([]string, 0), 30 helperImage: &types.ImageInspect{ID: "helper-image"}, 31 outdatedHelperImage: false, 32 } 33 34 return m, cClient 35 } 36 37 func TestCacheContainerManager_FindExistingCacheContainer(t *testing.T) { 38 containerName := "container-name" 39 containerPath := "container-path" 40 41 testCases := map[string]struct { 42 inspectResult types.ContainerJSON 43 inspectError error 44 expectedContainerID string 45 expectedRemoveID string 46 }{ 47 "error on container inspection": { 48 inspectError: errors.New("test error"), 49 expectedContainerID: "", 50 }, 51 "container with valid cache exists": { 52 inspectResult: types.ContainerJSON{ 53 ContainerJSONBase: &types.ContainerJSONBase{ 54 ID: "existingWithValidCacheID", 55 }, 56 Config: &container.Config{ 57 Volumes: map[string]struct{}{ 58 containerPath: {}, 59 }, 60 }, 61 }, 62 inspectError: nil, 63 expectedContainerID: "existingWithValidCacheID", 64 }, 65 "container without valid cache exists": { 66 inspectResult: types.ContainerJSON{ 67 ContainerJSONBase: &types.ContainerJSONBase{ 68 ID: "existingWithInvalidCacheID", 69 }, 70 Config: &container.Config{ 71 Volumes: map[string]struct{}{ 72 "different-path": {}, 73 }, 74 }, 75 }, 76 inspectError: nil, 77 expectedContainerID: "", 78 expectedRemoveID: "existingWithInvalidCacheID", 79 }, 80 } 81 82 for testName, testCase := range testCases { 83 t.Run(testName, func(t *testing.T) { 84 m, cClient := getCacheContainerManager() 85 defer cClient.AssertExpectations(t) 86 87 cClient.On("ContainerInspect", mock.Anything, containerName). 88 Return(testCase.inspectResult, testCase.inspectError). 89 Once() 90 91 if testCase.expectedRemoveID != "" { 92 cClient.On("RemoveContainer", mock.Anything, testCase.expectedRemoveID). 93 Return(nil). 94 Once() 95 } 96 97 containerID := m.FindOrCleanExisting(containerName, containerPath) 98 assert.Equal(t, testCase.expectedContainerID, containerID) 99 }) 100 } 101 } 102 103 func TestCacheContainerManager_CreateCacheContainer(t *testing.T) { 104 containerName := "container-name" 105 containerPath := "container-path" 106 107 testCases := map[string]struct { 108 expectedContainerID string 109 createResult container.ContainerCreateCreatedBody 110 createError error 111 containerID string 112 startError error 113 waitForContainerError error 114 expectedFailedContainerID string 115 expectedError error 116 }{ 117 "error on container create": { 118 createError: errors.New("test error"), 119 expectedError: errors.New("test error"), 120 }, 121 "error on container create with returnedID": { 122 createResult: container.ContainerCreateCreatedBody{ 123 ID: "containerID", 124 }, 125 createError: errors.New("test error"), 126 expectedFailedContainerID: "containerID", 127 expectedError: errors.New("test error"), 128 }, 129 "error on container start": { 130 createResult: container.ContainerCreateCreatedBody{ 131 ID: "containerID", 132 }, 133 containerID: "containerID", 134 startError: errors.New("test error"), 135 expectedFailedContainerID: "containerID", 136 expectedError: errors.New("test error"), 137 }, 138 "error on wait for container": { 139 createResult: container.ContainerCreateCreatedBody{ 140 ID: "containerID", 141 }, 142 containerID: "containerID", 143 waitForContainerError: errors.New("test error"), 144 expectedFailedContainerID: "containerID", 145 expectedError: errors.New("test error"), 146 }, 147 "success": { 148 createResult: container.ContainerCreateCreatedBody{ 149 ID: "containerID", 150 }, 151 containerID: "containerID", 152 expectedContainerID: "containerID", 153 expectedError: nil, 154 }, 155 } 156 157 // TODO: Remove in 12.0 158 outdatedHelperImageValues := map[bool][]string{ 159 true: {"gitlab-runner-cache", "container-path"}, 160 false: {"gitlab-runner-helper", "cache-init", "container-path"}, 161 } 162 163 for testName, testCase := range testCases { 164 for outdatedHelperImage, expectedCommand := range outdatedHelperImageValues { 165 t.Run(fmt.Sprintf("%s-outdated-helper-image-is-%v", testName, outdatedHelperImage), func(t *testing.T) { 166 m, cClient := getCacheContainerManager() 167 m.outdatedHelperImage = outdatedHelperImage 168 169 defer cClient.AssertExpectations(t) 170 171 configMatcher := mock.MatchedBy(func(config *container.Config) bool { 172 if config.Image != "helper-image" { 173 return false 174 } 175 176 if len(config.Cmd) != len(expectedCommand) { 177 return false 178 } 179 180 return config.Cmd[0] == expectedCommand[0] 181 }) 182 183 cClient.On("LabelContainer", configMatcher, "cache", fmt.Sprintf("cache.dir=%s", containerPath)). 184 Once() 185 186 cClient.On("ContainerCreate", mock.Anything, configMatcher, mock.Anything, mock.Anything, containerName). 187 Return(testCase.createResult, testCase.createError). 188 Once() 189 190 if testCase.createError == nil { 191 cClient.On("ContainerStart", mock.Anything, testCase.containerID, mock.Anything). 192 Return(testCase.startError). 193 Once() 194 195 if testCase.startError == nil { 196 cClient.On("WaitForContainer", testCase.containerID). 197 Return(testCase.waitForContainerError). 198 Once() 199 } 200 } 201 202 require.Empty(t, m.failedContainerIDs, "Initial list of failed containers should be empty") 203 204 containerID, err := m.Create(containerName, containerPath) 205 assert.Equal(t, err, testCase.expectedError) 206 assert.Equal(t, testCase.expectedContainerID, containerID) 207 208 if testCase.expectedFailedContainerID != "" { 209 assert.Len(t, m.failedContainerIDs, 1) 210 assert.Contains( 211 t, m.failedContainerIDs, testCase.expectedFailedContainerID, 212 "List of failed container should be updated with %s", testCase.expectedContainerID, 213 ) 214 } else { 215 assert.Empty(t, m.failedContainerIDs, "List of failed containers should not be updated") 216 } 217 }) 218 } 219 } 220 } 221 222 func TestCacheContainerManager_Cleanup(t *testing.T) { 223 ctx := context.Background() 224 225 containerClientMock := new(mockContainerClient) 226 defer containerClientMock.AssertExpectations(t) 227 228 loggerMock := new(mockDebugLogger) 229 defer loggerMock.AssertExpectations(t) 230 231 containerClientMock.On("RemoveContainer", ctx, "failed-container-1"). 232 Return(nil). 233 Once() 234 containerClientMock.On("RemoveContainer", ctx, "container-1-with-remove-error"). 235 Return(errors.New("test-error")). 236 Once() 237 containerClientMock.On("RemoveContainer", ctx, "container-1"). 238 Return(nil). 239 Once() 240 241 loggerMock.On("Debugln", "Error while removing the container: test-error"). 242 Once() 243 244 m := &cacheContainerManager{ 245 containerClient: containerClientMock, 246 logger: loggerMock, 247 failedContainerIDs: []string{"failed-container-1", "container-1-with-remove-error"}, 248 } 249 250 done := m.Cleanup(ctx, []string{"container-1"}) 251 252 <-done 253 }