Skip to content

Commit 87473a8

Browse files
committed
feat: allow supplying options on cli tool
1 parent d908ad5 commit 87473a8

File tree

1 file changed

+73
-25
lines changed

1 file changed

+73
-25
lines changed

mutmut/__main__.py

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,10 @@ def should_ignore_for_mutation(self, path):
689689
return False
690690

691691

692-
def config_reader():
692+
def config_reader(cli_overrides=None):
693+
if cli_overrides is None:
694+
cli_overrides = {}
695+
693696
path = Path('pyproject.toml')
694697
if path.exists():
695698
if sys.version_info >= (3, 11):
@@ -705,6 +708,9 @@ def config_reader():
705708
pass
706709
else:
707710
def s(key, default):
711+
# try CLI overrides first
712+
if key in cli_overrides and cli_overrides[key] is not None:
713+
return cli_overrides[key]
708714
try:
709715
result = config[key]
710716
except KeyError:
@@ -716,6 +722,9 @@ def s(key, default):
716722
config_parser.read('setup.cfg')
717723

718724
def s(key, default):
725+
# try CLI overrides first
726+
if key in cli_overrides and cli_overrides[key] is not None:
727+
return cli_overrides[key]
719728
try:
720729
result = config_parser.get('mutmut', key)
721730
except (NoOptionError, NoSectionError):
@@ -733,13 +742,13 @@ def s(key, default):
733742
return s
734743

735744

736-
def ensure_config_loaded():
745+
def ensure_config_loaded(cli_overrides=None):
737746
if mutmut.config is None:
738-
mutmut.config = load_config()
747+
mutmut.config = load_config(cli_overrides)
739748

740749

741-
def load_config():
742-
s = config_reader()
750+
def load_config(cli_overrides=None):
751+
s = config_reader(cli_overrides)
743752

