xref: /aosp_15_r20/external/autotest/frontend/afe/control_file.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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