Packages
espec
1.4.5
1.10.0
1.9.2
1.9.1
1.9.0
1.8.3
1.8.2
1.8.1
1.8.0
1.7.0
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.2
1.2.1
1.2.0
1.1.2
1.1.1
1.1.0
1.0.1
1.0.0
0.8.28
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.1
0.5.0
0.4.2
0.4.1
0.4.0
0.3.7
0.3.5
0.3.0
0.2.0
0.1.0
BDD testing framework for Elixir inspired by RSpec.
Current section
Files
Jump to
Current section
Files
lib/espec/example_runner.ex
defmodule ESpec.ExampleRunner do
@moduledoc """
Contains all the functions need to run a 'spec' example.
"""
defmodule AfterExampleError, do: defexception example_error: nil, message: nil
@dict_keys [:ok, :shared]
alias ESpec.Example
alias ESpec.AssertionError
alias ESpec.Output
@doc """
Runs one specific example and returns an `%ESpec.Example{}` struct.
The sequence in the following:
- evaluates 'befores' and 'lets'. 'befores' fill the map for `shared`, 'lets' can access `shared` ;
- runs 'example block';
- evaluate 'finally's'
The struct has fields `[status: :success, result: result]` or `[status: failed, error: error]`
The `result` is the value returned by example block.
`error` is a `%ESpec.AssertionError{}` struct.
"""
def run(example) do
contexts = Example.extract_contexts(example)
cond do
example.opts[:skip] || Enum.any?(contexts, &(&1.opts[:skip])) ->
run_skipped(example)
example.opts[:pending] ->
run_pending(example)
true ->
run_example(example, :os.timestamp)
end
end
defp run_example(example, start_time) do
{assigns, example} = before_example_actions(example)
try do
try_run(example, assigns, start_time)
catch
what, value -> do_catch(example, assigns, start_time, what, value)
rescue
error in [AssertionError] -> do_rescue(example, assigns, start_time, error)
error in [AfterExampleError] -> do_rescue(example, assigns, start_time, error.example_error, false)
other_error ->
error = %AssertionError{message: format_other_error(other_error)}
do_rescue(example, assigns, start_time, error)
after
unload_mocks()
end
end
defp initial_shared(example) do
Example.extract_options(example)
|> Enum.into(%{})
end
defp before_example_actions(example) do
{initial_shared(example), example}
|> run_config_before
|> run_befores_and_lets
end
defp try_run(example, assigns, start_time) do
if example.status == :failure, do: raise example.error
result = case apply(example.module, example.function, [assigns]) do
{ESpec.ExpectTo, res} -> res
res -> res
end
{_assigns, example} = after_example_actions(assigns, example)
if example.status == :failure, do: raise %AfterExampleError{example_error: example.error}
duration = duration_in_ms(start_time, :os.timestamp)
example = %Example{example | status: :success, result: result, duration: duration}
Output.example_finished(example)
example
end
defp do_catch(example, assigns, start_time, what, value) do
duration = duration_in_ms(start_time, :os.timestamp)
error = %AssertionError{message: format_catch(what, value)}
example = %Example{example | status: :failure, error: error, duration: duration}
Output.example_finished(example)
after_example_actions(assigns, example)
example
end
defp do_rescue(example, assigns, start_time, error, perform_after_example \\ true) do
duration = duration_in_ms(start_time, :os.timestamp)
example = %Example{example | status: :failure, error: error, duration: duration}
Output.example_finished(example)
if perform_after_example, do: after_example_actions(assigns, example)
example
end
def after_example_actions(assigns, example) do
{assigns, example}
|> run_finallies
|> run_config_finally
end
defp run_skipped(example) do
example = %Example{example | status: :pending, result: Example.skip_message(example)}
Output.example_finished(example)
example
end
defp run_pending(example) do
example = %Example{example | status: :pending, result: Example.pending_message(example)}
Output.example_finished(example)
example
end
defp run_config_before({assigns, example}) do
func = ESpec.Configuration.get(:before)
if func do
fun = if is_function(func, 1) do
fn -> {fill_dict(assigns, func.(assigns)), example} end
else
fn -> {fill_dict(assigns, func.()), example} end
end
call_with_rescue(fun, {assigns, example})
else
{assigns, example}
end
end
defp run_befores_and_lets({assigns, example}) do
ESpec.Let.Impl.clear_lets(example.module)
Example.extract_lets(example)
|> Enum.each(&ESpec.Let.Impl.run_before/1)
{assigns, example} = Example.extract_befores(example)
|> Enum.reduce({assigns, example}, fn(before, {assigns, example}) ->
ESpec.Let.Impl.update_shared(assigns)
fun = fn -> {do_run_before(before, assigns), example} end
call_with_rescue(fun, {assigns, example})
end)
ESpec.Let.Impl.update_shared(assigns)
{assigns, example}
end
defp run_finallies({assigns, example}) do
Example.extract_finallies(example)
|> Enum.reverse
|> Enum.reduce({assigns, example}, fn(finally, {map, example}) ->
fun = fn ->
assigns = apply(finally.module, finally.function, [map])
{fill_dict(map, assigns), example}
end
call_with_rescue(fun, {assigns, example})
end)
end
defp run_config_finally({assigns, example}) do
func = ESpec.Configuration.get(:finally)
if func do
run_config_finally({assigns, example}, func)
end
{assigns, example}
end
defp run_config_finally({assigns, example}, func) do
fun = fn ->
if is_function(func, 1), do: func.(assigns), else: func.()
{assigns, example}
end
call_with_rescue(fun, {assigns, example})
end
defp call_with_rescue(fun, {assigns, example}) do
try do
fun.()
catch
what, value -> do_catch(what, value, {assigns, example})
rescue
any_error -> do_before(any_error, {assigns, example})
end
end
defp do_catch(what, value, {map, example}) do
example = if example.error do
example
else
error = %AssertionError{message: format_catch(what, value)}
%Example{example | status: :failure, error: error}
end
{map, example}
end
defp do_before(error, {map, example}) do
example = if example.error do
example
else
error = %AssertionError{message: format_other_error(error)}
%Example{example | status: :failure, error: error}
end
{map, example}
end
defp do_run_before(%ESpec.Before{} = before, map) do
returned = apply(before.module, before.function, [map])
fill_dict(map, returned)
end
defp fill_dict(map, res) do
case res do
{key, list} when key in @dict_keys and (is_list(list) or is_map(list)) ->
if Keyword.keyword?(list) || is_map(list) do
Enum.reduce(list, map, fn({k,v}, a) -> Map.put(a, k, v) end)
else
map
end
_ -> map
end
end
defp unload_mocks, do: ESpec.Mock.unload
defp duration_in_ms(start_time, end_time) do
div(:timer.now_diff(end_time, start_time), 1000)
end
defp format_other_error(error) do
error_message = Exception.format_banner(:error, error)
stacktrace = Exception.format_stacktrace(System.stacktrace)
error_message <> "\n" <> stacktrace
end
defp format_catch(what, value), do: "#{what} #{inspect value}"
end