124 lines
3.3 KiB
Elixir
124 lines
3.3 KiB
Elixir
defrecord ExUnit.Runner.Config, formatter: ExUnit.Formatter, cases: [], max_cases: 4, taken_cases: 0, sync_cases: []
|
|
|
|
defmodule ExUnit.Runner do
|
|
@moduledoc false
|
|
|
|
# The runner entry point. At first, it will simply spawn async cases
|
|
# and expect messages as the cases finish. When all async cases are
|
|
# spawned and finished, we start running the sync cases. When sync
|
|
# cases finish, tell the formatter we finished and exit.
|
|
def start(config) do
|
|
if config.cases == [] do
|
|
cond do
|
|
config.taken_cases > 0 ->
|
|
do_loop config
|
|
config.sync_cases == [] ->
|
|
call_formatter config, :finish
|
|
true ->
|
|
do_loop spawn_sync_cases(config)
|
|
end
|
|
else
|
|
do_loop spawn_async_cases(config)
|
|
end
|
|
end
|
|
|
|
# Loop expecting messages from the spawned cases. Whenever a test
|
|
# case has finished executing, decrease the taken cases counter and
|
|
# attempt to spawn new ones.
|
|
defp do_loop(config) do
|
|
receive do
|
|
{ pid, :each, { test_case, test, final } } ->
|
|
call_formatter config, { :each, test_case, test, final }
|
|
do_loop config
|
|
{ pid, :each_case, test_case } ->
|
|
call_formatter config, { :each_case, test_case }
|
|
start config.increment_taken_cases(-1)
|
|
end
|
|
end
|
|
|
|
# Spawn the maximum possible of cases according to the max_cases value.
|
|
defp spawn_async_cases(config) do
|
|
case config.cases do
|
|
[test_case|t] ->
|
|
if config.taken_cases < config.max_cases do
|
|
spawn_case test_case
|
|
spawn_async_cases config.increment_taken_cases.cases(t)
|
|
else
|
|
config
|
|
end
|
|
[] ->
|
|
config
|
|
end
|
|
end
|
|
|
|
# After all cases were run, it is time to run the asynchronous ones.
|
|
def spawn_sync_cases(config) do
|
|
[test_case|t] = config.sync_cases
|
|
spawn_case test_case
|
|
config.sync_cases(t)
|
|
end
|
|
|
|
# Spawn each test case in a new process.
|
|
defp spawn_case(test_case) do
|
|
pid = Process.self
|
|
spawn_link fn -> run_tests(pid, test_case) end
|
|
end
|
|
|
|
defp run_tests(pid, test_case) do
|
|
try do
|
|
tests = tests_for(test_case)
|
|
test_case.setup_all
|
|
Enum.each tests, run_test(pid, test_case, &1)
|
|
test_case.teardown_all
|
|
after
|
|
pid <- { Process.self, :each_case, test_case }
|
|
end
|
|
end
|
|
|
|
defp run_test(pid, test_case, test) do
|
|
final = try do
|
|
partial = try do
|
|
apply test_case, test, []
|
|
nil
|
|
rescue
|
|
error1 ->
|
|
{ :error, error1, System.stacktrace }
|
|
catch
|
|
kind1 | error1 ->
|
|
{ kind1, error1, System.stacktrace }
|
|
end
|
|
|
|
partial
|
|
rescue
|
|
error2 ->
|
|
{ :error, error2, System.stacktrace }
|
|
catch
|
|
kind2 | error2 ->
|
|
{ kind2, error2, System.stacktrace }
|
|
end
|
|
|
|
pid <- { Process.self, :each, { test_case, test, final } }
|
|
end
|
|
|
|
defp call_formatter(config, message) do
|
|
Erlang.gen_server.call(config.formatter, message)
|
|
end
|
|
|
|
# Retrieves test functions from the module.
|
|
defp tests_for(mod) do
|
|
exports = mod.__info__(:functions)
|
|
tests_for exports, []
|
|
end
|
|
|
|
defp tests_for([{function,0}|t], acc) do
|
|
list = atom_to_list(function)
|
|
if match?('test_' ++ _, list) || match?('test ' ++ _, list) do
|
|
tests_for t, [function|acc]
|
|
else
|
|
tests_for t, acc
|
|
end
|
|
end
|
|
|
|
defp tests_for([_|t], acc), do: tests_for t, acc
|
|
defp tests_for([], acc), do: List.reverse(acc)
|
|
end |