README.org

#+TITLE: Snabbkaffe

[[https://travis-ci.org/klarna/snabbkaffe.svg?branch=master]]

* Introduction

This library provides functions for simple trace-based testing.

It works like this:

 1) Programmer manually instruments the code with trace points
 2) Testcases are split in two parts:
    - *Run stage* where the program runs and emits event trace
    - *Check stage* where trace is collected and validated against the
      spec(s)
 3) Trace points become ordinary log messages in the release build

This approach can be used in a component test involving an ensemble of
interacting processes. It has a few nice properties:

 + Checks can be separated from the program execution
 + Checks are independent from each other and fully composable
 + Trace contains complete history of the process execution, thus
   making certain types of concurrency bugs, like livelocks, easier to
   detect

* Usage

** Instrumenting the code

Code instrumentation is done manually by inserting =tp= macros at the
points of interest:

#+BEGIN_SRC erlang
?tp(my_server_state_change, #{old_state => foo, next_state => bar})
#+END_SRC

The above line of code, when compiled in test mode, emits an event of
/kind/ =my_server_state_change=, with some additional data specified
in the second argument. Any event has a /kind/, which is an atom
identifying type of the event. The second argument must be a map, and
it can contain any data.

In the release build this macro will become a [[https://github.com/tolbrino/hut/][hut]] log message with
=debug= level. One can as well tune trace point log level:

#+BEGIN_SRC erlang
?tp(notice, my_server_state_change, #{old_state => foo, next_state => bar})
#+END_SRC

** Collecting trace

Trace collector process must be started before running the test. Full
workflow looks like this:

#+BEGIN_SRC erlang
ok = snabbkaffe:start_trace(),
Return = RunYourCode(),
Trace = snabbkaffe:collect_trace(Timeout),
snabbkaffe:stop(),
RunCheck1(Return, Trace),
RunCheck2(Return, Trace),
...
RunCheckN(Return, Trace).
#+END_SRC

Note that =collect_trace= function is destructive: it cleans event
buffer of the collector process. Its argument =Timeout= specifies how
long the call will wait after the last event is received. Setting this
parameter to a non-zero value is useful when /run stage/ starts some
asynchronous tasks. By default =Timeout= is 0.

** Checking traces

Trace is just a list of maps. Any standard function can work with
it. Nonetheless, /Snabbkaffe/ comes with a few useful macros for trace
analysis.

Select events of certain kind:

#+BEGIN_SRC erlang
?of_kind(foo, Trace)
#+END_SRC

Extract values of a certain event field(s) to a list:

#+BEGIN_SRC erlang
[1, 2] = ?projection(foo, [#{foo => 1, quux => 1}, #{foo => 2}]),

[{1, 1}, {2, 2}] = ?projection([foo, bar], [#{foo => 1, bar => 1}, #{foo => 2, bar => 2}])
#+END_SRC

Check that events occur in certain order, and throw an exception
otherwise:

#+BEGIN_SRC erlang
?causality( #{kind := foo, foo := _A}
          , #{kind := bar, foo := _A}
          , Trace
          )
#+END_SRC

First argument of =?causality= macro matches an event that is called
the /cause/, and the second one matches so called /effect/. The above
example checks events of kind =bar= occur only after corresponding
events of kind =foo=.

This macro can be extended with a guard as well. Here guard checks
that two events actually make up a pair:

#+BEGIN_SRC erlang
?causality( #{kind := foo, foo := _A}
          , #{kind := bar, foo := _B}
          , _A + 1 =:= _B
          , Trace
          )
#+END_SRC

There is a version of the above macro that checks that all cause have
an effect:

#+BEGIN_SRC erlang
?strict_causality( #{kind := foo, foo := _A}
                 , #{kind := bar, foo := _A}
                 , Trace
                 )
#+END_SRC

Otherwise it works just like =?causality=.

Both =?causality= and =?strict_causality= are actually based on a more
powerful =?find_pairs= macro that is invoked like this:

#+BEGIN_SRC erlang
?find_pairs( Strict
           , MatchCause
           , MatchEffect
          [, Guard]
           , Trace
           )
#+END_SRC

where =Strict= is a boolean that determines whever events that matched
as =Effect= may preceed their cause.

It returns a list of tuples of type =snabbkaffe:maybe_pair/0= that is
defined like that:

#+BEGIN_SRC erlang
-type maybe_pair() :: {pair, event(), event()}
                    | {singleton, event()}.
#+END_SRC

** Gathering it all together

=?check_trace= is a convenience wrapper that starts the trace
collector process, executes /run stage/, collects traces and then
executes /check stage/:

#+BEGIN_SRC erlang
?check_trace(begin
               RunStage
             end,
             fun(ReturnValue, Trace) ->
                CheckStage
             end).
#+END_SRC

There is an extended version of this macro that takes additional
configuration as the first argument:

#+BEGIN_SRC erlang
?check_trace(#{timeout => Timeout, bucket => Bucket},
             begin
               RunStage
             end,
             fun(ReturnValue, Trace) ->
                CheckStage
             end).
#+END_SRC

or:

#+BEGIN_SRC erlang
?check_trace(Bucket,
             begin
               RunStage
             end,
             fun(ReturnValue, Trace) ->
                CheckStage
             end).
#+END_SRC

=Bucket= is a parameter used for benchmarking, more on that later.

** Blocking execution of testcase until certain event is emitted

Even though philosophy of this library lies in separation of run and
verify stages, sometimes the former needs to be somewhat aware of the
events. For example, the testcase may need to wait for asynchronous
initialization of some resource.

In this case =?block_until= macro should be used. It allows the
testcase to peek into the trace. Example usage:

#+BEGIN_SRC erlang
?block_until(#{kind := Kind}, Timeout, BackInTime)
#+END_SRC

Note: it's tempting to use this macro to check the result of some
asynchronous action, like this:

#+BEGIN_SRC erlang
{ok, Pid} = foo:async_init(),
{ok, Event} = ?block_until(#{kind := foo_init, pid := Pid}),
do_stuff(Pid)
#+END_SRC

However it's not a good idea, because the event can be emitted before
=?block_until= has a chance to run. Use the following macro to avoid
this race condition:

#+BEGIN_SRC
{{ok, Pid}, {ok, Event}} = ?wait_async_action( foo:async_init()
                                             , #{kind := foo_init, pid := Pid}
                                             ),
do_stuff(Pid)
#+END_SRC

** PropER integration

There are two useful macros for running /snabbkaffe/ together with [[https://proper-testing.github.io/][propER]]:

#+BEGIN_SRC erlang
?run_prop(Config, PROP)
#+END_SRC

where =PROP= is a proper spec that looks something like this:

#+BEGIN_SRC erlang
?FORALL({Ret, L}, {term(), list()},
   ?check_trace(
      begin
        RunStage
      end,
      fun(Return, Trace) ->
        CheckStage
      end))
#+END_SRC

There is another macro for the most common type of proper checks where
property is a simple =?FORALL= clause (like in the above example).

#+BEGIN_SRC erlang
?forall_trace({Ret, L}, {term(), list()},
              begin
                RunStage
              end,
              fun(Return, Trace) ->
                CheckStage
              end)
#+END_SRC

It combines =?FORALL= and =?run_prop=.

* Benchmarking

/Snabbkaffe/ automatically adds timestamps to the events, which makes
it a very unscientific benchmarking library.

There is a family of functions for reporting metric data.

Report a scalar metric called =my_metric1=:

#+BEGIN_SRC erlang
snabbkaffe:push_stat(my_metric1, 42),
snabbkaffe:push_stats(my_metric1, [42, 43, 42]),
%% Or even:
snabbkaffe:push_stats(my_metric1, [{pair, Event1, Event2}, {pair, Event3, Event4}, ...]),
#+END_SRC

Sometimes it is entertaining to see how metric value depends on the
size of the input data:

#+BEGIN_SRC erlang
snabbkaffe:push_stat(my_metric1, SizeOfData, 42),
snabbkaffe:push_stats(my_metric1, SizeOfData, [42, 43, 42])
#+END_SRC

Metrics can be reported by calling =snabbkaffe:analyze_statistics/0=
function that prints statistics for each reported metric, like in the
above example:

#+BEGIN_EXAMPLE
-------------------------------
foo_bar statistics:
[{min,9.999999999999999e-6},
 {max,9.999999999999999e-6},
 {arithmetic_mean,1.000000000000002e-5},
 {geometric_mean,1.0000000000000123e-5},
 {harmonic_mean,9.999999999999997e-6},
 {median,9.999999999999999e-6},
 {variance,4.174340734454146e-40},
 {standard_deviation,2.0431203426264804e-20},
 {skewness,-0.9850375627355535},
 {kurtosis,-2.0199000000000003},
 {percentile,[{50,9.999999999999999e-6},
              {75,9.999999999999999e-6},
              {90,9.999999999999999e-6},
              {95,9.999999999999999e-6},
              {99,9.999999999999999e-6},
              {999,9.999999999999999e-6}]},
 {histogram,[{9.999999999999999e-6,100}]},
 {n,100}]

Statisitics of test
100.479087 ^                                                                     *
           |                                                               *
           |                                                        *
           |                                                  *
           |
           |                                            *
           |                                     *
           |
           |                               *
           |                         *
           |                   *
           |
           |            *
           |      *
         0 +--------------------------------------------------------------------->
           0                                                                  1100

         N    min         max        avg
       110 1.23984e+0 1.09774e+1 5.97581e+0
       209 1.10121e+1 2.08884e+1 1.60011e+1
       308 2.13004e+1 3.09071e+1 2.60224e+1
       407 3.10212e+1 4.09074e+1 3.59904e+1
       506 4.10095e+1 5.09904e+1 4.60456e+1
       605 5.11370e+1 6.08557e+1 5.60354e+1
       704 6.10493e+1 7.09071e+1 6.59642e+1
       803 7.11237e+1 8.07733e+1 7.59588e+1
       902 8.10944e+1 9.09766e+1 8.60179e+1
      1001 9.10459e+1 9.99404e+1 9.54548e+1
      1100 1.00004e+2 1.00939e+2 1.00479e+2
#+END_EXAMPLE

Note: =?run_prop= does this automatically.