Skip to content

Tutorial Tests Automation #27

Tutorial Tests Automation

Tutorial Tests Automation #27

name: Tutorial Tests Automation
on:
push:
branches: [ main, develop ]
paths:
- 'Tutorials/**'
- 'Scripts/**'
pull_request:
branches: [ main, develop ]
paths:
- 'Tutorials/**'
- 'Scripts/**'
schedule:
- cron: '0 3 * * *'
# Allow manual triggering
workflow_dispatch:
inputs:
tutorial_filter:
description: 'Tutorial ID to test (leave empty for all)'
required: false
type: string
languages:
description: 'Languages to test (space-separated)'
default: 'pt-BR es-419 fr-FR'
required: false
type: string
env:
SLICER_VERSION: '5.8.1'
DEFAULT_LANGUAGES: 'pt-BR es-419 fr-FR'
SCREEN_RESOLUTION: '1920x1080'
jobs:
detect-tutorials:
name: Detect Tutorials
runs-on: ubuntu-latest
outputs:
tutorials: ${{ steps.scan.outputs.tutorials }}
tutorial-count: ${{ steps.scan.outputs.tutorial-count }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Scan for tutorials with IDs
id: scan
run: |
echo "Scanning for tutorials with valid IDs..."
tutorials_json="["
tutorial_count=0
for tutorial_dir in Tutorials/*/; do
if [[ -d "$tutorial_dir" ]]; then
tutorial_name=$(basename "$tutorial_dir")
echo "Checking tutorial: $tutorial_name"
# Extract ID from tutorial name (everything before first underscore)
if [[ "$tutorial_name" =~ ^([^_]+)_ ]]; then
tutorial_id="${BASH_REMATCH[1]}"
# Check if this tutorial should be filtered
if [[ -n "${{ github.event.inputs.tutorial_filter }}" ]]; then
if [[ "$tutorial_id" != "${{ github.event.inputs.tutorial_filter }}" ]]; then
echo " Skipping $tutorial_id (filtered out)"
continue
fi
fi
echo " Found valid tutorial: $tutorial_id -> $tutorial_name"
if [[ $tutorial_count -gt 0 ]]; then
tutorials_json="$tutorials_json,"
fi
tutorials_json="$tutorials_json{\"id\":\"$tutorial_id\",\"name\":\"$tutorial_name\",\"path\":\"$tutorial_dir\"}"
tutorial_count=$((tutorial_count + 1))
else
echo " Skipping $tutorial_name (no valid ID pattern)"
fi
fi
done
tutorials_json="$tutorials_json]"
echo "Found $tutorial_count tutorials with valid IDs"
echo "tutorials=$tutorials_json" >> $GITHUB_OUTPUT
echo "tutorial-count=$tutorial_count" >> $GITHUB_OUTPUT
echo "Tutorials JSON: $tutorials_json"
test-tutorials:
name: Test Tutorial
runs-on: ubuntu-latest
needs: detect-tutorials
if: needs.detect-tutorials.outputs.tutorial-count > 0
container:
image: ubuntu:22.04
options: --shm-size=2g
strategy:
fail-fast: false
matrix:
tutorial: ${{ fromJson(needs.detect-tutorials.outputs.tutorials) }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install system dependencies
run: |
apt-get update -qq
apt-get install -y \
wget curl unzip \
xvfb x11vnc \
python3 python3-pip \
libgl1-mesa-glx \
libxrender1 libxext6 libsm6 libice6 \
libfontconfig1 libxss1 libasound2 \
libglib2.0-0 libxrandr2 libxcomposite1 \
libxdamage1 libxcursor1 libxi6 libxtst6 \
libglu1-mesa \
ca-certificates \
git
- name: Setup Python
run: |
python3 -m pip install --upgrade pip
python3 -m pip install requests
- name: Download and install Slicer
run: |
echo "Downloading Slicer ${{ env.SLICER_VERSION }}..."
mkdir -p /opt/slicer
cd /opt/slicer
# Download Slicer 5.8.1
wget -O slicer.tar.gz "https://download.slicer.org/bitstream/66ec4de29b1e4f4dbb050b0a"
# Extract
tar -xzf slicer.tar.gz --strip-components=1
rm slicer.tar.gz
# Make executable
chmod +x ./Slicer
# Verify installation
ls -la /opt/slicer/
echo "Slicer installation completed"
- name: Setup virtual display
run: |
echo "Setting up virtual display ${{ env.SCREEN_RESOLUTION }}..."
export DISPLAY=:99
# Start Xvfb with specified resolution
Xvfb :99 -screen 0 ${{ env.SCREEN_RESOLUTION }}x24 > /dev/null 2>&1 &
sleep 3
# Verify display
echo "DISPLAY=$DISPLAY" >> $GITHUB_ENV
- name: Install required Slicer extensions
run: |
echo "Installing required Slicer extensions..."
export DISPLAY=:99
# Run the extension installation script
/opt/slicer/Slicer --no-splash --python-script Scripts/install-slicer-extensions.py
echo "Slicer extensions installation completed"
- name: Setup TutorialMaker JSON files
run: |
echo "Setting up TutorialMaker JSON files for tutorial: ${{ matrix.tutorial.name }}"
# Find TutorialMaker extension directory in Slicer
TUTORIALMAKER_DIR=""
for ext_dir in /opt/slicer/lib/Slicer-*/qt-scripted-modules/TutorialMaker*; do
if [[ -d "$ext_dir" ]]; then
TUTORIALMAKER_DIR="$ext_dir"
break
fi
done
# If not found in scripted modules, check other common locations
if [[ -z "$TUTORIALMAKER_DIR" ]]; then
for ext_dir in /opt/slicer/lib/Slicer-*/extensions-*/TutorialMaker*; do
if [[ -d "$ext_dir" ]]; then
TUTORIALMAKER_DIR="$ext_dir"
break
fi
done
fi
if [[ -z "$TUTORIALMAKER_DIR" ]]; then
echo "Warning: TutorialMaker extension directory not found. Creating fallback location..."
TUTORIALMAKER_DIR="/opt/slicer/lib/Slicer-5.8/qt-scripted-modules/TutorialMaker"
mkdir -p "$TUTORIALMAKER_DIR"
fi
echo "TutorialMaker directory: $TUTORIALMAKER_DIR"
# Create Outputs/Annotations directory
ANNOTATIONS_DIR="$TUTORIALMAKER_DIR/Outputs/Annotations"
mkdir -p "$ANNOTATIONS_DIR"
echo "Created annotations directory: $ANNOTATIONS_DIR"
# Copy tutorial JSON file as annotations.json
TUTORIAL_JSON="Tutorials/${{ matrix.tutorial.name }}/${{ matrix.tutorial.name }}.json"
if [[ -f "$TUTORIAL_JSON" ]]; then
echo "Copying $TUTORIAL_JSON to $ANNOTATIONS_DIR/annotations.json"
cp "$TUTORIAL_JSON" "$ANNOTATIONS_DIR/annotations.json"
else
# Try alternative naming pattern (ID.json)
TUTORIAL_JSON_ALT="Tutorials/${{ matrix.tutorial.name }}/${{ matrix.tutorial.id }}.json"
if [[ -f "$TUTORIAL_JSON_ALT" ]]; then
echo "Copying $TUTORIAL_JSON_ALT to $ANNOTATIONS_DIR/annotations.json"
cp "$TUTORIAL_JSON_ALT" "$ANNOTATIONS_DIR/annotations.json"
else
echo "Warning: Tutorial JSON file not found at $TUTORIAL_JSON or $TUTORIAL_JSON_ALT"
fi
fi
# Store variables for later use
echo "TUTORIALMAKER_DIR=$TUTORIALMAKER_DIR" >> $GITHUB_ENV
echo "ANNOTATIONS_DIR=$ANNOTATIONS_DIR" >> $GITHUB_ENV
- name: Prepare tutorial test environment
run: |
echo "Preparing test environment for tutorial: ${{ matrix.tutorial.name }}"
# Create Results directory in tutorial folder
tutorial_results_dir="Tutorials/${{ matrix.tutorial.name }}/Results"
mkdir -p "$tutorial_results_dir"
echo "Created results directory: $tutorial_results_dir"
# Set languages to test
if [[ -n "${{ github.event.inputs.languages }}" ]]; then
test_languages="${{ github.event.inputs.languages }}"
else
test_languages="${{ env.DEFAULT_LANGUAGES }}"
fi
echo "TEST_LANGUAGES=$test_languages" >> $GITHUB_ENV
echo "TUTORIAL_RESULTS_DIR=$tutorial_results_dir" >> $GITHUB_ENV
echo "Will test languages: $test_languages"
# Prepare language-specific JSON files for TutorialMaker
echo "Preparing language-specific translation files..."
IFS=' ' read -ra LANG_ARRAY <<< "$test_languages"
for language in "${LANG_ARRAY[@]}"; do
echo "Setting up files for language: $language"
# Path to language-specific text_dict_default.json
LANG_DICT="Tutorials/${{ matrix.tutorial.name }}/Translations/$language/text_dict_default.json"
if [[ -f "$LANG_DICT" ]]; then
echo "Found translation file: $LANG_DICT"
# Copy it to the annotations directory with language suffix for later use
cp "$LANG_DICT" "$ANNOTATIONS_DIR/text_dict_default_${language}.json"
echo "Copied $LANG_DICT to $ANNOTATIONS_DIR/text_dict_default_${language}.json"
else
echo "Warning: Translation file not found: $LANG_DICT"
# Create empty translation file as fallback
echo "{}" > "$ANNOTATIONS_DIR/text_dict_default_${language}.json"
echo "Created empty translation file for $language"
fi
done
- name: Run tutorial tests
run: |
echo "Running tests for tutorial: ${{ matrix.tutorial.name }} (ID: ${{ matrix.tutorial.id }})"
echo "Languages: $TEST_LANGUAGES"
echo "Results directory: $TUTORIAL_RESULTS_DIR"
export DISPLAY=:99
# Change to repository root
cd "$GITHUB_WORKSPACE"
# Run the test script
python3 Scripts/run_tutorial_tests_ci.py \
/opt/slicer/Slicer \
--tutorial "${{ matrix.tutorial.id }}" \
--languages $TEST_LANGUAGES \
--output "$TUTORIAL_RESULTS_DIR" \
--timeout 300
echo "Tutorial tests completed"
- name: Copy generated tutorial outputs
if: always()
run: |
echo "Copying generated tutorial outputs..."
# Check if TutorialMaker outputs exist
if [[ -n "$TUTORIALMAKER_DIR" && -d "$TUTORIALMAKER_DIR/Outputs" ]]; then
echo "Found TutorialMaker outputs directory: $TUTORIALMAKER_DIR/Outputs"
# Process each language
IFS=' ' read -ra LANG_ARRAY <<< "$TEST_LANGUAGES"
for language in "${LANG_ARRAY[@]}"; do
echo "Processing outputs for language: $language"
# Look for generated output directory with language suffix
output_pattern="$TUTORIALMAKER_DIR/Outputs/${{ matrix.tutorial.name }}_${language}"
if [[ -d "$output_pattern" ]]; then
echo "Found output directory: $output_pattern"
# Create target directory structure
target_dir="Tutorials/${{ matrix.tutorial.name }}/Translations/$language"
mkdir -p "$target_dir"
# Copy all contents from output directory to translations directory
echo "Copying from $output_pattern to $target_dir/"
cp -r "$output_pattern"/* "$target_dir/" 2>/dev/null || echo "No files to copy or copy failed"
# List what was copied
if [[ -d "$target_dir" ]]; then
echo "Files copied to $target_dir:"
ls -la "$target_dir/" || echo "Directory empty or listing failed"
fi
else
echo "Warning: No output directory found for pattern: $output_pattern"
# Try alternative patterns
for alt_pattern in "$TUTORIALMAKER_DIR/Outputs/${{ matrix.tutorial.id }}_${language}" "$TUTORIALMAKER_DIR/Outputs/*_${language}"; do
if [[ -d $alt_pattern ]]; then
echo "Found alternative output directory: $alt_pattern"
target_dir="Tutorials/${{ matrix.tutorial.name }}/Translations/$language"
mkdir -p "$target_dir"
cp -r $alt_pattern/* "$target_dir/" 2>/dev/null || echo "Copy failed"
break
fi
done
fi
done
# Also list all output directories for debugging
echo "All output directories found:"
find "$TUTORIALMAKER_DIR/Outputs" -maxdepth 1 -type d -name "*_*" 2>/dev/null || echo "No pattern directories found"
else
echo "Warning: TutorialMaker outputs directory not found or TUTORIALMAKER_DIR not set"
echo "TUTORIALMAKER_DIR: $TUTORIALMAKER_DIR"
# Try to find it in common locations
for possible_dir in /opt/slicer/lib/Slicer-*/qt-scripted-modules/TutorialMaker /opt/slicer/lib/Slicer-*/extensions-*/TutorialMaker*; do
if [[ -d "$possible_dir/Outputs" ]]; then
echo "Found TutorialMaker outputs in: $possible_dir/Outputs"
ls -la "$possible_dir/Outputs/" || echo "Listing failed"
fi
done
fi
- name: Process test results
if: always()
run: |
echo "Processing test results..."
# Check if results exist
if [[ -f "$TUTORIAL_RESULTS_DIR/test_report.json" ]]; then
echo "✅ Test report found"
# Show basic results using a simple approach
echo "Tutorial: ${{ matrix.tutorial.name }}"
cat "$TUTORIAL_RESULTS_DIR/test_report.json" | grep -E '"total_tests"|"successful_tests"|"failed_tests"|"success_rate"' || echo "Results available"
else
echo "❌ No test report found"
ls -la "$TUTORIAL_RESULTS_DIR" || echo "Results directory not found"
fi
- name: Commit test results
if: always()
run: |
# Configure git
git config --local user.email "[email protected]"
git config --local user.name "GitHub Actions Tutorial Tests"
# Add results and translations to git
git add "Tutorials/${{ matrix.tutorial.name }}/Results/"
git add "Tutorials/${{ matrix.tutorial.name }}/Translations/"
# Check if there are any changes to commit
if git diff --staged --quiet; then
echo "No new results to commit"
else
# Create commit message
COMMIT_MSG="Update test results and translations for tutorial ${{ matrix.tutorial.name }}"
# Add details about what was updated
if [[ -d "Tutorials/${{ matrix.tutorial.name }}/Results" ]]; then
COMMIT_MSG="$COMMIT_MSG - Test results updated"
fi
if [[ -d "Tutorials/${{ matrix.tutorial.name }}/Translations" ]]; then
COMMIT_MSG="$COMMIT_MSG - Generated translations for languages: $TEST_LANGUAGES"
fi
COMMIT_MSG="$COMMIT_MSG - Auto-generated by GitHub Actions"
git commit -m "$COMMIT_MSG"
git push
echo "Test results committed and pushed"
fi
- name: Upload test artifacts
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.tutorial.id }}
path: |
Tutorials/${{ matrix.tutorial.name }}/Results/
Tutorials/${{ matrix.tutorial.name }}/Translations/
retention-days: 30
- name: Check test success
if: always()
run: |
if [[ -f "$TUTORIAL_RESULTS_DIR/test_report.json" ]]; then
# Extract success rate using grep and basic text processing
SUCCESS_RATE=$(grep '"success_rate"' "$TUTORIAL_RESULTS_DIR/test_report.json" | sed 's/.*: *\([0-9.]*\).*/\1/' || echo "0")
echo "Tutorial ${{ matrix.tutorial.name }} success rate: $SUCCESS_RATE%"
# Consider test successful if success rate >= 75%
if (( $(echo "$SUCCESS_RATE >= 75.0" | bc -l 2>/dev/null) )); then
echo "✅ Tutorial tests passed"
else
echo "❌ Tutorial tests failed - success rate too low"
exit 1
fi
else
echo "❌ No test results available"
exit 1
fi