1#!/usr/bin/env vpython3 2# Copyright 2020 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 base64 7import json 8import os 9import shutil 10import tempfile 11import unittest 12 13import merge_js_lib as merger 14from parameterized import parameterized 15 16 17class MergeJSLibTest(unittest.TestCase): 18 def test_write_parsed_scripts(self): 19 test_files = [{ 20 'url': '//a/b/c/1.js', 21 'location': ['a', 'b', 'c', '1.js'], 22 'exists': True 23 }, { 24 'url': '//d/e/f/5.js', 25 'location': ['d', 'e', 'f', '5.js'], 26 'exists': True 27 }, { 28 'url': '//a/b/d/7.js', 29 'location': ['a', 'b', 'd', '7.js'], 30 'exists': True 31 }, { 32 'url': 'chrome://test_webui/file.js', 33 'exists': False 34 }, { 35 'url': 'file://testing/file.js', 36 'exists': False 37 }] 38 39 test_script_file = """{ 40"text": "test\\ncontents\\n%d", 41"url": "%s", 42"sourceMapURL":"%s" 43}""" 44 45 scripts_dir = None 46 expected_files = [] 47 48 try: 49 scripts_dir = tempfile.mkdtemp() 50 for i, test_script in enumerate(test_files): 51 file_path = os.path.join(scripts_dir, '%d.js.json' % i) 52 53 source_map = "" 54 if test_script['exists']: 55 # Create an inline sourcemap with just the required keys. 56 source_map_data_url = base64.b64encode( 57 json.dumps({ 58 "sources": 59 [os.path.join(*test_script['location'])], 60 "sourceRoot": 61 "" 62 }).encode('utf-8')) 63 64 source_map = 'data:application/json;base64,' + \ 65 source_map_data_url.decode('utf-8') 66 67 with open(file_path, 'w') as f: 68 f.write(test_script_file % 69 (i, test_script['url'], source_map)) 70 71 expected_files.append(file_path) 72 if test_script['exists']: 73 expected_files.append( 74 os.path.join(scripts_dir, 'parsed_scripts', 75 *test_script['location'])) 76 77 if len(expected_files) > 0: 78 expected_files.append( 79 os.path.join(scripts_dir, 'parsed_scripts', 80 'parsed_scripts.json')) 81 82 merger.write_parsed_scripts(scripts_dir, source_dir='') 83 actual_files = [] 84 85 for root, _, files in os.walk(scripts_dir): 86 for file_name in files: 87 actual_files.append(os.path.join(root, file_name)) 88 89 self.assertCountEqual(expected_files, actual_files) 90 finally: 91 shutil.rmtree(scripts_dir) 92 93 def test_write_parsed_scripts_negative_cases(self): 94 test_files = [{ 95 'url': '//a/b/c/1.js', 96 'contents': """{ 97"url": "%s" 98}""" 99 }, { 100 'url': '//d/e/f/1.js', 101 'contents': """{ 102"text": "test\\ncontents\\n%s" 103}""" 104 }] 105 106 scripts_dir = None 107 expected_files = [] 108 try: 109 scripts_dir = tempfile.mkdtemp() 110 for i, test_script in enumerate(test_files): 111 file_path = os.path.join(scripts_dir, '%d.js.json' % i) 112 expected_files.append(file_path) 113 with open(file_path, 'w') as f: 114 f.write(test_script['contents'] % test_script['url']) 115 116 merger.write_parsed_scripts(scripts_dir) 117 118 actual_files = [] 119 for root, _, files in os.walk(scripts_dir): 120 for file_name in files: 121 actual_files.append(os.path.join(root, file_name)) 122 123 self.assertCountEqual(expected_files, actual_files) 124 finally: 125 shutil.rmtree(scripts_dir) 126 127 def test_trailing_curly_brace_stripped(self): 128 test_script_file = """{ 129 "text":"test\\ncontents\\n0", 130 "url":"//a/b/c/1.js", 131 "sourceMapURL":"data:application/json;base64,eyJzb3VyY2VzIjogWyJhL2IvYy8xLmpzIl0sICJzb3VyY2VSb290IjogIiJ9" 132}}""" 133 134 scripts_dir = None 135 136 try: 137 scripts_dir = tempfile.mkdtemp() 138 file_path = os.path.join(scripts_dir, '0.js.json') 139 with open(file_path, 'w') as f: 140 f.write(test_script_file) 141 expected_files = [ 142 file_path, 143 os.path.join(scripts_dir, 'parsed_scripts', 'a', 'b', 'c', 144 '1.js'), 145 os.path.join(scripts_dir, 'parsed_scripts', 146 'parsed_scripts.json') 147 ] 148 149 merger.write_parsed_scripts(scripts_dir, source_dir='') 150 actual_files = [] 151 152 for root, _, files in os.walk(scripts_dir): 153 for file_name in files: 154 actual_files.append(os.path.join(root, file_name)) 155 156 self.assertCountEqual(expected_files, actual_files) 157 finally: 158 shutil.rmtree(scripts_dir) 159 160 def test_non_data_urls_are_ignored(self): 161 test_script_file = """{ 162"text": "test\\ncontents", 163"url": "http://test_url", 164"sourceMapURL":"%s" 165}""" 166 167 scripts_dir = None 168 expected_files = [] 169 170 try: 171 scripts_dir = tempfile.mkdtemp() 172 file_path = os.path.join(scripts_dir, 'external_map.js.json') 173 expected_files = [file_path] 174 175 # Write a script with an external URL as the sourcemap, this should 176 # exclude it from being written to disk. 177 with open(file_path, 'w') as f: 178 f.write(test_script_file % 'external.map') 179 180 merger.write_parsed_scripts(scripts_dir, source_dir='') 181 actual_files = [] 182 183 for root, _, files in os.walk(scripts_dir): 184 for file_name in files: 185 actual_files.append(os.path.join(root, file_name)) 186 187 self.assertCountEqual(expected_files, actual_files) 188 finally: 189 shutil.rmtree(scripts_dir) 190 191 def test_uninteresting_lines_are_excluded(self): 192 """This contrived istanbul coverage file represents the coverage from 193 the following example file: 194 """ 195 example_test_file = """// Copyright 2019 The Chromium Authors 196// Use of this source code is governed by a BSD-style license that can be 197// found in the LICENSE file. 198 199import './iframe.js'; 200 201/* 202 * function comment should be excluded. 203 */ 204export const add = (a, b) => a + b; // should not be excluded 205 206/* should be excluded */ 207 208""" 209 210 test_istanbul_file = """{ 211"%s":{ 212 "path":"%s", 213 "all":false, 214 "statementMap":{ 215 "1":{"start":{"line":1,"column":0},"end":{"line":1,"column":38}}, 216 "2":{"start":{"line":2,"column":0},"end":{"line":2,"column":73}}, 217 "3":{"start":{"line":3,"column":0},"end":{"line":3,"column":29}}, 218 "4":{"start":{"line":4,"column":0},"end":{"line":4,"column":0}}, 219 "5":{"start":{"line":5,"column":0},"end":{"line":5,"column":21}}, 220 "6":{"start":{"line":6,"column":0},"end":{"line":6,"column":0}}, 221 "7":{"start":{"line":7,"column":0},"end":{"line":7,"column":2}}, 222 "8":{"start":{"line":8,"column":0},"end":{"line":8,"column":39}}, 223 "9":{"start":{"line":9,"column":0},"end":{"line":9,"column":3}}, 224 "10":{"start":{"line":10,"column":0},"end":{"line":10,"column":61}}, 225 "11":{"start":{"line":11,"column":0},"end":{"line":11,"column":0}}, 226 "12":{"start":{"line":12,"column":0},"end":{"line":12,"column":24}}, 227 "13":{"start":{"line":13,"column":0},"end":{"line":13,"column":0}} 228 }, 229 "s":{ 230 "1": 1, 231 "2": 1, 232 "3": 1, 233 "4": 1, 234 "5": 1, 235 "6": 1, 236 "7": 1, 237 "8": 1, 238 "9": 1, 239 "10": 1, 240 "11": 1, 241 "12": 1, 242 "13": 1 243 } 244} 245 }""" 246 247 expected_output_file = """{ 248 "%s": { 249 "path": "%s", 250 "all": false, 251 "statementMap": { 252 "10": { 253 "start": { 254 "line": 10, 255 "column": 0 256 }, 257 "end": { 258 "line": 10, 259 "column": 61 260 } 261 } 262 }, 263 "s": { 264 "10": 1 265 } 266 } 267 }""" 268 269 try: 270 test_dir = tempfile.mkdtemp() 271 file_path = os.path.join(test_dir, 272 'coverage.json').replace('\\', '/') 273 example_test_file_path = os.path.join(test_dir, 274 'fileA.js').replace( 275 '\\', '/') 276 expected_output = json.loads( 277 expected_output_file % 278 (example_test_file_path, example_test_file_path)) 279 280 # Set up the tests files so that exclusions can be performed. 281 with open(file_path, 'w') as f: 282 f.write(test_istanbul_file % 283 (example_test_file_path, example_test_file_path)) 284 with open(example_test_file_path, 'w') as f: 285 f.write(example_test_file) 286 287 # Perform the exclusion. 288 merger.exclude_uninteresting_lines(file_path) 289 290 # Assert the final `coverage.json` file matches the expected output. 291 with open(file_path, 'rb') as f: 292 coverage_json = json.load(f) 293 self.assertEqual(coverage_json, expected_output) 294 295 finally: 296 shutil.rmtree(test_dir) 297 298 def test_paths_are_remapped_and_removed(self): 299 test_file_data = """{ 300 "/path/to/checkout/chrome/browser/fileA.js": { 301 "path": "/path/to/checkout/chrome/browser/fileA.js" 302 }, 303 "/path/to/checkout/out/dir/chrome/browser/fileB.js": { 304 "path": "/path/to/checkout/out/dir/chrome/browser/fileB.js" 305 }, 306 "/some/random/path/fileC.js": { 307 "path": "/some/random/path/fileC.js" 308 } 309 }""" 310 311 expected_after_remap = { 312 "chrome/browser/fileA.js": { 313 "path": "chrome/browser/fileA.js" 314 } 315 } 316 317 try: 318 test_dir = tempfile.mkdtemp() 319 coverage_file_path = os.path.join(test_dir, 320 'coverage.json').replace( 321 '\\', '/') 322 323 with open(coverage_file_path, 'w', encoding='utf-8', 324 newline='') as f: 325 f.write(test_file_data) 326 327 merger.remap_paths_to_relative(coverage_file_path, 328 "/path/to/checkout", 329 "/path/to/checkout/out/dir") 330 331 with open(coverage_file_path, 'rb') as f: 332 coverage_json = json.load(f) 333 self.assertEqual(coverage_json, expected_after_remap) 334 335 finally: 336 shutil.rmtree(test_dir) 337 338 @parameterized.expand([ 339 ('// test', True), 340 ('/* test', True), 341 ('*/ test', True), 342 (' * test', True), 343 ('import test', True), 344 (' x = 5 /* comment */', False), 345 ('x = 5', False), 346 ]) 347 def test_should_exclude(self, line, exclude): 348 self.assertEqual(merger.should_exclude(line), exclude) 349 350 351if __name__ == '__main__': 352 unittest.main() 353