# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Runs tempest tests This command is used for running the tempest tests Test Selection ============== Tempest run has several options: * **--regex/-r**: This is a selection regex like what testr uses. It will run any tests that match on re.match() with the regex * **--smoke**: Run all the tests tagged as smoke You can also use the **--list-tests** option in conjunction with selection arguments to list which tests will be run. Test Execution ============== There are several options to control how the tests are executed. By default tempest will run in parallel with a worker for each CPU present on the machine. If you want to adjust the number of workers use the **--concurrency** option and if you want to run tests serially use **--serial** Test Output =========== By default tempest run's output to STDOUT will be generated using the subunit-trace output filter. But, if you would prefer a subunit v2 stream be output to STDOUT use the **--subunit** flag """ import io import os import sys import threading from cliff import command from os_testr import subunit_trace from oslo_log import log as logging from testrepository.commands import run_argv from tempest import config LOG = logging.getLogger(__name__) CONF = config.CONF class TempestRun(command.Command): def _set_env(self): # NOTE(mtreinish): This is needed so that testr doesn't gobble up any # stacktraces on failure. if 'TESTR_PDB' in os.environ: return else: os.environ["TESTR_PDB"] = "" def take_action(self, parsed_args): self._set_env() # Local exceution mode if os.path.isfile('.testr.conf'): # If you're running in local execution mode and there is not a # testrepository dir create one if not os.path.isdir('.testrepository'): returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout, sys.stderr) if returncode: sys.exit(returncode) else: print("No .testr.conf file was found for local exceution") sys.exit(2) regex = self._build_regex(parsed_args) if parsed_args.list_tests: argv = ['tempest', 'list-tests', regex] returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr) else: options = self._build_options(parsed_args) returncode = self._run(regex, options) sys.exit(returncode) def get_description(self): return 'Run tempest' def get_parser(self, prog_name): parser = super(TempestRun, self).get_parser(prog_name) parser = self._add_args(parser) return parser def _add_args(self, parser): # test selection args regex = parser.add_mutually_exclusive_group() regex.add_argument('--smoke', action='store_true', help="Run the smoke tests only") regex.add_argument('--regex', '-r', default='', help='A normal testr selection regex used to ' 'specify a subset of tests to run') # list only args parser.add_argument('--list-tests', '-l', action='store_true', help='List tests', default=False) # exectution args parser.add_argument('--concurrency', '-w', help="The number of workers to use, defaults to " "the number of cpus") parallel = parser.add_mutually_exclusive_group() parallel.add_argument('--parallel', dest='parallel', action='store_true', help='Run tests in parallel (this is the' ' default)') parallel.add_argument('--serial', dest='parallel', action='store_false', help='Run tests serially') # output args parser.add_argument("--subunit", action='store_true', help='Enable subunit v2 output') parser.set_defaults(parallel=True) return parser def _build_regex(self, parsed_args): regex = '' if parsed_args.smoke: regex = 'smoke' elif parsed_args.regex: regex = parsed_args.regex return regex def _build_options(self, parsed_args): options = [] if parsed_args.subunit: options.append("--subunit") if parsed_args.parallel: options.append("--parallel") if parsed_args.concurrency: options.append("--concurrency=%s" % parsed_args.concurrency) return options def _run(self, regex, options): returncode = 0 argv = ['tempest', 'run', regex] + options if '--subunit' in options: returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr) else: argv.append('--subunit') stdin = io.StringIO() stdout_r, stdout_w = os.pipe() subunit_w = os.fdopen(stdout_w, 'wt') subunit_r = os.fdopen(stdout_r) returncodes = {} def run_argv_thread(): returncodes['testr'] = run_argv(argv, stdin, subunit_w, sys.stderr) subunit_w.close() run_thread = threading.Thread(target=run_argv_thread) run_thread.start() returncodes['subunit-trace'] = subunit_trace.trace(subunit_r, sys.stdout) run_thread.join() subunit_r.close() # python version of pipefail if returncodes['testr']: returncode = returncodes['testr'] elif returncodes['subunit-trace']: returncode = returncodes['subunit-trace'] return returncode