-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTracer.php
118 lines (96 loc) · 5.01 KB
/
Tracer.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
<?php
namespace Thomann\TwigCodeCoverage;
use Twig\TwigFunction;
final class Tracer {
private string $namespace;
private RenderEngineCoverageDriver $renderEngineCoverageDriver;
private ?NestableCoverageContainer $currentCoverageRun = null;
/** @var TemplateCoverageResult[] Array containing the coverages by template filename */
private array $coverages = [];
/**
* @param string $namespace
* @param RenderEngineCoverageDriver $renderEngineCoverageDriver
* @internal
*/
function __construct(string $namespace, RenderEngineCoverageDriver $renderEngineCoverageDriver) {
$this->namespace = $namespace;
$this->renderEngineCoverageDriver = $renderEngineCoverageDriver;
}
/**
* Returns a list of all coverage results
*
* @return TemplateCoverageResult[]
*/
function getCoverages(): array {
return $this->coverages;
}
/**
* Used to notify the Tracer that a new template will be loaded soon (causing the xdebug coverage to be
* overwritten). If there is a coverage trace in progress, it means a template has been included and the
* coverage buffer needs to be written to the current coverage container.
*
* @param string $templateName
* @internal
*/
function notifyNextTemplateWillBeLoaded (string $templateName) : void {
if ($this->currentCoverageRun !== null) {
$this->clearCoverageBufferAndWriteToContainer($this->currentCoverageRun);
}
}
/**
* @internal
*/
function getTwigFunctionTraceStart(): TwigFunction {
return new TwigFunction($this->getFunctionName('start'), function (string $templateName) {
$this->currentCoverageRun = new NestableCoverageContainer($this->getCurrentCalleeLineNumber(), $this->currentCoverageRun);
$this->currentCoverageRun->ignore($this->getCurrentCalleeLineNumber(), null);
xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
});
}
/**
* @internal
*/
function getTwigFunctionTraceEnd(): TwigFunction {
return new TwigFunction($this->getFunctionName('end'), function (string $templateName) {
$this->clearCoverageBufferAndWriteToContainer($this->currentCoverageRun);
$templateCoverageResult = iterator_to_array($this->currentCoverageRun->finalize($this->getCurrentCalleeLineNumber()));
// Remove tracer function call parts from the coverage (the closure and the start/stop expressions)
$templateCoverageResult = array_slice($templateCoverageResult, 1, -2, true);
$this->coverages[] = new TemplateCoverageResult($templateName, $templateCoverageResult);
$this->currentCoverageRun = $this->currentCoverageRun->getParent();
});
}
/**
* @internal
*/
function getTwigFunctionContinue() : TwigFunction {
return new TwigFunction($this->getFunctionName('continue'), function () {
// When we're continuing, we have to ignore this and the next line (next line is the next closure opener)
$calleeLine = $this->getCurrentCalleeLineNumber();
$this->currentCoverageRun->ignore($calleeLine, $calleeLine + 1);
xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
});
}
private function clearCoverageBufferAndWriteToContainer (NestableCoverageContainer $coverageContainer) : void {
$overallCoverageStatistics = xdebug_get_code_coverage();
xdebug_stop_code_coverage(true);
$coverageContainer->addCoverageResult($this->getRelevantCoverageStatistics($overallCoverageStatistics));
}
private function getFunctionName(string $functionName): string {
return sprintf('__%s_coverage_tracer__%s', $functionName, $this->namespace);
}
private function getRelevantCoverageStatistics(array $coverageStatistics): array {
$renderEngineExecutionKeyRegex = $this->renderEngineCoverageDriver->getExecutionKeyRegularExpression();
foreach ($coverageStatistics as $executionKey => $coverageStatistic) {
if (preg_match($renderEngineExecutionKeyRegex, $executionKey) === 1) {
return $coverageStatistic;
}
}
throw new \RuntimeException("Tracer could not find coverage statistics using execution key regular expression `{$renderEngineExecutionKeyRegex}`");
}
private function getCurrentCalleeLineNumber(): int {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $this->renderEngineCoverageDriver->getFunctionStackOffset());
$actualCallee = array_pop($backtrace);
return $actualCallee['line'];
}
}