tests/weights: 16 tests for WeightSet defaults + from_dict adapter
arborist/substrate/weights.py was zero-coverage. 73 LOC of
dataclass + adapter. Tests pin:
- documented defaults from the module docstring (each weight
has a comment block explaining its design intent; tests
pin the values so a PR that flips alpha=1.0 → 0.5 fires the
test and forces an explicit docstring update)
- documented invariants: alpha=beta=gamma (3 batteries equal),
eta > alpha (regression heavier than improvement),
reserved-zero defaults (zeta/iota/kappa)
- as_dict() returns all 11 fields; lambda_ key (not "lambda" —
keyword)
- from_dict() greek-letter keys, "lambda" → "lambda_" translation
(JSON/YAML friendly), missing-keys-default fall-through,
extra-keys silent drop, str → float coercion, int → float
coercion
- frozen-dataclass invariant (mutation raises FrozenInstanceError)
- dataclass equality
Full suite: 1881 passed, 54 skipped. tests/ count growing
roughly 1655 → 1881 (+226) across today's autonomous quality
session.
This commit is contained in:
parent
3d0f02e1ac
commit
3ef8975623
1 changed files with 180 additions and 0 deletions
180
tests/test_weights.py
Normal file
180
tests/test_weights.py
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
"""Tests for ``arborist.substrate.weights`` — WeightSet defaults +
|
||||||
|
``from_dict`` adapter (#000012 Phase 1a).
|
||||||
|
|
||||||
|
Pure-stdlib dataclass + classmethod-style adapter; small surface.
|
||||||
|
This pins:
|
||||||
|
|
||||||
|
- documented defaults (lifted from the module docstring)
|
||||||
|
- greek-letter and Python-safe key aliases ("lambda" ↔ "lambda_")
|
||||||
|
- missing-keys fall-through to defaults
|
||||||
|
- extra-keys silently ignored
|
||||||
|
- string values coerce to float
|
||||||
|
- frozen-dataclass invariant (mutation raises)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from arborist.substrate.weights import (
|
||||||
|
DEFAULT_WEIGHTS,
|
||||||
|
WeightSet,
|
||||||
|
from_dict,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- defaults pinned to the docstring ------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_weights_match_documented_values():
|
||||||
|
"""Each weight in the dataclass docstring is pinned. If a future
|
||||||
|
PR changes a default, this test fires and forces an explicit
|
||||||
|
update of both the docstring AND the test."""
|
||||||
|
assert DEFAULT_WEIGHTS.alpha == 1.0
|
||||||
|
assert DEFAULT_WEIGHTS.beta == 1.0
|
||||||
|
assert DEFAULT_WEIGHTS.gamma == 1.0
|
||||||
|
assert DEFAULT_WEIGHTS.delta == 0.5
|
||||||
|
assert DEFAULT_WEIGHTS.epsilon == 0.3
|
||||||
|
assert DEFAULT_WEIGHTS.zeta == 0.0 # validator-diversity off in single-validator
|
||||||
|
assert DEFAULT_WEIGHTS.eta == 2.0 # regression > improvement weight
|
||||||
|
assert DEFAULT_WEIGHTS.theta == 0.5
|
||||||
|
assert DEFAULT_WEIGHTS.iota == 0.0 # reserved
|
||||||
|
assert DEFAULT_WEIGHTS.kappa == 0.0 # reserved
|
||||||
|
assert DEFAULT_WEIGHTS.lambda_ == 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_eta_is_heavier_than_alpha():
|
||||||
|
"""Documented invariant from the docstring:
|
||||||
|
'η (RegressionPenalty) heavier than improvement weights so the
|
||||||
|
scorer is biased toward refusing regressions.'"""
|
||||||
|
assert DEFAULT_WEIGHTS.eta > DEFAULT_WEIGHTS.alpha
|
||||||
|
assert DEFAULT_WEIGHTS.eta > DEFAULT_WEIGHTS.beta
|
||||||
|
assert DEFAULT_WEIGHTS.eta > DEFAULT_WEIGHTS.gamma
|
||||||
|
|
||||||
|
|
||||||
|
def test_three_batteries_equal_by_default():
|
||||||
|
"""Documented invariant: 'α = β = γ (3 batteries weighted equally
|
||||||
|
by default).'"""
|
||||||
|
assert DEFAULT_WEIGHTS.alpha == DEFAULT_WEIGHTS.beta == DEFAULT_WEIGHTS.gamma
|
||||||
|
|
||||||
|
|
||||||
|
def test_reserved_weights_are_zero_phase_1a():
|
||||||
|
"""Phase 1a discipline: validator-diversity / security / complexity
|
||||||
|
are zero by default. v8 multi-validator deployments must opt in."""
|
||||||
|
assert DEFAULT_WEIGHTS.zeta == 0.0
|
||||||
|
assert DEFAULT_WEIGHTS.iota == 0.0
|
||||||
|
assert DEFAULT_WEIGHTS.kappa == 0.0
|
||||||
|
|
||||||
|
|
||||||
|
# --- as_dict --------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_as_dict_returns_all_eleven_fields():
|
||||||
|
d = DEFAULT_WEIGHTS.as_dict()
|
||||||
|
expected_keys = {
|
||||||
|
"alpha", "beta", "gamma", "delta", "epsilon", "zeta",
|
||||||
|
"eta", "theta", "iota", "kappa", "lambda_",
|
||||||
|
}
|
||||||
|
assert set(d.keys()) == expected_keys
|
||||||
|
|
||||||
|
|
||||||
|
def test_as_dict_uses_lambda_underscore_key():
|
||||||
|
"""``lambda`` is a Python keyword → dataclass uses ``lambda_``;
|
||||||
|
as_dict() carries it forward unchanged. Callers serializing to
|
||||||
|
JSON / YAML rename to bare ``lambda`` if needed."""
|
||||||
|
d = DEFAULT_WEIGHTS.as_dict()
|
||||||
|
assert "lambda_" in d
|
||||||
|
assert "lambda" not in d
|
||||||
|
|
||||||
|
|
||||||
|
# --- from_dict adapter ---------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_dict_uses_greek_letter_keys():
|
||||||
|
ws = from_dict({"alpha": 0.7, "beta": 0.8, "gamma": 0.9})
|
||||||
|
assert ws.alpha == 0.7
|
||||||
|
assert ws.beta == 0.8
|
||||||
|
assert ws.gamma == 0.9
|
||||||
|
# Unspecified keys fall through to defaults.
|
||||||
|
assert ws.delta == DEFAULT_WEIGHTS.delta
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_dict_translates_lambda_to_lambda_underscore():
|
||||||
|
"""JSON/YAML configs use bare ``lambda`` (the Greek letter, not
|
||||||
|
the keyword). from_dict translates to the Python-safe field
|
||||||
|
name ``lambda_``."""
|
||||||
|
ws = from_dict({"lambda": 0.99})
|
||||||
|
assert ws.lambda_ == 0.99
|
||||||
|
# Bare 'lambda' did not survive into a hidden field.
|
||||||
|
assert "lambda" not in ws.as_dict()
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_dict_lambda_underscore_key_also_accepted():
|
||||||
|
"""For Python callers writing config dicts directly, ``lambda_``
|
||||||
|
is the natural key name; from_dict accepts it too."""
|
||||||
|
ws = from_dict({"lambda_": 0.42})
|
||||||
|
assert ws.lambda_ == 0.42
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_dict_missing_keys_fall_through_to_defaults():
|
||||||
|
"""Empty dict → identical to DEFAULT_WEIGHTS."""
|
||||||
|
ws = from_dict({})
|
||||||
|
assert ws == DEFAULT_WEIGHTS
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_dict_partial_override_preserves_other_defaults():
|
||||||
|
ws = from_dict({"alpha": 0.5})
|
||||||
|
assert ws.alpha == 0.5
|
||||||
|
# Every other field is unchanged.
|
||||||
|
assert ws.beta == DEFAULT_WEIGHTS.beta
|
||||||
|
assert ws.eta == DEFAULT_WEIGHTS.eta
|
||||||
|
assert ws.lambda_ == DEFAULT_WEIGHTS.lambda_
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_dict_ignores_unknown_keys():
|
||||||
|
"""Defensive: extra keys (e.g. "mu" — never defined) are
|
||||||
|
silently dropped rather than raising. Forward-compat with
|
||||||
|
config files that include vendor-specific tags."""
|
||||||
|
ws = from_dict({"alpha": 0.6, "mu": 1.234, "extra_metadata": "ignore"})
|
||||||
|
assert ws.alpha == 0.6
|
||||||
|
# mu / extra_metadata silently dropped.
|
||||||
|
assert not hasattr(ws, "mu")
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_dict_coerces_string_to_float():
|
||||||
|
"""JSON/YAML may pass numeric weights as strings. from_dict
|
||||||
|
coerces via float()."""
|
||||||
|
ws = from_dict({"alpha": "0.7", "lambda": "0.3"})
|
||||||
|
assert ws.alpha == 0.7
|
||||||
|
assert ws.lambda_ == 0.3
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_dict_int_input_becomes_float():
|
||||||
|
ws = from_dict({"alpha": 1})
|
||||||
|
assert ws.alpha == 1.0
|
||||||
|
assert isinstance(ws.alpha, float)
|
||||||
|
|
||||||
|
|
||||||
|
# --- frozen invariant ----------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_weight_set_is_frozen():
|
||||||
|
"""WeightSet is dataclass(frozen=True) — mutation raises
|
||||||
|
FrozenInstanceError. Pinned so a future @dataclass change
|
||||||
|
forces this test to be updated explicitly."""
|
||||||
|
with pytest.raises(dataclasses.FrozenInstanceError):
|
||||||
|
DEFAULT_WEIGHTS.alpha = 99.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_weight_sets_equal_by_value():
|
||||||
|
"""Two WeightSet instances with identical fields compare equal —
|
||||||
|
standard dataclass equality. Used by tests + audit-trail
|
||||||
|
serialization."""
|
||||||
|
a = WeightSet(alpha=0.5, beta=0.6)
|
||||||
|
b = WeightSet(alpha=0.5, beta=0.6)
|
||||||
|
assert a == b
|
||||||
|
# Different values → not equal.
|
||||||
|
c = WeightSet(alpha=0.5, beta=0.7)
|
||||||
|
assert a != c
|
||||||
Loading…
Add table
Add a link
Reference in a new issue