arborist/tests/test_weights.py
russell@unturf.com 3ef8975623
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.
2026-05-10 12:41:49 -04:00

180 lines
6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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