blob: b6b70d770d06f69ea1b95130c33c2ac3614a20e6 [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
24 * **--smoke**: Run all the tests tagged as smoke
25
Matthew Treinisha6b4da92016-05-23 17:24:12 -040026There are also the **--blacklist_file** and **--whitelist_file** options that
27let you pass a filepath to tempest run with the file format being a line
28seperated regex, with '#' used to signify the start of a comment on a line.
29For 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
55and if you want to run tests serially use **--serial**
56
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
Matthew Treinisha051c222016-05-23 15:48:22 -040091from testrepository.commands import run_argv
92
Matthew Treinish30c9ee52016-06-09 17:58:47 -040093from tempest.cmd import init
Matthew Treinishc89a9512016-06-09 17:43:35 -040094from tempest.cmd import workspace
Matthew Treinisha051c222016-05-23 15:48:22 -040095from tempest import config
96
97
Matthew Treinisha051c222016-05-23 15:48:22 -040098CONF = config.CONF
99
100
101class TempestRun(command.Command):
102
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400103 def _set_env(self, config_file=None):
104 if config_file:
105 CONF.set_config_path(os.path.abspath(config_file))
Matthew Treinisha051c222016-05-23 15:48:22 -0400106 # NOTE(mtreinish): This is needed so that testr doesn't gobble up any
107 # stacktraces on failure.
108 if 'TESTR_PDB' in os.environ:
109 return
110 else:
111 os.environ["TESTR_PDB"] = ""
112
Matthew Treinishc89a9512016-06-09 17:43:35 -0400113 def _create_testrepository(self):
114 if not os.path.isdir('.testrepository'):
115 returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout,
116 sys.stderr)
117 if returncode:
118 sys.exit(returncode)
119
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400120 def _create_testr_conf(self):
121 top_level_path = os.path.dirname(os.path.dirname(__file__))
122 discover_path = os.path.join(top_level_path, 'test_discover')
123 file_contents = init.TESTR_CONF % (top_level_path, discover_path)
124 with open('.testr.conf', 'w+') as testr_conf_file:
125 testr_conf_file.write(file_contents)
126
Matthew Treinisha051c222016-05-23 15:48:22 -0400127 def take_action(self, parsed_args):
Masayuki Igawafe2fa002016-06-22 12:58:34 +0900128 returncode = 0
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400129 if parsed_args.config_file:
130 self._set_env(parsed_args.config_file)
131 else:
132 self._set_env()
Matthew Treinishc89a9512016-06-09 17:43:35 -0400133 # Workspace execution mode
134 if parsed_args.workspace:
135 workspace_mgr = workspace.WorkspaceManager(
136 parsed_args.workspace_path)
137 path = workspace_mgr.get_workspace(parsed_args.workspace)
138 os.chdir(path)
139 # NOTE(mtreinish): tempest init should create a .testrepository dir
140 # but since workspaces can be imported let's sanity check and
141 # ensure that one is created
142 self._create_testrepository()
zhuflbedb2ad2016-06-20 11:39:01 +0800143 # Local execution mode
Matthew Treinishc89a9512016-06-09 17:43:35 -0400144 elif os.path.isfile('.testr.conf'):
Matthew Treinisha051c222016-05-23 15:48:22 -0400145 # If you're running in local execution mode and there is not a
146 # testrepository dir create one
Matthew Treinishc89a9512016-06-09 17:43:35 -0400147 self._create_testrepository()
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400148 # local execution with config file mode
149 elif parsed_args.config_file:
150 self._create_testr_conf()
151 self._create_testrepository()
Matthew Treinisha051c222016-05-23 15:48:22 -0400152 else:
zhuflbedb2ad2016-06-20 11:39:01 +0800153 print("No .testr.conf file was found for local execution")
Matthew Treinisha051c222016-05-23 15:48:22 -0400154 sys.exit(2)
155
156 regex = self._build_regex(parsed_args)
157 if parsed_args.list_tests:
158 argv = ['tempest', 'list-tests', regex]
159 returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
160 else:
161 options = self._build_options(parsed_args)
162 returncode = self._run(regex, options)
163 sys.exit(returncode)
164
165 def get_description(self):
166 return 'Run tempest'
167
168 def get_parser(self, prog_name):
169 parser = super(TempestRun, self).get_parser(prog_name)
170 parser = self._add_args(parser)
171 return parser
172
173 def _add_args(self, parser):
Matthew Treinishc89a9512016-06-09 17:43:35 -0400174 # workspace args
175 parser.add_argument('--workspace', default=None,
176 help='Name of tempest workspace to use for running'
177 ' tests. You can see a list of workspaces '
178 'with tempest workspace list')
179 parser.add_argument('--workspace-path', default=None,
180 dest='workspace_path',
181 help="The path to the workspace file, the default "
182 "is ~/.tempest/workspace.yaml")
Matthew Treinish30c9ee52016-06-09 17:58:47 -0400183 # Configuration flags
184 parser.add_argument('--config-file', default=None, dest='config_file',
185 help='Configuration file to run tempest with')
Matthew Treinisha051c222016-05-23 15:48:22 -0400186 # test selection args
187 regex = parser.add_mutually_exclusive_group()
188 regex.add_argument('--smoke', action='store_true',
189 help="Run the smoke tests only")
190 regex.add_argument('--regex', '-r', default='',
191 help='A normal testr selection regex used to '
192 'specify a subset of tests to run')
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400193 list_selector = parser.add_mutually_exclusive_group()
194 list_selector.add_argument('--whitelist_file',
195 help="Path to a whitelist file, this file "
196 "contains a seperate regex on each "
197 "newline.")
198 list_selector.add_argument('--blacklist_file',
199 help='Path to a blacklist file, this file '
200 'contains a separate regex exclude on '
201 'each newline')
Matthew Treinisha051c222016-05-23 15:48:22 -0400202 # list only args
203 parser.add_argument('--list-tests', '-l', action='store_true',
204 help='List tests',
205 default=False)
Puneet Arora9ed41042016-07-05 19:46:06 +0000206 # execution args
Matthew Treinisha051c222016-05-23 15:48:22 -0400207 parser.add_argument('--concurrency', '-w',
208 help="The number of workers to use, defaults to "
209 "the number of cpus")
210 parallel = parser.add_mutually_exclusive_group()
211 parallel.add_argument('--parallel', dest='parallel',
212 action='store_true',
213 help='Run tests in parallel (this is the'
214 ' default)')
215 parallel.add_argument('--serial', dest='parallel',
216 action='store_false',
217 help='Run tests serially')
218 # output args
219 parser.add_argument("--subunit", action='store_true',
220 help='Enable subunit v2 output')
221
222 parser.set_defaults(parallel=True)
223 return parser
224
225 def _build_regex(self, parsed_args):
226 regex = ''
227 if parsed_args.smoke:
228 regex = 'smoke'
229 elif parsed_args.regex:
230 regex = parsed_args.regex
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400231 if parsed_args.whitelist_file or parsed_args.blacklist_file:
232 regex = regex_builder.construct_regex(parsed_args.blacklist_file,
233 parsed_args.whitelist_file,
234 regex, False)
Matthew Treinisha051c222016-05-23 15:48:22 -0400235 return regex
236
237 def _build_options(self, parsed_args):
238 options = []
239 if parsed_args.subunit:
240 options.append("--subunit")
241 if parsed_args.parallel:
242 options.append("--parallel")
243 if parsed_args.concurrency:
244 options.append("--concurrency=%s" % parsed_args.concurrency)
245 return options
246
247 def _run(self, regex, options):
248 returncode = 0
249 argv = ['tempest', 'run', regex] + options
250 if '--subunit' in options:
251 returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
252 else:
253 argv.append('--subunit')
254 stdin = io.StringIO()
255 stdout_r, stdout_w = os.pipe()
256 subunit_w = os.fdopen(stdout_w, 'wt')
257 subunit_r = os.fdopen(stdout_r)
258 returncodes = {}
259
260 def run_argv_thread():
261 returncodes['testr'] = run_argv(argv, stdin, subunit_w,
262 sys.stderr)
263 subunit_w.close()
264
265 run_thread = threading.Thread(target=run_argv_thread)
266 run_thread.start()
267 returncodes['subunit-trace'] = subunit_trace.trace(subunit_r,
268 sys.stdout)
269 run_thread.join()
270 subunit_r.close()
271 # python version of pipefail
272 if returncodes['testr']:
273 returncode = returncodes['testr']
274 elif returncodes['subunit-trace']:
275 returncode = returncodes['subunit-trace']
276 return returncode