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

ResultPrinter   C

Complexity

Total Complexity 58

Size/Duplication

Total Lines 532
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 81.91%

Importance

Changes 9
Bugs 5 Features 2
Metric Value
wmc 58
c 9
b 5
f 2
lcom 1
cbo 5
dl 0
loc 532
ccs 163
cts 199
cp 0.8191
rs 6.3005

28 Methods

Rating   Name   Duplication   Size   Complexity  
A getHeader() 0 4 1
A addWarnings() 0 4 1
A getWarnings() 0 4 1
A isSuccessful() 0 4 2
A getTotalCases() 0 4 1
A __construct() 0 5 1
A addTest() 0 8 1
B start() 0 20 5
A println() 0 5 1
A flush() 0 5 1
A printResults() 0 8 1
B printFeedback() 0 28 3
A getFooter() 0 6 2
A getFailures() 0 6 1
A getErrors() 0 6 1
A processReaderFeedback() 0 16 3
A printTestWarnings() 0 10 3
A isSkippedIncompleTestCanBeTracked() 0 5 3
A processTestOverhead() 0 13 3
A printSkippedAndIncomplete() 0 9 3
A printFeedbackItem() 0 10 2
B getDefects() 0 20 5
A getProgress() 0 9 2
B getSuccessFooter() 0 27 4
A green() 0 9 2
A clearLogs() 0 6 2
A getFailedFooter() 0 14 1
A red() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like ResultPrinter 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 ResultPrinter, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace ParaTest\Runners\PHPUnit;
3
4
use ParaTest\Logging\LogInterpreter;
5
use ParaTest\Logging\JUnit\Reader;
6
7
/**
8
 * Class ResultPrinter
9
 *
10
 * Used for outputing ParaTest results
11
 *
12
 * @package ParaTest\Runners\PHPUnit
13
 */
