sspec-support 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/Changelog.md +242 -0
  3. data/LICENSE.md +23 -0
  4. data/README.md +40 -0
  5. data/lib/rspec/support.rb +149 -0
  6. data/lib/rspec/support/caller_filter.rb +83 -0
  7. data/lib/rspec/support/comparable_version.rb +46 -0
  8. data/lib/rspec/support/differ.rb +215 -0
  9. data/lib/rspec/support/directory_maker.rb +63 -0
  10. data/lib/rspec/support/encoded_string.rb +165 -0
  11. data/lib/rspec/support/fuzzy_matcher.rb +48 -0
  12. data/lib/rspec/support/hunk_generator.rb +47 -0
  13. data/lib/rspec/support/matcher_definition.rb +42 -0
  14. data/lib/rspec/support/method_signature_verifier.rb +426 -0
  15. data/lib/rspec/support/mutex.rb +73 -0
  16. data/lib/rspec/support/object_formatter.rb +275 -0
  17. data/lib/rspec/support/recursive_const_methods.rb +76 -0
  18. data/lib/rspec/support/reentrant_mutex.rb +53 -0
  19. data/lib/rspec/support/ruby_features.rb +176 -0
  20. data/lib/rspec/support/source.rb +75 -0
  21. data/lib/rspec/support/source/location.rb +21 -0
  22. data/lib/rspec/support/source/node.rb +110 -0
  23. data/lib/rspec/support/source/token.rb +87 -0
  24. data/lib/rspec/support/spec.rb +81 -0
  25. data/lib/rspec/support/spec/deprecation_helpers.rb +64 -0
  26. data/lib/rspec/support/spec/formatting_support.rb +9 -0
  27. data/lib/rspec/support/spec/in_sub_process.rb +69 -0
  28. data/lib/rspec/support/spec/library_wide_checks.rb +150 -0
  29. data/lib/rspec/support/spec/shell_out.rb +84 -0
  30. data/lib/rspec/support/spec/stderr_splitter.rb +63 -0
  31. data/lib/rspec/support/spec/string_matcher.rb +46 -0
  32. data/lib/rspec/support/spec/with_isolated_directory.rb +13 -0
  33. data/lib/rspec/support/spec/with_isolated_stderr.rb +13 -0
  34. data/lib/rspec/support/version.rb +7 -0
  35. data/lib/rspec/support/warnings.rb +39 -0
  36. metadata +115 -0
