Skip to content

Commit 18525da

Browse files
authored
fix: isolate config in tests (#970)
1 parent 37d3d62 commit 18525da

4 files changed

Lines changed: 94 additions & 32 deletions

File tree

lib/sentry/config.ex

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,16 @@ defmodule Sentry.Config do
862862

863863
@compile {:inline, fetch!: 1}
864864
defp fetch!(key) do
865-
:persistent_term.get({:sentry_config, key})
865+
# Check process dictionary first for test-specific config overrides.
866+
# This allows tests to use put_test_config/1 for isolated configuration
867+
# without affecting other tests, even when running async: true.
868+
case Process.get({:sentry_test_config, key}, :__not_set__) do
869+
:__not_set__ ->
870+
:persistent_term.get({:sentry_config, key})
871+
872+
value ->
873+
value
874+
end
866875
rescue
867876
ArgumentError ->
868877
raise """
@@ -874,7 +883,16 @@ defmodule Sentry.Config do
874883

875884
@compile {:inline, get: 1}
876885
defp get(key) do
877-
:persistent_term.get({:sentry_config, key}, nil)
886+
# Check process dictionary first for test-specific config overrides.
887+
# This allows tests to use put_test_config/1 for isolated configuration
888+
# without affecting other tests, even when running async: true.
889+
case Process.get({:sentry_test_config, key}, :__not_set__) do
890+
:__not_set__ ->
891+
:persistent_term.get({:sentry_config, key}, nil)
892+
893+
value ->
894+
value
895+
end
878896
end
879897

880898
def __validate_path__(nil), do: {:ok, nil}

test/support/case.ex

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
11
defmodule Sentry.Case do
22
# We use this module mostly to add some additional checks before and after tests, especially
3-
# related to configuration. Configuration is a bit finicky due to the extensive use of
4-
# global state (:persistent_term), so better safe than sorry here.
3+
# related to configuration. Configuration is isolated per-process via the process dictionary,
4+
# so tests using put_test_config/1 will have their own view without affecting other tests.
55

66
use ExUnit.CaseTemplate
77

8-
import Sentry.TestHelpers
9-
108
setup context do
11-
config_before = all_config()
12-
13-
on_exit(fn ->
14-
assert config_before == all_config()
15-
end)
16-
179
# Start a fresh RateLimiter for each test with unique names for isolation.
1810
setup_rate_limiter()
1911

test/support/test_helpers.ex

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ defmodule Sentry.TestHelpers do
1818

1919
@spec put_test_config(keyword()) :: :ok
2020
def put_test_config(config) when is_list(config) do
21-
all_original_config = all_config()
22-
21+
# Store original values from both process dictionary and :persistent_term
22+
# We validate each key individually like Sentry.put_config/2 does
2323
original_config =
2424
for {key, val} <- config do
2525
renamed_key =
@@ -28,19 +28,45 @@ defmodule Sentry.TestHelpers do
2828
other -> other
2929
end
3030

31-
current_val = :persistent_term.get({:sentry_config, renamed_key}, :__not_set__)
32-
Sentry.put_config(renamed_key, val)
33-
{renamed_key, current_val}
31+
# Validate this single key-value pair (this also transforms values like DSN strings)
32+
validated_config = Sentry.Config.validate!([{renamed_key, val}])
33+
validated_val = Keyword.fetch!(validated_config, renamed_key)
34+
35+
# Store original values
36+
current_process_val = Process.get({:sentry_test_config, renamed_key}, :__not_set__)
37+
current_persistent_val = :persistent_term.get({:sentry_config, renamed_key}, :__not_set__)
38+
39+
# Set in both locations:
40+
# - Process dictionary for process-local isolation
41+
# - :persistent_term so spawned processes (like sender pool workers) can see it
42+
Process.put({:sentry_test_config, renamed_key}, validated_val)
43+
:persistent_term.put({:sentry_config, renamed_key}, validated_val)
44+
45+
{renamed_key, current_process_val, current_persistent_val}
3446
end
3547

48+
# Register cleanup to restore original values in both locations
3649
ExUnit.Callbacks.on_exit(fn ->
3750
Enum.each(original_config, fn
38-
{key, :__not_set__} -> :persistent_term.erase({:sentry_config, key})
39-
{key, original_val} -> :persistent_term.put({:sentry_config, key}, original_val)
40-
end)
51+
{key, :__not_set__, :__not_set__} ->
52+
Process.delete({:sentry_test_config, key})
53+
:persistent_term.erase({:sentry_config, key})
4154

42-
assert all_original_config == all_config()
55+
{key, :__not_set__, persistent_val} ->
56+
Process.delete({:sentry_test_config, key})
57+
:persistent_term.put({:sentry_config, key}, persistent_val)
58+
59+
{key, process_val, :__not_set__} ->
60+
Process.put({:sentry_test_config, key}, process_val)
61+
:persistent_term.erase({:sentry_config, key})
62+
63+
{key, process_val, persistent_val} ->
64+
Process.put({:sentry_test_config, key}, process_val)
65+
:persistent_term.put({:sentry_config, key}, persistent_val)
66+
end)
4367
end)
68+
69+
:ok
4470
end
4571

4672
@spec set_mix_shell(module()) :: :ok
@@ -101,9 +127,9 @@ defmodule Sentry.TestHelpers do
101127
items
102128
|> Enum.chunk_every(2)
103129
|> Enum.flat_map(fn
104-
[header, item] ->[{decode!(header), decode!(item)}]
130+
[header, item] -> [{decode!(header), decode!(item)}]
105131

106-
[""] ->[]
132+
[""] -> []
107133
end)
108134
end
109135
end

test_integrations/phoenix_app/test/support/test_helpers.ex

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ defmodule Sentry.TestHelpers do
1515

1616
@spec put_test_config(keyword()) :: :ok
1717
def put_test_config(config) when is_list(config) do
18-
all_original_config = all_config()
19-
18+
# Store original values from both process dictionary and :persistent_term
19+
# We validate each key individually like Sentry.put_config/2 does
2020
original_config =
2121
for {key, val} <- config do
2222
renamed_key =
@@ -25,19 +25,45 @@ defmodule Sentry.TestHelpers do
2525
other -> other
2626
end
2727

28-
current_val = :persistent_term.get({:sentry_config, renamed_key}, :__not_set__)
29-
Sentry.put_config(renamed_key, val)
30-
{renamed_key, current_val}
28+
# Validate this single key-value pair (this also transforms values like DSN strings)
29+
validated_config = Sentry.Config.validate!([{renamed_key, val}])
30+
validated_val = Keyword.fetch!(validated_config, renamed_key)
31+
32+
# Store original values
33+
current_process_val = Process.get({:sentry_test_config, renamed_key}, :__not_set__)
34+
current_persistent_val = :persistent_term.get({:sentry_config, renamed_key}, :__not_set__)
35+
36+
# Set in both locations:
37+
# - Process dictionary for process-local isolation
38+
# - :persistent_term so spawned processes (like sender pool workers) can see it
39+
Process.put({:sentry_test_config, renamed_key}, validated_val)
40+
:persistent_term.put({:sentry_config, renamed_key}, validated_val)
41+
42+
{renamed_key, current_process_val, current_persistent_val}
3143
end
3244

45+
# Register cleanup to restore original values in both locations
3346
ExUnit.Callbacks.on_exit(fn ->
3447
Enum.each(original_config, fn
35-
{key, :__not_set__} -> :persistent_term.erase({:sentry_config, key})
36-
{key, original_val} -> :persistent_term.put({:sentry_config, key}, original_val)
37-
end)
48+
{key, :__not_set__, :__not_set__} ->
49+
Process.delete({:sentry_test_config, key})
50+
:persistent_term.erase({:sentry_config, key})
3851

39-
assert all_original_config == all_config()
52+
{key, :__not_set__, persistent_val} ->
53+
Process.delete({:sentry_test_config, key})
54+
:persistent_term.put({:sentry_config, key}, persistent_val)
55+
56+
{key, process_val, :__not_set__} ->
57+
Process.put({:sentry_test_config, key}, process_val)
58+
:persistent_term.erase({:sentry_config, key})
59+
60+
{key, process_val, persistent_val} ->
61+
Process.put({:sentry_test_config, key}, process_val)
62+
:persistent_term.put({:sentry_config, key}, persistent_val)
63+
end)
4064
end)
65+
66+
:ok
4167
end
4268

4369
@spec set_mix_shell(module()) :: :ok

0 commit comments

Comments
 (0)