test_pip_provider.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. """
  2. Tests for the pip binary provider plugin.
  3. Tests cover:
  4. 1. Hook script execution
  5. 2. pip package detection
  6. 3. Virtual environment handling
  7. 4. JSONL output format
  8. """
  9. import json
  10. import os
  11. import subprocess
  12. import sys
  13. import tempfile
  14. from pathlib import Path
  15. from unittest.mock import patch, MagicMock
  16. import pytest
  17. from django.test import TestCase
  18. # Get the path to the pip provider hook
  19. PLUGIN_DIR = Path(__file__).parent.parent
  20. INSTALL_HOOK = next(PLUGIN_DIR.glob('on_Binary__*_pip_install.py'), None)
  21. class TestPipProviderHook(TestCase):
  22. """Test the pip binary provider installation hook."""
  23. def setUp(self):
  24. """Set up test environment."""
  25. self.temp_dir = tempfile.mkdtemp()
  26. self.output_dir = Path(self.temp_dir) / 'output'
  27. self.output_dir.mkdir()
  28. self.lib_dir = Path(self.temp_dir) / 'lib' / 'x86_64-linux'
  29. self.lib_dir.mkdir(parents=True, exist_ok=True)
  30. self.lib_dir = Path(self.temp_dir) / 'lib' / 'x86_64-linux'
  31. self.lib_dir.mkdir(parents=True, exist_ok=True)
  32. def tearDown(self):
  33. """Clean up."""
  34. import shutil
  35. shutil.rmtree(self.temp_dir, ignore_errors=True)
  36. def test_hook_script_exists(self):
  37. """Hook script should exist."""
  38. self.assertTrue(INSTALL_HOOK and INSTALL_HOOK.exists(), f"Hook not found: {INSTALL_HOOK}")
  39. def test_hook_help(self):
  40. """Hook should accept --help without error."""
  41. result = subprocess.run(
  42. [sys.executable, str(INSTALL_HOOK), '--help'],
  43. capture_output=True,
  44. text=True,
  45. timeout=30
  46. )
  47. # May succeed or fail depending on implementation
  48. # At minimum should not crash with Python error
  49. self.assertNotIn('Traceback', result.stderr)
  50. def test_hook_finds_pip(self):
  51. """Hook should find pip binary."""
  52. env = os.environ.copy()
  53. env['DATA_DIR'] = self.temp_dir
  54. env['LIB_DIR'] = str(self.lib_dir)
  55. result = subprocess.run(
  56. [
  57. sys.executable, str(INSTALL_HOOK),
  58. '--name=pip',
  59. '--binproviders=pip',
  60. '--binary-id=test-uuid',
  61. '--machine-id=test-machine',
  62. ],
  63. capture_output=True,
  64. text=True,
  65. cwd=str(self.output_dir),
  66. env=env,
  67. timeout=60
  68. )
  69. # Check for JSONL output
  70. jsonl_found = False
  71. for line in result.stdout.split('\n'):
  72. line = line.strip()
  73. if line.startswith('{'):
  74. try:
  75. record = json.loads(line)
  76. if record.get('type') == 'Binary' and record.get('name') == 'pip':
  77. jsonl_found = True
  78. # Verify structure
  79. self.assertIn('abspath', record)
  80. self.assertIn('version', record)
  81. break
  82. except json.JSONDecodeError:
  83. continue
  84. # Should not crash
  85. self.assertNotIn('Traceback', result.stderr)
  86. # Should find pip via pip provider
  87. self.assertTrue(jsonl_found, "Expected to find pip binary in JSONL output")
  88. def test_hook_unknown_package(self):
  89. """Hook should handle unknown packages gracefully."""
  90. env = os.environ.copy()
  91. env['DATA_DIR'] = self.temp_dir
  92. env['LIB_DIR'] = str(self.lib_dir)
  93. result = subprocess.run(
  94. [
  95. sys.executable, str(INSTALL_HOOK),
  96. '--name=nonexistent_package_xyz123',
  97. '--binproviders=pip',
  98. '--binary-id=test-uuid',
  99. '--machine-id=test-machine',
  100. ],
  101. capture_output=True,
  102. text=True,
  103. cwd=str(self.output_dir),
  104. env=env,
  105. timeout=60
  106. )
  107. # Should not crash
  108. self.assertNotIn('Traceback', result.stderr)
  109. # May have non-zero exit code for missing package
  110. class TestPipProviderIntegration(TestCase):
  111. """Integration tests for pip provider with real packages."""
  112. def setUp(self):
  113. """Set up test environment."""
  114. self.temp_dir = tempfile.mkdtemp()
  115. self.output_dir = Path(self.temp_dir) / 'output'
  116. self.output_dir.mkdir()
  117. def tearDown(self):
  118. """Clean up."""
  119. import shutil
  120. shutil.rmtree(self.temp_dir, ignore_errors=True)
  121. def test_hook_finds_pip_installed_binary(self):
  122. """Hook should find binaries installed via pip."""
  123. pip_check = subprocess.run(
  124. [sys.executable, '-m', 'pip', '--version'],
  125. capture_output=True,
  126. text=True,
  127. )
  128. assert pip_check.returncode == 0, "pip not available"
  129. env = os.environ.copy()
  130. env['DATA_DIR'] = self.temp_dir
  131. # Try to find 'pip' itself which should be available
  132. result = subprocess.run(
  133. [
  134. sys.executable, str(INSTALL_HOOK),
  135. '--name=pip',
  136. '--binproviders=pip,env',
  137. '--binary-id=test-uuid',
  138. '--machine-id=test-machine',
  139. ],
  140. capture_output=True,
  141. text=True,
  142. cwd=str(self.output_dir),
  143. env=env,
  144. timeout=60
  145. )
  146. # Look for success in output
  147. for line in result.stdout.split('\n'):
  148. line = line.strip()
  149. if line.startswith('{'):
  150. try:
  151. record = json.loads(line)
  152. if record.get('type') == 'Binary' and 'pip' in record.get('name', ''):
  153. # Found pip binary
  154. self.assertTrue(record.get('abspath'))
  155. return
  156. except json.JSONDecodeError:
  157. continue
  158. # If we get here without finding pip, that's acceptable
  159. # as long as the hook didn't crash
  160. self.assertNotIn('Traceback', result.stderr)
  161. if __name__ == '__main__':
  162. pytest.main([__file__, '-v'])