diff --git a/tests/studio/install/smoke_test_llama_prebuilt.py b/tests/studio/install/smoke_test_llama_prebuilt.py index 994757d2e2..d87537dc94 100644 --- a/tests/studio/install/smoke_test_llama_prebuilt.py +++ b/tests/studio/install/smoke_test_llama_prebuilt.py @@ -39,7 +39,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument( "--llama-tag", default = "latest", - help = "llama.cpp tag to resolve. Defaults to the approved prebuilt tag for this host.", + help = "llama.cpp tag to resolve. Defaults to the latest usable published Unsloth release.", ) parser.add_argument( "--published-repo", diff --git a/tests/studio/install/test_install_llama_prebuilt_logic.py b/tests/studio/install/test_install_llama_prebuilt_logic.py index eb30ac2745..622f01f946 100644 --- a/tests/studio/install/test_install_llama_prebuilt_logic.py +++ b/tests/studio/install/test_install_llama_prebuilt_logic.py @@ -33,6 +33,10 @@ activate_install_tree = INSTALL_LLAMA_PREBUILT.activate_install_tree create_install_staging_dir = INSTALL_LLAMA_PREBUILT.create_install_staging_dir sha256_file = INSTALL_LLAMA_PREBUILT.sha256_file source_archive_logical_name = INSTALL_LLAMA_PREBUILT.source_archive_logical_name +install_prebuilt = INSTALL_LLAMA_PREBUILT.install_prebuilt +write_prebuilt_metadata = INSTALL_LLAMA_PREBUILT.write_prebuilt_metadata +existing_install_matches_plan = INSTALL_LLAMA_PREBUILT.existing_install_matches_plan +existing_install_matches_choice = INSTALL_LLAMA_PREBUILT.existing_install_matches_choice def approved_checksums_for( @@ -318,6 +322,7 @@ def test_validate_prebuilt_choice_creates_repo_shaped_linux_install( probe_path, requested_tag = upstream_tag, llama_tag = upstream_tag, + release_tag = upstream_tag, approved_checksums = approved_checksums_for( upstream_tag, source_archive = source_archive, @@ -436,6 +441,7 @@ def test_validate_prebuilt_choice_creates_repo_shaped_windows_install( probe_path, requested_tag = upstream_tag, llama_tag = upstream_tag, + release_tag = upstream_tag, approved_checksums = approved_checksums_for( upstream_tag, source_archive = source_archive, @@ -610,6 +616,1236 @@ def test_binary_env_linux_includes_binary_parent_in_ld_library_path( assert str(install_dir) in ld_dirs +def test_install_prebuilt_falls_back_to_older_release_plan( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +): + install_dir = tmp_path / "llama.cpp" + host = HostInfo( + system = "Linux", + machine = "x86_64", + is_windows = False, + is_linux = True, + is_macos = False, + is_x86_64 = True, + is_arm64 = False, + nvidia_smi = None, + driver_cuda_version = None, + compute_caps = [], + visible_cuda_devices = None, + has_physical_nvidia = False, + has_usable_nvidia = False, + ) + + first_choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "old-release", + name = "app-b9002-linux-x64.tar.gz", + url = "https://example.com/app-b9002-linux-x64.tar.gz", + source_label = "published", + install_kind = "linux-cpu", + ) + second_choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "older-release", + name = "app-b9001-linux-x64.tar.gz", + url = "https://example.com/app-b9001-linux-x64.tar.gz", + source_label = "published", + install_kind = "linux-cpu", + ) + first_plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9002", + release_tag = "release-2", + attempts = [first_choice], + approved_checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-2", + upstream_tag = "b9002", + source_commit = None, + artifacts = {}, + ), + ) + second_plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + attempts = [second_choice], + approved_checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-1", + upstream_tag = "b9001", + source_commit = None, + artifacts = {}, + ), + ) + + monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "detect_host", lambda: host) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "resolve_install_release_plans", + lambda llama_tag, host, published_repo, published_release_tag: ( + "latest", + [first_plan, second_plan], + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "download_validation_model", + lambda probe_path, cache_path: probe_path.write_bytes(b"probe"), + ) + + call_log: list[tuple[str, bool]] = [] + + def fake_validate( + attempts, + host, + install_dir, + work_dir, + probe_path, + *, + requested_tag, + llama_tag, + release_tag, + approved_checksums, + initial_fallback_used = False, + existing_install_dir = None, + ): + call_log.append((llama_tag, initial_fallback_used)) + if llama_tag == "b9002": + raise PrebuiltFallback("validation failed for latest release") + staging_dir = create_install_staging_dir(install_dir) + (staging_dir / "marker.txt").write_text("ready\n") + return attempts[0], staging_dir, initial_fallback_used + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "validate_prebuilt_attempts", + fake_validate, + ) + + activated = {} + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "activate_install_tree", + lambda staging_dir, install_dir, host: activated.update( + {"staging_dir": staging_dir, "install_dir": install_dir} + ), + ) + ensured_tags: list[str] = [] + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "ensure_converter_scripts", + lambda install_dir, llama_tag: ensured_tags.append(llama_tag), + ) + + install_prebuilt(install_dir, "latest", "unslothai/llama.cpp", "") + + assert call_log == [("b9002", False), ("b9001", True)] + assert activated["install_dir"] == install_dir + assert ensured_tags == ["b9001"] + + +def write_linux_install_shape(install_dir: Path) -> None: + runtime_dir = install_dir / "build" / "bin" + runtime_dir.mkdir(parents = True, exist_ok = True) + (install_dir / "llama-server").write_text("#!/bin/sh\n", encoding = "utf-8") + (install_dir / "llama-quantize").write_text("#!/bin/sh\n", encoding = "utf-8") + (runtime_dir / "llama-server").write_text("#!/bin/sh\n", encoding = "utf-8") + (runtime_dir / "llama-quantize").write_text("#!/bin/sh\n", encoding = "utf-8") + (runtime_dir / "libllama.so.0").write_bytes(b"DLL") + (runtime_dir / "libggml.so.0").write_bytes(b"DLL") + (runtime_dir / "libggml-base.so.0").write_bytes(b"DLL") + (runtime_dir / "libggml-cpu-x64.so.0").write_bytes(b"DLL") + (runtime_dir / "libmtmd.so.0").write_bytes(b"DLL") + (install_dir / "convert_hf_to_gguf.py").write_text( + "#!/usr/bin/env python3\n", encoding = "utf-8" + ) + (install_dir / "gguf-py" / "gguf").mkdir(parents = True, exist_ok = True) + + +def write_windows_install_shape( + install_dir: Path, *, include_llama_dll: bool = True, include_cuda_dll: bool = False +) -> None: + runtime_dir = install_dir / "build" / "bin" / "Release" + runtime_dir.mkdir(parents = True, exist_ok = True) + (runtime_dir / "llama-server.exe").write_bytes(b"MZ") + (runtime_dir / "llama-quantize.exe").write_bytes(b"MZ") + if include_llama_dll: + (runtime_dir / "llama.dll").write_bytes(b"DLL") + if include_cuda_dll: + (runtime_dir / "ggml-cuda.dll").write_bytes(b"DLL") + (install_dir / "convert_hf_to_gguf.py").write_text( + "#!/usr/bin/env python3\n", encoding = "utf-8" + ) + (install_dir / "gguf-py" / "gguf").mkdir(parents = True, exist_ok = True) + + +def write_macos_install_shape( + install_dir: Path, + *, + include_libllama: bool = True, + include_libggml: bool = True, + include_libmtmd: bool = True, +) -> None: + runtime_dir = install_dir / "build" / "bin" + runtime_dir.mkdir(parents = True, exist_ok = True) + (install_dir / "llama-server").write_text("#!/bin/sh\n", encoding = "utf-8") + (install_dir / "llama-quantize").write_text("#!/bin/sh\n", encoding = "utf-8") + (runtime_dir / "llama-server").write_text("#!/bin/sh\n", encoding = "utf-8") + (runtime_dir / "llama-quantize").write_text("#!/bin/sh\n", encoding = "utf-8") + if include_libllama: + (runtime_dir / "libllama.0.dylib").write_bytes(b"DLL") + if include_libggml: + (runtime_dir / "libggml.0.dylib").write_bytes(b"DLL") + if include_libmtmd: + (runtime_dir / "libmtmd.0.dylib").write_bytes(b"DLL") + (install_dir / "convert_hf_to_gguf.py").write_text( + "#!/usr/bin/env python3\n", encoding = "utf-8" + ) + (install_dir / "gguf-py" / "gguf").mkdir(parents = True, exist_ok = True) + + +def test_existing_install_matches_plan_with_fingerprint_linux(tmp_path: Path): + install_dir = tmp_path / "llama.cpp" + install_dir.mkdir() + write_linux_install_shape(install_dir) + + host = HostInfo( + system = "Linux", + machine = "x86_64", + is_windows = False, + is_linux = True, + is_macos = False, + is_x86_64 = True, + is_arm64 = False, + nvidia_smi = None, + driver_cuda_version = None, + compute_caps = [], + visible_cuda_devices = None, + has_physical_nvidia = False, + has_usable_nvidia = False, + ) + choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "release-1", + name = "llama-b9001-bin-ubuntu-x64.tar.gz", + url = "https://example.com/llama-b9001-bin-ubuntu-x64.tar.gz", + source_label = "upstream", + install_kind = "linux-cpu", + expected_sha256 = "a" * 64, + ) + checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-1", + upstream_tag = "b9001", + source_commit = "deadbeef", + artifacts = { + source_archive_logical_name("b9001"): ApprovedArtifactHash( + asset_name = source_archive_logical_name("b9001"), + sha256 = "b" * 64, + repo = "ggml-org/llama.cpp", + kind = "upstream-source", + ), + choice.name: ApprovedArtifactHash( + asset_name = choice.name, + sha256 = choice.expected_sha256, + repo = "ggml-org/llama.cpp", + kind = "upstream-prebuilt", + ), + }, + ) + plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + attempts = [choice], + approved_checksums = checksums, + ) + + write_prebuilt_metadata( + install_dir, + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + choice = choice, + approved_checksums = checksums, + prebuilt_fallback_used = False, + ) + + assert existing_install_matches_plan(install_dir, host, plan) is True + + +def test_existing_install_matches_plan_false_without_fingerprint(tmp_path: Path): + install_dir = tmp_path / "llama.cpp" + install_dir.mkdir() + write_linux_install_shape(install_dir) + (install_dir / "UNSLOTH_PREBUILT_INFO.json").write_text( + json.dumps({"tag": "b9001", "asset": "llama-b9001-bin-ubuntu-x64.tar.gz"}) + + "\n", + encoding = "utf-8", + ) + + host = HostInfo( + system = "Linux", + machine = "x86_64", + is_windows = False, + is_linux = True, + is_macos = False, + is_x86_64 = True, + is_arm64 = False, + nvidia_smi = None, + driver_cuda_version = None, + compute_caps = [], + visible_cuda_devices = None, + has_physical_nvidia = False, + has_usable_nvidia = False, + ) + choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "release-1", + name = "llama-b9001-bin-ubuntu-x64.tar.gz", + url = "https://example.com/x.tar.gz", + source_label = "upstream", + install_kind = "linux-cpu", + expected_sha256 = "a" * 64, + ) + checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-1", + upstream_tag = "b9001", + source_commit = "deadbeef", + artifacts = { + source_archive_logical_name("b9001"): ApprovedArtifactHash( + asset_name = source_archive_logical_name("b9001"), + sha256 = "b" * 64, + repo = "ggml-org/llama.cpp", + kind = "upstream-source", + ), + choice.name: ApprovedArtifactHash( + asset_name = choice.name, + sha256 = choice.expected_sha256, + repo = "ggml-org/llama.cpp", + kind = "upstream-prebuilt", + ), + }, + ) + plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + attempts = [choice], + approved_checksums = checksums, + ) + + assert existing_install_matches_plan(install_dir, host, plan) is False + + +def test_existing_install_matches_plan_false_with_malformed_metadata(tmp_path: Path): + install_dir = tmp_path / "llama.cpp" + install_dir.mkdir() + write_linux_install_shape(install_dir) + (install_dir / "UNSLOTH_PREBUILT_INFO.json").write_text( + "{not-json\n", encoding = "utf-8" + ) + + host = HostInfo( + system = "Linux", + machine = "x86_64", + is_windows = False, + is_linux = True, + is_macos = False, + is_x86_64 = True, + is_arm64 = False, + nvidia_smi = None, + driver_cuda_version = None, + compute_caps = [], + visible_cuda_devices = None, + has_physical_nvidia = False, + has_usable_nvidia = False, + ) + choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "release-1", + name = "llama-b9001-bin-ubuntu-x64.tar.gz", + url = "https://example.com/x.tar.gz", + source_label = "upstream", + install_kind = "linux-cpu", + expected_sha256 = "a" * 64, + ) + checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-1", + upstream_tag = "b9001", + source_commit = "deadbeef", + artifacts = { + source_archive_logical_name("b9001"): ApprovedArtifactHash( + asset_name = source_archive_logical_name("b9001"), + sha256 = "b" * 64, + repo = "ggml-org/llama.cpp", + kind = "upstream-source", + ), + choice.name: ApprovedArtifactHash( + asset_name = choice.name, + sha256 = choice.expected_sha256, + repo = "ggml-org/llama.cpp", + kind = "upstream-prebuilt", + ), + }, + ) + plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + attempts = [choice], + approved_checksums = checksums, + ) + + assert existing_install_matches_plan(install_dir, host, plan) is False + + +def test_existing_install_matches_plan_windows_cpu_requires_llama_dll(tmp_path: Path): + install_dir = tmp_path / "llama.cpp" + install_dir.mkdir() + write_windows_install_shape(install_dir, include_llama_dll = True) + + host = HostInfo( + system = "Windows", + machine = "AMD64", + is_windows = True, + is_linux = False, + is_macos = False, + is_x86_64 = True, + is_arm64 = False, + nvidia_smi = None, + driver_cuda_version = None, + compute_caps = [], + visible_cuda_devices = None, + has_physical_nvidia = False, + has_usable_nvidia = False, + ) + choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "release-1", + name = "llama-b9001-bin-win-cpu-x64.zip", + url = "https://example.com/x.zip", + source_label = "published", + install_kind = "windows-cpu", + expected_sha256 = "a" * 64, + ) + checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-1", + upstream_tag = "b9001", + source_commit = "deadbeef", + artifacts = { + source_archive_logical_name("b9001"): ApprovedArtifactHash( + asset_name = source_archive_logical_name("b9001"), + sha256 = "b" * 64, + repo = "ggml-org/llama.cpp", + kind = "upstream-source", + ), + choice.name: ApprovedArtifactHash( + asset_name = choice.name, + sha256 = choice.expected_sha256, + repo = "unslothai/llama.cpp", + kind = "prebuilt", + ), + }, + ) + plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + attempts = [choice], + approved_checksums = checksums, + ) + write_prebuilt_metadata( + install_dir, + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + choice = choice, + approved_checksums = checksums, + prebuilt_fallback_used = False, + ) + + assert existing_install_matches_plan(install_dir, host, plan) is True + (install_dir / "build" / "bin" / "Release" / "llama.dll").unlink() + assert existing_install_matches_plan(install_dir, host, plan) is False + + +def test_existing_install_matches_plan_windows_cuda_requires_cuda_dll(tmp_path: Path): + install_dir = tmp_path / "llama.cpp" + install_dir.mkdir() + write_windows_install_shape( + install_dir, include_llama_dll = True, include_cuda_dll = True + ) + + host = HostInfo( + system = "Windows", + machine = "AMD64", + is_windows = True, + is_linux = False, + is_macos = False, + is_x86_64 = True, + is_arm64 = False, + nvidia_smi = None, + driver_cuda_version = (12, 4), + compute_caps = [], + visible_cuda_devices = None, + has_physical_nvidia = False, + has_usable_nvidia = True, + ) + choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "release-1", + name = "llama-b9001-bin-win-cuda-12.4-x64.zip", + url = "https://example.com/x.zip", + source_label = "published", + install_kind = "windows-cuda", + runtime_line = "cuda12", + expected_sha256 = "a" * 64, + ) + checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-1", + upstream_tag = "b9001", + source_commit = "deadbeef", + artifacts = { + source_archive_logical_name("b9001"): ApprovedArtifactHash( + asset_name = source_archive_logical_name("b9001"), + sha256 = "b" * 64, + repo = "ggml-org/llama.cpp", + kind = "upstream-source", + ), + choice.name: ApprovedArtifactHash( + asset_name = choice.name, + sha256 = choice.expected_sha256, + repo = "unslothai/llama.cpp", + kind = "prebuilt", + ), + }, + ) + plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + attempts = [choice], + approved_checksums = checksums, + ) + write_prebuilt_metadata( + install_dir, + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + choice = choice, + approved_checksums = checksums, + prebuilt_fallback_used = False, + ) + + assert existing_install_matches_plan(install_dir, host, plan) is True + (install_dir / "build" / "bin" / "Release" / "ggml-cuda.dll").unlink() + assert existing_install_matches_plan(install_dir, host, plan) is False + + +def test_existing_install_matches_plan_macos_requires_dylibs(tmp_path: Path): + install_dir = tmp_path / "llama.cpp" + install_dir.mkdir() + write_macos_install_shape(install_dir) + + host = HostInfo( + system = "Darwin", + machine = "arm64", + is_windows = False, + is_linux = False, + is_macos = True, + is_x86_64 = False, + is_arm64 = True, + nvidia_smi = None, + driver_cuda_version = None, + compute_caps = [], + visible_cuda_devices = None, + has_physical_nvidia = False, + has_usable_nvidia = False, + ) + choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "release-1", + name = "llama-b9001-bin-macos-arm64.tar.gz", + url = "https://example.com/x.tar.gz", + source_label = "published", + install_kind = "macos-arm64", + expected_sha256 = "a" * 64, + ) + checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-1", + upstream_tag = "b9001", + source_commit = "deadbeef", + artifacts = { + source_archive_logical_name("b9001"): ApprovedArtifactHash( + asset_name = source_archive_logical_name("b9001"), + sha256 = "b" * 64, + repo = "ggml-org/llama.cpp", + kind = "upstream-source", + ), + choice.name: ApprovedArtifactHash( + asset_name = choice.name, + sha256 = choice.expected_sha256, + repo = "unslothai/llama.cpp", + kind = "prebuilt", + ), + }, + ) + plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + attempts = [choice], + approved_checksums = checksums, + ) + write_prebuilt_metadata( + install_dir, + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + choice = choice, + approved_checksums = checksums, + prebuilt_fallback_used = False, + ) + + assert existing_install_matches_plan(install_dir, host, plan) is True + (install_dir / "build" / "bin" / "libggml.0.dylib").unlink() + assert existing_install_matches_plan(install_dir, host, plan) is False + + +def test_install_prebuilt_skips_download_when_existing_install_matches( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +): + install_dir = tmp_path / "llama.cpp" + install_dir.mkdir() + write_linux_install_shape(install_dir) + + host = HostInfo( + system = "Linux", + machine = "x86_64", + is_windows = False, + is_linux = True, + is_macos = False, + is_x86_64 = True, + is_arm64 = False, + nvidia_smi = None, + driver_cuda_version = None, + compute_caps = [], + visible_cuda_devices = None, + has_physical_nvidia = False, + has_usable_nvidia = False, + ) + choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "release-1", + name = "llama-b9001-bin-ubuntu-x64.tar.gz", + url = "https://example.com/llama-b9001-bin-ubuntu-x64.tar.gz", + source_label = "upstream", + install_kind = "linux-cpu", + expected_sha256 = "a" * 64, + ) + checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-1", + upstream_tag = "b9001", + source_commit = "deadbeef", + artifacts = { + source_archive_logical_name("b9001"): ApprovedArtifactHash( + asset_name = source_archive_logical_name("b9001"), + sha256 = "b" * 64, + repo = "ggml-org/llama.cpp", + kind = "upstream-source", + ), + choice.name: ApprovedArtifactHash( + asset_name = choice.name, + sha256 = choice.expected_sha256, + repo = "ggml-org/llama.cpp", + kind = "upstream-prebuilt", + ), + }, + ) + plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + attempts = [choice], + approved_checksums = checksums, + ) + + write_prebuilt_metadata( + install_dir, + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + choice = choice, + approved_checksums = checksums, + prebuilt_fallback_used = False, + ) + + monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "detect_host", lambda: host) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "resolve_install_release_plans", + lambda llama_tag, host, published_repo, published_release_tag: ( + "latest", + [plan], + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "download_validation_model", + lambda *args, **kwargs: (_ for _ in ()).throw( + AssertionError( + "matching install should skip before validation model download" + ) + ), + ) + + install_prebuilt(install_dir, "latest", "unslothai/llama.cpp", "") + + +def test_install_prebuilt_does_not_skip_unhealthy_existing_install( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +): + install_dir = tmp_path / "llama.cpp" + install_dir.mkdir() + write_linux_install_shape(install_dir) + (install_dir / "llama-quantize").unlink() + + host = HostInfo( + system = "Linux", + machine = "x86_64", + is_windows = False, + is_linux = True, + is_macos = False, + is_x86_64 = True, + is_arm64 = False, + nvidia_smi = None, + driver_cuda_version = None, + compute_caps = [], + visible_cuda_devices = None, + has_physical_nvidia = False, + has_usable_nvidia = False, + ) + choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "release-1", + name = "llama-b9001-bin-ubuntu-x64.tar.gz", + url = "https://example.com/llama-b9001-bin-ubuntu-x64.tar.gz", + source_label = "upstream", + install_kind = "linux-cpu", + expected_sha256 = "a" * 64, + ) + checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-1", + upstream_tag = "b9001", + source_commit = "deadbeef", + artifacts = { + source_archive_logical_name("b9001"): ApprovedArtifactHash( + asset_name = source_archive_logical_name("b9001"), + sha256 = "b" * 64, + repo = "ggml-org/llama.cpp", + kind = "upstream-source", + ), + choice.name: ApprovedArtifactHash( + asset_name = choice.name, + sha256 = choice.expected_sha256, + repo = "ggml-org/llama.cpp", + kind = "upstream-prebuilt", + ), + }, + ) + plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + attempts = [choice], + approved_checksums = checksums, + ) + + write_prebuilt_metadata( + install_dir, + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + choice = choice, + approved_checksums = checksums, + prebuilt_fallback_used = False, + ) + + monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "detect_host", lambda: host) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "resolve_install_release_plans", + lambda llama_tag, host, published_repo, published_release_tag: ( + "latest", + [plan], + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "download_validation_model", + lambda *args, **kwargs: (_ for _ in ()).throw( + AssertionError("unhealthy install must continue into normal install flow") + ), + ) + + with pytest.raises( + AssertionError, match = "unhealthy install must continue into normal install flow" + ): + install_prebuilt(install_dir, "latest", "unslothai/llama.cpp", "") + + +def test_install_prebuilt_skips_when_older_release_fallback_matches_existing_install( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +): + install_dir = tmp_path / "llama.cpp" + install_dir.mkdir() + write_linux_install_shape(install_dir) + + host = HostInfo( + system = "Linux", + machine = "x86_64", + is_windows = False, + is_linux = True, + is_macos = False, + is_x86_64 = True, + is_arm64 = False, + nvidia_smi = None, + driver_cuda_version = None, + compute_caps = [], + visible_cuda_devices = None, + has_physical_nvidia = False, + has_usable_nvidia = False, + ) + latest_choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "release-2", + name = "llama-b9002-bin-ubuntu-x64.tar.gz", + url = "https://example.com/llama-b9002-bin-ubuntu-x64.tar.gz", + source_label = "upstream", + install_kind = "linux-cpu", + expected_sha256 = "c" * 64, + ) + fallback_choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "release-1", + name = "llama-b9001-bin-ubuntu-x64.tar.gz", + url = "https://example.com/llama-b9001-bin-ubuntu-x64.tar.gz", + source_label = "upstream", + install_kind = "linux-cpu", + expected_sha256 = "a" * 64, + ) + latest_checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-2", + upstream_tag = "b9002", + source_commit = "beadfeed", + artifacts = { + source_archive_logical_name("b9002"): ApprovedArtifactHash( + asset_name = source_archive_logical_name("b9002"), + sha256 = "d" * 64, + repo = "ggml-org/llama.cpp", + kind = "upstream-source", + ), + latest_choice.name: ApprovedArtifactHash( + asset_name = latest_choice.name, + sha256 = latest_choice.expected_sha256, + repo = "ggml-org/llama.cpp", + kind = "upstream-prebuilt", + ), + }, + ) + fallback_checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-1", + upstream_tag = "b9001", + source_commit = "deadbeef", + artifacts = { + source_archive_logical_name("b9001"): ApprovedArtifactHash( + asset_name = source_archive_logical_name("b9001"), + sha256 = "b" * 64, + repo = "ggml-org/llama.cpp", + kind = "upstream-source", + ), + fallback_choice.name: ApprovedArtifactHash( + asset_name = fallback_choice.name, + sha256 = fallback_choice.expected_sha256, + repo = "ggml-org/llama.cpp", + kind = "upstream-prebuilt", + ), + }, + ) + latest_plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9002", + release_tag = "release-2", + attempts = [latest_choice], + approved_checksums = latest_checksums, + ) + fallback_plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + attempts = [fallback_choice], + approved_checksums = fallback_checksums, + ) + + write_prebuilt_metadata( + install_dir, + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + choice = fallback_choice, + approved_checksums = fallback_checksums, + prebuilt_fallback_used = True, + ) + + monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "detect_host", lambda: host) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "resolve_install_release_plans", + lambda llama_tag, host, published_repo, published_release_tag: ( + "latest", + [latest_plan, fallback_plan], + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "download_validation_model", + lambda probe_path, cache_path: probe_path.write_bytes(b"probe"), + ) + + call_log: list[str] = [] + + def fake_validate( + attempts, + host, + install_dir, + work_dir, + probe_path, + *, + requested_tag, + llama_tag, + release_tag, + approved_checksums, + initial_fallback_used = False, + existing_install_dir = None, + ): + call_log.append(llama_tag) + raise PrebuiltFallback("validation failed for latest release") + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "validate_prebuilt_attempts", + fake_validate, + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "activate_install_tree", + lambda *args, **kwargs: (_ for _ in ()).throw( + AssertionError("matching fallback install should not reactivate") + ), + ) + + install_prebuilt(install_dir, "latest", "unslothai/llama.cpp", "") + + assert call_log == ["b9002"] + + +def test_install_prebuilt_skips_same_release_fallback_attempt_when_installed( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +): + install_dir = tmp_path / "llama.cpp" + install_dir.mkdir() + write_linux_install_shape(install_dir) + + host = HostInfo( + system = "Linux", + machine = "x86_64", + is_windows = False, + is_linux = True, + is_macos = False, + is_x86_64 = True, + is_arm64 = False, + nvidia_smi = None, + driver_cuda_version = None, + compute_caps = [], + visible_cuda_devices = None, + has_physical_nvidia = False, + has_usable_nvidia = False, + ) + first_choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "release-1", + name = "llama-b9001-bin-ubuntu-x64-bad.tar.gz", + url = "https://example.com/llama-b9001-bin-ubuntu-x64-bad.tar.gz", + source_label = "published", + install_kind = "linux-cpu", + expected_sha256 = "c" * 64, + ) + fallback_choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "release-1", + name = "llama-b9001-bin-ubuntu-x64-good.tar.gz", + url = "https://example.com/llama-b9001-bin-ubuntu-x64-good.tar.gz", + source_label = "upstream", + install_kind = "linux-cpu", + expected_sha256 = "a" * 64, + ) + checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-1", + upstream_tag = "b9001", + source_commit = "deadbeef", + artifacts = { + source_archive_logical_name("b9001"): ApprovedArtifactHash( + asset_name = source_archive_logical_name("b9001"), + sha256 = "b" * 64, + repo = "ggml-org/llama.cpp", + kind = "upstream-source", + ), + first_choice.name: ApprovedArtifactHash( + asset_name = first_choice.name, + sha256 = first_choice.expected_sha256, + repo = "unslothai/llama.cpp", + kind = "prebuilt", + ), + fallback_choice.name: ApprovedArtifactHash( + asset_name = fallback_choice.name, + sha256 = fallback_choice.expected_sha256, + repo = "ggml-org/llama.cpp", + kind = "upstream-prebuilt", + ), + }, + ) + plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + attempts = [first_choice, fallback_choice], + approved_checksums = checksums, + ) + + write_prebuilt_metadata( + install_dir, + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + choice = fallback_choice, + approved_checksums = checksums, + prebuilt_fallback_used = True, + ) + assert ( + existing_install_matches_choice( + install_dir, + host, + llama_tag = "b9001", + release_tag = "release-1", + choice = fallback_choice, + approved_checksums = checksums, + ) + is True + ) + + monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "detect_host", lambda: host) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "resolve_install_release_plans", + lambda llama_tag, host, published_repo, published_release_tag: ( + "latest", + [plan], + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "download_validation_model", + lambda probe_path, cache_path: probe_path.write_bytes(b"probe"), + ) + + attempted_names: list[str] = [] + + def fake_validate_choice( + choice, + host, + staging_dir, + work_dir, + probe_path, + *, + requested_tag, + llama_tag, + release_tag, + approved_checksums, + prebuilt_fallback_used, + quantized_path, + ): + attempted_names.append(choice.name) + if choice.name == first_choice.name: + raise PrebuiltFallback("newest candidate failed") + raise AssertionError("installed fallback candidate should have been skipped") + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "validate_prebuilt_choice", + fake_validate_choice, + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "activate_install_tree", + lambda *args, **kwargs: (_ for _ in ()).throw( + AssertionError("installed fallback candidate should not be activated") + ), + ) + + install_prebuilt(install_dir, "latest", "unslothai/llama.cpp", "") + + assert attempted_names == [first_choice.name] + + +def test_install_prebuilt_same_tag_upstream_failure_uses_older_unsloth_release_plan( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +): + install_dir = tmp_path / "llama.cpp" + host = HostInfo( + system = "Linux", + machine = "x86_64", + is_windows = False, + is_linux = True, + is_macos = False, + is_x86_64 = True, + is_arm64 = False, + nvidia_smi = None, + driver_cuda_version = None, + compute_caps = [], + visible_cuda_devices = None, + has_physical_nvidia = False, + has_usable_nvidia = False, + ) + + same_tag_upstream_choice = AssetChoice( + repo = "ggml-org/llama.cpp", + tag = "b9002", + name = "llama-b9002-bin-ubuntu-x64.tar.gz", + url = "https://example.com/llama-b9002-bin-ubuntu-x64.tar.gz", + source_label = "upstream", + install_kind = "linux-cpu", + expected_sha256 = "a" * 64, + ) + older_release_choice = AssetChoice( + repo = "unslothai/llama.cpp", + tag = "release-1", + name = "llama-b9001-bin-ubuntu-x64.tar.gz", + url = "https://example.com/llama-b9001-bin-ubuntu-x64.tar.gz", + source_label = "upstream", + install_kind = "linux-cpu", + expected_sha256 = "b" * 64, + ) + latest_plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9002", + release_tag = "release-2", + attempts = [same_tag_upstream_choice], + approved_checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-2", + upstream_tag = "b9002", + source_commit = None, + artifacts = {}, + ), + ) + older_plan = INSTALL_LLAMA_PREBUILT.InstallReleasePlan( + requested_tag = "latest", + llama_tag = "b9001", + release_tag = "release-1", + attempts = [older_release_choice], + approved_checksums = ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "release-1", + upstream_tag = "b9001", + source_commit = None, + artifacts = {}, + ), + ) + + monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "detect_host", lambda: host) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "resolve_install_release_plans", + lambda llama_tag, host, published_repo, published_release_tag: ( + "latest", + [latest_plan, older_plan], + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "download_validation_model", + lambda probe_path, cache_path: probe_path.write_bytes(b"probe"), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "latest_upstream_release_tag", + lambda: (_ for _ in ()).throw( + AssertionError("install fallback should not walk upstream releases") + ), + ) + + attempted = [] + + def fake_validate( + attempts, + host, + install_dir, + work_dir, + probe_path, + *, + requested_tag, + llama_tag, + release_tag, + approved_checksums, + initial_fallback_used = False, + existing_install_dir = None, + ): + attempted.append((llama_tag, release_tag, attempts[0].source_label)) + if llama_tag == "b9002": + raise PrebuiltFallback("same-tag upstream asset failed validation") + staging_dir = create_install_staging_dir(install_dir) + (staging_dir / "marker.txt").write_text("ready\n") + return attempts[0], staging_dir, initial_fallback_used + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, "validate_prebuilt_attempts", fake_validate + ) + + activated = {} + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "activate_install_tree", + lambda staging_dir, install_dir, host: activated.update( + {"staging_dir": staging_dir, "install_dir": install_dir} + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "ensure_converter_scripts", + lambda install_dir, llama_tag: None, + ) + + install_prebuilt(install_dir, "latest", "unslothai/llama.cpp", "") + + assert attempted == [ + ("b9002", "release-2", "upstream"), + ("b9001", "release-1", "upstream"), + ] + assert activated["install_dir"] == install_dir + + def io_bytes(data: bytes): return io.BytesIO(data) diff --git a/tests/studio/install/test_llama_pr_force_and_source.py b/tests/studio/install/test_llama_pr_force_and_source.py new file mode 100644 index 0000000000..63a620b2a9 --- /dev/null +++ b/tests/studio/install/test_llama_pr_force_and_source.py @@ -0,0 +1,599 @@ +""" +Tests for UNSLOTH_LLAMA_PR_FORCE and UNSLOTH_LLAMA_SOURCE in setup.sh / setup.ps1. + +Tests cover: + - Bash subprocess: PR_FORCE promotion, user-override, zero/empty/invalid ignored + - Bash subprocess: custom source URL forces build, clone URL uses variable + - Static source checks: defaults present, clone URLs parameterized + - PowerShell subprocess: PR_FORCE promotion, user-override parity + +Run: pytest tests/studio/install/test_llama_pr_force_and_source.py -v +""" + +import os +import shlex +import subprocess +import textwrap +from pathlib import Path + +import pytest + +# --------------------------------------------------------------------------- +# Paths +# --------------------------------------------------------------------------- +PACKAGE_ROOT = Path(__file__).resolve().parents[3] +SETUP_SH = PACKAGE_ROOT / "studio" / "setup.sh" +SETUP_PS1 = PACKAGE_ROOT / "studio" / "setup.ps1" + +BASH = "/bin/bash" +PWSH = "/usr/bin/pwsh" +PWSH_AVAILABLE = os.path.isfile(PWSH) and os.access(PWSH, os.X_OK) +requires_pwsh = pytest.mark.skipif(not PWSH_AVAILABLE, reason = "pwsh not available") + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +def run_bash( + script: str, *, timeout: int = 10, env: dict | None = None +) -> subprocess.CompletedProcess: + """Run a bash script fragment and return the CompletedProcess.""" + run_env = os.environ.copy() + if env: + run_env.update(env) + return subprocess.run( + [BASH, "-c", script], + capture_output = True, + text = True, + timeout = timeout, + env = run_env, + ) + + +def run_pwsh( + script: str, *, timeout: int = 10, env: dict | None = None +) -> subprocess.CompletedProcess: + """Run a PowerShell script fragment and return the CompletedProcess.""" + run_env = os.environ.copy() + run_env["NO_COLOR"] = "1" + if env: + run_env.update(env) + return subprocess.run( + [PWSH, "-NoProfile", "-Command", script], + capture_output = True, + text = True, + timeout = timeout, + env = run_env, + ) + + +# --------------------------------------------------------------------------- +# Shared bash stubs +# --------------------------------------------------------------------------- +BASH_STUBS = textwrap.dedent("""\ + step() { echo "step:$1:$2"; } + substep() { :; } + verbose_substep() { :; } + print_llama_error_log() { :; } + C_ERR= C_WARN= C_OK= C_RST= C_TITLE= C_DIM= +""") + +RUN_QUIET_STUB = textwrap.dedent("""\ + run_quiet_no_exit() { local _label="$1"; shift; "$@"; return $?; } +""") + + +def make_mock_git(tmp_path: Path, *, fail_on: str = "") -> tuple[Path, Path]: + """Create a mock git binary that logs calls. Returns (mock_bin, log_file).""" + mock_bin = tmp_path / "mock_bin" + mock_bin.mkdir(exist_ok = True) + log_file = tmp_path / "git_calls.log" + + if fail_on: + script = ( + f'#!/bin/bash\necho "$*" >> {log_file}\n' + f'_args=("$@")\n' + f"_i=0\n" + f'while [ "${{_args[$_i]:-}}" = "-C" ]; do _i=$((_i+2)); done\n' + f'_subcmd="${{_args[$_i]:-}}"\n' + f'if [ "$_subcmd" = "{fail_on}" ]; then exit 1; fi\n' + f"exit 0\n" + ) + else: + script = f'#!/bin/bash\necho "$*" >> {log_file}\nexit 0\n' + + git_bin = mock_bin / "git" + git_bin.write_text(script) + git_bin.chmod(0o755) + return mock_bin, log_file + + +# ========================================================================= +# Bash fragment that exercises PR_FORCE and _LLAMA_SOURCE resolution +# ========================================================================= +def _bash_resolution_fragment( + llama_pr: str = "", + llama_pr_force: str = "", + llama_source: str = "", + default_pr_force: str = "", + default_source: str = "https://github.com/ggml-org/llama.cpp", +) -> str: + """Build the bash fragment that mirrors setup.sh resolution logic.""" + return BASH_STUBS + textwrap.dedent(f"""\ + _LLAMA_PR={shlex.quote(llama_pr) if llama_pr else '""'} + _DEFAULT_LLAMA_PR_FORCE={shlex.quote(default_pr_force) if default_pr_force else '""'} + _DEFAULT_LLAMA_SOURCE={shlex.quote(default_source)} + + _LLAMA_PR_FORCE={shlex.quote(llama_pr_force) if llama_pr_force else '"$_DEFAULT_LLAMA_PR_FORCE"'} + _LLAMA_SOURCE={shlex.quote(llama_source) if llama_source else '"$_DEFAULT_LLAMA_SOURCE"'} + _LLAMA_SOURCE="${{_LLAMA_SOURCE%.git}}" + + _NEED_LLAMA_SOURCE_BUILD=false + _SKIP_PREBUILT_INSTALL=false + + if [ "$_LLAMA_SOURCE" != "https://github.com/ggml-org/llama.cpp" ]; then + step "llama.cpp" "custom source: $_LLAMA_SOURCE -- forcing source build" + _NEED_LLAMA_SOURCE_BUILD=true + _SKIP_PREBUILT_INSTALL=true + fi + + if [ -z "$_LLAMA_PR" ] && [ -n "$_LLAMA_PR_FORCE" ] && \\ + [[ "$_LLAMA_PR_FORCE" =~ ^[0-9]+$ ]] && [ "$_LLAMA_PR_FORCE" -gt 0 ]; then + _LLAMA_PR="$_LLAMA_PR_FORCE" + step "llama.cpp" "baked-in PR_FORCE=$_LLAMA_PR_FORCE" + fi + + echo "LLAMA_PR=$_LLAMA_PR" + echo "LLAMA_SOURCE=$_LLAMA_SOURCE" + echo "NEED_SOURCE=$_NEED_LLAMA_SOURCE_BUILD" + echo "SKIP_PREBUILT=$_SKIP_PREBUILT_INSTALL" + """) + + +# ========================================================================= +# TEST GROUP A: Bash PR_FORCE promotion (subprocess) +# ========================================================================= +class TestBashPrForcePromotion: + """PR_FORCE promotes to _LLAMA_PR when user hasn't set one.""" + + def test_baked_in_pr_force_promotes(self): + script = _bash_resolution_fragment(default_pr_force = "12345") + r = run_bash(script) + assert r.returncode == 0 + assert "LLAMA_PR=12345" in r.stdout + assert "baked-in PR_FORCE=12345" in r.stdout + + def test_env_pr_force_promotes(self): + script = _bash_resolution_fragment(llama_pr_force = "999") + r = run_bash(script) + assert r.returncode == 0 + assert "LLAMA_PR=999" in r.stdout + + def test_user_pr_overrides_pr_force(self): + """UNSLOTH_LLAMA_PR takes priority over PR_FORCE.""" + script = _bash_resolution_fragment( + llama_pr = "100", + llama_pr_force = "200", + ) + r = run_bash(script) + assert r.returncode == 0 + assert "LLAMA_PR=100" in r.stdout + assert "baked-in PR_FORCE" not in r.stdout + + def test_user_pr_overrides_baked_in(self): + script = _bash_resolution_fragment( + llama_pr = "100", + default_pr_force = "200", + ) + r = run_bash(script) + assert r.returncode == 0 + assert "LLAMA_PR=100" in r.stdout + assert "baked-in PR_FORCE" not in r.stdout + + def test_pr_force_zero_ignored(self): + script = _bash_resolution_fragment(llama_pr_force = "0") + r = run_bash(script) + assert r.returncode == 0 + assert "LLAMA_PR=" in r.stdout + assert "baked-in PR_FORCE" not in r.stdout + + def test_pr_force_empty_ignored(self): + script = _bash_resolution_fragment(default_pr_force = "") + r = run_bash(script) + assert r.returncode == 0 + assert "LLAMA_PR=" in r.stdout + assert "baked-in PR_FORCE" not in r.stdout + + def test_pr_force_alpha_ignored(self): + script = _bash_resolution_fragment(llama_pr_force = "abc") + r = run_bash(script) + assert r.returncode == 0 + assert "LLAMA_PR=" in r.stdout + assert "baked-in PR_FORCE" not in r.stdout + + def test_pr_force_negative_ignored(self): + script = _bash_resolution_fragment(llama_pr_force = "-5") + r = run_bash(script) + assert r.returncode == 0 + assert "LLAMA_PR=" in r.stdout + + def test_pr_force_decimal_ignored(self): + script = _bash_resolution_fragment(llama_pr_force = "12.34") + r = run_bash(script) + assert r.returncode == 0 + assert "LLAMA_PR=" in r.stdout + + +# ========================================================================= +# TEST GROUP B: Bash custom source URL (subprocess) +# ========================================================================= +class TestBashCustomSource: + """Custom _LLAMA_SOURCE forces source build.""" + + def test_custom_source_forces_build(self): + script = _bash_resolution_fragment( + llama_source = "https://github.com/unslothai/llama.cpp", + ) + r = run_bash(script) + assert r.returncode == 0 + assert "NEED_SOURCE=true" in r.stdout + assert "SKIP_PREBUILT=true" in r.stdout + assert "custom source:" in r.stdout + + def test_default_source_no_force(self): + script = _bash_resolution_fragment() + r = run_bash(script) + assert r.returncode == 0 + assert "NEED_SOURCE=false" in r.stdout + assert "SKIP_PREBUILT=false" in r.stdout + assert "custom source:" not in r.stdout + + def test_trailing_git_stripped(self): + script = _bash_resolution_fragment( + llama_source = "https://github.com/unslothai/llama.cpp.git", + ) + r = run_bash(script) + assert r.returncode == 0 + assert "LLAMA_SOURCE=https://github.com/unslothai/llama.cpp" in r.stdout + assert "NEED_SOURCE=true" in r.stdout + + def test_baked_in_source_forces_build(self): + script = _bash_resolution_fragment( + default_source = "https://github.com/unslothai/llama.cpp", + ) + r = run_bash(script) + assert r.returncode == 0 + assert "NEED_SOURCE=true" in r.stdout + + def test_env_source_overrides_baked_in(self): + """User UNSLOTH_LLAMA_SOURCE overrides _DEFAULT_LLAMA_SOURCE.""" + script = _bash_resolution_fragment( + llama_source = "https://github.com/custom/llama.cpp", + default_source = "https://github.com/unslothai/llama.cpp", + ) + r = run_bash(script) + assert r.returncode == 0 + assert "LLAMA_SOURCE=https://github.com/custom/llama.cpp" in r.stdout + + +# ========================================================================= +# TEST GROUP C: Bash clone URL parameterization (subprocess with mock git) +# ========================================================================= +class TestBashCloneUrlParameterized: + """Verify git clone uses _LLAMA_SOURCE instead of hardcoded URL.""" + + @staticmethod + def _clone_script( + mock_bin: Path, + build_tmp: str, + llama_pr: str = "", + llama_source: str = "https://github.com/ggml-org/llama.cpp", + resolved_tag: str = "b8508", + ) -> str: + return RUN_QUIET_STUB + textwrap.dedent(f"""\ + export PATH="{mock_bin}:$PATH" + _LLAMA_PR={shlex.quote(llama_pr) if llama_pr else '""'} + _LLAMA_SOURCE={shlex.quote(llama_source)} + _RESOLVED_LLAMA_TAG={shlex.quote(resolved_tag)} + _BUILD_TMP={shlex.quote(build_tmp)} + BUILD_OK=true + + if [ -n "$_LLAMA_PR" ]; then + run_quiet_no_exit "clone llama.cpp" \\ + git clone --depth 1 "${{_LLAMA_SOURCE}}.git" "$_BUILD_TMP" || BUILD_OK=false + else + _CLONE_BRANCH_ARGS=() + if [ "$_RESOLVED_LLAMA_TAG" != "latest" ] && [ -n "$_RESOLVED_LLAMA_TAG" ]; then + _CLONE_BRANCH_ARGS=(--branch "$_RESOLVED_LLAMA_TAG") + fi + run_quiet_no_exit "clone llama.cpp" \\ + git clone --depth 1 "${{_CLONE_BRANCH_ARGS[@]}}" "${{_LLAMA_SOURCE}}.git" "$_BUILD_TMP" || BUILD_OK=false + fi + echo "BUILD_OK=$BUILD_OK" + """) + + def test_pr_path_uses_custom_source(self, tmp_path: Path): + mock_bin, log_file = make_mock_git(tmp_path) + build_tmp = str(tmp_path / "build_tmp") + script = self._clone_script( + mock_bin, + build_tmp, + llama_pr = "123", + llama_source = "https://github.com/unslothai/llama.cpp", + ) + r = run_bash(script) + assert r.returncode == 0 + log = log_file.read_text() + assert "unslothai/llama.cpp.git" in log + assert "ggml-org" not in log + + def test_non_pr_path_uses_custom_source(self, tmp_path: Path): + mock_bin, log_file = make_mock_git(tmp_path) + build_tmp = str(tmp_path / "build_tmp") + script = self._clone_script( + mock_bin, + build_tmp, + llama_source = "https://github.com/unslothai/llama.cpp", + ) + r = run_bash(script) + assert r.returncode == 0 + log = log_file.read_text() + assert "unslothai/llama.cpp.git" in log + assert "ggml-org" not in log + + def test_default_source_unchanged(self, tmp_path: Path): + mock_bin, log_file = make_mock_git(tmp_path) + build_tmp = str(tmp_path / "build_tmp") + script = self._clone_script(mock_bin, build_tmp) + r = run_bash(script) + assert r.returncode == 0 + log = log_file.read_text() + assert "ggml-org/llama.cpp.git" in log + + +# ========================================================================= +# TEST GROUP D: Static source patterns -- setup.sh +# ========================================================================= +class TestSourcePatternsSh: + """Verify setup.sh has the new defaults and parameterized clone URLs.""" + + @pytest.fixture(autouse = True) + def _load_source(self): + self.content = SETUP_SH.read_text() + + def test_has_default_pr_force(self): + assert '_DEFAULT_LLAMA_PR_FORCE=""' in self.content + + def test_has_default_source(self): + assert ( + '_DEFAULT_LLAMA_SOURCE="https://github.com/ggml-org/llama.cpp"' + in self.content + ) + + def test_has_pr_force_env_read(self): + assert "UNSLOTH_LLAMA_PR_FORCE" in self.content + + def test_has_source_env_read(self): + assert "UNSLOTH_LLAMA_SOURCE" in self.content + + def test_pr_force_resolution_block(self): + assert '_LLAMA_PR="$_LLAMA_PR_FORCE"' in self.content + + def test_source_trailing_git_strip(self): + assert "${_LLAMA_SOURCE%.git}" in self.content + + def test_clone_urls_parameterized_pr_path(self): + """PR clone path uses ${_LLAMA_SOURCE}.git, not hardcoded URL.""" + pr_clone_idx = self.content.index( + 'if [ -n "$_LLAMA_PR" ]; then\n' + ' run_quiet_no_exit "clone llama.cpp"' + ) + else_idx = self.content.index("else\n", pr_clone_idx) + pr_block = self.content[pr_clone_idx:else_idx] + assert '"${_LLAMA_SOURCE}.git"' in pr_block + assert "ggml-org/llama.cpp.git" not in pr_block + + def test_clone_urls_parameterized_tag_path(self): + """Non-PR clone path uses ${_LLAMA_SOURCE}.git, not hardcoded URL.""" + # Find the non-PR clone line (after _CLONE_BRANCH_ARGS) + idx = self.content.index("_CLONE_BRANCH_ARGS=()") + block = self.content[idx : idx + 400] + assert '"${_LLAMA_SOURCE}.git"' in block + assert "ggml-org/llama.cpp.git" not in block + + def test_custom_source_forces_build(self): + assert "custom source: $_LLAMA_SOURCE -- forcing source build" in self.content + + def test_no_hardcoded_clone_urls(self): + """No remaining hardcoded ggml-org clone URLs in clone commands.""" + lines = self.content.splitlines() + for i, line in enumerate(lines, 1): + if "git clone" in line and "ggml-org/llama.cpp.git" in line: + pytest.fail( + f"Line {i} has hardcoded ggml-org clone URL: {line.strip()}" + ) + + +# ========================================================================= +# TEST GROUP E: Static source patterns -- setup.ps1 +# ========================================================================= +class TestSourcePatternsPs1: + """Verify setup.ps1 has the new defaults and parameterized clone URLs.""" + + @pytest.fixture(autouse = True) + def _load_source(self): + self.content = SETUP_PS1.read_text() + + def test_has_default_pr_force(self): + assert '$DefaultLlamaPrForce = ""' in self.content + + def test_has_default_source(self): + assert ( + '$DefaultLlamaSource = "https://github.com/ggml-org/llama.cpp"' + in self.content + ) + + def test_has_pr_force_env_read(self): + assert "$env:UNSLOTH_LLAMA_PR_FORCE" in self.content + + def test_has_source_env_read(self): + assert "$env:UNSLOTH_LLAMA_SOURCE" in self.content + + def test_pr_force_promotion_block(self): + assert "$LlamaPr = $LlamaPrForce" in self.content + + def test_source_trailing_git_strip(self): + assert ".EndsWith('.git')" in self.content + + def test_clone_urls_parameterized_pr_path(self): + """PR clone path uses $LlamaSource.git, not hardcoded URL.""" + pr_idx = self.content.index( + "if ($LlamaPr) {\n", self.content.index("Cloning llama.cpp") + ) + else_idx = self.content.index("} else {", pr_idx) + pr_block = self.content[pr_idx:else_idx] + assert '"$LlamaSource.git"' in pr_block + assert "ggml-org/llama.cpp.git" not in pr_block + + def test_clone_urls_parameterized_tag_path(self): + """Non-PR clone path uses $LlamaSource.git, not hardcoded URL.""" + clone_args_idx = self.content.index('$cloneArgs = @("clone"') + block = self.content[clone_args_idx : clone_args_idx + 400] + assert '"$LlamaSource.git"' in block + assert "ggml-org/llama.cpp.git" not in block + + def test_custom_source_forces_build(self): + assert "custom source: $LlamaSource -- forcing source build" in self.content + + def test_no_hardcoded_clone_urls(self): + """No remaining hardcoded ggml-org clone URLs in clone commands.""" + lines = self.content.splitlines() + for i, line in enumerate(lines, 1): + if "git clone" in line and "ggml-org/llama.cpp.git" in line: + pytest.fail( + f"Line {i} has hardcoded ggml-org clone URL: {line.strip()}" + ) + + +# ========================================================================= +# TEST GROUP F: PowerShell PR_FORCE promotion (subprocess) +# ========================================================================= +@requires_pwsh +class TestPwshPrForcePromotion: + """PR_FORCE promotion and source URL logic via pwsh subprocess.""" + + FRAGMENT_TEMPLATE = textwrap.dedent("""\ + function step($a, $b, $c) { Write-Output "step:$a`:$b" } + + $DefaultLlamaPrForce = "%%DEFAULT_PR_FORCE%%" + $DefaultLlamaSource = "%%DEFAULT_SOURCE%%" + + $LlamaPr = if ($env:UNSLOTH_LLAMA_PR) { $env:UNSLOTH_LLAMA_PR.Trim() } else { "" } + $LlamaPrForce = if ($env:UNSLOTH_LLAMA_PR_FORCE) { $env:UNSLOTH_LLAMA_PR_FORCE.Trim() } else { $DefaultLlamaPrForce } + $LlamaSource = if ($env:UNSLOTH_LLAMA_SOURCE) { $env:UNSLOTH_LLAMA_SOURCE.Trim() } else { $DefaultLlamaSource } + if ($LlamaSource.EndsWith('.git')) { $LlamaSource = $LlamaSource.Substring(0, $LlamaSource.Length - 4) } + + $NeedLlamaSourceBuild = $false + $SkipPrebuiltInstall = $false + + if ($LlamaSource -ne "https://github.com/ggml-org/llama.cpp") { + step "llama.cpp" "custom source: $LlamaSource -- forcing source build" "Yellow" + $NeedLlamaSourceBuild = $true + $SkipPrebuiltInstall = $true + } + + if (-not $LlamaPr -and $LlamaPrForce -and $LlamaPrForce -match '^\\d+$' -and [int]$LlamaPrForce -gt 0) { + $LlamaPr = $LlamaPrForce + step "llama.cpp" "baked-in PR_FORCE=$LlamaPrForce" "Yellow" + } + + Write-Output "LLAMA_PR=$LlamaPr" + Write-Output "LLAMA_SOURCE=$LlamaSource" + Write-Output "NEED_SOURCE=$NeedLlamaSourceBuild" + Write-Output "SKIP_PREBUILT=$SkipPrebuiltInstall" + """) + + def _run( + self, + default_pr_force: str = "", + default_source: str = "https://github.com/ggml-org/llama.cpp", + env: dict | None = None, + ) -> subprocess.CompletedProcess: + script = self.FRAGMENT_TEMPLATE.replace( + "%%DEFAULT_PR_FORCE%%", + default_pr_force, + ).replace( + "%%DEFAULT_SOURCE%%", + default_source, + ) + run_env = {} + # Ensure env vars are unset by default + run_env["UNSLOTH_LLAMA_PR"] = "" + run_env["UNSLOTH_LLAMA_PR_FORCE"] = "" + run_env["UNSLOTH_LLAMA_SOURCE"] = "" + if env: + run_env.update(env) + return run_pwsh(script, env = run_env) + + def test_baked_in_pr_force_promotes(self): + r = self._run(default_pr_force = "12345") + assert r.returncode == 0 + assert "LLAMA_PR=12345" in r.stdout + assert "baked-in PR_FORCE=12345" in r.stdout + + def test_env_pr_force_promotes(self): + r = self._run(env = {"UNSLOTH_LLAMA_PR_FORCE": "999"}) + assert r.returncode == 0 + assert "LLAMA_PR=999" in r.stdout + + def test_user_pr_overrides_pr_force(self): + r = self._run( + env = { + "UNSLOTH_LLAMA_PR": "100", + "UNSLOTH_LLAMA_PR_FORCE": "200", + } + ) + assert r.returncode == 0 + assert "LLAMA_PR=100" in r.stdout + assert "baked-in PR_FORCE" not in r.stdout + + def test_pr_force_zero_ignored(self): + r = self._run(env = {"UNSLOTH_LLAMA_PR_FORCE": "0"}) + assert r.returncode == 0 + assert "LLAMA_PR=" in r.stdout + assert "baked-in PR_FORCE" not in r.stdout + + def test_pr_force_alpha_ignored(self): + r = self._run(env = {"UNSLOTH_LLAMA_PR_FORCE": "abc"}) + assert r.returncode == 0 + assert "baked-in PR_FORCE" not in r.stdout + + def test_custom_source_forces_build(self): + r = self._run( + env = { + "UNSLOTH_LLAMA_SOURCE": "https://github.com/unslothai/llama.cpp", + } + ) + assert r.returncode == 0 + assert "NEED_SOURCE=True" in r.stdout + assert "SKIP_PREBUILT=True" in r.stdout + + def test_default_source_no_force(self): + r = self._run() + assert r.returncode == 0 + assert "NEED_SOURCE=False" in r.stdout + assert "SKIP_PREBUILT=False" in r.stdout + + def test_trailing_git_stripped(self): + r = self._run( + env = { + "UNSLOTH_LLAMA_SOURCE": "https://github.com/unslothai/llama.cpp.git", + } + ) + assert r.returncode == 0 + assert "LLAMA_SOURCE=https://github.com/unslothai/llama.cpp" in r.stdout + + def test_baked_in_source_forces_build(self): + r = self._run(default_source = "https://github.com/unslothai/llama.cpp") + assert r.returncode == 0 + assert "NEED_SOURCE=True" in r.stdout diff --git a/tests/studio/install/test_pr4562_bugfixes.py b/tests/studio/install/test_pr4562_bugfixes.py index 9b8c6219de..b1fe00f3b3 100644 --- a/tests/studio/install/test_pr4562_bugfixes.py +++ b/tests/studio/install/test_pr4562_bugfixes.py @@ -6,7 +6,7 @@ Tests cover: - Bug 2: Source-build fallback ignores pinned tag (both .sh and .ps1) - Bug 3: Unix fallback deletes install before checking prerequisites - Bug 4: Linux LD_LIBRARY_PATH missing build/bin - - "latest" tag resolution fallback chain (Unsloth -> ggml-org -> raw) + - "latest" tag resolution fallback chain (helper -> raw) - Cross-platform binary_env (Linux, macOS, Windows) - Edge cases: malformed JSON, empty responses, env overrides @@ -40,6 +40,10 @@ SPEC.loader.exec_module(MOD) binary_env = MOD.binary_env HostInfo = MOD.HostInfo resolve_requested_llama_tag = MOD.resolve_requested_llama_tag +PublishedReleaseBundle = MOD.PublishedReleaseBundle +ApprovedArtifactHash = MOD.ApprovedArtifactHash +ApprovedReleaseChecksums = MOD.ApprovedReleaseChecksums +source_archive_logical_name = MOD.source_archive_logical_name SETUP_SH = PACKAGE_ROOT / "studio" / "setup.sh" SETUP_PS1 = PACKAGE_ROOT / "studio" / "setup.ps1" @@ -240,6 +244,57 @@ class TestResolveRequestedLlamaTag: monkeypatch.setattr(MOD, "latest_upstream_release_tag", lambda: "b5555") assert resolve_requested_llama_tag("") == "b5555" + def test_latest_with_published_repo_uses_latest_valid_published_release( + self, monkeypatch: pytest.MonkeyPatch + ): + invalid = PublishedReleaseBundle( + repo = "unslothai/llama.cpp", + release_tag = "v2.0", + upstream_tag = "b9000", + assets = {}, + manifest_asset_name = "llama-prebuilt-manifest.json", + artifacts = [], + selection_log = [], + ) + valid = PublishedReleaseBundle( + repo = "unslothai/llama.cpp", + release_tag = "v1.0", + upstream_tag = "b8999", + assets = {}, + manifest_asset_name = "llama-prebuilt-manifest.json", + artifacts = [], + selection_log = [], + ) + + monkeypatch.setattr( + MOD, + "iter_published_release_bundles", + lambda repo, published_release_tag = "": iter([invalid, valid]), + ) + + def fake_load(repo, release_tag): + if release_tag == "v2.0": + raise MOD.PrebuiltFallback("checksum asset missing") + return ApprovedReleaseChecksums( + repo = repo, + release_tag = release_tag, + upstream_tag = "b8999", + source_commit = None, + artifacts = { + source_archive_logical_name("b8999"): ApprovedArtifactHash( + asset_name = source_archive_logical_name("b8999"), + sha256 = "a" * 64, + repo = "ggml-org/llama.cpp", + kind = "upstream-source", + ) + }, + ) + + monkeypatch.setattr(MOD, "load_approved_release_checksums", fake_load) + monkeypatch.setattr(MOD, "latest_upstream_release_tag", lambda: "b7777") + + assert resolve_requested_llama_tag("latest", "unslothai/llama.cpp") == "b8999" + # ========================================================================= # TEST GROUP C: setup.sh logic (bash subprocess tests) @@ -429,140 +484,61 @@ class TestSetupShLogic: # TEST GROUP D: "latest" tag resolution (bash subprocess) # ========================================================================= class TestLatestTagResolution: - """Test the fallback chain: Unsloth API -> ggml-org API -> raw.""" + """Test the fallback chain: helper resolver -> raw.""" RESOLVE_TEMPLATE = textwrap.dedent("""\ - export PATH="{mock_bin}:$PATH" _REQUESTED_LLAMA_TAG="{requested_tag}" _RESOLVED_LLAMA_TAG="" - _RESOLVE_UPSTREAM_STATUS=1 - _HELPER_RELEASE_REPO="unslothai/llama.cpp" - if [ "$_RESOLVE_UPSTREAM_STATUS" -ne 0 ] || [ -z "$_RESOLVED_LLAMA_TAG" ]; then - if [ "$_REQUESTED_LLAMA_TAG" = "latest" ]; then - _RESOLVED_LLAMA_TAG="$(curl -fsSL "https://api.github.com/repos/${{_HELPER_RELEASE_REPO}}/releases/latest" 2>/dev/null | python -c "import sys,json; print(json.load(sys.stdin)['tag_name'])" 2>/dev/null)" || _RESOLVED_LLAMA_TAG="" - if [ -z "$_RESOLVED_LLAMA_TAG" ]; then - _RESOLVED_LLAMA_TAG="$(curl -fsSL https://api.github.com/repos/ggml-org/llama.cpp/releases/latest 2>/dev/null | python -c "import sys,json; print(json.load(sys.stdin)['tag_name'])" 2>/dev/null)" || _RESOLVED_LLAMA_TAG="" - fi - fi - if [ -z "$_RESOLVED_LLAMA_TAG" ]; then - _RESOLVED_LLAMA_TAG="$_REQUESTED_LLAMA_TAG" - fi + _RESOLVE_UPSTREAM_STATUS={resolve_status} + if [ "$_RESOLVE_UPSTREAM_STATUS" -eq 0 ] && [ -n "{resolved_tag}" ]; then + _RESOLVED_LLAMA_TAG="{resolved_tag}" + else + _RESOLVED_LLAMA_TAG="$_REQUESTED_LLAMA_TAG" fi echo "$_RESOLVED_LLAMA_TAG" """) - @staticmethod - def _make_curl_mock( - mock_bin: Path, unsloth_response: str | None, ggml_response: str | None - ): - """Create a curl mock that returns different responses per repo.""" - lines = ["#!/bin/bash"] - if unsloth_response is not None: - lines.append( - f'if echo "$*" | grep -q "unslothai/llama.cpp"; then echo \'{unsloth_response}\'; exit 0; fi' - ) - else: - lines.append( - 'if echo "$*" | grep -q "unslothai/llama.cpp"; then exit 1; fi' - ) - if ggml_response is not None: - lines.append( - f'if echo "$*" | grep -q "ggml-org/llama.cpp"; then echo \'{ggml_response}\'; exit 0; fi' - ) - else: - lines.append('if echo "$*" | grep -q "ggml-org/llama.cpp"; then exit 1; fi') - lines.append("exit 1") - curl_path = mock_bin / "curl" - curl_path.write_text("\n".join(lines) + "\n") - curl_path.chmod(0o755) - def _run_resolve( self, tmp_path: Path, requested_tag: str, - unsloth_resp: str | None, - ggml_resp: str | None, + resolved_tag: str, + resolve_status: int, ) -> str: - mock_bin = tmp_path / "mock_bin" - mock_bin.mkdir(exist_ok = True) - self._make_curl_mock(mock_bin, unsloth_resp, ggml_resp) script = self.RESOLVE_TEMPLATE.format( - mock_bin = mock_bin, requested_tag = requested_tag + requested_tag = requested_tag, + resolved_tag = resolved_tag, + resolve_status = resolve_status, ) return run_bash(script) - def test_unsloth_succeeds(self, tmp_path: Path): + def test_helper_resolution_succeeds(self, tmp_path: Path): output = self._run_resolve( tmp_path, "latest", - unsloth_resp = '{"tag_name":"b8508"}', - ggml_resp = '{"tag_name":"b9000"}', + resolved_tag = "b8508", + resolve_status = 0, ) assert output == "b8508" - def test_unsloth_fails_ggml_succeeds(self, tmp_path: Path): + def test_helper_resolution_falls_back_to_raw_requested_tag(self, tmp_path: Path): output = self._run_resolve( tmp_path, "latest", - unsloth_resp = None, - ggml_resp = '{"tag_name":"b9000"}', - ) - assert output == "b9000" - - def test_both_fail_raw_fallback(self, tmp_path: Path): - output = self._run_resolve( - tmp_path, - "latest", - unsloth_resp = None, - ggml_resp = None, + resolved_tag = "", + resolve_status = 1, ) assert output == "latest" - def test_concrete_tag_passes_through(self, tmp_path: Path): + def test_concrete_tag_passes_through_when_helper_fails(self, tmp_path: Path): output = self._run_resolve( tmp_path, "b7777", - unsloth_resp = '{"tag_name":"b8508"}', - ggml_resp = '{"tag_name":"b9000"}', + resolved_tag = "", + resolve_status = 1, ) assert output == "b7777" - def test_unsloth_malformed_json_falls_through(self, tmp_path: Path): - output = self._run_resolve( - tmp_path, - "latest", - unsloth_resp = '{"bad_key":"no_tag"}', - ggml_resp = '{"tag_name":"b9001"}', - ) - assert output == "b9001" - - def test_both_malformed_json_raw_fallback(self, tmp_path: Path): - output = self._run_resolve( - tmp_path, - "latest", - unsloth_resp = '{"bad":"data"}', - ggml_resp = '{"also":"bad"}', - ) - assert output == "latest" - - def test_unsloth_empty_body_falls_through(self, tmp_path: Path): - output = self._run_resolve( - tmp_path, - "latest", - unsloth_resp = "", - ggml_resp = '{"tag_name":"b7000"}', - ) - assert output == "b7000" - - def test_unsloth_empty_tag_name_falls_through(self, tmp_path: Path): - output = self._run_resolve( - tmp_path, - "latest", - unsloth_resp = '{"tag_name":""}', - ggml_resp = '{"tag_name":"b6000"}', - ) - assert output == "b6000" - def test_env_override_unsloth_llama_tag(self): output = run_bash( 'echo "${UNSLOTH_LLAMA_TAG:-latest}"', @@ -593,10 +569,10 @@ class TestSourceCodePatterns: def test_setup_sh_no_rm_before_prereq_check(self): """rm -rf must appear AFTER cmake/git checks, not before.""" content = SETUP_SH.read_text() - # Find the source-build block - idx_else = content.find("# Check prerequisites") - assert idx_else != -1 - block = content[idx_else:] + # Anchor on the source-build cmake check block. + idx_block = content.find("command -v cmake") + assert idx_block != -1 + block = content[idx_block:] # rm -rf should appear after the cmake/git checks idx_cmake = block.find("command -v cmake") idx_git = block.find("command -v git") @@ -619,14 +595,12 @@ class TestSourceCodePatterns: '_RESOLVED_LLAMA_TAG" != "latest"' in content ), "Should guard against literal 'latest' tag" - def test_setup_sh_latest_resolution_queries_unsloth_first(self): - """The Unsloth repo should be queried before ggml-org.""" + def test_setup_sh_latest_resolution_uses_helper_only(self): + """Shell fallback should rely on helper output, not raw GitHub API tag_name.""" content = SETUP_SH.read_text() - idx_unsloth = content.find("_HELPER_RELEASE_REPO}/releases/latest") - idx_ggml = content.find("ggml-org/llama.cpp/releases/latest") - assert idx_unsloth != -1, "Unsloth API query not found" - assert idx_ggml != -1, "ggml-org API query not found" - assert idx_unsloth < idx_ggml, "Unsloth should be queried before ggml-org" + assert "--resolve-llama-tag" in content + assert "_HELPER_RELEASE_REPO}/releases/latest" not in content + assert "ggml-org/llama.cpp/releases/latest" not in content def test_setup_ps1_uses_checkout_b(self): """PS1 should use checkout -B, not checkout --force FETCH_HEAD.""" @@ -658,14 +632,12 @@ class TestSourceCodePatterns: f"Found 'git pull' in llama.cpp build section at line {i+1}" ) - def test_setup_ps1_latest_resolution_queries_unsloth_first(self): - """PS1 should query Unsloth repo before ggml-org.""" + def test_setup_ps1_latest_resolution_uses_helper_only(self): + """PS1 fallback should rely on helper output, not raw GitHub API tag_name.""" content = SETUP_PS1.read_text() - idx_unsloth = content.find("$HelperReleaseRepo/releases/latest") - idx_ggml = content.find("ggml-org/llama.cpp/releases/latest") - assert idx_unsloth != -1, "Unsloth API query not found in PS1" - assert idx_ggml != -1, "ggml-org API query not found in PS1" - assert idx_unsloth < idx_ggml, "Unsloth should be queried before ggml-org" + assert "--resolve-llama-tag" in content + assert "$HelperReleaseRepo/releases/latest" not in content + assert "ggml-org/llama.cpp/releases/latest" not in content def test_binary_env_linux_has_binary_parent(self): """The Linux branch of binary_env should include binary_path.parent.""" diff --git a/tests/studio/install/test_selection_logic.py b/tests/studio/install/test_selection_logic.py index 906c978b0d..d7bea4bb5c 100644 --- a/tests/studio/install/test_selection_logic.py +++ b/tests/studio/install/test_selection_logic.py @@ -54,6 +54,12 @@ apply_approved_hashes = INSTALL_LLAMA_PREBUILT.apply_approved_hashes linux_cuda_choice_from_release = INSTALL_LLAMA_PREBUILT.linux_cuda_choice_from_release windows_cuda_attempts = INSTALL_LLAMA_PREBUILT.windows_cuda_attempts resolve_upstream_asset_choice = INSTALL_LLAMA_PREBUILT.resolve_upstream_asset_choice +resolve_requested_install_tag = INSTALL_LLAMA_PREBUILT.resolve_requested_install_tag +resolve_install_attempts = INSTALL_LLAMA_PREBUILT.resolve_install_attempts +resolve_install_release_plans = INSTALL_LLAMA_PREBUILT.resolve_install_release_plans +resolve_published_release = INSTALL_LLAMA_PREBUILT.resolve_published_release +source_archive_logical_name = INSTALL_LLAMA_PREBUILT.source_archive_logical_name +env_int = INSTALL_LLAMA_PREBUILT.env_int # --------------------------------------------------------------------------- @@ -131,6 +137,37 @@ def make_checksums(asset_names): ) +def make_checksums_with_source( + asset_names, + *, + release_tag = "v1.0", + upstream_tag = "b8508", +): + return ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = release_tag, + upstream_tag = upstream_tag, + source_commit = None, + artifacts = { + **{ + name: ApprovedArtifactHash( + asset_name = name, + sha256 = "a" * 64, + repo = "unslothai/llama.cpp", + kind = "prebuilt", + ) + for name in asset_names + }, + source_archive_logical_name(upstream_tag): ApprovedArtifactHash( + asset_name = source_archive_logical_name(upstream_tag), + sha256 = "b" * 64, + repo = "ggml-org/llama.cpp", + kind = "upstream-source", + ), + }, + ) + + def mock_linux_runtime(monkeypatch, lines): dirs = {line: ["/usr/lib/stub"] for line in lines} monkeypatch.setattr( @@ -408,7 +445,100 @@ class TestApplyApprovedHashes: # =========================================================================== -# J. linux_cuda_choice_from_release -- core selection +# J. published release resolution +# =========================================================================== + + +class TestPublishedReleaseResolution: + def test_latest_skips_invalid_release_and_uses_next_valid(self, monkeypatch): + invalid = make_release([], release_tag = "v2.0", upstream_tag = "b9000") + valid = make_release([], release_tag = "v1.0", upstream_tag = "b8999") + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "iter_published_release_bundles", + lambda repo, published_release_tag = "": iter([invalid, valid]), + ) + + def fake_load(repo, release_tag): + if release_tag == "v2.0": + raise PrebuiltFallback("checksum asset missing") + return make_checksums_with_source( + [], release_tag = "v1.0", upstream_tag = "b8999" + ) + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "load_approved_release_checksums", + fake_load, + ) + + resolved = resolve_published_release("latest", "unslothai/llama.cpp") + assert resolved.bundle.release_tag == "v1.0" + assert resolved.bundle.upstream_tag == "b8999" + assert resolved.checksums.release_tag == "v1.0" + + def test_concrete_tag_matches_manifest_upstream_tag(self, monkeypatch): + release = make_release([], release_tag = "release-b8508", upstream_tag = "b8508") + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "iter_published_release_bundles", + lambda repo, published_release_tag = "": iter([release]), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "load_approved_release_checksums", + lambda repo, release_tag: make_checksums_with_source( + [], + release_tag = release_tag, + upstream_tag = "b8508", + ), + ) + + assert ( + resolve_requested_install_tag("b8508", "", "unslothai/llama.cpp") == "b8508" + ) + + def test_concrete_tag_without_matching_release_raises(self, monkeypatch): + release = make_release([], release_tag = "release-b9000", upstream_tag = "b9000") + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "iter_published_release_bundles", + lambda repo, published_release_tag = "": iter([release]), + ) + + with pytest.raises(PrebuiltFallback, match = "matched upstream tag b8508"): + resolve_requested_install_tag("b8508", "", "unslothai/llama.cpp") + + def test_pinned_release_must_match_requested_upstream_tag(self, monkeypatch): + bundle = make_release( + [], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000" + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "pinned_published_release_bundle", + lambda repo, release_tag: bundle, + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "load_approved_release_checksums", + lambda repo, release_tag: make_checksums_with_source( + [], + release_tag = release_tag, + upstream_tag = "b9000", + ), + ) + + with pytest.raises(PrebuiltFallback, match = "but requested b8508"): + resolve_requested_install_tag( + "b8508", + "llama-prebuilt-latest", + "unslothai/llama.cpp", + ) + + +# =========================================================================== +# K. linux_cuda_choice_from_release -- core selection # =========================================================================== @@ -676,7 +806,554 @@ class TestLinuxCudaChoiceFromRelease: # =========================================================================== -# K. windows_cuda_attempts +# L. resolve_install_attempts +# =========================================================================== + + +class TestResolveInstallAttempts: + def test_windows_cuda_prefers_published_asset_from_selected_release( + self, monkeypatch + ): + host = make_host(system = "Windows", machine = "AMD64") + host.driver_cuda_version = (12, 4) + mock_windows_runtime(monkeypatch, ["cuda12"]) + asset_name = "llama-b9000-bin-win-cuda-12.4-x64.zip" + release = make_release( + [ + make_artifact( + asset_name, + install_kind = "windows-cuda", + runtime_line = "cuda12", + coverage_class = None, + supported_sms = [], + min_sm = None, + max_sm = None, + bundle_profile = None, + ) + ], + release_tag = "llama-prebuilt-latest", + upstream_tag = "b9000", + assets = {asset_name: f"https://published.example/{asset_name}"}, + ) + checksums = make_checksums_with_source( + [asset_name], + release_tag = release.release_tag, + upstream_tag = "b9000", + ) + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "iter_resolved_published_releases", + lambda requested_tag, published_repo, published_release_tag = "": iter( + [ + INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease( + bundle = release, + checksums = checksums, + ) + ] + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "github_release_assets", + lambda repo, tag: (_ for _ in ()).throw( + AssertionError( + "published Windows CUDA choice should not query upstream" + ) + ), + ) + + requested_tag, resolved_tag, attempts, approved = resolve_install_attempts( + "latest", + host, + "unslothai/llama.cpp", + "", + ) + + assert requested_tag == "latest" + assert resolved_tag == "b9000" + assert attempts[0].name == asset_name + assert attempts[0].source_label == "published" + assert attempts[0].expected_sha256 == "a" * 64 + assert approved.release_tag == "llama-prebuilt-latest" + + def test_windows_cuda_uses_selected_release_upstream_tag(self, monkeypatch): + host = make_host(system = "Windows", machine = "AMD64") + host.driver_cuda_version = (12, 4) + mock_windows_runtime(monkeypatch, ["cuda12"]) + release = make_release( + [], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000" + ) + checksums = make_checksums_with_source( + ["llama-b9000-bin-win-cuda-12.4-x64.zip"], + release_tag = release.release_tag, + upstream_tag = "b9000", + ) + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "iter_resolved_published_releases", + lambda requested_tag, published_repo, published_release_tag = "": iter( + [ + INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease( + bundle = release, + checksums = checksums, + ) + ] + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "github_release_assets", + lambda repo, tag: { + f"llama-{tag}-bin-win-cuda-12.4-x64.zip": f"https://example.com/llama-{tag}-bin-win-cuda-12.4-x64.zip" + }, + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "resolve_windows_cuda_choices", + lambda host, tag, assets: [ + AssetChoice( + repo = UPSTREAM_REPO, + tag = tag, + name = f"llama-{tag}-bin-win-cuda-12.4-x64.zip", + url = assets[f"llama-{tag}-bin-win-cuda-12.4-x64.zip"], + source_label = "upstream", + install_kind = "windows-cuda", + runtime_line = "cuda12", + ) + ], + ) + + requested_tag, resolved_tag, attempts, approved = resolve_install_attempts( + "latest", + host, + "unslothai/llama.cpp", + "", + ) + + assert requested_tag == "latest" + assert resolved_tag == "b9000" + assert attempts[0].name == "llama-b9000-bin-win-cuda-12.4-x64.zip" + assert attempts[0].expected_sha256 == "a" * 64 + assert approved.release_tag == "llama-prebuilt-latest" + + def test_linux_cpu_uses_same_tag_upstream_asset(self, monkeypatch): + host = make_host( + has_usable_nvidia = False, + has_physical_nvidia = False, + nvidia_smi = None, + ) + release = make_release( + [], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000" + ) + checksums = make_checksums_with_source( + ["llama-b9000-bin-ubuntu-x64.tar.gz"], + release_tag = release.release_tag, + upstream_tag = "b9000", + ) + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "iter_resolved_published_releases", + lambda requested_tag, published_repo, published_release_tag = "": iter( + [ + INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease( + bundle = release, + checksums = checksums, + ) + ] + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "github_release_assets", + lambda repo, tag: { + f"llama-{tag}-bin-ubuntu-x64.tar.gz": f"https://example.com/llama-{tag}-bin-ubuntu-x64.tar.gz" + }, + ) + + _requested_tag, resolved_tag, attempts, _approved = resolve_install_attempts( + "latest", + host, + "unslothai/llama.cpp", + "", + ) + + assert resolved_tag == "b9000" + assert attempts[0].name == "llama-b9000-bin-ubuntu-x64.tar.gz" + assert attempts[0].source_label == "upstream" + assert attempts[0].expected_sha256 == "a" * 64 + + def test_linux_cuda_does_not_fall_back_to_upstream_cpu(self, monkeypatch): + host = make_host(system = "Linux", machine = "x86_64", compute_caps = ["86"]) + release = make_release( + [], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000" + ) + checksums = make_checksums_with_source( + [], + release_tag = release.release_tag, + upstream_tag = "b9000", + ) + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "iter_resolved_published_releases", + lambda requested_tag, published_repo, published_release_tag = "": iter( + [ + INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease( + bundle = release, + checksums = checksums, + ) + ] + ), + ) + mock_linux_runtime(monkeypatch, ["cuda12"]) + + with pytest.raises( + PrebuiltFallback, match = "no compatible published Linux CUDA bundle" + ): + resolve_install_attempts("latest", host, "unslothai/llama.cpp", "") + + def test_windows_cpu_prefers_published_asset(self, monkeypatch): + host = make_host( + system = "Windows", + machine = "AMD64", + has_usable_nvidia = False, + has_physical_nvidia = False, + nvidia_smi = None, + ) + asset_name = "llama-b9000-bin-win-cpu-x64.zip" + release = make_release( + [ + make_artifact( + asset_name, + install_kind = "windows-cpu", + runtime_line = None, + coverage_class = None, + supported_sms = [], + min_sm = None, + max_sm = None, + bundle_profile = None, + ) + ], + release_tag = "llama-prebuilt-latest", + upstream_tag = "b9000", + assets = {asset_name: f"https://published.example/{asset_name}"}, + ) + checksums = make_checksums_with_source( + [asset_name], + release_tag = release.release_tag, + upstream_tag = "b9000", + ) + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "iter_resolved_published_releases", + lambda requested_tag, published_repo, published_release_tag = "": iter( + [ + INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease( + bundle = release, + checksums = checksums, + ) + ] + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "github_release_assets", + lambda repo, tag: (_ for _ in ()).throw( + AssertionError("published Windows CPU choice should not query upstream") + ), + ) + + _requested_tag, resolved_tag, attempts, _approved = resolve_install_attempts( + "latest", + host, + "unslothai/llama.cpp", + "", + ) + + assert resolved_tag == "b9000" + assert attempts[0].name == asset_name + assert attempts[0].source_label == "published" + + def test_macos_prefers_published_asset(self, monkeypatch): + host = make_host( + system = "Darwin", + machine = "arm64", + nvidia_smi = None, + driver_cuda_version = None, + compute_caps = [], + has_physical_nvidia = False, + has_usable_nvidia = False, + ) + asset_name = "llama-b9000-bin-macos-arm64.tar.gz" + release = make_release( + [ + make_artifact( + asset_name, + install_kind = "macos-arm64", + runtime_line = None, + coverage_class = None, + supported_sms = [], + min_sm = None, + max_sm = None, + bundle_profile = None, + ) + ], + release_tag = "llama-prebuilt-latest", + upstream_tag = "b9000", + assets = {asset_name: f"https://published.example/{asset_name}"}, + ) + checksums = make_checksums_with_source( + [asset_name], + release_tag = release.release_tag, + upstream_tag = "b9000", + ) + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "iter_resolved_published_releases", + lambda requested_tag, published_repo, published_release_tag = "": iter( + [ + INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease( + bundle = release, + checksums = checksums, + ) + ] + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "github_release_assets", + lambda repo, tag: (_ for _ in ()).throw( + AssertionError("published macOS choice should not query upstream") + ), + ) + + _requested_tag, resolved_tag, attempts, _approved = resolve_install_attempts( + "latest", + host, + "unslothai/llama.cpp", + "", + ) + + assert resolved_tag == "b9000" + assert attempts[0].name == asset_name + assert attempts[0].source_label == "published" + + def test_windows_cpu_missing_checksum_rejects_install(self, monkeypatch): + host = make_host( + system = "Windows", + machine = "AMD64", + has_usable_nvidia = False, + has_physical_nvidia = False, + nvidia_smi = None, + ) + published_name = "llama-b9000-bin-win-cpu-x64.zip" + release = make_release( + [ + make_artifact( + published_name, + install_kind = "windows-cpu", + runtime_line = None, + coverage_class = None, + supported_sms = [], + min_sm = None, + max_sm = None, + bundle_profile = None, + ) + ], + release_tag = "llama-prebuilt-latest", + upstream_tag = "b9000", + assets = {published_name: f"https://published.example/{published_name}"}, + ) + checksums = make_checksums_with_source( + [], + release_tag = release.release_tag, + upstream_tag = "b9000", + ) + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "iter_resolved_published_releases", + lambda requested_tag, published_repo, published_release_tag = "": iter( + [ + INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease( + bundle = release, + checksums = checksums, + ) + ] + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "github_release_assets", + lambda repo, tag: { + f"llama-{tag}-bin-win-cpu-x64.zip": f"https://upstream.example/llama-{tag}-bin-win-cpu-x64.zip" + }, + ) + + with pytest.raises( + PrebuiltFallback, + match = "approved checksum asset did not contain the selected prebuilt archive", + ): + resolve_install_attempts( + "latest", + host, + "unslothai/llama.cpp", + "", + ) + + +class TestResolveInstallReleasePlans: + def test_latest_collects_multiple_older_release_plans_up_to_limit( + self, monkeypatch + ): + host = make_host( + has_usable_nvidia = False, + has_physical_nvidia = False, + nvidia_smi = None, + ) + releases = [ + INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease( + bundle = make_release([], release_tag = "r3", upstream_tag = "b9003"), + checksums = make_checksums_with_source( + ["llama-b9003-bin-ubuntu-x64.tar.gz"], + release_tag = "r3", + upstream_tag = "b9003", + ), + ), + INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease( + bundle = make_release([], release_tag = "r2", upstream_tag = "b9002"), + checksums = make_checksums_with_source( + ["llama-b9002-bin-ubuntu-x64.tar.gz"], + release_tag = "r2", + upstream_tag = "b9002", + ), + ), + INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease( + bundle = make_release([], release_tag = "r1", upstream_tag = "b9001"), + checksums = make_checksums_with_source( + ["llama-b9001-bin-ubuntu-x64.tar.gz"], + release_tag = "r1", + upstream_tag = "b9001", + ), + ), + ] + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "iter_resolved_published_releases", + lambda requested_tag, published_repo, published_release_tag = "": iter( + releases + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "github_release_assets", + lambda repo, tag: { + f"llama-{tag}-bin-ubuntu-x64.tar.gz": f"https://example.com/llama-{tag}-bin-ubuntu-x64.tar.gz" + }, + ) + + requested_tag, plans = resolve_install_release_plans( + "latest", + host, + "unslothai/llama.cpp", + "", + max_release_fallbacks = 2, + ) + + assert requested_tag == "latest" + assert [plan.release_tag for plan in plans] == ["r3", "r2"] + assert [plan.llama_tag for plan in plans] == ["b9003", "b9002"] + + def test_latest_skips_non_installable_release_and_keeps_searching( + self, monkeypatch + ): + host = make_host( + has_usable_nvidia = False, + has_physical_nvidia = False, + nvidia_smi = None, + ) + releases = [ + INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease( + bundle = make_release([], release_tag = "r2", upstream_tag = "b9002"), + checksums = make_checksums_with_source( + [], + release_tag = "r2", + upstream_tag = "b9002", + ), + ), + INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease( + bundle = make_release([], release_tag = "r1", upstream_tag = "b9001"), + checksums = make_checksums_with_source( + ["llama-b9001-bin-ubuntu-x64.tar.gz"], + release_tag = "r1", + upstream_tag = "b9001", + ), + ), + ] + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "iter_resolved_published_releases", + lambda requested_tag, published_repo, published_release_tag = "": iter( + releases + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "github_release_assets", + lambda repo, tag: ( + {} + if tag == "b9002" + else { + f"llama-{tag}-bin-ubuntu-x64.tar.gz": f"https://example.com/llama-{tag}-bin-ubuntu-x64.tar.gz" + } + ), + ) + + _requested_tag, plans = resolve_install_release_plans( + "latest", + host, + "unslothai/llama.cpp", + "", + max_release_fallbacks = 2, + ) + + assert len(plans) == 1 + assert plans[0].release_tag == "r1" + assert plans[0].llama_tag == "b9001" + + def test_malformed_release_fallback_env_uses_default(self, monkeypatch): + monkeypatch.setenv("UNSLOTH_LLAMA_MAX_PREBUILT_RELEASE_FALLBACKS", "not-an-int") + assert ( + env_int("UNSLOTH_LLAMA_MAX_PREBUILT_RELEASE_FALLBACKS", 3, minimum = 1) == 3 + ) + + def test_import_with_malformed_release_fallback_env_does_not_crash( + self, monkeypatch + ): + monkeypatch.setenv("UNSLOTH_LLAMA_MAX_PREBUILT_RELEASE_FALLBACKS", "bad-value") + spec = importlib.util.spec_from_file_location( + "studio_install_llama_prebuilt_env_reload", + MODULE_PATH, + ) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + try: + spec.loader.exec_module(module) + assert module.DEFAULT_MAX_PREBUILT_RELEASE_FALLBACKS == 2 + finally: + sys.modules.pop(spec.name, None) + + +# =========================================================================== +# N. windows_cuda_attempts # =========================================================================== @@ -753,7 +1430,7 @@ class TestWindowsCudaAttempts: # =========================================================================== -# L. resolve_upstream_asset_choice -- platform routing +# O. resolve_upstream_asset_choice -- platform routing # =========================================================================== diff --git a/tests/studio/install/test_validate_llama_prebuilt.py b/tests/studio/install/test_validate_llama_prebuilt.py new file mode 100644 index 0000000000..0f384b715e --- /dev/null +++ b/tests/studio/install/test_validate_llama_prebuilt.py @@ -0,0 +1,175 @@ +import importlib.util +import sys +from pathlib import Path + + +import pytest + + +REPO_ROOT = Path(__file__).resolve().parents[3] +MODULE_PATH = REPO_ROOT / "validate-llama-prebuilt.py" + +if not MODULE_PATH.is_file(): + pytest.skip( + f"validate-llama-prebuilt.py not present at {MODULE_PATH}", + allow_module_level = True, + ) + +SPEC = importlib.util.spec_from_file_location("validate_llama_prebuilt", MODULE_PATH) +assert SPEC is not None and SPEC.loader is not None +VALIDATE = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = VALIDATE +SPEC.loader.exec_module(VALIDATE) + + +def test_build_local_approved_checksums_uses_staged_upstream_tag( + tmp_path: Path, monkeypatch +): + stage_dir = tmp_path / "release-1" + stage_dir.mkdir() + asset_path = stage_dir / "app-test-linux-x64-cuda12-newer.tar.gz" + asset_path.write_bytes(b"bundle") + sibling_checksums = stage_dir / VALIDATE.installer.DEFAULT_PUBLISHED_SHA256_ASSET + sibling_checksums.write_text( + """ +{ + "schema_version": 1, + "component": "llama.cpp", + "release_tag": "release-1", + "upstream_tag": "b9001", + "source_commit": "deadbeef", + "artifacts": { + "llama.cpp-source-b9001.tar.gz": { + "sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "repo": "ggml-org/llama.cpp", + "kind": "upstream-source" + } + } +} + """.strip() + + "\n", + encoding = "utf-8", + ) + asset = VALIDATE.LocalAsset( + path = asset_path, + tag = "test", + name = asset_path.name, + install_kind = "linux-cuda", + source_kind = "app-bundle", + native_runnable = True, + bundle_profile = "cuda12-newer", + runtime_line = "cuda12", + ) + + checksums = VALIDATE.build_local_approved_checksums( + asset, + allow_network_source_hash = False, + ) + + assert checksums.release_tag == "release-1" + assert checksums.upstream_tag == "b9001" + assert "llama.cpp-source-b9001.tar.gz" in checksums.artifacts + assert "llama.cpp-source-test.tar.gz" not in checksums.artifacts + + +def test_validate_native_asset_passes_release_tag_and_upstream_tag( + tmp_path: Path, monkeypatch +): + stage_dir = tmp_path / "release-7" + stage_dir.mkdir() + asset_path = stage_dir / "app-test-linux-x64-cuda12-newer.tar.gz" + asset_path.write_bytes(b"bundle") + sibling_checksums = stage_dir / VALIDATE.installer.DEFAULT_PUBLISHED_SHA256_ASSET + sibling_checksums.write_text( + """ +{ + "schema_version": 1, + "component": "llama.cpp", + "release_tag": "release-7", + "upstream_tag": "b9007", + "source_commit": "deadbeef", + "artifacts": { + "llama.cpp-source-b9007.tar.gz": { + "sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "repo": "ggml-org/llama.cpp", + "kind": "upstream-source" + } + } +} + """.strip() + + "\n", + encoding = "utf-8", + ) + asset = VALIDATE.LocalAsset( + path = asset_path, + tag = "test", + name = asset_path.name, + install_kind = "linux-cuda", + source_kind = "app-bundle", + native_runnable = True, + bundle_profile = "cuda12-newer", + runtime_line = "cuda12", + ) + + host = VALIDATE.installer.HostInfo( + system = "Linux", + machine = "x86_64", + is_windows = False, + is_linux = True, + is_macos = False, + is_x86_64 = True, + is_arm64 = False, + nvidia_smi = None, + driver_cuda_version = None, + compute_caps = [], + visible_cuda_devices = None, + has_physical_nvidia = False, + has_usable_nvidia = False, + ) + monkeypatch.setattr(VALIDATE.installer, "detect_host", lambda: host) + monkeypatch.setattr( + VALIDATE.installer, + "download_validation_model", + lambda probe_path, cache_path: probe_path.write_bytes(b"probe"), + ) + + captured = {} + + def fake_validate_prebuilt_attempts( + attempts, + host, + install_dir, + work_dir, + probe_path, + *, + requested_tag, + llama_tag, + release_tag, + approved_checksums, + initial_fallback_used = False, + existing_install_dir = None, + ): + captured["requested_tag"] = requested_tag + captured["llama_tag"] = llama_tag + captured["release_tag"] = release_tag + staging_dir = VALIDATE.installer.create_install_staging_dir(install_dir) + return attempts[0], staging_dir, False + + monkeypatch.setattr( + VALIDATE.installer, + "validate_prebuilt_attempts", + fake_validate_prebuilt_attempts, + ) + + record = VALIDATE.validate_native_asset( + asset, + keep_temp = False, + allow_network_source_hash = False, + ) + + assert record.status == "PASS" + assert captured == { + "requested_tag": "test", + "llama_tag": "b9007", + "release_tag": "release-7", + }