Clean up unittest attempt.
This commit is contained in:
-36
@@ -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
|
||||
-421
@@ -1,421 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<coverage version="7.10.6" timestamp="1757321291920" lines-valid="379" lines-covered="128" line-rate="0.3377" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
|
||||
<!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.10.6 -->
|
||||
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
|
||||
<sources>
|
||||
<source></source>
|
||||
</sources>
|
||||
<packages>
|
||||
<package name="." line-rate="0.9437" branch-rate="0" complexity="0">
|
||||
<classes>
|
||||
<class name="test_simple_coverage.py" filename="test_simple_coverage.py" complexity="0" line-rate="0.9437" branch-rate="0">
|
||||
<methods/>
|
||||
<lines>
|
||||
<line number="5" hits="1"/>
|
||||
<line number="6" hits="1"/>
|
||||
<line number="7" hits="1"/>
|
||||
<line number="8" hits="1"/>
|
||||
<line number="9" hits="1"/>
|
||||
<line number="12" hits="1"/>
|
||||
<line number="13" hits="1"/>
|
||||
<line number="16" hits="1"/>
|
||||
<line number="19" hits="1"/>
|
||||
<line number="22" hits="1"/>
|
||||
<line number="23" hits="1"/>
|
||||
<line number="24" hits="1"/>
|
||||
<line number="25" hits="1"/>
|
||||
<line number="26" hits="1"/>
|
||||
<line number="27" hits="1"/>
|
||||
<line number="29" hits="1"/>
|
||||
<line number="31" hits="1"/>
|
||||
<line number="32" hits="1"/>
|
||||
<line number="35" hits="1"/>
|
||||
<line number="40" hits="1"/>
|
||||
<line number="41" hits="1"/>
|
||||
<line number="44" hits="1"/>
|
||||
<line number="47" hits="1"/>
|
||||
<line number="53" hits="1"/>
|
||||
<line number="54" hits="1"/>
|
||||
<line number="57" hits="1"/>
|
||||
<line number="62" hits="1"/>
|
||||
<line number="63" hits="1"/>
|
||||
<line number="65" hits="0"/>
|
||||
<line number="66" hits="0"/>
|
||||
<line number="68" hits="1"/>
|
||||
<line number="70" hits="1"/>
|
||||
<line number="71" hits="1"/>
|
||||
<line number="74" hits="1"/>
|
||||
<line number="75" hits="1"/>
|
||||
<line number="77" hits="1"/>
|
||||
<line number="80" hits="1"/>
|
||||
<line number="82" hits="1"/>
|
||||
<line number="84" hits="1"/>
|
||||
<line number="87" hits="1"/>
|
||||
<line number="88" hits="1"/>
|
||||
<line number="89" hits="1"/>
|
||||
<line number="92" hits="1"/>
|
||||
<line number="93" hits="1"/>
|
||||
<line number="94" hits="1"/>
|
||||
<line number="95" hits="1"/>
|
||||
<line number="97" hits="1"/>
|
||||
<line number="99" hits="0"/>
|
||||
<line number="100" hits="0"/>
|
||||
<line number="102" hits="1"/>
|
||||
<line number="105" hits="1"/>
|
||||
<line number="107" hits="1"/>
|
||||
<line number="109" hits="1"/>
|
||||
<line number="110" hits="1"/>
|
||||
<line number="113" hits="1"/>
|
||||
<line number="114" hits="1"/>
|
||||
<line number="115" hits="1"/>
|
||||
<line number="118" hits="1"/>
|
||||
<line number="119" hits="1"/>
|
||||
<line number="122" hits="1"/>
|
||||
<line number="124" hits="1"/>
|
||||
<line number="127" hits="1"/>
|
||||
<line number="128" hits="1"/>
|
||||
<line number="131" hits="1"/>
|
||||
<line number="132" hits="1"/>
|
||||
<line number="135" hits="1"/>
|
||||
<line number="136" hits="1"/>
|
||||
<line number="139" hits="1"/>
|
||||
<line number="140" hits="1"/>
|
||||
<line number="143" hits="1"/>
|
||||
<line number="144" hits="1"/>
|
||||
</lines>
|
||||
</class>
|
||||
</classes>
|
||||
</package>
|
||||
<package name=".work.kschmidt.web" line-rate="0" branch-rate="0" complexity="0">
|
||||
<classes>
|
||||
<class name="generate_gallery.py" filename="/work/kschmidt/web/generate_gallery.py" complexity="0" line-rate="0" branch-rate="0">
|
||||
<methods/>
|
||||
<lines>
|
||||
<line number="16" hits="0"/>
|
||||
<line number="17" hits="0"/>
|
||||
<line number="18" hits="0"/>
|
||||
<line number="19" hits="0"/>
|
||||
<line number="20" hits="0"/>
|
||||
<line number="21" hits="0"/>
|
||||
<line number="22" hits="0"/>
|
||||
<line number="23" hits="0"/>
|
||||
<line number="25" hits="0"/>
|
||||
<line number="26" hits="0"/>
|
||||
<line number="35" hits="0"/>
|
||||
<line number="38" hits="0"/>
|
||||
<line number="40" hits="0"/>
|
||||
<line number="43" hits="0"/>
|
||||
<line number="45" hits="0"/>
|
||||
<line number="48" hits="0"/>
|
||||
<line number="49" hits="0"/>
|
||||
<line number="50" hits="0"/>
|
||||
<line number="51" hits="0"/>
|
||||
<line number="54" hits="0"/>
|
||||
<line number="67" hits="0"/>
|
||||
<line number="69" hits="0"/>
|
||||
<line number="70" hits="0"/>
|
||||
<line number="71" hits="0"/>
|
||||
<line number="72" hits="0"/>
|
||||
<line number="73" hits="0"/>
|
||||
<line number="75" hits="0"/>
|
||||
<line number="77" hits="0"/>
|
||||
<line number="78" hits="0"/>
|
||||
<line number="87" hits="0"/>
|
||||
<line number="98" hits="0"/>
|
||||
<line number="99" hits="0"/>
|
||||
<line number="101" hits="0"/>
|
||||
<line number="102" hits="0"/>
|
||||
<line number="104" hits="0"/>
|
||||
<line number="107" hits="0"/>
|
||||
<line number="123" hits="0"/>
|
||||
<line number="124" hits="0"/>
|
||||
<line number="126" hits="0"/>
|
||||
<line number="127" hits="0"/>
|
||||
<line number="130" hits="0"/>
|
||||
<line number="131" hits="0"/>
|
||||
<line number="133" hits="0"/>
|
||||
<line number="135" hits="0"/>
|
||||
<line number="137" hits="0"/>
|
||||
<line number="138" hits="0"/>
|
||||
<line number="140" hits="0"/>
|
||||
<line number="141" hits="0"/>
|
||||
<line number="143" hits="0"/>
|
||||
<line number="144" hits="0"/>
|
||||
<line number="146" hits="0"/>
|
||||
<line number="147" hits="0"/>
|
||||
<line number="148" hits="0"/>
|
||||
<line number="150" hits="0"/>
|
||||
<line number="152" hits="0"/>
|
||||
<line number="153" hits="0"/>
|
||||
<line number="155" hits="0"/>
|
||||
<line number="156" hits="0"/>
|
||||
<line number="157" hits="0"/>
|
||||
<line number="159" hits="0"/>
|
||||
<line number="162" hits="0"/>
|
||||
<line number="163" hits="0"/>
|
||||
<line number="166" hits="0"/>
|
||||
<line number="168" hits="0"/>
|
||||
<line number="177" hits="0"/>
|
||||
<line number="179" hits="0"/>
|
||||
<line number="180" hits="0"/>
|
||||
<line number="181" hits="0"/>
|
||||
<line number="182" hits="0"/>
|
||||
<line number="183" hits="0"/>
|
||||
<line number="185" hits="0"/>
|
||||
<line number="186" hits="0"/>
|
||||
<line number="188" hits="0"/>
|
||||
<line number="192" hits="0"/>
|
||||
<line number="193" hits="0"/>
|
||||
<line number="194" hits="0"/>
|
||||
<line number="196" hits="0"/>
|
||||
<line number="197" hits="0"/>
|
||||
<line number="198" hits="0"/>
|
||||
<line number="199" hits="0"/>
|
||||
<line number="201" hits="0"/>
|
||||
<line number="202" hits="0"/>
|
||||
<line number="204" hits="0"/>
|
||||
<line number="207" hits="0"/>
|
||||
<line number="208" hits="0"/>
|
||||
<line number="216" hits="0"/>
|
||||
<line number="217" hits="0"/>
|
||||
<line number="220" hits="0"/>
|
||||
<line number="221" hits="0"/>
|
||||
<line number="223" hits="0"/>
|
||||
<line number="224" hits="0"/>
|
||||
<line number="237" hits="0"/>
|
||||
<line number="239" hits="0"/>
|
||||
<line number="242" hits="0"/>
|
||||
<line number="252" hits="0"/>
|
||||
<line number="260" hits="0"/>
|
||||
<line number="261" hits="0"/>
|
||||
<line number="263" hits="0"/>
|
||||
<line number="264" hits="0"/>
|
||||
<line number="265" hits="0"/>
|
||||
<line number="266" hits="0"/>
|
||||
<line number="267" hits="0"/>
|
||||
<line number="269" hits="0"/>
|
||||
<line number="270" hits="0"/>
|
||||
<line number="271" hits="0"/>
|
||||
<line number="272" hits="0"/>
|
||||
<line number="273" hits="0"/>
|
||||
<line number="274" hits="0"/>
|
||||
<line number="276" hits="0"/>
|
||||
<line number="279" hits="0"/>
|
||||
<line number="289" hits="0"/>
|
||||
<line number="290" hits="0"/>
|
||||
<line number="292" hits="0"/>
|
||||
<line number="293" hits="0"/>
|
||||
<line number="294" hits="0"/>
|
||||
<line number="295" hits="0"/>
|
||||
<line number="296" hits="0"/>
|
||||
<line number="297" hits="0"/>
|
||||
<line number="299" hits="0"/>
|
||||
<line number="302" hits="0"/>
|
||||
<line number="307" hits="0"/>
|
||||
<line number="308" hits="0"/>
|
||||
<line number="309" hits="0"/>
|
||||
<line number="310" hits="0"/>
|
||||
<line number="311" hits="0"/>
|
||||
<line number="312" hits="0"/>
|
||||
<line number="313" hits="0"/>
|
||||
<line number="314" hits="0"/>
|
||||
<line number="317" hits="0"/>
|
||||
<line number="327" hits="0"/>
|
||||
<line number="329" hits="0"/>
|
||||
<line number="330" hits="0"/>
|
||||
<line number="331" hits="0"/>
|
||||
<line number="334" hits="0"/>
|
||||
<line number="337" hits="0"/>
|
||||
<line number="338" hits="0"/>
|
||||
<line number="340" hits="0"/>
|
||||
<line number="342" hits="0"/>
|
||||
<line number="343" hits="0"/>
|
||||
<line number="344" hits="0"/>
|
||||
<line number="345" hits="0"/>
|
||||
<line number="346" hits="0"/>
|
||||
<line number="347" hits="0"/>
|
||||
<line number="348" hits="0"/>
|
||||
<line number="350" hits="0"/>
|
||||
<line number="352" hits="0"/>
|
||||
<line number="353" hits="0"/>
|
||||
<line number="355" hits="0"/>
|
||||
<line number="356" hits="0"/>
|
||||
<line number="357" hits="0"/>
|
||||
<line number="359" hits="0"/>
|
||||
<line number="360" hits="0"/>
|
||||
<line number="362" hits="0"/>
|
||||
<line number="363" hits="0"/>
|
||||
<line number="364" hits="0"/>
|
||||
<line number="366" hits="0"/>
|
||||
<line number="367" hits="0"/>
|
||||
<line number="368" hits="0"/>
|
||||
<line number="370" hits="0"/>
|
||||
<line number="372" hits="0"/>
|
||||
<line number="373" hits="0"/>
|
||||
<line number="375" hits="0"/>
|
||||
<line number="376" hits="0"/>
|
||||
<line number="377" hits="0"/>
|
||||
<line number="379" hits="0"/>
|
||||
<line number="382" hits="0"/>
|
||||
<line number="384" hits="0"/>
|
||||
<line number="392" hits="0"/>
|
||||
<line number="393" hits="0"/>
|
||||
<line number="402" hits="0"/>
|
||||
<line number="404" hits="0"/>
|
||||
<line number="405" hits="0"/>
|
||||
<line number="406" hits="0"/>
|
||||
<line number="420" hits="0"/>
|
||||
<line number="421" hits="0"/>
|
||||
<line number="423" hits="0"/>
|
||||
<line number="424" hits="0"/>
|
||||
<line number="425" hits="0"/>
|
||||
<line number="426" hits="0"/>
|
||||
<line number="427" hits="0"/>
|
||||
<line number="429" hits="0"/>
|
||||
<line number="432" hits="0"/>
|
||||
<line number="435" hits="0"/>
|
||||
<line number="436" hits="0"/>
|
||||
<line number="437" hits="0"/>
|
||||
<line number="439" hits="0"/>
|
||||
<line number="441" hits="0"/>
|
||||
<line number="442" hits="0"/>
|
||||
<line number="447" hits="0"/>
|
||||
<line number="448" hits="0"/>
|
||||
</lines>
|
||||
</class>
|
||||
</classes>
|
||||
</package>
|
||||
<package name=".work.kschmidt.web.orchestration" line-rate="0.5169" branch-rate="0" complexity="0">
|
||||
<classes>
|
||||
<class name="config.py" filename="/work/kschmidt/web/orchestration/config.py" complexity="0" line-rate="0.6769" branch-rate="0">
|
||||
<methods/>
|
||||
<lines>
|
||||
<line number="9" hits="1"/>
|
||||
<line number="10" hits="1"/>
|
||||
<line number="11" hits="1"/>
|
||||
<line number="14" hits="1"/>
|
||||
<line number="15" hits="1"/>
|
||||
<line number="17" hits="1"/>
|
||||
<line number="18" hits="1"/>
|
||||
<line number="21" hits="1"/>
|
||||
<line number="22" hits="1"/>
|
||||
<line number="24" hits="1"/>
|
||||
<line number="25" hits="1"/>
|
||||
<line number="26" hits="1"/>
|
||||
<line number="29" hits="1"/>
|
||||
<line number="30" hits="1"/>
|
||||
<line number="32" hits="1"/>
|
||||
<line number="33" hits="1"/>
|
||||
<line number="36" hits="1"/>
|
||||
<line number="37" hits="1"/>
|
||||
<line number="39" hits="1"/>
|
||||
<line number="40" hits="1"/>
|
||||
<line number="41" hits="1"/>
|
||||
<line number="46" hits="1"/>
|
||||
<line number="47" hits="1"/>
|
||||
<line number="49" hits="1"/>
|
||||
<line number="50" hits="1"/>
|
||||
<line number="53" hits="1"/>
|
||||
<line number="54" hits="1"/>
|
||||
<line number="61" hits="1"/>
|
||||
<line number="62" hits="1"/>
|
||||
<line number="63" hits="1"/>
|
||||
<line number="64" hits="1"/>
|
||||
<line number="65" hits="1"/>
|
||||
<line number="67" hits="1"/>
|
||||
<line number="68" hits="1"/>
|
||||
<line number="70" hits="0"/>
|
||||
<line number="72" hits="1"/>
|
||||
<line number="73" hits="1"/>
|
||||
<line number="75" hits="0"/>
|
||||
<line number="77" hits="1"/>
|
||||
<line number="78" hits="1"/>
|
||||
<line number="80" hits="0"/>
|
||||
<line number="82" hits="1"/>
|
||||
<line number="83" hits="1"/>
|
||||
<line number="85" hits="0"/>
|
||||
<line number="87" hits="1"/>
|
||||
<line number="88" hits="1"/>
|
||||
<line number="102" hits="0"/>
|
||||
<line number="103" hits="0"/>
|
||||
<line number="105" hits="0"/>
|
||||
<line number="106" hits="0"/>
|
||||
<line number="107" hits="0"/>
|
||||
<line number="108" hits="0"/>
|
||||
<line number="109" hits="0"/>
|
||||
<line number="111" hits="0"/>
|
||||
<line number="112" hits="0"/>
|
||||
<line number="113" hits="0"/>
|
||||
<line number="114" hits="0"/>
|
||||
<line number="116" hits="0"/>
|
||||
<line number="121" hits="0"/>
|
||||
<line number="129" hits="1"/>
|
||||
<line number="139" hits="0"/>
|
||||
<line number="140" hits="0"/>
|
||||
<line number="143" hits="1"/>
|
||||
<line number="144" hits="0"/>
|
||||
<line number="145" hits="0"/>
|
||||
</lines>
|
||||
</class>
|
||||
<class name="metadata.py" filename="/work/kschmidt/web/orchestration/metadata.py" complexity="0" line-rate="0.3208" branch-rate="0">
|
||||
<methods/>
|
||||
<lines>
|
||||
<line number="15" hits="1"/>
|
||||
<line number="16" hits="1"/>
|
||||
<line number="17" hits="1"/>
|
||||
<line number="18" hits="1"/>
|
||||
<line number="21" hits="1"/>
|
||||
<line number="32" hits="0"/>
|
||||
<line number="33" hits="0"/>
|
||||
<line number="35" hits="0"/>
|
||||
<line number="36" hits="0"/>
|
||||
<line number="37" hits="0"/>
|
||||
<line number="38" hits="0"/>
|
||||
<line number="39" hits="0"/>
|
||||
<line number="40" hits="0"/>
|
||||
<line number="41" hits="0"/>
|
||||
<line number="43" hits="0"/>
|
||||
<line number="45" hits="0"/>
|
||||
<line number="46" hits="0"/>
|
||||
<line number="47" hits="0"/>
|
||||
<line number="48" hits="0"/>
|
||||
<line number="51" hits="1"/>
|
||||
<line number="62" hits="1"/>
|
||||
<line number="63" hits="1"/>
|
||||
<line number="64" hits="1"/>
|
||||
<line number="65" hits="0"/>
|
||||
<line number="67" hits="1"/>
|
||||
<line number="70" hits="1"/>
|
||||
<line number="82" hits="0"/>
|
||||
<line number="84" hits="0"/>
|
||||
<line number="85" hits="0"/>
|
||||
<line number="86" hits="0"/>
|
||||
<line number="87" hits="0"/>
|
||||
<line number="90" hits="0"/>
|
||||
<line number="93" hits="1"/>
|
||||
<line number="108" hits="1"/>
|
||||
<line number="109" hits="1"/>
|
||||
<line number="110" hits="1"/>
|
||||
<line number="113" hits="1"/>
|
||||
<line number="128" hits="0"/>
|
||||
<line number="129" hits="0"/>
|
||||
<line number="132" hits="0"/>
|
||||
<line number="133" hits="0"/>
|
||||
<line number="134" hits="0"/>
|
||||
<line number="135" hits="0"/>
|
||||
<line number="136" hits="0"/>
|
||||
<line number="139" hits="0"/>
|
||||
<line number="142" hits="1"/>
|
||||
<line number="153" hits="0"/>
|
||||
<line number="154" hits="0"/>
|
||||
<line number="155" hits="0"/>
|
||||
<line number="156" hits="0"/>
|
||||
<line number="157" hits="0"/>
|
||||
<line number="158" hits="0"/>
|
||||
<line number="159" hits="0"/>
|
||||
</lines>
|
||||
</class>
|
||||
</classes>
|
||||
</package>
|
||||
</packages>
|
||||
</coverage>
|
||||
+17
-20
@@ -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)
|
||||
|
||||
@@ -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}")
|
||||
@@ -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
|
||||
Binary file not shown.
@@ -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!"
|
||||
-5277
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
@@ -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!"
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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 = """
|
||||
<html>
|
||||
<title>{{ title }}</title>
|
||||
<body>
|
||||
{% for plot in plots %}
|
||||
<img src="{{ plot }}" alt="Plot {{ loop.index }}">
|
||||
{% endfor %}
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
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'])
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user