From 6162d4d87d7d6d3176c5b71661988cd4a5d1af74 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Sun, 26 Jul 2026 17:28:15 +0000 Subject: [PATCH] docker: protect the tested training stack from notebook install cells The pip shim fronts pip/uv inside the notebook kernel so an install cell cannot replace the baked cu128 stack, but _KEEP only covered torch/vLLM/unsloth. Across the 433 shipped notebooks that left the training half wide open: trl 382 pin an older release, 378 of them ending the install cell with `pip install --no-deps trl==0.22.2`, against a baked trl 0.24.0 torchao 273 reinstall it and 2 pin 0.15.0, replacing 0.17.0+cu128 torchcodec 92 reinstall it and 26 pin 0.5 or 0.7.0, replacing the 0.11.0+cu128 wheel the Dockerfile pairs with torch 2.11 datasets 254 reinstall it, observed falling from 4.3.0 to 3.0.0 peft 225 reinstall it, observed falling from 0.19.1 to 0.14.0 accelerate 225 reinstall it hf hub 240 reinstall it and tokenizers 64, both version-locked to transformers and shipped in matched copies inside every sidecar So every notebook run mutated the stack the image was validated with, while the shim printed that it was keeping the baked versions. The membership criterion is "replacing this invalidates the tested stack or breaks unsloth", not "a notebook mentions it": snac, causal-conv1d, mamba-ssm, omegaconf, protobuf, sentencepiece and the rest still install normally. Verified in the rebuilt image by running the Gemma3 (270M) install cell verbatim: trl 0.24.0, peft 0.19.1, datasets 4.3.0, accelerate 1.14.0, torchao 0.17.0+cu128, transformers 5.14.1 and huggingface_hub 1.24.0 are all unchanged afterwards, the requested transformers pin is still recorded for the sidecar, and a package the image does not bake still installs. The existing shim tests used peft as their "unprotected package" sentinel, so they move to snac. --- docker/unsloth_pip_shim.py | 42 +++- .../test_docker_pip_shim_training_stack.py | 196 ++++++++++++++++++ tests/python/test_unsloth_pip_shim.py | 108 +++++----- 3 files changed, 289 insertions(+), 57 deletions(-) create mode 100644 tests/python/test_docker_pip_shim_training_stack.py diff --git a/docker/unsloth_pip_shim.py b/docker/unsloth_pip_shim.py index 5d793303c7..35bc2ffe29 100644 --- a/docker/unsloth_pip_shim.py +++ b/docker/unsloth_pip_shim.py @@ -11,9 +11,15 @@ carefully-resolved cu128 torch/vLLM/transformers stack: * `transformers==X` -> NOT installed into the base venv. The version X is recorded so the sidecar mechanism (unsloth_nb_compat) activates it for the model cells. The base stack stays intact. - * torch / torchvision / torchaudio / triton / xformers / vllm / bitsandbytes / - flashinfer / nvidia-* -> SKIPPED (the baked, ABI-matched versions are kept; - a notebook reinstall here only ever breaks the GPU stack). + * torch / torchvision / torchaudio / torchao / torchcodec / triton / xformers / + vllm / bitsandbytes / flashinfer / nvidia-* -> SKIPPED (the baked, + ABI-matched versions are kept; a notebook reinstall here only ever breaks + the GPU stack). + * trl / peft / datasets / accelerate / huggingface_hub / tokenizers / + safetensors -> SKIPPED for the same reason one level up: 382 of the shipped + notebooks end their install cell with `pip install --no-deps trl==0.22.2`, + which used to walk straight past this shim and downgrade the tested + trl 0.24.0 / peft 0.19.1 / datasets 4.3.0 on every single run. * everything else (omegaconf, snac, causal-conv1d, ...) -> passed through to the real tool unchanged, so notebooks that genuinely need extra packages still get them. @@ -30,10 +36,32 @@ REAL = {"pip": "/opt/unsloth-venv/bin/pip", "uv": "/opt/unsloth-venv/bin/uv"} MARKER = os.environ.get("UNSLOTH_NB_TF_MARKER", "/tmp/unsloth_nb/requested_transformers") # Packages whose baked version must never be changed by a notebook install cell. +# +# Membership criterion: replacing this package silently invalidates the stack the +# image was BUILT and TESTED against, or breaks unsloth outright. That is either +# (a) an ABI/CUDA-matched wheel the Dockerfile resolved deliberately (a PyPI +# reinstall swaps a +cu128 build for a generic or cu13 one), or (b) a library +# unsloth/unsloth_zoo monkey-patches by version at import time. Anything else -- +# including packages the notebook genuinely needs and the image does not bake +# (snac, causal-conv1d, omegaconf, mamba-ssm, ...) -- installs normally. +# +# Measured over the 433 shipped notebooks (probe_notebook_pins.py), the entries +# below the original torch/vLLM group cover: +# trl 382 notebooks pin an older release (0.22.2 x378, 0.15.2 x4) vs baked 0.24.0 +# torchao 2 pin 0.15.0, and 271 more reinstall it, replacing 0.17.0+cu128 +# torchcodec 26 pin 0.5 / 0.7.0, replacing the 0.11.0+cu128 wheel paired with torch 2.11 +# datasets 254 reinstall it; a trl 0.22.2 resolve pulled it back to 3.0.0 from 4.3.0 +# peft 225 reinstall it; observed dropping 0.19.1 -> 0.14.0 +# accelerate 225 reinstall it (Trainer/torch glue, patched by unsloth_zoo) +# hf hub 240 reinstall it; tokenizers 64. Both are version-locked to +# transformers, and the sidecars ship their own matched copies, so a +# base-venv swap desynchronises every sidecar at once. _KEEP = { "torch", "torchvision", "torchaudio", + "torchao", + "torchcodec", "triton", "triton-rocm", "pytorch-triton", @@ -45,6 +73,14 @@ _KEEP = { "unsloth", "unsloth-zoo", "unsloth_zoo", + "trl", + "peft", + "datasets", + "accelerate", + "huggingface-hub", + "huggingface_hub", + "tokenizers", + "safetensors", } _KEEP_PREFIX = ("nvidia-", "nvidia_") # pip/uv flags that consume the next token as a value (not a requirement). diff --git a/tests/python/test_docker_pip_shim_training_stack.py b/tests/python/test_docker_pip_shim_training_stack.py new file mode 100644 index 0000000000..0633814458 --- /dev/null +++ b/tests/python/test_docker_pip_shim_training_stack.py @@ -0,0 +1,196 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright 2026-Present the Unsloth team. See /studio/LICENSE.AGPL-3.0 + +"""Regression guard for what the Docker pip shim protects. + +The shim fronts pip/uv inside the notebook kernel so a `!pip install` cell cannot +replace the baked, ABI-matched stack. It protected torch/vLLM/unsloth and stopped +there, which left the training stack wide open. Measured over the 433 shipped +notebooks (probe_notebook_pins.py against the baked image): + + trl 382 notebooks pin an older release -- 378 of them end their + install cell with `!pip install --no-deps trl==0.22.2`, against + a baked and tested trl 0.24.0 + torchao 273 reinstall it, 2 pin 0.15.0, replacing 0.17.0+cu128 with a + generic PyPI build + torchcodec 92 reinstall it, 26 pin 0.5 / 0.7.0, replacing the 0.11.0+cu128 + wheel the Dockerfile deliberately paired with torch 2.11 + datasets 254 reinstall it; a trl 0.22.2 resolve was observed pulling it + back from 4.3.0 to 3.0.0 + peft 225 reinstall it; observed dropping 0.19.1 -> 0.14.0 + accelerate 225 reinstall it + hf_hub 240 reinstall it, tokenizers 64 -- both version-locked to + transformers, and the sidecars ship their own matched copies + +So EVERY notebook run silently mutated the stack the image was validated with, +and printed "Successfully installed trl-0.22.2 peft-0.14.0 datasets-3.0.0" while +the shim reported it was keeping the baked versions. + +The criterion for _KEEP is "replacing this invalidates the tested stack or breaks +unsloth", not "any package a notebook mentions": a package the notebook genuinely +needs and the image does not bake still has to install normally. + +Static: drives the shim's main() with os.execv captured. No docker, no GPU, no +network. +""" + +from __future__ import annotations + +import importlib.util +import os +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).resolve().parents[2] +SHIM_PATH = REPO_ROOT / "docker" / "unsloth_pip_shim.py" + +# The install cell 378 of the 433 shipped notebooks actually end on. +SHIPPED_TRL_CELL = ["--no-deps", "trl==0.22.2"] +# A package the image does NOT bake: must keep installing normally. +UNBAKED = "snac" + + +class _Exec(Exception): + def __init__(self, path, argv): + self.path = path + self.argv = list(argv) + + +@pytest.fixture() +def shim(tmp_path, monkeypatch): + monkeypatch.setenv("UNSLOTH_NB_TF_MARKER", str(tmp_path / "requested_transformers")) + monkeypatch.setenv("UNSLOTH_NB_SHIM", "1") + assert SHIM_PATH.is_file(), f"missing shim: {SHIM_PATH}" + spec = importlib.util.spec_from_file_location("unsloth_pip_shim_stack_test", SHIM_PATH) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + + def _fake_execv(path, argv): + raise _Exec(path, argv) + + monkeypatch.setattr(mod.os, "execv", _fake_execv) + return mod + + +def _run(shim, args, tool = "pip"): + """Return the args that reached the real tool after `install`, or None when + the shim no-op'd. The always-injected protected-constraints pair is dropped.""" + argv = ["uv", "pip", "install", *args] if tool == "uv" else ["pip", "install", *args] + with pytest.MonkeyPatch.context() as mp: + mp.setattr(shim.sys, "argv", argv) + try: + shim.main() + return None + except _Exec as exc: + i = exc.argv.index("install") + execd = exc.argv[i + 1 :] + if ( + len(execd) >= 2 + and execd[-2] == "--constraint" + and os.path.basename(execd[-1]).startswith("unsloth-nb-protected-") + ): + execd = execd[:-2] + return execd + + +# -------------------------------------------------------------------------- +# Membership +# -------------------------------------------------------------------------- +@pytest.mark.parametrize( + "pkg", + ["trl", "peft", "datasets", "accelerate", "torchao", "torchcodec", + "huggingface-hub", "tokenizers", "safetensors"], +) +def test_training_stack_is_protected(shim, pkg): + assert pkg in shim._KEEP, ( + f"{pkg} is baked and tested; a notebook pin replacing it invalidates the image" + ) + + +def test_the_original_gpu_stack_is_still_protected(shim): + for pkg in ["torch", "torchvision", "torchaudio", "triton", "xformers", + "vllm", "bitsandbytes", "unsloth", "unsloth-zoo"]: + assert pkg in shim._KEEP + + +def test_unrelated_packages_are_not_swept_in(shim): + # The criterion is "invalidates the tested stack", not "a notebook mentions + # it". These are all installed by shipped notebooks and must stay installable. + for pkg in ["snac", "causal-conv1d", "mamba-ssm", "omegaconf", "timm", + "librosa", "trackio", "open-spiel", "protobuf", "sentencepiece"]: + assert pkg not in shim._KEEP, f"{pkg} must still install for the notebooks that need it" + + +# -------------------------------------------------------------------------- +# Behaviour +# -------------------------------------------------------------------------- +def test_the_shipped_trl_cell_installs_nothing(shim): + # `!pip install --no-deps trl==0.22.2` is the last line of 378 notebooks. + assert _run(shim, SHIPPED_TRL_CELL) is None + + +def test_a_mixed_cell_keeps_only_the_unbaked_package(shim): + execd = _run( + shim, + ["--no-deps", "trl==0.22.2", "peft==0.14.0", "datasets==3.0.0", + "accelerate==1.0.0", UNBAKED], + ) + assert execd == ["--no-deps", UNBAKED], execd + + +def test_cuda_matched_wheels_are_not_replaced_by_pypi_builds(shim): + # torchao 0.17.0+cu128 and torchcodec 0.11.0+cu128 are resolved from the + # cu128 index; a PyPI pin swaps in a generic (or cu13) build. + assert _run(shim, ["torchao==0.15.0", "torchcodec==0.5"]) is None + + +def test_transformers_companions_cannot_desynchronise_the_sidecars(shim): + # Each sidecar ships its own matched huggingface_hub/tokenizers/safetensors; + # replacing the base-venv copies desynchronises every sidecar at once. + assert _run(shim, ["huggingface_hub==0.30.0", "tokenizers==0.20.0", + "safetensors==0.4.0"]) is None + + +def test_an_unbaked_package_still_installs(shim): + assert _run(shim, [UNBAKED]) == [UNBAKED] + assert _run(shim, [UNBAKED], tool = "uv") == [UNBAKED] + + +def test_protection_survives_a_requirements_file(shim, tmp_path): + req = tmp_path / "requirements.txt" + req.write_text(f"trl==0.22.2\npeft==0.14.0\ndatasets==3.0.0\n{UNBAKED}\n") + execd = _run(shim, ["-r", str(req)]) + assert execd is not None and execd[0] == "-r" + filtered = Path(execd[1]).read_text() + assert UNBAKED in filtered + for dropped in ("trl", "peft", "datasets"): + assert dropped not in filtered, f"{dropped} slipped through the requirements file" + + +def test_protection_survives_a_direct_wheel_url(shim): + url = "https://files.pythonhosted.org/x/trl-0.22.2-py3-none-any.whl" + assert _run(shim, [url, UNBAKED]) == [UNBAKED] + + +def test_protection_survives_an_editable_vcs_install(shim): + assert _run(shim, ["-e", "git+https://github.com/huggingface/trl.git", UNBAKED]) == [UNBAKED] + + +def test_forwarded_installs_pin_the_protected_set_for_the_resolver(shim): + # Argument filtering alone does not stop a dependency of the kept target from + # dragging peft/datasets back down -- which is how peft 0.19.1 became 0.14.0 + # with no notebook ever naming peft. Every forwarded install carries pins. + with pytest.MonkeyPatch.context() as mp: + mp.setattr(shim.sys, "argv", ["pip", "install", UNBAKED]) + with pytest.raises(_Exec) as exc: + shim.main() + argv = exc.value.argv + assert "--constraint" in argv + pins = Path(argv[argv.index("--constraint") + 1]).read_text() + names = {line.split("==")[0].lower().replace("_", "-") for line in pins.splitlines() if line} + # only the installed subset is pinned, but nothing outside the protected set + assert names, "the constraints file must not be empty" + assert all( + n in shim._KEEP or n == "transformers" or n.startswith("nvidia-") for n in names + ), sorted(names) diff --git a/tests/python/test_unsloth_pip_shim.py b/tests/python/test_unsloth_pip_shim.py index 7a9d818bed..0573efcf31 100644 --- a/tests/python/test_unsloth_pip_shim.py +++ b/tests/python/test_unsloth_pip_shim.py @@ -102,7 +102,7 @@ def _run(shim, tool, args): # -------------------------------------------------------------------------- # Item 3541142907 -- pair -e/--editable with its target. A protected editable -# drops the flag WITH its value (never `pip install -e peft`); an unprotected +# drops the flag WITH its value (never `pip install -e snac`); an unprotected # editable is forwarded verbatim. # -------------------------------------------------------------------------- UNSLOTH_VCS = "git+https://github.com/unslothai/unsloth.git#egg=unsloth" @@ -114,13 +114,13 @@ KEPT = object() @pytest.mark.parametrize( "args, expected", [ - pytest.param(["-e", UNSLOTH_VCS, "peft"], ["peft"], id = "sep-protected"), + pytest.param(["-e", UNSLOTH_VCS, "snac"], ["snac"], id = "sep-protected"), # nothing left to install -> no-op, no dangling -e pytest.param(["-e", UNSLOTH_VCS], None, id = "sep-only-protected-noop"), pytest.param(["-e", "./localpkg"], KEPT, id = "sep-unprotected-kept"), - pytest.param(["--editable=" + UNSLOTH_VCS, "peft"], ["peft"], id = "inline-protected"), + pytest.param(["--editable=" + UNSLOTH_VCS, "snac"], ["snac"], id = "inline-protected"), pytest.param(["--editable=./localpkg"], KEPT, id = "inline-unprotected-kept"), - pytest.param(["-e" + UNSLOTH_VCS, "peft"], ["peft"], id = "attached-protected"), + pytest.param(["-e" + UNSLOTH_VCS, "snac"], ["snac"], id = "attached-protected"), ], ) def test_editable_forms(shim, args, expected): @@ -130,15 +130,15 @@ def test_editable_forms(shim, args, expected): # -------------------------------------------------------------------------- # Item 3541142906 -- filter uv -P/--upgrade-package values. `uv pip install -# -P torch peft` must not let uv refresh baked torch; a pinned transformers +# -P torch snac` must not let uv refresh baked torch; a pinned transformers # upgrade selector still feeds the sidecar marker. # -------------------------------------------------------------------------- @pytest.mark.parametrize( "args, expected, expected_marker", [ - pytest.param(["-P", "torch", "peft"], ["peft"], None, id = "protected-dropped"), - pytest.param(["--upgrade-package=transformers", "peft"], ["peft"], None, id = "inline"), - pytest.param(["-P", "transformers==4.55.0", "peft"], ["peft"], "4.55.0", id = "tf-pin"), + pytest.param(["-P", "torch", "snac"], ["snac"], None, id = "protected-dropped"), + pytest.param(["--upgrade-package=transformers", "snac"], ["snac"], None, id = "inline"), + pytest.param(["-P", "transformers==4.55.0", "snac"], ["snac"], "4.55.0", id = "tf-pin"), pytest.param(["-P", "requests", "requests"], KEPT, None, id = "unprotected-kept"), # -P is not itself a target pytest.param(["-P", "torch"], None, None, id = "only-protected-noop"), @@ -300,16 +300,16 @@ def test_attached_short_requirement_file_filtered(shim, tmp_path): def test_attached_short_constraint_file_filtered(shim, tmp_path): constraints = tmp_path / "constraints.txt" constraints.write_text("torch==2.11.0\n", encoding = "utf-8") - execd, _ = _run(shim, "pip", ["-c" + str(constraints), "peft"]) + execd, _ = _run(shim, "pip", ["-c" + str(constraints), "snac"]) assert execd is not None and execd[0] == "-c", execd - assert "peft" in execd + assert "snac" in execd filtered = Path(execd[1]).read_text(encoding = "utf-8") assert "torch" not in filtered def test_attached_short_upgrade_package_protected_dropped(shim): - execd, _ = _run(shim, "uv", ["-Ptorch", "peft"]) - assert execd == ["peft"], execd + execd, _ = _run(shim, "uv", ["-Ptorch", "snac"]) + assert execd == ["snac"], execd assert "torch" not in execd and "-P" not in execd @@ -337,13 +337,13 @@ def test_bare_wheel_filename_forms(shim, args, expected): # -------------------------------------------------------------------------- def test_vcs_url_without_egg_protected_dropped(shim): # git+https://github.com/huggingface/transformers.git -> transformers. - execd, _ = _run(shim, "pip", ["git+https://github.com/huggingface/transformers.git", "peft"]) - assert execd == ["peft"], execd + execd, _ = _run(shim, "pip", ["git+https://github.com/huggingface/transformers.git", "snac"]) + assert execd == ["snac"], execd def test_vcs_url_without_egg_with_ref_dropped(shim): - execd, _ = _run(shim, "pip", ["git+https://github.com/unslothai/unsloth-zoo.git@main", "peft"]) - assert execd == ["peft"], execd + execd, _ = _run(shim, "pip", ["git+https://github.com/unslothai/unsloth-zoo.git@main", "snac"]) + assert execd == ["snac"], execd def test_vcs_url_without_egg_unprotected_kept(shim): @@ -364,10 +364,10 @@ R_URL = "https://example.com/reqs.txt" [ # dropped, and no dangling -r left behind pytest.param(["-r", R_URL], None, id = "sep-r-only-noop"), - pytest.param(["-r", R_URL, "peft"], ["peft"], id = "sep-r-target-kept"), - pytest.param(["--requirement=" + R_URL, "peft"], ["peft"], id = "inline-r"), - pytest.param(["-r" + R_URL, "peft"], ["peft"], id = "attached-r"), - pytest.param(["-c", "https://example.com/constraints.txt", "peft"], ["peft"], id = "sep-c"), + pytest.param(["-r", R_URL, "snac"], ["snac"], id = "sep-r-target-kept"), + pytest.param(["--requirement=" + R_URL, "snac"], ["snac"], id = "inline-r"), + pytest.param(["-r" + R_URL, "snac"], ["snac"], id = "attached-r"), + pytest.param(["-c", "https://example.com/constraints.txt", "snac"], ["snac"], id = "sep-c"), ], ) def test_remote_requirement_and_constraint_urls_refused(shim, args, expected): @@ -392,18 +392,18 @@ def test_nested_remote_include_dropped(shim, tmp_path): # stripped so they cannot rebuild already-satisfied baked deps. # -------------------------------------------------------------------------- def test_force_reinstall_flag_stripped(shim): - execd, _ = _run(shim, "pip", ["--force-reinstall", "peft"]) - assert execd == ["peft"], execd + execd, _ = _run(shim, "pip", ["--force-reinstall", "snac"]) + assert execd == ["snac"], execd def test_ignore_installed_short_flag_stripped(shim): - execd, _ = _run(shim, "pip", ["-I", "peft"]) - assert execd == ["peft"], execd + execd, _ = _run(shim, "pip", ["-I", "snac"]) + assert execd == ["snac"], execd def test_uv_reinstall_flag_stripped(shim): - execd, _ = _run(shim, "uv", ["--reinstall", "peft"]) - assert execd == ["peft"], execd + execd, _ = _run(shim, "uv", ["--reinstall", "snac"]) + assert execd == ["snac"], execd # -------------------------------------------------------------------------- @@ -413,11 +413,11 @@ def test_uv_reinstall_flag_stripped(shim): @pytest.mark.parametrize( "args, expected, expected_marker", [ - pytest.param(["--reinstall-package", "torch", "peft"], ["peft"], None, id = "sep-protected"), - pytest.param(["--reinstall-package=torch", "peft"], ["peft"], None, id = "inline-protected"), + pytest.param(["--reinstall-package", "torch", "snac"], ["snac"], None, id = "sep-protected"), + pytest.param(["--reinstall-package=torch", "snac"], ["snac"], None, id = "inline-protected"), pytest.param(["--reinstall-package", "requests", "requests"], KEPT, None, id = "unprotected"), pytest.param( - ["--reinstall-package", "transformers==4.55.0", "peft"], ["peft"], "4.55.0", id = "tf-pin" + ["--reinstall-package", "transformers==4.55.0", "snac"], ["snac"], "4.55.0", id = "tf-pin" ), ], ) @@ -436,9 +436,9 @@ SDIST_URL = "https://files.pythonhosted.org/packages/aa/unsloth-2026.7.1.tar.gz" @pytest.mark.parametrize( "args, expected", [ - pytest.param([SDIST_URL, "peft"], ["peft"], id = "url-protected"), + pytest.param([SDIST_URL, "snac"], ["snac"], id = "url-protected"), pytest.param(["torch-2.11.0.tar.gz"], None, id = "bare-protected"), - pytest.param(["./transformers-4.55.0.zip", "peft"], ["peft"], id = "zip-protected"), + pytest.param(["./transformers-4.55.0.zip", "snac"], ["snac"], id = "zip-protected"), # flashinfer-python is protected; the name must survive the hyphen split. pytest.param(["flashinfer-python-0.5.0.tar.gz"], None, id = "hyphenated-name"), pytest.param(["numpy-2.1.0.tar.gz"], KEPT, id = "unprotected-kept"), @@ -466,9 +466,9 @@ def test_uv_plural_requirements_filtered(shim, tmp_path): def test_uv_plural_constraints_filtered(shim, tmp_path): constraints = tmp_path / "constraints.txt" constraints.write_text("torch==2.11.0\n", encoding = "utf-8") - execd, _ = _run(shim, "uv", ["--constraints", str(constraints), "peft"]) + execd, _ = _run(shim, "uv", ["--constraints", str(constraints), "snac"]) assert execd is not None and execd[0] == "--constraints", execd - assert "peft" in execd + assert "snac" in execd filtered = Path(execd[1]).read_text(encoding = "utf-8") assert "torch" not in filtered @@ -480,12 +480,12 @@ def test_uv_plural_constraints_filtered(shim, tmp_path): @pytest.mark.parametrize( "args, expected", [ - pytest.param(["-U", "--upgrade-strategy", "eager", "peft"], ["-U", "peft"], id = "eager"), - pytest.param(["--upgrade-strategy=eager", "peft"], ["peft"], id = "inline-eager"), + pytest.param(["-U", "--upgrade-strategy", "eager", "snac"], ["-U", "snac"], id = "eager"), + pytest.param(["--upgrade-strategy=eager", "snac"], ["snac"], id = "inline-eager"), # only-if-needed is pip's default, so dropping it is a harmless no-op that # keeps the kept target installing normally. pytest.param( - ["--upgrade-strategy", "only-if-needed", "peft"], ["peft"], id = "only-if-needed" + ["--upgrade-strategy", "only-if-needed", "snac"], ["snac"], id = "only-if-needed" ), ], ) @@ -512,7 +512,7 @@ def _raw_execd(shim, tool, args): def test_forwarded_install_carries_protected_constraints(shim): - execd = _raw_execd(shim, "pip", ["peft"]) + execd = _raw_execd(shim, "pip", ["snac"]) assert execd is not None and execd[-2] == "--constraint", execd pins = Path(execd[-1]).read_text(encoding = "utf-8").strip().splitlines() assert pins, "constraints file must pin the installed protected packages" @@ -600,8 +600,8 @@ def test_filter_write_failure_clean_file_passes_through(shim, tmp_path, monkeypa # resolver-wide destructive switches. # -------------------------------------------------------------------------- def test_uv_exact_flag_stripped(shim): - execd, _ = _run(shim, "uv", ["--exact", "peft"]) - assert execd == ["peft"], execd + execd, _ = _run(shim, "uv", ["--exact", "snac"]) + assert execd == ["snac"], execd # -------------------------------------------------------------------------- @@ -620,14 +620,14 @@ def _make_local_project(tmp_path, dirname, project_name): def test_local_dir_protected_by_metadata_dropped(shim, tmp_path): # Directory name is innocuous; pyproject names a protected package. path = _make_local_project(tmp_path, "my-checkout", "transformers") - execd, _ = _run(shim, "pip", [path, "peft"]) - assert execd == ["peft"], execd + execd, _ = _run(shim, "pip", [path, "snac"]) + assert execd == ["snac"], execd def test_local_dir_protected_editable_dropped(shim, tmp_path): path = _make_local_project(tmp_path, "unsloth", "unsloth") - execd, _ = _run(shim, "pip", ["-e", path, "peft"]) - assert execd == ["peft"], execd + execd, _ = _run(shim, "pip", ["-e", path, "snac"]) + assert execd == ["snac"], execd assert "-e" not in execd @@ -636,8 +636,8 @@ def test_local_dir_basename_fallback_setup_py(shim, tmp_path): proj = tmp_path / "torch" proj.mkdir() (proj / "setup.py").write_text("from setuptools import setup\nsetup()\n") - execd, _ = _run(shim, "pip", [str(proj), "peft"]) - assert execd == ["peft"], execd + execd, _ = _run(shim, "pip", [str(proj), "snac"]) + assert execd == ["snac"], execd def test_local_dir_unprotected_kept(shim, tmp_path): @@ -656,8 +656,8 @@ def test_local_dir_without_metadata_passes_through(shim, tmp_path): # -------------------------------------------------------------------------- # Item 3592835033 -- every uv/pip value-taking flag must be in _VALUE_FLAGS. # `--torch-backend cu128 torch` used to drop torch but keep the separated flag -# pair, exec'ing uv with no target; `--extra torch peft` misread the extra NAME -# "torch" as a target, leaving a dangling `--extra` that swallowed peft. +# pair, exec'ing uv with no target; `--extra torch snac` misread the extra NAME +# "torch" as a target, leaving a dangling `--extra` that swallowed snac. @pytest.mark.parametrize( @@ -689,15 +689,15 @@ def test_value_flag_protected_only_noops(shim, tool, flag, value): ], ) def test_value_flag_pair_forwarded_with_kept_target(shim, tool, flag, value): - execd, _ = _run(shim, tool, [flag, value, "torch", "peft"]) - assert execd == [flag, value, "peft"], execd + execd, _ = _run(shim, tool, [flag, value, "torch", "snac"]) + assert execd == [flag, value, "snac"], execd def test_extra_value_is_not_a_protected_target(shim): # `--extra torch` names an EXTRA, not the torch package: the pair stays and - # peft is not swallowed by a dangling --extra. - execd, _ = _run(shim, "uv", ["--extra", "torch", "peft"]) - assert execd == ["--extra", "torch", "peft"], execd + # snac is not swallowed by a dangling --extra. + execd, _ = _run(shim, "uv", ["--extra", "torch", "snac"]) + assert execd == ["--extra", "torch", "snac"], execd def _value_flags_from_help(cmd): @@ -762,8 +762,8 @@ def test_uv_help_value_flags_all_classified(shim): ], ) def test_vcs_slash_ref_still_protected(shim, url): - execd, _ = _run(shim, "pip", [url, "peft"]) - assert execd == ["peft"], execd + execd, _ = _run(shim, "pip", [url, "snac"]) + assert execd == ["snac"], execd def test_vcs_slash_ref_unprotected_kept(shim):