Passed
Push — test ( d838cf...cef4a5 )
by Tom
03:29
created

StepRunnerTest   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 706
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 411
dl 0
loc 706
rs 9.76
c 0
b 0
f 0
wmc 33

31 Methods

Rating   Name   Duplication   Size   Complexity  
A testCopyFailsAtSecondStage() 0 15 1
A testRunning() 0 13 1
A testKeepContainerOnErrorWithExistingContainer() 0 10 1
A testAfterScriptFailing() 0 18 1
A testArtifactsNoMatch() 0 19 1
A createTestStepRunner() 0 34 3
A testDockerClientMount() 0 29 1
A testCreation() 0 6 1
A testWithHostConfig() 0 16 1
A testGetDockerBinaryRepository() 0 16 1
A testArtifacts() 0 21 1
A testDockerClientInjection() 0 45 1
A testFailOnContainerCreation() 0 12 1
A testMountInCopyFails() 0 12 1
A testRunStepWithDockerHostParameterDockerSocket() 0 16 1
A testArtifactsFailure() 0 26 1
A keepContainerOnErrorExecTest() 0 13 1
A testCachesPrePopulated() 0 30 1
A testZapExistingContainer() 0 19 1
A testRunStepWithoutMountingDockerSocket() 0 12 1
A testMountWithoutHostConfig() 0 12 1
A testDockerHubImageLogin() 0 17 1
A testKeepContainerOnErrorWithNonExistentContainer() 0 9 1
A testRunStepWithPipDockerSocket() 0 30 1
A testDockerClientInjectInvalidPackage() 0 27 1
A testCopyFails() 0 14 1
A testKeepExistingContainer() 0 12 1
A testServicesObtainNetworkAndShutdown() 0 18 1
A testCaches() 0 22 1
A testAfterScript() 0 18 1
A testCopy() 0 19 1
1
<?php
2
3
/* this file is part of pipelines */
4
5
namespace Ktomk\Pipelines\Runner;
6
7
use Ktomk\Pipelines\Cli\Exec;
8
use Ktomk\Pipelines\Cli\ExecTester;
9
use Ktomk\Pipelines\Cli\Streams;
10
use Ktomk\Pipelines\LibFs;
11
use Ktomk\Pipelines\LibTmp;
12
use Ktomk\Pipelines\Project;
13
use Ktomk\Pipelines\Runner\Docker\Binary\Repository;
14
use Ktomk\Pipelines\Runner\Docker\Binary\UnPackager;
15
use Ktomk\Pipelines\Utility\OptionsMock;
16
use Ktomk\Pipelines\Value\SideEffect\DestructibleString;
17
use PHPUnit\Framework\MockObject\MockObject;
18
19
/**
20
 * Class StepRunnerTest
21
 *
22
 * @covers \Ktomk\Pipelines\Runner\StepRunner
23
 */
