Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

133 lines · 6.3 KBRaw History
1name: End-to-end
2
3on:
4 push:
5 branches: [main]
6 pull_request:
7
8permissions:
9 contents: read
10
11concurrency:
12 group: e2e-${{ github.ref }}
13 cancel-in-progress: true
14
15jobs:
16 # The suite in parallel shards of about the same length: the git, pull request and
17 # Actions file split in three (its real histories and large pushes on their own), agents, security, and
18 # every other test/e2e/*.test.mjs file (so a new file runs without editing this).
19 # Only the last shard needs a browser (passkeys).
20 e2e:
21 name: End-to-end (${{ matrix.name }})
22 runs-on: ubuntu-latest
23 timeout-minutes: 20
24 strategy:
25 fail-fast: false
26 matrix:
27 include:
28 - name: git, PRs, Actions
29 shard: core
30 - name: real histories
31 shard: histories
32 - name: large pushes
33 shard: large
34 - name: agent reviews, tasks, Today
35 shard: agents
36 - name: security
37 shard: security
38 - name: passkeys, registry, email
39 shard: rest
40 steps:
41 - uses: actions/checkout@v4
42 - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
43 - uses: actions/setup-node@v4
44 with:
45 node-version: 22
46 cache: pnpm
47 - run: pnpm install --frozen-lockfile
48 - run: pnpm -r --if-present build
49 - if: matrix.shard == 'rest'
50 run: pnpm --filter @diffinery/app exec playwright install --with-deps chromium
51 # Agent runs (terminal tasks, and headless runs in the agents shard) are in a bubblewrap jail
52 # (containers/runner/src/jail.ts); Ubuntu's AppArmor otherwise keeps
53 # it from creating user namespaces. Diffinery's own runner image already has bwrap and no sudo, running as root.
54 - if: matrix.shard == 'rest' || matrix.shard == 'agents'
55 run: |
56 as_root() { if [ "$(id -u)" = 0 ]; then "$@"; else sudo "$@"; fi; }
57 if ! command -v bwrap >/dev/null; then
58 as_root apt-get install -y --no-install-recommends bubblewrap || { as_root apt-get update -qq && as_root apt-get install -y --no-install-recommends bubblewrap; }
59 fi
60 as_root sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 2>/dev/null || true
61 bwrap --unshare-all --ro-bind / / --dev /dev --proc /proc -- true
62 - name: Run the shard's tests
63 working-directory: apps/diffinery
64 run: |
65 # Shards of similar length; a new test file runs in "rest" unless it's added to another.
66 core='/e2e\.test\.mjs$'
67 agents='/(agentic|review|tasks)\.test\.mjs$'
68 security='/(security|status)\.test\.mjs$'
69 # The core file's real histories and large pushes take minutes each (jquery's alone 2 to 3 on
70 # Diffinery's runners): two shards of their own, so no part of the file runs into node's
71 # per-file timeout, which is the --test-timeout here.
72 histories='^real histories:'
73 large='^(large files|large pushes):'
74 opts=(--test-timeout=600000)
75 case "${{ matrix.shard }}" in
76 core) files=$(ls test/e2e/*.test.mjs | grep -E "$core"); opts+=(--test-skip-pattern="$histories" --test-skip-pattern="$large") ;;
77 histories) files=$(ls test/e2e/*.test.mjs | grep -E "$core"); opts=(--test-timeout=900000 --test-name-pattern="$histories") ;;
78 large) files=$(ls test/e2e/*.test.mjs | grep -E "$core"); opts+=(--test-name-pattern="$large") ;;
79 agents) files=$(ls test/e2e/*.test.mjs | grep -E "$agents") ;;
80 security) files=$(ls test/e2e/*.test.mjs | grep -E "$security") ;;
81 *) files=$(ls test/e2e/*.test.mjs | grep -vE "$core|$agents|$security") ;;
82 esac
83 echo "$files"
84 node --test --test-concurrency=1 "${opts[@]}" $files
85
86 # Docker as the hosted runner image runs it, with this change's docker wrapper, daemon config
87 # and agent (its registry relay): this job runs on a hosted runner, so the real dockerd is there.
88 # A daemon an earlier step started is stopped first, so the first uses below start it afresh.
89 runner-docker:
90 name: End-to-end (runner Docker)
91 runs-on: ubuntu-latest
92 timeout-minutes: 20
93 steps:
94 - uses: actions/checkout@v4
95 - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
96 - uses: actions/setup-node@v4
97 with:
98 node-version: 22
99 cache: pnpm
100 - run: pnpm install --frozen-lockfile
101 - run: pnpm --filter @diffinery/runner build
102 - name: Use this change's Docker wrapper, daemon config and agent
103 run: |
104 set -euo pipefail
105 as_root() { if [ "$(id -u)" = 0 ]; then "$@"; else sudo "$@"; fi; }
106 if [ ! -f /opt/diffinery/agent.mjs ] || ! grep -q "registry relay" /usr/local/bin/docker 2>/dev/null; then
107 echo "::error::this job needs a Diffinery hosted runner (its docker wrapper and agent)"
108 exit 1
109 fi
110 as_root pkill -x dockerd || true
111 as_root pkill -f -- "--registry-relay" || true
112 for _ in $(seq 1 60); do pgrep -x dockerd >/dev/null || break; sleep 0.5; done
113 as_root rm -f /run/diffinery-dockerd.ready
114 as_root cp --remove-destination containers/runner/image/docker /usr/local/bin/docker
115 as_root cp --remove-destination containers/runner/image/daemon.json /etc/docker/daemon.json
116 as_root cp --remove-destination containers/runner/dist/agent.mjs /opt/diffinery/agent.mjs
117 - name: Docker's first uses, several at once
118 timeout-minutes: 4
119 run: |
120 set -uo pipefail
121 start=$(date +%s)
122 docker info >/dev/null & a=$!
123 docker ps >/dev/null & b=$!
124 docker version >/dev/null & c=$!
125 failed=0
126 for p in $a $b $c; do wait "$p" || failed=1; done
127 echo "three docker commands at once, the daemon starting: $(( $(date +%s) - start )) s"
128 [ "$failed" = 0 ] || { echo "::error::a docker command failed while the daemon started"; exit 1; }
129 docker run --rm mirror.gcr.io/library/alpine:3.20 echo "container ok"
130 - name: Runner Docker tests (job containers, services, Docker actions)
131 timeout-minutes: 10
132 working-directory: containers/runner
133 run: pnpm exec vitest run test/docker.test.ts test/docker-wrapper.test.ts