The story begins with this
It was a dark and stormy night...
A researcher was using a dial up modem to connect to his university computer cluster during an electrical storm. Random noise was fed over the telephone line, and everything crashed. He came up with the idea of doing this intentionally to test programs.
This has been done for X-windows system, windows NT, macOS, etc... Results have been getting worse on GUIs, but better on the command line. This testing, also called "fuzzing," has become a standard practice for security testing.
OCaml also has support for randomized testing. Let's go back to our leap year testing. QCheck is a library for doing quick, randomized checks.
#require "QCheck"
#require "OUnit2"
/Users/williambarkoff/.opam/cs3110-2021sp/lib/bytes: added to search path /Users/williambarkoff/.opam/cs3110-2021sp/lib/ocaml/unix.cma: loaded /Users/williambarkoff/.opam/cs3110-2021sp/lib/qcheck-core: added to search path /Users/williambarkoff/.opam/cs3110-2021sp/lib/qcheck-core/qcheck_core.cma: loaded /Users/williambarkoff/.opam/cs3110-2021sp/lib/qcheck-core/runner: added to search path /Users/williambarkoff/.opam/cs3110-2021sp/lib/qcheck-core/runner/qcheck_runner.cma: loaded /Users/williambarkoff/.opam/cs3110-2021sp/lib/stdlib-shims: added to search path /Users/williambarkoff/.opam/cs3110-2021sp/lib/stdlib-shims/stdlib_shims.cma: loaded /Users/williambarkoff/.opam/cs3110-2021sp/lib/ounit2/advanced: added to search path /Users/williambarkoff/.opam/cs3110-2021sp/lib/ounit2/advanced/oUnitAdvanced.cma: loaded /Users/williambarkoff/.opam/cs3110-2021sp/lib/ounit2: added to search path /Users/williambarkoff/.opam/cs3110-2021sp/lib/ounit2/oUnit.cma: loaded /Users/williambarkoff/.opam/cs3110-2021sp/lib/qcheck-ounit: added to search path /Users/williambarkoff/.opam/cs3110-2021sp/lib/qcheck-ounit/qcheck_ounit.cma: loaded /Users/williambarkoff/.opam/cs3110-2021sp/lib/QCheck: added to search path /Users/williambarkoff/.opam/cs3110-2021sp/lib/QCheck/qcheck.cma: loaded /Users/williambarkoff/.opam/cs3110-2021sp/lib/OUnit2: added to search path /Users/williambarkoff/.opam/cs3110-2021sp/lib/OUnit2/oUnit.cma: loaded
And let's bring back our things from earlier.
open OUnit2
let leap_year y =
y mod 4 = 0
&& (y mod 100 <> 0 || y mod 400 = 0)
let leap_year_test (n, y, b) =
n >:: fun _ -> assert_equal b (leap_year y)
val leap_year : int -> bool = <fun>
val leap_year_test : string * int * bool -> OUnit2.test = <fun>
Now let's generate some tests!
let mult_4_or_non_leap_year y =
y mod 4 == 0 || not (leap_year y)
let random_non_4s =
QCheck.Test.make ~count:1000 ~name:"non-multiples of 4 cannot be leap years"
QCheck.(1 -- 3000)
mult_4_or_non_leap_year
val mult_4_or_non_leap_year : int -> bool = <fun>
val random_non_4s : QCheck.Test.t = QCheck.Test.Test <abstr>
Here we generate a bunch of random years, then we check if they are either multiples of 4, or leap years. We can't generate all leap years or all not leap years, because that's the thing we're tyring the check!
let mult_400_or_non_leap_year y = y mod 400 = 0 || not (leap_year y)
let random_100s =
QCheck.Test.make ~count:1000
~name: "100s cannot be leap years unless also 400s"
QCheck.(map (( * ) 100) (1 -- 30))
mult_400_or_non_leap_year
val mult_400_or_non_leap_year : int -> bool = <fun>
val random_100s : QCheck.Test.t = QCheck.Test.Test <abstr>
We can then convert these to OUnit tests and throw them in our test suite!
let qcheck_tests = List.map QCheck_ounit.to_ounit2_test [
random_non_4s;
random_100s
]
val qcheck_tests : OUnit2.test list =
[OUnitTest.TestLabel ("non-multiples of 4 cannot be leap years",
OUnitTest.TestCase (OUnitTest.Long, <fun>));
OUnitTest.TestLabel ("100s cannot be leap years unless also 400s",
OUnitTest.TestCase (OUnitTest.Long, <fun>))]