Skip to content

Instantly share code, notes, and snippets.

@baileyparker
Last active February 10, 2016 03:39
Show Gist options
  • Save baileyparker/196c6750780c08592725 to your computer and use it in GitHub Desktop.
Save baileyparker/196c6750780c08592725 to your computer and use it in GitHub Desktop.
OCaml doctest-ish script

Doctest-ish for Ocaml

Useful for asserting test cases for EN.600.426 without all that copy 'n paste and visual comparing.

The code is admittedly sloppy and makes some fragile assumptions. It errors very easily and can't assert exceptions. It expects doctests of the form:

(*
# foo param another_one and_another_one;;
- : bytes list = ["major"; "key"]
*)

You can have as many of these as you want in a comment (separated by newlines), but they all must follow this format (they can have newlines in them, a test is defined as the code between # characters in a comment; the result is everything after the ;;).

If I get a chance (I probably won't), I'll update this to be a bit more resiliant (and support things like defining fixtures for tests).

Usage

# ./test.ml hw1.ml

Notes

It currently breaks on HW1 because one of the doc comments isn't correctly formatted. Also, you need to expose (uncomment) the sodoku grids otherwise those tests fail with an error.

Requirements

You need batteries and ocamlscript to run this. You can install them with opam install batteries and opam install ocamlscript.

#!/usr/bin/env ocamlscript
(* Compile and link with Batteries *)
Ocaml.packs := ["batteries"];;
--
open Array
open Batteries
open List
open Printf
let read_file filename =
(let lines = File.lines_of filename in
(Enum.reduce (fun a b -> a ^ "\n" ^ b) lines));;
let take_until sep str =
try
let sep_start = String.find str sep in
(String.slice ~last: sep_start str)
with
| _ -> str;;
let drop_until sep str =
try
let sep_start = String.find str sep in
(String.slice ~first: (sep_start + (String.length sep)) str)
with
| _ -> "";;
let rec extract_comments contents =
match contents with
| "" -> []
| _ ->
(let comment_start = drop_until "(*" contents in
(let comment = take_until "*)" comment_start in
(let comment_end = drop_until "*)" comment_start in
(comment :: (extract_comments comment_end)))));;
let is_test_comment str = String.starts_with str "\n#";;
let rec extract_tests comment =
match comment with
| "" -> []
| _ ->
(let test = drop_until "# " comment in
(let test_cmd = take_until ";;" test in
(let rest_of_test = drop_until ";;" test in
(let expected = drop_until "=" (drop_until "- :" rest_of_test) in
(let expected_value = take_until "\n" expected in
(let rest = drop_until "\n" expected in
((test_cmd, expected_value) :: (extract_tests rest))))))));;
(* TODO: Fragile assumption that first word in test is the name *)
let get_test_name cmd = take_until " " cmd;;
let single_test (cmd, expected) =
(let name = get_test_name cmd in
(sprintf "if ((%s) <> (%s))
then (print_string \"[FAILED!] %s\\n\")
else (print_string \"[PASS] %s\\n\");" cmd expected name name));;
let all_tests filename =
(let file = read_file filename in
(let test_comments = filter is_test_comment (extract_comments file) in
(let tests = flatten (map extract_tests test_comments) in
(rev (map single_test tests)))));;
let test_harness filename =
(let test_strs = String.concat "\n" (all_tests filename) in
(sprintf "#use \"%s\";;
let run_tests = [
%s
];;" filename test_strs));;
let run_tests code =
(let input_chan, output_chan = Unix.open_process "/usr/bin/env ocaml" in
(let obuf = Buffer.create 4096 in
(try
output_string output_chan code;
while true do
Buffer.add_channel obuf input_chan 1
done
with End_of_file -> ());
let _ = Unix.close_process (input_chan, output_chan) in
(Buffer.contents obuf)));;
let main =
if ((Array.length Sys.argv) <> 2)
then (print_string "Usage: ./doctest.ml [file]\n")
else (let test_code = test_harness (Array.get Sys.argv 1) in
(print_string (run_tests test_code)));;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment