Skip to content

Commit ff0d315

Browse files
authored
Merge pull request #127 from Codeception/master
Master into 2.0
2 parents 001f4b9 + 2700038 commit ff0d315

11 files changed

+1772
-13
lines changed

Makefile

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
.DEFAULT_GOAL := help
2+
3+
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
4+
current_dir := $(dir $(mkfile_path))
5+
6+
help:
7+
@echo "Use this makefile to execute your tests in correct php version"
8+
@echo "\tr.php-7.4\t\trun Tests with PHP 7.4"
9+
@echo "\tr.php-8.0\t\trun Tests with PHP 8.0"
10+
@echo "\tr.php-8.1\t\trun Tests with PHP 8.1"
11+
@echo "\tr.php-8.2\t\trun Tests with PHP 8.2"
12+
13+
r.php-7.4:
14+
docker build -t robo:php-7.4 --target PHP74 --build-arg PHP_VERSION=7.4 docker
15+
docker run --rm -v $(current_dir):/app -w /app robo:php-7.4 composer install
16+
docker run --rm -v $(current_dir):/app -w /app robo:php-7.4 composer test
17+
18+
r.php-8.0:
19+
docker build -t robo:php-8.0 --target PHP8 --build-arg PHP_VERSION=8.0 docker
20+
docker run --rm -v $(current_dir):/app -w /app robo:php-8.0 composer install
21+
docker run --rm -v $(current_dir):/app -w /app robo:php-8.0 composer test
22+
23+
r.php-8.1:
24+
docker build -t robo:php-8.1 --target PHP8 --build-arg PHP_VERSION=8.1 docker
25+
docker run --rm -v $(current_dir):/app -w /app robo:php-8.1 composer install
26+
docker run --rm -v $(current_dir):/app -w /app robo:php-8.1 composer test
27+
28+
r.php-8.2:
29+
docker build -t robo:php-8.2 --target PHP8 --build-arg PHP_VERSION=8.2 docker
30+
docker run --rm -v $(current_dir):/app -w /app robo:php-8.2 composer install
31+
docker run --rm -v $(current_dir):/app -w /app robo:php-8.2 composer test

docker/Dockerfile

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
ARG PHP_VERSION=7.4
2+
FROM php:${PHP_VERSION}-cli as base
3+
COPY --from=composer /usr/bin/composer /usr/bin/composer
4+
5+
RUN apt update \
6+
&& apt upgrade -y \
7+
&& apt install -y apt-utils libxml2-dev libzip-dev
8+
9+
FROM base as PHP74
10+
RUN docker-php-ext-install dom json xml zip
11+
12+
FROM base as PHP8
13+
RUN docker-php-ext-install dom xml zip

src/Merger/HtmlReportMerger.php

+77-6
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,20 @@ class HtmlReportMerger extends AbstractMerger
5050

5151
protected bool $previousLibXmlUseErrors = false;
5252

53-
private float $executionTimeSum = 0;
53+
protected bool $maxTime = false;
54+
55+
protected array $executionTime = [];
56+
57+
/**
58+
* @var string|float
59+
*/
60+
private $executionTimeSum = 0;
61+
62+
63+
public function maxTime(): void
64+
{
65+
$this->maxTime = true;
66+
}
5467

