Completed
Pull Request — master (#203)
by
unknown
02:25
created

Worker::isCrashed()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.0119

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 18
ccs 10
cts 11
cp 0.9091
rs 9.2
cc 4
eloc 11
nc 4
nop 0
crap 4.0119
1
<?php
2
namespace ParaTest\Runners\PHPUnit;
3
4
use Exception;
5
6
class Worker
7
{
8
    private static $descriptorspec = array(
9
       0 => array("pipe", "r"),
10
       1 => array("pipe", "w"),
11
       2 => array("pipe", "w")
12
    );
13
    private $proc;
14
    private $pipes;
15
    private $inExecution = 0;
16
    private $isRunning = false;
17
    private $exitCode = null;
18
    private $commands = array();
19
    private $chunks = '';
20
    private $alreadyReadOutput = '';
21
    /**
22
     * @var ExecutableTest
23
     */
24
    private $currentlyExecuting;
25
26 5
    public function start($wrapperBinary, $token = 1, $uniqueToken = null)
27
    {
28 5
        $bin = 'PARATEST=1 ';
29 5
        if (is_numeric($token)) {
30 5
            $bin .= "TEST_TOKEN=$token ";
31 5
        }
32 5
        if ($uniqueToken) {
33
            $bin .= "UNIQUE_TEST_TOKEN=$uniqueToken ";
34
        }
35 5
        $bin .= "exec \"$wrapperBinary\"";
36 5
        $pipes = array();
37 5
        $this->proc = proc_open($bin, self::$descriptorspec, $pipes);
38 5
        $this->pipes = $pipes;
39 5
        $this->isRunning = true;
40 5
    }
41
42
    public function stdout()
43
    {
44
        return $this->pipes[1];
45
    }
46
47 4
    public function execute($testCmd)
48
    {
49 4
        $this->checkStarted();
50 4
        $this->commands[] = $testCmd;
51 4
        fwrite($this->pipes[0], $testCmd . "\n");
52 4
        $this->inExecution++;
53 4
    }
54
55
    public function assign(ExecutableTest $test, $phpunit, $phpunitOptions)
56
    {
57
        if ($this->currentlyExecuting !== null) {
58
            throw new Exception("Worker already has a test assigned - did you forget to call reset()?");
59
        }
60
        $this->currentlyExecuting = $test;
61
        $this->execute($test->command($phpunit, $phpunitOptions));
62
    }
63
64
    public function printFeedback($printer)
65
    {
66
        if ($this->currentlyExecuting !== null) {
67
            $printer->printFeedback($this->currentlyExecuting);
68
        }
69
    }
70
71
    public function reset()
72
    {
73
        $this->currentlyExecuting = null;
74
    }
75
76 6
    public function isStarted()
77
    {
78 6
        return $this->proc !== null && $this->pipes !== null;
79
    }
80
81 4
    private function checkStarted()
82
    {
83 4
        if (!$this->isStarted()) {
84
            throw new \RuntimeException("You have to start the Worker first!");
85
        }
86 4
    }
87
88 3
    public function stop()
89
    {
90 3
        fwrite($this->pipes[0], "EXIT\n");
91 3
        fclose($this->pipes[0]);
92 3
    }
93
94
    /**
95
     * This is an utility function for tests.
96
     * Refactor or write it only in the test case.
97
     */
98 2
    public function waitForFinishedJob()
99
    {
100 2
        if ($this->inExecution == 0) {
101
            return;
102
        }
103 2
        $tellsUsItHasFinished = false;
104 2
        stream_set_blocking($this->pipes[1], 1);
105 2
        while ($line = fgets($this->pipes[1])) {
106 2
            if (strstr($line, "FINISHED\n")) {
107 2
                $tellsUsItHasFinished = true;
108 2
                $this->inExecution--;
109 2
                break;
110
            }
111 2
        }
112 2
        if (!$tellsUsItHasFinished) {
113
            throw new \RuntimeException("The Worker terminated without finishing the job.");
114
        }
115 2
    }
116
117 1
    public function isFree()
118
    {
119 1
        $this->checkNotCrashed();
120 1
        $this->updateStateFromAvailableOutput();
121 1
        return $this->inExecution == 0;
122
    }
123
124
    /**
125
     * @deprecated
126
     * This function consumes a lot of CPU while waiting for
127
     * the worker to finish. Use it only in testing paratest
128
     * itself.
129
     */
130 3
    public function waitForStop()
131
    {
132 3
        $status = proc_get_status($this->proc);
133 3
        while ($status['running']) {
134 3
            $status = proc_get_status($this->proc);
135 3
            $this->setExitCode($status);
136 3
        }
137 3
    }
138
139
    public function getCoverageFileName()
140
    {
141
        if ($this->currentlyExecuting !== null) {
142
            return $this->currentlyExecuting->getCoverageFileName();
143
        } else {
144
            return null;
145
        }
146
    }
147
148 4
    private function setExitCode($status)
149
    {
150 4
        if (!$status['running']) {
151 3
            if ($this->exitCode === null) {
152 3
                $this->exitCode = $status['exitcode'];
153 3
            }
154 3
        }
155 4
    }
156
157 1
    public function isRunning()
158
    {
159 1
        $this->checkNotCrashed();
160 1
        $this->updateStateFromAvailableOutput();
161 1
        return $this->isRunning;
162
    }
163
164 3
    public function isCrashed()
165
    {
166 3
        if (!$this->isStarted()) {
167 1
            return false;
168
        }
169 3
        $status = proc_get_status($this->proc);
170
171 3
        $this->updateStateFromAvailableOutput();
172 3
        if (!$this->isRunning) {
173 2
            return false;
174
        }
175
176 2
        $this->setExitCode($status);
177 2
        if ($this->exitCode === null) {
178 2
            return false;
179
        }
180
        return $this->exitCode != 0;
181
    }
182
183 2
    private function checkNotCrashed()
184
    {
185 2
        if ($this->isCrashed()) {
186
            throw new \RuntimeException(
187
                "This worker has crashed. Last executed command: " . end($this->commands) . PHP_EOL
188
                . "Output:" . PHP_EOL
189
                . "----------------------" . PHP_EOL
190
                . $this->alreadyReadOutput . PHP_EOL
191
                . "----------------------" . PHP_EOL
192
                . $this->readAllStderr()
193
            );
194
        }
195 2
    }
196
197
    private function readAllStderr()
198
    {
199
        return stream_get_contents($this->pipes[2]);
200
    }
201
202
    /**
203
     * Have to read even incomplete lines to play nice with stream_select()
204
     * Otherwise it would continue to non-block because there are bytes to be read,
205
     * but fgets() won't pick them up.
206
     */
207 3
    private function updateStateFromAvailableOutput()
208
    {
209 3
        if (isset($this->pipes[1])) {
210 2
            stream_set_blocking($this->pipes[1], 0);
211 2
            while ($chunk = fread($this->pipes[1], 4096)) {
212 1
                $this->chunks .= $chunk;
213 1
                $this->alreadyReadOutput .= $chunk;
214 1
            }
215 2
            $lines = explode("\n", $this->chunks);
216
            // last element is not a complete line,
217
            // becomes part of a line completed later
218 2
            $this->chunks = $lines[count($lines) - 1];
219 2
            unset($lines[count($lines) - 1]);
220
            // delivering complete lines to this Worker
221 2
            foreach ($lines as $line) {
222 1
                $line .= "\n";
223 1
                if (strstr($line, "FINISHED\n")) {
224
                    $this->inExecution--;
225
                }
226 1
                if (strstr($line, "EXITED\n")) {
227 1
                    $this->isRunning = false;
228 1
                }
229 2
            }
230 2
            stream_set_blocking($this->pipes[1], 1);
231 2
        }
232 3
    }
233
}
234