24
class StepRunnerTest extends RunnerTestCase
25
{
26
    private $removeDirectories = array();
0 ignored issues
show
introduced by
The private property $removeDirectories is not used, and could be removed.
Loading history...
27
28
    public function testCreation()
29
    {
30
        $stepRunner = new StepRunner(
31
            $this->createMock('Ktomk\Pipelines\Runner\Runner')
32
        );
33
        self::assertInstanceOf('Ktomk\Pipelines\Runner\StepRunner', $stepRunner);
34
    }
35
36
    /**
37
     * @covers \Ktomk\Pipelines\Runner\Docker\ImageLogin::loginImage
38
     */
39
    public function testFailOnContainerCreation()
40
    {
41
        $exec = new ExecTester($this);
42
        $exec->expect('capture', 'docker', 1);
43
        $exec->expect('capture', 'docker', 126);
44
45
        $step = $this->createTestStep();
46
        $runner = $this->createTestStepRunner($exec, null, array(null, 'php://output'));
47
48
        $this->expectOutputRegex('~pipelines: setting up the container failed~');
49
        $actual = $runner->runStep($step);
50
        self::assertNotSame(0, $actual);
51
    }
52
53
    /**
54
     * @covers \Ktomk\Pipelines\Runner\Containers\StepContainer
55
     */
56
    public function testRunning()
57
    {
58
        /** @var Exec|MockObject $exec */
59
        $exec = $this->createMock('Ktomk\Pipelines\Cli\Exec');
60
        $exec->method('pass')->willReturn(0);
61
        $exec->method('capture')->willReturn(0);
62
63
        $step = $this->createTestStep();
64
        $runner = $this->createTestStepRunner($exec, null, 'php://output');
65
66
        $this->expectOutputRegex('{^\x1d\+\+\+ step #1\n}');
67
        $actual = $runner->runStep($step);
68
        self::assertSame(0, $actual);
69
    }
70
71
    public function testCopy()
72
    {
73
        $exec = new ExecTester($this);
74
        $exec
75
            ->expect('capture', 'docker', 1, 'zap')
76
            ->expect('capture', 'docker', 0, 'run step container')
77
            ->expect('pass', $this->deploy_copy_cmd, 0, 'copy deployment /app create')
78
            ->expect('pass', $this->deploy_copy_cmd_2, 0, 'copy deployment /app copy')
79
            ->expect('pass', '~ docker exec ~', 0, 'run step script')
80
            ->expect('capture', 'docker', 0, 'docker kill')
81
            ->expect('capture', 'docker', 0, 'docker rm');
82
83
        $step = $this->createTestStep();
84
        $runner = $this->createTestStepRunner($exec, Flags::FLAG_DEPLOY_COPY | Flags::FLAGS, 'php://output');
85
86
        $this->expectOutputRegex('{^\x1D\+\+\+ copying files into container\.\.\.\n}m');
87
88
        $status = $runner->runStep($step);
89
        self::assertSame(0, $status);
90
    }
91
92
    /**
93
     * this is the first stage, however in general deploy copy fails, too.
94
     *
95
     * the first stage is to create the container target directory to copy
96
     * into.
97
     */
98
    public function testCopyFails()
99
    {
100
        $exec = new ExecTester($this);
101
        $exec
102
            ->expect('capture', 'docker', 1, 'zap')
103
            ->expect('capture', 'docker', 0, 'run step container')
104
            ->expect('pass', $this->deploy_copy_cmd, 1);
105
106
        $step = $this->createTestStep();
107
        $runner = $this->createTestStepRunner($exec, Flags::FLAG_DEPLOY_COPY | Flags::FLAGS, array(null, 'php://output'));
108
109
        $this->expectOutputRegex('{^pipelines: deploy copy failure}');
110
        $status = $runner->runStep($step);
111
        self::assertSame(1, $status);
112
    }
113
114
    /**
115
     * second stage is to copy project files into the container target directory
116
     */
117
    public function testCopyFailsAtSecondStage()
118
    {
119
        $exec = new ExecTester($this);
120
        $exec
121
            ->expect('capture', 'docker', 1, 'zap')
122
            ->expect('capture', 'docker', 0, 'run step container')
123
            ->expect('pass', $this->deploy_copy_cmd, 0)
124
            ->expect('pass', $this->deploy_copy_cmd_2, 1);
125
126
        $step = $this->createTestStep();
127
        $runner = $this->createTestStepRunner($exec, Flags::FLAG_DEPLOY_COPY | Flags::FLAGS, array(null, 'php://output'));
128
129
        $this->expectOutputRegex('{^pipelines: deploy copy failure}');
130
        $status = $runner->runStep($step);
131
        self::assertSame(1, $status);
132
    }
133
134
    /**
135
     * @covers \Ktomk\Pipelines\Runner\Containers\StepContainer
136
     */
137
    public function testKeepContainerOnErrorWithNonExistentContainer()
138
    {
139
        $exec = new ExecTester($this);
140
        $exec
141
            ->expect('capture', 'docker', 1, 'no id for name of potential re-use')
142
            ->expect('capture', 'docker', 0, 'run the container')
143
            ->expect('pass', '~ docker exec ~', 255);
144
145
        $this->keepContainerOnErrorExecTest($exec);
146
    }
147
148
    /**
149
     * @covers \Ktomk\Pipelines\Runner\Containers\StepContainer
150
     */
151
    public function testKeepContainerOnErrorWithExistingContainer()
152
    {
153
        $containerId = 'face42face42';
154
155
        $exec = new ExecTester($this);
156
        $exec
157
            ->expect('capture', 'docker', $containerId) # id for name of potential re-use
158
            ->expect('pass', '~ docker exec ~', 255);
159
160
        $this->keepContainerOnErrorExecTest($exec, $containerId);
161
    }
162
163
    public function testZapExistingContainer()
164
    {
165
        $exec = new ExecTester($this);
166
        $exec
167
            ->expect('capture', 'docker', "123456789\n", 'zap: docker ps')
168
            ->expect('capture', 'docker', "123456789\n", 'zap: docker kill')
169
            ->expect('capture', 'docker', "123456789\n", 'zap: docker rm')
170
            ->expect('capture', 'docker', 0, 'docker run step container')
171
            ->expect('pass', $this->deploy_copy_cmd, 0, 'deploy copy stage 1')
172
            ->expect('pass', $this->deploy_copy_cmd_2, 0, 'deploy copy stage 1')
173
            ->expect('pass', '~ docker exec ~', 0)
174
            ->expect('capture', 'docker', 0, 'docker kill')
175
            ->expect('capture', 'docker', 0, 'docker rm');
176
177
        $runner = $this->createTestStepRunner($exec, Flags::FLAG_DEPLOY_COPY | Flags::FLAGS);
178
        $step = $this->createTestStep();
179
180
        $status = $runner->runStep($step);
181
        self::assertSame(0, $status);
182
    }
183
184
    /**
185
     * @covers \Ktomk\Pipelines\Runner\Containers\StepContainer
186
     */
187
    public function testKeepExistingContainer()
188
    {
189
        $exec = new ExecTester($this);
190
        $exec
191
            ->expect('capture', 'docker', "123456789\n", 'existing id')
192
            ->expect('pass', '~ docker exec ~', 0);
193
194
        $runner = $this->createTestStepRunner($exec, (Flags::FLAG_DOCKER_KILL | Flags::FLAG_DOCKER_REMOVE) ^ Flags::FLAGS);
195
        $step = $this->createTestStep();
196
197
        $status = $runner->runStep($step);
198
        self::assertSame(0, $status);
199
    }
200
201
    public function testDockerHubImageLogin()
202
    {
203
        $exec = new Exec();
204
        $exec->setActive(false);
205
206
        $step = $this->createTestStep(array(
207
            'image' => array(
208
                'name' => 'foo/bar:latest',
209
                'username' => 'user',
210
                'password' => 'secret',
211
            ),
212
        ));
213
        $runner = $this->createTestStepRunner($exec, null, array(null, 'php://output'), array());
214
215
        $this->expectOutputString('');
216
        $status = $runner->runStep($step);
217
        self::assertSame(0, $status);
218
    }
219
220
    public function testMountInCopyFails()
221
    {
222
        $exec = new Exec();
223
        $exec->setActive(false);
224
225
        $step = $this->createTestStep();
226
        $this->setTestProject(new Project('/app')); # fake test-directory as if being inside a container FIXME(tk): hard encoded /app
227
        $runner = $this->createTestStepRunner($exec, null, array(null, 'php://output'), array('PIPELINES_PARENT_CONTAINER_NAME' => 'foo'));
228
229
        $this->expectOutputString("pipelines: fatal: can not detect /app mount point\npipelines: setting up the container failed\n");
230
        $status = $runner->runStep($step);
231
        self::assertSame(1, $status, 'non-zero status as mounting not possible with mock');
232
    }
233
234
    public function testMountWithoutHostConfig()
235
    {
236
        $exec = new Exec();
237
        $exec->setActive(false);
238
239
        $step = $this->createTestStep();
240
        $this->setTestProject(new Project('/app')); # fake test-directory as if being inside a container FIXME(tk): hard encoded /app
241
        $runner = $this->createTestStepRunner($exec, null, array(null, 'php://output'), array());
242
243
        $this->expectOutputString('');
244
        $status = $runner->runStep($step);
245
        self::assertSame(0, $status, 'passed as detected as non-pip');
246
    }
247
248
    public function testWithHostConfig()
249
    {
250
        $exec = new Exec();
251
        $exec->setActive(false);
252
253
        $step = $this->createTestStep();
254
        $this->setTestProject(new Project('/app')); # fake test-directory as if being inside a container FIXME(tk): hard encoded /app
255
        $inherit = array(
256
            'PIPELINES_PARENT_CONTAINER_NAME' => 'foo',
257
            'PIPELINES_PIP_CONTAINER_NAME' => 'foo',
258
        );
259
        $runner = $this->createTestStepRunner($exec, null, array(null, 'php://output'), $inherit);
260
261
        $this->expectOutputString("pipelines: fatal: can not detect /app mount point\npipelines: setting up the container failed\n");
262
        $status = $runner->runStep($step);
263
        self::assertSame(1, $status, 'non-zero status as mounting not possible with mock');
264
    }
265
266
    /* docker socket tests */
267
268
    public function testRunStepWithoutMountingDockerSocket()
269
    {
270
        $exec = new Exec();
271
        $exec->setActive(false);
272
273
        $step = $this->createTestStep();
274
        $flags = Flags::FLAG_DOCKER_REMOVE | Flags::FLAG_DOCKER_KILL;
275
        $runner = $this->createTestStepRunner($exec, $flags, array(null, 'php://output'), array('PIPELINES_PARENT_CONTAINER_NAME' => 'foo'));
276
277
        $this->expectOutputString('');
278
        $status = $runner->runStep($step);
279
        self::assertSame(0, $status);
280
    }
281
282
    public function testRunStepWithDockerHostParameterDockerSocket()
283
    {
284
        $inherit = array(
285
            'DOCKER_HOST' => 'unix:///var/run/docker.sock',
286
        );
287
288
        $exec = new Exec();
289
        $exec->setActive(false);
290
291
        $step = $this->createTestStep();
292
        $this->setTestProject(new Project('/app')); # fake test-directory as if being inside a container FIXME(tk): hard encoded /app
293
        $runner = $this->createTestStepRunner($exec, null, array(null, 'php://output'), $inherit);
294
295
        $this->expectOutputString('');
296
        $status = $runner->runStep($step);
297
        self::assertSame(0, $status);
298
    }
299
300
    public function testRunStepWithPipDockerSocket()
301
    {
302
        $inherit = array(
303
            'DOCKER_HOST' => 'unix:///run/user/1000/docker.sock',
304
            'PIPELINES_PARENT_CONTAINER_NAME' => 'parent_container_name',
305
            'PIPELINES_PIP_CONTAINER_NAME' => 'parent_container_name',
306
        );
307
308
        $exec = new ExecTester($this);
309
310
        $step = $this->createTestStep();
311
        $runner = $this->createTestStepRunner($exec, null, array(null, 'php://output'), $inherit);
312
313
        $this->expectOutputString('');
314
        $exec->expect('capture', 'docker', 0, 'ps');
315
316
        $buffer = json_encode(array(array(
317
            'HostConfig' => array(
318
                'Binds' => array('/dev/null:' . $this->getTestProject()->getPath() . '/var/run/docker.sock'),
319
            ),
320
        )));
321
        $exec->expect('capture', 'docker', $buffer, 'obtain socket bind');
322
        $exec->expect('capture', 'docker', 0, 'obtain mount bind');
323
        $exec->expect('capture', 'docker', 0, 'run');
324
        $exec->expect('pass', '~ docker exec ~', 0, 'script');
325
        $exec->expect('capture', 'docker', 0, 'kill');
326
        $exec->expect('capture', 'docker', 0, 'rm');
327
328
        $status = $runner->runStep($step);
329
        self::assertSame(0, $status);
330
    }
331
332
    /* docker client tests */
333
334
    /**
335
     * Test to install the test-stub for docker client
336
     *
337
     * Previously tested for docker client injection (copy), now tests
338
     * with mounting (here: no mount, so install the test package)
339
     */
340
    public function testDockerClientInjection()
341
    {
342
        $exec = new ExecTester($this);
343
        $exec
344
            ->expect('capture', 'docker', 1, 'no id for name of potential re-use')
345
            ->expect('capture', 'docker', 0, 'run the container')
346
            ->expect('pass', '~ docker exec ~', 0, 'run step script')
347
            ->expect('capture', 'docker', 0, 'kill')
348
            ->expect('capture', 'docker', 0, 'rm');
349
350
        $testDirectories = new Directories($_SERVER, $this->getTestProject());
0 ignored issues
show
Bug introduced by
It seems like $this->getTestProject() can also be of type string; however, parameter $project of Ktomk\Pipelines\Runner\Directories::__construct() does only seem to accept Ktomk\Pipelines\Project, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

350
        $testDirectories = new Directories($_SERVER, /** @scrutinizer ignore-type */ $this->getTestProject());
Loading history...
351
352
        $testRepository = $this->getMockBuilder('Ktomk\Pipelines\Runner\Docker\Binary\Repository')
0 ignored issues
show
Deprecated Code introduced by
The function PHPUnit\Framework\MockOb...ckBuilder::setMethods() has been deprecated: https://github.com/sebastianbergmann/phpunit/pull/3687 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

352
        $testRepository = /** @scrutinizer ignore-deprecated */ $this->getMockBuilder('Ktomk\Pipelines\Runner\Docker\Binary\Repository')

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
353
            ->setConstructorArgs(array($exec, array(), UnPackager::fromDirectories($exec, $testDirectories)))
354
            ->setMethods(array('getPackageLocalBinary'))
355
            ->getMock();
356
        $testRepository->method('getPackageLocalBinary')->willReturn(__DIR__ . '/../../data/package/docker-test-stub');
357
358
        $runner = new Runner(
359
            RunOpts::create('pipelinesunittest'),
360
            $testDirectories,
361
            $exec,
362
            new Flags(),
363
            Env::createEx(),
364
            new Streams()
365
        );
366
367
        /** @var MockObject|StepRunner $mockRunner */
368
        $mockRunner = $this->getMockBuilder('Ktomk\Pipelines\Runner\StepRunner')
0 ignored issues
show
Deprecated Code introduced by
The function PHPUnit\Framework\MockOb...ckBuilder::setMethods() has been deprecated: https://github.com/sebastianbergmann/phpunit/pull/3687 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

368
        $mockRunner = /** @scrutinizer ignore-deprecated */ $this->getMockBuilder('Ktomk\Pipelines\Runner\StepRunner')

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
369
            ->setConstructorArgs(array(
370
                $runner,
371
                RunOpts::create('pipelinesunittest'),
372
                $testDirectories,
373
                $exec,
374
                new Flags(),
375
                Env::createEx(),
376
                new Streams(),
377
            ))
378
            ->setMethods(array('getDockerBinaryRepository'))
379
            ->getMock();
380
        $mockRunner->method('getDockerBinaryRepository')->willReturn($testRepository);
381
382
        $step = $this->createTestStep(array('services' => array('docker')));
383
        $actual = $mockRunner->runStep($step);
384
        self::assertSame(0, $actual);
385
    }
386
387
    /**
388
     * @covers \Ktomk\Pipelines\Runner\StepRunner::obtainDockerClientMount()
389
     */
390
    public function testDockerClientMount()
391
    {
392
        $inherit = array(
393
            'PIPELINES_PARENT_CONTAINER_NAME' => 'parent_container_name',
394
            'PIPELINES_PIP_CONTAINER_NAME' => 'parent_container_name',
395
        );
396
397
        $exec = new ExecTester($this);
398
399
        $step = $this->createTestStep(array('services' => array('docker')));
400
        $runner = $this->createTestStepRunner($exec, null, array(null, 'php://output'), $inherit);
401
402
        $this->expectOutputString('');
403
        $exec->expect('capture', 'docker', 0, 'ps');
404
        $buffer = json_encode(array(array(
405
            'HostConfig' => array(
406
                'Binds' => array('/dev/null:/usr/bin/docker:ro'),
407
            ),
408
        )));
409
        $exec->expect('capture', 'docker', 0, 'obtain socket bind');
410
        $exec->expect('capture', 'docker', $buffer, 'obtain client bind');
411
        $exec->expect('capture', 'docker', 0, 'obtain mount bind');
412
        $exec->expect('capture', 'docker', 0, 'run');
413
        $exec->expect('pass', '~ docker exec ~', 0, 'script');
414
        $exec->expect('capture', 'docker', 0, 'kill');
415
        $exec->expect('capture', 'docker', 0, 'rm');
416
417
        $status = $runner->runStep($step);
418
        self::assertSame(0, $status);
419
    }
420
421
    /**
422
     *
423
     */
424
    public function testDockerClientInjectInvalidPackage()
425
    {
426
        $exec = new ExecTester($this);
427
        $exec
428
            ->expect('capture', 'docker', 1, 'no id for name of potential re-use');
429
        $testDirectories = new Directories($_SERVER, $this->getTestProject());
0 ignored issues
show
Bug introduced by
It seems like $this->getTestProject() can also be of type string; however, parameter $project of Ktomk\Pipelines\Runner\Directories::__construct() does only seem to accept Ktomk\Pipelines\Project, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

429
        $testDirectories = new Directories($_SERVER, /** @scrutinizer ignore-type */ $this->getTestProject());
Loading history...
430
431
        $runner = new Runner(
432
            RunOpts::create('pipelinesunittest', 'wrong-package'),
433
            $testDirectories,
434
            $exec,
435
            new Flags(),
436
            Env::createEx(),
437
            new Streams()
438
        );
439
440
        /** @var MockObject|StepRunner $mockRunner */
441
        $mockRunner = $this->getMockBuilder('Ktomk\Pipelines\Runner\StepRunner')
0 ignored issues
show
Deprecated Code introduced by
The function PHPUnit\Framework\MockOb...ckBuilder::setMethods() has been deprecated: https://github.com/sebastianbergmann/phpunit/pull/3687 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

441
        $mockRunner = /** @scrutinizer ignore-deprecated */ $this->getMockBuilder('Ktomk\Pipelines\Runner\StepRunner')

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
442
            ->setConstructorArgs(array($runner))
443
            ->setMethods(null)
444
            ->getMock();
445
446
        $step = $this->createTestStep(array('services' => array('docker')));
447
        $this->expectException('\InvalidArgumentException');
448
        $this->expectExceptionMessage('not a readable file');
449
        $mockRunner->runStep($step);
450
        self::fail('an expected exception was not thrown');
451
    }
452
453
    /* artifact tests */
454
455
    public function testArtifacts()
456
    {
457
        $tmpProjectDir = $this->getTestProject()->getPath();
458
459
        $exec = new ExecTester($this);
460
        $exec
461
            ->expect('capture', 'docker', 1, 'zap')
462
            ->expect('capture', 'docker', 0, 'docker run step container')
463
            ->expect('pass', $this->deploy_copy_cmd, 0)
464
            ->expect('pass', $this->deploy_copy_cmd_2, 0)
465
            ->expect('pass', '~ docker exec ~', 0)
466
            ->expect('capture', 'docker', './build/foo-package.tgz')
467
            ->expect('pass', 'docker exec -w /app \'*dry-run*\' tar c -f - build/foo-package.tgz | tar x -f - -C ' . $tmpProjectDir, 0)
468
            ->expect('capture', 'docker', 0, 'docker kill')
469
            ->expect('capture', 'docker', 0, 'docker rm');
470
471
        $step = $this->createTestStep(array('artifacts' => array('build/foo-package.tgz')));
472
        $runner = $this->createTestStepRunner($exec, Flags::FLAG_DEPLOY_COPY | Flags::FLAGS);
473
474
        $status = $runner->runStep($step);
475
        self::assertSame(0, $status);
476
    }
477
478
    public function testArtifactsNoMatch()
479
    {
480
        $exec = new ExecTester($this);
481
        $exec
482
            ->expect('capture', 'docker', 1, 'zap')
483
            ->expect('capture', 'docker', 0, 'docker run step container')
484
            ->expect('pass', $this->deploy_copy_cmd, 0)
485
            ->expect('pass', $this->deploy_copy_cmd_2, 0)
486
            ->expect('pass', '~ docker exec ~', 0)
487
            ->expect('capture', 'docker', './build/foo-package.tgz')
488
            ->expect('capture', 'docker', 0) # docker kill
489
            ->expect('capture', 'docker', 0) # docker rm
490
        ;
491
492
        $step = $this->createTestStep(array('artifacts' => array('build/bar-package.tgz')));
493
        $runner = $this->createTestStepRunner($exec, Flags::FLAG_DEPLOY_COPY | Flags::FLAGS);
494
495
        $status = $runner->runStep($step);
496
        self::assertSame(0, $status);
497
    }
498
499
    public function testArtifactsFailure()
500
    {
501
        $tmpProjectDir = $this->getTestProject()->getPath();
502
503
        $exec = new ExecTester($this);
504
        $exec
505
            ->expect('capture', 'docker', 1, 'zap')
506
            ->expect('capture', 'docker', 0, 'docker run step container')
507
            ->expect('pass', $this->deploy_copy_cmd, 0)
508
            ->expect('pass', $this->deploy_copy_cmd_2, 0)
509
            ->expect('pass', '~ docker exec ~', 0)
510
            ->expect('capture', 'docker', './build/foo-package.tgz')
511
            ->expect(
512
                'pass',
513
                'docker exec -w /app \'*dry-run*\' tar c -f - build/foo-package.tgz | tar x -f - -C ' . $tmpProjectDir,
514
                1
515
            )
516
            ->expect('capture', 'docker', 0, 'docker kill')
517
            ->expect('capture', 'docker', 0, 'docker rm');
518
519
        $this->expectOutputRegex('~^pipelines: Artifact failure: \'build/foo-package.tgz\' \\(1, 1 paths, 1\\d\\d bytes\\)$~m');
520
        $step = $this->createTestStep(array('artifacts' => array('build/foo-package.tgz')));
521
        $runner = $this->createTestStepRunner($exec, Flags::FLAG_DEPLOY_COPY | Flags::FLAGS, array(null, 'php://output'));
522
523
        $status = $runner->runStep($step);
524
        self::assertSame(0, $status);
525
    }
526
527
    /* public/new */
528
529
    public function testGetDockerBinaryRepository()
530
    {
531
        $exec = new ExecTester($this);
532
533
        $runner = new Runner(
534
            RunOpts::create('foo', Repository::PKG_TEST),
535
            new Directories($_SERVER, new Project('foo')),
536
            $exec,
537
            new Flags(),
538
            new Env(),
539
            new Streams()
540
        );
541
542
        $stepRunner = new StepRunner($runner);
543
        $actual = $stepRunner->getDockerBinaryRepository();
544
        self::assertInstanceOf('Ktomk\Pipelines\Runner\Docker\Binary\Repository', $actual);
545
    }
546
547
    public function testAfterScript()
548
    {
549
        $exec = new ExecTester($this);
550
        $exec
551
            ->expect('capture', 'docker', 1, 'zap')
552
            ->expect('capture', 'docker', 0, 'docker run step container')
553
            ->expect('pass', '~<<\'SCRIPT\' docker exec ~', 0, 'script')
554
            ->expect('pass', '~<<\'SCRIPT\' docker exec ~', 0, 'after-script')
555
            ->expect('capture', 'docker', 0, 'docker kill')
556
            ->expect('capture', 'docker', 0, 'docker rm');
557
558
        $step = $this->createTestStepFromFixture('after-script.yml');
559
        $runner = $this->createTestStepRunner($exec, Flags::FLAGS, 'php://output');
560
561
        $this->expectOutputRegex('{^After script:}m');
562
563
        $status = $runner->runStep($step);
564
        self::assertSame(0, $status);
565
    }
566
567
    public function testAfterScriptFailing()
568
    {
569
        $exec = new ExecTester($this);
570
        $exec
571
            ->expect('capture', 'docker', 1, 'zap')
572
            ->expect('capture', 'docker', 0, 'docker run step container')
573
            ->expect('pass', '~<<\'SCRIPT\' docker exec ~', 0, 'script')
574
            ->expect('pass', '~<<\'SCRIPT\' docker exec ~', 123, 'after-script')
575
            ->expect('capture', 'docker', 0, 'docker kill')
576
            ->expect('capture', 'docker', 0, 'docker rm');
577
578
        $step = $this->createTestStepFromFixture('after-script.yml');
579
        $runner = $this->createTestStepRunner($exec, Flags::FLAGS, array('php://output', 'php://output'));
580
581
        $this->expectOutputRegex('{^after-script non-zero exit status: 123$}m');
582
583
        $status = $runner->runStep($step);
584
        self::assertSame(0, $status);
585
    }
586
587
    /**
588
     * @see StepRunner::obtainServicesNetwork()
589
     * @see StepRunner::shutdownServices()
590
     */
591
    public function testServicesObtainNetworkAndShutdown()
592
    {
593
        $exec = new ExecTester($this);
594
        $exec
595
            ->expect('capture', 'docker', 1, 'zap')
596
            ->expect('capture', 'docker', 0, 'docker run step container')
597
            ->expect('capture', 'docker', 0, 'run services')
598
            ->expect('pass', '~<<\'SCRIPT\' docker exec ~', 0, 'script')
599
            ->expect('capture', 'docker', 0, 'docker kill')
600
            ->expect('capture', 'docker', 0, 'docker rm')
601
            ->expect('capture', 'docker', 0, 'service docker kill')
602
            ->expect('capture', 'docker', 0, 'service docker rm');
603
604
        $step = $this->createTestStepFromFixture('service-definitions.yml');
605
        $runner = $this->createTestStepRunner($exec, Flags::FLAGS, array('php://output', 'php://output'));
606
607
        $this->expectOutputRegex('~effective-image: redis$~m');
608
        self::assertSame(0, $runner->runStep($step));
609
    }
610
611
    /**
612
     * @covers \Ktomk\Pipelines\Runner\Docker\CacheIo
613
     */
614
    public function testCaches()
615
    {
616
        $tmpProjectDir = $this->getTestProject()->getPath();
617
        $tmpHomeDir = $tmpProjectDir . '/home';
618
619
        $exec = new ExecTester($this);
620
        $exec
621
            ->expect('capture', 'docker', 1, 'zap')
622
            ->expect('capture', 'docker', 0, 'docker run step container')
623
            ->expect('pass', '~<<\'SCRIPT\' docker exec ~', 0, 'script')
624
            ->expect('capture', '~^docker exec ~', 0, 'caches: map path')
625
            ->expect('capture', '~>.*/composer\.tar docker cp~', 0, 'caches: copy out')
626
            ->expect('capture', 'docker', 0, 'docker kill')
627
            ->expect('capture', 'docker', 0, 'docker rm');
628
629
        $step = $this->createTestStepFromFixture('cache.yml');
630
        $runner = $this->createTestStepRunner($exec, Flags::FLAGS, 'php://output', array('HOME' => $tmpHomeDir));
631
632
        $this->expectOutputRegex('{^ - composer ~/.composer/cache }m');
633
634
        $status = $runner->runStep($step);
635
        self::assertSame(0, $status);
636
    }
637
638
    /**
639
     * @covers \Ktomk\Pipelines\Runner\Docker\CacheIo
640
     */
641
    public function testCachesPrePopulated()
642
    {
643
        $tmpProjectDir = $this->getTestProject()->getPath();
644
        $tmpHomeDir = $tmpProjectDir . '/home';
645
646
        // fake caches file
647
        $tarDir = $tmpHomeDir . '/.cache/pipelines/caches/local-has-no-slug';
648
        LibFs::mkDir($tarDir);
649
        touch($tarDir . '/composer.tar');
650
651
        $exec = new ExecTester($this);
652
        $exec
653
            ->expect('capture', 'docker', 1, 'zap')
654
            ->expect('capture', 'docker', 0, 'docker run step container')
655
            ->expect('capture', '~^docker exec ~', 0, 'caches: map path')
656
            ->expect('capture', '~^docker exec .* mkdir -p ~', 0, 'caches: mkdir in container')
657
            ->expect('capture', '~<.*/composer\.tar docker cp ~', 0, 'caches: copy in')
658
            ->expect('pass', '~<<\'SCRIPT\' docker exec ~', 0, 'script')
659
            ->expect('capture', '~^docker exec ~', 0, 'caches: map path')
660
            ->expect('capture', '~>.*/composer\.tar docker cp~', 0, 'caches: copy out')
661
            ->expect('capture', 'docker', 0, 'docker kill')
662
            ->expect('capture', 'docker', 0, 'docker rm');
663
664
        $step = $this->createTestStepFromFixture('cache.yml');
665
        $runner = $this->createTestStepRunner($exec, Flags::FLAGS, 'php://output', array('HOME' => $tmpHomeDir));
666
667
        $this->expectOutputRegex('{^ - composer ~/.composer/cache }m');
668
669
        $status = $runner->runStep($step);
670
        self::assertSame(0, $status);
671
    }
672
673
    private function keepContainerOnErrorExecTest(ExecTester $exec, $id = '*dry-run*')
674
    {
675
        $expectedRegex = sprintf(
676
            '{script non-zero exit status: 255\nerror, keeping container id %s}',
677
            preg_quote($id, '{}')
678
        );
679
680
        $runner = $this->createTestStepRunner($exec, Flags::FLAGS | Flags::FLAG_KEEP_ON_ERROR, array(null, 'php://output'));
681
        $step = $this->createTestStep(); # 'script' => array('fatal me an error') not necessary
682
683
        $this->expectOutputRegex($expectedRegex);
684
        $status = $runner->runStep($step);
685
        self::assertSame(255, $status);
686
    }
687
688
    /**
689
     * @param Exec $exec
690
     * @param int $flags [optional] to override default flags
691
     * @param null|array|string $outErr [optional]
692
     * @param array $inherit [optional] inherit from environment
693
     *
694
     * @return StepRunner
695
     */
696
    private function createTestStepRunner(Exec $exec, $flags = null, $outErr = null, array $inherit = array())
697
    {
698
        list($out, $err) = ((array)$outErr) + array(null, null);
699
700
        $testProject = $this->getTestProject()->getPath();
701
702
        $options = OptionsMock::create();
703
        $options->define('step.clone-path', '/app');
704
705
        $clonePath = $options->get('step.clone-path');
706
707
        if (($clonePath !== $testProject) && is_dir($testProject)) {
708
            // fake docker.sock file inside temporary test directory so it exists
709
            $value = $testProject . '/var';
710
            LibFs::mkDir($value);
711
            $value .= '/run';
712
            LibFs::mkDir($value);
713
            $value .= '/docker.sock';
714
            touch($value);
715
        } else {
716
            $value = LibTmp::tmpFilePut('');
717
            $this->cleaners[] = DestructibleString::rm($value);
718
        }
719
720
        $options->define('docker.socket.path', $value);
721
722
        $runOpts = new RunOpts('pipelinesunittest', $options);
723
        $directories = new Directories($inherit + $_SERVER, new Project($testProject));
724
        $flagsObject = new Flags($flags);
725
        $env = Env::createEx($inherit);
726
        $streams = new Streams(null, $out, $err);
727
728
        return new StepRunner(
729
            new Runner($runOpts, $directories, $exec, $flagsObject, $env, $streams)
730
        );
731
    }
732
}
733