#!/usr/bin/env vpython3
# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import copy
import json
import os
import subprocess
import sys
import unittest
from unittest import mock
import merge_results
import merge_steps
import merge_lib as merger
class MergeProfilesTest(unittest.TestCase):
# pylint: disable=super-with-arguments
def __init__(self, *args, **kwargs):
super(MergeProfilesTest, self).__init__(*args, **kwargs)
self.maxDiff = None
# pylint: enable=super-with-arguments
def test_merge_script_api_parameters(self):
"""Test the step-level merge front-end."""
build_properties = json.dumps({
'some': {
'complicated': ['nested', {
'json': None,
'object': 'thing',
}]
}
})
task_output_dir = 'some/task/output/dir'
profdata_dir = '/some/different/path/to/profdata/default.profdata'
profdata_file = os.path.join(profdata_dir, 'base_unittests.profdata')
args = [
'script_name', '--output-json', 'output.json', '--build-properties',
build_properties, '--summary-json', 'summary.json', '--task-output-dir',
task_output_dir, '--profdata-dir', profdata_dir, '--llvm-profdata',
'llvm-profdata', 'a.json', 'b.json', 'c.json', '--test-target-name',
'base_unittests', '--sparse'
]
with mock.patch.object(merger, 'merge_profiles') as mock_merge:
mock_merge.return_value = None, None
with mock.patch.object(sys, 'argv', args):
merge_results.main()
self.assertEqual(
mock_merge.call_args,
mock.call(task_output_dir,
profdata_file,
'.profraw',
'llvm-profdata',
sparse=True,
skip_validation=False), None)
def test_merge_steps_parameters(self):
"""Test the build-level merge front-end."""
input_dir = 'some/task/output/dir'
output_file = '/some/different/path/to/profdata/merged.profdata'
args = [
'script_name', '--input-dir', input_dir, '--output-file', output_file,
'--llvm-profdata', 'llvm-profdata', '--profdata-filename-pattern', '.*'
]
with mock.patch.object(merger, 'merge_profiles') as mock_merge:
mock_merge.return_value = [], []
with mock.patch.object(sys, 'argv', args):
merge_steps.main()
self.assertEqual(
mock_merge.call_args,
mock.call(input_dir,
output_file,
'.profdata',
'llvm-profdata',
'.*',
sparse=False,
merge_timeout=3600,
weights={}))
@mock.patch('builtins.open', new_callable=mock.mock_open())
@mock.patch.object(merger, '_validate_and_convert_profraws')
def test_merge_profraw(self, mock_validate_and_convert_profraws,
mock_file_open):
mock_input_dir_walk = [
('/b/some/path', ['0', '1', '2', '3'], ['summary.json']),
('/b/some/path/0', [],
['output.json', 'default-1.profraw', 'default-2.profraw']),
('/b/some/path/1', [],
['output.json', 'default-1.profraw', 'default-2.profraw']),
]
mock_validate_and_convert_profraws.return_value = [
'/b/some/path/0/default-1.profdata',
'/b/some/path/1/default-2.profdata',
], [
'/b/some/path/0/default-2.profraw',
'/b/some/path/1/default-1.profraw',
], [
'/b/some/path/1/default-1.profraw',
]
with mock.patch.object(os, 'walk') as mock_walk:
with mock.patch.object(os, 'remove'):
mock_walk.return_value = mock_input_dir_walk
with mock.patch.object(subprocess, 'run') as mock_exec_cmd:
merger.merge_profiles('/b/some/path',
'output/dir/default.profdata',
'.profraw',
'llvm-profdata',
show_profdata=False)
self.assertEqual(
mock.call([
'llvm-profdata',
'merge',
'-o',
'output/dir/default.profdata',
'-f',
'output/dir/input-profdata-files.txt',
],
capture_output=True,
check=True,
text=True,
timeout=3600), mock_exec_cmd.call_args)
context = mock_file_open()
self.assertEqual(context.__enter__().write.call_count, 2)
context.__enter__().write.assert_any_call(
'/b/some/path/0/default-1.profdata\n')
context.__enter__().write.assert_any_call(
'/b/some/path/1/default-2.profdata\n')
self.assertTrue(mock_validate_and_convert_profraws.called)
@mock.patch('builtins.open', new_callable=mock.mock_open())
@mock.patch.object(merger, '_validate_and_convert_profraws')
def test_profraw_skip_validation(self, mock_validate_and_convert_profraws,
mock_file_open):
mock_input_dir_walk = [
('/b/some/path', ['0', '1', '2', '3'], ['summary.json']),
('/b/some/path/0', [],
['output.json', 'default-1.profraw', 'default-2.profraw']),
('/b/some/path/1', [],
['output.json', 'default-1.profraw', 'default-2.profraw']),
]
with mock.patch.object(os, 'walk') as mock_walk:
with mock.patch.object(os, 'remove'):
mock_walk.return_value = mock_input_dir_walk
with mock.patch.object(subprocess, 'run') as mock_exec_cmd:
merger.merge_profiles('/b/some/path',
'output/dir/default.profdata',
'.profraw',
'llvm-profdata',
skip_validation=True,
show_profdata=False)
self.assertEqual(
mock.call([
'llvm-profdata',
'merge',
'-o',
'output/dir/default.profdata',
'-f',
'output/dir/input-profdata-files.txt',
],
capture_output=True,
check=True,
text=True,
timeout=3600), mock_exec_cmd.call_args)
context = mock_file_open()
self.assertEqual(context.__enter__().write.call_count, 4)
context.__enter__().write.assert_any_call(
'/b/some/path/0/default-1.profraw\n')
context.__enter__().write.assert_any_call(
'/b/some/path/0/default-2.profraw\n')
context.__enter__().write.assert_any_call(
'/b/some/path/1/default-1.profraw\n')
context.__enter__().write.assert_any_call(
'/b/some/path/1/default-2.profraw\n')
# Skip validation should've passed all profraw files directly, and
# this validate call should not have been invoked.
self.assertFalse(mock_validate_and_convert_profraws.called)
def test_merge_profraw_skip_if_there_is_no_file(self):
mock_input_dir_walk = [
('/b/some/path', ['0', '1', '2', '3'], ['summary.json']),
]
with mock.patch.object(os, 'walk') as mock_walk:
mock_walk.return_value = mock_input_dir_walk
with mock.patch.object(subprocess, 'check_call') as mock_exec_cmd:
merger.merge_profiles('/b/some/path',
'output/dir/default.profdata',
'.profraw',
'llvm-profdata',
show_profdata=False)
self.assertFalse(mock_exec_cmd.called)
@mock.patch('builtins.open', new_callable=mock.mock_open())
@mock.patch.object(merger, '_validate_and_convert_profraws')
def test_merge_profdata(self, mock_validate_and_convert_profraws,
mock_file_open):
mock_input_dir_walk = [
('/b/some/path', ['base_unittests', 'url_unittests'], ['summary.json']),
('/b/some/path/base_unittests', [], ['output.json',
'default.profdata']),
('/b/some/path/url_unittests', [], ['output.json', 'default.profdata']),
]
with mock.patch.object(os, 'walk') as mock_walk:
with mock.patch.object(os, 'remove'):
mock_walk.return_value = mock_input_dir_walk
with mock.patch.object(subprocess, 'run') as mock_exec_cmd:
merger.merge_profiles('/b/some/path',
'output/dir/default.profdata',
'.profdata',
'llvm-profdata',
show_profdata=False)
self.assertEqual(
mock.call([
'llvm-profdata',
'merge',
'-o',
'output/dir/default.profdata',
'-f',
'output/dir/input-profdata-files.txt',
],
capture_output=True,
check=True,
text=True,
timeout=3600), mock_exec_cmd.call_args)
context = mock_file_open()
self.assertEqual(context.__enter__().write.call_count, 2)
context.__enter__().write.assert_any_call(
'/b/some/path/base_unittests/default.profdata\n')
context.__enter__().write.assert_any_call(
'/b/some/path/url_unittests/default.profdata\n')
# The mock method should only apply when merging .profraw files.
self.assertFalse(mock_validate_and_convert_profraws.called)
@mock.patch('builtins.open', new_callable=mock.mock_open())
@mock.patch.object(merger, '_validate_and_convert_profraws')
def test_merge_profdata_pattern(self, mock_validate_and_convert_profraws,
mock_file_open):
mock_input_dir_walk = [
('/b/some/path', ['base_unittests', 'url_unittests'], ['summary.json']),
('/b/some/path/base_unittests', [],
['output.json', 'base_unittests.profdata']),
(
'/b/some/path/url_unittests',
[],
['output.json', 'url_unittests.profdata'],
),
(
'/b/some/path/ios_chrome_smoke_eg2tests',
[],
['output.json', 'ios_chrome_smoke_eg2tests.profdata'],
),
]
with mock.patch.object(os, 'walk') as mock_walk:
with mock.patch.object(os, 'remove'):
mock_walk.return_value = mock_input_dir_walk
with mock.patch.object(subprocess, 'run') as mock_exec_cmd:
input_profdata_filename_pattern = r'.+_unittests\.profdata'
merger.merge_profiles('/b/some/path',
'output/dir/default.profdata',
'.profdata',
'llvm-profdata',
input_profdata_filename_pattern,
show_profdata=False)
self.assertEqual(
mock.call([
'llvm-profdata',
'merge',
'-o',
'output/dir/default.profdata',
'-f',
'output/dir/input-profdata-files.txt',
],
capture_output=True,
check=True,
text=True,
timeout=3600), mock_exec_cmd.call_args)
context = mock_file_open()
self.assertEqual(context.__enter__().write.call_count, 2)
context.__enter__().write.assert_any_call(
'/b/some/path/base_unittests/base_unittests.profdata\n')
context.__enter__().write.assert_any_call(
'/b/some/path/url_unittests/url_unittests.profdata\n')
# The mock method should only apply when merging .profraw files.
self.assertFalse(mock_validate_and_convert_profraws.called)
@mock.patch('builtins.open', new_callable=mock.mock_open())
@mock.patch.object(merger, '_validate_and_convert_profraws')
def test_merge_profiles_with_weights(self, mock_validate_and_convert_profraws,
mock_file_open):
mock_input_dir_walk = [
('/b/some/path', ['speedometer_benchmark', 'motionmark_benchmark'], []),
('/b/some/path/speedometer_benchmark', [], ['foo.profdata']),
('/b/some/path/motionmark_benchmark', [], ['foo.profdata']),
]
with mock.patch.object(os, 'walk') as mock_walk:
with mock.patch.object(os, 'remove'):
mock_walk.return_value = mock_input_dir_walk
with mock.patch.object(subprocess, 'run') as mock_exec_cmd:
merger.merge_profiles(
'/b/some/path',
'output/dir/default.profdata',
'.profdata',
'llvm-profdata',
'.*',
show_profdata=False,
weights={'speedometer_benchmark/foo.profdata': '3'})
self.assertEqual(
mock.call([
'llvm-profdata',
'merge',
'-o',
'output/dir/default.profdata',
'-f',
'output/dir/input-profdata-files.txt',
],
capture_output=True,
check=True,
text=True,
timeout=3600), mock_exec_cmd.call_args)
context = mock_file_open()
self.assertEqual(context.__enter__().write.call_count, 2)
context.__enter__().write.assert_any_call(
'3,/b/some/path/speedometer_benchmark/foo.profdata\n')
context.__enter__().write.assert_any_call(
'/b/some/path/motionmark_benchmark/foo.profdata\n')
# The mock method should only apply when merging .profraw files.
self.assertFalse(mock_validate_and_convert_profraws.called)
@mock.patch('merge_lib._JAVA_PATH', 'java')
def test_merge_java_exec_files(self):
mock_input_dir_walk = [
('/b/some/path', ['0', '1', '2', '3'], ['summary.json']),
('/b/some/path/0', [],
['output.json', 'default-1.exec', 'default-2.exec']),
('/b/some/path/1', [],
['output.json', 'default-3.exec', 'default-4.exec']),
]
with mock.patch.object(os, 'walk') as mock_walk:
mock_walk.return_value = mock_input_dir_walk
with mock.patch.object(subprocess, 'check_call') as mock_exec_cmd:
merger.merge_java_exec_files('/b/some/path', 'output/path',
'path/to/jacococli.jar')
self.assertEqual(
mock.call([
'java',
'-jar',
'path/to/jacococli.jar',
'merge',
'/b/some/path/0/default-1.exec',
'/b/some/path/0/default-2.exec',
'/b/some/path/1/default-3.exec',
'/b/some/path/1/default-4.exec',
'--destfile',
'output/path',
],
stderr=-2), mock_exec_cmd.call_args)
def test_merge_java_exec_files_if_there_is_no_file(self):
mock_input_dir_walk = [
('/b/some/path', ['0', '1', '2', '3'], ['summary.json']),
]
with mock.patch.object(os, 'walk') as mock_walk:
mock_walk.return_value = mock_input_dir_walk
with mock.patch.object(subprocess, 'check_call') as mock_exec_cmd:
merger.merge_java_exec_files('/b/some/path', 'output/path',
'path/to/jacococli.jar')
self.assertFalse(mock_exec_cmd.called)
def test_calls_merge_js_results_script(self):
task_output_dir = 'some/task/output/dir'
profdata_dir = '/some/different/path/to/profdata/default.profdata'
args = [
'script_name', '--output-json', 'output.json', '--task-output-dir',
task_output_dir, '--profdata-dir', profdata_dir, '--llvm-profdata',
'llvm-profdata', 'a.json', 'b.json', 'c.json', '--test-target-name',
'v8_unittests', '--sparse', '--javascript-coverage-dir',
'output/dir/devtools_code_coverage', '--chromium-src-dir',
'chromium/src', '--build-dir', 'output/dir'
]
with mock.patch.object(merger, 'merge_profiles') as mock_merge:
mock_merge.return_value = None, None
with mock.patch.object(sys, 'argv', args):
with mock.patch.object(subprocess, 'call') as mock_exec_cmd:
with mock.patch.object(os.path, 'join') as mock_os_path_join:
mock_merge_js_results_path = 'path/to/js/merge_js_results.py'
mock_os_path_join.return_value = mock_merge_js_results_path
python_exec = sys.executable
merge_results.main()
mock_exec_cmd.assert_called_with([
python_exec, mock_merge_js_results_path, '--task-output-dir',
task_output_dir, '--javascript-coverage-dir',
'output/dir/devtools_code_coverage', '--chromium-src-dir',
'chromium/src', '--build-dir', 'output/dir'
])
def test_argparse_sparse(self):
"""Ensure that sparse flag defaults to true, and is set to correct value"""
# Basic required args
build_properties = json.dumps({
'some': {
'complicated': ['nested', {
'json': None,
'object': 'thing',
}]
}
})
task_output_dir = 'some/task/output/dir'
profdata_dir = '/some/different/path/to/profdata/default.profdata'
profdata_file = os.path.join(profdata_dir, 'base_unittests.profdata')
args = [
'script_name', '--output-json', 'output.json', '--build-properties',
build_properties, '--summary-json', 'summary.json', '--task-output-dir',
task_output_dir, '--profdata-dir', profdata_dir, '--llvm-profdata',
'llvm-profdata', 'a.json', 'b.json', 'c.json', '--test-target-name',
'base_unittests'
]
test_scenarios = [
{
# Base set of args should set --sparse to false by default
'args': None,
'expected_outcome': False,
},
{
# Sparse should parse True when only --sparse is specified
'args': ['--sparse'],
'expected_outcome': True,
}
]
for scenario in test_scenarios:
args = copy.deepcopy(args)
additional_args = scenario['args']
if additional_args:
args.extend(additional_args)
expected_outcome = scenario['expected_outcome']
with mock.patch.object(merger, 'merge_profiles') as mock_merge:
mock_merge.return_value = None, None
with mock.patch.object(sys, 'argv', args):
merge_results.main()
self.assertEqual(
mock_merge.call_args,
mock.call(task_output_dir,
profdata_file,
'.profraw',
'llvm-profdata',
sparse=expected_outcome,
skip_validation=False), None)
if __name__ == '__main__':
unittest.main()