Browse Source

pipeline: Test more of the stage steps implementation

Functionally testing pipeline stage steps (build, run, publish, deploy,
exports) is tricky on account of either having to mock all calls to
workflow script and/or pipeline runner methods, or somehow setup
dependent backend systems such as k8s for systems tests.

The lack of coverage for these methods has resulted in a number of found
bugs and incremental fixes that could have been found through testing,
so the value of heavy mocking and system-test complexity in these cases
seems to outweigh the cost.

By installing a stubbed version of the `docker-pusher` script into the
Jenkins container, we can run a system test for the `publish` step, and
by stubbing out some of the previous stage/step context, we can more
effectively test `PipelineStage` step methods.

Change-Id: Ibed4f781e6c46dcbf5741eaf8adb79ebe4ef2d39
Dan Duvall 5 months ago
parent
commit
ed9a1ab248

+ 4
- 3
src/org/wikimedia/integration/PipelineStage.groovy View File

@@ -171,7 +171,7 @@ class PipelineStage implements Serializable {
171 171
         ws.dir(pipeline.directory) {
172 172
           for (def stageStep in STEPS) {
173 173
             if (config[stageStep]) {
174
-              ws.echo("step: ${stageStep}")
174
+              ws.echo("step: ${stageStep}, config: ${config.inspect()}")
175 175
               this."${stageStep}"(ws, runner)
176 176
             }
177 177
           }
@@ -526,8 +526,9 @@ class PipelineStage implements Serializable {
526 526
    * </dl>
527 527
    */
528 528
   void exports(ws, runner) {
529
-    config.exports.each { name, value ->
530
-      context[name] = context % value
529
+    config.exports.each { export, value ->
530
+      context[export] = context % value
531
+      ws.echo "exported ${name}.${export}=${context[export].inspect()}"
531 532
     }
532 533
   }
533 534
 }

+ 4
- 0
systemtests/jenkins/Dockerfile View File

@@ -15,6 +15,7 @@ RUN apt-get update && \
15 15
       ca-certificates \
16 16
       curl \
17 17
       gnupg2 \
18
+      sudo \
18 19
       software-properties-common && \
19 20
     ( curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - ) && \
20 21
     add-apt-repository \
@@ -23,6 +24,9 @@ RUN apt-get update && \
23 24
     apt-get install -y docker-ce-cli && \
24 25
     rm -rf /var/lib/apt/lists/*
25 26
 
27
+COPY systemtests/jenkins/bin/stub-script /usr/local/bin/docker-pusher
28
+COPY systemtests/jenkins/sudoers /etc/sudoers.d/jenkins
29
+
26 30
 USER jenkins
27 31
 
28 32
 RUN echo $VERSION > /usr/share/jenkins/ref/jenkins.install.UpgradeWizard.state

+ 3
- 0
systemtests/jenkins/bin/stub-script View File

@@ -0,0 +1,3 @@
1
+#!/bin/sh
2
+
3
+echo "stubbed execution of: ($0 $@)"

+ 1
- 1
systemtests/jenkins/init.groovy.d/02-nodes.groovy View File

@@ -1,7 +1,7 @@
1 1
 import jenkins.model.Jenkins
2 2
 
3 3
 Jenkins.instance.computers.each { c ->
4
-  c.node.labelString += "blubber"
4
+  c.node.labelString += "blubber dockerPublish"
5 5
 }
6 6
 
7 7
 Jenkins.instance.save()

+ 1
- 0
systemtests/jenkins/sudoers View File

@@ -0,0 +1 @@
1
+jenkins ALL=(root) NOPASSWD: /usr/local/bin/docker-pusher

+ 4
- 0
systemtests/repo1/.pipeline/config.yaml View File

@@ -9,6 +9,8 @@ pipelines:
9 9
       - name: candidate
10 10
         build: production
11 11
         run: false
12
+        publish:
13
+          image: true
12 14
     execution:
13 15
       - [unit, candidate]
14 16
       - [lint, candidate]
@@ -16,3 +18,5 @@ pipelines:
16 18
     directory: src/service2
17 19
     stages:
18 20
       - name: test
21
+        exports:
22
+          foo: '${setup.timestamp}-${.stage}'

+ 288
- 7
test/org/wikimedia/integration/PipelineStageTest.groovy View File

@@ -111,31 +111,312 @@ class PipelineStageTest extends GroovyTestCase {
111 111
     ]
112 112
   }
113 113
 
114
-  void testExports() {
114
+  void testBuild() {
115
+    def pipeline = new Pipeline("foo", [
116
+      stages: [
117
+        [
118
+          name: "foo",
119
+          build: "foovariant",
120
+        ],
121
+      ]
122
+    ])
123
+
124
+    def mockRunner = new MockFor(PipelineRunner)
125
+    def stack = pipeline.stack()
126
+    def stage = stack[1][0]
127
+
128
+    stubStageContexts(stack, [
129
+      setup: { ctx ->
130
+        ctx["imageLabels"] = [foo: "foolabel", bar: "barlabel"]
131
+      },
132
+    ])
133
+
134
+    mockRunner.demand.build { variant, labels ->
135
+      assert variant == "foovariant"
136
+      assert labels == [foo: "foolabel", bar: "barlabel"]
137
+
138
+      "foo-image-id"
139
+    }
140
+
141
+    mockRunner.use {
142
+      def ws = new WorkflowScript()
143
+      def runner = new PipelineRunner(ws)
144
+
145
+      stage.build(ws, runner)
146
+    }
147
+
148
+    assert stage.context["imageID"] == "foo-image-id"
149
+  }
150
+
151
+  void testDeploy_withTest() {
115 152
     def pipeline = new Pipeline("foo", [
116 153
       stages: [
117 154
         [
155
+          name: "foo",
156
+          build: "foovariant",
157
+          publish: [
158
+            image: true,
159
+          ],
160
+        ],
161
+        [
118 162
           name: "bar",
163
+          deploy: [
164
+            image: '${foo.publishedImage}',
165
+            chart: 'https://an.example/chart/foo-${.stage}.tar.gz',
166
+            tag: '${.stage}-tag',
167
+            test: true,
168
+          ],
169
+        ],
170
+      ]
171
+    ])
172
+
173
+    def mockRunner = new MockFor(PipelineRunner)
174
+    def stack = pipeline.stack()
175
+    def stage = stack[2][0]
176
+
177
+    stubStageContexts(stack, [
178
+      foo: { ctx ->
179
+        ctx["imageID"] = "foo-image-id"
180
+
181
+        ctx["imageName"] = "foo-project"
182
+        ctx["imageFullName"] = "registry.example/bar/foo-project"
183
+        ctx["imageTag"] = "0000-00-00-000000-foo"
184
+        ctx["imageTags"] = ["0000-00-00-000000-foo"]
185
+        ctx["publishedImage"] = "registry.example/bar/foo-project:0000-00-00-000000-foo"
186
+      },
187
+    ])
188
+
189
+    mockRunner.demand.deployWithChart { chart, image, tag ->
190
+      assert chart == "https://an.example/chart/foo-bar.tar.gz"
191
+      assert image == "registry.example/bar/foo-project:0000-00-00-000000-foo"
192
+      assert tag == "bar-tag"
193
+
194
+      "bar-release-name"
195
+    }
196
+
197
+    mockRunner.demand.testRelease { releaseName ->
198
+      assert releaseName == "bar-release-name"
199
+    }
200
+
201
+    mockRunner.use {
202
+      def ws = new WorkflowScript()
203
+      def runner = new PipelineRunner(ws)
204
+
205
+      stage.deploy(ws, runner)
206
+    }
207
+
208
+    assert stage.context["releaseName"] == "bar-release-name"
209
+  }
210
+
211
+  void testDeploy_withoutTest() {
212
+    def pipeline = new Pipeline("foo", [
213
+      stages: [
214
+        [
215
+          name: "foo",
216
+          build: "foovariant",
217
+          publish: [
218
+            image: true,
219
+          ],
220
+        ],
221
+        [
222
+          name: "bar",
223
+          deploy: [
224
+            image: '${foo.publishedImage}',
225
+            chart: 'https://an.example/chart/foo-${.stage}.tar.gz',
226
+            tag: '${.stage}-tag',
227
+            test: false,
228
+          ],
229
+        ],
230
+      ]
231
+    ])
232
+
233
+    def mockRunner = new MockFor(PipelineRunner)
234
+    def stack = pipeline.stack()
235
+    def stage = stack[2][0]
236
+
237
+    stubStageContexts(stack, [
238
+      foo: { ctx ->
239
+        ctx["imageID"] = "foo-image-id"
240
+
241
+        ctx["imageName"] = "foo-project"
242
+        ctx["imageFullName"] = "registry.example/bar/foo-project"
243
+        ctx["imageTag"] = "0000-00-00-000000-foo"
244
+        ctx["imageTags"] = ["0000-00-00-000000-foo"]
245
+        ctx["publishedImage"] = "registry.example/bar/foo-project:0000-00-00-000000-foo"
246
+      },
247
+    ])
248
+
249
+    mockRunner.demand.deployWithChart { chart, image, tag ->
250
+      assert chart == "https://an.example/chart/foo-bar.tar.gz"
251
+      assert image == "registry.example/bar/foo-project:0000-00-00-000000-foo"
252
+      assert tag == "bar-tag"
253
+
254
+      "bar-release-name"
255
+    }
256
+
257
+    mockRunner.use {
258
+      def ws = new WorkflowScript()
259
+      def runner = new PipelineRunner(ws)
260
+
261
+      stage.deploy(ws, runner)
262
+    }
263
+
264
+    assert stage.context["releaseName"] == "bar-release-name"
265
+  }
266
+
267
+  void testExports() {
268
+    def pipeline = new Pipeline("foo", [
269
+      stages: [
270
+        [
271
+          name: "foo",
272
+          build: "foovariant",
119 273
           exports: [
120
-            image: "fooimage",
274
+            thing: '${.imageID}-${setup.timestamp}',
121 275
           ],
122
-        ]
276
+        ],
123 277
       ]
124 278
     ])
125 279
 
126 280
     def mockRunner = new MockFor(PipelineRunner)
127 281
     def mockWS = new MockFor(WorkflowScript)
282
+    def stack = pipeline.stack()
283
+    def stage = stack[1][0]
128 284
 
129
-    def stage = pipeline.stack()[1][0]
285
+    stubStageContexts(stack, [
286
+      setup: { ctx ->
287
+        ctx["timestamp"] = "0000-00-00-000000"
288
+      },
289
+      foo: { ctx ->
290
+        ctx["imageID"] = "foo-image-id"
291
+      },
292
+    ])
130 293
 
131
-    assert stage.name == "bar"
294
+    mockWS.demand.echo { msg ->
295
+      assert msg == "exported foo.thing='foo-image-id-0000-00-00-000000'"
296
+    }
132 297
 
133 298
     mockWS.use {
134 299
       mockRunner.use {
135
-        stage.exports(mockWS, mockRunner)
300
+        def ws = new WorkflowScript()
301
+        def runner = new PipelineRunner(ws)
302
+
303
+        stage.exports(ws, runner)
136 304
       }
137 305
     }
138 306
 
139
-    assert stage.context["image"] == "fooimage"
307
+    assert stage.context["thing"] == "foo-image-id-0000-00-00-000000"
308
+  }
309
+
310
+  void testPublish() {
311
+    def pipeline = new Pipeline("foopipeline", [
312
+      stages: [
313
+        [
314
+          name: "built",
315
+          build: "foovariant",
316
+        ],
317
+        [
318
+          name: "published",
319
+          publish: [
320
+            image: [
321
+              id: '${built.imageID}',
322
+            ],
323
+          ],
324
+        ]
325
+      ]
326
+    ])
327
+
328
+    def mockRunner = new MockFor(PipelineRunner)
329
+    def stack = pipeline.stack()
330
+    def stage = stack[2][0]
331
+
332
+    stubStageContexts(stack, [
333
+      setup: { ctx ->
334
+        ctx["project"] = "foo-project"
335
+        ctx["timestamp"] = "0000-00-00-000000"
336
+        ctx["imageLabels"] = []
337
+      },
338
+      built: { ctx ->
339
+        ctx["imageID"] = "foo-image-id"
340
+      },
341
+    ])
342
+
343
+    mockRunner.demand.registerAs { imageID, imageName, tag ->
344
+      assert imageID == "foo-image-id"
345
+      assert imageName == "foo-project"
346
+      assert tag == "0000-00-00-000000-published"
347
+    }
348
+
349
+    mockRunner.demand.qualifyRegistryPath { imageName ->
350
+      assert imageName == "foo-project"
351
+
352
+      "registry.example/bar/foo-project"
353
+    }
354
+
355
+    mockRunner.use {
356
+      def ws = new WorkflowScript()
357
+      def runner = new PipelineRunner(ws)
358
+
359
+      stage.publish(ws, runner)
360
+    }
361
+
362
+    assert stage.context["imageName"] == "foo-project"
363
+    assert stage.context["imageFullName"] == "registry.example/bar/foo-project"
364
+    assert stage.context["imageTag"] == "0000-00-00-000000-published"
365
+    assert stage.context["imageTags"] == ["0000-00-00-000000-published"]
366
+    assert stage.context["publishedImage"] == "registry.example/bar/foo-project:0000-00-00-000000-published"
367
+  }
368
+
369
+  void testRun() {
370
+    def pipeline = new Pipeline("foo", [
371
+      stages: [
372
+        [
373
+          name: "foo",
374
+          build: "foovariant",
375
+        ],
376
+        [
377
+          name: "bar",
378
+          run: [
379
+            image: '${foo.imageID}',
380
+            arguments: ['${.stage}arg'],
381
+          ],
382
+        ],
383
+      ]
384
+    ])
385
+
386
+    def mockRunner = new MockFor(PipelineRunner)
387
+    def stack = pipeline.stack()
388
+    def stage = stack[2][0]
389
+
390
+    stubStageContexts(stack, [
391
+      foo: { ctx ->
392
+        ctx["imageID"] = "foo-image-id"
393
+      },
394
+    ])
395
+
396
+    mockRunner.demand.run { image, args ->
397
+      assert image == "foo-image-id"
398
+      assert args == ["bararg"]
399
+    }
400
+
401
+    mockRunner.use {
402
+      def ws = new WorkflowScript()
403
+      def runner = new PipelineRunner(ws)
404
+
405
+      stage.run(ws, runner)
406
+    }
407
+  }
408
+
409
+  private void stubStageContexts(stack, stubs) {
410
+    stack.each { stages ->
411
+      stages.each { stage ->
412
+        stage.context["stage"] = stage.name
413
+
414
+        stubs.each { stageName, stub ->
415
+          if (stage.name == stageName) {
416
+            stub(stage.context)
417
+          }
418
+        }
419
+      }
420
+    }
140 421
   }
141 422
 }

Loading…
Cancel
Save