Skip to content
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
93 changes: 89 additions & 4 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from __future__ import annotations

import copy
import json
import string
from collections.abc import Generator
from contextlib import nullcontext
from typing import TYPE_CHECKING

import pytest
Expand All @@ -27,6 +29,7 @@
from variantlib.constants import VARIANT_INFO_DEFAULT_PRIO_KEY
from variantlib.constants import VARIANT_INFO_NAMESPACE_KEY
from variantlib.constants import VARIANT_INFO_PROVIDER_BUILD_REQUIRES_KEY
from variantlib.constants import VARIANT_INFO_PROVIDER_BUILTIN_KEY
from variantlib.constants import VARIANT_INFO_PROVIDER_DATA_KEY
from variantlib.constants import VARIANT_INFO_PROVIDER_FEATURE_ORDER_KEY
from variantlib.constants import VARIANT_INFO_PROVIDER_OPTIONAL_KEY
Expand Down Expand Up @@ -346,7 +349,7 @@ def test_validate_variant(optional: bool) -> None:
@pytest.mark.parametrize("pyproject_toml", [None, PYPROJECT_TOML])
@pytest.mark.parametrize("label", ["foo", "xy1.2"])
def test_make_variant_dist_info(
pyproject_toml: VariantsJsonDict | None,
pyproject_toml: dict[str, VariantsJsonDict] | None,
label: str,
) -> None:
expected: VariantsJsonDict = {
Expand All @@ -367,14 +370,23 @@ def test_make_variant_dist_info(
}

if pyproject_toml is not None:
# we can't have build-requires in JSON, change it to requires
pyproject_toml = copy.deepcopy(pyproject_toml)
ns2_prov = pyproject_toml[PYPROJECT_TOML_TOP_KEY][
VARIANT_INFO_PROVIDER_DATA_KEY
]["ns2"]
ns2_prov[VARIANT_INFO_PROVIDER_REQUIRES_KEY] = ns2_prov.pop(
VARIANT_INFO_PROVIDER_BUILD_REQUIRES_KEY
)

expected[VARIANT_INFO_PROVIDER_DATA_KEY].update(
{
"ns1": {
VARIANT_INFO_PROVIDER_REQUIRES_KEY: ["ns1-provider >= 1.2.3"],
VARIANT_INFO_PROVIDER_PLUGIN_API_KEY: "ns1_provider.plugin:NS1Plugin", # noqa: E501
},
"ns2": {
VARIANT_INFO_PROVIDER_BUILD_REQUIRES_KEY: [
VARIANT_INFO_PROVIDER_REQUIRES_KEY: [
"ns2_provider; python_version >= '3.11'",
"old_ns2_provider; python_version < '3.11'",
],
Expand All @@ -388,11 +400,14 @@ def test_make_variant_dist_info(
},
VARIANT_INFO_PROVIDER_FEATURE_ORDER_KEY: ["f2", "f1"],
},
"ns4": {
VARIANT_INFO_PROVIDER_BUILTIN_KEY: "example",
},
}
)
expected[VARIANT_INFO_DEFAULT_PRIO_KEY].update(
{
VARIANT_INFO_NAMESPACE_KEY: ["ns1", "ns2", "ns3"],
VARIANT_INFO_NAMESPACE_KEY: ["ns1", "ns2", "ns3", "ns4"],
},
)

Expand All @@ -407,7 +422,7 @@ def test_make_variant_dist_info(
],
label=label,
),
variant_info=VariantPyProjectToml(pyproject_toml) # type: ignore[arg-type]
variant_info=VariantPyProjectToml(pyproject_toml)
if pyproject_toml is not None
else None,
variant_label=label,
Expand Down Expand Up @@ -774,3 +789,73 @@ def test_make_variant_dist_info_really_invalid_build_plugin() -> None:
variant_info=vinfo,
expand_aot_plugin_properties=True,
)


