Skip to content

Commit 0348200

Browse files
Merge pull request #100 from CodandoTV/fix/workflow-printf-and-pat
feat: smart test generation — only modified .kt files + kotlin guard + coverage table
2 parents 2cb9918 + 1a1a44f commit 0348200

2 files changed

Lines changed: 207 additions & 30 deletions

File tree

.github/workflows/generate-tests.yml

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,39 @@ jobs:
4848
ref: ${{ steps.ctx.outputs.head_sha }}
4949
fetch-depth: 0
5050

51-
# ── 3. Python ─────────────────────────────────────────────────────────────
51+
# ── 3. Verifica se há arquivos .kt modificados ───────────────────────────
52+
- name: Check for Kotlin file changes
53+
id: kt_guard
54+
run: |
55+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
56+
echo "has_kotlin=true" >> "$GITHUB_OUTPUT"
57+
echo "workflow_dispatch — pulando verificação de .kt"
58+
else
59+
KT=$(git diff --name-only origin/main...HEAD | grep '\.kt$' || true)
60+
if [ -n "$KT" ]; then
61+
echo "has_kotlin=true" >> "$GITHUB_OUTPUT"
62+
echo "Arquivos .kt detectados:"
63+
echo "$KT"
64+
else
65+
echo "has_kotlin=false" >> "$GITHUB_OUTPUT"
66+
echo "Nenhum arquivo .kt modificado. Pulando geração de testes."
67+
fi
68+
fi
69+
70+
# ── 4. Python ─────────────────────────────────────────────────────────────
5271
- name: Set up Python
72+
if: steps.kt_guard.outputs.has_kotlin == 'true'
5373
uses: actions/setup-python@v5
5474
with:
5575
python-version: "3.11"
5676

5777
- name: Install Python dependencies
78+
if: steps.kt_guard.outputs.has_kotlin == 'true'
5879
run: pip install anthropic
5980

