blob: 8777c0ecf18744abc57d42530d78a4dad6a63498 [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 Treinisha051c222016-05-23 15:48:22 -040067Test Output
68===========
69By default tempest run's output to STDOUT will be generated using the
70subunit-trace output filter. But, if you would prefer a subunit v2 stream be
71output to STDOUT use the **--subunit** flag
72
73"""
74
75import io
76import os
77import sys
78import threading
79
80from cliff import command
Matthew Treinisha6b4da92016-05-23 17:24:12 -040081from os_testr import regex_builder
Matthew Treinisha051c222016-05-23 15:48:22 -040082from os_testr import subunit_trace
83from oslo_log import log as logging
84from testrepository.commands import run_argv
85
Matthew Treinishc89a9512016-06-09 17:43:35 -040086from tempest.cmd import workspace
Matthew Treinisha051c222016-05-23 15:48:22 -040087from tempest import config
88
89
90LOG = logging.getLogger(__name__)
91CONF = config.CONF
92
93
94class TempestRun(command.Command):
95
96 def _set_env(self):
97 # NOTE(mtreinish): This is needed so that testr doesn't gobble up any
98 # stacktraces on failure.
99 if 'TESTR_PDB' in os.environ:
100 return
101 else:
102 os.environ["TESTR_PDB"] = ""
103
Matthew Treinishc89a9512016-06-09 17:43:35 -0400104 def _create_testrepository(self):
105 if not os.path.isdir('.testrepository'):
106 returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout,
107 sys.stderr)
108 if returncode:
109 sys.exit(returncode)
110
Matthew Treinisha051c222016-05-23 15:48:22 -0400111 def take_action(self, parsed_args):
112 self._set_env()
Masayuki Igawafe2fa002016-06-22 12:58:34 +0900113 returncode = 0
Matthew Treinishc89a9512016-06-09 17:43:35 -0400114 # Workspace execution mode
115 if parsed_args.workspace:
116 workspace_mgr = workspace.WorkspaceManager(
117 parsed_args.workspace_path)
118 path = workspace_mgr.get_workspace(parsed_args.workspace)
119 os.chdir(path)
120 # NOTE(mtreinish): tempest init should create a .testrepository dir
121 # but since workspaces can be imported let's sanity check and
122 # ensure that one is created
123 self._create_testrepository()
zhuflbedb2ad2016-06-20 11:39:01 +0800124 # Local execution mode
Matthew Treinishc89a9512016-06-09 17:43:35 -0400125 elif os.path.isfile('.testr.conf'):
Matthew Treinisha051c222016-05-23 15:48:22 -0400126 # If you're running in local execution mode and there is not a
127 # testrepository dir create one
Matthew Treinishc89a9512016-06-09 17:43:35 -0400128 self._create_testrepository()
Matthew Treinisha051c222016-05-23 15:48:22 -0400129 else:
zhuflbedb2ad2016-06-20 11:39:01 +0800130 print("No .testr.conf file was found for local execution")
Matthew Treinisha051c222016-05-23 15:48:22 -0400131 sys.exit(2)
132
133 regex = self._build_regex(parsed_args)
134 if parsed_args.list_tests:
135 argv = ['tempest', 'list-tests', regex]
136 returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
137 else:
138 options = self._build_options(parsed_args)
139 returncode = self._run(regex, options)
140 sys.exit(returncode)
141
142 def get_description(self):
143 return 'Run tempest'
144
145 def get_parser(self, prog_name):
146 parser = super(TempestRun, self).get_parser(prog_name)
147 parser = self._add_args(parser)
148 return parser
149
150 def _add_args(self, parser):
Matthew Treinishc89a9512016-06-09 17:43:35 -0400151 # workspace args
152 parser.add_argument('--workspace', default=None,
153 help='Name of tempest workspace to use for running'
154 ' tests. You can see a list of workspaces '
155 'with tempest workspace list')
156 parser.add_argument('--workspace-path', default=None,
157 dest='workspace_path',
158 help="The path to the workspace file, the default "
159 "is ~/.tempest/workspace.yaml")
Matthew Treinisha051c222016-05-23 15:48:22 -0400160 # test selection args
161 regex = parser.add_mutually_exclusive_group()
162 regex.add_argument('--smoke', action='store_true',
163 help="Run the smoke tests only")
164 regex.add_argument('--regex', '-r', default='',
165 help='A normal testr selection regex used to '
166 'specify a subset of tests to run')
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400167 list_selector = parser.add_mutually_exclusive_group()
168 list_selector.add_argument('--whitelist_file',
169 help="Path to a whitelist file, this file "
170 "contains a seperate regex on each "
171 "newline.")
172 list_selector.add_argument('--blacklist_file',
173 help='Path to a blacklist file, this file '
174 'contains a separate regex exclude on '
175 'each newline')
Matthew Treinisha051c222016-05-23 15:48:22 -0400176 # list only args
177 parser.add_argument('--list-tests', '-l', action='store_true',
178 help='List tests',
179 default=False)
180 # exectution args
181 parser.add_argument('--concurrency', '-w',
182 help="The number of workers to use, defaults to "
183 "the number of cpus")
184 parallel = parser.add_mutually_exclusive_group()
185 parallel.add_argument('--parallel', dest='parallel',
186 action='store_true',
187 help='Run tests in parallel (this is the'
188 ' default)')
189 parallel.add_argument('--serial', dest='parallel',
190 action='store_false',
191 help='Run tests serially')
192 # output args
193 parser.add_argument("--subunit", action='store_true',
194 help='Enable subunit v2 output')
195
196 parser.set_defaults(parallel=True)
197 return parser
198
199 def _build_regex(self, parsed_args):
200 regex = ''
201 if parsed_args.smoke:
202 regex = 'smoke'
203 elif parsed_args.regex:
204 regex = parsed_args.regex
Matthew Treinisha6b4da92016-05-23 17:24:12 -0400205 if parsed_args.whitelist_file or parsed_args.blacklist_file:
206 regex = regex_builder.construct_regex(parsed_args.blacklist_file,
207 parsed_args.whitelist_file,
208 regex, False)
Matthew Treinisha051c222016-05-23 15:48:22 -0400209 return regex
210
211 def _build_options(self, parsed_args):
212 options = []
213 if parsed_args.subunit:
214 options.append("--subunit")
215 if parsed_args.parallel:
216 options.append("--parallel")
217 if parsed_args.concurrency:
218 options.append("--concurrency=%s" % parsed_args.concurrency)
219 return options
220
221 def _run(self, regex, options):
222 returncode = 0
223 argv = ['tempest', 'run', regex] + options
224 if '--subunit' in options:
225 returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr)
226 else:
227 argv.append('--subunit')
228 stdin = io.StringIO()
229 stdout_r, stdout_w = os.pipe()
230 subunit_w = os.fdopen(stdout_w, 'wt')
231 subunit_r = os.fdopen(stdout_r)
232 returncodes = {}
233
234 def run_argv_thread():
235 returncodes['testr'] = run_argv(argv, stdin, subunit_w,
236 sys.stderr)
237 subunit_w.close()
238
239 run_thread = threading.Thread(target=run_argv_thread)
240 run_thread.start()
241 returncodes['subunit-trace'] = subunit_trace.trace(subunit_r,
242 sys.stdout)
243 run_thread.join()
244 subunit_r.close()
245 # python version of pipefail
246 if returncodes['testr']:
247 returncode = returncodes['testr']
248 elif returncodes['subunit-trace']:
249 returncode = returncodes['subunit-trace']
250 return returncode