1#!/usr/bin/env vpython3 2# Copyright 2021 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6import json 7import os 8import shutil 9import tempfile 10import unittest 11 12import merge_js_lib as merger 13from pathlib import Path 14import node 15 16_HERE_DIR = Path(__file__).parent.resolve() 17_SOURCE_MAP_PROCESSOR = (_HERE_DIR.parent.parent.parent / 18 'tools' / 'code_coverage' / 19 'js_source_maps' / 'create_js_source_maps' / 20 'create_js_source_maps.js').resolve() 21 22@unittest.skipIf(os.name == 'nt', 'Not intended to work on Windows') 23class ConvertToIstanbulTest(unittest.TestCase): 24 _TEST_SOURCE_A = """function add(a, b) { 25 return a + b; 26} 27 28function subtract(a, b) { 29 return a - b; 30} 31 32subtract(5, 2); 33""" 34 _INVALID_MAPPING_A = ( 35 "//# sourceMappingURL=data:application/json;base64," 36 "eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZvby50cyJdLCJuYW1lcyI6W10sIm1hcHBpb" 37 "mdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Oz" 38 "s7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUN" 39 "BIiwiZmlsZSI6Ii91c3IvbG9jYWwvZ29vZ2xlL2hvbWUvc3Jpbml2YXNoZWdkZS9jaHJv" 40 "bWl1bS9zcmMvZm9vX3ByZS50cyIsInNvdXJjZVJvb3QiOiIvdXNyL2xvY2FsL2dvb2dsZ" 41 "S9ob21lL3NyaW5pdmFzaGVnZGUvY2hyb21pdW0vc3JjIiwic291cmNlc0NvbnRlbnQiOl" 42 "siZnVuY3Rpb24gYWRkKGEsIGIpIHtcbiAgcmV0dXJuIGEgKyBiO1xufVxuXG5mdW5jdGl" 43 "vbiBzdWJ0cmFjdChhLCBiKSB7XG4gIHJldHVybiBhIC0gYjtcbn1cblxuc3VidHJhY3Qo" 44 "NSwgMik7XG4iXX0=" 45 ) 46 47 _TEST_COVERAGE_A = """{ 48 "result": [ 49 { 50 "scriptId":"72", 51 "url":"//file.js", 52 "functions":[ 53 { 54 "functionName":"", 55 "ranges":[ 56 {"startOffset":0,"endOffset":101,"count":1} 57 ], 58 "isBlockCoverage":true 59 }, 60 { 61 "functionName":"add", 62 "ranges":[ 63 {"startOffset":0,"endOffset":38,"count":0} 64 ], 65 "isBlockCoverage":false 66 }, 67 { 68 "functionName":"subtract", 69 "ranges":[ 70 {"startOffset":40,"endOffset":83,"count":1} 71 ], 72 "isBlockCoverage":true 73 } 74 ] 75 } 76 ] 77} 78""" 79 80 _TEST_COVERAGE_INVALID = """{ 81 "scriptId":"72", 82 "url":"//file.js", 83 "functions":[ 84 { 85 "functionName":"", 86 "ranges":[ 87 {"startOffset":0,"endOffset":101,"count":1} 88 ], 89 "isBlockCoverage":true 90 }, 91 { 92 "functionName":"add", 93 "ranges":[ 94 {"startOffset":0,"endOffset":38,"count":0} 95 ], 96 "isBlockCoverage":false 97 }, 98 { 99 "functionName":"subtract", 100 "ranges":[ 101 {"startOffset":40,"endOffset":83,"count":1} 102 ], 103 "isBlockCoverage":true 104 } 105 ] 106} 107""" 108 109 _TEST_SOURCE_B = """const {subtract} = require('./test1.js'); 110 111function add(a, b) { 112 return a + b; 113} 114 115subtract(5, 2); 116 117""" 118 119 _TEST_SOURCE_C = """exports.subtract = function(a, b) { 120 return a - b; 121} 122""" 123 124 _TEST_COVERAGE_B = """{ 125 "result":[ 126 { 127 "scriptId":"72", 128 "url":"//test.js", 129 "functions":[ 130 { 131 "functionName":"", 132 "ranges":[ 133 {"startOffset":0,"endOffset":99,"count":1} 134 ], 135 "isBlockCoverage":true 136 }, 137 { 138 "functionName":"add", 139 "ranges":[ 140 {"startOffset":43,"endOffset":81,"count":0} 141 ], 142 "isBlockCoverage":false 143 } 144 ] 145 }, 146 { 147 "scriptId":"73", 148 "url":"//test1.js", 149 "functions":[ 150 { 151 "functionName":"", 152 "ranges":[ 153 {"startOffset":0,"endOffset":54,"count":1} 154 ], 155 "isBlockCoverage":true 156 }, 157 { 158 "functionName":"exports.subtract", 159 "ranges":[ 160 {"startOffset":19,"endOffset":53,"count":1} 161 ], 162 "isBlockCoverage":true 163 } 164 ] 165 } 166 ] 167} 168""" 169 170 _TEST_COVERAGE_NO_LEADING_SLASH = """{ 171 "result":[ 172 { 173 "scriptId":"72", 174 "url":"file:///usr/local/google/home/benreich/v8-to-istanbul/test.js", 175 "functions":[ 176 { 177 "functionName":"", 178 "ranges":[ 179 {"startOffset":0,"endOffset":99,"count":1} 180 ], 181 "isBlockCoverage":true 182 }, 183 { 184 "functionName":"add", 185 "ranges":[ 186 {"startOffset":43,"endOffset":81,"count":0} 187 ], 188 "isBlockCoverage":false 189 } 190 ] 191 }, 192 { 193 "scriptId":"73", 194 "url":"//test1.js", 195 "functions":[ 196 { 197 "functionName":"", 198 "ranges":[ 199 {"startOffset":0,"endOffset":54,"count":1} 200 ], 201 "isBlockCoverage":true 202 }, 203 { 204 "functionName":"exports.subtract", 205 "ranges":[ 206 {"startOffset":19,"endOffset":53,"count":1} 207 ], 208 "isBlockCoverage":true 209 } 210 ] 211 } 212 ] 213} 214""" 215 216 _TEST_COVERAGE_DUPLICATE_SINGLE = """{ 217 "result":[ 218 { 219 "scriptId":"73", 220 "url":"//test1.js", 221 "functions":[ 222 { 223 "functionName":"", 224 "ranges":[ 225 {"startOffset":0,"endOffset":54,"count":1} 226 ], 227 "isBlockCoverage":true 228 }, 229 { 230 "functionName":"exports.subtract", 231 "ranges":[ 232 {"startOffset":19,"endOffset":53,"count":1} 233 ], 234 "isBlockCoverage":true 235 } 236 ] 237 } 238 ] 239} 240""" 241 242 _TEST_COVERAGE_DUPLICATE_DOUBLE = """{ 243 "result":[ 244 { 245 "scriptId":"72", 246 "url":"//test.js", 247 "functions":[ 248 { 249 "functionName":"", 250 "ranges":[ 251 {"startOffset":0,"endOffset":99,"count":1} 252 ], 253 "isBlockCoverage":true 254 }, 255 { 256 "functionName":"add", 257 "ranges":[ 258 {"startOffset":43,"endOffset":81,"count":0} 259 ], 260 "isBlockCoverage":false 261 } 262 ] 263 }, 264 { 265 "scriptId":"73", 266 "url":"//test1.js", 267 "functions":[ 268 { 269 "functionName":"", 270 "ranges":[ 271 {"startOffset":0,"endOffset":54,"count":1} 272 ], 273 "isBlockCoverage":true 274 }, 275 { 276 "functionName":"exports.subtract", 277 "ranges":[ 278 {"startOffset":19,"endOffset":53,"count":1} 279 ], 280 "isBlockCoverage":true 281 } 282 ] 283 } 284 ] 285} 286""" 287 288 def setUp(self): 289 self.task_output_dir = tempfile.mkdtemp() 290 self.coverage_dir = os.path.join(self.task_output_dir, 'coverages') 291 self.source_dir = os.path.join(self.task_output_dir, 'source') 292 self.out_dir = os.path.join(self.task_output_dir, 'out') 293 self.sourceRoot = '/' 294 295 os.makedirs(self.coverage_dir) 296 os.makedirs(self.source_dir) 297 os.makedirs(self.out_dir) 298 299 def tearDown(self): 300 shutil.rmtree(self.task_output_dir) 301 302 def list_files(self, absolute_path): 303 actual_files = [] 304 for root, _, files in os.walk(absolute_path): 305 actual_files.extend( 306 [os.path.join(root, file_name) for file_name in files]) 307 308 return actual_files 309 310 def _write_files(self, root_dir, *file_path_contents): 311 for data in file_path_contents: 312 file_path, contents = data 313 with open(os.path.join(root_dir, file_path), 'w') as f: 314 f.write(contents) 315 316 def _write_transformations( 317 self, source_dir, out_dir, 318 original_file_name, input_file_name, output_file_name): 319 original_file = os.path.join(source_dir, original_file_name) 320 input_file = os.path.join(source_dir, input_file_name) 321 output_file = os.path.join(out_dir, output_file_name) 322 node.RunNode([ 323 str(_SOURCE_MAP_PROCESSOR), 324 "--originals={}".format(" ".join([original_file])), 325 "--inputs={}".format(" ".join([input_file])), 326 "--outputs={}".format(" ".join([output_file])), 327 "--inline-sourcemaps", 328 "--sourceRoot={}".format(self.sourceRoot), 329 ]) 330 331 def write_sources(self, *file_path_contents): 332 url_to_path_map = {} 333 for path_url, contents in file_path_contents: 334 file_path, url = path_url 335 url_to_path_map[file_path] = url 336 self._write_files(self.source_dir, (url, contents)) 337 self._write_files(self.out_dir, (url, contents)) 338 self._write_transformations( 339 self.source_dir, self.out_dir, url, url, url) 340 with open(os.path.join(self.out_dir, 'parsed_scripts.json'), 341 'w', 342 encoding='utf-8') as f: 343 f.write(json.dumps(url_to_path_map)) 344 345 def write_coverages(self, *file_path_contents): 346 self._write_files(self.coverage_dir, *file_path_contents) 347 348 def test_happy_path(self): 349 self.write_sources((('//file.js', 'file.js'), self._TEST_SOURCE_A)) 350 self.write_coverages(('test_coverage.cov.json', self._TEST_COVERAGE_A)) 351 352 merger.convert_raw_coverage_to_istanbul([self.coverage_dir], 353 self.out_dir, 354 self.task_output_dir) 355 356 istanbul_files = self.list_files( 357 os.path.join(self.task_output_dir, 'istanbul')) 358 self.assertEqual(len(istanbul_files), 1) 359 360 def test_invalid_mapping(self): 361 self.write_sources((('//file.js', 'file.js'), self._TEST_SOURCE_A)) 362 self._write_files( 363 self.out_dir, ( 364 'file.js', self._TEST_SOURCE_A + '\n' + self._INVALID_MAPPING_A)) 365 self.write_coverages(('test_coverage.cov.json', self._TEST_COVERAGE_A)) 366 367 368 merger.convert_raw_coverage_to_istanbul([self.coverage_dir], 369 self.out_dir, 370 self.task_output_dir) 371 372 istanbul_files = self.list_files( 373 os.path.join(self.task_output_dir, 'istanbul')) 374 self.assertEqual(len(istanbul_files), 0) 375 376 def test_no_coverages_in_file(self): 377 coverage_file = """{ 378 "result": [] 379 } 380 """ 381 382 self.write_sources((('//file.js', 'file.js'), self._TEST_SOURCE_A)) 383 self.write_coverages(('test_coverage.cov.json', coverage_file)) 384 385 merger.convert_raw_coverage_to_istanbul([self.coverage_dir], 386 self.out_dir, 387 self.task_output_dir) 388 389 istanbul_files = self.list_files( 390 os.path.join(self.task_output_dir, 'istanbul')) 391 self.assertEqual(len(istanbul_files), 0) 392 393 def test_invalid_coverage_file(self): 394 self.write_sources((('//file.js', 'file.js'), self._TEST_SOURCE_A)) 395 self.write_coverages( 396 ('test_coverage.cov.json', self._TEST_COVERAGE_INVALID)) 397 398 with self.assertRaises(RuntimeError): 399 merger.convert_raw_coverage_to_istanbul([self.coverage_dir], 400 self.out_dir, 401 self.task_output_dir) 402 403 def test_multiple_coverages_single_file(self): 404 self.write_sources((('//test.js', 'test.js'), self._TEST_SOURCE_B), 405 (('//test1.js', 'test1.js'), self._TEST_SOURCE_C)) 406 self.write_coverages(('test_coverage.cov.json', self._TEST_COVERAGE_B)) 407 408 merger.convert_raw_coverage_to_istanbul([self.coverage_dir], 409 self.out_dir, 410 self.task_output_dir) 411 412 istanbul_files = self.list_files( 413 os.path.join(self.task_output_dir, 'istanbul')) 414 self.assertEqual(len(istanbul_files), 2) 415 416 def test_multiple_coverages_no_leading_double_slash(self): 417 self.write_sources((('//test.js', 'test.js'), self._TEST_SOURCE_B), 418 (('//test1.js', 'test1.js'), self._TEST_SOURCE_C)) 419 self.write_coverages( 420 ('test_coverage.cov.json', self._TEST_COVERAGE_NO_LEADING_SLASH)) 421 422 merger.convert_raw_coverage_to_istanbul([self.coverage_dir], 423 self.out_dir, 424 self.task_output_dir) 425 426 istanbul_files = self.list_files( 427 os.path.join(self.task_output_dir, 'istanbul')) 428 self.assertEqual(len(istanbul_files), 1) 429 430 431 def test_multiple_duplicate_coverages_flattened(self): 432 self.write_sources((('//test.js', 'test.js'), self._TEST_SOURCE_B), 433 (('//test1.js', 'test1.js'), self._TEST_SOURCE_C)) 434 self.write_coverages( 435 ('test_coverage_1.cov.json', self._TEST_COVERAGE_B)) 436 self.write_coverages( 437 ('test_coverage_2.cov.json', self._TEST_COVERAGE_DUPLICATE_DOUBLE)) 438 439 merger.convert_raw_coverage_to_istanbul([self.coverage_dir], 440 self.out_dir, 441 self.task_output_dir) 442 443 istanbul_files = self.list_files( 444 os.path.join(self.task_output_dir, 'istanbul')) 445 self.assertEqual(len(istanbul_files), 2) 446 447 448 def test_original_source_missing(self): 449 self.write_sources((('//file.js', 'file.js'), self._TEST_SOURCE_A)) 450 self.write_coverages(('test_coverage.cov.json', self._TEST_COVERAGE_A)) 451 os.remove(os.path.join(self.source_dir, "file.js")) 452 453 merger.convert_raw_coverage_to_istanbul([self.coverage_dir], 454 self.out_dir, 455 self.task_output_dir) 456 457 istanbul_files = self.list_files( 458 os.path.join(self.task_output_dir, 'istanbul')) 459 self.assertEqual(len(istanbul_files), 0) 460 461 462 def test_multiple_coverages_in_multiple_shards(self): 463 coverage_dir_1 = os.path.join(self.coverage_dir, 'coverage1') 464 coverage_dir_2 = os.path.join(self.coverage_dir, 'coverage2') 465 os.makedirs(coverage_dir_1) 466 os.makedirs(coverage_dir_2) 467 468 self.write_sources((('//test.js', 'test.js'), self._TEST_SOURCE_B), 469 (('//test1.js', 'test1.js'), self._TEST_SOURCE_C)) 470 self._write_files(coverage_dir_1, 471 ('test_coverage_1.cov.json', self._TEST_COVERAGE_B)) 472 self._write_files( 473 coverage_dir_2, 474 ('test_coverage_2.cov.json', self._TEST_COVERAGE_DUPLICATE_DOUBLE)) 475 476 merger.convert_raw_coverage_to_istanbul( 477 [coverage_dir_1, coverage_dir_2], self.out_dir, 478 self.task_output_dir) 479 480 istanbul_files = self.list_files( 481 os.path.join(self.task_output_dir, 'istanbul')) 482 self.assertEqual(len(istanbul_files), 2) 483 484 485if __name__ == '__main__': 486 unittest.main() 487