Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 48 additions & 15 deletions lib/sentry/test.ex
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,39 @@ defmodule Sentry.Test do
end
end

# Keeps reading until `expected_count` envelopes contain a payload of the
# requested type, or the next envelope does not arrive within `timeout`.
# Other envelopes are discarded: a log or metric posted by a background
# process must not consume a slot the caller reserved for a transaction.
defp collect_typed_envelopes(ref, expected_count, opts, extract_fun) do
timeout = Keyword.get(opts, :timeout, 1000)
do_collect_typed_envelopes(ref, expected_count, timeout, extract_fun, [])
end

defp do_collect_typed_envelopes(_ref, 0, _timeout, _extract_fun, acc), do: Enum.reverse(acc)

defp do_collect_typed_envelopes(ref, remaining, timeout, extract_fun, acc) do
receive do
{:bypass_envelope, ^ref, body} ->
case extract_fun.([decode_envelope!(body)]) do
[] ->
do_collect_typed_envelopes(ref, remaining, timeout, extract_fun, acc)

payloads ->
do_collect_typed_envelopes(
ref,
remaining - 1,
timeout,
extract_fun,
Enum.reverse(payloads, acc)
)
end
after
timeout ->
Enum.reverse(acc)
end
end

@doc """
Extracts event payloads from decoded envelope item lists.
"""
Expand Down Expand Up @@ -753,8 +786,8 @@ defmodule Sentry.Test do
@doc """
Collects events sent through a Bypass envelope collector.

This is a high-level helper combining `collect_envelopes/3` and `extract_events/1`.
Use this instead of `collect_envelopes(ref, count) |> extract_events()`.
Envelopes that do not contain an event are skipped and do not count toward
`expected_count`. `:timeout` is how long to wait for the next envelope.

## Options

Expand All @@ -770,14 +803,14 @@ defmodule Sentry.Test do
@doc since: "13.0.0"
@spec collect_sentry_events(reference(), pos_integer(), keyword()) :: [map()]
def collect_sentry_events(ref, expected_count, opts \\ []) do
collect_envelopes(ref, expected_count, opts) |> extract_events()
collect_typed_envelopes(ref, expected_count, opts, &extract_events/1)
end

@doc """
Collects transactions sent through a Bypass envelope collector.

This is a high-level helper combining `collect_envelopes/3` and `extract_transactions/1`.
Use this instead of `collect_envelopes(ref, count) |> extract_transactions()`.
Envelopes that do not contain a transaction are skipped and do not count
toward `expected_count`. `:timeout` is how long to wait for the next envelope.

## Options

Expand All @@ -793,14 +826,14 @@ defmodule Sentry.Test do
@doc since: "13.0.0"
@spec collect_sentry_transactions(reference(), pos_integer(), keyword()) :: [map()]
def collect_sentry_transactions(ref, expected_count, opts \\ []) do
collect_envelopes(ref, expected_count, opts) |> extract_transactions()
collect_typed_envelopes(ref, expected_count, opts, &extract_transactions/1)
end

@doc """
Collects log items sent through a Bypass envelope collector.

This is a high-level helper combining `collect_envelopes/3` and `extract_log_items/1`.
Use this instead of `collect_envelopes(ref, count) |> extract_log_items()`.
Envelopes that do not contain a log item are skipped and do not count toward
`expected_count`. `:timeout` is how long to wait for the next envelope.

## Options

Expand All @@ -816,14 +849,14 @@ defmodule Sentry.Test do
@doc since: "13.0.0"
@spec collect_sentry_logs(reference(), pos_integer(), keyword()) :: [map()]
def collect_sentry_logs(ref, expected_count, opts \\ []) do
collect_envelopes(ref, expected_count, opts) |> extract_log_items()
collect_typed_envelopes(ref, expected_count, opts, &extract_log_items/1)
end

@doc """
Collects check-in payloads sent through a Bypass envelope collector.

This is a high-level helper combining `collect_envelopes/3` and `extract_check_ins/1`.
Use this instead of manually destructuring `[[{header, body}]]` from `collect_envelopes/3`.
Envelopes that do not contain a check-in are skipped and do not count toward
`expected_count`. `:timeout` is how long to wait for the next envelope.

## Options

