blob: 20bdb013aa48a6b386b019dfe2a64cda741f6ec6 [file] [log] [blame]
Kuang-che Wu1fcc0222018-07-07 16:43:22 +08001#!/usr/bin/env python2
2# -*- coding: utf-8 -*-
3# Copyright 2018 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"""Diagnose ChromeOS autotest regressions.
7
8This is integrated bisection utility. Given ChromeOS, Chrome, Android source
Kuang-che Wu927231f2018-07-24 14:21:56 +08009tree, and necessary parameters, this script can determine which components to
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080010bisect, and hopefully output the culprit CL of regression.
11
12Sometimes the script failed to figure out the final CL for various reasons, it
13will cut down the search range as narrow as it can.
14"""
15from __future__ import print_function
16import argparse
17import fnmatch
18import glob
19import logging
20import os
21
22from bisect_kit import cli
23from bisect_kit import common
24from bisect_kit import configure
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080025from bisect_kit import cros_lab_util
26from bisect_kit import cros_util
27from bisect_kit import diagnoser_cros
Kuang-che Wue121fae2018-11-09 16:18:39 +080028from bisect_kit import errors
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080029from bisect_kit import util
Kuang-che Wud8fc9572018-10-03 21:00:41 +080030import setup_cros_bisect
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080031
32logger = logging.getLogger(__name__)
33
34# What chrome binaries to build for given autotest.
35# This dict is created manually by inspecting output of
36# 'grep -r ChromeBinaryTest autotest/files/client/site_tests'
37# If you change this dict, build_and_deploy_chrome_helper.sh may need update
38# as well.
39CHROME_BINARIES_OF_TEST = {
40 'graphics_Chrome.ozone_gl_unittests': ['ozone_gl_unittests'],
41 'security_SandboxLinuxUnittests': ['sandbox_linux_unittests'],
42 'video_HangoutHardwarePerf*': [
43 'video_decode_accelerator_unittest',
44 'video_encode_accelerator_unittest',
45 ],
46 'video_JDAPerf*': ['jpeg_decode_accelerator_unittest'],
47 'video_JEAPerf': ['jpeg_encode_accelerator_unittest'],
48 'video_JpegDecodeAccelerator': ['jpeg_decode_accelerator_unittest'],
49 'video_JpegEncodeAccelerator': ['jpeg_encode_accelerator_unittest'],
50 'video_VDAPerf': ['video_decode_accelerator_unittest'],
51 'video_VDASanity': ['video_decode_accelerator_unittest'],
52 'video_VEAPerf': ['video_encode_accelerator_unittest'],
53 'video_VideoDecodeAccelerator*': ['video_decode_accelerator_unittest'],
54 'video_VideoEncodeAccelerator*': ['video_encode_accelerator_unittest'],
55}
56
57
Kuang-che Wua41525a2018-10-17 23:52:24 +080058def grab_dut(config):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080059 # Assume "DEPENDENCIES" is identical between the period of
60 # `old` and `new` version.
Kuang-che Wua41525a2018-10-17 23:52:24 +080061 autotest_dir = os.path.join(config['chromeos_root'],
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080062 cros_util.prebuilt_autotest_dir)
Kuang-che Wua41525a2018-10-17 23:52:24 +080063 info = cros_util.get_autotest_test_info(autotest_dir, config['test_name'])
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080064 assert info
65
Kuang-che Wua41525a2018-10-17 23:52:24 +080066 reason = 'bisect-kit: %s' % config['session']
Kuang-che Wuf65c61d2018-10-19 17:48:30 +080067 if config.get('allocated_dut'):
68 host_name = cros_lab_util.dut_host_name(config['allocated_dut'])
69 logger.info('try to allocate the same host (%s) as last run', host_name)
70 host = cros_lab_util.allocate_host(reason, host=host_name)
71 else:
72 extra_labels = []
73 dependencies = info.variables.get('DEPENDENCIES', '')
74 for label in dependencies.split(','):
75 label = label.strip()
76 # Skip non-machine labels
77 if label in ['cleanup-reboot']:
78 continue
79 extra_labels.append(label)
80
81 host = cros_lab_util.allocate_host(
82 reason,
83 model=config['model'],
84 sku=config['sku'],
85 extra_labels=extra_labels)
86
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080087 if not host:
88 logger.error('unable to allocate dut')
89 return None
90
91 logger.info('allocated host %s', host)
92 return host
93
94
95def may_depend_on_extra_chrome_binaries(autotest_dir, test_name):
96 info = cros_util.get_autotest_test_info(autotest_dir, test_name)
97 assert info
98 dirpath = os.path.dirname(info.path)
99 for pypath in glob.glob(os.path.join(dirpath, '*.py')):
100 if 'ChromeBinaryTest' in open(pypath).read():
101 return True
102 return False
103
104
Kuang-che Wua41525a2018-10-17 23:52:24 +0800105def determine_chrome_binaries(chromeos_root, test_name):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800106 chrome_binaries = None
107 for name_pattern, binaries in CHROME_BINARIES_OF_TEST.items():
Kuang-che Wua41525a2018-10-17 23:52:24 +0800108 if fnmatch.fnmatch(test_name, name_pattern):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800109 chrome_binaries = binaries
110 break
111
Kuang-che Wua41525a2018-10-17 23:52:24 +0800112 autotest_dir = os.path.join(chromeos_root, cros_util.prebuilt_autotest_dir)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800113 if chrome_binaries:
114 logger.info('This test depends on chrome binary: %s', chrome_binaries)
Kuang-che Wua41525a2018-10-17 23:52:24 +0800115 elif may_depend_on_extra_chrome_binaries(autotest_dir, test_name):
Kuang-che Wu74768d32018-09-07 12:03:24 +0800116 logger.warning(
117 '%s code used ChromeBinaryTest but the binary is unknown; '
Kuang-che Wua41525a2018-10-17 23:52:24 +0800118 'please update CHROME_BINARIES_OF_TEST table', test_name)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800119 return chrome_binaries
120
121
Kuang-che Wua41525a2018-10-17 23:52:24 +0800122class DiagnoseCommandLine(object):
123 """Diagnose command line interface."""
Kuang-che Wud8fc9572018-10-03 21:00:41 +0800124
Kuang-che Wua41525a2018-10-17 23:52:24 +0800125 def __init__(self):
126 common.init()
127 self.argument_parser = self.create_argument_parser()
128 self.states = None
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800129
Kuang-che Wua41525a2018-10-17 23:52:24 +0800130 @property
131 def config(self):
132 return self.states.config
133
134 def check_options(self, opts, path_factory):
135 if not opts.chromeos_mirror:
136 opts.chromeos_mirror = path_factory.get_chromeos_mirror()
137 logger.info('chromeos_mirror = %s', opts.chromeos_mirror)
138 if not opts.chromeos_root:
139 opts.chromeos_root = path_factory.get_chromeos_tree()
140 logger.info('chromeos_root = %s', opts.chromeos_root)
141 if not opts.chrome_mirror:
142 opts.chrome_mirror = path_factory.get_chrome_cache()
143 logger.info('chrome_mirror = %s', opts.chrome_mirror)
144 if not opts.chrome_root:
145 opts.chrome_root = path_factory.get_chrome_tree()
146 logger.info('chrome_root = %s', opts.chrome_root)
147
Kuang-che Wu248c5182018-10-19 17:08:11 +0800148 if opts.dut == cros_lab_util.LAB_DUT:
149 if not opts.model and not opts.sku:
150 self.argument_parser.error(
151 'either --model or --sku need to be specified if DUT is "%s"' %
152 cros_lab_util.LAB_DUT)
153 # Board name cannot be deduced from auto allocated devices because they
154 # may be provisioned with image of unexpected board.
155 if not opts.board:
Kuang-che Wua41525a2018-10-17 23:52:24 +0800156 self.argument_parser.error('--board need to be specified if DUT is "%s"'
157 % cros_lab_util.LAB_DUT)
Kuang-che Wu248c5182018-10-19 17:08:11 +0800158 else:
159 if not opts.board:
Kuang-che Wua41525a2018-10-17 23:52:24 +0800160 opts.board = cros_util.query_dut_board(opts.dut)
161
162 if cros_util.is_cros_short_version(opts.old):
163 opts.old = cros_util.version_to_full(opts.board, opts.old)
164 if cros_util.is_cros_short_version(opts.new):
165 opts.new = cros_util.version_to_full(opts.board, opts.new)
166
167 if opts.metric:
168 if opts.old_value is None:
169 self.argument_parser.error('--old_value is not provided')
170 if opts.new_value is None:
171 self.argument_parser.error('--new_value is not provided')
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800172 else:
Kuang-che Wua41525a2018-10-17 23:52:24 +0800173 if opts.old_value is not None:
174 self.argument_parser.error(
175 '--old_value is provided but --metric is not')
176 if opts.new_value is not None:
177 self.argument_parser.error(
178 '--new_value is provided but --metric is not')
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800179
Kuang-che Wua41525a2018-10-17 23:52:24 +0800180 def cmd_init(self, opts):
181 path_factory = setup_cros_bisect.DefaultProjectPathFactory(
182 opts.mirror_base, opts.work_base, opts.session)
183 self.check_options(opts, path_factory)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800184
Kuang-che Wua41525a2018-10-17 23:52:24 +0800185 config = dict(
186 session=opts.session,
187 mirror_base=opts.mirror_base,
188 work_base=opts.work_base,
189 chromeos_root=opts.chromeos_root,
190 chromeos_mirror=opts.chromeos_mirror,
191 chrome_root=opts.chrome_root,
192 chrome_mirror=opts.chrome_mirror,
193 android_root=opts.android_root,
194 android_mirror=opts.android_mirror,
Kuang-che Wu248c5182018-10-19 17:08:11 +0800195 dut=opts.dut,
196 model=opts.model,
197 sku=opts.sku,
198 board=opts.board,
Kuang-che Wua41525a2018-10-17 23:52:24 +0800199 old=opts.old,
200 new=opts.new,
201 test_name=opts.test_name,
202 metric=opts.metric,
Kuang-che Wua41525a2018-10-17 23:52:24 +0800203 old_value=opts.old_value,
204 new_value=opts.new_value,
Kuang-che Wua41525a2018-10-17 23:52:24 +0800205 noisy=opts.noisy,
206 test_that_args=opts.args,
207 always_reflash=opts.always_reflash,
208 )
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800209
Kuang-che Wua41525a2018-10-17 23:52:24 +0800210 self.states.init(config)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800211
Kuang-che Wua41525a2018-10-17 23:52:24 +0800212 # Unpack old autotest prebuilt, assume following information don't change
213 # between versions:
214 # - what chrome binaries to run
215 # - dependency labels for DUT allocation
216 common_switch_cmd, _common_eval_cmd = self._build_cmds()
217 util.check_call(*(common_switch_cmd + [self.config['old']]))
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800218
Kuang-che Wua41525a2018-10-17 23:52:24 +0800219 self.states.save()
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800220
Kuang-che Wua41525a2018-10-17 23:52:24 +0800221 def _build_cmds(self):
222 # prebuilt version will be specified later.
223 common_switch_cmd = [
224 './switch_autotest_prebuilt.py',
225 '--chromeos_root', self.config['chromeos_root'],
226 '--test_name', self.config['test_name'],
227 '--board', self.config['board'],
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800228 ] # yapf: disable
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800229
Kuang-che Wua41525a2018-10-17 23:52:24 +0800230 common_eval_cmd = [
231 './eval_cros_autotest.py',
232 '--chromeos_root', self.config['chromeos_root'],
233 '--test_name', self.config['test_name'],
234 ] # yapf: disable
235 if self.config['metric']:
236 common_eval_cmd += [
237 '--metric', self.config['metric'],
238 '--old_value', str(self.config['old_value']),
239 '--new_value', str(self.config['new_value']),
240 ] # yapf: disable
241 if self.config['test_that_args']:
242 common_eval_cmd += ['--args', self.config['test_that_args']]
243 return common_switch_cmd, common_eval_cmd
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800244
Kuang-che Wua41525a2018-10-17 23:52:24 +0800245 def cmd_run(self, opts):
246 del opts # unused
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800247
Kuang-che Wua41525a2018-10-17 23:52:24 +0800248 self.states.load()
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800249
Kuang-che Wu8b654092018-11-09 17:56:25 +0800250 try:
251 path_factory = setup_cros_bisect.DefaultProjectPathFactory(
252 self.config['mirror_base'], self.config['work_base'],
253 self.config['session'])
254 common_switch_cmd, common_eval_cmd = self._build_cmds()
255
256 chrome_binaries = determine_chrome_binaries(self.config['chromeos_root'],
257 self.config['test_name'])
258
259 with cros_lab_util.dut_manager(self.config['dut'],
260 lambda: grab_dut(self.config)) as dut:
261 if not dut:
262 raise errors.NoDutAvailable('unable to allocate DUT')
263 assert cros_util.is_dut(dut)
264 if self.config['dut'] == cros_lab_util.LAB_DUT:
265 self.config['allocated_dut'] = dut
266 self.states.save()
267 common_eval_cmd.append(dut)
268
269 diagnoser = diagnoser_cros.CrosDiagnoser(
270 self.states, self.config['session'], path_factory,
271 self.config['chromeos_root'], self.config['chromeos_mirror'],
272 self.config['android_root'], self.config['android_mirror'],
273 self.config['chrome_root'], self.config['chrome_mirror'],
274 self.config['board'], self.config['noisy'], dut)
275
276 eval_cmd = common_eval_cmd + ['--prebuilt', '--reinstall']
277 # Do not specify version for autotest prebuilt switching here. The trick
278 # is that version number is obtained via bisector's environment variable
279 # CROS_VERSION.
280 extra_switch_cmd = common_switch_cmd
281 diagnoser.narrow_down_chromeos_prebuilt(
282 self.config['old'],
283 self.config['new'],
284 eval_cmd,
285 extra_switch_cmd=extra_switch_cmd)
286
287 diagnoser.switch_chromeos_to_old(force=self.config['always_reflash'])
288 util.check_call(*(common_switch_cmd + [diagnoser.cros_old]))
289 util.check_call('ssh', dut, 'rm', '-rf', '/usr/local/autotest')
290
291 try:
292 if diagnoser.narrow_down_android(eval_cmd):
293 return
294 except errors.VerificationFailed:
295 raise
296 except Exception:
297 logger.exception('exception in android bisector before verification; '
298 'assume culprit is not inside android and continue')
299 # Assume it's ok to leave random version of android prebuilt on DUT.
300
301 # Don't --reinstall to keep chrome binaries override.
302 eval_cmd = common_eval_cmd + ['--prebuilt']
303 try:
304 if diagnoser.narrow_down_chrome(
Kuang-che Wu50d8ff42018-11-26 12:48:30 +0800305 eval_cmd, chrome_binaries=chrome_binaries):
Kuang-che Wu8b654092018-11-09 17:56:25 +0800306 return
307 except errors.VerifyOldFailed:
308 logger.fatal('expect old chrome has old behavior but failed')
309 raise
310 except Exception:
311 logger.exception('exception in chrome bisector before verification; '
312 'assume culprit is not inside chrome and continue')
313
314 eval_cmd = common_eval_cmd + ['--reinstall']
315 diagnoser.narrow_down_chromeos_localbuild(eval_cmd)
316 logger.info('%s done', __file__)
317 except Exception as e:
318 logger.exception('got exception; stop')
319 exception_name = e.__class__.__name__
320 self.states.add_history(
321 'failed', '%s: %s' % (exception_name, e), exception=exception_name)
322
323 def cmd_log(self, opts):
324 self.states.load()
Kuang-che Wua41525a2018-10-17 23:52:24 +0800325 path_factory = setup_cros_bisect.DefaultProjectPathFactory(
326 self.config['mirror_base'], self.config['work_base'],
327 self.config['session'])
Kuang-che Wu8b654092018-11-09 17:56:25 +0800328 diagnoser = diagnoser_cros.CrosDiagnoser(
329 self.states, self.config['session'], path_factory,
330 self.config['chromeos_root'], self.config['chromeos_mirror'],
331 self.config['android_root'], self.config['android_mirror'],
332 self.config['chrome_root'], self.config['chrome_mirror'],
333 self.config['board'], self.config['noisy'], None)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800334
Kuang-che Wu8b654092018-11-09 17:56:25 +0800335 diagnoser.cmd_log(opts.json)
Kuang-che Wua41525a2018-10-17 23:52:24 +0800336
Kuang-che Wue80bb872018-11-15 19:45:25 +0800337 def cmd_view(self, opts):
338 self.states.load()
339 path_factory = setup_cros_bisect.DefaultProjectPathFactory(
340 self.config['mirror_base'], self.config['work_base'],
341 self.config['session'])
342 diagnoser = diagnoser_cros.CrosDiagnoser(
343 self.states, self.config['session'], path_factory,
344 self.config['chromeos_root'], self.config['chromeos_mirror'],
345 self.config['android_root'], self.config['android_mirror'],
346 self.config['chrome_root'], self.config['chrome_mirror'],
347 self.config['board'], self.config['noisy'], None)
348 diagnoser.cmd_view(opts.json, opts.verbose)
349
Kuang-che Wua41525a2018-10-17 23:52:24 +0800350 def create_argument_parser(self):
351 parser = argparse.ArgumentParser()
352 common.add_common_arguments(parser)
353 parser.add_argument('--session_base', default='bisect.sessions')
354 parser.add_argument('--session', help='Session name', required=True)
355 subparsers = parser.add_subparsers(
356 dest='command', title='commands', metavar='<command>')
357
358 parser_init = subparsers.add_parser('init', help='Initialize')
359 group = parser_init.add_argument_group(
360 title='Source tree path options',
361 description='''
362 Specify the paths of chromeos/chrome/android mirror and checkout. They
363 have the same default values as setup_cros_bisect.py, so usually you can
364 omit them and it just works.
365 ''')
366 group.add_argument(
367 '--mirror_base',
368 metavar='MIRROR_BASE',
369 default=configure.get('MIRROR_BASE',
370 setup_cros_bisect.DEFAULT_MIRROR_BASE),
371 help='Directory for mirrors (default: %(default)s)')
372 group.add_argument(
373 '--work_base',
374 metavar='WORK_BASE',
375 default=configure.get('WORK_BASE', setup_cros_bisect.DEFAULT_WORK_BASE),
376 help='Directory for bisection working directories '
377 '(default: %(default)s)')
378 group.add_argument(
379 '--chromeos_root',
380 metavar='CHROMEOS_ROOT',
381 type=cli.argtype_dir_path,
382 default=configure.get('CHROMEOS_ROOT'),
383 help='ChromeOS tree root')
384 group.add_argument(
385 '--chromeos_mirror',
386 type=cli.argtype_dir_path,
387 default=configure.get('CHROMEOS_MIRROR'),
388 help='ChromeOS repo mirror path')
389 group.add_argument(
390 '--android_root',
391 metavar='ANDROID_ROOT',
392 type=cli.argtype_dir_path,
393 default=configure.get('ANDROID_ROOT'),
394 help='Android tree root')
395 group.add_argument(
396 '--android_mirror',
397 type=cli.argtype_dir_path,
398 default=configure.get('ANDROID_MIRROR'),
399 help='Android repo mirror path')
400 group.add_argument(
401 '--chrome_root',
402 metavar='CHROME_ROOT',
403 type=cli.argtype_dir_path,
404 default=configure.get('CHROME_ROOT'),
405 help='Chrome tree root')
406 group.add_argument(
407 '--chrome_mirror',
408 metavar='CHROME_MIRROR',
409 type=cli.argtype_dir_path,
410 default=configure.get('CHROME_MIRROR'),
411 help="chrome's gclient cache dir")
412
Kuang-che Wu248c5182018-10-19 17:08:11 +0800413 group = parser_init.add_argument_group(title='DUT allocation options')
414 group.add_argument(
415 '--dut',
416 metavar='DUT',
417 required=True,
418 help='Address of DUT (Device Under Test). If "%s", DUT will be '
419 'automatically allocated from the lab' % cros_lab_util.LAB_DUT)
420 group.add_argument(
421 '--model',
422 metavar='MODEL',
423 help='"model" criteria if DUT is auto allocated from the lab')
424 group.add_argument(
425 '--sku',
426 metavar='SKU',
427 help='"sku" criteria if DUT is auto allocated from the lab')
428
Kuang-che Wua41525a2018-10-17 23:52:24 +0800429 group = parser_init.add_argument_group(title='Essential options')
430 group.add_argument(
Kuang-che Wu248c5182018-10-19 17:08:11 +0800431 '--board',
432 metavar='BOARD',
433 default=configure.get('BOARD'),
434 help='ChromeOS board name; auto detected if DUT is not auto allocated')
435 group.add_argument(
Kuang-che Wua41525a2018-10-17 23:52:24 +0800436 '--old',
437 type=cros_util.argtype_cros_version,
438 required=True,
439 help='ChromeOS version with old behavior')
440 group.add_argument(
441 '--new',
442 type=cros_util.argtype_cros_version,
443 required=True,
444 help='ChromeOS version with new behavior')
445 group.add_argument('--test_name', required=True, help='Test name')
446
447 group = parser_init.add_argument_group(title='Options for benchmark test')
448 group.add_argument('--metric', help='Metric name of benchmark test')
449 group.add_argument(
450 '--old_value',
451 type=float,
452 help='For benchmark test, old value of metric')
453 group.add_argument(
454 '--new_value',
455 type=float,
456 help='For benchmark test, new value of metric')
457
Kuang-che Wua41525a2018-10-17 23:52:24 +0800458 group = parser_init.add_argument_group(title='Options passed to test_that')
459 group.add_argument(
460 '--args',
461 help='Extra args passed to "test_that --args"; Overrides the default')
462
463 group = parser_init.add_argument_group(title='Bisect behavior options')
464 group.add_argument(
465 '--noisy',
466 help='Enable noisy binary search. Example value: "old=1/10,new=2/3"')
467 group.add_argument(
468 '--always_reflash',
469 action='store_true',
470 help='Do not trust ChromeOS version number of DUT and always reflash. '
471 'This is usually only needed when resume because previous bisect was '
472 'interrupted and the DUT may be in an unexpected state')
473 parser_init.set_defaults(func=self.cmd_init)
474
475 parser_run = subparsers.add_parser('run', help='Start auto bisection')
476 parser_run.set_defaults(func=self.cmd_run)
477
Kuang-che Wu8b654092018-11-09 17:56:25 +0800478 parser_log = subparsers.add_parser(
479 'log', help='Prints what has been done so far')
480 parser_log.add_argument(
481 '--json', action='store_true', help='Machine readable output')
482 parser_log.set_defaults(func=self.cmd_log)
483
Kuang-che Wue80bb872018-11-15 19:45:25 +0800484 parser_view = subparsers.add_parser(
485 'view', help='Prints summary of current status')
486 parser_view.add_argument('--verbose', '-v', action='store_true')
487 parser_view.add_argument(
488 '--json', action='store_true', help='Machine readable output')
489 parser_view.set_defaults(func=self.cmd_view)
490
Kuang-che Wua41525a2018-10-17 23:52:24 +0800491 return parser
492
493 def main(self, args=None):
494 opts = self.argument_parser.parse_args(args)
495 common.config_logging(opts)
496
497 session_base = configure.get('SESSION_BASE', common.DEFAULT_SESSION_BASE)
498 session_file = os.path.join(session_base, opts.session,
499 self.__class__.__name__)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800500 self.states = diagnoser_cros.DiagnoseStates(session_file)
Kuang-che Wua41525a2018-10-17 23:52:24 +0800501 opts.func(opts)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800502
503
504if __name__ == '__main__':
Kuang-che Wua41525a2018-10-17 23:52:24 +0800505 DiagnoseCommandLine().main()