|
2 | 2 |
|
3 | 3 | import os |
4 | 4 | import subprocess |
5 | | -import sys |
6 | 5 | import tempfile |
7 | 6 | from pathlib import Path |
8 | 7 | from typing import ClassVar |
|
27 | 26 | EXAMPLE_CONFIG_URL = "https://example.com/cchk.toml" |
28 | 27 |
|
29 | 28 |
|
30 | | -def _restore_config_attribute(module): |
31 | | - """Point ``commit_check.config`` back at *module* after a fresh import. |
32 | | -
|
33 | | - Re-importing the config module rebinds the attribute on the package as |
34 | | - well as the sys.modules entry, and restoring sys.modules alone leaves |
35 | | - the attribute on the copy. Python 3.10's patch() resolves a target such |
36 | | - as URLOPEN_MODULE through that attribute, so every later patch landed on |
37 | | - the copy and the tests behind it reached the network. |
38 | | - """ |
39 | | - if module is not None: |
40 | | - sys.modules["commit_check"].config = module |
41 | | - |
42 | | - |
43 | 29 | class TestConfig: |
44 | 30 | @pytest.mark.benchmark |
45 | 31 | def test_load_config_with_path_hint(self): |
@@ -243,76 +229,6 @@ def test_toml_load_function_exists(self): |
243 | 229 | finally: |
244 | 230 | os.unlink(f.name) |
245 | 231 |
|
246 | | - @pytest.mark.benchmark |
247 | | - def test_tomli_import_fallback(self): |
248 | | - """Test that tomli is imported when tomllib is not available (lines 10-13).""" |
249 | | - import sys |
250 | | - |
251 | | - # Save original modules |
252 | | - original_tomllib = sys.modules.get("tomllib") |
253 | | - original_config = sys.modules.get("commit_check.config") |
254 | | - |
255 | | - try: |
256 | | - # Remove modules from cache to force fresh import |
257 | | - if "tomllib" in sys.modules: |
258 | | - del sys.modules["tomllib"] |
259 | | - if "commit_check.config" in sys.modules: |
260 | | - del sys.modules["commit_check.config"] |
261 | | - |
262 | | - # Mock tomllib module to not exist |
263 | | - with patch.dict("sys.modules", {"tomllib": None}): |
264 | | - # Force import error by patching __import__ for tomllib specifically |
265 | | - original_import = __builtins__["__import__"] |
266 | | - |
267 | | - def mock_import(name, *args, **kwargs): |
268 | | - if name == "tomllib": |
269 | | - raise ImportError("No module named 'tomllib'") |
270 | | - # For tomli, return a working mock |
271 | | - if name == "tomli": |
272 | | - |
273 | | - class MockTomli: |
274 | | - @staticmethod |
275 | | - def load(f): |
276 | | - content = f.read().decode("utf-8") |
277 | | - # Simple parser for test |
278 | | - if '[test]\nvalue = "tomli_works"' in content: |
279 | | - return {"test": {"value": "tomli_works"}} |
280 | | - return {} |
281 | | - |
282 | | - return MockTomli() |
283 | | - return original_import(name, *args, **kwargs) |
284 | | - |
285 | | - with patch("builtins.__import__", side_effect=mock_import): |
286 | | - # Import the config module - should use tomli fallback |
287 | | - import commit_check.config as config_module |
288 | | - |
289 | | - # Verify that the module loaded successfully |
290 | | - assert hasattr(config_module, "toml_load") |
291 | | - assert callable(config_module.toml_load) |
292 | | - |
293 | | - # Test that it can actually parse TOML content |
294 | | - config_content = b'[test]\nvalue = "tomli_works"\n' |
295 | | - with tempfile.NamedTemporaryFile( |
296 | | - mode="wb", suffix=".toml", delete=False |
297 | | - ) as f: |
298 | | - f.write(config_content) |
299 | | - f.flush() |
300 | | - |
301 | | - try: |
302 | | - with open(f.name, "rb") as config_file: |
303 | | - result = config_module.toml_load(config_file) |
304 | | - assert result == {"test": {"value": "tomli_works"}} |
305 | | - finally: |
306 | | - os.unlink(f.name) |
307 | | - |
308 | | - finally: |
309 | | - # Restore original modules |
310 | | - if original_tomllib is not None: |
311 | | - sys.modules["tomllib"] = original_tomllib |
312 | | - if original_config is not None: |
313 | | - sys.modules["commit_check.config"] = original_config |
314 | | - _restore_config_attribute(original_config) |
315 | | - |
316 | 232 |
|
317 | 233 | class TestConfigEdgeCases: |
318 | 234 | """Test TOML parsing errors and exception handling.""" |
@@ -360,190 +276,6 @@ def test_load_config_file_permission_error(self): |
360 | 276 | os.chmod(f.name, 0o644) |
361 | 277 | os.unlink(f.name) |
362 | 278 |
|
363 | | - @pytest.mark.benchmark |
364 | | - def test_tomli_import_fallback(self): |
365 | | - """Test the tomli import fallback when tomllib is not available.""" |
366 | | - # We need to test the import fallback behavior |
367 | | - # This is complex because the imports happen at module load time |
368 | | - |
369 | | - # Create a minimal test by importing the config module's behavior |
370 | | - config_content = b""" |
371 | | -[test] |
372 | | -fallback = true |
373 | | -""" |
374 | | - |
375 | | - with tempfile.NamedTemporaryFile(mode="wb", suffix=".toml", delete=False) as f: |
376 | | - f.write(config_content) |
377 | | - f.flush() |
378 | | - |
379 | | - try: |
380 | | - # Test that we can load config regardless of which TOML library is used |
381 | | - config = load_config(f.name) |
382 | | - assert config["test"]["fallback"] is True |
383 | | - |
384 | | - # Since we can't easily test the import fallback on Python 3.11+, |
385 | | - # let's at least verify that the toml_load function works |
386 | | - from commit_check.config import toml_load |
387 | | - |
388 | | - with open(f.name, "rb") as config_file: |
389 | | - result = toml_load(config_file) |
390 | | - assert result["test"]["fallback"] is True |
391 | | - |
392 | | - finally: |
393 | | - os.unlink(f.name) |
394 | | - |
395 | | - |
396 | | -class TestConfigFallback: |
397 | | - """Direct test of the config import fallback using module manipulation.""" |
398 | | - |
399 | | - @pytest.mark.benchmark |
400 | | - def test_config_tomli_fallback_direct(self): |
401 | | - """Test config.py fallback to tomli by manipulating imports.""" |
402 | | - |
403 | | - # Save original state |
404 | | - original_modules = sys.modules.copy() |
405 | | - |
406 | | - try: |
407 | | - # Remove config module if already imported |
408 | | - if "commit_check.config" in sys.modules: |
409 | | - del sys.modules["commit_check.config"] |
410 | | - |
411 | | - # Make tomllib unavailable by raising ImportError |
412 | | - original_import = __import__ |
413 | | - |
414 | | - def mock_import(name, globals=None, locals=None, fromlist=(), level=0): |
415 | | - if name == "tomllib": |
416 | | - raise ImportError("No module named 'tomllib'") |
417 | | - # For tomli, return a working mock |
418 | | - if name == "tomli": |
419 | | - |
420 | | - class MockTomli: |
421 | | - @staticmethod |
422 | | - def load(f): |
423 | | - content = f.read().decode("utf-8") |
424 | | - # Simple parser for test |
425 | | - if 'test_key = "test_value"' in content: |
426 | | - return {"test_key": "test_value"} |
427 | | - return {} |
428 | | - |
429 | | - return MockTomli() |
430 | | - return original_import(name, globals, locals, fromlist, level) |
431 | | - |
432 | | - with patch("builtins.__import__", side_effect=mock_import): |
433 | | - # Now import config - should use tomli fallback |
434 | | - from commit_check.config import toml_load |
435 | | - |
436 | | - # Test that it works |
437 | | - config_content = b'test_key = "test_value"' |
438 | | - with tempfile.NamedTemporaryFile( |
439 | | - mode="wb", suffix=".toml", delete=False |
440 | | - ) as f: |
441 | | - f.write(config_content) |
442 | | - f.flush() |
443 | | - |
444 | | - try: |
445 | | - with open(f.name, "rb") as config_file: |
446 | | - result = toml_load(config_file) |
447 | | - assert result == {"test_key": "test_value"} |
448 | | - finally: |
449 | | - os.unlink(f.name) |
450 | | - |
451 | | - finally: |
452 | | - # Restore original modules |
453 | | - sys.modules.clear() |
454 | | - sys.modules.update(original_modules) |
455 | | - _restore_config_attribute(original_modules.get("commit_check.config")) |
456 | | - |
457 | | - |
458 | | -class TestConfigImport: |
459 | | - """Test import fallback by creating a test version of config.py.""" |
460 | | - |
461 | | - @pytest.mark.benchmark |
462 | | - @pytest.mark.skipif( |
463 | | - sys.version_info < (3, 11), reason="tomllib only available in Python 3.11+" |
464 | | - ) |
465 | | - def test_tomli_import_fallback_simulation(self): |
466 | | - """Test tomli import fallback by simulating the ImportError condition.""" |
467 | | - |
468 | | - # Simulates the config.py import logic |
469 | | - def pick_loader(): |
470 | | - try: |
471 | | - import tomllib |
472 | | - |
473 | | - return tomllib.load, True |
474 | | - except ImportError: |
475 | | - import tomli |
476 | | - |
477 | | - return tomli.load, False |
478 | | - |
479 | | - # Test case 1: Normal case (tomllib available) |
480 | | - toml_load, used_tomllib = pick_loader() |
481 | | - assert used_tomllib is True |
482 | | - assert callable(toml_load) |
483 | | - |
484 | | - # Test case 2: Simulate ImportError for tomllib |
485 | | - with ( |
486 | | - patch.dict("sys.modules", {"tomllib": None}), |
487 | | - patch( |
488 | | - "builtins.__import__", |
489 | | - side_effect=self._mock_import_error, |
490 | | - ), |
491 | | - ): |
492 | | - toml_load, used_tomllib = pick_loader() |
493 | | - assert used_tomllib is False |
494 | | - assert callable(toml_load) |
495 | | - |
496 | | - @staticmethod |
497 | | - def _mock_import_error(name, *args, **kwargs): |
498 | | - """Mock import function that raises ImportError for tomllib.""" |
499 | | - if name == "tomllib": |
500 | | - raise ImportError("No module named 'tomllib'") |
501 | | - |
502 | | - # For tomli, we need to mock it since it might not be installed |
503 | | - if name == "tomli": |
504 | | - # Create a mock tomli module |
505 | | - class MockTomli: |
506 | | - @staticmethod |
507 | | - def load(f): |
508 | | - # Simple TOML parser for testing |
509 | | - content = f.read().decode("utf-8") |
510 | | - if "[test]" in content and 'value = "test"' in content: |
511 | | - return {"test": {"value": "test"}} |
512 | | - return {} |
513 | | - |
514 | | - return MockTomli() |
515 | | - |
516 | | - # For all other imports, use the real import |
517 | | - return __import__(name, *args, **kwargs) |
518 | | - |
519 | | - @pytest.mark.benchmark |
520 | | - def test_import_paths_coverage(self): |
521 | | - """Ensure both import paths are conceptually tested.""" |
522 | | - # This test verifies that both the tomllib and tomli code paths |
523 | | - # would work in their respective environments |
524 | | - |
525 | | - # Test the function signature matches expectation |
526 | | - config_content = b""" |
527 | | -[test] |
528 | | -value = "test" |
529 | | -""" |
530 | | - |
531 | | - with tempfile.NamedTemporaryFile(mode="wb", suffix=".toml", delete=False) as f: |
532 | | - f.write(config_content) |
533 | | - f.flush() |
534 | | - |
535 | | - try: |
536 | | - # Test using the actual config module (uses tomllib on Python 3.11+) |
537 | | - from commit_check.config import toml_load |
538 | | - |
539 | | - with open(f.name, "rb") as config_file: |
540 | | - result = toml_load(config_file) |
541 | | - |
542 | | - assert result == {"test": {"value": "test"}} |
543 | | - |
544 | | - finally: |
545 | | - os.unlink(f.name) |
546 | | - |
547 | 279 |
|
548 | 280 | class TestDeepMerge: |
549 | 281 | """Tests for the deep_merge helper.""" |
|