Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/fortress-setup-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ jobs:
# Setup the configuration for the CI environment
# ----------------------------------------------------------------------------------
setup-config:
name: 🎯 Setup CI Config
name: 🔧 Setup CI Config
runs-on: ${{ inputs.primary-runner }}
outputs:
benchmarks-enabled: ${{ steps.config.outputs.benchmarks-enabled }}
Expand Down
175 changes: 175 additions & 0 deletions .github/workflows/fortress-test-fuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,179 @@ jobs:
mode: "text"
failures-file: "fuzz-failures.txt"

# ————————————————————————————————————————————————————————————————
# Create structured fuzz test failure summary for validation workflow
# ————————————————————————————————————————————————————————————————
- name: 📋 Create structured fuzz test failure summary
if: always() && steps.run-fuzz-tests.outputs.fuzz-exit-code != '0'
continue-on-error: true
run: |
echo "📋 Creating structured fuzz test failure summary..."

# Initialize the JSON structure
echo '[]' > test-failures-summary.json

if [ -f fuzz-output.log ]; then
echo "🔍 Processing fuzz test output for structured failures..."

# Extract failed fuzz tests from output log
# Pattern: --- FAIL: FuzzTestName (0.34s)
grep -E "^--- FAIL: Fuzz[A-Za-z0-9_]+" fuzz-output.log | while IFS= read -r fail_line; do
echo "📄 Processing failure line: $fail_line"

# Extract test name from the failure line
# Format: --- FAIL: FuzzGetTokenFromHeader (0.34s)
if [[ "$fail_line" =~ ^---[[:space:]]*FAIL:[[:space:]]*([^[:space:]]+) ]]; then
FUZZ_TEST_NAME="${BASH_REMATCH[1]}"
echo "📋 Found failed fuzz test: $FUZZ_TEST_NAME"

