Files
freecad-robust-mcp-fc111/.github/workflows/macro-test.yaml
T
Sean P. KaneandGitHub a775852ce9 chrore: major overhaul of docs, commands, and workflows (#23)
* ci: add status badges to README

* chore: major overhaul - docs, commands & workflows

* fix: general fixes and improvements

* chore: various updates and fixes

* chore: minor fixes
2026-01-07 09:44:45 -08:00

235 lines
8.4 KiB
YAML

name: Integration Tests
on:
push:
branches: [main, master]
paths:
- "src/freecad_mcp/**/*.py"
- "macros/**/*.FCMacro"
- "macros/**/*.py"
- "addon/FreecadRobustMCP/**/*.py"
- "tests/integration/**/*.py"
- ".github/workflows/macro-test.yaml"
pull_request:
branches: [main, master]
paths:
- "src/freecad_mcp/**/*.py"
- "macros/**/*.FCMacro"
- "macros/**/*.py"
- "addon/FreecadRobustMCP/**/*.py"
- "tests/integration/**/*.py"
- ".github/workflows/macro-test.yaml"
workflow_dispatch:
workflow_call:
# Cancel in-progress runs for the same branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test-integration:
name: Integration Tests with FreeCAD
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install mise
uses: jdx/mise-action@v3
- name: Get latest FreeCAD release info
id: freecad-release
run: |
# Get the latest stable release tag from GitHub API
RELEASE_INFO=$(curl -s https://api.github.com/repos/FreeCAD/FreeCAD/releases/latest)
TAG_NAME=$(echo "$RELEASE_INFO" | jq -r '.tag_name')
echo "Latest FreeCAD release: $TAG_NAME"
echo "tag=$TAG_NAME" >> "$GITHUB_OUTPUT"
# Find the Linux AppImage asset URL
APPIMAGE_URL=$(echo "$RELEASE_INFO" | jq -r '.assets[] | select(.name | test("Linux-x86_64.*\\.AppImage$")) | .browser_download_url' | head -1)
APPIMAGE_NAME=$(echo "$RELEASE_INFO" | jq -r '.assets[] | select(.name | test("Linux-x86_64.*\\.AppImage$")) | .name' | head -1)
if [ -z "$APPIMAGE_URL" ] || [ "$APPIMAGE_URL" = "null" ]; then
echo "ERROR: Could not find Linux AppImage in release assets"
exit 1
fi
echo "AppImage URL: $APPIMAGE_URL"
echo "AppImage name: $APPIMAGE_NAME"
echo "url=$APPIMAGE_URL" >> "$GITHUB_OUTPUT"
echo "name=$APPIMAGE_NAME" >> "$GITHUB_OUTPUT"
- name: Cache FreeCAD AppImage
id: cache-freecad
uses: actions/cache@v5
with:
path: ~/freecad-appimage
key: freecad-appimage-${{ steps.freecad-release.outputs.tag }}
- name: Download FreeCAD AppImage
if: steps.cache-freecad.outputs.cache-hit != 'true'
run: |
mkdir -p ~/freecad-appimage
echo "Downloading FreeCAD ${{ steps.freecad-release.outputs.tag }}..."
curl -L -o ~/freecad-appimage/FreeCAD.AppImage "${{ steps.freecad-release.outputs.url }}"
chmod +x ~/freecad-appimage/FreeCAD.AppImage
- name: Setup FreeCAD AppImage
run: |
# Make AppImage executable (in case restored from cache)
chmod +x ~/freecad-appimage/FreeCAD.AppImage
# Extract AppImage for headless use (AppImages need FUSE which isn't available in CI)
cd ~/freecad-appimage
./FreeCAD.AppImage --appimage-extract > /dev/null 2>&1
# Create symlinks for easy access
sudo ln -sf ~/freecad-appimage/squashfs-root/usr/bin/freecadcmd /usr/local/bin/freecadcmd
sudo ln -sf ~/freecad-appimage/squashfs-root/usr/bin/freecad /usr/local/bin/freecad
- name: Verify FreeCAD installation
run: |
echo "Checking FreeCAD installation..."
freecadcmd --version || freecad --version || echo "Version check failed"
which freecadcmd
# Show Python version bundled with FreeCAD
freecadcmd -c "import sys; print(f'FreeCAD Python: {sys.version}')" || true
- name: Cache uv dependencies
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: "**/uv.lock"
- name: Install dependencies
run: uv sync --all-extras
- name: Validate macro syntax
run: |
echo "Validating macro Python syntax..."
for macro in macros/**/*.FCMacro; do
echo "Checking: $macro"
python3 -m py_compile "$macro" || {
echo "ERROR: Syntax error in $macro"
exit 1
}
done
echo "All macros have valid Python syntax"
- name: Start FreeCAD headless with MCP bridge
run: |
echo "Using FreeCAD: freecadcmd"
# Start FreeCAD headless with MCP bridge in background
# Uses the workbench addon's headless server script
freecadcmd addon/FreecadRobustMCP/freecad_mcp_bridge/headless_server.py > /tmp/freecad_bridge.log 2>&1 &
FREECAD_PID=$!
echo "FREECAD_PID=$FREECAD_PID" >> "$GITHUB_ENV"
# Wait for bridge to be ready (check XML-RPC port)
echo "Waiting for MCP bridge to start..."
for i in {1..60}; do
if curl -s --max-time 2 -X POST \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName></methodCall>' \
http://localhost:9875 > /dev/null 2>&1; then
echo "MCP bridge is ready (took ${i}s)"
break
fi
if [ "$i" -eq 60 ]; then
echo "ERROR: MCP bridge did not start within 60s"
echo "=== Bridge log ==="
cat /tmp/freecad_bridge.log || true
kill "$FREECAD_PID" 2>/dev/null || true
exit 1
fi
sleep 1
done
# Log the instance ID for debugging (pytest tests verify bridge via conftest.py fixtures)
BRIDGE_INSTANCE_ID=$(grep -o 'FREECAD_MCP_BRIDGE_INSTANCE_ID=[^ ]*' /tmp/freecad_bridge.log | cut -d= -f2 | head -1)
if [ -n "$BRIDGE_INSTANCE_ID" ]; then
echo "Bridge Instance ID: $BRIDGE_INSTANCE_ID"
else
echo "Warning: Could not extract bridge instance ID from log"
cat /tmp/freecad_bridge.log || true
fi
- name: Run CutObjectForMagnets macro tests
env:
FREECAD_MODE: xmlrpc
# Note: Tests use boolean holes (not PartDesign::Hole) for CI compatibility.
# PartDesign::Hole has a CADKernelError bug in some headless AppImage environments.
run: |
uv run pytest tests/integration/test_cut_object_for_magnets.py -v --tb=short
- name: Run MultiExport macro tests
env:
FREECAD_MODE: xmlrpc
# Export functionality should work in headless mode
continue-on-error: true
run: |
uv run pytest tests/integration/test_multi_export.py -v --tb=short
- name: Run headless mode integration tests
env:
FREECAD_MODE: xmlrpc
# Tests for headless FreeCAD operations (primitives, booleans, exports, etc.)
run: |
uv run pytest tests/integration/test_headless_mode.py -v --tb=short
- name: Stop FreeCAD
if: always()
run: |
if [ -n "$FREECAD_PID" ]; then
kill "$FREECAD_PID" 2>/dev/null || true
fi
lint-macros:
name: Lint Macro Files
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install mise
uses: jdx/mise-action@v3
- name: Cache uv dependencies
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: "**/uv.lock"
- name: Install dependencies
run: uv sync --all-extras
- name: Lint macro files with Ruff
run: |
# Run ruff on macro files (check only, don't fail on issues specific to FreeCAD macros)
uv run ruff check macros/ --select=E,F,W --ignore=F401,E402 || true
- name: Check macro documentation
run: |
# Ensure each macro directory has a README
for macro_dir in macros/*/; do
if [ -d "$macro_dir" ]; then
readme_found=false
for readme in "$macro_dir"README*.md; do
if [ -f "$readme" ]; then
readme_found=true
break
fi
done
if [ "$readme_found" = false ]; then
echo "WARNING: No README found in $macro_dir"
else
echo "OK: README found in $macro_dir"
fi
fi
done