ci: run a test directory with a group and a channel on the app runner

A new backend job builds simplex-chat and simplex-directory-service with the
server-capable configuration. The app job starts a chat relay and a directory
on the preset servers, registers and approves a group and a channel from a CLI
peer, and checks that a service request for "ci seed" finds both, before the
app is built. The app still uses the production directory link; the smoke
test is unchanged.

The cabal store cache now uses setup-haskell's store path, with a key prefix
per job, so rebuilds after Haskell changes reuse built dependencies.

This run tests: backend build, relay and directory start, registration and
approval over the preset servers, the service request, and that M2 still
passes.
This commit is contained in:
spacedandy-agents-simplex committed 2026-10-14 11:06:28 +00:00
1 parent 53cf2fe64e
commit fe992744c7
3 files changed
+340 -7

No files matched your search

+96 -7
View File
@@ -17,6 +17,7 @@ env:
GHC_VER: 9.6.3
CABAL_VER: 3.10.2.0
CORE_PKG: pkg-ios-aarch64-swift-json.zip
BACKEND_PKG: ios-sim-backend.zip
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_INSTALL_CLEANUP: 1
@@ -44,6 +45,7 @@ jobs:
key: ios-sim-core-${{ hashFiles('src/**', 'cabal.project', 'simplex-chat.cabal', 'scripts/ci/ios-sim/build-core.sh') }}
- name: Setup Haskell
id: haskell
if: steps.core-cache.outputs.cache-hit != 'true'
uses: simplex-chat/setup-haskell-action@v2
with:
@@ -56,13 +58,12 @@ jobs:
uses: actions/cache/restore@v4
with:
path: |
~/.cabal/store
${{ steps.haskell.outputs.cabal-store }}
dist-newstyle
key: ios-sim-ghc${{ env.GHC_VER }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }}-${{ github.run_id }}
key: ios-sim-core-store-ghc${{ env.GHC_VER }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }}-${{ github.run_id }}
restore-keys: |
ios-sim-ghc${{ env.GHC_VER }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }}-
ios-sim-ghc${{ env.GHC_VER }}-
macos-latest-ghc${{ env.GHC_VER }}-
ios-sim-core-store-ghc${{ env.GHC_VER }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }}-
ios-sim-core-store-ghc${{ env.GHC_VER }}-
- name: Install libraries
if: steps.core-cache.outputs.cache-hit != 'true'
@@ -78,7 +79,7 @@ jobs:
uses: actions/cache/save@v4
with:
path: |
~/.cabal/store
${{ steps.haskell.outputs.cabal-store }}
dist-newstyle
key: ${{ steps.cabal-cache.outputs.cache-primary-key }}
@@ -101,9 +102,74 @@ jobs:
retention-days: 3
if-no-files-found: error
backend:
name: test backend for the app
runs-on: macos-latest
timeout-minutes: 240
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Restore backend
id: backend-cache
uses: actions/cache/restore@v4
with:
path: ~/Downloads/${{ env.BACKEND_PKG }}
key: ios-sim-backend-${{ hashFiles('src/**', 'apps/simplex-chat/**', 'apps/simplex-directory-service/**', 'cabal.project', 'simplex-chat.cabal', 'scripts/ci/ios-sim/build-backend.sh') }}
- name: Setup Haskell
id: haskell
if: steps.backend-cache.outputs.cache-hit != 'true'
uses: simplex-chat/setup-haskell-action@v2
with:
ghc-version: ${{ env.GHC_VER }}
cabal-version: ${{ env.CABAL_VER }}
- name: Restore cabal store
id: cabal-cache
if: steps.backend-cache.outputs.cache-hit != 'true'
uses: actions/cache/restore@v4
with:
path: |
${{ steps.haskell.outputs.cabal-store }}
dist-newstyle
key: ios-sim-backend-store-ghc${{ env.GHC_VER }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }}-${{ github.run_id }}
restore-keys: |
ios-sim-backend-store-ghc${{ env.GHC_VER }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }}-
ios-sim-backend-store-ghc${{ env.GHC_VER }}-
- name: Build backend
if: steps.backend-cache.outputs.cache-hit != 'true'
run: scripts/ci/ios-sim/build-backend.sh ~/Downloads/${{ env.BACKEND_PKG }}
- name: Save cabal store
if: ${{ !cancelled() && steps.backend-cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v4
with:
path: |
${{ steps.haskell.outputs.cabal-store }}
dist-newstyle
key: ${{ steps.cabal-cache.outputs.cache-primary-key }}
- name: Save backend
if: steps.backend-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: ~/Downloads/${{ env.BACKEND_PKG }}
key: ${{ steps.backend-cache.outputs.cache-primary-key }}
- name: Upload backend
uses: actions/upload-artifact@v4
with:
name: ios-sim-backend
path: ~/Downloads/${{ env.BACKEND_PKG }}
compression-level: 0
retention-days: 3
if-no-files-found: error
app:
name: app in the iOS simulator
needs: core
needs: [core, backend]
runs-on: macos-latest
timeout-minutes: 90
steps:
@@ -128,6 +194,16 @@ jobs:
scripts/ci/ios-sim/prepare-libs.sh
scripts/ios/update-pbxproj.sh
- name: Download backend
uses: actions/download-artifact@v4
with:
name: ios-sim-backend
path: ~/Downloads
- name: Start test backend
timeout-minutes: 30
run: scripts/ci/ios-sim/backend.sh start ~/Downloads/${{ env.BACKEND_PKG }} "$RUNNER_TEMP/backend"
- name: Build app
run: |
xcodebuild build -quiet \
@@ -151,6 +227,10 @@ jobs:
- name: Run smoke test
run: scripts/ci/ios-sim/smoke-test.sh "$RUNNER_TEMP/DerivedData/Build/Products/Debug-iphonesimulator/SimpleX.app" "$RUNNER_TEMP/smoke-test"
- name: Stop test backend
if: ${{ !cancelled() }}
run: scripts/ci/ios-sim/backend.sh stop "$RUNNER_TEMP/backend"
- name: Upload smoke test results
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
@@ -159,3 +239,12 @@ jobs:
path: ${{ runner.temp }}/smoke-test
retention-days: 3
if-no-files-found: warn
- name: Upload backend logs
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: ios-sim-backend-logs
path: ${{ runner.temp }}/backend/*.log
retention-days: 3
if-no-files-found: warn
+205
View File
@@ -0,0 +1,205 @@
#!/bin/bash
# Test backend on the app runner: a chat relay and a directory service on the preset servers, with one group
# and one channel registered and approved by a CLI peer, and a search request that must find both.
# Usage:
# scripts/ci/ios-sim/backend.sh start <backend zip> <work dir> writes the directory's short address to <work dir>/directory-link
# scripts/ci/ios-sim/backend.sh stop <work dir> stops the relay and the directory, prints their logs
# The relay and the directory keep running between start and stop.
set -euo pipefail
directory_name="SimpleX Directory"
admin=ci_admin
search_text="ci seed"
# Runs one chat command in a CLI client that stays online for <secs> after it; the output is in $work/out.
chat() {
local db=$1 secs=$2 cmd=$3
shift 3
local status=0
"$bin/simplex-chat" -d "$work/$db" -e "$cmd" -t "$secs" --execute-log all "$@" < /dev/null > "$work/out" 2>&1 || status=$?
cat "$work/out" >> "$work/$db-cli.log"
echo "::group::$db: $cmd"
cat "$work/out"
echo "::endgroup::"
return "$status"
}
admin() {
chat "$admin" "$1" "$2" --user-display-name "$admin"
}
expect_out() {
if ! grep -qE "$1" "$work/out"; then
echo "error: \"$1\" not in the output"
return 1
fi
}
# Each run keeps the client online for a few seconds, which also lets the handshakes it waits for progress.
admin_until() {
local pattern=$1 secs=$2 cmd=$3
local deadline=$((SECONDS + secs))
until admin 5 "$cmd" && grep -qE "$pattern" "$work/out"; do
if [ "$SECONDS" -ge "$deadline" ]; then
echo "error: \"$pattern\" not shown by \"$cmd\" after $secs s"
return 1
fi
done
}
db_query() {
sqlite3 "$work/$1_chat.db" "$2"
}
start_relay() {
nohup "$bin/simplex-chat" -d "$work/relay" --relay --headless --user-display-name ci_relay < /dev/null > "$work/relay.log" 2>&1 &
echo $! > "$work/relay.pid"
local deadline=$((SECONDS + 120))
until grep -qE '^https://[^ ]+/r#' "$work/relay.log"; do
if ! kill -0 "$(cat "$work/relay.pid")" 2> /dev/null || [ "$SECONDS" -ge "$deadline" ]; then
echo "error: the relay did not create its address"
cat "$work/relay.log"
return 1
fi
sleep 1
done
relay_link=$(grep -m 1 -oE '^https://[^ ]+/r#[^ ]+' "$work/relay.log")
echo "relay address: $relay_link"
}
# The address is created here, like the directory tests do, with auto-accept on before the service subscribes to it.
create_directory() {
chat directory 1 "/ad pq_ratchet=on" --create-bot-display-name "$directory_name"
directory_link=$(awk '/Your new chat address is created!/ {getline; getline; print; exit}' "$work/out")
case "$directory_link" in
https://*/a#*) ;;
*)
echo "error: no short directory address in the output"
return 1
;;
esac
chat directory 1 "/auto_accept on"
expect_out "^auto_accept on"
echo "$directory_link" > "$work/directory-link"
echo "directory address: $directory_link"
}
start_directory() {
nohup "$bin/simplex-directory-service" -d "$work/directory" --super-users "2:$admin" < /dev/null > "$work/directory.log" 2>&1 &
echo $! > "$work/directory.pid"
sleep 5
if ! kill -0 "$(cat "$work/directory.pid")" 2> /dev/null; then
echo "error: the directory service exited"
cat "$work/directory.log"
return 1
fi
}
connect_admin() {
admin 10 "/c $directory_link"
expect_out "connection request sent!"
admin_until "Welcome to $directory_name" 180 "/tail @'$directory_name' 10"
local id
id=$(db_query directory "SELECT contact_id FROM contacts WHERE local_display_name = '$admin'")
if [ "$id" != 2 ]; then
echo "error: $admin is contact \"$id\" of the directory, the service was started with --super-users 2:$admin"
return 1
fi
}
# The directory sends each registration to super users as a message with the exact /approve command.
approve() {
local name=$1 reply=$2
admin_until "/approve [0-9]+:$name [0-9]+" 300 "/tail @'$directory_name' 20"
local cmd
cmd=$(grep -oE "/approve [0-9]+:$name [0-9]+" "$work/out" | tail -n 1)
admin 5 "@'$directory_name' $cmd"
admin_until "$reply" 120 "/tail @'$directory_name' 20"
}
seed_group() {
admin 1 "/g ci_group CI seed group"
expect_out "group #ci_group \(CI seed group\) is created"
admin 5 "/a ci_group '$directory_name' admin"
expect_out "invitation to join the group #ci_group sent to"
approve ci_group "Group approved!"
}
seed_channel() {
admin 1 "/relays name=ci_relay $relay_link"
expect_out "^ok$"
local relay_id
relay_id=$(db_query "$admin" "SELECT chat_relay_id FROM chat_relays WHERE display_name = 'ci_relay' AND deleted = 0")
admin 10 "/public group relays=$relay_id #ci_channel CI seed channel"
expect_out "group #ci_channel \(CI seed channel\) is created"
local group_id
group_id=$(db_query "$admin" "SELECT group_id FROM groups WHERE local_display_name = 'ci_channel'")
admin_until "relay id [0-9]+: active" 180 "/_get relays #$group_id"
admin 5 "/share chat #ci_channel @'$directory_name'"
expect_out "link to join channel #ci_channel"
approve ci_channel "Channel approved!"
}
# What the app will send, from a client that never connected to the directory.
probe_search() {
chat probe 1 "/_service_request 1 $directory_link timeout=30 {\"type\":\"search\",\"searchText\":\"$search_text\"}" --user-display-name ci_probe
expect_out '"displayName":"ci_group"'
expect_out '"displayName":"ci_channel"'
echo "search for \"$search_text\" found ci_group and ci_channel"
}
dump_directory() {
echo "::group::directory registrations"
db_query directory "SELECT group_id, group_reg_status FROM sx_directory_group_regs" || true
echo "::endgroup::"
}
start() {
bin=$work/bin
mkdir -p "$bin"
unzip -o -q "$1" -d "$bin"
"$bin/simplex-chat" --version
"$bin/simplex-directory-service" --version
start_relay
create_directory
start_directory
trap dump_directory EXIT
connect_admin
seed_group
seed_channel
probe_search
}
stop() {
local p
for p in directory relay; do
if [ -f "$work/$p.pid" ]; then
kill -INT "$(cat "$work/$p.pid")" 2> /dev/null || true
fi
done
sleep 3
for p in directory relay; do
echo "::group::$p log"
cat "$work/$p.log" 2> /dev/null || true
echo "::endgroup::"
done
}
case "${1:-}" in
start)
work=$3
mkdir -p "$work"
start "$2"
;;
stop)
work=$2
stop
;;
*)
echo "usage: $0 start <backend zip> <work dir> | stop <work dir>"
exit 1
;;
esac
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
# Builds the CLI and the directory service that the app job runs as its test backend.
# Usage: scripts/ci/ios-sim/build-backend.sh <output zip>
# They need the server-capable configuration (no client_library), as build.yml's macOS job builds the CLI.
set -euo pipefail
mkdir -p "$(dirname "$1")"
out="$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
openssl_dir=$(brew --prefix openssl@3)
ghc --version
cabal --version
cat > cabal.project.local <<EOF
ignore-project: False
package simplexmq
extra-include-dirs: $openssl_dir/include
extra-lib-dirs: $openssl_dir/lib
package direct-sqlcipher
flags: +openssl
extra-include-dirs: $openssl_dir/include
extra-lib-dirs: $openssl_dir/lib
EOF
cabal build -j exe:simplex-chat exe:simplex-directory-service
pkg=$(mktemp -d)
for exe in simplex-chat simplex-directory-service; do
cp "$(cabal list-bin "exe:$exe")" "$pkg"
done
otool -L "$pkg"/*
rm -f "$out"
(cd "$pkg" && zip -9 "$out" *)
ls -la "$out"