The previous strategy wasn't robust enough to detect typos in function calls. For example, if you misremembered String.duplicate/2 as String.repeat/2 the koan runner would hang and have to be restarted. This is because the stacktrace for an error like that doesn't have line numbers. To avoid the problem, we detect the first pieces of the stracktrace that is in the koan module. I can't decide if that's a perfect solution, but it seemed to work in my testing. Hoping to get other's feedback.
55 lines
1.4 KiB
Elixir
55 lines
1.4 KiB
Elixir
defmodule Execute do
|
|
def run_module(module, callback \\ fn(_result, _module, _koan) -> nil end) do
|
|
Enum.reduce_while(module.all_koans, :passed, fn(koan, _) ->
|
|
module
|
|
|> run_koan(koan)
|
|
|> hook(module, koan, callback)
|
|
|> continue?
|
|
end)
|
|
end
|
|
|
|
defp hook(result, module, koan, callback) do
|
|
callback.(result, module, koan)
|
|
result
|
|
end
|
|
|
|
defp continue?(:passed), do: {:cont, :passed}
|
|
defp continue?(result), do: {:halt, result}
|
|
|
|
def run_koan(module, name, args \\ []) do
|
|
parent = self()
|
|
spawn(fn -> exec(module, name, args, parent) end)
|
|
listen_for_result(module, name)
|
|
end
|
|
|
|
def listen_for_result(module, name) do
|
|
receive do
|
|
:ok -> :passed
|
|
%{error: _} = failure -> {:failed, failure, module, name}
|
|
_ -> listen_for_result(module, name)
|
|
end
|
|
end
|
|
|
|
defp exec(module, name, args, parent) do
|
|
result = apply(module, name, args)
|
|
send parent, expand(result, module)
|
|
Process.exit(self(), :kill)
|
|
end
|
|
|
|
defp expand(:ok, _), do: :ok
|
|
defp expand(error, module) do
|
|
{file, line} = System.stacktrace
|
|
|> Enum.drop_while(&!in_koan?(&1, module))
|
|
|> List.first
|
|
|> extract_file_and_line
|
|
|
|
%{error: error, file: file, line: line}
|
|
end
|
|
|
|
defp in_koan?({module, _, _, _}, koan), do: module == koan
|
|
|
|
defp extract_file_and_line({_, _, _, [file: file, line: line]}) do
|
|
{file, line}
|
|
end
|
|
end
|