11# Copyright 2025 Open Source Robotics Foundation, Inc.
22# Licensed under the Apache License, Version 2.0
33
4- import pytest
4+ from packaging . specifiers import InvalidSpecifier
55from pallet_patcher .solver import _parse_rust_specifier , solve_dependency
6+ import pytest
67
78
89# Test code generated with LLM help
9- @pytest .mark .parametrize ("input , expected_matches, expected_non_matches" , [
10+ @pytest .mark .parametrize ('r_input , expected_matches, expected_non_matches' , [
1011 # 1. Explicit Equals (=)
11- (" =1.2.3" , [" 1.2.3" ], [" 1.2.4" , " 1.2.2" ]),
12+ (' =1.2.3' , [' 1.2.3' ], [' 1.2.4' , ' 1.2.2' ]),
1213
1314 # 2. Tilde Requirements (~)
1415 # ~1.2.3 := >=1.2.3, <1.3.0
15- (" ~1.2.3" , [" 1.2.3" , " 1.2.9" ], [" 1.3.0" , " 1.1.0" ]),
16+ (' ~1.2.3' , [' 1.2.3' , ' 1.2.9' ], [' 1.3.0' , ' 1.1.0' ]),
1617 # ~1.2 := >=1.2, <1.3.0
17- (" ~1.2" , [" 1.2.0" , " 1.2.5" ], [" 1.3.0" , " 1.1.0" ]),
18+ (' ~1.2' , [' 1.2.0' , ' 1.2.5' ], [' 1.3.0' , ' 1.1.0' ]),
1819 # ~1 := >=1, <2.0.0
19- ("~1" , [" 1.0.0" , " 1.5.0" , " 1.9.9" ], [" 2.0.0" , " 0.9.0" ]),
20+ ('~1' , [' 1.0.0' , ' 1.5.0' , ' 1.9.9' ], [' 2.0.0' , ' 0.9.0' ]),
2021
2122 # 3. Caret Requirements (^) - Major > 0
2223 # ^1.2.3 := >=1.2.3, <2.0.0
23- (" ^1.2.3" , [" 1.2.3" , " 1.9.9" ], [" 2.0.0" , " 1.2.2" ]),
24+ (' ^1.2.3' , [' 1.2.3' , ' 1.9.9' ], [' 2.0.0' , ' 1.2.2' ]),
2425 # Bare 1.2.3 (same as ^)
25- (" 1.2.3" , [" 1.2.3" , " 1.5.0" , " 1.9.9" ], [" 2.0.0" , " 1.2.2" ]),
26+ (' 1.2.3' , [' 1.2.3' , ' 1.5.0' , ' 1.9.9' ], [' 2.0.0' , ' 1.2.2' ]),
2627
2728 # 4. Caret Requirements (^) - Major == 0 (The tricky ones)
2829
2930 # Case B.1: ^0 := >=0.0.0, <1.0.0
30- ("^0" , [" 0.0.0" , " 0.1.0" , " 0.9.9" ], [" 1.0.0" ]),
31- ("0" , [" 0.1.5" ], [" 1.0.0" ]),
31+ ('^0' , [' 0.0.0' , ' 0.1.0' , ' 0.9.9' ], [' 1.0.0' ]),
32+ ('0' , [' 0.1.5' ], [' 1.0.0' ]),
3233
3334 # Case B.2: ^0.2.3 (Minor > 0) := >=0.2.3, <0.3.0
34- (" ^0.2.3" , [" 0.2.3" , " 0.2.9" ], [" 0.3.0" , " 0.2.2" ]),
35- (" 0.2.3" , [" 0.2.3" , " 0.2.9" ], [" 0.3.0" ]),
35+ (' ^0.2.3' , [' 0.2.3' , ' 0.2.9' ], [' 0.3.0' , ' 0.2.2' ]),
36+ (' 0.2.3' , [' 0.2.3' , ' 0.2.9' ], [' 0.3.0' ]),
3637
3738 # Case B.3: ^0.0.3 (Minor == 0, Patch specified) := >=0.0.3, <0.0.4
38- (" ^0.0.3" , [" 0.0.3" ], [" 0.0.4" , " 0.0.2" , " 0.1.0" ]),
39- (" 0.0.3" , [" 0.0.3" ], [" 0.0.4" ]),
39+ (' ^0.0.3' , [' 0.0.3' ], [' 0.0.4' , ' 0.0.2' , ' 0.1.0' ]),
40+ (' 0.0.3' , [' 0.0.3' ], [' 0.0.4' ]),
4041
4142 # Case B.4: ^0.0 (Minor == 0, Patch missing) := >=0.0, <0.1.0
42- (" ^0.0" , [" 0.0.0" , " 0.0.5" ], [" 0.1.0" ]),
43- (" 0.0" , [" 0.0.1" ], [" 0.1.0" ]),
43+ (' ^0.0' , [' 0.0.0' , ' 0.0.5' ], [' 0.1.0' ]),
44+ (' 0.0' , [' 0.0.1' ], [' 0.1.0' ]),
4445])
45- def test_rust_specifier_logic (input , expected_matches , expected_non_matches ):
46+ def test_rust_specifier_logic (r_input , expected_matches , expected_non_matches ):
4647 """
47- Tests that the converted Python SpecifierSet correctly matches
48- and excludes the versions dictated by Rust SemVer logic.
48+ Tests that the converted Python SpecifierSet correctly matches.
49+
50+ It should also exclude the versions dictated by Rust SemVer logic.
4951 """
50- spec_set = _parse_rust_specifier (input )
52+ spec_set = _parse_rust_specifier (r_input )
5153
5254 for version in expected_matches :
5355 assert version in spec_set , \
54- f"""Rust spec '{ input } ' should MATCH { version } ,
56+ f"""Rust spec '{ r_input } ' should MATCH { version } ,
5557 but Python spec { spec_set } did not."""
5658
5759 for version in expected_non_matches :
5860 assert version not in spec_set , \
59- f"""Rust spec '{ input } ' should NOT match { version } ,
61+ f"""Rust spec '{ r_input } ' should NOT match { version } ,
6062 but Python spec { spec_set } did."""
6163
6264
63- @pytest .mark .parametrize (" input_str, expected_str_repr" , [
64- (" >=1.5" , " >=1.5" ), # Passthrough standard python
65- (" <2.0" , " <2.0" ), # Passthrough standard python
66- (" ==1.2.3" , " ==1.2.3" ), # Passthrough explicit equality
67- ("" , "" ), # Empty string handling
65+ @pytest .mark .parametrize (' input_str, expected_str_repr' , [
66+ (' >=1.5' , ' >=1.5' ), # Passthrough standard python
67+ (' <2.0' , ' <2.0' ), # Passthrough standard python
68+ (' ==1.2.3' , ' ==1.2.3' ), # Passthrough explicit equality
69+ ('' , '' ), # Empty string handling
6870])
6971def test_standard_python_fallback (input_str , expected_str_repr ):
70- """
71- Tests that standard Python specifiers or other strings
72- are passed through to SpecifierSet mostly unchanged.
73- """
72+ """Tests that std Python specifiers or other strings are passed through."""
7473 spec = _parse_rust_specifier (input_str )
7574 # Note: SpecifierSet normalization might change string spacing,
7675 # but the logic checks if it parses without crashing.
7776 assert str (spec ) == expected_str_repr or not input_str
7877
7978
8079def test_invalid_version_strings ():
81- """
82- Ensure the function handles malformed strings gracefully
83- (falling back to SpecifierSet wich might raise or handle it).
84- """
80+ """Ensure the function handles malformed strings gracefully."""
8581 # This falls through to "Bare" logic, fails try/except, hits fallback
8682 # SpecifierSet("invalid") is technically valid in packaging but matches
8783 # nothing or raises InvalidSpecifier depending on version.
8884 # Here we just want to ensure your code doesn't crash internally.
89- try :
90- _parse_rust_specifier ("invalid_string_with_char" )
91- except Exception as e :
92- # It is acceptable if SpecifierSet raises, but your parsing logic
93- # shouldn't
94- assert "Invalid specifier" in str (e ) or isinstance (e , ValueError )
85+ # This acts as both the try/except and the assertion
86+ with pytest .raises ((ValueError , InvalidSpecifier )):
87+ _parse_rust_specifier ('invalid_string_with_char' )
9588
9689
97- @pytest .mark .parametrize (" spec, available, expected" , [
90+ @pytest .mark .parametrize (' spec, available, expected' , [
9891 # 1. Basic Caret Priority
9992 # ^1.2.0 allows >=1.2.0, <2.0.0.
10093 # It should pick 1.9.9 (highest), ignore 2.0.0 (high) and 1.1.0 (low).
101- (" ^1.2.0" , [" 1.1.0" , " 1.2.0" , " 1.2.5" , " 1.9.9" , " 2.0.0" ], " 1.9.9" ),
94+ (' ^1.2.0' , [' 1.1.0' , ' 1.2.0' , ' 1.2.5' , ' 1.9.9' , ' 2.0.0' ], ' 1.9.9' ),
10295
10396 # 2. Basic Tilde Priority
10497 # ~1.2.0 allows >=1.2.0, <1.3.0.
10598 # Should pick 1.2.9, ignore 1.3.0.
106- (" ~1.2.0" , [" 1.2.0" , " 1.2.5" , " 1.2.9" , " 1.3.0" , " 1.4.0" ], " 1.2.9" ),
99+ (' ~1.2.0' , [' 1.2.0' , ' 1.2.5' , ' 1.2.9' , ' 1.3.0' , ' 1.4.0' ], ' 1.2.9' ),
107100
108- # 3. Rust " 0.x.y" Semantics (Major 0 breaking changes)
101+ # 3. Rust ' 0.x.y' Semantics (Major 0 breaking changes)
109102 # ^0.2.0 allows >=0.2.0, <0.3.0.
110103 # Should ignore 0.3.0 even though it's higher.
111- (" ^0.2.0" , [" 0.1.9" , " 0.2.0" , " 0.2.5" , " 0.3.0" ], " 0.2.5" ),
104+ (' ^0.2.0' , [' 0.1.9' , ' 0.2.0' , ' 0.2.5' , ' 0.3.0' ], ' 0.2.5' ),
112105
113- # 4. Rust " 0.0.x" Semantics (Patch 0 breaking changes)
106+ # 4. Rust ' 0.0.x' Semantics (Patch 0 breaking changes)
114107 # ^0.0.3 allows >=0.0.3, <0.0.4.
115108 # Should strictly match 0.0.3.
116- (" ^0.0.3" , [" 0.0.2" , " 0.0.3" , " 0.0.4" , " 0.1.0" ], " 0.0.3" ),
109+ (' ^0.0.3' , [' 0.0.2' , ' 0.0.3' , ' 0.0.4' , ' 0.1.0' ], ' 0.0.3' ),
117110
118111 # 5. Exact Match
119- (" =1.5.0" , [" 1.4.0" , " 1.5.0" , " 1.6.0" ], " 1.5.0" ),
112+ (' =1.5.0' , [' 1.4.0' , ' 1.5.0' , ' 1.6.0' ], ' 1.5.0' ),
120113
121114 # 6. No Match Found
122115 # Range is >=1.0.0, <2.0.0. Only have 0.9 and 2.1.
123- (" ^1.0.0" , [" 0.9.0" , " 2.1.0" , " 3.0.0" ], None ),
116+ (' ^1.0.0' , [' 0.9.0' , ' 2.1.0' , ' 3.0.0' ], None ),
124117
125118 # 7. Empty List
126- (" ^1.0.0" , [], None ),
119+ (' ^1.0.0' , [], None ),
127120
128- # 8. Handling " Bare" versions (Implies ^)
129- (" 1.2.0" , [" 1.2.0" , " 1.5.0" , " 2.0.0" ], " 1.5.0" ),
121+ # 8. Handling ' Bare' versions (Implies ^)
122+ (' 1.2.0' , [' 1.2.0' , ' 1.5.0' , ' 2.0.0' ], ' 1.5.0' ),
130123])
131124def test_solve_dependency_logic (spec , available , expected ):
132- """
133- Verifies that the solver picks the highest version that satisfies
134- the Rust-style specifier.
135- """
125+ """Verify that solver picks the highest ver that satisfies the spec."""
136126 result = solve_dependency (spec , available )
137127 assert result == expected , \
138128 f"""For spec '{ spec } ' and versions { available } , expected '{ expected } '
@@ -142,30 +132,28 @@ def test_solve_dependency_logic(spec, available, expected):
142132def test_solve_dependency_sorting_correctness ():
143133 """
144134 Specifically tests that 1.10.0 is considered greater than 1.2.0.
135+
145136 String sorting would say "1.2" > "1.10", but numeric sorting says 10 > 2.
146137 """
147- spec = " ^1.0.0"
138+ spec = ' ^1.0.0'
148139 # If sorting was string-based, it might encounter 1.2.0 first (descending).
149140 # Correct numeric sort: 1.10.0 is highest.
150- available = [" 1.1.0" , " 1.2.0" , " 1.9.0" , " 1.10.0" ]
141+ available = [' 1.1.0' , ' 1.2.0' , ' 1.9.0' , ' 1.10.0' ]
151142
152143 result = solve_dependency (spec , available )
153- assert result == " 1.10.0"
144+ assert result == ' 1.10.0'
154145
155146
156147def test_solve_dependency_invalid_input_handling ():
157- """
158- Test how the function handles non-integer version strings
159- given the lambda sorting logic provided.
160- """
148+ """Test how the function handles non-integer version strings."""
161149 # NOTE: The provided function uses `int(part)` which will crash on "beta".
162150 # This test documents that behavior. If you want to support prereleases,
163151 # the sort key in the function needs to change.
164- spec = " ^1.0.0"
165- available = [" 1.0.0" , " 1.1.0-beta" ]
152+ spec = ' ^1.0.0'
153+ available = [' 1.0.0' , ' 1.1.0-beta' ]
166154
167155 with pytest .raises (ValueError ) as excinfo :
168156 solve_dependency (spec , available )
169157
170158 # Confirm it failed where we expected (in the sort lambda)
171- assert " invalid literal for int()" in str (excinfo .value )
159+ assert ' invalid literal for int()' in str (excinfo .value )
0 commit comments