Skip to content

Commit 7086b0f

Browse files
committed
[RuntimeEnv] Always uninstall ray before installing ray
1 parent 278aa09 commit 7086b0f

File tree

2 files changed

+125
-2
lines changed

2 files changed

+125
-2
lines changed

python/ray/_private/runtime_env/install_ray_or_pip_packages.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@ def find_first_matching_wheel(whl_dir: str, whl_file_name: str) -> str:
1818
matches = list(dir_path.glob(whl_file_name))
1919
return str(matches[0]) if matches else ""
2020

21+
# Uninstall to ensure the python environments are clean
22+
def uninstall_ray():
23+
pip_uninstall_command = [sys.executable, "-m", "pip", "uninstall", "-y", "ray", "ant-ray", "ant-ray-nightly"]
24+
result = subprocess.run(pip_uninstall_command)
25+
logger.info("Uninstalled ray: {}. Return code: {}".format(pip_uninstall_command, result.returncode))
26+
27+
2128
def install_ray_package(ray_version, whl_dir):
2229
pip_install_command = [sys.executable, "-m", "pip", "install", "-U"]
2330

2431
if whl_dir:
2532
# generate whl file name
2633
whl_file_name = (
27-
f"ant_ray-*cp{sys.version_info.major}{sys.version_info.minor}*.whl"
34+
f"ant_ray*cp{sys.version_info.major}{sys.version_info.minor}*.whl"
2835
)
2936

3037
# got the first matched wheel file
@@ -34,7 +41,7 @@ def install_ray_package(ray_version, whl_dir):
3441
pip_install_command.append(whl_file_path + "[default]")
3542
else:
3643
# generate ray package name
37-
ray_package_name = f"ant_ray=={ray_version}"
44+
ray_package_name = f"ant_ray[default]=={ray_version}"
3845
pip_install_command.append(ray_package_name)
3946

4047
logger.info("Starting install ray: {}".format(pip_install_command))
@@ -97,9 +104,11 @@ def install_pip_package(pip_packages, isolate_pip_installation):
97104
)
98105

99106
if args.ray_version or args.whl_dir:
107+
uninstall_ray()
100108
install_ray_package(args.ray_version, args.whl_dir)
101109
if args.packages:
102110
pip_packages = json.loads(
103111
base64.b64decode(args.packages.encode("utf-8")).decode("utf-8")
104112
)
105113
install_pip_package(pip_packages, isolate_pip_installation)
114+
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""
2+
Unit tests for uninstall_ray function in install_ray_or_pip_packages.py
3+
"""
4+
import os
5+
import sys
6+
import subprocess
7+
import unittest
8+
from unittest.mock import patch, MagicMock
9+
10+
# Add the project root to Python path to import ray modules
11+
project_root = os.path.join(os.path.dirname(__file__), '..', '..')
12+
sys.path.insert(0, project_root)
13+
14+
from ray._private.runtime_env.install_ray_or_pip_packages import uninstall_ray
15+
16+
17+
class TestUninstallRay(unittest.TestCase):
18+
"""Unit test class for uninstall_ray function"""
19+
20+
def setUp(self):
21+
"""Set up test fixtures before each test method."""
22+
pass
23+
24+
def tearDown(self):
25+
"""Clean up after each test method."""
26+
pass
27+
28+
@patch('ray._private.runtime_env.install_ray_or_pip_packages.subprocess.run')
29+
@patch('ray._private.runtime_env.install_ray_or_pip_packages.logger')
30+
def test_uninstall_ray_success(self, mock_logger, mock_subprocess_run):
31+
"""Test successful uninstallation of ray packages"""
32+
# Set up mock return value
33+
mock_result = MagicMock()
34+
mock_result.returncode = 0
35+
mock_subprocess_run.return_value = mock_result
36+
37+
# Call the function under test
38+
uninstall_ray()
39+
40+
# Verify subprocess.run was called correctly
41+
expected_command = [sys.executable, '-m', 'pip', 'uninstall', '-y',
42+
'ray', 'ant-ray', 'ant-ray-nightly']
43+
mock_subprocess_run.assert_called_once_with(expected_command)
44+
45+
# Verify logger.info was called
46+
mock_logger.info.assert_called_once()
47+
call_args = mock_logger.info.call_args[0][0]
48+
self.assertIn("Uninstalled ray", call_args)
49+
self.assertIn("Return code: 0", call_args)
50+
51+
@patch('ray._private.runtime_env.install_ray_or_pip_packages.subprocess.run')
52+
@patch('ray._private.runtime_env.install_ray_or_pip_packages.logger')
53+
def test_uninstall_ray_failure(self, mock_logger, mock_subprocess_run):
54+
"""Test uninstallation with non-zero return code"""
55+
# Set up mock return value
56+
mock_result = MagicMock()
57+
mock_result.returncode = 1
58+
mock_subprocess_run.return_value = mock_result
59+
60+
# Call the function under test
61+
uninstall_ray()
62+
63+
# Verify subprocess.run was called
64+
expected_command = [sys.executable, '-m', 'pip', 'uninstall', '-y',
65+
'ray', 'ant-ray', 'ant-ray-nightly']
66+
mock_subprocess_run.assert_called_once_with(expected_command)
67+
68+
# Verify logger.info was called even with non-zero return code
69+
mock_logger.info.assert_called_once()
70+
call_args = mock_logger.info.call_args[0][0]
71+
self.assertIn("Uninstalled ray", call_args)
72+
self.assertIn("Return code: 1", call_args)
73+
74+
@patch('ray._private.runtime_env.install_ray_or_pip_packages.subprocess.run')
75+
@patch('ray._private.runtime_env.install_ray_or_pip_packages.logger')
76+
def test_uninstall_ray_command_structure(self, mock_logger, mock_subprocess_run):
77+
"""Test the structure of the uninstall command"""
78+
mock_result = MagicMock()
79+
mock_result.returncode = 0
80+
mock_subprocess_run.return_value = mock_result
81+
82+
uninstall_ray()
83+
84+
# Verify the command structure
85+
call_args = mock_subprocess_run.call_args[0][0]
86+
self.assertEqual(call_args[0], sys.executable)
87+
self.assertEqual(call_args[1], '-m')
88+
self.assertEqual(call_args[2], 'pip')
89+
self.assertEqual(call_args[3], 'uninstall')
90+
self.assertEqual(call_args[4], '-y')
91+
92+
# Verify all expected packages are included
93+
packages = call_args[5:]
94+
self.assertIn('ray', packages)
95+
self.assertIn('ant-ray', packages)
96+
self.assertIn('ant-ray-nightly', packages)
97+
self.assertEqual(len(packages), 3)
98+
99+
@patch('ray._private.runtime_env.install_ray_or_pip_packages.subprocess.run')
100+
@patch('ray._private.runtime_env.install_ray_or_pip_packages.logger')
101+
def test_uninstall_ray_exception_propagation(self, mock_logger, mock_subprocess_run):
102+
"""Test that exceptions from subprocess.run are propagated"""
103+
# Mock subprocess.run to raise an exception
104+
mock_subprocess_run.side_effect = Exception("Mocked subprocess error")
105+
106+
# The exception should be propagated
107+
with self.assertRaises(Exception) as context:
108+
uninstall_ray()
109+
110+
self.assertEqual(str(context.exception), "Mocked subprocess error")
111+
112+
113+
if __name__ == '__main__':
114+
unittest.main()

0 commit comments

Comments
 (0)