@pytest.mark.parametrize("provider", ["builtin", "static"])
@pytest.mark.parametrize(
"function",
[
"get_variants_by_priority",
"validate_variant",
"check_variant_supported",
],
)
def test_builtin_provider(provider: str, function: str) -> None:
variants_json = VariantsJson(
{
VARIANT_INFO_SCHEMA_KEY: VARIANT_INFO_SCHEMA_URL,
VARIANT_INFO_DEFAULT_PRIO_KEY: {
VARIANT_INFO_NAMESPACE_KEY: ["builtin", "static"],
},
VARIANT_INFO_PROVIDER_DATA_KEY: {
"builtin": {
VARIANT_INFO_PROVIDER_BUILTIN_KEY: "test",
},
"static": {
VARIANT_INFO_PROVIDER_STATIC_PROPERTIES_KEY: {
"feature": ["value"],
},
},
},
VARIANT_INFO_VARIANT_DATA_KEY: {
"test": {
provider: {
"feature": ["value"],
},
}
},
}
)

expected = (
pytest.raises(RuntimeError, match=r"Builtin providers are not supported")
if provider == "builtin"
else nullcontext()
)
with expected:
if function == "get_variants_by_priority":
assert get_variants_by_priority(variants_json=variants_json) == [
VariantDescription(
label="test",
properties=[VariantProperty("static", "feature", "value")],
),
VariantDescription(),
]
elif function == "validate_variant":
assert validate_variant(
variants_json.variants["test"], variants_json
) == VariantValidationResult(
{
VariantProperty(
namespace="static", feature="feature", value="value"
): True
},
frozenset(),
)
elif function == "check_variant_supported":
assert (
check_variant_supported(
vdesc=variants_json.variants["test"], variant_info=variants_json
)
== variants_json.variants["test"]
)
115 changes: 48 additions & 67 deletions tests/test_pyproject_toml.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import itertools
import sys
from typing import TYPE_CHECKING

Expand All @@ -9,6 +10,7 @@
from variantlib.constants import VARIANT_INFO_DEFAULT_PRIO_KEY
from variantlib.constants import VARIANT_INFO_NAMESPACE_KEY
from variantlib.constants import VARIANT_INFO_PROVIDER_BUILD_REQUIRES_KEY
from variantlib.constants import VARIANT_INFO_PROVIDER_BUILTIN_KEY
from variantlib.constants import VARIANT_INFO_PROVIDER_DATA_KEY
from variantlib.constants import VARIANT_INFO_PROVIDER_FEATURE_ORDER_KEY
from variantlib.constants import VARIANT_INFO_PROVIDER_OPTIONAL_KEY
Expand Down Expand Up @@ -38,7 +40,7 @@
version = "1.2.3"

[{PYPROJECT_TOML_TOP_KEY}.{VARIANT_INFO_DEFAULT_PRIO_KEY}]
{VARIANT_INFO_NAMESPACE_KEY} = ["ns1", "ns2", "ns3"]
{VARIANT_INFO_NAMESPACE_KEY} = ["ns1", "ns2", "ns3", "ns4"]

[{PYPROJECT_TOML_TOP_KEY}.{VARIANT_INFO_PROVIDER_DATA_KEY}.ns1]
{VARIANT_INFO_PROVIDER_REQUIRES_KEY} = ["ns1-provider >= 1.2.3"]
Expand All @@ -58,6 +60,9 @@
[{PYPROJECT_TOML_TOP_KEY}.{VARIANT_INFO_PROVIDER_DATA_KEY}.ns3.{VARIANT_INFO_PROVIDER_STATIC_PROPERTIES_KEY}]
f1 = ["v1", "v2"]
f2 = ["v3", "v4"]