744753
return Config(
745754
do_not_mutate=s('do_not_mutate', []),
@@ -765,8 +774,36 @@ def load_config():
765774

766775
@click.group()
767776
@click.version_option(mutmut.__version__)
768-
def cli():
769-
pass
777+
@click.option('--debug', is_flag=True, help='Enable debug mode')
778+
@click.option('--paths-to-mutate', multiple=True, help='Paths to mutate')
779+
@click.option('--tests-dir', multiple=True, help='Directory with tests')
780+
@click.pass_context
781+
def cli(ctx, debug, paths_to_mutate, tests_dir):
782+
ctx.ensure_object(dict)
783+
784+
cli_overrides = {}
785+
if debug:
786+
cli_overrides['debug'] = True
787+
if paths_to_mutate:
788+
split_paths = []
789+
for path in paths_to_mutate:
790+
if ',' in path:
791+
split_paths.extend(p.strip() for p in path.split(','))
792+
else:
793+
split_paths.append(path)
794+
cli_overrides['paths_to_mutate'] = split_paths
795+
if tests_dir:
796+
# Support comma-separated values
797+
split_tests = []
798+
for test_dir in tests_dir:
799+
if ',' in test_dir:
800+
split_tests.extend(t.strip() for t in test_dir.split(','))
801+
else:
802+
split_tests.append(test_dir)
803+
cli_overrides['tests_dir'] = split_tests
804+
805+
ctx.obj['cli_overrides'] = cli_overrides
806+
770807

771808

772809
def run_stats_collection(runner, tests=None):
@@ -881,9 +918,11 @@ def estimated_worst_case_time(mutant_name):
881918

882919
@cli.command()
883920
@click.argument('mutant_names', required=False, nargs=-1)
884-
def print_time_estimates(mutant_names):
921+
@click.pass_context
922+
def print_time_estimates(ctx, mutant_names):
885923
assert isinstance(mutant_names, (tuple, list)), mutant_names
886-
ensure_config_loaded()
924+
cli_overrides = ctx.obj.get('cli_overrides', {})
925+
ensure_config_loaded(cli_overrides)
887926

888927
runner = PytestRunner()
889928
runner.prepare_main_test_run()
@@ -947,16 +986,18 @@ def inner_timout_checker():
947986
@cli.command()
948987
@click.option('--max-children', type=int)
949988
@click.argument('mutant_names', required=False, nargs=-1)
950-
def run(mutant_names, *, max_children):
989+
@click.pass_context
990+
def run(ctx, mutant_names, *, max_children):
951991
assert isinstance(mutant_names, (tuple, list)), mutant_names
952-
_run(mutant_names, max_children)
992+
cli_overrides = ctx.obj.get('cli_overrides', {})
993+
_run(mutant_names, max_children, cli_overrides)
953994

954995
# separate function, so we can call it directly from the tests
955-
def _run(mutant_names: Union[tuple, list], max_children: Union[None, int]):
996+
def _run(mutant_names: Union[tuple, list], max_children: Union[None, int], cli_overrides=None):
956997
# TODO: run no-ops once in a while to detect if we get false negatives
957998
# TODO: we should be able to get information on which tests killed mutants, which means we can get a list of tests and how many mutants each test kills. Those that kill zero mutants are redundant!
958999
os.environ['MUTANT_UNDER_TEST'] = 'mutant_generation'
959-
ensure_config_loaded()
1000+
ensure_config_loaded(cli_overrides)
9601001

9611002
if max_children is None:
9621003
max_children = os.cpu_count() or 4
@@ -1138,8 +1179,10 @@ def tests_for_mutant_names(mutant_names):
11381179

11391180
@cli.command()
11401181
@click.option('--all', default=False)
1141-
def results(all):
1142-
ensure_config_loaded()
1182+
@click.pass_context
1183+
def results(ctx, all):
1184+
cli_overrides = ctx.obj.get('cli_overrides', {})
1185+
ensure_config_loaded(cli_overrides)
11431186
for path in walk_source_files():
11441187
if not str(path).endswith('.py'):
11451188
continue
@@ -1233,17 +1276,20 @@ def get_diff_for_mutant(mutant_name, source=None, path=None):
12331276

12341277
@cli.command()
12351278
@click.argument('mutant_name')
1236-
def show(mutant_name):
1237-
ensure_config_loaded()
1279+
@click.pass_context
1280+
def show(ctx, mutant_name):
1281+
cli_overrides = ctx.obj.get('cli_overrides', {})
1282+
ensure_config_loaded(cli_overrides)
12381283
print(get_diff_for_mutant(mutant_name))
12391284
return
12401285

12411286

12421287
@cli.command()
12431288
@click.argument('mutant_name')
1244-
def apply(mutant_name):
1245-
# try:
1246-
ensure_config_loaded()
1289+
@click.pass_context
1290+
def apply(ctx, mutant_name):
1291+
cli_overrides = ctx.obj.get('cli_overrides', {})
1292+
ensure_config_loaded(cli_overrides)
12471293
apply_mutant(mutant_name)
12481294
# except FileNotFoundError as e:
12491295
# print(e)
@@ -1275,8 +1321,10 @@ def apply_mutant(mutant_name):
12751321

12761322
@cli.command()
12771323
@click.option("--show-killed", is_flag=True, default=False, help="Display killed mutants.")
1278-
def browse(show_killed):
1279-
ensure_config_loaded()
1324+
@click.pass_context
1325+
def browse(ctx, show_killed):
1326+
cli_overrides = ctx.obj.get('cli_overrides', {})
1327+
ensure_config_loaded(cli_overrides)
12801328

12811329
from textual.app import App
12821330
from textual.containers import Container
@@ -1333,7 +1381,7 @@ def on_mount(self):
13331381
self.populate_files_table()
13341382

13351383
def read_data(self):
1336-
ensure_config_loaded()
1384+
ensure_config_loaded(cli_overrides)
13371385
self.source_file_mutation_data_and_stat_by_path = {}
13381386
self.path_by_name = {}
13391387

@@ -1388,7 +1436,7 @@ def on_data_table_row_highlighted(self, event):
13881436
path = self.path_by_name.get(event.row_key.value)
13891437

13901438
def load_thread():
1391-
ensure_config_loaded()
1439+
ensure_config_loaded(cli_overrides)
13921440
try:
13931441
d = get_diff_for_mutant(event.row_key.value, path=path)
13941442
if event.row_key.value == self.loading_id:
@@ -1427,7 +1475,7 @@ def action_retest_module(self):
14271475
self.retest(self.get_mutant_name_from_selection().rpartition('.')[0] + '.*')
14281476

14291477
def action_apply_mutant(self):
1430-
ensure_config_loaded()
1478+
ensure_config_loaded(cli_overrides)
14311479
# noinspection PyTypeChecker
14321480
mutants_table: DataTable = self.query_one('#mutants')
14331481
if mutants_table.cursor_row is None:

0 commit comments

Comments
 (0)