Merge pull request #29 from ukutaht/process_koans
Koans about processes.
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
defmodule BlankAssertions do
|
defmodule BlankAssertions do
|
||||||
|
require ExUnit.Assertions
|
||||||
|
|
||||||
defmacro assert(expr) do
|
defmacro assert(expr) do
|
||||||
if contains_blank?(expr) do
|
if contains_blank?(expr) do
|
||||||
code = Macro.escape(expr)
|
code = Macro.escape(expr)
|
||||||
@@ -25,6 +27,19 @@ defmodule BlankAssertions do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defmacro assert_receive(expr) do
|
||||||
|
if contains_blank?(expr) do
|
||||||
|
code = Macro.escape(expr)
|
||||||
|
quote do
|
||||||
|
raise ExUnit.AssertionError, expr: unquote(code)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
quote do
|
||||||
|
ExUnit.Assertions.assert_receive(unquote(expr), 100)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def assert(value, opts) do
|
def assert(value, opts) do
|
||||||
ExUnit.Assertions.assert(value, opts)
|
ExUnit.Assertions.assert(value, opts)
|
||||||
end
|
end
|
||||||
@@ -33,6 +48,12 @@ defmodule BlankAssertions do
|
|||||||
ExUnit.Assertions.refute(value, opts)
|
ExUnit.Assertions.refute(value, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defmacro flunk(message \\ "Flunked!") do
|
||||||
|
quote do
|
||||||
|
assert false, message: unquote(message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp contains_blank?(expr) do
|
defp contains_blank?(expr) do
|
||||||
{_, blank} = Macro.prewalk(expr, false, &blank?/2)
|
{_, blank} = Macro.prewalk(expr, false, &blank?/2)
|
||||||
blank
|
blank
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ defmodule Display do
|
|||||||
|
|
||||||
IO.puts("Now meditate upon #{display_module(module)}")
|
IO.puts("Now meditate upon #{display_module(module)}")
|
||||||
IO.puts("---------------------------------------")
|
IO.puts("---------------------------------------")
|
||||||
IO.puts(format_cyan(display_failed_assertion(module, expr)))
|
IO.puts(format_cyan(last_failure_location))
|
||||||
IO.puts(display_koan(name))
|
IO.puts(display_koan(name))
|
||||||
IO.puts(format_red(Macro.to_string(expr)))
|
IO.puts(format_red(Macro.to_string(expr)))
|
||||||
end
|
end
|
||||||
@@ -23,8 +23,20 @@ defmodule Display do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def display_failed_assertion(module, expr) do
|
def last_failure_location do
|
||||||
"Assertion failed in #{source_file(module)}:#{line_number(expr)}"
|
{file, line} = System.stacktrace
|
||||||
|
|> Enum.drop_while(&in_ex_unit?/1)
|
||||||
|
|> List.first
|
||||||
|
|> extract_file_and_line
|
||||||
|
|
||||||
|
"Assertion failed in #{file}:#{line}"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp in_ex_unit?({ExUnit.Assertions, _, _, _}), do: true
|
||||||
|
defp in_ex_unit?(_), do: false
|
||||||
|
|
||||||
|
defp extract_file_and_line({_, _, _, [file: file, line: line]}) do
|
||||||
|
{file, line}
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_compile_error(error) do
|
def format_compile_error(error) do
|
||||||
@@ -32,16 +44,6 @@ defmodule Display do
|
|||||||
IO.puts(format_red(Exception.format(:error, error, trace)))
|
IO.puts(format_red(Exception.format(:error, error, trace)))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp line_number({_, [line: line], _}) do
|
|
||||||
line
|
|
||||||
end
|
|
||||||
|
|
||||||
defp source_file(module) do
|
|
||||||
module.__info__(:compile)
|
|
||||||
|> Dict.get(:source)
|
|
||||||
|> Path.relative_to(@current_dir)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp format_red(str) do
|
defp format_red(str) do
|
||||||
Enum.join([ANSI.red, str, ANSI.reset], "")
|
Enum.join([ANSI.red, str, ANSI.reset], "")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ defmodule Koans do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defmacro __using__(_) do
|
defmacro __using__(opts) do
|
||||||
quote do
|
quote do
|
||||||
import Koans
|
|
||||||
require ExUnit.Assertions
|
require ExUnit.Assertions
|
||||||
|
import Koans
|
||||||
import BlankAssertions
|
import BlankAssertions
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
123
lib/koans/10_processes.ex
Normal file
123
lib/koans/10_processes.ex
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
defmodule Processes do
|
||||||
|
use Koans
|
||||||
|
|
||||||
|
koan "tests run in a process!" do
|
||||||
|
assert Process.alive?(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "can spew out information about a process" do
|
||||||
|
information = Process.info(self)
|
||||||
|
|
||||||
|
assert information[:status] == :running
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "process can send messages to itself" do
|
||||||
|
send self(), "hola!"
|
||||||
|
assert_receive "hola!"
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "a common pattern is to include the sender in the message" do
|
||||||
|
pid = spawn(fn -> receive do
|
||||||
|
{:hello, sender} -> send sender, :how_are_you?
|
||||||
|
_ -> assert false
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
send pid, {:hello, self()}
|
||||||
|
assert_receive :how_are_you?
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "you don't have to wait forever for messages" do
|
||||||
|
parent = self()
|
||||||
|
spawn(fn -> receive do
|
||||||
|
_anything -> flunk "I really wasn't expecting messages"
|
||||||
|
after
|
||||||
|
10 -> send parent, {:waited_too_long, "I am inpatient"}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert_receive {:waited_too_long, "I am inpatient"}
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "killing a process will terminate it" do
|
||||||
|
pid = spawn(fn -> Process.exit(self(), :kill) end)
|
||||||
|
:timer.sleep(500)
|
||||||
|
refute Process.alive?(pid)
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "killing a process kills it for good" do
|
||||||
|
pid = spawn(fn -> receive do
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
assert Process.alive?(pid)
|
||||||
|
Process.exit(pid, :kill)
|
||||||
|
refute Process.alive?(pid)
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "can trap a signal in a child process" do
|
||||||
|
parent = self()
|
||||||
|
pid = spawn(fn ->
|
||||||
|
Process.flag(:trap_exit, true)
|
||||||
|
send parent, :ready
|
||||||
|
receive do
|
||||||
|
{:EXIT, _pid, reason} -> send parent, {:exited, reason}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
wait()
|
||||||
|
Process.exit(pid, :random_reason)
|
||||||
|
|
||||||
|
assert_receive {:exited, :random_reason}
|
||||||
|
refute Process.alive?(pid)
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "quitting normally has no effect" do
|
||||||
|
pid = spawn(fn -> receive do
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
Process.exit(pid, :normal)
|
||||||
|
assert Process.alive?(pid)
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "quititing your own process normally does terminate it though" do
|
||||||
|
pid = spawn(fn -> receive do
|
||||||
|
:bye -> Process.exit(self(), :normal)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert Process.alive?(pid)
|
||||||
|
send pid, :bye
|
||||||
|
:timer.sleep(100)
|
||||||
|
refute Process.alive?(pid)
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "linked processes are informed about exit signals of children when trapping those signals" do
|
||||||
|
parent = self()
|
||||||
|
spawn(fn ->
|
||||||
|
Process.flag(:trap_exit, true)
|
||||||
|
spawn_link(fn -> Process.exit(self(), :normal) end)
|
||||||
|
receive do
|
||||||
|
{:EXIT, _pid ,reason} -> send parent, {:exited, reason}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert_receive {:exited, :normal}
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "monitoring processes are informed via messages without having trapping" do
|
||||||
|
parent = self()
|
||||||
|
spawn(fn ->
|
||||||
|
spawn_monitor(fn -> Process.exit(self(), :normal) end)
|
||||||
|
receive do
|
||||||
|
{:DOWN, _ref, :process, _pid, reason} -> send parent, {:exited, reason}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert_receive {:exited, :normal}
|
||||||
|
end
|
||||||
|
|
||||||
|
def wait do
|
||||||
|
receive do
|
||||||
|
:ready -> true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
49
lib/koans/11_task.ex
Normal file
49
lib/koans/11_task.ex
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
defmodule Tasks do
|
||||||
|
use Koans
|
||||||
|
|
||||||
|
koan "Tasks can be used for asynchronous computations with results" do
|
||||||
|
task = Task.async(fn -> 3 *3 end)
|
||||||
|
do_other_stuff()
|
||||||
|
assert Task.await(task) + 1 == 10
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "if you don't need a result, use start_link/1" do
|
||||||
|
{:ok, pid} = Task.start_link(fn -> 1+1 end)
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "yield returns nil if the task isn't done yet" do
|
||||||
|
handle = Task.async(fn ->
|
||||||
|
:timer.sleep(100)
|
||||||
|
3 * 3
|
||||||
|
end)
|
||||||
|
assert Task.yield(handle, 10) == nil
|
||||||
|
assert Task.await(handle) == 9
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "tasks can be aborted with shutdown" do
|
||||||
|
handle = Task.async(fn ->
|
||||||
|
:timer.sleep(100)
|
||||||
|
3 * 3
|
||||||
|
end)
|
||||||
|
refute Task.shutdown(handle)
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "shutdown will give you an answer if it has it" do
|
||||||
|
handle = Task.async(fn -> 3 * 3 end)
|
||||||
|
:timer.sleep(10)
|
||||||
|
assert Task.shutdown(handle) == {:ok, 9}
|
||||||
|
end
|
||||||
|
|
||||||
|
koan "you can yield to multiple tasks at once and extract the results" do
|
||||||
|
squares = [1,2,3,4]
|
||||||
|
|> Enum.map(fn(number) -> Task.async(fn -> number * number end) end)
|
||||||
|
|> Task.yield_many(100)
|
||||||
|
|> Enum.map(fn({_task,{:ok, result}}) -> result end)
|
||||||
|
|
||||||
|
assert squares == [1,4,9,16]
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_other_stuff do
|
||||||
|
:timer.sleep(50)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -3,6 +3,8 @@ defmodule Mix.Tasks.Meditate do
|
|||||||
alias Options
|
alias Options
|
||||||
|
|
||||||
def run(args) do
|
def run(args) do
|
||||||
|
ExUnit.start
|
||||||
|
Application.ensure_all_started(:ex_unit)
|
||||||
Application.ensure_all_started(:elixir_koans)
|
Application.ensure_all_started(:elixir_koans)
|
||||||
Code.compiler_options(ignore_module_conflict: true)
|
Code.compiler_options(ignore_module_conflict: true)
|
||||||
Watcher.start
|
Watcher.start
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ defmodule Runner do
|
|||||||
Enums,
|
Enums,
|
||||||
Arithmetic,
|
Arithmetic,
|
||||||
Structs,
|
Structs,
|
||||||
PatternMatching
|
PatternMatching,
|
||||||
|
Processes,
|
||||||
|
Tasks
|
||||||
]
|
]
|
||||||
|
|
||||||
def run do
|
def run do
|
||||||
|
|||||||
Reference in New Issue
Block a user