plugins/testing/skills/mutation-tester/SKILL.md
Execute tests against generated mutants and analyze results to validate test suite quality for Kubernetes operators
npx skillsauth add openshift-eng/ai-helpers mutation-testerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill executes the test suite against each generated mutant and analyzes the results to determine test suite effectiveness. A killed mutant (test fails) indicates good test coverage; a survived mutant (test passes) indicates a gap in testing.
Use this skill when:
Before testing mutants, ensure original tests pass:
1.1 Run Baseline Tests
echo "Running baseline tests against original code..."
cd "${OPERATOR_PATH}"
go test ./controllers/... -v -timeout 10m \
> "${WORK_DIR}/baseline-test-output.txt" 2>&1
BASELINE_EXIT=$?
if [ $BASELINE_EXIT -ne 0 ]; then
echo "❌ ERROR: Baseline tests failed"
echo "Fix failing tests before running mutation testing"
cat "${WORK_DIR}/baseline-test-output.txt"
exit 1
fi
echo "✓ Baseline tests passed"
1.2 Record Baseline Metrics
# Count tests
TOTAL_TESTS=$(grep -c "^=== RUN" "${WORK_DIR}/baseline-test-output.txt" || echo "0")
# Execution time
TEST_DURATION=$(grep "^PASS" "${WORK_DIR}/baseline-test-output.txt" | \
awk '{print $NF}' | sed 's/[^0-9.]//g' | head -1)
echo "Baseline: $TOTAL_TESTS tests in ${TEST_DURATION}s"
2.1 Iterate Through Mutants
MUTANTS_DIR="${WORK_DIR}/mutants"
RESULTS_DIR="${WORK_DIR}/results"
mkdir -p "$RESULTS_DIR"
# Initialize counters
TOTAL_MUTANTS=$(find "$MUTANTS_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l)
KILLED_MUTANTS=0
SURVIVED_MUTANTS=0
TIMEOUT_MUTANTS=0
ERROR_MUTANTS=0
echo ""
echo "Testing $TOTAL_MUTANTS mutants..."
echo "This may take a while (estimated: $((TOTAL_MUTANTS * TEST_DURATION / 60)) minutes)"
echo ""
CURRENT=0
for mutant_dir in "$MUTANTS_DIR"/mutant-*; do
CURRENT=$((CURRENT + 1))
MUTANT_ID=$(basename "$mutant_dir")
# Load mutation metadata
MUTATION_FILE="$mutant_dir/MUTATION.json"
if [ ! -f "$MUTATION_FILE" ]; then
echo "[$CURRENT/$TOTAL_MUTANTS] ⚠️ $MUTANT_ID: No metadata"
continue
fi
MUTATION_DESC=$(jq -r '.description' "$MUTATION_FILE")
MUTATION_TYPE=$(jq -r '.type' "$MUTATION_FILE")
# Progress indicator
echo -n "[$CURRENT/$TOTAL_MUTANTS] Testing $MUTANT_ID ($MUTATION_TYPE)... "
# Test mutant (with timeout)
cd "$mutant_dir"
# Use timeout to prevent hanging tests
timeout 300s go test ./controllers/... -v \
> "$RESULTS_DIR/${MUTANT_ID}-output.txt" 2>&1
TEST_EXIT=$?
cd - > /dev/null
# Analyze result
if [ $TEST_EXIT -eq 124 ]; then
# Timeout occurred
echo "⏱️ KILLED (timeout)"
KILLED_MUTANTS=$((KILLED_MUTANTS + 1))
TIMEOUT_MUTANTS=$((TIMEOUT_MUTANTS + 1))
STATUS="killed-timeout"
elif [ $TEST_EXIT -ne 0 ]; then
# Tests failed - mutant killed (GOOD!)
echo "✓ KILLED"
KILLED_MUTANTS=$((KILLED_MUTANTS + 1))
STATUS="killed"
else
# Tests passed - mutant survived (BAD - indicates missing test)
echo "⚠️ SURVIVED"
SURVIVED_MUTANTS=$((SURVIVED_MUTANTS + 1))
STATUS="survived"
fi
# Save result metadata (using jq for safe JSON construction)
jq -n \
--arg mutant_id "$MUTANT_ID" \
--arg status "$STATUS" \
--argjson exit_code "$TEST_EXIT" \
--arg output_file "$RESULTS_DIR/${MUTANT_ID}-output.txt" \
--slurpfile mutation "$MUTATION_FILE" \
'{
mutant_id: $mutant_id,
status: $status,
exit_code: $exit_code,
mutation: $mutation[0],
output_file: $output_file
}' > "$RESULTS_DIR/${MUTANT_ID}-result.json"
done
cd "$OPERATOR_PATH"
2.2 Display Progress
For long-running mutation testing, show periodic updates:
# Every 10 mutants, show summary
if [ $((CURRENT % 10)) -eq 0 ] && [ "$CURRENT" -gt 0 ]; then
MUTATION_SCORE=$(awk "BEGIN {printf \"%.1f\", ($KILLED_MUTANTS / $CURRENT) * 100}")
echo ""
echo " Progress: $CURRENT/$TOTAL_MUTANTS ($MUTATION_SCORE% killed so far)"
echo ""
fi
3.1 Compute Overall Score
# Mutation Score = (Killed / Total) * 100
# Check for zero or unset TOTAL_MUTANTS to avoid division by zero
if [ -z "$TOTAL_MUTANTS" ] || [ "$TOTAL_MUTANTS" -eq 0 ]; then
echo "❌ ERROR: No mutants generated (TOTAL_MUTANTS=$TOTAL_MUTANTS)"
exit 1
fi
MUTATION_SCORE=$(awk "BEGIN {printf \"%.2f\", ($KILLED_MUTANTS / $TOTAL_MUTANTS) * 100}")
echo ""
echo "════════════════════════════════════════════════════════════"
echo " MUTATION TESTING RESULTS"
echo "════════════════════════════════════════════════════════════"
echo ""
echo "Total Mutants: $TOTAL_MUTANTS"
echo "Killed (Good): $KILLED_MUTANTS"
echo " - By tests: $((KILLED_MUTANTS - TIMEOUT_MUTANTS))"
echo " - By timeout: $TIMEOUT_MUTANTS"
echo "Survived (Bad): $SURVIVED_MUTANTS"
echo ""
echo "Mutation Score: ${MUTATION_SCORE}%"
echo ""
3.2 Interpret Score
if (( $(echo "$MUTATION_SCORE >= 90" | bc -l) )); then
echo "✓✓ EXCELLENT - Strong test suite!"
VERDICT="excellent"
elif (( $(echo "$MUTATION_SCORE >= 80" | bc -l) )); then
echo "✓ GOOD - Solid test coverage"
VERDICT="good"
elif (( $(echo "$MUTATION_SCORE >= 70" | bc -l) )); then
echo "⚠️ FAIR - Room for improvement"
VERDICT="fair"
else
echo "❌ POOR - Significant gaps in test coverage"
VERDICT="poor"
fi
echo "════════════════════════════════════════════════════════════"
echo ""
3.3 Calculate Score by Mutation Type
# Analyze by mutation type
echo "Mutation Score by Type:"
echo ""
for mut_type in conditionals error-handling returns requeue status api-calls; do
TYPE_TOTAL=$(jq -r "select(.mutation.type == \"$mut_type\") | .mutant_id" \
"$RESULTS_DIR"/*-result.json 2>/dev/null | wc -l)
if [ "$TYPE_TOTAL" -gt 0 ]; then
TYPE_KILLED=$(jq -r "select(.mutation.type == \"$mut_type\" and .status == \"killed\") | .mutant_id" \
"$RESULTS_DIR"/*-result.json 2>/dev/null | wc -l)
TYPE_SCORE=$(awk "BEGIN {printf \"%.1f\", ($TYPE_KILLED / $TYPE_TOTAL) * 100}")
printf " %-20s: %5.1f%% (%d/%d)\n" "$mut_type" "$TYPE_SCORE" "$TYPE_KILLED" "$TYPE_TOTAL"
fi
done
echo ""
4.1 Identify High-Priority Survived Mutants
echo "Analyzing survived mutants..."
echo ""
# Critical types that should not survive
CRITICAL_TYPES=("error-handling" "conditionals")
# List survived mutants
SURVIVED_MUTANTS_LIST=()
for result_file in "$RESULTS_DIR"/*-result.json; do
STATUS=$(jq -r '.status' "$result_file")
if [ "$STATUS" == "survived" ]; then
MUTANT_ID=$(jq -r '.mutant_id' "$result_file")
SURVIVED_MUTANTS_LIST+=("$MUTANT_ID")
fi
done
if [ ${#SURVIVED_MUTANTS_LIST[@]} -eq 0 ]; then
echo "✓ No survived mutants - all mutations were caught by tests!"
else
echo "⚠️ ${#SURVIVED_MUTANTS_LIST[@]} survived mutants need attention:"
echo ""
# Show top 10 most critical
COUNT=0
for mutant_id in "${SURVIVED_MUTANTS_LIST[@]}"; do
if [ $COUNT -ge 10 ]; then
echo " ... and $((${#SURVIVED_MUTANTS_LIST[@]} - 10)) more"
break
fi
RESULT_FILE="$RESULTS_DIR/${mutant_id}-result.json"
TYPE=$(jq -r '.mutation.type' "$RESULT_FILE")
DESC=$(jq -r '.mutation.description' "$RESULT_FILE")
FILE=$(jq -r '.mutation.file' "$RESULT_FILE")
LINE=$(jq -r '.mutation.line' "$RESULT_FILE")
echo " $mutant_id:"
echo " Type: $TYPE"
echo " Location: $FILE:$LINE"
echo " Mutation: $DESC"
echo ""
COUNT=$((COUNT + 1))
done
fi
4.2 Generate Recommendations
For each survived mutant, suggest what test to add:
echo "Recommendations:"
echo ""
for mutant_id in "${SURVIVED_MUTANTS_LIST[@]:0:5}"; do # Top 5
RESULT_FILE="$RESULTS_DIR/${mutant_id}-result.json"
TYPE=$(jq -r '.mutation.type' "$RESULT_FILE")
FILE=$(jq -r '.mutation.file' "$RESULT_FILE")
PATTERN=$(jq -r '.mutation.pattern' "$RESULT_FILE")
case "$TYPE" in
"error-handling")
echo "→ $mutant_id: Add test case for error handling in $FILE"
echo " Suggestion: Test what happens when API call fails"
;;
"conditionals")
echo "→ $mutant_id: Add test for opposite condition in $FILE"
echo " Suggestion: Test both true and false branches"
;;
"requeue")
echo "→ $mutant_id: Validate requeue behavior in $FILE"
echo " Suggestion: Assert Result.Requeue value in test"
;;
"status")
echo "→ $mutant_id: Verify status updates in $FILE"
echo " Suggestion: Assert object status after reconciliation"
;;
esac
echo ""
done
5.1 Create JSON Report
# Compile all results into single JSON (using jq for safe construction)
# First, collect survived mutant result files
SURVIVED_JSON_FILES=()
for mutant_id in "${SURVIVED_MUTANTS_LIST[@]}"; do
if [ -f "$RESULTS_DIR/${mutant_id}-result.json" ]; then
SURVIVED_JSON_FILES+=("$RESULTS_DIR/${mutant_id}-result.json")
fi
done
# Build report with jq
jq -n \
--argjson total "$TOTAL_MUTANTS" \
--argjson killed "$KILLED_MUTANTS" \
--argjson survived "$SURVIVED_MUTANTS" \
--argjson timeout "$TIMEOUT_MUTANTS" \
--argjson score "$MUTATION_SCORE" \
--arg verdict "$VERDICT" \
--argjson duration "$TEST_DURATION" \
--arg timestamp "$(date -Iseconds)" \
--slurpfile survived_mutants <(jq -s '.' "${SURVIVED_JSON_FILES[@]}" 2>/dev/null || echo '[]') \
'{
summary: {
total_mutants: $total,
killed: $killed,
survived: $survived,
timeout: $timeout,
mutation_score: $score,
verdict: $verdict,
test_duration_seconds: $duration,
timestamp: $timestamp
},
survived_mutants: $survived_mutants[0]
}' > "$RESULTS_DIR/mutation-report.json"
5.2 Generate HTML Report
Create visual HTML report (simplified version):
cat > "$RESULTS_DIR/mutation-report.html" << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>Mutation Testing Report</title>
<style>
body { font-family: sans-serif; margin: 40px; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; }
h1 { color: #333; border-bottom: 3px solid #4CAF50; padding-bottom: 10px; }
.summary { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin: 30px 0; }
.metric { background: #f9f9f9; padding: 20px; border-radius: 5px; text-align: center; }
.metric .value { font-size: 36px; font-weight: bold; color: #4CAF50; }
.metric .label { color: #666; margin-top: 10px; }
.excellent { color: #4CAF50; }
.good { color: #8BC34A; }
.fair { color: #FF9800; }
.poor { color: #F44336; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th, td { text-align: left; padding: 12px; border-bottom: 1px solid #ddd; }
th { background: #4CAF50; color: white; }
.survived { background: #ffebee; }
.killed { background: #e8f5e9; }
</style>
</head>
<body>
<div class="container">
<h1>🧬 Mutation Testing Report</h1>
<div class="summary">
<div class="metric">
<div class="value">$MUTATION_SCORE%</div>
<div class="label">Mutation Score</div>
</div>
<div class="metric">
<div class="value">$KILLED_MUTANTS</div>
<div class="label">Killed (Good)</div>
</div>
<div class="metric">
<div class="value">$SURVIVED_MUTANTS</div>
<div class="label">Survived (Bad)</div>
</div>
<div class="metric">
<div class="value">$TOTAL_MUTANTS</div>
<div class="label">Total Mutants</div>
</div>
</div>
<h2>Verdict: <span class="$VERDICT">$(echo $VERDICT | tr '[:lower:]' '[:upper:]')</span></h2>
<h2>Survived Mutants (Need Attention)</h2>
<table>
<tr>
<th>ID</th>
<th>Type</th>
<th>Location</th>
<th>Description</th>
</tr>
EOF
# Add survived mutants to table
for mutant_id in "${SURVIVED_MUTANTS_LIST[@]}"; do
RESULT_FILE="$RESULTS_DIR/${mutant_id}-result.json"
TYPE=$(jq -r '.mutation.type' "$RESULT_FILE")
FILE=$(jq -r '.mutation.file' "$RESULT_FILE")
LINE=$(jq -r '.mutation.line' "$RESULT_FILE")
DESC=$(jq -r '.mutation.description' "$RESULT_FILE")
cat >> "$RESULTS_DIR/mutation-report.html" << EOF
<tr class="survived">
<td>$mutant_id</td>
<td>$TYPE</td>
<td>$FILE:$LINE</td>
<td>$DESC</td>
</tr>
EOF
done
cat >> "$RESULTS_DIR/mutation-report.html" << 'EOF'
</table>
</div>
</body>
</html>
EOF
echo "📊 HTML Report: file://$(realpath "$RESULTS_DIR/mutation-report.html")"
5.3 Generate Markdown Report
cat > "$RESULTS_DIR/mutation-report.md" << EOF
# Mutation Testing Report
**Date:** $(date '+%Y-%m-%d %H:%M:%S')
## Summary
| Metric | Value |
|--------|-------|
| **Mutation Score** | **${MUTATION_SCORE}%** |
| Total Mutants | $TOTAL_MUTANTS |
| Killed (Good) | $KILLED_MUTANTS |
| Survived (Bad) | $SURVIVED_MUTANTS |
| Verdict | ${VERDICT^^} |
## Mutation Score by Type
EOF
# Add score by type table
for mut_type in conditionals error-handling returns requeue status api-calls; do
TYPE_TOTAL=$(jq -r "select(.mutation.type == \"$mut_type\") | .mutant_id" \
"$RESULTS_DIR"/*-result.json 2>/dev/null | wc -l)
if [ "$TYPE_TOTAL" -gt 0 ]; then
TYPE_KILLED=$(jq -r "select(.mutation.type == \"$mut_type\" and .status == \"killed\") | .mutant_id" \
"$RESULTS_DIR"/*-result.json 2>/dev/null | wc -l)
TYPE_SCORE=$(awk "BEGIN {printf \"%.1f\", ($TYPE_KILLED / $TYPE_TOTAL) * 100}")
echo "| $mut_type | ${TYPE_SCORE}% | $TYPE_KILLED/$TYPE_TOTAL |" >> "$RESULTS_DIR/mutation-report.md"
fi
done
cat >> "$RESULTS_DIR/mutation-report.md" << 'EOF'
## Survived Mutants
The following mutants survived, indicating gaps in test coverage:
| Mutant ID | Type | Location | Description |
|-----------|------|----------|-------------|
EOF
for mutant_id in "${SURVIVED_MUTANTS_LIST[@]}"; do
RESULT_FILE="$RESULTS_DIR/${mutant_id}-result.json"
TYPE=$(jq -r '.mutation.type' "$RESULT_FILE")
FILE=$(jq -r '.mutation.file' "$RESULT_FILE")
LINE=$(jq -r '.mutation.line' "$RESULT_FILE")
DESC=$(jq -r '.mutation.description' "$RESULT_FILE")
echo "| $mutant_id | $TYPE | $FILE:$LINE | $DESC |" >> "$RESULTS_DIR/mutation-report.md"
done
echo "" >> "$RESULTS_DIR/mutation-report.md"
Parallel Testing
For faster mutation testing, test mutants in parallel:
# Use GNU parallel if available
if command -v parallel &> /dev/null; then
export -f test_single_mutant
ls "$MUTANTS_DIR"/mutant-* | \
parallel -j 4 --bar test_single_mutant {}
fi
Incremental Testing
Only test new mutants if running repeatedly:
# Skip already-tested mutants
for mutant_dir in "$MUTANTS_DIR"/mutant-*; do
MUTANT_ID=$(basename "$mutant_dir")
RESULT_FILE="$RESULTS_DIR/${MUTANT_ID}-result.json"
if [ -f "$RESULT_FILE" ]; then
echo "Skipping $MUTANT_ID (already tested)"
continue
fi
# Test mutant...
done
mutation-report.json - Complete results in JSON formatmutation-report.html - Visual HTML report (default)mutation-report.md - Markdown report for PRs{mutant-id}-output.txt - Test output for each mutant{mutant-id}-result.json - Result metadata for each mutantresearch
Shared engine for analyzing Jira issue activity and generating status summaries
testing
Snapshot OpenShift payload data (release controller, PR diffs, comments, CI jobs, JUnit results, regression tracking) to a local directory for offline analysis
development
Analyze a payload snapshot to identify root causes of blocking job failures, score candidate PRs, and produce an HTML report with revert recommendations
tools
Create TRT JIRA bugs, open revert PRs, and trigger payload jobs for high-confidence revert candidates