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 3 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
25 changes: 17 additions & 8 deletions tests/commands/test_make_variant.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

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
from variantlib.errors import ValidationError

if TYPE_CHECKING:
from pathlib import Path
Expand Down Expand Up @@ -37,7 +38,7 @@ def mocked_plugin_reqs(
("label", "properties"),
[
# Null Variant
(NULL_VARIANT_HASH, None),
(NULL_VARIANT_LABEL, None),
# Variant 1
(
"5d8be4b9",
Expand Down Expand Up @@ -109,12 +110,17 @@ def test_make_variant(
([], "error: one of the arguments -p/--property --null-variant is required"),
(["--property=x::y"], "argument -p/--property: invalid from_str value"),
(
["--property=x::y::z", "--variant-label=123456789"],
"error: invalid variant label",
[
"--property=x::y::z",
"--skip-plugin-validation",
"--variant-label=123456789",
],
"Invalid variant label: '123456789' (must be up to 8 alphanumeric "
"characters)",
),
(
["--null-variant", "--variant-label=null"],
"error: --variant-label cannot be usedwith --null-variant",
["--null-variant", "--variant-label=zuul"],
"Null variant must always use 'null' label",
),
],
)
Expand All @@ -128,7 +134,7 @@ def test_make_variant_error(
) -> None:
pyproject_f = test_artifact_path / "test-package/pyproject.toml"

with pytest.raises(SystemExit):
with pytest.raises((SystemExit, ValidationError)) as e:
main(
[
"make-variant",
Expand All @@ -142,4 +148,7 @@ def test_make_variant_error(
]
)

assert error in capsys.readouterr().err
if isinstance(e.value, SystemExit):
assert error in capsys.readouterr().err
else:
assert error in str(e.value)
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
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",
)
12 changes: 6 additions & 6 deletions tests/test_variants_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import pytest

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_FEATURE_KEY
from variantlib.constants import VARIANT_INFO_NAMESPACE_KEY
Expand Down Expand Up @@ -45,7 +45,7 @@ def test_validate_variants_json() -> None:

variants_json = VariantsJson(data)
assert variants_json.variants == {
NULL_VARIANT_HASH: VariantDescription(),
NULL_VARIANT_LABEL: VariantDescription(),
"03e04d5e": VariantDescription(
properties=[
VariantProperty(
Expand Down Expand Up @@ -503,12 +503,12 @@ def test_merge_variants() -> None:
def test_null_variant_label():
with pytest.raises(
ValidationError,
match=rf"{NULL_VARIANT_HASH} label can only be used for the null variant",
match=rf"{NULL_VARIANT_LABEL!r} label can only be used for the null variant",
):
VariantsJson(
{VARIANTS_JSON_VARIANT_DATA_KEY: {NULL_VARIANT_HASH: {"x": {"y": ["z"]}}}}
{VARIANTS_JSON_VARIANT_DATA_KEY: {NULL_VARIANT_LABEL: {"x": {"y": ["z"]}}}}
)
with pytest.raises(
ValidationError, match=rf"Null variant must use {NULL_VARIANT_HASH} label"
ValidationError, match=rf"Null variant must use {NULL_VARIANT_LABEL!r} label"
):
VariantsJson({VARIANTS_JSON_VARIANT_DATA_KEY: {"null": {}}})
VariantsJson({VARIANTS_JSON_VARIANT_DATA_KEY: {"zuul": {}}})
Loading
Loading