Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Commit 89a2eaf

Browse files
committed
Support objects that don't respond to #inspect in ObjectFormatter
``` [3] pry(main)> RSpec::Support::ObjectFormatter.format(BasicObject.new) NoMethodError: undefined method `inspect' for #<BasicObject:0x007fb0252a5a08> ```
1 parent a35f9d8 commit 89a2eaf

File tree

3 files changed

+79
-5
lines changed

3 files changed

+79
-5
lines changed

Changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
### Development
22
[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.5.0.beta1...master)
33

4+
Bug Fixes:
5+
6+
* Fix `ObjectFormatter` so that formatting objects that don't respond to
7+
`#inspect` (such as `BasicObject`) does not cause `NoMethodError`.
8+
(Yuji Nakayama, #269)
9+
410
### 3.5.0.beta1 / 2016-02-06
511
[Full Changelog](http://github.com/rspec/rspec-support/compare/v3.4.1...v3.5.0.beta1)
612

lib/rspec/support/object_formatter.rb

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Support
55
# Provide additional output details beyond what `inspect` provides when
66
# printing Time, DateTime, or BigDecimal
77
# @api private
8-
class ObjectFormatter
8+
class ObjectFormatter # rubocop:disable ClassLength
99
attr_accessor :max_formatted_output_length
1010

1111
def initialize(max_formatted_output_length=200)
@@ -37,8 +37,6 @@ def self.format(object)
3737
@default_instance.format(object)
3838
end
3939

40-
# rubocop:disable MethodLength
41-
4240
# Prepares the provided object to be formatted by wrapping it as needed
4341
# in something that, when `inspect` is called on it, will produce the
4442
# desired output.
@@ -48,7 +46,7 @@ def self.format(object)
4846
# with custom items that have `inspect` defined to return the desired output
4947
# for that item. Then we can just use `Array#inspect` or `Hash#inspect` to
5048
# format the entire thing.
51-
def prepare_for_inspection(object)
49+
def prepare_for_inspection(object) # rubocop:disable MethodLength, CyclomaticComplexity
5250
case object
5351
when Array
5452
return object.map { |o| prepare_for_inspection(o) }
@@ -61,6 +59,8 @@ def prepare_for_inspection(object)
6159
inspection = format_date_time(object)
6260
elsif defined?(BigDecimal) && BigDecimal === object
6361
inspection = "#{object.to_s 'F'} (#{object.inspect})"
62+
elsif UninspectableObjectInspector.uninspectable_object?(object)
63+
return UninspectableObjectInspector.new(object)
6464
elsif RSpec::Support.is_a_matcher?(object) && object.respond_to?(:description)
6565
inspection = object.description
6666
else
@@ -70,7 +70,6 @@ def prepare_for_inspection(object)
7070

7171
InspectableItem.new(inspection)
7272
end
73-
# rubocop:enable MethodLength
7473

7574
def self.prepare_for_inspection(object)
7675
@default_instance.prepare_for_inspection(object)
@@ -141,6 +140,41 @@ def pretty_print(pp)
141140
pp.text inspect
142141
end
143142
end
143+
144+
UninspectableObjectInspector = Struct.new(:object) do
145+
OBJECT_ID_FORMAT = '%#016x'
146+
147+
def self.uninspectable_object?(object)
148+
object.inspect
149+
false
150+
rescue NoMethodError
151+
true
152+
end
153+
154+
# NoMethodError: undefined method `inspect' for #<BasicObject:0x007fe26d175140>
155+
def inspect
156+
"#<#{klass}:#{native_object_id}>"
157+
end
158+
159+
def pretty_print(pp)
160+
pp.text inspect
161+
end
162+
163+
private
164+
165+
def klass
166+
singleton_class = class << object; self; end
167+
singleton_class.ancestors.find { |ancestor| !ancestor.equal?(singleton_class) }
168+
end
169+
170+
# http://stackoverflow.com/a/2818916
171+
def native_object_id
172+
OBJECT_ID_FORMAT % (object.__id__ << 1)
173+
rescue NoMethodError
174+
# In Ruby 1.9.2, BasicObject responds to none of #__id__, #object_id, #id...
175+
'-'
176+
end
177+
end
144178
end
145179
end
146180
end

spec/rspec/support/object_formatter_spec.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,40 @@ def with_delegate_loaded
159159
end
160160
end
161161

162+
context 'with an object that does not respond to #inspect such as BasicObject' do
163+
subject(:output) do
164+
ObjectFormatter.format(input)
165+
end
166+
167+
let(:input) do
168+
if defined?(BasicObject)
169+
BasicObject.new
170+
else
171+
fake_basic_object_class.new
172+
end
173+
end
174+
175+
let(:fake_basic_object_class) do
176+
Class.new do
177+
def self.to_s
178+
'BasicObject'
179+
end
180+
181+
undef inspect, respond_to?
182+
end
183+
end
184+
185+
if RUBY_VERSION == '1.9.2'
186+
it 'produces an #inspect-like output without object id' do
187+
expect(output).to eq('#<BasicObject:->')
188+
end
189+
else
190+
it "produces an output emulating MRI's #inspect-like output generated by C implementation" do
191+
expect(output).to match(/\A#<BasicObject:0x[0-9a-f]{14}>\z/)
192+
end
193+
end
194+
end
195+
162196
context 'with truncation enabled' do
163197
it 'produces an output of limited length' do
164198
formatter = ObjectFormatter.new(10)

0 commit comments

Comments
 (0)