github.com/secure-build/gitlab-runner@v12.5.0+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) 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 } 32 33 return m, cClient 34 } 35 36 func TestCacheContainerManager_FindExistingCacheContainer(t *testing.T) { 37 containerName := "container-name" 38 containerPath := "container-path" 39 40 testCases := map[string]struct { 41 inspectResult types.ContainerJSON 42 inspectError error 43 expectedContainerID string 44 expectedRemoveID string 45 }{ 46 "error on container inspection": { 47 inspectError: errors.New("test error"), 48 expectedContainerID: "", 49 }, 50 "container with valid cache exists": { 51 inspectResult: types.ContainerJSON{ 52 ContainerJSONBase: &types.ContainerJSONBase{ 53 ID: "existingWithValidCacheID", 54 }, 55 Config: &container.Config{ 56 Volumes: map[string]struct{}{ 57 containerPath: {}, 58 }, 59 }, 60 }, 61 inspectError: nil, 62 expectedContainerID: "existingWithValidCacheID", 63 }, 64 "container without valid cache exists": { 65 inspectResult: types.ContainerJSON{ 66 ContainerJSONBase: &types.ContainerJSONBase{ 67 ID: "existingWithInvalidCacheID", 68 }, 69 Config: &container.Config{ 70 Volumes: map[string]struct{}{ 71 "different-path": {}, 72 }, 73 }, 74 }, 75 inspectError: nil, 76 expectedContainerID: "", 77 expectedRemoveID: "existingWithInvalidCacheID", 78 }, 79 } 80 81 for testName, testCase := range testCases { 82 t.Run(testName, func(t *testing.T) { 83 m, cClient := getCacheContainerManager() 84 defer cClient.AssertExpectations(t) 85 86 cClient.On("ContainerInspect", mock.Anything, containerName). 87 Return(testCase.inspectResult, testCase.inspectError). 88 Once() 89 90 if testCase.expectedRemoveID != "" { 91 cClient.On("RemoveContainer", mock.Anything, testCase.expectedRemoveID). 92 Return(nil). 93 Once() 94 } 95 96 containerID := m.FindOrCleanExisting(containerName, containerPath) 97 assert.Equal(t, testCase.expectedContainerID, containerID) 98 }) 99 } 100 } 101 102 func TestCacheContainerManager_CreateCacheContainer(t *testing.T) { 103 containerName := "container-name" 104 containerPath := "container-path" 105 expectedCacheCmd := []string{"gitlab-runner-helper", "cache-init", containerPath} 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 for testName, testCase := range testCases { 158 t.Run(testName, func(t *testing.T) { 159 m, cClient := getCacheContainerManager() 160 161 defer cClient.AssertExpectations(t) 162 163 configMatcher := mock.MatchedBy(func(config *container.Config) bool { 164 if config.Image != "helper-image" { 165 return false 166 } 167 168 if len(config.Cmd) != len(expectedCacheCmd) { 169 return false 170 } 171 172 return config.Cmd[0] == expectedCacheCmd[0] 173 }) 174 175 cClient.On("LabelContainer", configMatcher, "cache", fmt.Sprintf("cache.dir=%s", containerPath)). 176 Once() 177 178 cClient.On("ContainerCreate", mock.Anything, configMatcher, mock.Anything, mock.Anything, containerName). 179 Return(testCase.createResult, testCase.createError). 180 Once() 181 182 if testCase.createError == nil { 183 cClient.On("ContainerStart", mock.Anything, testCase.containerID, mock.Anything). 184 Return(testCase.startError). 185 Once() 186 187 if testCase.startError == nil { 188 cClient.On("WaitForContainer", testCase.containerID). 189 Return(testCase.waitForContainerError). 190 Once() 191 } 192 } 193 194 require.Empty(t, m.failedContainerIDs, "Initial list of failed containers should be empty") 195 196 containerID, err := m.Create(containerName, containerPath) 197 assert.Equal(t, err, testCase.expectedError) 198 assert.Equal(t, testCase.expectedContainerID, containerID) 199 200 if testCase.expectedFailedContainerID != "" { 201 assert.Len(t, m.failedContainerIDs, 1) 202 assert.Contains( 203 t, m.failedContainerIDs, testCase.expectedFailedContainerID, 204 "List of failed container should be updated with %s", testCase.expectedContainerID, 205 ) 206 } else { 207 assert.Empty(t, m.failedContainerIDs, "List of failed containers should not be updated") 208 } 209 }) 210 } 211 } 212 213 func TestCacheContainerManager_Cleanup(t *testing.T) { 214 ctx := context.Background() 215 216 containerClientMock := new(mockContainerClient) 217 defer containerClientMock.AssertExpectations(t) 218 219 loggerMock := new(mockDebugLogger) 220 defer loggerMock.AssertExpectations(t) 221 222 containerClientMock.On("RemoveContainer", ctx, "failed-container-1"). 223 Return(nil). 224 Once() 225 containerClientMock.On("RemoveContainer", ctx, "container-1-with-remove-error"). 226 Return(errors.New("test-error")). 227 Once() 228 containerClientMock.On("RemoveContainer", ctx, "container-1"). 229 Return(nil). 230 Once() 231 232 loggerMock.On("Debugln", "Error while removing the container: test-error"). 233 Once() 234 235 m := &cacheContainerManager{ 236 containerClient: containerClientMock, 237 logger: loggerMock, 238 failedContainerIDs: []string{"failed-container-1", "container-1-with-remove-error"}, 239 } 240 241 done := m.Cleanup(ctx, []string{"container-1"}) 242 243 <-done 244 }