Expand All @@ -840,14 +873,14 @@ defmodule Sentry.Test do
@doc since: "13.0.0"
@spec collect_sentry_check_ins(reference(), pos_integer(), keyword()) :: [map()]
def collect_sentry_check_ins(ref, expected_count, opts \\ []) do
collect_envelopes(ref, expected_count, opts) |> extract_check_ins()
collect_typed_envelopes(ref, expected_count, opts, &extract_check_ins/1)
end

@doc """
Collects metric batch payloads sent through a Bypass envelope collector.

This is a high-level helper combining `collect_envelopes/3` and `extract_metric_items/1`.
Use this instead of `collect_envelopes(ref, count) |> extract_metric_items()`.
Envelopes that do not contain a metric batch are skipped and do not count
toward `expected_count`. `:timeout` is how long to wait for the next envelope.

Each returned map has an `"items"` key containing the individual metric maps.

Expand All @@ -867,7 +900,7 @@ defmodule Sentry.Test do
@doc since: "13.0.0"
@spec collect_sentry_metric_items(reference(), pos_integer(), keyword()) :: [map()]
def collect_sentry_metric_items(ref, expected_count, opts \\ []) do
collect_envelopes(ref, expected_count, opts) |> extract_metric_items()
collect_typed_envelopes(ref, expected_count, opts, &extract_metric_items/1)
end

@doc """
Expand Down
4 changes: 2 additions & 2 deletions lib/sentry/test/assertions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -290,13 +290,13 @@ defmodule Sentry.Test.Assertions do
Collects up to `:count` transaction envelopes via `ref` and returns the
first one matching `criteria`. Raises if no transaction matches.

This is shorthand for:
Envelopes that do not contain a transaction are skipped. This is shorthand for:

find_sentry_report!(collect_sentry_transactions(ref, count, timeout: timeout), criteria)

Reserved options in `criteria`:

* `:count` — max number of envelopes to collect (default: `1`)
* `:count` — max number of transaction envelopes to collect (default: `1`)
* `:timeout` — ms to wait for each envelope (default: `#{1000}`)

## Examples
Expand Down
28 changes: 28 additions & 0 deletions test/sentry/test_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,26 @@ defmodule Sentry.TestTest do
)
end

test "typed collectors skip envelopes that do not contain the requested item" do
%{ref: ref} = SentryTest.setup_sentry(collect_envelopes: true)

send(self(), {:bypass_envelope, ref, envelope("log", %{"items" => []})})
send(self(), {:bypass_envelope, ref, envelope("transaction", %{"transaction" => "first"})})
send(self(), {:bypass_envelope, ref, envelope("trace_metric", %{"items" => []})})
send(self(), {:bypass_envelope, ref, envelope("transaction", %{"transaction" => "second"})})

assert [%{"transaction" => "first"}, %{"transaction" => "second"}] =
SentryTest.collect_sentry_transactions(ref, 2, timeout: 50)
end

test "typed collectors return what arrived when the next envelope times out" do
%{ref: ref} = SentryTest.setup_sentry(collect_envelopes: true)

send(self(), {:bypass_envelope, ref, envelope("event", %{"message" => "ignored"})})

assert [] = SentryTest.collect_sentry_transactions(ref, 1, timeout: 50)
end

test "collect_envelopes coexists with :telemetry_processor and config options" do
%{ref: ref, telemetry_processor: name} =
SentryTest.setup_sentry(
Expand Down Expand Up @@ -617,4 +637,12 @@ defmodule Sentry.TestTest do
assert dropped_count == 9
end
end

defp envelope(type, payload) do
json_library = Sentry.Config.json_library()
{:ok, id_line} = Sentry.JSON.encode(%{"event_id" => Sentry.UUID.uuid4_hex()}, json_library)
{:ok, header} = Sentry.JSON.encode(%{"type" => type}, json_library)
{:ok, item} = Sentry.JSON.encode(payload, json_library)
Enum.join([id_line, header, item], "\n")
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ defmodule Sentry.Integrations.Phoenix.ExceptionTest do
original_exception: %RuntimeError{message: "Traced exception"}
)

transaction = find_sentry_transaction!(ref, count: 2, transaction: "process_order")
transaction = find_sentry_transaction!(ref, transaction: "process_order")

assert event.contexts.trace.trace_id == transaction["contexts"]["trace"]["trace_id"]
assert event.contexts.trace.span_id == transaction["contexts"]["trace"]["span_id"]
Expand Down
Loading