-
has a changelog entry
This MR tries to improve on the testing framework for easier usability. Currently, it modifies the previous framework as follows.
- There's a new type for representing contracts (or typed addresses) with parameter and storage:
(parameter, storage) typed_address
. - The new type for
Test.originate
isTest.originate : ('parameter * 'storage -> operation list * 'storage) -> 'storage -> tez -> ('parameter, 'storage) typed_address
, previousoriginate
is still accessible asTest.originate_from_file
, but with an extra argument intez
for initial balance. - There's a new function for transforming a
typed_address
into acontract
:Test.to_contract : ('p, 's) typed_address -> 'p contract
. There's also some initial support for entrypoints, usingTest.to_entrypoint : string -> ('p, 's) typed_address -> 'q contract
that requires an additional string representing the entrypoint (the term needs to be annotated). Actually, the type ofTest.to_contract
is('p, 's) typed_address -> 'e contract
where'e
is the type of the default entrypoint in'p
in case there's one, or'p
if not. - The new type for
Test.get_storage
isTest.get_storage : ('p, 's) typed_address -> 's
, previousget_storage
is still accessible asTest.get_storage_of_address
. - There's a new function
Test.eval
for getting themichelson_program
of a value:Test.eval : 'a -> michelson_program
. - There's a new function
Test.run
that takes a function and an argument, it will: a) compile the function to Michelsonf_mich
; b) it will take the value to which the argument evaluates to, and compile it to Michelsonv_mich
; c) run the Michelson interpreter on codef_mich
with starting stack[ v_mich ]
. It returns something of typemichelson_program
. -
Test.get_balance
now has typeTest.get_balance : address -> tez
. -
Test.transfer
andTest.transfer_exn
now taketez
instead ofnat
. - There are new functions for transferring directly to a contract:
transfer_to_contract : 'p contract -> 'p -> tez -> test_exec_result
and the correspondingtransfer_to_contract_exn
. - Tests are top-level, names prefixed with
test
are printed in the output together with the value they evaluate to.
Let's see a self-contained example (test.mligo
):
(* This is the example from LIGO's frontpage: *)
type storage = int
type parameter =
Increment of int
| Decrement of int
| Reset
type return = operation list * storage
// Two entrypoints
let add (store, delta : storage * int) : storage = store + delta
let sub (store, delta : storage * int) : storage = store - delta
(* Main access point that dispatches to the entrypoints according to
the smart contract parameter. *)
let main (action, store : parameter * storage) : return =
([] : operation list), // No operations
(match action with
Increment (n) -> add (store, n)
| Decrement (n) -> sub (store, n)
| Reset -> 0)
(* Here comes the test: *)
let test =
(* Test.run f x : compiles x, compiles f, and runs f on the stack with x *)
let x : michelson_program = Test.run (fun (x : int) -> Some x) 42 in
(* This will fail, as argument uses Test primitive:
let x = Test.run (fun (g : unit -> unit) -> g ()) (fun () -> Test.log "hello test!") in *)
(* Originate main contract with 42 as initial storage *)
let (typed_addr, _, _) = Test.originate main 42 0tez in
(* We recover the contract from the typed_address *)
let contr = Test.to_contract typed_addr in
(* We execute an external call to the contract with parameter Increment 5 *)
match Test.transfer_to_contract contr (Increment 5) 0tez with
Success ->
let st = Test.get_storage typed_addr in
(47 = st)
| Fail (_) ->
false
We run it as before:
$ dune exec -- ligo test test.mligo test
Everything at the top-level was executed.
- test exited with value true.
Slightly more complex variant, that uses records in the storage, mainly for internal testing:
type flag = Reseted of int | Not_reseted
type storage = { value : int ; reseted : flag }
type parameter =
Increment of int
| Decrement of int
| Reset
type return = operation list * storage
// Two entrypoints
let add (store, delta : int * int) : int = store + delta
let sub (store, delta : int * int) : int = store - delta
(* Main access point that dispatches to the entrypoints according to
the smart contract parameter. *)
let main (action, store : parameter * storage) : return =
([] : operation list), // No operations
(match action with
Increment (n) -> { store with value = add (store.value, n) }
| Decrement (n) -> { store with value = sub (store.value, n) }
| Reset -> { value = 0; reseted = Reseted store.value })
let test =
let init_st = { value = 12 ; reseted = Not_reseted } in
let (typed_addr, _, _) = Test.originate main init_st 0tez in
let contr = Test.to_contract typed_addr in
let () = Test.transfer_to_contract_exn contr Reset 1mutez in
match Test.transfer_to_contract contr (Increment 5) 1mutez with
Success ->
let st = Test.get_storage typed_addr in
(5 = st.value) && st.reseted <> Not_reseted
| Fail (_) ->
false
As more interesting example, here's FA1.2.mligo
file from LIGO distribution tested using the framework (scroll to the end to see the tests).