Skip to content

Commit 8d4ffae

Browse files
committed
feat(tests): support for telemetry_processor opt in setup_sentry
1 parent fde3f50 commit 8d4ffae

2 files changed

Lines changed: 92 additions & 28 deletions

File tree

lib/sentry/test.ex

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ defmodule Sentry.Test do
6666
Opens a Bypass on a random port, configures the DSN to point to it,
6767
wires up `before_send` / `before_send_log` callbacks to capture structs
6868
in an isolated ETS table, and starts a per-test `Sentry.TelemetryProcessor`
69-
(via `setup_telemetry_processor/0`) so that assertions work for events
69+
(via `setup_telemetry_processor/1`) so that assertions work for events
7070
that travel through the TelemetryProcessor pipeline (logs, metrics, or
7171
`send_result: :none`).
7272
@@ -80,6 +80,12 @@ defmodule Sentry.Test do
8080
Any extra Sentry config options (e.g., `dedup_events: false`, `traces_sample_rate: 1.0`)
8181
will be forwarded to the test config.
8282
83+
The reserved `:telemetry_processor` option is *not* forwarded to the test
84+
config. Instead, its value (a keyword list) is passed to the per-test
85+
`Sentry.TelemetryProcessor` (e.g. `buffer_configs`, `buffer_capacities`,
86+
`scheduler_weights`, `transport_capacity`). This replaces the need to
87+
manually `stop_supervised!/1` and re-`start_supervised!/2` the processor.
88+
8389
## Examples
8490
8591
setup do
@@ -90,19 +96,12 @@ defmodule Sentry.Test do
9096
Sentry.Test.setup_sentry(dedup_events: false)
9197
end
9298
93-
Replacing the auto-started processor with a custom-configured one:
99+
Configuring the per-test processor (e.g. a smaller log batch size):
94100
95101
setup do
96-
%{telemetry_processor: name} = ctx = Sentry.Test.setup_sentry()
97-
stop_supervised!(name)
98-
99-
start_supervised!(
100-
{Sentry.TelemetryProcessor,
101-
name: name, buffer_configs: %{log: %{batch_size: 1}}},
102-
id: name
102+
Sentry.Test.setup_sentry(
103+
telemetry_processor: [buffer_configs: %{log: %{batch_size: 1}}]
103104
)
104-
105-
ctx
106105
end
107106
108107
"""
@@ -112,6 +111,8 @@ defmodule Sentry.Test do
112111
ensure_bypass_loaded!()
113112
ensure_nimble_ownership_loaded!()
114113

114+
{tp_opts, extra_config} = Keyword.pop(extra_config, :telemetry_processor, [])
115+
115116
# Open a per-test Bypass and stub the envelope endpoint
116117
bypass = Bypass.open()
117118

@@ -121,7 +122,7 @@ defmodule Sentry.Test do
121122

122123
# Start a per-test TelemetryProcessor before setup_collector/1 so that
123124
# the collector wires this test's scheduler into its registry.
124-
processor_name = setup_telemetry_processor()
125+
processor_name = setup_telemetry_processor(tp_opts)
125126

126127
# Set up collector with DSN pointing to this test's Bypass
127128
bypass_config = [dsn: "http://public:secret@localhost:#{bypass.port}/1"]
@@ -154,34 +155,69 @@ defmodule Sentry.Test do
154155
Must be called from within an ExUnit test because it uses
155156
`ExUnit.Callbacks.start_supervised!/2` for automatic cleanup.
156157
157-
If a per-test processor is already registered for this test (for example
158-
when using `Sentry.Case`), this function is idempotent and returns the
159-
existing processor name instead of starting a new one.
158+
## Options
159+
160+
`tp_opts` is a keyword list forwarded to the per-test
161+
`Sentry.TelemetryProcessor` child spec (e.g. `buffer_configs`,
162+
`buffer_capacities`, `scheduler_weights`, `transport_capacity`).
163+
164+
Idempotency depends on `tp_opts`:
165+
166+
* with no `tp_opts`, an already-registered live processor (for example
167+
one started by `Sentry.Case`) is reused and its name returned;
168+
* with `tp_opts`, an already-registered live processor is stopped and
169+
restarted under the same name with the given options, so callers no
170+
longer need to `stop_supervised!/1` + `start_supervised!/2` manually.
160171
"""
161172
@doc since: "13.0.0"
162-
@spec setup_telemetry_processor() :: atom()
163-
def setup_telemetry_processor do
173+
@spec setup_telemetry_processor(keyword()) :: atom()
174+
def setup_telemetry_processor(tp_opts \\ []) do
164175
case Process.get(:sentry_telemetry_processor) do
165176
name when is_atom(name) and not is_nil(name) ->
166-
if processor_alive?(name), do: name, else: start_telemetry_processor()
177+
cond do
178+
not processor_alive?(name) -> start_telemetry_processor(tp_opts)
179+
tp_opts == [] -> name
180+
true -> restart_telemetry_processor(name, tp_opts)
181+
end
167182

168183
_ ->
169-
start_telemetry_processor()
184+
start_telemetry_processor(tp_opts)
170185
end
171186
end
172187

173-
defp start_telemetry_processor do
188+
defp start_telemetry_processor(tp_opts) do
174189
uid = System.unique_integer([:positive])
175190
processor_name = :"test_telemetry_processor_#{uid}"
176191

177-
ExUnit.Callbacks.start_supervised!(
178-
{Sentry.TelemetryProcessor,
179-
name: processor_name, processor_resolver: &Sentry.Test.Registry.lookup_processor_for/1},
180-
id: processor_name
181-
)
192+
start_processor_child(processor_name, tp_opts)
182193

194+
# Must be set before tag_scheduler/1, which reads
195+
# `:sentry_telemetry_processor` from this process's dictionary via
196+
# `fetch_owner_processor/1`. Tagging would otherwise be a silent no-op.
183197
Process.put(:sentry_telemetry_processor, processor_name)
184198

199+
tag_scheduler(processor_name)
200+
processor_name
201+
end
202+
203+
defp restart_telemetry_processor(name, tp_opts) do
204+
ExUnit.Callbacks.stop_supervised!(name)
205+
start_processor_child(name, tp_opts)
206+
# The process dictionary already holds `name`; the new scheduler pid
207+
# must be re-tagged since the old one died with the old supervisor.
208+
tag_scheduler(name)
209+
name
210+
end
211+
212+
defp start_processor_child(name, tp_opts) do
213+
opts =
214+
[name: name, processor_resolver: &Sentry.Test.Registry.lookup_processor_for/1]
215+
|> Keyword.merge(tp_opts)
216+
217+
ExUnit.Callbacks.start_supervised!({Sentry.TelemetryProcessor, opts}, id: name)
218+
end
219+
220+
defp tag_scheduler(processor_name) do
185221
scheduler_pid = Sentry.TelemetryProcessor.get_scheduler(processor_name)
186222

187223
if scheduler_pid do
@@ -192,7 +228,7 @@ defmodule Sentry.Test do
192228
tag_processor_for_allowed_pid(self(), scheduler_pid)
193229
end
194230

195-
processor_name
231+
:ok
196232
end
197233

198234
defp processor_alive?(name) do
@@ -378,9 +414,9 @@ defmodule Sentry.Test do
378414
# callback drops the event.
379415
#
380416
# The owner's processor name is looked up from its process
381-
# dictionary; tests set it in `setup_telemetry_processor/0`. If the
417+
# dictionary; tests set it in `setup_telemetry_processor/1`. If the
382418
# owner has no per-test processor (e.g. legacy
383-
# `start_collecting/1` without `setup_telemetry_processor/0`), the
419+
# `start_collecting/1` without `setup_telemetry_processor/1`), the
384420
# tag is skipped and the buffered event still falls back to the
385421
# global processor — the same behaviour as before this change.
386422
defp tag_processor_for_allowed_pid(owner_pid, allowed_pid) do

test/sentry/test_test.exs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,34 @@ defmodule Sentry.TestTest do
2727

2828
assert Sentry.Test.Registry.lookup_processor_for(scheduler_pid) == processor_name
2929
end
30+
31+
test ":telemetry_processor option configures the per-test processor" do
32+
%{telemetry_processor: name} =
33+
SentryTest.setup_sentry(telemetry_processor: [buffer_configs: %{log: %{batch_size: 1}}])
34+
35+
log_buffer = Sentry.TelemetryProcessor.get_buffer(name, :log)
36+
37+
assert :sys.get_state(log_buffer).batch_size == 1
38+
end
39+
40+
test ":telemetry_processor option coexists with sibling config options" do
41+
SentryTest.setup_sentry(
42+
dedup_events: false,
43+
telemetry_processor: [buffer_configs: %{log: %{batch_size: 1}}]
44+
)
45+
46+
assert Sentry.Config.dedup_events?() == false
47+
end
48+
49+
test "re-tags the scheduler after restarting with :telemetry_processor opts" do
50+
%{telemetry_processor: name} =
51+
SentryTest.setup_sentry(telemetry_processor: [buffer_configs: %{log: %{batch_size: 1}}])
52+
53+
scheduler_pid = Sentry.TelemetryProcessor.get_scheduler(name)
54+
55+
assert is_pid(scheduler_pid)
56+
assert Sentry.Test.Registry.lookup_processor_for(scheduler_pid) == name
57+
end
3058
end
3159

3260
describe "start_collecting_sentry_reports/0" do

0 commit comments

Comments
 (0)