From 958e19c3369c9d79f35329ca0bb1ec10cfec283d Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Tue, 2 Feb 2021 03:02:07 +0000 Subject: [PATCH] Add workaround for rails infected error classes --- Changelog.md | 14 +++++++++++- Gemfile.lock | 2 +- lib/mutant.rb | 3 ++- lib/mutant/isolation/exception.rb | 22 +++++++++++++++++++ lib/mutant/isolation/fork.rb | 6 ++++- .../reporter/cli/printer/isolation_result.rb | 4 +++- lib/mutant/version.rb | 2 +- spec/unit/mutant/isolation/fork_spec.rb | 6 ++++- .../cli/printer/isolation_result_spec.rb | 17 ++++++-------- 9 files changed, 59 insertions(+), 17 deletions(-) create mode 100644 lib/mutant/isolation/exception.rb diff --git a/Changelog.md b/Changelog.md index 7c5ba72bd..db6aab081 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,10 +1,22 @@ -# Unreleased +# v0.10.27 2021-02-02 + +* [#1216](https://github.com/mbj/mutant/pull/1216) + + Fix exception serialization form rails infected code bases. + This case can happen when the killfork terminates abnormal, + and the resulting exception in the worker has to be propagated to + the main process for reporting. + On "normal" Ruby the exceptions are dump/loadable but rails and + its core extensions break this invariant. Hence mutant now + captures the essence of the exception in an attribute copy for + propagation. * [#1207](https://github.com/mbj/mutant/pull/1207) * Remove `#eql?` -> `#equal?` mutation * [#1210](https://github.com/mbj/mutant/pull/1210) + * Remove generic mutation to `self` # v0.10.26 2021-01-16 diff --git a/Gemfile.lock b/Gemfile.lock index b5a0da84c..e37783876 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - mutant (0.10.26) + mutant (0.10.27) diff-lcs (~> 1.3) parser (~> 3.0.0) regexp_parser (~> 2.0, >= 2.0.3) diff --git a/lib/mutant.rb b/lib/mutant.rb index 5eac7d048..018c71bab 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -71,8 +71,9 @@ module Mutant require 'mutant/ast/meta/resbody' require 'mutant/parser' require 'mutant/isolation' -require 'mutant/isolation/none' +require 'mutant/isolation/exception' require 'mutant/isolation/fork' +require 'mutant/isolation/none' require 'mutant/parallel' require 'mutant/parallel/driver' require 'mutant/parallel/source' diff --git a/lib/mutant/isolation/exception.rb b/lib/mutant/isolation/exception.rb new file mode 100644 index 000000000..6305f2a48 --- /dev/null +++ b/lib/mutant/isolation/exception.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Mutant + # Module providing isolation + class Isolation + # Generic serializable exception data. + # + # This is required as our honored guests the Rails* ecosystem + # makes Marshal.dump on exceptions impossible. + # + # @see https://twitter.com/_m_b_j_/status/1356433184850907137 + # + # for the full story and evenutal reactions. + class Exception + include Adamantium, Anima.new( + :backtrace, + :message, + :original_class + ) + end # Exception + end # Isolation +end # Mutant diff --git a/lib/mutant/isolation/fork.rb b/lib/mutant/isolation/fork.rb index d43e8c21d..0a7390d4c 100644 --- a/lib/mutant/isolation/fork.rb +++ b/lib/mutant/isolation/fork.rb @@ -133,7 +133,11 @@ def read_child_result def load_result(result_fragments) @value = world.marshal.load(result_fragments.join) rescue ArgumentError => exception - @exception = exception + @exception = Exception.new( + backtrace: exception.backtrace, + message: exception.message, + original_class: exception.class + ) end # rubocop:disable Metrics/MethodLength diff --git a/lib/mutant/reporter/cli/printer/isolation_result.rb b/lib/mutant/reporter/cli/printer/isolation_result.rb index d3c19f43c..dd09d93dd 100644 --- a/lib/mutant/reporter/cli/printer/isolation_result.rb +++ b/lib/mutant/reporter/cli/printer/isolation_result.rb @@ -28,6 +28,7 @@ class IsolationResult < self ``` %s %s + %s ``` MESSAGE @@ -81,7 +82,8 @@ def print_exception puts( EXCEPTION_ERROR_MESSAGE % [ - exception.inspect, + exception.original_class, + exception.message, exception.backtrace.join("\n") ] ) diff --git a/lib/mutant/version.rb b/lib/mutant/version.rb index aef0ff834..045c32207 100644 --- a/lib/mutant/version.rb +++ b/lib/mutant/version.rb @@ -2,5 +2,5 @@ module Mutant # Current mutant version - VERSION = '0.10.26' + VERSION = '0.10.27' end # Mutant diff --git a/spec/unit/mutant/isolation/fork_spec.rb b/spec/unit/mutant/isolation/fork_spec.rb index 7b74166a9..0b148b145 100644 --- a/spec/unit/mutant/isolation/fork_spec.rb +++ b/spec/unit/mutant/isolation/fork_spec.rb @@ -500,7 +500,11 @@ def timer(now) expect(apply).to eql( described_class::Result.new( log: log_fragment, - exception: exception, + exception: Mutant::Isolation::Exception.new( + backtrace: exception.backtrace, + message: exception.message, + original_class: exception.class + ), process_status: child_status_success, timeout: nil, value: nil diff --git a/spec/unit/mutant/reporter/cli/printer/isolation_result_spec.rb b/spec/unit/mutant/reporter/cli/printer/isolation_result_spec.rb index 9a01d3807..df211d793 100644 --- a/spec/unit/mutant/reporter/cli/printer/isolation_result_spec.rb +++ b/spec/unit/mutant/reporter/cli/printer/isolation_result_spec.rb @@ -26,15 +26,11 @@ context 'on exception isolation error' do let(:exception) do - Class.new(RuntimeError) do - def inspect - '' - end - - def backtrace - %w[first last] - end - end.new('foo') + Mutant::Isolation::Exception.new( + backtrace: %w[first last], + message: 'Some Exception Message', + original_class: ArgumentError + ) end it_reports <<~'STR' @@ -51,7 +47,8 @@ def backtrace The following exception was raised while reading the killfork result: ``` - + ArgumentError + Some Exception Message first last ```