Passed
Push — master ( 85f9d4...f6c128 )
by Jakub
12:52
created

Tester::printResults()   B

Complexity

Conditions 7
Paths 32

Size

Total Lines 41
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 7.0796

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 34
c 3
b 0
f 0
dl 0
loc 41
ccs 30
cts 34
cp 0.8824
rs 8.4426
cc 7
nc 32
nop 0
crap 7.0796
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 readonly string $folder;
46
    private bool $useColors = false;
47
    /** @var SkippedTest[] */
48
    private array $skipped = [];
49
    /** @var TestWarning[] */
50
    private array $warnings = [];
51
    private string $results = "";
52
    private Collector $codeCoverageCollector;
53
54 1
    public function __construct(
55
        string $folder,
56
        ITestSuitesFinder $testSuitesFinder = null,
57
        ITestSuiteFactory $testSuiteFactory = new TestSuiteFactory()
58
    ) {
59 1
        $this->onExecute[] = [$this, "setup"];
60 1
        $this->onExecute[] = [$this, "deleteOutputFiles"];
61 1
        $this->onExecute[] = [$this, "printInfo"];
62 1
        $this->onFinish[] = [$this, "printResults"];
63 1
        $this->onFinish[] = [$this, "reportCodeCoverage"];
64 1
        if ($testSuitesFinder === null) {
65 1
            $testSuitesFinder = new ChainTestSuitesFinder();
66 1
            $testSuitesFinder->registerFinder(new ComposerTestSuitesFinder());
67 1
            $testSuitesFinder->registerFinder(new TestSuitesFinder());
68
        }
69 1
        $this->testSuitesFinder = $testSuitesFinder;
70 1
        $this->testSuiteFactory = $testSuiteFactory;
71 1
        $this->folder = $folder;
72 1
        $this->console = new Console();
73 1
        $this->codeCoverageCollector = new Collector();
74 1
        $this->codeCoverageCollector->registerEngine(new PcovEngine());
75 1
        $this->codeCoverageCollector->registerEngine(new PhpdbgEngine());
76 1
        $this->codeCoverageCollector->registerEngine(new XDebugEngine());
77
    }
78
79
    /**
80
     * @return string[]
81
     */
82 1
    protected function getSuites(): array
83
    {
84 1
        if (count($this->suites) === 0) {
85 1
            $this->suites = $this->testSuitesFinder->getSuites($this->folder);
86
        }
87 1
        return $this->suites;
88
    }
89
90
    protected function isUseColors(): bool
91
    {
92
        return $this->useColors;
93
    }
94
95 1
    protected function setUseColors(bool $useColors): void
96
    {
97 1
        $this->useColors = $useColors;
98 1
        $this->console->useColors($useColors);
99
    }
100
101
    /**
102
     * Execute all tests
103
     */
104 1
    public function execute(): void
105
    {
106 1
        $this->onExecute();
107 1
        $failed = false;
108 1
        foreach ($this->getSuites() as $suite) {
109 1
            $suite = $this->testSuiteFactory->create($suite);
110 1
            if (!$suite->run()) {
111
                $failed = true;
112
            }
113 1
            $this->saveResults($suite);
114
        }
115 1
        $this->onFinish();
116 1
        exit((int) $failed);
117
    }
118
119 1
    private function setup(): void
120
    {
121 1
        Timer::start(static::TIMER_NAME);
122
        try {
123 1
            $this->codeCoverageCollector->start();
124
        } catch (CodeCoverageException $e) {
125
            if ($e->getCode() !== CodeCoverageException::NO_ENGINE_AVAILABLE) {
126
                throw $e;
127
            }
128
        }
129
    }
130
131 1
    private function deleteOutputFiles(): void
132
    {
133 1
        $files = Finder::findFiles("*.errors")->in($this->folder);
134 1
        foreach ($files as $name => $file) {
135
            try {
136
                FileSystem::delete($name);
137
            } catch (IOException $e) {
138
            }
139
        }
140
    }
141
142
    /**
143
     * Print version of My Tester and PHP
144
     */
145 1
    private function printInfo(): void
146
    {
147 1
        $version = InstalledVersions::getPrettyVersion(static::PACKAGE_NAME);
148 1
        echo $this->console->color("silver", "My Tester $version\n");
149 1
        echo "\n";
150 1
        echo $this->console->color("silver", "PHP " . PHP_VERSION . "(" . PHP_SAPI . ")\n");
151 1
        echo "\n";
152
    }
