Completed
Pull Request — master (#204)
by
unknown
02:07
created

Worker   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 228
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 71.21%

Importance

Changes 5
Bugs 2 Features 1
Metric Value
wmc 42
c 5
b 2
f 1
lcom 1
cbo 1
dl 0
loc 228
ccs 94
cts 132
cp 0.7121
rs 8.295

19 Methods

Rating   Name   Duplication   Size   Complexity  
A stdout() 0 4 1
A reset() 0 4 1
A isStarted() 0 4 2
A readAllStderr() 0 4 1
A start() 0 15 3
A execute() 0 7 1
A assign() 0 8 2
A printFeedback() 0 6 2
A checkStarted() 0 6 2
A stop() 0 5 1
B waitForFinishedJob() 0 18 5
A isFree() 0 6 1
A waitForStop() 0 8 2
A getCoverageFileName() 0 8 2
A setExitCode() 0 8 3
A isRunning() 0 6 1
A isCrashed() 0 18 4
A checkNotCrashed() 0 13 2
B updateStateFromAvailableOutput() 0 26 6

How to fix   Complexity   

Complex Class

Complex classes like Worker often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Worker, and based on these observations, apply Extract Interface, too.

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