mirror of https://github.com/razor-ai/soup.git
255 lines
9.5 KiB
Python
255 lines
9.5 KiB
Python
"""Tests for dataset info registry: register, unregister, list, resolve."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import patch
|
|
|
|
from typer.testing import CliRunner
|
|
|
|
from soup_cli.commands.data import app
|
|
|
|
runner = CliRunner()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Registry CRUD
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestDatasetRegistry:
|
|
"""Tests for dataset registry utility functions."""
|
|
|
|
def test_register_dataset(self, tmp_path):
|
|
"""register_dataset adds entry to registry."""
|
|
from soup_cli.utils.registry import (
|
|
load_registry,
|
|
register_dataset,
|
|
)
|
|
|
|
registry_path = tmp_path / "datasets.json"
|
|
register_dataset(
|
|
"my-instruct",
|
|
str(tmp_path / "data.jsonl"),
|
|
"alpaca",
|
|
registry_path=registry_path,
|
|
)
|
|
reg = load_registry(registry_path)
|
|
assert "my-instruct" in reg
|
|
assert reg["my-instruct"]["format"] == "alpaca"
|
|
|
|
def test_register_overwrites(self, tmp_path):
|
|
"""Registering same name overwrites previous entry."""
|
|
from soup_cli.utils.registry import (
|
|
load_registry,
|
|
register_dataset,
|
|
)
|
|
|
|
registry_path = tmp_path / "datasets.json"
|
|
register_dataset("ds", "/a.jsonl", "alpaca", registry_path=registry_path)
|
|
register_dataset("ds", "/b.jsonl", "sharegpt", registry_path=registry_path)
|
|
reg = load_registry(registry_path)
|
|
assert reg["ds"]["path"].endswith("b.jsonl")
|
|
assert reg["ds"]["format"] == "sharegpt"
|
|
|
|
def test_unregister_dataset(self, tmp_path):
|
|
"""unregister_dataset removes entry."""
|
|
from soup_cli.utils.registry import (
|
|
load_registry,
|
|
register_dataset,
|
|
unregister_dataset,
|
|
)
|
|
|
|
registry_path = tmp_path / "datasets.json"
|
|
register_dataset("ds", "/a.jsonl", "alpaca", registry_path=registry_path)
|
|
result = unregister_dataset("ds", registry_path=registry_path)
|
|
assert result is True
|
|
reg = load_registry(registry_path)
|
|
assert "ds" not in reg
|
|
|
|
def test_unregister_nonexistent(self, tmp_path):
|
|
"""unregister_dataset returns False for missing name."""
|
|
from soup_cli.utils.registry import unregister_dataset
|
|
|
|
registry_path = tmp_path / "datasets.json"
|
|
result = unregister_dataset("nope", registry_path=registry_path)
|
|
assert result is False
|
|
|
|
def test_load_empty_registry(self, tmp_path):
|
|
"""load_registry returns empty dict for missing file."""
|
|
from soup_cli.utils.registry import load_registry
|
|
|
|
registry_path = tmp_path / "datasets.json"
|
|
assert load_registry(registry_path) == {}
|
|
|
|
def test_resolve_registered_name(self, tmp_path):
|
|
"""resolve_dataset returns path for registered name."""
|
|
from soup_cli.utils.registry import register_dataset, resolve_dataset
|
|
|
|
registry_path = tmp_path / "datasets.json"
|
|
data_path = str(tmp_path / "data.jsonl")
|
|
register_dataset("my-ds", data_path, "alpaca", registry_path=registry_path)
|
|
resolved = resolve_dataset("my-ds", registry_path=registry_path)
|
|
assert resolved is not None
|
|
assert resolved["path"] == data_path
|
|
|
|
def test_resolve_unregistered_name(self, tmp_path):
|
|
"""resolve_dataset returns None for unknown name."""
|
|
from soup_cli.utils.registry import resolve_dataset
|
|
|
|
registry_path = tmp_path / "datasets.json"
|
|
assert resolve_dataset("unknown", registry_path=registry_path) is None
|
|
|
|
def test_list_registry(self, tmp_path):
|
|
"""load_registry returns all entries."""
|
|
from soup_cli.utils.registry import (
|
|
load_registry,
|
|
register_dataset,
|
|
)
|
|
|
|
registry_path = tmp_path / "datasets.json"
|
|
register_dataset("a", "/a.jsonl", "alpaca", registry_path=registry_path)
|
|
register_dataset("b", "/b.jsonl", "dpo", registry_path=registry_path)
|
|
reg = load_registry(registry_path)
|
|
assert len(reg) == 2
|
|
assert "a" in reg
|
|
assert "b" in reg
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CLI commands
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestRegistryCLI:
|
|
"""Tests for `soup data register/unregister/list` CLI commands."""
|
|
|
|
@patch("soup_cli.commands.data._get_registry_path")
|
|
def test_register_cli(self, mock_path, tmp_path, monkeypatch):
|
|
"""CLI register creates registry entry."""
|
|
monkeypatch.chdir(tmp_path)
|
|
registry_path = tmp_path / "datasets.json"
|
|
mock_path.return_value = registry_path
|
|
|
|
# Create a dummy data file
|
|
data_file = tmp_path / "train.jsonl"
|
|
data_file.write_text('{"instruction": "hi", "output": "hello"}\n')
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["register", "--name", "my-ds", "--path", str(data_file), "--format", "alpaca"],
|
|
)
|
|
assert result.exit_code == 0
|
|
assert "registered" in result.output.lower()
|
|
|
|
@patch("soup_cli.commands.data._get_registry_path")
|
|
def test_unregister_cli(self, mock_path, tmp_path):
|
|
"""CLI unregister removes entry."""
|
|
from soup_cli.utils.registry import register_dataset
|
|
|
|
registry_path = tmp_path / "datasets.json"
|
|
mock_path.return_value = registry_path
|
|
register_dataset("my-ds", "/data.jsonl", "alpaca", registry_path=registry_path)
|
|
|
|
result = runner.invoke(app, ["unregister", "--name", "my-ds"])
|
|
assert result.exit_code == 0
|
|
assert "removed" in result.output.lower()
|
|
|
|
@patch("soup_cli.commands.data._get_registry_path")
|
|
def test_unregister_missing_cli(self, mock_path, tmp_path):
|
|
"""CLI unregister for missing name shows error."""
|
|
registry_path = tmp_path / "datasets.json"
|
|
mock_path.return_value = registry_path
|
|
|
|
result = runner.invoke(app, ["unregister", "--name", "nonexistent"])
|
|
assert result.exit_code == 1
|
|
|
|
@patch("soup_cli.commands.data._get_registry_path")
|
|
def test_list_registry_cli(self, mock_path, tmp_path):
|
|
"""CLI list shows registered datasets."""
|
|
from soup_cli.utils.registry import register_dataset
|
|
|
|
registry_path = tmp_path / "datasets.json"
|
|
mock_path.return_value = registry_path
|
|
register_dataset("ds1", "/a.jsonl", "alpaca", registry_path=registry_path)
|
|
register_dataset("ds2", "/b.jsonl", "dpo", registry_path=registry_path)
|
|
|
|
# list is a Python builtin, command is "registry" or "list"
|
|
result = runner.invoke(app, ["registry"])
|
|
assert result.exit_code == 0
|
|
assert "ds1" in result.output
|
|
assert "ds2" in result.output
|
|
|
|
@patch("soup_cli.commands.data._get_registry_path")
|
|
def test_list_empty_registry_cli(self, mock_path, tmp_path):
|
|
"""CLI list on empty registry shows message."""
|
|
registry_path = tmp_path / "datasets.json"
|
|
mock_path.return_value = registry_path
|
|
|
|
result = runner.invoke(app, ["registry"])
|
|
assert result.exit_code == 0
|
|
assert "no datasets" in result.output.lower() or "empty" in result.output.lower()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Name validation
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestRegistryValidation:
|
|
"""Tests for dataset name validation."""
|
|
|
|
def test_name_with_path_separator_rejected(self, tmp_path):
|
|
"""Names with / or \\ are rejected."""
|
|
from soup_cli.utils.registry import register_dataset
|
|
|
|
registry_path = tmp_path / "datasets.json"
|
|
with __import__("pytest").raises(ValueError):
|
|
register_dataset(
|
|
"bad/name", "/data.jsonl", "alpaca", registry_path=registry_path
|
|
)
|
|
|
|
def test_name_with_null_byte_rejected(self, tmp_path):
|
|
"""Names with null bytes are rejected."""
|
|
from soup_cli.utils.registry import register_dataset
|
|
|
|
registry_path = tmp_path / "datasets.json"
|
|
with __import__("pytest").raises(ValueError):
|
|
register_dataset(
|
|
"bad\x00name", "/data.jsonl", "alpaca", registry_path=registry_path
|
|
)
|
|
|
|
def test_empty_name_rejected(self, tmp_path):
|
|
"""Empty names are rejected."""
|
|
from soup_cli.utils.registry import register_dataset
|
|
|
|
registry_path = tmp_path / "datasets.json"
|
|
with __import__("pytest").raises(ValueError):
|
|
register_dataset("", "/data.jsonl", "alpaca", registry_path=registry_path)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Error handling
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestRegistryErrorHandling:
|
|
"""Tests for registry error handling."""
|
|
|
|
def test_load_corrupted_registry(self, tmp_path):
|
|
"""Corrupted JSON file raises ValueError."""
|
|
from soup_cli.utils.registry import load_registry
|
|
|
|
path = tmp_path / "datasets.json"
|
|
path.write_text("{not valid json", encoding="utf-8")
|
|
with __import__("pytest").raises(ValueError, match="corrupted"):
|
|
load_registry(path)
|
|
|
|
def test_load_non_dict_registry(self, tmp_path):
|
|
"""Non-dict JSON content raises ValueError."""
|
|
from soup_cli.utils.registry import load_registry
|
|
|
|
path = tmp_path / "datasets.json"
|
|
path.write_text("[1, 2, 3]", encoding="utf-8")
|
|
with __import__("pytest").raises(ValueError, match="unexpected format"):
|
|
load_registry(path)
|