|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* A utility for transforming a raw code coverage |
|
4
|
|
|
* report into a clover.xml file for consumption. |
|
5
|
|
|
* |
|
6
|
|
|
* @package automattic/jetpack-autoloader |
|
7
|
|
|
*/ |
|
8
|
|
|
|
|
9
|
|
|
use SebastianBergmann\CodeCoverage\Report\Clover; |
|
10
|
|
|
use SebastianBergmann\CodeCoverage\Version; |
|
11
|
|
|
|
|
12
|
|
|
// phpcs:disabled WordPress.Security.EscapeOutput.OutputNotEscaped |
|
13
|
|
|
|
|
14
|
|
|
define( 'ROOT_DIR', dirname( dirname( dirname( __DIR__ ) ) ) ); |
|
15
|
|
|
|
|
16
|
|
|
require_once ROOT_DIR . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; |
|
17
|
|
|
|
|
18
|
|
|
/** |
|
19
|
|
|
* Returns the path to the clover.xml output file. |
|
20
|
|
|
* |
|
21
|
|
|
* @return string The path to the output file. |
|
22
|
|
|
*/ |
|
23
|
|
|
function get_output_file() { |
|
24
|
|
|
global $argc; |
|
25
|
|
|
global $argv; |
|
26
|
|
|
|
|
27
|
|
|
if ( $argc < 2 ) { |
|
28
|
|
|
echo "Usage: test-coverage [clover.xml file]\n"; |
|
29
|
|
|
exit( -1 ); |
|
30
|
|
|
} |
|
31
|
|
|
|
|
32
|
|
|
return $argv[1]; |
|
33
|
|
|
} |
|
34
|
|
|
|
|
35
|
|
|
/** |
|
36
|
|
|
* Attempts to load the report from the tmp file we should have generated. |
|
37
|
|
|
* |
|
38
|
|
|
* @return SebastianBergmann\CodeCoverage\CodeCoverage The unserialized code coverage object. |
|
39
|
|
|
*/ |
|
40
|
|
|
function load_report() { |
|
41
|
|
|
$coverage_report = implode( DIRECTORY_SEPARATOR, array( ROOT_DIR, 'tests', 'php', 'tmp', 'coverage-report.php' ) ); |
|
42
|
|
|
if ( ! file_exists( $coverage_report ) ) { |
|
43
|
|
|
echo "There is no coverage report to process.\n"; |
|
44
|
|
|
exit( -1 ); |
|
45
|
|
|
} |
|
46
|
|
|
|
|
47
|
|
|
return require_once $coverage_report; |
|
48
|
|
|
} |
|
49
|
|
|
|
|
50
|
|
|
/** |
|
51
|
|
|
* Evaluates the version of sebastianbergmann/php-code-coverage that we've generated the coverage report using. |
|
52
|
|
|
* |
|
53
|
|
|
* @return string The version for the code coverage package. |
|
54
|
|
|
*/ |
|
55
|
|
|
function get_coverage_version() { |
|
56
|
|
|
return Version::id(); |
|
57
|
|
|
} |
|
58
|
|
|
|
|
59
|
|
|
/** |
|
60
|
|
|
* Counts the number of lines in a file before the `class` keyword. |
|
61
|
|
|
* |
|
62
|
|
|
* @param string $file The file to check. |
|
63
|
|
|
* @return int|null The number of lines or null if there is no class keyword. |
|
64
|
|
|
*/ |
|
65
|
|
|
function count_lines_before_class_keyword( $file ) { |
|
66
|
|
|
// Find the line that the `class` keyword occurs on so that we can use it to calculate an offset from the header. |
|
67
|
|
|
$content = file_get_contents( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents |
|
68
|
|
|
|
|
69
|
|
|
// Find the class keyword and capture the number of characters in the string before this point. |
|
70
|
|
|
if ( 0 === preg_match_all( '/^class /m', $content, $matches, PREG_OFFSET_CAPTURE ) ) { |
|
71
|
|
|
return null; |
|
72
|
|
|
} |
|
73
|
|
|
|
|
74
|
|
|
// Count the line endings leading up to the `class` keyword. |
|
75
|
|
|
$newlines = substr_count( $content, "\n", 0, $matches[0][0][1] ); |
|
76
|
|
|
if ( $newlines > 0 ) { |
|
77
|
|
|
return $newlines + 1; |
|
78
|
|
|
} |
|
79
|
|
|
|
|
80
|
|
|
return null; |
|
81
|
|
|
} |
|
82
|
|
|
|
|
83
|
|
|
/** |
|
84
|
|
|
* Creates a map for converting file paths to src paths. |
|
85
|
|
|
* |
|
86
|
|
|
* @param string[] $report_file_paths An array containing all of the paths for the report. |
|
87
|
|
|
* @return array A map describing how to transform built files into src coverage. |
|
88
|
|
|
*/ |
|
89
|
|
|
function get_path_transformation_map( $report_file_paths ) { |
|
90
|
|
|
// We're going to create a map describing how to transform files to src files. |
|
91
|
|
|
// We're also going to store any metadata needed to perform the merge safetly. |
|
92
|
|
|
$transformation_map = array(); |
|
93
|
|
|
|
|
94
|
|
|
// Scan the src directory so that we can create the map to convert between files. |
|
95
|
|
|
$raw_src_files = scandir( ROOT_DIR . DIRECTORY_SEPARATOR . 'src' ); |
|
96
|
|
|
$src_file_map = array(); |
|
97
|
|
|
foreach ( $raw_src_files as $file ) { |
|
98
|
|
|
// Only PHP files will be copied. |
|
99
|
|
|
if ( substr( $file, -4 ) !== '.php' ) { |
|
100
|
|
|
continue; |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
$file = ROOT_DIR . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . $file; |
|
104
|
|
|
if ( ! file_exists( $file ) ) { |
|
105
|
|
|
continue; |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
// We need to use the class keyword to address the line offset from injecting the header. |
|
109
|
|
|
$class_line = count_lines_before_class_keyword( $file ); |
|
110
|
|
|
if ( null === $class_line ) { |
|
111
|
|
|
// The autoloader only has class files and so this is fine. |
|
112
|
|
|
continue; |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
|
|
$src_file_map[ $file ] = array( |
|
116
|
|
|
'file' => $file, |
|
117
|
|
|
'class_line' => $class_line, |
|
118
|
|
|
); |
|
119
|
|
|
} |
|
120
|
|
|
|
|
121
|
|
|
// Create a map describing the file transformations. |
|
122
|
|
|
foreach ( $report_file_paths as $report_file_path ) { |
|
123
|
|
|
// We will use the class line from the report file to calculate the offset from the src file to apply to coverage lines. |
|
124
|
|
|
$class_line = count_lines_before_class_keyword( $report_file_path ); |
|
125
|
|
|
if ( ! isset( $class_line ) ) { |
|
126
|
|
|
continue; |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
// Attempt to find the original file. |
|
130
|
|
|
// Note: This does not support nested directories! |
|
131
|
|
|
$src_file_path = null; |
|
132
|
|
|
foreach ( $src_file_map as $src_file ) { |
|
133
|
|
|
// We don't need to perform any transformations if the file path is the same. |
|
134
|
|
|
if ( $src_file['file'] === $report_file_path ) { |
|
135
|
|
|
continue; |
|
136
|
|
|
} |
|
137
|
|
|
|
|
138
|
|
|
if ( basename( $src_file['file'] ) === basename( $report_file_path ) ) { |
|
139
|
|
|
$src_file_path = $src_file['file']; |
|
140
|
|
|
break; |
|
141
|
|
|
} |
|
142
|
|
|
} |
|
143
|
|
|
if ( ! $src_file_path ) { |
|
144
|
|
|
continue; |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
// We can finally calculate the line offset since we have the class line for both. |
|
148
|
|
|
$line_offset = $class_line - $src_file_map[ $src_file_path ]['class_line']; |
|
149
|
|
|
|
|
150
|
|
|
// Record the file in the transformation map. |
|
151
|
|
|
$transformation_map[ $report_file_path ] = array( |
|
152
|
|
|
'src' => $src_file_path, |
|
153
|
|
|
'line_offset' => $line_offset, |
|
154
|
|
|
); |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
return $transformation_map; |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
/** |
|
161
|
|
|
* Processes a v9 CodeCoverage report. |
|
162
|
|
|
* |
|
163
|
|
|
* @param SebastianBergmann\CodeCoverage\CodeCoverage $report The report to process. |
|
164
|
|
|
* @return SebastianBergmann\CodeCoverage\CodeCoverage The processed report. |
|
165
|
|
|
*/ |
|
166
|
|
|
function process_coverage_9( $report ) { |
|
167
|
|
|
$data = $report->getData( true ); |
|
168
|
|
|
|
|
169
|
|
|
// We're going to merge the line coverage from compiled files into the src files. |
|
170
|
|
|
$line_coverage = $data->lineCoverage(); |
|
171
|
|
|
$transformations = get_path_transformation_map( array_keys( $line_coverage ) ); |
|
172
|
|
|
|
|
173
|
|
|
$removed_files = array(); |
|
174
|
|
|
foreach ( $line_coverage as $file => $lines ) { |
|
175
|
|
|
if ( ! isset( $transformations[ $file ] ) ) { |
|
176
|
|
|
continue; |
|
177
|
|
|
} |
|
178
|
|
|
|
|
179
|
|
|
// Prepare the transformations we are going to make. |
|
180
|
|
|
$src_file = $transformations[ $file ]['src']; |
|
181
|
|
|
$line_offset = $transformations[ $file ]['line_offset']; |
|
182
|
|
|
|
|
183
|
|
|
// Create a new line coverage mapped to the src file. |
|
184
|
|
|
$new_coverage = array(); |
|
185
|
|
|
foreach ( $lines as $line => $coverage ) { |
|
186
|
|
|
$new_coverage[ $src_file ][ $line - $line_offset ] = $coverage; |
|
187
|
|
|
} |
|
188
|
|
|
|
|
189
|
|
|
// Merge the coverage since multiple compiled files may map to a single src file. |
|
190
|
|
|
$merge = new SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData(); |
|
191
|
|
|
$merge->setLineCoverage( $new_coverage ); |
|
192
|
|
|
$data->merge( $merge ); |
|
193
|
|
|
|
|
194
|
|
|
// Mark the file for removal from the original coverage. |
|
195
|
|
|
$removed_files[] = $file; |
|
196
|
|
|
} |
|
197
|
|
|
|
|
198
|
|
|
// Remove all of the files that we've transformed from the coverage. |
|
199
|
|
|
$line_coverage = $data->lineCoverage(); |
|
200
|
|
|
foreach ( $removed_files as $file ) { |
|
201
|
|
|
// Make sure the uncovered file does not show up in the report. |
|
202
|
|
|
$report->filter()->excludeFile( $file ); |
|
203
|
|
|
unset( $line_coverage[ $file ] ); |
|
204
|
|
|
} |
|
205
|
|
|
$data->setLineCoverage( $line_coverage ); |
|
206
|
|
|
|
|
207
|
|
|
return $report; |
|
208
|
|
|
} |
|
209
|
|
|
|
|
210
|
|
|
/** |
|
211
|
|
|
* Processes the code coverage report and outputs a clover.xml file. |
|
212
|
|
|
*/ |
|
213
|
|
|
function process_coverage() { |
|
214
|
|
|
echo "Aggregating compiled coverage into unified code coverage report\n"; |
|
215
|
|
|
|
|
216
|
|
|
// We're going to transform the code coverage object into a Clover XML report. |
|
217
|
|
|
$output_file = get_output_file(); |
|
218
|
|
|
|
|
219
|
|
|
// Since there is no backwards compatibility guarantee in place for the code coverage |
|
220
|
|
|
// object we need to handle it according to each major version independently. |
|
221
|
|
|
$coverage_version = get_coverage_version(); |
|
222
|
|
|
$major_version = substr( $coverage_version, 0, strpos( $coverage_version, '.' ) ); |
|
223
|
|
|
|
|
224
|
|
|
$function = 'process_coverage_' . $major_version; |
|
225
|
|
|
if ( ! function_exists( $function ) ) { |
|
226
|
|
|
echo "No handler defined for major version $major_version\n"; |
|
227
|
|
|
die( -1 ); |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
// We can finally load the report that we're wanting to process. |
|
231
|
|
|
$report = load_report(); |
|
232
|
|
|
|
|
233
|
|
|
// Process the report using the handler. |
|
234
|
|
|
$report = call_user_func( $function, $report ); |
|
235
|
|
|
|
|
236
|
|
|
// Generate the XML file for the report. |
|
237
|
|
|
$clover = new Clover(); |
|
238
|
|
|
$clover->process( $report, $output_file ); |
|
239
|
|
|
echo "Generated code coverage report in Clover format\n"; |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
// Process the coverage report into the new output. |
|
243
|
|
|
process_coverage(); |
|
244
|
|
|
|