Skip to content

Commit 1ef67c5

Browse files
author
José Valim
committed
Merge pull request #1103 from mururu/iex-test
Capture_io can has mock input and ExUnit.Server stops when getting :eof as an input
2 parents 6e8fb6c + 2403d0c commit 1ef67c5

File tree

3 files changed

+263
-33
lines changed

3 files changed

+263
-33
lines changed

lib/ex_unit/lib/ex_unit/capture_io.ex

+132-18
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ defmodule ExUnit.CaptureIO do
2828
named device like `:stderr` is also possible globally by
2929
giving the registered device name explicitly as argument.
3030
31-
The input is mocked to return `:eof`.
31+
A developer can set a string as an input. The default
32+
input is `:eof`.
3233
3334
## Examples
3435
@@ -38,19 +39,36 @@ defmodule ExUnit.CaptureIO do
3839
true
3940
iex> capture_io(:stderr, fn -> IO.write(:stderr, "josé") end) == "josé"
4041
true
42+
iex> capture_io("this is input", fn->
43+
...> input = IO.gets ""
44+
...> IO.write input
45+
...> end) == "this is input"
46+
true
4147
4248
"""
43-
def capture_io(device // :stdio, fun) when is_atom(device) do
44-
do_capture_io(map_dev(device), fun)
49+
def capture_io(device, input, fun) do
50+
do_capture_io(map_dev(device), input, fun)
51+
end
52+
53+
def capture_io(device, fun) when is_atom(device) do
54+
do_capture_io(map_dev(device), "", fun)
55+
end
56+
57+
def capture_io(input, fun) when is_binary(input) do
58+
do_capture_io(:standard_io, input, fun)
59+
end
60+
61+
def capture_io(fun) do
62+
do_capture_io(:standard_io, "", fun)
4563
end
4664

4765
defp map_dev(:stdio), do: :standard_io
4866
defp map_dev(:stderr), do: :standard_error
4967
defp map_dev(other), do: other
5068

51-
defp do_capture_io(:standard_io, fun) do
69+
defp do_capture_io(:standard_io, input, fun) do
5270
original_gl = :erlang.group_leader
53-
capture_gl = new_group_leader(self)
71+
capture_gl = new_group_leader(self, input)
5472
:erlang.group_leader(capture_gl, self)
5573

5674
try do
@@ -65,13 +83,13 @@ defmodule ExUnit.CaptureIO do
6583
end
6684
end
6785

68-
defp do_capture_io(device, fun) do
86+
defp do_capture_io(device, input, fun) do
6987
unless original_io = Process.whereis(device) do
7088
raise "could not find IO device registered at #{inspect device}"
7189
end
7290

7391
Process.unregister(device)
74-
capture_io = new_group_leader(self)
92+
capture_io = new_group_leader(self, input)
7593
Process.register(capture_io, device)
7694

7795
try do
@@ -87,14 +105,36 @@ defmodule ExUnit.CaptureIO do
87105
end
88106
end
89107

90-
defp new_group_leader(runner) do
91-
spawn_link(fn -> group_leader_process(runner) end)
108+
defp new_group_leader(runner, input) do
109+
spawn_link(fn -> group_leader_process(runner, input) end)
92110
end
93111

94-
defp group_leader_process(runner) do
112+
defp group_leader_process(runner, input) do
113+
register_input(input)
95114
group_leader_loop(runner, :infinity, [])
96115
end
97116

117+
defp register_input(nil) do
118+
set_input(nil)
119+
end
120+
121+
defp register_input(input) do
122+
chars = :unicode.characters_to_list(input)
123+
set_input(chars)
124+
end
125+
126+
defp set_input(:eof) do
127+
set_input([])
128+
end
129+
130+
defp set_input(input) do
131+
Process.put(:capture_io_input, input)
132+
end
133+
134+
defp get_input do
135+
Process.get(:capture_io_input)
136+
end
137+
98138
defp group_leader_loop(runner, wait, buf) do
99139
receive do
100140
{ :io_request, from, reply_as, req } ->
@@ -141,24 +181,28 @@ defmodule ExUnit.CaptureIO do
141181
io_request({ :put_chars, mod, func, args }, buf)
142182
end
143183

144-
defp io_request({ :get_chars, _enc, _propmpt, _n }, buf) do
145-
{ :eof, buf }
184+
defp io_request({ :get_chars, _enc, _prompt, n }, buf) when n >= 0 do
185+
{ get_chars(n), buf }
146186
end
147187

148-
defp io_request({ :get_chars, _prompt, _n }, buf) do
149-
{ :eof, buf }
188+
defp io_request({ :get_chars, _prompt, n }, buf) when n >= 0 do
189+
{ get_chars(n), buf }
150190
end
151191

152192
defp io_request({ :get_line, _prompt }, buf) do
153-
{ :eof, buf }
193+
{ get_line, buf }
154194
end
155195

156196
defp io_request({ :get_line, _enc, _prompt }, buf) do
157-
{ :eof, buf }
197+
{ get_line, buf }
158198
end
159199

160-
defp io_request({ :get_until, _prompt, _m, _f, _as }, buf) do
161-
{ :eof, buf }
200+
defp io_request({ :get_until, _prompt, mod, fun, args }, buf) do
201+
{ get_until(mod, fun, args), buf }
202+
end
203+
204+
defp io_request({ :get_until, _encoding, _prompt, mod, fun, args}, buf) do
205+
{ get_until(mod, fun, args), buf }
162206
end
163207

164208
defp io_request({ :setopts, _opts }, buf) do
@@ -193,6 +237,76 @@ defmodule ExUnit.CaptureIO do
193237
result
194238
end
195239

240+
defp get_line do
241+
input = get_input
242+
243+
case input do
244+
[] ->
245+
:eof
246+
_ ->
247+
{ line, rest } = Enum.split_while(input, fn(char) -> char != ?\n end)
248+
case rest do
249+
[] ->
250+
set_input([])
251+
:unicode.characters_to_binary(line)
252+
[_|t] ->
253+
set_input(t)
254+
:unicode.characters_to_binary(line ++ '\n')
255+
end
256+
end
257+
end
258+
259+
defp get_chars(n) do
260+
input = get_input
261+
262+
case input do
263+
[] ->
264+
:eof
265+
_ ->
266+
{ chars, rest } = Enum.split(input, n)
267+
set_input(rest)
268+
:unicode.characters_to_binary(chars)
269+
end
270+
end
271+
272+
defp get_until(mod, fun, args) do
273+
input = get_input
274+
do_get_until(input, mod, fun, args)
275+
end
276+
277+
defp do_get_until([], mod, fun, args, continuation // []) do
278+
case apply(mod, fun, [continuation, :eof | args]) do
279+
{ :done, result, rest_chars } ->
280+
set_input(rest_chars)
281+
result
282+
{ :more, next_continuation } ->
283+
do_get_until([], mod, fun, args, next_continuation)
284+
end
285+
end
286+
287+
defp do_get_until(input, mod, fun, args, continuation // []) do
288+
{ line, rest } = Enum.split_while(input, fn(char) -> char != ?\n end)
289+
290+
case rest do
291+
[] ->
292+
case apply(mod, fun, [continuation, line | args]) do
293+
{ :done, result, rest_chars } ->
294+
set_input(rest_chars)
295+
result
296+
{ :more, next_continuation } ->
297+
do_get_until([], mod, fun, args, next_continuation)
298+
end
299+
[_|t] ->
300+
case apply(mod, fun, [continuation, line ++ '\n' | args]) do
301+
{ :done, result, rest_chars } ->
302+
set_input(rest_chars ++ t)
303+
result
304+
{ :more, next_continuation } ->
305+
do_get_until(t, mod, fun, args, next_continuation)
306+
end
307+
end
308+
end
309+
196310
defp buffer_to_result([]) do
197311
nil
198312
end

lib/ex_unit/test/ex_unit/capture_io_test.exs

+115-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,30 @@ end
66

77
alias ExUnit.CaptureIOTest.Value
88

9+
defmodule ExUnit.CaptureIOTest.GetUntil do
10+
def until_new_line(_, :eof, _) do
11+
{ :done, :eof, [] }
12+
end
13+
14+
def until_new_line(this_far, chars, stop_char) do
15+
case Enum.split_while(chars, fn(c) -> c != stop_char end) do
16+
{ l, [] } ->
17+
{ :more, this_far ++ l }
18+
{ l, [stop_char|rest] } ->
19+
{ :done, this_far ++ l ++ [stop_char], rest }
20+
end
21+
end
22+
23+
def get_line(device // Process.group_leader) do
24+
device <- { :io_request, self, device, { :get_until, :unicode, "", __MODULE__, :until_new_line, [?\n] } }
25+
receive do
26+
{ :io_reply, _, data } -> data
27+
end
28+
end
29+
end
30+
31+
alias ExUnit.CaptureIOTest.GetUntil
32+
933
defmodule ExUnit.CaptureIOTest do
1034
use ExUnit.Case, async: true
1135

@@ -36,6 +60,10 @@ defmodule ExUnit.CaptureIOTest do
3660
:io.put_chars("josé")
3761
end) == "josé"
3862

63+
assert capture_io(fn ->
64+
spawn(fn -> :io.put_chars("a") end)
65+
end) == "a"
66+
3967
assert capture_io(fn ->
4068
assert :io.put_chars("a") == :ok
4169
end)
@@ -55,6 +83,22 @@ defmodule ExUnit.CaptureIOTest do
5583
capture_io(fn ->
5684
assert :io.get_chars(">", 3) == :eof
5785
end)
86+
87+
capture_io("", fn ->
88+
assert :io.get_chars(">", 3) == :eof
89+
end)
90+
91+
capture_io("abc\ndef", fn ->
92+
assert :io.get_chars(">", 3) == "abc"
93+
assert :io.get_chars(">", 5) == "\ndef"
94+
assert :io.get_chars(">", 7) == :eof
95+
end)
96+
97+
capture_io("あいう", fn ->
98+
assert :io.get_chars(">", 2) == "あい"
99+
assert :io.get_chars(">", 1) == "う"
100+
assert :io.get_chars(">", 1) == :eof
101+
end)
58102
end
59103

60104
test :capture_io_with_get_line do
@@ -65,15 +109,84 @@ defmodule ExUnit.CaptureIOTest do
65109
capture_io(fn ->
66110
assert :io.get_line(">") == :eof
67111
end)
112+
113+
capture_io("", fn ->
114+
assert :io.get_line(">") == :eof
115+
end)
116+
117+
capture_io("\n", fn ->
118+
assert :io.get_line(">") == "\n"
119+
assert :io.get_line(">") == :eof
120+
end)
121+
122+
capture_io("a", fn ->
123+
assert :io.get_line(">") == "a"
124+
assert :io.get_line(">") == :eof
125+
end)
126+
127+
capture_io("a\n", fn ->
128+
assert :io.get_line(">") == "a\n"
129+
assert :io.get_line(">") == :eof
130+
end)
131+
132+
capture_io("a\nb", fn ->
133+
assert :io.get_line(">") == "a\n"
134+
assert :io.get_line(">") == "b"
135+
assert :io.get_line(">") == :eof
136+
end)
137+
138+
capture_io("あい\nう", fn ->
139+
assert :io.get_line(">") == "あい\n"
140+
assert :io.get_line(">") == "う"
141+
assert :io.get_line(">") == :eof
142+
end)
68143
end
69144

70145
test :capture_io_with_get_until do
71146
assert capture_io(fn ->
72-
send_and_receive_io({ :get_until, '>', :m, :f, :as })
147+
assert :io.scan_erl_form('>')
73148
end) == nil
74149

75150
capture_io(fn ->
76-
assert send_and_receive_io({ :get_until, '>', :m, :f, :as }) == :eof
151+
assert :io.scan_erl_form('>') == { :eof, 1 }
152+
end)
153+
154+
capture_io("1", fn ->
155+
assert :io.scan_erl_form('>') == { :ok, [{ :integer, 1, 1 }], 1 }
156+
assert :io.scan_erl_form('>') == { :eof, 1 }
157+
end)
158+
159+
capture_io("1\n.", fn ->
160+
assert :io.scan_erl_form('>') == { :ok, [{ :integer, 1, 1 }, { :dot, 2 }], 2 }
161+
assert :io.scan_erl_form('>') == { :eof, 1 }
162+
end)
163+
164+
capture_io("1.\n.", fn ->
165+
assert :io.scan_erl_form('>') == { :ok, [{ :integer, 1, 1 }, { :dot, 1 }], 2 }
166+
assert :io.scan_erl_form('>') == { :ok, [dot: 1], 1}
167+
assert :io.scan_erl_form('>') == { :eof, 1 }
168+
end)
169+
170+
capture_io("\"a", fn ->
171+
assert :io.scan_erl_form('>') == { :error, { 1, :erl_scan, { :string, 34, 'a' } }, 1 }
172+
assert :io.scan_erl_form('>') == { :eof, 1 }
173+
end)
174+
175+
capture_io("\"a\n\"", fn ->
176+
assert :io.scan_erl_form('>') == { :ok, [{ :string, 1, 'a\n' }], 2 }
177+
assert :io.scan_erl_form('>') == { :eof, 1 }
178+
end)
179+
180+
capture_io(":erl. mof*,,l", fn ->
181+
assert :io.scan_erl_form('>') == { :ok, [{ :":", 1 }, { :atom, 1, :erl }, { :dot, 1 }], 1 }
182+
assert :io.scan_erl_form('>') == { :ok, [{ :atom, 1, :mof }, { :*, 1 }, { :"," , 1 }, { :",", 1 }, { :atom, 1, :l }], 1 }
183+
assert :io.scan_erl_form('>') == { :eof, 1 }
184+
end)
185+
186+
capture_io("a\nb\nc", fn ->
187+
assert GetUntil.get_line == 'a\n'
188+
assert GetUntil.get_line == 'b\n'
189+
assert GetUntil.get_line == :eof
77190
end)
78191
end
79192

0 commit comments

Comments
 (0)