153
154 1
    private function printResults(): void
155
    {
156 1
        $results = $this->results;
157 1
        $rp = JobResult::PASSED->output();
158 1
        $rf = JobResult::FAILED->output();
159 1
        $rs = JobResult::SKIPPED->output();
160 1
        $rw = JobResult::WARNING->output();
161 1
        $results = str_replace($rf, $this->console->color("red", $rf), $results);
162 1
        $results = str_replace($rs, $this->console->color("yellow", $rs), $results);
163 1
        $results = str_replace($rw, $this->console->color("yellow", $rw), $results);
164 1
        echo $results . "\n";
165 1
        $results = $this->results;
166 1
        $this->printWarnings();
167 1
        $this->printSkipped();
168 1
        $failed = str_contains($results, $rf);
169 1
        if (!$failed) {
170 1
            echo "\n";
171 1
            $resultsLine = "OK";
172
        } else {
173
            $this->printFailed();
174
            echo "\n";
175
            $resultsLine = "Failed";
176
        }
177 1
        $resultsLine .= " (" . strlen($results) . " tests";
178 1
        if (str_contains($results, $rp)) {
179 1
            $resultsLine .= ", " . substr_count($results, $rp) . " passed";
180
        }
181 1
        if (str_contains($results, $rw)) {
182 1
            $resultsLine .= ", " . substr_count($results, $rw) . " passed with warnings";
183
        }
184 1
        if ($failed) {
185
            $resultsLine .= ", " . substr_count($results, $rf) . " failed";
186
        }
187 1
        if (str_contains($results, $rs)) {
188 1
            $resultsLine .= ", " . substr_count($results, $rs) . " skipped";
189
        }
190 1
        Timer::stop(static::TIMER_NAME);
191 1
        $time = Timer::read(static::TIMER_NAME, Timer::FORMAT_HUMAN);
192 1
        $resultsLine .= ", $time)";
193 1
        $resultsLine = $this->console->color((!$failed) ? "green" : "red", $resultsLine);
194 1
        echo $resultsLine . "\n";
195
    }
196
197
    /**
198
     * Print info about tests with warnings
199
     */
200 1
    private function printWarnings(): void
201
    {
202 1
        foreach ($this->warnings as $testWarning) {
203 1
            echo $testWarning;
204
        }
205
    }
206
207
    /**
208
     * Print info about skipped tests
209
     */
210 1
    private function printSkipped(): void
211
    {
212 1
        foreach ($this->skipped as $skipped) {
213 1
            echo $skipped;
214
        }
215
    }
216
217
    /**
218
     * Print info about failed tests
219
     */
220
    private function printFailed(): void
221
    {
222
        $filenameSuffix = ".errors";
223
        $files = Finder::findFiles("*$filenameSuffix")->in($this->folder);
224
        /** @var \SplFileInfo $file */
225
        foreach ($files as $name => $file) {
226
            echo "--- " . $file->getBasename($filenameSuffix) . "\n";
227
            echo file_get_contents($name);
228
        }
229
    }
230
231 1
    private function saveResults(TestCase $testCase): void
232
    {
233 1
        $jobs = $testCase->jobs;
234 1
        foreach ($jobs as $job) {
235 1
            switch ($job->result) {
236 1
                case JobResult::SKIPPED:
237 1
                    $this->skipped[] = new SkippedTest($job->name, (is_string($job->skip) ? $job->skip : ""));
238 1
                    break;
239 1
                case JobResult::FAILED:
240
                    $output = $job->output;
241
                    if (strlen($output) > 0) {
242
                        file_put_contents("$this->folder/$job->name.errors", $output);
243
                    }
244
                    break;
245 1
                case JobResult::WARNING:
246 1
                    $output = $job->output;
247 1
                    $output = str_replace("Warning: ", "", $output);
248 1
                    $this->warnings[] = new TestWarning($job->name, $output);
249 1
                    break;
250
            }
251 1
            $this->results .= $job->result->output();
252
        }
253
    }
254
255 1
    private function reportCodeCoverage(): void
256
    {
257
        try {
258 1
            $coverageData = $this->codeCoverageCollector->finish();
259
        } catch (CodeCoverageException $e) {
260
            if ($e->getCode() === CodeCoverageException::COLLECTOR_NOT_STARTED) {
261
                return;
262
            }
263
            throw $e;
264
        }
265
266 1
        $percentFormatter = new PercentFormatter();
267 1
        echo $percentFormatter->render($coverageData);
268
    }
269
}
270