1import argparse 2import os 3 4import pytest 5 6import flynt 7from flynt.cli import run_flynt_cli 8from flynt.cli_messages import farewell_message 9 10 11class ArgumentParser(): 12 """ 13 Mock class for argparse.ArgumentParser 14 15 Parameters: 16 shadow_parser: a real argparse.ArgumentParser 17 parse_args_return_value: the Namespace that should be returned from parse_args. 18 """ 19 20 def __init__( 21 self, 22 shadow_parser: argparse.ArgumentParser, 23 parse_args_return_value: argparse.Namespace, 24 *args, 25 **kwargs, 26 ): 27 self.parse_args_return_value = parse_args_return_value 28 self.shadow_parser = shadow_parser 29 30 def add_mutually_exclusive_group(self): 31 return MutuallyExclusiveGroup(self) 32 33 def add_argument(self, *args, **kwargs): 34 arg = self.shadow_parser.add_argument(*args, **kwargs) 35 if arg.dest not in self.parse_args_return_value: 36 setattr(self.parse_args_return_value, arg.dest, arg.default) 37 return arg 38 39 def print_usage(self): 40 return self.shadow_parser.print_usage() 41 42 def parse_args(self): 43 return self.parse_args_return_value 44 45 46class MutuallyExclusiveGroup(object): 47 """ 48 Mock class for argparse.MutuallyExclusiveGroup 49 """ 50 51 def __init__(self, parent): 52 self.parent = parent 53 54 def add_argument(self, *args, **kwargs): 55 return self.parent.add_argument(*args, **kwargs) 56 57 58def run_cli_test(monkeypatch, **kwargs): 59 """ 60 Runs the CLI, setting arguments according to **kwargs. 61 For example, running with version=True is like passing the --version argument to the CLI. 62 """ 63 shadow_parser = argparse.ArgumentParser() 64 parse_args_return_value = argparse.Namespace(**kwargs) 65 66 def argument_parser_mock(*args, **kwargs): 67 return ArgumentParser(shadow_parser, parse_args_return_value, *args, **kwargs) 68 69 monkeypatch.setattr(argparse, "ArgumentParser", argument_parser_mock) 70 return run_flynt_cli() 71 72 73def test_cli_no_args(monkeypatch, capsys): 74 """ 75 With no arguments, it should require src to be set 76 """ 77 return_code = run_cli_test(monkeypatch) 78 assert return_code == 1 79 80 out, err = capsys.readouterr() 81 assert "the following arguments are required: src" in out 82 83 84def test_cli_version(monkeypatch, capsys): 85 """ 86 With --version set, it should only print the version 87 """ 88 return_code = run_cli_test(monkeypatch, version=True) 89 assert return_code == 0 90 91 out, err = capsys.readouterr() 92 assert out == f"{flynt.__version__}\n" 93 assert err == "" 94 95 96# Code snippets for testing the -s/--string argument 97cli_string_snippets = pytest.mark.parametrize( 98 "code_in, code_out", 99 [ 100 ("'{}'.format(x) + '{}'.format(y)", "f'{x}' + f'{y}'"), 101 ( 102 "['{}={}'.format(key, value) for key, value in x.items()]", 103 "[f'{key}={value}' for key, value in x.items()]", 104 ), 105 ( 106 '["{}={}".format(key, value) for key, value in x.items()]', 107 '[f"{key}={value}" for key, value in x.items()]', 108 ), 109 ( 110 "This ! isn't <> valid .. Python $ code", 111 "This ! isn't <> valid .. Python $ code", 112 ), 113 ], 114) 115 116 117@cli_string_snippets 118def test_cli_string_quoted(monkeypatch, capsys, code_in, code_out): 119 """ 120 Tests an invocation with quotes, like: 121 122 flynt -s "some code snippet" 123 124 Then the src argument will be ["some code snippet"]. 125 """ 126 return_code = run_cli_test(monkeypatch, string=True, src=[code_in]) 127 assert return_code == 0 128 129 out, err = capsys.readouterr() 130 assert out.strip() == code_out 131 assert err == "" 132 133 134@cli_string_snippets 135def test_cli_string_unquoted(monkeypatch, capsys, code_in, code_out): 136 """ 137 Tests an invocation with no quotes, like: 138 139 flynt -s some code snippet 140 141 Then the src argument will be ["some", "code", "snippet"]. 142 """ 143 return_code = run_cli_test(monkeypatch, string=True, src=code_in.split()) 144 assert return_code == 0 145 146 out, err = capsys.readouterr() 147 assert out.strip() == code_out 148 assert err == "" 149 150 151@pytest.mark.parametrize( 152 "sample_file", 153 ["all_named.py", "first_string.py", "percent_dict.py", "multiline_limit.py"], 154) 155def test_cli_dry_run(monkeypatch, capsys, sample_file): 156 """ 157 Tests the --dry-run option with a few files, all changed lines should be shown in the diff 158 """ 159 # Get input/output paths and read them 160 folder = os.path.dirname(__file__) 161 source_path = os.path.join(folder, "samples_in", sample_file) 162 expected_path = os.path.join(folder, "expected_out", sample_file) 163 with open(source_path) as file: 164 source_lines = file.readlines() 165 with open(expected_path) as file: 166 converted_lines = file.readlines() 167 168 # Run the CLI 169 return_code = run_cli_test(monkeypatch, dry_run=True, src=[source_path]) 170 assert return_code == 0 171 172 # Check that the output includes all changed lines, and the farewell message 173 out, err = capsys.readouterr() 174 175 for line in source_lines: 176 if line not in converted_lines: 177 assert f"-{line}" in out, "Original source line missing from output" 178 for line in converted_lines: 179 if line not in source_lines: 180 assert f"+{line}" in out, "Converted source line missing from output" 181 182 assert out.strip().endswith(farewell_message.strip()) 183 assert err == "" 184