-
Notifications
You must be signed in to change notification settings - Fork 3
174 lines (160 loc) · 7.29 KB
/
tests.yml
File metadata and controls
174 lines (160 loc) · 7.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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