Ctrl K
ComunidadGuías…

tests/test_arrays.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Unit tests for job-array template expansion (jobd.arrays)."""

from jobd.arrays import (
    index_subs,
    render_cmd,
    render_env,
    render_template,
    sweep_member_subs,
)


def test_render_template_substitutes_index():
    assert render_template("--fold {i}", {"i": "3"}) == "--fold 3"


def test_render_template_leaves_unmatched_braces_untouched():
    # JSON literal in an arg must survive — this is why we use .replace, not
    # str.format (which would raise KeyError on {"a": 1} or mangle {} ).
    arg = '--config={"lr": 0.1, "n": {i}}'
    assert render_template(arg, {"i": "7"}) == '--config={"lr": 0.1, "n": 7}'


def test_render_template_no_key_present_is_noop():
    assert render_template("python train.py", {"i": "2"}) == "python train.py"


def test_render_template_multiple_keys_for_future_sweep():
    # The engine generalizes to named sweep axes with no code change.
    out = render_template("--lr {lr} --seed {seed}", {"lr": "0.01", "seed": "5"})
    assert out == "--lr 0.01 --seed 5"


def test_render_template_repeated_key_replaces_all_occurrences():
    assert render_template("{i}-{i}", {"i": "4"}) == "4-4"


def test_render_cmd_applies_per_arg():
    cmd = ["python", "train.py", "--fold", "{i}", "--out", "run-{i}/"]
    assert render_cmd(cmd, index_subs(2)) == [
        "python",
        "train.py",
        "--fold",
        "2",
        "--out",
        "run-2/",
    ]


def test_render_env_substitutes_values_not_keys():
    env = {"FOLD": "{i}", "STATIC": "x"}
    assert render_env(env, index_subs(6)) == {"FOLD": "6", "STATIC": "x"}


def test_index_subs_is_zero_based_string():
    assert index_subs(0) == {"i": "0"}
    assert index_subs(11) == {"i": "11"}


def test_sweep_single_axis():
    subs = sweep_member_subs([("lr", ["0.1", "0.01"])])
    assert subs == [{"lr": "0.1", "i": "0"}, {"lr": "0.01", "i": "1"}]


def test_sweep_cartesian_product_last_axis_fastest():
    subs = sweep_member_subs([("lr", ["0.1", "0.01"]), ("seed", ["1", "2", "3"])])
    assert len(subs) == 6
    # odometer: lr outer, seed inner
    assert subs[0] == {"lr": "0.1", "seed": "1", "i": "0"}
    assert subs[1] == {"lr": "0.1", "seed": "2", "i": "1"}
    assert subs[3] == {"lr": "0.01", "seed": "1", "i": "3"}
    assert subs[5] == {"lr": "0.01", "seed": "3", "i": "5"}


def test_sweep_index_available_alongside_keys():
    subs = sweep_member_subs([("k", ["a", "b"])])
    assert all("i" in s for s in subs)
    assert [s["i"] for s in subs] == ["0", "1"]


def test_sweep_composes_with_render():
    subs = sweep_member_subs([("lr", ["0.1", "0.01"])])
    cmds = [render_cmd(["t.py", "--lr", "{lr}", "--out", "run{i}"], s) for s in subs]
    assert cmds == [
        ["t.py", "--lr", "0.1", "--out", "run0"],
        ["t.py", "--lr", "0.01", "--out", "run1"],
    ]


def test_sweep_empty_axes_is_single_empty_member():
    # itertools.product() with no iterables yields one empty tuple → one member.
    assert sweep_member_subs([]) == [{"i": "0"}]