# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This is a library for printing test result as specified by
//docs/testing/test_executable_api.md (e.g. --isolated-script-test-output)
and //docs/testing/json_test_results_format.md
Typical usage:
import argparse
import test_results
cmdline_parser = argparse.ArgumentParser()
test_results.add_cmdline_args(cmdline_parser)
... adding other cmdline parameter definitions ...
parsed_cmdline_args = cmdline_parser.parse_args()
test_results = []
test_results.append(test_results.TestResult(
'test-suite/test-name', 'PASS'))
...
test_results.print_test_results(parsed_cmdline_args, test_results)
"""
import argparse
import json
import os
class TestResult:
"""TestResult represents a result of executing a single test once.
"""
def __init__(self,
test_name,
actual_test_result,
expected_test_result='PASS'):
self.test_name = test_name
self.actual_test_result = actual_test_result
self.expected_test_result = expected_test_result
def __eq__(self, other):
self_tuple = tuple(sorted(self.__dict__.items()))
other_tuple = tuple(sorted(other.__dict__.items()))
return self_tuple == other_tuple
def __hash__(self):
return hash(tuple(sorted(self.__dict__.items())))
def __repr__(self):
result = 'TestResult[{}: {}'.format(self.test_name,
self.actual_test_result)
if self.expected_test_result != 'PASS':
result += ' (expecting: {})]'.format(self.expected_test_result)
else:
result += ']'
return result
def _validate_output_directory(outdir):
if not os.path.isdir(outdir):
raise argparse.ArgumentTypeError('No such directory: ' + outdir)
return outdir
def add_cmdline_args(argparse_parser):
"""Adds test-result-specific cmdline parameter definitions to
`argparse_parser`.
Args:
argparse_parser: An object of argparse.ArgumentParser type.
"""
outdir_help = 'Directory where test results will be written into.'
argparse_parser.add_argument('--isolated-outdir',
dest='outdir',
help=outdir_help,
metavar='DIRPATH',
type=_validate_output_directory)
outfile_help = 'If this argument is provided, then test results in the ' \
'JSON Test Results Format will be written here. See also ' \
'//docs/testing/json_test_results_format.md'
argparse_parser.add_argument('--isolated-script-test-output',
dest='test_output',
default=None,
help=outfile_help,
metavar='FILENAME')
argparse_parser.add_argument('--isolated-script-test-perf-output',
dest='ignored_perf_output',
default=None,
help='Deprecated and ignored.',
metavar='IGNORED')
def _build_json_data(list_of_test_results, seconds_since_epoch):
num_failures_by_type = {}
tests = {}
for res in list_of_test_results:
old_count = num_failures_by_type.get(res.actual_test_result, 0)
num_failures_by_type[res.actual_test_result] = old_count + 1
path = res.test_name.split('//')
group = tests
for group_name in path[:-1]:
if not group_name in group:
group[group_name] = {}
group = group[group_name]
group[path[-1]] = {
'expected': res.expected_test_result,
'actual': res.actual_test_result,
}
return {
'interrupted': False,
'path_delimiter': '//',
'seconds_since_epoch': seconds_since_epoch,
'version': 3,
'tests': tests,
'num_failures_by_type': num_failures_by_type,
}
def print_test_results(argparse_parsed_args, list_of_test_results,
testing_start_time_as_seconds_since_epoch):
"""Prints `list_of_test_results` to a file specified on the cmdline.
Args:
argparse_parsed_arg: A result of an earlier call to
argparse_parser.parse_args() call (where `argparse_parser` has been
populated via an even earlier call to add_cmdline_args).
list_of_test_results: A list of TestResult objects.
testing_start_time_as_seconds_since_epoch: A number from an earlier
`time.time()` call.
"""
if argparse_parsed_args.test_output is None:
return
json_data = _build_json_data(list_of_test_results,
testing_start_time_as_seconds_since_epoch)
filepath = argparse_parsed_args.test_output
with open(filepath, mode='w', encoding='utf-8') as f:
json.dump(json_data, f, indent=2)