Passed
Push — master ( 492f4c...0725f5 )
by Jakub
02:01
created

Tester::setup()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 4.944

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 3
nop 0
dl 0
loc 8
ccs 2
cts 5
cp 0.4
crap 4.944
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace MyTester;
5
6
use Ayesh\PHP_Timer\Timer;
7
use Composer\InstalledVersions;
8
use MyTester\Bridges\NetteRobotLoader\TestSuitesFinder;
9
use MyTester\CodeCoverage\CodeCoverageException;
10
use MyTester\CodeCoverage\Collector;
11
use MyTester\CodeCoverage\PcovEngine;
12
use MyTester\CodeCoverage\PercentFormatter;
13
use MyTester\CodeCoverage\XDebugEngine;
14
use Nette\CommandLine\Console;
15
use Nette\IOException;
16
use Nette\Utils\FileSystem;
17
use Nette\Utils\Finder;
18
19
/**
20
 * Automated tests runner
21
 *
22
 * @author Jakub Konečný
23
 * @property-read string[] $suites
24
 * @property bool $useColors
25
 * @method void onExecute()
26
 * @method void onFinish()
27
 */
28
final class Tester
29
{
30
    use \Nette\SmartObject;
31
32
    private const PACKAGE_NAME = "konecnyjakub/mytester";
33
    private const TIMER_NAME = "My Tester";
34
35
    /** @var string[] */
36
    private array $suites = [];
37
    /** @var callable[] */
38
    public array $onExecute = [];
39
    /** @var callable[] */
40
    public array $onFinish = [];
41
    public ITestSuiteFactory $testSuiteFactory;
42
    public ITestSuitesFinder $testSuitesFinder;
43
    private Console $console;
44
    private readonly string $folder;
45
    private bool $useColors = false;
46
    /** @var SkippedTest[] */
47
    private array $skipped = [];
48
    /** @var TestWarning[] */
49
    private array $warnings = [];
50
    private string $results = "";
51
    private Collector $codeCoverageCollector;
52
53
    public function __construct(
54
        string $folder,
55
        ITestSuitesFinder $testSuitesFinder = null,
56
        ITestSuiteFactory $testSuiteFactory = new TestSuiteFactory()
57
    ) {
58 1
        $this->onExecute[] = [$this, "setup"];
59 1
        $this->onExecute[] = [$this, "deleteOutputFiles"];
60 1
        $this->onExecute[] = [$this, "printInfo"];
61 1
        $this->onFinish[] = [$this, "printResults"];
62 1
        $this->onFinish[] = [$this, "reportCodeCoverage"];
63 1
        if ($testSuitesFinder === null) {
64 1
            $testSuitesFinder = new ChainTestSuitesFinder();
65 1
            $testSuitesFinder->registerFinder(new ComposerTestSuitesFinder());
66 1
            $testSuitesFinder->registerFinder(new TestSuitesFinder());
67
        }
68 1
        $this->testSuitesFinder = $testSuitesFinder;
69 1
        $this->testSuiteFactory = $testSuiteFactory;
70 1
        $this->folder = $folder;
71 1
        $this->console = new Console();
72 1
        $this->codeCoverageCollector = new Collector();
73 1
        $this->codeCoverageCollector->registerEngine(new PcovEngine());
74 1
        $this->codeCoverageCollector->registerEngine(new XDebugEngine());
75
    }
76
77
    /**
78
     * @return string[]
79
     */
80
    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
    protected function setUseColors(bool $useColors): void
94
    {
95 1
        $this->useColors = $useColors;
96 1
        $this->console->useColors($useColors);
97
    }
98
99
    /**
100
     * Execute all tests
101
     */
102
    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
    /**
118
     * @throws CodeCoverageException
119
     */
120
    private function setup(): void
121
    {
122 1
        Timer::start(static::TIMER_NAME);
123
        try {
124 1
            $this->codeCoverageCollector->start();
125
        } catch (CodeCoverageException $e) {
126
            if ($e->getCode() !== CodeCoverageException::NO_ENGINE_AVAILABLE) {
127
                throw $e;
128
            }
129
        }
130
    }
131
132
    private function deleteOutputFiles(): void
133
    {
134 1
        $files = Finder::findFiles("*.errors")->in($this->folder);
135 1
        foreach ($files as $name => $file) {
136
            try {
137
                FileSystem::delete($name);
138
            } catch (IOException) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement
139
            }
140
        }
141
    }
142
143
    /**
144
     * Print version of My Tester and PHP
145
     */
146
    private function printInfo(): void
147
    {
148 1
        $version = InstalledVersions::getPrettyVersion(static::PACKAGE_NAME);
149 1
        echo $this->console->color("silver", "My Tester $version\n");
150 1
        echo "\n";
151 1
        echo $this->console->color("silver", "PHP " . PHP_VERSION . "(" . PHP_SAPI . ")\n");
152 1
        echo "\n";
153
    }
