forked from Rust-related/RustPython
622 lines
29 KiB
Python
622 lines
29 KiB
Python
#!/usr/bin/env python
|
|
|
|
# Modified from https://github.com/agramian/custom-text-test-runner
|
|
|
|
# The MIT License (MIT)
|
|
#
|
|
# Copyright (c) 2015 Abtin Gramian
|
|
#
|
|
# 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 unittest
|
|
import os, sys, traceback
|
|
import inspect
|
|
import json
|
|
import time
|
|
import re
|
|
import operator
|
|
from unittest.runner import result
|
|
from unittest.runner import registerResult
|
|
from functools import reduce
|
|
|
|
class TablePrinter(object):
|
|
# Modified from https://github.com/agramian/table-printer, same license as above
|
|
"Print a list of dicts as a table"
|
|
def __init__(self, fmt, sep='', ul=None, tl=None, bl=None):
|
|
"""
|
|
@param fmt: list of tuple(heading, key, width)
|
|
heading: str, column label
|
|
key: dictionary key to value to print
|
|
width: int, column width in chars
|
|
@param sep: string, separation between columns
|
|
@param ul: string, character to underline column label, or None for no underlining
|
|
@param tl: string, character to draw as top line over table, or None
|
|
@param bl: string, character to draw as bottom line under table, or None
|
|
"""
|
|
super(TablePrinter,self).__init__()
|
|
fmt = [x + ('left',) if len(x) < 4 else x for x in fmt]
|
|
self.fmt = str(sep).join('{lb}{0}:{align}{1}{rb}'.format(key, width, lb='{', rb='}', align='<' if alignment == 'left' else '>') for heading,key,width,alignment in fmt)
|
|
self.head = {key:heading for heading,key,width,alignment in fmt}
|
|
self.ul = {key:str(ul)*width for heading,key,width,alignment in fmt} if ul else None
|
|
self.width = {key:width for heading,key,width,alignment in fmt}
|
|
self.tl = {key:str(tl)*width for heading,key,width,alignment in fmt} if tl else None
|
|
self.bl = {key:str(bl)*width for heading,key,width,alignment in fmt} if bl else None
|
|
|
|
def row(self, data, separation_character=False):
|
|
if separation_character:
|
|
return self.fmt.format(**{ k:str(data.get(k,''))[:w] for k,w in self.width.items() })
|
|
else:
|
|
data = { k:str(data.get(k,'')) if len(str(data.get(k,''))) <= w else '%s...' %str(data.get(k,''))[:(w-3)] for k,w in self.width.items() }
|
|
return self.fmt.format(**data)
|
|
|
|
def __call__(self, data_list, totals=None):
|
|
_r = self.row
|
|
res = [_r(data) for data in data_list]
|
|
res.insert(0, _r(self.head))
|
|
if self.ul:
|
|
res.insert(1, _r(self.ul, True))
|
|
if self.tl:
|
|
res.insert(0, _r(self.tl, True))
|
|
if totals:
|
|
if self.ul:
|
|
res.insert(len(res), _r(self.ul, True))
|
|
res.insert(len(res), _r(totals))
|
|
if self.bl:
|
|
res.insert(len(res), _r(self.bl, True))
|
|
return '\n'.join(res)
|
|
|
|
|
|
def get_function_args(func_ref):
|
|
try:
|
|
return [p for p in inspect.getargspec(func_ref).args if p != 'self']
|
|
except:
|
|
return None
|
|
|
|
def store_class_fields(class_ref, args_passed):
|
|
""" Store the passed in class fields in self
|
|
"""
|
|
params = get_function_args(class_ref.__init__)
|
|
for p in params: setattr(class_ref, p, args_passed[p])
|
|
|
|
def sum_dict_key(d, key, cast_type=None):
|
|
""" Sum together all values matching a key given a passed dict
|
|
"""
|
|
return reduce( (lambda x, y: x + y), [eval("%s(x['%s'])" %(cast_type, key)) if cast_type else x[key] for x in d] )
|
|
|
|
def case_name(name):
|
|
""" Test case name decorator to override function name.
|
|
"""
|
|
def decorator(function):
|
|
function.__dict__['test_case_name'] = name
|
|
return function
|
|
return decorator
|
|
|
|
def skip_device(name):
|
|
""" Decorator to mark a test to only run on certain devices
|
|
Takes single device name or list of names as argument
|
|
"""
|
|
def decorator(function):
|
|
name_list = name if type(name) == list else [name]
|
|
function.__dict__['skip_device'] = name_list
|
|
return function
|
|
return decorator
|
|
|
|
def _set_test_type(function, test_type):
|
|
""" Test type setter
|
|
"""
|
|
if 'test_type' in function.__dict__:
|
|
function.__dict__['test_type'].append(test_type)
|
|
else:
|
|
function.__dict__['test_type'] = [test_type]
|
|
return function
|
|
|
|
def smoke(function):
|
|
""" Test decorator to mark test as smoke type
|
|
"""
|
|
return _set_test_type(function, 'smoke')
|
|
|
|
def guide_discovery(function):
|
|
""" Test decorator to mark test as guide_discovery type
|
|
"""
|
|
return _set_test_type(function, 'guide_discovery')
|
|
|
|
def focus(function):
|
|
""" Test decorator to mark test as focus type to all rspec style debugging of cases
|
|
"""
|
|
return _set_test_type(function, 'focus')
|
|
|
|
class _WritelnDecorator(object):
|
|
"""Used to decorate file-like objects with a handy 'writeln' method"""
|
|
def __init__(self,stream):
|
|
self.stream = stream
|
|
|
|
def __getattr__(self, attr):
|
|
if attr in ('stream', '__getstate__'):
|
|
raise AttributeError(attr)
|
|
return getattr(self.stream,attr)
|
|
|
|
def writeln(self, arg=None):
|
|
if arg:
|
|
self.write(arg)
|
|
self.write('\n') # text-mode streams translate to \r\n if needed
|
|
|
|
class CustomTextTestResult(result.TestResult):
|
|
_num_formatting_chars = 150
|
|
_execution_time_significant_digits = 4
|
|
_pass_percentage_significant_digits = 2
|
|
|
|
def __init__(self, stream, descriptions, verbosity, results_file_path, result_screenshots_dir, show_previous_results, config, test_types):
|
|
super(CustomTextTestResult, self).__init__(stream, descriptions, verbosity)
|
|
store_class_fields(self, locals())
|
|
self.show_overall_results = verbosity > 0
|
|
self.show_test_info = verbosity > 1
|
|
self.show_individual_suite_results = verbosity > 2
|
|
self.show_errors = verbosity > 3
|
|
self.show_errors_detail = verbosity > 4
|
|
self.show_all = verbosity > 4
|
|
self.suite = None
|
|
self.total_execution_time = 0
|
|
self.separator1 = "=" * CustomTextTestResult._num_formatting_chars
|
|
self.separator2 = "-" * CustomTextTestResult._num_formatting_chars
|
|
self.separator3 = "_" * CustomTextTestResult._num_formatting_chars
|
|
self.separator4 = "*" * CustomTextTestResult._num_formatting_chars
|
|
self.separator_failure = "!" * CustomTextTestResult._num_formatting_chars
|
|
self.separator_pre_result = '.' * CustomTextTestResult._num_formatting_chars
|
|
|
|
def getDescription(self, test):
|
|
doc_first_line = test.shortDescription()
|
|
if self.descriptions and doc_first_line:
|
|
return '\n'.join((str(test), doc_first_line))
|
|
else:
|
|
return str(test)
|
|
|
|
def getSuiteDescription(self, test):
|
|
doc = test.__class__.__doc__
|
|
return doc and doc.split("\n")[0].strip() or None
|
|
|
|
def startTestRun(self):
|
|
self.results = None
|
|
self.previous_suite_runs = []
|
|
if os.path.isfile(self.results_file_path):
|
|
with open(self.results_file_path, 'rb') as f:
|
|
try:
|
|
self.results = json.load(f)
|
|
# recreated results dict with int keys
|
|
self.results['suites'] = {int(k):v for (k,v) in list(self.results['suites'].items())}
|
|
self.suite_map = {v['name']:int(k) for (k,v) in list(self.results['suites'].items())}
|
|
self.previous_suite_runs = list(self.results['suites'].keys())
|
|
except:
|
|
pass
|
|
if not self.results:
|
|
self.results = {'suites': {},
|
|
'name': '',
|
|
'num_passed': 0,
|
|
'num_failed': 0,
|
|
'num_skipped': 0,
|
|
'num_expected_failures': 0,
|
|
'execution_time': None}
|
|
self.suite_number = int(sorted(self.results['suites'].keys())[-1]) + 1 if len(self.results['suites']) else 0
|
|
self.case_number = 0
|
|
self.suite_map = {}
|
|
|
|
def stopTestRun(self):
|
|
# if no tests or some failure occurred execution time may not have been set
|
|
try:
|
|
self.results['suites'][self.suite_map[self.suite]]['execution_time'] = format(self.suite_execution_time, '.%sf' %CustomTextTestResult._execution_time_significant_digits)
|
|
except:
|
|
pass
|
|
self.results['execution_time'] = format(self.total_execution_time, '.%sf' %CustomTextTestResult._execution_time_significant_digits)
|
|
self.stream.writeln(self.separator3)
|
|
with open(self.results_file_path, 'w') as f:
|
|
json.dump(self.results, f)
|
|
|
|
def startTest(self, test):
|
|
suite_base_category = test.__class__.base_test_category if hasattr(test.__class__, 'base_test_category') else ''
|
|
self.next_suite = os.path.join(suite_base_category, test.__class__.name if hasattr(test.__class__, 'name') else test.__class__.__name__)
|
|
self.case = test._testMethodName
|
|
super(CustomTextTestResult, self).startTest(test)
|
|
if not self.suite or self.suite != self.next_suite:
|
|
if self.suite:
|
|
self.results['suites'][self.suite_map[self.suite]]['execution_time'] = format(self.suite_execution_time, '.%sf' %CustomTextTestResult._execution_time_significant_digits)
|
|
self.suite_execution_time = 0
|
|
self.suite = self.next_suite
|
|
if self.show_test_info:
|
|
self.stream.writeln(self.separator1)
|
|
self.stream.writeln("TEST SUITE: %s" %self.suite)
|
|
self.stream.writeln("Description: %s" %self.getSuiteDescription(test))
|
|
try:
|
|
name_override = getattr(test, test._testMethodName).__func__.__dict__['test_case_name']
|
|
except:
|
|
name_override = None
|
|
self.case = name_override if name_override else self.case
|
|
if self.show_test_info:
|
|
# self.stream.writeln(self.separator2)
|
|
self.stream.write("CASE: %s" %self.case)
|
|
if desc := test.shortDescription(): self.stream.write(" (Description: %s)" % desc)
|
|
self.stream.write("... ")
|
|
# self.stream.writeln(self.separator2)
|
|
self.stream.flush()
|
|
self.current_case_number = self.case_number
|
|
if self.suite not in self.suite_map:
|
|
self.suite_map[self.suite] = self.suite_number
|
|
self.results['suites'][self.suite_number] = {
|
|
'name': self.suite,
|
|
'class': test.__class__.__name__,
|
|
'module': re.compile('.* \((.*)\)').match(str(test)).group(1),
|
|
'description': self.getSuiteDescription(test),
|
|
'cases': {},
|
|
'used_case_names': {},
|
|
'num_passed': 0,
|
|
'num_failed': 0,
|
|
'num_skipped': 0,
|
|
'num_expected_failures': 0,
|
|
'execution_time': None}
|
|
self.suite_number += 1
|
|
self.num_cases = 0
|
|
self.num_passed = 0
|
|
self.num_failed = 0
|
|
self.num_skipped = 0
|
|
self.num_expected_failures = 0
|
|
self.results['suites'][self.suite_map[self.suite]]['cases'][self.case_number] = {
|
|
'name': self.case,
|
|
'method': test._testMethodName,
|
|
'result': None,
|
|
'description': test.shortDescription(),
|
|
'note': None,
|
|
'errors': None,
|
|
'failures': None,
|
|
'screenshots': [],
|
|
'new_version': 'No',
|
|
'execution_time': None}
|
|
self.start_time = time.time()
|
|
if self.test_types:
|
|
if ('test_type' in getattr(test, test._testMethodName).__func__.__dict__
|
|
and set([s.lower() for s in self.test_types]) == set([s.lower() for s in getattr(test, test._testMethodName).__func__.__dict__['test_type']])):
|
|
pass
|
|
else:
|
|
getattr(test, test._testMethodName).__func__.__dict__['__unittest_skip_why__'] = 'Test run specified to only run tests of type "%s"' %','.join(self.test_types)
|
|
getattr(test, test._testMethodName).__func__.__dict__['__unittest_skip__'] = True
|
|
if 'skip_device' in getattr(test, test._testMethodName).__func__.__dict__:
|
|
for device in getattr(test, test._testMethodName).__func__.__dict__['skip_device']:
|
|
if self.config and device.lower() in self.config['device_name'].lower():
|
|
getattr(test, test._testMethodName).__func__.__dict__['__unittest_skip_why__'] = 'Test is marked to be skipped on %s' %device
|
|
getattr(test, test._testMethodName).__func__.__dict__['__unittest_skip__'] = True
|
|
break
|
|
|
|
def stopTest(self, test):
|
|
self.end_time = time.time()
|
|
self.execution_time = self.end_time - self.start_time
|
|
self.suite_execution_time += self.execution_time
|
|
self.total_execution_time += self.execution_time
|
|
super(CustomTextTestResult, self).stopTest(test)
|
|
self.num_cases += 1
|
|
self.results['suites'][self.suite_map[self.suite]]['num_passed'] = self.num_passed
|
|
self.results['suites'][self.suite_map[self.suite]]['num_failed'] = self.num_failed
|
|
self.results['suites'][self.suite_map[self.suite]]['num_skipped'] = self.num_skipped
|
|
self.results['suites'][self.suite_map[self.suite]]['num_expected_failures'] = self.num_expected_failures
|
|
self.results['suites'][self.suite_map[self.suite]]['cases'][self.current_case_number]['execution_time']= format(self.execution_time, '.%sf' %CustomTextTestResult._execution_time_significant_digits)
|
|
self.results['num_passed'] += self.num_passed
|
|
self.results['num_failed'] += self.num_failed
|
|
self.results['num_skipped'] += self.num_skipped
|
|
self.results['num_expected_failures'] += self.num_expected_failures
|
|
self.case_number += 1
|
|
|
|
def print_error_string(self, err):
|
|
error_string = ''.join(traceback.format_exception(err[0], err[1], err[2]))
|
|
if self.show_errors:
|
|
self.stream.writeln(self.separator_failure)
|
|
self.stream.write(error_string)
|
|
return error_string
|
|
|
|
def addScreenshots(self, test):
|
|
for root, dirs, files in os.walk(self.result_screenshots_dir):
|
|
for file in files:
|
|
self.results['suites'][self.suite_map[self.suite]]['cases'][self.current_case_number]['screenshots'].append(os.path.join(root, file))
|
|
|
|
def addSuccess(self, test):
|
|
super(CustomTextTestResult, self).addSuccess(test)
|
|
if self.show_test_info:
|
|
# self.stream.writeln(self.separator_pre_result)
|
|
self.stream.writeln("PASS")
|
|
self.stream.flush()
|
|
self.results['suites'][self.suite_map[self.suite]]['cases'][self.current_case_number]['result'] = 'passed'
|
|
self.num_passed += 1
|
|
self.addScreenshots(test)
|
|
|
|
def addError(self, test, err):
|
|
error_string = self.print_error_string(err)
|
|
super(CustomTextTestResult, self).addError(test, err)
|
|
if self.show_test_info:
|
|
# self.stream.writeln(self.separator_pre_result)
|
|
self.stream.writeln("ERROR")
|
|
self.stream.flush()
|
|
self.results['suites'][self.suite_map[self.suite]]['cases'][self.current_case_number]['result'] = 'error'
|
|
self.results['suites'][self.suite_map[self.suite]]['cases'][self.current_case_number]['errors'] = error_string
|
|
self.num_failed += 1
|
|
self.addScreenshots(test)
|
|
|
|
def addFailure(self, test, err):
|
|
error_string = self.print_error_string(err)
|
|
super(CustomTextTestResult, self).addFailure(test, err)
|
|
if self.show_test_info:
|
|
# self.stream.writeln(self.separator_pre_result)
|
|
self.stream.writeln("FAIL")
|
|
self.stream.flush()
|
|
self.results['suites'][self.suite_map[self.suite]]['cases'][self.current_case_number]['result'] = 'failed'
|
|
self.results['suites'][self.suite_map[self.suite]]['cases'][self.current_case_number]['failures'] = error_string
|
|
self.num_failed += 1
|
|
self.addScreenshots(test)
|
|
|
|
def addSkip(self, test, reason):
|
|
super(CustomTextTestResult, self).addSkip(test, reason)
|
|
if self.show_test_info:
|
|
# self.stream.writeln(self.separator_pre_result)
|
|
self.stream.writeln("SKIPPED {0!r}".format(reason))
|
|
self.stream.flush()
|
|
self.results['suites'][self.suite_map[self.suite]]['cases'][self.current_case_number]['result'] = 'skipped'
|
|
self.results['suites'][self.suite_map[self.suite]]['cases'][self.current_case_number]['note'] = reason
|
|
self.num_skipped += 1
|
|
|
|
def addExpectedFailure(self, test, err):
|
|
super(CustomTextTestResult, self).addExpectedFailure(test, err)
|
|
if self.show_test_info:
|
|
# self.stream.writeln(self.separator_pre_result)
|
|
self.stream.writeln("EXPECTED FAILURE")
|
|
self.stream.flush()
|
|
self.results['suites'][self.suite_map[self.suite]]['cases'][self.current_case_number]['result'] = 'expected_failure'
|
|
self.num_expected_failures += 1
|
|
self.addScreenshots(test)
|
|
|
|
def addUnexpectedSuccess(self, test):
|
|
super(CustomTextTestResult, self).addUnexpectedSuccess(test)
|
|
if self.show_test_info:
|
|
# self.stream.writeln(self.separator_pre_result)
|
|
self.stream.writeln("UNEXPECTED SUCCESS")
|
|
self.stream.flush()
|
|
self.num_failed += 1
|
|
self.addScreenshots(test)
|
|
|
|
def printOverallSuiteResults(self, r):
|
|
self.stream.writeln()
|
|
self.stream.writeln(self.separator4)
|
|
self.stream.writeln('OVERALL SUITE RESULTS')
|
|
fmt = [
|
|
('SUITE', 'suite', 50, 'left'),
|
|
('CASES', 'cases', 15, 'right'),
|
|
('PASSED', 'passed', 15, 'right'),
|
|
('FAILED', 'failed', 15, 'right'),
|
|
('SKIPPED', 'skipped', 15, 'right'),
|
|
('%', 'percentage', 20, 'right'),
|
|
('TIME (s)', 'time', 20, 'right')
|
|
]
|
|
data = []
|
|
for x in r: data.append({'suite': r[x]['name'],
|
|
'cases': r[x]['num_passed'] + r[x]['num_failed'],
|
|
'passed': r[x]['num_passed'],
|
|
'failed': r[x]['num_failed'],
|
|
'skipped': r[x]['num_skipped'],
|
|
'expected_failures': r[x]['num_expected_failures'],
|
|
'percentage': float(r[x]['num_passed'])/(r[x]['num_passed'] + r[x]['num_failed']) * 100 if (r[x]['num_passed'] + r[x]['num_failed']) > 0 else 0,
|
|
'time': r[x]['execution_time']})
|
|
total_suites_passed = len([x for x in data if not x['failed']])
|
|
total_suites_passed_percentage = format(float(total_suites_passed)/len(data) * 100, '.%sf' %CustomTextTestResult._pass_percentage_significant_digits)
|
|
totals = {'suite': 'TOTALS %s/%s (%s%%) suites passed' %(total_suites_passed, len(data), total_suites_passed_percentage),
|
|
'cases': sum_dict_key(data, 'cases'),
|
|
'passed': sum_dict_key(data, 'passed'),
|
|
'failed': sum_dict_key(data, 'failed'),
|
|
'skipped': sum_dict_key(data, 'skipped'),
|
|
'percentage': sum_dict_key(data, 'percentage')/len(data),
|
|
'time': sum_dict_key(data, 'time', 'float')}
|
|
for x in data: operator.setitem(x, 'percentage', format(x['percentage'], '.%sf' %CustomTextTestResult._pass_percentage_significant_digits))
|
|
totals['percentage'] = format(totals['percentage'], '.%sf' %CustomTextTestResult._pass_percentage_significant_digits)
|
|
self.stream.writeln( TablePrinter(fmt, tl=self.separator1, ul=self.separator2, bl=self.separator3)(data, totals) )
|
|
self.stream.writeln()
|
|
|
|
def printIndividualSuiteResults(self, r):
|
|
self.stream.writeln()
|
|
self.stream.writeln(self.separator4)
|
|
self.stream.writeln('INDIVIDUAL SUITE RESULTS')
|
|
fmt = [
|
|
('CASE', 'case', 50, 'left'),
|
|
('DESCRIPTION', 'description', 50, 'right'),
|
|
('RESULT', 'result', 25, 'right'),
|
|
('TIME (s)', 'time', 25, 'right')
|
|
]
|
|
for suite in r:
|
|
self.stream.writeln(self.separator1)
|
|
self.stream.write('{0: <50}'.format('SUITE: %s' %r[suite]['name']))
|
|
self.stream.writeln('{0: <100}'.format('DESCRIPTION: %s' %(r[suite]['description'] if not r[suite]['description'] or len(r[suite]['description']) <= (100 - len('DESCRIPTION: '))
|
|
else '%s...' %r[suite]['description'][:(97 - len('DESCRIPTION: '))])))
|
|
data = []
|
|
cases = r[suite]['cases']
|
|
for x in cases: data.append({'case': cases[x]['name'],
|
|
'description': cases[x]['description'],
|
|
'result': cases[x]['result'].upper() if cases[x]['result'] else cases[x]['result'],
|
|
'time': cases[x]['execution_time']})
|
|
self.stream.writeln( TablePrinter(fmt, tl=self.separator1, ul=self.separator2)(data) )
|
|
self.stream.writeln(self.separator3)
|
|
self.stream.writeln()
|
|
|
|
def printErrorsOverview(self, r):
|
|
self.stream.writeln()
|
|
self.stream.writeln(self.separator4)
|
|
self.stream.writeln('FAILURES AND ERRORS OVERVIEW')
|
|
fmt = [
|
|
('SUITE', 'suite', 50, 'left'),
|
|
('CASE', 'case', 50, 'left'),
|
|
('RESULT', 'result', 50, 'right')
|
|
]
|
|
data = []
|
|
for suite in r:
|
|
cases = {k:v for (k,v) in list(r[suite]['cases'].items()) if v['failures'] or v['errors']}
|
|
for x in cases: data.append({'suite': '%s%s' %(r[suite]['name'], ' (%s)' %r[suite]['module'] if r[suite]['class'] != r[suite]['name'] else ''),
|
|
'case': '%s%s' %(cases[x]['name'], ' (%s)' %cases[x]['method'] if cases[x]['name'] != cases[x]['method'] else ''),
|
|
'result': cases[x]['result'].upper()})
|
|
self.stream.writeln( TablePrinter(fmt, tl=self.separator1, ul=self.separator2)(data) )
|
|
self.stream.writeln(self.separator3)
|
|
self.stream.writeln()
|
|
|
|
def printErrorsDetail(self, r):
|
|
self.stream.writeln()
|
|
self.stream.writeln(self.separator4)
|
|
self.stream.writeln('FAILURES AND ERRORS DETAIL')
|
|
for suite in r:
|
|
failures_and_errors = [k for (k,v) in list(r[suite]['cases'].items()) if v['failures'] or v['errors']]
|
|
#print failures_and_errors
|
|
suite_str = '%s%s' %(r[suite]['name'], ' (%s)' %r[suite]['module'] if r[suite]['class'] != r[suite]['name'] else '')
|
|
for case in failures_and_errors:
|
|
case_ref = r[suite]['cases'][case]
|
|
case_str = '%s%s' %(case_ref['name'], ' (%s)' %case_ref['method'] if case_ref['name'] != case_ref['method'] else '')
|
|
errors = case_ref['errors']
|
|
failures = case_ref['failures']
|
|
self.stream.writeln(self.separator1)
|
|
if errors:
|
|
self.stream.writeln('ERROR: %s [%s]' %(case_str, suite_str))
|
|
self.stream.writeln(self.separator2)
|
|
self.stream.writeln(errors)
|
|
if failures:
|
|
self.stream.writeln('FAILURE: %s [%s]' %(case_str, suite_str))
|
|
self.stream.writeln(self.separator2)
|
|
self.stream.writeln(failures)
|
|
self.stream.writeln(self.separator3)
|
|
self.stream.writeln()
|
|
|
|
def printSkippedDetail(self, r):
|
|
self.stream.writeln()
|
|
self.stream.writeln(self.separator4)
|
|
self.stream.writeln('SKIPPED DETAIL')
|
|
fmt = [
|
|
('SUITE', 'suite', 50, 'left'),
|
|
('CASE', 'case', 50, 'left'),
|
|
('REASON', 'reason', 50, 'right')
|
|
]
|
|
data = []
|
|
for suite in r:
|
|
cases = {k:v for (k,v) in list(r[suite]['cases'].items()) if v['result'] == 'skipped'}
|
|
for x in cases: data.append({'suite': '%s%s' %(r[suite]['name'], ' (%s)' %r[suite]['module'] if r[suite]['class'] != r[suite]['name'] else ''),
|
|
'case': '%s%s' %(cases[x]['name'], ' (%s)' %cases[x]['method'] if cases[x]['name'] != cases[x]['method'] else ''),
|
|
'reason': cases[x]['note']})
|
|
self.stream.writeln( TablePrinter(fmt, tl=self.separator1, ul=self.separator2)(data) )
|
|
self.stream.writeln(self.separator3)
|
|
self.stream.writeln()
|
|
|
|
def returnCode(self):
|
|
return not self.wasSuccessful()
|
|
|
|
class CustomTextTestRunner(unittest.TextTestRunner):
|
|
"""A test runner class that displays results in textual form.
|
|
It prints out the names of tests as they are run, errors as they
|
|
occur, and a summary of the results at the end of the test run.
|
|
"""
|
|
|
|
def __init__(self,
|
|
stream=sys.stderr,
|
|
descriptions=True,
|
|
verbosity=1,
|
|
failfast=False,
|
|
buffer=False,
|
|
resultclass=CustomTextTestResult,
|
|
results_file_path="results.json",
|
|
result_screenshots_dir='',
|
|
show_previous_results=False,
|
|
test_name=None,
|
|
test_description=None,
|
|
config=None,
|
|
test_types=None):
|
|
store_class_fields(self, locals())
|
|
self.stream = _WritelnDecorator(stream)
|
|
|
|
def _makeResult(self):
|
|
return self.resultclass(self.stream, self.descriptions, self.verbosity,
|
|
self.results_file_path, self.result_screenshots_dir, self.show_previous_results,
|
|
self.config, self.test_types)
|
|
|
|
def run(self, test):
|
|
output = ""
|
|
"Run the given test case or test suite."
|
|
result = self._makeResult()
|
|
registerResult(result)
|
|
result.failfast = self.failfast
|
|
result.buffer = self.buffer
|
|
startTime = time.time()
|
|
startTestRun = getattr(result, 'startTestRun', None)
|
|
if startTestRun is not None:
|
|
startTestRun()
|
|
try:
|
|
test(result)
|
|
finally:
|
|
stopTestRun = getattr(result, 'stopTestRun', None)
|
|
if stopTestRun is not None:
|
|
stopTestRun()
|
|
stopTime = time.time()
|
|
timeTaken = stopTime - startTime
|
|
# filter results to output
|
|
if result.show_previous_results:
|
|
r = result.results['suites']
|
|
else:
|
|
r = {k:v for (k,v) in list(result.results['suites'].items()) if k not in result.previous_suite_runs}
|
|
# print results based on verbosity
|
|
if result.show_all:
|
|
result.printSkippedDetail(r)
|
|
if result.show_errors_detail:
|
|
result.printErrorsDetail(r)
|
|
if result.show_individual_suite_results:
|
|
result.printIndividualSuiteResults(r)
|
|
if result.show_errors:
|
|
result.printErrorsOverview(r)
|
|
if result.show_overall_results:
|
|
result.printOverallSuiteResults(r)
|
|
run = result.testsRun
|
|
self.stream.writeln("Ran %d test case%s in %.4fs" %
|
|
(run, run != 1 and "s" or "", timeTaken))
|
|
self.stream.writeln()
|
|
|
|
expectedFails = unexpectedSuccesses = skipped = 0
|
|
try:
|
|
results = map(len, (result.expectedFailures,
|
|
result.unexpectedSuccesses,
|
|
result.skipped))
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
expectedFails, unexpectedSuccesses, skipped = results
|
|
|
|
infos = []
|
|
if not result.wasSuccessful():
|
|
self.stream.write("FAILED")
|
|
failed, errored = map(len, (result.failures, result.errors))
|
|
if failed:
|
|
infos.append("failures=%d" % failed)
|
|
if errored:
|
|
infos.append("errors=%d" % errored)
|
|
else:
|
|
self.stream.write("OK")
|
|
if skipped:
|
|
infos.append("skipped=%d" % skipped)
|
|
if expectedFails:
|
|
infos.append("expected failures=%d" % expectedFails)
|
|
if unexpectedSuccesses:
|
|
infos.append("unexpected successes=%d" % unexpectedSuccesses)
|
|
if infos:
|
|
self.stream.writeln(" (%s)" % (", ".join(infos),))
|
|
else:
|
|
self.stream.write("\n")
|
|
return result
|