Skip to content
Merged
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
73 changes: 49 additions & 24 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ on:
tags:
- "v*"
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
workflow_dispatch:
inputs:
tests:
description: "hspec match patterns, one per line; / runs all tests, empty runs none"
type: string
default: ""

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -24,7 +31,15 @@ jobs:

maybe-release:
runs-on: ubuntu-latest
outputs:
reduced: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.draft == true }}
steps:
- name: Reject a manual run on a tag
if: github.event_name == 'workflow_dispatch' && github.ref_type == 'tag'
run: |
echo "::error::A manual run starts on a branch; to retry a release, re-run the jobs of its tag push."
exit 1

- name: Clone project
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/checkout@v3
Expand Down Expand Up @@ -66,21 +81,22 @@ jobs:
build:
name: "ubuntu-${{ matrix.os }}-${{ matrix.arch }}, GHC: ${{ matrix.ghc }}"
needs: maybe-release
if: needs.maybe-release.outputs.reduced != 'true'
permissions:
contents: write
packages: write
env:
env: &build-env
apps: "smp-server xftp-server ntf-server xftp"
opt: ${{ github.event_name == 'pull_request' && 'O0' || 'O2' }}
ghc_options: ${{ github.event_name == 'pull_request' && '--ghc-options=-O0' || '' }}
opt: ${{ github.event_name == 'push' && 'O2' || 'O0' }}
ghc_options: ${{ github.event_name != 'push' && '--ghc-options=-O0' || '' }}
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
postgres_dist: ${{ startsWith(github.ref, 'refs/tags/v') && 'dist-newstyle' || 'dist-postgres' }}
branch: ${{ github.base_ref || github.ref_name }}
cache_o0: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/stable') }}
image_cache: ghcr.io/simplex-chat/simplexmq-build-cache:${{ matrix.os }}-${{ matrix.arch }}-ghc${{ matrix.ghc }}
image_cache: ghcr.io/${{ github.repository_owner }}/simplexmq-build-cache:${{ matrix.os }}-${{ matrix.arch }}-ghc${{ matrix.ghc }}
source_hashes: dist-newstyle/sources.sha256
runs-on: ${{ matrix.runner }}
services:
services: &build-services
postgres:
image: postgres:15
env:
Expand Down Expand Up @@ -127,7 +143,7 @@ jobs:
runner: "ubuntu-24.04-arm"
ghc: "9.6.3"
should_run: true
steps:
steps: &build-steps
- name: Clone project
if: matrix.should_run == true
uses: actions/checkout@v3
Expand Down Expand Up @@ -291,7 +307,7 @@ jobs:

- name: Build everything else (standard)
id: build-standard
if: matrix.should_run == true
if: matrix.should_run == true && needs.maybe-release.outputs.reduced != 'true'
shell: docker exec -t builder sh -eu {0}
run: |
cabal build --jobs=$(nproc) ${{ env.ghc_options }}
Expand Down Expand Up @@ -394,23 +410,11 @@ jobs:
shell: bash
env:
PGHOST: localhost
run: |
i=1
attempts=1
${{ (github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) }} && attempts=3
while [ "$i" -le "$attempts" ]; do
if ./simplexmq-test; then
break
else
echo "Attempt $i failed, retrying..."
i=$((i + 1))
sleep 1
fi
done
if [ "$i" -gt "$attempts" ]; then
echo "All "$attempts" attempts failed."
exit 1
fi
REDUCED: ${{ needs.maybe-release.outputs.reduced }}
TESTS: ${{ github.event_name == 'workflow_dispatch' && inputs.tests || '' }}
PR_BODY: ${{ github.event.pull_request.draft == true && github.event.pull_request.body || '' }}
ATTEMPTS: ${{ (github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) && 3 || 1 }}
run: scripts/ci/run-tests.sh ./simplexmq-test

- name: Remove -O2 build directories
id: remove-o2
Expand Down Expand Up @@ -458,6 +462,27 @@ jobs:
dist-postgres
key: ${{ steps.keys.outputs.dist }}-O0-${{ env.branch }}-${{ github.sha }}

