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.

136 lines · 6.5 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.test.mjs, security-*.test.mjs (security-txt's too) and status.test.mjs.
69 security='/(security(-[a-z-]+)?|status)\.test\.mjs$'
70 # The core file's real histories and large pushes take minutes each (jquery's alone 2 to 3 on
71 # Diffinery's runners): two shards of their own, so no part of the file runs into node's
72 # per-file timeout, which is the --test-timeout here.
73 histories='^real histories:'
74 large='^(large files|large pushes):'
75 opts=(--test-timeout=600000)
76 case "${{ matrix.shard }}" in
77 core) files=$(ls test/e2e/*.test.mjs | grep -E "$core"); opts+=(--test-skip-pattern="$histories" --test-skip-pattern="$large") ;;
78 histories) files=$(ls test/e2e/*.test.mjs | grep -E "$core"); opts=(--test-timeout=900000 --test-name-pattern="$histories") ;;
79 large) files=$(ls test/e2e/*.test.mjs | grep -E "$core"); opts+=(--test-name-pattern="$large") ;;
80 agents) files=$(ls test/e2e/*.test.mjs | grep -E "$agents") ;;
81 security) files=$(ls test/e2e/*.test.mjs | grep -E "$security") ;;
82 *) files=$(ls test/e2e/*.test.mjs | grep -vE "$core|$agents|$security") ;;
83 esac
84 echo "$files"
85 # Two files at a time: each starts its own local server on its own port, database and
86 # directory, so they don't share state, and a file's server mostly keeps one core busy.
87 node --test --test-concurrency=2 "${opts[@]}" $files
88
89 # Docker as the hosted runner image runs it, with this change's docker wrapper, daemon config
90 # and agent (its registry relay): this job runs on a hosted runner, so the real dockerd is there.
91 # A daemon an earlier step started is stopped first, so the first uses below start it afresh.
92 runner-docker:
93 name: End-to-end (runner Docker)
94 runs-on: ubuntu-latest
95 timeout-minutes: 20
96 steps:
97 - uses: actions/checkout@v4
98 - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
99 - uses: actions/setup-node@v4
100 with:
101 node-version: 22
102 cache: pnpm
103 - run: pnpm install --frozen-lockfile
104 - run: pnpm --filter @diffinery/runner build
105 - name: Use this change's Docker wrapper, daemon config and agent
106 run: |
107 set -euo pipefail
108 as_root() { if [ "$(id -u)" = 0 ]; then "$@"; else sudo "$@"; fi; }
109 if [ ! -f /opt/diffinery/agent.mjs ] || ! grep -q "registry relay" /usr/local/bin/docker 2>/dev/null; then
110 echo "::error::this job needs a Diffinery hosted runner (its docker wrapper and agent)"
111 exit 1
112 fi
113 as_root pkill -x dockerd || true
114 as_root pkill -f -- "--registry-relay" || true
115 for _ in $(seq 1 60); do pgrep -x dockerd >/dev/null || break; sleep 0.5; done
116 as_root rm -f /run/diffinery-dockerd.ready
117 as_root cp --remove-destination containers/runner/image/docker /usr/local/bin/docker
118 as_root cp --remove-destination containers/runner/image/daemon.json /etc/docker/daemon.json
119 as_root cp --remove-destination containers/runner/dist/agent.mjs /opt/diffinery/agent.mjs
120 - name: Docker's first uses, several at once
121 timeout-minutes: 4
122 run: |
123 set -uo pipefail
124 start=$(date +%s)
125 docker info >/dev/null & a=$!
126 docker ps >/dev/null & b=$!
127 docker version >/dev/null & c=$!
128 failed=0
129 for p in $a $b $c; do wait "$p" || failed=1; done
130 echo "three docker commands at once, the daemon starting: $(( $(date +%s) - start )) s"
131 [ "$failed" = 0 ] || { echo "::error::a docker command failed while the daemon started"; exit 1; }
132 docker run --rm mirror.gcr.io/library/alpine:3.20 echo "container ok"
133 - name: Runner Docker tests (job containers, services, Docker actions)
134 timeout-minutes: 10
135 working-directory: containers/runner
136 run: pnpm exec vitest run test/docker.test.ts test/docker-wrapper.test.ts