Matthew Treinish | a051c22 | 2016-05-23 15:48:22 -0400 | [diff] [blame] | 1 | # 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 | """ |
| 14 | Runs tempest tests |
| 15 | |
| 16 | This command is used for running the tempest tests |
| 17 | |
| 18 | Test Selection |
| 19 | ============== |
| 20 | Tempest 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 | |
| 26 | You can also use the **--list-tests** option in conjunction with selection |
| 27 | arguments to list which tests will be run. |
| 28 | |
| 29 | Test Execution |
| 30 | ============== |
| 31 | There are several options to control how the tests are executed. By default |
| 32 | tempest will run in parallel with a worker for each CPU present on the machine. |
| 33 | If you want to adjust the number of workers use the **--concurrency** option |
| 34 | and if you want to run tests serially use **--serial** |
| 35 | |
| 36 | Test Output |
| 37 | =========== |
| 38 | By default tempest run's output to STDOUT will be generated using the |
| 39 | subunit-trace output filter. But, if you would prefer a subunit v2 stream be |
| 40 | output to STDOUT use the **--subunit** flag |
| 41 | |
| 42 | """ |
| 43 | |
| 44 | import io |
| 45 | import os |
| 46 | import sys |
| 47 | import threading |
| 48 | |
| 49 | from cliff import command |
| 50 | from os_testr import subunit_trace |
| 51 | from oslo_log import log as logging |
| 52 | from testrepository.commands import run_argv |
| 53 | |
| 54 | from tempest import config |
| 55 | |
| 56 | |
| 57 | LOG = logging.getLogger(__name__) |
| 58 | CONF = config.CONF |
| 59 | |
| 60 | |
| 61 | class TempestRun(command.Command): |
| 62 | |
| 63 | def _set_env(self): |
| 64 | # NOTE(mtreinish): This is needed so that testr doesn't gobble up any |
| 65 | # stacktraces on failure. |
| 66 | if 'TESTR_PDB' in os.environ: |
| 67 | return |
| 68 | else: |
| 69 | os.environ["TESTR_PDB"] = "" |
| 70 | |
| 71 | def take_action(self, parsed_args): |
| 72 | self._set_env() |
Masayuki Igawa | fe2fa00 | 2016-06-22 12:58:34 +0900 | [diff] [blame] | 73 | returncode = 0 |
zhufl | bedb2ad | 2016-06-20 11:39:01 +0800 | [diff] [blame] | 74 | # Local execution mode |
Matthew Treinish | a051c22 | 2016-05-23 15:48:22 -0400 | [diff] [blame] | 75 | if os.path.isfile('.testr.conf'): |
| 76 | # If you're running in local execution mode and there is not a |
| 77 | # testrepository dir create one |
| 78 | if not os.path.isdir('.testrepository'): |
| 79 | returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout, |
| 80 | sys.stderr) |
Masayuki Igawa | fe2fa00 | 2016-06-22 12:58:34 +0900 | [diff] [blame] | 81 | if returncode: |
| 82 | sys.exit(returncode) |
Matthew Treinish | a051c22 | 2016-05-23 15:48:22 -0400 | [diff] [blame] | 83 | else: |
zhufl | bedb2ad | 2016-06-20 11:39:01 +0800 | [diff] [blame] | 84 | print("No .testr.conf file was found for local execution") |
Matthew Treinish | a051c22 | 2016-05-23 15:48:22 -0400 | [diff] [blame] | 85 | sys.exit(2) |
| 86 | |
| 87 | regex = self._build_regex(parsed_args) |
| 88 | if parsed_args.list_tests: |
| 89 | argv = ['tempest', 'list-tests', regex] |
| 90 | returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr) |
| 91 | else: |
| 92 | options = self._build_options(parsed_args) |
| 93 | returncode = self._run(regex, options) |
| 94 | sys.exit(returncode) |
| 95 | |
| 96 | def get_description(self): |
| 97 | return 'Run tempest' |
| 98 | |
| 99 | def get_parser(self, prog_name): |
| 100 | parser = super(TempestRun, self).get_parser(prog_name) |
| 101 | parser = self._add_args(parser) |
| 102 | return parser |
| 103 | |
| 104 | def _add_args(self, parser): |
| 105 | # test selection args |
| 106 | regex = parser.add_mutually_exclusive_group() |
| 107 | regex.add_argument('--smoke', action='store_true', |
| 108 | help="Run the smoke tests only") |
| 109 | regex.add_argument('--regex', '-r', default='', |
| 110 | help='A normal testr selection regex used to ' |
| 111 | 'specify a subset of tests to run') |
| 112 | # list only args |
| 113 | parser.add_argument('--list-tests', '-l', action='store_true', |
| 114 | help='List tests', |
| 115 | default=False) |
| 116 | # exectution args |
| 117 | parser.add_argument('--concurrency', '-w', |
| 118 | help="The number of workers to use, defaults to " |
| 119 | "the number of cpus") |
| 120 | parallel = parser.add_mutually_exclusive_group() |
| 121 | parallel.add_argument('--parallel', dest='parallel', |
| 122 | action='store_true', |
| 123 | help='Run tests in parallel (this is the' |
| 124 | ' default)') |
| 125 | parallel.add_argument('--serial', dest='parallel', |
| 126 | action='store_false', |
| 127 | help='Run tests serially') |
| 128 | # output args |
| 129 | parser.add_argument("--subunit", action='store_true', |
| 130 | help='Enable subunit v2 output') |
| 131 | |
| 132 | parser.set_defaults(parallel=True) |
| 133 | return parser |
| 134 | |
| 135 | def _build_regex(self, parsed_args): |
| 136 | regex = '' |
| 137 | if parsed_args.smoke: |
| 138 | regex = 'smoke' |
| 139 | elif parsed_args.regex: |
| 140 | regex = parsed_args.regex |
| 141 | return regex |
| 142 | |
| 143 | def _build_options(self, parsed_args): |
| 144 | options = [] |
| 145 | if parsed_args.subunit: |
| 146 | options.append("--subunit") |
| 147 | if parsed_args.parallel: |
| 148 | options.append("--parallel") |
| 149 | if parsed_args.concurrency: |
| 150 | options.append("--concurrency=%s" % parsed_args.concurrency) |
| 151 | return options |
| 152 | |
| 153 | def _run(self, regex, options): |
| 154 | returncode = 0 |
| 155 | argv = ['tempest', 'run', regex] + options |
| 156 | if '--subunit' in options: |
| 157 | returncode = run_argv(argv, sys.stdin, sys.stdout, sys.stderr) |
| 158 | else: |
| 159 | argv.append('--subunit') |
| 160 | stdin = io.StringIO() |
| 161 | stdout_r, stdout_w = os.pipe() |
| 162 | subunit_w = os.fdopen(stdout_w, 'wt') |
| 163 | subunit_r = os.fdopen(stdout_r) |
| 164 | returncodes = {} |
| 165 | |
| 166 | def run_argv_thread(): |
| 167 | returncodes['testr'] = run_argv(argv, stdin, subunit_w, |
| 168 | sys.stderr) |
| 169 | subunit_w.close() |
| 170 | |
| 171 | run_thread = threading.Thread(target=run_argv_thread) |
| 172 | run_thread.start() |
| 173 | returncodes['subunit-trace'] = subunit_trace.trace(subunit_r, |
| 174 | sys.stdout) |
| 175 | run_thread.join() |
| 176 | subunit_r.close() |
| 177 | # python version of pipefail |
| 178 | if returncodes['testr']: |
| 179 | returncode = returncodes['testr'] |
| 180 | elif returncodes['subunit-trace']: |
| 181 | returncode = returncodes['subunit-trace'] |
| 182 | return returncode |