11import pathlib
22import typing
3+ from unittest import mock
34
45import pytest
56from packaging import markers
67from packaging .requirements import Requirement
78from packaging .version import Version
89
9- from fromager import constraints
10+ from fromager . constraints import Constraints , InvalidConstraintError
1011
1112
1213def test_constraint_is_satisfied_by () -> None :
13- c = constraints . Constraints ()
14+ c = Constraints ()
1415 c .add_constraint ("foo<=1.1" )
1516 assert c .is_satisfied_by ("foo" , Version ("1.1" ))
1617 assert c .is_satisfied_by ("foo" , Version ("1.0" ))
1718 assert c .is_satisfied_by ("bar" , Version ("2.0" ))
1819
1920
2021def test_constraint_canonical_name () -> None :
21- c = constraints . Constraints ()
22+ c = Constraints ()
2223 c .add_constraint ("flash_attn<=1.1" )
2324 assert c .is_satisfied_by ("flash_attn" , Version ("1.1" ))
2425 assert c .is_satisfied_by ("flash-attn" , Version ("1.1" ))
@@ -27,78 +28,72 @@ def test_constraint_canonical_name() -> None:
2728
2829
2930def test_constraint_not_is_satisfied_by () -> None :
30- c = constraints . Constraints ()
31+ c = Constraints ()
3132 c .add_constraint ("foo<=1.1" )
3233 c .add_constraint ("bar>=2.0" )
3334 assert not c .is_satisfied_by ("foo" , Version ("1.2" ))
3435 assert not c .is_satisfied_by ("foo" , Version ("2.0" ))
3536 assert not c .is_satisfied_by ("bar" , Version ("1.0" ))
3637
3738
39+ @mock .patch ("platform.machine" , mock .Mock (return_value = "atari" ))
3840def test_add_constraint_conflict () -> None :
39- c = constraints .Constraints ()
41+ assert markers .default_environment ()["platform_machine" ] == "atari"
42+
43+ c = Constraints ()
4044 c .add_constraint ("foo<=1.1" )
4145 c .add_constraint ("flit_core==2.0rc3" )
4246
43- # Exact duplicate should raise error (same package, same marker)
44- with pytest .raises (KeyError ):
45- c .add_constraint ("foo<=1.1" )
46-
47- # Different version, same marker (no marker) should raise error
48- with pytest .raises (KeyError ):
47+ # Conflicting version, same marker (no marker) should raise error
48+ with pytest .raises (InvalidConstraintError ):
4949 c .add_constraint ("foo>1.1" )
5050
51- # Different version for flit_core should raise error
52- with pytest .raises (KeyError ):
51+ # Conflicting version for flit_core should raise error
52+ with pytest .raises (InvalidConstraintError ):
5353 c .add_constraint ("flit_core>2.0.0" )
5454
5555 # Normalized name conflict should raise error
56- with pytest .raises (KeyError ):
56+ with pytest .raises (InvalidConstraintError ):
5757 c .add_constraint ("flit-core>2.0.0" )
5858
59- # Different, but equivalent markers should raise KeyError
60- with pytest .raises (KeyError ):
61- # arm64 -> macos; aarch64 -> linux
62- for arch in ["x86_64" , "arm64" , "aarch64" ]:
63- c .add_constraint (
64- f"bar==1.0; python_version >= '3.11' and platform_machine == '{ arch } '"
65- )
66- c .add_constraint (
67- f"bar==1.1; platform_machine == '{ arch } ' and python_version >= '3.11'"
68- )
59+ # Constraints for other platforms are ignored
60+ c .add_constraint (
61+ "bar==1.0; python_version >= '3.11' and platform_machine == 'amiga'"
62+ )
63+ assert c .get_constraint ("bar" ) is None
64+
65+ c .add_constraint (
66+ "bar==1.0; python_version >= '3.11' and platform_machine == 'atari'"
67+ )
68+ # Make sure correct constraint is added
69+ constraint = c .get_constraint ("bar" )
70+ assert constraint
71+ assert constraint .name == "bar"
72+ assert constraint .specifier == "==1.0"
73+ assert constraint .marker == markers .Marker (
74+ 'python_version >= "3.11" and platform_machine == "atari"'
75+ )
76+
77+ # Different, but equivalent markers should raise error
78+ with pytest .raises (InvalidConstraintError ):
79+ c .add_constraint (
80+ "bar==1.1; platform_machine == 'atari' and python_version >= '3.11'"
81+ )
6982
7083 # Same package with different markers should NOT raise error
71- c .add_constraint ("baz==1.0; platform_machine != 'ppc64le '" )
72- c .add_constraint ("baz==1.1; platform_machine == 'ppc64le '" )
84+ c .add_constraint ("baz==1.0; platform_machine != 'amiga '" )
85+ c .add_constraint ("baz==1.1; platform_machine == 'amiga '" )
7386
7487 # But same package with same marker should raise error
75- with pytest .raises (KeyError ):
76- c .add_constraint ("foo==1.2; platform_machine != 'ppc64le '" )
88+ with pytest .raises (InvalidConstraintError ):
89+ c .add_constraint ("foo==1.2; platform_machine != 'amiga '" )
7790
7891 # Verify multiple constraints for same package are stored
79- assert len (c ._data ) == 4 # flit_core, foo, bar, and baz
80-
81- # Make sure correct constraint is added
82- env = typing .cast (dict [str , str ], markers .default_environment ())
83- constraint = c .get_constraint ("bar" )
84-
85- if env .get ("platform_machine" ) == "x86_64" and constraint is not None :
86- assert constraint .name == "bar"
87- assert constraint .specifier == "==1.0"
88- assert constraint .marker == markers .Marker (
89- 'python_version >= "3.11" and platform_machine == "x86_64"'
90- )
91-
92- if env .get ("platform_machine" ) == "arm64" and constraint is not None :
93- assert constraint .name == "bar"
94- assert constraint .specifier == "==1.0"
95- assert constraint .marker == markers .Marker (
96- 'python_version >= "3.11" and platform_machine == "arm64"'
97- )
92+ assert len (c ) == 4 # flit_core, foo, bar, and baz
9893
9994
10095def test_allow_prerelease () -> None :
101- c = constraints . Constraints ()
96+ c = Constraints ()
10297 c .add_constraint ("foo>=1.1" )
10398 assert not c .allow_prerelease ("foo" )
10499 c .add_constraint ("bar>=1.1a0" )
@@ -109,15 +104,40 @@ def test_allow_prerelease() -> None:
109104
110105def test_load_non_existant_constraints_file (tmp_path : pathlib .Path ) -> None :
111106 non_existant_file = tmp_path / "non_existant.txt"
112- c = constraints . Constraints ()
107+ c = Constraints ()
113108 with pytest .raises (FileNotFoundError ):
114109 c .load_constraints_file (non_existant_file )
115110
116111
117112def test_load_constraints_file (tmp_path : pathlib .Path ) -> None :
118113 constraint_file = tmp_path / "constraint.txt"
119- constraint_file .write_text ("egg\n torch==3.1.0 # comment\n " )
120- c = constraints . Constraints ()
114+ constraint_file .write_text ("egg==1.0 \n torch==3.1.0 # comment\n " )
115+ c = Constraints ()
121116 c .load_constraints_file (constraint_file )
122117 assert list (c ) == ["egg" , "torch" ] # type: ignore
123118 assert c .get_constraint ("torch" ) == Requirement ("torch==3.1.0" )
119+
120+
121+ def test_invalid_constraints () -> None :
122+ c = Constraints ()
123+ with pytest .raises (InvalidConstraintError , match = r".*no specifier" ):
124+ c .add_constraint ("foo" )
125+ with pytest .raises (InvalidConstraintError , match = r".*has extras" ):
126+ c .add_constraint ("foo[extra]>=1.0" )
127+ with pytest .raises (InvalidConstraintError , match = r".*has an url" ):
128+ c .add_constraint ("foo@https://foo.test" )
129+
130+
131+ def test_unsatisfiable () -> None :
132+ c = Constraints ()
133+ with pytest .raises (InvalidConstraintError ):
134+ c .add_constraint ("foo<1.0,>2.0" )
135+
136+
137+ def test_combine_constraints () -> None :
138+ c = Constraints ()
139+ c .add_constraint ("foo>=1.0" )
140+ c .add_constraint ("foo<2.0" )
141+ assert c .get_constraint ("foo" ) == Requirement ("foo<2.0,>=1.0" )
142+ c .add_constraint ("foo!=1.1.0" )
143+ assert c .get_constraint ("foo" ) == Requirement ("foo<2.0,>=1.0,!=1.1.0" )
0 commit comments