11import pathlib
2- import typing
2+ from unittest import mock
33
44import pytest
55from packaging import markers
66from packaging .requirements import Requirement
77from packaging .version import Version
88
9- from fromager import constraints
9+ from fromager . constraints import Constraints , InvalidConstraintError
1010
1111
1212def test_constraint_is_satisfied_by () -> None :
13- c = constraints . Constraints ()
13+ c = Constraints ()
1414 c .add_constraint ("foo<=1.1" )
1515 assert c .is_satisfied_by ("foo" , Version ("1.1" ))
1616 assert c .is_satisfied_by ("foo" , Version ("1.0" ))
1717 assert c .is_satisfied_by ("bar" , Version ("2.0" ))
1818
1919
2020def test_constraint_canonical_name () -> None :
21- c = constraints . Constraints ()
21+ c = Constraints ()
2222 c .add_constraint ("flash_attn<=1.1" )
2323 assert c .is_satisfied_by ("flash_attn" , Version ("1.1" ))
2424 assert c .is_satisfied_by ("flash-attn" , Version ("1.1" ))
@@ -27,78 +27,72 @@ def test_constraint_canonical_name() -> None:
2727
2828
2929def test_constraint_not_is_satisfied_by () -> None :
30- c = constraints . Constraints ()
30+ c = Constraints ()
3131 c .add_constraint ("foo<=1.1" )
3232 c .add_constraint ("bar>=2.0" )
3333 assert not c .is_satisfied_by ("foo" , Version ("1.2" ))
3434 assert not c .is_satisfied_by ("foo" , Version ("2.0" ))
3535 assert not c .is_satisfied_by ("bar" , Version ("1.0" ))
3636
3737
38+ @mock .patch ("platform.machine" , mock .Mock (return_value = "atari" ))
3839def test_add_constraint_conflict () -> None :
39- c = constraints .Constraints ()
40+ assert markers .default_environment ()["platform_machine" ] == "atari"
41+
42+ c = Constraints ()
4043 c .add_constraint ("foo<=1.1" )
4144 c .add_constraint ("flit_core==2.0rc3" )
4245
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 ):
46+ # Conflicting version, same marker (no marker) should raise error
47+ with pytest .raises (InvalidConstraintError ):
4948 c .add_constraint ("foo>1.1" )
5049
51- # Different version for flit_core should raise error
52- with pytest .raises (KeyError ):
50+ # Conflicting version for flit_core should raise error
51+ with pytest .raises (InvalidConstraintError ):
5352 c .add_constraint ("flit_core>2.0.0" )
5453
5554 # Normalized name conflict should raise error
56- with pytest .raises (KeyError ):
55+ with pytest .raises (InvalidConstraintError ):
5756 c .add_constraint ("flit-core>2.0.0" )
5857
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- )
58+ # Constraints for other platforms are ignored
59+ c .add_constraint (
60+ "bar==1.0; python_version >= '3.11' and platform_machine == 'amiga'"
61+ )
62+ assert c .get_constraint ("bar" ) is None
63+
64+ c .add_constraint (
65+ "bar==1.0; python_version >= '3.11' and platform_machine == 'atari'"
66+ )
67+ # Make sure correct constraint is added
68+ constraint = c .get_constraint ("bar" )
69+ assert constraint
70+ assert constraint .name == "bar"
71+ assert constraint .specifier == "==1.0"
72+ assert constraint .marker == markers .Marker (
73+ 'python_version >= "3.11" and platform_machine == "atari"'
74+ )
75+
76+ # Different, but equivalent markers should raise error
77+ with pytest .raises (InvalidConstraintError ):
78+ c .add_constraint (
79+ "bar==1.1; platform_machine == 'atari' and python_version >= '3.11'"
80+ )
6981
7082 # 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 '" )
83+ c .add_constraint ("baz==1.0; platform_machine != 'amiga '" )
84+ c .add_constraint ("baz==1.1; platform_machine == 'amiga '" )
7385
7486 # But same package with same marker should raise error
75- with pytest .raises (KeyError ):
76- c .add_constraint ("foo==1.2; platform_machine != 'ppc64le '" )
87+ with pytest .raises (InvalidConstraintError ):
88+ c .add_constraint ("foo==1.2; platform_machine != 'amiga '" )
7789
7890 # 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- )
91+ assert len (c ) == 4 # flit_core, foo, bar, and baz
9892
9993
10094def test_allow_prerelease () -> None :
101- c = constraints . Constraints ()
95+ c = Constraints ()
10296 c .add_constraint ("foo>=1.1" )
10397 assert not c .allow_prerelease ("foo" )
10498 c .add_constraint ("bar>=1.1a0" )
@@ -109,15 +103,40 @@ def test_allow_prerelease() -> None:
109103
110104def test_load_non_existant_constraints_file (tmp_path : pathlib .Path ) -> None :
111105 non_existant_file = tmp_path / "non_existant.txt"
112- c = constraints . Constraints ()
106+ c = Constraints ()
113107 with pytest .raises (FileNotFoundError ):
114108 c .load_constraints_file (non_existant_file )
115109
116110
117111def test_load_constraints_file (tmp_path : pathlib .Path ) -> None :
118112 constraint_file = tmp_path / "constraint.txt"
119- constraint_file .write_text ("egg\n torch==3.1.0 # comment\n " )
120- c = constraints . Constraints ()
113+ constraint_file .write_text ("egg==1.0 \n torch==3.1.0 # comment\n " )
114+ c = Constraints ()
121115 c .load_constraints_file (constraint_file )
122116 assert list (c ) == ["egg" , "torch" ] # type: ignore
123117 assert c .get_constraint ("torch" ) == Requirement ("torch==3.1.0" )
118+
119+
120+ def test_invalid_constraints () -> None :
121+ c = Constraints ()
122+ with pytest .raises (InvalidConstraintError , match = r".*no specifier" ):
123+ c .add_constraint ("foo" )
124+ with pytest .raises (InvalidConstraintError , match = r".*has extras" ):
125+ c .add_constraint ("foo[extra]>=1.0" )
126+ with pytest .raises (InvalidConstraintError , match = r".*has an url" ):
127+ c .add_constraint ("foo@https://foo.test" )
128+
129+
130+ def test_unsatisfiable () -> None :
131+ c = Constraints ()
132+ with pytest .raises (InvalidConstraintError ):
133+ c .add_constraint ("foo<1.0,>2.0" )
134+
135+
136+ def test_combine_constraints () -> None :
137+ c = Constraints ()
138+ c .add_constraint ("foo>=1.0" )
139+ c .add_constraint ("foo<2.0" )
140+ assert c .get_constraint ("foo" ) == Requirement ("foo<2.0,>=1.0" )
141+ c .add_constraint ("foo!=1.1.0" )
142+ assert c .get_constraint ("foo" ) == Requirement ("foo<2.0,>=1.0,!=1.1.0" )
0 commit comments