Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
455 changes: 455 additions & 0 deletions .github/copilot-instructions.md

Large diffs are not rendered by default.

600 changes: 600 additions & 0 deletions .github/fork/PROCESS.md

Large diffs are not rendered by default.

1,676 changes: 1,676 additions & 0 deletions .github/fork/forkctl.py

Large diffs are not rendered by default.

1,543 changes: 1,543 additions & 0 deletions .github/fork/test_forkctl.py

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!-- Fork PR into integration/3.0. Keep it short; the Fork gates check proves the rest. -->

## What

<!-- One or two sentences. If you need "and" for unrelated changes, split the PR. -->

## Why

<!-- The problem, or the capability we need. Link the issue if there is one. -->

## How tested

<!-- Only what CI cannot run for you. If CI covers it, write "CI" and stop.
Name anything you ran locally that CI does not: docker-smoke, perf, a GPU run,
a manual reproduction. A claim that a suite passed means it ran to completion. -->

New behaviour is covered by a new test / the fix has a test that fails without it:
<!-- name the test, or say why none applies -->

## Upstream disposition

- [ ] Mirror PR requested now — a human must verify title, body and diff first, and the body
carries the disclosure from `.github/fork/PROCESS.md` → *Agent authorship*
- [ ] Contributable, held for now because: <!-- reason -->
- [ ] **Never upstream** — fork-only `chore(fork):` change

<!-- Fork-only? Then ALL of: draft PR, [do-not-mirror][do-not-merge] in the title, both labels,
and the box above. Merging it would leave a second chore(fork) commit and abort every future
sync; it gets folded in by fixup instead. See PROCESS.md -> Changing fork-only content. -->

## Risk / notes

<!-- Behaviour, config or observation-layout changes, perf impact, follow-ups. Delete if none. -->
93 changes: 84 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,64 @@ name: CI

on:
pull_request:
branches: [ main, '3.0' ]
branches: [ main, '3.0', 'integration/3.0' ]
push:
branches: [ main, '3.0' ]

jobs:
# Every test job runs unless this job succeeded and positively reported
# code=false, so a broken detector costs time rather than coverage.
changes:
name: Detect code changes
runs-on: ubuntu-latest
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Decide whether anything testable changed
id: filter
env:
BASE_REF: ${{ github.base_ref }}
run: |
# Conservative by construction: only a recognised documentation path
# votes "no code". Anything unrecognised -- and every push build, which
# has no base to diff against -- runs the full suite.
if [ -z "$BASE_REF" ]; then
echo "Push build: running everything."
echo "code=true" >> "$GITHUB_OUTPUT"
exit 0
fi

git fetch --no-tags origin "$BASE_REF"
base=$(git merge-base FETCH_HEAD HEAD)
# --no-renames: a rename is reported as its destination alone, so moving
# a module under docs/ would otherwise hide the deleted code path.
changed=$(git diff --no-renames --name-only "$base" HEAD)

echo "Changed files:"
echo "$changed"

# `grep -v` exits 1 when everything was filtered out, which is exactly
# the documentation-only case, so it must not trip `set -e`.
testable=$(echo "$changed" | grep -Ev '(\.md$|^docs/|^\.github/ISSUE_TEMPLATE/)' || true)

if [ -z "$testable" ]; then
echo "Documentation only: skipping the test jobs."
echo "code=false" >> "$GITHUB_OUTPUT"
else
echo "Testable changes present:"
echo "$testable"
echo "code=true" >> "$GITHUB_OUTPUT"
fi

unit-tests:
name: Unit Tests
needs: changes
if: always() && !(needs.changes.result == 'success' && needs.changes.outputs.code == 'false')
runs-on: ubuntu-latest
env:
PUFFER_CPU: 1
Expand All @@ -33,8 +84,12 @@ jobs:
PIP_NO_CACHE_DIR: 1
run: |
sudo apt-get update && sudo apt-get install -y build-essential cmake
python -m pip install -U pip pytest
pip install -e . --no-cache-dir
python -m pip install -U pip "setuptools<81" wheel numpy pytest
# CPU-only torch: the CUDA stack no longer fits the runner's disk
# quota. --no-build-isolation then reuses it for the build instead
# of resolving a second copy. See tests/smoke_tests/Dockerfile.
pip install --index-url https://download.pytorch.org/whl/cpu torch
pip install -e . --no-build-isolation --no-cache-dir
python setup.py build_ext --inplace --force

- name: Unit tests
Expand All @@ -46,6 +101,8 @@ jobs:

c-drive-tests:
name: C Drive Tests
needs: changes
if: always() && !(needs.changes.result == 'success' && needs.changes.outputs.code == 'false')
runs-on: ubuntu-latest
env:
PUFFER_CPU: 1
Expand All @@ -70,15 +127,21 @@ jobs:
PIP_NO_CACHE_DIR: 1
run: |
sudo apt-get update && sudo apt-get install -y build-essential cmake xvfb libgl1-mesa-dev libegl1-mesa-dev libx11-dev libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev libxxf86vm-dev
python -m pip install -U pip
pip install -e . --no-cache-dir
python -m pip install -U pip "setuptools<81" wheel numpy
# CPU-only torch: the CUDA stack no longer fits the runner's disk
# quota. --no-build-isolation then reuses it for the build instead
# of resolving a second copy. See tests/smoke_tests/Dockerfile.
pip install --index-url https://download.pytorch.org/whl/cpu torch
pip install -e . --no-build-isolation --no-cache-dir
python setup.py build_ext --inplace --force

