nsip 0.4.0

NSIP Search API client for nsipsearch.nsip.org/api
Documentation
name: Mutation Testing

on:
  pull_request:
    paths:
      - 'src/**'
      - 'tests/**'
      - 'Cargo.toml'
      - 'Cargo.lock'
  workflow_dispatch:
    inputs:
      target:
        description: 'Target file or module to mutate'
        required: false
        default: ''

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read
  pull-requests: write

jobs:
  mutation-test:
    name: Mutation Testing with cargo-mutants
    runs-on: ubuntu-latest
    timeout-minutes: 60
    
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2

      - name: Setup Rust with caching
        uses: ./.github/actions/setup-rust-cached
        with:
          toolchain: stable
          cache-key: mutation

      - name: Install cargo-mutants
        uses: ./.github/actions/install-cargo-tool
        with:
          tool: cargo-mutants@24.11.0

      - name: Run mutation tests
        run: |
          if [ -n "${{ github.event.inputs.target }}" ]; then
            cargo mutants --file "${{ github.event.inputs.target }}" --output mutants.out --json
          else
            cargo mutants --output mutants.out --json --timeout 300
          fi
        continue-on-error: true

      - name: Generate mutation report
        if: always()
        run: |
          echo "# Mutation Testing Report" > mutation-report.md
          echo "" >> mutation-report.md
          
          if [ -f mutants.out/mutants.json ]; then
            echo "## Summary" >> mutation-report.md
            jq -r '.summary | "- Total mutants: \(.total)\n- Caught: \(.caught)\n- Missed: \(.missed)\n- Timeout: \(.timeout)\n- Score: \(.score)%"' mutants.out/mutants.json >> mutation-report.md
            
            echo "" >> mutation-report.md
            echo "## Missed Mutants (Require Attention)" >> mutation-report.md
            echo "" >> mutation-report.md
            jq -r '.mutants[] | select(.outcome == "missed") | "### \(.function)\n\n**File:** \(.file):\(.line)\n\n**Mutation:** \(.replacement)\n\n---\n"' mutants.out/mutants.json >> mutation-report.md || echo "No missed mutants" >> mutation-report.md
          else
            echo "Mutation testing output not found" >> mutation-report.md
          fi

      - name: Upload mutation report
        if: always()
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f  # v7.0.0
        with:
          name: mutation-test-report
          path: |
            mutation-report.md
            mutants.out/
          retention-days: 30

      - name: Comment PR with results
        if: github.event_name == 'pull_request' && always()
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
        with:
          script: |
            const fs = require('fs');
            if (fs.existsSync('mutation-report.md')) {
              const report = fs.readFileSync('mutation-report.md', 'utf8');
              github.rest.issues.createComment({
                issue_number: context.issue.number,
                owner: context.repo.owner,
                repo: context.repo.repo,
                body: report
              });
            }