ServiceTest   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 307
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 3
dl 0
loc 307
rs 10
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A setUp() 0 4 1
A tearDown() 0 11 3
A testServiceName() 0 8 1
A testIsRunning() 0 9 1
A testIsNotRunning() 0 9 1
A testCheckInvalidServiceThrowsException() 0 10 1
A testStart() 0 12 1
A testStartAlreadyRunning() 0 11 1
A testStartInvalidServiceThrowsException() 0 11 1
A testStop() 0 12 1
A testStopWhenNotRunning() 0 11 1
A testStopInvalidServiceThrowsException() 0 11 1
A testRestart() 0 11 1
A testRestartInvalidServiceThrowsException() 0 10 1
A testExceptionHasProcess() 0 15 2
A getMockedService() 0 22 1
A expectCall() 0 21 2
A expectNoOtherCalls() 0 6 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the systemctl PHP library.
7
 *
8
 * (c) Martin Janser <[email protected]>
9
 *
10
 * This source file is subject to the GPL license that is bundled
11
 * with this source code in the file LICENSE.
12
 */
13
14
namespace SystemCtl\Tests;
15
16
use PHPUnit\Framework\TestCase;
17
use Symfony\Component\Process\Process;
18
use SystemCtl\CommandFailedException;
19
use SystemCtl\Service;
20
21
/**
22
 * @covers \SystemCtl\Service::__construct
23
 * @covers \SystemCtl\Service::setCommand
24
 * @covers \SystemCtl\Service::sudo
25
 * @covers \SystemCtl\Service::<private>
26
 */
