Auto Generate Cover+Test #24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Auto Generate Cover+Test | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| override_files: | |
| description: 'Opcional: arquivos .kt específicos (separados por espaço). Vazio = escaneia tudo sem cobertura.' | |
| required: false | |
| default: '' | |
| workflow_run: | |
| workflows: ["Build and Test for PRs"] | |
| types: [completed] | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| generate-tests: | |
| name: Generate Unit Tests with Claude | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| ( | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.actor.login != 'github-actions[bot]' | |
| ) | |
| steps: | |
| # ── 1. Normaliza contexto para os dois gatilhos ─────────────────────────── | |
| - name: Resolve trigger context | |
| id: ctx | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "head_sha=${{ github.sha }}" >> "$GITHUB_OUTPUT" | |
| echo "pr_number=manual" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "head_sha=${{ github.event.workflow_run.head_sha }}" >> "$GITHUB_OUTPUT" | |
| echo "pr_number=${{ github.event.workflow_run.pull_requests[0].number }}" >> "$GITHUB_OUTPUT" | |
| fi | |
| # ── 2. Checkout ─────────────────────────────────────────────────────────── | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ steps.ctx.outputs.head_sha }} | |
| fetch-depth: 0 | |
| # ── 3. Python ───────────────────────────────────────────────────────────── | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install Python dependencies | |
| run: pip install anthropic | |
| # ── 4. Escaneia craftd-core buscando arquivos sem cobertura ────────────── | |
| - name: Find uncovered Kotlin files in craftd-core | |
| id: changed | |
| run: | | |
| OVERRIDE="${{ github.event.inputs.override_files }}" | |
| # Conta total de arquivos fonte | |
| TOTAL=$(find android_kmp/craftd-core/src \ | |
| \( -path "*/commonMain/kotlin/*.kt" -o -path "*/androidMain/kotlin/*.kt" \) \ | |
| | grep -v "Test\.kt" | wc -l | tr -d ' ') | |
| if [ -n "$OVERRIDE" ]; then | |
| UNCOVERED=$(echo "$OVERRIDE" | tr ' ' '\n' | grep -v "^$" || true) | |
| UNCOVERED_COUNT=$(echo "$UNCOVERED" | grep -v "^$" | wc -l | tr -d ' ') | |
| echo "Modo override — arquivos informados manualmente:" | |
| else | |
| echo "Scan completo — buscando arquivos sem cobertura em craftd-core..." | |
| UNCOVERED="" | |
| while IFS= read -r SRC; do | |
| TEST=$(echo "$SRC" \ | |
| | sed 's|src/commonMain/kotlin/|src/test/java/|' \ | |
| | sed 's|src/androidMain/kotlin/|src/test/java/|' \ | |
| | sed 's|\.kt$|Test.kt|') | |
| if [ ! -f "$TEST" ]; then | |
| UNCOVERED="$UNCOVERED"$'\n'"$SRC" | |
| fi | |
| done < <(find android_kmp/craftd-core/src \ | |
| \( -path "*/commonMain/kotlin/*.kt" -o -path "*/androidMain/kotlin/*.kt" \) \ | |
| | grep -v "Test\.kt" | sort) | |
| UNCOVERED=$(echo "$UNCOVERED" | grep -v "^$" || true) | |
| UNCOVERED_COUNT=$(echo "$UNCOVERED" | grep -v "^$" | wc -l | tr -d ' ') | |
| fi | |
| echo "$UNCOVERED" | |
| COVERED_BEFORE=$((TOTAL - UNCOVERED_COUNT)) | |
| COVERED_AFTER=$TOTAL | |
| if [ "$TOTAL" -gt "0" ]; then | |
| PCT_BEFORE=$(( (COVERED_BEFORE * 100) / TOTAL )) | |
| PCT_AFTER=100 | |
| else | |
| PCT_BEFORE=0 | |
| PCT_AFTER=0 | |
| fi | |
| echo "total=$TOTAL" >> "$GITHUB_OUTPUT" | |
| echo "covered_before=$COVERED_BEFORE" >> "$GITHUB_OUTPUT" | |
| echo "covered_after=$COVERED_AFTER" >> "$GITHUB_OUTPUT" | |
| echo "pct_before=$PCT_BEFORE" >> "$GITHUB_OUTPUT" | |
| echo "pct_after=$PCT_AFTER" >> "$GITHUB_OUTPUT" | |
| if [ -z "$UNCOVERED" ]; then | |
| echo "has_changes=false" >> "$GITHUB_OUTPUT" | |
| echo "Nenhum arquivo sem cobertura. Nada a fazer." | |
| else | |
| echo "has_changes=true" >> "$GITHUB_OUTPUT" | |
| printf "files<<EOF\n%s\nEOF\n" "$UNCOVERED" >> "$GITHUB_OUTPUT" | |
| fi | |
| # ── 5. Chama Claude API para gerar os testes ───────────────────────────── | |
| - name: Generate unit tests with Claude API | |
| if: steps.changed.outputs.has_changes == 'true' | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| CHANGED_FILES: ${{ steps.changed.outputs.files }} | |
| run: python .github/scripts/generate_tests.py | |
| # ── 6. Verifica se arquivos foram gerados ───────────────────────────────── | |
| - name: Check generated files | |
| if: steps.changed.outputs.has_changes == 'true' | |
| id: check | |
| run: | | |
| COUNT=$(find android_kmp/craftd-core/src/test -name "*Test.kt" 2>/dev/null | wc -l | tr -d ' ') | |
| echo "count=$COUNT" >> "$GITHUB_OUTPUT" | |
| echo "Found $COUNT test file(s)" | |
| if [ "$COUNT" -gt "0" ]; then | |
| echo "has_tests=true" >> "$GITHUB_OUTPUT" | |
| NAMES=$(find android_kmp/craftd-core/src/test -name "*Test.kt" \ | |
| | xargs -I{} basename {} \ | |
| | paste -sd ", ") | |
| echo "covered_names=$NAMES" >> "$GITHUB_OUTPUT" | |
| echo "Files: $NAMES" | |
| else | |
| echo "has_tests=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| # ── 7. Cria branch com nome incremental e commita os testes ────────────── | |
| - name: Commit generated tests | |
| if: steps.check.outputs.has_tests == 'true' | |
| id: commit | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Autentica o push com GH_PAT via URL do remote | |
| git remote set-url origin https://x-access-token:${{ secrets.GH_PAT }}@github.com/CodandoTV/CraftD.git | |
| # Determina nome do branch com auto-incremento: cover/test, cover/test-1, cover/test-2 ... | |
| BASE="cover/test" | |
| BRANCH="$BASE" | |
| N=1 | |
| while git ls-remote --exit-code --heads origin "$BRANCH" > /dev/null 2>&1; do | |
| BRANCH="${BASE}-${N}" | |
| N=$((N + 1)) | |
| done | |
| echo "Branch escolhido: $BRANCH" | |
| git checkout -b "$BRANCH" | |
| git add --force android_kmp/craftd-core/src/test/ | |
| git commit -m "test: add unit tests for craftd-core (auto-generated via Claude)" | |
| git push origin "$BRANCH" | |
| echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" | |
| # ── 8. Abre PR com os testes gerados e evolução de cobertura ───────────── | |
| - name: Open Pull Request with generated tests | |
| if: steps.check.outputs.has_tests == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_PAT }} | |
| run: | | |
| PR_NUMBER="${{ steps.ctx.outputs.pr_number }}" | |
| BRANCH="${{ steps.commit.outputs.branch }}" | |
| COVERED="${{ steps.check.outputs.covered_names }}" | |
| TOTAL="${{ steps.changed.outputs.total }}" | |
| COV_BEFORE="${{ steps.changed.outputs.covered_before }}" | |
| COV_AFTER="${{ steps.changed.outputs.covered_after }}" | |
| PCT_BEFORE="${{ steps.changed.outputs.pct_before }}" | |
| PCT_AFTER="${{ steps.changed.outputs.pct_after }}" | |
| gh pr create \ | |
| --base "main" \ | |
| --head "$BRANCH" \ | |
| --title "[Auto] Add unit tests for craftd-core" \ | |
| --body "$(cat <<EOF | |
| ## Auto-generated Unit Tests | |
| Este PR foi gerado automaticamente pelo workflow **Auto Generate Cover+Test** usando a Claude API. | |
| ### Evolução de cobertura | |
| | | Arquivos | Cobertura | | |
| |---|---|---| | |
| | Antes | ${COV_BEFORE} / ${TOTAL} | ${PCT_BEFORE}% | | |
| | Depois | ${COV_AFTER} / ${TOTAL} | ${PCT_AFTER}% | | |
| ### Arquivos cobertos | |
| \`\`\` | |
| ${COVERED} | |
| \`\`\` | |
| ### Como funciona | |
| 1. Um PR tocou arquivos em \`android_kmp/craftd-core\` | |
| 2. Após o CI passar, o workflow escaneou todos os arquivos sem cobertura | |
| 3. Claude gerou testes **JUnit4 + MockK** para cada arquivo | |
| 4. Este PR foi criado automaticamente com o resultado | |
| ### Checklist de revisão | |
| - [ ] Testes compilam sem erros (\`./gradlew testDebugUnitTest\`) | |
| - [ ] Testes cobrem os principais caminhos de lógica | |
| - [ ] Edge cases tratados (null, vazio, JSON inválido) | |
| > Triggered by PR #${PR_NUMBER} | |
| EOF | |
| )" |