21 Dec 2022
3 min read
Between tests and compiler warnings quite a few bugs and typos can be eliminated from an implementation. There are still a few issues with, for example, handling floating point math in the design that I need to address; something that I will write about once I have that module in place. The code snippets shown here are part of ocal and for anyone interested can follow that repository.
Dune supports inline_tests through ppx_inline_test. I used to fall in the test code should be separate from the main code as tests could trigger some unintended consequences. However, when I started using inline tests I could see the power of using inline tests.
(inline_tests) (preprocess (pps ppx_inline_test))
Here is how it looks for one of the modules
let poly (x : real) (polyList : real list) = List.fold_left (fun acc ele -> (acc *. x) +. ele) 0.0 polyList let epoch : int = 0 let rd (tee : int) = tee - epoch let%test _ = let p0 = poly 1.0 ([1.0; 2.0; 3.0]) in let expected = 6.0 in expected = p0 let%test _ = epoch = 0
Testing is a big topic; it is nice to have the support of these tools to make our life a bit easier. There are some disadvantages of the above approach and the
let%test expressions are not necessarily the preferred way of running tests because, in my opinion, they do clutter the application space.
The test below are wrapped in a binary that depends on the library that we need to test. These tests cover variant types with Crowbar's generators and there is a test case using the Alcotest library.
let dayofweek () = add_test ~name:"dayofweek" [ day_of_week_gen ] @@ function | d -> let i = Calendar.int_of_day d in let d2 = Calendar.day_from_int i in check_eq ~pp:Calendar.pp_day d d2 let months () = add_test ~name:"months" [months_gen] @@ function | m -> let i = G.int_from_month_type m in let m2 = G.month_type_from_int i in check_eq ~pp:G.pp_month m m2 let binary_search_test () = Alcotest.(check @@ float epsilon_float) "Test bisection" (CTLibrary.to_float @@ CTLibrary.binary_search (CTLibrary.from_float 0.) (CTLibrary.from_float 1.) (fun (x, y) -> ((CTLibrary.to_float x) -. (CTLibrary.to_float y)) < epsilon_float) (fun r -> r < (CTLibrary.from_float 1.))) 0.5 let () = binary_search_test (); dayofweek (); months ();
In the above tests, there are some simple generators that generate tests for variant types and there is also an instance of a Alcotest case that checks for expected results.
The test case for binary_search demonstrates the need to create conversion routines for an abstract type, because if such functions are not available the compiler will throw the following error
File "test/ocal_fuzzer.ml", line 43, characters 70-75: 43 | "Test bisection" (CTLibrary.to_float @@ CTLibrary.binary_search ( 0.) (CTLibrary.from_float 1.) ^^^^^ Error: This expression has type float but an expression was expected of type CTLibrary.real
module type Library = sig type real val from_float : float -> real val to_float : real -> float val binary_search : real -> real -> (real * real -> bool) -> (real -> bool) -> real (** Bisection search for x in lo to hi such that end holds, test determines when to go left. binarySearch l lo h hi x test end. *) end module Library : Library = struct type real = float let to_float r = r let from_float r = r let binary_search (lo : real) (hi : real) (predicate : real * real -> bool) (endCondition : real -> bool) = failwith "Implementation removed" end
Consider the following module.
module type JulianCalendar = sig module JD = CT.Date val jd_epoch : JD.fixed_date end module JulianCalendar : JulianCalendar = struct module JulianCalendar : JulianCalendar = struct let jd_epoch = JD.rd @@ JD.fixed_date_from_int @@ int_of_float @@ -1721424.5 let jd_epoch = failwith "test" end
In the above example there was a typo and the same function signature is being duplicated. In this example, since
let jd_epoch = failwith "test"
is the last definition of jd_epoch, which is the definition that will get picked up at runtime. The above unused value warning catches an exception that would prevent one to even use
dune utop to come for the module.
I had almost decided to ignore this warning in the file, obviously not a wise thing to do with warnings.
As I am getting my feet wet with OCaml and its rich module interface, I find these warnings and tests to help keep the design in check. There are still a few issues in the design that I need to handle specially when some calendars are ok with integer types whereas others are using float (floating point computations present their own challenges, that would need a separate post.)
A demo app using libreoffice scripting framework: https://www.youtube.com/watch?v=INT6duJM7X8
See other articles by dinkar
Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ
108 E 16th Street, New York, NY 10003
Join over 111,000 others and get access to exclusive content, job opportunities and more!