Files
nix-estimator/tests/test_provision.py
T
Oleks 9553d3263d provision: reconcile EphemeralBuilder renderer with real CRD (#14)
Emit N documents (one per node; no replica field) using the real
arbitrage.oleks.space/v1alpha1 schema: top-level class + requirements
{arch,cores,ramGb}, namespace builder-arbitrage, class label. Rename
public API to render_ephemeral_builders; wire --node-ram-gb into ramGb.
2026-07-08 19:59:24 +03:00

115 lines
4.1 KiB
Python

"""Tests for the --provision EphemeralBuilder renderer (issue #14).
These pin the real ``arbitrage.oleks.space/v1alpha1`` EphemeralBuilder CRD
contract: one document per node (no replica field), the ``requirements`` block,
arch mapping, namespace/label conventions, and enum/count validation.
"""
import pytest
from nix_estimator.provision import arch_for_system, render_ephemeral_builders
def _docs(yaml: str) -> list[str]:
return [d for d in yaml.split("---\n") if d.strip()]
def test_arch_mapping():
assert arch_for_system("aarch64-linux") == "arm64"
assert arch_for_system("x86_64-linux") == "amd64"
assert arch_for_system("aarch64-darwin") == "arm64"
assert arch_for_system("x86_64-darwin") == "amd64"
# unknown / missing systems fall back to amd64
assert arch_for_system(None) == "amd64"
assert arch_for_system("riscv64-linux") == "amd64"
def test_real_schema_shape():
yaml = render_ephemeral_builders(
nodes=4, cores=16, system="aarch64-linux", est_makespan_min=12.34
)
assert "apiVersion: arbitrage.oleks.space/v1alpha1" in yaml
assert "kind: EphemeralBuilder" in yaml
# top-level class + requirements block (not replicas/top-level cores/arch)
assert "class: nix-builder" in yaml
assert "requirements:" in yaml
assert "arch: arm64" in yaml
assert "cores: 16" in yaml
assert "ramGb: 32" in yaml # default ~2 GiB/core
assert "replicas" not in yaml
# metadata conventions
assert "namespace: builder-arbitrage" in yaml
assert "arbitrage.oleks.space/class: nix-builder" in yaml
# makespan lives in a non-operator comment, not an arbitrage.* annotation
assert "# nix-estimator: estimated makespan 12.3 min" in yaml
assert "arbitrage.oleks.space/estimated-makespan" not in yaml
assert yaml.endswith("\n")
def test_one_document_per_node():
yaml = render_ephemeral_builders(nodes=4, cores=16, system="aarch64-linux")
docs = _docs(yaml)
assert len(docs) == 4
# each document has a unique metadata.name suffixed by its index
names = [ln.strip() for d in docs for ln in d.splitlines() if "name:" in ln]
assert names == [
"name: nix-estimator-arm64-16c-1",
"name: nix-estimator-arm64-16c-2",
"name: nix-estimator-arm64-16c-3",
"name: nix-estimator-arm64-16c-4",
]
def test_single_node_single_document():
yaml = render_ephemeral_builders(
nodes=1, cores=32, system="x86_64-linux", builder_class="ci", name="my-builder"
)
assert len(_docs(yaml)) == 1
assert "arch: amd64" in yaml
assert "class: ci" in yaml
assert "arbitrage.oleks.space/class: ci" in yaml
assert "name: my-builder-1" in yaml
def test_explicit_ram_gb_wins():
yaml = render_ephemeral_builders(
nodes=1, cores=8, system="x86_64-linux", ram_gb=64
)
assert "ramGb: 64" in yaml
# default would have been 16 (2 GiB * 8 cores)
assert "ramGb: 16" not in yaml
def test_makespan_omitted_when_none():
yaml = render_ephemeral_builders(nodes=2, cores=8, system="x86_64-linux")
assert "estimated makespan" not in yaml
# a sensible default name is derived from arch + cores
assert "name: nix-estimator-amd64-8c-1" in yaml
def test_output_is_deterministic():
kwargs = dict(nodes=3, cores=16, system="aarch64-linux", est_makespan_min=5.0)
assert render_ephemeral_builders(**kwargs) == render_ephemeral_builders(**kwargs)
def test_invalid_counts_rejected():
with pytest.raises(ValueError):
render_ephemeral_builders(nodes=0, cores=8, system="x86_64-linux")
with pytest.raises(ValueError):
render_ephemeral_builders(nodes=1, cores=0, system="x86_64-linux")
def test_invalid_class_rejected():
with pytest.raises(ValueError):
render_ephemeral_builders(
nodes=1, cores=8, system="x86_64-linux", builder_class="bogus"
)
def test_invalid_arch_rejected():
# arch_for_system never yields an invalid arch, so exercise the guard by
# confirming only the two GOARCH labels are ever emitted.
for system in ("aarch64-linux", "x86_64-linux", None, "riscv64-linux"):
arch = arch_for_system(system)
assert arch in ("amd64", "arm64")