Files
enlace/tests/test_config.py
T
msaldain 4048936067 Corregir lo que reportó ruff, que nunca se había ejecutado
ruff estaba configurado en pyproject.toml desde el primer commit y jamás se
había corrido. Tenía 15 hallazgos. Dos importan más allá del estilo:

- zip() sin strict= trunca en silencio al más corto. En las comparaciones de
  lotes eso significa que un test podía pasar sin haber comparado todo. Donde
  los largos deben coincidir ahora es strict=True; donde difieren a propósito
  (pares consecutivos) queda strict=False, que documenta la intención.
- Un import sin usar delataba algo peor: BraveBackend se había escrito sin una
  sola prueba. Se agregan ocho, contra una respuesta con la forma que devuelve
  la API, incluidas la limpieza de etiquetas, el caso de límite de tasa —que
  tiene que distinguirse de 'no respondió'— y que la credencial viaje en la
  cabecera y nunca en la URL. El respaldo tiene que funcionar justo cuando el
  primario ya falló; merecía la misma cobertura.

El resto es orden de imports, collections.abc y líneas largas.
2026-07-28 07:25:51 -03:00

130 lines
3.9 KiB
Python

"""La config tiene que fallar al arrancar, no a las tres horas."""
from __future__ import annotations
from pathlib import Path
import pytest
import yaml
from enlace.config.load import ConfigError, compose, load_config
REPO = Path(__file__).resolve().parents[1]
def test_composicion_por_capas():
cfg = load_config(REPO / "configs/runs/pretrain-2060.yaml")
assert cfg.hardware.name == "turing-2060"
assert cfg.model.name == "tiny-50m"
assert cfg.train.run_name == "pretrain-tiny-50m"
assert cfg.data.source == "shards"
def test_el_yaml_raiz_pisa_las_capas_incluidas():
cfg = load_config(REPO / "configs/runs/smoke-2060.yaml")
# El perfil de la 2060 declara micro_batch_size 8; la corrida lo sube a 64
# porque el modelo char es diminuto.
assert cfg.hardware.micro_batch_size == 64
assert cfg.hardware.dtype == "float16" # esto sí viene del perfil
def test_overrides_de_linea_de_comandos():
cfg = load_config(REPO / "configs/runs/smoke-cpu.yaml", ["train.seed=99"])
assert cfg.train.seed == 99
@pytest.mark.parametrize("perfil", ["turing-2060", "blackwell-5090", "cpu"])
def test_todos_los_perfiles_de_hardware_son_validos(perfil, tmp_path):
"""Un perfil roto solo se descubre al migrar de placa; mejor ahora."""
raiz = {
"include": {
"hardware": f"hardware/{perfil}.yaml",
"model": "model/tiny-50m.yaml",
"train": "train/pretrain.yaml",
"data": "data/corpus.yaml",
}
}
path = REPO / "configs" / "runs" / f"_tmp_{perfil}.yaml"
path.write_text(yaml.safe_dump(raiz))
try:
cfg = load_config(path)
assert cfg.hardware.name == perfil
finally:
path.unlink()
def _raiz(tmp_path: Path, **parches) -> Path:
"""Config raíz válida con parches encima, para probar validaciones."""
base = {
"include": {
"hardware": "hardware/cpu.yaml",
"model": "model/tiny-50m.yaml",
"train": "train/pretrain.yaml",
"data": "data/corpus.yaml",
}
}
base.update(parches)
path = REPO / "configs" / "runs" / "_tmp_test.yaml"
path.write_text(yaml.safe_dump(base))
return path
def _espera_error(path: Path, fragmento: str):
try:
with pytest.raises(ConfigError) as exc:
load_config(path)
assert fragmento in str(exc.value)
finally:
path.unlink()
def test_rechaza_campos_desconocidos(tmp_path):
# Un typo tiene que ser un error ruidoso, no un default silencioso.
_espera_error(_raiz(tmp_path, model={"n_layers": 12}), "n_layers")
def test_rechaza_fp16_sin_grad_scaler(tmp_path):
_espera_error(
_raiz(tmp_path, hardware={"dtype": "float16", "use_grad_scaler": False}),
"use_grad_scaler",
)
def test_rechaza_grad_scaler_sin_fp16(tmp_path):
_espera_error(
_raiz(tmp_path, hardware={"dtype": "float32", "use_grad_scaler": True}),
"use_grad_scaler",
)
def test_rechaza_dmodel_no_divisible_por_heads(tmp_path):
_espera_error(_raiz(tmp_path, model={"d_model": 500}), "no es divisible")
def test_rechaza_gqa_incoherente(tmp_path):
_espera_error(_raiz(tmp_path, model={"n_head": 8, "n_kv_head": 3}), "n_kv_head")
def test_rechaza_schedule_que_no_entra(tmp_path):
_espera_error(
_raiz(
tmp_path,
train={"max_steps": 100, "schedule": {"warmup_steps": 80, "decay_steps": 80}},
),
"no quedaría fase estable",
)
def test_rechaza_data_source_sin_su_ruta(tmp_path):
_espera_error(_raiz(tmp_path, data={"source": "chars", "text_path": None}), "text_path")
def test_include_con_capa_desconocida(tmp_path):
path = REPO / "configs" / "runs" / "_tmp_bad.yaml"
path.write_text(yaml.safe_dump({"include": {"hardwar": "hardware/cpu.yaml"}}))
try:
with pytest.raises(ConfigError, match="capas desconocidas"):
compose(path)
finally:
path.unlink()