@@ -0,0 +1,73 @@
1
+ module RSpec
2
+ module Support
3
+ # On 1.8.7, it's in the stdlib.
4
+ # We don't want to load the stdlib, b/c this is a test tool, and can affect
5
+ # the test environment, causing tests to pass where they should fail.
6
+ #
7
+ # So we're transcribing/modifying it from
8
+ # https://github.com/ruby/ruby/blob/v1_8_7_374/lib/thread.rb#L56
9
+ # Some methods we don't need are deleted. Anything I don't
10
+ # understand (there's quite a bit, actually) is left in.
11
+ #
12
+ # Some formating changes are made to appease the robot overlord:
13
+ # https://travis-ci.org/rspec/rspec-core/jobs/54410874
14
+ # @private
15
+ class Mutex
16
+ def initialize
17
+ @waiting = []
18
+ @locked = false
19
+ @waiting.taint
20
+ taint
21
+ end
22
+
23
+ # @private
24
+ def lock
25
+ while Thread.critical = true && @locked
26
+ @waiting.push Thread.current
27
+ Thread.stop
28
+ end
29
+ @locked = true
30
+ Thread.critical = false
31
+ self
32
+ end
33
+
34
+ # @private
35
+ def unlock
36
+ return unless @locked
37
+ Thread.critical = true
38
+ @locked = false
39
+ wakeup_and_run_waiting_thread
40
+ self
41
+ end
42
+
43
+ # @private
44
+ def synchronize
45
+ lock
46
+ begin
47
+ yield
48
+ ensure
49
+ unlock
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def wakeup_and_run_waiting_thread
56
+ begin
57
+ t = @waiting.shift
58
+ t.wakeup if t
59
+ rescue ThreadError
60
+ retry
61
+ end
62
+ Thread.critical = false
63
+ begin
64
+ t.run if t
65
+ rescue ThreadError
66
+ :noop
67
+ end
68
+ end
69
+
70
+ # Avoid warnings for library wide checks spec
71
+ end unless defined?(::RSpec::Support::Mutex) || defined?(::Mutex)
72
+ end
73
+ end
@@ -0,0 +1,275 @@
1
+ RSpec::Support.require_rspec_support 'matcher_definition'
2
+
3
+ module RSpec
4
+ module Support
5
+ # Provide additional output details beyond what `inspect` provides when
6
+ # printing Time, DateTime, or BigDecimal
7
+ # @api private
8
+ class ObjectFormatter # rubocop:disable Metrics/ClassLength
9
+ ELLIPSIS = "..."
10
+
11
+ attr_accessor :max_formatted_output_length
12
+
13
+ # Methods are deferred to a default instance of the class to maintain the interface
14
+ # For example, calling ObjectFormatter.format is still possible
15
+ def self.default_instance
16
+ @default_instance ||= new
17
+ end
18
+
19
+ def self.format(object)
20
+ default_instance.format(object)
21
+ end
22
+
23
+ def self.prepare_for_inspection(object)
24
+ default_instance.prepare_for_inspection(object)
25
+ end
26
+
27
+ def initialize(max_formatted_output_length=200)
28
+ @max_formatted_output_length = max_formatted_output_length
29
+ @current_structure_stack = []
30
+ end
31
+
32
+ def format(object)
33
+ if max_formatted_output_length.nil?
34
+ prepare_for_inspection(object).inspect
35
+ else
36
+ formatted_object = prepare_for_inspection(object).inspect
37
+ if formatted_object.length < max_formatted_output_length
38
+ formatted_object
39
+ else
40
+ beginning = truncate_string formatted_object, 0, max_formatted_output_length / 2
41
+ ending = truncate_string formatted_object, -max_formatted_output_length / 2, -1
42
+ beginning + ELLIPSIS + ending
43
+ end
44
+ end
45
+ end
46
+
47
+ # Prepares the provided object to be formatted by wrapping it as needed
48
+ # in something that, when `inspect` is called on it, will produce the
49
+ # desired output.
50
+ #
51
+ # This allows us to apply the desired formatting to hash/array data structures
52
+ # at any level of nesting, simply by walking that structure and replacing items
53
+ # with custom items that have `inspect` defined to return the desired output
54
+ # for that item. Then we can just use `Array#inspect` or `Hash#inspect` to
55
+ # format the entire thing.
56
+ def prepare_for_inspection(object)
57
+ case object
58
+ when Array
59
+ prepare_array(object)
60
+ when Hash
61
+ prepare_hash(object)
62
+ else
63
+ inspector_class = INSPECTOR_CLASSES.find { |inspector| inspector.can_inspect?(object) }
64
+ inspector_class.new(object, self)
65
+ end
66
+ end
67
+
68
+ def prepare_array(array)
69
+ with_entering_structure(array) do
70
+ array.map { |element| prepare_element(element) }
71
+ end
72
+ end
73
+
74
+ def prepare_hash(input_hash)
75
+ with_entering_structure(input_hash) do
76
+ sort_hash_keys(input_hash).inject({}) do |output_hash, key_and_value|
77
+ key, value = key_and_value.map { |element| prepare_element(element) }
78
+ output_hash[key] = value
79
+ output_hash
80
+ end
81
+ end
82
+ end
83
+
84
+ def sort_hash_keys(input_hash)
85
+ if input_hash.keys.all? { |k| k.is_a?(String) || k.is_a?(Symbol) }
86
+ Hash[input_hash.sort_by { |k, _v| k.to_s }]
87
+ else
88
+ input_hash
89
+ end
90
+ end
91
+
92
+ def prepare_element(element)
93
+ if recursive_structure?(element)
94
+ case element
95
+ when Array then InspectableItem.new('[...]')
96
+ when Hash then InspectableItem.new('{...}')
97
+ else raise # This won't happen
98
+ end
99
+ else
100
+ prepare_for_inspection(element)
101
+ end
102
+ end
103
+
104
+ def with_entering_structure(structure)
105
+ @current_structure_stack.push(structure)
106
+ return_value = yield
107
+ @current_structure_stack.pop
108
+ return_value
109
+ end
110
+
111
+ def recursive_structure?(object)
112
+ @current_structure_stack.any? { |seen_structure| seen_structure.equal?(object) }
113
+ end
114
+
115
+ InspectableItem = Struct.new(:text) do
116
+ def inspect
117
+ text
118
+ end
119
+
120
+ def pretty_print(pp)
121
+ pp.text(text)
122
+ end
123
+ end
124
+
125
+ BaseInspector = Struct.new(:object, :formatter) do
126
+ def self.can_inspect?(_object)
127
+ raise NotImplementedError
128
+ end
129
+
130
+ def inspect
131
+ raise NotImplementedError
132
+ end
133
+
134
+ def pretty_print(pp)
135
+ pp.text(inspect)
136
+ end
137
+ end
138
+
139
+ class TimeInspector < BaseInspector
140
+ FORMAT = "%Y-%m-%d %H:%M:%S"
141
+
142
+ def self.can_inspect?(object)
143
+ Time === object
144
+ end
145
+
146
+ if Time.method_defined?(:nsec)
147
+ def inspect
148
+ object.strftime("#{FORMAT}.#{"%09d" % object.nsec} %z")
149
+ end
150
+ else # for 1.8.7
151
+ def inspect
152
+ object.strftime("#{FORMAT}.#{"%06d" % object.usec} %z")
153
+ end
154
+ end
155
+ end
156
+
157
+ class DateTimeInspector < BaseInspector
158
+ FORMAT = "%a, %d %b %Y %H:%M:%S.%N %z"
159
+
160
+ def self.can_inspect?(object)
161
+ defined?(DateTime) && DateTime === object
162
+ end
163
+
164
+ # ActiveSupport sometimes overrides inspect. If `ActiveSupport` is
165
+ # defined use a custom format string that includes more time precision.
166
+ def inspect
167
+ if defined?(ActiveSupport)
168
+ object.strftime(FORMAT)
169
+ else
170
+ object.inspect
171
+ end
172
+ end
173
+ end
174
+
175
+ class BigDecimalInspector < BaseInspector
176
+ def self.can_inspect?(object)
177
+ defined?(BigDecimal) && BigDecimal === object
178
+ end
179
+
180
+ def inspect
181
+ "#{object.to_s('F')} (#{object.inspect})"
182
+ end
183
+ end
184
+
185
+ class DescribableMatcherInspector < BaseInspector
186
+ def self.can_inspect?(object)
187
+ Support.is_a_matcher?(object) && object.respond_to?(:description)
188
+ end
189
+
190
+ def inspect
191
+ object.description
192
+ end
193
+ end
194
+
195
+ class UninspectableObjectInspector < BaseInspector
196
+ OBJECT_ID_FORMAT = '%#016x'
197
+
198
+ def self.can_inspect?(object)
199
+ object.inspect
200
+ false
201
+ rescue NoMethodError
202
+ true
203
+ end
204
+
205
+ def inspect
206
+ "#<#{klass}:#{native_object_id}>"
207
+ end
208
+
209
+ def klass
210
+ Support.class_of(object)
211
+ end
212
+
213
+ # http://stackoverflow.com/a/2818916
214
+ def native_object_id
215
+ OBJECT_ID_FORMAT % (object.__id__ << 1)
216
+ rescue NoMethodError
217
+ # In Ruby 1.9.2, BasicObject responds to none of #__id__, #object_id, #id...
218
+ '-'
219
+ end
220
+ end
221
+
222
+ class DelegatorInspector < BaseInspector
223
+ def self.can_inspect?(object)
224
+ defined?(Delegator) && Delegator === object
225
+ end
226
+
227
+ def inspect
228
+ "#<#{object.class}(#{formatter.format(object.__getobj__)})>"
229
+ end
230
+ end
231
+
232
+ class InspectableObjectInspector < BaseInspector
233
+ def self.can_inspect?(object)
234
+ object.inspect
235
+ true
236
+ rescue NoMethodError
237
+ false
238
+ end
239
+
240
+ def inspect
241
+ object.inspect
242
+ end
243
+ end
244
+
245
+ INSPECTOR_CLASSES = [
246
+ TimeInspector,
247
+ DateTimeInspector,
248
+ BigDecimalInspector,
249
+ UninspectableObjectInspector,
250
+ DescribableMatcherInspector,
251
+ DelegatorInspector,
252
+ InspectableObjectInspector
253
+ ].tap do |classes|
254
+ # 2.4 has improved BigDecimal formatting so we do not need
255
+ # to provide our own.
256
+ # https://github.com/ruby/bigdecimal/pull/42
257
+ classes.delete(BigDecimalInspector) if RUBY_VERSION >= '2.4'
258
+ end
259
+
260
+ private
261
+
262
+ # Returns the substring defined by the start_index and end_index
263
+ # If the string ends with a partial ANSI code code then that
264
+ # will be removed as printing partial ANSI
265
+ # codes to the terminal can lead to corruption
266
+ def truncate_string(str, start_index, end_index)
267
+ cut_str = str[start_index..end_index]
268
+
269
+ # ANSI color codes are like: \e[33m so anything with \e[ and a
270
+ # number without a 'm' is an incomplete color code
271
+ cut_str.sub(/\e\[\d+$/, '')
272
+ end
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,76 @@
1
+ module RSpec
2
+ module Support
3
+ # Provides recursive constant lookup methods useful for
4
+ # constant stubbing.
5
+ module RecursiveConstMethods
6
+ # We only want to consider constants that are defined directly on a
7
+ # particular module, and not include top-level/inherited constants.
8
+ # Unfortunately, the constant API changed between 1.8 and 1.9, so
9
+ # we need to conditionally define methods to ignore the top-level/inherited
10
+ # constants.
11
+ #
12
+ # Given:
13
+ # class A; B = 1; end
14
+ # class C < A; end
15
+ #
16
+ # On 1.8:
17
+ # - C.const_get("Hash") # => ::Hash
18
+ # - C.const_defined?("Hash") # => false
19
+ # - C.constants # => ["B"]
20
+ # - None of these methods accept the extra `inherit` argument
21
+ # On 1.9:
22
+ # - C.const_get("Hash") # => ::Hash
23
+ # - C.const_defined?("Hash") # => true
24
+ # - C.const_get("Hash", false) # => raises NameError
25
+ # - C.const_defined?("Hash", false) # => false
26
+ # - C.constants # => [:B]
27
+ # - C.constants(false) #=> []
28
+ if Module.method(:const_defined?).arity == 1
29
+ def const_defined_on?(mod, const_name)
30
+ mod.const_defined?(const_name)
31
+ end
32
+
33
+ def get_const_defined_on(mod, const_name)
34
+ return mod.const_get(const_name) if const_defined_on?(mod, const_name)
35
+
36
+ raise NameError, "uninitialized constant #{mod.name}::#{const_name}"
37
+ end
38
+
39
+ def constants_defined_on(mod)
40
+ mod.constants.select { |c| const_defined_on?(mod, c) }
41
+ end
42
+ else
43
+ def const_defined_on?(mod, const_name)
44
+ mod.const_defined?(const_name, false)
45
+ end
46
+
47
+ def get_const_defined_on(mod, const_name)
48
+ mod.const_get(const_name, false)
49
+ end
50
+
51
+ def constants_defined_on(mod)
52
+ mod.constants(false)
53
+ end
54
+ end
55
+
56
+ def recursive_const_get(const_name)
57
+ normalize_const_name(const_name).split('::').inject(Object) do |mod, name|
58
+ get_const_defined_on(mod, name)
59
+ end
60
+ end
61
+
62
+ def recursive_const_defined?(const_name)
63
+ parts = normalize_const_name(const_name).split('::')
64
+ parts.inject([Object, '']) do |(mod, full_name), name|
65
+ yield(full_name, name) if block_given? && !(Module === mod)
66
+ return false unless const_defined_on?(mod, name)
67
+ [get_const_defined_on(mod, name), [mod.name, name].join('::')]
68
+ end
69
+ end
70
+
71
+ def normalize_const_name(const_name)
72
+ const_name.sub(/\A::/, '')
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,53 @@
1
+ module RSpec
2
+ module Support
3
+ # Allows a thread to lock out other threads from a critical section of code,
4
+ # while allowing the thread with the lock to reenter that section.
5
+ #
6
+ # Based on Monitor as of 2.2 -
7
+ # https://github.com/ruby/ruby/blob/eb7ddaa3a47bf48045d26c72eb0f263a53524ebc/lib/monitor.rb#L9
8
+ #
9
+ # Depends on Mutex, but Mutex is only available as part of core since 1.9.1:
10
+ # exists - http://ruby-doc.org/core-1.9.1/Mutex.html
11
+ # dne - http://ruby-doc.org/core-1.9.0/Mutex.html
12
+ #
13
+ # @private
14
+ class ReentrantMutex
15
+ def initialize
16
+ @owner = nil
17
+ @count = 0
18
+ @mutex = Mutex.new
19
+ end
20
+
21
+ def synchronize
22
+ enter
23
+ yield
24
+ ensure
25
+ exit
26
+ end
27
+
28
+ private
29
+
30
+ def enter
31
+ @mutex.lock if @owner != Thread.current
32
+ @owner = Thread.current
33
+ @count += 1
34
+ end
35
+
36
+ def exit
37
+ @count -= 1
38
+ return unless @count == 0
39
+ @owner = nil
40
+ @mutex.unlock
41
+ end
42
+ end
43
+
44
+ if defined? ::Mutex
45
+ # On 1.9 and up, this is in core, so we just use the real one
46
+ Mutex = ::Mutex
47
+ else # For 1.8.7
48
+ # :nocov:
49
+ RSpec::Support.require_rspec_support "mutex"
50
+ # :nocov:
51
+ end
52
+ end
53
+ end