blob: f07f197c27b7ad864308c5332ee3a30042155d95 [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
ubuntu0dba54c2017-07-25 15:25:22 -050050You can also use the **--load-list** option that lets you pass a filepath to
51tempest run with the file format being in a non-regex format, similar to the
52tests generated by the **--list-tests** option. You can specify target tests
53by removing unnecessary tests from a list file which is generated from
54**--list-tests** option.
55
Matthew Treinisha051c222016-05-23 15:48:22 -040056Test Execution
57==============
58There are several options to control how the tests are executed. By default
59tempest will run in parallel with a worker for each CPU present on the machine.
60If you want to adjust the number of workers use the **--concurrency** option
Nicolas Bockff27d3b2017-01-11 13:30:32 -070061and if you want to run tests serially use **--serial/-t**
Matthew Treinisha051c222016-05-23 15:48:22 -040062
Matthew Treinishc89a9512016-06-09 17:43:35 -040063Running with Workspaces
64-----------------------
65Tempest run enables you to run your tempest tests from any setup tempest
66workspace it relies on you having setup a tempest workspace with either the
67``tempest init`` or ``tempest workspace`` commands. Then using the
68``--workspace`` CLI option you can specify which one of your workspaces you
69want to run tempest from. Using this option you don't have to run Tempest
70directly with you current working directory being the workspace, Tempest will
71take care of managing everything to be executed from there.
72
Matthew Treinish30c9ee52016-06-09 17:58:47 -040073Running from Anywhere
74---------------------
75Tempest run provides you with an option to execute tempest from anywhere on
76your system. You are required to provide a config file in this case with the
77``--config-file`` option. When run tempest will create a .testrepository
78directory and a .testr.conf file in your current working directory. This way
79you can use testr commands directly to inspect the state of the previous run.
80
Matthew Treinisha051c222016-05-23 15:48:22 -040081Test Output
82===========
83By default tempest run's output to STDOUT will be generated using the
84subunit-trace output filter. But, if you would prefer a subunit v2 stream be
85output to STDOUT use the **--subunit** flag
86
Matthew Treinish7d6e48c2017-03-03 12:44:50 -050087Combining Runs
88==============
89
90There are certain situations in which you want to split a single run of tempest
91across 2 executions of tempest run. (for example to run part of the tests
92serially and others in parallel) To accomplish this but still treat the results
93as a single run you can leverage the **--combine** option which will append
94the current run's results with the previous runs.
Matthew Treinisha051c222016-05-23 15:48:22 -040095"""
96
97import io
98import os
99import sys
Matthew Treinish7d6e48c2017-03-03 12:44:50 -0500100import tempfile
Matthew Treinisha051c222016-05-23 15:48:22 -0400101import threading
102
103from cliff import command
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400104from os_testr import regex_builder
Matthew Treinisha051c222016-05-23 15:48:22 -0400105from os_testr import subunit_trace
Prateek Aroraa028de12017-03-14 09:01:03 -0400106from oslo_serialization import jsonutils as json
Davanum Srinivas00e3f452017-01-05 12:40:45 -0500107import six
Matthew Treinisha051c222016-05-23 15:48:22 -0400108from testrepository.commands import run_argv
109
ghanshyam009a1f62017-08-08 10:22:57 +0300110from tempest import clients
Prateek Aroraa028de12017-03-14 09:01:03 -0400111from tempest.cmd import cleanup_service
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400112from tempest.cmd import init
Matthew Treinishc89a9512016-06-09 17:43:35 -0400113from tempest.cmd import workspace
Prateek Aroraa028de12017-03-14 09:01:03 -0400114from tempest.common import credentials_factory as credentials
Matthew Treinisha051c222016-05-23 15:48:22 -0400115from tempest import config
116
117
Matthew Treinisha051c222016-05-23 15:48:22 -0400118CONF = config.CONF
Prateek Aroraa028de12017-03-14 09:01:03 -0400119SAVED_STATE_JSON = "saved_state.json"
Matthew Treinisha051c222016-05-23 15:48:22 -0400120
121
122class TempestRun(command.Command):
123
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400124 def _set_env(self, config_file=None):
125 if config_file:
126 CONF.set_config_path(os.path.abspath(config_file))
Matthew Treinisha051c222016-05-23 15:48:22 -0400127 # NOTE(mtreinish): This is needed so that testr doesn't gobble up any
128 # stacktraces on failure.
129 if 'TESTR_PDB' in os.environ:
130 return
131 else:
132 os.environ["TESTR_PDB"] = ""
Davanum Srinivas00e3f452017-01-05 12:40:45 -0500133 # NOTE(dims): most of our .testr.conf try to test for PYTHON
134 # environment variable and fall back to "python", under python3
135 # if it does not exist. we should set it to the python3 executable
136 # to deal with this situation better for now.
137 if six.PY3 and 'PYTHON' not in os.environ:
138 os.environ['PYTHON'] = sys.executable
Matthew Treinisha051c222016-05-23 15:48:22 -0400139
Matthew Treinishc89a9512016-06-09 17:43:35 -0400140 def _create_testrepository(self):
141 if not os.path.isdir('.testrepository'):
142 returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout,
143 sys.stderr)
144 if returncode:
145 sys.exit(returncode)
146
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400147 def _create_testr_conf(self):
148 top_level_path = os.path.dirname(os.path.dirname(__file__))
149 discover_path = os.path.join(top_level_path, 'test_discover')
150 file_contents = init.TESTR_CONF % (top_level_path, discover_path)
151 with open('.testr.conf', 'w+') as testr_conf_file:
152 testr_conf_file.write(file_contents)
153
Matthew Treinisha051c222016-05-23 15:48:22 -0400154 def take_action(self, parsed_args):
Masayuki Igawafe2fa002016-06-22 12:58:34 +0900155 returncode = 0
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400156 if parsed_args.config_file:
157 self._set_env(parsed_args.config_file)
158 else:
159 self._set_env()
Matthew Treinishc89a9512016-06-09 17:43:35 -0400160 # Workspace execution mode
161 if parsed_args.workspace:
162 workspace_mgr = workspace.WorkspaceManager(
163 parsed_args.workspace_path)
164 path = workspace_mgr.get_workspace(parsed_args.workspace)
Brant Knudson6a090f42016-10-13 12:51:49 -0500165 if not path:
166 sys.exit(
167 "The %r workspace isn't registered in "
168 "%r. Use 'tempest init' to "
169 "register the workspace." %
170 (parsed_args.workspace, workspace_mgr.path))
Matthew Treinishc89a9512016-06-09 17:43:35 -0400171 os.chdir(path)
172 # NOTE(mtreinish): tempest init should create a .testrepository dir
173 # but since workspaces can be imported let's sanity check and
174 # ensure that one is created
175 self._create_testrepository()
zhuflbedb2ad2016-06-20 11:39:01 +0800176 # Local execution mode
Matthew Treinishc89a9512016-06-09 17:43:35 -0400177 elif os.path.isfile('.testr.conf'):
Matthew Treinisha051c222016-05-23 15:48:22 -0400178 # If you're running in local execution mode and there is not a
179 # testrepository dir create one
Matthew Treinishc89a9512016-06-09 17:43:35 -0400180 self._create_testrepository()
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400181 # local execution with config file mode
182 elif parsed_args.config_file:
183 self._create_testr_conf()
184 self._create_testrepository()
Matthew Treinisha051c222016-05-23 15:48:22 -0400185 else:
zhuflbedb2ad2016-06-20 11:39:01 +0800186 print("No .testr.conf file was found for local execution")
Matthew Treinisha051c222016-05-23 15:48:22 -0400187 sys.exit(2)
Prateek Aroraa028de12017-03-14 09:01:03 -0400188 if parsed_args.state:
189 self._init_state()
190 else:
191 pass
192
Matthew Treinish7d6e48c2017-03-03 12:44:50 -0500193 if parsed_args.combine:
194 temp_stream = tempfile.NamedTemporaryFile()
195 return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
196 temp_stream, sys.stderr)
197 if return_code > 0:
198 sys.exit(return_code)
Matthew Treinisha051c222016-05-23 15:48:22 -0400199
200 regex = self._build_regex(parsed_args)
201 if parsed_args.list_tests:
202 argv = ['tempest', 'list-tests', regex]
203 returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
204 else:
205 options = self._build_options(parsed_args)
206 returncode = self._run(regex, options)
Matthew Treinish7d6e48c2017-03-03 12:44:50 -0500207 if returncode > 0:
208 sys.exit(returncode)
209
210 if parsed_args.combine:
211 return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
212 temp_stream, sys.stderr)
213 if return_code > 0:
214 sys.exit(return_code)
215 returncode = run_argv(['tempest', 'load', temp_stream.name],
216 sys.stdin, sys.stdout, sys.stderr)
Matthew Treinisha051c222016-05-23 15:48:22 -0400217 sys.exit(returncode)
218
219 def get_description(self):
220 return 'Run tempest'
221
Prateek Aroraa028de12017-03-14 09:01:03 -0400222 def _init_state(self):
223 print("Initializing saved state.")
224 data = {}
225 self.global_services = cleanup_service.get_global_cleanup_services()
ghanshyam009a1f62017-08-08 10:22:57 +0300226 self.admin_mgr = clients.Manager(
227 credentials.get_configured_admin_credentials())
Prateek Aroraa028de12017-03-14 09:01:03 -0400228 admin_mgr = self.admin_mgr
229 kwargs = {'data': data,
230 'is_dry_run': False,
231 'saved_state_json': data,
232 'is_preserve': False,
233 'is_save_state': True}
234 for service in self.global_services:
235 svc = service(admin_mgr, **kwargs)
236 svc.run()
237
238 with open(SAVED_STATE_JSON, 'w+') as f:
239 f.write(json.dumps(data,
240 sort_keys=True, indent=2, separators=(',', ': ')))
241
Matthew Treinisha051c222016-05-23 15:48:22 -0400242 def get_parser(self, prog_name):
243 parser = super(TempestRun, self).get_parser(prog_name)
244 parser = self._add_args(parser)
245 return parser
246
247 def _add_args(self, parser):
Matthew Treinishc89a9512016-06-09 17:43:35 -0400248 # workspace args
249 parser.add_argument('--workspace', default=None,
250 help='Name of tempest workspace to use for running'
251 ' tests. You can see a list of workspaces '
252 'with tempest workspace list')
253 parser.add_argument('--workspace-path', default=None,
254 dest='workspace_path',
255 help="The path to the workspace file, the default "
256 "is ~/.tempest/workspace.yaml")
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400257 # Configuration flags
258 parser.add_argument('--config-file', default=None, dest='config_file',
259 help='Configuration file to run tempest with')
Matthew Treinisha051c222016-05-23 15:48:22 -0400260 # test selection args
261 regex = parser.add_mutually_exclusive_group()
Nicolas Bockff27d3b2017-01-11 13:30:32 -0700262 regex.add_argument('--smoke', '-s', action='store_true',
Matthew Treinisha051c222016-05-23 15:48:22 -0400263 help="Run the smoke tests only")
264 regex.add_argument('--regex', '-r', default='',
265 help='A normal testr selection regex used to '
266 'specify a subset of tests to run')
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400267 list_selector = parser.add_mutually_exclusive_group()
Masayuki Igawa0dcc6062016-08-24 17:06:11 +0900268 list_selector.add_argument('--whitelist-file', '--whitelist_file',
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400269 help="Path to a whitelist file, this file "
zhangyanxian68d31b82016-07-13 01:48:33 +0000270 "contains a separate regex on each "
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400271 "newline.")
Masayuki Igawa0dcc6062016-08-24 17:06:11 +0900272 list_selector.add_argument('--blacklist-file', '--blacklist_file',
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400273 help='Path to a blacklist file, this file '
274 'contains a separate regex exclude on '
275 'each newline')
ubuntu0dba54c2017-07-25 15:25:22 -0500276 list_selector.add_argument('--load-list', '--load_list',
277 help='Path to a non-regex whitelist file, '
278 'this file contains a seperate test '
279 'on each newline. This command'
280 'supports files created by the tempest'
281 'run ``--list-tests`` command')
Matthew Treinisha051c222016-05-23 15:48:22 -0400282 # list only args
283 parser.add_argument('--list-tests', '-l', action='store_true',
284 help='List tests',
285 default=False)
Puneet Arora9ed41042016-07-05 19:46:06 +0000286 # execution args
Matthew Treinisha051c222016-05-23 15:48:22 -0400287 parser.add_argument('--concurrency', '-w',
288 help="The number of workers to use, defaults to "
289 "the number of cpus")
290 parallel = parser.add_mutually_exclusive_group()
291 parallel.add_argument('--parallel', dest='parallel',
292 action='store_true',
293 help='Run tests in parallel (this is the'
294 ' default)')
Nicolas Bockff27d3b2017-01-11 13:30:32 -0700295 parallel.add_argument('--serial', '-t', dest='parallel',
Matthew Treinisha051c222016-05-23 15:48:22 -0400296 action='store_false',
297 help='Run tests serially')
Prateek Aroraa028de12017-03-14 09:01:03 -0400298 parser.add_argument('--save-state', dest='state',
299 action='store_true',
300 help="To save the state of the cloud before "
301 "running tempest.")
Matthew Treinisha051c222016-05-23 15:48:22 -0400302 # output args
303 parser.add_argument("--subunit", action='store_true',
304 help='Enable subunit v2 output')
Matthew Treinish7d6e48c2017-03-03 12:44:50 -0500305 parser.add_argument("--combine", action='store_true',
306 help='Combine the output of this run with the '
307 "previous run's as a combined stream in the "
308 "testr repository after it finish")
Matthew Treinisha051c222016-05-23 15:48:22 -0400309
310 parser.set_defaults(parallel=True)
311 return parser
312
313 def _build_regex(self, parsed_args):
314 regex = ''
315 if parsed_args.smoke:
316 regex = 'smoke'
317 elif parsed_args.regex:
318 regex = parsed_args.regex
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400319 if parsed_args.whitelist_file or parsed_args.blacklist_file:
320 regex = regex_builder.construct_regex(parsed_args.blacklist_file,
321 parsed_args.whitelist_file,
322 regex, False)
Matthew Treinisha051c222016-05-23 15:48:22 -0400323 return regex
324
325 def _build_options(self, parsed_args):
326 options = []
327 if parsed_args.subunit:
328 options.append("--subunit")
329 if parsed_args.parallel:
330 options.append("--parallel")
331 if parsed_args.concurrency:
332 options.append("--concurrency=%s" % parsed_args.concurrency)
ubuntu0dba54c2017-07-25 15:25:22 -0500333 if parsed_args.load_list:
334 options.append("--load-list=%s" % parsed_args.load_list)
Matthew Treinisha051c222016-05-23 15:48:22 -0400335 return options
336
337 def _run(self, regex, options):
338 returncode = 0
339 argv = ['tempest', 'run', regex] + options
340 if '--subunit' in options:
341 returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
342 else:
343 argv.append('--subunit')
344 stdin = io.StringIO()
345 stdout_r, stdout_w = os.pipe()
346 subunit_w = os.fdopen(stdout_w, 'wt')
347 subunit_r = os.fdopen(stdout_r)
348 returncodes = {}
349
350 def run_argv_thread():
351 returncodes['testr'] = run_argv(argv, stdin, subunit_w,
352 sys.stderr)
353 subunit_w.close()
354
355 run_thread = threading.Thread(target=run_argv_thread)
356 run_thread.start()
Matthew Treinish18d2d672016-09-20 08:30:34 -0400357 returncodes['subunit-trace'] = subunit_trace.trace(
358 subunit_r, sys.stdout, post_fails=True, print_failures=True)
Matthew Treinisha051c222016-05-23 15:48:22 -0400359 run_thread.join()
360 subunit_r.close()
361 # python version of pipefail
362 if returncodes['testr']:
363 returncode = returncodes['testr']
364 elif returncodes['subunit-trace']:
365 returncode = returncodes['subunit-trace']
366 return returncode