11import os
2+ import subprocess
23import sys
34from unittest .mock import MagicMock , patch
45
@@ -34,19 +35,98 @@ def test_dockerize_command(monkeypatch, capsys):
3435
3536
3637@patch ("litserve.cli.is_package_installed" )
37- @patch ("subprocess.check_call" )
38- def test_ensure_lightning_installed (mock_check_call , mock_is_package_installed ):
38+ @patch ("litserve.cli.importlib.util.find_spec" )
39+ @patch ("litserve.cli.shutil.which" )
40+ @patch ("subprocess.run" )
41+ def test_ensure_lightning_installed_with_pip (mock_run , mock_which , mock_find_spec , mock_is_package_installed ):
3942 mock_is_package_installed .return_value = False
43+ mock_find_spec .return_value = True # pip available
44+ mock_which .return_value = None # uv not available
4045 _ensure_lightning_installed ()
41- mock_check_call .assert_called_once_with ([sys .executable , "-m" , "pip" , "install" , "-U" , "lightning-sdk" ])
46+ mock_run .assert_called_once_with ([sys .executable , "-m" , "pip" , "install" , "-U" , "lightning-sdk" ], check = True )
47+
48+
49+ @patch ("litserve.cli.is_package_installed" )
50+ @patch ("litserve.cli.importlib.util.find_spec" )
51+ @patch ("litserve.cli.shutil.which" )
52+ @patch ("subprocess.run" )
53+ def test_ensure_lightning_installed_pip_preferred (mock_run , mock_which , mock_find_spec , mock_is_package_installed ):
54+ """When both pip and uv are available, pip should be used first."""
55+ mock_is_package_installed .return_value = False
56+ mock_find_spec .return_value = True # pip available
57+ mock_which .return_value = "/usr/bin/uv" # uv also available
58+ _ensure_lightning_installed ()
59+ mock_run .assert_called_once_with ([sys .executable , "-m" , "pip" , "install" , "-U" , "lightning-sdk" ], check = True )
60+
61+
62+ @patch ("litserve.cli.is_package_installed" )
63+ @patch ("litserve.cli.importlib.util.find_spec" )
64+ @patch ("litserve.cli.shutil.which" )
65+ @patch ("subprocess.run" )
66+ def test_ensure_lightning_installed_with_uv (mock_run , mock_which , mock_find_spec , mock_is_package_installed ):
67+ mock_is_package_installed .return_value = False
68+ mock_find_spec .return_value = None # pip not available
69+ mock_which .return_value = "/usr/bin/uv" # uv available
70+ _ensure_lightning_installed ()
71+ mock_run .assert_called_once_with (["uv" , "pip" , "install" , "-U" , "lightning-sdk" ], check = True )
72+
73+
74+ @patch ("litserve.cli.is_package_installed" )
75+ @patch ("litserve.cli.importlib.util.find_spec" )
76+ @patch ("litserve.cli.shutil.which" )
77+ @patch ("subprocess.run" )
78+ def test_ensure_lightning_installed_fallback_to_uv (mock_run , mock_which , mock_find_spec , mock_is_package_installed ):
79+ """When pip fails, should fall back to uv."""
80+ mock_is_package_installed .return_value = False
81+ mock_find_spec .return_value = True # pip available
82+ mock_which .return_value = "/usr/bin/uv" # uv also available
83+ mock_run .side_effect = [subprocess .CalledProcessError (1 , "pip" ), None ] # pip fails, uv succeeds
84+ _ensure_lightning_installed ()
85+ assert mock_run .call_count == 2
86+ mock_run .assert_called_with (["uv" , "pip" , "install" , "-U" , "lightning-sdk" ], check = True )
87+
88+
89+ @patch ("litserve.cli.is_package_installed" )
90+ @patch ("litserve.cli.importlib.util.find_spec" )
91+ @patch ("litserve.cli.shutil.which" )
92+ @patch ("subprocess.run" )
93+ def test_ensure_lightning_installed_failure (mock_run , mock_which , mock_find_spec , mock_is_package_installed ):
94+ """When all available installers fail, should exit with error."""
95+ mock_is_package_installed .return_value = False
96+ mock_find_spec .return_value = True # pip available
97+ mock_which .return_value = "/usr/bin/uv" # uv also available
98+ mock_run .side_effect = subprocess .CalledProcessError (1 , "install" ) # both fail
99+
100+ with pytest .raises (SystemExit , match = "Failed to install lightning-sdk" ):
101+ _ensure_lightning_installed ()
102+ assert mock_run .call_count == 2 # tried both pip and uv
103+
104+
105+ @patch ("litserve.cli.is_package_installed" )
106+ @patch ("litserve.cli.importlib.util.find_spec" )
107+ @patch ("litserve.cli.shutil.which" )
108+ @patch ("subprocess.run" )
109+ def test_ensure_lightning_installed_no_installer_available (
110+ mock_run , mock_which , mock_find_spec , mock_is_package_installed
111+ ):
112+ """When neither pip nor uv is available, should exit with error."""
113+ mock_is_package_installed .return_value = False
114+ mock_find_spec .return_value = None # pip not available
115+ mock_which .return_value = None # uv not available
116+
117+ with pytest .raises (SystemExit , match = "Failed to install lightning-sdk" ):
118+ _ensure_lightning_installed ()
119+ mock_run .assert_not_called () # no installer was tried
42120
43121
44122# TODO: Remove this once we have a fix for Python 3.10
45123@pytest .mark .skipif (sys .version_info [:2 ] in [(3 , 10 )], reason = "Test fails on Python 3.10" )
46124@patch ("litserve.cli.is_package_installed" )
47- @patch ("subprocess.check_call" )
125+ @patch ("litserve.cli.importlib.util.find_spec" )
126+ @patch ("litserve.cli.shutil.which" )
127+ @patch ("subprocess.run" )
48128@patch ("builtins.__import__" )
49- def test_cli_main_lightning_not_installed (mock_import , mock_check_call , mock_is_package_installed ):
129+ def test_cli_main_lightning_not_installed (mock_import , mock_run , mock_which , mock_find_spec , mock_is_package_installed ):
50130 # Create a mock for the lightning_sdk module and its components
51131 mock_lightning_sdk = MagicMock ()
52132 mock_lightning_sdk .cli .entrypoint .main_cli = MagicMock ()
@@ -58,6 +138,8 @@ def side_effect(name, *args, **kwargs):
58138 return __import__ (name , * args , ** kwargs )
59139
60140 mock_import .side_effect = side_effect
141+ mock_find_spec .return_value = True # pip available
142+ mock_which .return_value = None # uv not available
61143
62144 # Test when lightning_sdk is not installed but gets installed dynamically
63145 mock_is_package_installed .side_effect = [False , True ] # First call returns False, second call returns True
@@ -66,7 +148,7 @@ def side_effect(name, *args, **kwargs):
66148 with patch .object (sys , "argv" , test_args ):
67149 cli_main ()
68150
69- mock_check_call .assert_called_once_with ([sys .executable , "-m" , "pip" , "install" , "-U" , "lightning-sdk" ])
151+ mock_run .assert_called_once_with ([sys .executable , "-m" , "pip" , "install" , "-U" , "lightning-sdk" ], check = True )
70152
71153
72154@pytest .mark .skipif (sys .version_info [:2 ] in [(3 , 10 )], reason = "Test fails on Python 3.10" )
0 commit comments