pax_global_header00006660000000000000000000000064142104030770014510gustar00rootroot0000000000000052 comment=bfd27e495251fd5e23b8b23e01ef42ba185acc4c crowbar-0.2.1/000077500000000000000000000000001421040307700131475ustar00rootroot00000000000000crowbar-0.2.1/.gitignore000066400000000000000000000000311421040307700151310ustar00rootroot00000000000000_build/ _opam/ *.install crowbar-0.2.1/CHANGES.md000066400000000000000000000003431421040307700145410ustar00rootroot00000000000000v0.2.1 (04 March 2022) --------------------- Build and compatibility fixes. v0.2 (04 May 2020) --------------------- New generators, printers and port to dune. v0.1 (01 February 2018) --------------------- Initial release crowbar-0.2.1/LICENSE.md000066400000000000000000000020421421040307700145510ustar00rootroot00000000000000Copyright (c) 2017 Stephen Dolan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. crowbar-0.2.1/README.md000066400000000000000000000073241421040307700144340ustar00rootroot00000000000000# Crowbar **Crowbar** is a library for testing code, combining QuickCheck-style property-based testing and the magical bug-finding powers of [afl-fuzz](http://lcamtuf.coredump.cx/afl/). ## TL;DR There are [some examples](./examples). Some brief hints: 1. Use an opam switch with AFL instrumentation enabled (e.g. `opam sw 4.04.0+afl`). 2. Run in AFL mode with `afl-fuzz -i in -o out -- ./_build/myprog.exe @@`. 3. If you run your executable without arguments, crowbar will perform some simple (non-AFL) testing instead. 4. Test binaries have a small amount of documentation, available with `--help`. ## writing tests To test your software, come up with a property you'd like to test, then decide on the input you'd like for Crowbar to vary. A Crowbar test is some invocation of `Crowbar.check_eq` or `Crowbar.check`: ```ocaml let identity x = Crowbar.check_eq x x ``` and instructions for running the test with generated items with `Crowbar.add_test`: ```ocaml let () = Crowbar.(add_test ~name:"identity function" [int] (fun i -> identity i)) ``` There are [more examples available](./examples), with varying levels complexity. ## building tests Include `crowbar` in your list of dependencies via your favorite build system. The resulting executable is a Crowbar test. (Be sure to build a native-code executable, not bytecode.) To build tests that run under AFL, you'll need to build your tests with a compiler that has AFL instrumentation enabled. (You can also enable it specifically for your build, although this is not recommended if your code has any dependencies, including the OCaml standard library). OCaml compiler variants with AFL enabled by default are available in `opam` with the `+afl` tag. All versions published starting with 4.05.0 are available, along with a backported 4.04.0. ```shell $ opam switch 4.06.0+afl $ eval `opam config env` $ ./build_my_rad_test.sh # or your relevant build runes ``` ## running Tests Crowbar tests have two modes: * a simple quickcheck-like mode for testing propositions against totally random input * a mode using [afl-persistent](https://github.com/stedolan/ocaml-afl-persistent) to get good performance from `afl-fuzz` with OCaml's instrumentation enabled Crowbar tests can be directly invoked with `--help` for more documentation at runtime. ### fully random test mode If you wish to use the quickcheck-like, fully random mode to run all tests distributed here, build the tests as above and then run the binary with no arguments. ``` $ ./my_rad_test.exe | head -5 the first test: PASS the second test: PASS ``` ### AFL mode requirements To run the tests in AFL mode, you'll need to install American Fuzzy Lop ([latest source tarball](http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz), although your distribution may also have a package available). Once `afl-fuzz` is available on your system, create an `input` directory with a non-empty file in it (or use `test/input`, conveniently provided in this repository), and an `output` directory for `afl-fuzz` to store its findings. Then, invoke your test binary: ``` afl-fuzz -i test/input -o output ./my_rad_test.exe @@ ``` This will launch AFL, which will generate new test cases and track the exploration of the state space. When inputs are discovered which cause a property not to hold, they will be reported as crashes (along with actual crashes, although in the OCaml standard library these are rare). See the [afl-fuzz documentation](https://lcamtuf.coredump.cx/afl/status_screen.txt) for more on AFL's excellent interface. # What bugs have you found? [An open issue](https://github.com/stedolan/crowbar/issues/2) has a list of issues discovered by testing with Crowbar. If you use Crowbar to improve your software, please let us know! crowbar-0.2.1/crowbar.opam000066400000000000000000000022341421040307700154650ustar00rootroot00000000000000# This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "Write tests, let a fuzzer find failing cases" description: """ Crowbar is a library for testing code, combining QuickCheck-style property-based testing and the magical bug-finding powers of [afl-fuzz](http://lcamtuf.coredump.cx/afl/). """ maintainer: ["Stephen Dolan "] authors: ["Stephen Dolan "] license: "MIT" homepage: "https://github.com/stedolan/crowbar" bug-reports: "https://github.com/stedolan/crowbar/issues" depends: [ "dune" {>= "2.9"} "ocaml" {>= "4.08"} "ocplib-endian" "cmdliner" {>= "1.1.0"} "afl-persistent" {>= "1.1"} "calendar" {>= "2.00" & with-test} "fpath" {with-test} "pprint" {with-test} "uucp" {with-test} "uunf" {with-test} "uutf" {with-test} "odoc" {with-doc} ] build: [ ["dune" "subst"] {dev} [ "dune" "build" "-p" name "-j" jobs "--promote-install-files=false" "@install" "@runtest" {with-test} "@doc" {with-doc} ] ["dune" "install" "-p" name "--create-install-files" name] ] dev-repo: "git+https://github.com/stedolan/crowbar.git" crowbar-0.2.1/dune000066400000000000000000000000571421040307700140270ustar00rootroot00000000000000(env (dev (flags (:standard -warn-error -A)))) crowbar-0.2.1/dune-project000066400000000000000000000014301421040307700154670ustar00rootroot00000000000000(lang dune 2.9) (name crowbar) (formatting disabled) (generate_opam_files true) (source (github stedolan/crowbar)) (license MIT) (authors "Stephen Dolan ") (maintainers "Stephen Dolan ") (package (name crowbar) (synopsis "Write tests, let a fuzzer find failing cases") (description "\| Crowbar is a library for testing code, combining QuickCheck-style "\| property-based testing and the magical bug-finding powers of "\| [afl-fuzz](http://lcamtuf.coredump.cx/afl/). ) (depends (ocaml (>= "4.08")) ocplib-endian (cmdliner (>= 1.1.0)) (afl-persistent (>= "1.1")) ("calendar" (and (>= "2.00") :with-test)) ("fpath" :with-test) ("pprint" :with-test) ("uucp" :with-test) ("uunf" :with-test) ("uutf" :with-test))) crowbar-0.2.1/examples/000077500000000000000000000000001421040307700147655ustar00rootroot00000000000000crowbar-0.2.1/examples/.gitignore000066400000000000000000000000071421040307700167520ustar00rootroot00000000000000output crowbar-0.2.1/examples/calendar/000077500000000000000000000000001421040307700165365ustar00rootroot00000000000000crowbar-0.2.1/examples/calendar/dune000066400000000000000000000000731421040307700174140ustar00rootroot00000000000000(test (name test_calendar) (libraries crowbar calendar)) crowbar-0.2.1/examples/calendar/test_calendar.ml000066400000000000000000000012711421040307700217010ustar00rootroot00000000000000open Crowbar module C = CalendarLib.Calendar.Precise let time = map [int64] (fun a -> try C.from_mjd (Int64.to_float a /. 100_000_000_000_000.) with CalendarLib.Date.Out_of_bounds -> bad_test ()) let pp_time ppf t = pp ppf "%04d-%02d-%02d %02d:%02d:%02d" (C.year t) (C.month t |> C.Date.int_of_month) (C.day_of_month t) (C.hour t) (C.minute t) (C.second t) let time = with_printer pp_time time let period = map [const 0;const 0;int8;int8;int8;int8] C.Period.make let () = add_test ~name:"calendar" [time; time] @@ fun t1 t2 -> guard (C.compare t1 t2 < 0); check_eq ~pp:pp_time ~eq:C.equal (C.add t1 (C.precise_sub t2 t1)) t2 crowbar-0.2.1/examples/fpath/000077500000000000000000000000001421040307700160675ustar00rootroot00000000000000crowbar-0.2.1/examples/fpath/dune000066400000000000000000000001131421040307700167400ustar00rootroot00000000000000(test (name test_fpath) (modules test_fpath) (libraries crowbar fpath)) crowbar-0.2.1/examples/fpath/test_fpath.ml000066400000000000000000000007031421040307700205620ustar00rootroot00000000000000open Crowbar open Astring open Fpath let fpath = map [bytes] (fun s -> try v s with Invalid_argument _ -> bad_test ()) let () = add_test ~name:"segs" [fpath] @@ fun p -> let np = normalize p in assert (is_dir_path p = is_dir_path np); assert (is_file_path p = is_file_path np); assert (filename p = filename np); check_eq ~eq:equal p (v @@ (fst @@ split_volume p) ^ (String.concat ~sep:dir_sep (segs p))) crowbar-0.2.1/examples/input/000077500000000000000000000000001421040307700161245ustar00rootroot00000000000000crowbar-0.2.1/examples/input/testcase000066400000000000000000000000051421040307700176550ustar00rootroot00000000000000asdf crowbar-0.2.1/examples/map/000077500000000000000000000000001421040307700155425ustar00rootroot00000000000000crowbar-0.2.1/examples/map/dune000066400000000000000000000001111421040307700164110ustar00rootroot00000000000000(test (name test_map) (flags (:standard -w -27)) (libraries crowbar)) crowbar-0.2.1/examples/map/test_map.ml000066400000000000000000000031251421040307700177110ustar00rootroot00000000000000open Crowbar module Map = Map.Make (struct type t = int let compare (i : int) (j : int) = compare i j end) type t = ((int * int) list * int Map.t) let check_map ((list, map) : t) = let rec dedup k = function | [] -> [] | (k', v') :: rest when k = k' -> dedup k rest | (k', v') :: rest -> (k', v') :: dedup k' rest in let list = match List.stable_sort (fun a b -> compare (fst a) (fst b)) list with | [] -> [] | (k, v) :: rest -> (k, v) :: dedup k rest in List.for_all (fun (k, v) -> Map.find k map = v) list && list = Map.bindings map let map_gen : t gen = fix (fun map_gen -> choose [ const ([], Map.empty); map [uint8; uint8; map_gen] (fun k v (l, m) -> (k, v) :: l, Map.add k v m); map [uint8; uint8] (fun k v -> [k, v], Map.singleton k v); map [uint8; map_gen] (fun k (l, m) -> let rec rem_all k l = let l' = List.remove_assoc k l in if l = l' then l else rem_all k l' in rem_all k l, Map.remove k m); (* merge? *) map [map_gen; map_gen] (fun (l, m) (l', m') -> l @ l', Map.union (fun k a b -> Some a) m m'); map [uint8; map_gen] (fun k (list, map) -> let (l, v, r) = Map.split k map in let (l', vr') = List.partition (fun (kx,vx) -> kx < k) list in let r' = List.filter (fun (kx, vx) -> kx <> k) vr' in let v' = match List.assoc k vr' with n -> Some n | exception Not_found -> None in assert (v = v'); (l' @ List.map (fun (k,v) -> k,v+42) r', Map.union (fun k a b -> assert false) l (Map.map (fun v -> v + 42) r)))]) let () = add_test ~name:"map" [map_gen] @@ fun m -> check (check_map m) crowbar-0.2.1/examples/pprint/000077500000000000000000000000001421040307700163015ustar00rootroot00000000000000crowbar-0.2.1/examples/pprint/dune000066400000000000000000000000671421040307700171620ustar00rootroot00000000000000(test (name test_pprint) (libraries crowbar pprint)) crowbar-0.2.1/examples/pprint/test_pprint.ml000066400000000000000000000024161421040307700212110ustar00rootroot00000000000000open PPrint open Crowbar type t = (string * PPrint.document) let doc = fix (fun doc -> choose [ const ("", empty); const ("a", PPrint.char 'a'); const ("123", string "123"); const ("Hello", string "Hello"); const ("awordwhichisalittlebittoolong", string "awordwhichisalittlebittoolong"); const ("", hardline); map [range 10] (fun n -> ("", break n)); map [range 10] (fun n -> ("", break n)); map [doc; doc] (fun (sa,da) (sb,db) -> (sa ^ sb, da ^^ db)); map [range 10; doc] (fun n (s,d) -> (s, nest n d)); map [doc] (fun (s, d) -> (s, group d)); map [doc] (fun (s, d) -> (s, align d)) ]) let check_doc (s, d) = let b = Buffer.create 100 in let w = 40 in ToBuffer.pretty 1.0 w b d; let text = Bytes.to_string (Buffer.to_bytes b) in let ws = Str.regexp "[ \t\n\r]*" in (* Printf.printf "doc2{\n%s\n}%!" text; *) let del_ws = Str.global_replace ws "" in (* Printf.printf "[%s] = [%s]\n%!" (del_ws s) (del_ws text);*) Str.split (Str.regexp "\n") text |> List.iter (fun s -> let mspace = Str.regexp "[^ ] " in if String.length s > w then match Str.search_forward mspace s w with | _ -> assert false | exception Not_found -> ()); check_eq (del_ws s) (del_ws text) let () = add_test ~name:"pprint" [doc] check_doc crowbar-0.2.1/examples/serializer/000077500000000000000000000000001421040307700171365ustar00rootroot00000000000000crowbar-0.2.1/examples/serializer/dune000066400000000000000000000000641421040307700200140ustar00rootroot00000000000000(test (name test_serializer) (libraries crowbar)) crowbar-0.2.1/examples/serializer/serializer.ml000066400000000000000000000021301421040307700216350ustar00rootroot00000000000000type data = | Datum of string | Block of header * data list and header = string type _ ty = | Int : int ty | Bool : bool ty | Prod : 'a ty * 'b ty -> ('a * 'b) ty | List : 'a ty -> 'a list ty let rec pp_ty : type a . _ -> a ty -> unit = fun ppf -> let printf fmt = Format.fprintf ppf fmt in function | Int -> printf "Int" | Bool -> printf "Bool" | Prod(ta, tb) -> printf "Prod(%a,%a)" pp_ty ta pp_ty tb | List t -> printf "List(%a)" pp_ty t let rec serialize : type a . a ty -> a -> data = function | Int -> fun n -> Datum (string_of_int n) | Bool -> fun b -> Datum (string_of_bool b) | Prod (ta, tb) -> fun (va, vb) -> Block("pair", [serialize ta va; serialize tb vb]) | List t -> fun vs -> Block("list", List.map (serialize t) vs) let rec deserialize : type a . a ty -> data -> a = function[@warning "-8"] | Int -> fun (Datum s) -> int_of_string s | Bool -> fun (Datum s) -> bool_of_string s | Prod (ta, tb) -> fun (Block("pair", [sa; sb])) -> (deserialize ta sa, deserialize tb sb) | List t -> fun (Block("list", ss)) -> List.map (deserialize t) ss crowbar-0.2.1/examples/serializer/test_serializer.ml000066400000000000000000000026621421040307700227060ustar00rootroot00000000000000open Crowbar module S = Serializer type any_ty = Any : 'a S.ty -> any_ty let ty_gen = with_printer (fun ppf (Any t)-> S.pp_ty ppf t) @@ fix (fun ty_gen -> choose [ const (Any S.Int); const (Any S.Bool); map [ty_gen; ty_gen] (fun (Any ta) (Any tb) -> Any (S.Prod (ta, tb))); map [ty_gen] (fun (Any t) -> Any (List t)); ]) let prod_gen ga gb = map [ga; gb] (fun va vb -> (va, vb)) let rec gen_of_ty : type a . a S.ty -> a gen = function | S.Int -> int | S.Bool -> bool | S.Prod (ta, tb) -> prod_gen (gen_of_ty ta) (gen_of_ty tb) | S.List t -> list (gen_of_ty t) type pair = Pair : 'a S.ty * 'a -> pair (* The generator for the final value, [gen_of_ty t], depends on the generated type representation, [t]. This dynamic dependency cannot be expressed with [map], it requires [dynamic_bind]. *) let pair_gen : pair gen = dynamic_bind ty_gen @@ fun (Any t) -> map [gen_of_ty t] (fun v -> Pair (t, v)) let rec printer_of_ty : type a . a S.ty -> a printer = function | S.Int -> pp_int | S.Bool -> pp_bool | S.Prod (ta, tb) -> (fun ppf (a, b) -> pp ppf "(%a, %a)" (printer_of_ty ta) a (printer_of_ty tb) b) | S.List t -> pp_list (printer_of_ty t) let check_pair (Pair (t, v)) = let data = S.serialize t v in match S.deserialize t data with | exception _ -> fail "incorrect deserialization" | v' -> check_eq ~pp:(printer_of_ty t) v v' let () = add_test ~name:"pairs" [pair_gen] check_pair crowbar-0.2.1/examples/uunf/000077500000000000000000000000001421040307700157425ustar00rootroot00000000000000crowbar-0.2.1/examples/uunf/dune000066400000000000000000000000751421040307700166220ustar00rootroot00000000000000(test (name test_uunf) (libraries uunf uutf uucp crowbar)) crowbar-0.2.1/examples/uunf/test_uunf.ml000066400000000000000000000036221421040307700203130ustar00rootroot00000000000000open Crowbar let uchar = map [int32] (fun n -> let n = (Int32.to_int n land 0xFFFFFFF) mod 0x10FFFF in try Uchar.of_int n with Invalid_argument _ -> bad_test ()) let unicode = list1 uchar let norm form str = let n = Uunf.create form in let rec add acc v = match Uunf.add n v with | `Uchar u -> add (u :: acc) `Await | `Await | `End -> acc in let rec go acc = function | [] -> List.rev (add acc `End) | (v :: vs) -> go (add acc (`Uchar v)) vs in go [] str let unicode_to_string s = let b = Buffer.create 10 in List.iter (Uutf.Buffer.add_utf_8 b) s; Buffer.contents b let pp_unicode ppf s = Format.fprintf ppf "@["; Format.fprintf ppf "@[\"%s\"@]@ " (unicode_to_string s); s |> List.iter (fun u -> Format.fprintf ppf "@[U+%04x %s (%a)@]@ " (Uchar.to_int u) (Uucp.Name.name u) Uucp.Block.pp (Uucp.Block.block u)); Format.fprintf ppf "@]\n" let unicode = with_printer pp_unicode unicode let () = add_test ~name:"uunf" [unicode] @@ fun s -> let nfc = norm `NFC s in let nfd = norm `NFD s in let nfkc = norm `NFKC s in let nfkd = norm `NFKD s in (* [s; nfc; nfd; nfkc; nfkd] |> List.iter (fun s -> Printf.printf "[%s]\n" (unicode_to_string s)); Printf.printf "\n%!";*) let tests = [ nfc, [ norm `NFC nfc; norm `NFC nfd]; nfd, [ norm `NFD nfc; norm `NFD nfd]; nfkc, [ norm `NFC nfkc; norm `NFC nfkd; norm `NFKC nfc; norm `NFKC nfd; norm `NFKC nfkc; norm `NFKC nfkd]; nfkd, [ norm `NFD nfkc; norm `NFD nfkd; norm `NFKD nfc; norm `NFKD nfd; norm `NFKD nfkc; norm `NFKD nfkd] ] in tests |> List.iter (fun (s, eqs) -> List.iter (fun s' -> check_eq ~pp:pp_unicode s s') eqs) crowbar-0.2.1/examples/xmldiff/000077500000000000000000000000001421040307700164165ustar00rootroot00000000000000crowbar-0.2.1/examples/xmldiff/dune000066400000000000000000000001501421040307700172700ustar00rootroot00000000000000; disabled because of xmldiff compat issues ;(test ; (name test_xmldiff) ; (libraries xmldiff crowbar)) crowbar-0.2.1/examples/xmldiff/test_xmldiff.ml000066400000000000000000000020451421040307700214410ustar00rootroot00000000000000open Crowbar let ident = choose [const "a"; const "b"; const "c"] let elem_name = map [ident] (fun s -> ("", s)) let attrs = choose [ const Xmldiff.Nmap.empty; map [elem_name; ident] Xmldiff.Nmap.singleton ] let rec xml = lazy ( choose [ const (`D "a"); map [ident] (fun s -> `D s); map [elem_name; attrs; list (unlazy xml)] (fun s attrs elems -> let rec normalise = function | ([] | [_]) as x -> x | `E _ as el :: xs -> el :: normalise xs | `D s :: xs -> match normalise xs with | `D s' :: xs' -> `D (s ^ s') :: xs' | xs' -> `D s :: xs' in `E (s, attrs, normalise elems)) ]) let lazy xml = xml let xml = map [xml] (fun d -> `E (("", "a"), Xmldiff.Nmap.empty, [d])) let pp_xml ppf xml = pp ppf "%s" (Xmldiff.string_of_xml xml) let xml = with_printer pp_xml xml let () = add_test ~name:"xmldiff" [xml; xml] @@ fun xml1 xml2 -> let (patch, xml3) = Xmldiff.diff_with_final_tree xml1 xml2 in check_eq ~pp:pp_xml xml2 xml3 crowbar-0.2.1/src/000077500000000000000000000000001421040307700137365ustar00rootroot00000000000000crowbar-0.2.1/src/crowbar.ml000066400000000000000000000462261421040307700157410ustar00rootroot00000000000000type src = Random of Random.State.t | Fd of Unix.file_descr type state = { chan : src; buf : Bytes.t; mutable offset : int; mutable len : int } type 'a printer = Format.formatter -> 'a -> unit type 'a strat = | Choose of 'a gen list | Map : ('f, 'a) gens * 'f -> 'a strat | Bind : 'a gen * ('a -> 'b gen) -> 'b strat | Option : 'a gen -> 'a option strat | List : 'a gen -> 'a list strat | List1 : 'a gen -> 'a list strat | Unlazy of 'a gen Lazy.t | Primitive of (state -> 'a) | Print of 'a printer * 'a gen and 'a gen = { strategy: 'a strat; small_examples: 'a list; } and ('k, 'res) gens = | [] : ('res, 'res) gens | (::) : 'a gen * ('k, 'res) gens -> ('a -> 'k, 'res) gens type nonrec +'a list = 'a list = [] | (::) of 'a * 'a list let unlazy f = { strategy = Unlazy f; small_examples = [] } let fix f = let rec lazygen = lazy (f (unlazy lazygen)) in unlazy lazygen let map (type f) (type a) (gens : (f, a) gens) (f : f) = { strategy = Map (gens, f); small_examples = match gens with [] -> [f] | _ -> [] } let dynamic_bind m f = {strategy = Bind(m, f); small_examples = [] } let const x = map [] x let choose gens = { strategy = Choose gens; small_examples = List.map (fun x -> x.small_examples) gens |> List.concat } let option gen = { strategy = Option gen; small_examples = [None] } let list gen = { strategy = List gen; small_examples = [[]] } let list1 gen = { strategy = List1 gen; small_examples = List.map (fun x -> [x]) gen.small_examples } let primitive f ex = { strategy = Primitive f; small_examples = [ex] } let pair gena genb = map (gena :: genb :: []) (fun a b -> (a, b)) let concat_gen_list sep l = match l with | h::t -> List.fold_left (fun acc e -> map [acc; sep; e] (fun acc sep e -> acc ^ sep ^ e) ) h t | [] -> const "" let with_printer pp gen = {strategy = Print (pp, gen); small_examples = gen.small_examples } let result gena genb = choose [ map [gena] (fun va -> Ok va); map [genb] (fun vb -> Error vb); ] let pp = Format.fprintf let pp_int ppf n = pp ppf "%d" n let pp_int32 ppf n = pp ppf "%s" (Int32.to_string n) let pp_int64 ppf n = pp ppf "%s" (Int64.to_string n) let pp_float ppf f = pp ppf "%f" f let pp_bool ppf b = pp ppf "%b" b let pp_char ppf c = pp ppf "%c" c let pp_uchar ppf c = pp ppf "U+%04x" (Uchar.to_int c) let pp_string ppf s = pp ppf "\"%s\"" (String.escaped s) let pp_list pv ppf l = pp ppf "@[[%a]@]" (Format.pp_print_list ~pp_sep:(fun ppf () -> pp ppf ";@ ") pv) l let pp_option pv ppf = function | None -> Format.fprintf ppf "None" | Some x -> Format.fprintf ppf "(Some %a)" pv x exception BadTest of string exception FailedTest of unit printer let guard = function | true -> () | false -> raise (BadTest "guard failed") let bad_test () = raise (BadTest "bad test") let nonetheless = function | None -> bad_test () | Some a -> a let get_data chan buf off len = match chan with | Random rand -> for i = off to off + len - 1 do Bytes.set buf i (Char.chr (Random.State.bits rand land 0xff)) done; len - off | Fd ch -> Unix.read ch buf off len let refill src = assert (src.offset <= src.len); let remaining = src.len - src.offset in (* move remaining data to start of buffer *) Bytes.blit src.buf src.offset src.buf 0 remaining; src.len <- remaining; src.offset <- 0; let read = get_data src.chan src.buf remaining (Bytes.length src.buf - remaining) in if read = 0 then raise (BadTest "premature end of file") else src.len <- remaining + read let rec getbytes src n = assert (src.offset <= src.len); if n > Bytes.length src.buf then failwith "request too big"; if src.len - src.offset >= n then let off = src.offset in (src.offset <- src.offset + n; off) else (refill src; getbytes src n) let read_char src = let off = getbytes src 1 in Bytes.get src.buf off let read_byte src = Char.code (read_char src) let read_bool src = let n = read_byte src in n land 1 = 1 let bool = with_printer pp_bool (primitive read_bool false) let uint8 = with_printer pp_int (primitive read_byte 0) let int8 = with_printer pp_int (map [uint8] (fun n -> n - 128)) let read_uint16 src = let off = getbytes src 2 in EndianBytes.LittleEndian.get_uint16 src.buf off let read_int16 src = let off = getbytes src 2 in EndianBytes.LittleEndian.get_int16 src.buf off let uint16 = with_printer pp_int (primitive read_uint16 0) let int16 = with_printer pp_int (primitive read_int16 0) let read_int32 src = let off = getbytes src 4 in EndianBytes.LittleEndian.get_int32 src.buf off let read_int64 src = let off = getbytes src 8 in EndianBytes.LittleEndian.get_int64 src.buf off let int32 = with_printer pp_int32 (primitive read_int32 0l) let int64 = with_printer pp_int64 (primitive read_int64 0L) let int = with_printer pp_int (if Sys.word_size <= 32 then map [int32] Int32.to_int else map [int64] Int64.to_int) let float = with_printer pp_float (primitive (fun src -> let off = getbytes src 8 in EndianBytes.LittleEndian.get_double src.buf off) 0.) let char = with_printer pp_char (primitive read_char 'a') (* maybe print as a hexdump? *) let bytes = with_printer pp_string (primitive (fun src -> (* null-terminated, with '\001' as an escape code *) let buf = Bytes.make 64 '\255' in let rec read_bytes p = if p >= Bytes.length buf then p else match read_char src with | '\000' -> p | '\001' -> Bytes.set buf p (read_char src); read_bytes (p + 1) | c -> Bytes.set buf p c; read_bytes (p + 1) in let count = read_bytes 0 in Bytes.sub_string buf 0 count) "") let bytes_fixed n = with_printer pp_string (primitive (fun src -> let off = getbytes src n in Bytes.sub_string src.buf off n) (String.make n 'a')) let choose_int n state = assert (n > 0); if n = 1 then 0 else if (n <= 0x100) then read_byte state mod n else if (n < 0x1000000) then Int32.(to_int (abs (rem (read_int32 state) (of_int n)))) else Int64.(to_int (abs (rem (read_int64 state) (of_int n)))) let range ?(min=0) n = if n <= 0 then raise (Invalid_argument "Crowbar.range: argument n must be positive"); if min < 0 then raise (Invalid_argument "Crowbar.range: argument min must be positive or null"); with_printer pp_int (primitive (fun s -> min + choose_int n s) min) let uchar : Uchar.t gen = map [range 0x110000] (fun x -> guard (Uchar.is_valid x); Uchar.of_int x) let uchar = with_printer pp_uchar uchar let rec sequence = function g::gs -> map [g; sequence gs] (fun x xs -> x::xs) | [] -> const [] let shuffle_arr arr = let n = Array.length arr in let gs = List.init n (fun i -> range ~min:i (n - i)) in map [sequence gs] @@ fun js -> js |> List.iteri (fun i j -> let t = arr.(i) in arr.(i) <- arr.(j); arr.(j) <- t); arr let shuffle l = map [shuffle_arr (Array.of_list l)] Array.to_list exception GenFailed of exn * Printexc.raw_backtrace * unit printer let rec generate : type a . int -> state -> a gen -> a * unit printer = fun size input gen -> if size <= 1 && gen.small_examples <> [] then List.hd gen.small_examples, fun ppf () -> pp ppf "?" else match gen.strategy with | Choose gens -> (* FIXME: better distribution? *) (* FIXME: choices of size > 255? *) let n = choose_int (List.length gens) input in let v, pv = generate size input (List.nth gens n) in v, fun ppf () -> pp ppf "#%d %a" n pv () | Map ([], k) -> k, fun ppf () -> pp ppf "?" | Map (gens, f) -> let rec len : type k res . int -> (k, res) gens -> int = fun acc xs -> match xs with | [] -> acc | _ :: xs -> len (1 + acc) xs in let n = len 0 gens in (* the size parameter is (apparently?) meant to ensure that generation eventually terminates, by limiting the set of options from which the generator might choose once we've gotten deep into a tree. make sure we always mark our passing, even when we've mapped one value into another, so we don't blow the stack. *) let size = (size - 1) / n in let v, pvs = gen_apply size input gens f in begin match v with | Ok v -> v, pvs | Error (e, bt) -> raise (GenFailed (e, bt, pvs)) end | Bind (m, f) -> let index, pv_index = generate (size - 1) input m in let a, pv = generate (size - 1) input (f index) in a, (fun ppf () -> pp ppf "(%a) => %a" pv_index () pv ()) | Option gen -> if size < 1 then None, fun ppf () -> pp ppf "None" else if read_bool input then let v, pv = generate size input gen in Some v, fun ppf () -> pp ppf "Some (%a)" pv () else None, fun ppf () -> pp ppf "None" | List gen -> let elems = generate_list size input gen in List.map fst elems, fun ppf () -> pp_list (fun ppf (_, pv) -> pv ppf ()) ppf elems | List1 gen -> let elems = generate_list1 size input gen in List.map fst elems, fun ppf () -> pp_list (fun ppf (_, pv) -> pv ppf ()) ppf elems | Primitive gen -> gen input, fun ppf () -> pp ppf "?" | Unlazy gen -> generate size input (Lazy.force gen) | Print (ppv, gen) -> let v, _ = generate size input gen in v, fun ppf () -> ppv ppf v and generate_list : type a . int -> state -> a gen -> (a * unit printer) list = fun size input gen -> if size <= 1 then [] else if read_bool input then generate_list1 size input gen else [] and generate_list1 : type a . int -> state -> a gen -> (a * unit printer) list = fun size input gen -> let ans = generate (size/2) input gen in ans :: generate_list (size/2) input gen and gen_apply : type k res . int -> state -> (k, res) gens -> k -> (res, exn * Printexc.raw_backtrace) result * unit printer = fun size state gens f -> let rec go : type k res . int -> state -> (k, res) gens -> k -> (res, exn * Printexc.raw_backtrace) result * unit printer list = fun size input gens -> match gens with | [] -> fun x -> Ok x, [] | g :: gs -> fun f -> let v, pv = generate size input g in let res, pvs = match f v with | exception (BadTest _ as e) -> raise e | exception e -> Error (e, Printexc.get_raw_backtrace ()) , [] | fv -> go size input gs fv in res, pv :: pvs in let v, pvs = go size state gens f in let pvs = fun ppf () -> match pvs with | [pv] -> pv ppf () | pvs -> pp_list (fun ppf pv -> pv ppf ()) ppf pvs in v, pvs let fail s = raise (FailedTest (fun ppf () -> pp ppf "%s" s)) let failf format = Format.kasprintf fail format let check = function | true -> () | false -> raise (FailedTest (fun ppf () -> pp ppf "check false")) let check_eq ?pp:pv ?cmp ?eq a b = let pass = match eq, cmp with | Some eq, _ -> eq a b | None, Some cmp -> cmp a b = 0 | None, None -> Stdlib.compare a b = 0 in if pass then () else raise (FailedTest (fun ppf () -> match pv with | None -> pp ppf "different" | Some pv -> pp ppf "@[%a@ !=@ %a@]" pv a pv b)) let () = Printexc.record_backtrace true type test = Test : string * ('f, unit) gens * 'f -> test type test_status = | TestPass of unit printer | BadInput of string | GenFail of exn * Printexc.raw_backtrace * unit printer | TestExn of exn * Printexc.raw_backtrace * unit printer | TestFail of unit printer * unit printer let run_once (gens : (_, unit) gens) f state = match gen_apply 100 state gens f with | Ok (), pvs -> TestPass pvs | Error (FailedTest p, _), pvs -> TestFail (p, pvs) | Error (e, bt), pvs -> TestExn (e, bt, pvs) | exception (BadTest s) -> BadInput s | exception (GenFailed (e, bt, pvs)) -> GenFail (e, bt, pvs) let classify_status = function | TestPass _ -> `Pass | BadInput _ -> `Bad | GenFail _ -> `Fail (* slightly dubious... *) | TestExn _ | TestFail _ -> `Fail let print_status ppf status = let print_ex ppf (e, bt) = pp ppf "%s" (Printexc.to_string e); bt |> Printexc.raw_backtrace_to_string |> Str.split (Str.regexp "\n") |> List.iter (pp ppf "@,%s") in match status with | TestPass pvs -> pp ppf "When given the input:@.@[@,%a@,@]@.the test passed." pvs () | BadInput s -> pp ppf "The testcase was invalid:@.%s" s | GenFail (e, bt, pvs) -> pp ppf "When given the input:@.@[<4>%a@]@.the testcase generator threw an exception:@.@[@,%a@,@]" pvs () print_ex (e, bt) | TestExn (e, bt, pvs) -> pp ppf "When given the input:@.@[@,%a@,@]@.the test threw an exception:@.@[@,%a@,@]" pvs () print_ex (e, bt) | TestFail (err, pvs) -> pp ppf "When given the input:@.@[@,%a@,@]@.the test failed:@.@[@,%a@,@]" pvs () err () let prng_state_of_seed seed = (* try to make this independent of word size *) let seed = Int64.( [| to_int (logand (of_int 0xffff) seed); to_int (logand (of_int 0xffff) (shift_right seed 16)); to_int (logand (of_int 0xffff) (shift_right seed 32)); to_int (logand (of_int 0xffff) (shift_right seed 48)) |]) in Random.State.make seed let src_of_seed seed = Random (prng_state_of_seed seed) let run_test ~mode ~silent ?(verbose=false) (Test (name, gens, f)) = let show_status_line ?(clear=false) stat = Printf.printf "%s: %s\n" name stat; if clear then print_newline (); flush stdout in let ppf = Format.std_formatter in if not silent && Unix.isatty Unix.stdout then show_status_line ~clear:false "...."; let status = match mode with | `Once state -> run_once gens f state | `Repeat (iters, seedseed) -> let worst_status = ref (TestPass (fun _ () -> ())) in let npass = ref 0 in let nbad = ref 0 in let seedsrc = prng_state_of_seed seedseed in while !npass < iters && classify_status !worst_status = `Pass do let seed = Random.State.int64 seedsrc Int64.max_int in let state = { chan = src_of_seed seed; buf = Bytes.make 256 '0'; offset = 0; len = 0 } in let status = run_once gens f state in begin match classify_status status with | `Pass -> incr npass | `Bad -> incr nbad | `Fail -> worst_status := status end; done; let status = !worst_status in status in if silent && verbose && classify_status status = `Fail then begin show_status_line ~clear:true "FAIL"; pp ppf "%a@." print_status status; end; if not silent then begin match classify_status status with | `Pass -> show_status_line ~clear:true "PASS"; if verbose then pp ppf "%a@." print_status status | `Fail -> show_status_line ~clear:true "FAIL"; pp ppf "%a@." print_status status; | `Bad -> show_status_line ~clear:true "BAD"; pp ppf "%a@." print_status status; end; status exception TestFailure let run_all_tests seed repeat file verbosity infinity tests = match file with | None -> let seed = match seed with | Some seed -> seed | None -> Random.int64 (Int64.max_int) in if infinity then (* infinite QuickCheck mode *) let rec go ntests alltests tests = match tests with | [] -> go ntests alltests alltests | t :: rest -> if ntests mod 10000 = 0 then Printf.eprintf "\r%d%!" ntests; let chan = src_of_seed seed in let state = { chan ; buf = Bytes.make 256 '0'; offset = 0; len = 0 } in match classify_status (run_test ~mode:(`Once state) ~silent:true ~verbose:true t) with | `Fail -> Printf.printf "%d tests passed before first failure\n%!" ntests | _ -> go (ntests + 1) alltests rest in let () = go 0 tests tests in 1 else (* limited-run QuickCheck mode *) let failures = ref 0 in let () = tests |> List.iter (fun t -> match (run_test ~mode:(`Repeat (repeat, seed)) ~silent:false t |> classify_status) with | `Fail -> failures := !failures + 1 | _ -> () ) in !failures | Some file -> (* AFL mode *) let verbose = List.length verbosity > 0 in let () = AflPersistent.run (fun () -> let fd = Unix.openfile file [Unix.O_RDONLY] 0o000 in let state = { chan = Fd fd; buf = Bytes.make 256 '0'; offset = 0; len = 0 } in let status = try run_test ~mode:(`Once state) ~silent:false ~verbose @@ List.nth tests (choose_int (List.length tests) state) with BadTest s -> BadInput s in Unix.close fd; match classify_status status with | `Pass | `Bad -> () | `Fail -> Printexc.record_backtrace false; raise TestFailure) in 0 (* failures come via the exception mechanism above *) let last_generated_name = ref 0 let generate_name () = incr last_generated_name; "test" ^ string_of_int !last_generated_name let registered_tests = ref [] let add_test ?name gens f = let name = match name with | None -> generate_name () | Some name -> name in registered_tests := Test (name, gens, f) :: !registered_tests (* cmdliner stuff *) let randomness_file = let doc = "A file containing some bytes, consulted in constructing test cases. \ When `afl-fuzz` is calling the test binary, use `@@` to indicate that \ `afl-fuzz` should put its test case here \ (e.g. `afl-fuzz -i input -o output ./my_crowbar_test @@`). Re-run a test by \ supplying the test file here \ (e.g. `./my_crowbar_test output/crashes/id:000000`). If no file is \ specified, the test will use OCaml's Random module as a source of \ randomness for a predefined number of rounds." in Cmdliner.Arg.(value & pos 0 (some file) None & info [] ~doc ~docv:"FILE") let seed = let doc = "The seed (an int64) for the PRNG. Use as an alternative to FILE when running in non-AFL (quickcheck) mode." in Cmdliner.Arg.(value & opt (some int64) None & info ["s"; "seed"] ~doc ~docv:"SEED") let repeat = let doc = "The number of times to repeat the test in quick-check." in Cmdliner.Arg.(value & opt int 5000 & info ["r"; "repeat"] ~doc ~docv:"REPEAT") let verbosity = let doc = "Print information on each test as it's conducted." in Cmdliner.Arg.(value & flag_all & info ["v"; "verbose"] ~doc ~docv:"VERBOSE") let infinity = let doc = "In non-AFL (quickcheck) mode, continue running until a test failure is \ discovered. No attempt is made to track which tests have already been run, \ so some tests may be repeated, and if there are no failures reachable, the \ test will never terminate without outside intervention." in Cmdliner.Arg.(value & flag & info ["i"] ~doc ~docv:"INFINITE") let crowbar_info = Cmdliner.Cmd.info @@ Filename.basename Sys.argv.(0) let () = at_exit (fun () -> let t = !registered_tests in registered_tests := []; match t with | [] -> () | t -> let cmd = Cmdliner.Term.(const run_all_tests $ seed $ repeat $ randomness_file $ verbosity $ infinity $ const (List.rev t)) in exit @@ Cmdliner.Cmd.eval' ~catch:false (Cmdliner.Cmd.v crowbar_info cmd) ) crowbar-0.2.1/src/crowbar.mli000066400000000000000000000213401421040307700161000ustar00rootroot00000000000000(** {1:top Types } *) type 'a gen (** ['a gen] knows how to generate ['a] for use in Crowbar tests. *) type ('k, 'res) gens = | [] : ('res, 'res) gens | (::) : 'a gen * ('k, 'res) gens -> ('a -> 'k, 'res) gens (** multiple generators are passed to functions using a listlike syntax. for example, [map [int; int] (fun a b -> a + b)] *) type 'a printer = Format.formatter -> 'a -> unit (** pretty-printers for items generated by Crowbar; useful for the user in translating test failures into bugfixes. *) (**/**) (* re-export stdlib's list We only want to override [] syntax in the argument to Map *) type nonrec +'a list = 'a list = [] | (::) of 'a * 'a list (**/**) (** {1:generators Generators } *) (** {2:simple_generators Simple Generators } *) val int : int gen (** [int] generates an integer ranging from min_int to max_int, inclusive. If you need integers from a smaller domain, consider using {!range}. *) val uint8 : int gen (** [uint8] generates an unsigned byte, ranging from 0 to 255 inclusive. *) val int8 : int gen (** [int8] generates a signed byte, ranging from -128 to 127 inclusive. *) val uint16 : int gen (** [uint16] generates an unsigned 16-bit integer, ranging from 0 to 65535 inclusive. *) val int16 : int gen (** [int16] generates a signed 16-bit integer, ranging from -32768 to 32767 inclusive. *) val int32 : Int32.t gen (** [int32] generates a 32-bit signed integer. *) val int64 : Int64.t gen (** [int64] generates a 64-bit signed integer. *) val float : float gen (** [float] generates a double-precision floating-point number. *) val char : char gen (** [char] generates a char. *) val uchar : Uchar.t gen (** [uchar] generates a Unicode scalar value *) val bytes : string gen (** [bytes] generates a string of arbitrary length (including zero-length strings). *) val bytes_fixed : int -> string gen (** [bytes_fixed length] generates a string of the specified length. *) val bool : bool gen (** [bool] generates a yes or no answer. *) val range : ?min:int -> int -> int gen (** [range ?min n] is a generator for integers between [min] (inclusive) and [min + n] (exclusive). Default [min] value is 0. [range ?min n] will raise [Invalid_argument] for [n <= 0]. *) (** {2:generator_functions Functions on Generators } *) val map : ('f, 'a) gens -> 'f -> 'a gen (** [map gens map_fn] provides a means for creating generators using other generators' output. For example, one might generate a Char.t from a {!uint8}: {[ open Crowbar let char_gen : Char.t gen = map [uint8] Char.chr ]} *) val unlazy : 'a gen Lazy.t -> 'a gen (** [unlazy gen] forces the generator [gen]. It is useful when defining generators for recursive data types: {[ open Crowbar type a = A of int | Self of a let rec a_gen = lazy ( choose [ map [int] (fun i -> A i); map [(unlazy a_gen)] (fun s -> Self s); ]) let lazy a_gen = a_gen ]} *) val fix : ('a gen -> 'a gen) -> 'a gen (** [fix fn] applies the function [fn]. It is useful when defining generators for recursive data types: {[ open Crowbar type a = A of int | Self of a let rec a_gen = fix (fun a_gen -> choose [ map [int] (fun i -> A i); map [a_gen] (fun s -> Self s); ]) ]} *) val const : 'a -> 'a gen (** [const a] always generates [a]. *) val choose : 'a gen list -> 'a gen (** [choose gens] chooses a generator arbitrarily from [gens]. *) val option : 'a gen -> 'a option gen (** [option gen] generates either [None] or [Some x], where [x] is the item generated by [gen]. *) val pair : 'a gen -> 'b gen -> ('a * 'b) gen (** [pair gena gen] generates (a, b) where [a] is generated by [gena] and [b] by [genb]. *) val result : 'a gen -> 'b gen -> ('a, 'b) result gen (** [result gena genb] generates either [Ok va] or [Error vb], where [va], [vb] are generated by [gena], [genb] respectively. *) val list : 'a gen -> 'a list gen (** [list gen] makes a generator for lists using [gen]. Lists may be empty; for non-empty lists, use {!list1}. *) val list1 : 'a gen -> 'a list gen (** [list1 gen] makes non-empty list generators. For potentially empty lists, use {!list}.*) val shuffle : 'a list -> 'a list gen (** [shuffle l] generates random permutations of [l]. *) val concat_gen_list : string gen -> string gen list -> string gen (** [concat_gen_list sep l] concatenates a list of string gen [l] inserting the separator [sep] between each *) val with_printer : 'a printer -> 'a gen -> 'a gen (** [with_printer printer gen] generates the same values as [gen]. If [gen] is used to create a failing test case and the test was reached by calling [check_eq] without [pp] set, [printer] will be used to print the failing test case. *) val dynamic_bind : 'a gen -> ('a -> 'b gen) -> 'b gen (** [dynamic_bind gen f] is a monadic bind, it allows to express the generation of a value whose generator itself depends on a previously generated value. This is in contrast with [map gen f], where no further generation happens in [f] after [gen] has generated an element. An typical example where this sort of dependencies is required is a serialization library exporting combinators letting you build values of the form ['a serializer]. You may want to test this library by first generating a pair of a serializer and generator ['a serializer * 'a gen] for arbitrary ['a], and then generating values of type ['a] depending on the (generated) generator to test the serializer. There is such an example in the [examples/serializer/] directory of the Crowbar implementation. Because the structure of a generator built with [dynamic_bind] is opaque/dynamic (it depends on generated values), the Crowbar library cannot analyze its statically (without generating anything) -- the generator is opaque to the library, hidden in a function. In particular, many optimizations or or fuzzing techniques based on generator analysis are impossible. As a client of the library, you should avoid [dynamic_bind] whenever it is not strictly required to express a given generator, so that you can take advantage of these features (present or future ones). Use the least powerful/complex combinators that suffice for your needs. *) (** {1:printing Printing } *) (* Format.fprintf, renamed *) val pp : Format.formatter -> ('a, Format.formatter, unit) format -> 'a val pp_int : int printer val pp_int32 : Int32.t printer val pp_int64 : Int64.t printer val pp_float : float printer val pp_bool : bool printer val pp_string : string printer val pp_list : 'a printer -> 'a list printer val pp_option : 'a printer -> 'a option printer (** {1:testing Testing} *) val add_test : ?name:string -> ('f, unit) gens -> 'f -> unit (** [add_test name generators test_fn] adds [test_fn] to the list of eligible tests to be run when the program is invoked. At runtime, random data will be sent to [generators] to create the input necessary to run [test_fn]. Any failures will be printed annotated with [name]. *) (** {2:aborting Aborting Tests} *) val guard : bool -> unit (** [guard b] aborts a test if [b] is false. The test will not be recorded or reported as a failure. *) val bad_test : unit -> 'a (** [bad_test ()] aborts a test. The test will not be recorded or reported as a failure. *) val nonetheless : 'a option -> 'a (** [nonetheless o] aborts a test if [o] is None. The test will not be recorded or reported as a failure. *) (** {2:failing Failing} *) val fail : string -> 'a (** [fail message] generates a test failure and prints [message]. *) val failf : ('a, Format.formatter, unit, _) format4 -> 'a (** [failf format ...] generates a test failure and prints the message specified by the format string [format] and the following arguments. It is set up so that [%a] calls for an ['a printer] and an ['a] value. *) (** {2:asserting Asserting Properties} *) val check : bool -> unit (** [check b] generates a test failure if [b] is false. No useful information will be printed in this case. *) val check_eq : ?pp:('a printer) -> ?cmp:('a -> 'a -> int) -> ?eq:('a -> 'a -> bool) -> 'a -> 'a -> unit (** [check_eq pp cmp eq x y] evaluates whether x and y are equal, and if they are not, raises a failure and prints an error message. Equality is evaluated as follows: {ol {- use a provided [eq]} {- if no [eq] is provided, use a provided [cmp]} {- if neither [eq] nor [cmp] is provided, use Stdlib.compare}} If [pp] is provided, use this to print [x] and [y] if they are not equal. If [pp] is not provided, a best-effort printer will be generated from the printers for primitive generators and any printers registered with [with_printer] and used. *) crowbar-0.2.1/src/dune000066400000000000000000000001301421040307700146060ustar00rootroot00000000000000(library (public_name crowbar) (libraries cmdliner ocplib-endian afl-persistent str)) crowbar-0.2.1/src/todo000066400000000000000000000005361421040307700146320ustar00rootroot00000000000000join/bind (v2?) command line interface: - afl-fuzz mode - quickcheck mode - random fuzzing mode (for me testing, really) - file / file list mode - reproduction mode (seed / file) - select which tests to run output: - seeds for failed tests - maybe use notty to figure out pretty-printing width api: - manual testsuite interface?