jm

Odin for scripts: small packages and a runner, on core: only
Log | Files | Refs | README

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 }