rails/activesupport/test/error_reporter_test.rb

298 lines
8.9 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
require_relative "abstract_unit"
require "active_support/execution_context/test_helper"
require "active_support/error_reporter/test_helper"
class ErrorReporterTest < ActiveSupport::TestCase
# ExecutionContext is automatically reset in Rails app via executor hooks set in railtie
# But not in Active Support's own test suite.
include ActiveSupport::ExecutionContext::TestHelper
include ActiveSupport::ErrorReporter::TestHelper
setup do
@reporter = ActiveSupport::ErrorReporter.new
@subscriber = ActiveSupport::ErrorReporter::TestHelper::ErrorSubscriber.new
@reporter.subscribe(@subscriber)
@error = ArgumentError.new("Oops")
end
test "receives the execution context" do
@reporter.set_context(section: "admin")
error = ArgumentError.new("Oops")
@reporter.report(error, handled: true)
assert_equal [[error, true, :warning, "application", { section: "admin" }]], @subscriber.events
end
test "passed context has priority over the execution context" do
@reporter.set_context(section: "admin")
error = ArgumentError.new("Oops")
@reporter.report(error, handled: true, context: { section: "public" })
assert_equal [[error, true, :warning, "application", { section: "public" }]], @subscriber.events
end
test "passed source is forwarded" do
error = ArgumentError.new("Oops")
@reporter.report(error, handled: true, source: "my_gem")
assert_equal [[error, true, :warning, "my_gem", {}]], @subscriber.events
end
test "#disable allow to skip a subscriber" do
@reporter.disable(@subscriber) do
@reporter.report(ArgumentError.new("Oops"), handled: true)
end
assert_equal [], @subscriber.events
end
test "#disable allow to skip a subscribers per class" do
@reporter.disable(ErrorSubscriber) do
@reporter.report(ArgumentError.new("Oops"), handled: true)
end
assert_equal [], @subscriber.events
end
test "#handle swallow and report any unhandled error" do
error = ArgumentError.new("Oops")
@reporter.handle do
raise error
end
assert_equal [[error, true, :warning, "application", {}]], @subscriber.events
end
test "#handle can be scoped to an exception class" do
assert_raises ArgumentError do
@reporter.handle(NameError) do
raise ArgumentError
end
end
assert_equal [], @subscriber.events
end
test "#handle can be scoped to several exception classes" do
assert_raises ArgumentError do
@reporter.handle(NameError, NoMethodError) do
raise ArgumentError
end
end
assert_equal [], @subscriber.events
end
test "#handle swallows and reports matching errors" do
error = ArgumentError.new("Oops")
@reporter.handle(NameError, ArgumentError) do
raise error
end
assert_equal [[error, true, :warning, "application", {}]], @subscriber.events
end
test "#handle passes through the return value" do
result = @reporter.handle do
2 + 2
end
assert_equal 4, result
end
test "#handle returns nil on handled raise" do
result = @reporter.handle do
raise StandardError
2 + 2
end
assert_nil result
end
test "#handle returns the value of the fallback as a proc on handled raise" do
result = @reporter.handle(fallback: -> { 2 + 2 }) do
raise StandardError
end
assert_equal 4, result
end
test "#handle raises if the fallback is not a callable" do
assert_raises NoMethodError do
@reporter.handle(fallback: "four") do
raise StandardError
end
end
end
test "#handle raises the error up if fallback is a proc that then also raises" do
assert_raises ArgumentError do
@reporter.handle(fallback: -> { raise ArgumentError }) do
raise StandardError
end
end
end
test "#record report any unhandled error and re-raise them" do
error = ArgumentError.new("Oops")
assert_raises ArgumentError do
@reporter.record do
raise error
end
end
assert_equal [[error, false, :error, "application", {}]], @subscriber.events
end
test "#record can be scoped to an exception class" do
assert_raises ArgumentError do
@reporter.record(NameError) do
raise ArgumentError
end
end
assert_equal [], @subscriber.events
end
test "#record can be scoped to several exception classes" do
assert_raises ArgumentError do
@reporter.record(NameError, NoMethodError) do
raise ArgumentError
end
end
assert_equal [], @subscriber.events
end
test "#record report any matching, unhandled error and re-raise them" do
error = ArgumentError.new("Oops")
assert_raises ArgumentError do
@reporter.record(NameError, ArgumentError) do
raise error
end
end
assert_equal [[error, false, :error, "application", {}]], @subscriber.events
end
test "#record passes through the return value" do
result = @reporter.record do
2 + 2
end
assert_equal 4, result
end
test "#unexpected swallows errors by default" do
error = RuntimeError.new("Oops")
assert_nil @reporter.unexpected(error)
assert_equal [[error, true, :warning, "application", {}]], @subscriber.events
assert_not_predicate error.backtrace, :empty?
end
test "#unexpected accepts an error message" do
assert_nil @reporter.unexpected("Oops")
assert_equal 1, @subscriber.events.size
error, *event_details = @subscriber.events.first
assert_equal [true, :warning, "application", {}], event_details
assert_equal "Oops", error.message
assert_equal RuntimeError, error.class
assert_not_predicate error.backtrace, :empty?
end
test "#unexpected re-raise errors in development and test" do
@reporter.debug_mode = true
error = RuntimeError.new("Oops")
raise_line = __LINE__ + 2
raised_error = assert_raises ActiveSupport::ErrorReporter::UnexpectedError do
@reporter.unexpected(error)
end
assert_includes raised_error.message, "RuntimeError: Oops"
assert_not_nil raised_error.cause
assert_same error, raised_error.cause
assert_includes raised_error.backtrace.first, "#{__FILE__}:#{raise_line}"
end
test "can have multiple subscribers" do
second_subscriber = ErrorSubscriber.new
@reporter.subscribe(second_subscriber)
error = ArgumentError.new("Oops")
@reporter.report(error, handled: true)
assert_equal 1, @subscriber.events.size
assert_equal 1, second_subscriber.events.size
end
test "can unsubscribe" do
second_subscriber = ErrorSubscriber.new
@reporter.subscribe(second_subscriber)
error = ArgumentError.new("Oops")
@reporter.report(error, handled: true)
@reporter.unsubscribe(second_subscriber)
error = ArgumentError.new("Oops 2")
@reporter.report(error, handled: true)
assert_equal 2, @subscriber.events.size
assert_equal 1, second_subscriber.events.size
@reporter.subscribe(second_subscriber)
@reporter.unsubscribe(ErrorSubscriber)
error = ArgumentError.new("Oops 3")
@reporter.report(error, handled: true)
assert_equal 2, @subscriber.events.size
assert_equal 1, second_subscriber.events.size
end
test "handled errors default to :warning severity" do
@reporter.report(@error, handled: true)
assert_equal :warning, @subscriber.events.dig(0, 2)
end
test "unhandled errors default to :error severity" do
@reporter.report(@error, handled: false)
assert_equal :error, @subscriber.events.dig(0, 2)
end
test "report errors only once" do
assert_difference -> { @subscriber.events.size }, +1 do
@reporter.report(@error, handled: false)
end
assert_no_difference -> { @subscriber.events.size } do
3.times do
@reporter.report(@error, handled: false)
end
end
end
test "can report frozen exceptions" do
assert_difference -> { @subscriber.events.size }, +1 do
@reporter.report(@error.freeze, handled: false)
end
end
class FailingErrorSubscriber
Error = Class.new(StandardError)
def initialize(error)
@error = error
end
def report(_error, handled:, severity:, context:, source:)
raise @error
end
end
test "subscriber errors are re-raised if no logger is set" do
subscriber_error = FailingErrorSubscriber::Error.new("Big Oopsie")
@reporter.subscribe(FailingErrorSubscriber.new(subscriber_error))
assert_raises FailingErrorSubscriber::Error do
@reporter.report(@error, handled: true)
end
end
test "subscriber errors are logged if a logger is set" do
subscriber_error = FailingErrorSubscriber::Error.new("Big Oopsie")
@reporter.subscribe(FailingErrorSubscriber.new(subscriber_error))
log = StringIO.new
@reporter.logger = ActiveSupport::Logger.new(log)
@reporter.report(@error, handled: true)
expected = "Error subscriber raised an error: Big Oopsie (ErrorReporterTest::FailingErrorSubscriber::Error)"
assert_equal expected, log.string.lines.first.chomp
end
end