blob: ff0e12aa5b3810cc38837fd973479462dce7feb0 [file] [log] [blame]
mblighe8819cd2008-02-15 16:48:40 +00001"""\
2Logic for control file generation.
3"""
4
5__author__ = 'showard@google.com (Steve Howard)'
6
showard9ca52702008-06-02 21:14:49 +00007import re, os
mbligh120351e2009-01-24 01:40:45 +00008
9import common
Allen Licdd00f22017-02-01 18:01:52 -080010from autotest_lib.client.common_lib import error
11from autotest_lib.client.common_lib.cros import dev_server
mbligh120351e2009-01-24 01:40:45 +000012from autotest_lib.frontend.afe import model_logic
Allen Licdd00f22017-02-01 18:01:52 -080013from autotest_lib.server.cros.dynamic_suite import control_file_getter
14from autotest_lib.server.cros.dynamic_suite import suite as SuiteBase
mblighe8819cd2008-02-15 16:48:40 +000015import frontend.settings
16
17AUTOTEST_DIR = os.path.abspath(os.path.join(
18 os.path.dirname(frontend.settings.__file__), '..'))
19
mblighc86113b2009-04-28 18:32:51 +000020EMPTY_TEMPLATE = 'def step_init():\n'
mblighe8819cd2008-02-15 16:48:40 +000021
mblighf5fdfab2008-06-16 23:57:25 +000022CLIENT_STEP_TEMPLATE = " job.next_step('step%d')\n"
mblighc86113b2009-04-28 18:32:51 +000023SERVER_STEP_TEMPLATE = ' step%d()\n'
showard9ca52702008-06-02 21:14:49 +000024
showard232b7ae2009-11-10 00:46:48 +000025
Richard Barnette8e33b4e2016-05-21 12:12:26 -070026def _read_control_file(test):
Michael Tang84a2ecf2016-06-07 15:10:53 -070027 """Reads the test control file from local disk.
28
29 @param test The test name.
30
31 @return The test control file string.
32 """
jadmanski0afbb632008-06-06 21:10:57 +000033 control_file = open(os.path.join(AUTOTEST_DIR, test.path))
34 control_contents = control_file.read()
35 control_file.close()
36 return control_contents
mblighe8819cd2008-02-15 16:48:40 +000037
38
Richard Barnette8e33b4e2016-05-21 12:12:26 -070039def _add_boilerplate_to_nested_steps(lines):
Michael Tang84a2ecf2016-06-07 15:10:53 -070040 """Adds boilerplate magic.
41
42 @param lines The string of lines.
43
44 @returns The string lines.
45 """
jadmanski0afbb632008-06-06 21:10:57 +000046 # Look for a line that begins with 'def step_init():' while
47 # being flexible on spacing. If it's found, this will be
48 # a nested set of steps, so add magic to make it work.
49 # See client/bin/job.py's step_engine for more info.
50 if re.search(r'^(.*\n)*def\s+step_init\s*\(\s*\)\s*:', lines):
51 lines += '\nreturn locals() '
52 lines += '# Boilerplate magic for nested sets of steps'
53 return lines
showard9ca52702008-06-02 21:14:49 +000054
55
Richard Barnette8e33b4e2016-05-21 12:12:26 -070056def _format_step(item, lines):
Michael Tang84a2ecf2016-06-07 15:10:53 -070057 """Format a line item.
58 @param item The item number.
59 @param lines The string of lines.
60
61 @returns The string lines.
62 """
Richard Barnette8e33b4e2016-05-21 12:12:26 -070063 lines = _indent_text(lines, ' ')
jadmanski0afbb632008-06-06 21:10:57 +000064 lines = 'def step%d():\n%s' % (item, lines)
65 return lines
showard9ca52702008-06-02 21:14:49 +000066
jadmanski0afbb632008-06-06 21:10:57 +000067
Richard Barnette8e33b4e2016-05-21 12:12:26 -070068def _get_tests_stanza(tests, is_server, prepend=None, append=None,
Michael Tang84a2ecf2016-06-07 15:10:53 -070069 client_control_file='', test_source_build=None):
mbligh120351e2009-01-24 01:40:45 +000070 """ Constructs the control file test step code from a list of tests.
mbligh12eafff2008-11-05 23:42:42 +000071
mbligh120351e2009-01-24 01:40:45 +000072 @param tests A sequence of test control files to run.
73 @param is_server bool, Is this a server side test?
74 @param prepend A list of steps to prepend to each client test.
75 Defaults to [].
76 @param append A list of steps to append to each client test.
77 Defaults to [].
78 @param client_control_file If specified, use this text as the body of a
79 final client control file to run after tests. is_server must be False.
Michael Tang84a2ecf2016-06-07 15:10:53 -070080 @param test_source_build: Build to be used to retrieve test code. Default
81 to None.
mbligh120351e2009-01-24 01:40:45 +000082
83 @returns The control file test code to be run.
mbligh12eafff2008-11-05 23:42:42 +000084 """
mbligh120351e2009-01-24 01:40:45 +000085 assert not (client_control_file and is_server)
mbligh12eafff2008-11-05 23:42:42 +000086 if not prepend:
87 prepend = []
88 if not append:
89 append = []
Michael Tang84a2ecf2016-06-07 15:10:53 -070090 if test_source_build:
Allen Licdd00f22017-02-01 18:01:52 -080091 raw_control_files = _get_test_control_files_by_build(
Michael Tang84a2ecf2016-06-07 15:10:53 -070092 tests, test_source_build)
93 else:
Richard Barnette8e33b4e2016-05-21 12:12:26 -070094 raw_control_files = [_read_control_file(test) for test in tests]
mbligh120351e2009-01-24 01:40:45 +000095 if client_control_file:
Richard Barnette8e33b4e2016-05-21 12:12:26 -070096 # 'return locals()' is always appended in case the user forgot, it
mbligh120351e2009-01-24 01:40:45 +000097 # is necessary to allow for nested step engine execution to work.
98 raw_control_files.append(client_control_file + '\nreturn locals()')
Richard Barnette8e33b4e2016-05-21 12:12:26 -070099 raw_steps = prepend + [_add_boilerplate_to_nested_steps(step)
jadmanski0afbb632008-06-06 21:10:57 +0000100 for step in raw_control_files] + append
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700101 steps = [_format_step(index, step)
jadmanski0afbb632008-06-06 21:10:57 +0000102 for index, step in enumerate(raw_steps)]
mblighc86113b2009-04-28 18:32:51 +0000103 if is_server:
104 step_template = SERVER_STEP_TEMPLATE
105 footer = '\n\nstep_init()\n'
106 else:
107 step_template = CLIENT_STEP_TEMPLATE
108 footer = ''
109
110 header = ''.join(step_template % i for i in xrange(len(steps)))
111 return header + '\n' + '\n\n'.join(steps) + footer
mblighe8819cd2008-02-15 16:48:40 +0000112
113
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700114def _indent_text(text, indent):
showardd2624152009-04-29 21:29:01 +0000115 """Indent given lines of python code avoiding indenting multiline
Michael Tang84a2ecf2016-06-07 15:10:53 -0700116 quoted content (only for triple " and ' quoting for now).
117
118 @param text The string of lines.
119 @param indent The indent string.
120
121 @return The indented string.
122 """
showardd2624152009-04-29 21:29:01 +0000123 regex = re.compile('(\\\\*)("""|\'\'\')')
124
125 res = []
126 in_quote = None
127 for line in text.splitlines():
128 # if not within a multinline quote indent the line contents
129 if in_quote:
130 res.append(line)
131 else:
132 res.append(indent + line)
133
134 while line:
135 match = regex.search(line)
136 if match:
137 # for an even number of backslashes before the triple quote
138 if len(match.group(1)) % 2 == 0:
139 if not in_quote:
140 in_quote = match.group(2)[0]
141 elif in_quote == match.group(2)[0]:
142 # if we found a matching end triple quote
143 in_quote = None
144 line = line[match.end():]
145 else:
146 break
147
148 return '\n'.join(res)
mblighe8819cd2008-02-15 16:48:40 +0000149
150
showard91f85102009-10-12 20:34:52 +0000151def _get_profiler_commands(profilers, is_server, profile_only):
showard2b9a88b2008-06-13 20:55:03 +0000152 prepend, append = [], []
showard91f85102009-10-12 20:34:52 +0000153 if profile_only is not None:
mblighfbf73ae2009-12-19 05:22:42 +0000154 prepend.append("job.default_profile_only = %r" % profile_only)
showard2b9a88b2008-06-13 20:55:03 +0000155 for profiler in profilers:
156 prepend.append("job.profilers.add('%s')" % profiler.name)
157 append.append("job.profilers.delete('%s')" % profiler.name)
158 return prepend, append
159
160
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700161def _sanity_check_generate_control(is_server, client_control_file):
mbligh12eafff2008-11-05 23:42:42 +0000162 """
mbligh120351e2009-01-24 01:40:45 +0000163 Sanity check some of the parameters to generate_control().
164
165 This exists as its own function so that site_control_file may call it as
166 well from its own generate_control().
167
168 @raises ValidationError if any of the parameters do not make sense.
169 """
170 if is_server and client_control_file:
171 raise model_logic.ValidationError(
172 {'tests' : 'You cannot run server tests at the same time '
173 'as directly supplying a client-side control file.'})
174
175
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700176def generate_control(tests, is_server=False, profilers=(),
177 client_control_file='', profile_only=None,
178 test_source_build=None):
mbligh120351e2009-01-24 01:40:45 +0000179 """
180 Generate a control file for a sequence of tests.
181
182 @param tests A sequence of test control files to run.
mbligh120351e2009-01-24 01:40:45 +0000183 @param is_server bool, Is this a server control file rather than a client?
184 @param profilers A list of profiler objects to enable during the tests.
185 @param client_control_file Contents of a client control file to run as the
mbligha3c58d22009-08-24 22:01:51 +0000186 last test after everything in tests. Requires is_server=False.
showard91f85102009-10-12 20:34:52 +0000187 @param profile_only bool, should this control file run all tests in
188 profile_only mode by default
Michael Tang84a2ecf2016-06-07 15:10:53 -0700189 @param test_source_build: Build to be used to retrieve test code. Default
190 to None.
mbligh120351e2009-01-24 01:40:45 +0000191
192 @returns The control file text as a string.
193 """
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700194 _sanity_check_generate_control(is_server=is_server,
195 client_control_file=client_control_file)
196 control_file_text = EMPTY_TEMPLATE
showard91f85102009-10-12 20:34:52 +0000197 prepend, append = _get_profiler_commands(profilers, is_server, profile_only)
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700198 control_file_text += _get_tests_stanza(tests, is_server, prepend, append,
199 client_control_file,
200 test_source_build)
jadmanski0afbb632008-06-06 21:10:57 +0000201 return control_file_text
Allen Licdd00f22017-02-01 18:01:52 -0800202
203
204def _get_test_control_files_by_build(tests, build, ignore_invalid_tests=False):
205 """Get the test control files that are available for the specified build.
206
207 @param tests A sequence of test objects to run.
208 @param build: unique name by which to refer to the image.
209 @param ignore_invalid_tests: flag on if unparsable tests are ignored.
210
211 @return: A sorted list of all tests that are in the build specified.
212 """
213 raw_control_files = []
214 # shortcut to avoid staging the image.
215 if not tests:
216 return raw_control_files
217
218 cfile_getter = _initialize_control_file_getter(build)
219 if SuiteBase.ENABLE_CONTROLS_IN_BATCH:
220 control_file_info_list = cfile_getter.get_suite_info()
221
222 for test in tests:
223 # Read and parse the control file
224 if SuiteBase.ENABLE_CONTROLS_IN_BATCH:
225 control_file = control_file_info_list[test.path]
226 else:
227 control_file = cfile_getter.get_control_file_contents(
228 test.path)
229 raw_control_files.append(control_file)
230 return raw_control_files
231
232
233def _initialize_control_file_getter(build):
234 """Get the remote control file getter.
235
236 @param build: unique name by which to refer to a remote build image.
237
238 @return: A control file getter object.
239 """
240 # Stage the test artifacts.
241 try:
242 ds = dev_server.ImageServer.resolve(build)
243 ds_name = ds.hostname
244 build = ds.translate(build)
245 except dev_server.DevServerException as e:
246 raise ValueError('Could not resolve build %s: %s' %
247 (build, e))
248
249 try:
250 ds.stage_artifacts(image=build, artifacts=['test_suites'])
251 except dev_server.DevServerException as e:
252 raise error.StageControlFileFailure(
253 'Failed to stage %s on %s: %s' % (build, ds_name, e))
254
255 # Collect the control files specified in this build
256 return control_file_getter.DevServerGetter.create(build, ds)