From 3f20c94b59b1267423cd83b4efe38d681eaaf4ac Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Wed, 14 Jan 2026 22:46:51 -0500 Subject: [PATCH 1/3] Refactor workflow pipeline and add makefile --- .actrc | 3 + .github/actions/setup-ci-tools/action.yml | 58 ++ .github/actions/setup-python-env/action.yml | 32 + .../actions/setup-rendering-deps/action.yml | 55 ++ .github/workflows/notebook-pr.yaml | 76 ++- .github/workflows/publish-book.yml | 63 +- Makefile | 144 +++++ TESTING_LOCALLY.md | 594 ++++++++++++++++++ 8 files changed, 944 insertions(+), 81 deletions(-) create mode 100644 .actrc create mode 100644 .github/actions/setup-ci-tools/action.yml create mode 100644 .github/actions/setup-python-env/action.yml create mode 100644 .github/actions/setup-rendering-deps/action.yml create mode 100644 Makefile create mode 100644 TESTING_LOCALLY.md diff --git a/.actrc b/.actrc new file mode 100644 index 000000000..75491be3d --- /dev/null +++ b/.actrc @@ -0,0 +1,3 @@ +-P ubuntu-latest=catthehacker/ubuntu:act-latest +--container-architecture linux/amd64 +--artifact-server-path /tmp/artifacts diff --git a/.github/actions/setup-ci-tools/action.yml b/.github/actions/setup-ci-tools/action.yml new file mode 100644 index 000000000..7c7a3387a --- /dev/null +++ b/.github/actions/setup-ci-tools/action.yml @@ -0,0 +1,58 @@ +name: 'Setup nmaci CI Tools' +description: 'Download and install Neuromatch CI tools' + +inputs: + branch: + description: 'nmaci branch to use' + required: false + default: 'main' + commit-message: + description: 'Commit message to parse for branch override' + required: false + default: '' + +outputs: + nmaci-branch: + description: 'nmaci branch used' + value: ${{ steps.detect-branch.outputs.branch }} + +runs: + using: 'composite' + steps: + - name: Detect nmaci branch + id: detect-branch + shell: bash + run: | + BRANCH="${{ inputs.branch }}" + if [ -n "${{ inputs.commit-message }}" ]; then + OVERRIDE=$(python3 -c "import os, re; m = re.search(r'nmaci:([\w-]+)', '${{ inputs.commit-message }}'); print(m.group(1) if m else '')") + if [ -n "$OVERRIDE" ]; then + BRANCH="$OVERRIDE" + fi + fi + echo "branch=$BRANCH" >> $GITHUB_OUTPUT + echo "Using nmaci branch: $BRANCH" + + - name: Cache nmaci tools + id: cache-nmaci + uses: actions/cache@v3 + with: + path: ci/ + key: nmaci-${{ steps.detect-branch.outputs.branch }}-${{ hashFiles('.github/workflows/*.yml', '.github/workflows/*.yaml') }} + restore-keys: nmaci-${{ steps.detect-branch.outputs.branch }}- + + - name: Download nmaci tools + if: steps.cache-nmaci.outputs.cache-hit != 'true' + shell: bash + run: | + BRANCH=${{ steps.detect-branch.outputs.branch }} + wget -q https://github.com/neuromatch/nmaci/archive/refs/heads/$BRANCH.tar.gz + tar -xzf $BRANCH.tar.gz + pip install --upgrade pip + pip install -r nmaci-$BRANCH/requirements.txt + mv nmaci-$BRANCH/scripts/ ci/ + rm -r nmaci-$BRANCH $BRANCH.tar.gz + + - name: Ignore ci directory + shell: bash + run: echo "ci/" >> .gitignore diff --git a/.github/actions/setup-python-env/action.yml b/.github/actions/setup-python-env/action.yml new file mode 100644 index 000000000..453780fdf --- /dev/null +++ b/.github/actions/setup-python-env/action.yml @@ -0,0 +1,32 @@ +name: 'Setup Python Environment' +description: 'Set up Python with caching and install base dependencies' + +inputs: + python-version: + description: 'Python version to install' + required: false + default: '3.9' + +outputs: + cache-hit: + description: 'Whether pip cache was hit' + value: ${{ steps.setup-python.outputs.cache-hit }} + +runs: + using: 'composite' + steps: + - name: Set up Python + id: setup-python + uses: actions/setup-python@v4 + with: + python-version: ${{ inputs.python-version }} + cache: 'pip' + cache-dependency-path: 'requirements.txt' + + - name: Upgrade pip + shell: bash + run: pip install --upgrade pip wheel + + - name: Install requirements + shell: bash + run: pip install -r requirements.txt diff --git a/.github/actions/setup-rendering-deps/action.yml b/.github/actions/setup-rendering-deps/action.yml new file mode 100644 index 000000000..0c92f0291 --- /dev/null +++ b/.github/actions/setup-rendering-deps/action.yml @@ -0,0 +1,55 @@ +name: 'Setup Rendering Dependencies' +description: 'Install fonts, backend libraries, and graphviz for notebook rendering' + +inputs: + skip-fonts: + description: 'Skip XKCD font installation' + required: false + default: 'false' + skip-backend: + description: 'Skip backend libraries' + required: false + default: 'false' + skip-graphviz: + description: 'Skip graphviz installation' + required: false + default: 'false' + +runs: + using: 'composite' + steps: + - name: Cache APT packages + id: cache-apt + uses: actions/cache@v3 + with: + path: | + /var/cache/apt/archives + /usr/share/fonts/truetype/humor-sans + key: apt-packages-${{ runner.os }}-fonts-backend-graphviz-v2 + restore-keys: apt-packages-${{ runner.os }}- + + - name: Install XKCD fonts + if: ${{ inputs.skip-fonts != 'true' }} + shell: bash + run: | + if [ ! -f /usr/share/fonts/truetype/humor-sans/Humor-Sans.ttf ]; then + sudo apt-get update -yq + wget -q http://archive.ubuntu.com/ubuntu/pool/universe/f/fonts-humor-sans/fonts-humor-sans_1.0-4_all.deb + sudo dpkg -i --force-all fonts-humor-sans_1.0-4_all.deb <<< 'yes' 2>/dev/null || true + sudo apt install -fy + rm -f fonts-humor-sans_1.0-4_all.deb $HOME/.matplotlib/fontList.cache + else + echo "XKCD fonts already cached" + fi + + - name: Install backend libraries + if: ${{ inputs.skip-backend != 'true' }} + shell: bash + run: | + sudo apt-get update -yq || true + sudo apt-get install -y libglew-dev libglfw3 ffmpeg + echo "MUJOCO_GL=egl" >> $GITHUB_ENV + + - name: Install Graphviz + if: ${{ inputs.skip-graphviz != 'true' }} + uses: tlylt/install-graphviz@v1 diff --git a/.github/workflows/notebook-pr.yaml b/.github/workflows/notebook-pr.yaml index 95418dc4d..f0dacf394 100644 --- a/.github/workflows/notebook-pr.yaml +++ b/.github/workflows/notebook-pr.yaml @@ -27,53 +27,25 @@ jobs: readonly local msg=$(git log -1 --pretty=format:"%s") echo "COMMIT_MESSAGE=$msg" >> $GITHUB_ENV - - name: Set up Python + - name: Setup Python environment if: "!contains(env.COMMIT_MESSAGE, 'skip ci')" - uses: actions/setup-python@v4 - with: - python-version: 3.9 + uses: ./.github/actions/setup-python-env - - name: Install CI tools - if: "!contains(env.COMMIT_MESSAGE, 'skip ci')" - run: | - BRANCH=`python -c 'import os, re; m = re.search(r"nmaci:([\w-]+)", os.environ["COMMIT_MESSAGE"]); print("main" if m is None else m.group(1))'` - # NOTE: might enventually change this back if we integrate changes - wget https://github.com/neuromatch/nmaci/archive/refs/heads/$BRANCH.tar.gz - tar -xzf $BRANCH.tar.gz - pip install --upgrade pip - pip install -r nmaci-$BRANCH/requirements.txt - mv nmaci-$BRANCH/scripts/ ci/ - rm -r nmaci-$BRANCH - echo ci/ >> .gitignore - - - name: Install dependencies + - name: Install additional dependencies if: "!contains(env.COMMIT_MESSAGE, 'skip ci')" run: | - python -m pip install --upgrade pip wheel - pip install -r requirements.txt - pip install jupyter_client==7.3.5 # downgrade jupyter-client to fix hangs - pip install jinja2==3.1.2 # to fix pandas dep + pip install jupyter_client==7.3.5 + pip install jinja2==3.1.2 - - name: Install XKCD fonts + - name: Setup CI tools if: "!contains(env.COMMIT_MESSAGE, 'skip ci')" - run: | - sudo apt-get update -yq - wget http://archive.ubuntu.com/ubuntu/pool/universe/f/fonts-humor-sans/fonts-humor-sans_1.0-4_all.deb - sudo dpkg -i --force-all fonts-humor-sans_1.0-4_all.deb <<< 'yes' - sudo apt install -fy - rm -f $HOME/.matplotlib/fontList.cache + uses: ./.github/actions/setup-ci-tools + with: + commit-message: ${{ env.COMMIT_MESSAGE }} - - name: Install backend libs + - name: Setup rendering dependencies if: "!contains(env.COMMIT_MESSAGE, 'skip ci')" - run: | - # required for macrocircuits project - sudo apt-get install -y libglew-dev - sudo apt-get install -y libglfw3 - sudo apt install -y ffmpeg - echo "MUJOCO_GL=egl" >> $GITHUB_ENV - - - name: Install Graphviz - uses: tlylt/install-graphviz@v1 + uses: ./.github/actions/setup-rendering-deps - name: Get changed files if: "!contains(env.COMMIT_MESSAGE, 'skip ci')" @@ -86,8 +58,20 @@ jobs: echo "$file was changed." done - - name: Process notebooks + - name: Check for notebook changes + id: notebook-check if: "!contains(env.COMMIT_MESSAGE, 'skip ci')" + run: | + CHANGED="${{ steps.changed-files.outputs.all_changed_files }}" + if [[ ! "$CHANGED" =~ \.ipynb ]]; then + echo "has_notebooks=false" >> $GITHUB_OUTPUT + echo "No notebooks changed, skipping processing" + else + echo "has_notebooks=true" >> $GITHUB_OUTPUT + fi + + - name: Process notebooks + if: "!contains(env.COMMIT_MESSAGE, 'skip ci') && steps.notebook-check.outputs.has_notebooks == 'true'" id: process_notebooks run: | branch=${{ github.event.pull_request.head.ref }} @@ -109,8 +93,18 @@ jobs: # with: # path: comment.txt - - name: Update READMEs + - name: Check if materials.yml changed + id: materials-check if: "!contains(env.COMMIT_MESSAGE, 'skip ci')" + run: | + if [[ "${{ steps.changed-files.outputs.all_changed_files }}" =~ materials\.yml ]]; then + echo "materials_changed=true" >> $GITHUB_OUTPUT + else + echo "materials_changed=false" >> $GITHUB_OUTPUT + fi + + - name: Update READMEs + if: "!contains(env.COMMIT_MESSAGE, 'skip ci') && steps.materials-check.outputs.materials_changed == 'true'" run: python ci/generate_tutorial_readmes.py - name: Remove unreferenced derivatives diff --git a/.github/workflows/publish-book.yml b/.github/workflows/publish-book.yml index 1feb55887..28aa98c1a 100644 --- a/.github/workflows/publish-book.yml +++ b/.github/workflows/publish-book.yml @@ -29,50 +29,20 @@ jobs: readonly local msg=$(git log -1 --pretty=format:"%s") echo "COMMIT_MESSAGE=$msg" >> $GITHUB_ENV - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: 3.9 + - name: Setup Python environment + uses: ./.github/actions/setup-python-env - - name: Install CI tools - run: | - BRANCH=`python -c 'import os, re; m = re.search(r"nmaci:([\w-]+)", os.environ["COMMIT_MESSAGE"]); print("main" if m is None else m.group(1))'` - # NOTE: might enventually change this back if we integrate changes - wget https://github.com/$ORG/nmaci/archive/refs/heads/$BRANCH.tar.gz - tar -xzf $BRANCH.tar.gz - pip install --upgrade pip - pip install -r nmaci-$BRANCH/requirements.txt - mv nmaci-$BRANCH/scripts/ ci/ - rm -r nmaci-$BRANCH - rm -r $BRANCH.tar.gz - echo ci/ >> .gitignore + - name: Install Jupyter Book + run: pip install jupyter-book==0.14.0 ghp-import - - name: Install dependencies - run: | - pip install -r requirements.txt - pip install jupyter-book==0.14.0 ghp-import - # pip install jupyter_client==7.3.5 # downgrade jupyter-client to fix hangs + - name: Setup CI tools + uses: ./.github/actions/setup-ci-tools + with: + commit-message: ${{ env.COMMIT_MESSAGE }} - - name: Install XKCD fonts - if: "!contains(env.COMMIT_MESSAGE, 'skip ci')" - run: | - sudo apt-get update -yq - wget http://archive.ubuntu.com/ubuntu/pool/universe/f/fonts-humor-sans/fonts-humor-sans_1.0-4_all.deb - sudo dpkg -i --force-all fonts-humor-sans_1.0-4_all.deb <<< 'yes' - sudo apt install -fy - rm -f $HOME/.matplotlib/fontList.cache - - - name: Install backend libs + - name: Setup rendering dependencies if: "!contains(env.COMMIT_MESSAGE, 'skip ci')" - run: | - # required for macrocircuits project - sudo apt-get install -y libglew-dev - sudo apt-get install -y libglfw3 - sudo apt install -y ffmpeg - echo "MUJOCO_GL=egl" >> $GITHUB_ENV - - - name: Install Graphviz - uses: tlylt/install-graphviz@v1 + uses: ./.github/actions/setup-rendering-deps # - name: Copy tutorials from precourse repo # if: "!contains(env.COMMIT_MESSAGE, 'skip precourse')" @@ -87,6 +57,19 @@ jobs: # rm -r precourse-$BRANCH # rm -r $BRANCH.tar.gz + - name: Get date for cache rotation + id: cache-date + run: echo "date=$(date +'%Y-%m')" >> $GITHUB_OUTPUT + + - name: Cache Jupyter execution + uses: actions/cache@v3 + with: + path: book/.jupyter-cache + key: jupyter-exec-${{ steps.cache-date.outputs.date }}-${{ hashFiles('tutorials/**/*.ipynb', 'requirements.txt') }} + restore-keys: | + jupyter-exec-${{ steps.cache-date.outputs.date }}- + jupyter-exec- + - name: Build student book run: | python ci/generate_book.py student diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..17a1d5c8a --- /dev/null +++ b/Makefile @@ -0,0 +1,144 @@ +.PHONY: help verify-setup test-workflows test-scripts build-local clean safety-check + +help: + @echo "NeuroAI Course - Local Testing Commands" + @echo "" + @echo "⚠️ CRITICAL: ALWAYS commit your changes before running act tests!" + @echo "⚠️ act can manipulate your git repository when testing pull_request events" + @echo "⚠️ Use 'make safety-check' to verify git status before testing" + @echo "" + @echo "Setup:" + @echo " make verify-setup - Check prerequisites (Docker, act)" + @echo " make safety-check - Verify git status is safe for testing" + @echo "" + @echo "Safe Testing (Recommended):" + @echo " make test-workflows-dryrun - Validate workflows without execution (SAFE)" + @echo " make test-composite-dryrun - Validate composite actions (SAFE)" + @echo "" + @echo "Full Workflow Testing (REQUIRES COMMIT FIRST):" + @echo " make test-workflow-publish - Test publish-book workflow" + @echo " make test-workflow-pr - Test notebook-pr workflow" + @echo "" + @echo "Script Testing (No git manipulation):" + @echo " make test-scripts - Test CI scripts with pytest" + @echo "" + @echo "Book Building:" + @echo " make build-local - Build book locally with JB 1.x" + @echo " make serve-book - Serve book at localhost:8000" + @echo "" + @echo "Cleanup:" + @echo " make clean - Remove build artifacts" + @echo "" + +verify-setup: + @echo "Verifying prerequisites..." + @which docker > /dev/null || (echo "❌ Docker not found. Install: https://docs.docker.com/get-docker/" && exit 1) + @which act > /dev/null || (echo "❌ act not found. Install: curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash" && exit 1) + @docker ps > /dev/null 2>&1 || (echo "❌ Docker daemon not running. Start with: sudo systemctl start docker" && exit 1) + @echo "✅ All prerequisites satisfied" + @echo "" + @echo "Docker version: $$(docker --version)" + @echo "act version: $$(act --version)" + +safety-check: + @echo "Checking git status..." + @if [ -n "$$(git status --porcelain)" ]; then \ + echo ""; \ + echo "⚠️ WARNING: You have uncommitted changes!"; \ + echo "⚠️ act can corrupt your git repository when testing pull_request events"; \ + echo "⚠️ STRONGLY RECOMMENDED: Commit your changes first"; \ + echo ""; \ + git status --short; \ + echo ""; \ + echo "Options:"; \ + echo " 1. Commit: git add -A && git commit -m 'WIP: testing'"; \ + echo " 2. Use dry-run mode: make test-workflows-dryrun"; \ + echo " 3. Proceed anyway (NOT RECOMMENDED): make test-workflow-pr-unsafe"; \ + echo ""; \ + exit 1; \ + else \ + echo "✅ Working tree is clean - safe to run act"; \ + fi + +# Safe testing - validates without execution +test-workflows-dryrun: + @echo "🔍 Validating workflows (dry-run mode - SAFE)..." + @echo "" + @echo "Testing notebook-pr workflow..." + act pull_request -W .github/workflows/notebook-pr.yaml --dryrun + @echo "" + @echo "Testing publish-book workflow..." + act workflow_dispatch -W .github/workflows/publish-book.yml --dryrun + @echo "" + @echo "✅ All workflows validated successfully" + +test-composite-dryrun: + @echo "🔍 Validating composite actions (dry-run mode - SAFE)..." + act pull_request -W .github/workflows/notebook-pr.yaml --dryrun + @echo "✅ Composite actions validated" + +# Full workflow testing - REQUIRES COMMIT FIRST +test-workflow-publish: safety-check + @echo "🧪 Testing publish-book workflow..." + @echo "⚠️ This will create Docker containers and may take several minutes" + act workflow_dispatch -W .github/workflows/publish-book.yml + +test-workflow-pr: safety-check + @echo "🧪 Testing notebook-pr workflow..." + @echo "⚠️ This will create Docker containers and may take several minutes" + act pull_request -W .github/workflows/notebook-pr.yaml + +# Unsafe versions that bypass safety check (NOT RECOMMENDED) +test-workflow-pr-unsafe: + @echo "⚠️⚠️⚠️ RUNNING WITHOUT SAFETY CHECK - YOUR GIT REPO MAY BE CORRUPTED ⚠️⚠️⚠️" + @sleep 3 + act pull_request -W .github/workflows/notebook-pr.yaml + +test-workflow-publish-unsafe: + @echo "⚠️⚠️⚠️ RUNNING WITHOUT SAFETY CHECK - YOUR GIT REPO MAY BE CORRUPTED ⚠️⚠️⚠️" + @sleep 3 + act workflow_dispatch -W .github/workflows/publish-book.yml + +# Script testing - safe, doesn't manipulate git +test-scripts: + @echo "🧪 Testing CI scripts..." + @if [ -d "tests" ]; then \ + pytest tests/ -v; \ + else \ + echo "⚠️ No tests directory found"; \ + echo "Create tests/test_ci_scripts.py for script testing"; \ + fi + +# Local book building +build-local: + @echo "📚 Building book locally..." + @if [ ! -d "ci" ]; then \ + echo "Downloading nmaci tools..."; \ + wget -q https://github.com/neuromatch/nmaci/archive/refs/heads/main.tar.gz; \ + tar -xzf main.tar.gz; \ + pip install -r nmaci-main/requirements.txt > /dev/null; \ + mv nmaci-main/scripts/ ci/; \ + rm -r nmaci-main main.tar.gz; \ + fi + @python ci/generate_book.py student + @cd book && ln -sf ../tutorials tutorials && ln -sf ../projects projects && cd .. + @jupyter-book build book + @echo "✅ Book built successfully" + @echo "📖 Open: book/_build/html/index.html" + +serve-book: + @echo "🌐 Serving book at http://localhost:8000" + @cd book/_build/html && python -m http.server 8000 + +# Cleanup +clean: + @echo "🧹 Cleaning build artifacts..." + @rm -rf book/_build book/.jupyter-cache + @find . -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true + @find . -name "*.pyc" -delete 2>/dev/null || true + @echo "✅ Cleanup complete" + +clean-all: clean + @echo "🧹 Removing ci directory..." + @rm -rf ci/ + @echo "✅ Deep cleanup complete" diff --git a/TESTING_LOCALLY.md b/TESTING_LOCALLY.md new file mode 100644 index 000000000..0c180a04a --- /dev/null +++ b/TESTING_LOCALLY.md @@ -0,0 +1,594 @@ +# Local Testing Guide for NeuroAI Course CI/CD + +This guide explains how to test workflows and CI scripts locally before pushing to GitHub. + +## Table of Contents +- [Prerequisites](#prerequisites) +- [Quick Start](#quick-start) +- [Testing Strategies](#testing-strategies) +- [Safety Guidelines](#safety-guidelines) +- [Troubleshooting](#troubleshooting) +- [Understanding act](#understanding-act) + +--- + +## Prerequisites + +### Required Tools + +1. **Docker** (version 20.10+) + ```bash + # Install Docker + curl -fsSL https://get.docker.com -o get-docker.sh + sudo sh get-docker.sh + + # Start Docker daemon + sudo systemctl start docker + sudo systemctl enable docker + + # Add user to docker group (logout/login required) + sudo usermod -aG docker $USER + ``` + +2. **act** (GitHub Actions local runner) + ```bash + # Install act + curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash + + # Verify installation + act --version + ``` + +3. **Python 3.9+** with pip + ```bash + python3.9 -m venv .venv + source .venv/bin/activate + pip install -r requirements.txt + ``` + +### Verify Setup + +Run the verification command: +```bash +make verify-setup +``` + +Expected output: +``` +✅ All prerequisites satisfied + +Docker version: Docker version 24.0.7 +act version: act version 0.2.56 +``` + +--- + +## Quick Start + +### Safe Testing (Recommended) + +The safest way to test workflows is using **dry-run mode**, which validates syntax and structure without executing: + +```bash +# Validate all workflows +make test-workflows-dryrun + +# Validate composite actions +make test-composite-dryrun +``` + +### Full Workflow Testing + +⚠️ **CRITICAL**: Always commit changes before running full workflow tests! + +```bash +# 1. Check if it's safe to test +make safety-check + +# 2. Commit your changes +git add -A +git commit -m "WIP: testing workflows" + +# 3. Test workflows +make test-workflow-pr # Test PR workflow +make test-workflow-publish # Test publish workflow +``` + +### Local Book Building + +Build and view the book locally without Docker: + +```bash +# Build book +make build-local + +# Serve at http://localhost:8000 +make serve-book +``` + +--- + +## Testing Strategies + +### 1. Dry-Run Validation (Fastest, Safest) + +**Use for**: Quick syntax validation, composite action testing + +```bash +make test-workflows-dryrun +``` + +**Advantages**: +- ⚡ Fast (seconds vs minutes) +- ✅ No git manipulation +- ✅ No Docker containers created +- ✅ Safe to run on uncommitted changes + +**Limitations**: +- Doesn't actually execute steps +- Won't catch runtime errors + +--- + +### 2. Script Testing (Safe, Fast) + +**Use for**: Testing CI scripts without workflows + +```bash +make test-scripts +``` + +**Advantages**: +- ✅ No git manipulation +- ✅ Tests actual Python logic +- ✅ Fast execution + +**Requirements**: +- Create `tests/test_ci_scripts.py` with pytest tests + +**Example test**: +```python +# tests/test_ci_scripts.py +import pytest + +def test_generate_book_imports(): + """Test that generate_book.py can be imported.""" + import sys + sys.path.insert(0, 'ci') + import generate_book + assert hasattr(generate_book, 'generate_toc') +``` + +--- + +### 3. Local Book Build (Safe, Slow) + +**Use for**: End-to-end book building without GitHub Actions + +```bash +make build-local +make serve-book +``` + +**Advantages**: +- ✅ No git manipulation +- ✅ Tests actual book building +- ✅ Can inspect HTML output + +**Use case**: Testing book structure, TOC changes, theme updates + +--- + +### 4. Full Workflow Testing with act (Advanced) + +**Use for**: Testing complete workflow execution including environment setup + +⚠️ **REQUIRES**: Clean git working tree (commit first!) + +```bash +# ALWAYS run safety check first +make safety-check + +# Test specific workflow +make test-workflow-pr +``` + +**Advantages**: +- ✅ Most realistic testing +- ✅ Tests full environment setup +- ✅ Validates composite actions in context + +**Disadvantages**: +- ⚠️ Slow (5-15 minutes) +- ⚠️ **CAN CORRUPT GIT REPOSITORY IF NOT COMMITTED** +- ⚠️ Creates large Docker containers (~2GB) + +--- + +## Safety Guidelines + +### ⚠️ Critical: act and Git Manipulation + +**The Problem**: When `act` tests `pull_request` events on uncommitted changes, it: + +1. Creates a fake merge commit +2. Checks out a detached HEAD state +3. May change file ownership (Docker runs as root) +4. Can lose uncommitted work + +**Real Example of What Went Wrong**: +```bash +$ make test-workflow-pr # WITHOUT committing first + +# Result: +# - Lost all composite action files +# - Repository in detached HEAD state +# - File ownership changed to root +# - Had to recreate everything +``` + +### ✅ Safe Testing Workflow + +**Always follow this order**: + +```bash +# 1. Check git status +make safety-check + +# 2. If uncommitted changes, commit them +git add -A +git commit -m "WIP: testing workflows" + +# 3. Now safe to test +make test-workflow-pr + +# 4. After testing, verify git status +git status + +# 5. If needed, reset to before test commit +git reset --soft HEAD~1 +``` + +### ✅ Alternative: Use Dry-Run Mode + +Skip the git risk entirely: + +```bash +# No commit needed - completely safe +make test-workflows-dryrun +``` + +### ⚠️ If Git Gets Corrupted + +If act corrupts your repository: + +```bash +# 1. Fix file permissions +sudo chown -R $USER:$USER . + +# 2. Check git status +git status + +# 3. If detached HEAD, return to branch +git checkout main # or your branch name + +# 4. If you lost work, check reflog +git reflog +git reset --hard HEAD@{n} # where n is before corruption +``` + +--- + +## Understanding act + +### What is act? + +`act` runs GitHub Actions workflows locally using Docker containers. It: +- Reads `.github/workflows/*.yml` files +- Creates Docker containers matching GitHub Actions runners +- Executes workflow steps inside containers +- Simulates GitHub event payloads (pull_request, push, etc.) + +### How act Works + +``` +Local Machine Docker Container (ubuntu-latest) +┌──────────────┐ ┌─────────────────────────────────┐ +│ │ │ │ +│ Workflow │─────▶│ 1. Checkout code │ +│ .yml file │ │ 2. Setup Python │ +│ │ │ 3. Run composite actions │ +│ │ │ 4. Execute scripts │ +│ │ │ │ +│ .actrc │─────▶│ Uses catthehacker/ubuntu image │ +│ config │ │ │ +└──────────────┘ └─────────────────────────────────┘ + │ + ▼ + Writes back to local filesystem + (via Docker bind mount) +``` + +### act Configuration (.actrc) + +```ini +# Use GitHub Actions compatible Docker image +-P ubuntu-latest=catthehacker/ubuntu:act-latest + +# Set architecture +--container-architecture linux/amd64 + +# Store artifacts locally +--artifact-server-path /tmp/artifacts +``` + +### act Event Simulation + +When you run `act pull_request`, it: + +1. Creates a fake pull request event JSON +2. Simulates a merge commit (combines your branch with target) +3. Checks out the merge commit +4. Runs workflow steps + +**This is why uncommitted changes get lost!** + +--- + +## Troubleshooting + +### Error: "permission denied" when running Docker + +**Cause**: User not in docker group + +**Fix**: +```bash +sudo usermod -aG docker $USER +newgrp docker # Or logout/login +``` + +--- + +### Error: "Cannot connect to Docker daemon" + +**Cause**: Docker daemon not running + +**Fix**: +```bash +sudo systemctl start docker +sudo systemctl enable docker # Auto-start on boot +``` + +--- + +### Error: "failed to read 'action.yml' from action" + +**Cause**: Composite action paths not resolved by act + +**Solution**: Use dry-run mode to validate: +```bash +make test-composite-dryrun +``` + +--- + +### Error: "fatal: detected dubious ownership" + +**Cause**: Docker created files as root + +**Fix**: +```bash +sudo chown -R $USER:$USER . +``` + +--- + +### Error: "HEAD detached at pull/%!f()/merge" + +**Cause**: act simulated pull request event + +**Fix**: +```bash +# Return to your branch +git checkout jupyter-book-2-migration + +# Check for lost work +git reflog +``` + +--- + +### Workflow runs forever / hangs + +**Cause**: Step waiting for input, or resource constraint + +**Fix**: +```bash +# Kill act process +Ctrl+C + +# Check Docker containers +docker ps +docker kill $(docker ps -q) # Kill all running containers + +# Check Docker resources +docker system df +docker system prune # Clean up space +``` + +--- + +## Testing Workflow + +### Recommended Development Cycle + +```bash +# 1. Make changes to workflows or composite actions +vim .github/workflows/notebook-pr.yaml + +# 2. Quick validation (5 seconds) +make test-workflows-dryrun + +# 3. If validation passes, test scripts locally +make test-scripts + +# 4. Build book locally to verify end-to-end +make build-local +make serve-book # Review at localhost:8000 + +# 5. Commit changes +git add -A +git commit -m "feat: improve workflow caching" + +# 6. Optional: Full workflow test with act +make safety-check +make test-workflow-pr # Only if needed + +# 7. Push and let GitHub Actions do final test +git push +``` + +### When to Use Each Method + +| Test Method | When to Use | Time | Risk | +|-------------|-------------|------|------| +| `test-workflows-dryrun` | Every change | 5s | None | +| `test-scripts` | Script changes | 10s | None | +| `build-local` | Book changes | 2-5m | None | +| `test-workflow-pr` (act) | Major workflow changes | 10-15m | High if uncommitted | +| Push to GitHub | Final validation | 20-30m | None | + +--- + +## Advanced Usage + +### Test Specific Workflow Job + +```bash +# Test only specific job from workflow +act pull_request -W .github/workflows/notebook-pr.yaml -j process-notebooks +``` + +### Use Different Event Types + +```bash +# Test push event instead of pull_request +act push -W .github/workflows/publish-book.yml + +# Test workflow_dispatch with inputs +act workflow_dispatch -W .github/workflows/publish-book.yml +``` + +### Debug Mode + +```bash +# Verbose output +act pull_request -W .github/workflows/notebook-pr.yaml -v + +# Even more verbose +act pull_request -W .github/workflows/notebook-pr.yaml -v -v +``` + +### Use Secrets + +```bash +# Create .secrets file +echo "GITHUB_TOKEN=your_token" > .secrets + +# Use secrets in act +act pull_request -W .github/workflows/notebook-pr.yaml --secret-file .secrets +``` + +--- + +## FAQ + +### Q: Do I need to use act? + +**A**: No! act is optional. You can: +- Use dry-run mode for validation +- Build locally with `make build-local` +- Test scripts with pytest +- Push to GitHub and test there + +act is most useful for testing complex workflows, but adds complexity. + +--- + +### Q: Why does act take so long? + +**A**: First run downloads Docker images (~2GB). Subsequent runs are faster due to: +- Docker image caching +- pip caching (if workflow uses it) +- nmaci tools caching + +Expect: 10-15 min first run, 3-5 min subsequent runs. + +--- + +### Q: Can I test without Docker? + +**A**: Yes, for book building: +```bash +make build-local # No Docker needed +``` + +For workflow testing, Docker is required by act. + +--- + +### Q: How do I avoid losing work with act? + +**A**: Three options: + +1. **Always commit before testing** (recommended) +2. **Use dry-run mode** (safe, faster) +3. **Test in separate repo clone**: + ```bash + git clone . ../neuroai-test + cd ../neuroai-test + make test-workflow-pr # Safe, won't affect main repo + ``` + +--- + +### Q: What if GitHub Actions work but act fails? + +**A**: act is not 100% compatible with GitHub Actions. Differences: +- Networking (some external services blocked) +- Docker-in-Docker limitations +- Missing GitHub-specific environment variables + +**Solution**: If act fails but you're confident the workflow is correct, push to GitHub and test there. + +--- + +## Resources + +- **act documentation**: https://github.com/nektos/act +- **GitHub Actions docs**: https://docs.github.com/en/actions +- **Makefile commands**: Run `make help` +- **CI/CD optimization plan**: See `CI_CD_OPTIMIZATION_PLAN.md` + +--- + +## Summary + +**For most testing needs**: +```bash +# Quick validation +make test-workflows-dryrun + +# Local building +make build-local +``` + +**For comprehensive workflow testing**: +```bash +# ALWAYS commit first! +git add -A && git commit -m "WIP" +make safety-check +make test-workflow-pr +``` + +**Remember**: act is powerful but dangerous with uncommitted changes. When in doubt, use dry-run mode or build locally. From 561b4715a1f6283ea017ae4dff7b96f9bdfe266c Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Wed, 14 Jan 2026 23:25:55 -0500 Subject: [PATCH 2/3] Update python version to 3.10 --- .github/actions/setup-python-env/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-python-env/action.yml b/.github/actions/setup-python-env/action.yml index 453780fdf..6771ef938 100644 --- a/.github/actions/setup-python-env/action.yml +++ b/.github/actions/setup-python-env/action.yml @@ -5,7 +5,7 @@ inputs: python-version: description: 'Python version to install' required: false - default: '3.9' + default: '3.10' outputs: cache-hit: From 523a6ac2b62ec423e0b12f0b674020d03e559a01 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Thu, 15 Jan 2026 01:14:49 -0500 Subject: [PATCH 3/3] Update package name --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c4704a8b9..52c79d0ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,4 +32,4 @@ git+https://github.com/neuromatch/GNS-Modeling#egg=gns git+https://github.com/neuromatch/pyBPL#egg=pybpl git+https://github.com/neuromatch/MotorNet#egg=motornet git+https://github.com/ctn-waterloo/sspspace@neuromatch#egg=sspspace -git+https://github.com/mitchellostrow/DSA#egg=DSA \ No newline at end of file +git+https://github.com/mitchellostrow/DSA#egg=dsa-metric \ No newline at end of file