Files
ETPlot/tests/test_build_container.py
T
2025-09-08 10:52:58 +02:00

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)