""" Container-optimized test suite for gallery generator. Designed to run inside Apptainer/Singularity containers. """ import unittest import tempfile import shutil import time import os from pathlib import Path import subprocess import sys class TestContainerEnvironment(unittest.TestCase): """Test that the container environment is properly configured.""" def test_python_version(self): """Test that Python 3.11+ is available.""" version = sys.version_info self.assertGreaterEqual(version.major, 3) self.assertGreaterEqual(version.minor, 11) def test_required_modules(self): """Test that required Python modules are installed.""" try: import jinja2 import yaml self.assertTrue(True) # Success if no ImportError except ImportError as e: self.fail(f"Required module not found: {e}") def test_imagemagick_available(self): """Test that ImageMagick is installed and accessible.""" try: result = subprocess.run(['convert', '-version'], capture_output=True, text=True, timeout=10) self.assertEqual(result.returncode, 0) self.assertIn('ImageMagick', result.stdout) except (subprocess.TimeoutExpired, FileNotFoundError): self.fail("ImageMagick not available or not working") def test_working_directory(self): """Test that the source code is available.""" expected_files = ['generate_gallery.py', 'config.yaml', 'orchestration/'] for file_path in expected_files: path = Path('/src') / file_path self.assertTrue(path.exists(), f"Missing: {file_path}") class TestUtilityFunctions(unittest.TestCase): """Test core utility functions.""" def test_format_file_size(self): """Test file size formatting utility.""" # Import the function from the container's source sys.path.insert(0, '/src') from generate_gallery import format_file_size self.assertEqual(format_file_size(0), "0 B") self.assertEqual(format_file_size(1024), "1.0 KB") self.assertEqual(format_file_size(1048576), "1.0 MB") self.assertEqual(format_file_size(1073741824), "1.0 GB") def test_needs_update(self): """Test file update checking.""" sys.path.insert(0, '/src') from generate_gallery import needs_update with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) # Test missing target source = temp_path / "source.txt" target = temp_path / "target.txt" source.write_text("test") self.assertTrue(needs_update(source, target)) # Test up-to-date target target.write_text("test") time.sleep(0.1) # Ensure different timestamp os.utime(target, (time.time(), time.time())) self.assertFalse(needs_update(source, target)) class TestMetadataSystem(unittest.TestCase): """Test metadata loading and processing.""" def setUp(self): sys.path.insert(0, '/src') def test_metadata_loading(self): """Test loading metadata files.""" from orchestration.metadata import load_metadata_file with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) # Test YAML metadata yaml_file = temp_path / "test.yaml" yaml_content = "title: Test\nauthor: Container Test\n" yaml_file.write_text(yaml_content) metadata = load_metadata_file(yaml_file) self.assertEqual(metadata['title'], 'Test') self.assertEqual(metadata['author'], 'Container Test') def test_metadata_inheritance(self): """Test metadata inheritance through directories.""" from orchestration.metadata import merge_metadata parent = {'project': 'Test', 'version': '1.0'} child = {'experiment': 'A', 'version': '1.1'} merged = merge_metadata(parent, child) self.assertEqual(merged['project'], 'Test') self.assertEqual(merged['experiment'], 'A') self.assertEqual(merged['version'], '1.1') # Child overrides parent class TestPDFProcessing(unittest.TestCase): """Test PDF processing functionality.""" def setUp(self): sys.path.insert(0, '/src') self.temp_dir = Path(tempfile.mkdtemp()) def tearDown(self): shutil.rmtree(self.temp_dir) def create_mock_pdf(self, path: Path): """Create a minimal mock PDF.""" path.write_text("%PDF-1.4\nMock PDF for testing") def test_pdf_conversion(self): """Test PDF to PNG conversion.""" from generate_gallery import convert_pdf_to_png # Create a mock PDF pdf_path = self.temp_dir / "test.pdf" self.create_mock_pdf(pdf_path) # This should work in the container with ImageMagick try: convert_pdf_to_png(pdf_path) png_path = pdf_path.with_suffix('.png') self.assertTrue(png_path.exists()) except subprocess.CalledProcessError: # Allow test to pass if ImageMagick can't process our mock PDF # (Real PDFs would work, but our mock might not) self.skipTest("Mock PDF not processable by ImageMagick") class TestGalleryGeneration(unittest.TestCase): """Test end-to-end gallery generation.""" def setUp(self): sys.path.insert(0, '/src') self.temp_dir = Path(tempfile.mkdtemp()) self.source_dir = self.temp_dir / "source" self.web_dir = self.temp_dir / "web" self.source_dir.mkdir() self.web_dir.mkdir() def tearDown(self): shutil.rmtree(self.temp_dir) def create_test_structure(self): """Create a minimal test directory structure.""" # Create mock PDF pdf_path = self.source_dir / "test_plot.pdf" pdf_path.write_text("%PDF-1.4\nTest plot content") # Create metadata metadata_path = self.source_dir / "metadata.yaml" metadata_path.write_text("title: Container Test\nauthor: CI Pipeline\n") def test_build_gallery(self): """Test building a simple gallery.""" from generate_gallery import build_gallery self.create_test_structure() # This should complete without errors try: build_gallery(self.source_dir, self.web_dir) # Check that HTML was generated html_file = self.web_dir / "index.html" self.assertTrue(html_file.exists()) # Check that files were copied pdf_file = self.web_dir / "test_plot.pdf" self.assertTrue(pdf_file.exists()) except Exception as e: self.fail(f"Gallery generation failed: {e}") def run_container_tests(): """Run all tests suitable for container execution.""" print("=" * 60) print("GALLERY GENERATOR CONTAINER TESTS") print("=" * 60) print(f"Python version: {sys.version}") print(f"Working directory: {os.getcwd()}") print(f"Python path: {sys.path[:3]}...") print("=" * 60) # Create test suite loader = unittest.TestLoader() suite = unittest.TestSuite() # Add test classes test_classes = [ TestContainerEnvironment, TestUtilityFunctions, TestMetadataSystem, TestPDFProcessing, TestGalleryGeneration ] for test_class in test_classes: tests = loader.loadTestsFromTestCase(test_class) suite.addTests(tests) # Run tests runner = unittest.TextTestRunner(verbosity=2) result = runner.run(suite) # Print summary print("=" * 60) if result.wasSuccessful(): print("✅ ALL TESTS PASSED") else: print("❌ SOME TESTS FAILED") print(f"Failures: {len(result.failures)}") print(f"Errors: {len(result.errors)}") print("=" * 60) return 0 if result.wasSuccessful() else 1 if __name__ == '__main__': exit_code = run_container_tests() sys.exit(exit_code)