Skip to content

Commit 1ad1d6d

Browse files
fishcakezJosé Valim
authored andcommitted
Merge pull request #3382 from lexmag/string_io-fixes
Fix `StringIO` processes leakage in the `ExUnit.CaptureIO` Signed-off-by: José Valim <[email protected]>
1 parent 83d8161 commit 1ad1d6d

File tree

3 files changed

+52
-26
lines changed

3 files changed

+52
-26
lines changed

lib/elixir/test/elixir/string_io_test.exs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,12 @@ Code.require_file "test_helper.exs", __DIR__
33
defmodule StringIOTest do
44
use ExUnit.Case, async: true
55

6-
test "start and stop" do
6+
test "open and close" do
77
{:ok, pid} = StringIO.open("")
88
assert StringIO.close(pid) == {:ok, {"", ""}}
99
end
1010

11-
test "start_link and stop" do
12-
{:ok, pid} = StringIO.open("")
13-
assert StringIO.close(pid) == {:ok, {"", ""}}
14-
end
15-
16-
test "peek" do
11+
test "contents" do
1712
{:ok, pid} = StringIO.open("abc")
1813
IO.write(pid, "edf")
1914
assert StringIO.contents(pid) == {"abc", "edf"}

lib/ex_unit/lib/ex_unit/capture_io.ex

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ defmodule ExUnit.CaptureIO do
99
1010
import ExUnit.CaptureIO
1111
12-
test :example do
13-
assert capture_io(fn ->
14-
IO.puts "a"
15-
end) == "a\n"
12+
test "checking the return value and the IO output" do
13+
fun = fn ->
14+
assert Enum.each(["some", "example"], &(IO.puts &1)) == :ok
15+
end
16+
17+
assert capture_io(fun) == "some\nexample\n"
18+
19+
# Or use only: `capture_io(fun)` to silence the
20+
# IO output (so only assert the return value)
1621
end
1722
end
18-
1923
"""
2024

2125
@doc """
@@ -91,15 +95,13 @@ defmodule ExUnit.CaptureIO do
9195
prompt_config = Keyword.get(options, :capture_prompt, true)
9296
input = Keyword.get(options, :input, "")
9397

94-
original_gl = :erlang.group_leader
98+
original_gl = Process.group_leader()
9599
{:ok, capture_gl} = StringIO.open(input, capture_prompt: prompt_config)
96-
:erlang.group_leader(capture_gl, self)
97-
98100
try do
99-
fun.()
100-
StringIO.close(capture_gl) |> elem(1) |> elem(1)
101+
Process.group_leader(self(), capture_gl)
102+
do_capture_io(capture_gl, fun)
101103
after
102-
:erlang.group_leader(original_gl, self)
104+
Process.group_leader(self(), original_gl)
103105
end
104106
end
105107

@@ -119,8 +121,7 @@ defmodule ExUnit.CaptureIO do
119121
Process.register(capture_io, device)
120122

121123
try do
122-
fun.()
123-
StringIO.close(capture_io) |> elem(1) |> elem(1)
124+
do_capture_io(capture_io, fun)
124125
after
125126
try do
126127
Process.unregister(device)
@@ -131,4 +132,20 @@ defmodule ExUnit.CaptureIO do
131132
ExUnit.Server.remove_device(device)
132133
end
133134
end
135+
136+
defp do_capture_io(string_io, fun) do
137+
try do
138+
_ = fun.()
139+
:ok
140+
catch
141+
kind, reason ->
142+
stack = System.stacktrace()
143+
_ = StringIO.close(string_io)
144+
:erlang.raise(kind, reason, stack)
145+
else
146+
:ok ->
147+
{:ok, output} = StringIO.close(string_io)
148+
elem(output, 1)
149+
end
150+
end
134151
end

lib/ex_unit/test/ex_unit/capture_io_test.exs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,25 @@ defmodule ExUnit.CaptureIOTest do
2828
import ExUnit.CaptureIO
2929
doctest ExUnit.CaptureIO, import: true
3030

31+
test "no leakage on failures" do
32+
group_leader = Process.group_leader()
33+
34+
test = self()
35+
assert_raise ArgumentError, fn ->
36+
capture_io(fn ->
37+
send(test, {:string_io, Process.group_leader()})
38+
raise ArgumentError
39+
end)
40+
end
41+
42+
receive do
43+
{:string_io, pid} ->
44+
ref = Process.monitor(pid)
45+
assert_receive {:DOWN, ^ref, _, _, _}
46+
end
47+
assert Process.group_leader() == group_leader
48+
end
49+
3150
test "with no output" do
3251
assert capture_io(fn ->
3352
end) == ""
@@ -290,19 +309,14 @@ defmodule ExUnit.CaptureIOTest do
290309
end
291310

292311
test "with assert inside" do
293-
group_leader = :erlang.group_leader
294-
295312
try do
296313
capture_io(fn ->
297314
assert false
298315
end)
299316
rescue
300317
error in [ExUnit.AssertionError] ->
301-
"Expected truthy, got false" = error.message
318+
assert error.message == "Expected truthy, got false"
302319
end
303-
304-
# Ensure no leakage on failures
305-
assert group_leader == :erlang.group_leader
306320
end
307321

308322
test "capture :stderr by two processes" do

0 commit comments

Comments
 (0)