harnais_runner v0.1.0 Harnais.Runner.Prova View Source

The prova manages a complete test with one or more test calls.

Each test specification is used to create a prova.

Each test call specification is used to create a cridar.

See Harnais.Runner for the overview.

Prova State

A prova has the following fields:

KeyAliases
:test_flag:f, :flag
:test_call:c, :call
:test_value:v, :value
:test_args:a, :args
:test_module:d, :module
:test_result:r, :result
:test_comp:comp, :compare, :test_compare
:test_namer:n, :namer
:test_spec
:test_cridars

The default for all fields is the unset value (Plymio.Fontais.Guard.the_unset_value/0).

Prova Field: :test_flag

The :test_flag is optional in the Map and Keyword test forms but required in the positional Tuple and List forms.

Even when required, usually the :test_flag can be nil; it is ignored.

The :test_flag is most frequently used to catch an exception e.g. {:e ArgumentError}.

The flag is important though when using the reduce runner (Harnais.Runner.run_tests_reduce_test_value/1) as described above when a value of :w will set the :test_value for the next test to the result of the current test.

Prova Field: :test_call

The test call holds one or more (List) call specifications each of which is used to create a cridar.

Prova Field: :test_value

The :test_value supplies the 2nd argument to the Harnais.Runner.Cridar.call/2 (the first being the cridar itself).

Prova Field: :test_args

If the cridar’s :rest_args (sic) is unset, the prova’s :test_args is used to set the cridar’s :rest_args.

The expectation is that the cridar’s :rest_args will be used often with the :test_value in a MFA apply.

Prova Field: :test_module

If the cridar’s :test_module is unset, the prova’s :test_module is used.

Prova Field: :test_result

Each prova (test spec) must have a :test_result value.

If the value is a function of arity one, it is called with the actual result.

If the value is a function of arity two, it is called with the actual result and the prova.

The answer from a function is normalised with a definition of extended truthy: The usual nil, false and true are supplemented with {:ok, value} (true), {error, error} (false), value is an Exception (false), and value (true).

Any other value is just compared (==) with the actual result and the compare asserted.

Prova Field: :test_comp

The :test_comp field is used to specify another test that should return the same answer as the current test.

If the :test_comp is a function, it is treated as the :test_result.

Otherwise it is treated as a new test specification that is instantiated, tested and compared with the answer from the current test.

See the examples for more specifics.

Prova Field: :test_namer

When the :test_call has an Atom function name (e.g. :get), the namer function is called with the function name and should return the actual function (Atom) to call in the :test_module.

Prova Field: :test_spec

The original (pre transform) test specification.

Prova Field: :test_cridars

Each test call specification in the :test_call is used to create a cridar. The :test_cridars holds the list of cridars.

Link to this section Summary

Functions

new/1 creates a new instance of the module’s struct and, if the optional opts were given, calls update/2 with the instance and the opts, returning {:ok, instance}, else {:error, error}

new!/1 calls new/1 and, if the result is {:ok, instance} returns the instance

test/1 takes a prova and runs each test call (cridar)

update/2 takes an instance of the module’s struct and an optional opts

update!/2 calls update/2 and, if the result is {:ok, instance} returns the instance

Link to this section Types

Link to this type t() View Source
t() :: %Harnais.Runner.Prova{
  test_args: term(),
  test_call: term(),
  test_comp: term(),
  test_cridars: term(),
  test_flag: term(),
  test_module: term(),
  test_namer: term(),
  test_result: term(),
  test_spec: term(),
  test_value: term()
}

Link to this section Functions

Link to this function new(opts \\ []) View Source
new(any()) :: {:ok, t()} | {:error, error()}

new/1 creates a new instance of the module’s struct and, if the optional opts were given, calls update/2 with the instance and the opts, returning {:ok, instance}, else {:error, error}.

Link to this function new!(opts \\ []) View Source
new!(any()) :: t() | no_return()

new!/1 calls new/1 and, if the result is {:ok, instance} returns the instance.

Link to this function test(prova) View Source
test(t()) :: {:ok, {any(), t()}} | {:error, error()}

test/1 takes a prova and runs each test call (cridar)

Examples

A simple example of a Map.get/2 call where the result is expected to be 42:

iex> {:ok, {answer, %PROVA{}}} = [
...>    module: Map, value: %{a: 42},
...>    args: [:a], call: [:get], result: 42,
...> ] |> PROVA.new! |> PROVA.test
...> answer
42

The :test_result here is 99 so an error is returned:

iex> {:error, error} = [
...>    module: Map, value: %{a: 42},
...>    args: [:a], call: [:get], result: 99,
...> ] |> PROVA.new! |> PROVA.test
...> error |> Exception.message
"result mismatch; expect 99; actual 42"

Here the :test_result is a fun/1 that always returns false causing an error to be returned.

iex> {:error, error} = [
...>    module: Map, value: %{a: 42},
...>    args: [:a], call: [:get], result: fn _ -> false end,
...> ] |> PROVA.new! |> PROVA.test
...> error |> Exception.message
"result invalid, got: 42"

Precreated cridars can be used in the :test_call:

