Passed
Push — master ( aa061c...2e1140 )
by Jakub
02:07
created

Tester   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 247
Duplicated Lines 0 %

Test Coverage

Coverage 79.69%

Importance

Changes 46
Bugs 6 Features 10
Metric Value
eloc 134
c 46
b 6
f 10
dl 0
loc 247
rs 9.28
ccs 102
cts 128
cp 0.7969
wmc 39

14 Methods

Rating   Name   Duplication   Size   Complexity  
A isUseColors() 0 3 1
A setUseColors() 0 4 1
A execute() 0 13 3
A getSuites() 0 6 2
A __construct() 0 23 2
A setup() 0 8 3
A reportCodeCoverage() 0 15 3
A printWarnings() 0 4 2
A printFailed() 0 8 2
B saveResults() 0 21 7
B printResults() 0 41 7
A deleteOutputFiles() 0 7 3
A printSkipped() 0 4 2
A printInfo() 0 7 1
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
    /**
120
     * @throws CodeCoverageException
121
     */
122 1
    private function setup(): void
123
    {
124 1
        Timer::start(static::TIMER_NAME);
125
        try {
126 1
            $this->codeCoverageCollector->start();
127
        } catch (CodeCoverageException $e) {
128
            if ($e->getCode() !== CodeCoverageException::NO_ENGINE_AVAILABLE) {
129
                throw $e;
130
            }
131
        }
132
    }
133
134 1
    private function deleteOutputFiles(): void
135
    {
136 1
        $files = Finder::findFiles("*.errors")->in($this->folder);
137 1
        foreach ($files as $name => $file) {
138
            try {
139
                FileSystem::delete($name);
140
            } catch (IOException) {
141
            }
142
        }
143
    }
144
145
    /**
146
     * Print version of My Tester and PHP
147
     */
148 1
    private function printInfo(): void
149
    {
150 1
        $version = InstalledVersions::getPrettyVersion(static::PACKAGE_NAME);
151 1
        echo $this->console->color("silver", "My Tester $version\n");
152 1
        echo "\n";
153 1
        echo $this->console->color("silver", "PHP " . PHP_VERSION . "(" . PHP_SAPI . ")\n");
154 1
        echo "\n";
155
    }
156
157 1
    private function printResults(): void
158
    {
159 1
        $results = $this->results;
160 1
        $rp = JobResult::PASSED->output();
161 1
        $rf = JobResult::FAILED->output();
162 1
        $rs = JobResult::SKIPPED->output();
163 1
        $rw = JobResult::WARNING->output();
164 1
        $results = str_replace($rf, $this->console->color("red", $rf), $results);
165 1
        $results = str_replace($rs, $this->console->color("yellow", $rs), $results);
166 1
        $results = str_replace($rw, $this->console->color("yellow", $rw), $results);
167 1
        echo $results . "\n";
168 1
        $results = $this->results;
169 1
        $this->printWarnings();
170 1
        $this->printSkipped();
171 1
        $failed = str_contains($results, $rf);
172 1
        if (!$failed) {
173 1
            echo "\n";
174 1
            $resultsLine = "OK";
175
        } else {
176
            $this->printFailed();
177
            echo "\n";
178
            $resultsLine = "Failed";
179
        }
180 1
        $resultsLine .= " (" . strlen($results) . " tests";
181 1
        if (str_contains($results, $rp)) {
182 1
            $resultsLine .= ", " . substr_count($results, $rp) . " passed";
183
        }
184 1
        if (str_contains($results, $rw)) {
185 1
            $resultsLine .= ", " . substr_count($results, $rw) . " passed with warnings";
186
        }
187 1
        if ($failed) {
188
            $resultsLine .= ", " . substr_count($results, $rf) . " failed";
189
        }
190 1
        if (str_contains($results, $rs)) {
191 1
            $resultsLine .= ", " . substr_count($results, $rs) . " skipped";
192
        }
193 1
        Timer::stop(static::TIMER_NAME);
194 1
        $time = Timer::read(static::TIMER_NAME, Timer::FORMAT_HUMAN);
195 1
        $resultsLine .= ", $time)";
196 1
        $resultsLine = $this->console->color((!$failed) ? "green" : "red", $resultsLine);
197 1
        echo $resultsLine . "\n";
198
    }
199
200
    /**
201
     * Print info about tests with warnings
202
     */
203 1
    private function printWarnings(): void
204
    {
205 1
        foreach ($this->warnings as $testWarning) {
206 1
            echo $testWarning;
207
        }
208
    }
209
210
    /**
211
     * Print info about skipped tests
212
     */
213 1
    private function printSkipped(): void
214
    {
215 1
        foreach ($this->skipped as $skipped) {
216 1
            echo $skipped;
217
        }
218
    }
219
220
    /**
221
     * Print info about failed tests
222
     */
223
    private function printFailed(): void
224
    {
225
        $filenameSuffix = ".errors";
226
        $files = Finder::findFiles("*$filenameSuffix")->in($this->folder);
227
        /** @var \SplFileInfo $file */
228
        foreach ($files as $name => $file) {
229
            echo "--- " . $file->getBasename($filenameSuffix) . "\n";
230
            echo file_get_contents($name);
231
        }
232
    }
233
234 1
    private function saveResults(TestCase $testCase): void
235
    {
236 1
        $jobs = $testCase->jobs;
237 1
        foreach ($jobs as $job) {
238 1
            switch ($job->result) {
239 1
                case JobResult::SKIPPED:
240 1
                    $this->skipped[] = new SkippedTest($job->name, (is_string($job->skip) ? $job->skip : ""));
241 1
                    break;
242 1
                case JobResult::FAILED:
243
                    $output = $job->output;
244
                    if (strlen($output) > 0) {
245
                        file_put_contents("$this->folder/$job->name.errors", $output);
246
                    }
247
                    break;
248 1
                case JobResult::WARNING:
249 1
                    $output = $job->output;
250 1
                    $output = str_replace("Warning: ", "", $output);
251 1
                    $this->warnings[] = new TestWarning($job->name, $output);
252 1
                    break;
253
            }
254 1
            $this->results .= $job->result->output();
255
        }
256
    }
257
258
    /**
259
     * @throws CodeCoverageException
260
     */
261 1
    private function reportCodeCoverage(): void
262
    {
263
        try {
264 1
            $engineName = $this->codeCoverageCollector->getEngineName();
265 1
            echo "\nCollecting code coverage via $engineName\n";
266 1
            $coverageData = $this->codeCoverageCollector->finish();
267
        } catch (CodeCoverageException $e) {
268
            if (in_array($e->getCode(), [CodeCoverageException::NO_ENGINE_AVAILABLE, CodeCoverageException::COLLECTOR_NOT_STARTED, ])) {
0 ignored issues
show
introduced by
Line exceeds 120 characters; contains 136 characters
Loading history...
269
                return;
270
            }
271
            throw $e;
272
        }
273
274 1
        $percentFormatter = new PercentFormatter();
275 1
        echo $percentFormatter->render($coverageData);
276
    }
277
}
278