-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #17 from HeyItsBATMAN/add-simple-testing-framework
Add simple testing framework, for testing import and export of (for now) IIIF manifests and (later) Blender scenes
- Loading branch information
Showing
6 changed files
with
361 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
name: Test IIIF Manifests | ||
|
||
on: | ||
push: | ||
branches: [main] | ||
pull_request: | ||
branches: [main] | ||
|
||
jobs: | ||
test-manifests: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- name: Set up Python | ||
uses: actions/setup-python@v4 | ||
with: | ||
python-version: "3.12" | ||
|
||
- name: Install system dependencies | ||
run: | | ||
sudo apt-get update | ||
sudo apt-get install -y \ | ||
libxrender1 \ | ||
libxxf86vm1 \ | ||
libxfixes3 \ | ||
libxi6 \ | ||
libxkbcommon0 \ | ||
libxkbcommon-x11-0 \ | ||
libgl1 \ | ||
libglu1-mesa \ | ||
libsm6 \ | ||
libxext6 \ | ||
libx11-6 \ | ||
libxcb1 \ | ||
libtinfo5 | ||
- name: Cache Blender | ||
id: cache-blender | ||
uses: actions/cache@v3 | ||
with: | ||
path: /tmp/blender.tar.xz | ||
key: blender-4.2.0 | ||
|
||
- name: Install Blender 4.2 | ||
run: | | ||
BLENDER_VERSION="4.2.4" | ||
BLENDER_FILE="blender-${BLENDER_VERSION}-linux-x64.tar.xz" | ||
if [ ! -f /tmp/blender.tar.xz ]; then | ||
# Download Blender | ||
wget "https://download.blender.org/release/Blender4.2/${BLENDER_FILE}" -O /tmp/blender.tar.xz | ||
fi | ||
# Extract Blender | ||
tar -xf /tmp/blender.tar.xz | ||
# Move Blender to /usr/local and create symlink | ||
sudo mv "blender-${BLENDER_VERSION}-linux-x64" /usr/local/blender | ||
sudo ln -s /usr/local/blender/blender /usr/local/bin/blender | ||
# Verify installation | ||
blender --background --version | ||
- name: Install Python dependencies | ||
run: | | ||
python -m pip install --upgrade pip | ||
# Add any additional dependencies your script needs here | ||
# pip install -r requirements.txt | ||
- name: Build & install Blender plugin | ||
run: | | ||
blender --command extension build --output-filepath iiif_blender.zip | ||
blender --command extension install-file --enable --repo user_default iiif_blender.zip | ||
- name: Run tests | ||
id: run-tests | ||
run: | | ||
echo "Running manifest tests..." | ||
bash tests/run_tests.sh | ||
- name: Report test results | ||
if: always() | ||
run: | | ||
echo "Test execution completed" | ||
if [ "${{ steps.run-tests.outcome }}" == "failure" ]; then | ||
echo "❌ Tests failed" | ||
exit 1 | ||
else | ||
echo "✅ Tests passed" | ||
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import json | ||
import sys | ||
import os | ||
from typing import Any, Dict, List, Tuple, Callable | ||
import subprocess | ||
import bpy | ||
import difflib | ||
|
||
# Ensure the script receives the correct number of arguments | ||
if len(sys.argv) < 4: | ||
print( | ||
"Usage: blender --background --python run_blender_with_plugin.py -- <input_manifest>" | ||
) | ||
sys.exit(1) | ||
|
||
# Get the input and output manifest file paths from the command line arguments | ||
input_manifest = sys.argv[sys.argv.index("--") + 1] | ||
output_manifest = input_manifest.replace(".json", "_export.json") | ||
|
||
context = bpy.context | ||
if context is None: | ||
print("Failed to get the Blender context") | ||
sys.exit(1) | ||
|
||
|
||
def get_extension_id(): | ||
try: | ||
manifest_path = os.path.join(os.path.dirname(__file__), "blender_manifest.toml") | ||
with open(manifest_path, "r") as f: | ||
for line in f: | ||
line = line.strip() | ||
if line.startswith("id = "): | ||
# Extract the value between quotes | ||
return line.split("=")[1].strip().strip('"').strip("'") | ||
except Exception as e: | ||
print(f"Error reading blender_manifest.toml: {e}") | ||
sys.exit(1) | ||
print("Could not find id in blender_manifest.toml") | ||
sys.exit(1) | ||
|
||
|
||
# Load the plugin | ||
needle = get_extension_id() | ||
ext_name = None | ||
for key in context.preferences.addons.keys(): | ||
if needle in key: | ||
ext_name = key | ||
break | ||
|
||
if not ext_name: | ||
print("Failed to find the plugin") | ||
sys.exit(1) | ||
|
||
bpy.ops.preferences.addon_enable(module=ext_name) | ||
|
||
if ext_name not in context.preferences.addons: | ||
print("Failed to load the plugin") | ||
sys.exit(1) | ||
|
||
|
||
def safe_delete(file_path): | ||
try: | ||
# Check if file exists before attempting deletion | ||
if os.path.exists(file_path): | ||
os.remove(file_path) | ||
print(f"File {file_path} has been deleted successfully") | ||
else: | ||
print(f"File {file_path} does not exist") | ||
except Exception as e: | ||
print(f"Error occurred while deleting file: {e}") | ||
|
||
|
||
RED: Callable[[str], str] = lambda text: f"\u001b[31m{text}\033\u001b[0m" | ||
GREEN: Callable[[str], str] = lambda text: f"\u001b[32m{text}\033\u001b[0m" | ||
|
||
|
||
def get_edits_string(old: str, new: str) -> Tuple[str, bool]: | ||
result = "" | ||
|
||
lines = difflib.ndiff(old.splitlines(keepends=True), new.splitlines(keepends=True)) | ||
|
||
has_changes = False | ||
|
||
for line in lines: | ||
line = line.rstrip() | ||
if line.startswith("+"): | ||
has_changes = True | ||
result += GREEN(line) + "\n" | ||
elif line.startswith("-"): | ||
has_changes = True | ||
result += RED(line) + "\n" | ||
elif line.startswith("?"): | ||
continue | ||
else: | ||
result += line + "\n" | ||
|
||
return (result, has_changes) | ||
|
||
|
||
def get_json_diff(file1_path: str, file2_path: str) -> Tuple[str, bool]: | ||
with open(file1_path) as f1: | ||
json1 = json.load(f1) | ||
with open(file2_path) as f2: | ||
json2 = json.load(f2) | ||
return get_edits_string( | ||
json.dumps(json1, indent=2, sort_keys=True), | ||
json.dumps(json2, indent=2, sort_keys=True), | ||
) | ||
|
||
|
||
def get_indent(level): | ||
return " " * level | ||
|
||
|
||
def print_object_hierarchy(obj, level): | ||
indent = get_indent(level) | ||
print(f"{indent}- {obj.name} ({obj.type})") | ||
|
||
for child in obj.children: | ||
print_object_hierarchy(child, level + 1) | ||
|
||
|
||
def print_collection_hierarchy(collection, level=0): | ||
indent = get_indent(level) | ||
print(f"{indent}{collection.name} (Collection):") | ||
|
||
for obj in collection.objects: | ||
if not obj.parent: | ||
print_object_hierarchy(obj, level + 1) | ||
|
||
for child_col in collection.children: | ||
print_collection_hierarchy(child_col, level + 1) | ||
|
||
|
||
bpy.ops.import_scene.iiif_manifest(filepath=input_manifest) | ||
|
||
print("\n\nPrinting scene hierarchy:") | ||
print_collection_hierarchy(bpy.context.scene.collection) | ||
print("\n\n") | ||
|
||
bpy.ops.export_scene.iiif_manifest(filepath=output_manifest) | ||
|
||
differences, has_changes = get_json_diff(input_manifest, output_manifest) | ||
|
||
# Delete output manifest | ||
safe_delete(output_manifest) | ||
|
||
if has_changes: | ||
print("Imported manifest differs from exported manifest:") | ||
print(differences) | ||
sys.exit(1) | ||
else: | ||
print("Imported manifest equals exported manifest") | ||
sys.exit(0) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
"@context": "http://iiif.io/api/presentation/4/context.json", | ||
"id": "https://example.org/iiif/3d/model_origin.json", | ||
"type": "Manifest", | ||
"label": { "en": ["Single Model"] }, | ||
"summary": { | ||
"en": [ | ||
"Viewer should render the model at the scene origin, and then viewer should add default lighting and camera" | ||
] | ||
}, | ||
"items": [ | ||
{ | ||
"id": "https://example.org/iiif/scene1/page/p1/1", | ||
"type": "Scene", | ||
"label": { "en": ["A Scene"] }, | ||
"items": [ | ||
{ | ||
"id": "https://example.org/iiif/scene1/page/p1/1", | ||
"type": "AnnotationPage", | ||
"items": [ | ||
{ | ||
"id": "https://example.org/iiif/3d/anno1", | ||
"type": "Annotation", | ||
"motivation": ["painting"], | ||
"body": { | ||
"id": "https://raw.githubusercontent.com/IIIF/3d/main/assets/astronaut/astronaut.glb", | ||
"type": "Model" | ||
}, | ||
"target": "https://example.org/iiif/scene1/page/p1/1" | ||
} | ||
] | ||
} | ||
] | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
{ | ||
"@context": "http://iiif.io/api/presentation/4/context.json", | ||
"id": "https://example.org/iiif/3d/model_origin.json", | ||
"type": "Manifest", | ||
"label": { "en": ["Model with Explicit Perspective Camera"] }, | ||
"summary": { "en": ["Viewer should render the model at the scene origin, and the camera at the scene origin facing -Z, then add default lighting"] }, | ||
"items": [ | ||
{ | ||
"id": "https://example.org/iiif/scene1/page/p1/1", | ||
"type": "Scene", | ||
"label": { "en": ["Scene with Model and Camera"] }, | ||
"items": [ | ||
{ | ||
"id": "https://example.org/iiif/scene1/page/p1/1", | ||
"type": "AnnotationPage", | ||
"items": [ | ||
{ | ||
"id": "https://example.org/iiif/3d/anno1", | ||
"type": "Annotation", | ||
"motivation": ["painting"], | ||
"body": { | ||
"id": "https://raw.githubusercontent.com/IIIF/3d/main/assets/astronaut/astronaut.glb", | ||
"type": "Model" | ||
}, | ||
"target": "https://example.org/iiif/scene1/page/p1/1" | ||
}, | ||
{ | ||
"id": "https://example.org/iiif/3d/anno1", | ||
"type": "Annotation", | ||
"motivation": ["painting"], | ||
"body": { | ||
"id": "https://example.org/iiif/3d/cameras/1", | ||
"type": "PerspectiveCamera", | ||
"label": {"en": ["Perspective Camera 1"]} | ||
}, | ||
"target": "https://example.org/iiif/scene1/page/p1/1" | ||
} | ||
] | ||
} | ||
] | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
#!/usr/bin/env bash | ||
|
||
FAILED_TESTS=0 | ||
|
||
run_test() { | ||
local command="$1" | ||
local expected_code="${2:-0}" | ||
|
||
eval "$command" > /dev/null 2>&1 | ||
local actual_code=$? | ||
|
||
if [ "$actual_code" -eq "$expected_code" ]; then | ||
echo "✅ Test passed: '$command' (Exit code: $actual_code)" | ||
return 0 | ||
else | ||
echo "❌ Test failed: '$command'" | ||
echo "ℹ️ Expected exit code: $expected_code" | ||
echo "ℹ️ Actual exit code: $actual_code" | ||
return 1 | ||
fi | ||
} | ||
|
||
for manifest in tests/iiif_manifests/*.json; do | ||
echo "ℹ️ Testing manifest: $manifest" | ||
if ! run_test "blender --background --python run_blender_with_plugin.py -- '$manifest'"; then | ||
((FAILED_TESTS++)) | ||
fi | ||
done | ||
|
||
if [ $FAILED_TESTS -gt 0 ]; then | ||
echo "❌ $FAILED_TESTS test(s) failed" | ||
exit 1 | ||
else | ||
echo "✅ All tests passed" | ||
exit 0 | ||
fi |