Coverage for tests/test_sleazy.py: 100%
210 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-12 17:08 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-12 17:08 +0200
1"""
2Fixme: these tests were automatically generated and should still be manually confirmed!
3"""
5import typing as t
7import pytest
9from src.sleazy import (
10 TypedDict,
11 parse,
12 parse_count_spec,
13 stringify,
14)
17def test_parse_count_spec():
18 # Exact numbers
19 assert parse_count_spec("0") == 0
20 assert parse_count_spec("1") == 1
21 assert parse_count_spec("") == 1
22 assert parse_count_spec("5") == 5
24 # Direct argparse-style symbols
25 assert parse_count_spec("+") == "+"
26 assert parse_count_spec("*") == "*"
27 assert parse_count_spec("?") == "?"
29 # Comparison operators are not supported anymore → default to "?"
30 for spec in ("> 0", "<=5", ">= 1", "==3", "<1", ">=0", "invalid", "foo"):
31 with pytest.raises(SyntaxError):
32 parse_count_spec(spec)
35def test_basic_type_parsing():
36 class BasicTypes(t.TypedDict):
37 string_val: str
38 int_val: int
39 float_val: float
40 bool_val: bool
42 args = [
43 "--string-val",
44 "test",
45 "--int-val",
46 "42",
47 "--float-val",
48 "3.14",
49 "--bool-val",
50 ]
51 result = parse(BasicTypes, args)
53 assert result["string_val"] == "test"
54 assert result["int_val"] == 42
55 assert result["float_val"] == 3.14
56 assert result["bool_val"] is True
59def test_positional_args():
60 class PositionalArgs(t.TypedDict):
61 pos1: t.Annotated[str, "?"]
62 pos2: t.Annotated[int, "?"]
63 opt1: str
65 args = ["value1", "42", "--opt1", "option"]
66 result = parse(PositionalArgs, args)
68 assert result["pos1"] == "value1"
69 assert result["pos2"] == 42
70 assert result["opt1"] == "option"
73def test_literal_types():
74 class LiteralTypes(t.TypedDict):
75 mode: t.Literal["auto", "manual", "hybrid"]
76 level: t.Literal[1, 2, 3]
78 # Test valid literals
79 args = ["--mode", "auto", "--level", "2"]
80 result = parse(LiteralTypes, args)
82 assert result["mode"] == "auto"
83 assert result["level"] == 2
85 # Test invalid literals - should raise error
86 with pytest.raises(SystemExit):
87 parse(LiteralTypes, ["--mode", "invalid", "--level", "2"])
89 with pytest.raises(SystemExit):
90 parse(LiteralTypes, ["--mode", "auto", "--level", "5"])
93def test_positional_literal():
94 class PosLiteral(t.TypedDict):
95 mode: t.Annotated[t.Literal["auto", "manual"], "1"]
97 args = ["auto"]
98 result = parse(PosLiteral, args)
99 assert result["mode"] == "auto"
101 with pytest.raises(SystemExit):
102 parse(PosLiteral, ["invalid"])
105def test_positional_count_zero_or_more():
106 class CountTest(t.TypedDict):
107 files: t.Annotated[list[str], "*"]
109 # Test with multiple values
110 args = ["file1.txt", "file2.txt", "file3.txt"]
111 result = parse(CountTest, args)
112 assert result["files"] == ["file1.txt", "file2.txt", "file3.txt"]
114 # Test with no values
115 args = []
116 result = parse(CountTest, args)
117 assert result["files"] == []
120def test_positional_count_one_or_more():
121 class CountTest(TypedDict):
122 files: t.Annotated[list[str], "+"]
124 # Test with multiple values
125 args = ["file1.txt", "file2.txt"]
126 result = parse(CountTest, args)
127 assert result["files"] == ["file1.txt", "file2.txt"]
129 # Test with no values - should fail
130 with pytest.raises(SystemExit):
131 parse(CountTest, [])
134def test_positional_count_greater_than():
135 class CountTest(t.TypedDict):
136 files: t.Annotated[list[str], "+"]
138 # Test with values
139 args = ["file1.txt", "file2.txt"]
140 result = parse(CountTest, args)
141 assert result["files"] == ["file1.txt", "file2.txt"]
143 # Test with no values - should fail
144 with pytest.raises(SystemExit):
145 parse(CountTest, [])
148def test_positional_count_at_most_one():
149 class CountTest(t.TypedDict):
150 file: t.Annotated[str, "?"]
152 # Test with one value
153 args = ["file1.txt"]
154 result = parse(CountTest, args)
155 assert result["file"] == "file1.txt"
157 # Test with no values
158 args = []
159 result = parse(CountTest, args)
160 assert result["file"] is None
162 # Test with multiple values - should fail
163 with pytest.raises(SystemExit):
164 parse(CountTest, ["file1.txt", "file2.txt"])
167def test_positional_count_less_than():
168 class CountTest(t.TypedDict):
169 file: t.Annotated[str, "?"]
171 # Test with one value
172 args = ["file1.txt"]
173 result = parse(CountTest, args)
174 assert result["file"] == "file1.txt"
176 # Test with no values
177 args = []
178 result = parse(CountTest, args)
179 assert result["file"] is None
181 # Test with multiple values - should fail
182 with pytest.raises(SystemExit):
183 parse(CountTest, ["file1.txt", "file2.txt"])
186def test_positional_count_exactly():
187 class CountTest(t.TypedDict):
188 files: t.Annotated[list[str], "3"]
190 # Test with exact number of values
191 args = ["file1.txt", "file2.txt", "file3.txt"]
192 result = parse(CountTest, args)
193 assert result["files"] == ["file1.txt", "file2.txt", "file3.txt"]
195 # Test with too few values - should fail
196 with pytest.raises(SystemExit):
197 parse(CountTest, ["file1.txt", "file2.txt"])
199 # Test with too many values - should fail
200 with pytest.raises(SystemExit):
201 parse(CountTest, ["file1.txt", "file2.txt", "file3.txt", "file4.txt"])
204def test_positional_count_exactly_one():
205 class CountTest(t.TypedDict):
206 command: t.Annotated[str, 1]
208 # Test with single value
209 args = ["build"]
210 result = parse(CountTest, args)
211 assert result["command"] == "build" # Should be a string, not a list
212 assert not isinstance(result["command"], list)
214 # Test with multiple values - should fail
215 with pytest.raises(SystemExit):
216 parse(CountTest, ["build", "extra"])
219def test_multiple_positional_args_with_fixed_counts():
220 class FixedCounts(t.TypedDict):
221 command: t.Annotated[str, "1"]
222 subcommand: t.Annotated[str, 1]
223 target: t.Annotated[str, "1"]
224 option: t.Annotated[str, "?"]
226 # Test with all arguments
227 args = ["build", "web", "app.py", "debug"]
228 result = parse(FixedCounts, args)
229 assert result["command"] == "build"
230 assert result["subcommand"] == "web"
231 assert result["target"] == "app.py"
232 assert result["option"] == "debug"
234 # Test with minimum required
235 args = ["build", "web", "app.py"]
236 result = parse(FixedCounts, args)
237 assert result["command"] == "build"
238 assert result["subcommand"] == "web"
239 assert result["target"] == "app.py"
240 assert result["option"] is None
243def test_positional_with_count_constraints():
244 class PositionalWithConstraints(t.TypedDict):
245 command: t.Annotated[str, "1"]
246 files: t.Annotated[list[str], "2"]
248 # Test with exact file count
249 args = ["compress", "input.txt", "output.gz"]
250 result = parse(PositionalWithConstraints, args)
251 assert result["command"] == "compress"
252 assert result["files"] == ["input.txt", "output.gz"]
254 # Test with wrong file count - should fail
255 with pytest.raises(SystemExit):
256 print(parse(PositionalWithConstraints, ["compress", "input.txt"]))
259def test_exact_numeric_count():
260 class CountTest(t.TypedDict):
261 files: t.Annotated[list[str], "2"]
263 # Test with exact number
264 args = ["file1.txt", "file2.txt"]
265 result = parse(CountTest, args)
266 assert result["files"] == ["file1.txt", "file2.txt"]
268 # Test with wrong number - should fail
269 with pytest.raises(SystemExit):
270 parse(CountTest, ["file1.txt"])
273def test_larger_exact_count():
274 class CountTest(t.TypedDict):
275 files: t.Annotated[list[str], "5"]
277 # Test with exact number
278 args = ["file1.txt", "file2.txt", "file3.txt", "file4.txt", "file5.txt"]
279 result = parse(CountTest, args)
280 assert result["files"] == [
281 "file1.txt",
282 "file2.txt",
283 "file3.txt",
284 "file4.txt",
285 "file5.txt",
286 ]
289def test_typeddict_to_cli_args_basic():
290 class TestDict(t.TypedDict):
291 name: str
292 count: int
293 verbose: bool
295 # Create a dictionary that would be an instance of TestDict
296 data: TestDict = {"name": "test", "count": 42, "verbose": True}
298 args = stringify(data, TestDict)
299 # The order might vary, so we'll check for inclusion
300 assert "--name" in args
301 assert "test" in args
302 assert "--count" in args
303 assert "42" in args
304 assert "--verbose" in args
307def test_typeddict_to_cli_args_with_positionals():
308 class TestDict(t.TypedDict):
309 pos1: t.Annotated[str, "1"]
310 pos_multi: t.Annotated[list[str], "+"]
311 flag: bool
312 option: str
314 # Create a dictionary that would be an instance of TestDict
315 data: TestDict = {
316 "pos1": "value1",
317 "pos_multi": ["a", "b", "c"],
318 "flag": True,
319 "option": "opt_val",
320 }
322 args = stringify(data, TestDict)
324 # The positionals should come first in order
325 assert args[0] == "value1"
326 assert args[1:4] == ["a", "b", "c"]
328 # Check for inclusion of optional arguments
329 assert "--flag" in args
330 assert "--option" in args
331 assert "opt_val" in args
334def test_typeddict_to_cli_args_with_literal():
335 class TestDict(t.TypedDict):
336 mode: t.Literal["fast", "slow"]
337 level: t.Annotated[t.Literal[1, 2, 3], "1"]
339 data = {"mode": "fast", "level": 2}
341 args = stringify(data, TestDict)
343 assert args[0] == "2" # Positional comes first
344 assert "--mode" in args
345 assert "fast" in args
348## hooman:
351def test_list_repeat():
352 class MyConfigDict(t.TypedDict):
353 repeat_me: list[str]
355 a = parse(MyConfigDict, ["--repeat-me", "once"])
356 b = parse(MyConfigDict, ["--repeat-me", "once", "--repeat-me", "twice"])
357 c = parse(MyConfigDict, [])
359 assert a["repeat_me"] == ["once"]
360 assert b["repeat_me"] == ["once", "twice"]
362 assert stringify(a) == ["--repeat-me", "once"]
363 assert stringify(a, MyConfigDict) == ["--repeat-me", "once"]
364 assert stringify(b, MyConfigDict) == ["--repeat-me", "once", "--repeat-me", "twice"]
365 assert stringify(c, MyConfigDict) == []