-
Notifications
You must be signed in to change notification settings - Fork 0
339 lines (328 loc) · 14.5 KB
/
Copy pathci.yml
File metadata and controls
339 lines (328 loc) · 14.5 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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
name: CI
on:
push:
# develop entra aqui porque, sem PR develop → main aberta, commit nenhum
# de develop rodava CI — foi assim que 74d89c0 ficou sem um run sequer
# (FINDING-2/FINDING-4 da avaliação de 2026-09-28).
branches: [main, master, develop]
pull_request:
# Para gerar o dado sem precisar abrir PR: `gh workflow run CI --ref <branch>`.
workflow_dispatch:
# Um run vivo por PR: push novo na mesma PR cancela o anterior, que já não
# descreve a ponta dela — o android-e2e sozinho chega a 60 min (FINDING-7).
# Push e dispatch são agrupados pelo SHA, então nenhum commit de main/develop
# perde o próprio resultado para o commit seguinte.
concurrency:
group: ci-${{ github.event_name }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.sha }}
cancel-in-progress: true
# Todos os jobs fixam ubuntu-24.04 em vez de ubuntu-latest: o label migra
# para Ubuntu 26 a partir de 2026-10-19 sem aviso no dia, e o android-e2e
# (KVM, udev) e os builds Android (NDK/CMake em cache) são os mais sensíveis à
# troca. A migração é feita de propósito, depois de um ensaio — ver a Seção 6
# de docs/ci-audit/2026-09-28-avaliacao-ci-develop.md.
jobs:
# Guarda do próprio workflow: actionlint e as invariantes que a avaliação de
# 2026-09-28 mostrou derivarem sem ninguém ver (jobs × documentação,
# gatilhos, concorrência, versões de ações, imagem do runner).
workflow-lint:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- name: actionlint
run: docker run --rm -v "$PWD:/repo" --workdir /repo rhysd/actionlint:1.7.12 -color
- name: Invariantes do CI
run: |
python3 -c 'import yaml' 2>/dev/null || { sudo apt-get update && sudo apt-get install -y python3-yaml; }
./scripts/qa/ci_invariants.sh
serverpod-backend:
runs-on: ubuntu-24.04
defaults:
run:
working-directory: backend
env:
# Senha do Postgres efêmero deste job. NÃO é um segredo: o banco existe só
# dentro do runner, some ao fim do job e nunca é alcançável de fora.
#
# O que importa é que este valor não seja compartilhado com mais nada.
# Antes era a MESMA senha do config/passwords.yaml local — que o
# .gitignore protege e que é idêntica nos blocos development, test,
# staging e production. O segredo protegido pelo .gitignore estava, na
# prática, publicado aqui.
#
# Declarado uma vez e referenciado nos dois lugares: o serviço abaixo e a
# geração do passwords.yaml. Eram dois literais mantidos iguais à mão, e
# divergir fazia a suíte morrer sem log nenhum.
CI_POSTGRES_PASSWORD: ci-ephemeral-not-a-secret
services:
# Banco que o harness de teste do Serverpod espera, conforme
# sinalacs_server/config/test.yaml. O harness aplica as migrações sozinho
# e reverte o banco a cada caso, então não há passo de schema nem de seed —
# é o que substitui os quatro psql enumerados à mão do job backend.
postgres:
image: postgres:15-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${{ env.CI_POSTGRES_PASSWORD }}
POSTGRES_DB: sinalacs_test
ports:
- 9090:5432
options: >-
--health-cmd="pg_isready -U postgres -d sinalacs_test"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
- uses: actions/checkout@v7
- uses: dart-lang/setup-dart@v1
with:
sdk: 3.12.2
- run: dart pub get
- run: dart analyze
# config/passwords.yaml é gitignored (guarda segredos de dev), então não
# existe num checkout limpo — inclusive o do runner. Sem ele o harness do
# Serverpod não carrega a senha do banco, falha ainda no carregamento de
# config e chama exit(1); como o exit() do Dart não dá flush no stdout, a
# mensagem de erro se perde e a falha fica totalmente silenciosa (exit 1,
# zero linhas de log).
#
# Localmente, quem gera este arquivo é scripts/dev/bootstrap_env.sh.
- name: Cria config/passwords.yaml para o harness de teste
run: |
printf "test:\n database: '%s'\n" "$CI_POSTGRES_PASSWORD" \
> sinalacs_server/config/passwords.yaml
# Suíte completa: os herméticos de test/unit (fakes das interfaces
# AlertPublisher/AlertStore e afins, sem banco) mais os de test/integration,
# que sobem o servidor em processo pelo harness e exercitam os endpoints
# tipados. A contagem exata está na saída deste job — de propósito, não
# aqui: número em comentário envelhece e alguém o cita como se fosse atual.
- run: dart test
working-directory: backend/sinalacs_server
backend-docker-build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
# Imagem do servidor Serverpod, a mesma que o docker-compose constrói.
# O contexto é a raiz do workspace, porque o build precisa do pubspec.lock
# compartilhado entre sinalacs_server e sinalacs_client.
- name: Build Serverpod Docker image
run: docker build -f backend/sinalacs_server/Dockerfile backend
patient-app:
runs-on: ubuntu-24.04
defaults:
run:
working-directory: apps/patient
steps:
- uses: actions/checkout@v7
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.44.8'
- run: flutter pub get
# apps/patient/pubspec.yaml declara assets/certs/ — a pasta das CAs de
# desenvolvimento, gitignored e produzida por scripts/dev/sync_dev_ca.sh.
# Aquele script exige a stack de pé (valida a CA contra a folha que ela
# assina), e este job não tem Docker nem stack; então a pasta é criada
# VAZIA, que é o que basta para o que este job roda: o `flutter analyze`
# só confere se o diretório existe. Sem ele, o analyze sai 1 num clone
# limpo (`asset_directory_does_not_exist`) — é o que acontece aqui desde
# que a declaração entrou no pubspec.
#
# VAZIA NÃO SERVE PARA BUILDAR. `flutter build apk` e `flutter test` não
# reclamam do diretório ausente: saem verdes, e o APK vai sem nenhum .crt
# dentro — medido, com o diretório ausente e com ele vazio. Com o
# diretório ausente ainda sai uma linha "Error: unable to find directory
# entry in pubspec.yaml", que o build imprime e ignora; com o diretório
# vazio essa linha some, e o build fica silencioso de vez. Quem
# acrescentar um build a este job precisa das CAs de verdade:
# `docker compose up` e depois ./scripts/dev/sync_dev_ca.sh, que é quem
# copia certificado, não pasta.
- name: Cria o diretório assets/certs/ que o pubspec declara
run: mkdir -p assets/certs
- run: flutter analyze
- run: flutter test
acs-app:
runs-on: ubuntu-24.04
defaults:
run:
working-directory: apps/acs
steps:
- uses: actions/checkout@v7
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.44.8'
- run: flutter pub get
# Mesmo motivo do job patient-app, e o comentário completo está lá:
# assets/certs/ é gitignored e vem do scripts/dev/sync_dev_ca.sh, que
# precisa da stack de pé. O acs-app já falhava aqui por causa disso antes
# de o app do paciente declarar a mesma pasta.
- name: Cria o diretório assets/certs/ que o pubspec declara
run: mkdir -p assets/certs
- run: flutter analyze
- run: flutter test
admin-app:
runs-on: ubuntu-24.04
defaults:
run:
working-directory: apps/admin
steps:
- uses: actions/checkout@v7
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.44.8'
- run: flutter pub get
- run: flutter analyze
- run: flutter test
coverage-report:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: dart-lang/setup-dart@v1
with:
sdk: 3.12.2
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.44.8'
- name: Dependência nativa do SQLCipher/SQLite no Linux
run: sudo apt-get update && sudo apt-get install -y libsqlite3-0
- name: Bootstrap do ambiente local de testes
run: ./scripts/dev/bootstrap_env.sh
- name: Banco de teste do backend
run: docker compose --profile test up -d postgres-test
- name: Mede cobertura dos quatro pacotes
run: ./scripts/qa/measure_coverage.sh
- name: Derruba banco de teste
if: always()
run: docker compose --profile test down
- uses: actions/upload-artifact@v7
if: always()
with:
name: coverage-reports
path: build/qa/coverage
android-e2e:
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
steps:
- uses: actions/checkout@v7
# Sem isto o emulador roda por emulação de CPU em software: medido, o log
# dizia "ProbeKVM: This user doesn't have permissions to use KVM", o boot
# levava ~12 min e cada `adb install` de 45 a 95 s. O runner tem
# /dev/kvm, só não para o usuário do job — a regra do udev abre o
# dispositivo. É o passo documentado pelo android-emulator-runner.
- name: Habilita KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '17'
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.44.8'
cache: true
# O 1º `assembleDebug` levava 311 s sem cache, 24 deles instalando o
# CMake do NDK. Chave pelos pubspec.lock e scripts Gradle dos dois apps
# que rodam no emulador — mesmo padrão do job admin-android-build.
- uses: actions/cache@v6
with:
path: |
~/.pub-cache
~/.gradle/caches
~/.gradle/wrapper
/usr/local/lib/android/sdk/cmake
key: e2e-deps-${{ hashFiles('apps/patient/pubspec.lock', 'apps/acs/pubspec.lock', 'apps/patient/android/**/*.gradle.kts', 'apps/acs/android/**/*.gradle.kts', 'apps/*/android/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: e2e-deps-
# Snapshot do AVD já inicializado: sem ele, todo run paga o primeiro
# boot a frio. Os parâmetros do emulador precisam ser os mesmos aqui e
# no step que roda os testes, ou o snapshot é descartado.
- uses: actions/cache@v6
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-36-x86_64-pixel_7-ubuntu-24.04
- name: Gera o snapshot do AVD para o cache
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 36
arch: x86_64
profile: pixel_7
force-avd-creation: false
disable-animations: false
emulator-options: -no-window -no-audio -gpu swiftshader_indirect -no-boot-anim
script: echo "snapshot do AVD gerado para o cache"
- name: Bootstrap da stack local
run: ./scripts/dev/bootstrap_env.sh
# O que não depende do emulador sai de dentro do step dele: o
# `docker compose up --build` e os `flutter pub get` do e2e.sh viram
# cache hit (os `pub get` sequenciais levavam ~4 min lá dentro).
- name: Pré-build da stack e dependências dos apps
run: |
# `wait` sem argumento sempre sai 0; esperar cada PID propaga a falha.
docker compose build & p1=$!
(cd apps/patient && flutter pub get) & p2=$!
(cd apps/acs && flutter pub get) & p3=$!
wait $p1 && wait $p2 && wait $p3
- name: E2E no emulador Android
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 36
arch: x86_64
profile: pixel_7
force-avd-creation: false
disable-animations: true
emulator-options: -no-window -no-audio -gpu swiftshader_indirect -no-snapshot-save -no-boot-anim
# Uma linha só: a action roda cada linha de "script" como um `sh -c`
# separado, então lógica multi-linha (if/fi, set -a + source .env)
# precisa viver num script de verdade — ver run_android_e2e.sh.
# Roda só o smoke de cada app (e2e.sh --emulator, sem --full).
script: ./scripts/qa/run_android_e2e.sh
# O volume do Postgres fica no workspace com modo 700 e dono do
# container. O pós-passo do flutter-action faz hashFiles('**/pubspec.lock')
# sobre o workspace inteiro, não consegue ler pg_data/ e derruba o job
# depois de o E2E ter passado — medido no run 36494654391.
- name: Remove pg_data/ do workspace
if: always()
run: sudo rm -rf pg_data
- uses: actions/upload-artifact@v7
if: always()
with:
name: android-e2e-metrics
path: build/qa/latency
# Único job do repositório que executa o Gradle. `flutter analyze` e
# `flutter test` não enxergam namespace inconsistente, AGP/Kotlin
# incompatíveis, MainActivity em pacote errado nem merge de manifest
# quebrado — erros que só apareceriam quando alguém tentasse compilar.
#
# É o app admin e não o ACS porque `apps/acs/android/app/build.gradle.kts`
# tem a guarda que faz o build falhar sem SINALACS_MQTT_PASSWORD: cobri-lo
# aqui exigiria compilar um APK que a própria guarda declara inútil.
admin-android-build:
runs-on: ubuntu-24.04
defaults:
run:
working-directory: apps/admin
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '17'
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.44.8'
# Sem cache, cada execução rebaixa ~200MB de Gradle e AGP.
- uses: actions/cache@v6
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-admin-${{ hashFiles('apps/admin/android/**/*.gradle.kts', 'apps/admin/android/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: gradle-admin-
- run: flutter pub get
- run: flutter build apk --debug