# SPDX-License-Identifier: AGPL-3.0-only # Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 """Tests for the `--verbose/-v` Studio flag: option registration on both the plain callback and the `run` subcommand, re-exec forwarding, the access-log env override, and rejection before a subcommand. Modeled on test_studio_secure_flag.py.""" from __future__ import annotations import sys from pathlib import Path from typer.testing import CliRunner _REPO_ROOT = Path(__file__).resolve().parents[2] if str(_REPO_ROOT) not in sys.path: sys.path.insert(0, str(_REPO_ROOT)) _DEDUP = "UNSLOTH_STUDIO_ACCESS_LOG_DEDUP_MS" _POLL = "UNSLOTH_STUDIO_ACCESS_LOG_POLL_DEDUP_MS" _BASE = ["--model", "unsloth/Qwen3-1.7B-GGUF"] def _studio(): from unsloth_cli.commands import studio as _studio_mod return _studio_mod # ── option registration ────────────────────────────────────────────── def test_run_exposes_verbose_option_default_off(): import inspect opt = inspect.signature(_studio().run).parameters["verbose"].default decls = set(getattr(opt, "param_decls", []) or []) assert "--verbose" in decls and "-v" in decls assert getattr(opt, "default", None) is False def test_studio_default_exposes_verbose_option_default_off(): import inspect opt = inspect.signature(_studio().studio_default).parameters["verbose"].default decls = set(getattr(opt, "param_decls", []) or []) assert "--verbose" in decls and "-v" in decls assert getattr(opt, "default", None) is False # ── re-exec capture plumbing (mirrors test_studio_secure_flag.py) ───── class _ExecCaptured(SystemExit): def __init__(self, argv): super().__init__(0) self.argv = list(argv) def _invoke_run(monkeypatch, args): import typer as _typer studio_mod = _studio() captured = [] monkeypatch.setattr(sys, "prefix", "/nonexistent/outer/venv") fake_venv = Path("/fake/studio/venv/unsloth_studio") monkeypatch.setattr(studio_mod, "_studio_venv_python", lambda: fake_venv / "bin" / "python") fake_bin = fake_venv / "bin" / "unsloth" real_is_file = Path.is_file monkeypatch.setattr( Path, "is_file", lambda self: True if str(self) == str(fake_bin) else real_is_file(self), ) from unsloth_cli import _tool_policy as _tp_mod monkeypatch.setattr( _tp_mod, "resolve_tool_policy", lambda host, flag, yes, silent: False if flag is None else bool(flag), ) monkeypatch.setattr(sys, "platform", "linux") def fake_execvp(file, argv): captured.append(list(argv)) raise _ExecCaptured(argv) monkeypatch.setattr(studio_mod.os, "execvp", fake_execvp) app = _typer.Typer() app.command( context_settings = {"allow_extra_args": True, "ignore_unknown_options": True}, )(studio_mod.run) CliRunner().invoke(app, args, catch_exceptions = True) return captured # ── re-exec forwarding + env override ───────────────────────────────── def test_run_verbose_sets_env_and_forwards_on_reexec(monkeypatch): monkeypatch.delenv(_DEDUP, raising = False) monkeypatch.delenv(_POLL, raising = False) captured = _invoke_run(monkeypatch, _BASE + ["--verbose"]) assert len(captured) == 1, captured assert "--verbose" in captured[0], captured[0] import os as _os assert _os.environ.get(_DEDUP) == "0" assert _os.environ.get(_POLL) == "0" def test_run_without_verbose_leaves_env_unset(monkeypatch): monkeypatch.delenv(_DEDUP, raising = False) monkeypatch.delenv(_POLL, raising = False) captured = _invoke_run(monkeypatch, _BASE) assert len(captured) == 1, captured assert "--verbose" not in captured[0], captured[0] assert "--log-verbose" not in captured[0], captured[0] import os as _os assert _os.environ.get(_DEDUP) is None assert _os.environ.get(_POLL) is None def test_run_verbose_preserves_llama_server_verbosity(monkeypatch): # Studio consumes --verbose but still forwards llama-server's own verbosity. monkeypatch.delenv(_DEDUP, raising = False) monkeypatch.delenv(_POLL, raising = False) captured = _invoke_run(monkeypatch, _BASE + ["--verbose"]) assert len(captured) == 1, captured assert "--log-verbose" in captured[0], captured[0] def test_run_verbose_does_not_duplicate_existing_llama_verbose(monkeypatch): monkeypatch.delenv(_DEDUP, raising = False) monkeypatch.delenv(_POLL, raising = False) captured = _invoke_run(monkeypatch, _BASE + ["--verbose", "--log-verbose"]) assert len(captured) == 1, captured assert captured[0].count("--log-verbose") == 1, captured[0] # ── --verbose before a subcommand is rejected ───────────────────────── def test_studio_default_rejects_verbose_with_subcommand(): import typer as _typer studio_mod = _studio() app = _typer.Typer() app.add_typer(studio_mod.studio_app, name = "studio") result = CliRunner().invoke(app, ["studio", "--verbose", "run", "--model", "X"]) assert result.exit_code == 2, result.output combined = (result.output or "") + (getattr(result, "stderr", "") or "") assert "--verbose" in combined, combined