Skip to content

Commit 1a45a4e

Browse files
committed
test: fix Finch name reuse race and silence reconnect log spam
- fake_http_server: stop the Finch supervisor synchronously instead of killing the registry, so the name slot is free before the next test reuses it; check the supervisor name too so a crashed test can't collide with the next start - capture WSConnection reconnect logs per test module, showing them only when a test fails - unlink contexts before Process.exit(ctx, :kill) — under capture_log the kill signal could race the test process's own exit - silence Logger info chatter in test_helper.exs
1 parent 9d8a8c2 commit 1a45a4e

9 files changed

Lines changed: 74 additions & 6 deletions

test/longbridge/connection/session_test.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
defmodule Longbridge.Connection.SessionTest do
22
use ExUnit.Case, async: true
33

4+
# Refresh-failure tests deliberately exercise the token-refresher
5+
# error paths, which log warnings; capture them and show them only
6+
# when a test fails.
7+
@moduletag capture_log: true
8+
49
alias Longbridge.Config
510
alias Longbridge.Connection.Session
611

test/longbridge/quote_context_test.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
defmodule Longbridge.QuoteContextTest do
22
use ExUnit.Case, async: false
33

4+
# The fake WS server teardown deliberately kills connections, which
5+
# makes WSConnection log reconnect churn; capture it and show it only
6+
# when a test fails.
7+
@moduletag capture_log: true
8+
49
alias Longbridge.{Config, Protocol, QuoteContext, WSConnection}
510
alias Longbridge.Control.V1, as: Ctrl
611
alias Longbridge.Protocol.Header

test/longbridge/trade_context_extra_test.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
defmodule Longbridge.TradeContextExtraTest do
22
use ExUnit.Case, async: false
33

4+
# Subscribe/unsubscribe tests run a real WS connection against fake
5+
# servers and deliberately exercise disconnect paths; capture the
6+
# WSConnection logs and show them only when a test fails.
7+
@moduletag capture_log: true
8+
49
import Bitwise
510

611
alias Longbridge.{Config, Protocol, TradeContext}
@@ -332,6 +337,7 @@ defmodule Longbridge.TradeContextExtraTest do
332337
{:ok, ctx} = TradeContext.start_link(config, skip_connection: true)
333338
Process.sleep(50)
334339
assert {:ok, nil, nil} = TradeContext.session(ctx)
340+
Process.unlink(ctx)
335341
Process.exit(ctx, :kill)
336342
end
337343
end
@@ -425,6 +431,7 @@ defmodule Longbridge.TradeContextExtraTest do
425431
)
426432

427433
assert_receive :got, 1_000
434+
Process.unlink(ctx)
428435
Process.exit(ctx, :kill)
429436
end
430437
end
@@ -443,6 +450,7 @@ defmodule Longbridge.TradeContextExtraTest do
443450
# that falls into `_ -> :ok`.
444451
send(ctx, {:longbridge, ctx, {:push, 99, "not-json-at-all"}})
445452
refute_receive {:default, _}, 500
453+
Process.unlink(ctx)
446454
Process.exit(ctx, :kill)
447455
end
448456
end

test/longbridge/trade_context_test.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
defmodule Longbridge.TradeContextTest do
22
use ExUnit.Case, async: false
33

4+
# Reconnect tests deliberately break sockets and watch the retry
5+
# backoff, flooding the output with WSConnection logs; capture them
6+
# and show them only when a test fails.
7+
@moduletag capture_log: true
8+
49
alias Longbridge.{Config, TradeContext}
510

611
# ── Fake HTTP server ─────────────────────────────────────

test/longbridge/trade_push_test.exs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ defmodule Longbridge.TradePushTest do
33

44
alias Longbridge.{Config, TradeContext}
55

6+
# Push tests run a real WS connection and exercise disconnect paths;
7+
# capture the WSConnection logs and show them only on failure.
8+
@moduletag capture_log: true
69
@moduletag :trade_push
710

811
defp encode_notification(notification) do
@@ -53,6 +56,7 @@ defmodule Longbridge.TradePushTest do
5356
assert event["status"] == "PartiallyFilled"
5457
assert event["filled_qty"] == "50"
5558

59+
Process.unlink(ctx)
5660
Process.exit(ctx, :kill)
5761
end
5862

@@ -86,6 +90,7 @@ defmodule Longbridge.TradePushTest do
8690
# Should NOT receive any event (empty topic)
8791
refute_receive {:event, _}, 500
8892

93+
Process.unlink(ctx)
8994
Process.exit(ctx, :kill)
9095
end
9196

@@ -134,6 +139,7 @@ defmodule Longbridge.TradePushTest do
134139
assert event["executed_quantity"] == "200"
135140
assert event["executed_price"] == "50.5"
136141

142+
Process.unlink(ctx)
137143
Process.exit(ctx, :kill)
138144
end
139145

@@ -166,6 +172,7 @@ defmodule Longbridge.TradePushTest do
166172

167173
refute_receive {:event, _}, 500
168174

