-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1059 from mururu/io_protocol
Implement capture_io for ex_unit
- Loading branch information
Showing
2 changed files
with
331 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
defmodule ExUnit.CaptureIO do | ||
@moduledoc """ | ||
This module provides functionality to capture IO to test it. | ||
The way to use this module is to import them into your module. | ||
## Examples | ||
defmodule AssertionTest do | ||
use ExUnit.Case | ||
import ExUnit.CaptureIO | ||
test :example do | ||
assert capture_io(fn -> | ||
IO.puts "a" | ||
end) == "a\n" | ||
end | ||
end | ||
""" | ||
|
||
@doc """ | ||
Captures IO. Returns nil in case of no output. | ||
Otherwise returns the binary which is captured outputs. | ||
The input is mocked to return :eof. | ||
## Examples | ||
iex> capture_io(fn -> IO.write "josé" end) == "josé" | ||
true | ||
iex> capture_io(fn -> :ok end) == nil | ||
true | ||
""" | ||
def capture_io(fun) do | ||
original_gl = :erlang.group_leader | ||
capture_gl = new_group_leader(self) | ||
|
||
:erlang.group_leader(capture_gl, self) | ||
fun.() | ||
:erlang.group_leader(original_gl, self) | ||
|
||
group_leader_sync(capture_gl) | ||
end | ||
|
||
defp new_group_leader(runner) do | ||
spawn_link(fn -> group_leader_process(runner) end) | ||
end | ||
|
||
defp group_leader_process(runner) do | ||
group_leader_loop(runner, :infinity, []) | ||
end | ||
|
||
defp group_leader_loop(runner, wait, buf) do | ||
receive do | ||
{ :io_request, from, reply_as, req } -> | ||
p = :erlang.process_flag(:priority, :normal) | ||
buf = io_request(from, reply_as, req, buf) | ||
:erlang.process_flag(:priority, p) | ||
group_leader_loop(runner, wait, buf) | ||
:stop -> | ||
receive after: (2 -> :ok) | ||
:erlang.process_flag(:priority, :low) | ||
group_leader_loop(runner, 0, buf) | ||
_ -> | ||
group_leader_loop(runner, 0, buf) | ||
after wait -> | ||
:erlang.process_flag(:priority, :normal) | ||
runner <- { self, buffer_to_result(buf) } | ||
end | ||
end | ||
|
||
defp group_leader_sync(gl) do | ||
gl <- :stop | ||
|
||
receive do | ||
{ ^gl, buf } -> buf | ||
end | ||
end | ||
|
||
defp io_request(from, reply_as, req, buf) do | ||
{ reply, buf1 } = io_request(req, buf) | ||
io_reply(from, reply_as, reply) | ||
buf1 | ||
end | ||
|
||
defp io_reply(from, reply_as, reply) do | ||
from <- { :io_reply, reply_as, reply } | ||
end | ||
|
||
defp io_request({ :put_chars, chars }, buf) do | ||
{ :ok, [chars|buf] } | ||
end | ||
|
||
defp io_request({ :put_chars, m, f, as }, buf) do | ||
chars = apply(m ,f, as) | ||
{ :ok, [chars|buf] } | ||
end | ||
|
||
defp io_request({ :put_chars, _enc, chars }, buf) do | ||
io_request({ :put_chars, chars }, buf) | ||
end | ||
|
||
defp io_request({ :put_chars, _enc, mod, func, args }, buf) do | ||
io_request({ :put_chars, mod, func, args }, buf) | ||
end | ||
|
||
defp io_request({ :get_chars, _enc, _propmpt, _n }, buf) do | ||
{ :eof, buf } | ||
end | ||
|
||
defp io_request({ :get_chars, _prompt, _n }, buf) do | ||
{ :eof, buf } | ||
end | ||
|
||
defp io_request({ :get_line, _prompt }, buf) do | ||
{ :eof, buf } | ||
end | ||
|
||
defp io_request({ :get_line, _enc, _prompt }, buf) do | ||
{ :eof, buf } | ||
end | ||
|
||
defp io_request({ :get_until, _prompt, _m, _f, _as }, buf) do | ||
{ :eof, buf } | ||
end | ||
|
||
defp io_request({ :setopts, _opts }, buf) do | ||
{ :ok, buf } | ||
end | ||
|
||
defp io_request(:getopts, buf) do | ||
{ { :error, :enotsup }, buf } | ||
end | ||
|
||
defp io_request({ :get_geometry, :columns }, buf) do | ||
{ { :error, :enotsup }, buf } | ||
end | ||
|
||
defp io_request({ :get_geometry, :rows }, buf) do | ||
{ { :error, :enotsup }, buf } | ||
end | ||
|
||
defp io_request({ :requests, reqs }, buf) do | ||
io_requests(reqs, { :ok, buf }) | ||
end | ||
|
||
defp io_request(_, buf) do | ||
{ { :error, :request }, buf } | ||
end | ||
|
||
defp io_requests([r|rs], { :ok, buf }) do | ||
io_requests(rs, io_request(r, buf)) | ||
end | ||
|
||
defp io_requests(_, result) do | ||
result | ||
end | ||
|
||
defp buffer_to_result([]) do | ||
nil | ||
end | ||
|
||
defp buffer_to_result([bin]) when is_binary(bin) do | ||
bin | ||
end | ||
|
||
defp buffer_to_result(buf) do | ||
buf |> :lists.reverse |> list_to_binary | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
Code.require_file "../test_helper.exs", __DIR__ | ||
|
||
defmodule ExUnit.CaptureIOTest.Value do | ||
def binary, do: "a" | ||
end | ||
|
||
alias ExUnit.CaptureIOTest.Value | ||
|
||
defmodule ExUnit.CaptureIOTest do | ||
use ExUnit.Case, async: true | ||
|
||
doctest ExUnit.CaptureIO, import: true | ||
|
||
import ExUnit.CaptureIO | ||
|
||
test :capture_io_with_nothing do | ||
assert capture_io(fn -> | ||
end) == nil | ||
end | ||
|
||
test :capture_io_with_put_chars do | ||
assert capture_io(fn -> | ||
:io.put_chars("") | ||
end) == "" | ||
|
||
assert capture_io(fn -> | ||
:io.put_chars("a") | ||
:io.put_chars("b") | ||
end) == "ab" | ||
|
||
assert capture_io(fn -> | ||
send_and_receive_io({ :put_chars, :unicode, Value, :binary, [] }) | ||
end) == "a" | ||
|
||
assert capture_io(fn -> | ||
:io.put_chars("josé") | ||
end) == "josé" | ||
|
||
assert capture_io(fn -> | ||
assert :io.put_chars("a") == :ok | ||
end) | ||
end | ||
|
||
test :capture_io_with_put_chars_to_stderr do | ||
assert capture_io(fn -> | ||
:io.put_chars(:standard_error, "a") | ||
end) == nil | ||
end | ||
|
||
test :capture_io_with_get_chars do | ||
assert capture_io(fn -> | ||
:io.get_chars(">", 3) | ||
end) == nil | ||
|
||
capture_io(fn -> | ||
assert :io.get_chars(">", 3) == :eof | ||
end) | ||
end | ||
|
||
test :capture_io_with_get_line do | ||
assert capture_io(fn -> | ||
:io.get_line ">" | ||
end) == nil | ||
|
||
capture_io(fn -> | ||
assert :io.get_line(">") == :eof | ||
end) | ||
end | ||
|
||
test :capture_io_with_get_until do | ||
assert capture_io(fn -> | ||
send_and_receive_io({ :get_until, '>', :m, :f, :as }) | ||
end) == nil | ||
|
||
capture_io(fn -> | ||
assert send_and_receive_io({ :get_until, '>', :m, :f, :as }) == :eof | ||
end) | ||
end | ||
|
||
test :capture_io_with_setopts do | ||
assert capture_io(fn -> | ||
:io.setopts({ :encoding, :latin1 }) | ||
end) == nil | ||
|
||
capture_io(fn -> | ||
assert :io.setopts({ :encoding, :latin1 }) == :ok | ||
end) | ||
end | ||
|
||
test :capture_io_with_getopts do | ||
assert capture_io(fn -> | ||
:io.getopts | ||
end) == nil | ||
|
||
capture_io(fn -> | ||
assert :io.getopts == { :error, :enotsup } | ||
end) | ||
end | ||
|
||
test :capture_io_with_columns do | ||
assert capture_io(fn -> | ||
:io.columns | ||
end) == nil | ||
|
||
capture_io(fn -> | ||
assert :io.columns == { :error, :enotsup } | ||
end) | ||
end | ||
|
||
test :capture_io_with_rows do | ||
assert capture_io(fn -> | ||
:io.rows | ||
end) == nil | ||
|
||
capture_io(fn -> | ||
assert :io.rows == { :error, :enotsup } | ||
end) | ||
end | ||
|
||
test :capture_io_with_multiple_io_requests do | ||
assert capture_io(fn -> | ||
send_and_receive_io({ :requests, [{ :put_chars, :unicode, "a" }, | ||
{ :put_chars, :unicode, "b" }]}) | ||
end) == "ab" | ||
|
||
capture_io(fn -> | ||
assert send_and_receive_io({ :requests, [{ :put_chars, :unicode, "a" }, | ||
{ :put_chars, :unicode, "b" }]}) == :ok | ||
end) | ||
end | ||
|
||
test :caputure_io_with_unknown_io_request do | ||
assert capture_io(fn -> | ||
send_and_receive_io(:unknown) | ||
end) == nil | ||
|
||
capture_io(fn -> | ||
assert send_and_receive_io(:unknown) == { :error, :request } | ||
end) | ||
end | ||
|
||
test :capture_io_with_inside_assert do | ||
try do | ||
capture_io(fn -> | ||
assert false | ||
end) | ||
rescue | ||
error in [ExUnit.AssertionError] -> | ||
"Expected false to be true" = error.message | ||
end | ||
end | ||
|
||
defp send_and_receive_io(req) do | ||
:erlang.group_leader <- { :io_request, self, self, req } | ||
s = self | ||
receive do | ||
{ :io_reply, ^s, res} -> res | ||
end | ||
end | ||
end |