os-testr-1.0.0/ 0000775 0001750 0001750 00000000000 13154262453 014423 5 ustar jenkins jenkins 0000000 0000000 os-testr-1.0.0/os_testr/ 0000775 0001750 0001750 00000000000 13154262453 016265 5 ustar jenkins jenkins 0000000 0000000 os-testr-1.0.0/os_testr/ostestr.py 0000775 0001750 0001750 00000027162 13154262145 020353 0 ustar jenkins jenkins 0000000 0000000 #!/usr/bin/env python2 # Copyright 2015 Hewlett-Packard Development Company, L.P. # # 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. import argparse import copy import io import os import subprocess import sys import warnings import pbr.version import six.moves from stestr import commands from subunit import run as subunit_run from testtools import run as testtools_run from os_testr import regex_builder as rb __version__ = pbr.version.VersionInfo('os_testr').version_string() def get_parser(args): parser = argparse.ArgumentParser( description='Tool to run openstack tests') parser.add_argument('--version', action='version', version='%s' % __version__) parser.add_argument('--blacklist-file', '-b', '--blacklist_file', help='Path to a blacklist file, this file ' 'contains a separate regex exclude on each ' 'newline') parser.add_argument('--whitelist-file', '-w', '--whitelist_file', help='Path to a whitelist file, this file ' 'contains a separate regex on each newline.') group = parser.add_mutually_exclusive_group() group.add_argument('--regex', '-r', help='A normal testr selection regex.') group.add_argument('--path', metavar='FILE_OR_DIRECTORY', help='A file name or directory of tests to run.') group.add_argument('--no-discover', '-n', metavar='TEST_ID', help="Takes in a single test to bypasses test " "discover and just execute the test specified. " "A file name may be used in place of a test " "name.") parser.add_argument('--black-regex', '-B', help='Test rejection regex. If a test cases name ' 'matches on re.search() operation , ' 'it will be removed from the final test list. ' 'Effectively the black-regex is added to ' ' black regex list, but you do need to edit a file. ' 'The black filtering happens after the initial ' ' white selection, which by default is everything.') pretty = parser.add_mutually_exclusive_group() pretty.add_argument('--pretty', '-p', dest='pretty', action='store_true', help='Print pretty output from subunit-trace. This is ' 'mutually exclusive with --subunit') pretty.add_argument('--no-pretty', dest='pretty', action='store_false', help='Disable the pretty output with subunit-trace') parser.add_argument('--subunit', '-s', action='store_true', help='output the raw subunit v2 from the test run ' 'this is mutually exclusive with --pretty') parser.add_argument('--list', '-l', action='store_true', help='List all the tests which will be run.') parser.add_argument('--color', action='store_true', help='Use color in the pretty output') slowest = parser.add_mutually_exclusive_group() slowest.add_argument('--slowest', dest='slowest', action='store_true', help="after the test run print the slowest tests") slowest.add_argument('--no-slowest', dest='slowest', action='store_false', help="after the test run don't print the slowest " "tests") parser.add_argument('--pdb', metavar='TEST_ID', help='Run a single test that has pdb traces added') 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') parser.add_argument('--concurrency', '-c', type=int, metavar='WORKERS', help='The number of workers to use when running in ' 'parallel. By default this is the number of cpus') parser.add_argument('--until-failure', action='store_true', help='Run the tests in a loop until a failure is ' 'encountered. Running with subunit or pretty' 'output enable will force the loop to run tests' 'serially') parser.add_argument('--print-exclude', action='store_true', help='If an exclude file is used this option will ' 'prints the comment from the same line and all ' 'skipped tests before the test run') parser.set_defaults(pretty=True, slowest=True, parallel=True) return parser.parse_known_args(args) def call_testr(regex, subunit, pretty, list_tests, slowest, parallel, concur, until_failure, color, others=None, blacklist_file=None, whitelist_file=None, black_regex=None, load_list=None): # Handle missing .stestr.conf from users from before stestr migration test_dir = None top_dir = None group_regex = None if not os.path.isfile('.stestr.conf') and os.path.isfile('.testr.conf'): msg = ('No .stestr.conf file found in the CWD. Please create one to ' 'to replace the .testr.conf. You can find a script to do this ' 'in the stestr git repository.') warnings.warn(msg) with open('.testr.conf', 'r') as testr_conf_file: config = six.moves.configparser.ConfigParser() config.readfp(testr_conf_file) test_command = config.get('DEFAULT', 'test_command') group_regex = None if config.has_option('DEFAULT', 'group_regex'): group_regex = config.get('DEFAULT', 'group_regex') for line in test_command.split('\n'): if 'subunit.run discover' in line: command_parts = line.split(' ') top_dir_present = '-t' in line for idx, val in enumerate(command_parts): if top_dir_present: if val == '-t': top_dir = command_parts[idx + 1] test_dir = command_parts[idx + 2] else: if val == 'discover': test_dir = command_parts[idx + 2] elif not os.path.isfile( '.testr.conf') and not os.path.isfile('.stestr.conf'): msg = ('No .stestr.conf found, please create one.') print(msg) sys.exit(1) regexes = None if regex: regexes = regex.split() serial = not parallel if list_tests: # TODO(mtreinish): remove init call after list command detects and # autocreates the repository if not os.path.isdir('.stestr'): commands.init_command() return commands.list_command(filters=regexes) return_code = commands.run_command(filters=regexes, subunit_out=subunit, concurrency=concur, test_path=test_dir, top_dir=top_dir, group_regex=group_regex, until_failure=until_failure, serial=serial, pretty_out=pretty, load_list=load_list, blacklist_file=blacklist_file, whitelist_file=whitelist_file, black_regex=black_regex) if slowest: sys.stdout.write("\nSlowest Tests:\n") commands.slowest_command() return return_code def call_subunit_run(test_id, pretty, subunit): env = copy.deepcopy(os.environ) cmd_save_results = ['testr', 'load', '--subunit'] if pretty: # Use subunit run module cmd = ['python', '-m', 'subunit.run', test_id] ps = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE) # Save subunit results via testr pfile = subprocess.Popen(cmd_save_results, env=env, stdin=ps.stdout, stdout=subprocess.PIPE) ps.stdout.close() # Transform output via subunit-trace proc = subprocess.Popen(['subunit-trace', '--no-failure-debug', '-f'], env=env, stdin=pfile.stdout) pfile.stdout.close() proc.communicate() return proc.returncode elif subunit: sstdout = io.BytesIO() subunit_run.main([sys.argv[0], test_id], sstdout) pfile = subprocess.Popen(cmd_save_results, env=env, stdin=subprocess.PIPE) pfile.communicate(input=sstdout.getvalue()) else: testtools_run.main([sys.argv[0], test_id], sys.stdout) def _select_and_call_runner(opts, exclude_regex, others): ec = 1 if not opts.no_discover and not opts.pdb: ec = call_testr(exclude_regex, opts.subunit, opts.pretty, opts.list, opts.slowest, opts.parallel, opts.concurrency, opts.until_failure, opts.color, others, blacklist_file=opts.blacklist_file, whitelist_file=opts.whitelist_file, black_regex=opts.black_regex) else: if others: print('Unexpected arguments: ' + ' '.join(others)) return 2 test_to_run = opts.no_discover or opts.pdb if test_to_run.find('/') != -1: test_to_run = rb.path_to_regex(test_to_run) ec = call_subunit_run(test_to_run, opts.pretty, opts.subunit) return ec def ostestr(args): opts, others = get_parser(args) if opts.pretty and opts.subunit: msg = ('Subunit output and pretty output cannot be specified at the ' 'same time') print(msg) return 2 if opts.list and opts.no_discover: msg = ('you can not list tests when you are bypassing discovery to ' 'run a single test') print(msg) return 3 if not opts.parallel and opts.concurrency: msg = "You can't specify a concurrency to use when running serially" print(msg) return 4 if (opts.pdb or opts.no_discover) and opts.until_failure: msg = "You can not use until_failure mode with pdb or no-discover" print(msg) return 5 if ((opts.pdb or opts.no_discover) and (opts.blacklist_file or opts.whitelist_file)): msg = "You can not use blacklist or whitelist with pdb or no-discover" print(msg) return 6 if ((opts.pdb or opts.no_discover) and (opts.black_regex)): msg = "You can not use black-regex with pdb or no-discover" print(msg) return 7 if opts.path: regex = rb.path_to_regex(opts.path) else: regex = opts.regex return _select_and_call_runner(opts, regex, others) def main(): exit(ostestr(sys.argv[1:])) if __name__ == '__main__': main() os-testr-1.0.0/os_testr/__init__.py 0000664 0001750 0001750 00000001230 13154262145 020370 0 ustar jenkins jenkins 0000000 0000000 # -*- coding: utf-8 -*- # 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. import pbr.version __version__ = pbr.version.VersionInfo( 'os_testr').version_string() os-testr-1.0.0/os_testr/regex_builder.py 0000664 0001750 0001750 00000006672 13154262145 021470 0 ustar jenkins jenkins 0000000 0000000 # Copyright 2016 Hewlett-Packard Development Company, L.P. # # 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. import copy import os import subprocess def _get_test_list(regex, env=None): env = env or copy.deepcopy(os.environ) testr_args = ['stestr', 'list'] if regex: testr_args.append(regex) proc = subprocess.Popen(testr_args, env=env, stdout=subprocess.PIPE, universal_newlines=True) out = proc.communicate()[0] raw_test_list = out.split('\n') bad = False test_list = [] exclude_list = ['OS_', 'CAPTURE', 'TEST_TIMEOUT', 'PYTHON', 'subunit.run discover'] for line in raw_test_list: for exclude in exclude_list: if exclude in line or not line: bad = True break if not bad: test_list.append(line) bad = False return test_list def print_skips(regex, message): test_list = _get_test_list(regex) if test_list: if message: print(message) else: print('Skipped because of regex %s:' % regex) for test in test_list: print(test) # Extra whitespace to separate print('\n') def path_to_regex(path): root, _ = os.path.splitext(path) return root.replace('/', '.') def get_regex_from_whitelist_file(file_path): lines = [] with open(file_path) as white_file: for line in white_file.read().splitlines(): split_line = line.strip().split('#') # Before the # is the regex line_regex = split_line[0].strip() if line_regex: lines.append(line_regex) return '|'.join(lines) def construct_regex(blacklist_file, whitelist_file, regex, print_exclude): """Deprecated, please use testlist_builder.construct_list instead.""" if not blacklist_file: exclude_regex = '' else: with open(blacklist_file, 'r') as black_file: exclude_regex = '' for line in black_file: raw_line = line.strip() split_line = raw_line.split('#') # Before the # is the regex line_regex = split_line[0].strip() if len(split_line) > 1: # After the # is a comment comment = split_line[1].strip() else: comment = '' if line_regex: if print_exclude: print_skips(line_regex, comment) if exclude_regex: exclude_regex = '|'.join([line_regex, exclude_regex]) else: exclude_regex = line_regex if exclude_regex: exclude_regex = "^((?!" + exclude_regex + ").)*$" if regex: exclude_regex += regex if whitelist_file: exclude_regex += '%s' % get_regex_from_whitelist_file(whitelist_file) return exclude_regex os-testr-1.0.0/os_testr/utils/ 0000775 0001750 0001750 00000000000 13154262453 017425 5 ustar jenkins jenkins 0000000 0000000 os-testr-1.0.0/os_testr/utils/__init__.py 0000664 0001750 0001750 00000000000 13154262145 021522 0 ustar jenkins jenkins 0000000 0000000 os-testr-1.0.0/os_testr/utils/colorizer.py 0000664 0001750 0001750 00000007165 13154262145 022016 0 ustar jenkins jenkins 0000000 0000000 # Copyright 2015 NEC Corporation # All Rights Reserved. # # 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. # # Colorizer Code is borrowed from Twisted: # Copyright (c) 2001-2010 Twisted Matrix Laboratories. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import sys class AnsiColorizer(object): """A colorizer is an object that loosely wraps around a stream allowing callers to write text to the stream in a particular color. Colorizer classes must implement C{supported()} and C{write(text, color)}. """ _colors = dict(black=30, red=31, green=32, yellow=33, blue=34, magenta=35, cyan=36, white=37) def __init__(self, stream): self.stream = stream @classmethod def supported(cls, stream=sys.stdout): """Check the current platform supports coloring terminal output A class method that returns True if the current platform supports coloring terminal output using this method. Returns False otherwise. """ if not stream.isatty(): return False # auto color only on TTYs try: import curses except ImportError: return False else: try: try: return curses.tigetnum("colors") > 2 except curses.error: curses.setupterm() return curses.tigetnum("colors") > 2 except Exception: # guess false in case of error return False def write(self, text, color): """Write the given text to the stream in the given color. @param text: Text to be written to the stream. @param color: A string label for a color. e.g. 'red', 'white'. """ color = self._colors[color] self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text)) class NullColorizer(object): """See _AnsiColorizer docstring.""" def __init__(self, stream): self.stream = stream @classmethod def supported(cls, stream=sys.stdout): return True def write(self, text, color): self.stream.write(text) os-testr-1.0.0/os_testr/testlist_builder.py 0000664 0001750 0001750 00000007030 13154262145 022216 0 ustar jenkins jenkins 0000000 0000000 # Copyright 2016 RedHat, Inc. # # 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. from os_testr import regex_builder import re def black_reader(blacklist_file): black_file = open(blacklist_file, 'r') regex_comment_lst = [] # tupple of (regex_compild, msg, skipped_lst) for line in black_file: raw_line = line.strip() split_line = raw_line.split('#') # Before the # is the regex line_regex = split_line[0].strip() if len(split_line) > 1: # After the # is a comment comment = ''.join(split_line[1:]).strip() else: comment = 'Skipped because of regex %s:' % line_regex if not line_regex: continue regex_comment_lst.append((re.compile(line_regex), comment, [])) return regex_comment_lst def print_skips(regex, message, test_list): for test in test_list: print(test) # Extra whitespace to separate print('\n') def construct_list(blacklist_file, whitelist_file, regex, black_regex, print_exclude): """Filters the discovered test cases :retrun: iterable of strings. The strings are full test cases names, including tags like.: "project.api.TestClass.test_case[positive]" """ if not regex: regex = '' # handle the other false things if whitelist_file: white_re = regex_builder.get_regex_from_whitelist_file(whitelist_file) else: white_re = '' if not regex and white_re: regex = white_re elif regex and white_re: regex = '|'.join((regex, white_re)) if blacklist_file: black_data = black_reader(blacklist_file) else: black_data = None if black_regex: msg = "Skipped because of regex provided as a command line argument:" record = (re.compile(black_regex), msg, []) if black_data: black_data.append(record) else: black_data = [record] search_filter = re.compile(regex) # NOTE(afazekas): we do not want to pass a giant re # to an external application due to the arg length limitatios list_of_test_cases = [test_case for test_case in regex_builder._get_test_list('') if search_filter.search(test_case)] set_of_test_cases = set(list_of_test_cases) if not black_data: return set_of_test_cases # NOTE(afazekas): We might use a faster logic when the # print option is not requested for (rex, msg, s_list) in black_data: for test_case in list_of_test_cases: if rex.search(test_case): # NOTE(mtreinish): In the case of overlapping regex the test # case might have already been removed from the set of tests if test_case in set_of_test_cases: set_of_test_cases.remove(test_case) s_list.append(test_case) if print_exclude: for (rex, msg, s_list) in black_data: if s_list: print_skips(rex, msg, s_list) return set_of_test_cases os-testr-1.0.0/os_testr/subunit2html.py 0000775 0001750 0001750 00000055317 13154262145 021313 0 ustar jenkins jenkins 0000000 0000000 #!/usr/bin/python # # Copyright 2012-2013 Hewlett-Packard Development Company, L.P. # # 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. """ Utility to convert a subunit stream to an html results file. Code is adapted from the pyunit Html test runner at http://tungwaiyip.info/software/HTMLTestRunner.html Takes two arguments. First argument is path to subunit log file, second argument is path of desired output file. Second argument is optional, defaults to 'results.html'. Original HTMLTestRunner License: ------------------------------------------------------------------------ Copyright (c) 2004-2007, Wai Yip Tung All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Wai Yip Tung nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import collections import datetime import io import sys import traceback from xml.sax import saxutils import pbr.version import subunit import testtools __version__ = pbr.version.VersionInfo('os_testr').version_string() class TemplateData(object): """Define a HTML template for report customerization and generation. Overall structure of an HTML report HTML +------------------------+ | | |
| | | | STYLESHEET | | +----------------+ | | | | | | +----------------+ | | | | | | | | | | | | HEADING | | +----------------+ | | | | | | +----------------+ | | | | REPORT | | +----------------+ | | | | | | +----------------+ | | | | ENDING | | +----------------+ | | | | | | +----------------+ | | | | | | | +------------------------+ """ STATUS = { 0: 'pass', 1: 'fail', 2: 'error', 3: 'skip', } DEFAULT_TITLE = 'Unit Test Report' DEFAULT_DESCRIPTION = '' # ------------------------------------------------------------------------ # HTML Template HTML_TMPL = r"""%(description)s
%(name)s: %(value)s
""" # variables: (name, value) # ------------------------------------------------------------------------ # Report # REPORT_TMPL = """Test Group/Test case | Count | Pass | Fail | Error | Skip | View | |
Total | %(count)s | %(Pass)s | %(fail)s | %(error)s | %(skip)s |
%(script)s