Skip to content

Model B: DIRECT messages retry until delivered (proof-wait timeout) #274

Model B: DIRECT messages retry until delivered (proof-wait timeout)

Model B: DIRECT messages retry until delivered (proof-wait timeout) #274

Workflow file for this run

name: Tests
on:
pull_request:
branches: [main]
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
# macos-26 carries Xcode 26.x, which the embedded-Python (Columba) scheme
# needs — the Python.framework clang module map fails to resolve under
# Xcode 16.x ("module 'Python' … not defined in any loaded module map"),
# which is what devs build with locally (26.x). See the Select Xcode step.
runs-on: macos-26
steps:
- uses: actions/checkout@v4
- name: Clone sibling packages
run: |
git clone https://github.com/torlando-tech/reticulum-swift.git ../reticulum-swift
git clone https://github.com/torlando-tech/LXMF-swift.git ../LXMF-swift
git clone https://github.com/torlando-tech/LXST-swift.git ../LXST-swift
# The Python-backend (`Columba`) scheme links Python.xcframework + the
# bundled wheels; without these the build phase that copies them fails.
# Fetch them before any xcodebuild step (pinned deps → reproducible).
- name: Fetch Python framework + wheels
run: |
support/fetch-python.sh
support/fetch-wheels.sh
- name: Select Xcode
run: |
echo "Available Xcodes:"; ls -d /Applications/Xcode_*.app 2>/dev/null || true
# Pick the newest Xcode on the runner (was pinned to 16.x, which can't
# build the Python scheme — see runs-on comment). Version-sort so 26.x
# wins over 16.x regardless of exact point release.
XCODE=$(ls -d /Applications/Xcode_*.app 2>/dev/null | sort -V | tail -1)
echo "Using $XCODE"
sudo xcode-select -s "$XCODE"
xcodebuild -version
# Resolve the local Swift packages (reticulum-swift / LXMF-swift / LXST-swift)
# once, up front. Without this the `xcodebuild test` action can intermittently
# fail to resolve LXMFSwift/ReticulumSwift for the ColumbaAppTests target even
# though the app build steps resolved them fine — a fresh-runner resolution
# race. One explicit resolve makes every later build/test step reuse it.
- name: Resolve Swift packages
run: |
# Pre-warm both schemes' package deps (same sibling packages, same
# .xcodeproj, but be explicit so the Swift-native build benefits too).
xcodebuild -resolvePackageDependencies \
-project Columba.xcodeproj \
-scheme Columba
xcodebuild -resolvePackageDependencies \
-project Columba.xcodeproj \
-scheme Columba-Swift
- name: Find simulator
id: sim
run: |
DEVICE_ID=$(xcrun simctl list devices available -j \
| python3 -c "import sys,json; devs=json.load(sys.stdin)['devices']; print(next(d['udid'] for rt in devs for d in devs[rt] if 'iPhone' in d['name']))")
echo "device_id=$DEVICE_ID" >> "$GITHUB_OUTPUT"
echo "Using simulator: $DEVICE_ID"
- name: Build (Python backend — default)
run: |
xcodebuild build \
-project Columba.xcodeproj \
-scheme Columba \
-sdk iphonesimulator \
-destination "id=${{ steps.sim.outputs.device_id }}" \
CODE_SIGN_IDENTITY=- \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=YES \
DEVELOPMENT_TEAM="" \
PROVISIONING_PROFILE_SPECIFIER=""
# Verify the native reticulum-swift/LXMF-swift backend variant also builds
# (COLUMBA_BACKEND_SWIFT). The UI/AppServices are backend-agnostic, so this
# guards against the Swift backend or its seam drifting out of compile.
- name: Build (Swift-native backend)
run: |
xcodebuild build \
-project Columba.xcodeproj \
-scheme Columba-Swift \
-sdk iphonesimulator \
-destination "id=${{ steps.sim.outputs.device_id }}" \
CODE_SIGN_IDENTITY=- \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=YES \
DEVELOPMENT_TEAM="" \
PROVISIONING_PROFILE_SPECIFIER=""
- name: Build and run tests
run: |
xcodebuild test \
-project Columba.xcodeproj \
-scheme Columba \
-sdk iphonesimulator \
-destination "id=${{ steps.sim.outputs.device_id }}" \
-only-testing:ColumbaAppTests \
-resultBundlePath TestResults.xcresult \
-enableCodeCoverage YES \
CODE_SIGN_IDENTITY=- \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=YES \
DEVELOPMENT_TEAM="" \
PROVISIONING_PROFILE_SPECIFIER=""
- name: Export coverage report (function-level lcov)
run: |
set -euo pipefail
# Coverage on iOS app projects with -resultBundlePath lives
# inside the .xcresult bundle. The only toolchain path that
# reads it cleanly under Xcode 16 is
# `xccov view --report --json`, which exposes function-level
# granularity (no per-line `DA:` records). Per-line data
# would require either a working `xccov view --file` (broken
# against both the bare .xcresult and `xcresulttool export
# coverage`'s output: "Error: unrecognized file format") or
# a raw .profdata (Xcode embeds it inside the xcresult, not
# in DerivedData). codecov's `patch` check is configured
# `informational: true` so the absence of line records is
# advisory rather than blocking.
XCRESULT=TestResults.xcresult
if [ ! -d "$XCRESULT" ]; then
echo "$XCRESULT does not exist" >&2
exit 1
fi
mkdir -p coverage
xcrun xccov view --report --json "$XCRESULT" > coverage/coverage.json
python3 <<'PY'
import json, sys
data = json.load(open('coverage/coverage.json'))
out = []
file_count = 0
for target in data.get('targets', []):
for f in target.get('files', []):
path = f['path']
# Filter out tests + third-party packages so the
# report shows first-party app code only.
if 'Tests' in path or 'SourcePackages' in path or 'DerivedData' in path:
continue
out.append(f"SF:{path}")
for fn in f.get('functions', []):
ln = fn.get('lineNumber', 0)
hits = fn.get('executionCount', 0)
name = fn.get('name', '?')
out.append(f"FN:{ln},{name}")
out.append(f"FNDA:{hits},{name}")
out.append("end_of_record")
file_count += 1
with open('coverage/coverage.lcov', 'w') as fh:
fh.write("\n".join(out) + "\n")
print(f"Wrote {file_count} files, {len(out)} total lcov lines")
if file_count == 0:
print("ERROR: zero files — coverage extraction is broken", file=sys.stderr)
sys.exit(1)
PY
head -5 coverage/coverage.lcov
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: coverage/coverage.lcov
fail_ci_if_error: false