What a computation actually runs, for a test that checks a closure
(Roux.Code.closure/2) against it: every module the computation
executes must be in the closure of the roots its code version is
made of, or an edit to that module would leave stale results in
place.
{_result, ran} = Roux.Code.Verify.executed(fn -> MyGraph.extract(db, key) end)
{:ok, closure} = Roux.Code.closure([MyGraph.Extraction])
assert ran -- Enum.map(closure, &elem(&1, 0)) == []executed/2 counts one run. A test that counts many turns counting
on once (counting/2), since turning it on and off again for a few
hundred modules takes a fraction of a second, and reads each run as
the counts it moved (calls/2):
Roux.Code.Verify.counting(fn session ->
for key <- keys do
{_result, calls} = Roux.Code.Verify.calls(session, fn -> MyGraph.extract(db, key) end)
{key, Roux.Code.Verify.modules(calls)}
end
end)Call counts are kept per VM, not per process: anything else the VM
runs meanwhile counts too. Run it in a VM of its own (:peer). Two
sessions in one VM take turns (counting/2 waits for the other to
end), since one ending turns counting off for the modules it watched,
which the other may watch too.
A session only reads counters between runs, never sets them back to
zero: :erlang.trace_pattern/3's :restart over every function also
clears other tracing in the VM, such as a call trace another test set.
Summary
Functions
Runs fun within session: its result, and the functions of the
session's modules it called, each with how often, among those still
counted (see ignore/2). The counts are read before and after the
run, and a run is what it moved.
Runs fun with every function of the watched modules counted, and
stops counting after: its result. fun takes the session, to read
runs within it with calls/2.
Runs fun with call counting on, and returns its result with the
modules it called into, sorted.
Stops counting functions for the rest of session: hot functions,
already known to be called, whose counter every worker bumps at once.
A run reads them as not called.
The modules among calls/2's functions, sorted.
Functions
@spec calls(Roux.Code.Verify.Session.t(), (-> result)) :: {result, %{required(mfa()) => pos_integer()}} when result: var
Runs fun within session: its result, and the functions of the
session's modules it called, each with how often, among those still
counted (see ignore/2). The counts are read before and after the
run, and a run is what it moved.
Raises Roux.Code.Verify.UncountedError when a module of the session
is no longer counted at all.
@spec counting( (Roux.Code.Verify.Session.t() -> result), keyword() ) :: result when result: var
Runs fun with every function of the watched modules counted, and
stops counting after: its result. fun takes the session, to read
runs within it with calls/2.
Sessions in one VM take turns: one opened while another process's is open waits for it to end. A process does not wait for its own, so do not open a session within another: the inner one would turn counting off for the outer one's modules when it ends.
Options
:modules— the modules to watch, as forexecuted/2.
Runs fun with call counting on, and returns its result with the
modules it called into, sorted.
Options
:modules— the modules to watch (default: every module of every loaded application that is not OTP's or Elixir's). A module is loaded before the run, since only loaded code can be counted. This module is never watched.
@spec ignore(Roux.Code.Verify.Session.t(), [mfa()]) :: :ok
Stops counting functions for the rest of session: hot functions,
already known to be called, whose counter every worker bumps at once.
A run reads them as not called.
@spec modules(%{required(mfa()) => pos_integer()}) :: [module()]
The modules among calls/2's functions, sorted.