154
155
    private function printResults(): void
156
    {
157 1
        $results = $this->results;
158 1
        $rp = JobResult::PASSED->output();
159 1
        $rf = JobResult::FAILED->output();
160 1
        $rs = JobResult::SKIPPED->output();
161 1
        $rw = JobResult::WARNING->output();
162 1
        $results = str_replace($rf, $this->console->color("red", $rf), $results);
163 1
        $results = str_replace($rs, $this->console->color("yellow", $rs), $results);
164 1
        $results = str_replace($rw, $this->console->color("yellow", $rw), $results);
165 1
        echo $results . "\n";
166 1
        $results = $this->results;
167 1
        $this->printWarnings();
168 1
        $this->printSkipped();
169 1
        $failed = str_contains($results, $rf);
170 1
        if (!$failed) {
171 1
            echo "\n";
172 1
            $resultsLine = "OK";
173
        } else {
174
            $this->printFailed();
175
            echo "\n";
176
            $resultsLine = "Failed";
177
        }
178 1
        $resultsLine .= " (" . strlen($results) . " tests";
179 1
        if (str_contains($results, $rp)) {
180 1
            $resultsLine .= ", " . substr_count($results, $rp) . " passed";
181
        }
182 1
        if (str_contains($results, $rw)) {
183 1
            $resultsLine .= ", " . substr_count($results, $rw) . " passed with warnings";
184
        }
185 1
        if ($failed) {
186
            $resultsLine .= ", " . substr_count($results, $rf) . " failed";
187
        }
188 1
        if (str_contains($results, $rs)) {
189 1
            $resultsLine .= ", " . substr_count($results, $rs) . " skipped";
190
        }
191 1
        Timer::stop(static::TIMER_NAME);
192 1
        $time = Timer::read(static::TIMER_NAME, Timer::FORMAT_HUMAN);
193 1
        $resultsLine .= ", $time)";
194 1
        $resultsLine = $this->console->color((!$failed) ? "green" : "red", $resultsLine);
195 1
        echo $resultsLine . "\n";
196
    }
197
198
    /**
199
     * Print info about tests with warnings
200
     */
201
    private function printWarnings(): void
202
    {
203 1
        foreach ($this->warnings as $testWarning) {
204 1
            echo $testWarning;
205
        }
206
    }
207
208
    /**
209
     * Print info about skipped tests
210
     */
211
    private function printSkipped(): void
212
    {
213 1
        foreach ($this->skipped as $skipped) {
214 1
            echo $skipped;
215
        }
216
    }
217
218
    /**
219
     * Print info about failed tests
220
     */
221
    private function printFailed(): void
222
    {
223
        $filenameSuffix = ".errors";
224
        $files = Finder::findFiles("*$filenameSuffix")->in($this->folder);
225
        /** @var \SplFileInfo $file */
226
        foreach ($files as $name => $file) {
227
            echo "--- " . $file->getBasename($filenameSuffix) . "\n";
228
            echo file_get_contents($name);
229
        }
230
    }
231
232
    private function saveResults(TestCase $testCase): void
233
    {
234 1
        $jobs = $testCase->jobs;
235 1
        foreach ($jobs as $job) {
236 1
            switch ($job->result) {
237 1
                case JobResult::SKIPPED:
238 1
                    $this->skipped[] = new SkippedTest($job->name, (is_string($job->skip) ? $job->skip : ""));
239 1
                    break;
240 1
                case JobResult::FAILED:
241
                    $output = $job->output;
242
                    if (strlen($output) > 0) {
243
                        file_put_contents("$this->folder/$job->name.errors", $output);
244
                    }
245
                    break;
246 1
                case JobResult::WARNING:
247 1
                    $output = $job->output;
248 1
                    $output = str_replace("Warning: ", "", $output);
249 1
                    $this->warnings[] = new TestWarning($job->name, $output);
250 1
                    break;
251
            }
252 1
            $this->results .= $job->result->output();
253
        }
254
    }
255
256
    /**
257
     * @throws CodeCoverageException
258
     */
259
    private function reportCodeCoverage(): void
260
    {
261
        try {
262 1
            $engineName = $this->codeCoverageCollector->getEngineName();
263 1
            echo "\nCollecting code coverage via $engineName\n";
264 1
            $coverageData = $this->codeCoverageCollector->finish();
265
        } catch (CodeCoverageException $e) {
266
            if (
267
                in_array(
268
                    $e->getCode(),
269
                    [CodeCoverageException::NO_ENGINE_AVAILABLE, CodeCoverageException::COLLECTOR_NOT_STARTED, ]
270
                )
271
            ) {
272
                return;
273
            }
274
            throw $e;
275
        }
276
277 1
        $percentFormatter = new PercentFormatter();
278 1
        echo $percentFormatter->render($coverageData);
279
    }
280
}
281