Skip to content

Null variant labels #110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
}
},
"variants": {
"00000000": {},
"null": {},
"5d8be4b9": {
"installable_plugin": {
"feat1": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
]
}
},
"00000000": {},
"null": {},
"3f7188c1": {
"fictional_hw": {
"architecture": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
}
},
"variants": {
"00000000": {},
"null": {},
"38ea48ce": {
"fictional_hw": {
"architecture": [
Expand Down Expand Up @@ -69,4 +69,4 @@
}
}
}
}
}
6 changes: 3 additions & 3 deletions tests/commands/test_analyze_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import TYPE_CHECKING

from variantlib.commands.main import main
from variantlib.constants import NULL_VARIANT_HASH
from variantlib.constants import NULL_VARIANT_LABEL

if TYPE_CHECKING:
import pytest
Expand Down Expand Up @@ -34,13 +34,13 @@ def test_analyze_wheel_null_variant(
"analyze-wheel",
"-i",
"tests/artifacts/test-package/dist/test_package-0-py3-none-any-"
f"{NULL_VARIANT_HASH}.whl",
f"{NULL_VARIANT_LABEL}.whl",
]
)
assert (
capsys.readouterr().out
== f"""\
############################## Variant: `{NULL_VARIANT_HASH}` \
############################## Variant: `{NULL_VARIANT_LABEL}` \
#############################
################################################################################
"""
Expand Down
6 changes: 3 additions & 3 deletions tests/commands/test_generate_index_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import TYPE_CHECKING

from variantlib.commands.main import main
from variantlib.constants import NULL_VARIANT_HASH
from variantlib.constants import NULL_VARIANT_LABEL
from variantlib.constants import VARIANT_INFO_DEFAULT_PRIO_KEY
from variantlib.constants import VARIANT_INFO_NAMESPACE_KEY
from variantlib.constants import VARIANT_INFO_PROVIDER_DATA_KEY
Expand All @@ -25,7 +25,7 @@ def test_generate_index_json(
) -> None:
filenames = [
"test_package-0-py3-none-any.whl",
f"test_package-0-py3-none-any-{NULL_VARIANT_HASH}.whl",
f"test_package-0-py3-none-any-{NULL_VARIANT_LABEL}.whl",
"test_package-0-py3-none-any-5d8be4b9.whl",
]
artifact_dir = Path("tests/artifacts/test-package/dist")
Expand All @@ -51,7 +51,7 @@ def test_generate_index_json(
},
},
VARIANTS_JSON_VARIANT_DATA_KEY: {
NULL_VARIANT_HASH: {},
NULL_VARIANT_LABEL: {},
"5d8be4b9": {
"installable_plugin": {
"feat1": ["val1c"],
Expand Down
5 changes: 3 additions & 2 deletions tests/commands/test_get_variant_hash.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
from __future__ import annotations

import hashlib
from itertools import chain

import pytest

from variantlib.commands.main import main
from variantlib.constants import NULL_VARIANT_HASH
from variantlib.constants import VARIANT_HASH_LEN


@pytest.mark.parametrize(
("properties", "expected"),
[
([], NULL_VARIANT_HASH),
([], hashlib.sha256(b"").hexdigest()[:VARIANT_HASH_LEN]),
(["a::b::c"], "01a9783a"),
(["d::e::f"], "41665eee"),
(["a::b::c", "d::e::f"], "eb9a66a7"),
Expand Down
12 changes: 8 additions & 4 deletions tests/commands/test_make_variant.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from tests.utils import assert_zips_equal
from variantlib.commands.main import main
from variantlib.constants import NULL_VARIANT_HASH
from variantlib.constants import NULL_VARIANT_LABEL

if TYPE_CHECKING:
from pathlib import Path
Expand Down Expand Up @@ -37,7 +37,7 @@ def mocked_plugin_reqs(
("label", "properties"),
[
# Null Variant
(NULL_VARIANT_HASH, None),
(NULL_VARIANT_LABEL, None),
# Variant 1
(
"5d8be4b9",
Expand Down Expand Up @@ -86,7 +86,7 @@ def test_make_variant(
itertools.chain.from_iterable(["-p", vprop] for vprop in properties)
)

if len(label) != 8:
if len(label) != 8 and label != "null":
cmd_args.append(f"--variant-label={label}")

main([*cmd_args])
Expand All @@ -112,9 +112,13 @@ def test_make_variant(
["--property=x::y::z", "--variant-label=123456789"],
"error: invalid variant label",
),
(
["--property=x::y::z", "--variant-label=null"],
"error: invalid variant label",
),
(
["--null-variant", "--variant-label=null"],
"error: --variant-label cannot be usedwith --null-variant",
"error: --variant-label cannot be used with --null-variant",
),
],
)
Expand Down
4 changes: 2 additions & 2 deletions tests/commands/test_unmake_variant.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

from tests.utils import assert_zips_equal
from variantlib.commands.main import main
from variantlib.constants import NULL_VARIANT_HASH
from variantlib.constants import NULL_VARIANT_LABEL


@pytest.mark.parametrize(
"filename",
[
f"test_package-0-py3-none-any-{NULL_VARIANT_HASH}.whl",
f"test_package-0-py3-none-any-{NULL_VARIANT_LABEL}.whl",
"test_package-0-py3-none-any-5d8be4b9.whl",
"test_package-0-py3-none-any-60567bd9.whl",
"test_package-0-py3-none-any-fbe82642.whl",
Expand Down
3 changes: 1 addition & 2 deletions tests/models/test_variant.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from hypothesis import given
from hypothesis import strategies as st

from variantlib.constants import NULL_VARIANT_HASH
from variantlib.constants import VALIDATION_FEATURE_NAME_REGEX
from variantlib.constants import VALIDATION_NAMESPACE_REGEX
from variantlib.constants import VALIDATION_VALUE_REGEX
Expand Down Expand Up @@ -238,7 +237,7 @@ def test_variantprop_sorting() -> None:
def test_null_variant() -> None:
vdesc = VariantDescription()
assert vdesc.properties == []
assert vdesc.hexdigest == NULL_VARIANT_HASH
assert vdesc.hexdigest == hashlib.sha256(b"").hexdigest()[:VARIANT_HASH_LEN]


def test_variantdescription_initialization() -> None:
Expand Down
118 changes: 105 additions & 13 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import json
import re
import string
from collections.abc import Generator
from typing import TYPE_CHECKING
Expand All @@ -24,10 +25,11 @@
from variantlib.api import VariantValidationResult
from variantlib.api import check_variant_supported
from variantlib.api import get_variant_environment_dict
from variantlib.api import get_variant_label
from variantlib.api import get_variants_by_priority
from variantlib.api import make_variant_dist_info
from variantlib.api import validate_variant
from variantlib.constants import NULL_VARIANT_HASH
from variantlib.constants import NULL_VARIANT_LABEL
from variantlib.constants import PYPROJECT_TOML_TOP_KEY
from variantlib.constants import VALIDATION_FEATURE_NAME_REGEX
from variantlib.constants import VALIDATION_NAMESPACE_REGEX
Expand Down Expand Up @@ -130,10 +132,10 @@ def test_get_variants_by_priority_roundtrip(
},
VARIANTS_JSON_VARIANT_DATA_KEY: {
f"foo{vdesc.hexdigest[:4]}"
if custom_labels and vdesc.hexdigest != NULL_VARIANT_HASH
else vdesc.hexdigest: vdesc.to_dict()
if custom_labels and not vdesc.is_null_variant()
else get_variant_label(vdesc): vdesc.to_dict()
for vdesc in combinations
if explicit_null or vdesc.hexdigest != NULL_VARIANT_HASH
if explicit_null or not vdesc.is_null_variant()
},
}

Expand All @@ -146,8 +148,8 @@ def test_get_variants_by_priority_roundtrip(

assert get_variants_by_priority(variants_json=typed_variants_json) == [
f"foo{vdesc.hexdigest[:4]}"
if custom_labels and vdesc.hexdigest != NULL_VARIANT_HASH
else vdesc.hexdigest
if custom_labels and not vdesc.is_null_variant()
else get_variant_label(vdesc)
for vdesc in combinations
]

Expand Down Expand Up @@ -220,7 +222,7 @@ def get_or_skip_combinations() -> Generator[VariantDescription]:

variants_json = {
VARIANTS_JSON_VARIANT_DATA_KEY: {
vdesc.hexdigest: vdesc.to_dict() for vdesc in combinations
get_variant_label(vdesc): vdesc.to_dict() for vdesc in combinations
}
}

Expand All @@ -234,7 +236,7 @@ def get_or_skip_combinations() -> Generator[VariantDescription]:
).return_value = {provider_cfg.namespace: provider_cfg for provider_cfg in configs}

assert get_variants_by_priority(variants_json=typed_variants_json) == [
vdesc.hexdigest for vdesc in combinations
get_variant_label(vdesc) for vdesc in combinations
]


Expand Down Expand Up @@ -550,24 +552,114 @@ def test_get_variant_environment_dict() -> None:

def test_make_variant_dist_info_invalid_label():
with pytest.raises(
ValidationError, match=r"Variant label cannot be specified for the null variant"
ValidationError,
match=rf"Null variant must always use {NULL_VARIANT_LABEL!r} label",
):
make_variant_dist_info(VariantDescription([]), variant_label="foo")
with pytest.raises(
ValidationError,
match=rf"{NULL_VARIANT_HASH} label can be used only for the null variant",
match=rf"{NULL_VARIANT_LABEL!r} label can be used only for the null variant",
):
make_variant_dist_info(
VariantDescription([VariantProperty("a", "b", "c")]),
variant_label=NULL_VARIANT_HASH,
variant_label=NULL_VARIANT_LABEL,
)
with pytest.raises(ValidationError, match=r"Invalid variant label: foo/bar"):
with pytest.raises(
ValidationError,
match=re.escape(
"Invalid variant label: 'foo/bar' (must be up to 8 alphanumeric characters)"
),
):
make_variant_dist_info(
VariantDescription([VariantProperty("a", "b", "c")]),
variant_label="foo/bar",
)
with pytest.raises(ValidationError, match=r"Invalid variant label: 123456789"):
with pytest.raises(
ValidationError,
match=re.escape(
"Invalid variant label: '123456789' (must be up to 8 alphanumeric "
"characters)"
),
):
make_variant_dist_info(
VariantDescription([VariantProperty("a", "b", "c")]),
variant_label="123456789",
)


def test_get_variant_label() -> None:
assert get_variant_label(VariantDescription()) == NULL_VARIANT_LABEL
assert (
get_variant_label(VariantDescription(), NULL_VARIANT_LABEL)
== 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")]
)
)
== "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")]
),
"foo",
)
== "foo"
)

with pytest.raises(
ValidationError,
match=rf"Null variant must always use {NULL_VARIANT_LABEL!r} label",
):
get_variant_label(VariantDescription([]), "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")]),
NULL_VARIANT_LABEL,
)
with pytest.raises(
ValidationError,
match=re.escape(
"Invalid variant label: 'foo/bar' (must be up to 8 alphanumeric characters)"
),
):
get_variant_label(
VariantDescription([VariantProperty("a", "b", "c")]),
"foo/bar",
)
with pytest.raises(
ValidationError,
match=re.escape(
"Invalid variant label: '123456789' (must be up to 8 alphanumeric "
"characters)"
),
):
get_variant_label(
VariantDescription([VariantProperty("a", "b", "c")]),
"123456789",
)
Loading
Loading