# Extract detailed error output for this specific test
# Start from the failure line and capture subsequent error details
ERROR_OUTPUT=$(awk "
BEGIN { capture = 0; found_start = 0; }
/^--- FAIL: $FUZZ_TEST_NAME/ {
found_start = 1;
capture = 1;
output = \$0;
next;
}
found_start && /^--- FAIL:/ && !/^--- FAIL: $FUZZ_TEST_NAME/ {
# Another test failure started, stop capturing
exit;
}
found_start && /^(PASS|ok |FAIL)/ && capture {
# Next test result line, stop capturing
exit;
}
found_start && capture && /^[[:space:]]/ {
# Indented lines are part of the test output
if (length(output) < 2000) {
output = output \"\\n\" \$0;
}
}
found_start && capture && /^[[:space:]]*$/ {
# Empty line, continue capturing briefly
next;
}
found_start && capture && !/^[[:space:]]/ && !/^(To re-run:|Failing input)/ {
# Non-indented line that's not our test, stop capturing unless it's re-run info
if (\$0 !~ /^github\.com/) exit;
}
END {
if (found_start) print output;
}
" fuzz-output.log)

# Get package name from go.mod or use git context fallback
PACKAGE_NAME="${{ github.repository }}"
if [[ -f go.mod ]]; then
PACKAGE_NAME=$(head -1 go.mod | awk '{print $2}')
elif [[ -n "${{ github.server_url }}" && -n "${{ github.repository }}" ]]; then
# Use GitHub context as fallback
PACKAGE_NAME="${{ github.server_url }}/${{ github.repository }}"
PACKAGE_NAME=${PACKAGE_NAME#https://}
fi

# Calculate elapsed time from the failure line (extract from parentheses)
ELAPSED="unknown"
if [[ "$fail_line" =~ \(([0-9.]+[a-z]*)\) ]]; then
ELAPSED="${BASH_REMATCH[1]}"
fi

# Create JSON entry for this failed fuzz test
FUZZ_JSON=$(jq -n \
--arg pkg "$PACKAGE_NAME" \
--arg test "$FUZZ_TEST_NAME" \
--arg output "$ERROR_OUTPUT" \
--arg elapsed "$ELAPSED" \
'{
Package: $pkg,
Type: "test",
failures: [{
Test: $test,
Elapsed: $elapsed,
Output: $output
}]
}')

echo "📝 Adding fuzz test failure to summary:"
echo "$FUZZ_JSON" | jq '.'

# Add to summary file
jq --argjson new_entry "$FUZZ_JSON" '. += [$new_entry]' test-failures-summary.json > test-failures-summary.json.tmp
mv test-failures-summary.json.tmp test-failures-summary.json
fi
done

# Also update the signatures file if it exists but is empty
if [[ -f fuzz-failures-signatures.json ]] && [[ $(jq 'length' fuzz-failures-signatures.json 2>/dev/null || echo "0") -eq 0 ]]; then
echo "📝 Updating empty signatures file with fuzz test failures..."

# Get package name from go.mod or use git context fallback
PACKAGE_NAME="${{ github.repository }}"
if [[ -f go.mod ]]; then
PACKAGE_NAME=$(head -1 go.mod | awk '{print $2}')
elif [[ -n "${{ github.server_url }}" && -n "${{ github.repository }}" ]]; then
# Use GitHub context as fallback
PACKAGE_NAME="${{ github.server_url }}/${{ github.repository }}"
PACKAGE_NAME=${PACKAGE_NAME#https://}
fi

# Create signature entries from the failures
SIGNATURE_ENTRIES='[]'
if [[ -f test-failures-summary.json ]] && [[ $(jq 'length' test-failures-summary.json 2>/dev/null || echo "0") -gt 0 ]]; then
# Store GitHub Actions values in bash variable for proper escaping
MATRIX_JOB_ID="fuzz-${{ inputs.primary-runner }}-${{ inputs.go-primary-version }}"

SIGNATURE_ENTRIES=$(jq --arg matrix_job "$MATRIX_JOB_ID" '[
.[] as $parent | $parent.failures[] | {
type: "test",
package: $parent.Package,
test: .Test,
output: .Output,
signature: ($parent.Package + ":" + .Test),
unique_id: (($parent.Package + ":" + .Test) | gsub("[^a-zA-Z0-9_/.-]"; "_")),
matrix_job: $matrix_job
}
]' test-failures-summary.json)
fi

echo "$SIGNATURE_ENTRIES" > fuzz-failures-signatures.json
echo "✅ Updated signatures file with $(echo "$SIGNATURE_ENTRIES" | jq 'length') entries"
fi

echo "✅ Structured failure summary created with $(jq 'length' test-failures-summary.json 2>/dev/null || echo "0") fuzz test packages"

# Debug: Show the created summary
if [[ -f test-failures-summary.json ]]; then
echo "📊 Final fuzz failure summary:"
jq . test-failures-summary.json
fi
else
echo "⚠️ No fuzz-output.log found, creating minimal failure entry"

# Create minimal entry when output log is missing
# Get package name from go.mod or use git context fallback
PACKAGE_NAME="${{ github.repository }}"
if [[ -f go.mod ]]; then
PACKAGE_NAME=$(head -1 go.mod | awk '{print $2}')
elif [[ -n "${{ github.server_url }}" && -n "${{ github.repository }}" ]]; then
# Use GitHub context as fallback
PACKAGE_NAME="${{ github.server_url }}/${{ github.repository }}"
PACKAGE_NAME=${PACKAGE_NAME#https://}
fi

jq -n --arg pkg "$PACKAGE_NAME" '[{
Package: $pkg,
Type: "test",
failures: [{
Test: "unknown_fuzz_test",
Elapsed: "unknown",
Output: "Fuzz test failed but no output log available (exit code: ${{ steps.run-fuzz-tests.outputs.fuzz-exit-code }})"
}]
}]' > test-failures-summary.json
fi

# ————————————————————————————————————————————————————————————————
# Fuzz test failure analysis and reporting
# ————————————————————————————————————————————————————————————————
Expand Down Expand Up @@ -317,6 +490,8 @@ jobs:
fuzz-output.log
fuzz-failures.txt
fuzz-failures-detailed.txt
test-failures-summary.json
fuzz-failures-signatures.json
retention-days: 1
if-no-files-found: ignore

Expand Down
71 changes: 70 additions & 1 deletion .github/workflows/fortress-test-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,22 @@ jobs:
if compgen -G "test-results/*test-failures-summary.json" >/dev/null 2>&1; then
for summary_file in test-results/*test-failures-summary.json; do
if [[ -s "$summary_file" ]]; then
LEGACY_COUNT=$(jq '[.[] | select(.failures) | .failures[]] | length' "$summary_file" 2>/dev/null || echo "0")
echo "📄 Processing summary file: $summary_file for failure counting"

# Count test failures - handle both regular test format and fuzz test format
LEGACY_TEST_COUNT=$(jq '[.[] | select(.failures) | .failures[]] | length' "$summary_file" 2>/dev/null || echo "0")

# Also count entries that are fuzz tests (Type == "test" and no .failures field)
FUZZ_TEST_COUNT=$(jq '[.[] | select(.Type == "test" and (.failures | length) > 0)] | length' "$summary_file" 2>/dev/null || echo "0")

# Count build failures
BUILD_COUNT=$(jq '[.[] | select(.Type == "build")] | length' "$summary_file" 2>/dev/null || echo "0")

LEGACY_COUNT=$((LEGACY_TEST_COUNT + FUZZ_TEST_COUNT))
TOTAL_FAILURES=$((TOTAL_FAILURES + LEGACY_COUNT + BUILD_COUNT))

echo " • Test failures: $LEGACY_TEST_COUNT, Fuzz failures: $FUZZ_TEST_COUNT, Build failures: $BUILD_COUNT"
echo " • Total from this file: $((LEGACY_COUNT + BUILD_COUNT))"
fi
done
fi
Expand Down Expand Up @@ -344,6 +357,39 @@ jobs:
fi
fi

# Fix: Update TOTAL_FAILURES if validation failed but we haven't counted any failures yet
# This handles the case where fuzz tests failed but TOTAL_FAILURES is still 0
if [[ "$VALIDATION_FAILED" == "true" ]] && [[ $TOTAL_FAILURES -eq 0 ]]; then
echo "🔧 Detected validation failure with 0 counted failures - recounting..."

# Recount failures including fuzz test failures that were just detected
if compgen -G "test-results/*test-failures-summary.json" >/dev/null 2>&1; then
for summary_file in test-results/*test-failures-summary.json; do
if [[ -s "$summary_file" ]]; then
echo "📄 Recounting failures from: $summary_file"

# Count all failure types
RECOUNT_TEST=$(jq '[.[] | select(.failures) | .failures[]] | length' "$summary_file" 2>/dev/null || echo "0")
RECOUNT_FUZZ=$(jq '[.[] | select(.Type == "test" and (.failures | length) > 0)] | length' "$summary_file" 2>/dev/null || echo "0")
RECOUNT_BUILD=$(jq '[.[] | select(.Type == "build")] | length' "$summary_file" 2>/dev/null || echo "0")

FILE_FAILURES=$((RECOUNT_TEST + RECOUNT_FUZZ + RECOUNT_BUILD))
TOTAL_FAILURES=$((TOTAL_FAILURES + FILE_FAILURES))

echo " • Found $FILE_FAILURES failures in this file (test:$RECOUNT_TEST, fuzz:$RECOUNT_FUZZ, build:$RECOUNT_BUILD)"
fi
done
fi

# If still no failures found but validation failed, use fallback
if [[ $TOTAL_FAILURES -eq 0 ]]; then
TOTAL_FAILURES=1
echo "⚠️ Using fallback count: validation failed but no structured failures found"
else
echo "✅ Recount successful: found $TOTAL_FAILURES total failures"
fi
fi

# Enhanced validation summary with deduplication info
echo ""
echo "🏁 Validation Summary:"
Expand Down Expand Up @@ -397,6 +443,29 @@ jobs:
"\n" + (.output // "No error output available")' deduplicated-failures.json 2>/dev/null | head -c 3000
fi

# Show fuzz test failures with special formatting
FUZZ_FAILURES=$(jq -r '.[] | select(.type == "test" and (.test | startswith("Fuzz"))) |
" 🎯 " + (.package | split("/") | .[-1] // .[-2] // .) + ": " + .test + " (" + (.occurrences | tostring) + " matrix jobs)"' deduplicated-failures.json 2>/dev/null)
if [[ -n "$FUZZ_FAILURES" ]]; then
echo ""
echo "🎯 Fuzz Test Failures:"
echo "---------------------"
echo "$FUZZ_FAILURES"

# Show detailed fuzz test outputs with enhanced formatting
echo ""
echo "📝 Fuzz Test Error Details:"
echo "--------------------------"
jq -r '.[] | select(.type == "test" and (.test | startswith("Fuzz"))) |
"🎯 " + .test + " (" + (.package | split("/") | .[-1] // .[-2] // .) + "):" +
"\n🔍 Error Output:" +
"\n" + (.output // "No fuzz test output available") +
"\n" +
(if (.output | contains("Failing input written to")) then
"\n💡 Re-run command: " + ((.output | split("\n")[] | select(. | contains("go test -run="))) // "Check output above for re-run command")
else "" end)' deduplicated-failures.json 2>/dev/null | head -c 4000
fi

# Fallback to old structured failure details
elif compgen -G "test-results/*test-failures-summary.json" >/dev/null 2>&1; then
echo "📋 Found legacy structured failure details:"
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/fortress.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ------------------------------------------------------------------------------------
# 🏰 GoFortress - Enterprise-grade CI/CD fortress for Go applications
#
# Version: 1.1.0 | Released: 2025-06-01
# Version: 1.0.1 | Released: 2025-09-09
#
# Built Strong. Tested Harder.
#
Expand All @@ -18,6 +18,7 @@
# 🚀 Release Citadel: Automated deployments with GoReleaser and GoDocs
#
# Maintainer: @mrz1836
# Repository: https://github.com/mrz1836/go-fortress
#
# Copyright 2025 @mrz1836
# SPDX-License-Identifier: MIT
Expand Down Expand Up @@ -93,7 +94,7 @@ jobs:
# Setup Configuration Workflow
# ----------------------------------------------------------------------------------
setup:
name: 🎯 Setup Configuration
name: 🔧 Setup Configuration
needs: [load-env]
permissions:
contents: read # Read repository content for setup configuration
Expand Down