File: object_formatter_spec.rb

package info (click to toggle)
ruby-rspec 3.12.0c0e1m1s0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 6,752 kB
  • sloc: ruby: 69,818; sh: 1,861; makefile: 99
file content (375 lines) | stat: -rw-r--r-- 12,735 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
require 'rspec/support/object_formatter'
require 'rspec/matchers/fail_matchers'

module RSpec
  module Support
    RSpec.describe ObjectFormatter, ".format" do
      context 'with an array object containing other objects for which we have custom formatting' do
        let(:time)  { Time.utc(1969, 12, 31, 19, 01, 40, 101) }
        let(:formatted_time) { ObjectFormatter.format(time) }
        let(:input) { ["string", time, [3, time]] }

        it 'formats those objects within the array output, at any level of nesting' do
          formatted = ObjectFormatter.format(input)
          expect(formatted).to eq(%Q{["string", #{formatted_time}, [3, #{formatted_time}]]})
        end
      end

      context "with a hash object containing other objects for which we have custom formatting" do
        let(:time)  { Time.utc(1969, 12, 31, 19, 01, 40, 101) }
        let(:formatted_time) { ObjectFormatter.format(time) }
        let(:input) { { "key" => time, time => "value", "nested" => { "key" => time } } }

        it 'formats those objects within the hash output, at any level of nesting' do
          formatted = ObjectFormatter.format(input)

          if RUBY_VERSION == '1.8.7'
            # We can't count on the ordering of the hash on 1.8.7...
            expect(formatted).to include(%Q{"key"=>#{formatted_time}}, %Q{#{formatted_time}=>"value"}, %Q{"nested"=>{"key"=>#{formatted_time}}})
          else
            expect(formatted).to eq(%Q{{"key"=>#{formatted_time}, #{formatted_time}=>"value", "nested"=>{"key"=>#{formatted_time}}}})
          end
        end
      end

      unless RUBY_VERSION == '1.8.7' # We can't count on the ordering of the hash on 1.8.7...
        context 'with a hash object' do
          let(:input) { { :c => "ccc", :a => "aaa", "b" => 'bbb' } }
          let(:expected) { '{:a=>"aaa", "b"=>"bbb", :c=>"ccc"}' }

          it 'sorts keys to ensure objects are always displayed the same way' do
            formatted = ObjectFormatter.format(input)
            expect(formatted).to eq expected
          end
        end
      end

      context 'with Time objects' do
        let(:time) { Time.utc(1969, 12, 31, 19, 01, 40, 101) }
        let(:formatted_time) { ObjectFormatter.format(time) }

        it 'produces an extended output' do
          expected_output = "1969-12-31 19:01:40.000101"
          expect(formatted_time).to include(expected_output)
        end
      end

      context 'with DateTime objects' do
        def with_date_loaded
          in_sub_process_if_possible do
            require 'date'
            yield
          end
        end

        let(:date_time) { DateTime.new(2000, 1, 1, 1, 1, Rational(1, 10)) }
        let(:formatted_date_time) { ObjectFormatter.format(date_time) }

        it 'formats the DateTime using inspect' do
          with_date_loaded do
            expect(formatted_date_time).to eq(date_time.inspect)
          end
        end

        it 'does not require DateTime to be defined since you need to require `date` to make it available' do
          hide_const('DateTime')
          expect(ObjectFormatter.format('Test String')).to eq('"Test String"')
        end

        context 'when ActiveSupport is loaded' do
          it "uses a custom format to ensure the output is different when DateTimes differ" do
            stub_const("ActiveSupport", Module.new)

            with_date_loaded do
              expected_date_time = 'Sat, 01 Jan 2000 01:01:00.100000000 +0000'
              expect(formatted_date_time).to eq(expected_date_time)
            end
          end
        end
      end

      context 'with BigDecimal objects' do
        let(:float)   { 3.3 }
        let(:decimal) { BigDecimal("3.3") }

        let(:formatted_decimal) { ObjectFormatter.format(decimal) }

        if RUBY_VERSION >= '2.4'
          it "uses Ruby's BigDecimal formatting since it is improved in 2.4+" do
            in_sub_process_if_possible do
              require 'bigdecimal'
              expect(formatted_decimal).to eq('0.33e1')
            end
          end
        else
          it 'includes a conventional representation of the decimal' do
            in_sub_process_if_possible do
              require 'bigdecimal'
              # Suppress warning on JRuby 1.7:
              #   file:/Users/me/.rbenv/versions/jruby-1.7.26/lib/jruby.jar!/jruby/bigdecimal.rb:1
              #   warning: loading in progress, circular require considered harmful - bigdecimal.jar
              $stderr.reset!

              expect(formatted_decimal).to include('3.3 (#<BigDecimal')
            end
          end
        end

        it 'does not require BigDecimal to be defined since you need to require `bigdecimal` to make it available' do
          hide_const('BigDecimal')
          expect(ObjectFormatter.format('Test String')).to eq('"Test String"')
        end
      end

      context 'given a delegator' do
        def with_delegate_loaded
          in_sub_process_if_possible do
            require 'delegate'
            yield
          end
        end

        let(:object) { Object.new }
        let(:delegator) do
          SimpleDelegator.new(object)
        end

        it 'includes the delegator class in the description' do
          with_delegate_loaded do
            expect(ObjectFormatter.format(delegator)).to eq "#<SimpleDelegator(#{object.inspect})>"
          end
        end

        it 'includes the delegator class in the description even when protected' do
          with_delegate_loaded do
            protected_delegator = Class.new(SimpleDelegator) { protected :__getobj__ }
            expect(
              ObjectFormatter.format(protected_delegator.new(object))
            ).to eq "#<#{protected_delegator.inspect}(#{object.inspect})>"
          end
        end

        it 'does not require Delegator to be defined' do
          hide_const("Delegator")
          expect(ObjectFormatter.format(object)).to eq object.inspect
        end

        context 'for a specially-formatted object' do
          let(:decimal) { BigDecimal("3.3") }
          let(:formatted_decimal) { ObjectFormatter.format(decimal) }
          let(:object) { decimal }

          it 'formats the underlying object normally' do
            with_delegate_loaded do
              require 'bigdecimal'
              # Suppress warning on JRuby 1.7:
              #   file:/Users/me/.rbenv/versions/jruby-1.7.26/lib/jruby.jar!/jruby/bigdecimal.rb:1
              #   warning: loading in progress, circular require considered harmful - bigdecimal.jar
              $stderr.reset!

              expect(ObjectFormatter.format(delegator)).to eq "#<SimpleDelegator(#{formatted_decimal})>"
            end
          end
        end
      end

      context 'with objects that implement description' do
        RSpec::Matchers.define :matcher_with_description do
          match { true }
          description { "description" }
        end

        RSpec::Matchers.define :matcher_without_a_description do
          match { true }
          undef description
        end

        it "produces a description when a matcher object has a description" do
          expect(ObjectFormatter.format(matcher_with_description)).to eq("description")
        end

        it "does not produce a description unless the object is a matcher" do
          double = double('non-matcher double', :description => true)
          expect(ObjectFormatter.format(double)).to eq(double.inspect)
        end

        it "produces an inspected object when a matcher is missing a description" do
          expect(ObjectFormatter.format(matcher_without_a_description)).to eq(
            matcher_without_a_description.inspect)
        end
      end

      context 'with an object that does not respond to #class and #inspect such as BasicObject' do
        subject(:output) do
          ObjectFormatter.format(input)
        end

        let(:input) do
          if defined?(BasicObject)
            BasicObject.new
          else
            fake_basic_object_class.new
          end
        end

        let(:fake_basic_object_class) do
          Class.new do
            def self.to_s
              'BasicObject'
            end

            undef class, inspect, respond_to?
          end
        end

        if RUBY_VERSION == '1.9.2'
          it 'produces an #inspect-like output without object id' do
            expect(output).to eq('#<BasicObject:->')
          end
        else
          it "produces an output emulating MRI's #inspect-like output generated by C implementation" do
            expect(output).to match(/\A#<BasicObject:0x[0-9a-f]{14,16}>\z/)
          end
        end
      end

      context 'with a recursive array' do
        subject(:output) do
          ObjectFormatter.format(input)
        end

        let(:input) do
          array = [time]
          array << array
          array
        end

        let(:time) { Time.utc(1969, 12, 31, 19, 01, 40, 101) }

        let(:formatted_time) { ObjectFormatter.format(time) }

        it 'formats the recursive element as [...] and other elements with custom formatting' do
          expect(output).to eq("[#{formatted_time}, [...]]")
        end
      end

      context 'with a recursive-key hash' do
        subject(:output) do
          ObjectFormatter.format(input)
        end

        let(:input) do
          hash = {}
          hash[hash] = time
          hash
        end

        let(:time) { Time.utc(1969, 12, 31, 19, 01, 40, 101) }

        let(:formatted_time) { ObjectFormatter.format(time) }

        it 'formats the recursive element as {...} and other elements with custom formatting' do
          expect(output).to eq("{{...}=>#{formatted_time}}")
        end
      end

      context 'with a recursive-value hash' do
        subject(:output) do
          ObjectFormatter.format(input)
        end

        let(:input) do
          hash = {}
          hash[time] = hash
          hash
        end

        let(:time) { Time.utc(1969, 12, 31, 19, 01, 40, 101) }

        let(:formatted_time) { ObjectFormatter.format(time) }

        it 'formats the recursive element as {...} and other elements with custom formatting' do
          expect(output).to eq("{#{formatted_time}=>{...}}")
        end
      end

      context 'with a non-immediate recursive array' do
        subject(:output) do
          ObjectFormatter.format(input)
        end

        let(:input) do
          array = []
          array[0] = { :recursive_array => array }
          array
        end

        it 'formats the recursive element as [...]' do
          expect(output).to eq('[{:recursive_array=>[...]}]')
        end
      end

      context 'with a non-immediate recursive hash' do
        subject(:output) do
          ObjectFormatter.format(input)
        end

        let(:input) do
          hash = {}
          hash[:array] = [:next_is_recursive_hash, hash]
          hash
        end

        it 'formats the recursive element as {...}' do
          expect(output).to eq('{:array=>[:next_is_recursive_hash, {...}]}')
        end
      end

      context 'with an array including a same collection object multiple times' do
        subject(:output) do
          ObjectFormatter.format(input)
        end

        let(:input) do
          hash = { :key => 'value' }
          [hash, hash]
        end

        it 'does not omit them' do
          expect(output).to eq('[{:key=>"value"}, {:key=>"value"}]')
        end
      end

      context 'with truncation enabled' do
        it 'produces an output of limited length' do
          formatter = ObjectFormatter.new(10)
          expect(formatter.format('Test String Of A Longer Length')).to eq('"Test ...ngth"')
        end

        it 'does not truncate shorter strings' do
          formatter = ObjectFormatter.new(10)
          expect(formatter.format('Testing')).to eq('"Testing"')
        end

        context 'with ANSI escape codes that fall on the truncate split' do
          it 'removes that escape code so terminals do not get corrupted print a partial escape code' do
            formatter = ObjectFormatter.new(38)
            object = Class.new do
              def inspect
                "#<\e[33mClass\e[0m \e[36mname: \e[0m\"foobars\" \e[36mcount: \e[0m42>"
              end
            end.new
            expect(formatter.format(object)).to eq("#<\e[33mClass\e[0m ...\e[36mcount: \e[0m42>")
          end
        end
      end

      context 'with truncation disabled' do
        it 'does not limit the output length' do
          formatter = ObjectFormatter.new(nil)
          expect(formatter.format('Test String Of A Longer Length')).to eq('"Test String Of A Longer Length"')
        end
      end
    end
  end
end