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
|
|
|
|