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.object(merger, '_validate_and_convert_profraws') 83 def test_merge_profraw(self, mock_validate_and_convert_profraws): 84 mock_input_dir_walk = [ 85 ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), 86 ('/b/some/path/0', [], 87 ['output.json', 'default-1.profraw', 'default-2.profraw']), 88 ('/b/some/path/1', [], 89 ['output.json', 'default-1.profraw', 'default-2.profraw']), 90 ] 91 92 mock_validate_and_convert_profraws.return_value = [ 93 '/b/some/path/0/default-1.profdata', 94 '/b/some/path/1/default-2.profdata', 95 ], [ 96 '/b/some/path/0/default-2.profraw', 97 '/b/some/path/1/default-1.profraw', 98 ], [ 99 '/b/some/path/1/default-1.profraw', 100 ] 101 102 with mock.patch.object(os, 'walk') as mock_walk: 103 with mock.patch.object(os, 'remove'): 104 mock_walk.return_value = mock_input_dir_walk 105 with mock.patch.object(subprocess, 'run') as mock_exec_cmd: 106 merger.merge_profiles('/b/some/path', 'output/dir/default.profdata', 107 '.profraw', 'llvm-profdata') 108 self.assertEqual( 109 mock.call( 110 [ 111 'llvm-profdata', 112 'merge', 113 '-o', 114 'output/dir/default.profdata', 115 '/b/some/path/0/default-1.profdata', 116 '/b/some/path/1/default-2.profdata', 117 ], 118 capture_output=True, 119 check=True, 120 text=True, 121 timeout=3600 122 ), mock_exec_cmd.call_args) 123 124 self.assertTrue(mock_validate_and_convert_profraws.called) 125 126 @mock.patch.object(merger, '_validate_and_convert_profraws') 127 def test_profraw_skip_validation(self, mock_validate_and_convert_profraws): 128 mock_input_dir_walk = [ 129 ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), 130 ('/b/some/path/0', [], 131 ['output.json', 'default-1.profraw', 'default-2.profraw']), 132 ('/b/some/path/1', [], 133 ['output.json', 'default-1.profraw', 'default-2.profraw']), 134 ] 135 136 with mock.patch.object(os, 'walk') as mock_walk: 137 with mock.patch.object(os, 'remove'): 138 mock_walk.return_value = mock_input_dir_walk 139 with mock.patch.object(subprocess, 'run') as mock_exec_cmd: 140 merger.merge_profiles('/b/some/path', 141 'output/dir/default.profdata', 142 '.profraw', 143 'llvm-profdata', 144 skip_validation=True) 145 self.assertEqual( 146 mock.call( 147 [ 148 'llvm-profdata', 149 'merge', 150 '-o', 151 'output/dir/default.profdata', 152 '/b/some/path/0/default-1.profraw', 153 '/b/some/path/0/default-2.profraw', 154 '/b/some/path/1/default-1.profraw', 155 '/b/some/path/1/default-2.profraw' 156 ], 157 capture_output=True, 158 check=True, 159 text=True, 160 timeout=3600 161 ), mock_exec_cmd.call_args) 162 163 # Skip validation should've passed all profraw files directly, and 164 # this validate call should not have been invoked. 165 self.assertFalse(mock_validate_and_convert_profraws.called) 166 167 168 def test_merge_profraw_skip_if_there_is_no_file(self): 169 mock_input_dir_walk = [ 170 ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), 171 ] 172 173 with mock.patch.object(os, 'walk') as mock_walk: 174 mock_walk.return_value = mock_input_dir_walk 175 with mock.patch.object(subprocess, 'check_call') as mock_exec_cmd: 176 merger.merge_profiles('/b/some/path', 'output/dir/default.profdata', 177 '.profraw', 'llvm-profdata') 178 self.assertFalse(mock_exec_cmd.called) 179 180 181 @mock.patch.object(merger, '_validate_and_convert_profraws') 182 def test_merge_profdata(self, mock_validate_and_convert_profraws): 183 mock_input_dir_walk = [ 184 ('/b/some/path', ['base_unittests', 'url_unittests'], ['summary.json']), 185 ('/b/some/path/base_unittests', [], ['output.json', 186 'default.profdata']), 187 ('/b/some/path/url_unittests', [], ['output.json', 'default.profdata']), 188 ] 189 with mock.patch.object(os, 'walk') as mock_walk: 190 with mock.patch.object(os, 'remove'): 191 mock_walk.return_value = mock_input_dir_walk 192 with mock.patch.object(subprocess, 'run') as mock_exec_cmd: 193 merger.merge_profiles('/b/some/path', 'output/dir/default.profdata', 194 '.profdata', 'llvm-profdata') 195 self.assertEqual( 196 mock.call( 197 [ 198 'llvm-profdata', 199 'merge', 200 '-o', 201 'output/dir/default.profdata', 202 '/b/some/path/base_unittests/default.profdata', 203 '/b/some/path/url_unittests/default.profdata', 204 ], 205 capture_output=True, 206 check=True, 207 text=True, 208 timeout=3600 209 ), mock_exec_cmd.call_args) 210 211 # The mock method should only apply when merging .profraw files. 212 self.assertFalse(mock_validate_and_convert_profraws.called) 213 214 @mock.patch.object(merger, '_validate_and_convert_profraws') 215 def test_merge_profdata_pattern(self, mock_validate_and_convert_profraws): 216 mock_input_dir_walk = [ 217 ('/b/some/path', ['base_unittests', 'url_unittests'], ['summary.json']), 218 ('/b/some/path/base_unittests', [], ['output.json', 219 'base_unittests.profdata']), 220 ('/b/some/path/url_unittests', [], ['output.json', 221 'url_unittests.profdata'],), 222 ('/b/some/path/ios_chrome_smoke_eg2tests', 223 [], ['output.json','ios_chrome_smoke_eg2tests.profdata'],), 224 ] 225 with mock.patch.object(os, 'walk') as mock_walk: 226 with mock.patch.object(os, 'remove'): 227 mock_walk.return_value = mock_input_dir_walk 228 with mock.patch.object(subprocess, 'run') as mock_exec_cmd: 229 input_profdata_filename_pattern = '.+_unittests\.profdata' 230 merger.merge_profiles('/b/some/path', 231 'output/dir/default.profdata', 232 '.profdata', 233 'llvm-profdata', 234 input_profdata_filename_pattern) 235 self.assertEqual( 236 mock.call( 237 [ 238 'llvm-profdata', 239 'merge', 240 '-o', 241 'output/dir/default.profdata', 242 '/b/some/path/base_unittests/base_unittests.profdata', 243 '/b/some/path/url_unittests/url_unittests.profdata', 244 ], 245 capture_output=True, 246 check=True, 247 text=True, 248 timeout=3600 249 ), mock_exec_cmd.call_args) 250 251 # The mock method should only apply when merging .profraw files. 252 self.assertFalse(mock_validate_and_convert_profraws.called) 253 254 @mock.patch('merge_lib._JAVA_PATH', 'java') 255 def test_merge_java_exec_files(self): 256 mock_input_dir_walk = [ 257 ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), 258 ('/b/some/path/0', [], 259 ['output.json', 'default-1.exec', 'default-2.exec']), 260 ('/b/some/path/1', [], 261 ['output.json', 'default-3.exec', 'default-4.exec']), 262 ] 263 264 with mock.patch.object(os, 'walk') as mock_walk: 265 mock_walk.return_value = mock_input_dir_walk 266 with mock.patch.object(subprocess, 'check_call') as mock_exec_cmd: 267 merger.merge_java_exec_files( 268 '/b/some/path', 'output/path', 'path/to/jacococli.jar') 269 self.assertEqual( 270 mock.call( 271 [ 272 'java', 273 '-jar', 274 'path/to/jacococli.jar', 275 'merge', 276 '/b/some/path/0/default-1.exec', 277 '/b/some/path/0/default-2.exec', 278 '/b/some/path/1/default-3.exec', 279 '/b/some/path/1/default-4.exec', 280 '--destfile', 281 'output/path', 282 ], 283 stderr=-2 284 ), mock_exec_cmd.call_args) 285 286 def test_merge_java_exec_files_if_there_is_no_file(self): 287 mock_input_dir_walk = [ 288 ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']), 289 ] 290 291 with mock.patch.object(os, 'walk') as mock_walk: 292 mock_walk.return_value = mock_input_dir_walk 293 with mock.patch.object(subprocess, 'check_call') as mock_exec_cmd: 294 merger.merge_java_exec_files( 295 '/b/some/path', 'output/path', 'path/to/jacococli.jar') 296 self.assertFalse(mock_exec_cmd.called) 297 298 def test_calls_merge_js_results_script(self): 299 task_output_dir = 'some/task/output/dir' 300 profdata_dir = '/some/different/path/to/profdata/default.profdata' 301 302 args = [ 303 'script_name', '--output-json', 'output.json', '--task-output-dir', 304 task_output_dir, '--profdata-dir', profdata_dir, '--llvm-profdata', 305 'llvm-profdata', 'a.json', 'b.json', 'c.json', '--test-target-name', 306 'v8_unittests', '--sparse', 307 '--javascript-coverage-dir', 'output/dir/devtools_code_coverage', 308 '--chromium-src-dir', 'chromium/src', '--build-dir', 'output/dir' 309 ] 310 with mock.patch.object(merger, 'merge_profiles') as mock_merge: 311 mock_merge.return_value = None, None 312 with mock.patch.object(sys, 'argv', args): 313 with mock.patch.object(subprocess, 'call') as mock_exec_cmd: 314 with mock.patch.object(os.path, 'join') as mock_os_path_join: 315 mock_merge_js_results_path = 'path/to/js/merge_js_results.py' 316 mock_os_path_join.return_value = mock_merge_js_results_path 317 python_exec = sys.executable 318 merge_results.main() 319 320 mock_exec_cmd.assert_called_with( 321 [python_exec, mock_merge_js_results_path, '--task-output-dir', 322 task_output_dir, '--javascript-coverage-dir', 323 'output/dir/devtools_code_coverage', '--chromium-src-dir', 324 'chromium/src', '--build-dir', 'output/dir']) 325 326 def test_argparse_sparse(self): 327 """Ensure that sparse flag defaults to true, and is set to correct value""" 328 # Basic required args 329 build_properties = json.dumps({ 330 'some': { 331 'complicated': ['nested', { 332 'json': None, 333 'object': 'thing', 334 }] 335 } 336 }) 337 task_output_dir = 'some/task/output/dir' 338 profdata_dir = '/some/different/path/to/profdata/default.profdata' 339 profdata_file = os.path.join(profdata_dir, 'base_unittests.profdata') 340 args = [ 341 'script_name', '--output-json', 'output.json', '--build-properties', 342 build_properties, '--summary-json', 'summary.json', '--task-output-dir', 343 task_output_dir, '--profdata-dir', profdata_dir, '--llvm-profdata', 344 'llvm-profdata', 'a.json', 'b.json', 'c.json', '--test-target-name', 345 'base_unittests' 346 ] 347 348 test_scenarios = [ 349 { 350 # Base set of args should set --sparse to false by default 351 'args': None, 352 'expected_outcome': False, 353 }, 354 { 355 # Sparse should parse True when only --sparse is specified 356 'args': ['--sparse'], 357 'expected_outcome': True, 358 } 359 ] 360 361 for scenario in test_scenarios: 362 args = copy.deepcopy(args) 363 additional_args = scenario['args'] 364 if additional_args: 365 args.extend(additional_args) 366 expected_outcome = scenario['expected_outcome'] 367 368 with mock.patch.object(merger, 'merge_profiles') as mock_merge: 369 mock_merge.return_value = None, None 370 with mock.patch.object(sys, 'argv', args): 371 merge_results.main() 372 self.assertEqual( 373 mock_merge.call_args, 374 mock.call(task_output_dir, profdata_file, '.profraw', 375 'llvm-profdata', sparse=expected_outcome, 376 skip_validation=False), None) 377 378 379if __name__ == '__main__': 380 unittest.main() 381