blob: 54b844ad7860bc1848c3503f2941be216a2aaafe [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
81"""
82
83import io
84import os
85import sys
86import threading
87
88from cliff import command
Matthew Treinisha6b4da92016-05-23 17:24:12 -040089from os_testr import regex_builder
Matthew Treinisha051c222016-05-23 15:48:22 -040090from os_testr import subunit_trace
Davanum Srinivas00e3f452017-01-05 12:40:45 -050091import six
Matthew Treinisha051c222016-05-23 15:48:22 -040092from testrepository.commands import run_argv
93
Matthew Treinish30c9ee52016-06-09 17:58:47 -040094from tempest.cmd import init
Matthew Treinishc89a9512016-06-09 17:43:35 -040095from tempest.cmd import workspace
Matthew Treinisha051c222016-05-23 15:48:22 -040096from tempest import config
97
98
Matthew Treinisha051c222016-05-23 15:48:22 -040099CONF = config.CONF
100
101
102class TempestRun(command.Command):
103
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400104 def _set_env(self, config_file=None):
105 if config_file:
106 CONF.set_config_path(os.path.abspath(config_file))
Matthew Treinisha051c222016-05-23 15:48:22 -0400107 # NOTE(mtreinish): This is needed so that testr doesn't gobble up any
108 # stacktraces on failure.
109 if 'TESTR_PDB' in os.environ:
110 return
111 else:
112 os.environ["TESTR_PDB"] = ""
Davanum Srinivas00e3f452017-01-05 12:40:45 -0500113 # NOTE(dims): most of our .testr.conf try to test for PYTHON
114 # environment variable and fall back to "python", under python3
115 # if it does not exist. we should set it to the python3 executable
116 # to deal with this situation better for now.
117 if six.PY3 and 'PYTHON' not in os.environ:
118 os.environ['PYTHON'] = sys.executable
Matthew Treinisha051c222016-05-23 15:48:22 -0400119
Matthew Treinishc89a9512016-06-09 17:43:35 -0400120 def _create_testrepository(self):
121 if not os.path.isdir('.testrepository'):
122 returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout,
123 sys.stderr)
124 if returncode:
125 sys.exit(returncode)
126
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400127 def _create_testr_conf(self):
128 top_level_path = os.path.dirname(os.path.dirname(__file__))
129 discover_path = os.path.join(top_level_path, 'test_discover')
130 file_contents = init.TESTR_CONF % (top_level_path, discover_path)
131 with open('.testr.conf', 'w+') as testr_conf_file:
132 testr_conf_file.write(file_contents)
133
Matthew Treinisha051c222016-05-23 15:48:22 -0400134 def take_action(self, parsed_args):
Masayuki Igawafe2fa002016-06-22 12:58:34 +0900135 returncode = 0
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400136 if parsed_args.config_file:
137 self._set_env(parsed_args.config_file)
138 else:
139 self._set_env()
Matthew Treinishc89a9512016-06-09 17:43:35 -0400140 # Workspace execution mode
141 if parsed_args.workspace:
142 workspace_mgr = workspace.WorkspaceManager(
143 parsed_args.workspace_path)
144 path = workspace_mgr.get_workspace(parsed_args.workspace)
Brant Knudson6a090f42016-10-13 12:51:49 -0500145 if not path:
146 sys.exit(
147 "The %r workspace isn't registered in "
148 "%r. Use 'tempest init' to "
149 "register the workspace." %
150 (parsed_args.workspace, workspace_mgr.path))
Matthew Treinishc89a9512016-06-09 17:43:35 -0400151 os.chdir(path)
152 # NOTE(mtreinish): tempest init should create a .testrepository dir
153 # but since workspaces can be imported let's sanity check and
154 # ensure that one is created
155 self._create_testrepository()
zhuflbedb2ad2016-06-20 11:39:01 +0800156 # Local execution mode
Matthew Treinishc89a9512016-06-09 17:43:35 -0400157 elif os.path.isfile('.testr.conf'):
Matthew Treinisha051c222016-05-23 15:48:22 -0400158 # If you're running in local execution mode and there is not a
159 # testrepository dir create one
Matthew Treinishc89a9512016-06-09 17:43:35 -0400160 self._create_testrepository()
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400161 # local execution with config file mode
162 elif parsed_args.config_file:
163 self._create_testr_conf()
164 self._create_testrepository()
Matthew Treinisha051c222016-05-23 15:48:22 -0400165 else:
zhuflbedb2ad2016-06-20 11:39:01 +0800166 print("No .testr.conf file was found for local execution")
Matthew Treinisha051c222016-05-23 15:48:22 -0400167 sys.exit(2)
168
169 regex = self._build_regex(parsed_args)
170 if parsed_args.list_tests:
171 argv = ['tempest', 'list-tests', regex]
172 returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
173 else:
174 options = self._build_options(parsed_args)
175 returncode = self._run(regex, options)
176 sys.exit(returncode)
177
178 def get_description(self):
179 return 'Run tempest'
180
181 def get_parser(self, prog_name):
182 parser = super(TempestRun, self).get_parser(prog_name)
183 parser = self._add_args(parser)
184 return parser
185
186 def _add_args(self, parser):
Matthew Treinishc89a9512016-06-09 17:43:35 -0400187 # workspace args
188 parser.add_argument('--workspace', default=None,
189 help='Name of tempest workspace to use for running'
190 ' tests. You can see a list of workspaces '
191 'with tempest workspace list')
192 parser.add_argument('--workspace-path', default=None,
193 dest='workspace_path',
194 help="The path to the workspace file, the default "
195 "is ~/.tempest/workspace.yaml")
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400196 # Configuration flags
197 parser.add_argument('--config-file', default=None, dest='config_file',
198 help='Configuration file to run tempest with')
Matthew Treinisha051c222016-05-23 15:48:22 -0400199 # test selection args
200 regex = parser.add_mutually_exclusive_group()
Nicolas Bockff27d3b2017-01-11 13:30:32 -0700201 regex.add_argument('--smoke', '-s', action='store_true',
Matthew Treinisha051c222016-05-23 15:48:22 -0400202 help="Run the smoke tests only")
203 regex.add_argument('--regex', '-r', default='',
204 help='A normal testr selection regex used to '
205 'specify a subset of tests to run')
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400206 list_selector = parser.add_mutually_exclusive_group()
Masayuki Igawa0dcc6062016-08-24 17:06:11 +0900207 list_selector.add_argument('--whitelist-file', '--whitelist_file',
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400208 help="Path to a whitelist file, this file "
zhangyanxian68d31b82016-07-13 01:48:33 +0000209 "contains a separate regex on each "
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400210 "newline.")
Masayuki Igawa0dcc6062016-08-24 17:06:11 +0900211 list_selector.add_argument('--blacklist-file', '--blacklist_file',
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400212 help='Path to a blacklist file, this file '
213 'contains a separate regex exclude on '
214 'each newline')
Matthew Treinisha051c222016-05-23 15:48:22 -0400215 # list only args
216 parser.add_argument('--list-tests', '-l', action='store_true',
217 help='List tests',
218 default=False)
Puneet Arora9ed41042016-07-05 19:46:06 +0000219 # execution args
Matthew Treinisha051c222016-05-23 15:48:22 -0400220 parser.add_argument('--concurrency', '-w',
221 help="The number of workers to use, defaults to "
222 "the number of cpus")
223 parallel = parser.add_mutually_exclusive_group()
224 parallel.add_argument('--parallel', dest='parallel',
225 action='store_true',
226 help='Run tests in parallel (this is the'
227 ' default)')
Nicolas Bockff27d3b2017-01-11 13:30:32 -0700228 parallel.add_argument('--serial', '-t', dest='parallel',
Matthew Treinisha051c222016-05-23 15:48:22 -0400229 action='store_false',
230 help='Run tests serially')
231 # output args
232 parser.add_argument("--subunit", action='store_true',
233 help='Enable subunit v2 output')
234
235 parser.set_defaults(parallel=True)
236 return parser
237
238 def _build_regex(self, parsed_args):
239 regex = ''
240 if parsed_args.smoke:
241 regex = 'smoke'
242 elif parsed_args.regex:
243 regex = parsed_args.regex
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400244 if parsed_args.whitelist_file or parsed_args.blacklist_file:
245 regex = regex_builder.construct_regex(parsed_args.blacklist_file,
246 parsed_args.whitelist_file,
247 regex, False)
Matthew Treinisha051c222016-05-23 15:48:22 -0400248 return regex
249
250 def _build_options(self, parsed_args):
251 options = []
252 if parsed_args.subunit:
253 options.append("--subunit")
254 if parsed_args.parallel:
255 options.append("--parallel")
256 if parsed_args.concurrency:
257 options.append("--concurrency=%s" % parsed_args.concurrency)
258 return options
259
260 def _run(self, regex, options):
261 returncode = 0
262 argv = ['tempest', 'run', regex] + options
263 if '--subunit' in options:
264 returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
265 else:
266 argv.append('--subunit')
267 stdin = io.StringIO()
268 stdout_r, stdout_w = os.pipe()
269 subunit_w = os.fdopen(stdout_w, 'wt')
270 subunit_r = os.fdopen(stdout_r)
271 returncodes = {}
272
273 def run_argv_thread():
274 returncodes['testr'] = run_argv(argv, stdin, subunit_w,
275 sys.stderr)
276 subunit_w.close()
277
278 run_thread = threading.Thread(target=run_argv_thread)
279 run_thread.start()
Matthew Treinish18d2d672016-09-20 08:30:34 -0400280 returncodes['subunit-trace'] = subunit_trace.trace(
281 subunit_r, sys.stdout, post_fails=True, print_failures=True)
Matthew Treinisha051c222016-05-23 15:48:22 -0400282 run_thread.join()
283 subunit_r.close()
284 # python version of pipefail
285 if returncodes['testr']:
286 returncode = returncodes['testr']
287 elif returncodes['subunit-trace']:
288 returncode = returncodes['subunit-trace']
289 return returncode