Passed
Push — master ( 3fd808...c2290a )
by Jakub
01:52
created

Tester::printResults()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 35
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 6.0944

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 29
c 2
b 0
f 0
dl 0
loc 35
ccs 25
cts 29
cp 0.8621
rs 8.8337
cc 6
nc 16
nop 0
crap 6.0944
1
<?php
2
3
declare(strict_types=1);
4
5
namespace MyTester;
6
7
use Ayesh\PHP_Timer\Timer;
8
use Composer\InstalledVersions;
9
use MyTester\Bridges\NetteRobotLoader\TestSuitesFinder;
10
use MyTester\CodeCoverage\Collector;
11
use MyTester\CodeCoverage\PcovEngine;
12
use MyTester\CodeCoverage\PercentFormatter;
13
use MyTester\CodeCoverage\PhpdbgEngine;
14
use MyTester\CodeCoverage\XDebugEngine;
15
use Nette\CommandLine\Console;
16
use Nette\IOException;
17
use Nette\Utils\FileSystem;
18
use Nette\Utils\Finder;
19
20
/**
21
 * Automated tests runner
22
 *
23
 * @author Jakub Konečný
24
 * @property-read string[] $suites
25
 * @property bool $useColors
26
 * @method void onExecute()
27
 * @method void onFinish()
28
 */