iex> {:ok, %CRIDAR{} = cridar} = [
...>    mod: Map, fun: :get, args: [%{a: 42}, :a]
...> ] |> CRIDAR.new
...> {:ok, {answer, %PROVA{}}} = [
...>    module: List, value: %{b: 99},
...>    args: [:a], call: cridar, result: 42,
...> ] |> PROVA.new! |> PROVA.test
...> answer
42

In this example two precreated cridars are chained together: the result of the first is used as the :test_value for the second. Note the cridars have :rest_args (not :args) as they will be applied together with the :test_value:

iex> call1 = [f: :delete, rest_args: :a] |> CRIDAR.new!
...> call2 = [f: :get, rest_args: [:a, :no_a_found]] |> CRIDAR.new!
...> {:ok, {answer, %PROVA{}}} = [
...>    mod: Map, call: [call1, call2],
...>    value: %{a: 1}, result: :no_a_found,
...> ] |> PROVA.new! |> PROVA.test
...> answer
:no_a_found

Test call specifications can be mixed: A variation of the example above where a value is put for :a using an explicit fun:

iex> call1 = [f: :delete, rest_args: :a] |> CRIDAR.new!
...> call2 = [f: :get, rest_args: [:a, :no_a_found]] |> CRIDAR.new!
...> call3 = fn test_value -> Map.put(test_value, :a, 42) end
...> {:ok, {answer, %PROVA{}}} = [
...>    mod: Map, call: [call1, call3, call2],
...>    value: %{a: 1}, result: 42,
...> ] |> PROVA.new! |> PROVA.test
...> answer
42

Same example but the put for :a is Keyword format call specification:

iex> call1 = [f: :delete, rest_args: :a] |> CRIDAR.new!
...> call2 = [f: :get, rest_args: [:a, :no_a_found]] |> CRIDAR.new!
...> call3 = [f: :put, rest_args: [:a, 99]]
...> {:ok, {answer, %PROVA{}}} = [
...>    mod: Map, call: [call1, call3, call2],
...>    value: %{a: 1}, result: 99,
...> ] |> PROVA.new! |> PROVA.test
...> answer
99

These two examples show the use of the :test_namer. Note it is applied to precreated cridars as well.

iex> {:ok, {answer, %PROVA{}}} = [
...>    test_namer: fn :g -> :get end,
...>    module: Map, value: %{a: 42},
...>    args: [:a], call: [:g], result: 42,
...> ] |> PROVA.new! |> PROVA.test
...> answer
42

iex> call1 = [f: :d, rest_args: :a] |> CRIDAR.new!
...> call3 = [f: :p, rest_args: [:a, 99]]
...> call2 = [f: :g, rest_args: [:a, :no_a_found]] |> CRIDAR.new!
...> test_namer = fn
...>   :g -> :get
...>   :p -> :put
...>   :d -> :delete
...> end
...> {:ok, {answer, %PROVA{}}} = [
...>    namer: test_namer,
...>    mod: Map, call: [call1, call3, call2],
...>    value: %{a: 1}, result: 99,
...> ] |> PROVA.new! |> PROVA.test
...> answer
99

The new few examples demonstrate the use of :test_comp. First, if the :test_comp is a function, it is treated as a :test_result function. Here is always returns true.

iex> {:ok, {answer, %PROVA{}}} = [
...>    compare: fn _ -> true end,
...>    module: Map, value: %{a: 42},
...>    args: [:a], call: [:get],
...> ] |> PROVA.new! |> PROVA.test
...> answer
42

Otherwise the :test_comp is used to create a compare prova. which is then, in turn, used to create a :test_result arity 2 function for the test prova.

When the test prova’s :test_result fun/2 function is called, the first argument (the test answer) is used to set the compare prova’s :test_result value (but only if not already set).

Similarly the :test_value from the second argument (the test prova) is used to set the compare prova’s :test_value (but again only if not already set).

The compare prova is then tested and if it returns true according to the extended truthy definition, {:ok, {test_answer, test_prova}} is returned.

iex> {:ok, {answer, %PROVA{}}} = [
...>    compare: [call: fn test_value -> test_value |> Map.get(:a) end],
...>    module: Map, value: %{a: 42},
...>    args: [:a], call: [:get],
...> ] |> PROVA.new! |> PROVA.test
...> answer
42

iex> {:error, error} = [
...>    compare: [call: fn test_value -> test_value |> Map.get(:b) end],
...>    module: Map, value: %{a: 42},
...>    args: [:a], call: [:get],
...> ] |> PROVA.new! |> PROVA.test
...> error |> Exception.message
"result invalid, got: 42"
Link to this function update(t, opts \\ []) View Source
update(t(), opts()) :: {:ok, t()} | {:error, error()}

update/2 takes an instance of the module’s struct and an optional opts.

The opts are normalised by calling the module’s update_canonical_opts/1 and then reduced with update_field/2:

 opts |> Enum.reduce(instance, fn {k,v}, s -> s |> update_field({k,v}) end)

{:ok, instance} is returned.

Link to this function update!(t, opts \\ []) View Source
update!(t(), any()) :: t() | no_return()

update!/2 calls update/2 and, if the result is {:ok, instance} returns the instance.