Permalink
Browse files

Support GenServer in new ExUnit formatters

  • Loading branch information...
josevalim committed Nov 20, 2016
1 parent a4682f3 commit e7f2d14036121916fffafda4be9a2137ee39b5bd
@@ -1,7 +1,6 @@
defmodule ExUnit.CLIFormatter do
@moduledoc false
use GenEvent
use GenServer
import ExUnit.Formatter, only: [format_time: 2, format_filters: 2, format_test_failure: 5,
format_test_case_failure: 5]
@@ -23,47 +22,47 @@ defmodule ExUnit.CLIFormatter do
{:ok, config}
end
def handle_event({:suite_started, _opts}, config) do
{:ok, config}
def handle_cast({:suite_started, _opts}, config) do
{:noreply, config}
end
def handle_event({:suite_finished, run_us, load_us}, config) do
def handle_cast({:suite_finished, run_us, load_us}, config) do
print_suite(config, run_us, load_us)
:remove_handler
{:noreply, config}
end
def handle_event({:test_started, %ExUnit.Test{} = test}, config) do
def handle_cast({:test_started, %ExUnit.Test{} = test}, config) do
if config.trace, do: IO.write " * #{test.name}"
{:ok, config}
{:noreply, config}
end
def handle_event({:test_finished, %ExUnit.Test{state: nil} = test}, config) do
def handle_cast({:test_finished, %ExUnit.Test{state: nil} = test}, config) do
if config.trace do
IO.puts success(trace_test_result(test), config)
else
IO.write success(".", config)
end
{:ok, %{config | test_counter: update_test_counter(config.test_counter, test)}}
{:noreply, %{config | test_counter: update_test_counter(config.test_counter, test)}}
end
def handle_event({:test_finished, %ExUnit.Test{state: {:skip, _}} = test}, config) do
def handle_cast({:test_finished, %ExUnit.Test{state: {:skip, _}} = test}, config) do
if config.trace, do: IO.puts trace_test_skip(test)
{:ok, %{config | test_counter: update_test_counter(config.test_counter, test),
skipped_counter: config.skipped_counter + 1}}
{:noreply, %{config | test_counter: update_test_counter(config.test_counter, test),
skipped_counter: config.skipped_counter + 1}}
end
def handle_event({:test_finished, %ExUnit.Test{state: {:invalid, _}} = test}, config) do
def handle_cast({:test_finished, %ExUnit.Test{state: {:invalid, _}} = test}, config) do
if config.trace do
IO.puts invalid(trace_test_result(test), config)
else
IO.write invalid("?", config)
end
{:ok, %{config | test_counter: update_test_counter(config.test_counter, test),
invalid_counter: config.invalid_counter + 1}}
{:noreply, %{config | test_counter: update_test_counter(config.test_counter, test),
invalid_counter: config.invalid_counter + 1}}
end
def handle_event({:test_finished, %ExUnit.Test{state: {:failed, failures}} = test}, config) do
def handle_cast({:test_finished, %ExUnit.Test{state: {:failed, failures}} = test}, config) do
if config.trace do
IO.puts failure(trace_test_result(test), config)
end
@@ -73,27 +72,27 @@ defmodule ExUnit.CLIFormatter do
print_failure(formatted, config)
print_logs(test.logs)
{:ok, %{config | test_counter: update_test_counter(config.test_counter, test),
failure_counter: config.failure_counter + 1}}
{:noreply, %{config | test_counter: update_test_counter(config.test_counter, test),
failure_counter: config.failure_counter + 1}}
end
def handle_event({:case_started, %ExUnit.TestCase{name: name}}, config) do
def handle_cast({:case_started, %ExUnit.TestCase{name: name}}, config) do
if config.trace do
IO.puts("\n#{inspect name}")
end
{:ok, config}
{:noreply, config}
end
def handle_event({:case_finished, %ExUnit.TestCase{state: nil}}, config) do
{:ok, config}
def handle_cast({:case_finished, %ExUnit.TestCase{state: nil}}, config) do
{:noreply, config}
end
def handle_event({:case_finished, %ExUnit.TestCase{state: {:failed, failures}} = test_case}, config) do
def handle_cast({:case_finished, %ExUnit.TestCase{state: {:failed, failures}} = test_case}, config) do
formatted = format_test_case_failure(test_case, failures, config.failure_counter + 1,
config.width, &formatter(&1, &2, config))
print_failure(formatted, config)
{:ok, %{config | failure_counter: config.failure_counter + 1}}
{:noreply, %{config | failure_counter: config.failure_counter + 1}}
end
## Tracing
@@ -1,54 +1,68 @@
# This module publishes events during the test suite run.
# This is used, for example, by formatters to print user
# information as well as internal statistics for ExUnit.
defmodule ExUnit.EventManager do
@moduledoc false
@timeout 30_000
def start_link() do
:gen_event.start_link()
end
def add_handler(ref, handler, args) do
:gen_event.add_handler(ref, handler, args)
end
# TODO: Deprecate GenEvent callbacks on Elixir v1.5 alongside GenEvent
def delete_handler(ref, handler, args) do
:gen_event.delete_handler(ref, handler, args)
end
@doc """
Starts an event manager that publishes events during the suite run.
def which_handlers(ref) do
:gen_event.which_handlers(ref)
This is what power formatters as well as the
internal statistics server for ExUnit.
"""
def start_link() do
import Supervisor.Spec
child = worker(GenServer, [], restart: :temporary)
{:ok, sup} = Supervisor.start_link([child], strategy: :simple_one_for_one)
{:ok, event} = :gen_event.start_link()
{:ok, {sup, event}}
end
def call(ref, handler, request) do
:gen_event.call(ref, handler, request)
def stop({sup, event}) do
for {_, pid, _, _} <- Supervisor.which_children(sup) do
GenServer.stop(pid, :normal, @timeout)
end
Supervisor.stop(sup)
GenEvent.stop(event)
end
def call(ref, handler, request, timeout) do
:gen_event.call(ref, handler, request, timeout)
def add_handler({sup, event}, handler, opts) do
if Code.ensure_loaded?(handler) and function_exported?(handler, :handle_call, 2) do
:gen_event.add_handler(event, handler, opts)
else
Supervisor.start_child(sup, [handler, opts])
end
end
def suite_started(ref, opts) do
:gen_event.notify(ref, {:suite_started, opts})
notify(ref, {:suite_started, opts})
end
def suite_finished(ref, run_us, load_us) do
:gen_event.notify(ref, {:suite_finished, run_us, load_us})
notify(ref, {:suite_finished, run_us, load_us})
end
def case_started(ref, test_case) do
:gen_event.notify(ref, {:case_started, test_case})
notify(ref, {:case_started, test_case})
end
def case_finished(ref, test_case) do
:gen_event.notify(ref, {:case_finished, test_case})
notify(ref, {:case_finished, test_case})
end
def test_started(ref, test) do
:gen_event.notify(ref, {:test_started, test})
notify(ref, {:test_started, test})
end
def test_finished(ref, test) do
:gen_event.notify(ref, {:test_finished, test})
notify(ref, {:test_finished, test})
end
defp notify({sup, event}, msg) do
:gen_event.notify(event, msg)
for {_, pid, _, _} <- Supervisor.which_children(sup) do
GenServer.cast(pid, msg)
end
:ok
end
end
@@ -1,10 +1,9 @@
defmodule ExUnit.Formatter do
@moduledoc """
This module holds helper functions related to formatting and contains
documentation about the formatting protocol.
Helper functions for formatting and the formatting protocols.
Formatters are registered at the `ExUnit.EventManager` event manager and
will be send events by the runner.
Formatters are `GenServer`s specified during ExUnit configuration
that receives a series of events as cast messages.
The following events are possible:
@@ -11,7 +11,6 @@ defmodule ExUnit.OnExitHandler do
Agent.update(@name, &Map.put(&1, pid, []))
end
@spec add(pid, term, (()-> term)) :: :ok | :error
def add(pid, name_or_ref, callback) when is_pid(pid) and is_function(callback, 0) do
Agent.get_and_update(@name, fn map ->
@@ -16,21 +16,24 @@ defmodule ExUnit.Runner do
end
EM.suite_finished(config.manager, run_us, load_us)
EM.call(config.manager, ExUnit.RunnerStats, :stop, :infinity)
result = ExUnit.RunnerStats.stats(config.stats)
EM.stop(config.manager)
result
end
def configure(opts) do
opts = normalize_opts(opts)
{:ok, pid} = EM.start_link
formatters = [ExUnit.RunnerStats | opts[:formatters]]
Enum.each formatters, &(:ok = EM.add_handler(pid, &1, opts))
{:ok, manager} = EM.start_link
{:ok, stats} = EM.add_handler(manager, ExUnit.RunnerStats, opts)
Enum.each opts[:formatters], &EM.add_handler(manager, &1, opts)
config = %{
capture_log: opts[:capture_log],
exclude: opts[:exclude],
include: opts[:include],
manager: pid,
manager: manager,
stats: stats,
max_cases: opts[:max_cases],
seed: opts[:seed],
cases: :async,
@@ -1,32 +1,35 @@
# Small event consumer to handle runner statistics.
defmodule ExUnit.RunnerStats do
@moduledoc false
use GenEvent
use GenServer
def init(_opts) do
{:ok, %{total: 0, failures: 0, skipped: 0}}
end
def handle_call(:stop, map) do
{:remove_handler, map}
def stats(pid) do
GenServer.call(pid, :stats, :infinity)
end
def handle_event({:test_finished, %ExUnit.Test{state: {tag, _}}},
def handle_call(:stats, _from, map) do
{:reply, map, map}
end
def handle_cast({:test_finished, %ExUnit.Test{state: {tag, _}}},
%{total: total, failures: failures} = map) when tag in [:failed, :invalid] do
{:ok, %{map | total: total + 1, failures: failures + 1}}
{:noreply, %{map | total: total + 1, failures: failures + 1}}
end
def handle_event({:test_finished, %ExUnit.Test{state: {:skip, _}}},
def handle_cast({:test_finished, %ExUnit.Test{state: {:skip, _}}},
%{total: total, skipped: skipped} = map) do
{:ok, %{map | total: total + 1, skipped: skipped + 1}}
{:noreply, %{map | total: total + 1, skipped: skipped + 1}}
end
def handle_event({:test_finished, _}, %{total: total} = map) do
{:ok, %{map | total: total + 1}}
def handle_cast({:test_finished, _}, %{total: total} = map) do
{:noreply, %{map | total: total + 1}}
end
def handle_event(_, map) do
{:ok, map}
def handle_cast(_, map) do
{:noreply, map}
end
end

0 comments on commit e7f2d14

Please sign in to comment.