60-
# ── 4. Escaneia craftd-core buscando arquivos sem cobertura ──────────────
81+
# ── 5. Escaneia craftd-core buscando arquivos sem cobertura ──────────────
6182
- name: Find uncovered Kotlin files in craftd-core
83+
if: steps.kt_guard.outputs.has_kotlin == 'true'
6284
id: changed
6385
run: |
6486
OVERRIDE="${{ github.event.inputs.override_files }}"
@@ -69,11 +91,33 @@ jobs:
6991
| grep -v "Test\.kt" | wc -l | tr -d ' ')
7092
7193
if [ -n "$OVERRIDE" ]; then
94+
# Modo manual com arquivos específicos
7295
UNCOVERED=$(echo "$OVERRIDE" | tr ' ' '\n' | grep -v "^$" || true)
7396
UNCOVERED_COUNT=$(echo "$UNCOVERED" | grep -v "^$" | wc -l | tr -d ' ')
7497
echo "Modo override — arquivos informados manualmente:"
98+
99+
elif [ "${{ github.event_name }}" = "workflow_run" ]; then
100+
# Modo automático (PR) — apenas .kt modificados no PR sem teste ainda
101+
echo "Modo PR — apenas arquivos .kt modificados neste PR sem cobertura..."
102+
UNCOVERED=""
103+
while IFS= read -r SRC; do
104+
# Só considera arquivos dentro do craftd-core
105+
if echo "$SRC" | grep -q "android_kmp/craftd-core/src/"; then
106+
TEST=$(echo "$SRC" \
107+
| sed 's|src/commonMain/kotlin/|src/test/java/|' \
108+
| sed 's|src/androidMain/kotlin/|src/test/java/|' \
109+
| sed 's|\.kt$|Test.kt|')
110+
if [ ! -f "$TEST" ]; then
111+
UNCOVERED="$UNCOVERED"$'\n'"$SRC"
112+
fi
113+
fi
114+
done < <(git diff --name-only origin/main...HEAD | grep '\.kt$' | grep -v "Test\.kt" | sort)
115+
UNCOVERED=$(echo "$UNCOVERED" | grep -v "^$" || true)
116+
UNCOVERED_COUNT=$(echo "$UNCOVERED" | grep -v "^$" | wc -l | tr -d ' ')
117+
75118
else
76-
echo "Scan completo — buscando arquivos sem cobertura em craftd-core..."
119+
# Modo workflow_dispatch sem override — scan completo
120+
echo "Scan completo — buscando todos os arquivos sem cobertura em craftd-core..."
77121
UNCOVERED=""
78122
while IFS= read -r SRC; do
79123
TEST=$(echo "$SRC" \
@@ -93,11 +137,11 @@ jobs:
93137
echo "$UNCOVERED"
94138
95139
COVERED_BEFORE=$((TOTAL - UNCOVERED_COUNT))
96-
COVERED_AFTER=$TOTAL
140+
COVERED_AFTER=$((COVERED_BEFORE + UNCOVERED_COUNT))
97141
98142
if [ "$TOTAL" -gt "0" ]; then
99143
PCT_BEFORE=$(( (COVERED_BEFORE * 100) / TOTAL ))
100-
PCT_AFTER=100
144+
PCT_AFTER=$(( (COVERED_AFTER * 100) / TOTAL ))
101145
else
102146
PCT_BEFORE=0
103147
PCT_AFTER=0
@@ -117,17 +161,17 @@ jobs:
117161
printf "files<<EOF\n%s\nEOF\n" "$UNCOVERED" >> "$GITHUB_OUTPUT"
118162
fi
119163
120-
# ── 5. Chama Claude API para gerar os testes ─────────────────────────────
164+
# ── 6. Chama Claude API para gerar os testes ─────────────────────────────
121165
- name: Generate unit tests with Claude API
122-
if: steps.changed.outputs.has_changes == 'true'
166+
if: steps.kt_guard.outputs.has_kotlin == 'true' && steps.changed.outputs.has_changes == 'true'
123167
env:
124168
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
125169
CHANGED_FILES: ${{ steps.changed.outputs.files }}
126170
run: python .github/scripts/generate_tests.py
127171

128-
# ── 6. Verifica se arquivos foram gerados ─────────────────────────────────
172+
# ── 7. Verifica se arquivos foram gerados ─────────────────────────────────
129173
- name: Check generated files
130-
if: steps.changed.outputs.has_changes == 'true'
174+
if: steps.kt_guard.outputs.has_kotlin == 'true' && steps.changed.outputs.has_changes == 'true'
131175
id: check
132176
run: |
133177
COUNT=$(find android_kmp/craftd-core/src/test -name "*Test.kt" 2>/dev/null | wc -l | tr -d ' ')
@@ -145,7 +189,7 @@ jobs:
145189
echo "has_tests=false" >> "$GITHUB_OUTPUT"
146190
fi
147191
148-
# ── 7. Cria branch com nome incremental e commita os testes ──────────────
192+
# ── 8. Cria branch com nome incremental e commita os testes ──────────────
149193
- name: Commit generated tests
150194
if: steps.check.outputs.has_tests == 'true'
151195
id: commit
@@ -173,7 +217,7 @@ jobs:
173217
174218
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
175219
176-
# ── 8. Abre PR com os testes gerados e evolução de cobertura ─────────────
220+
# ── 9. Abre PR com os testes gerados e evolução de cobertura ─────────────
177221
- name: Open Pull Request with generated tests
178222
if: steps.check.outputs.has_tests == 'true'
179223
env:

claude-doc/step_command_ci_test.md

Lines changed: 152 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,37 @@ PR aberto
1414
→ Escaneia arquivos sem cobertura
1515
→ Chama Claude API para cada arquivo
1616
→ Gera arquivos *Test.kt
17-
→ Cria branch + abre PR automático com os testes
17+
→ Cria branch cover/test (auto-incremento) + abre PR automático
18+
→ PR body mostra evolução de cobertura (antes vs depois)
1819
```
1920

2021
---
2122

23+
## Como usar este guia em um novo repositório
24+
25+
Se você está replicando este fluxo em um repo diferente, passe as seguintes
26+
instruções para o Claude Code no início da sessão:
27+
28+
```
29+
Quero configurar geração automática de testes com Claude API + GitHub Actions.
30+
Leia o arquivo claude-doc/step_command_ci_test.md do repositório CodandoTV/CraftD
31+
e replique o mesmo fluxo aqui para o módulo <nome-do-modulo>.
32+
33+
Contexto:
34+
- Módulo alvo: <caminho/do/modulo>
35+
- Source sets: <commonMain e/ou androidMain>
36+
- CI existente: <nome exato do workflow de CI>
37+
- Secrets já configurados: ANTHROPIC_API_KEY=sim/não, GH_PAT=sim/não
38+
```
39+
40+
O Claude vai:
41+
1. Analisar os arquivos sem cobertura no módulo
42+
2. Criar `.github/scripts/generate_tests.py`
43+
3. Criar `.github/workflows/generate-tests.yml`
44+
4. Abrir o PR — você só precisa mergear e cadastrar os secrets
45+
46+
---
47+
2248
## Fase 1 — Análise do módulo
2349

2450
Antes de configurar qualquer coisa, entenda o que existe no módulo alvo.
@@ -132,14 +158,46 @@ if: |
132158
| Step | O que faz |
133159
|------|-----------|
134160
| Resolve trigger context | Normaliza `head_sha` e `pr_number` para os dois gatilhos |
135-
| Checkout | Usa `GH_PAT` (não `GITHUB_TOKEN`) para permitir push |
136-
| Set up Python | Versão 3.11 |
161+
| Checkout | **Sem token** — repo público não precisa de auth para fetch |
162+
| Check for Kotlin changes | Verifica `git diff origin/main...HEAD` — pula tudo se não houver `.kt` modificado. `workflow_dispatch` sempre passa. |
163+
| Set up Python | Versão 3.11 (só roda se houver `.kt` modificado) |
137164
| Install dependencies | `pip install anthropic` |
138-
| Find uncovered files | `find` nos diretórios `commonMain` e `androidMain` |
165+
| Find uncovered files | **`workflow_run`**: só processa `.kt` modificados no PR sem teste. **`workflow_dispatch`** sem override: scan completo. Calcula cobertura antes/depois. |
139166
| Generate tests | Chama `generate_tests.py` com `CHANGED_FILES` |
140167
| Check generated files | Usa `find` (não `git status`) para contar `*Test.kt` |
141-
| Commit tests | `git add --force` + push para nova branch |
142-
| Open PR | Usa `GH_TOKEN: ${{ secrets.GH_PAT }}` |
168+
| Commit tests | Branch com auto-incremento `cover/test` → `cover/test-1` → ..., `git add --force` + push autenticado via `git remote set-url` |
169+
| Open PR | Usa `GH_TOKEN: ${{ secrets.GH_PAT }}` com tabela de evolução de cobertura |
170+
171+
> ⚠️ `workflow_run` não suporta filtros de `paths` nativamente (ao contrário de `push`/`pull_request`).
172+
> O filtro de `.kt` é feito manualmente via `git diff` dentro do job.
173+
174+
**Autenticação no push (crítico):**
175+
```yaml
176+
# Checkout SEM token — repo público não precisa de auth para fetch
177+
- name: Checkout
178+
uses: actions/checkout@v4
179+
with:
180+
ref: ${{ steps.ctx.outputs.head_sha }}
181+
fetch-depth: 0
182+
# NÃO colocar token aqui — causa "fatal: could not read Username" mesmo em repo público
183+
184+
# Push autentica via remote URL (não via checkout token)
185+
- name: Commit generated tests
186+
run: |
187+
git remote set-url origin https://x-access-token:${{ secrets.GH_PAT }}@github.com/org/repo.git
188+
git push origin "$BRANCH"
189+
```
190+
191+
**Branch com auto-incremento:**
192+
```bash
193+
BASE="cover/test"
194+
BRANCH="$BASE"
195+
N=1
196+
while git ls-remote --exit-code --heads origin "$BRANCH" > /dev/null 2>&1; do
197+
BRANCH="${BASE}-${N}"
198+
N=$((N + 1))
199+
done
200+
```
143201

144202
---
145203

@@ -154,8 +212,7 @@ Configure em: **Settings → Secrets and variables → Actions → New repositor
154212

155213
### 🔑 `ANTHROPIC_API_KEY` — Chave da API do Claude
156214

157-
**O que é:** A chave que autentica as chamadas à Claude API. Sem ela o script
158-
não consegue chamar o modelo e nenhum teste é gerado.
215+
**O que é:** A chave que autentica as chamadas à Claude API.
159216

160217
**Como obter:**
161218
1. Acesse [console.anthropic.com](https://console.anthropic.com)
@@ -175,8 +232,7 @@ não consegue chamar o modelo e nenhum teste é gerado.
175232
| `claude-haiku-4-5-20251001` | ~$0.09 | ~$0.25 |
176233
| `claude-opus-4-6` | ~$1.50 | ~$4.50 |
177234

178-
> Use **Haiku** para geração de testes — é ~10x mais barato e suficiente para
179-
> data classes, extension functions, DiffUtil e enums.
235+
> Use **Haiku** para geração de testes — é ~10x mais barato e suficiente.
180236

181237
**Erro comum:** key correta mas conta sem saldo →
182238
```
@@ -188,8 +244,7 @@ Solução: console.anthropic.com → Plans & Billing → Add credits.
188244
189245
### 🔑 `GH_PAT` — Personal Access Token do GitHub
190246
191-
**O que é:** Um token pessoal do GitHub que permite ao workflow abrir PRs
192-
em nome de um usuário real. Substitui o `GITHUB_TOKEN` padrão do Actions.
247+
**O que é:** Um token pessoal do GitHub que permite ao workflow fazer push e abrir PRs.
193248
194249
**Por que não usar `GITHUB_TOKEN`:**
195250
O `GITHUB_TOKEN` gerado automaticamente pelo Actions tem uma restrição de segurança
@@ -200,13 +255,14 @@ intencional do GitHub — ele **não pode abrir PRs** que disparariam outros wor
200255
1. Acesse seu perfil no GitHub → **Settings**
201256
2. **Developer settings → Personal access tokens → Tokens (classic)**
202257
3. **Generate new token (classic)**
203-
4. Marque o escopo: `repo` (acesso completo)
258+
4. Marque **apenas** os escopos: `repo` e `workflow`
204259
5. Copie o token (começa com `ghp_...`)
205260
206261
**Boas práticas:**
207262
- Dê um nome descritivo ao token (ex: `craftd-actions-bot`)
208263
- Defina uma data de expiração (90 dias é um bom equilíbrio)
209264
- Guarde o token em local seguro — o GitHub não mostra novamente
265+
- Se o token expirar, crie um novo e atualize o secret `GH_PAT` no repositório
210266
211267
**Erro comum:** usar `GITHUB_TOKEN` no lugar do PAT →
212268
```
@@ -251,7 +307,7 @@ Para testar antes do merge, use `workflow_dispatch` manualmente.
251307
### ❌ Actions não consegue criar PR (permission error)
252308
**Causa:** `GITHUB_TOKEN` não tem permissão para abrir PRs que disparam workflows.
253309

254-
**Solução:** Criar um PAT pessoal com escopo `repo`, salvar como secret `GH_PAT`
310+
**Solução:** Criar um PAT pessoal com escopo `repo` + `workflow`, salvar como secret `GH_PAT`
255311
e usar `GH_TOKEN: ${{ secrets.GH_PAT }}` no step de criação de PR.
256312

257313
---
@@ -264,6 +320,81 @@ Com Haiku, $5 cobrem ~50 execuções completas do módulo.
264320

265321
---
266322

323+
### `token:` no checkout causa "fatal: could not read Username"
324+
**Causa:** Passar o `GH_PAT` como `token:` no step de `actions/checkout` configura
325+
um credential helper que falha mesmo em repos públicos quando o token está
326+
vazio, inválido ou expirado — bloqueando até o simples `git fetch`.
327+
328+
**Solução:** Remover o `token:` do checkout completamente. Para repos públicos,
329+
o fetch não precisa de autenticação. A autenticação só é necessária no `git push`,
330+
e deve ser feita via `git remote set-url`:
331+
```yaml
332+
# ✅ Checkout sem token
333+
- name: Checkout
334+
uses: actions/checkout@v4
335+
with:
336+
ref: ${{ steps.ctx.outputs.head_sha }}
337+
fetch-depth: 0
338+
339+
# ✅ Push autenticado via remote URL
340+
- name: Commit generated tests
341+
run: |
342+
git remote set-url origin https://x-access-token:${{ secrets.GH_PAT }}@github.com/org/repo.git
343+
git push origin "$BRANCH"
344+
```
345+
346+
---
347+
348+
### ❌ Push rejeitado com "non-fast-forward" em re-runs
349+
**Causa:** O branch `cover/test` já existia no remote de uma execução anterior.
350+
O `git push` padrão não sobrescreve branches divergentes.
351+
352+
**Solução:** Usar auto-incremento no nome do branch — verifica se existe no remote
353+
antes de criar:
354+
```bash
355+
BASE="cover/test"
356+
BRANCH="$BASE"
357+
N=1
358+
while git ls-remote --exit-code --heads origin "$BRANCH" > /dev/null 2>&1; do
359+
BRANCH="${BASE}-${N}"
360+
N=$((N + 1))
361+
done
362+
# Resulta em: cover/test, cover/test-1, cover/test-2, ...
363+
```
364+
365+
---
366+
367+
### ❌ `printf` com variável errada + sem redirecionamento
368+
**Causa:** Ao editar o workflow manualmente, o step de scan ficou assim (errado):
369+
```bash
370+
printf "files<<EOF\n%s\nEOF\n" "$UNCOVER_OUTPUT" # ❌
371+
```
372+
`$UNCOVER_OUTPUT` apontava para o path do arquivo `$GITHUB_OUTPUT`, não para a lista
373+
de arquivos. Além disso, faltava o redirecionamento.
374+
375+
**Solução:**
376+
```bash
377+
printf "files<<EOF\n%s\nEOF\n" "$UNCOVERED" >> "$GITHUB_OUTPUT" # ✅
378+
```
379+
380+
---
381+
382+
### ❌ Repo renomeado quebra o checkout
383+
**Causa:** O repositório `CodandoTV/CraftD-android` foi renomeado para `CodandoTV/CraftD`.
384+
Sem o campo `repository:` explícito no checkout, o Actions usa o nome antigo e falha.
385+
386+
**Solução:** Sempre especificar `repository:` no checkout de workflows que usam `workflow_run`:
387+
```yaml
388+
- name: Checkout
389+
uses: actions/checkout@v4
390+
with:
391+
repository: org/repo # nome atual do repositório
392+
ref: ${{ steps.ctx.outputs.head_sha }}
393+
fetch-depth: 0
394+
```
395+
396+
---
397+
267398
## Fase 6 — Como testar antes do merge
268399

269400
**Opção 1 — Rodar o script localmente:**
@@ -295,11 +426,12 @@ Deixe o campo vazio para escanear tudo, ou informe arquivos específicos.
295426
296427
Após o workflow rodar, verifique:
297428
298-
1. **Step "Find uncovered Kotlin files"** — lista os arquivos detectados
429+
1. **Step "Find uncovered Kotlin files"** — lista os arquivos detectados e mostra cobertura antes/depois
299430
2. **Step "Generate unit tests with Claude API"** — cada arquivo deve mostrar `[OK] Written: ...`
300431
3. **Step "Check generated files"** — deve mostrar `Found N test file(s)`
301-
4. **Step "Open Pull Request"** — URL do PR gerado aparece no log
302-
5. **PR aberto automaticamente** com os testes em `src/test/java/...`
432+
4. **Step "Commit tests"** — deve mostrar o nome do branch escolhido (`cover/test`, `cover/test-1` etc.)
433+
5. **Step "Open Pull Request"** — URL do PR gerado aparece no log
434+
6. **PR aberto automaticamente** com tabela de evolução de cobertura no body
303435
304436
---
305437
@@ -328,7 +460,8 @@ Após o workflow rodar, verifique:
328460
- [ ] Criar `.github/scripts/generate_tests.py`
329461
- [ ] Criar `.github/workflows/generate-tests.yml` apontando para o nome correto do CI
330462
- [ ] Adicionar secret `ANTHROPIC_API_KEY` (console.anthropic.com)
331-
- [ ] Adicionar secret `GH_PAT` (PAT com escopo `repo`)
463+
- [ ] Adicionar secret `GH_PAT` (PAT classic com escopos `repo` + `workflow`)
464+
- [ ] **Não colocar `token:` no step de checkout** — usar `git remote set-url` para push
332465
- [ ] Abrir PR com os arquivos do workflow e mergear para `main`
333466
- [ ] Abrir qualquer PR tocando o módulo alvo e acompanhar o Actions
334-
467+
- [ ] Verificar que o PR gerado mostra a tabela de evolução de cobertura

0 commit comments

Comments
 (0)