blob: 11e00719af26354a9557cbdd9e6126dab9d41052 [file] [log] [blame]
Kuang-che Wu32f27242019-05-16 17:34:50 +08001#!/usr/bin/env python2
2# -*- coding: utf-8 -*-
3# Copyright 2019 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6"""Evaluate ChromeOS tast tests."""
7from __future__ import print_function
8import argparse
9import json
10import logging
11import os
12import re
13import subprocess
14import sys
15
16from bisect_kit import catapult_util
17from bisect_kit import cli
18from bisect_kit import common
19from bisect_kit import configure
20from bisect_kit import cros_util
Kuang-che Wu11713052019-05-30 16:21:54 +080021from bisect_kit import util
Kuang-che Wu32f27242019-05-16 17:34:50 +080022
23logger = logging.getLogger(__name__)
24
25OLD = 'old'
26NEW = 'new'
27SKIP = 'skip'
28FATAL = 'fatal'
29
30EXIT_CODE_MAP = {
31 OLD: cli.EXIT_CODE_OLD,
32 NEW: cli.EXIT_CODE_NEW,
33 SKIP: cli.EXIT_CODE_SKIP,
34 FATAL: cli.EXIT_CODE_FATAL,
35}
36
37
38def create_argument_parser():
39 parser = argparse.ArgumentParser(description=__doc__)
40 common.add_common_arguments(parser)
41 parser.add_argument(
42 'dut',
43 nargs='?',
44 type=cli.argtype_notempty,
45 metavar='DUT',
46 default=configure.get('DUT', ''))
47 parser.add_argument(
48 '--chromeos_root',
49 type=cli.argtype_dir_path,
50 metavar='CHROMEOS_ROOT',
51 default=configure.get('CHROMEOS_ROOT', ''),
52 help='ChromeOS tree root')
53 parser.add_argument(
54 '--tast_build',
55 action='store_true',
56 help='Build tast test bundle (-build=true) if specified; '
57 'default is using prebuilt bundle on the DUT')
58 parser.add_argument(
59 '--reboot_before_test',
60 action='store_true',
61 help='Reboot before test run')
62
63 group = parser.add_argument_group(title='Options for normal autotest tests')
64 group.add_argument(
65 '--test_name',
66 required=True,
67 help='Test name, like "video_VideoDecodeAccelerator.h264"')
68 group.add_argument(
69 '--fail_to_pass',
70 action='store_true',
71 help='For functional tests: old behavior is FAIL and new behavior is '
72 'PASS; If not specified, default = old behavior is PASS and new '
73 'behavior is FAIL')
74 group.add_argument(
75 '--metric',
76 help=
77 'Metric name of performance test; example: "cheets_SystemRawImageSize"')
78
79 return parser
80
81
82def prepare_to_run_test(opts):
83 if opts.reboot_before_test:
84 cros_util.reboot(opts.dut)
85
86
Kuang-che Wu11713052019-05-30 16:21:54 +080087def list_tast_tests(opts, pattern=None):
88 cmd = ['tast', 'list']
89 if not opts.tast_build:
90 cmd.append('-build=false')
91 cmd.append(opts.dut)
92 if pattern:
93 cmd.append(pattern)
94 return cros_util.cros_sdk(opts.chromeos_root, *cmd).splitlines()
95
96
Kuang-che Wu32f27242019-05-16 17:34:50 +080097def run_test(opts):
98 """Runs an autotest test.
99
100 Args:
101 opts: An argparse.Namespace to hold command line arguments.
102
103 Returns:
104 path of test result (outside chroot)
105 """
106 # Set results dir inside source tree, so it's easier to access them outside
107 # chroot.
108 results_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
109 'tmp/tast_results_tmp')
110 # TODO(kcwu): add -timeout
111 cmd = ['tast', '-verbose', 'run']
112 if not opts.tast_build:
113 cmd.append('-build=false')
114 cmd += ['-resultsdir', results_dir, opts.dut, opts.test_name]
115
116 try:
117 cros_util.cros_sdk(opts.chromeos_root, *cmd)
118 except subprocess.CalledProcessError:
119 logger.fatal('failed to run tast, incorrect test name?')
120 return None
121
122 return results_dir.replace(cros_util.chromeos_root_inside_chroot,
123 opts.chromeos_root)
124
125
126def gather_test_result(opts, result_dir):
127 error_path = os.path.join(result_dir, 'run_error.txt')
128 if os.path.exists(error_path):
129 message = open(error_path).read()
130 raise Exception('tast global error: %s' % message)
131
132 results_path = os.path.join(result_dir, 'results.json')
133 passed = None
134 for result in json.load(open(results_path)):
135 if result['name'] != opts.test_name:
136 logger.warning('unexpected test ran: %s', result['name'])
137 continue
138 passed = result['errors'] is None
139 if passed is None:
140 raise Exception('no test result for "%s"?' % opts.test_name)
141
142 values = []
143 if opts.metric:
144 chart_path = os.path.join(result_dir, 'tests', opts.test_name,
145 'results-chart.json')
146 values = catapult_util.get_benchmark_values(chart_path, opts.metric)
147
148 return passed, values
149
150
151def main(args=None):
152 common.init()
153 parser = create_argument_parser()
154 opts = parser.parse_args(args)
155 common.config_logging(opts)
156
157 if not cros_util.is_dut(opts.dut):
158 logger.error('%r is not a valid DUT address', opts.dut)
159 return FATAL
160
161 # Verify command line options.
162 if opts.metric:
163 if opts.fail_to_pass:
164 logger.error('--fail_to_pass is not for benchmark test (--metric)')
165 return FATAL
166 # Remove the "tast." prefix prepended by autotest.
167 opts.test_name = re.sub(r'^tast\.', '', opts.test_name)
168
Kuang-che Wu11713052019-05-30 16:21:54 +0800169 tests = list_tast_tests(opts, opts.test_name)
170 if tests:
171 if len(tests) != 1 or tests[0] != opts.test_name:
172 # For example, tast in chroot after 12205.0.0 is IPC incompatible with
173 # tast on DUT earlier than 12028.0.0 (crbug/932307)
174 logger.fatal('"tast list" returns unexpected tests; '
175 'incompatible tast on DUT and in chroot?')
176 return FATAL
177 else:
178 tests = list_tast_tests(opts)
179 util.show_similar_candidates('test name', opts.test_name, tests)
180 return FATAL
181
Kuang-che Wu32f27242019-05-16 17:34:50 +0800182 try:
183 prepare_to_run_test(opts)
184 except Exception:
185 logger.exception('failed when prepare, assume it is temporary; SKIP')
186 return SKIP
187
188 result_dir = run_test(opts)
189 if not result_dir:
190 return FATAL
191
192 try:
193 passed, values = gather_test_result(opts, result_dir)
194 except Exception:
195 logger.exception('failed to parse test result')
196 return FATAL
197
198 if opts.metric:
199 if not values:
200 logger.warning('no values found; SKIP')
201 return SKIP
202
203 print('BISECT_RESULT_VALUES=', ' '.join(map(str, values)))
204 logger.info('values=%s', values)
205 # The exit code doesn't matter.
206 return OLD
207 else:
208 if opts.fail_to_pass:
209 if passed:
210 logger.info('passed')
211 return NEW
212 logger.info('failed')
213 return OLD
214 else:
215 if passed:
216 logger.info('passed')
217 return OLD
218 logger.info('failed')
219 return NEW
220
221
222if __name__ == '__main__':
223 sys.exit(EXIT_CODE_MAP[main()])