#!/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.
"""Runs gtest perf tests and process results.
Runs gtest and processes traces (run metrics) to produce perf results.
"""
import argparse
import json
import os
import shutil
import sys
from core import path_util
path_util.AddPyUtilsToPath()
sys.path.append(path_util.GetTracingDir())
from core import results_processor
sys.path.append(os.path.join(path_util.GetChromiumSrcDir(), 'testing'))
import test_env
# Note the name should be the one used by results_processor.ProcessResults.
MERGED_RESULTS = '_test_results.jsonl'
def _GetTraceDir(options):
return os.path.join(options.intermediate_dir, 'trace')
def RunGTest(options, gtest_args):
"""Runs gtest with --trace-dir switch pointing at intermediate dir.
Args:
options: Parsed command line options.
gtest_args: List of args to run gtest.
Returns gtest run return code.
"""
trace_dir = _GetTraceDir(options)
os.makedirs(trace_dir)
gtest_args.append('--trace-dir=%s' % trace_dir)
gtest_command = [options.executable]
gtest_command.extend(gtest_args)
return_code = test_env.run_command(gtest_command)
return return_code
def _MapDeviceTracePath(trace_dir, result_json):
"""Maps trace file paths to |trace_dir|. It is needed when gtest runs
on a real device and trace file is the absolute path on the device. If
gtest runs on a bot, the returned result should be the same as input.
Args:
result_json: JSON string of a test result.
Returns the JSON string of a LUCI test result with trace file path mapped.
"""
result = json.loads(result_json)
test_result = result.get('testResult', {})
artifacts = test_result.get('outputArtifacts', {})
trace_names = [name for name in artifacts if name.startswith('trace/')]
for name in trace_names:
trace_file = artifacts[name]['filePath']
trace_file = os.path.join(trace_dir, os.path.basename(trace_file))
artifacts[name]['filePath'] = trace_file
if artifacts:
result['testResult']['outputArtifacts'] = artifacts
return json.dumps(result)
def _MergeResultsJson(trace_dir, output_file):
"""Merge results json files generated in each test case into output_file.
Gtest test cases store results in LUCI test results format.
See: go/luci-test-results-design
This function reads the individual LUCI test results JSON files and
concatenates them into a jsonl file to feed result processor scripts later on.
"""
result_files = [
os.path.join(trace_dir, trace)
for trace in os.listdir(trace_dir)
if trace.endswith('test_result.json')
]
with open(output_file, 'w') as output:
for result_file in result_files:
with open(result_file) as f:
stripped_lines = [line.rstrip() for line in f]
for line in stripped_lines:
output.write('%s\n' % _MapDeviceTracePath(trace_dir, line))
def ProcessResults(options):
"""Collect generated results and call results_processor to compute results."""
_MergeResultsJson(_GetTraceDir(options),
os.path.join(options.intermediate_dir, MERGED_RESULTS))
process_return_code = results_processor.ProcessResults(options)
if process_return_code != 0:
return process_return_code
expected_perf_filename = os.path.join(options.output_dir, 'histograms.json')
output_perf_results = os.path.join(options.output_dir, 'perf_results.json')
shutil.move(expected_perf_filename, output_perf_results)
return process_return_code
def main(args):
parser = argparse.ArgumentParser(parents=[results_processor.ArgumentParser()])
parser.add_argument('executable', help='The name of the executable to run.')
options, leftover_args = parser.parse_known_args(args)
options.test_path_format = 'gtest'
results_processor.ProcessOptions(options)
run_return_code = RunGTest(options, leftover_args)
process_return_code = ProcessResults(options)
if process_return_code != 0:
return process_return_code
return run_return_code
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))