main.odin (3319B)
1 /* 2 sqlite3-fuzz runs jm:sqlite3/fuzz for as long as it is asked to and reports 3 what did not hold. 4 5 sqlite3-fuzz 1000 cases from a seed off the clock 6 sqlite3-fuzz -iters=1000000 a million cases 7 sqlite3-fuzz -for=30s as many as fit in thirty seconds 8 sqlite3-fuzz -seed=12345 replay a reported seed exactly 9 sqlite3-fuzz -quiet only the summary 10 11 The exit status is 0 when every property held and 1 when one did not, so it 12 drops into a pipeline. A failure prints the seed to replay it with. 13 14 Build it with -sanitize:address to put the FFI boundary under a sanitizer as 15 well; `just fuzz asan=1` does that. 16 */ 17 package main 18 19 import "core:fmt" 20 import "core:os" 21 import "core:strconv" 22 import "core:strings" 23 import "core:time" 24 25 import "jm:sqlite3/fuzz" 26 27 main :: proc() { 28 opts := fuzz.Opts { 29 log = report, 30 } 31 for arg in os.args[1:] { 32 switch { 33 case arg == "-h", arg == "--help": 34 fmt.eprintln(USAGE) 35 os.exit(2) 36 case arg == "-quiet": 37 opts.log = nil 38 case arg == "-stop": 39 opts.stop_on_first = true 40 case strings.has_prefix(arg, "-seed="): 41 opts.seed = u64(number(arg, "-seed=")) 42 case strings.has_prefix(arg, "-iters="): 43 opts.iterations = number(arg, "-iters=") 44 case strings.has_prefix(arg, "-for="): 45 opts.duration = span(arg[len("-for="):]) 46 case: 47 fmt.eprintfln("sqlite3-fuzz: unknown argument %s", arg) 48 fmt.eprintln(USAGE) 49 os.exit(2) 50 } 51 } 52 // A run bounded only by time needs no iteration ceiling to stop at. 53 if opts.duration > 0 && opts.iterations == 0 { 54 opts.iterations = max(int) 55 } 56 57 report := fuzz.run(opts) 58 fmt.printfln( 59 "sqlite3-fuzz: %d iterations in %v, seed %d, %d failures", 60 report.iterations, 61 time.duration_round(report.elapsed, time.Millisecond), 62 report.seed, 63 len(report.failures), 64 ) 65 if len(report.failures) == 0 { 66 return 67 } 68 for f in report.failures { 69 fmt.eprintfln(" %s at iteration %d: %s", f.property, f.iteration, f.detail) 70 } 71 fmt.eprintfln("replay with: sqlite3-fuzz -seed=%d", report.failures[0].seed) 72 os.exit(1) 73 } 74 75 USAGE :: `usage: sqlite3-fuzz [-seed=N] [-iters=N] [-for=30s] [-stop] [-quiet]` 76 77 // number reads the digits after a flag's prefix, or gives up loudly: a 78 // mistyped bound that silently became zero would report a clean run. 79 number :: proc(arg, prefix: string) -> int { 80 v, ok := strconv.parse_int(arg[len(prefix):]) 81 if !ok || v < 0 { 82 fmt.eprintfln("sqlite3-fuzz: %s needs a whole number, got %s", prefix, arg) 83 os.exit(2) 84 } 85 return v 86 } 87 88 // span reads a duration written as 30s, 5m or 250ms. 89 span :: proc(s: string) -> time.Duration { 90 unit := time.Second 91 digits := s 92 switch { 93 case strings.has_suffix(s, "ms"): 94 unit, digits = time.Millisecond, s[:len(s) - 2] 95 case strings.has_suffix(s, "s"): 96 unit, digits = time.Second, s[:len(s) - 1] 97 case strings.has_suffix(s, "m"): 98 unit, digits = time.Minute, s[:len(s) - 1] 99 case strings.has_suffix(s, "h"): 100 unit, digits = time.Hour, s[:len(s) - 1] 101 } 102 v, ok := strconv.parse_int(digits) 103 if !ok || v < 0 { 104 fmt.eprintfln("sqlite3-fuzz: -for needs a duration like 30s, got %s", s) 105 os.exit(2) 106 } 107 return time.Duration(v) * unit 108 } 109 110 // report is what the run calls as it goes, so a long run says something 111 // before it finishes. 112 report :: proc(format: string, args: ..any) { 113 fmt.eprintfln(format, ..args) 114 }