* FreeCAD addon support for Workbench and Plugins * docs: refactor docs and clean up linters, etc. * Remove mdformat * test: improve test coverage * test:Lots of general fixes * chore: more general fixes
233 lines
8.3 KiB
YAML
233 lines
8.3 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"
|
|
|
|
# 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
|