1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activesupport/test/error_reporter_test.rb
Jean Boussier 8cbc19d86b Rails standardized error reporting interface
Fix: https://github.com/rails/rails/issues/43472

The reporter is held by the executor, but the `Rails` module provides a
nicer `Rails.error` shortcut.

For ease of use, two block based specialized methods are exposed.

`handle`, which swallow errors and forward them to the subscribers:

```ruby
Rails.error.handle do
  1 + '1' # raises TypeError
end
1 + 1 # This will be executed
```

`record`, which forward the errors to the subscribes but let it
continue rewinding the call stack:

```ruby
Rails.error.record do
  1 + '1' # raises TypeError
end
1 + 1 # This won't be executed.
```

For cases where the blocked based API isn't suitable, the lower level
`report` method can be used:

```ruby
Rails.error.report(error, handled: true / false)
```
2021-11-16 13:34:53 +01:00

131 lines
3.9 KiB
Ruby

# frozen_string_literal: true
require_relative "abstract_unit"
require "active_support/execution_context/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
class ErrorSubscriber
attr_reader :events
def initialize
@events = []
end
def report(error, handled:, severity:, context:)
@events << [error, handled, severity, context]
end
end
setup do
@reporter = ActiveSupport::ErrorReporter.new
@subscriber = 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, { 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, { section: "public" }]], @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, {}]], @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 "#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, {}]], @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 "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 "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
class FailingErrorSubscriber
Error = Class.new(StandardError)
def initialize(error)
@error = error
end
def report(_error, handled:, severity:, context:)
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