Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
6ca13f1
test: Add active_job spec harness scaffolding
solnic Apr 27, 2026
ede573a
fixup: simplify
solnic Apr 27, 2026
291aa2c
test: Add spec for Sentry + ActiveJob on the test adapter
solnic Apr 27, 2026
5d16eac
test: add shared examples for error capturing and retry semantics in …
solnic Apr 27, 2026
d8fe935
test: add shared examples for ActiveJob discard semantics
solnic Apr 28, 2026
c5d2927
test(active_job): add error_context shared example
solnic Apr 28, 2026
9662c9b
test(active_job): add scope_isolation shared example
solnic Apr 28, 2026
b46686e
test(active_job): add rescue_from_handling shared example
solnic Apr 28, 2026
5b7b097
test(active_job): add adapter_skipping shared example
solnic Apr 28, 2026
af10cc4
test(active_job): add argument_serialization shared example
solnic Apr 28, 2026
5fe607c
test(active_job): cover active_job_report_on_retry_error flag
solnic Apr 28, 2026
4ab06b2
test(active_job): add deserialization_error shared example
solnic Apr 28, 2026
e268ceb
test(active_job): add consumer_transaction tracing shared example
solnic Apr 28, 2026
e4f2c5c
test(active_job): add scheduled_jobs shared example
solnic Apr 28, 2026
120549c
test(active_job): add cron_check_ins shared example
solnic Apr 28, 2026
103eb4f
test(active_job): add structured_logging shared example
solnic Apr 28, 2026
9e15b08
style(active_job): fix space inside empty proc braces
solnic Apr 28, 2026
eaabdbf
test(active_job): remove monolithic activejob_spec.rb
solnic Apr 28, 2026
1df034e
test(active_job): update spec pattern to include active_job specs
solnic Apr 28, 2026
1a9f285
test(active_job): enhance error context spec with variable tracking
solnic Apr 28, 2026
a02d99f
test(active_job): add shared example for preserving job return value
solnic Apr 28, 2026
1c7dd78
test(active_job): enhance serialization spec for ActiveSupport::TimeW…
solnic Apr 28, 2026
3916d0f
test(active_job): add spec for ActiveJob behavior without Sentry init…
solnic Apr 28, 2026
ef05dba
test(active_job): refactor Sentry configuration setup and add db span…
solnic Apr 28, 2026
8e9e231
test(active_job): enhance cron check-ins specs with slugs and monitor…
solnic Apr 28, 2026
7b149f5
fixup(active_job): update drain method for old rubies
solnic Apr 28, 2026
f72ae06
fix(active_job): remove nil from global serializers set during app re…
solnic Apr 28, 2026
1ba54c5
test(active_job): skip range serialization examples on Rails < 7.0
solnic Apr 28, 2026
459136f
test(active_job): skip scheduled_at example on Rails < 6.1 (no perfor…
solnic Apr 28, 2026
71afe16
test(active_job): fix drain on Rails 5.2 (perform_enqueued_jobs requi…
solnic Apr 28, 2026
5948fef
test(active_job): skip retry semantics examples on Rails < 6.0
solnic Apr 28, 2026
7ebf399
test(active_job): compact DeserializationError backtrace to avoid JRu…
solnic Apr 28, 2026
146e48b
test(active_job): use def instead of define_method to avoid JRuby Dyn…
solnic Apr 28, 2026
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
2 changes: 1 addition & 1 deletion sentry-rails/Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ require "bundler/gem_tasks"
require_relative "../lib/sentry/test/rake_tasks"

Sentry::Test::RakeTasks.define_spec_tasks(
spec_pattern: "spec/sentry/**/*_spec.rb",
spec_pattern: "{spec/sentry,spec/active_job}/**/*_spec.rb",
spec_rspec_opts: "--order rand --format progress",
isolated_specs_pattern: "spec/isolated/**/*_spec.rb",
isolated_rspec_opts: "--format progress"
Expand Down
24 changes: 24 additions & 0 deletions sentry-rails/spec/active_job/shared_examples/adapter_skipping.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

RSpec.shared_examples "an ActiveJob backend that respects skippable_job_adapters" do
let(:failing_job) do
job_fixture do
def perform
raise "boom from failing_job spec"
end
end
end

it "captures no events when the adapter is in skippable_job_adapters" do
Sentry.configuration.rails.skippable_job_adapters = [
failing_job.queue_adapter.class.to_s
]

expect do
failing_job.perform_later
drain
end.to raise_error(RuntimeError, /boom from failing_job spec/)

expect(sentry_events).to be_empty
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# frozen_string_literal: true

RSpec.shared_examples "an ActiveJob backend that serializes complex arguments" do
let(:failing_job) do
job_fixture do
def perform(*_args, **_kwargs)
raise "boom from argument_serialization spec"
end
end
end

def event_arguments
last_sentry_event.extra[:arguments]
end

it "serializes ActiveRecord arguments via global id" do
post = Post.create!

expect do
failing_job.perform_later(post)
drain
end.to raise_error(RuntimeError, /boom from argument_serialization spec/)

expect(event_arguments).to eq([post.to_global_id.to_s])
end

it "recursively serializes nested hashes containing global ids" do
post = Post.create!

expect do
failing_job.perform_later(wrapper: { post: post })
drain
end.to raise_error(RuntimeError, /boom from argument_serialization spec/)

expect(event_arguments).to eq([{ wrapper: { post: post.to_global_id.to_s } }])
end

it "expands integer ranges into arrays", skip: RAILS_VERSION < 7.0 do
expect do
failing_job.perform_later(1..3)
drain
end.to raise_error(RuntimeError, /boom from argument_serialization spec/)

expect(event_arguments).to eq([[1, 2, 3]])
end

it "stringifies ActiveSupport::TimeWithZone ranges preserving the boundary operator", skip: RAILS_VERSION < 7.0 do
range = 1.day.ago...Time.zone.now

expect do
failing_job.perform_later(range)
drain
end.to raise_error(RuntimeError, /boom from argument_serialization spec/)

serialized = event_arguments.first
expect(serialized).to be_a(String)
expect(serialized).to eq("#{range.first}...#{range.last}")
end

it "falls back to the original argument when to_global_id raises" do
post = Post.create!

problematic_job = job_fixture do
def perform(passed_post)
def passed_post.to_global_id
raise "intentional"
end

raise "boom from argument_serialization spec"
end
end

expect do
problematic_job.perform_later(post)
drain
end.to raise_error(RuntimeError, /boom from argument_serialization spec/)

expect(event_arguments).to eq([post])
end

it "passes through objects that do not respond to to_global_id unchanged" do
mod = Module.new

module_job = job_fixture do
def perform(_mod)
raise "boom from argument_serialization spec"
end
end

expect do
module_job.perform_now(mod)
end.to raise_error(RuntimeError, /boom from argument_serialization spec/)

expect(event_arguments).to eq([mod])
end
end
72 changes: 72 additions & 0 deletions sentry-rails/spec/active_job/shared_examples/cron_check_ins.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# frozen_string_literal: true

RSpec.shared_examples "an ActiveJob backend that emits cron check-ins for monitor jobs" do
let(:cron_job) do
job_fixture do
include Sentry::Cron::MonitorCheckIns
sentry_monitor_check_ins slug: "test-cron-ok-job"

def perform
"ok"
end
end
end

let(:failing_cron_job) do
job_fixture do
include Sentry::Cron::MonitorCheckIns
sentry_monitor_check_ins(
slug: "test-cron-fail-job",
monitor_config: Sentry::Cron::MonitorConfig.from_crontab("5 * * * *")
)

def perform
raise "boom from failing_cron_job spec"
end
end
end

it "emits in_progress and ok check-ins with correct slug for a successful job" do
cron_job.perform_later
drain

check_ins = sentry_events.select { |e| e.is_a?(Sentry::CheckInEvent) }
expect(check_ins.size).to eq(2)

first, second = check_ins
expect(first.to_h).to include(
type: "check_in",
status: :in_progress,
monitor_slug: "test-cron-ok-job"
)
expect(second.to_h).to include(
type: "check_in",
status: :ok,
check_in_id: first.check_in_id,
monitor_slug: "test-cron-ok-job"
)
expect(second.to_h).to have_key(:duration)
end

it "returns the job's perform value through the cron mixin" do
result = cron_job.perform_now
expect(result).to eq("ok")
end

it "emits in_progress and error check-ins with monitor_config for a failing job" do
expect do
failing_cron_job.perform_later
drain
end.to raise_error(RuntimeError, /boom from failing_cron_job spec/)

check_ins = sentry_events.select { |e| e.is_a?(Sentry::CheckInEvent) }
error_events = sentry_events.select { |e| e.is_a?(Sentry::ErrorEvent) }

expect(check_ins.map { |e| e.to_h[:status] }).to eq(%i[in_progress error])
expect(check_ins.map { |e| e.to_h[:monitor_slug] }).to all(eq("test-cron-fail-job"))
expect(check_ins.map { |e| e.to_h[:monitor_config] }).to all(include(
schedule: { type: :crontab, value: "5 * * * *" }
))
expect(error_events.size).to eq(1)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

RSpec.shared_examples "an ActiveJob backend that unwraps DeserializationError" do
let(:deserialization_error_job) do
job_fixture do
def perform
1 / 0
rescue
err = ActiveJob::DeserializationError.new
# DeserializationError#initialize copies $!.backtrace, which on JRuby can
# contain nil elements for frames defined in anonymous Class.new blocks.
# Compact the backtrace to avoid a JRuby NPE in traceRaise at shutdown.
err.set_backtrace(Array(err.backtrace).compact)
raise err
end
end
end

it "captures the root cause when wrapped in ActiveJob::DeserializationError" do
expect do
deserialization_error_job.perform_later
drain
end.to raise_error(ActiveJob::DeserializationError)

expect(sentry_events.size).to eq(1)

types = extract_sentry_exceptions(sentry_events.last).map(&:type)
expect(types.first).to eq("ZeroDivisionError")
end
end
22 changes: 22 additions & 0 deletions sentry-rails/spec/active_job/shared_examples/discard_semantics.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

RSpec.shared_examples "an ActiveJob backend that respects discard semantics" do
let(:discardable_job) do
job_fixture do
discard_on StandardError

def perform
raise "boom from discardable_job spec"
end
end
end

it "does not capture an event when the job is discarded" do
expect do
discardable_job.perform_later
drain
end.not_to raise_error

expect(sentry_events).to be_empty
end
end
23 changes: 23 additions & 0 deletions sentry-rails/spec/active_job/shared_examples/error_capture.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

RSpec.shared_examples "an ActiveJob backend that captures errors" do
let(:failing_job) do
job_fixture do
def perform
raise "boom from failing_job spec"
end
end
end

it "captures an error event when a job fails" do
expect do
failing_job.perform_later
drain
end.to raise_error(RuntimeError, /boom from failing_job spec/)

expect(sentry_events.size).to eq(1)

exception = extract_sentry_exceptions(sentry_events.last).first
expect(exception.value).to match(/boom from failing_job spec/)
end
end
39 changes: 39 additions & 0 deletions sentry-rails/spec/active_job/shared_examples/error_context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

RSpec.shared_examples "an ActiveJob backend that attaches job context to error events" do
let(:failing_job) do
job_fixture do
def perform
a = 1
b = 0
raise "boom from failing_job spec"
end
end
end

it "attaches job context to extras and tags on the captured event" do
expect do
failing_job.perform_later
drain
end.to raise_error(RuntimeError, /boom from failing_job spec/)

event = last_sentry_event

expect(event.extra).to include(
active_job: failing_job.name,
arguments: [],
job_id: a_kind_of(String)
)
expect(event.extra).to have_key(:provider_job_id)
expect(event.extra).to have_key(:locale)
expect(event.extra).to have_key(:scheduled_at)

expect(event.tags).to include(
job_id: event.extra[:job_id],
provider_job_id: event.extra[:provider_job_id]
)

last_frame = event.exception.values.first.stacktrace.frames.last
expect(last_frame.vars).to include(a: "1", b: "0")
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

RSpec.shared_examples "an ActiveJob backend that respects rescue_from" do
context "when rescue_from suppresses the error" do
let(:rescued_job) do
job_fixture do
rescue_from(StandardError) { |_error| nil }

def perform
raise "boom from rescued_job spec"
end
end
end

it "does not capture an event" do
expect do
rescued_job.perform_later
drain
end.not_to raise_error

expect(sentry_events).to be_empty
end
end

context "when the rescue_from callback raises a new error" do
let(:problematic_rescued_job) do
job_fixture do
rescue_from(StandardError) { |_error| raise "boom from rescue callback" }

def perform
raise "original boom from problematic_rescued_job spec"
end
end
end

it "captures one event chaining the original and callback errors" do
expect do
problematic_rescued_job.perform_later
drain
end.to raise_error(RuntimeError, /boom from rescue callback/)

expect(sentry_events.size).to eq(1)

messages = extract_sentry_exceptions(sentry_events.last).map(&:value)
expect(messages).to include(match(/original boom from problematic_rescued_job spec/))
expect(messages).to include(match(/boom from rescue callback/))
end
end
end
Loading
Loading