5568
/**
5669
* HtmlReportMerger constructor.
@@ -198,9 +211,10 @@ private function countExecutionTime(DOMDocument $dstFile): void
198211
if (!$nodeList) {
199212
throw XPathExpressionException::malformedXPath($xpathHeadline);
200213
}
201-
214+
$hoursMinutesSeconds = '(([0-1]?\d|2[0-3])(?::([0-5]?\d))?(?::([0-5]?\d))\.\d+)';
215+
$seconds = '\d+\.\d+s';
202216
$pregResult = preg_match(
203-
'#^Codeception Results .* \((?<timesum>\d+\.\d+)s\)$#',
217+
"#^Codeception Results .* \((?<timesum>$hoursMinutesSeconds|$seconds)\)$#",
204218
$nodeList[0]->nodeValue,
205219
$matches
206220
);
@@ -213,7 +227,18 @@ private function countExecutionTime(DOMDocument $dstFile): void
213227
return;
214228
}
215229

216-
$this->executionTimeSum += (float)$matches['timesum'];
230+
if (str_contains($matches['timesum'], 's')) {
231+
$matches['timesum'] = str_replace('s', '', $matches['timesum']);
232+
}
233+
if (!$this->maxTime) {
234+
if (str_contains($matches['timesum'], ':')) {
235+
$this->executionTimeSum = $this->sumTime(strval($this->executionTimeSum), (string)$matches['timesum']);
236+
} else {
237+
$this->executionTimeSum += (float)$matches['timesum'];
238+
}
239+
} else {
240+
$this->executionTime[] = (string)$matches['timesum'];
241+
}
217242
}
218243

219244
/**
@@ -238,8 +263,20 @@ private function updateHeaderLine(DOMDocument $dstFile): void
238263
$statusNode->nodeValue = 'FAILED';
239264
$statusAttr->value = 'color: red';
240265
}
241-
242-
$executionTimeNode->nodeValue = sprintf(' (%ss)', $this->executionTimeSum);
266+
if (!$this->maxTime) {
267+
$executionTime = (string)$this->executionTimeSum;
268+
} else {
269+
usort($this->executionTime, function ($a, $b) {
270+
return strcmp($a, $b);
271+
});
272+
$executionTime = max($this->executionTime);
273+
}
274+
$executionTimeNode->nodeValue = sprintf(
275+
(preg_match('#([0-1]?\d|2[0-3])(?::([0-5]?\d))?(?::([0-5]?\d))\.\d+#', $executionTime))
276+
? ' (%s)'
277+
: ' (%ss)',
278+
$executionTime
279+
);
243280
}
244281

245282
/**
@@ -342,4 +379,38 @@ private function updateButtons(DOMDocument $dstFile): void
342379
$table->setAttribute('id', "stepContainer" . $n);
343380
}
344381
}
382+
383+
private function sumTime(string $time1, string $time2): string
384+
{
385+
$times = [$time1, $time2];
386+
$seconds = 0;
387+
$milliseconds = 0;
388+
$isHour = false;
389+
foreach ($times as $time) {
390+
if ($time !== '0') {
391+
$output = explode(':', $time);
392+
if (count($output) > 2) {
393+
$isHour = true;
394+
[$hour, $minute, $second] = $output;
395+
$seconds += $hour * 3600;
396+
} else {
397+
[$minute, $second] = $output;
398+
}
399+
$seconds += $minute * 60;
400+
[$second, $millisecond] = explode('.', $second);
401+
$seconds += $second;
402+
$milliseconds += $millisecond;
403+
}
404+
}
405+
if ($isHour) {
406+
$hours = floor($seconds / 3600);
407+
$seconds -= $hours * 3600;
408+
}
409+
$minutes = floor($seconds / 60);
410+
$seconds -= $minutes * 60;
411+
412+
return $isHour
413+
? sprintf('%02d:%02d:%02d.%02d', $hours, $minutes, $seconds, $milliseconds)
414+
: sprintf('%02d:%02d.%02d', $minutes, $seconds, $milliseconds);
415+
}
345416
}

src/Merger/XmlReportMergerTask.php

+19-3
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ class XmlReportMergerTask extends AbstractMerger
1818
protected array $src = [];
1919

2020
protected string $dst = '';
21+
protected array $suiteDuration = [];
2122

2223
protected bool $summarizeTime = true;
24+
protected bool $maxSuiteTime = false;
2325

2426
protected bool $mergeRewrite = false;
2527

@@ -36,6 +38,12 @@ public function maxTime(): void
3638
$this->summarizeTime = false;
3739
}
3840

41+
public function maxSuiteTime(): void
42+
{
43+
$this->summarizeTime = false;
44+
$this->maxSuiteTime = true;
45+
}
46+
3947
public function mergeRewrite(): self
4048
{
4149
$this->mergeRewrite = true;
@@ -111,6 +119,7 @@ public function run(): void
111119

112120
protected function loadSuites(DOMElement $current): void
113121
{
122+
$this->suiteDuration[$current->getAttribute('name')][] = (float) $current->getAttribute('time');
114123
/** @var DOMNode $node */
115124
foreach ($current->childNodes as $node) {
116125
if ($node instanceof DOMElement) {
@@ -136,13 +145,20 @@ protected function mergeSuites(DOMDocument $dstXml): void
136145
'errors' => 0,
137146
'time' => 0,
138147
];
148+
139149
foreach ($tests as $test) {
140150
$resultNode->appendChild($test);
141151

142152
$data['assertions'] += (int)$test->getAttribute('assertions');
143-
$data['time'] = $this->summarizeTime
144-
? ((float)$test->getAttribute('time') + $data['time'])
145-
: max($test->getAttribute('time'), $data['time']);
153+
if ($this->summarizeTime) {
154+
$data['time'] = ((float)$test->getAttribute('time') + $data['time']);
155+
} else {
156+
if ($this->maxSuiteTime) {
157+
$data['time'] = max($this->suiteDuration[$suiteName]);
158+
} else {
159+
$data['time'] = max($test->getAttribute('time'), $data['time']);
160+
}
161+
}
146162

147163
$data['failures'] += $test->getElementsByTagName('failure')->length;
148164
$data['errors'] += $test->getElementsByTagName('error')->length;

src/Splitter/TestFileSplitterTask.php

-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
* ->testsFrom('tests/unit/Acme')
2121
* ->codeceptionRoot('projects/tested')
2222
* ->groupsTo('tests/_log/paratest_')
23-
* ->addFilter(new Filter1())
24-
* ->addFilter(new Filter2())
2523
* ->run();
2624
* ```
2725
*

tests/Merger/HtmlReportMergerTest.php

+152
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,156 @@ public function testRun(): void
6262

6363
$this->assertSame($expectedTimeInSeconds, (float)$matches['timesum']);
6464
}
65+
66+
/**
67+
* @covers ::run
68+
*/
69+
public function testRunWithCodeception5Reports(): void
70+
{
71+
$expectedTimeInSeconds = '03:34.98';
72+
$expectedSuccess= 3;
73+
74+
$reportPath = TEST_PATH . '/fixtures/reports/html/';
75+
$task = new HtmlReportMerger();
76+
$task->setLogger(new Logger(new NullOutput()));
77+
78+
$resultReport = TEST_PATH . '/result/report_codeception5.html';
79+
$task
80+
->from(
81+
[
82+
$reportPath . 'report_0_codeception5.html', // this file did not exists and it should not fail
83+
$reportPath . 'report_1_codeception5.html',
84+
$reportPath . 'report_2_codeception5.html',
85+
$reportPath . 'report_3_codeception5.html',
86+
]
87+
)
88+
->into($resultReport)
89+
->run();
90+
91+
$this->assertFileExists($resultReport);
92+
93+
//read first source file as main
94+
$dstHTML = new DOMDocument();
95+
$dstHTML->loadHTMLFile($resultReport, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
96+
/** @var DOMNodeList $values */
97+
$values = (new DOMXPath($dstHTML))
98+
->query("//*[contains(@class,'scenarioSuccessValue')]");
99+
100+
$this->assertCount(1, $values);
101+
$this->assertSame($expectedSuccess, (int)$values[0]->nodeValue);
102+
103+
$values = (new DOMXPath($dstHTML))
104+
->query("//h1[text() = 'Codeception Results ']");
105+
preg_match(
106+
'#^Codeception Results .* \((?<timesum>(([0-1]?\d|2[0-3])(?::([0-5]?\d))?(?::([0-5]?\d))\.\d+))\)$#',
107+
$values[0]->nodeValue,
108+
$matches
109+
);
110+
111+
$this->assertSame($expectedTimeInSeconds, (string)$matches['timesum']);
112+
}
113+
114+
/**
115+
* @covers ::run
116+
*/
117+
public function testRunMaxTimeReports(): void
118+
{
119+
$expectedTime = '129.25';
120+
$expectedSuccess= 3;
121+
122+
$reportPath = TEST_PATH . '/fixtures/reports/html/';
123+
$task = new HtmlReportMerger();
124+
$task->setLogger(new Logger(new NullOutput()));
125+
126+
$resultReport = TEST_PATH . '/result/report_max_time.html';
127+
$task->maxTime();
128+
$task
129+
->from(
130+
[
131+
$reportPath . 'report_0.html', // this file did not exists and it should not fail
132+
$reportPath . 'report_1.html',
133+
$reportPath . 'report_2.html',
134+
$reportPath . 'report_3.html',
135+
]
136+
)
137+
->into($resultReport)
138+
->run();
139+
140+
$this->assertFileExists($resultReport);
141+
142+
//read first source file as main
143+
$dstHTML = new DOMDocument();
144+
$dstHTML->loadHTMLFile($resultReport, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
145+
/** @var DOMNodeList $values */
146+
$values = (new DOMXPath($dstHTML))
147+
->query("//*[contains(@class,'scenarioSuccessValue')]");
148+
149+
$this->assertCount(1, $values);
150+
$this->assertSame($expectedSuccess, (int)$values[0]->nodeValue);
151+
152+
$values = (new DOMXPath($dstHTML))
153+
->query("//h1[text() = 'Codeception Results ']");
154+
preg_match(
155+
'#^Codeception Results .* \((?<timesum>\d+\.\d+)s\)$#',
156+
$values[0]->nodeValue,
157+
$matches
158+
);
159+
$executionTime[] = (string)$matches['timesum'];
160+
usort($executionTime, function ($a, $b) {
161+
return strcmp($a, $b);
162+
});
163+
$this->assertSame($expectedTime, max($executionTime));
164+
}
165+
166+
/**
167+
* @covers ::run
168+
*/
169+
public function testRunMaxTimeWithCodeception5Reports(): void
170+
{
171+
$expectedTime = '02:09.25';
172+
$expectedSuccess= 3;
173+
174+
$reportPath = TEST_PATH . '/fixtures/reports/html/';
175+
$task = new HtmlReportMerger();
176+
$task->setLogger(new Logger(new NullOutput()));
177+
178+
$resultReport = TEST_PATH . '/result/report_codeception5_max_time.html';
179+
$task->maxTime();
180+
$task
181+
->from(
182+
[
183+
$reportPath . 'report_0_codeception5.html', // this file did not exists and it should not fail
184+
$reportPath . 'report_1_codeception5.html',
185+
$reportPath . 'report_2_codeception5.html',
186+
$reportPath . 'report_3_codeception5.html',
187+
]
188+
)
189+
->into($resultReport)
190+
->run();
191+
192+
$this->assertFileExists($resultReport);
193+
194+
//read first source file as main
195+
$dstHTML = new DOMDocument();
196+
$dstHTML->loadHTMLFile($resultReport, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
197+
/** @var DOMNodeList $values */
198+
$values = (new DOMXPath($dstHTML))
199+
->query("//*[contains(@class,'scenarioSuccessValue')]");
200+
201+
$this->assertCount(1, $values);
202+
$this->assertSame($expectedSuccess, (int)$values[0]->nodeValue);
203+
204+
$values = (new DOMXPath($dstHTML))
205+
->query("//h1[text() = 'Codeception Results ']");
206+
preg_match(
207+
'#^Codeception Results .* \((?<timesum>(([0-1]?\d|2[0-3])(?::([0-5]?\d))?(?::([0-5]?\d))\.\d+))\)$#',
208+
$values[0]->nodeValue,
209+
$matches
210+
);
211+
$executionTime[] = (string)$matches['timesum'];
212+
usort($executionTime, function ($a, $b) {
213+
return strcmp($a, $b);
214+
});
215+
$this->assertSame($expectedTime, max($executionTime));
216+
}
65217
}

0 commit comments

Comments
 (0)