sspec-support 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Changelog.md +242 -0
- data/LICENSE.md +23 -0
- data/README.md +40 -0
- data/lib/rspec/support.rb +149 -0
- data/lib/rspec/support/caller_filter.rb +83 -0
- data/lib/rspec/support/comparable_version.rb +46 -0
- data/lib/rspec/support/differ.rb +215 -0
- data/lib/rspec/support/directory_maker.rb +63 -0
- data/lib/rspec/support/encoded_string.rb +165 -0
- data/lib/rspec/support/fuzzy_matcher.rb +48 -0
- data/lib/rspec/support/hunk_generator.rb +47 -0
- data/lib/rspec/support/matcher_definition.rb +42 -0
- data/lib/rspec/support/method_signature_verifier.rb +426 -0
- data/lib/rspec/support/mutex.rb +73 -0
- data/lib/rspec/support/object_formatter.rb +275 -0
- data/lib/rspec/support/recursive_const_methods.rb +76 -0
- data/lib/rspec/support/reentrant_mutex.rb +53 -0
- data/lib/rspec/support/ruby_features.rb +176 -0
- data/lib/rspec/support/source.rb +75 -0
- data/lib/rspec/support/source/location.rb +21 -0
- data/lib/rspec/support/source/node.rb +110 -0
- data/lib/rspec/support/source/token.rb +87 -0
- data/lib/rspec/support/spec.rb +81 -0
- data/lib/rspec/support/spec/deprecation_helpers.rb +64 -0
- data/lib/rspec/support/spec/formatting_support.rb +9 -0
- data/lib/rspec/support/spec/in_sub_process.rb +69 -0
- data/lib/rspec/support/spec/library_wide_checks.rb +150 -0
- data/lib/rspec/support/spec/shell_out.rb +84 -0
- data/lib/rspec/support/spec/stderr_splitter.rb +63 -0
- data/lib/rspec/support/spec/string_matcher.rb +46 -0
- data/lib/rspec/support/spec/with_isolated_directory.rb +13 -0
- data/lib/rspec/support/spec/with_isolated_stderr.rb +13 -0
- data/lib/rspec/support/version.rb +7 -0
- data/lib/rspec/support/warnings.rb +39 -0
- 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
|