Testing LIGO
#
Testing LIGO codeThe LIGO command-line interpreter provides commands to directly test your LIGO code. The three main commands we currently support are:
ligo run test
ligo run interpret
ligo run dry-run
We will show how to use the first two, while an example on how to use the third one was already explained here.
ligo run test
#
Testing with The command ligo run test
can be used to test a contract using LIGO.
⚠️ Please keep in mind that this command is still BETA, and that there are features that are work in progress and are subject to change. No real test procedure should rely on this command alone.
When running the ligo run test
command, LIGO code has access to an
additional Test
module. This module provides ways of originating
contracts and executing transactions, as well as additional helper
functions that allow to control different parameters of the Tezos
testing library.
Note: The LIGO interpreter uses the same library that Tezos internally uses for testing.
The function Test.originate
allows to deploy a contract in the
testing environment. It takes a contract, which is represented as a
function of type 'parameter * 'storage -> operation list * 'storage
,
an initial storage of type 'storage
, and an initial balance for the
contract being deployed. This function deploys the contract, and
returns the type ('parameter, 'storage) typed_address
, the compiled
program in Michelson of type michelson_program
, and the size of the
program of type int
.
The storage of a deployed contract can be queried using the
Test.get_storage
function, that given a typed address ('parameter,
'storage) typed_address
, returns the 'storage
value.
As a concrete example, suppose we have the following contract:
We can deploy it and query the storage right after, to check that the storage is in fact the one which we started with:
The test sub-command will evaluate all top-level definitions and print any
entries that begin with the prefix test
as well as the value that these
definitions evaluate to. If any of the definitions are found to have
failed, a message will be issued with the line number where the problem
occurred.
The function Test.transfer_to_contract
allows to bake a transaction.
It takes a target account of type 'parameter contract
, the parameter
of type 'parameter
and an amount of type tez
. This function
performs the transaction, and returns a test_exec_result
which
can be matched on to know whether the transaction was successful or not.
In case of success you will get access to the gas consumed by the execution
of the contract and in case of failure you will get access to a test_exec_error
describing the error.
There is an alternative version, called Test.transfer_to_contract_exn
which performs the transaction and will only return the gas consumption,
failing in case that there was an error.
We can extend the previous example by executing a transaction that increments the storage after deployment, we also print the gas consumption:
The environment assumes a source for the operations which can be set
using the function Test.set_source : address -> unit
.
#
Unit testing a functionConsider a map binding addresses to amounts and a function removing all entries in that map having an amount less to a given threshold.
Let us imagine that we want to test this function against a range of thresholds with the LIGO test framework.
First, let's include the file under test and reset the state with 5 bootstrap accounts (we are going to use the bootstrap addresses later)
Now build the balances
map that will serve as the input of our test.
Our simple test loop will call balances_under
with the compiled map
defined above, get the size of the resulting map and compare it to an
expected value with Test.michelson_equal
.
The call to balance_under
and the computation of the size of the resulting map is achieved through the primitive Test.run
.
This primitive runs a function on an input, translating both (function and input)
to Michelson before running on the Michelson interpreter.
More concretely Test.run f v
performs the following:
- Compiles the function argument
f
to Michelsonf_mich
- Compiles the value argument
v
(which was already evaluated) to Michelsonv_mich
- Runs the Michelson interpreter on the code
f_mich
with the initial stack[ v_mich ]
The function that is being compiled is called tester
.
We also print the actual and expected sizes for good measure.
You can now execute the test:
ligo run interpret
#
Testing with The command ligo run interpret
allows to interpret an expression in a
context initialised by a source file. The interpretation is done using
Michelson's interpreter.
We can see how it works on an example. Suppose we want to test the following contract.
This contract keeps an integer as storage, and has three entry-points:
one for incrementing the storage, one for decrementing the storage,
and one for resetting the storage to 0
.
As a simple property, we check whether starting with a storage of
10
, if we execute the entry-point for incrementing 32
, then we get
a resulting storage of 42
. For checking it, we can interpret the
main
function:
With the argument --init-file
we pass the contract we want to test,
and the sub-command requires also the expression to evaluate in that
context, in this case, a call to our contract (main
) with parameter
Increment (32)
and storage 10
. As a result, we can check that the
resulting storage is 42
(the second component of the pair), and
there are no further operations to execute (the first component).
We can tune certain parameters of the execution by passing them as arguments: