• 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.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