27
class ServiceTest extends TestCase
28
{
29
    /**
30
     * @var string
31
     */
32
    private $commandFilename;
33
34
    /**
35
     * @var string
36
     */
37
    private $callCountFilename;
38
39
    /**
40
     * @var int
41
     */
42
    private $callCount = 1;
43
44
    protected function setUp()
45
    {
46
        $this->callCount = 1;
47
    }
48
49
    protected function tearDown()
50
    {
51
        if ($this->commandFilename) {
52
            unlink($this->commandFilename);
53
            $this->commandFilename = null;
54
        }
55
        if ($this->callCountFilename) {
56
            unlink($this->callCountFilename);
57
            $this->callCountFilename = null;
58
        }
59
    }
60
61
    /**
62
     * @covers \SystemCtl\Service::__toString
63
     */
64
    public function testServiceName()
65
    {
66
        $service = $this->getMockedService('test-service');
67
68
        $this->expectNoOtherCalls();
69
70
        $this->assertSame('test-service', (string) $service, 'Service name should match');
71
    }
72
73
    /**
74
     * @covers \SystemCtl\Service::isRunning
75
     */
76
    public function testIsRunning()
77
    {
78
        $service = $this->getMockedService('test-service');
79
80
        $this->expectCall(['--lines=0', 'status', 'test-service'], 0);
81
        $this->expectNoOtherCalls();
82
83
        $this->assertTrue($service->isRunning(), 'Service should be running');
84
    }
85
86
    /**
87
     * @covers \SystemCtl\Service::isRunning
88
     */
89
    public function testIsNotRunning()
90
    {
91
        $service = $this->getMockedService('test-service');
92
93
        $this->expectCall(['--lines=0', 'status', 'test-service'], Service::STATUS_STOPPED);
94
        $this->expectNoOtherCalls();
95
96
        $this->assertFalse($service->isRunning(), 'Service should not be running');
97
    }
98
99
    /**
100
     * @covers \SystemCtl\Service::isRunning
101
     * @covers \SystemCtl\CommandFailedException
102
     */
103
    public function testCheckInvalidServiceThrowsException()
104
    {
105
        $service = $this->getMockedService('test-service');
106
107
        $this->expectCall(['status', 'test-service'], 6);
108
        $this->expectNoOtherCalls();
109
        $this->expectException(CommandFailedException::class);
110
111
        $service->isRunning();
112
    }
113
114
    /**
115
     * @covers \SystemCtl\Service::isRunning
116
     * @covers \SystemCtl\Service::start
117
     */
118
    public function testStart()
119
    {
120
        $service = $this->getMockedService('test-service');
121
122
        $this->expectCall(['--lines=0', 'status', 'test-service'], Service::STATUS_STOPPED);
123
        $this->expectCall(['start', 'test-service'], 0);
124
        $this->expectNoOtherCalls();
125
126
        $service->start();
127
128
        $this->assertTrue(true);
129
    }
130
131
    /**
132
     * @covers \SystemCtl\Service::isRunning
133
     * @covers \SystemCtl\Service::start
134
     */
135
    public function testStartAlreadyRunning()
136
    {
137
        $service = $this->getMockedService('test-service');
138
139
        $this->expectCall(['--lines=0', 'status', 'test-service'], 0);
140
        $this->expectNoOtherCalls();
141
142
        $service->start();
143
144
        $this->assertTrue(true);
145
    }
146
147
    /**
148
     * @covers \SystemCtl\Service::isRunning
149
     * @covers \SystemCtl\Service::start
150
     * @covers \SystemCtl\CommandFailedException
151
     */
152
    public function testStartInvalidServiceThrowsException()
153
    {
154
        $service = $this->getMockedService('test-service');
155
156
        $this->expectCall(['--lines=0', 'status', 'test-service'], Service::STATUS_STOPPED);
157
        $this->expectCall(['start', 'test-service'], 6);
158
        $this->expectNoOtherCalls();
159
        $this->expectException(CommandFailedException::class);
160
161
        $service->start();
162
    }
163
164
    /**
165
     * @covers \SystemCtl\Service::isRunning
166
     * @covers \SystemCtl\Service::stop
167
     */
168
    public function testStop()
169
    {
170
        $service = $this->getMockedService('test-service');
171
172
        $this->expectCall(['--lines=0', 'status', 'test-service'], 0);
173
        $this->expectCall(['stop', 'test-service'], 0);
174
        $this->expectNoOtherCalls();
175
176
        $service->stop();
177
178
        $this->assertTrue(true);
179
    }
180
181
    /**
182
     * @covers \SystemCtl\Service::isRunning
183
     * @covers \SystemCtl\Service::stop
184
     */
185
    public function testStopWhenNotRunning()
186
    {
187
        $service = $this->getMockedService('test-service');
188
189
        $this->expectCall(['--lines=0', 'status', 'test-service'], Service::STATUS_STOPPED);
190
        $this->expectNoOtherCalls();
191
192
        $service->stop();
193
194
        $this->assertTrue(true);
195
    }
196
197
    /**
198
     * @covers \SystemCtl\Service::isRunning
199
     * @covers \SystemCtl\Service::stop
200
     * @covers \SystemCtl\CommandFailedException
201
     */
202
    public function testStopInvalidServiceThrowsException()
203
    {
204
        $service = $this->getMockedService('test-service');
205
206
        $this->expectCall(['--lines=0', 'status', 'test-service'], 0);
207
        $this->expectCall(['stop', 'test-service'], 6);
208
        $this->expectNoOtherCalls();
209
        $this->expectException(CommandFailedException::class);
210
211
        $service->stop();
212
    }
213
214
    /**
215
     * @covers \SystemCtl\Service::restart
216
     */
217
    public function testRestart()
218
    {
219
        $service = $this->getMockedService('test-service');
220
221
        $this->expectCall(['restart', 'test-service'], 0);
222
        $this->expectNoOtherCalls();
223
224
        $service->restart();
225
226
        $this->assertTrue(true);
227
    }
228
229
    /**
230
     * @covers \SystemCtl\Service::isRunning
231
     * @covers \SystemCtl\Service::restart
232
     * @covers \SystemCtl\CommandFailedException
233
     */
234
    public function testRestartInvalidServiceThrowsException()
235
    {
236
        $service = $this->getMockedService('test-service');
237
238
        $this->expectCall(['restart', 'test-service'], 6);
239
        $this->expectNoOtherCalls();
240
        $this->expectException(CommandFailedException::class);
241
242
        $service->restart();
243
    }
244
245
    /**
246
     * @covers \SystemCtl\Service::isRunning
247
     * @covers \SystemCtl\Service::restart
248
     * @covers \SystemCtl\CommandFailedException
249
     */
250
    public function testExceptionHasProcess()
251
    {
252
        $service = $this->getMockedService('test-service');
253
254
        $this->expectCall(['restart', 'test-service'], 6);
255
        $this->expectNoOtherCalls();
256
257
        try {
258
            $service->restart();
259
260
            $this->fail('Command should fail');
261
        } catch (CommandFailedException $e) {
262
            $this->assertInstanceOf(Process::class, $e->getProcess());
263
        }
264
    }
265
266
    /**
267
     * Returns a service instance with a mocked systemctl command.
268
     *
269
     * @param string $name Service name
270
     *
271
     * @return Service
272
     */
273
    private function getMockedService($name)
274
    {
275
        $this->commandFilename = tempnam(sys_get_temp_dir(), 'systemctl');
276
        $this->callCountFilename = tempnam(sys_get_temp_dir(), 'systemctl');
277
278
        file_put_contents($this->callCountFilename, '0');
279
280
        file_put_contents($this->commandFilename, '<?php'."\n");
281
        file_put_contents($this->commandFilename, sprintf(
282
            '$c = file_get_contents(\'%s\');'."\n",
283
            $this->callCountFilename
284
        ), FILE_APPEND);
285
        file_put_contents($this->commandFilename, sprintf(
286
            'file_put_contents(\'%s\', ++$c);'."\n",
287
            $this->callCountFilename
288
        ), FILE_APPEND);
289
290
        Service::setCommand('php '.$this->commandFilename);
291
        Service::sudo(false);
292
293
        return new Service($name);
294
    }
295
296
    /**
297
     * Adds an expected call to the systemctl command.
298
     *
299
     * @param string[] $arguments List of expected arguments
300
     * @param int      $exitCode  Exit code which the command should return
301
     */
302
    private function expectCall(array $arguments, $exitCode)
303
    {
304
        $conditions = [];
305
        $index = 1;
306
        foreach ($arguments as $argument) {
307
            $conditions[] = sprintf(
308
                'isset($argv[%1$d]) && $argv[%1$d] === \'%2$s\'',
309
                $index++,
310
                $argument
311
            );
312
        }
313
314
        $code = sprintf(
315
            'if (%d == $c && %s) { exit(%d); }'."\n",
316
            $this->callCount++,
317
            implode(' && ', $conditions),
318
            $exitCode
319
        );
320
321
        file_put_contents($this->commandFilename, $code, FILE_APPEND);
322
    }
323
324
    /**
325
     * Sets no more expected calls to the systemctl command.
326
     */
327
    private function expectNoOtherCalls()
328
    {
329
        $code = 'fwrite(STDERR, "Invalid call count or arguments specified: ".$c.", ".var_export($argv, true)); exit(250);'."\n";
330
331
        file_put_contents($this->commandFilename, $code, FILE_APPEND);
332
    }
333
}
334