This repository was archived by the owner on Jun 18, 2026. It is now read-only.
feat(adversary): GraphAdversaryForecaster — agentic adversarial-inten… #704
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: Code Coverage | |
| on: | |
| push: | |
| branches: [main, master] | |
| pull_request: | |
| branches: [main, master] | |
| permissions: | |
| contents: read | |
| jobs: | |
| coverage: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: 17 | |
| distribution: temurin | |
| - name: Download test dependencies | |
| run: | | |
| mkdir -p Gvisual/lib/test | |
| # JUnit 4 + Hamcrest | |
| curl -sL -o Gvisual/lib/test/junit-4.13.2.jar \ | |
| https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar | |
| curl -sL -o Gvisual/lib/test/hamcrest-core-1.3.jar \ | |
| https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar | |
| # JaCoCo agent + CLI for coverage instrumentation and reporting | |
| curl -sL -o /tmp/jacoco.zip \ | |
| https://repo1.maven.org/maven2/org/jacoco/jacoco/0.8.12/jacoco-0.8.12.zip | |
| unzip -o /tmp/jacoco.zip -d /tmp/jacoco | |
| cp /tmp/jacoco/lib/jacocoagent.jar Gvisual/lib/test/jacocoagent.jar | |
| cp /tmp/jacoco/lib/jacococli.jar Gvisual/lib/test/jacococli.jar | |
| - name: Compile source | |
| working-directory: Gvisual | |
| run: | | |
| mkdir -p build/classes | |
| find src -name '*.java' > sources.txt | |
| javac -source 8 -target 8 \ | |
| -cp "$(find lib -name '*.jar' -not -path 'lib/test/*' | tr '\n' ':')" \ | |
| -d build/classes \ | |
| @sources.txt | |
| - name: Compile tests | |
| working-directory: Gvisual | |
| run: | | |
| mkdir -p build/test/classes | |
| find test -name '*.java' > test-sources.txt | |
| javac -source 8 -target 8 \ | |
| -cp "build/classes:$(find lib -name '*.jar' | tr '\n' ':')" \ | |
| -d build/test/classes \ | |
| @test-sources.txt | |
| - name: Discover test classes | |
| id: discover-tests | |
| working-directory: Gvisual | |
| run: | | |
| # Auto-discover all *Test classes from compiled test output | |
| TEST_CLASSES=$(find build/test/classes -name '*Test.class' \ | |
| | sed 's|build/test/classes/||;s|\.class$||;s|/|.|g' \ | |
| | sort) | |
| echo "Found $(echo "$TEST_CLASSES" | wc -l) test classes" | |
| echo "$TEST_CLASSES" | |
| # Store as space-separated for JUnitCore | |
| echo "classes=$(echo $TEST_CLASSES | tr '\n' ' ')" >> $GITHUB_OUTPUT | |
| - name: Run tests with JaCoCo coverage | |
| working-directory: Gvisual | |
| run: | | |
| java \ | |
| -javaagent:lib/test/jacocoagent.jar=destfile=build/jacoco.exec,includes=gvisual.*:app.* \ | |
| -cp "build/classes:build/test/classes:$(find lib -name '*.jar' | tr '\n' ':')" \ | |
| org.junit.runner.JUnitCore \ | |
| ${{ steps.discover-tests.outputs.classes }} | |
| - name: Generate coverage report | |
| working-directory: Gvisual | |
| run: | | |
| mkdir -p build/coverage | |
| # Generate HTML report | |
| java -jar lib/test/jacococli.jar report build/jacoco.exec \ | |
| --classfiles build/classes \ | |
| --sourcefiles src \ | |
| --html build/coverage/html \ | |
| --xml build/coverage/coverage.xml \ | |
| --csv build/coverage/coverage.csv \ | |
| --name "GraphVisual Coverage Report" | |
| - name: Coverage summary | |
| working-directory: Gvisual | |
| run: | | |
| echo "## 📊 Code Coverage Report" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # Parse CSV for summary (skip header, sum instruction covered/missed) | |
| if [ -f build/coverage/coverage.csv ]; then | |
| echo "| Package | Class | Instruction Coverage |" >> $GITHUB_STEP_SUMMARY | |
| echo "|---------|-------|---------------------|" >> $GITHUB_STEP_SUMMARY | |
| tail -n +2 build/coverage/coverage.csv | while IFS=',' read -r group package class inst_missed inst_covered branch_missed branch_covered line_missed line_covered complexity_missed complexity_covered method_missed method_covered; do | |
| total=$((inst_missed + inst_covered)) | |
| if [ "$total" -gt 0 ]; then | |
| pct=$((inst_covered * 100 / total)) | |
| echo "| $package | $class | ${pct}% (${inst_covered}/${total}) |" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| done | |
| fi | |
| - name: Upload coverage report | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: coverage-report | |
| path: Gvisual/build/coverage/ | |
| retention-days: 30 | |
| - name: Enforce coverage threshold | |
| working-directory: Gvisual | |
| run: | | |
| if [ -f build/coverage/coverage.csv ]; then | |
| # Calculate overall instruction coverage | |
| total_covered=0 | |
| total_missed=0 | |
| while IFS=',' read -r group package class inst_missed inst_covered rest; do | |
| total_missed=$((total_missed + inst_missed)) | |
| total_covered=$((total_covered + inst_covered)) | |
| done < <(tail -n +2 build/coverage/coverage.csv) | |
| total=$((total_missed + total_covered)) | |
| if [ "$total" -gt 0 ]; then | |
| pct=$((total_covered * 100 / total)) | |
| echo "Overall instruction coverage: ${pct}%" | |
| THRESHOLD=50 | |
| if [ "$pct" -lt "$THRESHOLD" ]; then | |
| echo "::error::Coverage ${pct}% is below threshold ${THRESHOLD}%" | |
| exit 1 | |
| fi | |
| echo "✅ Coverage ${pct}% meets threshold ${THRESHOLD}%" | |
| fi | |
| fi | |
| - name: Comment coverage on PR | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const csvPath = 'Gvisual/build/coverage/coverage.csv'; | |
| if (!fs.existsSync(csvPath)) return; | |
| const lines = fs.readFileSync(csvPath, 'utf8').trim().split('\n').slice(1); | |
| let totalCovered = 0, totalMissed = 0; | |
| const rows = []; | |
| for (const line of lines) { | |
| const cols = line.split(','); | |
| const pkg = cols[1], cls = cols[2]; | |
| const missed = parseInt(cols[3]), covered = parseInt(cols[4]); | |
| const total = missed + covered; | |
| const pct = total > 0 ? Math.round(covered * 100 / total) : 0; | |
| totalMissed += missed; | |
| totalCovered += covered; | |
| rows.push(`| ${pkg} | ${cls} | ${pct}% (${covered}/${total}) |`); | |
| } | |
| const overallTotal = totalMissed + totalCovered; | |
| const overallPct = overallTotal > 0 ? Math.round(totalCovered * 100 / overallTotal) : 0; | |
| const body = [ | |
| '## 📊 Code Coverage Report', | |
| '', | |
| `**Overall: ${overallPct}%** (${totalCovered}/${overallTotal} instructions)`, | |
| '', | |
| '| Package | Class | Coverage |', | |
| '|---------|-------|----------|', | |
| ...rows, | |
| ].join('\n'); | |
| // Find and update existing comment or create new | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const existing = comments.find(c => c.body.includes('📊 Code Coverage Report')); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body, | |
| }); | |
| } | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v6 | |
| with: | |
| files: Gvisual/build/coverage/coverage.xml | |
| fail_ci_if_error: false | |
| continue-on-error: true |