build-reduced:
name: "ubuntu-${{ matrix.os }}-${{ matrix.arch }}, GHC: ${{ matrix.ghc }}"
needs: maybe-release
if: needs.maybe-release.outputs.reduced == 'true'
permissions:
contents: write
packages: write
env: *build-env
runs-on: ${{ matrix.runner }}
services: *build-services
strategy:
matrix:
include:
- os: 24.04
os_underscore: 24_04
arch: x86-64
runner: "ubuntu-24.04"
ghc: "9.6.3"
should_run: true
steps: *build-steps

# =============================
# Resolver test job
# =============================
Expand Down
50 changes: 50 additions & 0 deletions scripts/ci/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# CI on GitHub's runners

## Builds

`build.yml` runs one of two builds:

| Trigger | Build | Jobs |
|---|---|---|
| a push to a draft pull request<br>a manual run (workflow_dispatch) | reduced | build-reduced: ubuntu-24.04 x86-64, GHC 9.6.3, at -O0, without the standard build |
| a pull request marked ready for review, and later pushes to it<br>a push to master or stable<br>a push of a v* tag | full | build: 5 entries (GHC 8.10.7 and 9.6.3, x86-64 and aarch64), at -O0 on pull requests and -O2 on pushes |

Both builds also run maybe-release and resolver (python). Tests run on the x86-64 entries. The reduced build restores the -O0 build caches saved by master pushes; the Docker image cache is tagged by ISO week, so the first run of a week rebuilds the image.

## Test selection

- Draft pull request: a fenced block with the info string `ci-tests` in the description, one hspec match pattern per line. The block is read at each push; an edit applies from the next push.
- Manual run: input `tests`, one pattern per line.
- `/` runs all tests. With no pattern, a reduced build runs no tests. A full build runs all tests and ignores the selection.
- Each pattern is passed as `-m <pattern>`. With at least one pattern, `--fail-on=empty` is added, so a pattern that matches nothing fails the run.
- A pattern matches the item path `/group/subgroup/item/` as a substring. Group names are the `describe` names in tests/Test.hs.

Example block:

````
```ci-tests
/Core tests/Encoding tests/
SMP proxy
```
````

## Manual run

```sh
curl -fsS -X POST \
-H "Authorization: Bearer $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/repos/<owner>/simplexmq/actions/workflows/build.yml/dispatches \
-d '{"ref": "<branch>", "inputs": {"tests": "/Core tests/Encoding tests/\nSMP proxy"}}'
```

A manual run starts on a branch; a release is retried by re-running the jobs of its tag push.

## Local run

scripts/ci/run-tests.sh runs the selection as in CI: it appends the patterns of `TESTS` to the test command.

```sh
cabal build --enable-tests --ghc-options=-O0 simplexmq-test
TESTS='/Core tests/Encoding tests/' scripts/ci/run-tests.sh "$(cabal list-bin simplexmq-test --ghc-options=-O0)"
```
38 changes: 38 additions & 0 deletions scripts/ci/run-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/bash

set -euo pipefail

if [ "$#" -eq 0 ]; then
echo "usage: $0 <test command>" >&2
exit 1
fi

if [ -n "${PR_BODY:-}" ]; then
TESTS=$(printf '%s\n' "$PR_BODY" | tr -d '\r' | awk '/^```ci-tests[ \t]*$/ {f = 1; next} /^```[ \t]*$/ {f = 0} f')
fi
args=()
while read -r p; do
if [ -n "$p" ]; then args+=(-m "$p"); fi
done <<< "${TESTS:-}"
if [ "${#args[@]}" -gt 0 ]; then
args+=(--fail-on=empty)
elif [ "${REDUCED:-}" = true ]; then
echo "No tests selected"
exit 0
fi

i=1
attempts=${ATTEMPTS:-1}
while [ "$i" -le "$attempts" ]; do
if "$@" "${args[@]}"; then
break
else
echo "Attempt $i failed, retrying..."
i=$((i + 1))
sleep 1
fi
done
if [ "$i" -gt "$attempts" ]; then
echo "All $attempts attempts failed."
exit 1
fi
Loading