29
final class Tester
30
{
31
    use \Nette\SmartObject;
32
33
    private const PACKAGE_NAME = "konecnyjakub/mytester";
34
    private const TIMER_NAME = "My Tester";
35
36
    /** @var string[] */
37
    private array $suites = [];
38
    /** @var callable[] */
39
    public array $onExecute = [];
40
    /** @var callable[] */
41
    public array $onFinish = [];
42
    public ITestSuiteFactory $testSuiteFactory;
43
    public ITestSuitesFinder $testSuitesFinder;
44
    private Console $console;
45
    private string $folder;
46
    private bool $useColors = false;
47
    /** @var SkippedTest[] */
48
    private array $skipped = [];
49
    private string $results = "";
50
    private Collector $codeCoverageCollector;
51
52 1
    public function __construct(
53
        string $folder,
54
        ITestSuitesFinder $testSuitesFinder = null,
55
        ITestSuiteFactory $testSuiteFactory = null
56
    ) {
57 1
        $this->onExecute[] = [$this, "setup"];
58 1
        $this->onExecute[] = [$this, "deleteOutputFiles"];
59 1
        $this->onExecute[] = [$this, "printInfo"];
60 1
        $this->onFinish[] = [$this, "printResults"];
61 1
        $this->onFinish[] = [$this, "reportCodeCoverage"];
62 1
        if ($testSuitesFinder === null) {
63 1
            $testSuitesFinder = new ChainTestSuitesFinder();
64 1
            $testSuitesFinder->registerFinder(new ComposerTestSuitesFinder());
65 1
            $testSuitesFinder->registerFinder(new TestSuitesFinder());
66
        }
67 1
        $this->testSuitesFinder = $testSuitesFinder;
68 1
        $this->testSuiteFactory = $testSuiteFactory ?? new TestSuiteFactory();
69 1
        $this->folder = $folder;
70 1
        $this->console = new Console();
71 1
        $this->codeCoverageCollector = new Collector();
72 1
        $this->codeCoverageCollector->registerEngine(new PcovEngine());
73 1
        $this->codeCoverageCollector->registerEngine(new PhpdbgEngine());
74 1
        $this->codeCoverageCollector->registerEngine(new XDebugEngine());
75 1
    }
76
77
    /**
78
     * @return string[]
79
     */
80 1
    protected function getSuites(): array
81
    {
82 1
        if (count($this->suites) === 0) {
83 1
            $this->suites = $this->testSuitesFinder->getSuites($this->folder);
84
        }
85 1
        return $this->suites;
86
    }
87
88
    protected function isUseColors(): bool
89
    {
90
        return $this->useColors;
91
    }
92
93 1
    protected function setUseColors(bool $useColors): void
94
    {
95 1
        $this->useColors = $useColors;
96 1
        $this->console->useColors($useColors);
97 1
    }
98
99
    /**
100
     * Execute all tests
101
     */
102 1
    public function execute(): void
103
    {
104 1
        $this->onExecute();
105 1
        $failed = false;
106 1
        foreach ($this->getSuites() as $suite) {
107 1
            $suite = $this->testSuiteFactory->create($suite);
108 1
            if (!$suite->run()) {
109
                $failed = true;
110
            }
111 1
            $this->saveResults($suite);
112
        }
113 1
        $this->onFinish();
114 1
        exit((int) $failed);
115
    }
116
117 1
    private function setup(): void
118
    {
119 1
        Timer::start(static::TIMER_NAME);
120
        try {
121 1
            $this->codeCoverageCollector->start();
122
        } catch (CodeCoverageException $e) {
123
            if ($e->getCode() !== CodeCoverageException::NO_ENGINE_AVAILABLE) {
124
                throw $e;
125
            }
126
        }
127 1
    }
128
129 1
    private function deleteOutputFiles(): void
130
    {
131 1
        $files = Finder::findFiles("*.errors")->in($this->folder);
132 1
        foreach ($files as $name => $file) {
133
            try {
134
                FileSystem::delete($name);
135
            } catch (IOException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
introduced by
Empty CATCH statement detected
Loading history...
136
            }
137
        }
138 1
    }
139
140
    /**
141
     * Print version of My Tester and PHP
142
     */
143 1
    private function printInfo(): void
144
    {
145 1
        $version = InstalledVersions::getPrettyVersion(static::PACKAGE_NAME);
146 1
        echo $this->console->color("silver", "My Tester $version\n");
147 1
        echo "\n";
148 1
        echo $this->console->color("silver", "PHP " . PHP_VERSION . "(" . PHP_SAPI . ")\n");
149 1
        echo "\n";
150 1
    }
151
152 1
    private function printResults(): void
153
    {
154 1
        $results = $this->results;
155 1
        $rp = TestCase::RESULT_PASSED;
156 1
        $rf = TestCase::RESULT_FAILED;
157 1
        $rs = TestCase::RESULT_SKIPPED;
158 1
        $results = str_replace($rf, $this->console->color("red", $rf), $results);
159 1
        $results = str_replace($rs, $this->console->color("yellow", $rs), $results);
160 1
        echo $results . "\n";
161 1
        $results = $this->results;
162 1
        $this->printSkipped();
163 1
        $failed = str_contains($results, TestCase::RESULT_FAILED);
164 1
        if (!$failed) {
165 1
            echo "\n";
166 1
            $resultsLine = "OK";
167
        } else {
168
            $this->printFailed();
169
            echo "\n";
170
            $resultsLine = "Failed";
171
        }
172 1
        $resultsLine .= " (" . strlen($results) . " tests";
173 1
        if (str_contains($results, $rp)) {
174 1
            $resultsLine .= ", " . substr_count($results, $rp) . " passed";
175
        }
176 1
        if ($failed) {
177
            $resultsLine .= ", " . substr_count($results, TestCase::RESULT_FAILED) . " failed";
178
        }
179 1
        if (str_contains($results, TestCase::RESULT_SKIPPED)) {
180 1
            $resultsLine .= ", " . substr_count($results, TestCase::RESULT_SKIPPED) . " skipped";
181
        }
182 1
        Timer::stop(static::TIMER_NAME);
183 1
        $time = Timer::read(static::TIMER_NAME, Timer::FORMAT_HUMAN);
184 1
        $resultsLine .= ", $time)";
185 1
        $resultsLine = $this->console->color((!$failed) ? "green" : "red", $resultsLine);
186 1
        echo $resultsLine . "\n";
187 1
    }
188
189
    /**
190
     * Print info about skipped tests
191
     */
192 1
    private function printSkipped(): void
193
    {
194 1
        foreach ($this->skipped as $skipped) {
195 1
            echo $skipped;
196
        }
197 1
    }
198
199
    /**
200
     * Print info about failed tests
201
     */
202
    private function printFailed(): void
203
    {
204
        $filenameSuffix = ".errors";
205
        $files = Finder::findFiles("*$filenameSuffix")->in($this->folder);
206
        /** @var \SplFileInfo $file */
207
        foreach ($files as $name => $file) {
208
            echo "--- " . $file->getBasename($filenameSuffix) . "\n";
209
            echo file_get_contents($name);
210
        }
211
    }
212
213 1
    private function saveResults(TestCase $testCase): void
214
    {
215 1
        $jobs = $testCase->jobs;
216 1
        foreach ($jobs as $job) {
217 1
            switch ($job->result) {
218 1
                case Job::RESULT_PASSED:
219 1
                    $result = TestCase::RESULT_PASSED;
220 1
                    break;
221 1
                case Job::RESULT_SKIPPED:
222 1
                    $result = TestCase::RESULT_SKIPPED;
223 1
                    $this->skipped[] = new SkippedTest($job->name, (is_string($job->skip) ? $job->skip : ""));
224 1
                    break;
225
                case Job::RESULT_FAILED:
226
                    $result = TestCase::RESULT_FAILED;
227
                    $output = $job->output;
228
                    if (strlen($output) > 0) {
229
                        file_put_contents("$this->folder/$job->name.errors", $output);
230
                    }
231
                    break;
232
                default:
233
                    $result = "";
234
                    break;
235
            }
236 1
            $this->results .= $result;
237
        }
238 1
    }
239
240 1
    private function reportCodeCoverage(): void
241
    {
242
        try {
243 1
            $coverageData = $this->codeCoverageCollector->finish();
244
        } catch (CodeCoverageException $e) {
245
            if ($e->getCode() === CodeCoverageException::COLLECTOR_NOT_STARTED) {
246
                return;
247
            }
248
            throw $e;
249
        }
250
251 1
        $percentFormatter = new PercentFormatter();
252 1
        echo $percentFormatter->render($coverageData);
253 1
    }
254
}
255