blob: b36bf5c8435bf50468c085e9c424d6f43aa998f8 [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
Davanum Srinivas00e3f452017-01-05 12:40:45 -0500100import six
Matthew Treinisha051c222016-05-23 15:48:22 -0400101from testrepository.commands import run_argv
102
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400103from tempest.cmd import init
Matthew Treinishc89a9512016-06-09 17:43:35 -0400104from tempest.cmd import workspace
Matthew Treinisha051c222016-05-23 15:48:22 -0400105from tempest import config
106
107
Matthew Treinisha051c222016-05-23 15:48:22 -0400108CONF = config.CONF
109
110
111class TempestRun(command.Command):
112
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400113 def _set_env(self, config_file=None):
114 if config_file:
115 CONF.set_config_path(os.path.abspath(config_file))
Matthew Treinisha051c222016-05-23 15:48:22 -0400116 # NOTE(mtreinish): This is needed so that testr doesn't gobble up any
117 # stacktraces on failure.
118 if 'TESTR_PDB' in os.environ:
119 return
120 else:
121 os.environ["TESTR_PDB"] = ""
Davanum Srinivas00e3f452017-01-05 12:40:45 -0500122 # NOTE(dims): most of our .testr.conf try to test for PYTHON
123 # environment variable and fall back to "python", under python3
124 # if it does not exist. we should set it to the python3 executable
125 # to deal with this situation better for now.
126 if six.PY3 and 'PYTHON' not in os.environ:
127 os.environ['PYTHON'] = sys.executable
Matthew Treinisha051c222016-05-23 15:48:22 -0400128
Matthew Treinishc89a9512016-06-09 17:43:35 -0400129 def _create_testrepository(self):
130 if not os.path.isdir('.testrepository'):
131 returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout,
132 sys.stderr)
133 if returncode:
134 sys.exit(returncode)
135
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400136 def _create_testr_conf(self):
137 top_level_path = os.path.dirname(os.path.dirname(__file__))
138 discover_path = os.path.join(top_level_path, 'test_discover')
139 file_contents = init.TESTR_CONF % (top_level_path, discover_path)
140 with open('.testr.conf', 'w+') as testr_conf_file:
141 testr_conf_file.write(file_contents)
142
Matthew Treinisha051c222016-05-23 15:48:22 -0400143 def take_action(self, parsed_args):
Masayuki Igawafe2fa002016-06-22 12:58:34 +0900144 returncode = 0
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400145 if parsed_args.config_file:
146 self._set_env(parsed_args.config_file)
147 else:
148 self._set_env()
Matthew Treinishc89a9512016-06-09 17:43:35 -0400149 # Workspace execution mode
150 if parsed_args.workspace:
151 workspace_mgr = workspace.WorkspaceManager(
152 parsed_args.workspace_path)
153 path = workspace_mgr.get_workspace(parsed_args.workspace)
Brant Knudson6a090f42016-10-13 12:51:49 -0500154 if not path:
155 sys.exit(
156 "The %r workspace isn't registered in "
157 "%r. Use 'tempest init' to "
158 "register the workspace." %
159 (parsed_args.workspace, workspace_mgr.path))
Matthew Treinishc89a9512016-06-09 17:43:35 -0400160 os.chdir(path)
161 # NOTE(mtreinish): tempest init should create a .testrepository dir
162 # but since workspaces can be imported let's sanity check and
163 # ensure that one is created
164 self._create_testrepository()
zhuflbedb2ad2016-06-20 11:39:01 +0800165 # Local execution mode
Matthew Treinishc89a9512016-06-09 17:43:35 -0400166 elif os.path.isfile('.testr.conf'):
Matthew Treinisha051c222016-05-23 15:48:22 -0400167 # If you're running in local execution mode and there is not a
168 # testrepository dir create one
Matthew Treinishc89a9512016-06-09 17:43:35 -0400169 self._create_testrepository()
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400170 # local execution with config file mode
171 elif parsed_args.config_file:
172 self._create_testr_conf()
173 self._create_testrepository()
Matthew Treinisha051c222016-05-23 15:48:22 -0400174 else:
zhuflbedb2ad2016-06-20 11:39:01 +0800175 print("No .testr.conf file was found for local execution")
Matthew Treinisha051c222016-05-23 15:48:22 -0400176 sys.exit(2)
Matthew Treinish7d6e48c2017-03-03 12:44:50 -0500177 if parsed_args.combine:
178 temp_stream = tempfile.NamedTemporaryFile()
179 return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
180 temp_stream, sys.stderr)
181 if return_code > 0:
182 sys.exit(return_code)
Matthew Treinisha051c222016-05-23 15:48:22 -0400183
184 regex = self._build_regex(parsed_args)
185 if parsed_args.list_tests:
186 argv = ['tempest', 'list-tests', regex]
187 returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
188 else:
189 options = self._build_options(parsed_args)
190 returncode = self._run(regex, options)
Matthew Treinish7d6e48c2017-03-03 12:44:50 -0500191 if returncode > 0:
192 sys.exit(returncode)
193
194 if parsed_args.combine:
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)
199 returncode = run_argv(['tempest', 'load', temp_stream.name],
200 sys.stdin, sys.stdout, sys.stderr)
Matthew Treinisha051c222016-05-23 15:48:22 -0400201 sys.exit(returncode)
202
203 def get_description(self):
204 return 'Run tempest'
205
206 def get_parser(self, prog_name):
207 parser = super(TempestRun, self).get_parser(prog_name)
208 parser = self._add_args(parser)
209 return parser
210
211 def _add_args(self, parser):
Matthew Treinishc89a9512016-06-09 17:43:35 -0400212 # workspace args
213 parser.add_argument('--workspace', default=None,
214 help='Name of tempest workspace to use for running'
215 ' tests. You can see a list of workspaces '
216 'with tempest workspace list')
217 parser.add_argument('--workspace-path', default=None,
218 dest='workspace_path',
219 help="The path to the workspace file, the default "
220 "is ~/.tempest/workspace.yaml")
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400221 # Configuration flags
222 parser.add_argument('--config-file', default=None, dest='config_file',
223 help='Configuration file to run tempest with')
Matthew Treinisha051c222016-05-23 15:48:22 -0400224 # test selection args
225 regex = parser.add_mutually_exclusive_group()
Nicolas Bockff27d3b2017-01-11 13:30:32 -0700226 regex.add_argument('--smoke', '-s', action='store_true',
Matthew Treinisha051c222016-05-23 15:48:22 -0400227 help="Run the smoke tests only")
228 regex.add_argument('--regex', '-r', default='',
229 help='A normal testr selection regex used to '
230 'specify a subset of tests to run')
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400231 list_selector = parser.add_mutually_exclusive_group()
Masayuki Igawa0dcc6062016-08-24 17:06:11 +0900232 list_selector.add_argument('--whitelist-file', '--whitelist_file',
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400233 help="Path to a whitelist file, this file "
zhangyanxian68d31b82016-07-13 01:48:33 +0000234 "contains a separate regex on each "
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400235 "newline.")
Masayuki Igawa0dcc6062016-08-24 17:06:11 +0900236 list_selector.add_argument('--blacklist-file', '--blacklist_file',
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400237 help='Path to a blacklist file, this file '
238 'contains a separate regex exclude on '
239 'each newline')
Matthew Treinisha051c222016-05-23 15:48:22 -0400240 # list only args
241 parser.add_argument('--list-tests', '-l', action='store_true',
242 help='List tests',
243 default=False)
Puneet Arora9ed41042016-07-05 19:46:06 +0000244 # execution args
Matthew Treinisha051c222016-05-23 15:48:22 -0400245 parser.add_argument('--concurrency', '-w',
246 help="The number of workers to use, defaults to "
247 "the number of cpus")
248 parallel = parser.add_mutually_exclusive_group()
249 parallel.add_argument('--parallel', dest='parallel',
250 action='store_true',
251 help='Run tests in parallel (this is the'
252 ' default)')
Nicolas Bockff27d3b2017-01-11 13:30:32 -0700253 parallel.add_argument('--serial', '-t', dest='parallel',
Matthew Treinisha051c222016-05-23 15:48:22 -0400254 action='store_false',
255 help='Run tests serially')
256 # output args
257 parser.add_argument("--subunit", action='store_true',
258 help='Enable subunit v2 output')
Matthew Treinish7d6e48c2017-03-03 12:44:50 -0500259 parser.add_argument("--combine", action='store_true',
260 help='Combine the output of this run with the '
261 "previous run's as a combined stream in the "
262 "testr repository after it finish")
Matthew Treinisha051c222016-05-23 15:48:22 -0400263
264 parser.set_defaults(parallel=True)
265 return parser
266
267 def _build_regex(self, parsed_args):
268 regex = ''
269 if parsed_args.smoke:
270 regex = 'smoke'
271 elif parsed_args.regex:
272 regex = parsed_args.regex
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400273 if parsed_args.whitelist_file or parsed_args.blacklist_file:
274 regex = regex_builder.construct_regex(parsed_args.blacklist_file,
275 parsed_args.whitelist_file,
276 regex, False)
Matthew Treinisha051c222016-05-23 15:48:22 -0400277 return regex
278
279 def _build_options(self, parsed_args):
280 options = []
281 if parsed_args.subunit:
282 options.append("--subunit")
283 if parsed_args.parallel:
284 options.append("--parallel")
285 if parsed_args.concurrency:
286 options.append("--concurrency=%s" % parsed_args.concurrency)
287 return options
288
289 def _run(self, regex, options):
290 returncode = 0
291 argv = ['tempest', 'run', regex] + options
292 if '--subunit' in options:
293 returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
294 else:
295 argv.append('--subunit')
296 stdin = io.StringIO()
297 stdout_r, stdout_w = os.pipe()
298 subunit_w = os.fdopen(stdout_w, 'wt')
299 subunit_r = os.fdopen(stdout_r)
300 returncodes = {}
301
302 def run_argv_thread():
303 returncodes['testr'] = run_argv(argv, stdin, subunit_w,
304 sys.stderr)
305 subunit_w.close()
306
307 run_thread = threading.Thread(target=run_argv_thread)
308 run_thread.start()
Matthew Treinish18d2d672016-09-20 08:30:34 -0400309 returncodes['subunit-trace'] = subunit_trace.trace(
310 subunit_r, sys.stdout, post_fails=True, print_failures=True)
Matthew Treinisha051c222016-05-23 15:48:22 -0400311 run_thread.join()
312 subunit_r.close()
313 # python version of pipefail
314 if returncodes['testr']:
315 returncode = returncodes['testr']
316 elif returncodes['subunit-trace']:
317 returncode = returncodes['subunit-trace']
318 return returncode