def load_plugin(module_name): import importlib try: plugin_module = importlib.import_module(module_name + '.plugin') except ImportError: plugin_module = importlib.import_module(module_name) parser = FakePytestParser() try: if hasattr(plugin_module, 'pytest_load_initial_conftests'): plugin_module.pytest_load_initial_conftests( early_config=early_config, parser=parser, args=[]) if hasattr(plugin_module, 'pytest_configure'): import inspect if inspect.signature(plugin_module.pytest_configure).parameters: plugin_module.pytest_configure(early_config) else: plugin_module.pytest_configure() except Exception: hammett.print(f'Loading plugin {module_name} failed: ') import traceback hammett.print(traceback.format_exc()) hammett.g.results['abort'] += 1 hammett.g.should_stop = True return try: importlib.import_module(module_name + '.fixtures') except ImportError: pass return True
def _teardown_yield_fixture(fixturefunc, it): """Executes the teardown of a fixture function by advancing the iterator after the yield and ensure the iteration ends (if not it means there is more than one yield in the function)""" try: next(it) except StopIteration: pass else: hammett.print(f"yield_fixture {fixturefunc} function has more than one 'yield'") exit(1)
def inc_test_result(status, _name, _f, duration, stdout, stderr): verbose = hammett.g.verbose message = MESSAGES[status]['v' if verbose else 's'] if verbose: hammett.print(message + (str(duration) if duration else '')) else: hammett.print(message, end='', flush=True) if hammett.g.fail_fast and status not in ('success', 'skipped'): hammett.g.should_stop = True filename = _f.__module__.replace('.', os.sep) + '.py' assert _name.startswith(_f.__module__) _name = _name[len(_f.__module__) + 1:] hammett.g.result_db['test_results'][filename][_name] = dict(stdout=stdout, stderr=stderr, status=status)
def register_fixture(fixture, *args, autouse=False, scope='function'): if scope == 'class': # hammett does not support class based tests return assert scope != 'package', 'Package scope is not supported at this time' name = fixture_function_name(fixture) # pytest uses shadowing.. I don't like it but I guess we have to follow that? # assert name not in fixtures, 'A fixture with this name is already registered' if hammett.g.verbose and name in fixtures and name != 'request': hammett.print(f'{fixture} shadows {fixtures[name]}') if autouse: auto_use_fixtures.add(name) assert scope in ('function', 'class', 'module', 'package', 'session') fixture_scope[name] = scope fixtures[name] = fixture
def feedback_for_exception(): type, value, tb = sys.exc_info() while tb.tb_next: tb = tb.tb_next local_variables = tb.tb_frame.f_locals if local_variables: hammett.print('--- Local variables ---') for k, v in sorted(local_variables.items()): hammett.print(f'{k}:') try: hammett.print(indent(pretty_format(v))) except Exception as e: hammett.print(f' Error getting local variable repr: {e}') if type == AssertionError: analyze_assert(tb)
def run_test(_name, _f, _module_request, **kwargs): if should_skip(_f): inc_test_result(SKIPPED, _name, _f, duration=0, stdout='', stderr='') return from io import StringIO req = hammett.Request(scope='function', parent=_module_request, function=_f) def request(): return req register_fixture(request, autouse=True) del request hijacked_stdout = StringIO() hijacked_stderr = StringIO() prev_stdout = sys.stdout prev_stderr = sys.stderr status = None duration = None start = None setup_time = None if hammett.g.verbose: hammett.print(_name + '...', end='', flush=True) try: sys.stdout = hijacked_stdout sys.stderr = hijacked_stderr if hammett.g.durations: start = datetime.now() resolved_function, resolved_kwargs = dependency_injection(_f, fixtures, kwargs, request=req) if hammett.g.durations: setup_time = datetime.now() - start start = datetime.now() resolved_function(**resolved_kwargs) if hammett.g.durations: duration = datetime.now() - start hammett.g.durations_results.append((_name, duration, setup_time)) sys.stdout = prev_stdout sys.stderr = prev_stderr status = SUCCESS except KeyboardInterrupt: sys.stdout = prev_stdout sys.stderr = prev_stderr hammett.print() hammett.print('ABORTED') hammett.g.results['abort'] += 1 hammett.g.should_stop = True except SkipTest: sys.stdout = prev_stdout sys.stderr = prev_stderr status = SKIPPED except: sys.stdout = prev_stdout sys.stderr = prev_stderr hammett.print(RED) if not hammett.g.verbose: hammett.print() hammett.print('Failed:', _name) hammett.print() import traceback hammett.print(traceback.format_exc()) hammett.print() if hijacked_stdout.getvalue(): hammett.print(YELLOW) hammett.print('--- stdout ---') hammett.print(hijacked_stdout.getvalue()) if hijacked_stderr.getvalue(): hammett.print(RED) hammett.print('--- stderr ---') hammett.print(hijacked_stderr.getvalue()) hammett.print(RESET_COLOR) if not hammett.g.quiet: feedback_for_exception() if hammett.g.drop_into_debugger: try: import ipdb as pdb except ImportError: import pdb pdb.set_trace() status = FAILED assert status is not None inc_test_result(status, _name, _f, duration, stdout=hijacked_stdout.getvalue(), stderr=hijacked_stderr.getvalue()) # Tests can change this which breaks everything. Reset! os.chdir(hammett.g.orig_cwd) req.teardown()
def analyze_assert(tb): if hammett.g.disable_assert_analyze: return # grab assert source line try: with open(tb.tb_frame.f_code.co_filename) as f: source = f.read().split('\n') except FileNotFoundError: try: with open( os.path.join(hammett.g.orig_cwd, tb.tb_frame.f_code.co_filename)) as f: source = f.read().split('\n') except FileNotFoundError: hammett.print( 'Failed to analyze assert statement: file not found. Most likely there was a change of current directory.' ) return line_no = tb.tb_frame.f_lineno - 1 relevant_source = source[line_no] # if it spans multiple lines grab them all while line_no and not relevant_source.strip().startswith('assert '): line_no -= 1 relevant_source = source[line_no] + '\n' + relevant_source if not relevant_source.strip().startswith('assert '): hammett.print( 'Failed to analyze assert statement (Did not find the assert)') return import ast try: assert_statement = ast.parse(relevant_source.strip()).body[0] except SyntaxError: try: # grab one more line after the the one where we got an exception, and try again relevant_source += '\n' + source[tb.tb_frame.f_lineno] assert_statement = ast.parse(relevant_source.strip()).body[0] except SyntaxError: hammett.print('Failed to analyze assert statement (SyntaxError)') return # We only analyze further if it's a comparison if assert_statement.test.__class__.__name__ != 'Compare': return hammett.print() hammett.print('--- Assert components ---') from astunparse import unparse try: left = eval(unparse(assert_statement.test.left), tb.tb_frame.f_globals, tb.tb_frame.f_locals) hammett.print('left:') hammett.print(indent(pretty_format(left))) right = eval(unparse(assert_statement.test.comparators), tb.tb_frame.f_globals, tb.tb_frame.f_locals) except Exception as e: hammett.print(f'Failed to analyze assert statement ({type(e)}: {e})') return hammett.print('right:') hammett.print(indent(pretty_format(right))) if isinstance(left, str) and isinstance( right, str) and len(left) > DIFF_STRING_SIZE_CUTOFF and len( right) > DIFF_STRING_SIZE_CUTOFF and '\n' in left: hammett.print() hammett.print('--- Diff of left and right assert components ---') left_lines = left.split('\n') right_lines = right.split('\n') from difflib import unified_diff for line in unified_diff(left_lines, right_lines, fromfile='expected', tofile='actual', lineterm=''): color = '' if line: color = { '+': GREEN, '-': RED, '@': MAGENTA, }.get(line[0], '') hammett.print(f'{color}{line}{RESET_COLOR}')
def print_status(_name, result: Result): verbose = hammett.g.verbose message = MESSAGES[result.status]['v' if verbose else 's'] if verbose: hammett.print(_name + ' ' + message + (str(result.duration) if result.duration else '')) else: hammett.print(message, end='', flush=True) if result.status == FAILED: hammett.print(RED) if not hammett.g.verbose: hammett.print() hammett.print('Failed: ', _name) hammett.print() hammett.print(result.stack_trace) hammett.print() if result.stdout: hammett.print(YELLOW) hammett.print('--- stdout ---') hammett.print(result.stdout) if result.stderr: hammett.print(RED) hammett.print('--- stderr ---') hammett.print(result.stderr) hammett.print(RESET_COLOR) if result.feedback_for_exception: hammett.print(result.feedback_for_exception)