blob: e71032a6ec8981f5446d5db85ff5b584f6d8d520 [file] [log] [blame]
Matthew Treinisha051c222016-05-23 15:48:22 -04001# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13"""
14Runs tempest tests
15
16This command is used for running the tempest tests
17
18Test Selection
19==============
20Tempest run has several options:
21
22 * **--regex/-r**: This is a selection regex like what testr uses. It will run
23 any tests that match on re.match() with the regex
Nicolas Bockff27d3b2017-01-11 13:30:32 -070024 * **--smoke/-s**: Run all the tests tagged as smoke
Matthew Treinisha051c222016-05-23 15:48:22 -040025
Masayuki Igawa0dcc6062016-08-24 17:06:11 +090026There are also the **--blacklist-file** and **--whitelist-file** options that
Matthew Treinisha6b4da92016-05-23 17:24:12 -040027let you pass a filepath to tempest run with the file format being a line
zhangyanxian68d31b82016-07-13 01:48:33 +000028separated regex, with '#' used to signify the start of a comment on a line.
Matthew Treinisha6b4da92016-05-23 17:24:12 -040029For example::
30
31 # Regex file
32 ^regex1 # Match these tests
33 .*regex2 # Match those tests
34
35The blacklist file will be used to construct a negative lookahead regex and
36the whitelist file will simply OR all the regexes in the file. The whitelist
37and blacklist file options are mutually exclusive so you can't use them
38together. However, you can combine either with a normal regex or the *--smoke*
39flag. When used with a blacklist file the generated regex will be combined to
40something like::
41
42 ^((?!black_regex1|black_regex2).)*$cli_regex1
43
44When combined with a whitelist file all the regexes from the file and the CLI
45regexes will be ORed.
46
Matthew Treinisha051c222016-05-23 15:48:22 -040047You can also use the **--list-tests** option in conjunction with selection
48arguments to list which tests will be run.
49
50Test Execution
51==============
52There are several options to control how the tests are executed. By default
53tempest will run in parallel with a worker for each CPU present on the machine.
54If you want to adjust the number of workers use the **--concurrency** option
Nicolas Bockff27d3b2017-01-11 13:30:32 -070055and if you want to run tests serially use **--serial/-t**
Matthew Treinisha051c222016-05-23 15:48:22 -040056
Matthew Treinishc89a9512016-06-09 17:43:35 -040057Running with Workspaces
58-----------------------
59Tempest run enables you to run your tempest tests from any setup tempest
60workspace it relies on you having setup a tempest workspace with either the
61``tempest init`` or ``tempest workspace`` commands. Then using the
62``--workspace`` CLI option you can specify which one of your workspaces you
63want to run tempest from. Using this option you don't have to run Tempest
64directly with you current working directory being the workspace, Tempest will
65take care of managing everything to be executed from there.
66
Matthew Treinish30c9ee52016-06-09 17:58:47 -040067Running from Anywhere
68---------------------
69Tempest run provides you with an option to execute tempest from anywhere on
70your system. You are required to provide a config file in this case with the
71``--config-file`` option. When run tempest will create a .testrepository
72directory and a .testr.conf file in your current working directory. This way
73you can use testr commands directly to inspect the state of the previous run.
74
Matthew Treinisha051c222016-05-23 15:48:22 -040075Test Output
76===========
77By default tempest run's output to STDOUT will be generated using the
78subunit-trace output filter. But, if you would prefer a subunit v2 stream be
79output to STDOUT use the **--subunit** flag
80
Matthew Treinish7d6e48c2017-03-03 12:44:50 -050081Combining Runs
82==============
83
84There are certain situations in which you want to split a single run of tempest
85across 2 executions of tempest run. (for example to run part of the tests
86serially and others in parallel) To accomplish this but still treat the results
87as a single run you can leverage the **--combine** option which will append
88the current run's results with the previous runs.
Matthew Treinisha051c222016-05-23 15:48:22 -040089"""
90
91import io
92import os
93import sys
Matthew Treinish7d6e48c2017-03-03 12:44:50 -050094import tempfile
Matthew Treinisha051c222016-05-23 15:48:22 -040095import threading
96
97from cliff import command
Matthew Treinisha6b4da92016-05-23 17:24:12 -040098from os_testr import regex_builder
Matthew Treinisha051c222016-05-23 15:48:22 -040099from os_testr import subunit_trace
Prateek Aroraa028de12017-03-14 09:01:03 -0400100from oslo_serialization import jsonutils as json
Davanum Srinivas00e3f452017-01-05 12:40:45 -0500101import six
Matthew Treinisha051c222016-05-23 15:48:22 -0400102from testrepository.commands import run_argv
103
ghanshyam009a1f62017-08-08 10:22:57 +0300104from tempest import clients
Prateek Aroraa028de12017-03-14 09:01:03 -0400105from tempest.cmd import cleanup_service
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400106from tempest.cmd import init
Matthew Treinishc89a9512016-06-09 17:43:35 -0400107from tempest.cmd import workspace
Prateek Aroraa028de12017-03-14 09:01:03 -0400108from tempest.common import credentials_factory as credentials
Matthew Treinisha051c222016-05-23 15:48:22 -0400109from tempest import config
110
111
Matthew Treinisha051c222016-05-23 15:48:22 -0400112CONF = config.CONF
Prateek Aroraa028de12017-03-14 09:01:03 -0400113SAVED_STATE_JSON = "saved_state.json"
Matthew Treinisha051c222016-05-23 15:48:22 -0400114
115
116class TempestRun(command.Command):
117
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400118 def _set_env(self, config_file=None):
119 if config_file:
120 CONF.set_config_path(os.path.abspath(config_file))
Matthew Treinisha051c222016-05-23 15:48:22 -0400121 # NOTE(mtreinish): This is needed so that testr doesn't gobble up any
122 # stacktraces on failure.
123 if 'TESTR_PDB' in os.environ:
124 return
125 else:
126 os.environ["TESTR_PDB"] = ""
Davanum Srinivas00e3f452017-01-05 12:40:45 -0500127 # NOTE(dims): most of our .testr.conf try to test for PYTHON
128 # environment variable and fall back to "python", under python3
129 # if it does not exist. we should set it to the python3 executable
130 # to deal with this situation better for now.
131 if six.PY3 and 'PYTHON' not in os.environ:
132 os.environ['PYTHON'] = sys.executable
Matthew Treinisha051c222016-05-23 15:48:22 -0400133
Matthew Treinishc89a9512016-06-09 17:43:35 -0400134 def _create_testrepository(self):
135 if not os.path.isdir('.testrepository'):
136 returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout,
137 sys.stderr)
138 if returncode:
139 sys.exit(returncode)
140
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400141 def _create_testr_conf(self):
142 top_level_path = os.path.dirname(os.path.dirname(__file__))
143 discover_path = os.path.join(top_level_path, 'test_discover')
144 file_contents = init.TESTR_CONF % (top_level_path, discover_path)
145 with open('.testr.conf', 'w+') as testr_conf_file:
146 testr_conf_file.write(file_contents)
147
Matthew Treinisha051c222016-05-23 15:48:22 -0400148 def take_action(self, parsed_args):
Masayuki Igawafe2fa002016-06-22 12:58:34 +0900149 returncode = 0
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400150 if parsed_args.config_file:
151 self._set_env(parsed_args.config_file)
152 else:
153 self._set_env()
Matthew Treinishc89a9512016-06-09 17:43:35 -0400154 # Workspace execution mode
155 if parsed_args.workspace:
156 workspace_mgr = workspace.WorkspaceManager(
157 parsed_args.workspace_path)
158 path = workspace_mgr.get_workspace(parsed_args.workspace)
Brant Knudson6a090f42016-10-13 12:51:49 -0500159 if not path:
160 sys.exit(
161 "The %r workspace isn't registered in "
162 "%r. Use 'tempest init' to "
163 "register the workspace." %
164 (parsed_args.workspace, workspace_mgr.path))
Matthew Treinishc89a9512016-06-09 17:43:35 -0400165 os.chdir(path)
166 # NOTE(mtreinish): tempest init should create a .testrepository dir
167 # but since workspaces can be imported let's sanity check and
168 # ensure that one is created
169 self._create_testrepository()
zhuflbedb2ad2016-06-20 11:39:01 +0800170 # Local execution mode
Matthew Treinishc89a9512016-06-09 17:43:35 -0400171 elif os.path.isfile('.testr.conf'):
Matthew Treinisha051c222016-05-23 15:48:22 -0400172 # If you're running in local execution mode and there is not a
173 # testrepository dir create one
Matthew Treinishc89a9512016-06-09 17:43:35 -0400174 self._create_testrepository()
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400175 # local execution with config file mode
176 elif parsed_args.config_file:
177 self._create_testr_conf()
178 self._create_testrepository()
Matthew Treinisha051c222016-05-23 15:48:22 -0400179 else:
zhuflbedb2ad2016-06-20 11:39:01 +0800180 print("No .testr.conf file was found for local execution")
Matthew Treinisha051c222016-05-23 15:48:22 -0400181 sys.exit(2)
Prateek Aroraa028de12017-03-14 09:01:03 -0400182 if parsed_args.state:
183 self._init_state()
184 else:
185 pass
186
Matthew Treinish7d6e48c2017-03-03 12:44:50 -0500187 if parsed_args.combine:
188 temp_stream = tempfile.NamedTemporaryFile()
189 return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
190 temp_stream, sys.stderr)
191 if return_code > 0:
192 sys.exit(return_code)
Matthew Treinisha051c222016-05-23 15:48:22 -0400193
194 regex = self._build_regex(parsed_args)
195 if parsed_args.list_tests:
196 argv = ['tempest', 'list-tests', regex]
197 returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
198 else:
199 options = self._build_options(parsed_args)
200 returncode = self._run(regex, options)
Matthew Treinish7d6e48c2017-03-03 12:44:50 -0500201 if returncode > 0:
202 sys.exit(returncode)
203
204 if parsed_args.combine:
205 return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
206 temp_stream, sys.stderr)
207 if return_code > 0:
208 sys.exit(return_code)
209 returncode = run_argv(['tempest', 'load', temp_stream.name],
210 sys.stdin, sys.stdout, sys.stderr)
Matthew Treinisha051c222016-05-23 15:48:22 -0400211 sys.exit(returncode)
212
213 def get_description(self):
214 return 'Run tempest'
215
Prateek Aroraa028de12017-03-14 09:01:03 -0400216 def _init_state(self):
217 print("Initializing saved state.")
218 data = {}
219 self.global_services = cleanup_service.get_global_cleanup_services()
ghanshyam009a1f62017-08-08 10:22:57 +0300220 self.admin_mgr = clients.Manager(
221 credentials.get_configured_admin_credentials())
Prateek Aroraa028de12017-03-14 09:01:03 -0400222 admin_mgr = self.admin_mgr
223 kwargs = {'data': data,
224 'is_dry_run': False,
225 'saved_state_json': data,
226 'is_preserve': False,
227 'is_save_state': True}
228 for service in self.global_services:
229 svc = service(admin_mgr, **kwargs)
230 svc.run()
231
232 with open(SAVED_STATE_JSON, 'w+') as f:
233 f.write(json.dumps(data,
234 sort_keys=True, indent=2, separators=(',', ': ')))
235
Matthew Treinisha051c222016-05-23 15:48:22 -0400236 def get_parser(self, prog_name):
237 parser = super(TempestRun, self).get_parser(prog_name)
238 parser = self._add_args(parser)
239 return parser
240
241 def _add_args(self, parser):
Matthew Treinishc89a9512016-06-09 17:43:35 -0400242 # workspace args
243 parser.add_argument('--workspace', default=None,
244 help='Name of tempest workspace to use for running'
245 ' tests. You can see a list of workspaces '
246 'with tempest workspace list')
247 parser.add_argument('--workspace-path', default=None,
248 dest='workspace_path',
249 help="The path to the workspace file, the default "
250 "is ~/.tempest/workspace.yaml")
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400251 # Configuration flags
252 parser.add_argument('--config-file', default=None, dest='config_file',
253 help='Configuration file to run tempest with')
Matthew Treinisha051c222016-05-23 15:48:22 -0400254 # test selection args
255 regex = parser.add_mutually_exclusive_group()
Nicolas Bockff27d3b2017-01-11 13:30:32 -0700256 regex.add_argument('--smoke', '-s', action='store_true',
Matthew Treinisha051c222016-05-23 15:48:22 -0400257 help="Run the smoke tests only")
258 regex.add_argument('--regex', '-r', default='',
259 help='A normal testr selection regex used to '
260 'specify a subset of tests to run')
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400261 list_selector = parser.add_mutually_exclusive_group()
Masayuki Igawa0dcc6062016-08-24 17:06:11 +0900262 list_selector.add_argument('--whitelist-file', '--whitelist_file',
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400263 help="Path to a whitelist file, this file "
zhangyanxian68d31b82016-07-13 01:48:33 +0000264 "contains a separate regex on each "
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400265 "newline.")
Masayuki Igawa0dcc6062016-08-24 17:06:11 +0900266 list_selector.add_argument('--blacklist-file', '--blacklist_file',
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400267 help='Path to a blacklist file, this file '
268 'contains a separate regex exclude on '
269 'each newline')
Matthew Treinisha051c222016-05-23 15:48:22 -0400270 # list only args
271 parser.add_argument('--list-tests', '-l', action='store_true',
272 help='List tests',
273 default=False)
Puneet Arora9ed41042016-07-05 19:46:06 +0000274 # execution args
Matthew Treinisha051c222016-05-23 15:48:22 -0400275 parser.add_argument('--concurrency', '-w',
276 help="The number of workers to use, defaults to "
277 "the number of cpus")
278 parallel = parser.add_mutually_exclusive_group()
279 parallel.add_argument('--parallel', dest='parallel',
280 action='store_true',
281 help='Run tests in parallel (this is the'
282 ' default)')
Nicolas Bockff27d3b2017-01-11 13:30:32 -0700283 parallel.add_argument('--serial', '-t', dest='parallel',
Matthew Treinisha051c222016-05-23 15:48:22 -0400284 action='store_false',
285 help='Run tests serially')
Prateek Aroraa028de12017-03-14 09:01:03 -0400286 parser.add_argument('--save-state', dest='state',
287 action='store_true',
288 help="To save the state of the cloud before "
289 "running tempest.")
Matthew Treinisha051c222016-05-23 15:48:22 -0400290 # output args
291 parser.add_argument("--subunit", action='store_true',
292 help='Enable subunit v2 output')
Matthew Treinish7d6e48c2017-03-03 12:44:50 -0500293 parser.add_argument("--combine", action='store_true',
294 help='Combine the output of this run with the '
295 "previous run's as a combined stream in the "
296 "testr repository after it finish")
Matthew Treinisha051c222016-05-23 15:48:22 -0400297
298 parser.set_defaults(parallel=True)
299 return parser
300
301 def _build_regex(self, parsed_args):
302 regex = ''
303 if parsed_args.smoke:
304 regex = 'smoke'
305 elif parsed_args.regex:
306 regex = parsed_args.regex
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400307 if parsed_args.whitelist_file or parsed_args.blacklist_file:
308 regex = regex_builder.construct_regex(parsed_args.blacklist_file,
309 parsed_args.whitelist_file,
310 regex, False)
Matthew Treinisha051c222016-05-23 15:48:22 -0400311 return regex
312
313 def _build_options(self, parsed_args):
314 options = []
315 if parsed_args.subunit:
316 options.append("--subunit")
317 if parsed_args.parallel:
318 options.append("--parallel")
319 if parsed_args.concurrency:
320 options.append("--concurrency=%s" % parsed_args.concurrency)
321 return options
322
323 def _run(self, regex, options):
324 returncode = 0
325 argv = ['tempest', 'run', regex] + options
326 if '--subunit' in options:
327 returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
328 else:
329 argv.append('--subunit')
330 stdin = io.StringIO()
331 stdout_r, stdout_w = os.pipe()
332 subunit_w = os.fdopen(stdout_w, 'wt')
333 subunit_r = os.fdopen(stdout_r)
334 returncodes = {}
335
336 def run_argv_thread():
337 returncodes['testr'] = run_argv(argv, stdin, subunit_w,
338 sys.stderr)
339 subunit_w.close()
340
341 run_thread = threading.Thread(target=run_argv_thread)
342 run_thread.start()
Matthew Treinish18d2d672016-09-20 08:30:34 -0400343 returncodes['subunit-trace'] = subunit_trace.trace(
344 subunit_r, sys.stdout, post_fails=True, print_failures=True)
Matthew Treinisha051c222016-05-23 15:48:22 -0400345 run_thread.join()
346 subunit_r.close()
347 # python version of pipefail
348 if returncodes['testr']:
349 returncode = returncodes['testr']
350 elif returncodes['subunit-trace']:
351 returncode = returncodes['subunit-trace']
352 return returncode