175+
Process.unlink(ctx)
169176
Process.exit(ctx, :kill)
170177
end
171178
end

test/longbridge/ws_connection_extra_test.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
defmodule Longbridge.WSConnectionExtraTest do
22
use ExUnit.Case, async: false
33

4+
# Connect-failure tests deliberately point at dead servers and watch
5+
# the reconnect backoff, flooding the output with WSConnection logs;
6+
# capture them and show them only when a test fails.
7+
@moduletag capture_log: true
8+
49
alias Longbridge.{Config, Protocol, WSConnection}
510
alias Longbridge.Control.V1, as: Ctrl
611
alias Longbridge.Protocol.Header

test/longbridge/ws_connection_test.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
defmodule Longbridge.WSConnectionTest do
22
use ExUnit.Case, async: false
33

4+
# Connection tests deliberately drop sockets and time out auth to
5+
# exercise reconnect paths; capture the WSConnection logs and show
6+
# them only when a test fails.
7+
@moduletag capture_log: true
8+
49
alias Longbridge.{Config, Protocol, WSConnection}
510
alias Longbridge.Control.V1, as: Ctrl
611
alias Longbridge.Protocol.Header

test/support/fake_http_server.ex

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,20 @@ defmodule Longbridge.TestSupport.FakeHTTPServer do
7676

7777
try do
7878
handler.(conn)
79-
rescue
80-
exception ->
79+
catch
80+
kind, reason ->
81+
exception = Exception.normalize(kind, reason, __STACKTRACE__)
8182
HandlerStore.set(store, {:error, exception, __STACKTRACE__})
8283
Conn.send_resp(conn, 500, "test handler crashed")
8384
end
8485
end
8586

87+
# Pre-created Finch names, one per concurrently-running test server.
88+
# Tests run sequentially, so this pool never runs dry; the whereis
89+
# check reuses a slot as soon as the previous server's Finch is gone.
90+
@finch_names for i <- 0..31, do: :"longbridge_test_finch_#{i}"
91+
@finch_supervisor_names for name <- @finch_names, into: %{}, do: {name, :"#{name}.Supervisor"}
92+
8693
@doc """
8794
Starts a server AND a fresh Finch pool whose name is stored in the
8895
test process's dictionary so `HTTPClient` auto-picks it up.
@@ -94,8 +101,7 @@ defmodule Longbridge.TestSupport.FakeHTTPServer do
94101
finch: atom()
95102
}
96103
def start_with_finch(handler) when is_function(handler, 1) do
97-
# credo:disable-for-next-line Credo.Check.Warning.UnsafeToAtom
98-
finch_name = String.to_atom("longbridge_test_finch_#{System.unique_integer([:positive])}")
104+
finch_name = finch_name()
99105

100106
{:ok, _pid} =
101107
Finch.start_link(
@@ -126,8 +132,14 @@ defmodule Longbridge.TestSupport.FakeHTTPServer do
126132
if Process.alive?(sup), do: Supervisor.stop(sup, :shutdown)
127133
if Process.alive?(store), do: Agent.stop(store)
128134

129-
if finch_pid = Process.whereis(finch_name),
130-
do: Process.exit(finch_pid, :kill)
135+
# Unlink before stopping: the Finch supervisor was started from the
136+
# test process, and a linked caller dies with the supervisor's
137+
# :shutdown exit signal. Stopping synchronously guarantees the name
138+
# slot is free before the next test reuses it.
139+
if finch_sup = Process.whereis(@finch_supervisor_names[finch_name]) do
140+
Process.unlink(finch_sup)
141+
Supervisor.stop(finch_sup, :shutdown)
142+
end
131143

132144
:ok
133145
end
@@ -193,6 +205,16 @@ defmodule Longbridge.TestSupport.FakeHTTPServer do
193205

194206
# ── Internal ──────────────────────────────────────────────
195207

208+
defp finch_name do
209+
Enum.find(@finch_names, fn name ->
210+
# The registry (registered under `name`) and the supervisor
211+
# (registered under `name.Supervisor`) are the last processes to
212+
# unregister during teardown; check both so a crashed test that
213+
# skipped `stop_with_finch/1` doesn't collide with the next start.
214+
is_nil(Process.whereis(name)) and is_nil(Process.whereis(@finch_supervisor_names[name]))
215+
end) || raise "all #{length(@finch_names)} fake Finch names are in use"
216+
end
217+
196218
defp free_port do
197219
{:ok, listen} = :gen_tcp.listen(0, [:binary, active: false, reuseaddr: true])
198220
{:ok, port} = :inet.port(listen)

test/test_helper.exs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1+
# The WS reconnect and token-refresh failure-path tests deliberately
2+
# trigger library log lines, flooding `mix test` output. Silence the
3+
# info/debug chatter here (capture_log-based tests still work because
4+
# warning messages pass the level check).
5+
Logger.configure(level: :warning)
6+
17
ExUnit.start()

0 commit comments

Comments
 (0)