-
Notifications
You must be signed in to change notification settings - Fork 16
268 lines (258 loc) · 13.2 KB
/
Copy pathci.yml
File metadata and controls
268 lines (258 loc) · 13.2 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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
name: CI
# Runs on pull requests targeting main and develop. Both branches are
# protected and cannot be pushed to directly, so every change lands through a
# PR that these checks gate. workflow_dispatch allows a manual run from the
# Actions tab.
on:
pull_request:
branches: [main, develop]
workflow_dispatch:
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# ---- Required, deterministic gate -------------------------------------
# The core Dart suite on every host OS. Running the suite on Linux, Windows
# and macOS executes each platform class's OS-specific code natively (#77).
# Live-network tests are excluded (`-x live`) so the gate stays reproducible.
core:
name: core (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dart-lang/setup-dart@v1
# --no-example: this dart-only runner has no Flutter SDK, and the bundled
# example/ is a Flutter package. Skipping its resolution keeps the gate
# clean (the example is built by the macOS ios-swift job, not here).
- run: dart pub get --no-example
- run: dart analyze --fatal-infos
# `-x toybox -x legacy` excludes the Android flag-acceptance checks, which
# need an installed toybox `ping` / a built legacy iputils `ping`/`ping6`
# absent on these hosts; they run in the dedicated `android-toybox` /
# `android-legacy` jobs instead (§spec:android-ci-gate,
# §spec:android-legacy-ci-gate). The fixture-driven Android output-parity
# assertions for BOTH dialects are untagged pure-Dart and DO run here.
- run: dart test -x live -x toybox -x legacy
# ---- Required, deterministic gate: Android flag acceptance ------------
# §spec:android-ci-gate — proves a REAL stock toybox `ping` (Android's ping)
# accepts every flag dart_ping sends on Android, so the #100 exit-code
# regression (a flag toybox rejects at argument parsing) cannot silently
# return. The test sources the exact Android argument list from
# `PingLinux.params` and runs it against an installed toybox `ping` targeting
# LOOPBACK — it exercises toybox's argument PARSER only, with no
# external-network ICMP round trip, so it is deterministic on the hosted
# runner (consistent with the live suite staying out of CI). The paired
# output-parity fixtures run in the `core` job (untagged pure Dart). Runs on
# PRs to both `main` and `develop` by construction — same workflow triggers.
android-toybox:
name: android flags (toybox)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dart-lang/setup-dart@v1
- run: dart pub get --no-example
- name: Install stock toybox ping
run: sudo apt-get update && sudo apt-get install -y toybox
# Fail LOUD if toybox — or specifically its `ping` applet — is missing,
# rather than letting the tagged test be silently skipped or pass falsely.
# toybox is a multicall binary: `command -v toybox` only proves the binary
# is on PATH, not that this build includes `ping`. A toybox compiled
# without `ping` would make `toybox ping ...` print `Unknown command`,
# which the flag-acceptance test's rejection regex does NOT match — a false
# green. Asserting `ping` is in the applet list closes that gap, so a green
# check means a real toybox `ping` parsed our args.
- name: Verify toybox and its ping applet are present
run: |
command -v toybox
toybox | grep -qw ping || {
echo "installed toybox has no 'ping' applet"; exit 1;
}
- run: dart test -x live -t toybox
# ---- Required, deterministic gate: Android flag acceptance (legacy) ----
# §spec:android-legacy-ci-gate — the sibling of `android-toybox` for the OTHER
# mainstream Android dialect: the legacy AOSP `external/ping` / `ping6`
# (2008-vintage iputils, a SPLIT `ping` (IPv4-only) + separate `ping6`). It
# proves a REAL legacy binary accepts the flagless IPv4 / `ping6` IPv6 /
# interface-bound argv dart_ping produces for a legacy image, and — the control
# — that reintroducing the `-4`/`-6` family flag WOULD be rejected (mirroring
# the `-O`/`-n` toybox control), so Batch 1's dialect selection is verified
# against the parser that rejects it, not merely asserted in code. Like the
# toybox job it targets LOOPBACK — argument PARSER only, no external-network
# ICMP round trip — and runs on PRs to both `main` and `develop` by
# construction. The paired output-parity fixtures (`legacy_*` under
# test/fixtures/android/) run in the `core` job (untagged pure Dart).
#
# There is no apt package that both accepts iputils flags (`-W`/`-i`/`-t`/`-c`)
# AND rejects `-4`/`-6`: modern iputils accepts `-4`, and GNU inetutils uses a
# different flag grammar. So the job BUILDS the exact legacy lineage from
# source — iputils tag `s20101006`, whose option string
# (`…c:…i:…t:…W:…`, no `4`/`6`) matches the AOSP `external/ping` — and a guard
# step fails LOUD if the built binary is not genuinely legacy, so a wrong
# tag/build turns the job red instead of falsely green.
android-legacy:
name: android flags (legacy external/ping)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dart-lang/setup-dart@v1
- run: dart pub get --no-example
- name: Build legacy iputils ping/ping6 from source (tag s20101006)
run: |
set -euo pipefail
sudo apt-get update
# libssl-dev provides openssl/md5.h, which this vintage's ping6 needs
# for its ICMP node-information query (-N) support.
sudo apt-get install -y build-essential libssl-dev
git clone --depth 1 --branch s20101006 \
https://github.com/iputils/iputils /tmp/iputils-legacy
# Pin by commit SHA: a Git tag is mutable, so verify the shallow clone
# resolved to the exact expected commit before building it. A moved or
# compromised tag fails the job here instead of silently compiling
# attacker-controlled source (keeps the gate reproducible).
#
# NB: `s20101006` is an ANNOTATED tag. `git ls-remote` reports the tag
# OBJECT sha (642417d5…), but `git clone --branch <tag>` leaves HEAD
# detached at the COMMIT the tag peels to — which is what `rev-parse
# HEAD` returns and what we must compare against. Pinning the tag-object
# sha here would make this guard fail on every run.
test "$(git -C /tmp/iputils-legacy rev-parse HEAD)" \
= "2384927c0ac1bb3ad21966350644d08faadb7a76" \
|| { echo "iputils tag s20101006 no longer resolves to the pinned commit"; exit 1; }
make -C /tmp/iputils-legacy ping ping6
echo "LEGACY_PING_BIN=/tmp/iputils-legacy/ping" >> "$GITHUB_ENV"
echo "LEGACY_PING6_BIN=/tmp/iputils-legacy/ping6" >> "$GITHUB_ENV"
- name: Verify the built binaries are genuinely legacy
run: |
set -u
# A legacy binary REJECTS the family flag it predates. If it does not,
# it is not the legacy dialect and the flag-acceptance gate below would
# be a false green — so fail loud here instead.
v4="$("$LEGACY_PING_BIN" -4 2>&1 || true)"
case "$v4" in
*"invalid option"*) : ;;
*) echo "built ping does not reject -4 — not the legacy dialect: $v4"; exit 1 ;;
esac
v6="$("$LEGACY_PING6_BIN" -6 2>&1 || true)"
case "$v6" in
*"invalid option"*) : ;;
*) echo "built ping6 does not reject -6 — not the legacy dialect: $v6"; exit 1 ;;
esac
# ...and it must ACCEPT the flagless Android argv at argument parsing (a
# socket-permission error at the network stage is expected and fine).
ok="$("$LEGACY_PING_BIN" -W 2 -i 1 -t 64 -c 1 127.0.0.1 2>&1 || true)"
case "$ok" in
*"invalid option"*|*"option requires an argument"*)
echo "built ping rejected the flagless Android argv: $ok"; exit 1 ;;
esac
- run: dart test -x live -t legacy
# ---- Required on the RELEASE PATH only --------------------------------
# Publishability gate (§spec:publish-dry-run-check). `dart pub publish
# --dry-run` validates the package archive without uploading anything and
# without pub.dev credentials, so it runs deterministically on the Linux host
# with no iOS SDK and no network publish step. It exits non-zero on any
# publish error the client reports, so an archive/pubspec/structural break
# turns this check red on the PR that introduced it instead of surfacing at
# `dart pub publish`. (Note: on Dart 3.12.x the experimental
# "Hook files are experimental and <file> is not allowed yet" restriction is
# enforced server-side at publish, not by the client dry run — so a stray
# non-entrypoint file in hook/ is caught at `dart pub publish`, not here.)
#
# `if: github.base_ref == 'main'` scopes this to the develop->main release
# path (§spec:ci-develop) — it does NOT run on feature PRs into develop, so
# the per-feature gate is unchanged. One workflow file, gated by target
# branch, keeps a single source of truth.
publish-dry-run:
name: publish dry-run (release gate)
if: github.base_ref == 'main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dart-lang/setup-dart@v1
# --no-example mirrors the `core` job: the Flutter example/ is not
# resolvable on a Flutter-less runner, and the dry run validates the root
# dart_ping package archive, not the example subpackage.
- run: dart pub get --no-example
- run: dart pub publish --dry-run
# The Swift ICMP framing/parse suite. Requires a macOS runner with Xcode and
# an iOS simulator (#74). Building the example first generates the Flutter
# artifacts the Runner target links against.
#
# The iOS Dart event-mapping + bindings suite is no longer a separate job: it
# now lives in test/ios_*.dart and runs under the `core` matrix via
# `dart test -x live` (consolidation #28 — single package).
ios-swift:
name: ios-swift (macos)
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
- run: flutter --version
- name: flutter pub get (example)
working-directory: example
run: flutter pub get
- name: Build example for the simulator (generates Flutter artifacts)
working-directory: example
run: flutter build ios --simulator --no-codesign
- name: Run RunnerTests via xcodebuild
working-directory: example/ios
run: |
set -o pipefail
# Pick whatever iPhone simulator the runner image actually ships,
# rather than hard-coding a model that may not exist.
DEVICE=$(xcrun simctl list devices available \
| grep -oE 'iPhone [0-9]+( Pro( Max)?)?' | head -1)
DEVICE=${DEVICE:-iPhone 15}
echo "Using simulator: $DEVICE"
xcodebuild test \
-workspace Runner.xcworkspace \
-scheme Runner \
-destination "platform=iOS Simulator,name=$DEVICE" \
-only-testing:RunnerTests \
CODE_SIGNING_ALLOWED=NO
# ---- Informational, non-gating ----------------------------------------
# Coverage report for dart_ping, published as a build artifact and a job
# summary. There is no coverage-threshold gate (by decision); this surfaces
# the numbers without blocking merges. Live tests are excluded so the number
# is reproducible — note `base_ping`'s spawn/cleanup path is therefore not
# measured here (hosted Linux/Windows runners block ICMP anyway); it is
# exercised by the local/manual live suite.
coverage:
name: coverage (report only)
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: dart-lang/setup-dart@v1
- name: Core coverage
run: |
dart pub get --no-example
dart test -x live -x toybox -x legacy --coverage=coverage
dart run coverage:format_coverage \
--lcov --in=coverage --out=coverage/lcov.info --report-on=lib
- name: Summarize
run: |
summarize() {
awk '
/^LF:/ { sub("LF:",""); f += $0 }
/^LH:/ { sub("LH:",""); h += $0 }
END { if (f > 0) printf "%.1f%% (%d/%d lines)\n", 100*h/f, h, f }
' "$1"
}
{
echo "## Coverage"
echo ""
echo "- **dart_ping**: $(summarize coverage/lcov.info)"
} >> "$GITHUB_STEP_SUMMARY"
# NOTE: the live-network suite (`dart test -t live` — real `ping` to external
# hosts) is intentionally NOT run in CI. Hosted Linux/Windows runners block
# unprivileged ICMP, so those tests can never pass there, and live round
# trips are non-deterministic everywhere. They stay tagged `live`, run by
# default locally, and remain the manual acceptance path (§spec:ci).