jm

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

fuzz_test.odin (2585B)


      1 package fuzz
      2 
      3 import "core:testing"
      4 import "core:time"
      5 
      6 // A short run on fixed seeds, so `just test` catches a regression the
      7 // example-based tests would not: they compare each column while the cursor is
      8 // still on its row, which is exactly when a column read that forgot to clone
      9 // still looks correct. Long runs are `just fuzz`.
     10 @(test)
     11 properties_hold :: proc(t: ^testing.T) {
     12 	for seed in ([]u64{1, 3, 7, 99}) {
     13 		report := run({seed = seed, iterations = 600, case_timeout = 30 * time.Second})
     14 		testing.expect_value(t, report.iterations, 600)
     15 		for f in report.failures {
     16 			testing.expectf(
     17 				t,
     18 				false,
     19 				"%s failed at iteration %d (replay: sqlite3-fuzz -seed=%d): %s",
     20 				f.property,
     21 				f.iteration,
     22 				f.seed,
     23 				f.detail,
     24 			)
     25 		}
     26 	}
     27 }
     28 
     29 // A run must be a pure function of its seed, or a reported failure cannot be
     30 // replayed and the harness is not worth having. The digest is what makes that
     31 // checkable on a run where nothing failed, which is most of them.
     32 @(test)
     33 same_seed_same_run :: proc(t: ^testing.T) {
     34 	first := run({seed = 42, iterations = 120})
     35 	second := run({seed = 42, iterations = 120})
     36 	// Absolute, so two equally empty reports cannot satisfy the pairwise
     37 	// comparisons below.
     38 	testing.expect_value(t, first.iterations, 120)
     39 	testing.expect_value(t, second.iterations, 120)
     40 	testing.expect(t, first.digest != 0, "a run that generated cases has a digest")
     41 	testing.expect_value(t, first.seed, 42)
     42 	testing.expect_value(t, first.digest, second.digest)
     43 	testing.expect_value(t, len(first.failures), len(second.failures))
     44 	for f, i in first.failures {
     45 		testing.expect_value(t, f.property, second.failures[i].property)
     46 		testing.expect_value(t, f.iteration, second.failures[i].iteration)
     47 		testing.expect_value(t, f.detail, second.failures[i].detail)
     48 	}
     49 }
     50 
     51 // A different seed must reach different cases, or the digest above would hold
     52 // for any two runs and prove nothing.
     53 @(test)
     54 different_seed_different_run :: proc(t: ^testing.T) {
     55 	a := run({seed = 42, iterations = 120})
     56 	b := run({seed = 43, iterations = 120})
     57 	testing.expect(t, a.digest != b.digest, "two seeds must not generate the same cases")
     58 }
     59 
     60 // A run asked for no particular seed must still report the one it used, or a
     61 // random run that fails cannot be turned back into a deterministic one.
     62 @(test)
     63 unset_seed_is_filled_in :: proc(t: ^testing.T) {
     64 	report := run({iterations = 6})
     65 	testing.expect(t, report.seed != 0, "the seed used must come back in the report")
     66 	replay := run({seed = report.seed, iterations = 6})
     67 	testing.expect_value(t, replay.digest, report.digest)
     68 }