14
class ResultPrinter
15
{
16
    /**
17
     * A collection of ExecutableTest objects
18
     *
19
     * @var array
20
     */
21
    protected $suites = array();
22
23
    /**
24
     * @var \ParaTest\Logging\LogInterpreter
25
     */
26
    protected $results;
27
28
    /**
29
     * The number of tests results currently printed.
30
     * Used to determine when to tally current results
31
     * and start a new row
32
     *
33
     * @var int
34
     */
35
    protected $numTestsWidth;
36
37
    /**
38
     * Used for formatting results to a given width
39
     *
40
     * @var int
41
     */
42
    protected $maxColumn;
43
44
    /**
45
     * The total number of cases to be run
46
     *
47
     * @var int
48
     */
49
    protected $totalCases = 0;
50
51
    /**
52
     * The current column being printed to
53
     *
54
     * @var int
55
     */
56
    protected $column = 0;
57
58
    /**
59
     * @var \PHP_Timer
60
     */
61
    protected $timer;
62
63
    /**
64
     * The total number of cases printed so far
65
     *
66
     * @var int
67
     */
68
    protected $casesProcessed = 0;
69
70
    /**
71
     * Whether to display a red or green bar
72
     *
73
     * @var bool
74
     */
75
    protected $colors;
76
77
    /**
78
     * Warnings generated by the cases
79
     *
80
     * @var array
81
     */
82
    protected $warnings = array();
83
84
    /**
85
     * Number of columns
86
     *
87
     * @var integer
88
     */
89
    protected $numberOfColumns = 80;
90
91
    /**
92
     * Number of skipped or incomplete tests
93
     *
94
     * @var integer
95
     */
96
    protected $totalSkippedOrIncomplete = 0;
97
98
    /**
99
     * Do we need to try to process skipped/incompleted tests.
100
     *
101
     * @var boolean
102
     */
103
    protected $processSkipped = false;
104
105 28
    public function __construct(LogInterpreter $results)
106
    {
107 28
        $this->results = $results;
108 28
        $this->timer = new \PHP_Timer();
109 28
    }
110
111
    /**
112
     * Adds an ExecutableTest to the tracked results
113
     *
114
     * @param ExecutableTest $suite
115
     * @return $this
116
     */
117 16
    public function addTest(ExecutableTest $suite)
118
    {
119 16
        $this->suites[] = $suite;
120 16
        $increment = $suite->getTestCount();
121 16
        $this->totalCases = $this->totalCases + $increment;
122
123 16
        return $this;
124
    }
125
126
    /**
127
     * Initializes printing constraints, prints header
128
     * information and starts the test timer
129
     *
130
     * @param Options $options
131
     */
132 9
    public function start(Options $options)
133
    {
134 9
        $this->numTestsWidth = strlen((string) $this->totalCases);
135 9
        $this->maxColumn = $this->numberOfColumns
136 9
                         + (DIRECTORY_SEPARATOR == "\\" ? -1 : 0) // fix windows blank lines
137 9
                         - strlen($this->getProgress());
138 9
        printf(
139 9
            "\nRunning phpunit in %d process%s with %s%s\n\n",
140 9
            $options->processes,
141 9
            $options->processes > 1 ? 'es' : '',
142 9
            $options->phpunit,
143 9
            $options->functional ? '. Functional mode is ON.' : ''
144 9
        );
145 9
        if (isset($options->filtered['configuration'])) {
146 9
            printf("Configuration read from %s\n\n", $options->filtered['configuration']->getPath());
147 9
        }
148 9
        $this->timer->start();
149 9
        $this->colors = $options->colors;
0 ignored issues
show
Documentation introduced by
The property $colors is declared protected in ParaTest\Runners\PHPUnit\Options. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
150 9
        $this->processSkipped = $this->isSkippedIncompleTestCanBeTracked($options);
151 9
    }
152
153
    /**
154
     * @param string $string
155
     */
156 2
    public function println($string = "")
157
    {
158 2
        $this->column = 0;
159 2
        print("$string\n");
160 2
    }
161
162
    /**
163
     * Prints all results and removes any log files
164
     * used for aggregating results
165
     */
166
    public function flush()
167
    {
168
        $this->printResults();
169
        $this->clearLogs();
170
    }
171
172
    /**
173
     * Print final results
174
     */
175 2
    public function printResults()
176
    {
177 2
        print $this->getHeader();
178 2
        print $this->getErrors();
179 2
        print $this->getFailures();
180 2
        print $this->getWarnings();
181 2
        print $this->getFooter();
182 2
    }
183
184
    /**
185
     * Prints the individual "quick" feedback for run
186
     * tests, that is the ".EF" items
187
     *
188
     * @param ExecutableTest $test
189
     */
190 12
    public function printFeedback(ExecutableTest $test)
191
    {
192
        try {
193 12
            $reader = new Reader($test->getTempFile());
194 12
        } catch (\InvalidArgumentException $e) {
195
            throw new \RuntimeException(sprintf(
196
                "%s\n" .
197
                "The process: %s\n" .
198
                "This means a PHPUnit process was unable to run \"%s\"\n" ,
199
                $e->getmessage(),
200
                $test->getLastCommand(),
201
                $test->getPath()
202
            ));
203
        }
204 12
        if (!$reader->hasResults()) {
205 1
            throw new \RuntimeException(sprintf(
206
                "The process: %s\nLog file \"%s\" is empty.\n" .
207 1
                "This means a PHPUnit process was unable to run \"%s\"\n" .
208 1
                "Maybe there is more than one class in this file.",
209 1
                $test->getLastCommand(),
210 1
                $test->getTempFile(),
211 1
                $test->getPath()
212 1
            ));
213
        }
214 11
        $this->results->addReader($reader);
215 11
        $this->processReaderFeedback($reader, $test->getTestCount());
216 11
        $this->printTestWarnings($test);
217 11
    }
218
219
    /**
220
     * Returns the header containing resource usage
221
     *
222
     * @return string
223
     */
224 3
    public function getHeader()
225
    {
226 3
        return "\n\n" . $this->timer->resourceUsage() . "\n\n";
227
    }
228
229
    /**
230
     * Add an array of warning strings. These cause the test run to be shown
231
     * as failed
232
     */
233
    public function addWarnings(array $warnings)
234
    {
235
        $this->warnings = array_merge($this->warnings, $warnings);
236
    }
237
238
    /**
239
     * Returns warning messages as a string
240
     */
241 2
    public function getWarnings()
242
    {
243 2
        return $this->getDefects($this->warnings, 'warning');
244
    }
245
246
    /**
247
     * Whether the test run is successful and has no warnings
248
     *
249
     * @return bool
250
     */
251 4
    public function isSuccessful()
252
    {
253 4
        return $this->results->isSuccessful() && count($this->warnings) == 0;
254
    }
255
256
    /**
257
     * Return the footer information reporting success
258
     * or failure
259
     *
260
     * @return string
261
     */
262 4
    public function getFooter()
263
    {
264 4
        return $this->isSuccessful()
265 4
                    ? $this->getSuccessFooter()
266 4
                    : $this->getFailedFooter();
267
    }
268
269
    /**
270
     * Returns the failure messages
271
     *
272
     * @return string
273
     */
274 3
    public function getFailures()
275
    {
276 3
        $failures = $this->results->getFailures();
0 ignored issues
show
Documentation Bug introduced by
The method getFailures does not exist on object<ParaTest\Logging\LogInterpreter>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
277
278 3
        return $this->getDefects($failures, 'failure');
279
    }
280
281
    /**
282
     * Returns error messages
283
     *
284
     * @return string
285
     */
286 4
    public function getErrors()
287
    {
288 4
        $errors = $this->results->getErrors();
0 ignored issues
show
Documentation Bug introduced by
The method getErrors does not exist on object<ParaTest\Logging\LogInterpreter>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
289
290 4
        return $this->getDefects($errors, 'error');
291
    }
292
293
    /**
294
     * Returns the total cases being printed
295
     *
296
     * @return int
297
     */
298 2
    public function getTotalCases()
299
    {
300 2
        return $this->totalCases;
301
    }
302
303
    /**
304
     * Process reader feedback and print it.
305
     *
306
     * @param  Reader $reader
307
     * @param  int    $expectedTestCount
308
     */
309 11
    protected function processReaderFeedback($reader, $expectedTestCount)
310
    {
311 11
        $feedbackItems = $reader->getFeedback();
312
313 11
        $actualTestCount = count($feedbackItems);
314
315 11
        $this->processTestOverhead($actualTestCount, $expectedTestCount);
316
317 11
        foreach ($feedbackItems as $item) {
318 11
            $this->printFeedbackItem($item);
319 11
        }
320
321 11
        if ($this->processSkipped) {
322 4
            $this->printSkippedAndIncomplete($actualTestCount, $expectedTestCount);
323 4
        }
324 11
    }
325
326
    /**
327
     * Prints test warnings.
328
     *
329
     * @param  ExecutableTest $test
330
     */
331 11
    protected function printTestWarnings($test)
332
    {
333 11
        $warnings = $test->getWarnings();
334 11
        if ($warnings) {
335
            $this->addWarnings($warnings);
336
            foreach ($warnings as $warning) {
337
                $this->printFeedbackItem('W');
338
            }
339
        }
340 11
    }
341
342
    /**
343
     * Is skipped/incomplete amount can be properly processed.
344
     *
345
     * @todo Skipped/Incomplete test tracking available only in functional mode for now
346
     *       or in regular mode but without group/exclude-group filters.
347
     *
348
     * @return boolean
349
     */
350 9
    protected function isSkippedIncompleTestCanBeTracked($options)
351
    {
352 9
        return $options->functional
353 9
            || (empty($options->groups) && empty($options->excludeGroups));
354
    }
355
356
    /**
357
     * Process test overhead.
358
     *
359
     * In some situations phpunit can return more tests then we expect and in that case
360
     * this method correct total amount of tests so paratest progress will be auto corrected.
361
     *
362
     * @todo May be we need to throw Exception here instead of silent correction.
363
     *
364
     * @param  int $actualTestCount
365
     * @param  int $expectedTestCount
366
     */
367 11
    protected function processTestOverhead($actualTestCount, $expectedTestCount)
368
    {
369 11
        $overhead = $actualTestCount - $expectedTestCount;
370 11
        if ($this->processSkipped) {
371 4
            if ($overhead > 0) {
372 1
                $this->totalCases += $overhead;
373 1
            } else {
374 3
                $this->totalSkippedOrIncomplete += -$overhead;
375
            }
376 4
        } else {
377 7
            $this->totalCases += $overhead;
378
        }
379 11
    }
380
381
    /**
382
     * Prints S for skipped and incomplete tests.
383
     *
384
     * If for some reason process return less tests than expected then we threat all remaining
385
     * as skipped or incomplete and print them as skipped (S letter)
386
     *
387
     * @param  int $actualTestCount
388
     * @param  int $expectedTestCount
389
     */
390 4
    protected function printSkippedAndIncomplete($actualTestCount, $expectedTestCount)
391
    {
392 4
        $overhead = $expectedTestCount - $actualTestCount;
393 4
        if ($overhead > 0) {
394
            for ($i = 0; $i < $overhead; $i++) {
395
                $this->printFeedbackItem("S");
396
            }
397
        }
398 4
    }
399
400
    /**
401
     * Prints a single "quick" feedback item and increments
402
     * the total number of processed cases and the column
403
     * position
404
     *
405
     * @param $item
406
     */
407 11
    protected function printFeedbackItem($item)
408
    {
409 11
        print $item;
410 11
        $this->column++;
411 11
        $this->casesProcessed++;
412 11
        if ($this->column == $this->maxColumn) {
413 2
            print $this->getProgress();
414 2
            $this->println();
415 2
        }
416 11
    }
417
418
    /**
419
     * Method that returns a formatted string
420
     * for a collection of errors or failures
421
     *
422
     * @param array $defects
423
     * @param $type
424
     * @return string
425
     */
426 5
    protected function getDefects(array $defects, $type)
427
    {
428 5
        $count = sizeof($defects);
429 5
        if ($count == 0) {
430 2
            return '';
431
        }
432 5
        $output = sprintf(
433 5
            "There %s %d %s%s:\n",
434 5
            ($count == 1) ? 'was' : 'were',
435 5
            $count,
436 5
            $type,
437 5
            ($count == 1) ? '' : 's'
438 5
        );
439
440 5
        for ($i = 1, $max = sizeof($defects); $i <= $max; $i++) {
441 5
            $output .= sprintf("\n%d) %s\n", $i, $defects[$i - 1]);
442 5
        }
443
444 5
        return $output;
445
    }
446
447
    /**
448
     * Prints progress for large test collections
449
     */
450 9
    protected function getProgress()
451
    {
452 9
        return sprintf(
453 9
            ' %' . $this->numTestsWidth . 'd / %' . $this->numTestsWidth . 'd (%3s%%)',
454 9
            $this->casesProcessed,
455 9
            $this->totalCases,
456 9
            floor(($this->totalCases ? $this->casesProcessed / $this->totalCases : 0) * 100)
457 9
        );
458
    }
459
460
    /**
461
     * Get the footer for a test collection that had tests with
462
     * failures or errors
463
     *
464
     * @return string
465
     */
466 3
    private function getFailedFooter()
467
    {
468 3
        $formatString = "FAILURES!\nTests: %d, Assertions: %d, Failures: %d, Errors: %d.\n";
469
470 3
        return "\n" . $this->red(
471 3
            sprintf(
472 3
                $formatString,
473 3
                $this->results->getTotalTests(),
0 ignored issues
show
Documentation Bug introduced by
The method getTotalTests does not exist on object<ParaTest\Logging\LogInterpreter>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
474 3
                $this->results->getTotalAssertions(),
0 ignored issues
show
Documentation Bug introduced by
The method getTotalAssertions does not exist on object<ParaTest\Logging\LogInterpreter>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
475 3
                $this->results->getTotalFailures(),
0 ignored issues
show
Documentation Bug introduced by
The method getTotalFailures does not exist on object<ParaTest\Logging\LogInterpreter>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
476 3
                $this->results->getTotalErrors()
0 ignored issues
show
Documentation Bug introduced by
The method getTotalErrors does not exist on object<ParaTest\Logging\LogInterpreter>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
477 3
            )
478 3
        );
479
    }
480
481
    /**
482
     * Get the footer for a test collection containing all successful
483
     * tests
484
     *
485
     * @return string
486
     */
487 1
    private function getSuccessFooter()
488
    {
489 1
        $tests = $this->totalCases;
490 1
        $asserts = $this->results->getTotalAssertions();
0 ignored issues
show
Documentation Bug introduced by
The method getTotalAssertions does not exist on object<ParaTest\Logging\LogInterpreter>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
491
492 1
        if ($this->totalSkippedOrIncomplete > 0) {
493
            // phpunit 4.5 produce NOT plural version for test(s) and assertion(s) in that case
494
            // also it shows result in standard color scheme
495
            return sprintf(
496
                "OK, but incomplete, skipped, or risky tests!\n"
497
                . "Tests: %d, Assertions: %d, Incomplete: %d.\n",
498
                $tests,
499
                $asserts,
500
                $this->totalSkippedOrIncomplete
501
            );
502
        } else {
503
            // phpunit 4.5 produce plural version for test(s) and assertion(s) in that case
504
            // also it shows result as black text on green background
505 1
            return $this->green(sprintf(
506 1
                "OK (%d test%s, %d assertion%s)\n",
507 1
                $tests,
508 1
                ($tests == 1) ? '' : 's',
509 1
                $asserts,
510 1
                ($asserts == 1) ? '' : 's'
511 1
            ));
512
        }
513
    }
514
515 1
    private function green($text)
516
    {
517 1
        if ($this->colors) {
518
            return "\x1b[30;42m\x1b[2K"
519
                . $text
520
                . "\x1b[0m\x1b[2K";
521
        }
522 1
        return $text;
523
    }
524
525 3
    private function red($text)
526
    {
527 3
        if ($this->colors) {
528
            return "\x1b[37;41m\x1b[2K"
529
                . $text
530
                . "\x1b[0m\x1b[2K";
531
        }
532 3
        return $text;
533
    }
534
535
    /**
536
     * Deletes all the temporary log files for ExecutableTest objects
537
     * being printed
538
     */
539
    private function clearLogs()
540
    {
541
        foreach ($this->suites as $suite) {
542
            $suite->deleteFile();
543
        }
544
    }
545
}
546