bypass
bypass copied to clipboard
Bypass hangs in Bypass.do_verify_expectations/2 when using hackney inside a task with a timeout
I want to test that I'm able to abort a request that takes too long for any reason. I decided to use a task to wrap my request, the task is shut down after a timeout. I use bypass in my test to simulate a request that takes longer than the timeout value.
When using httpc to make the request the test succeeds.
When using hackney to make the request, the test hangs in Bypass.do_verify_expectations/2
.
I use {:hackney, "~> 1.15"}
and {:bypass, "~> 1.0.0", only: :test}
.
Below is the test runner output and the code for the two tests.
1) test bypass with hackney (HackneyBypassTest)
test/peertube_index/hackney_bypass_test.exs:5
** (ExUnit.TimeoutError) on_exit callback timed out after 2000ms. You can change the timeout:
1. per test by setting "@tag timeout: x"
2. per case by setting "@moduletag timeout: x"
3. globally via "ExUnit.start(timeout: x)" configuration
4. or set it to infinity per run by calling "mix test --trace"
(useful when using IEx.pry)
Timeouts are given as integers in milliseconds.
stacktrace:
(stdlib) gen.erl:169: :gen.do_call/4
(elixir) lib/gen_server.ex:921: GenServer.call/3
(bypass) lib/bypass.ex:71: Bypass.do_verify_expectations/2
(ex_unit) lib/ex_unit/on_exit_handler.ex:140: ExUnit.OnExitHandler.exec_callback/1
(ex_unit) lib/ex_unit/on_exit_handler.ex:126: ExUnit.OnExitHandler.on_exit_runner_loop/0
defmodule HackneyBypassTest do
use ExUnit.Case
@tag timeout: 2000
test "bypass with hackney" do
bypass = Bypass.open
reponse_delay = 600
timeout = reponse_delay - 100
Bypass.expect(bypass, "GET", "/", fn conn ->
Process.sleep(reponse_delay)
Plug.Conn.resp(conn, 200, "response data")
end)
request = Task.async(fn ->
:hackney.get("http://localhost:#{bypass.port}", [], "", [follow_redirect: true, with_body: true])
end)
result =
case Task.yield(request, timeout) || Task.shutdown(request) do
{:ok, result} ->
{:ok, result}
nil ->
{:error, :timeout}
end
assert result == {:error, :timeout}
IO.puts "DEBUG: End of test reached"
end
test "bypass with httpc" do
bypass = Bypass.open
reponse_delay = 600
timeout = reponse_delay - 100
Bypass.expect(bypass, "GET", "/", fn conn ->
Process.sleep(reponse_delay)
Plug.Conn.resp(conn, 200, "response data")
end)
request = Task.async(fn ->
:httpc.request(:get, {String.to_charlist("http://localhost:#{bypass.port}"), []}, [], body_format: :binary)
end)
result =
case Task.yield(request, timeout) || Task.shutdown(request) do
{:ok, result} ->
{:ok, result}
nil ->
{:error, :timeout}
end
assert result == {:error, :timeout}
IO.puts "DEBUG: End of test reached"
end
end
I had a similar issue and managed to resolve it by adding a Bypass.pass(bypass)
call just prior to my assertion (or just before the end of the test). It's kind of a work around, I think, but it solved my issue. So, using your example, maybe this would work:
@tag timeout: 2000
test "bypass with hackney" do
...
assert result == {:error, :timeout}
# Clear any expectation of bypassed requests completing
Bypass.pass(bypass)
IO.puts "DEBUG: End of test reached"
end