Packages
espec
0.8.25
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/runner.ex
defmodule ESpec.Runner do
@moduledoc """
Defines functions which runs the examples.
Uses GenServer behavior.
"""
use GenServer
alias ESpec.Configuration
alias ESpec.Example
alias ESpec.ExampleRunner
@doc "Starts the `ESpec.Runner` server"
def start do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
@doc "Initiate the `ESpec.Runner` server with specs and options"
def init(_args) do
state = %{specs: ESpec.specs, opts: Configuration.all}
{:ok, state}
end
@doc "Runs all examples."
def run, do: GenServer.call(__MODULE__, :run, :infinity)
@doc false
def stop, do: GenServer.call(__MODULE__, :stop)
@doc false
def handle_call(:run, _pid, state) do
result = do_run(state[:specs], state[:opts])
{:reply, result, state}
end
@doc false
def handle_call(:stop, _pid, _state) do
{:stop, :normal, :ok, []}
end
defp do_run(specs, opts) do
examples = if Configuration.get(:order) do
run_in_order(specs, opts)
else
seed_random!
run_in_random(specs, opts)
end
Configuration.add([finish_specs_time: :os.timestamp])
ESpec.Output.print_result(examples)
!Enum.any?(Example.failure(examples))
end
defp run_in_order(specs, opts) do
specs |> Enum.reverse
|> Enum.map(fn(module) ->
filter(module.examples |> Enum.reverse, opts)
|> run_examples
end)
|> List.flatten
end
defp run_in_random(specs, opts) do
examples = Enum.map(specs, fn(module) ->
filter(module.examples, opts)
end) |> List.flatten |> Enum.shuffle
run_examples(examples)
end
@doc "Runs examples."
def run_examples(examples) do
{async, sync} = partition_async(examples)
run_async(async) ++ run_sync(sync)
end
@doc false
def partition_async(examples) do
Enum.partition(examples, &Example.extract_option(&1, :async) === true)
end
defp run_async(examples) do
examples
|> Enum.map(&Task.async(fn -> ExampleRunner.run(&1) end))
|> Enum.map(&Task.await(&1, :infinity))
end
defp run_sync(examples), do: Enum.map(examples, &ExampleRunner.run(&1))
@doc false
def filter(examples, opts) do
file_opts = opts[:file_opts] || []
examples = filter_shared(examples)
examples = if Enum.any?(file_opts), do: file_opts_filter(examples, file_opts), else: examples
examples = if opts[:focus], do: filter_focus(examples), else: examples
examples = if opts[:only], do: filter_only(examples, opts[:only]), else: examples
examples = if opts[:exclude], do: filter_only(examples, opts[:exclude], true), else: examples
examples = if opts[:string], do: filter_string(examples, opts[:string]), else: examples
examples
end
defp filter_shared(examples), do: Enum.filter(examples, &(!&1.shared))
defp file_opts_filter(examples, file_opts) do
grouped_by_file = Enum.group_by(examples, fn(example) -> example.file end)
filtered = Enum.reduce(grouped_by_file, [], fn({file, exs}, acc) ->
opts = opts_for_file(file, file_opts)
line = Keyword.get(opts, :line)
if line do
closest = get_closest(Enum.map(exs, &(&1.line)), line)
acc ++ Enum.filter(exs, &(&1.line == closest))
else
acc ++ exs
end
end)
filtered
end
defp get_closest(arr, value) do
arr = Enum.sort(arr)
diff = abs(value - hd(arr))
{_d, el} = Enum.reduce(arr, {diff, hd(arr)}, fn(el, {d, e}) ->
diff = abs(value - el)
if diff < d, do: {diff, el}, else: {d, e}
end)
el
end
defp opts_for_file(file, opts_list) do
case opts_list |> Enum.find(fn {k, _} -> k == file end) do
{_file, opts} -> opts
nil -> []
end
end
defp filter_focus(examples) do
Enum.filter(examples, fn(example) ->
contexts = Example.extract_contexts(example)
example.opts[:focus] || Enum.any?(contexts, &(&1.opts[:focus]))
end)
end
defp filter_string(examples, string) do
Enum.filter(examples, fn(example) ->
description = Enum.join([example.description | Example.context_descriptions(example)])
String.contains?(description, string)
end)
end
defp filter_only(examples, only, reverse \\ false) do
[key, value] = extract_opts(only)
Enum.filter(examples, fn(example) ->
contexts = Example.extract_contexts(example)
key = String.to_atom(key)
example_tag_value = example.opts[key]
context_tag_values = Enum.map(contexts, &(&1.opts[key]))
tag_values = Enum.filter([example_tag_value | context_tag_values], &(&1))
if reverse do
if Enum.empty?(tag_values), do: true, else: !is_any_with_tag?(tag_values, value)
else
is_any_with_tag?(tag_values, value)
end
end)
end
defp is_any_with_tag?(tag_values, value) do
Enum.any?(tag_values, fn(tag) ->
if is_atom(tag) do
if value, do: Atom.to_string(tag) == value, else: tag
else
if value, do: tag == value, else: tag
end
end)
end
defp extract_opts(key_value) do
if String.match?(key_value, ~r/:/) do
String.split(key_value, ":")
else
[key_value, false]
end
end
defp seed_random! do
conf_seed = if seed = Configuration.get(:seed) do
seed
else
seed = :os.timestamp |> elem(2)
Configuration.add(seed: seed)
seed
end
seed = cond do
is_number(conf_seed) -> conf_seed
is_binary(conf_seed) -> String.to_integer(conf_seed)
end
:random.seed({3172, 9814, seed})
end
end