hatch-xclam 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hatch/__init__.py +21 -0
- hatch/cli_hatch.py +2748 -0
- hatch/environment_manager.py +1375 -0
- hatch/installers/__init__.py +25 -0
- hatch/installers/dependency_installation_orchestrator.py +636 -0
- hatch/installers/docker_installer.py +545 -0
- hatch/installers/hatch_installer.py +198 -0
- hatch/installers/installation_context.py +109 -0
- hatch/installers/installer_base.py +195 -0
- hatch/installers/python_installer.py +342 -0
- hatch/installers/registry.py +179 -0
- hatch/installers/system_installer.py +588 -0
- hatch/mcp_host_config/__init__.py +38 -0
- hatch/mcp_host_config/backup.py +458 -0
- hatch/mcp_host_config/host_management.py +572 -0
- hatch/mcp_host_config/models.py +602 -0
- hatch/mcp_host_config/reporting.py +181 -0
- hatch/mcp_host_config/strategies.py +513 -0
- hatch/package_loader.py +263 -0
- hatch/python_environment_manager.py +734 -0
- hatch/registry_explorer.py +171 -0
- hatch/registry_retriever.py +335 -0
- hatch/template_generator.py +179 -0
- hatch_xclam-0.7.0.dist-info/METADATA +150 -0
- hatch_xclam-0.7.0.dist-info/RECORD +93 -0
- hatch_xclam-0.7.0.dist-info/WHEEL +5 -0
- hatch_xclam-0.7.0.dist-info/entry_points.txt +2 -0
- hatch_xclam-0.7.0.dist-info/licenses/LICENSE +661 -0
- hatch_xclam-0.7.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/run_environment_tests.py +124 -0
- tests/test_cli_version.py +122 -0
- tests/test_data/packages/basic/base_pkg/hatch_mcp_server.py +18 -0
- tests/test_data/packages/basic/base_pkg/mcp_server.py +21 -0
- tests/test_data/packages/basic/base_pkg_v2/hatch_mcp_server.py +18 -0
- tests/test_data/packages/basic/base_pkg_v2/mcp_server.py +21 -0
- tests/test_data/packages/basic/utility_pkg/hatch_mcp_server.py +18 -0
- tests/test_data/packages/basic/utility_pkg/mcp_server.py +21 -0
- tests/test_data/packages/dependencies/complex_dep_pkg/hatch_mcp_server.py +18 -0
- tests/test_data/packages/dependencies/complex_dep_pkg/mcp_server.py +21 -0
- tests/test_data/packages/dependencies/docker_dep_pkg/hatch_mcp_server.py +18 -0
- tests/test_data/packages/dependencies/docker_dep_pkg/mcp_server.py +21 -0
- tests/test_data/packages/dependencies/mixed_dep_pkg/hatch_mcp_server.py +18 -0
- tests/test_data/packages/dependencies/mixed_dep_pkg/mcp_server.py +21 -0
- tests/test_data/packages/dependencies/python_dep_pkg/hatch_mcp_server.py +18 -0
- tests/test_data/packages/dependencies/python_dep_pkg/mcp_server.py +21 -0
- tests/test_data/packages/dependencies/simple_dep_pkg/hatch_mcp_server.py +18 -0
- tests/test_data/packages/dependencies/simple_dep_pkg/mcp_server.py +21 -0
- tests/test_data/packages/dependencies/system_dep_pkg/hatch_mcp_server.py +18 -0
- tests/test_data/packages/dependencies/system_dep_pkg/mcp_server.py +21 -0
- tests/test_data/packages/error_scenarios/circular_dep_pkg/hatch_mcp_server.py +18 -0
- tests/test_data/packages/error_scenarios/circular_dep_pkg/mcp_server.py +21 -0
- tests/test_data/packages/error_scenarios/circular_dep_pkg_b/hatch_mcp_server.py +18 -0
- tests/test_data/packages/error_scenarios/circular_dep_pkg_b/mcp_server.py +21 -0
- tests/test_data/packages/error_scenarios/invalid_dep_pkg/hatch_mcp_server.py +18 -0
- tests/test_data/packages/error_scenarios/invalid_dep_pkg/mcp_server.py +21 -0
- tests/test_data/packages/error_scenarios/version_conflict_pkg/hatch_mcp_server.py +18 -0
- tests/test_data/packages/error_scenarios/version_conflict_pkg/mcp_server.py +21 -0
- tests/test_data/packages/schema_versions/schema_v1_1_0_pkg/main.py +11 -0
- tests/test_data/packages/schema_versions/schema_v1_2_0_pkg/main.py +11 -0
- tests/test_data/packages/schema_versions/schema_v1_2_1_pkg/hatch_mcp_server.py +18 -0
- tests/test_data/packages/schema_versions/schema_v1_2_1_pkg/mcp_server.py +21 -0
- tests/test_data_utils.py +472 -0
- tests/test_dependency_orchestrator_consent.py +266 -0
- tests/test_docker_installer.py +524 -0
- tests/test_env_manip.py +991 -0
- tests/test_hatch_installer.py +179 -0
- tests/test_installer_base.py +221 -0
- tests/test_mcp_atomic_operations.py +276 -0
- tests/test_mcp_backup_integration.py +308 -0
- tests/test_mcp_cli_all_host_specific_args.py +303 -0
- tests/test_mcp_cli_backup_management.py +295 -0
- tests/test_mcp_cli_direct_management.py +453 -0
- tests/test_mcp_cli_discovery_listing.py +582 -0
- tests/test_mcp_cli_host_config_integration.py +823 -0
- tests/test_mcp_cli_package_management.py +360 -0
- tests/test_mcp_cli_partial_updates.py +859 -0
- tests/test_mcp_environment_integration.py +520 -0
- tests/test_mcp_host_config_backup.py +257 -0
- tests/test_mcp_host_configuration_manager.py +331 -0
- tests/test_mcp_host_registry_decorator.py +348 -0
- tests/test_mcp_pydantic_architecture_v4.py +603 -0
- tests/test_mcp_server_config_models.py +242 -0
- tests/test_mcp_server_config_type_field.py +221 -0
- tests/test_mcp_sync_functionality.py +316 -0
- tests/test_mcp_user_feedback_reporting.py +359 -0
- tests/test_non_tty_integration.py +281 -0
- tests/test_online_package_loader.py +202 -0
- tests/test_python_environment_manager.py +882 -0
- tests/test_python_installer.py +327 -0
- tests/test_registry.py +51 -0
- tests/test_registry_retriever.py +250 -0
- tests/test_system_installer.py +733 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""Integration tests for non-TTY handling across the full workflow.
|
|
2
|
+
|
|
3
|
+
This module tests the complete integration of non-TTY handling from CLI
|
|
4
|
+
through to the dependency installation orchestrator, ensuring the full
|
|
5
|
+
workflow operates correctly in both TTY and non-TTY environments.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
import tempfile
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from unittest.mock import patch
|
|
13
|
+
from hatch.environment_manager import HatchEnvironmentManager
|
|
14
|
+
from wobble.decorators import integration_test, slow_test
|
|
15
|
+
from test_data_utils import NonTTYTestDataLoader, TestDataLoader
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestNonTTYIntegration(unittest.TestCase):
|
|
19
|
+
"""Integration tests for non-TTY handling across the full workflow."""
|
|
20
|
+
|
|
21
|
+
def setUp(self):
|
|
22
|
+
"""Set up integration test environment with centralized test data."""
|
|
23
|
+
self.temp_dir = tempfile.mkdtemp()
|
|
24
|
+
self.env_manager = HatchEnvironmentManager(
|
|
25
|
+
environments_dir=Path(self.temp_dir) / "envs",
|
|
26
|
+
simulation_mode=True
|
|
27
|
+
)
|
|
28
|
+
self.test_data = NonTTYTestDataLoader()
|
|
29
|
+
self.addCleanup(self._cleanup_temp_dir)
|
|
30
|
+
|
|
31
|
+
def _cleanup_temp_dir(self):
|
|
32
|
+
"""Clean up temporary directory."""
|
|
33
|
+
import shutil
|
|
34
|
+
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
35
|
+
|
|
36
|
+
@integration_test(scope="component")
|
|
37
|
+
@slow_test
|
|
38
|
+
@patch('sys.stdin.isatty', return_value=False)
|
|
39
|
+
def test_cli_package_add_non_tty(self, mock_isatty):
|
|
40
|
+
"""Test package addition in non-TTY environment via CLI."""
|
|
41
|
+
# Create test environment
|
|
42
|
+
self.env_manager.create_environment("test_env", "Test environment")
|
|
43
|
+
|
|
44
|
+
# Test package addition without hanging
|
|
45
|
+
test_loader = TestDataLoader()
|
|
46
|
+
pkg_path = test_loader.packages_dir / "basic" / "base_pkg"
|
|
47
|
+
|
|
48
|
+
# Ensure the test package exists
|
|
49
|
+
if not pkg_path.exists():
|
|
50
|
+
self.skipTest(f"Test package not found: {pkg_path}")
|
|
51
|
+
|
|
52
|
+
result = self.env_manager.add_package_to_environment(
|
|
53
|
+
str(pkg_path),
|
|
54
|
+
"test_env",
|
|
55
|
+
auto_approve=False # Test environment variable handling
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
self.assertTrue(result, "Package addition should succeed in non-TTY mode")
|
|
59
|
+
mock_isatty.assert_called()
|
|
60
|
+
|
|
61
|
+
@integration_test(scope="component")
|
|
62
|
+
@slow_test
|
|
63
|
+
@patch.dict(os.environ, {'HATCH_AUTO_APPROVE': '1'})
|
|
64
|
+
def test_environment_variable_integration(self):
|
|
65
|
+
"""Test HATCH_AUTO_APPROVE environment variable integration."""
|
|
66
|
+
# Create test environment
|
|
67
|
+
self.env_manager.create_environment("test_env", "Test environment")
|
|
68
|
+
|
|
69
|
+
# Test with centralized test data
|
|
70
|
+
test_loader = TestDataLoader()
|
|
71
|
+
pkg_path = test_loader.packages_dir / "basic" / "base_pkg"
|
|
72
|
+
|
|
73
|
+
# Ensure the test package exists
|
|
74
|
+
if not pkg_path.exists():
|
|
75
|
+
self.skipTest(f"Test package not found: {pkg_path}")
|
|
76
|
+
|
|
77
|
+
result = self.env_manager.add_package_to_environment(
|
|
78
|
+
str(pkg_path),
|
|
79
|
+
"test_env",
|
|
80
|
+
auto_approve=False # Environment variable should override
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
self.assertTrue(result, "Package addition should succeed with HATCH_AUTO_APPROVE")
|
|
84
|
+
|
|
85
|
+
@integration_test(scope="component")
|
|
86
|
+
@slow_test
|
|
87
|
+
@patch('sys.stdin.isatty', return_value=False)
|
|
88
|
+
def test_multiple_package_installation_non_tty(self, mock_isatty):
|
|
89
|
+
"""Test multiple package installation in non-TTY environment."""
|
|
90
|
+
# Create test environment
|
|
91
|
+
self.env_manager.create_environment("test_env", "Test environment")
|
|
92
|
+
|
|
93
|
+
test_loader = TestDataLoader()
|
|
94
|
+
|
|
95
|
+
# Install first package
|
|
96
|
+
base_pkg_path = test_loader.packages_dir / "basic" / "base_pkg"
|
|
97
|
+
if base_pkg_path.exists():
|
|
98
|
+
result1 = self.env_manager.add_package_to_environment(
|
|
99
|
+
str(base_pkg_path),
|
|
100
|
+
"test_env",
|
|
101
|
+
auto_approve=False
|
|
102
|
+
)
|
|
103
|
+
self.assertTrue(result1, "First package installation should succeed")
|
|
104
|
+
|
|
105
|
+
# Install second package
|
|
106
|
+
utility_pkg_path = test_loader.packages_dir / "basic" / "utility_pkg"
|
|
107
|
+
if utility_pkg_path.exists():
|
|
108
|
+
result2 = self.env_manager.add_package_to_environment(
|
|
109
|
+
str(utility_pkg_path),
|
|
110
|
+
"test_env",
|
|
111
|
+
auto_approve=False
|
|
112
|
+
)
|
|
113
|
+
self.assertTrue(result2, "Second package installation should succeed")
|
|
114
|
+
|
|
115
|
+
@integration_test(scope="component")
|
|
116
|
+
@slow_test
|
|
117
|
+
@patch.dict(os.environ, {'HATCH_AUTO_APPROVE': 'true'})
|
|
118
|
+
def test_environment_variable_case_insensitive_integration(self):
|
|
119
|
+
"""Test case-insensitive environment variable in full integration."""
|
|
120
|
+
# Create test environment
|
|
121
|
+
self.env_manager.create_environment("test_env", "Test environment")
|
|
122
|
+
|
|
123
|
+
test_loader = TestDataLoader()
|
|
124
|
+
pkg_path = test_loader.packages_dir / "basic" / "base_pkg"
|
|
125
|
+
|
|
126
|
+
if not pkg_path.exists():
|
|
127
|
+
self.skipTest(f"Test package not found: {pkg_path}")
|
|
128
|
+
|
|
129
|
+
result = self.env_manager.add_package_to_environment(
|
|
130
|
+
str(pkg_path),
|
|
131
|
+
"test_env",
|
|
132
|
+
auto_approve=False
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
self.assertTrue(result, "Package addition should succeed with case-insensitive env var")
|
|
136
|
+
|
|
137
|
+
@integration_test(scope="component")
|
|
138
|
+
@slow_test
|
|
139
|
+
@patch('sys.stdin.isatty', return_value=True)
|
|
140
|
+
@patch.dict(os.environ, {'HATCH_AUTO_APPROVE': 'invalid'})
|
|
141
|
+
@patch('builtins.input', return_value='y')
|
|
142
|
+
def test_invalid_environment_variable_fallback_integration(self, mock_input, mock_isatty):
|
|
143
|
+
"""Test fallback to interactive mode with invalid environment variable."""
|
|
144
|
+
# Create test environment
|
|
145
|
+
self.env_manager.create_environment("test_env", "Test environment")
|
|
146
|
+
|
|
147
|
+
test_loader = TestDataLoader()
|
|
148
|
+
pkg_path = test_loader.packages_dir / "basic" / "base_pkg"
|
|
149
|
+
|
|
150
|
+
if not pkg_path.exists():
|
|
151
|
+
self.skipTest(f"Test package not found: {pkg_path}")
|
|
152
|
+
|
|
153
|
+
result = self.env_manager.add_package_to_environment(
|
|
154
|
+
str(pkg_path),
|
|
155
|
+
"test_env",
|
|
156
|
+
auto_approve=False
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
self.assertTrue(result, "Package addition should succeed with user approval")
|
|
160
|
+
# Verify that input was called (fallback to interactive mode)
|
|
161
|
+
mock_input.assert_called()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class TestNonTTYErrorScenarios(unittest.TestCase):
|
|
165
|
+
"""Test error scenarios in non-TTY environments."""
|
|
166
|
+
|
|
167
|
+
def setUp(self):
|
|
168
|
+
"""Set up test environment."""
|
|
169
|
+
self.temp_dir = tempfile.mkdtemp()
|
|
170
|
+
self.env_manager = HatchEnvironmentManager(
|
|
171
|
+
environments_dir=Path(self.temp_dir) / "envs",
|
|
172
|
+
simulation_mode=True
|
|
173
|
+
)
|
|
174
|
+
self.test_data = NonTTYTestDataLoader()
|
|
175
|
+
self.addCleanup(self._cleanup_temp_dir)
|
|
176
|
+
|
|
177
|
+
def _cleanup_temp_dir(self):
|
|
178
|
+
"""Clean up temporary directory."""
|
|
179
|
+
import shutil
|
|
180
|
+
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
181
|
+
|
|
182
|
+
@integration_test(scope="component")
|
|
183
|
+
@slow_test
|
|
184
|
+
@patch('sys.stdin.isatty', return_value=True)
|
|
185
|
+
@patch('builtins.input', side_effect=KeyboardInterrupt())
|
|
186
|
+
def test_keyboard_interrupt_integration(self, mock_input, mock_isatty):
|
|
187
|
+
"""Test KeyboardInterrupt handling in full integration."""
|
|
188
|
+
# Create test environment
|
|
189
|
+
self.env_manager.create_environment("test_env", "Test environment")
|
|
190
|
+
|
|
191
|
+
test_loader = TestDataLoader()
|
|
192
|
+
pkg_path = test_loader.packages_dir / "basic" / "base_pkg"
|
|
193
|
+
|
|
194
|
+
if not pkg_path.exists():
|
|
195
|
+
self.skipTest(f"Test package not found: {pkg_path}")
|
|
196
|
+
|
|
197
|
+
result = self.env_manager.add_package_to_environment(
|
|
198
|
+
str(pkg_path),
|
|
199
|
+
"test_env",
|
|
200
|
+
auto_approve=False
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Should return False due to user cancellation
|
|
204
|
+
self.assertFalse(result, "Package installation should be cancelled by user")
|
|
205
|
+
|
|
206
|
+
@integration_test(scope="component")
|
|
207
|
+
@slow_test
|
|
208
|
+
@patch('sys.stdin.isatty', return_value=True)
|
|
209
|
+
@patch('builtins.input', side_effect=EOFError())
|
|
210
|
+
def test_eof_error_integration(self, mock_input, mock_isatty):
|
|
211
|
+
"""Test EOFError handling in full integration."""
|
|
212
|
+
# Create test environment
|
|
213
|
+
self.env_manager.create_environment("test_env", "Test environment")
|
|
214
|
+
|
|
215
|
+
test_loader = TestDataLoader()
|
|
216
|
+
pkg_path = test_loader.packages_dir / "basic" / "base_pkg"
|
|
217
|
+
|
|
218
|
+
if not pkg_path.exists():
|
|
219
|
+
self.skipTest(f"Test package not found: {pkg_path}")
|
|
220
|
+
|
|
221
|
+
result = self.env_manager.add_package_to_environment(
|
|
222
|
+
str(pkg_path),
|
|
223
|
+
"test_env",
|
|
224
|
+
auto_approve=False
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Should return False due to EOF error
|
|
228
|
+
self.assertFalse(result, "Package installation should be cancelled due to EOF")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class TestEnvironmentVariableIntegrationScenarios(unittest.TestCase):
|
|
232
|
+
"""Test comprehensive environment variable scenarios in full integration."""
|
|
233
|
+
|
|
234
|
+
def setUp(self):
|
|
235
|
+
"""Set up test environment."""
|
|
236
|
+
self.temp_dir = tempfile.mkdtemp()
|
|
237
|
+
self.env_manager = HatchEnvironmentManager(
|
|
238
|
+
environments_dir=Path(self.temp_dir) / "envs",
|
|
239
|
+
simulation_mode=True
|
|
240
|
+
)
|
|
241
|
+
self.test_data = NonTTYTestDataLoader()
|
|
242
|
+
self.addCleanup(self._cleanup_temp_dir)
|
|
243
|
+
|
|
244
|
+
def _cleanup_temp_dir(self):
|
|
245
|
+
"""Clean up temporary directory."""
|
|
246
|
+
import shutil
|
|
247
|
+
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
248
|
+
|
|
249
|
+
@integration_test(scope="component")
|
|
250
|
+
@slow_test
|
|
251
|
+
def test_all_valid_environment_variables_integration(self):
|
|
252
|
+
"""Test all valid environment variable values in integration."""
|
|
253
|
+
# Create test environment
|
|
254
|
+
self.env_manager.create_environment("test_env", "Test environment")
|
|
255
|
+
|
|
256
|
+
test_loader = TestDataLoader()
|
|
257
|
+
pkg_path = test_loader.packages_dir / "basic" / "base_pkg"
|
|
258
|
+
|
|
259
|
+
if not pkg_path.exists():
|
|
260
|
+
self.skipTest(f"Test package not found: {pkg_path}")
|
|
261
|
+
|
|
262
|
+
# Test all valid environment variable values
|
|
263
|
+
valid_values = ["1", "true", "yes", "TRUE", "YES", "True"]
|
|
264
|
+
|
|
265
|
+
for i, value in enumerate(valid_values):
|
|
266
|
+
with self.subTest(env_value=value):
|
|
267
|
+
env_name = f"test_env_{i}"
|
|
268
|
+
self.env_manager.create_environment(env_name, f"Test environment {i}")
|
|
269
|
+
|
|
270
|
+
with patch.dict(os.environ, {'HATCH_AUTO_APPROVE': value}):
|
|
271
|
+
result = self.env_manager.add_package_to_environment(
|
|
272
|
+
str(pkg_path),
|
|
273
|
+
env_name,
|
|
274
|
+
auto_approve=False
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
self.assertTrue(result, f"Package installation should succeed with env var: {value}")
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
if __name__ == '__main__':
|
|
281
|
+
unittest.main()
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import unittest
|
|
3
|
+
import tempfile
|
|
4
|
+
import shutil
|
|
5
|
+
import logging
|
|
6
|
+
import json
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from wobble.decorators import regression_test, integration_test, slow_test
|
|
11
|
+
|
|
12
|
+
# Import path management removed - using test_data_utils for test dependencies
|
|
13
|
+
|
|
14
|
+
from hatch.environment_manager import HatchEnvironmentManager
|
|
15
|
+
from hatch.package_loader import HatchPackageLoader, PackageLoaderError
|
|
16
|
+
from hatch.registry_retriever import RegistryRetriever
|
|
17
|
+
from hatch.registry_explorer import find_package, get_package_release_url
|
|
18
|
+
|
|
19
|
+
# Configure logging
|
|
20
|
+
logging.basicConfig(
|
|
21
|
+
level=logging.DEBUG,
|
|
22
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
23
|
+
)
|
|
24
|
+
logger = logging.getLogger("hatch.package_loader_tests")
|
|
25
|
+
|
|
26
|
+
class OnlinePackageLoaderTests(unittest.TestCase):
|
|
27
|
+
"""Tests for package downloading and caching functionality using online mode."""
|
|
28
|
+
|
|
29
|
+
def setUp(self):
|
|
30
|
+
"""Set up test environment before each test."""
|
|
31
|
+
# Create temporary directories
|
|
32
|
+
self.temp_dir = tempfile.mkdtemp()
|
|
33
|
+
self.cache_dir = Path(self.temp_dir) / "cache"
|
|
34
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
self.env_dir = Path(self.temp_dir) / "envs"
|
|
36
|
+
self.env_dir.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
|
|
38
|
+
# Initialize registry retriever in online mode
|
|
39
|
+
self.retriever = RegistryRetriever(
|
|
40
|
+
local_cache_dir=self.cache_dir,
|
|
41
|
+
simulation_mode=False # Use online mode
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Get registry data for test packages
|
|
45
|
+
self.registry_data = self.retriever.get_registry()
|
|
46
|
+
|
|
47
|
+
# Initialize package loader (needed for some lower-level tests)
|
|
48
|
+
self.package_loader = HatchPackageLoader(cache_dir=self.cache_dir)
|
|
49
|
+
|
|
50
|
+
# Initialize environment manager
|
|
51
|
+
self.env_manager = HatchEnvironmentManager(
|
|
52
|
+
environments_dir=self.env_dir,
|
|
53
|
+
cache_dir=self.cache_dir,
|
|
54
|
+
simulation_mode=False
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def tearDown(self):
|
|
58
|
+
"""Clean up test environment after each test."""
|
|
59
|
+
# Remove temporary directory
|
|
60
|
+
shutil.rmtree(self.temp_dir)
|
|
61
|
+
|
|
62
|
+
@integration_test(scope="service")
|
|
63
|
+
@slow_test
|
|
64
|
+
def test_download_package_online(self):
|
|
65
|
+
"""Test downloading a package from online registry."""
|
|
66
|
+
# Use base_pkg_1 for testing since it's mentioned as a reliable test package
|
|
67
|
+
package_name = "base_pkg_1"
|
|
68
|
+
version = "==1.0.1"
|
|
69
|
+
|
|
70
|
+
# Add package to environment using the environment manager
|
|
71
|
+
result = self.env_manager.add_package_to_environment(
|
|
72
|
+
package_name,
|
|
73
|
+
version_constraint=version,
|
|
74
|
+
auto_approve=True # Automatically approve installation in tests
|
|
75
|
+
)
|
|
76
|
+
self.assertTrue(result, f"Failed to add package {package_name}@{version} to environment")
|
|
77
|
+
|
|
78
|
+
# Verify package is in environment
|
|
79
|
+
current_env = self.env_manager.get_current_environment()
|
|
80
|
+
env_data = self.env_manager.get_current_environment_data()
|
|
81
|
+
installed_packages = {pkg["name"]: pkg["version"] for pkg in env_data.get("packages", [])}
|
|
82
|
+
self.assertIn(package_name, installed_packages, f"Package {package_name} not found in environment")
|
|
83
|
+
|
|
84
|
+
# def test_multiple_package_versions(self):
|
|
85
|
+
# """Test downloading multiple versions of the same package."""
|
|
86
|
+
# package_name = "base_pkg_1"
|
|
87
|
+
# versions = ["1.0.0", "1.1.0"] # Test multiple versions if available
|
|
88
|
+
|
|
89
|
+
# # Find package data in the registry
|
|
90
|
+
# package_data = find_package(self.registry_data, package_name)
|
|
91
|
+
# self.assertIsNotNone(package_data, f"Package '{package_name}' not found in registry")
|
|
92
|
+
|
|
93
|
+
# # Try to download each version
|
|
94
|
+
# for version in versions:
|
|
95
|
+
# try:
|
|
96
|
+
# # Get package URL
|
|
97
|
+
# package_url = get_package_release_url(package_data, version)
|
|
98
|
+
# if package_url:
|
|
99
|
+
# # Download the package
|
|
100
|
+
# cached_path = self.package_loader.download_package(package_url, package_name, version)
|
|
101
|
+
# self.assertTrue(cached_path.exists(), f"Package download failed for {version}")
|
|
102
|
+
# logger.info(f"Successfully downloaded {package_name}@{version}")
|
|
103
|
+
# except Exception as e:
|
|
104
|
+
# logger.warning(f"Couldn't download {package_name}@{version}: {e}")
|
|
105
|
+
|
|
106
|
+
@integration_test(scope="service")
|
|
107
|
+
@slow_test
|
|
108
|
+
def test_install_and_caching(self):
|
|
109
|
+
"""Test installing and caching a package."""
|
|
110
|
+
package_name = "base_pkg_1"
|
|
111
|
+
version = "1.0.1"
|
|
112
|
+
version_constraint = f"=={version}"
|
|
113
|
+
|
|
114
|
+
# Find package in registry
|
|
115
|
+
package_data = find_package(self.registry_data, package_name)
|
|
116
|
+
self.assertIsNotNone(package_data, f"Package {package_name} not found in registry")
|
|
117
|
+
|
|
118
|
+
# Create a specific test environment for this test
|
|
119
|
+
test_env_name = "test_install_env"
|
|
120
|
+
self.env_manager.create_environment(test_env_name, "Test environment for installation test")
|
|
121
|
+
|
|
122
|
+
# Add the package to the environment
|
|
123
|
+
try:
|
|
124
|
+
result = self.env_manager.add_package_to_environment(
|
|
125
|
+
package_name,
|
|
126
|
+
env_name=test_env_name,
|
|
127
|
+
version_constraint=version_constraint,
|
|
128
|
+
auto_approve=True # Automatically approve installation in tests
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
self.assertTrue(result, f"Failed to add package {package_name}@{version_constraint} to environment")
|
|
132
|
+
|
|
133
|
+
# Get environment path
|
|
134
|
+
env_path = self.env_manager.get_environment_path(test_env_name)
|
|
135
|
+
installed_path = env_path / package_name
|
|
136
|
+
|
|
137
|
+
# Verify installation
|
|
138
|
+
self.assertTrue(installed_path.exists(), f"Package not installed to environment directory: {installed_path}")
|
|
139
|
+
self.assertTrue((installed_path / "hatch_metadata.json").exists(), f"Installation missing metadata file: {installed_path / 'hatch_metadata.json'}")
|
|
140
|
+
|
|
141
|
+
# Verify the cache contains the package
|
|
142
|
+
cache_path = self.cache_dir / "packages" / f"{package_name}-{version}"
|
|
143
|
+
self.assertTrue(cache_path.exists(), f"Package not cached during installation: {cache_path}")
|
|
144
|
+
self.assertTrue((cache_path / "hatch_metadata.json").exists(), f"Cache missing metadata file: {cache_path / 'hatch_metadata.json'}")
|
|
145
|
+
|
|
146
|
+
logger.info(f"Successfully installed and cached package: {package_name}@{version}")
|
|
147
|
+
except Exception as e:
|
|
148
|
+
self.fail(f"Package installation raised exception: {e}")
|
|
149
|
+
|
|
150
|
+
@integration_test(scope="service")
|
|
151
|
+
@slow_test
|
|
152
|
+
def test_cache_reuse(self):
|
|
153
|
+
"""Test that the cache is reused for multiple installs."""
|
|
154
|
+
package_name = "base_pkg_1"
|
|
155
|
+
version = "1.0.1"
|
|
156
|
+
version_constraint = f"=={version}"
|
|
157
|
+
|
|
158
|
+
# Find package in registry
|
|
159
|
+
package_data = find_package(self.registry_data, package_name)
|
|
160
|
+
self.assertIsNotNone(package_data, f"Package {package_name} not found in registry")
|
|
161
|
+
|
|
162
|
+
# Get package URL
|
|
163
|
+
package_url = get_package_release_url(package_data, version_constraint)
|
|
164
|
+
self.assertIsNotNone(package_url, f"No download URL found for {package_name}@{version_constraint}")
|
|
165
|
+
|
|
166
|
+
# Create two test environments
|
|
167
|
+
first_env = "test_cache_env1"
|
|
168
|
+
second_env = "test_cache_env2"
|
|
169
|
+
self.env_manager.create_environment(first_env, "First test environment for cache test")
|
|
170
|
+
self.env_manager.create_environment(second_env, "Second test environment for cache test")
|
|
171
|
+
|
|
172
|
+
# First install to create cache
|
|
173
|
+
start_time_first = time.time()
|
|
174
|
+
result_first = self.env_manager.add_package_to_environment(
|
|
175
|
+
package_name,
|
|
176
|
+
env_name=first_env,
|
|
177
|
+
version_constraint=version_constraint,
|
|
178
|
+
auto_approve=True # Automatically approve installation in tests
|
|
179
|
+
)
|
|
180
|
+
first_install_time = time.time() - start_time_first
|
|
181
|
+
logger.info(f"First installation took {first_install_time:.2f} seconds")
|
|
182
|
+
self.assertTrue(result_first, f"Failed to add package {package_name}@{version_constraint} to first environment")
|
|
183
|
+
first_env_path = self.env_manager.get_environment_path(first_env)
|
|
184
|
+
self.assertTrue((first_env_path / package_name).exists(), f"Package not found at the expected path: {first_env_path / package_name}")
|
|
185
|
+
|
|
186
|
+
# Second install - should use cache
|
|
187
|
+
start_time = time.time()
|
|
188
|
+
result_second = self.env_manager.add_package_to_environment(
|
|
189
|
+
package_name,
|
|
190
|
+
env_name=second_env,
|
|
191
|
+
version_constraint=version_constraint,
|
|
192
|
+
auto_approve=True # Automatically approve installation in tests
|
|
193
|
+
)
|
|
194
|
+
install_time = time.time() - start_time
|
|
195
|
+
|
|
196
|
+
logger.info(f"Second installation took {install_time:.2f} seconds (should be faster if cache used)")
|
|
197
|
+
|
|
198
|
+
second_env_path = self.env_manager.get_environment_path(second_env)
|
|
199
|
+
self.assertTrue((second_env_path / package_name).exists(), f"Package not found at the expected path: {second_env_path / package_name}")
|
|
200
|
+
|
|
201
|
+
if __name__ == "__main__":
|
|
202
|
+
unittest.main()
|