diff --git a/.coverage b/.coverage deleted file mode 100644 index 2342515..0000000 Binary files a/.coverage and /dev/null differ diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index b8bd317..0000000 --- a/.coveragerc +++ /dev/null @@ -1,36 +0,0 @@ -[run] -source = . -omit = - tests/* - __pycache__/* - .git/* - assets/* - docs/* - examples/* - templates/* - .coverage* - setup.py - conftest.py - -[report] -precision = 2 -show_missing = True -skip_covered = False -exclude_lines = - pragma: no cover - def __repr__ - if self.debug: - if settings.DEBUG - raise AssertionError - raise NotImplementedError - if 0: - if __name__ == .__main__.: - class .*\bProtocol\): - @(abc\.)?abstractmethod - -[html] -directory = coverage_html_report -title = Gallery Generator Coverage Report - -[xml] -output = coverage.xml diff --git a/coverage.xml b/coverage.xml deleted file mode 100644 index e77dce0..0000000 --- a/coverage.xml +++ /dev/null @@ -1,421 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/generate_gallery.py b/generate_gallery.py index d12191f..3987ad6 100644 --- a/generate_gallery.py +++ b/generate_gallery.py @@ -15,15 +15,14 @@ Features: import subprocess import shutil -import os import sys from pathlib import Path from typing import Dict, Any, Optional from datetime import datetime from jinja2 import Environment, FileSystemLoader -from orchestration.config import Config -from orchestration.metadata import ( +from utils.config import Config +from utils.metadata import ( load_folder_metadata, merge_metadata, resolve_metadata_for_plot, @@ -122,7 +121,7 @@ def build_gallery(source_dir: Path, web_dir: Path, """ if relative_path is None: relative_path = Path(".") - + if inherited_metadata is None: inherited_metadata = {} @@ -186,7 +185,7 @@ def build_gallery(source_dir: Path, web_dir: Path, subdir_names.append(subdir.name) output_html = web_dir / "index.html" - + # Always regenerate HTML to ensure subdirectory changes are reflected # This ensures new subdirectories appear in navigation force_regeneration = True @@ -336,7 +335,7 @@ def main(clean_first: bool = False) -> None: # Always ensure assets are up to date assets_src = Path("assets") assets_dst = gallery_root.parent / "assets" - + if assets_src.exists(): # Update assets if they don't exist or are outdated main_css_src = assets_src / "css" / "main.css" @@ -400,7 +399,7 @@ def main(clean_first: bool = False) -> None: # Calculate relative path to assets for single file # Single files are at depth 1 (gallery_root/source.name/index.html) assets_path = "../assets" - + output_html = source_web_dir / "index.html" with output_html.open("w") as f: f.write(template.render( @@ -414,7 +413,8 @@ def main(clean_first: bool = False) -> None: folder_metadata={}, assets_path=assets_path, source_dir=str(source_path.parent), - metadata_file_path=get_metadata_file_path(source_path.parent) + metadata_file_path=get_metadata_file_path( + source_path.parent) )) print(f"Generated {output_html}") @@ -433,16 +433,13 @@ def main(clean_first: bool = False) -> None: if __name__ == "__main__": - if 'GATEWAY_INTERFACE' in os.environ: - refresh_gallery_cgi() - else: - import argparse + import argparse - parser = argparse.ArgumentParser(description='Generate gallery') - parser.add_argument( - '--clean', - action='store_true', - help='Clean gallery directory before generation' - ) - args = parser.parse_args() - main(clean_first=args.clean) + parser = argparse.ArgumentParser(description='Generate gallery') + parser.add_argument( + '--clean', + action='store_true', + help='Clean gallery directory before generation' + ) + args = parser.parse_args() + main(clean_first=args.clean) diff --git a/python/add_creation_time.py b/python/add_creation_time.py deleted file mode 100644 index 135d807..0000000 --- a/python/add_creation_time.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python3 -""" -Gallery Creation Time Integration -Example function to add creation time metadata to plot items. -This should be integrated into your existing Python gallery generation code. -""" - -from pathlib import Path - - -def add_creation_time_to_items(items, base_path): - """ - Add creation time to plot items for sorting functionality. - - Args: - items: List of plot item dictionaries - base_path: Base path where plot files are located - - Returns: - Updated items list with creation_time field - """ - for item in items: - try: - # Try to get creation time from PNG file first, then PDF - png_path = None - pdf_path = None - - # Extract relative path from href - if 'png_href' in item: - png_rel_path = item['png_href'].replace('../', '').replace( - './', '') - png_path = Path(base_path) / png_rel_path - - if 'pdf_href' in item: - pdf_rel_path = item['pdf_href'].replace('../', '').replace( - './', '') - pdf_path = Path(base_path) / pdf_rel_path - - # Use PNG creation time if available, otherwise PDF - creation_time = 0 - if png_path and png_path.exists(): - creation_time = int(png_path.stat().st_ctime) - elif pdf_path and pdf_path.exists(): - creation_time = int(pdf_path.stat().st_ctime) - - # Add creation time as timestamp (JavaScript can handle this) - item['creation_time'] = creation_time - - except Exception as e: - # Fallback to 0 if there's any error - name = item.get('name', 'unknown') - print(f"Warning: Could not get creation time for {name}: {e}") - item['creation_time'] = 0 - - return items - - -def example_integration(): - """ - Example of how to integrate this into your existing gallery generation. - """ - # This would be part of your existing gallery generation code - items = [ - { - 'name': 'plot1.png', - 'png_href': './plot1.png', - 'pdf_href': './plot1.pdf' - }, - { - 'name': 'plot2.png', - 'png_href': './plot2.png', - 'pdf_href': './plot2.pdf' - } - ] - - base_path = "/path/to/your/gallery/directory" - - # Add creation times - items_with_time = add_creation_time_to_items(items, base_path) - - # Now items_with_time can be passed to your Jinja2 template - # The template will have access to item.creation_time for each item - - return items_with_time - - -if __name__ == "__main__": - # Test the function - items = example_integration() - for item in items: - created = item['creation_time'] - print(f"Plot: {item['name']}, Created: {created}") diff --git a/python/add_metadata.py b/python/add_metadata.py deleted file mode 100644 index cb2d5ef..0000000 --- a/python/add_metadata.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import Dict, Any -import json -from pathlib import Path - - -def open_metadata(path: str, filename: str = "metadata.json") -> Dict[str, Any]: - """ - Open metadata file and return its contents as a dictionary. - - Args: - path: The directory path where the metadata file is located. - filename: The name of the metadata file (default: "metadata.json"). - - Returns: - A dictionary containing the metadata. - """ - with open(Path(path) / filename, 'r', encoding='utf-8') as f: - try: - return json.load(f) - except json.JSONDecodeError as e: - raise ValueError(f"Error decoding JSON from {filename}: {e}") - except FileNotFoundError: - raise FileNotFoundError(f"Metadata file {filename} not found in {path}") - except Exception as e: - raise RuntimeError(f"Unexpected error reading metadata: {e}") - - -def merge_metadata( - base_metadata: Dict[str, Any], - additional_metadata: Dict[str, Any] -) -> Dict[str, Any]: - """ - Merge two metadata dictionaries. - - Args: - base_metadata: The base metadata dictionary. - additional_metadata: The additional metadata dictionary to merge. - - Returns: - A new dictionary containing the merged metadata. - """ - merged = base_metadata.copy() - for key, value in additional_metadata.items(): - merged[key] = value - return merged diff --git a/tests/.coverage b/tests/.coverage deleted file mode 100644 index 159f6fa..0000000 Binary files a/tests/.coverage and /dev/null differ diff --git a/tests/cleanup.sh b/tests/cleanup.sh deleted file mode 100755 index da743fe..0000000 --- a/tests/cleanup.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -set -e - -echo "๐Ÿงน Cleaning up test directory..." -echo - -# Remove Python cache files -echo "Removing Python cache files..." -find /work/kschmidt/web -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true -find /work/kschmidt/web -name "*.pyc" -delete 2>/dev/null || true - -# Remove temporary test files -echo "Removing temporary test files..." -rm -f /work/kschmidt/web/tests/*.sif -rm -f /work/kschmidt/web/tests/test-results.xml -rm -f /work/kschmidt/web/*.sif -rm -f /work/kschmidt/web/.coverage -rm -rf /work/kschmidt/web/coverage_html_report/ -rm -f /work/kschmidt/web/coverage.xml - -# List remaining test files -echo -echo "๐Ÿ“ Remaining test files:" -ls -la /work/kschmidt/web/tests/ - -echo -echo "๐Ÿ“Š Test directory size:" -du -sh /work/kschmidt/web/tests/ - -echo -echo "โœ… Test directory cleanup complete!" diff --git a/tests/coverage.xml b/tests/coverage.xml deleted file mode 100644 index b805b44..0000000 --- a/tests/coverage.xml +++ /dev/null @@ -1,5277 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/run_coverage.py b/tests/run_coverage.py deleted file mode 100755 index 6e91ed5..0000000 --- a/tests/run_coverage.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/bin/bash -""" -Automated coverage testing script for the gallery generator. -This script runs tests with coverage analysis and generates comprehensive reports. -""" - -import subprocess -import sys -import os -from pathlib import Path - - -def run_coverage_tests(): - """Run tests with coverage analysis.""" - print("๐Ÿ”ฌ Starting automated coverage testing...") - - # Ensure we're in the right directory - os.chdir('/src' if Path('/src').exists() else Path(__file__).parent.parent) - - # Remove old coverage data - subprocess.run(['coverage', 'erase'], capture_output=True) - - # Run tests with coverage - print("๐Ÿ“Š Running tests with coverage analysis...") - - test_files = [ - 'tests/test_simple_coverage.py', - 'tests/test_container.py', - 'tests/test_build_container.py' - ] - - success = True - for test_file in test_files: - if Path(test_file).exists(): - print(f" Running {test_file}...") - result = subprocess.run([ - 'coverage', 'run', '--append', '-m', 'unittest', - test_file.replace('/', '.').replace('.py', '') - ], capture_output=True, text=True) - - if result.returncode != 0: - print(f"โŒ Failed: {test_file}") - print(f"Error: {result.stderr}") - success = False - else: - print(f"โœ… Passed: {test_file}") - - if not success: - print("โŒ Some tests failed. Coverage report may be incomplete.") - return False - - # Generate coverage reports - print("\n๐Ÿ“ˆ Generating coverage reports...") - - # Console report - print("\n๐Ÿ–ฅ๏ธ Console Coverage Report:") - subprocess.run(['coverage', 'report']) - - # HTML report - html_result = subprocess.run(['coverage', 'html'], capture_output=True, text=True) - if html_result.returncode == 0: - print("\n๐ŸŒ HTML coverage report generated: coverage_html_report/index.html") - - # XML report for CI/CD - xml_result = subprocess.run(['coverage', 'xml'], capture_output=True, text=True) - if xml_result.returncode == 0: - print("๐Ÿ“„ XML coverage report generated: coverage.xml") - - # Coverage percentage - percentage_result = subprocess.run([ - 'coverage', 'report', '--format=total' - ], capture_output=True, text=True) - - if percentage_result.returncode == 0: - try: - coverage_pct = float(percentage_result.stdout.strip()) - print(f"\n๐ŸŽฏ Total Coverage: {coverage_pct:.2f}%") - - if coverage_pct >= 80: - print("โœ… Coverage target met (โ‰ฅ80%)") - return True - else: - print("โš ๏ธ Coverage below target (โ‰ฅ80%)") - return False - except ValueError: - print("โš ๏ธ Could not parse coverage percentage") - - return success - - -def main(): - """Main coverage testing function.""" - print("=" * 60) - print("๐Ÿงช Gallery Generator - Automated Coverage Testing") - print("=" * 60) - - try: - success = run_coverage_tests() - - if success: - print("\nโœ… Coverage testing completed successfully!") - sys.exit(0) - else: - print("\nโŒ Coverage testing failed!") - sys.exit(1) - - except Exception as e: - print(f"\n๐Ÿ’ฅ Coverage testing error: {e}") - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/tests/run_coverage_local.sh b/tests/run_coverage_local.sh deleted file mode 100755 index 9a33eb4..0000000 --- a/tests/run_coverage_local.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -# Local coverage testing script - run coverage without container - -echo "๐Ÿ”ฌ Running local coverage testing..." - -# Ensure coverage is installed -pip install coverage 2>/dev/null || echo "Coverage already installed" - -# Change to project root -cd /work/kschmidt/web - -# Clean previous coverage data -coverage erase - -# Run tests with coverage -echo "๐Ÿ“Š Running tests with coverage..." -coverage run --source=/work/kschmidt/web /work/kschmidt/web/tests/test_simple_coverage.py 2>/dev/null || \ -coverage run --append --source=/work/kschmidt/web -m unittest tests.test_container 2>/dev/null || \ -coverage run --append --source=/work/kschmidt/web -m unittest tests.test_build_container 2>/dev/null || \ -echo "Running fallback coverage..." - -# Generate reports -echo "๐Ÿ“ˆ Generating coverage reports..." -echo -echo "๐Ÿ–ฅ๏ธ Console Coverage Report:" -coverage report --include="*generate_gallery*,*orchestration*" || coverage report - -echo -echo "๐ŸŒ Generating HTML report..." -coverage html --directory=coverage_html_report -echo "HTML report generated: coverage_html_report/index.html" - -echo -echo "๐Ÿ“„ Generating XML report..." -coverage xml -echo "XML report generated: coverage.xml" - -# Show coverage percentage -COVERAGE_PCT=$(coverage report --format=total 2>/dev/null || echo "0") -echo -echo "๐ŸŽฏ Total Coverage: ${COVERAGE_PCT}%" - -if [ "${COVERAGE_PCT}" != "0" ] && (( $(echo "$COVERAGE_PCT >= 80" | bc -l 2>/dev/null || echo "0") )); then - echo "โœ… Coverage target met (โ‰ฅ80%)" -else - echo "โš ๏ธ Coverage below target (โ‰ฅ80%)" -fi - -echo -echo "โœ… Local coverage testing complete!" diff --git a/tests/run_pytest_coverage.py b/tests/run_pytest_coverage.py deleted file mode 100755 index 5f27f53..0000000 --- a/tests/run_pytest_coverage.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python3 -""" -Clean pytest-based coverage runner for container environment. -""" - -import subprocess -import sys -import os -from pathlib import Path - - -def run_pytest_with_coverage(): - """Run pytest with coverage analysis.""" - print("๐Ÿงช Running pytest with coverage...") - - # Ensure we're in the right directory - if Path('/src').exists(): - os.chdir('/src') - else: - os.chdir(Path(__file__).parent.parent) - - # Clean previous coverage data - subprocess.run(['coverage', 'erase'], capture_output=True) - - # Run pytest with coverage - cmd = [ - 'python3', '-m', 'pytest', - 'tests/test_pytest_suite.py', - '--cov=.', - '--cov-report=term-missing', - '--cov-report=html:coverage_html_report', - '--cov-report=xml:coverage.xml', - '--cov-config=.coveragerc', - '-v' - ] - - print(f"Running: {' '.join(cmd)}") - result = subprocess.run(cmd) - - if result.returncode == 0: - print("\nโœ… Pytest coverage completed successfully!") - - # Extract coverage percentage - try: - coverage_result = subprocess.run( - ['coverage', 'report', '--format=total'], - capture_output=True, text=True - ) - if coverage_result.returncode == 0: - coverage_pct = float(coverage_result.stdout.strip()) - print(f"๐ŸŽฏ Total Coverage: {coverage_pct:.2f}%") - - if coverage_pct >= 80: - print("โœ… Coverage target met (โ‰ฅ80%)") - return True - else: - print("โš ๏ธ Coverage below target (โ‰ฅ80%)") - except (ValueError, subprocess.SubprocessError): - print("โš ๏ธ Could not extract coverage percentage") - - return True - else: - print("โŒ Pytest coverage failed!") - return False - - -def main(): - """Main entry point.""" - print("=" * 60) - print("๐Ÿ”ฌ Gallery Generator - Pytest Coverage Testing") - print("=" * 60) - - success = run_pytest_with_coverage() - sys.exit(0 if success else 1) - - -if __name__ == "__main__": - main() diff --git a/tests/test_build_container.py b/tests/test_build_container.py deleted file mode 100644 index 1130fc8..0000000 --- a/tests/test_build_container.py +++ /dev/null @@ -1,213 +0,0 @@ -""" -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) diff --git a/tests/test_container.py b/tests/test_container.py deleted file mode 100644 index 43ab248..0000000 --- a/tests/test_container.py +++ /dev/null @@ -1,250 +0,0 @@ -""" -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) diff --git a/tests/test_container.sh b/tests/test_container.sh deleted file mode 100755 index c870583..0000000 --- a/tests/test_container.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/bin/bash -set -e - -echo "===================================================================" -echo "Gallery Generator Container Test Suite with Coverage" -echo "===================================================================" -echo - -# Check if we're in the right directory -if [[ ! -f "Singularity.def" ]]; then - echo "โŒ Error: Singularity.def not found. Please run from project root." - exit 1 -fi - -# Check for apptainer/singularity -CONTAINER_CMD="" -if command -v apptainer &> /dev/null; then - CONTAINER_CMD="apptainer" -elif command -v singularity &> /dev/null; then - CONTAINER_CMD="singularity" -else - echo "โŒ Error: Neither 'apptainer' nor 'singularity' found." - echo "Please install Apptainer/Singularity to run container tests." - exit 1 -fi - -echo "Using container runtime: $CONTAINER_CMD" -echo - -# Container file -CONTAINER_FILE="gallery-test.sif" - -# Clean up any existing container -if [[ -f "$CONTAINER_FILE" ]]; then - echo "๐Ÿงน Removing existing container..." - rm -f "$CONTAINER_FILE" -fi - -echo "๐Ÿ”จ Building container..." -echo "Command: $CONTAINER_CMD build $CONTAINER_FILE Singularity.def" -echo - -if ! $CONTAINER_CMD build "$CONTAINER_FILE" Singularity.def; then - echo "โŒ Container build failed!" - exit 1 -fi - -echo -echo "โœ… Container built successfully!" -echo "Container size: $(du -h "$CONTAINER_FILE" | cut -f1)" -echo - -echo "๐Ÿงช Running container build tests..." -echo -if ! python3 tests/test_build_container.py; then - echo "โŒ Container build tests failed!" - exit 1 -fi - -echo -echo "๐Ÿงช Running tests inside container..." -echo -if ! $CONTAINER_CMD exec "$CONTAINER_FILE" python3 /src/tests/test_container.py; then - echo "โŒ Container tests failed!" - exit 1 -fi - -echo -echo "๐Ÿงช Testing container runtime..." -echo -echo "Python version in container:" -$CONTAINER_CMD exec "$CONTAINER_FILE" python3 --version - -echo -echo "Installed packages:" -$CONTAINER_CMD exec "$CONTAINER_FILE" pip list - -echo -echo "Testing ImageMagick:" -$CONTAINER_CMD exec "$CONTAINER_FILE" convert -version | head -n 2 - -echo -echo "๐Ÿงช Running automated coverage tests..." -$CONTAINER_CMD exec "$CONTAINER_FILE" python3 /src/tests/run_coverage.py - -echo -echo "===================================================================" -echo "โœ… ALL TESTS PASSED!" -echo "Container is ready for use with coverage analysis completed." -echo "===================================================================" -echo -echo "To use the container:" -echo " $CONTAINER_CMD run $CONTAINER_FILE [args]" -echo " $CONTAINER_CMD exec $CONTAINER_FILE python3 /src/generate_gallery.py [args]" -echo " $CONTAINER_CMD exec $CONTAINER_FILE python3 /src/tests/run_coverage.py # Run coverage tests" -echo - -# Optional: Clean up -read -p "Remove test container? (y/N) " -n 1 -r -echo -if [[ $REPLY =~ ^[Yy]$ ]]; then - rm -f "$CONTAINER_FILE" - echo "๐Ÿงน Test container removed." -fi diff --git a/tests/test_coverage.py b/tests/test_coverage.py deleted file mode 100644 index 0a4308a..0000000 --- a/tests/test_coverage.py +++ /dev/null @@ -1,258 +0,0 @@ -""" -Test coverage analysis for the gallery generator container test suite. -This module analyzes what functionality is covered by our streamlined tests. -""" - -import unittest -import inspect -import sys -from pathlib import Path - - -class TestCoverage(unittest.TestCase): - """Analyze test coverage of the container test suite.""" - - def setUp(self): - """Set up test environment.""" - sys.path.insert(0, '/src' if Path('/src').exists() else str(Path(__file__).parent.parent)) - - def test_core_functions_covered(self): - """Test that core functions are covered by our test suite.""" - try: - from generate_gallery import ( - format_file_size, - needs_update, - convert_pdf_to_png, - build_gallery, - calculate_directory_stats - ) - - # These functions should be importable - self.assertTrue(callable(format_file_size)) - self.assertTrue(callable(needs_update)) - self.assertTrue(callable(convert_pdf_to_png)) - self.assertTrue(callable(build_gallery)) - self.assertTrue(callable(calculate_directory_stats)) - - print("โœ… Core functions are accessible") - - except ImportError as e: - self.fail(f"Core functions not accessible: {e}") - - def test_metadata_functions_covered(self): - """Test that metadata functions are covered.""" - try: - from orchestration.metadata import ( - load_metadata_file, - load_folder_metadata, - merge_metadata, - resolve_metadata_for_plot, - save_metadata_cache - ) - - # These functions should be importable - self.assertTrue(callable(load_metadata_file)) - self.assertTrue(callable(load_folder_metadata)) - self.assertTrue(callable(merge_metadata)) - self.assertTrue(callable(resolve_metadata_for_plot)) - self.assertTrue(callable(save_metadata_cache)) - - print("โœ… Metadata functions are accessible") - - except ImportError as e: - self.fail(f"Metadata functions not accessible: {e}") - - def test_config_functions_covered(self): - """Test that config functions are covered.""" - try: - from orchestration.config import Config - - self.assertTrue(hasattr(Config, 'from_yaml')) - - print("โœ… Config functions are accessible") - - except ImportError as e: - self.fail(f"Config functions not accessible: {e}") - - def test_logger_functions_covered(self): - """Test that logger functions are covered.""" - try: - from orchestration.logger import GalleryLogger, create_logger - - self.assertTrue(callable(GalleryLogger)) - self.assertTrue(callable(create_logger)) - - print("โœ… Logger functions are accessible") - - except ImportError as e: - self.fail(f"Logger functions not accessible: {e}") - - def test_container_test_completeness(self): - """Analyze what our container tests actually cover.""" - from test_container import ( - TestContainerEnvironment, - TestUtilityFunctions, - TestMetadataSystem, - TestPDFProcessing, - TestGalleryGeneration - ) - - # Count test methods in each class - coverage_map = {} - - test_classes = [ - TestContainerEnvironment, - TestUtilityFunctions, - TestMetadataSystem, - TestPDFProcessing, - TestGalleryGeneration - ] - - total_tests = 0 - for test_class in test_classes: - methods = [m for m in dir(test_class) if m.startswith('test_')] - coverage_map[test_class.__name__] = len(methods) - total_tests += len(methods) - - print(f"\n๐Ÿ“Š Container Test Coverage Analysis:") - print(f" Total test methods: {total_tests}") - for class_name, count in coverage_map.items(): - print(f" {class_name}: {count} tests") - - # Ensure we have comprehensive coverage - self.assertGreaterEqual(total_tests, 8, "Should have at least 8 test methods") - self.assertGreater(coverage_map['TestContainerEnvironment'], 2, - "Should test container environment thoroughly") - self.assertGreater(coverage_map['TestUtilityFunctions'], 1, - "Should test utility functions") - self.assertGreater(coverage_map['TestMetadataSystem'], 1, - "Should test metadata system") - - def test_critical_paths_covered(self): - """Test that critical execution paths are covered.""" - critical_paths = { - 'PDF conversion': 'convert_pdf_to_png', - 'Gallery building': 'build_gallery', - 'Metadata loading': 'load_metadata_file', - 'File operations': 'needs_update', - 'Configuration': 'Config.from_yaml' - } - - print(f"\n๐ŸŽฏ Critical Path Coverage:") - - covered_paths = [] - for path_name, function_name in critical_paths.items(): - try: - if '.' in function_name: - # Handle class methods - module_name, method_name = function_name.split('.') - if module_name == 'Config': - from orchestration.config import Config - self.assertTrue(hasattr(Config, method_name)) - else: - # Handle regular functions - if function_name in ['convert_pdf_to_png', 'build_gallery', 'needs_update']: - from generate_gallery import convert_pdf_to_png, build_gallery, needs_update - elif function_name == 'load_metadata_file': - from orchestration.metadata import load_metadata_file - - covered_paths.append(path_name) - print(f" โœ… {path_name}") - - except ImportError: - print(f" โŒ {path_name} - not accessible") - - coverage_percentage = (len(covered_paths) / len(critical_paths)) * 100 - print(f"\n๐Ÿ“ˆ Critical path coverage: {coverage_percentage:.1f}%") - - self.assertGreaterEqual(coverage_percentage, 80, - "Should cover at least 80% of critical paths") - - def test_dependency_coverage(self): - """Test that all required dependencies are covered.""" - required_deps = ['jinja2', 'yaml', 'subprocess', 'pathlib'] - - print(f"\n๐Ÿ”— Dependency Coverage:") - - covered_deps = [] - for dep in required_deps: - try: - if dep == 'yaml': - import yaml - elif dep == 'jinja2': - import jinja2 - elif dep == 'subprocess': - import subprocess - elif dep == 'pathlib': - import pathlib - - covered_deps.append(dep) - print(f" โœ… {dep}") - - except ImportError: - print(f" โŒ {dep} - not available") - - coverage_percentage = (len(covered_deps) / len(required_deps)) * 100 - print(f"\n๐Ÿ“ˆ Dependency coverage: {coverage_percentage:.1f}%") - - self.assertGreaterEqual(coverage_percentage, 75, - "Should have at least 75% of dependencies available") - - -def analyze_test_coverage(): - """Run coverage analysis and print detailed report.""" - print("=" * 60) - print("GALLERY GENERATOR TEST COVERAGE ANALYSIS") - print("=" * 60) - - # Run coverage tests - loader = unittest.TestLoader() - suite = loader.loadTestsFromTestCase(TestCoverage) - runner = unittest.TextTestRunner(verbosity=2, stream=sys.stdout) - result = runner.run(suite) - - print("\n" + "=" * 60) - print("COVERAGE SUMMARY") - print("=" * 60) - - if result.wasSuccessful(): - print("โœ… All coverage requirements met!") - print("\n๐Ÿ“‹ Test Suite Status:") - print(" โ€ข Container environment validation: โœ…") - print(" โ€ข Core functionality testing: โœ…") - print(" โ€ข Metadata system testing: โœ…") - print(" โ€ข PDF processing testing: โœ…") - print(" โ€ข End-to-end workflow testing: โœ…") - print(" โ€ข Dependency validation: โœ…") - - print("\n๐ŸŽฏ What our tests cover:") - print(" โ€ข Python 3.11+ environment") - print(" โ€ข jinja2 and pyyaml dependencies") - print(" โ€ข ImageMagick integration") - print(" โ€ข File operations and utilities") - print(" โ€ข YAML/JSON metadata processing") - print(" โ€ข PDF to PNG conversion") - print(" โ€ข Gallery generation workflow") - print(" โ€ข Error handling and edge cases") - - print("\nโœจ Benefits of our streamlined approach:") - print(" โ€ข No external test dependencies") - print(" โ€ข Container-native testing") - print(" โ€ข Real environment validation") - print(" โ€ข CI/CD pipeline integration") - print(" โ€ข Production-ready validation") - - else: - print("โŒ Some coverage requirements not met") - print(f" Failures: {len(result.failures)}") - print(f" Errors: {len(result.errors)}") - - print("=" * 60) - - return 0 if result.wasSuccessful() else 1 - - -if __name__ == '__main__': - import sys - exit_code = analyze_test_coverage() - sys.exit(exit_code) diff --git a/tests/test_focused_coverage.py b/tests/test_focused_coverage.py deleted file mode 100644 index 10ce0d7..0000000 --- a/tests/test_focused_coverage.py +++ /dev/null @@ -1,134 +0,0 @@ -""" -Focused coverage test - tests actual functions without container dependencies -""" - -import unittest -import tempfile -import os -import sys -from pathlib import Path - -# Add project root to path -sys.path.insert(0, str(Path(__file__).parent.parent)) - - -class TestActualFunctions(unittest.TestCase): - """Test actual functions for coverage analysis.""" - - def setUp(self): - """Set up test environment.""" - self.test_dir = tempfile.mkdtemp() - - def tearDown(self): - """Clean up test environment.""" - import shutil - shutil.rmtree(self.test_dir, ignore_errors=True) - - def test_config_loading(self): - """Test configuration loading.""" - from orchestration.config import Config - - # Create a test config file - config_content = """ -title: "Test Gallery" -description: "Test gallery for coverage" -output_dir: "output" -""" - config_file = os.path.join(self.test_dir, "test_config.yaml") - with open(config_file, 'w') as f: - f.write(config_content) - - # Test loading - config = Config.from_yaml(config_file) - self.assertEqual(config.title, "Test Gallery") - self.assertEqual(config.description, "Test gallery for coverage") - - def test_metadata_functions(self): - """Test metadata functions.""" - from orchestration.metadata import load_metadata_file, merge_metadata - - # Create test metadata - metadata_content = """ -title: "Test Plot" -author: "Test Author" -date: "2025-01-01" -""" - metadata_file = os.path.join(self.test_dir, "metadata.yaml") - with open(metadata_file, 'w') as f: - f.write(metadata_content) - - # Test loading - metadata = load_metadata_file(metadata_file) - self.assertEqual(metadata['title'], "Test Plot") - self.assertEqual(metadata['author'], "Test Author") - - # Test merging - base_meta = {'title': 'Base', 'type': 'plot'} - override_meta = {'title': 'Override', 'new_field': 'value'} - merged = merge_metadata(base_meta, override_meta) - - self.assertEqual(merged['title'], 'Override') # Override wins - self.assertEqual(merged['type'], 'plot') # Base preserved - self.assertEqual(merged['new_field'], 'value') # New field added - - def test_logger_creation(self): - """Test logger creation.""" - from orchestration.logger import create_logger, GalleryLogger - - # Test creating a logger - logger = create_logger("test_logger") - self.assertIsNotNone(logger) - - # Test GalleryLogger - gallery_logger = GalleryLogger("test_gallery") - self.assertIsNotNone(gallery_logger) - - def test_file_operations(self): - """Test file operation utilities.""" - # Create test files - old_file = os.path.join(self.test_dir, "old.txt") - new_file = os.path.join(self.test_dir, "new.txt") - - # Create old file first - with open(old_file, 'w') as f: - f.write("old content") - - # Wait a moment then create new file - import time - time.sleep(0.1) - - with open(new_file, 'w') as f: - f.write("new content") - - # Test file modification times - old_stat = os.stat(old_file) - new_stat = os.stat(new_file) - - self.assertLess(old_stat.st_mtime, new_stat.st_mtime) - - def test_path_operations(self): - """Test path and directory operations.""" - test_path = Path(self.test_dir) - - # Test path exists - self.assertTrue(test_path.exists()) - self.assertTrue(test_path.is_dir()) - - # Create subdirectory - subdir = test_path / "subdir" - subdir.mkdir() - - self.assertTrue(subdir.exists()) - self.assertTrue(subdir.is_dir()) - - # Create file in subdir - test_file = subdir / "test.txt" - test_file.write_text("test content") - - self.assertTrue(test_file.exists()) - self.assertTrue(test_file.is_file()) - self.assertEqual(test_file.read_text(), "test content") - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_pytest_suite.py b/tests/test_pytest_suite.py deleted file mode 100644 index 9cc86cd..0000000 --- a/tests/test_pytest_suite.py +++ /dev/null @@ -1,210 +0,0 @@ -""" -Pytest-based test suite for the gallery generator container. -Clean, focused tests using pytest conventions. -""" - -import pytest -import sys -import tempfile -import shutil -from pathlib import Path - - -# Add project root to Python path for container testing -if Path('/src').exists(): - sys.path.insert(0, '/src') -else: - sys.path.insert(0, str(Path(__file__).parent.parent)) - - -@pytest.fixture -def temp_dir(): - """Create a temporary directory for tests.""" - temp_path = tempfile.mkdtemp() - yield temp_path - shutil.rmtree(temp_path, ignore_errors=True) - - -@pytest.fixture -def mock_pdf_content(): - """Mock PDF content for testing.""" - return (b'%PDF-1.4\n1 0 obj\n<<\n/Type /Catalog\n/Pages 2 0 R\n>>' - b'\nendobj\nxref\n0 3\n0000000000 65535 f \ntrailer\n<<\n' - b'/Size 3\n/Root 1 0 R\n>>\nstartxref\n9\n%%EOF') - - -class TestEnvironment: - """Test container environment and dependencies.""" - - def test_python_version(self): - """Test Python version is correct.""" - assert sys.version_info.major == 3 - assert sys.version_info.minor >= 10 - - def test_required_packages(self): - """Test required packages are available.""" - import jinja2 - import yaml - import coverage - assert jinja2.__version__ - assert yaml.__version__ - assert coverage.__version__ - - -class TestCoreModules: - """Test core application modules.""" - - def test_generate_gallery_import(self): - """Test main module imports correctly.""" - try: - import generate_gallery - assert hasattr(generate_gallery, 'main') - except ImportError: - pytest.skip("generate_gallery not available in test environment") - - def test_config_module(self): - """Test config module functionality.""" - try: - from orchestration.config import Config - assert hasattr(Config, 'from_yaml') - except ImportError: - pytest.skip("Config module not available") - - def test_logger_module(self): - """Test logger module functionality.""" - try: - from orchestration.logger import create_logger - logger = create_logger('test') - assert logger.name == 'test' - except ImportError: - pytest.skip("Logger module not available") - - -class TestUtilityFunctions: - """Test utility functions.""" - - def test_file_operations(self, temp_dir): - """Test basic file operations.""" - test_file = Path(temp_dir) / 'test.txt' - test_file.write_text('test content') - assert test_file.exists() - assert test_file.read_text() == 'test content' - - def test_directory_operations(self, temp_dir): - """Test directory operations.""" - test_subdir = Path(temp_dir) / 'subdir' - test_subdir.mkdir() - assert test_subdir.is_dir() - - -class TestPDFProcessing: - """Test PDF-related functionality.""" - - def test_mock_pdf_creation(self, temp_dir, mock_pdf_content): - """Test creating mock PDF files.""" - pdf_path = Path(temp_dir) / 'test.pdf' - pdf_path.write_bytes(mock_pdf_content) - assert pdf_path.exists() - assert pdf_path.stat().st_size > 0 - - def test_imagemagick_available(self): - """Test ImageMagick is available in container.""" - import subprocess - try: - result = subprocess.run(['convert', '-version'], - capture_output=True, text=True) - assert result.returncode == 0 - assert 'ImageMagick' in result.stdout - except FileNotFoundError: - pytest.skip("ImageMagick not available") - - -class TestMetadataSystem: - """Test metadata handling.""" - - def test_yaml_processing(self, temp_dir): - """Test YAML metadata processing.""" - import yaml - - metadata = { - 'title': 'Test Gallery', - 'description': 'Test description', - 'plots': ['plot1.pdf', 'plot2.pdf'] - } - - yaml_path = Path(temp_dir) / 'metadata.yaml' - with open(yaml_path, 'w') as f: - yaml.dump(metadata, f) - - # Read back and verify - with open(yaml_path, 'r') as f: - loaded = yaml.safe_load(f) - - assert loaded['title'] == 'Test Gallery' - assert len(loaded['plots']) == 2 - - def test_metadata_module(self): - """Test metadata module if available.""" - try: - from orchestration.metadata import load_metadata_file - # Test with minimal functionality - assert callable(load_metadata_file) - except ImportError: - pytest.skip("Metadata module not available") - - -class TestGalleryGeneration: - """Test gallery generation workflow.""" - - def test_template_processing(self, temp_dir): - """Test Jinja2 template processing.""" - from jinja2 import Template - - template_content = """ - - {{ title }} - - {% for plot in plots %} - Plot {{ loop.index }} - {% endfor %} - - - """ - - template = Template(template_content) - result = template.render( - title='Test Gallery', - plots=['plot1.png', 'plot2.png'] - ) - - assert 'Test Gallery' in result - assert 'plot1.png' in result - assert 'plot2.png' in result - - def test_gallery_workflow(self, temp_dir, mock_pdf_content): - """Test complete gallery workflow simulation.""" - # Create mock directory structure - input_dir = Path(temp_dir) / 'input' - output_dir = Path(temp_dir) / 'output' - input_dir.mkdir() - output_dir.mkdir() - - # Create mock PDF - pdf_path = input_dir / 'test.pdf' - pdf_path.write_bytes(mock_pdf_content) - - # Create metadata - import yaml - metadata = {'title': 'Test', 'description': 'Test gallery'} - meta_path = input_dir / 'metadata.yaml' - with open(meta_path, 'w') as f: - yaml.dump(metadata, f) - - # Verify setup - assert pdf_path.exists() - assert meta_path.exists() - assert output_dir.exists() - - -if __name__ == '__main__': - pytest.main([__file__, '-v']) diff --git a/tests/test_simple_coverage.py b/tests/test_simple_coverage.py deleted file mode 100644 index 8d6f83c..0000000 --- a/tests/test_simple_coverage.py +++ /dev/null @@ -1,144 +0,0 @@ -""" -Simple coverage test that actually works -""" - -import unittest -import sys -import tempfile -import shutil -from pathlib import Path - -# Add the project root to Python path -project_root = Path(__file__).parent.parent -sys.path.insert(0, str(project_root)) - - -class TestSimpleCoverage(unittest.TestCase): - """Simple tests that will give us coverage data.""" - - def test_basic_imports(self): - """Test that we can import basic modules.""" - # These should work - import os - import sys - import pathlib - self.assertTrue(os.path.exists('/')) - self.assertIsNotNone(sys.version) - self.assertIsNotNone(pathlib.Path.cwd()) - - def test_orchestration_config(self): - """Test config module import and basic functionality.""" - try: - from orchestration.config import Config, PathConfig, GalleryConfig, UIConfig - - # Test PathConfig creation - path_config = PathConfig( - work_dir="/tmp", - web_folder="/tmp/web" - ) - - self.assertEqual(path_config.work_dir, "/tmp") - self.assertEqual(path_config.web_folder, "/tmp/web") - - # Test Config class exists - self.assertTrue(hasattr(Config, 'from_yaml')) - - # Test GalleryConfig - gallery_config = GalleryConfig( - plot_root="/plots", - png_dpi=150, - backup_folder="/backup" - ) - - self.assertEqual(gallery_config.plot_root, "/plots") - self.assertEqual(gallery_config.png_dpi, 150) - - # Test UIConfig - ui_config = UIConfig( - max_recent_plots=10, - search_debounce_ms=300 - ) - - self.assertEqual(ui_config.max_recent_plots, 10) - self.assertEqual(ui_config.search_debounce_ms, 300) - - except ImportError: - self.skipTest("Config module not available") - - def test_orchestration_metadata(self): - """Test metadata module functions.""" - try: - from orchestration.metadata import merge_metadata, load_folder_metadata - - # Test merge_metadata function - base = {"title": "Base Title", "author": "Base Author"} - override = {"title": "Override Title", "type": "plot"} - - merged = merge_metadata(base, override) - - # Override should win for title - self.assertEqual(merged["title"], "Override Title") - # Base should be preserved for author - self.assertEqual(merged["author"], "Base Author") - # New field should be added - self.assertEqual(merged["type"], "plot") - - # Test empty metadata - empty_base = {} - empty_merged = merge_metadata(empty_base, override) - self.assertEqual(empty_merged["title"], "Override Title") - - # Test load_folder_metadata with non-existent path - temp_dir = tempfile.mkdtemp() - try: - folder_meta = load_folder_metadata(Path(temp_dir)) - self.assertIsInstance(folder_meta, dict) - finally: - shutil.rmtree(temp_dir, ignore_errors=True) - - except ImportError: - self.skipTest("Metadata module not available") - - def test_file_operations(self): - """Test basic file operations that generate coverage.""" - # Create temp directory - temp_dir = tempfile.mkdtemp() - - try: - # Create a test file - test_file = Path(temp_dir) / "test.txt" - test_file.write_text("Hello, World!") - - # Verify file exists and has content - self.assertTrue(test_file.exists()) - content = test_file.read_text() - self.assertEqual(content, "Hello, World!") - - # Test file size - size = test_file.stat().st_size - self.assertGreater(size, 0) - - finally: - shutil.rmtree(temp_dir, ignore_errors=True) - - def test_path_manipulations(self): - """Test path manipulations to generate more coverage.""" - # Test various path operations - current_path = Path.cwd() - self.assertTrue(current_path.exists()) - - # Test path joining - test_path = current_path / "non_existent_file.txt" - self.assertFalse(test_path.exists()) - - # Test path parts - parts = current_path.parts - self.assertGreater(len(parts), 0) - - # Test parent - parent = current_path.parent - self.assertIsInstance(parent, Path) - - -if __name__ == '__main__': - unittest.main() diff --git a/orchestration/backup.py b/utils/backup.py similarity index 100% rename from orchestration/backup.py rename to utils/backup.py diff --git a/orchestration/config.py b/utils/config.py similarity index 100% rename from orchestration/config.py rename to utils/config.py diff --git a/orchestration/metadata.py b/utils/metadata.py similarity index 100% rename from orchestration/metadata.py rename to utils/metadata.py