Files
freecad-robust-mcp-fc111/.github/workflows/macro-test.yaml
T
cd77560297 ci: Add CI and Release workflows and other test fixes (#7)
* ci: add caching and fix workflow failures

- Add UV package caching to all workflows for faster dependency installation
- Add pre-commit hook caching with OS-specific cache keys
- Skip no-commit-to-branch hook in CI (fails on main branch)
- Remove broken apt cache action (doesn't work with PPAs)
- Simplify FreeCAD command detection (PPA installs to standard PATH)
- Add FreeCAD Python version logging for debugging

* ci: Add code Rabbit configuration file

* ci: fix issues in CI workflows

* ci: add uv.lock

* ci: tweaks

* ci: Fix errors and add UUID generation and checking

* ci: Use the GitHub FreeCAD release latest stables

* ci: skip macro test for now, due to headless mode

* feat: Add a multi export macro

* ci: Fix tests

* ci: fix docker build workflow

* ci: skip trufflehog in GitHub Actions due to wasm panic bug

TruffleHog has a known wasm/go-re2 panic bug that causes failures
in GitHub Actions environment. The hook still runs locally during
development for secrets detection.

- Add trufflehog to SKIP env var in pre-commit.yaml
- Update trufflehog to v3.88.7 (latest)
- Add reference to upstream issue #3321

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test: use boolean holes instead of PartDesign::Hole in CI

PartDesign::Hole has a CADKernelError bug in FreeCAD AppImage headless
mode on Linux (used in GitHub Actions) where it fails with "Cannot make
face from profile". The SmartCutter class already supports boolean holes
as an alternative.

Changes:
- Modify SmartCutter.execute() to use boolean holes by default
- Update all test assertions for Part::Feature output type
- Update test docstrings and class descriptions
- Remove PartDesign-specific checks (Group, Sketcher::SketchObject)
- Update workflow comment explaining the CI limitation

Boolean holes work reliably in both GUI and headless mode across all
FreeCAD configurations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* ci: clean up GitHub Actions workflows

* ci: Dependabot improvements

* ci: add CodeQL scanning workflow

* ci: AI review suggested improvements

* ci: add coderabbit updates for intentional decisions

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 19:29:25 -08:00

226 lines
8.1 KiB
YAML

name: Macro Tests
on:
push:
branches: [main, master]
paths:
- "macros/**/*.FCMacro"
- "macros/**/*.py"
- "tests/integration/test_cut_object_for_magnets.py"
- "tests/integration/test_multi_export.py"
- ".github/workflows/macro-test.yaml"
pull_request:
branches: [main, master]
paths:
- "macros/**/*.FCMacro"
- "macros/**/*.py"
- "tests/integration/test_cut_object_for_magnets.py"
- "tests/integration/test_multi_export.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-macros:
name: Test FreeCAD Macros
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- 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: |
# Set up environment
export PYTHONPATH="${PWD}/src:${PYTHONPATH:-}"
echo "Using FreeCAD: freecadcmd"
# Start FreeCAD headless with MCP bridge in background
# Capture stdout to a file so we can extract the instance ID
freecadcmd src/freecad_mcp/freecad_plugin/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: 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@v4
- 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