diff --git a/tests/commands/test_analyze_wheel.py b/tests/commands/test_analyze_wheel.py index 889e8452..61c0e510 100644 --- a/tests/commands/test_analyze_wheel.py +++ b/tests/commands/test_analyze_wheel.py @@ -83,7 +83,7 @@ def test_analyze_wheel_variant_custom_label( assert ( capsys.readouterr().out == """\ -############################## Variant: `60567bd9` ############################# +############################## Variant: `foo` ############################# installable_plugin :: feat1 :: val1c ################################################################################ """ diff --git a/tests/models/test_variant.py b/tests/models/test_variant.py index f0812e58..62a107d1 100644 --- a/tests/models/test_variant.py +++ b/tests/models/test_variant.py @@ -6,9 +6,11 @@ import pytest from hypothesis import given from hypothesis import strategies as st +from variantlib.constants import NULL_VARIANT_LABEL from variantlib.constants import VALIDATION_FEATURE_NAME_REGEX from variantlib.constants import VALIDATION_NAMESPACE_REGEX from variantlib.constants import VALIDATION_VALUE_REGEX +from variantlib.constants import VALIDATION_VARIANT_LABEL_REGEX from variantlib.errors import ValidationError from variantlib.models.variant import VARIANT_HASH_LENGTH from variantlib.models.variant import VariantDescription @@ -177,29 +179,6 @@ def test_from_str_invalid_format() -> None: VariantProperty.from_str(input_str) -def test_variantprop_serialization() -> None: - vprop = VariantProperty(namespace="provider", feature="feature", value="value") - assert vprop.serialize() == { - "namespace": "provider", - "feature": "feature", - "value": "value", - } - - -def test_variantprop_deserialization() -> None: - data = { - "namespace": "provider", - "feature": "feature", - "value": "value", - } - - vprop = VariantProperty.deserialize(data) - - assert vprop.namespace == data["namespace"] - assert vprop.feature == data["feature"] - assert vprop.value == data["value"] - - def test_variantprop_sorting() -> None: data = [ VariantProperty("z", "a", "a"), @@ -237,6 +216,7 @@ def test_null_variant() -> None: vdesc = VariantDescription() assert vdesc.properties == [] assert vdesc.hexdigest == hashlib.sha256(b"").hexdigest()[:VARIANT_HASH_LENGTH] + assert vdesc.label == NULL_VARIANT_LABEL def test_variantdescription_initialization() -> None: @@ -247,7 +227,8 @@ def test_variantdescription_initialization() -> None: vprop2 = VariantProperty( namespace="tyrell_corporation", feature="client_id", value="secret_pass" ) - vdesc = VariantDescription([vprop1, vprop2]) + vdesc = VariantDescription([vprop1, vprop2], label="test") + assert vdesc.label == "test" # Check that the _data property is a list assert isinstance(vdesc.properties, list) @@ -280,14 +261,13 @@ def test_variantdescription_duplicate_data() -> None: def test_variantdescription_partial_duplicate_data() -> None: - # Test that duplicate VariantProperty instances are removed vprop1 = VariantProperty( namespace="omnicorp", feature="custom_feat", value="secret_value" ) vprop2 = VariantProperty( namespace="omnicorp", feature="custom_feat", value="another_value" ) - VariantDescription([vprop1, vprop2]) + VariantDescription([vprop1, vprop2], label="test") def test_variantdescription_sorted_data() -> None: @@ -301,7 +281,7 @@ def test_variantdescription_sorted_data() -> None: vprop3 = VariantProperty( namespace="omnicorp", feature="secret_pass", value="client_value" ) - vdesc = VariantDescription([vprop1, vprop2, vprop3]) + vdesc = VariantDescription([vprop1, vprop2, vprop3], label="test") # Check that data is sorted by namespace, feature, and value sorted_vprops = sorted( @@ -319,7 +299,7 @@ def test_variantdescription_hexdigest() -> None: namespace="tyrell_corporation", feature="client_id", value="secret_pass" ) vprops = [vprop1, vprop2] - vdesc = VariantDescription(vprops) + vdesc = VariantDescription(vprops, label="test") # Compute the expected hash using shake_128 (mock the hash output for testing) hash_object = hashlib.sha256( @@ -337,48 +317,19 @@ def test_variantdescription_hexdigest_adjacent_strings() -> None: [ VariantProperty("a", "b", "cx"), VariantProperty("d", "e", "f"), - ] + ], + label="test", ).hexdigest != VariantDescription( [ VariantProperty("a", "b", "c"), VariantProperty("xd", "e", "f"), - ] + ], + label="another", ).hexdigest ) -def test_variantdescription_serialization() -> None: - vprop = VariantProperty(namespace="provider", feature="feature", value="value") - vdesc = VariantDescription(properties=[vprop]) - - assert vdesc.serialize() == [ - { - "namespace": "provider", - "feature": "feature", - "value": "value", - } - ] - - -def test_variantdescription_deserialization() -> None: - data = [ - { - "namespace": "provider", - "feature": "feature", - "value": "value", - } - ] - - vdesc = VariantDescription.deserialize(data) - - assert len(vdesc.properties) == 1 - assert vdesc.properties[0].namespace == "provider" - assert vdesc.properties[0].feature == "feature" - assert vdesc.properties[0].value == "value" - assert vdesc.hexdigest == "c44d3adf" - - # ----------------------------------------------- # Fuzzy Testing # ----------------------------------------------- @@ -444,7 +395,7 @@ def test_fuzzy_variantprop(namespace: str, feature: str, value: str) -> None: ) def test_fuzzy_variantdescription(vprop: list[VariantProperty]) -> None: # Fuzzy test for random combinations of VariantDescription - vdesc = VariantDescription(vprop) + vdesc = VariantDescription(vprop, label="test") assert isinstance(vdesc.properties, list) assert len(vdesc.properties) >= 1 @@ -468,8 +419,22 @@ def test_fuzzy_variantdescription(vprop: list[VariantProperty]) -> None: value=st.from_regex(VALIDATION_NAMESPACE_REGEX, fullmatch=True), ), ), + label=st.from_regex(VALIDATION_VARIANT_LABEL_REGEX, fullmatch=True), ) ) def test_random_hexdigest(vdesc: VariantDescription) -> None: assert isinstance(vdesc.hexdigest, str) assert len(vdesc.hexdigest) == VARIANT_HASH_LENGTH + + +def test_null_variant_label(): + with pytest.raises( + ValidationError, + match=rf"{NULL_VARIANT_LABEL!r} label can be used only for the null variant", + ): + VariantDescription([VariantProperty("a", "b", "c")], label=NULL_VARIANT_LABEL) + with pytest.raises( + ValidationError, + match=rf"Null variant must always use {NULL_VARIANT_LABEL!r} label", + ): + VariantDescription(label="zuul") diff --git a/tests/resolver/test_filtering.py b/tests/resolver/test_filtering.py index b6efe8c7..32838184 100644 --- a/tests/resolver/test_filtering.py +++ b/tests/resolver/test_filtering.py @@ -32,9 +32,9 @@ def vdescs(vprops: list[VariantProperty]) -> list[VariantDescription]: vprop1, vprop2 = vprops return [ - VariantDescription([vprop1]), - VariantDescription([vprop2]), - VariantDescription([vprop1, vprop2]), + VariantDescription([vprop1], label="a"), + VariantDescription([vprop2], label="b"), + VariantDescription([vprop1, vprop2], label="c"), ] @@ -139,11 +139,11 @@ def test_filter_variants_by_namespaces(vdescs: list[VariantDescription]) -> None ("vdescs", "forbidden_namespaces"), [ ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], "not a list", ), ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], [VariantProperty("not", "a", "str")], ), ("not a list", ["omnicorp"]), @@ -245,11 +245,11 @@ def test_filter_variants_by_features( ("vdescs", "forbidden_features"), [ ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], "not a list", ), ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], ["not a `VariantFeature`"], ), ("not a list", VariantFeature("a", "b")), @@ -453,22 +453,22 @@ def test_filter_variants_by_property( [VariantProperty("a", "b", "c")], ), ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], "not a list", [VariantProperty("a", "b", "c")], ), ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], ["not a `VariantFeature`"], [VariantProperty("a", "b", "c")], ), ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], [VariantProperty("a", "b", "c")], "not a list", ), ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], [VariantProperty("a", "b", "c")], ["not a `VariantFeature`"], ), diff --git a/tests/resolver/test_lib.py b/tests/resolver/test_lib.py index 2d32449b..3a5656d6 100644 --- a/tests/resolver/test_lib.py +++ b/tests/resolver/test_lib.py @@ -100,82 +100,82 @@ def vdescs(vprops: list[VariantProperty]) -> list[VariantDescription]: # Important: vprop4 and vprop5 are mutually exclusive return [ # variants with 5 properties - VariantDescription([vprop1, vprop2, vprop3, vprop4, vprop6]), - VariantDescription([vprop1, vprop2, vprop3, vprop5, vprop6]), + VariantDescription([vprop1, vprop2, vprop3, vprop4, vprop6], label="a"), + VariantDescription([vprop1, vprop2, vprop3, vprop5, vprop6], label="b"), # variants with 4 properties - VariantDescription([vprop1, vprop2, vprop3, vprop4]), # - vprop6 - VariantDescription([vprop1, vprop2, vprop3, vprop5]), # - vprop6 + VariantDescription([vprop1, vprop2, vprop3, vprop4], label="c"), # - vprop6 + VariantDescription([vprop1, vprop2, vprop3, vprop5], label="d"), # - vprop6 - VariantDescription([vprop1, vprop2, vprop3, vprop6]), # - vprop4/5 + VariantDescription([vprop1, vprop2, vprop3, vprop6], label="c"), # - vprop4/5 - VariantDescription([vprop1, vprop2, vprop4, vprop6]), # - vprop3 - VariantDescription([vprop1, vprop2, vprop5, vprop6]), # - vprop3 + VariantDescription([vprop1, vprop2, vprop4, vprop6], label="d"), # - vprop3 + VariantDescription([vprop1, vprop2, vprop5, vprop6], label="e"), # - vprop3 - VariantDescription([vprop1, vprop3, vprop4, vprop6]), # - vprop2 - VariantDescription([vprop1, vprop3, vprop5, vprop6]), # - vprop2 + VariantDescription([vprop1, vprop3, vprop4, vprop6], label="f"), # - vprop2 + VariantDescription([vprop1, vprop3, vprop5, vprop6], label="g"), # - vprop2 - VariantDescription([vprop2, vprop3, vprop5, vprop6]), # - vprop1 - VariantDescription([vprop2, vprop3, vprop5, vprop6]), # - vprop1 + VariantDescription([vprop2, vprop3, vprop5, vprop6], label="h"), # - vprop1 + VariantDescription([vprop2, vprop3, vprop5, vprop6], label="i"), # - vprop1 # variants with 3 properties # --- vprop1 --- # - VariantDescription([vprop1, vprop2, vprop3]), - VariantDescription([vprop1, vprop2, vprop4]), - VariantDescription([vprop1, vprop2, vprop5]), - VariantDescription([vprop1, vprop2, vprop6]), + VariantDescription([vprop1, vprop2, vprop3], label="j"), + VariantDescription([vprop1, vprop2, vprop4], label="k"), + VariantDescription([vprop1, vprop2, vprop5], label="l"), + VariantDescription([vprop1, vprop2, vprop6], label="m"), - VariantDescription([vprop1, vprop3, vprop4]), - VariantDescription([vprop1, vprop3, vprop5]), - VariantDescription([vprop1, vprop3, vprop6]), + VariantDescription([vprop1, vprop3, vprop4], label="n"), + VariantDescription([vprop1, vprop3, vprop5], label="o"), + VariantDescription([vprop1, vprop3, vprop6], label="p"), - VariantDescription([vprop1, vprop4, vprop6]), - VariantDescription([vprop1, vprop5, vprop6]), + VariantDescription([vprop1, vprop4, vprop6], label="q"), + VariantDescription([vprop1, vprop5, vprop6], label="r"), # --- vprop2 --- # - VariantDescription([vprop2, vprop3, vprop4]), - VariantDescription([vprop2, vprop3, vprop5]), - VariantDescription([vprop2, vprop3, vprop6]), + VariantDescription([vprop2, vprop3, vprop4], label="s"), + VariantDescription([vprop2, vprop3, vprop5], label="t"), + VariantDescription([vprop2, vprop3, vprop6], label="u"), - VariantDescription([vprop2, vprop4, vprop6]), - VariantDescription([vprop2, vprop5, vprop6]), + VariantDescription([vprop2, vprop4, vprop6], label="v"), + VariantDescription([vprop2, vprop5, vprop6], label="w"), # --- vprop3 --- # - VariantDescription([vprop3, vprop4, vprop6]), - VariantDescription([vprop3, vprop5, vprop6]), + VariantDescription([vprop3, vprop4, vprop6], label="x"), + VariantDescription([vprop3, vprop5, vprop6], label="y"), # variants with 2 properties # --- vprop1 --- # - VariantDescription([vprop1, vprop2]), - VariantDescription([vprop1, vprop3]), - VariantDescription([vprop1, vprop4]), - VariantDescription([vprop1, vprop5]), - VariantDescription([vprop1, vprop6]), + VariantDescription([vprop1, vprop2], label="z"), + VariantDescription([vprop1, vprop3], label="aa"), + VariantDescription([vprop1, vprop4], label="ab"), + VariantDescription([vprop1, vprop5], label="ac"), + VariantDescription([vprop1, vprop6], label="ad"), # --- vprop2 --- # - VariantDescription([vprop2, vprop3]), - VariantDescription([vprop2, vprop4]), - VariantDescription([vprop2, vprop5]), - VariantDescription([vprop2, vprop6]), + VariantDescription([vprop2, vprop3], label="ae"), + VariantDescription([vprop2, vprop4], label="af"), + VariantDescription([vprop2, vprop5], label="ag"), + VariantDescription([vprop2, vprop6], label="ah"), # --- vprop3 --- # - VariantDescription([vprop3, vprop4]), - VariantDescription([vprop3, vprop5]), - VariantDescription([vprop3, vprop6]), + VariantDescription([vprop3, vprop4], label="ai"), + VariantDescription([vprop3, vprop5], label="aj"), + VariantDescription([vprop3, vprop6], label="ak"), # --- vprop4 --- # - VariantDescription([vprop4, vprop6]), + VariantDescription([vprop4, vprop6], label="al"), # --- vprop5 --- # - VariantDescription([vprop5, vprop6]), + VariantDescription([vprop5, vprop6], label="am"), # variants with 1 property - VariantDescription([vprop1]), - VariantDescription([vprop2]), - VariantDescription([vprop3]), - VariantDescription([vprop4]), - VariantDescription([vprop5]), - VariantDescription([vprop6]), + VariantDescription([vprop1], label="an"), + VariantDescription([vprop2], label="ao"), + VariantDescription([vprop3], label="ap"), + VariantDescription([vprop4], label="aq"), + VariantDescription([vprop5], label="ar"), + VariantDescription([vprop6], label="as"), ] # fmt: on @@ -207,7 +207,7 @@ def test_filter_variants_only_one_prop_allowed( vdescs=inputs_vdescs, allowed_properties=[vprop4], ) - ) == [VariantDescription([vprop4])] + ) == [VariantDescription([vprop4], label="aq")] assert ( list( @@ -258,7 +258,7 @@ def test_filter_variants_forbidden_feature_allowed_prop( allowed_properties=[vprop4], forbidden_features=[vprop2.feature_object], ) - ) == [VariantDescription([vprop4])] + ) == [VariantDescription([vprop4], label="aq")] def test_filter_variants_forbidden_namespace_allowed_prop( @@ -276,7 +276,7 @@ def test_filter_variants_forbidden_namespace_allowed_prop( allowed_properties=[vprop4], forbidden_namespaces=["NotExisting"], ) - ) == [VariantDescription([vprop4])] + ) == [VariantDescription([vprop4], label="aq")] assert list( filter_variants( @@ -284,7 +284,7 @@ def test_filter_variants_forbidden_namespace_allowed_prop( allowed_properties=[vprop4], forbidden_namespaces=[vprop1.namespace], ) - ) == [VariantDescription([vprop4])] + ) == [VariantDescription([vprop4], label="aq")] def test_filter_variants_only_remove_duplicates( @@ -320,22 +320,22 @@ def test_filter_variants_remove_duplicates_and_namespaces( expected_vdescs = [ # --- vprop3 --- # - VariantDescription([vprop3, vprop4, vprop6]), - VariantDescription([vprop3, vprop5, vprop6]), + VariantDescription([vprop3, vprop4, vprop6], label="test"), + VariantDescription([vprop3, vprop5, vprop6], label="test"), # variants with 2 properties # --- vprop3 --- # - VariantDescription([vprop3, vprop4]), - VariantDescription([vprop3, vprop5]), - VariantDescription([vprop3, vprop6]), + VariantDescription([vprop3, vprop4], label="test"), + VariantDescription([vprop3, vprop5], label="test"), + VariantDescription([vprop3, vprop6], label="test"), # --- vprop4 --- # - VariantDescription([vprop4, vprop6]), + VariantDescription([vprop4, vprop6], label="test"), # --- vprop5 --- # - VariantDescription([vprop5, vprop6]), + VariantDescription([vprop5, vprop6], label="test"), # variants with 1 property - VariantDescription([vprop3]), - VariantDescription([vprop4]), - VariantDescription([vprop5]), - VariantDescription([vprop6]), + VariantDescription([vprop3], label="test"), + VariantDescription([vprop4], label="test"), + VariantDescription([vprop5], label="test"), + VariantDescription([vprop6], label="test"), # Null-Variant is never removed and last VariantDescription(), ] @@ -379,12 +379,12 @@ def test_filter_variants_remove_duplicates_and_features( expected_vdescs = [ # variants with 2 properties # --- vprop1 --- # - VariantDescription([vprop1, vprop4]), - VariantDescription([vprop1, vprop5]), + VariantDescription([vprop1, vprop4], label="test"), + VariantDescription([vprop1, vprop5], label="test"), # variants with 1 property - VariantDescription([vprop1]), - VariantDescription([vprop4]), - VariantDescription([vprop5]), + VariantDescription([vprop1], label="test"), + VariantDescription([vprop4], label="test"), + VariantDescription([vprop5], label="test"), # Null-Variant is never removed and last VariantDescription(), ] @@ -434,12 +434,12 @@ def test_filter_variants_remove_duplicates_and_properties( expected_vdescs = [ # variants with 2 properties # --- vprop1 --- # - VariantDescription([vprop1, vprop4]), - VariantDescription([vprop1, vprop5]), + VariantDescription([vprop1, vprop4], label="test"), + VariantDescription([vprop1, vprop5], label="test"), # variants with 1 property - VariantDescription([vprop1]), - VariantDescription([vprop4]), - VariantDescription([vprop5]), + VariantDescription([vprop1], label="test"), + VariantDescription([vprop4], label="test"), + VariantDescription([vprop5], label="test"), # Null-Variant is never removed and last VariantDescription(), ] @@ -520,48 +520,48 @@ def test_sort_and_filter_supported_variants( # 1. Everything with vprop6 # 1.1. + vprop3 # 1.1.1. + vprop5 - VariantDescription([vprop1, vprop3, vprop5, vprop6]), - VariantDescription([vprop3, vprop5, vprop6]), + VariantDescription([vprop1, vprop3, vprop5, vprop6], label="g"), + VariantDescription([vprop3, vprop5, vprop6], label="y"), # 1.1.2. + vprop4 - VariantDescription([vprop1, vprop3, vprop4, vprop6]), - VariantDescription([vprop3, vprop4, vprop6]), + VariantDescription([vprop1, vprop3, vprop4, vprop6], label="f"), + VariantDescription([vprop3, vprop4, vprop6], label="x"), # 1.1.3. + vprop1 - VariantDescription([vprop1, vprop3, vprop6]), + VariantDescription([vprop1, vprop3, vprop6], label="p"), # 1.1.4. vprop6 + vprop3 - VariantDescription([vprop3, vprop6]), + VariantDescription([vprop3, vprop6], label="ak"), # 1.2. + vprop5 - VariantDescription([vprop1, vprop5, vprop6]), - VariantDescription([vprop5, vprop6]), + VariantDescription([vprop1, vprop5, vprop6], label="r"), + VariantDescription([vprop5, vprop6], label="am"), # 1.3. + vprop4 - VariantDescription([vprop1, vprop4, vprop6]), - VariantDescription([vprop4, vprop6]), + VariantDescription([vprop1, vprop4, vprop6], label="q"), + VariantDescription([vprop4, vprop6], label="al"), # 1.4. + vprop1 - VariantDescription([vprop1, vprop6]), + VariantDescription([vprop1, vprop6], label="ad"), # 1. sole vprop6 - VariantDescription([vprop6]), + VariantDescription([vprop6], label="as"), # 2. Everything with vprop3 # 2.1. + vprop5 - VariantDescription([vprop1, vprop3, vprop5]), - VariantDescription([vprop3, vprop5]), + VariantDescription([vprop1, vprop3, vprop5], label="o"), + VariantDescription([vprop3, vprop5], label="aj"), # 2.2. + vprop4 - VariantDescription([vprop1, vprop3, vprop4]), - VariantDescription([vprop3, vprop4]), + VariantDescription([vprop1, vprop3, vprop4], label="n"), + VariantDescription([vprop3, vprop4], label="ai"), # 2.3. + vprop1 - VariantDescription([vprop1, vprop3]), + VariantDescription([vprop1, vprop3], label="aa"), # 2. sole vprop3 - VariantDescription([vprop3]), + VariantDescription([vprop3], label="ap"), # 3. vprop5 - VariantDescription([vprop1, vprop5]), - VariantDescription([vprop5]), + VariantDescription([vprop1, vprop5], label="ac"), + VariantDescription([vprop5], label="ar"), # 4. vprop4 - VariantDescription([vprop1, vprop4]), - VariantDescription([vprop4]), + VariantDescription([vprop1, vprop4], label="ab"), + VariantDescription([vprop4], label="aq"), # 5. sole vprop1 - VariantDescription([vprop1]), + VariantDescription([vprop1], label="an"), # Null-Variant is never removed and last - Implicitly added VariantDescription(), @@ -590,11 +590,11 @@ def test_sort_and_filter_supported_variants( ("vdescs", "feature_priorities"), [ ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], "not a list", ), ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], {"a": [VariantFeature("not_a", "variantproperty")]}, ), ("not a list", {"a": ["a"]}), diff --git a/tests/resolver/test_sorting.py b/tests/resolver/test_sorting.py index 3966aebb..d730e352 100644 --- a/tests/resolver/test_sorting.py +++ b/tests/resolver/test_sorting.py @@ -239,13 +239,16 @@ def test_sort_variants_descriptions() -> None: VariantProperty(namespace="omnicorp", feature="feat_b", value="value1"), VariantProperty(namespace="omnicorp", feature="feat_c", value="value"), VariantProperty(namespace="other_corp", feature="feat_c", value="value"), - ] + ], + label="a", ) vdesc2 = VariantDescription( - [VariantProperty(namespace="other_corp", feature="feat_a", value="value")] + [VariantProperty(namespace="other_corp", feature="feat_a", value="value")], + label="b", ) vdesc3 = VariantDescription( - [VariantProperty(namespace="omnicorp", feature="feat_a", value="value")] + [VariantProperty(namespace="omnicorp", feature="feat_a", value="value")], + label="c", ) assert sort_variants_descriptions( @@ -317,7 +320,8 @@ def test_sort_variants_descriptions() -> None: VariantProperty( namespace="omnicorp", feature="feat", value="other_value" ) - ] + ], + label="a", ), VariantDescription( properties=[ @@ -326,6 +330,7 @@ def test_sort_variants_descriptions() -> None: namespace="omnicorp", feature="other_feat", value="other_value" ), ], + label="b", ), ], ) @@ -347,13 +352,16 @@ def test_sort_variants_descriptions_ranking_validation_error( [ ("not a list", [VariantProperty("a", "b", "c")]), (["not a VariantDescription"], [VariantProperty("a", "b", "c")]), - (VariantDescription([VariantProperty("a", "b", "c")]), "not a list or None"), ( - VariantDescription([VariantProperty("a", "b", "c")]), + VariantDescription([VariantProperty("a", "b", "c")], label="test"), + "not a list or None", + ), + ( + VariantDescription([VariantProperty("a", "b", "c")], label="test"), VariantProperty("not", "a", "list"), ), ( - VariantDescription([VariantProperty("a", "b", "c")]), + VariantDescription([VariantProperty("a", "b", "c")], label="test"), [{"not a VariantProperty": True}], ), ], @@ -366,3 +374,67 @@ def test_sort_variants_descriptions_validation_error( vdescs=vdescs, property_priorities=property_priorities, ) + + +def test_sort_variants_descriptions_by_label() -> None: + vprops_proprioty_list = [ + VariantProperty(namespace="omnicorp", feature="feat_a", value="value"), + VariantProperty(namespace="omnicorp", feature="feat_b", value="value1"), + ] + + vdesc1a = VariantDescription( + [ + VariantProperty(namespace="omnicorp", feature="feat_a", value="value"), + ], + label="a", + ) + vdesc1b = VariantDescription( + [ + VariantProperty(namespace="omnicorp", feature="feat_a", value="value"), + ], + label="b", + ) + vdesc1c = VariantDescription( + [ + VariantProperty(namespace="omnicorp", feature="feat_a", value="value"), + ], + label="c", + ) + + vdesc2a = VariantDescription( + [ + VariantProperty(namespace="omnicorp", feature="feat_b", value="value1"), + ], + label="a", + ) + vdesc2b = VariantDescription( + [ + VariantProperty(namespace="omnicorp", feature="feat_b", value="value1"), + ], + label="b", + ) + vdesc2c = VariantDescription( + [ + VariantProperty(namespace="omnicorp", feature="feat_b", value="value1"), + ], + label="c", + ) + + assert sort_variants_descriptions( + vdescs=[vdesc1a, vdesc1b, vdesc1c], property_priorities=vprops_proprioty_list + ) == [vdesc1a, vdesc1b, vdesc1c] + assert sort_variants_descriptions( + vdescs=[vdesc1b, vdesc1c, vdesc1a], property_priorities=vprops_proprioty_list + ) == [vdesc1a, vdesc1b, vdesc1c] + assert sort_variants_descriptions( + vdescs=[vdesc1c, vdesc1b, vdesc1a], property_priorities=vprops_proprioty_list + ) == [vdesc1a, vdesc1b, vdesc1c] + + assert sort_variants_descriptions( + vdescs=[vdesc1a, vdesc1b, vdesc1c, vdesc2a, vdesc2b], + property_priorities=vprops_proprioty_list, + ) == [vdesc1a, vdesc1b, vdesc1c, vdesc2a, vdesc2b] + assert sort_variants_descriptions( + vdescs=[vdesc2a, vdesc2c, vdesc1a, vdesc1b], + property_priorities=vprops_proprioty_list, + ) == [vdesc1a, vdesc1b, vdesc2a, vdesc2c] diff --git a/tests/test_api.py b/tests/test_api.py index d6ed1d35..dab8da74 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,7 +1,6 @@ from __future__ import annotations import json -import re import string from collections.abc import Generator from typing import TYPE_CHECKING @@ -336,7 +335,7 @@ def test_validate_variant(optional: bool) -> None: # Verify whether the variant properties are valid res = validate_variant( - VariantDescription(list(expected.keys())), + VariantDescription(list(expected.keys()), label="test"), variant_info=variant_info, ) @@ -357,10 +356,10 @@ def test_validate_variant(optional: bool) -> None: @pytest.mark.parametrize( "pyproject_toml", [None, PYPROJECT_TOML, PYPROJECT_TOML_MINIMAL] ) -@pytest.mark.parametrize("label", [None, "foo", "xy1.2"]) +@pytest.mark.parametrize("label", ["foo", "xy1.2"]) def test_make_variant_dist_info( pyproject_toml: VariantsJsonDict | None, - label: str | None, + label: str, ) -> None: expected: VariantsJsonDict = { VARIANTS_JSON_SCHEMA_KEY: VARIANTS_JSON_SCHEMA_URL, @@ -441,7 +440,8 @@ def test_make_variant_dist_info( VariantProperty("ns1", "f1", "p1"), VariantProperty("ns1", "f2", "p2"), VariantProperty("ns2", "f1", "p1"), - ] + ], + label=label, ), variant_info=VariantPyProjectToml(pyproject_toml) # type: ignore[arg-type] if pyproject_toml is not None @@ -478,7 +478,8 @@ def common_variant_info() -> VariantInfo: [ VariantProperty("test_namespace", "name2", "val2c"), VariantProperty("second_namespace", "name3", "val3a"), - ] + ], + label="test", ), True, ), @@ -486,7 +487,8 @@ def common_variant_info() -> VariantInfo: VariantDescription( [ VariantProperty("test_namespace", "name1", "val1c"), - ] + ], + label="test", ), False, ), @@ -526,27 +528,30 @@ def test_check_variant_supported_generic() -> None: [ VariantProperty("test_namespace", "name2", "val2c"), VariantProperty("second_namespace", "name3", "val3a"), - ] + ], + label="test", ), variant_info=variant_info, ) # test an usupported variant assert not check_variant_supported( - vdesc=VariantDescription([VariantProperty("test_namespace", "name1", "val1c")]), + vdesc=VariantDescription( + [VariantProperty("test_namespace", "name1", "val1c")], label="test" + ), variant_info=variant_info, ) -@pytest.mark.parametrize("label", [None, "foo"]) -def test_get_variant_environment_dict(label: str | None) -> None: +def test_get_variant_environment_dict() -> None: vdesc = VariantDescription( [ VariantProperty("ns1", "feat1", "val1"), VariantProperty("ns1", "feat2", "val2"), VariantProperty("ns2", "feat1", "val1"), VariantProperty("ns3", "feat2", "val2"), - ] + ], + label="foo", ) expected: dict[str, set[str] | str] = { "variant_features": { @@ -566,10 +571,9 @@ def test_get_variant_environment_dict(label: str | None) -> None: "ns2 :: feat1 :: val1", "ns3 :: feat2 :: val2", }, + "variant_label": "foo", } - if label is not None: - expected["variant_label"] = label - assert get_variant_environment_dict(vdesc, label) == expected + assert get_variant_environment_dict(vdesc, "foo") == expected def test_make_variant_dist_info_invalid_label(): @@ -583,18 +587,17 @@ def test_make_variant_dist_info_invalid_label(): match=rf"{NULL_VARIANT_LABEL!r} label can be used only for the null variant", ): make_variant_dist_info( - VariantDescription([VariantProperty("a", "b", "c")]), + VariantDescription( + [VariantProperty("a", "b", "c")], label=NULL_VARIANT_LABEL + ), variant_label=NULL_VARIANT_LABEL, ) with pytest.raises( ValidationError, - match=re.escape( - "Invalid variant label: 'foo/bar' " - "(must consist only of alphanumeric characters, underscores and dots)" - ), + match=("Value `foo/bar` must match regex"), ): make_variant_dist_info( - VariantDescription([VariantProperty("a", "b", "c")]), + VariantDescription([VariantProperty("a", "b", "c")], label="foo/bar"), variant_label="foo/bar", ) @@ -606,35 +609,17 @@ def test_get_variant_label() -> None: == NULL_VARIANT_LABEL ) - assert ( - get_variant_label(VariantDescription([VariantProperty("a", "b", "c")])) - == "01a9783a" - ) - assert ( - get_variant_label( - VariantDescription( - [VariantProperty("a", "b", "c"), VariantProperty("d", "e", "f")] - ) - ) - == "eb9a66a7" - ) assert ( get_variant_label( - VariantDescription( - [VariantProperty("d", "e", "f"), VariantProperty("a", "b", "c")] - ) + VariantDescription([VariantProperty("a", "b", "c")], label="foo"), "foo" ) - == "eb9a66a7" - ) - - assert ( - get_variant_label(VariantDescription([VariantProperty("a", "b", "c")]), "foo") == "foo" ) assert ( get_variant_label( VariantDescription( - [VariantProperty("a", "b", "c"), VariantProperty("d", "e", "f")] + [VariantProperty("a", "b", "c"), VariantProperty("d", "e", "f")], + label="foo", ), "foo", ) @@ -645,24 +630,23 @@ def test_get_variant_label() -> None: ValidationError, match=rf"Null variant must always use {NULL_VARIANT_LABEL!r} label", ): - get_variant_label(VariantDescription([]), "foo") + get_variant_label(VariantDescription([], label="foo"), "foo") with pytest.raises( ValidationError, match=rf"{NULL_VARIANT_LABEL!r} label can be used only for the null variant", ): get_variant_label( - VariantDescription([VariantProperty("a", "b", "c")]), + VariantDescription( + [VariantProperty("a", "b", "c")], label=NULL_VARIANT_LABEL + ), NULL_VARIANT_LABEL, ) with pytest.raises( ValidationError, - match=re.escape( - "Invalid variant label: 'foo/bar' " - "(must consist only of alphanumeric characters, underscores and dots)" - ), + match=("Value `foo/bar` must match regex"), ): get_variant_label( - VariantDescription([VariantProperty("a", "b", "c")]), + VariantDescription([VariantProperty("a", "b", "c")], label="foo/bar"), "foo/bar", ) @@ -677,7 +661,8 @@ def test_make_variant_dist_info_expand_aot_plugin_properties( vdesc = VariantDescription( [ VariantProperty("aot_plugin", "name1", "val1a"), - ] + ], + label="test", ) plugin_api = "tests.mocked_plugins:MockedAoTPlugin" vinfo = VariantInfo( @@ -745,7 +730,8 @@ def test_make_variant_dist_info_invalid_aot_plugin_property() -> None: vdesc = VariantDescription( [ VariantProperty("aot_plugin", "name1", "val1d"), - ] + ], + label="test", ) plugin_api = "tests.mocked_plugins:MockedAoTPlugin" vinfo = VariantInfo( @@ -776,7 +762,8 @@ def test_make_variant_dist_info_invalid_aot_plugin_multi_value() -> None: vdesc = VariantDescription( [ VariantProperty("aot_plugin", "name1", "val1a"), - ] + ], + label="test", ) plugin_api = "tests.mocked_plugins:MultiValueAoTPlugin" vinfo = VariantInfo( @@ -806,7 +793,8 @@ def test_make_variant_dist_info_really_invalid_build_plugin() -> None: vdesc = VariantDescription( [ VariantProperty("second_namespace", "name3", "val3a"), - ] + ], + label="test", ) plugin_api = "tests.mocked_plugins:MockedPluginB" vinfo = VariantInfo( diff --git a/tests/test_utils.py b/tests/test_utils.py index c809073b..f6298aa5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -39,29 +39,29 @@ def test_get_combinations(configs: list[ProviderConfig]) -> None: ] assert list(get_combinations(configs, namespace_priorities)) == [ - VariantDescription([val1a, val2a, val3a]), - VariantDescription([val1a, val2a]), - VariantDescription([val1a, val2b, val3a]), - VariantDescription([val1a, val2b]), - VariantDescription([val1a, val2c, val3a]), - VariantDescription([val1a, val2c]), - VariantDescription([val1a, val3a]), - VariantDescription([val1a]), - VariantDescription([val1b, val2a, val3a]), - VariantDescription([val1b, val2a]), - VariantDescription([val1b, val2b, val3a]), - VariantDescription([val1b, val2b]), - VariantDescription([val1b, val2c, val3a]), - VariantDescription([val1b, val2c]), - VariantDescription([val1b, val3a]), - VariantDescription([val1b]), - VariantDescription([val2a, val3a]), - VariantDescription([val2a]), - VariantDescription([val2b, val3a]), - VariantDescription([val2b]), - VariantDescription([val2c, val3a]), - VariantDescription([val2c]), - VariantDescription([val3a]), + VariantDescription([val1a, val2a, val3a], label="0_0"), + VariantDescription([val1a, val2a], label="0_1"), + VariantDescription([val1a, val2b, val3a], label="0_2"), + VariantDescription([val1a, val2b], label="0_3"), + VariantDescription([val1a, val2c, val3a], label="0_4"), + VariantDescription([val1a, val2c], label="0_5"), + VariantDescription([val1a, val3a], label="0_6"), + VariantDescription([val1a], label="0_7"), + VariantDescription([val1b, val2a, val3a], label="0_8"), + VariantDescription([val1b, val2a], label="0_9"), + VariantDescription([val1b, val2b, val3a], label="0_10"), + VariantDescription([val1b, val2b], label="0_11"), + VariantDescription([val1b, val2c, val3a], label="0_12"), + VariantDescription([val1b, val2c], label="0_13"), + VariantDescription([val1b, val3a], label="0_14"), + VariantDescription([val1b], label="0_15"), + VariantDescription([val2a, val3a], label="1_0"), + VariantDescription([val2a], label="1_1"), + VariantDescription([val2b, val3a], label="1_2"), + VariantDescription([val2b], label="1_3"), + VariantDescription([val2c, val3a], label="1_4"), + VariantDescription([val2c], label="1_5"), + VariantDescription([val3a], label="2_0"), VariantDescription(), ] @@ -81,29 +81,29 @@ def test_get_combinations_flipped_order(configs: list[ProviderConfig]) -> None: ] assert list(get_combinations(configs, namespace_priorities)) == [ - VariantDescription([val1a, val2a, val3a]), - VariantDescription([val1a, val2b, val3a]), - VariantDescription([val1a, val2c, val3a]), - VariantDescription([val1a, val3a]), - VariantDescription([val1b, val2a, val3a]), - VariantDescription([val1b, val2b, val3a]), - VariantDescription([val1b, val2c, val3a]), - VariantDescription([val1b, val3a]), - VariantDescription([val2a, val3a]), - VariantDescription([val2b, val3a]), - VariantDescription([val2c, val3a]), - VariantDescription([val3a]), - VariantDescription([val1a, val2a]), - VariantDescription([val1a, val2b]), - VariantDescription([val1a, val2c]), - VariantDescription([val1a]), - VariantDescription([val1b, val2a]), - VariantDescription([val1b, val2b]), - VariantDescription([val1b, val2c]), - VariantDescription([val1b]), - VariantDescription([val2a]), - VariantDescription([val2b]), - VariantDescription([val2c]), + VariantDescription([val1a, val2a, val3a], label="0_0"), + VariantDescription([val1a, val2b, val3a], label="0_1"), + VariantDescription([val1a, val2c, val3a], label="0_2"), + VariantDescription([val1a, val3a], label="0_3"), + VariantDescription([val1b, val2a, val3a], label="0_4"), + VariantDescription([val1b, val2b, val3a], label="0_5"), + VariantDescription([val1b, val2c, val3a], label="0_6"), + VariantDescription([val1b, val3a], label="0_7"), + VariantDescription([val2a, val3a], label="0_8"), + VariantDescription([val2b, val3a], label="0_9"), + VariantDescription([val2c, val3a], label="0_10"), + VariantDescription([val3a], label="0_11"), + VariantDescription([val1a, val2a], label="1_0"), + VariantDescription([val1a, val2b], label="1_1"), + VariantDescription([val1a, val2c], label="1_2"), + VariantDescription([val1a], label="1_3"), + VariantDescription([val1b, val2a], label="1_4"), + VariantDescription([val1b, val2b], label="1_5"), + VariantDescription([val1b, val2c], label="1_6"), + VariantDescription([val1b], label="1_7"), + VariantDescription([val2a], label="2_0"), + VariantDescription([val2b], label="2_1"), + VariantDescription([val2c], label="2_2"), VariantDescription(), ] @@ -117,7 +117,8 @@ def test_get_combinations_one_one_namespace_one(configs: list[ProviderConfig]) - VariantDescription( [ VariantProperty("second_namespace", "name3", "val3a"), - ] + ], + label="0_0", ), VariantDescription(), ] @@ -136,17 +137,17 @@ def test_get_combinations_one_one_namespace_two(configs: list[ProviderConfig]) - ] assert list(get_combinations(configs, namespace_priorities)) == [ - VariantDescription([val1a, val2a]), - VariantDescription([val1a, val2b]), - VariantDescription([val1a, val2c]), - VariantDescription([val1a]), - VariantDescription([val1b, val2a]), - VariantDescription([val1b, val2b]), - VariantDescription([val1b, val2c]), - VariantDescription([val1b]), - VariantDescription([val2a]), - VariantDescription([val2b]), - VariantDescription([val2c]), + VariantDescription([val1a, val2a], label="0_0"), + VariantDescription([val1a, val2b], label="0_1"), + VariantDescription([val1a, val2c], label="0_2"), + VariantDescription([val1a], label="0_3"), + VariantDescription([val1b, val2a], label="0_4"), + VariantDescription([val1b, val2b], label="0_5"), + VariantDescription([val1b, val2c], label="0_6"), + VariantDescription([val1b], label="0_7"), + VariantDescription([val2a], label="1_0"), + VariantDescription([val2b], label="1_1"), + VariantDescription([val2c], label="1_2"), VariantDescription(), ] diff --git a/tests/test_variant_dist_info.py b/tests/test_variant_dist_info.py index 1a9c2814..c6c2850d 100644 --- a/tests/test_variant_dist_info.py +++ b/tests/test_variant_dist_info.py @@ -26,12 +26,12 @@ VARIANT_INFO_PROVIDER_DATA_KEY: { "ns": {VARIANT_INFO_PROVIDER_REQUIRES_KEY: ["ns-pkg"]} }, - VARIANTS_JSON_VARIANT_DATA_KEY: {"bdbc6ca0": {"ns": {"f": ["v"]}}}, + VARIANTS_JSON_VARIANT_DATA_KEY: {"test": {"ns": {"f": ["v"]}}}, } @pytest.mark.parametrize("json_type", [str, bytes]) -@pytest.mark.parametrize("expected_label", [None, "bdbc6ca0"]) +@pytest.mark.parametrize("expected_label", [None, "test"]) def test_variant_dist_info(json_type: type, expected_label: str | None) -> None: vjson_str = ( json.dumps(VARIANT_JSON) @@ -43,21 +43,21 @@ def test_variant_dist_info(json_type: type, expected_label: str | None) -> None: assert variant_dist_info.feature_priorities == {} assert variant_dist_info.property_priorities == {} assert variant_dist_info.providers == {"ns": ProviderInfo(requires=["ns-pkg"])} - vdesc = VariantDescription([VariantProperty("ns", "f", "v")]) - assert variant_dist_info.variants == {vdesc.hexdigest: vdesc} + vdesc = VariantDescription([VariantProperty("ns", "f", "v")], label="test") + assert variant_dist_info.variants == {"test": vdesc} assert variant_dist_info.variant_desc == vdesc - assert variant_dist_info.variant_label == vdesc.hexdigest + assert variant_dist_info.variant_label == "test" @pytest.mark.parametrize("expected_label", [None, "fancy1"]) def test_variant_dist_info_custom_label(expected_label: str | None) -> None: - vjson_str = json.dumps(VARIANT_JSON).replace("bdbc6ca0", "fancy1") + vjson_str = json.dumps(VARIANT_JSON).replace("test", "fancy1") variant_dist_info = VariantDistInfo(vjson_str, expected_label=expected_label) assert variant_dist_info.namespace_priorities == ["ns"] assert variant_dist_info.feature_priorities == {} assert variant_dist_info.property_priorities == {} assert variant_dist_info.providers == {"ns": ProviderInfo(requires=["ns-pkg"])} - vdesc = VariantDescription([VariantProperty("ns", "f", "v")]) + vdesc = VariantDescription([VariantProperty("ns", "f", "v")], label="fancy1") assert variant_dist_info.variants == {"fancy1": vdesc} assert variant_dist_info.variant_desc == vdesc assert variant_dist_info.variant_label == "fancy1" @@ -85,25 +85,27 @@ def test_new_variant_dist_info() -> None: namespace_priorities=["ns"], providers={"ns": ProviderInfo(requires=["ns-pkg"])} ) variant_dist_info = VariantDistInfo(variant_info) - vdesc = VariantDescription([VariantProperty("ns", "f", "v")]) + vdesc = VariantDescription([VariantProperty("ns", "f", "v")], label="a") variant_dist_info.variant_desc = vdesc assert variant_dist_info.namespace_priorities == variant_info.namespace_priorities assert variant_dist_info.providers == variant_info.providers - assert variant_dist_info.variant_label == vdesc.hexdigest + assert variant_dist_info.variant_label == "a" assert variant_dist_info.variant_desc == vdesc # changing vdesc should update the label - vdesc2 = VariantDescription([VariantProperty("ns2", "f2", "v2")]) + vdesc2 = VariantDescription([VariantProperty("ns2", "f2", "v2")], label="b") variant_dist_info.variant_desc = vdesc2 - assert variant_dist_info.variant_label == vdesc2.hexdigest + assert variant_dist_info.variant_label == "b" assert variant_dist_info.variant_desc == vdesc2 # set a custom label variant_dist_info.variant_label = "fancy2" assert variant_dist_info.variant_label == "fancy2" - assert variant_dist_info.variant_desc == vdesc2 + assert variant_dist_info.variant_desc == VariantDescription( + vdesc2.properties, label="fancy2" + ) # changing vdesc should reset the label variant_dist_info.variant_desc = vdesc2 - assert variant_dist_info.variant_label == vdesc2.hexdigest + assert variant_dist_info.variant_label == "b" assert variant_dist_info.variant_desc == vdesc2 diff --git a/tests/test_variants_json.py b/tests/test_variants_json.py index 4805c9fa..51d2ec7e 100644 --- a/tests/test_variants_json.py +++ b/tests/test_variants_json.py @@ -46,6 +46,7 @@ def test_validate_variants_json() -> None: assert variants_json.variants == { NULL_VARIANT_LABEL: VariantDescription(), "03e04d5e": VariantDescription( + label="03e04d5e", properties=[ VariantProperty( namespace="fictional_hw", @@ -60,6 +61,7 @@ def test_validate_variants_json() -> None: ], ), "36028aca": VariantDescription( + label="36028aca", properties=[ VariantProperty( namespace="fictional_hw", @@ -89,6 +91,7 @@ def test_validate_variants_json() -> None: ], ), "3f7188c1": VariantDescription( + label="3f7188c1", properties=[ VariantProperty( namespace="fictional_hw", @@ -113,6 +116,7 @@ def test_validate_variants_json() -> None: ], ), "7db6d39f": VariantDescription( + label="7db6d39f", properties=[ VariantProperty( namespace="fictional_tech", @@ -132,6 +136,7 @@ def test_validate_variants_json() -> None: ], ), "808c7f9d": VariantDescription( + label="808c7f9d", properties=[ VariantProperty( namespace="fictional_tech", @@ -151,6 +156,7 @@ def test_validate_variants_json() -> None: ], ), "80fa16ff": VariantDescription( + label="80fa16ff", properties=[ VariantProperty( namespace="fictional_hw", @@ -175,6 +181,7 @@ def test_validate_variants_json() -> None: ], ), "3351fc6a": VariantDescription( + label="3351fc6a", properties=[ VariantProperty( namespace="fictional_hw", @@ -182,9 +189,10 @@ def test_validate_variants_json() -> None: value=str(value), ) for value in range(4, 6) - ] + ], ), "181830db": VariantDescription( + label="181830db", properties=[ VariantProperty( namespace="fictional_hw", @@ -192,9 +200,10 @@ def test_validate_variants_json() -> None: value=str(value), ) for value in range(4, 8) - ] + ], ), "72c47fce": VariantDescription( + label="72c47fce", properties=[ VariantProperty( namespace="fictional_hw", @@ -206,7 +215,7 @@ def test_validate_variants_json() -> None: feature="compute_capability", value="8", ), - ] + ], ), } assert variants_json.namespace_priorities == ["fictional_hw", "fictional_tech"] @@ -308,16 +317,18 @@ def test_to_str() -> None: [ VariantProperty("ns1", "f1", "v1"), VariantProperty("ns2", "f2", "v1"), - ] + ], + label="a", ) vdesc2 = VariantDescription( [ VariantProperty("ns2", "f2", "v2"), - ] + ], + label="b", ) variants_json.variants = { - vdesc1.hexdigest: vdesc1, - vdesc2.hexdigest: vdesc2, + "a": vdesc1, + "b": vdesc2, } assert json.loads(variants_json.to_str()) == { VARIANTS_JSON_SCHEMA_KEY: VARIANTS_JSON_SCHEMA_URL, @@ -338,8 +349,8 @@ def test_to_str() -> None: }, }, VARIANTS_JSON_VARIANT_DATA_KEY: { - "b3b0305c": {"ns1": {"f1": ["v1"]}, "ns2": {"f2": ["v1"]}}, - "9177ff3f": {"ns2": {"f2": ["v2"]}}, + "a": {"ns1": {"f1": ["v1"]}, "ns2": {"f2": ["v1"]}}, + "b": {"ns2": {"f2": ["v2"]}}, }, } @@ -492,12 +503,13 @@ def test_merge_variants() -> None: def test_null_variant_label(): with pytest.raises( ValidationError, - match=rf"{NULL_VARIANT_LABEL!r} label can only be used for the null variant", + match=rf"{NULL_VARIANT_LABEL!r} label can be used only for the null variant", ): VariantsJson( {VARIANTS_JSON_VARIANT_DATA_KEY: {NULL_VARIANT_LABEL: {"x": {"y": ["z"]}}}} ) with pytest.raises( - ValidationError, match=rf"Null variant must use {NULL_VARIANT_LABEL!r} label" + ValidationError, + match=rf"Null variant must always use {NULL_VARIANT_LABEL!r} label", ): VariantsJson({VARIANTS_JSON_VARIANT_DATA_KEY: {"zuul": {}}}) diff --git a/tests/utils.py b/tests/utils.py index ea05b9fb..4d894b4b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -51,8 +51,8 @@ def yield_all_values( yield [VariantProperty(namespace, feature, value)] for start in range(len(all_properties)): - for properties in yield_all_values(all_properties[start:]): - yield VariantDescription(properties) + for j, properties in enumerate(yield_all_values(all_properties[start:])): + yield VariantDescription(properties, label=f"{start}_{j}") # Finish by the null variant yield VariantDescription() diff --git a/variantlib/api.py b/variantlib/api.py index e79c497a..2fdf628d 100644 --- a/variantlib/api.py +++ b/variantlib/api.py @@ -5,6 +5,7 @@ import itertools import logging import pathlib +import warnings from typing import TYPE_CHECKING from variantlib.configuration import VariantConfiguration @@ -57,6 +58,8 @@ def get_variants_by_priority( if not isinstance(variants_json, VariantsJson): variants_json = VariantsJson(variants_json) + assert all(vdesc.label == label for label, vdesc in variants_json.variants.items()) + venv_python_executable = ( venv_python_executable if venv_python_executable is None @@ -76,14 +79,9 @@ def get_variants_by_priority( ) config = VariantConfiguration.get_config() - label_map = { - vdesc.hexdigest: label for label, vdesc in variants_json.variants.items() - } - # handle the implicit null variant - label_map.setdefault(VariantDescription([]).hexdigest, NULL_VARIANT_LABEL) return [ - label_map[vdesc.hexdigest] + vdesc.label for vdesc in sort_and_filter_supported_variants( list(variants_json.variants.values()), supported_vprops, @@ -185,7 +183,7 @@ def make_variant_dist_info( instead. variant_label specifies the variant label to use. If not specified, - the default of variant hash is used. + the default of variant hash is used. Deprecated: set vdesc.label instead. If expand_aot_plugin_properties is True, then default-priorities for ahead-of-time plugins will be filled with the current list @@ -198,6 +196,12 @@ def make_variant_dist_info( variant_info = VariantInfo() variant_json = VariantDistInfo(variant_info) variant_json.variant_desc = vdesc + if variant_label is not None: + warnings.warn( + "Passing variant_label is deprecated, provide VariantDescription() " + "with label instead", + stacklevel=2, + ) variant_json.variant_label = get_variant_label(vdesc, variant_label) if expand_aot_plugin_properties: @@ -336,8 +340,14 @@ def get_variant_environment_dict( vprop.feature_object.to_str() for vprop in variant_desc.properties }, "variant_properties": {vprop.to_str() for vprop in variant_desc.properties}, + "variant_label": variant_desc.label, } if variant_label is not None: + warnings.warn( + "Passing variant_label is deprecated, provide VariantDescription() " + "with label instead", + stacklevel=2, + ) ret["variant_label"] = variant_label return ret @@ -352,14 +362,17 @@ def get_variant_label( Get the label corresponding to `variant_desc`. If `custom_label` is provided, validate it and use it. If `custom_label` is invalid, raises a `ValidationError`. + + This function is deprecated: use VariantDescription.label instead. """ if custom_label is None: - return ( - NULL_VARIANT_LABEL - if variant_desc.is_null_variant() - else variant_desc.hexdigest - ) + return variant_desc.label + + warnings.warn( + "get_variant_label() is deprecated, use VariantDescription.label instead", + stacklevel=2, + ) if variant_desc.is_null_variant(): if custom_label != NULL_VARIANT_LABEL: diff --git a/variantlib/models/variant.py b/variantlib/models/variant.py index ed79e9e6..c7369eb2 100644 --- a/variantlib/models/variant.py +++ b/variantlib/models/variant.py @@ -9,11 +9,13 @@ from dataclasses import field from functools import cached_property +from variantlib.constants import NULL_VARIANT_LABEL from variantlib.constants import VALIDATION_FEATURE_NAME_REGEX from variantlib.constants import VALIDATION_FEATURE_REGEX from variantlib.constants import VALIDATION_NAMESPACE_REGEX from variantlib.constants import VALIDATION_PROPERTY_REGEX from variantlib.constants import VALIDATION_VALUE_REGEX +from variantlib.constants import VALIDATION_VARIANT_LABEL_REGEX from variantlib.constants import VariantInfoJsonDict from variantlib.errors import ValidationError from variantlib.models.base import BaseModel @@ -69,16 +71,6 @@ def to_str(self) -> str: # Variant-Property: :: :: return f"{self.namespace} :: {self.feature}" - def serialize(self) -> dict[str, str]: - return asdict(self) - - @classmethod - def deserialize(cls, data: dict[str, str]) -> Self: - for field_name in cls.__dataclass_fields__: - if field_name not in data: - raise ValidationError(f"Extra field not known: `{field_name}`") - return cls(**data) - @classmethod def from_str(cls, input_str: str) -> Self: # Try matching the input string with the regex pattern @@ -170,6 +162,19 @@ class VariantDescription(BaseModel): default_factory=list, ) + label: str = field( + metadata={ + "validator": lambda val: validate_and( + [ + lambda v: validate_type(v, str), + lambda v: validate_matches_re(v, VALIDATION_VARIANT_LABEL_REGEX), # pyright: ignore[reportArgumentType] + ], + value=val, + ), + }, + default="", + ) + def __post_init__(self) -> None: # We sort the data so that they always get displayed/hashed # in a consistent manner. @@ -179,6 +184,24 @@ def __post_init__(self) -> None: # Only "legal way" to modify a frozen dataclass attribute post init. object.__setattr__(self, "properties", sorted(self.properties)) + # default the label to "null" or hexdigest + if not self.label: + with contextlib.suppress(AttributeError): + object.__setattr__( + self, + "label", + NULL_VARIANT_LABEL if self.is_null_variant() else self.hexdigest, + ) + elif self.is_null_variant(): + if self.label != NULL_VARIANT_LABEL: + raise ValidationError( + f"Null variant must always use {NULL_VARIANT_LABEL!r} label" + ) + elif self.label == NULL_VARIANT_LABEL: + raise ValidationError( + f"{NULL_VARIANT_LABEL!r} label can be used only for the null variant" + ) + # Execute the validator super().__post_init__() @@ -213,15 +236,6 @@ def hexdigest(self) -> str: # Source: https://docs.python.org/3/library/hashlib.html#hashlib.hash.hexdigest return hash_object.hexdigest()[:VARIANT_HASH_LENGTH] - @classmethod - def deserialize(cls, properties: list[dict[str, str]]) -> Self: - return cls( - properties=[VariantProperty.deserialize(vdata) for vdata in properties] - ) - - def serialize(self) -> list[dict[str, str]]: - return [vprop.serialize() for vprop in self.properties] - def to_dict(self) -> VariantInfoJsonDict: data = asdict(self) @@ -238,7 +252,7 @@ def to_dict(self) -> VariantInfoJsonDict: return dict(result) @classmethod - def from_dict(cls, data: VariantInfoJsonDict) -> Self: + def from_dict(cls, data: VariantInfoJsonDict, label: str) -> Self: vprops = [ VariantProperty(namespace=namespace, feature=key, value=vprop_val) for namespace, vdata in data.items() @@ -246,7 +260,7 @@ def from_dict(cls, data: VariantInfoJsonDict) -> Self: for vprop_val in vprop_values ] - return cls(vprops) + return cls(properties=vprops, label=label) @dataclass(frozen=True) diff --git a/variantlib/resolver/sorting.py b/variantlib/resolver/sorting.py index 1de41d8c..a4ddf5a7 100644 --- a/variantlib/resolver/sorting.py +++ b/variantlib/resolver/sorting.py @@ -195,7 +195,7 @@ def sort_variants_descriptions( property_lookup_table = dict(property_lookup_table) lookup_table_size = len(property_lookup_table) - def _get_rank_tuple(vdesc: VariantDescription) -> tuple[int, ...]: + def _get_rank_tuple(vdesc: VariantDescription) -> tuple[tuple[int, ...], str]: """ Get the rank tuple of a `VariantDescription` object. @@ -232,6 +232,7 @@ def _get_rank_tuple(vdesc: VariantDescription) -> tuple[int, ...]: if any(ranking_array[idx] == sys.maxsize for idx in vdesc_feature_indexes): raise ValidationError("Filtering should be applied first.") - return tuple(ranking_array) + # As a fallback, sort on variant label. + return (tuple(ranking_array), vdesc.label) return sorted(vdescs, key=_get_rank_tuple) diff --git a/variantlib/variant_dist_info.py b/variantlib/variant_dist_info.py index 79f6cecc..9c2d3160 100644 --- a/variantlib/variant_dist_info.py +++ b/variantlib/variant_dist_info.py @@ -2,16 +2,13 @@ import json from dataclasses import dataclass -from typing import TYPE_CHECKING from variantlib.constants import VARIANT_DIST_INFO_FILENAME from variantlib.errors import ValidationError +from variantlib.models.variant import VariantDescription from variantlib.models.variant_info import VariantInfo from variantlib.variants_json import VariantsJson -if TYPE_CHECKING: - from variantlib.models.variant import VariantDescription - @dataclass(init=False) class VariantDistInfo(VariantsJson): @@ -47,6 +44,9 @@ def variant_label(self) -> str: @variant_label.setter def variant_label(self, new_label: str) -> None: + self.variant_desc = VariantDescription( + self.variant_desc.properties, label=new_label + ) self.variants = {new_label: self.variant_desc} @property @@ -56,4 +56,4 @@ def variant_desc(self) -> VariantDescription: @variant_desc.setter def variant_desc(self, new_desc: VariantDescription) -> None: - self.variants = {new_desc.hexdigest: new_desc} + self.variants = {new_desc.label: new_desc} diff --git a/variantlib/variants_json.py b/variantlib/variants_json.py index 363eeb5f..52aabec9 100644 --- a/variantlib/variants_json.py +++ b/variantlib/variants_json.py @@ -90,6 +90,8 @@ def providers_dict(self) -> dict[str, dict[str, str | list[str] | bool]]: def to_str(self) -> str: """Serialize variants.json as a JSON string""" + assert all(label == vdesc.label for label, vdesc in self.variants.items()) + data: dict[str, Any] = { VARIANTS_JSON_SCHEMA_KEY: VARIANTS_JSON_SCHEMA_URL, VARIANT_INFO_DEFAULT_PRIO_KEY: dict(self._priorities_to_json()), @@ -179,7 +181,9 @@ def _process(self, variant_table: VariantsJsonDict) -> None: VariantInfoJsonDict, ignore_subkeys=True, ) as packed_vdesc: - vdesc = VariantDescription.from_dict(packed_vdesc) + vdesc = VariantDescription.from_dict( + packed_vdesc, label=variant_label + ) if vdesc.is_null_variant() and variant_label != NULL_VARIANT_LABEL: raise ValidationError( f"Null variant must use {NULL_VARIANT_LABEL!r} label"