1*9c5db199SXin Li"""\ 2*9c5db199SXin LiLogic for control file generation. 3*9c5db199SXin Li""" 4*9c5db199SXin Li 5*9c5db199SXin Li__author__ = '[email protected] (Steve Howard)' 6*9c5db199SXin Li 7*9c5db199SXin Liimport re, os 8*9c5db199SXin Li 9*9c5db199SXin Liimport common 10*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 11*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import dev_server 12*9c5db199SXin Lifrom autotest_lib.frontend.afe import model_logic 13*9c5db199SXin Lifrom autotest_lib.server.cros.dynamic_suite import control_file_getter 14*9c5db199SXin Lifrom autotest_lib.server.cros.dynamic_suite import suite_common 15*9c5db199SXin Liimport frontend.settings 16*9c5db199SXin Li 17*9c5db199SXin LiAUTOTEST_DIR = os.path.abspath(os.path.join( 18*9c5db199SXin Li os.path.dirname(frontend.settings.__file__), '..')) 19*9c5db199SXin Li 20*9c5db199SXin LiEMPTY_TEMPLATE = 'def step_init():\n' 21*9c5db199SXin Li 22*9c5db199SXin LiCLIENT_STEP_TEMPLATE = " job.next_step('step%d')\n" 23*9c5db199SXin LiSERVER_STEP_TEMPLATE = ' step%d()\n' 24*9c5db199SXin Li 25*9c5db199SXin Li 26*9c5db199SXin Lidef _read_control_file(test): 27*9c5db199SXin Li """Reads the test control file from local disk. 28*9c5db199SXin Li 29*9c5db199SXin Li @param test The test name. 30*9c5db199SXin Li 31*9c5db199SXin Li @return The test control file string. 32*9c5db199SXin Li """ 33*9c5db199SXin Li control_file = open(os.path.join(AUTOTEST_DIR, test.path)) 34*9c5db199SXin Li control_contents = control_file.read() 35*9c5db199SXin Li control_file.close() 36*9c5db199SXin Li return control_contents 37*9c5db199SXin Li 38*9c5db199SXin Li 39*9c5db199SXin Lidef _add_boilerplate_to_nested_steps(lines): 40*9c5db199SXin Li """Adds boilerplate magic. 41*9c5db199SXin Li 42*9c5db199SXin Li @param lines The string of lines. 43*9c5db199SXin Li 44*9c5db199SXin Li @returns The string lines. 45*9c5db199SXin Li """ 46*9c5db199SXin Li # Look for a line that begins with 'def step_init():' while 47*9c5db199SXin Li # being flexible on spacing. If it's found, this will be 48*9c5db199SXin Li # a nested set of steps, so add magic to make it work. 49*9c5db199SXin Li # See client/bin/job.py's step_engine for more info. 50*9c5db199SXin Li if re.search(r'^(.*\n)*def\s+step_init\s*\(\s*\)\s*:', lines): 51*9c5db199SXin Li lines += '\nreturn locals() ' 52*9c5db199SXin Li lines += '# Boilerplate magic for nested sets of steps' 53*9c5db199SXin Li return lines 54*9c5db199SXin Li 55*9c5db199SXin Li 56*9c5db199SXin Lidef _format_step(item, lines): 57*9c5db199SXin Li """Format a line item. 58*9c5db199SXin Li @param item The item number. 59*9c5db199SXin Li @param lines The string of lines. 60*9c5db199SXin Li 61*9c5db199SXin Li @returns The string lines. 62*9c5db199SXin Li """ 63*9c5db199SXin Li lines = _indent_text(lines, ' ') 64*9c5db199SXin Li lines = 'def step%d():\n%s' % (item, lines) 65*9c5db199SXin Li return lines 66*9c5db199SXin Li 67*9c5db199SXin Li 68*9c5db199SXin Lidef _get_tests_stanza(tests, is_server, prepend=None, append=None, 69*9c5db199SXin Li client_control_file='', test_source_build=None): 70*9c5db199SXin Li """ Constructs the control file test step code from a list of tests. 71*9c5db199SXin Li 72*9c5db199SXin Li @param tests A sequence of test control files to run. 73*9c5db199SXin Li @param is_server bool, Is this a server side test? 74*9c5db199SXin Li @param prepend A list of steps to prepend to each client test. 75*9c5db199SXin Li Defaults to []. 76*9c5db199SXin Li @param append A list of steps to append to each client test. 77*9c5db199SXin Li Defaults to []. 78*9c5db199SXin Li @param client_control_file If specified, use this text as the body of a 79*9c5db199SXin Li final client control file to run after tests. is_server must be False. 80*9c5db199SXin Li @param test_source_build: Build to be used to retrieve test code. Default 81*9c5db199SXin Li to None. 82*9c5db199SXin Li 83*9c5db199SXin Li @returns The control file test code to be run. 84*9c5db199SXin Li """ 85*9c5db199SXin Li assert not (client_control_file and is_server) 86*9c5db199SXin Li if not prepend: 87*9c5db199SXin Li prepend = [] 88*9c5db199SXin Li if not append: 89*9c5db199SXin Li append = [] 90*9c5db199SXin Li if test_source_build: 91*9c5db199SXin Li raw_control_files = _get_test_control_files_by_build( 92*9c5db199SXin Li tests, test_source_build) 93*9c5db199SXin Li else: 94*9c5db199SXin Li raw_control_files = [_read_control_file(test) for test in tests] 95*9c5db199SXin Li if client_control_file: 96*9c5db199SXin Li # 'return locals()' is always appended in case the user forgot, it 97*9c5db199SXin Li # is necessary to allow for nested step engine execution to work. 98*9c5db199SXin Li raw_control_files.append(client_control_file + '\nreturn locals()') 99*9c5db199SXin Li raw_steps = prepend + [_add_boilerplate_to_nested_steps(step) 100*9c5db199SXin Li for step in raw_control_files] + append 101*9c5db199SXin Li steps = [_format_step(index, step) 102*9c5db199SXin Li for index, step in enumerate(raw_steps)] 103*9c5db199SXin Li if is_server: 104*9c5db199SXin Li step_template = SERVER_STEP_TEMPLATE 105*9c5db199SXin Li footer = '\n\nstep_init()\n' 106*9c5db199SXin Li else: 107*9c5db199SXin Li step_template = CLIENT_STEP_TEMPLATE 108*9c5db199SXin Li footer = '' 109*9c5db199SXin Li 110*9c5db199SXin Li header = ''.join(step_template % i for i in xrange(len(steps))) 111*9c5db199SXin Li return header + '\n' + '\n\n'.join(steps) + footer 112*9c5db199SXin Li 113*9c5db199SXin Li 114*9c5db199SXin Lidef _indent_text(text, indent): 115*9c5db199SXin Li """Indent given lines of python code avoiding indenting multiline 116*9c5db199SXin Li quoted content (only for triple " and ' quoting for now). 117*9c5db199SXin Li 118*9c5db199SXin Li @param text The string of lines. 119*9c5db199SXin Li @param indent The indent string. 120*9c5db199SXin Li 121*9c5db199SXin Li @return The indented string. 122*9c5db199SXin Li """ 123*9c5db199SXin Li regex = re.compile('(\\\\*)("""|\'\'\')') 124*9c5db199SXin Li 125*9c5db199SXin Li res = [] 126*9c5db199SXin Li in_quote = None 127*9c5db199SXin Li for line in text.splitlines(): 128*9c5db199SXin Li # if not within a multinline quote indent the line contents 129*9c5db199SXin Li if in_quote: 130*9c5db199SXin Li res.append(line) 131*9c5db199SXin Li else: 132*9c5db199SXin Li res.append(indent + line) 133*9c5db199SXin Li 134*9c5db199SXin Li while line: 135*9c5db199SXin Li match = regex.search(line) 136*9c5db199SXin Li if match: 137*9c5db199SXin Li # for an even number of backslashes before the triple quote 138*9c5db199SXin Li if len(match.group(1)) % 2 == 0: 139*9c5db199SXin Li if not in_quote: 140*9c5db199SXin Li in_quote = match.group(2)[0] 141*9c5db199SXin Li elif in_quote == match.group(2)[0]: 142*9c5db199SXin Li # if we found a matching end triple quote 143*9c5db199SXin Li in_quote = None 144*9c5db199SXin Li line = line[match.end():] 145*9c5db199SXin Li else: 146*9c5db199SXin Li break 147*9c5db199SXin Li 148*9c5db199SXin Li return '\n'.join(res) 149*9c5db199SXin Li 150*9c5db199SXin Li 151*9c5db199SXin Lidef _get_profiler_commands(profilers, is_server, profile_only): 152*9c5db199SXin Li prepend, append = [], [] 153*9c5db199SXin Li if profile_only is not None: 154*9c5db199SXin Li prepend.append("job.default_profile_only = %r" % profile_only) 155*9c5db199SXin Li for profiler in profilers: 156*9c5db199SXin Li prepend.append("job.profilers.add('%s')" % profiler.name) 157*9c5db199SXin Li append.append("job.profilers.delete('%s')" % profiler.name) 158*9c5db199SXin Li return prepend, append 159*9c5db199SXin Li 160*9c5db199SXin Li 161*9c5db199SXin Lidef _check_generate_control(is_server, client_control_file): 162*9c5db199SXin Li """ 163*9c5db199SXin Li Check some of the parameters to generate_control(). 164*9c5db199SXin Li 165*9c5db199SXin Li This exists as its own function so that site_control_file may call it as 166*9c5db199SXin Li well from its own generate_control(). 167*9c5db199SXin Li 168*9c5db199SXin Li @raises ValidationError if any of the parameters do not make sense. 169*9c5db199SXin Li """ 170*9c5db199SXin Li if is_server and client_control_file: 171*9c5db199SXin Li raise model_logic.ValidationError( 172*9c5db199SXin Li {'tests' : 'You cannot run server tests at the same time ' 173*9c5db199SXin Li 'as directly supplying a client-side control file.'}) 174*9c5db199SXin Li 175*9c5db199SXin Li 176*9c5db199SXin Lidef generate_control(tests, is_server=False, profilers=(), 177*9c5db199SXin Li client_control_file='', profile_only=None, 178*9c5db199SXin Li test_source_build=None): 179*9c5db199SXin Li """ 180*9c5db199SXin Li Generate a control file for a sequence of tests. 181*9c5db199SXin Li 182*9c5db199SXin Li @param tests A sequence of test control files to run. 183*9c5db199SXin Li @param is_server bool, Is this a server control file rather than a client? 184*9c5db199SXin Li @param profilers A list of profiler objects to enable during the tests. 185*9c5db199SXin Li @param client_control_file Contents of a client control file to run as the 186*9c5db199SXin Li last test after everything in tests. Requires is_server=False. 187*9c5db199SXin Li @param profile_only bool, should this control file run all tests in 188*9c5db199SXin Li profile_only mode by default 189*9c5db199SXin Li @param test_source_build: Build to be used to retrieve test code. Default 190*9c5db199SXin Li to None. 191*9c5db199SXin Li 192*9c5db199SXin Li @returns The control file text as a string. 193*9c5db199SXin Li """ 194*9c5db199SXin Li _check_generate_control(is_server=is_server, 195*9c5db199SXin Li client_control_file=client_control_file) 196*9c5db199SXin Li control_file_text = EMPTY_TEMPLATE 197*9c5db199SXin Li prepend, append = _get_profiler_commands(profilers, is_server, profile_only) 198*9c5db199SXin Li control_file_text += _get_tests_stanza(tests, is_server, prepend, append, 199*9c5db199SXin Li client_control_file, 200*9c5db199SXin Li test_source_build) 201*9c5db199SXin Li return control_file_text 202*9c5db199SXin Li 203*9c5db199SXin Li 204*9c5db199SXin Lidef _get_test_control_files_by_build(tests, build, ignore_invalid_tests=False): 205*9c5db199SXin Li """Get the test control files that are available for the specified build. 206*9c5db199SXin Li 207*9c5db199SXin Li @param tests A sequence of test objects to run. 208*9c5db199SXin Li @param build: unique name by which to refer to the image. 209*9c5db199SXin Li @param ignore_invalid_tests: flag on if unparsable tests are ignored. 210*9c5db199SXin Li 211*9c5db199SXin Li @return: A sorted list of all tests that are in the build specified. 212*9c5db199SXin Li """ 213*9c5db199SXin Li raw_control_files = [] 214*9c5db199SXin Li # shortcut to avoid staging the image. 215*9c5db199SXin Li if not tests: 216*9c5db199SXin Li return raw_control_files 217*9c5db199SXin Li 218*9c5db199SXin Li cfile_getter = _initialize_control_file_getter(build) 219*9c5db199SXin Li if suite_common.ENABLE_CONTROLS_IN_BATCH: 220*9c5db199SXin Li control_file_info_list = cfile_getter.get_suite_info() 221*9c5db199SXin Li 222*9c5db199SXin Li for test in tests: 223*9c5db199SXin Li # Read and parse the control file 224*9c5db199SXin Li if suite_common.ENABLE_CONTROLS_IN_BATCH: 225*9c5db199SXin Li control_file = control_file_info_list[test.path] 226*9c5db199SXin Li else: 227*9c5db199SXin Li control_file = cfile_getter.get_control_file_contents( 228*9c5db199SXin Li test.path) 229*9c5db199SXin Li raw_control_files.append(control_file) 230*9c5db199SXin Li return raw_control_files 231*9c5db199SXin Li 232*9c5db199SXin Li 233*9c5db199SXin Lidef _initialize_control_file_getter(build): 234*9c5db199SXin Li """Get the remote control file getter. 235*9c5db199SXin Li 236*9c5db199SXin Li @param build: unique name by which to refer to a remote build image. 237*9c5db199SXin Li 238*9c5db199SXin Li @return: A control file getter object. 239*9c5db199SXin Li """ 240*9c5db199SXin Li # Stage the test artifacts. 241*9c5db199SXin Li try: 242*9c5db199SXin Li ds = dev_server.ImageServer.resolve(build) 243*9c5db199SXin Li ds_name = ds.hostname 244*9c5db199SXin Li build = ds.translate(build) 245*9c5db199SXin Li except dev_server.DevServerException as e: 246*9c5db199SXin Li raise ValueError('Could not resolve build %s: %s' % 247*9c5db199SXin Li (build, e)) 248*9c5db199SXin Li 249*9c5db199SXin Li try: 250*9c5db199SXin Li ds.stage_artifacts(image=build, artifacts=['test_suites']) 251*9c5db199SXin Li except dev_server.DevServerException as e: 252*9c5db199SXin Li raise error.StageControlFileFailure( 253*9c5db199SXin Li 'Failed to stage %s on %s: %s' % (build, ds_name, e)) 254*9c5db199SXin Li 255*9c5db199SXin Li # Collect the control files specified in this build 256*9c5db199SXin Li return control_file_getter.DevServerGetter.create(build, ds) 257