|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +# Progress bar wrapper for libFuzzer |
| 4 | +# Usage: ./scripts/fuzz-progress.sh <fuzzer_binary> <max_time_seconds> [fuzzer_args...] |
| 5 | +# |
| 6 | +# Features: |
| 7 | +# - Visual progress bar using Unicode block characters |
| 8 | +# - Real-time metrics (coverage, corpus size, exec/s) |
| 9 | +# - Automatic quiet mode in CI/CD environments |
| 10 | +# - Elapsed/remaining time display |
| 11 | +# |
| 12 | +# CI/CD Detection: |
| 13 | +# - Set CI=true, GITHUB_ACTIONS=true, or JENKINS_URL to enable quiet mode |
| 14 | +# - Or run with FUZZ_QUIET=1 to force quiet mode |
| 15 | + |
| 16 | +set -e |
| 17 | + |
| 18 | +# Check arguments |
| 19 | +if [ $# -lt 2 ]; then |
| 20 | + echo "Usage: $0 <fuzzer_binary> <max_time_seconds> [fuzzer_args...]" |
| 21 | + echo "Example: $0 tests/fuzz 60 -max_len=1024" |
| 22 | + exit 1 |
| 23 | +fi |
| 24 | + |
| 25 | +FUZZER="$1" |
| 26 | +MAX_TIME="$2" |
| 27 | +shift 2 |
| 28 | + |
| 29 | +# Validate MAX_TIME is a positive integer |
| 30 | +if ! [[ "$MAX_TIME" =~ ^[0-9]+$ ]] || [ "$MAX_TIME" -eq 0 ]; then |
| 31 | + echo "Error: max_time_seconds must be a positive integer (got: '$MAX_TIME')" |
| 32 | + exit 1 |
| 33 | +fi |
| 34 | + |
| 35 | +# Remaining arguments are passed to fuzzer (use array to handle spaces) |
| 36 | +FUZZER_ARGS=("$@") |
| 37 | + |
| 38 | +# Detect CI/CD environment |
| 39 | +is_ci() { |
| 40 | + [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ] || [ -n "$JENKINS_URL" ] || |
| 41 | + [ -n "$GITLAB_CI" ] || [ -n "$CIRCLECI" ] || [ -n "$TRAVIS" ] || |
| 42 | + [ -n "$FUZZ_QUIET" ] |
| 43 | +} |
| 44 | + |
| 45 | +# ANSI color codes |
| 46 | +if [ -t 1 ] && ! is_ci; then |
| 47 | + COLOR_GREEN="\033[32m" |
| 48 | + COLOR_YELLOW="\033[33m" |
| 49 | + COLOR_CYAN="\033[36m" |
| 50 | + COLOR_BOLD="\033[1m" |
| 51 | + COLOR_RESET="\033[0m" |
| 52 | + CLEAR_LINE="\033[2K\r" |
| 53 | +else |
| 54 | + COLOR_GREEN="" |
| 55 | + COLOR_YELLOW="" |
| 56 | + COLOR_CYAN="" |
| 57 | + COLOR_BOLD="" |
| 58 | + COLOR_RESET="" |
| 59 | + CLEAR_LINE="" |
| 60 | +fi |
| 61 | + |
| 62 | +# Progress bar configuration |
| 63 | +BAR_WIDTH=30 |
| 64 | + |
| 65 | +# Draw progress bar |
| 66 | +draw_progress_bar() { |
| 67 | + local percentage=$1 |
| 68 | + local filled=$((percentage * BAR_WIDTH / 100)) |
| 69 | + local empty=$((BAR_WIDTH - filled)) |
| 70 | + |
| 71 | + printf "${COLOR_CYAN}" |
| 72 | + for ((i = 0; i < filled; i++)); do printf "█"; done |
| 73 | + for ((i = 0; i < empty; i++)); do printf "░"; done |
| 74 | + printf "${COLOR_RESET}" |
| 75 | +} |
| 76 | + |
| 77 | +# Format time as MM:SS |
| 78 | +format_time() { |
| 79 | + local seconds=$1 |
| 80 | + printf "%02d:%02d" $((seconds / 60)) $((seconds % 60)) |
| 81 | +} |
| 82 | + |
| 83 | +# Parse libFuzzer output and extract metrics |
| 84 | +parse_metrics() { |
| 85 | + local line="$1" |
| 86 | + |
| 87 | + # Extract coverage (cov: or NEW: lines contain coverage info) |
| 88 | + if [[ "$line" =~ cov:\ *([0-9]+) ]]; then |
| 89 | + echo "cov=${BASH_REMATCH[1]}" |
| 90 | + fi |
| 91 | + |
| 92 | + # Extract corpus size |
| 93 | + if [[ "$line" =~ corp:\ *([0-9]+) ]]; then |
| 94 | + echo "corp=${BASH_REMATCH[1]}" |
| 95 | + fi |
| 96 | + |
| 97 | + # Extract exec/s |
| 98 | + if [[ "$line" =~ exec/s:\ *([0-9]+) ]]; then |
| 99 | + echo "execs=${BASH_REMATCH[1]}" |
| 100 | + fi |
| 101 | + |
| 102 | + # Detect special events |
| 103 | + if [[ "$line" =~ (BINGO|ERROR|CRASH|TIMEOUT|SUMMARY) ]]; then |
| 104 | + echo "event=${BASH_REMATCH[1]}" |
| 105 | + fi |
| 106 | +} |
| 107 | + |
| 108 | +# Main progress display function |
| 109 | +run_with_progress() { |
| 110 | + local start_time=$(date +%s) |
| 111 | + local coverage=0 |
| 112 | + local corpus=0 |
| 113 | + local execs=0 |
| 114 | + local last_event="" |
| 115 | + |
| 116 | + # Create temporary file for fuzzer output |
| 117 | + local tmpfile=$(mktemp) |
| 118 | + trap "rm -f $tmpfile" EXIT |
| 119 | + |
| 120 | + # Start fuzzer in background |
| 121 | + "$FUZZER" -max_total_time="$MAX_TIME" "${FUZZER_ARGS[@]}" 2>&1 | tee "$tmpfile" | |
| 122 | + while IFS= read -r line; do |
| 123 | + # Parse metrics from line |
| 124 | + metrics=$(parse_metrics "$line") |
| 125 | + |
| 126 | + for metric in $metrics; do |
| 127 | + case "$metric" in |
| 128 | + cov=*) coverage="${metric#cov=}" ;; |
| 129 | + corp=*) corpus="${metric#corp=}" ;; |
| 130 | + execs=*) execs="${metric#execs=}" ;; |
| 131 | + event=*) last_event="${metric#event=}" ;; |
| 132 | + esac |
| 133 | + done |
| 134 | + |
| 135 | + # Calculate timing |
| 136 | + local now=$(date +%s) |
| 137 | + local elapsed=$((now - start_time)) |
| 138 | + local remaining=$((MAX_TIME - elapsed)) |
| 139 | + if [ $remaining -lt 0 ]; then remaining=0; fi |
| 140 | + |
| 141 | + local percentage=$((elapsed * 100 / MAX_TIME)) |
| 142 | + if [ $percentage -gt 100 ]; then percentage=100; fi |
| 143 | + |
| 144 | + # Display progress line |
| 145 | + printf "${CLEAR_LINE}" |
| 146 | + draw_progress_bar $percentage |
| 147 | + printf " ${COLOR_BOLD}%3d%%${COLOR_RESET}" $percentage |
| 148 | + printf " ${COLOR_GREEN}%s${COLOR_RESET}/%s" "$(format_time $elapsed)" "$(format_time $MAX_TIME)" |
| 149 | + printf " ${COLOR_YELLOW}cov:%d${COLOR_RESET}" $coverage |
| 150 | + printf " corp:%d" $corpus |
| 151 | + printf " exec/s:%d" $execs |
| 152 | + |
| 153 | + # Show special events |
| 154 | + if [ -n "$last_event" ]; then |
| 155 | + printf " ${COLOR_BOLD}[%s]${COLOR_RESET}" "$last_event" |
| 156 | + fi |
| 157 | + done |
| 158 | + |
| 159 | + # Final newline |
| 160 | + printf "\n" |
| 161 | + |
| 162 | + # Check for crashes |
| 163 | + if grep -q "SUMMARY.*: [1-9]" "$tmpfile" 2>/dev/null; then |
| 164 | + echo "" |
| 165 | + echo "${COLOR_BOLD}Fuzzing found issues. Check crash files in the current directory.${COLOR_RESET}" |
| 166 | + return 1 |
| 167 | + fi |
| 168 | + |
| 169 | + return 0 |
| 170 | +} |
| 171 | + |
| 172 | +# Run in quiet mode (for CI/CD) |
| 173 | +run_quiet() { |
| 174 | + echo "Running fuzzer for ${MAX_TIME}s (quiet mode)..." |
| 175 | + "$FUZZER" -max_total_time="$MAX_TIME" "${FUZZER_ARGS[@]}" |
| 176 | + local status=$? |
| 177 | + |
| 178 | + if [ $status -eq 0 ]; then |
| 179 | + echo "Fuzzing completed successfully." |
| 180 | + else |
| 181 | + echo "Fuzzing found issues (exit code: $status)." |
| 182 | + fi |
| 183 | + |
| 184 | + return $status |
| 185 | +} |
| 186 | + |
| 187 | +# Main entry point |
| 188 | +main() { |
| 189 | + # Verify fuzzer exists |
| 190 | + if [ ! -x "$FUZZER" ]; then |
| 191 | + echo "Error: Fuzzer binary '$FUZZER' not found or not executable." |
| 192 | + exit 1 |
| 193 | + fi |
| 194 | + |
| 195 | + echo "sse2neon Fuzzer" |
| 196 | + echo "===============" |
| 197 | + echo "Binary: $FUZZER" |
| 198 | + echo "Duration: ${MAX_TIME}s" |
| 199 | + echo "Extra args: ${FUZZER_ARGS[*]:-none}" |
| 200 | + echo "" |
| 201 | + |
| 202 | + if is_ci; then |
| 203 | + run_quiet |
| 204 | + else |
| 205 | + run_with_progress |
| 206 | + fi |
| 207 | +} |
| 208 | + |
| 209 | +main |
0 commit comments