175 lines
6.1 KiB
Python
175 lines
6.1 KiB
Python
"""Unit tests for the cost model — trivial-glue tier and long-pole detection."""
|
||
|
||
from nix_estimator import costmodel, schedule
|
||
from nix_estimator.estimate import _recommend
|
||
|
||
|
||
def test_glue_derivations_are_trivial():
|
||
# NixOS assembly glue must not be costed like a real compile.
|
||
for name in [
|
||
"unit-dbus-broker.service",
|
||
"X-Restart-Triggers-dbus-broker",
|
||
"user-units",
|
||
"etc",
|
||
"activate",
|
||
"system-path",
|
||
"nixos-system-emmett-26.11",
|
||
"dbus-1",
|
||
"sshd.service",
|
||
]:
|
||
assert costmodel.is_trivial(name), name
|
||
mins, _ = costmodel.cost(f"/nix/store/hash-{name}.drv")
|
||
assert mins <= 0.1, (name, mins)
|
||
|
||
|
||
def test_real_packages_are_not_trivial():
|
||
for name in [
|
||
"onnxruntime-1.17.0",
|
||
"hello-2.12",
|
||
"openssl-3.0.0",
|
||
"python3.13-numpy-2.0",
|
||
]:
|
||
assert not costmodel.is_trivial(name), name
|
||
|
||
|
||
def test_glue_chain_does_not_dominate_span():
|
||
# A 100-deep glue chain used to fabricate a ~100-min span at DEFAULT=1min.
|
||
names = [f"unit-svc{i}.service" for i in range(100)]
|
||
dur = {n: costmodel.cost(f"/nix/store/h-{n}.drv")[0] for n in names}
|
||
preds = {n: ([names[i - 1]] if i else []) for i, n in enumerate(names)}
|
||
span, _ = schedule.critical_path(dur, preds)
|
||
assert span < 10.0 # 100 × 0.05 == 5 min, not 100
|
||
|
||
|
||
def _cost(name):
|
||
return costmodel.cost(f"/nix/store/hash-{name}.drv")
|
||
|
||
|
||
def test_heavy_substring_does_not_over_fire():
|
||
# Substring keys used to inflate ordinary libs/wrappers to HEAVY (issue #6).
|
||
for name in [
|
||
"python3.12-requests-2.31.0", # "python3" is a substring, not the pname
|
||
"util-linux-2.39", # "linux-" is a substring, not the pname
|
||
"gcc-wrapper-13", # toolchain shim
|
||
"rustc-wrapper", # toolchain shim
|
||
"clang-wrapper-16",
|
||
"grpcio-1.62", # not grpc
|
||
]:
|
||
mins, _ = _cost(name)
|
||
assert mins <= costmodel.DEFAULT[0], (name, mins)
|
||
|
||
|
||
def test_heavy_real_pnames_still_fire():
|
||
# Genuine heavy derivations must still hit the HEAVY table.
|
||
expected = {
|
||
"llvm-15.0.7": costmodel.HEAVY["llvm"],
|
||
"onnxruntime-1.16.0": costmodel.HEAVY["onnxruntime"],
|
||
"ghc-9.4.8": costmodel.HEAVY["ghc"],
|
||
"rustc-1.75.0": costmodel.HEAVY["rustc"], # the real compiler
|
||
"python3-3.12.7": costmodel.HEAVY["python3"], # the interpreter
|
||
"gcc-13.2.0": costmodel.HEAVY["gcc"],
|
||
"linux-6.6.30": costmodel.HEAVY["linux-"], # the kernel
|
||
}
|
||
for name, val in expected.items():
|
||
assert _cost(name) == val, name
|
||
|
||
|
||
def test_history_longest_prefix_wins_regardless_of_order():
|
||
# Overlapping prefixes must resolve deterministically to the longest match.
|
||
drv = "/nix/store/hash-gcc-13.2.0.drv"
|
||
for hist in (
|
||
{"gcc": 40.0, "gcc-13": 5.0},
|
||
{"gcc-13": 5.0, "gcc": 40.0},
|
||
):
|
||
mins, _ = costmodel.cost(drv, history=hist)
|
||
assert mins == 5.0, hist
|
||
|
||
|
||
def test_history_exact_name_wins_first():
|
||
drv = "/nix/store/hash-gcc-13.2.0.drv"
|
||
hist = {"gcc-13.2.0": 7.0, "gcc-13": 5.0, "gcc": 40.0}
|
||
mins, _ = costmodel.cost(drv, history=hist)
|
||
assert mins == 7.0
|
||
|
||
|
||
def test_default_min_override_only_moves_default_bucket():
|
||
# A DEFAULT-bucket lib picks up the override...
|
||
lib = "/nix/store/hash-somelib-1.2.3.drv"
|
||
assert costmodel.cost(lib)[0] == costmodel.DEFAULT[0]
|
||
mins, scaling = costmodel.cost(lib, default_min=3.0)
|
||
assert mins == 3.0
|
||
assert scaling == 0.30
|
||
|
||
# ...but HEAVY, fixed-output, trivial and history results are untouched.
|
||
heavy = "/nix/store/hash-llvm-15.0.7.drv"
|
||
assert costmodel.cost(heavy, default_min=3.0) == costmodel.HEAVY["llvm"]
|
||
|
||
fetch = "/nix/store/hash-source.drv"
|
||
fo_drv = {"outputs": {"out": {"hash": "abc", "hashAlgo": "sha256"}}}
|
||
assert costmodel.cost(fetch, drv=fo_drv, default_min=3.0) == costmodel.FIXED_OUTPUT
|
||
|
||
glue = "/nix/store/hash-unit-sshd.service.drv"
|
||
assert costmodel.cost(glue, default_min=3.0) == costmodel.TRIVIAL
|
||
|
||
hist_mins, _ = costmodel.cost(lib, history={"somelib-1.2.3": 9.0}, default_min=3.0)
|
||
assert hist_mins == 9.0
|
||
|
||
|
||
def test_fit_defaults_per_bucket_medians():
|
||
closure = {
|
||
# HEAVY — must be excluded from the DEFAULT median despite a history entry.
|
||
"/nix/store/h-llvm-15.0.7.drv": {},
|
||
# fixed-output fetch.
|
||
"/nix/store/h-mysource-1.0.drv": {
|
||
"outputs": {"out": {"hash": "z", "hashAlgo": "sha256"}}
|
||
},
|
||
# trivial glue.
|
||
"/nix/store/h-unit-foo.service.drv": {},
|
||
# DEFAULT libs (odd count -> exact median).
|
||
"/nix/store/h-liba-1.drv": {},
|
||
"/nix/store/h-libb-1.drv": {},
|
||
"/nix/store/h-libc-1.drv": {},
|
||
}
|
||
history = {
|
||
"llvm-15.0.7": 200.0, # HEAVY, excluded
|
||
"mysource-1.0": 0.5,
|
||
"unit-foo.service": 0.02,
|
||
"liba-1": 2.0,
|
||
"libb-1": 6.0,
|
||
"libc-1": 10.0,
|
||
}
|
||
out = costmodel.fit_defaults(history, closure)
|
||
assert out["default_min"] == 6.0 # median(2,6,10), llvm excluded
|
||
assert out["n_default"] == 3
|
||
assert out["fixed_output_min"] == 0.5
|
||
assert out["n_fixed_output"] == 1
|
||
assert out["trivial_min"] == 0.02
|
||
assert out["n_trivial"] == 1
|
||
|
||
|
||
def test_fit_defaults_empty_bucket_reports_none():
|
||
closure = {"/nix/store/h-liba-1.drv": {}, "/nix/store/h-libb-1.drv": {}}
|
||
history = {"liba-1": 4.0, "libb-1": 8.0} # no fetch/glue measured
|
||
out = costmodel.fit_defaults(history, closure)
|
||
assert out["default_min"] == 6.0 # median(4,8) even count -> mean of middle two
|
||
assert out["n_default"] == 2
|
||
assert out["fixed_output_min"] is None
|
||
assert out["n_fixed_output"] == 0
|
||
assert out["trivial_min"] is None
|
||
assert out["n_trivial"] == 0
|
||
|
||
|
||
def test_recommendation_note_branches_on_dominator():
|
||
grid = {
|
||
(1, 8): 40.0,
|
||
(1, 16): 30.0,
|
||
(1, 32): 25.0,
|
||
(2, 8): 40.0,
|
||
(2, 16): 30.0,
|
||
(2, 32): 25.0,
|
||
}
|
||
heavy = _recommend(grid, 4, (8, 16, 32), (1, 2), 0.12, span_dominator_frac=0.8)
|
||
assert "one heavy derivation" in heavy["note"]
|
||
chain = _recommend(grid, 4, (8, 16, 32), (1, 2), 0.12, span_dominator_frac=0.05)
|
||
assert "long chain of small derivations" in chain["note"]
|