Files
unslothai--unsloth/studio/backend/hub/tests/test_dataset_services.py
T
wehub-resource-sync e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:59:56 +08:00

357 lines
11 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
import asyncio
from pathlib import Path
from types import SimpleNamespace
import pytest
from fastapi import HTTPException
from hub.schemas.datasets import CheckFormatRequest, LocalDatasetItem
from hub.services.datasets import cache_inventory, downloads, formatting, local
from hub.utils import download_manifest, download_registry, state_dir
class _Upload:
def __init__(self, filename: str, payload: bytes):
self.filename = filename
self._payload = payload
self._offset = 0
async def read(self, size: int) -> bytes:
if self._offset >= len(self._payload):
return b""
chunk = self._payload[self._offset : self._offset + size]
self._offset += len(chunk)
return chunk
def test_dataset_cache_scan_merges_raw_and_processed_rows(monkeypatch):
raw_repo = SimpleNamespace(
repo_id = "Org/Data",
repo_type = "dataset",
repo_path = "/cache/datasets--Org--Data",
size_on_disk = 100,
revisions = [SimpleNamespace(files = [], commit_hash = "abc")],
)
monkeypatch.setattr(
cache_inventory,
"_collect_hf_cache_scans",
lambda: ([SimpleNamespace(repos = [raw_repo])], {"/cache"}),
)
monkeypatch.setattr(
cache_inventory.hf_cache_scan,
"is_snapshot_partial",
lambda _repo_type, _repo_id, _cache_dir: False,
)
monkeypatch.setattr(
cache_inventory,
"_scan_hub_dataset_cache_dirs",
lambda: [],
)
monkeypatch.setattr(
cache_inventory,
"_scan_processed_dataset_caches",
lambda: [
{
"repo_id": "org/data",
"size_bytes": 250,
"cache_path": "/processed/org___data",
"processed_cache": True,
"partial": False,
}
],
)
rows = cache_inventory._scan_hf_dataset_caches()
assert len(rows) == 1
assert rows[0]["repo_id"] == "Org/Data"
assert rows[0]["size_bytes"] == 250
assert rows[0]["partial"] is False
def test_delete_cached_dataset_attempts_all_roots_before_raising(monkeypatch):
calls = []
purged_state = []
class _DeleteStrategy:
def __init__(self, label: str, fail: bool):
self.label = label
self.fail = fail
def execute(self):
calls.append(self.label)
if self.fail:
raise RuntimeError(f"{self.label} failed")
class _Cache:
def __init__(self, label: str, fail: bool):
self.cache_dir = label
self.repos = [
SimpleNamespace(
repo_type = "dataset",
repo_id = "Org/Data",
revisions = [SimpleNamespace(commit_hash = f"{label}-rev")],
)
]
self.fail = fail
def delete_revisions(self, *_revisions):
return _DeleteStrategy(self.cache_dir, self.fail)
monkeypatch.setattr(
cache_inventory,
"_collect_hf_cache_scans",
lambda: ([_Cache("first", True), _Cache("second", False)], set()),
)
monkeypatch.setattr(
cache_inventory,
"_delete_processed_dataset_cache",
lambda _repo_id: (True, []),
)
monkeypatch.setattr(
cache_inventory.download_manifest,
"purge_all_state_for_repo",
lambda *_args: purged_state.append(True) or 1,
)
with pytest.raises(HTTPException) as exc_info:
cache_inventory._delete_cached_dataset_blocking("Org/Data")
assert exc_info.value.status_code == 500
assert calls == ["first", "second"]
assert purged_state == []
def test_delete_cached_dataset_purges_blob_only_repo_dir(monkeypatch):
"""A blob-only ``datasets--owner--repo`` dir (no usable snapshot/refs) is
fully removable: purge_partial_repo alone clears only ``.incomplete`` files
and would leave the complete blobs and the row."""
purged_dirs: list[str] = []
monkeypatch.setattr(
cache_inventory,
"_collect_hf_cache_scans",
lambda: ([], set()),
)
monkeypatch.setattr(
cache_inventory,
"_delete_processed_dataset_cache",
lambda _repo_id: (False, []),
)
monkeypatch.setattr(
cache_inventory,
"purge_repo_cache_dirs",
lambda _repo_type, repo_id: purged_dirs.append(repo_id) or True,
)
monkeypatch.setattr(
cache_inventory,
"purge_partial_repo",
lambda *_args: False,
)
monkeypatch.setattr(
cache_inventory.download_manifest,
"purge_all_state_for_repo",
lambda *_args: 0,
)
result = cache_inventory._delete_cached_dataset_blocking("Org/Data")
assert result == {"status": "deleted", "repo_id": "Org/Data"}
assert purged_dirs == ["Org/Data"]
def test_delete_cached_dataset_absent_everywhere_raises_404(monkeypatch):
monkeypatch.setattr(
cache_inventory,
"_collect_hf_cache_scans",
lambda: ([], set()),
)
monkeypatch.setattr(
cache_inventory,
"_delete_processed_dataset_cache",
lambda _repo_id: (False, []),
)
monkeypatch.setattr(
cache_inventory,
"purge_repo_cache_dirs",
lambda *_args: False,
)
monkeypatch.setattr(
cache_inventory,
"purge_partial_repo",
lambda *_args: False,
)
monkeypatch.setattr(
cache_inventory.download_manifest,
"purge_all_state_for_repo",
lambda *_args: 0,
)
with pytest.raises(HTTPException) as exc_info:
cache_inventory._delete_cached_dataset_blocking("Org/Missing")
assert exc_info.value.status_code == 404
def test_check_format_rejects_invalid_path_as_400():
with pytest.raises(HTTPException) as exc_info:
formatting.check_format_response(CheckFormatRequest(dataset_name = "../../etc/passwd"))
assert exc_info.value.status_code == 400
def test_dataset_download_status_preserves_idle_shape():
status = downloads._dataset_status("Org/Data")
assert status.state == "idle"
assert status.error is None
def test_dataset_download_registry_key_is_case_insensitive():
registry = download_registry.DownloadRegistry()
claimed, state = registry.claim(
"Org/Data",
download_registry.TRANSPORT_HTTP,
repo_type = "dataset",
repo_id = "Org/Data",
)
duplicate_claimed, duplicate_state = registry.claim(
"org/data",
download_registry.TRANSPORT_HTTP,
repo_type = "dataset",
repo_id = "org/data",
)
assert claimed is True
assert state == "running"
assert duplicate_claimed is False
assert duplicate_state == "running"
assert registry.active_jobs("ORG/DATA") == {"org/data": "running"}
def test_dataset_idle_status_uses_cancel_marker_after_restart(monkeypatch, tmp_path):
monkeypatch.setattr(state_dir, "cache_root", lambda: tmp_path)
monkeypatch.setattr(downloads, "_registry", download_registry.DownloadRegistry())
assert download_manifest.write_cancel_marker("dataset", "Owner/Data", None, "http")
status = asyncio.run(downloads.get_dataset_download_status_response("owner/data"))
assert status.state == "cancelled"
assert status.error is None
def test_dataset_claim_register_cancel_uses_registry_marker_owner(monkeypatch):
killed = []
class _Registry:
def claim(self, *_args, **_kwargs):
return True, "running"
def current_generation(self, _key):
return 1
def register_process(self, _key, _proc):
return False
def persist_cancel_for_key(self, *_args, **_kwargs):
raise AssertionError("register_process owns pending-cancel markers")
def get_job(self, _key):
return SimpleNamespace(state = "cancelled", error = None)
monkeypatch.setattr(downloads, "_registry", _Registry())
monkeypatch.setattr(
downloads,
"resolve_cached_repo_id_case",
lambda repo_id, **_kwargs: repo_id,
)
monkeypatch.setattr(
downloads.download_registry,
"download_transport_unavailable_reason",
lambda _transport: None,
)
monkeypatch.setattr(
downloads.download_lifecycle,
"spawn_worker",
lambda *_args, **_kwargs: object(),
)
monkeypatch.setattr(
downloads.download_lifecycle,
"kill_and_reap_process",
lambda proc, **_kwargs: killed.append(proc),
)
result = asyncio.run(
downloads.download_dataset_response(SimpleNamespace(repo_id = "Org/Data", use_xet = False))
)
assert result["state"] == "cancelled"
assert killed
def test_dataset_cancel_pending_spawn_arms_pending_cancel(monkeypatch):
events = []
class _Registry:
def get_process(self, _key):
return None
def mark_pending_cancel(self, key, generation):
events.append(("pending", key, generation))
return True
def get_job(self, _key):
return SimpleNamespace(state = "running")
monkeypatch.setattr(downloads, "_registry", _Registry())
monkeypatch.setattr(
downloads,
"resolve_cached_repo_id_case",
lambda repo_id, **_kwargs: repo_id,
)
result = asyncio.run(
downloads.cancel_dataset_download_response(
SimpleNamespace(repo_id = "Org/Data", generation = 5)
)
)
assert result == {"repo_id": "Org/Data", "state": "cancelling"}
assert events == [("pending", "org/data", 5)]
def test_upload_dataset_response_writes_non_empty_file(monkeypatch, tmp_path):
payload = b'{"text":"hello"}\n'
monkeypatch.setattr(local, "DATASET_UPLOAD_DIR", tmp_path)
response = asyncio.run(local.upload_dataset_response(_Upload("../train.jsonl", payload)))
stored_path = Path(response.stored_path)
assert response.filename == "train.jsonl"
assert stored_path.parent == tmp_path
assert stored_path.name.endswith("_train.jsonl")
assert stored_path.read_bytes() == payload
def test_local_dataset_items_expose_recipe_and_upload_source(monkeypatch, tmp_path):
recipe_root = tmp_path / "recipes"
upload_root = tmp_path / "uploads"
parquet_dir = recipe_root / "recipe_alpha" / "parquet-files"
parquet_dir.mkdir(parents = True)
(parquet_dir / "part.parquet").write_bytes(b"parquet")
upload_root.mkdir()
(upload_root / "manual.jsonl").write_text('{"text":"hello"}\n', encoding = "utf-8")
monkeypatch.setattr(local, "LOCAL_DATASETS_ROOT", recipe_root)
monkeypatch.setattr(local, "DATASET_UPLOAD_DIR", upload_root)
response = local.list_local_datasets_response()
assert "source" in LocalDatasetItem.__annotations__
by_id = {item.id: item for item in response.datasets}
assert by_id["recipe_alpha"].source == "recipe"
assert by_id["manual.jsonl"].source == "upload"