from __future__ import annotations import ast from pathlib import Path SAVE_PY = Path(__file__).resolve().parents[2] / "unsloth" / "save.py" def _function_calls(source: str, function_name: str) -> list[ast.Call]: tree = ast.parse(source, filename = str(SAVE_PY)) for node in tree.body: if isinstance(node, ast.FunctionDef) and node.name == function_name: return [child for child in ast.walk(node) if isinstance(child, ast.Call)] raise AssertionError(f"Function {function_name} not found in save.py") def _assert_safe_ggml_calls(calls: list[ast.Call]) -> None: popen_calls = [] for call in calls: if isinstance(call.func, ast.Attribute) and call.func.attr == "Popen": if ( isinstance(call.func.value, ast.Name) and call.func.value.id == "subprocess" ): popen_calls.append(call) assert popen_calls, "Expected at least one subprocess.Popen call" ggml_calls = [] for call in popen_calls: if not call.args: continue argv = call.args[0] if isinstance(argv, ast.List) and len(argv.elts) >= 2: second_arg = argv.elts[1] if ( isinstance(second_arg, ast.Constant) and second_arg.value == "llama.cpp/convert-lora-to-ggml.py" ): ggml_calls.append(call) assert ggml_calls, "Expected the GGML conversion subprocess call" for call in ggml_calls: shell_kwargs = [ keyword for keyword in call.keywords if keyword.arg == "shell" and isinstance(keyword.value, ast.Constant) and keyword.value.value is True ] assert not shell_kwargs, "subprocess.Popen must not use shell=True" assert call.args, "subprocess.Popen must receive argv as a positional argument" argv = call.args[0] assert isinstance( argv, ast.List ), "subprocess.Popen must be called with an argv list" assert len(argv.elts) == 5, "GGML conversion argv should have five elements" second_arg = argv.elts[1] assert isinstance(second_arg, ast.Constant) assert second_arg.value == "llama.cpp/convert-lora-to-ggml.py" def test_ggml_conversion_paths_do_not_use_shell() -> None: source = SAVE_PY.read_text(encoding = "utf-8") for function_name in ( "unsloth_convert_lora_to_ggml_and_push_to_hub", "unsloth_convert_lora_to_ggml_and_save_locally", ): calls = _function_calls(source, function_name) _assert_safe_ggml_calls(calls)