forked from rspec/rspec-support
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsupport.rb
162 lines (144 loc) · 5.34 KB
/
support.rb
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
# frozen_string_literal: true
module RSpec
module Support
# @api private
#
# Defines a helper method that is optimized to require files from the
# named lib. The passed block MUST be `{ |f| require_relative f }`
# because for `require_relative` to work properly from within the named
# lib the line of code must be IN that lib.
#
# `require_relative` is preferred when available because it is always O(1),
# regardless of the number of dirs in $LOAD_PATH. `require`, on the other
# hand, does a linear O(N) search over the dirs in the $LOAD_PATH until
# it can resolve the file relative to one of the dirs.
def self.define_optimized_require_for_rspec(lib, &require_relative)
name = "require_rspec_#{lib}"
if RUBY_PLATFORM == 'java' && !Kernel.respond_to?(:require)
# JRuby 9.1.17.0 has developed a regression for require
(class << self; self; end).__send__(:define_method, name) do |f|
Kernel.send(:require, "rspec/#{lib}/#{f}")
end
elsif Kernel.respond_to?(:require_relative)
(class << self; self; end).__send__(:define_method, name) do |f|
require_relative.call("#{lib}/#{f}")
end
else
(class << self; self; end).__send__(:define_method, name) do |f|
require "rspec/#{lib}/#{f}"
end
end
end
define_optimized_require_for_rspec(:support) { |f| require_relative(f) }
require_rspec_support "version"
require_rspec_support "ruby_features"
# @api private
KERNEL_METHOD_METHOD = ::Kernel.instance_method(:method)
# @api private
#
# Used internally to get a method handle for a particular object
# and method name.
#
# Includes handling for a few special cases:
#
# - Objects that redefine #method (e.g. an HTTPRequest struct)
# - BasicObject subclasses that mixin a Kernel dup (e.g. SimpleDelegator)
# - Objects that undefine method and delegate everything to another
# object (e.g. Mongoid association objects)
if RubyFeatures.supports_rebinding_module_methods?
def self.method_handle_for(object, method_name)
KERNEL_METHOD_METHOD.bind(object).call(method_name)
rescue NameError => original
begin
handle = object.method(method_name)
raise original unless handle.is_a? Method
handle
rescue Support::AllExceptionsExceptOnesWeMustNotRescue
raise original
end
end
else
def self.method_handle_for(object, method_name)
if ::Kernel === object
KERNEL_METHOD_METHOD.bind(object).call(method_name)
else
object.method(method_name)
end
rescue NameError => original
begin
handle = object.method(method_name)
raise original unless handle.is_a? Method
handle
rescue Support::AllExceptionsExceptOnesWeMustNotRescue
raise original
end
end
end
# @api private
#
# Used internally to get a class of a given object, even if it does not respond to #class.
def self.class_of(object)
object.class
rescue NoMethodError
singleton_class = class << object; self; end
singleton_class.ancestors.find { |ancestor| !ancestor.equal?(singleton_class) }
end
# A single thread local variable so we don't excessively pollute that namespace.
if RUBY_VERSION.to_f >= 2
def self.thread_local_data
Thread.current.thread_variable_get(:__rspec) || Thread.current.thread_variable_set(:__rspec, {})
end
else
def self.thread_local_data
Thread.current[:__rspec] ||= {}
end
end
# @api private
def self.failure_notifier=(callable)
thread_local_data[:failure_notifier] = callable
end
# @private
DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure }
# @api private
def self.failure_notifier
thread_local_data[:failure_notifier] || DEFAULT_FAILURE_NOTIFIER
end
# @api private
def self.notify_failure(failure, options={})
failure_notifier.call(failure, options)
end
# @api private
def self.with_failure_notifier(callable)
orig_notifier = failure_notifier
self.failure_notifier = callable
yield
ensure
self.failure_notifier = orig_notifier
end
class << self
# @api private
attr_writer :warning_notifier
end
# @private
DEFAULT_WARNING_NOTIFIER = lambda { |warning| ::Kernel.warn warning }
# @api private
def self.warning_notifier
@warning_notifier ||= DEFAULT_WARNING_NOTIFIER
end
# @private
module AllExceptionsExceptOnesWeMustNotRescue
# These exceptions are dangerous to rescue as rescuing them
# would interfere with things we should not interfere with.
AVOID_RESCUING = [NoMemoryError, SignalException, Interrupt, SystemExit]
def self.===(exception)
AVOID_RESCUING.none? { |ar| ar === exception }
end
end
# The Differ is only needed when a spec fails with a diffable failure.
# In the more common case of all specs passing or the only failures being
# non-diffable, we can avoid the extra cost of loading the differ, diff-lcs,
# pp, etc by avoiding an unnecessary require. Instead, autoload will take
# care of loading the differ on first use.
autoload :Differ, "rspec/support/differ"
end
end