Skip to content

Commit a9afbd8

Browse files
authored
Merge pull request #354 from WillGibson/improve-failed-forced-fail-error
Improve error message when forced fail test fails
2 parents ddbaaf1 + eeb9a65 commit a9afbd8

File tree

2 files changed

+83
-5
lines changed

2 files changed

+83
-5
lines changed

mutmut/__main__.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -544,14 +544,15 @@ def print_stats(source_file_mutation_data_by_path, force_output=False):
544544
print_status(f'{(s.total - s.not_checked)}/{s.total} 🎉 {s.killed} 🫥 {s.no_tests}{s.timeout} 🤔 {s.suspicious} 🙁 {s.survived} 🔇 {s.skipped}', force_output=force_output)
545545

546546

547-
def run_forced_fail(runner):
547+
def run_forced_fail_test(runner):
548548
os.environ['MUTANT_UNDER_TEST'] = 'fail'
549+
print("'Running forced fail test'")
549550
with CatchOutput(spinner_title='Running forced fail test') as catcher:
550551
try:
551552
if runner.run_forced_fail() == 0:
552553
catcher.dump_output()
553-
print("FAILED")
554-
os._exit(1)
554+
print("FAILED: Unable to force test failures")
555+
raise SystemExit(1)
555556
except MutmutProgrammaticFailException:
556557
pass
557558
os.environ['MUTANT_UNDER_TEST'] = ''
@@ -918,7 +919,7 @@ def _run(mutant_names: Union[tuple, list], max_children: Union[None, int]):
918919
print(' done')
919920

920921
# this can't be the first thing, because it can fail deep inside pytest/django setup and then everything is destroyed
921-
run_forced_fail(runner)
922+
run_forced_fail_test(runner)
922923

923924
runner.prepare_main_test_run()
924925

tests/test_mutation.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
import os
2+
from unittest.mock import Mock, patch
13
import pytest
24
from libcst import parse_statement
35

6+
import mutmut
47
from mutmut.__main__ import (
58
CLASS_NAME_SEPARATOR,
69
get_diff_for_mutant,
710
orig_function_and_class_names_from_key,
11+
run_forced_fail_test,
12+
Config,
13+
MutmutProgrammaticFailException,
14+
CatchOutput,
815
)
916
from mutmut.trampoline_templates import trampoline_impl, yield_from_trampoline_impl, mangle_function_name
1017
from mutmut.file_mutation import create_mutations, mutate_file_contents, is_generator
@@ -448,6 +455,76 @@ def bar():
448455
assert not is_generator(parse_statement(source)) # type: ignore
449456

450457

458+
# Negate the effects of CatchOutput because it does not play nicely with capfd in GitHub Actions
459+
@patch.object(CatchOutput, 'dump_output')
460+
@patch.object(CatchOutput, 'stop')
461+
@patch.object(CatchOutput, 'start')
462+
def test_run_forced_fail_test_with_failing_test(_start, _stop, _dump_output, capfd):
463+
mutmut.config = _default_mutmut_config()
464+
runner = _mocked_runner_run_forced_failed(return_value=1)
465+
466+
run_forced_fail_test(runner)
467+
468+
out, err = capfd.readouterr()
469+
470+
print()
471+
print(f"out: {out}")
472+
print(f"err: {err}")
473+
assert 'Running forced fail test' in out
474+
assert 'done' in out
475+
assert os.environ['MUTANT_UNDER_TEST'] is ''
476+
477+
478+
# Negate the effects of CatchOutput because it does not play nicely with capfd in GitHub Actions
479+
@patch.object(CatchOutput, 'dump_output')
480+
@patch.object(CatchOutput, 'stop')
481+
@patch.object(CatchOutput, 'start')
482+
def test_run_forced_fail_test_with_mutmut_programmatic_fail_exception(_start, _stop, _dump_output, capfd):
483+
mutmut.config = _default_mutmut_config()
484+
runner = _mocked_runner_run_forced_failed(side_effect=MutmutProgrammaticFailException())
485+
486+
run_forced_fail_test(runner)
487+
488+
out, err = capfd.readouterr()
489+
assert 'Running forced fail test' in out
490+
assert 'done' in out
491+
assert os.environ['MUTANT_UNDER_TEST'] is ''
492+
493+
494+
# Negate the effects of CatchOutput because it does not play nicely with capfd in GitHub Actions
495+
@patch.object(CatchOutput, 'dump_output')
496+
@patch.object(CatchOutput, 'stop')
497+
@patch.object(CatchOutput, 'start')
498+
def test_run_forced_fail_test_with_all_tests_passing(_start, _stop, _dump_output, capfd):
499+
mutmut.config = _default_mutmut_config()
500+
runner = _mocked_runner_run_forced_failed(return_value=0)
501+
502+
with pytest.raises(SystemExit) as error:
503+
run_forced_fail_test(runner)
504+
505+
assert error.value.code is 1
506+
out, err = capfd.readouterr()
507+
assert 'Running forced fail test' in out
508+
assert 'FAILED: Unable to force test failures' in out
509+
510+
511+
def _default_mutmut_config():
512+
return Config(
513+
do_not_mutate=[],
514+
also_copy=[],
515+
max_stack_depth=-1,
516+
debug=False,
517+
paths_to_mutate=[]
518+
)
519+
520+
def _mocked_runner_run_forced_failed(return_value=None, side_effect=None):
521+
runner = Mock()
522+
runner.run_forced_fail = Mock(
523+
return_value=return_value,
524+
side_effect=side_effect
525+
)
526+
return runner
527+
451528
def test_do_not_mutate_top_level_decorators():
452529
# Modifying top-level decorators could influence all mutations
453530
# because they are executed at import time
@@ -587,4 +664,4 @@ def add(self, *args, **kwargs):
587664
add.__signature__ = _mutmut_signature(xǁAdderǁadd__mutmut_orig)
588665
xǁAdderǁadd__mutmut_orig.__name__ = 'xǁAdderǁadd'
589666
590-
print(Adder(1).add(2))"""
667+
print(Adder(1).add(2))"""

0 commit comments

Comments
 (0)