1#!/usr/bin/env vpython3 2# Copyright 2019 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 copy 7import json 8import os 9import subprocess 10import sys 11import unittest 12 13import mock 14 15import merge_results 16import merge_steps 17import merge_lib as merger 18 19 20class MergeProfilesTest(unittest.TestCase): 21 22 # pylint: disable=super-with-arguments 23 def __init__(self, *args, **kwargs): 24 super(MergeProfilesTest, self).__init__(*args, **kwargs) 25 self.maxDiff = None 26 # pylint: enable=super-with-arguments 27 28 def test_merge_script_api_parameters(self): 29 """Test the step-level merge front-end.""" 30 build_properties = json.dumps({ 31 'some': { 32 'complicated': ['nested', { 33 'json': None, 34 'object': 'thing', 35 }] 36 } 37 }) 38 task_output_dir = 'some/task/output/dir' 39 profdata_dir = '/some/different/path/to/profdata/default.profdata' 40 profdata_file = os.path.join(profdata_dir, 'base_unittests.profdata') 41 args = [ 42 'script_name', '--output-json', 'output.json', '--build-properties', 43 build_properties, '--summary-json', 'summary.json', '--task-output-dir', 44 task_output_dir, '--profdata-dir', profdata_dir, '--llvm-profdata', 45 'llvm-profdata', 'a.json', 'b.json', 'c.json', '--test-target-name', 46 'base_unittests', '--sparse' 47 ] 48 with mock.patch.object(merger, 'merge_profiles') as mock_merge: 49 mock_merge.return_value = None, None 50 with mock.patch.object(sys, 'argv', args): 51 merge_results.main() 52 self.assertEqual( 53 mock_merge.call_args, 54 mock.call(task_output_dir, profdata_file, '.profraw', 55 'llvm-profdata', sparse=True, 56 skip_validation=False), None) 57 58 def test_merge_steps_parameters(self): 59 """Test the build-level merge front-end.""" 60 input_dir = 'some/task/output/dir' 61 output_file = '/some/different/path/to/profdata/merged.profdata' 62 args = [ 63 'script_name', 64 '--input-dir', 65 input_dir, 66 '--output-file', 67 output_file, 68 '--llvm-profdata', 69 'llvm-profdata', 70 '--profdata-filename-pattern', 71 '.*' 72 ] 73 with mock.patch.object(merger, 'merge_profiles') as mock_merge: 74 mock_merge.return_value = [], [] 75 with mock.patch.object(sys, 'argv', args): 76 merge_steps.main() 77 self.assertEqual( 78 mock_merge.call_args, 79 mock.call(input_dir, output_file, '.profdata', 'llvm-profdata', 80 '.*', sparse=False, merge_timeout=3600)) 81 82 @mock.patch('builtins.open', new_callable=mock.mock_open()) 83 @mock.patch.object(merger, '_validate_and_convert_profraws') 84 def test_merge_profraw(self, mock_validate_and_convert_profraws, 85 mock_file_open): 86 mock_input_dir_walk = [ 87 ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), 88 ('/b/some/path/0', [], 89 ['output.json', 'default-1.profraw', 'default-2.profraw']), 90 ('/b/some/path/1', [], 91 ['output.json', 'default-1.profraw', 'default-2.profraw']), 92 ] 93 94 mock_validate_and_convert_profraws.return_value = [ 95 '/b/some/path/0/default-1.profdata', 96 '/b/some/path/1/default-2.profdata', 97 ], [ 98 '/b/some/path/0/default-2.profraw', 99 '/b/some/path/1/default-1.profraw', 100 ], [ 101 '/b/some/path/1/default-1.profraw', 102 ] 103 104 with mock.patch.object(os, 'walk') as mock_walk: 105 with mock.patch.object(os, 'remove'): 106 mock_walk.return_value = mock_input_dir_walk 107 with mock.patch.object(subprocess, 'run') as mock_exec_cmd: 108 merger.merge_profiles('/b/some/path', 'output/dir/default.profdata', 109 '.profraw', 'llvm-profdata') 110 self.assertEqual( 111 mock.call( 112 [ 113 'llvm-profdata', 114 'merge', 115 '-o', 116 'output/dir/default.profdata', 117 '-f', 118 'output/dir/input-profdata-files.txt', 119 ], 120 capture_output=True, 121 check=True, 122 text=True, 123 timeout=3600 124 ), mock_exec_cmd.call_args) 125 context = mock_file_open() 126 self.assertEqual(context.__enter__().write.call_count, 2) 127 context.__enter__().write.assert_any_call( 128 '/b/some/path/0/default-1.profdata\n') 129 context.__enter__().write.assert_any_call( 130 '/b/some/path/1/default-2.profdata\n') 131 132 self.assertTrue(mock_validate_and_convert_profraws.called) 133 134 @mock.patch('builtins.open', new_callable=mock.mock_open()) 135 @mock.patch.object(merger, '_validate_and_convert_profraws') 136 def test_profraw_skip_validation(self, mock_validate_and_convert_profraws, 137 mock_file_open): 138 mock_input_dir_walk = [ 139 ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), 140 ('/b/some/path/0', [], 141 ['output.json', 'default-1.profraw', 'default-2.profraw']), 142 ('/b/some/path/1', [], 143 ['output.json', 'default-1.profraw', 'default-2.profraw']), 144 ] 145 146 with mock.patch.object(os, 'walk') as mock_walk: 147 with mock.patch.object(os, 'remove'): 148 mock_walk.return_value = mock_input_dir_walk 149 with mock.patch.object(subprocess, 'run') as mock_exec_cmd: 150 merger.merge_profiles('/b/some/path', 151 'output/dir/default.profdata', 152 '.profraw', 153 'llvm-profdata', 154 skip_validation=True) 155 self.assertEqual( 156 mock.call( 157 [ 158 'llvm-profdata', 159 'merge', 160 '-o', 161 'output/dir/default.profdata', 162 '-f', 163 'output/dir/input-profdata-files.txt', 164 ], 165 capture_output=True, 166 check=True, 167 text=True, 168 timeout=3600 169 ), mock_exec_cmd.call_args) 170 context = mock_file_open() 171 self.assertEqual(context.__enter__().write.call_count, 4) 172 context.__enter__().write.assert_any_call( 173 '/b/some/path/0/default-1.profraw\n') 174 context.__enter__().write.assert_any_call( 175 '/b/some/path/0/default-2.profraw\n') 176 context.__enter__().write.assert_any_call( 177 '/b/some/path/1/default-1.profraw\n') 178 context.__enter__().write.assert_any_call( 179 '/b/some/path/1/default-2.profraw\n') 180 181 # Skip validation should've passed all profraw files directly, and 182 # this validate call should not have been invoked. 183 self.assertFalse(mock_validate_and_convert_profraws.called) 184 185 186 def test_merge_profraw_skip_if_there_is_no_file(self): 187 mock_input_dir_walk = [ 188 ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), 189 ] 190 191 with mock.patch.object(os, 'walk') as mock_walk: 192 mock_walk.return_value = mock_input_dir_walk 193 with mock.patch.object(subprocess, 'check_call') as mock_exec_cmd: 194 merger.merge_profiles('/b/some/path', 'output/dir/default.profdata', 195 '.profraw', 'llvm-profdata') 196 self.assertFalse(mock_exec_cmd.called) 197 198 199 @mock.patch('builtins.open', new_callable=mock.mock_open()) 200 @mock.patch.object(merger, '_validate_and_convert_profraws') 201 def test_merge_profdata(self, mock_validate_and_convert_profraws, 202 mock_file_open): 203 mock_input_dir_walk = [ 204 ('/b/some/path', ['base_unittests', 'url_unittests'], ['summary.json']), 205 ('/b/some/path/base_unittests', [], ['output.json', 206 'default.profdata']), 207 ('/b/some/path/url_unittests', [], ['output.json', 'default.profdata']), 208 ] 209 with mock.patch.object(os, 'walk') as mock_walk: 210 with mock.patch.object(os, 'remove'): 211 mock_walk.return_value = mock_input_dir_walk 212 with mock.patch.object(subprocess, 'run') as mock_exec_cmd: 213 merger.merge_profiles('/b/some/path', 'output/dir/default.profdata', 214 '.profdata', 'llvm-profdata') 215 self.assertEqual( 216 mock.call( 217 [ 218 'llvm-profdata', 219 'merge', 220 '-o', 221 'output/dir/default.profdata', 222 '-f', 223 'output/dir/input-profdata-files.txt', 224 ], 225 capture_output=True, 226 check=True, 227 text=True, 228 timeout=3600 229 ), mock_exec_cmd.call_args) 230 context = mock_file_open() 231 self.assertEqual(context.__enter__().write.call_count, 2) 232 context.__enter__().write.assert_any_call( 233 '/b/some/path/base_unittests/default.profdata\n') 234 context.__enter__().write.assert_any_call( 235 '/b/some/path/url_unittests/default.profdata\n') 236 237 # The mock method should only apply when merging .profraw files. 238 self.assertFalse(mock_validate_and_convert_profraws.called) 239 240 @mock.patch('builtins.open', new_callable=mock.mock_open()) 241 @mock.patch.object(merger, '_validate_and_convert_profraws') 242 def test_merge_profdata_pattern(self, mock_validate_and_convert_profraws, 243 mock_file_open): 244 mock_input_dir_walk = [ 245 ('/b/some/path', ['base_unittests', 'url_unittests'], ['summary.json']), 246 ('/b/some/path/base_unittests', [], ['output.json', 247 'base_unittests.profdata']), 248 ('/b/some/path/url_unittests', [], ['output.json', 249 'url_unittests.profdata'],), 250 ('/b/some/path/ios_chrome_smoke_eg2tests', 251 [], ['output.json','ios_chrome_smoke_eg2tests.profdata'],), 252 ] 253 with mock.patch.object(os, 'walk') as mock_walk: 254 with mock.patch.object(os, 'remove'): 255 mock_walk.return_value = mock_input_dir_walk 256 with mock.patch.object(subprocess, 'run') as mock_exec_cmd: 257 input_profdata_filename_pattern = '.+_unittests\.profdata' 258 merger.merge_profiles('/b/some/path', 259 'output/dir/default.profdata', 260 '.profdata', 261 'llvm-profdata', 262 input_profdata_filename_pattern) 263 self.assertEqual( 264 mock.call( 265 [ 266 'llvm-profdata', 267 'merge', 268 '-o', 269 'output/dir/default.profdata', 270 '-f', 271 'output/dir/input-profdata-files.txt', 272 ], 273 capture_output=True, 274 check=True, 275 text=True, 276 timeout=3600 277 ), mock_exec_cmd.call_args) 278 context = mock_file_open() 279 self.assertEqual(context.__enter__().write.call_count, 2) 280 context.__enter__().write.assert_any_call( 281 '/b/some/path/base_unittests/base_unittests.profdata\n') 282 context.__enter__().write.assert_any_call( 283 '/b/some/path/url_unittests/url_unittests.profdata\n') 284 285 # The mock method should only apply when merging .profraw files. 286 self.assertFalse(mock_validate_and_convert_profraws.called) 287 288 @mock.patch('merge_lib._JAVA_PATH', 'java') 289 def test_merge_java_exec_files(self): 290 mock_input_dir_walk = [ 291 ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), 292 ('/b/some/path/0', [], 293 ['output.json', 'default-1.exec', 'default-2.exec']), 294 ('/b/some/path/1', [], 295 ['output.json', 'default-3.exec', 'default-4.exec']), 296 ] 297 298 with mock.patch.object(os, 'walk') as mock_walk: 299 mock_walk.return_value = mock_input_dir_walk 300 with mock.patch.object(subprocess, 'check_call') as mock_exec_cmd: 301 merger.merge_java_exec_files( 302 '/b/some/path', 'output/path', 'path/to/jacococli.jar') 303 self.assertEqual( 304 mock.call( 305 [ 306 'java', 307 '-jar', 308 'path/to/jacococli.jar', 309 'merge', 310 '/b/some/path/0/default-1.exec', 311 '/b/some/path/0/default-2.exec', 312 '/b/some/path/1/default-3.exec', 313 '/b/some/path/1/default-4.exec', 314 '--destfile', 315 'output/path', 316 ], 317 stderr=-2 318 ), mock_exec_cmd.call_args) 319 320 def test_merge_java_exec_files_if_there_is_no_file(self): 321 mock_input_dir_walk = [ 322 ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), 323 ] 324 325 with mock.patch.object(os, 'walk') as mock_walk: 326 mock_walk.return_value = mock_input_dir_walk 327 with mock.patch.object(subprocess, 'check_call') as mock_exec_cmd: 328 merger.merge_java_exec_files( 329 '/b/some/path', 'output/path', 'path/to/jacococli.jar') 330 self.assertFalse(mock_exec_cmd.called) 331 332 def test_calls_merge_js_results_script(self): 333 task_output_dir = 'some/task/output/dir' 334 profdata_dir = '/some/different/path/to/profdata/default.profdata' 335 336 args = [ 337 'script_name', '--output-json', 'output.json', '--task-output-dir', 338 task_output_dir, '--profdata-dir', profdata_dir, '--llvm-profdata', 339 'llvm-profdata', 'a.json', 'b.json', 'c.json', '--test-target-name', 340 'v8_unittests', '--sparse', 341 '--javascript-coverage-dir', 'output/dir/devtools_code_coverage', 342 '--chromium-src-dir', 'chromium/src', '--build-dir', 'output/dir' 343 ] 344 with mock.patch.object(merger, 'merge_profiles') as mock_merge: 345 mock_merge.return_value = None, None 346 with mock.patch.object(sys, 'argv', args): 347 with mock.patch.object(subprocess, 'call') as mock_exec_cmd: 348 with mock.patch.object(os.path, 'join') as mock_os_path_join: 349 mock_merge_js_results_path = 'path/to/js/merge_js_results.py' 350 mock_os_path_join.return_value = mock_merge_js_results_path 351 python_exec = sys.executable 352 merge_results.main() 353 354 mock_exec_cmd.assert_called_with( 355 [python_exec, mock_merge_js_results_path, '--task-output-dir', 356 task_output_dir, '--javascript-coverage-dir', 357 'output/dir/devtools_code_coverage', '--chromium-src-dir', 358 'chromium/src', '--build-dir', 'output/dir']) 359 360 def test_argparse_sparse(self): 361 """Ensure that sparse flag defaults to true, and is set to correct value""" 362 # Basic required args 363 build_properties = json.dumps({ 364 'some': { 365 'complicated': ['nested', { 366 'json': None, 367 'object': 'thing', 368 }] 369 } 370 }) 371 task_output_dir = 'some/task/output/dir' 372 profdata_dir = '/some/different/path/to/profdata/default.profdata' 373 profdata_file = os.path.join(profdata_dir, 'base_unittests.profdata') 374 args = [ 375 'script_name', '--output-json', 'output.json', '--build-properties', 376 build_properties, '--summary-json', 'summary.json', '--task-output-dir', 377 task_output_dir, '--profdata-dir', profdata_dir, '--llvm-profdata', 378 'llvm-profdata', 'a.json', 'b.json', 'c.json', '--test-target-name', 379 'base_unittests' 380 ] 381 382 test_scenarios = [ 383 { 384 # Base set of args should set --sparse to false by default 385 'args': None, 386 'expected_outcome': False, 387 }, 388 { 389 # Sparse should parse True when only --sparse is specified 390 'args': ['--sparse'], 391 'expected_outcome': True, 392 } 393 ] 394 395 for scenario in test_scenarios: 396 args = copy.deepcopy(args) 397 additional_args = scenario['args'] 398 if additional_args: 399 args.extend(additional_args) 400 expected_outcome = scenario['expected_outcome'] 401 402 with mock.patch.object(merger, 'merge_profiles') as mock_merge: 403 mock_merge.return_value = None, None 404 with mock.patch.object(sys, 'argv', args): 405 merge_results.main() 406 self.assertEqual( 407 mock_merge.call_args, 408 mock.call(task_output_dir, profdata_file, '.profraw', 409 'llvm-profdata', sparse=expected_outcome, 410 skip_validation=False), None) 411 412 413if __name__ == '__main__': 414 unittest.main() 415