• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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