Passed
Push — master ( d40832...b64ee5 )
by Jakub
01:41
created

Tester   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 222
Duplicated Lines 0 %

Test Coverage

Coverage 77.69%

Importance

Changes 39
Bugs 6 Features 8
Metric Value
wmc 36
eloc 129
c 39
b 6
f 8
dl 0
loc 222
ccs 101
cts 130
cp 0.7769
rs 9.52

12 Methods

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