Skip to content

Commit 4559989

Browse files
committed
Address review: raise RuntimeError on subsequent empty cycle, simplify test
1 parent 93752db commit 4559989

2 files changed

Lines changed: 28 additions & 27 deletions

File tree

lib/elixir/lib/stream.ex

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,7 +1437,7 @@ defmodule Stream do
14371437
do_cycle(cycle, [], cycle, fun.(element, acc), fun)
14381438

14391439
{_, []} ->
1440-
do_cycle(check_cycle_first_element(cycle), [], cycle, {:cont, acc}, fun)
1440+
do_cycle(check_cycle_subsequent_element(cycle), [], cycle, {:cont, acc}, fun)
14411441
end
14421442
end
14431443

@@ -1446,10 +1446,26 @@ defmodule Stream do
14461446
end
14471447

14481448
defp check_cycle_first_element(reduce) do
1449+
check_cycle_non_empty(
1450+
reduce,
1451+
ArgumentError,
1452+
"cannot cycle over an empty enumerable"
1453+
)
1454+
end
1455+
1456+
defp check_cycle_subsequent_element(reduce) do
1457+
check_cycle_non_empty(
1458+
reduce,
1459+
RuntimeError,
1460+
"cycled enumerable became empty after a previous iteration produced elements"
1461+
)
1462+
end
1463+
1464+
defp check_cycle_non_empty(reduce, exception, message) do
14491465
fn acc ->
14501466
case reduce.(acc) do
14511467
{state, []} when state in [:done, :halted] and elem(acc, 0) != :halt ->
1452-
raise ArgumentError, "cannot cycle over an empty enumerable"
1468+
raise exception, message
14531469

14541470
other ->
14551471
other

lib/elixir/test/elixir/stream_test.exs

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -281,41 +281,26 @@ defmodule StreamTest do
281281
[1, 1, 1, 1, 1]
282282
end
283283

284-
test "cycle/1 raises and does not infinite-loop when a subsequent reduce yields no elements" do
285-
{:ok, agent} = Agent.start_link(fn -> 0 end)
284+
test "cycle/1 raises when a subsequent reduce yields no elements" do
285+
Process.put(:cycle_counter, 0)
286286

287287
stream =
288288
Stream.resource(
289-
fn -> Agent.get_and_update(agent, fn n -> {n, n + 1} end) end,
289+
fn ->
290+
n = Process.get(:cycle_counter)
291+
Process.put(:cycle_counter, n + 1)
292+
n
293+
end,
290294
fn
291295
0 -> {[:a], :done}
292296
_ -> {:halt, :ok}
293297
end,
294298
fn _ -> :ok end
295299
)
296300

297-
parent = self()
298-
299-
task =
300-
Task.async(fn ->
301-
try do
302-
Stream.cycle(stream) |> Enum.take(3)
303-
rescue
304-
e in ArgumentError -> send(parent, {:raised, e.message})
305-
end
306-
end)
307-
308-
try do
309-
case Task.yield(task, 1000) || Task.shutdown(task, :brutal_kill) do
310-
{:ok, _} ->
311-
assert_received {:raised, "cannot cycle over an empty enumerable"}
312-
313-
nil ->
314-
flunk("Stream.cycle/1 entered an unbounded loop with no progress")
315-
end
316-
after
317-
Agent.stop(agent)
318-
end
301+
assert_raise RuntimeError,
302+
"cycled enumerable became empty after a previous iteration produced elements",
303+
fn -> Stream.cycle(stream) |> Enum.take(3) end
319304
end
320305

321306
test "dedup/1 is lazy" do

0 commit comments

Comments
 (0)