214 lines
7.5 KiB
Python
214 lines
7.5 KiB
Python
"""
|
|
Test for building and validating the Apptainer container.
|
|
"""
|
|
|
|
import unittest
|
|
import subprocess
|
|
import os
|
|
import tempfile
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
|
|
class TestContainerBuild(unittest.TestCase):
|
|
"""Test Apptainer container building and basic functionality."""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
"""Set up test environment - run once for all tests."""
|
|
cls.project_root = Path(__file__).parent.parent.absolute()
|
|
cls.singularity_def = cls.project_root / "Singularity.def"
|
|
cls.test_dir = Path(tempfile.mkdtemp())
|
|
cls.container_path = cls.test_dir / "gallery_test.sif"
|
|
|
|
print(f"Project root: {cls.project_root}")
|
|
print(f"Test directory: {cls.test_dir}")
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
"""Clean up test environment."""
|
|
if cls.test_dir.exists():
|
|
shutil.rmtree(cls.test_dir)
|
|
|
|
def test_01_singularity_def_exists(self):
|
|
"""Test that Singularity.def file exists and is valid."""
|
|
self.assertTrue(self.singularity_def.exists(),
|
|
"Singularity.def file not found")
|
|
|
|
content = self.singularity_def.read_text()
|
|
self.assertIn("Bootstrap:", content)
|
|
self.assertIn("From:", content)
|
|
self.assertIn("%post", content)
|
|
self.assertIn("imagemagick", content.lower())
|
|
self.assertIn("jinja2", content.lower())
|
|
self.assertIn("pyyaml", content.lower())
|
|
|
|
def test_02_apptainer_available(self):
|
|
"""Test that Apptainer/Singularity is available."""
|
|
try:
|
|
# Try apptainer first (newer)
|
|
result = subprocess.run(['apptainer', '--version'],
|
|
capture_output=True, text=True, timeout=10)
|
|
if result.returncode == 0:
|
|
self.container_cmd = 'apptainer'
|
|
return
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
try:
|
|
# Fall back to singularity
|
|
result = subprocess.run(['singularity', '--version'],
|
|
capture_output=True, text=True, timeout=10)
|
|
if result.returncode == 0:
|
|
self.container_cmd = 'singularity'
|
|
return
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
self.fail("Neither 'apptainer' nor 'singularity' command found")
|
|
|
|
def test_03_build_container(self):
|
|
"""Test building the container from Singularity.def."""
|
|
# Ensure we have a container command from previous test
|
|
if not hasattr(self, 'container_cmd'):
|
|
self.test_02_apptainer_available()
|
|
|
|
print(f"Building container with {self.container_cmd}...")
|
|
|
|
# Build command
|
|
build_cmd = [
|
|
self.container_cmd, 'build',
|
|
str(self.container_path),
|
|
str(self.singularity_def)
|
|
]
|
|
|
|
# Change to project directory for build context
|
|
original_cwd = os.getcwd()
|
|
try:
|
|
os.chdir(self.project_root)
|
|
|
|
# Run build with extended timeout
|
|
result = subprocess.run(
|
|
build_cmd,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=300 # 5 minutes should be enough
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
print("STDOUT:", result.stdout)
|
|
print("STDERR:", result.stderr)
|
|
self.fail(f"Container build failed with return code {result.returncode}")
|
|
|
|
# Verify container was created
|
|
self.assertTrue(self.container_path.exists(),
|
|
"Container file was not created")
|
|
|
|
# Check container size (should be > 100MB for a real container)
|
|
size_mb = self.container_path.stat().st_size / (1024 * 1024)
|
|
self.assertGreater(size_mb, 50,
|
|
f"Container seems too small: {size_mb:.1f}MB")
|
|
|
|
print(f"✅ Container built successfully: {size_mb:.1f}MB")
|
|
|
|
finally:
|
|
os.chdir(original_cwd)
|
|
|
|
def test_04_container_exec_python(self):
|
|
"""Test that Python works inside the container."""
|
|
if not self.container_path.exists():
|
|
self.skipTest("Container not built")
|
|
|
|
cmd = [self.container_cmd, 'exec', str(self.container_path),
|
|
'python3', '-c', 'import sys; print(f"Python {sys.version_info.major}.{sys.version_info.minor}")']
|
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
|
|
self.assertEqual(result.returncode, 0,
|
|
f"Python execution failed: {result.stderr}")
|
|
self.assertIn("Python 3.", result.stdout)
|
|
|
|
def test_05_container_dependencies(self):
|
|
"""Test that required dependencies are installed."""
|
|
if not self.container_path.exists():
|
|
self.skipTest("Container not built")
|
|
|
|
# Test Python dependencies
|
|
python_test = """
|
|
import sys
|
|
try:
|
|
import jinja2
|
|
import yaml
|
|
print("✅ Python dependencies OK")
|
|
except ImportError as e:
|
|
print(f"❌ Missing dependency: {e}")
|
|
sys.exit(1)
|
|
"""
|
|
|
|
cmd = [self.container_cmd, 'exec', str(self.container_path),
|
|
'python3', '-c', python_test]
|
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
|
|
self.assertEqual(result.returncode, 0,
|
|
f"Dependency check failed: {result.stderr}")
|
|
self.assertIn("Python dependencies OK", result.stdout)
|
|
|
|
# Test ImageMagick
|
|
cmd = [self.container_cmd, 'exec', str(self.container_path),
|
|
'convert', '-version']
|
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
|
|
self.assertEqual(result.returncode, 0,
|
|
f"ImageMagick not working: {result.stderr}")
|
|
self.assertIn("ImageMagick", result.stdout)
|
|
|
|
def test_06_container_run_tests(self):
|
|
"""Test running the container test suite."""
|
|
if not self.container_path.exists():
|
|
self.skipTest("Container not built")
|
|
|
|
# Run the container tests
|
|
cmd = [self.container_cmd, 'exec', str(self.container_path),
|
|
'python3', '/src/tests/test_container.py']
|
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
|
|
print("Container test output:")
|
|
print(result.stdout)
|
|
if result.stderr:
|
|
print("Errors:")
|
|
print(result.stderr)
|
|
|
|
self.assertEqual(result.returncode, 0,
|
|
f"Container tests failed: {result.stderr}")
|
|
self.assertIn("ALL TESTS PASSED", result.stdout)
|
|
|
|
|
|
def run_build_tests():
|
|
"""Run container build tests."""
|
|
print("=" * 60)
|
|
print("APPTAINER CONTAINER BUILD TESTS")
|
|
print("=" * 60)
|
|
|
|
# Run tests in order
|
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestContainerBuild)
|
|
runner = unittest.TextTestRunner(verbosity=2)
|
|
result = runner.run(suite)
|
|
|
|
if result.wasSuccessful():
|
|
print("\n✅ All container build tests passed!")
|
|
else:
|
|
print(f"\n❌ Container build tests failed!")
|
|
print(f"Failures: {len(result.failures)}")
|
|
print(f"Errors: {len(result.errors)}")
|
|
|
|
return 0 if result.wasSuccessful() else 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
exit_code = run_build_tests()
|
|
sys.exit(exit_code)
|