1#!/usr/bin/python3 2# Copyright 2018 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Fake implementation of tast executable used by tast_unittest.py. 7 8In unit tests, this file is executed instead of the real tast executable by the 9tast.py server test. It: 10 11- reads a config.json file installed alongside this script, 12- parses command-line arguments passed by tast.py, 13- checks that the arguments included all required args specified by the config 14 file for the given command (e.g. 'list', 'run' etc.), and 15- performs actions specified by the config file. 16""" 17 18from __future__ import print_function 19 20import argparse 21import json 22import os 23import six 24import sys 25 26 27def main(): 28 # pylint: disable=missing-docstring 29 30 # The config file is written by TastTest._run_test in tast_unittest.py and 31 # consists of a dict from command names (e.g. 'list', 'run', etc.) to 32 # command definition dicts (see TastCommand in tast_unittest.py). 33 path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 34 'config.json') 35 with open(path) as f: 36 cfg = json.load(f) 37 38 args = parse_args() 39 cmd = cfg.get(args.command) 40 if not cmd: 41 raise RuntimeError('Unexpected command "%s"' % args.command) 42 43 # If patterns is ("group:none"), this is a warm-up run, so skip checking 44 # arguments. 45 if args.patterns != [['("group:none")']]: 46 for arg in cmd.get('required_args', []): 47 name, expected_value = arg.split('=', 1) 48 # argparse puts the repeated "pattern" args into a list of lists 49 # instead of a single list. Pull the args back out in this case. 50 val = getattr(args, name) 51 if isinstance(val, list) and len(val) == 1 and isinstance( 52 val[0], list): 53 val = val[0] 54 actual_value = str(val) 55 if actual_value != expected_value: 56 raise RuntimeError('Got arg %s with value "%s"; want "%s"' % 57 (name, actual_value, expected_value)) 58 59 if cmd.get('stdout'): 60 sys.stdout.write(cmd['stdout']) 61 if cmd.get('stderr'): 62 sys.stderr.write(cmd['stderr']) 63 64 if cmd.get('files_to_write'): 65 for path, data in six.iteritems(cmd['files_to_write']): 66 dirname = os.path.dirname(path) 67 if not os.path.exists(dirname): 68 os.makedirs(dirname, 0o755) 69 with open(path, 'w') as f: 70 f.write(data) 71 72 sys.exit(cmd.get('status')) 73 74 75def parse_args(): 76 """Parses command-line arguments. 77 78 @returns: argparse.Namespace object containing parsed attributes. 79 """ 80 def to_bool(v): 81 """Converts a boolean flag's value to a Python bool value.""" 82 if v == 'true' or v == '': 83 return True 84 if v == 'false': 85 return False 86 raise argparse.ArgumentTypeError('"true" or "false" expected') 87 88 parser = argparse.ArgumentParser() 89 parser.add_argument('-logtime', type=to_bool, default=False, nargs='?') 90 parser.add_argument('-verbose', type=to_bool, default=False, nargs='?') 91 parser.add_argument('-version', action='version', version='1.0') 92 93 subparsers = parser.add_subparsers(dest='command') 94 95 def add_common_args(subparser): 96 """Adds arguments shared between tast's 'list' and 'run' subcommands.""" 97 subparser.add_argument('-build', type=to_bool, default=True, nargs='?') 98 subparser.add_argument('-buildbundle', default='cros') 99 subparser.add_argument('-checkbuilddeps', type=to_bool, default=True, 100 nargs='?') 101 subparser.add_argument('-downloadprivatebundles', type=to_bool, 102 default=False, nargs='?') 103 subparser.add_argument('-devservers') 104 subparser.add_argument('-remotebundledir') 105 subparser.add_argument('-remotedatadir') 106 subparser.add_argument('-remoterunner') 107 subparser.add_argument('-sshretries') 108 subparser.add_argument('-downloaddata') 109 subparser.add_argument('-totalshards') 110 subparser.add_argument('-shardindex') 111 subparser.add_argument('target') 112 subparser.add_argument('patterns', action='append', nargs='*') 113 114 list_parser = subparsers.add_parser('list') 115 add_common_args(list_parser) 116 list_parser.add_argument('-json', type=to_bool, default=False, nargs='?') 117 118 run_parser = subparsers.add_parser('run') 119 add_common_args(run_parser) 120 run_parser.add_argument('-resultsdir') 121 run_parser.add_argument('-waituntilready') 122 run_parser.add_argument('-timeout') 123 run_parser.add_argument('-continueafterfailure', type=to_bool, 124 default=False, nargs='?') 125 run_parser.add_argument('-var', action='append', default=[]) 126 run_parser.add_argument('-defaultvarsdir') 127 run_parser.add_argument('-varsfile', action='append', default=[]) 128 run_parser.add_argument('-companiondut', action='append', default=[]) 129 run_parser.add_argument('-buildartifactsurl') 130 run_parser.add_argument('-maybemissingvars') 131 run_parser.add_argument('-testfilterfile', action='append', default=[]) 132 133 return parser.parse_args() 134 135 136if __name__ == '__main__': 137 main() 138