Neuter exception error for ActiveRecord::RecordInvalid
moiristo opened this issue · comments
For some reason, an AR object I have is unmarshallable (rails 7.0.3.1). When this record is passed in ActiveRecord::RecordInvalid
and raised in a test, the following error occurs since 5.16.0:
Minitest::UnexpectedError: NoMethodError: undefined method `errors' for "Validation failed: Receipt is invalid":String
activerecord (7.0.3.1) lib/active_record/validations.rb:21:in `initialize'
minitest (5.16.2) lib/minitest/test.rb:223:in `new'
minitest (5.16.2) lib/minitest/test.rb:223:in `new_exception'
minitest (5.16.2) lib/minitest/test.rb:215:in `neuter_exception'
minitest (5.16.2) lib/minitest/test.rb:208:in `rescue in sanitize_exception'
minitest (5.16.2) lib/minitest/test.rb:204:in `sanitize_exception'
minitest (5.16.2) lib/minitest/test.rb:201:in `rescue in capture_exceptions'
minitest (5.16.2) lib/minitest/test.rb:194:in `capture_exceptions'
minitest (5.16.2) lib/minitest/test.rb:95:in `block (2 levels) in run'
minitest-hooks (1.5.0) lib/minitest/hooks/test.rb:38:in `block (2 levels) in time_it'
minitest-hooks (1.5.0) lib/minitest/hooks/test.rb:31:in `around'
minitest-hooks (1.5.0) lib/minitest/hooks/test.rb:37:in `block in time_it'
minitest (5.16.2) lib/minitest.rb:296:in `time_it'
minitest-hooks (1.5.0) lib/minitest/hooks/test.rb:36:in `time_it'
minitest (5.16.2) lib/minitest/test.rb:94:in `block in run'
minitest (5.16.2) lib/minitest.rb:391:in `on_signal'
minitest (5.16.2) lib/minitest/test.rb:243:in `with_info_handler'
minitest (5.16.2) lib/minitest/test.rb:93:in `run'
minitest-reporters (1.5.0) lib/minitest/reporters.rb:48:in `run_with_hooks'
minitest (5.16.2) lib/minitest.rb:1059:in `run_one_method'
minitest (5.16.2) lib/minitest.rb:365:in `run_one_method'
minitest (5.16.2) lib/minitest.rb:352:in `block (2 levels) in run'
minitest (5.16.2) lib/minitest.rb:351:in `each'
minitest (5.16.2) lib/minitest.rb:351:in `block in run'
minitest (5.16.2) lib/minitest.rb:391:in `on_signal'
minitest (5.16.2) lib/minitest.rb:378:in `with_info_handler'
minitest-hooks (1.5.0) lib/minitest/hooks/test.rb:81:in `block in with_info_handler'
minitest-hooks (1.5.0) lib/minitest/hooks/test.rb:26:in `around_all'
minitest-hooks (1.5.0) lib/minitest/hooks/test.rb:70:in `with_info_handler'
minitest (5.16.2) lib/minitest.rb:350:in `run'
railties (7.0.3.1) lib/rails/test_unit/line_filtering.rb:10:in `run'
minitest (5.16.2) lib/minitest.rb:182:in `block in __run'
minitest (5.16.2) lib/minitest.rb:182:in `map'
minitest (5.16.2) lib/minitest.rb:182:in `__run'
minitest (5.16.2) lib/minitest.rb:159:in `run'
minitest (5.16.2) lib/minitest.rb:83:in `block in autorun'
activesupport (7.0.3.1) lib/active_support/fork_tracker.rb:18:in `fork'
activesupport (7.0.3.1) lib/active_support/fork_tracker.rb:18:in `fork'
/Users/be/.rbenv/versions/2.7.5/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:83:in `require'
/Users/be/.rbenv/versions/2.7.5/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:83:in `require'
-e:1:in `<main>'
This is because the same error class is instantiated with a String message, while the constructor expects an AR object: https://github.com/rails/rails/blob/main/activerecord/lib/active_record/validations.rb#L18. I hope I can find out why the object cannot be marshalled (maybe caused by pry
?), but I think the neutering of the exception should be improved anyway to properly handle this case.
C'mon people... Ya gotta give me more information than that. This is unworkable so far.
Oh, I thought my explanation was pretty clear. In short, when an exception is unmarshallable, the fallback approach is to instantiate the same exception class with a message string (https://github.com/minitest/minitest/blob/master/lib/minitest/test.rb#L215). However, custom exceptions like ActiveRecord::RecordInvalid
expect the first argument to be an AR object (https://github.com/rails/rails/blob/main/activerecord/lib/active_record/validations.rb#L18).
I don't think you can really deduce from a custom exception what the first argument in its constructor is supposed to be. So maybe the final approach in https://github.com/minitest/minitest/blob/master/lib/minitest/test.rb#L219 should always be done instead?
I can write a repro if you like. It doesn't need to be an ActiveRecord::RecordInvalid
exception of course; it can be any exception with a custom constructor..
Please see moiristo@9cebd67. This fails with
1) Error:
TestMinitestTest#test_spec_marshal_with_custom_constructor_exception:
NoMethodError: undefined method `text' for "this is bad!":String
Proposed fix is to never reuse the original exception class: moiristo@47d2a01
I wonder if the https://ruby-doc.org/core-3.1.2/Exception.html#method-i-exception could be used on ActiveRecord object and help here https://github.com/moiristo/minitest/blob/47d2a013e03d308bb92bd90a82aea215d7c1d1fe/lib/minitest/test.rb#L222
Though the #exception
method is sometimes "custom" even in ruby stdlib too. Just few days ago discovered it to be defined in Timeout
https://ruby-doc.org/stdlib-3.1.2/libdoc/timeout/rdoc/Timeout/Error.html#method-i-exception and have no clue what it is doing but it was ruining my passed args.
In what way? It doesn't make the exception marshallable I think. Today, we found out that this bug also breaks our CI build and custom reporting when an error occurs, so for now we've monkeypatched sanitize_exception
, as we are not really interested in marshallability anyway:
module Minitest
class Test
def sanitize_exception e
e
end
end
end
Would removing the TypeError
restriction be sufficient?