[{PYPROJECT_TOML_TOP_KEY}.{VARIANT_INFO_PROVIDER_DATA_KEY}.ns4]
{VARIANT_INFO_PROVIDER_BUILTIN_KEY} = "example"
"""

TOML_DATA = (
Expand All @@ -78,7 +83,7 @@

def test_pyproject_toml() -> None:
pyproj = VariantPyProjectToml(PYPROJECT_TOML)
assert pyproj.namespace_priorities == ["ns1", "ns2", "ns3"]
assert pyproj.namespace_priorities == ["ns1", "ns2", "ns3", "ns4"]
assert pyproj.providers == {
"ns1": ProviderInfo(
requires=["ns1-provider >= 1.2.3"],
Expand All @@ -96,6 +101,9 @@ def test_pyproject_toml() -> None:
static_properties={"f1": ["v1", "v2"], "f2": ["v3", "v4"]},
feature_order=["f2", "f1"],
),
"ns4": ProviderInfo(
builtin="example",
),
}
assert pyproj.variants == {
"var1": VariantDescription(
Expand All @@ -117,7 +125,7 @@ def test_pyproject_toml() -> None:

def test_pyproject_toml_minimal() -> None:
pyproj = VariantPyProjectToml(tomllib.loads(TOML_DATA_MINIMAL))
assert pyproj.namespace_priorities == ["ns1", "ns2", "ns3"]
assert pyproj.namespace_priorities == ["ns1", "ns2", "ns3", "ns4"]
assert pyproj.providers == {
"ns1": ProviderInfo(
requires=["ns1-provider >= 1.2.3"],
Expand All @@ -135,6 +143,9 @@ def test_pyproject_toml_minimal() -> None:
static_properties={"f1": ["v1", "v2"], "f2": ["v3", "v4"]},
feature_order=["f2", "f1"],
),
"ns4": ProviderInfo(
builtin="example",
),
}
assert pyproj.variants == {}

Expand Down Expand Up @@ -316,30 +327,6 @@ def test_invalid_provider_plugin_api() -> None:
)


def test_missing_required_key() -> None:
with pytest.raises(
ValidationError,
match=rf"{PYPROJECT_TOML_TOP_KEY}\.{VARIANT_INFO_PROVIDER_DATA_KEY}\.ns: "
rf"exactly one of {VARIANT_INFO_PROVIDER_REQUIRES_KEY}, "
rf"{VARIANT_INFO_PROVIDER_STATIC_PROPERTIES_KEY} or "
rf"{VARIANT_INFO_PROVIDER_BUILD_REQUIRES_KEY} must be specified",
):
VariantPyProjectToml(
{
PYPROJECT_TOML_TOP_KEY: {
VARIANT_INFO_DEFAULT_PRIO_KEY: {
VARIANT_INFO_NAMESPACE_KEY: ["ns"],
},
VARIANT_INFO_PROVIDER_DATA_KEY: {
"ns": {
VARIANT_INFO_PROVIDER_OPTIONAL_KEY: False,
}
},
}
}
)


def test_missing_namespace_priority() -> None:
with pytest.raises(
ValidationError,
Expand Down Expand Up @@ -432,11 +419,11 @@ def test_conversion(cls: type[VariantPyProjectToml | VariantsJson]) -> None:
converted = cls(pyproj)

# Mangle the original to ensure everything was copied
pyproj.namespace_priorities.append("ns4")
pyproj.providers["ns4"] = ProviderInfo(requires=["foo"], plugin_api="foo:bar")
pyproj.namespace_priorities.append("ns9")
pyproj.providers["ns9"] = ProviderInfo(requires=["foo"], plugin_api="foo:bar")
pyproj.providers["ns2"].requires.append("frobnicate")

assert converted.namespace_priorities == ["ns1", "ns2", "ns3"]
assert converted.namespace_priorities == ["ns1", "ns2", "ns3", "ns4"]
assert converted.providers == {
"ns1": ProviderInfo(
requires=["ns1-provider >= 1.2.3"],
Expand All @@ -454,6 +441,9 @@ def test_conversion(cls: type[VariantPyProjectToml | VariantsJson]) -> None:
static_properties={"f1": ["v1", "v2"], "f2": ["v3", "v4"]},
feature_order=["f2", "f1"],
),
"ns4": ProviderInfo(
builtin="example",
),
}

# Non-common fields should be reset to defaults
Expand Down Expand Up @@ -545,41 +535,30 @@ def test_static_properties_missing_priorities() -> None:
)


@pytest.mark.parametrize(
"requires_key",
[VARIANT_INFO_PROVIDER_REQUIRES_KEY, VARIANT_INFO_PROVIDER_BUILD_REQUIRES_KEY],
)
def test_static_properties_and_requires(requires_key: str) -> None:
with pytest.raises(
ValidationError,
match=rf"{PYPROJECT_TOML_TOP_KEY}\.{VARIANT_INFO_PROVIDER_DATA_KEY}\.ns: "
rf"exactly one of {VARIANT_INFO_PROVIDER_REQUIRES_KEY}, "
rf"{VARIANT_INFO_PROVIDER_STATIC_PROPERTIES_KEY} or "
rf"{VARIANT_INFO_PROVIDER_BUILD_REQUIRES_KEY} must be specified",
):
VariantPyProjectToml(
{
PYPROJECT_TOML_TOP_KEY: {
VARIANT_INFO_DEFAULT_PRIO_KEY: {
VARIANT_INFO_NAMESPACE_KEY: ["ns"],
},
VARIANT_INFO_PROVIDER_DATA_KEY: {
"ns": {
requires_key: ["example"],
VARIANT_INFO_PROVIDER_STATIC_PROPERTIES_KEY: {"f": ["v"]},
}
},
}
}
)
PROVIDER_TEST_VALUES = {
VARIANT_INFO_PROVIDER_REQUIRES_KEY: ["test"],
VARIANT_INFO_PROVIDER_BUILD_REQUIRES_KEY: ["test"],
VARIANT_INFO_PROVIDER_STATIC_PROPERTIES_KEY: {"f": ["v"]},
VARIANT_INFO_PROVIDER_BUILTIN_KEY: "test",
}


def test_double_requires() -> None:
@pytest.mark.parametrize(
"keys",
[
(),
*itertools.combinations(PROVIDER_TEST_VALUES, 2),
*itertools.combinations(PROVIDER_TEST_VALUES, 3),
*itertools.combinations(PROVIDER_TEST_VALUES, 4),
],
)
def test_mutually_exclusive_keys(keys: tuple[str, ...]) -> None:
with pytest.raises(
ValidationError,
match=rf"{PYPROJECT_TOML_TOP_KEY}\.{VARIANT_INFO_PROVIDER_DATA_KEY}\.ns: "
rf"exactly one of {VARIANT_INFO_PROVIDER_REQUIRES_KEY}, "
rf"{VARIANT_INFO_PROVIDER_STATIC_PROPERTIES_KEY} or "
rf"{VARIANT_INFO_PROVIDER_STATIC_PROPERTIES_KEY}, "
rf"{VARIANT_INFO_PROVIDER_BUILTIN_KEY} or "
rf"{VARIANT_INFO_PROVIDER_BUILD_REQUIRES_KEY} must be specified",
):
VariantPyProjectToml(
Expand All @@ -589,22 +568,24 @@ def test_double_requires() -> None:
VARIANT_INFO_NAMESPACE_KEY: ["ns"],
},
VARIANT_INFO_PROVIDER_DATA_KEY: {
"ns": {
VARIANT_INFO_PROVIDER_BUILD_REQUIRES_KEY: ["example"],
VARIANT_INFO_PROVIDER_REQUIRES_KEY: ["example"],
}
"ns": {key: PROVIDER_TEST_VALUES[key] for key in keys}
},
}
}
)


def test_static_properties_and_plugin_api() -> None:
@pytest.mark.parametrize(
"key",
[VARIANT_INFO_PROVIDER_STATIC_PROPERTIES_KEY, VARIANT_INFO_PROVIDER_BUILTIN_KEY],
)
def test_unexpected_plugin_api(key: str) -> None:
with pytest.raises(
ValidationError,
match=rf"{PYPROJECT_TOML_TOP_KEY}\.{VARIANT_INFO_PROVIDER_DATA_KEY}\.ns: "
rf"{VARIANT_INFO_PROVIDER_PLUGIN_API_KEY} is not valid with "
rf"{VARIANT_INFO_PROVIDER_STATIC_PROPERTIES_KEY}",
rf"{VARIANT_INFO_PROVIDER_PLUGIN_API_KEY} is valid only with "
rf"{VARIANT_INFO_PROVIDER_REQUIRES_KEY} or "
rf"{VARIANT_INFO_PROVIDER_BUILD_REQUIRES_KEY}",
):
VariantPyProjectToml(
{
Expand All @@ -615,7 +596,7 @@ def test_static_properties_and_plugin_api() -> None:
VARIANT_INFO_PROVIDER_DATA_KEY: {
"ns": {
VARIANT_INFO_PROVIDER_PLUGIN_API_KEY: "example",
VARIANT_INFO_PROVIDER_STATIC_PROPERTIES_KEY: {"f": ["v"]},
key: PROVIDER_TEST_VALUES[key],
}
},
}
Expand Down
Loading
Loading