- name: C drive tests
run: make -C tests/drive test

notebook-tests:
name: Notebook Execution
needs: changes
if: always() && !(needs.changes.result == 'success' && needs.changes.outputs.code == 'false')
runs-on: ubuntu-latest
env:
PUFFER_CPU: 1
Expand All @@ -103,8 +166,12 @@ jobs:
PIP_NO_CACHE_DIR: 1
run: |
sudo apt-get update && sudo apt-get install -y build-essential cmake
python -m pip install -U pip pytest jupytext nbclient ipykernel
pip install -e . --no-cache-dir
python -m pip install -U pip "setuptools<81" wheel numpy pytest jupytext nbclient ipykernel
# CPU-only torch: the CUDA stack no longer fits the runner's disk
# quota. --no-build-isolation then reuses it for the build instead
# of resolving a second copy. See tests/smoke_tests/Dockerfile.
pip install --index-url https://download.pytorch.org/whl/cpu torch
pip install -e . --no-build-isolation --no-cache-dir
python setup.py build_ext --inplace --force

- name: Run notebooks
Expand All @@ -113,6 +180,8 @@ jobs:

smoke-tests:
name: Smoke Train Test
needs: changes
if: always() && !(needs.changes.result == 'success' && needs.changes.outputs.code == 'false')
runs-on: ubuntu-latest
steps:
- name: Checkout code
Expand All @@ -127,6 +196,8 @@ jobs:

perf-tests:
name: Smoke Performance Test
needs: changes
if: always() && !(needs.changes.result == 'success' && needs.changes.outputs.code == 'false')
runs-on: ubuntu-latest
env:
PUFFER_CPU: 1
Expand All @@ -151,8 +222,12 @@ jobs:
PIP_NO_CACHE_DIR: 1
run: |
sudo apt-get update && sudo apt-get install -y build-essential cmake libgl1-mesa-dev libegl1-mesa-dev libx11-dev libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev libxxf86vm-dev
python -m pip install -U pip
pip install -e . --no-cache-dir
python -m pip install -U pip "setuptools<81" wheel numpy
# CPU-only torch: the CUDA stack no longer fits the runner's disk
# quota. --no-build-isolation then reuses it for the build instead
# of resolving a second copy. See tests/smoke_tests/Dockerfile.
pip install --index-url https://download.pytorch.org/whl/cpu torch
pip install -e . --no-build-isolation --no-cache-dir
python setup.py build_ext --inplace --force

- name: Test simulator performance
Expand Down
75 changes: 75 additions & 0 deletions .github/workflows/fork-gates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Fork-only. Never cherry-pick this file upstream.
#
# Runs the mechanical merge gates so nobody has to run them by hand or paste
# their output into a pull request. Gate 1 is the rest of CI; this job is
# check-commit plus Gate 2, reported like any other check. Text output, not
# annotations: this log is what a reviewer reads instead of a pasted block.
name: Fork gates

on:
pull_request:
branches:
- 'integration/3.0'

permissions:
contents: read

jobs:
gates:
name: Fork gates
runs-on: ubuntu-latest
steps:
- name: Checkout the pull request head
uses: actions/checkout@v4
with:
# The merge ref would put a synthetic merge commit at HEAD, and every
# gate reasons about the real commit the branch carries.
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0

# Checking out a SHA leaves HEAD detached, which `mirror` refuses: it has
# to know which branch to return you to.
- name: Put HEAD on a branch
run: git switch -c gate-head

# `mirror` cherry-picks, which commits, and a runner has no git identity.
# Without this the gate fails with "Committer identity unknown".
- name: Configure git
run: |
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'

- name: Fetch upstream
run: |
git remote add upstream https://github.com/Emerge-Lab/PufferDrive.git
git fetch --no-tags --quiet upstream 3.0:refs/remotes/upstream/3.0

- name: Identify the change
id: id
run: |
branch='${{ github.event.pull_request.head.ref }}'
echo "slug=${branch#*/}" >> "$GITHUB_OUTPUT"
case "$(git log -1 --format=%s)" in
'chore(fork):'*) echo "chore=true" >> "$GITHUB_OUTPUT" ;;
*) echo "chore=false" >> "$GITHUB_OUTPUT" ;;
esac

- name: Review track
run: python .github/fork/forkctl.py track

- name: check-commit
run: |
if [ '${{ steps.id.outputs.chore }}' = 'true' ]; then
python .github/fork/forkctl.py check-commit --chore
else
python .github/fork/forkctl.py check-commit
fi

# A chore(fork) commit is fork-only by definition, so it has no upstream
# form to prove. Section 4.8 exempts it.
#
# always(): an author should get both gate results from one run, not fix
# check-commit and only then discover Gate 2 is red too.
- name: 'Gate 2: upstream-readiness'
if: always() && steps.id.outputs.chore == 'false'
run: python .github/fork/forkctl.py mirror '${{ steps.id.outputs.slug }}' --sha HEAD --no-publish
Loading
Loading