We need to be able to overcome the limitation that implementations cannot expose additional functionality very easily using dune virtual libraries. As it stands, we need to make Mirage's virtual libraries unwrapped (i.e. set (wrapped false)). This allows implementers to expose additional modules alongside the implementation of the virtual library.
For VPNkit, we need this to port some of the DNS forwarding tests which use a fake clock and sleep module. Previously, they could be provided directly as functor arguments to the various DNS modules e.g.
let module R =
Dns_forward.Resolver.Make(Proto_client)(Fake.Time)(Fake.Clock)
In this new world of dune virtual libraries, our Resolver is significantly more simple.
let module R = Dns_forward.Resolver.Make(Proto_client)
The flexibility of time and clock implementation has been pushed to a build-time decision in a dune file. This means that in order to provide the same fake clock and time we will need to create a private dune library that (a) implements the respective signatures of the virtual libraries and (b) provides the extended interface to allow the test suite to modify the internals of the implementations.
The first thing I had to do was make the mirage-mtime and mirage-sleep virtual libraries unwrapped. This allowed me to make a new private dune library called fake_time with two public modules: the normal interface required by mirage-mtime and another module called Fake_time_state containing:
let timeofday = ref 0L
let c = Lwt_condition.create ()
let advance nsecs =
timeofday := Int64.add !timeofday nsecs;
Lwt_condition.broadcast c ()
let reset () =
timeofday := 0L;
Lwt_condition.broadcast c ()
I did the same for fake_sleep which depended on the state from the fake_time library (something a normal library wouldn't).
Once I had the implementations for the virtual libraries in place, all that remained was to tell the executable (in this case the test suite) to select these new implementations instead of the defaults. Whereas before, all the tests could live in the same test suite, now they had to be split into those that needed the fake implementation and those that did not. This is another slight limitation of this virtual library approach. You can only link one implementation at a time to an executable (or test). Thus, the dune rules for running the dns_forward test suite became:
(executable
(name test)
(libraries dns_forward alcotest))
(executable
(name test_fake)
(libraries dns_forward alcotest fake_sleep fake_time))
With all of this machinery in place, I could now update test_fake to use the new modules that would provide the custom functionality for the clock and time implementations.
Fake_time_state.reset ();
Fake_time_state.advance Duration.(of_ms 500);