mock-suey 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +342 -4
- data/lib/.rbnext/3.0/mock_suey/core.rb +246 -0
- data/lib/.rbnext/3.0/mock_suey/ext/instance_class.rb +22 -0
- data/lib/.rbnext/3.0/mock_suey/ext/rspec.rb +37 -0
- data/lib/.rbnext/3.0/mock_suey/mock_contract.rb +150 -0
- data/lib/.rbnext/3.0/mock_suey/rspec/mock_context.rb +129 -0
- data/lib/.rbnext/3.0/mock_suey/type_checks/ruby.rb +251 -0
- data/lib/.rbnext/3.1/mock_suey/core.rb +246 -0
- data/lib/.rbnext/3.1/mock_suey/mock_contract.rb +150 -0
- data/lib/.rbnext/3.1/mock_suey/rspec/mock_context.rb +129 -0
- data/lib/.rbnext/3.1/mock_suey/rspec/proxy_method_invoked.rb +47 -0
- data/lib/.rbnext/3.1/mock_suey/tracer.rb +173 -0
- data/lib/.rbnext/3.1/mock_suey/type_checks/ruby.rb +251 -0
- data/lib/mock_suey/core.rb +246 -0
- data/lib/mock_suey/ext/instance_class.rb +22 -0
- data/lib/mock_suey/ext/rspec.rb +37 -0
- data/lib/mock_suey/logging.rb +29 -0
- data/lib/mock_suey/method_call.rb +71 -0
- data/lib/mock_suey/mock_contract.rb +150 -0
- data/lib/mock_suey/rspec/mock_context.rb +129 -0
- data/lib/mock_suey/rspec/proxy_method_invoked.rb +47 -0
- data/lib/mock_suey/rspec.rb +60 -0
- data/lib/mock_suey/tracer.rb +173 -0
- data/lib/mock_suey/type_checks/ruby.rb +251 -0
- data/lib/mock_suey/type_checks.rb +8 -0
- data/lib/mock_suey/version.rb +1 -1
- data/lib/mock_suey.rb +15 -0
- metadata +27 -3
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
using RubyNext;
|
3
|
+
require "mock_suey/ext/instance_class"
|
4
|
+
|
5
|
+
module MockSuey
|
6
|
+
class MockContract
|
7
|
+
using Ext::InstanceClass
|
8
|
+
|
9
|
+
class Error < StandardError
|
10
|
+
attr_reader :contract
|
11
|
+
|
12
|
+
def initialize(contract, msg)
|
13
|
+
@contract = contract
|
14
|
+
super(msg)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def captured_calls_message(calls)
|
20
|
+
calls.map do |call|
|
21
|
+
contract.args_pattern.map.with_index do |arg, i|
|
22
|
+
(ANYTHING == arg) ? "_" : call.arguments[i].inspect
|
23
|
+
end.join(", ").then do |args_desc|
|
24
|
+
" (#{args_desc}) -> #{call.return_value.class}"
|
25
|
+
end
|
26
|
+
end.uniq.join("\n")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class NoMethodCalls < Error
|
31
|
+
def initialize(contract)
|
32
|
+
super(
|
33
|
+
contract,
|
34
|
+
"Mock contract verification failed:\n" \
|
35
|
+
" No method calls captured for #{contract.method_desc}"
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class NoMatchingMethodCalls < Error
|
41
|
+
def initialize(contract, real_calls)
|
42
|
+
@contract = contract
|
43
|
+
super(
|
44
|
+
contract,
|
45
|
+
"Mock contract verification failed:\n" \
|
46
|
+
" No matching calls captured for #{contract.inspect}.\n" \
|
47
|
+
" Captured call patterns:\n" \
|
48
|
+
"#{captured_calls_message(real_calls)}"
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class NoMatchingReturnType < Error
|
54
|
+
def initialize(contract, real_calls)
|
55
|
+
@contract = contract
|
56
|
+
super(
|
57
|
+
contract,
|
58
|
+
"Mock contract verification failed:\n" \
|
59
|
+
" No calls with the expected return type captured for #{contract.inspect}.\n" \
|
60
|
+
" Captured call patterns:\n" \
|
61
|
+
"#{captured_calls_message(real_calls)}"
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
ANYTHING = Object.new.freeze
|
67
|
+
|
68
|
+
def self.from_stub(call_obj)
|
69
|
+
(__m__ = call_obj) && ((((receiver_class, method_name, return_value) = nil) || (__m__.respond_to?(:deconstruct_keys) && (((__m_hash__ = __m__.deconstruct_keys([:receiver_class, :method_name, :return_value])) || true) && (Hash === __m_hash__ || Kernel.raise(TypeError, "#deconstruct_keys must return Hash"))) && (((__m_hash__.key?(:receiver_class) && __m_hash__.key?(:method_name)) && __m_hash__.key?(:return_value)) && (((receiver_class = __m_hash__[:receiver_class]) || true) && (((method_name = __m_hash__[:method_name]) || true) && ((return_value = __m_hash__[:return_value]) || true)))))) || Kernel.raise(NoMatchingPatternError, __m__.inspect))
|
70
|
+
|
71
|
+
args_pattern = call_obj.arguments.map do
|
72
|
+
contractable_arg?(_1) ? _1 : ANYTHING
|
73
|
+
end
|
74
|
+
|
75
|
+
new(
|
76
|
+
receiver_class: receiver_class,
|
77
|
+
method_name: method_name,
|
78
|
+
args_pattern: args_pattern,
|
79
|
+
return_type: return_value.class
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.contractable_arg?(val)
|
84
|
+
case val
|
85
|
+
when TrueClass, FalseClass, Numeric, String, Regexp, NilClass
|
86
|
+
true
|
87
|
+
when Array
|
88
|
+
val.all? { |v| contractable_arg?(v) }
|
89
|
+
when Hash
|
90
|
+
contractable_arg?(val.values)
|
91
|
+
else
|
92
|
+
false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
attr_reader :receiver_class, :method_name,
|
97
|
+
:args_pattern, :return_type
|
98
|
+
|
99
|
+
def initialize(receiver_class:, method_name:, args_pattern:, return_type:)
|
100
|
+
@receiver_class = receiver_class
|
101
|
+
@method_name = method_name
|
102
|
+
@args_pattern = args_pattern
|
103
|
+
@return_type = return_type
|
104
|
+
end
|
105
|
+
|
106
|
+
def verify!(calls)
|
107
|
+
return if noop?
|
108
|
+
|
109
|
+
raise NoMethodCalls.new(self) if calls.nil? || calls.empty?
|
110
|
+
|
111
|
+
matching_input_calls = calls.select { matching_args?(_1) }
|
112
|
+
raise NoMatchingMethodCalls.new(self, calls) if matching_input_calls.empty?
|
113
|
+
|
114
|
+
matching_input_calls.each do
|
115
|
+
return if _1.return_value.class <= return_type
|
116
|
+
end
|
117
|
+
|
118
|
+
raise NoMatchingReturnType.new(self, matching_input_calls)
|
119
|
+
end
|
120
|
+
|
121
|
+
def noop? ; args_pattern.all? { _1 == ANYTHING }; end
|
122
|
+
|
123
|
+
def method_desc
|
124
|
+
delimeter = receiver_class.singleton_class? ? "." : "#"
|
125
|
+
|
126
|
+
"#{receiver_class.instance_class_name}#{delimeter}#{method_name}"
|
127
|
+
end
|
128
|
+
|
129
|
+
def inspect
|
130
|
+
args_pattern.map do
|
131
|
+
(_1 == ANYTHING) ? "_" : _1.inspect
|
132
|
+
end.join(", ").then do |args_desc|
|
133
|
+
"#{method_desc}: (#{args_desc}) -> #{return_type}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def matching_args?(call)
|
140
|
+
args_pattern.each.with_index do |arg, i|
|
141
|
+
next if arg == ANYTHING
|
142
|
+
# Use case-eq here to make it possible to use composed
|
143
|
+
# matchers in the future
|
144
|
+
return false unless arg === call.arguments[i]
|
145
|
+
end
|
146
|
+
|
147
|
+
true
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
using RubyNext;
|
3
|
+
require "mock_suey/ext/rspec"
|
4
|
+
require "mock_suey/ext/instance_class"
|
5
|
+
|
6
|
+
module MockSuey
|
7
|
+
module RSpec
|
8
|
+
# Special type of shared context for mocks.
|
9
|
+
# The main difference is that it can track mocked classes and methods.
|
10
|
+
module MockContext
|
11
|
+
NAMESPACE = "mock::"
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_writer :context_namespace
|
15
|
+
|
16
|
+
def context_namespace ; @context_namespace || NAMESPACE; end
|
17
|
+
|
18
|
+
def collector ; @collector ||= MocksCollector.new; end
|
19
|
+
|
20
|
+
def registry ; collector.registry; end
|
21
|
+
end
|
22
|
+
|
23
|
+
class MocksCollector
|
24
|
+
using Ext::RSpec
|
25
|
+
using Ext::InstanceClass
|
26
|
+
|
27
|
+
# Registry contains all identified mocks/stubs in a form:
|
28
|
+
# Hash[Class: Hash[Symbol method_name, Array[MethodCall]]
|
29
|
+
attr_reader :registry
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
@registry = Hash.new { |h, k| h[k] = {} }
|
33
|
+
@mocks = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def watch(context_id)
|
37
|
+
return if mocks.key?(context_id)
|
38
|
+
|
39
|
+
mocks[context_id] = true
|
40
|
+
|
41
|
+
evaluate_context!(context_id)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :mocks
|
47
|
+
|
48
|
+
def evaluate_context!(context_id)
|
49
|
+
store = registry
|
50
|
+
|
51
|
+
Class.new(::RSpec::Core::ExampleGroup) do
|
52
|
+
def self.metadata ; {}; end
|
53
|
+
|
54
|
+
def self.filtered_examples ; examples; end
|
55
|
+
|
56
|
+
::RSpec::Core::MemoizedHelpers.define_helpers_on(self)
|
57
|
+
|
58
|
+
include_context(context_id)
|
59
|
+
|
60
|
+
specify("true") { expect(true).to be(true) }
|
61
|
+
|
62
|
+
after do
|
63
|
+
::RSpec::Mocks.space.proxies.values.each do |proxy|
|
64
|
+
proxy.method_doubles.values.each do |double|
|
65
|
+
method_name = double.method_name
|
66
|
+
receiver_class = proxy.target_class
|
67
|
+
|
68
|
+
# Simple doubles don't have targets
|
69
|
+
next unless receiver_class
|
70
|
+
|
71
|
+
# TODO: Make conversion customizable (see proxy_method_invoked)
|
72
|
+
if method_name == :new && receiver_class.singleton_class?
|
73
|
+
receiver_class, method_name = receiver_class.instance_class, :initialize
|
74
|
+
end
|
75
|
+
|
76
|
+
expected_calls = store[receiver_class][method_name] = []
|
77
|
+
|
78
|
+
double.stubs.each do |stub|
|
79
|
+
arguments =
|
80
|
+
if (__m__ = stub.expected_args) && (__m__.respond_to?(:deconstruct) && (((__m_arr__ = __m__.deconstruct) || true) && (Array === __m_arr__ || Kernel.raise(TypeError, "#deconstruct must return Array"))) && (1 == __m_arr__.size) && (::RSpec::Mocks::ArgumentMatchers::NoArgsMatcher === __m_arr__[0]))
|
81
|
+
[]
|
82
|
+
else
|
83
|
+
stub.expected_args
|
84
|
+
end
|
85
|
+
|
86
|
+
return_value = stub.implementation.terminal_action.call
|
87
|
+
|
88
|
+
expected_calls << MethodCall.new(
|
89
|
+
receiver_class: receiver_class,
|
90
|
+
method_name: method_name,
|
91
|
+
arguments: arguments,
|
92
|
+
return_value: return_value
|
93
|
+
)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end.run
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
module DSL
|
103
|
+
def mock_context(name, **opts, &block)
|
104
|
+
::RSpec.shared_context("#{MockContext.context_namespace}#{name}", **opts, &block)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
module ExampleGroup
|
109
|
+
def include_mock_context(name)
|
110
|
+
context_id = "#{MockContext.context_namespace}#{name}"
|
111
|
+
|
112
|
+
MockContext.collector.watch(context_id)
|
113
|
+
include_context(context_id)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Extending RSpec
|
121
|
+
RSpec.extend(MockSuey::RSpec::MockContext::DSL)
|
122
|
+
|
123
|
+
if RSpec.configuration.expose_dsl_globally?
|
124
|
+
Object.include(MockSuey::RSpec::MockContext::DSL)
|
125
|
+
Module.extend(MockSuey::RSpec::MockContext::DSL)
|
126
|
+
end
|
127
|
+
|
128
|
+
RSpec::Core::ExampleGroup.extend(MockSuey::RSpec::MockContext::ExampleGroup)
|
129
|
+
RSpec::Core::ExampleGroup.extend(MockSuey::RSpec::MockContext::DSL)
|
@@ -0,0 +1,251 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
gem "rbs", "~> 2.0"
|
4
|
+
require "rbs"
|
5
|
+
require "rbs/test"
|
6
|
+
|
7
|
+
require "set"
|
8
|
+
require "pathname"
|
9
|
+
|
10
|
+
require "mock_suey/ext/instance_class"
|
11
|
+
|
12
|
+
module MockSuey
|
13
|
+
module TypeChecks
|
14
|
+
using Ext::InstanceClass
|
15
|
+
|
16
|
+
class Ruby
|
17
|
+
class SignatureGenerator
|
18
|
+
attr_reader :klass, :method_calls, :constants, :singleton
|
19
|
+
alias_method :singleton?, :singleton
|
20
|
+
|
21
|
+
def initialize(klass, method_calls)
|
22
|
+
@klass = klass
|
23
|
+
@singleton = klass.singleton_class?
|
24
|
+
@method_calls = method_calls
|
25
|
+
@constants = Set.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_rbs
|
29
|
+
[
|
30
|
+
header,
|
31
|
+
*method_calls.map { |name, calls| method_sig(name, calls) },
|
32
|
+
footer
|
33
|
+
].join("\n")
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def header
|
39
|
+
nesting_parts = klass.instance_class_name.split("::")
|
40
|
+
|
41
|
+
base = Kernel
|
42
|
+
nesting = 0
|
43
|
+
|
44
|
+
lines = []
|
45
|
+
|
46
|
+
nesting_parts.map do |const|
|
47
|
+
base = base.const_get(const)
|
48
|
+
lines << "#{" " * nesting}#{base.is_a?(Class) ? "class" : "module"} #{const}"
|
49
|
+
nesting += 1
|
50
|
+
end
|
51
|
+
|
52
|
+
@nesting = nesting_parts.size
|
53
|
+
|
54
|
+
lines.join("\n")
|
55
|
+
end
|
56
|
+
|
57
|
+
def footer
|
58
|
+
@nesting.times.map do |n|
|
59
|
+
"#{" " * (@nesting - n - 1)}end"
|
60
|
+
end.join("\n")
|
61
|
+
end
|
62
|
+
|
63
|
+
def method_sig(name, calls)
|
64
|
+
"#{" " * @nesting}def #{singleton? ? "self." : ""}#{name}: (#{[args_sig(calls.map(&:pos_args)), kwargs_sig(calls.map(&:kwargs))].compact.join(", ")}) -> (#{return_sig(name, calls.map(&:return_value))})"
|
65
|
+
end
|
66
|
+
|
67
|
+
def args_sig(args)
|
68
|
+
return if args.all?(&:empty?)
|
69
|
+
|
70
|
+
args.transpose.map do |arg_values|
|
71
|
+
arg_values.map(&:class).uniq.map do
|
72
|
+
constants << _1
|
73
|
+
"::#{_1.name}"
|
74
|
+
end
|
75
|
+
end.join(", ")
|
76
|
+
end
|
77
|
+
|
78
|
+
def kwargs_sig(kwargs)
|
79
|
+
return if kwargs.all?(&:empty?)
|
80
|
+
|
81
|
+
key_values = kwargs.each_with_object(Hash.new { |h, k| h[k] = [] }) { |pairs, acc|
|
82
|
+
pairs.each { acc[_1] << _2 }
|
83
|
+
}
|
84
|
+
|
85
|
+
key_values.map do |key, values|
|
86
|
+
values_sig = values.map(&:class).uniq.map do
|
87
|
+
constants << _1
|
88
|
+
"::#{_1.name}"
|
89
|
+
end.join(" | ")
|
90
|
+
|
91
|
+
"?#{key}: (#{values_sig})"
|
92
|
+
end.join(", ")
|
93
|
+
end
|
94
|
+
|
95
|
+
def return_sig(name, values)
|
96
|
+
# Special case
|
97
|
+
return "self" if name == :initialize
|
98
|
+
|
99
|
+
values.map(&:class).uniq.map do
|
100
|
+
constants << _1
|
101
|
+
"::#{_1.name}"
|
102
|
+
end.join(" | ")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def initialize(load_dirs: [])
|
107
|
+
@load_dirs = Array(load_dirs)
|
108
|
+
end
|
109
|
+
|
110
|
+
def typecheck!(call_obj, raise_on_missing: false)
|
111
|
+
method_name = call_obj.method_name
|
112
|
+
|
113
|
+
method_call = RBS::Test::ArgumentsReturn.return(
|
114
|
+
arguments: call_obj.arguments,
|
115
|
+
value: call_obj.return_value
|
116
|
+
)
|
117
|
+
|
118
|
+
call_trace = RBS::Test::CallTrace.new(
|
119
|
+
method_name: method_name,
|
120
|
+
method_call: method_call,
|
121
|
+
# TODO: blocks support
|
122
|
+
block_calls: [],
|
123
|
+
block_given: false
|
124
|
+
)
|
125
|
+
|
126
|
+
method_type = type_for(call_obj.receiver_class, method_name)
|
127
|
+
|
128
|
+
unless method_type
|
129
|
+
raise MissingSignature, "No signature found for #{call_obj.method_desc}" if raise_on_missing
|
130
|
+
return
|
131
|
+
end
|
132
|
+
|
133
|
+
self_class = call_obj.receiver_class
|
134
|
+
instance_class = call_obj.receiver_class
|
135
|
+
class_class = call_obj.receiver_class.singleton_class? ? call_obj.receiver_class : call_obj.receiver_class.singleton_class
|
136
|
+
|
137
|
+
typecheck = RBS::Test::TypeCheck.new(
|
138
|
+
self_class: self_class,
|
139
|
+
builder: builder,
|
140
|
+
sample_size: 100, # What should be the value here?
|
141
|
+
unchecked_classes: [],
|
142
|
+
instance_class: instance_class,
|
143
|
+
class_class: class_class
|
144
|
+
)
|
145
|
+
|
146
|
+
errors = []
|
147
|
+
typecheck.overloaded_call(
|
148
|
+
method_type,
|
149
|
+
"#{self_class.singleton_class? ? "." : "#"}#{method_name}",
|
150
|
+
call_trace,
|
151
|
+
errors: errors
|
152
|
+
)
|
153
|
+
|
154
|
+
reject_returned_doubles!(errors)
|
155
|
+
|
156
|
+
# TODO: Use custom error class
|
157
|
+
raise RBS::Test::Tester::TypeError.new(errors) unless errors.empty?
|
158
|
+
end
|
159
|
+
|
160
|
+
def load_signatures_from_calls(calls)
|
161
|
+
constants = Set.new
|
162
|
+
|
163
|
+
calls.group_by(&:receiver_class).each do |klass, klass_calls|
|
164
|
+
calls_per_method = klass_calls.group_by(&:method_name)
|
165
|
+
generator = SignatureGenerator.new(klass, calls_per_method)
|
166
|
+
|
167
|
+
generator.to_rbs.then do |rbs|
|
168
|
+
MockSuey.logger.debug "Generated RBS for #{klass.instance_class_name}:\n#{rbs}\n"
|
169
|
+
load_rbs(rbs)
|
170
|
+
end
|
171
|
+
|
172
|
+
constants |= generator.constants
|
173
|
+
end
|
174
|
+
|
175
|
+
constants.each do |const|
|
176
|
+
next if type_defined?(const)
|
177
|
+
|
178
|
+
SignatureGenerator.new(const, {}).to_rbs.then do |rbs|
|
179
|
+
MockSuey.logger.debug "Generated RBS for constant #{const.instance_class_name}:\n#{rbs}\n"
|
180
|
+
load_rbs(rbs)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
def load_rbs(rbs)
|
188
|
+
::RBS::Parser.parse_signature(rbs).then do |declarations|
|
189
|
+
declarations.each do |decl|
|
190
|
+
env << decl
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def env
|
196
|
+
return @env if instance_variable_defined?(:@env)
|
197
|
+
|
198
|
+
loader = RBS::EnvironmentLoader.new
|
199
|
+
@load_dirs&.each { loader.add(path: Pathname(_1)) }
|
200
|
+
@env = RBS::Environment.from_loader(loader).resolve_type_names
|
201
|
+
end
|
202
|
+
|
203
|
+
def builder ; @builder ||= RBS::DefinitionBuilder.new(env: env); end
|
204
|
+
|
205
|
+
def type_for(klass, method_name)
|
206
|
+
type = type_for_class(klass.instance_class)
|
207
|
+
return unless env.class_decls[type]
|
208
|
+
|
209
|
+
decl = klass.singleton_class? ? builder.build_singleton(type) : builder.build_instance(type)
|
210
|
+
|
211
|
+
decl.methods[method_name]
|
212
|
+
end
|
213
|
+
|
214
|
+
def type_for_class(klass)
|
215
|
+
*path, name = *klass.instance_class_name.split("::").map(&:to_sym)
|
216
|
+
|
217
|
+
namespace = path.empty? ? RBS::Namespace.root : RBS::Namespace.new(absolute: true, path: path)
|
218
|
+
|
219
|
+
RBS::TypeName.new(name: name, namespace: namespace)
|
220
|
+
end
|
221
|
+
|
222
|
+
def type_defined?(klass)
|
223
|
+
!env.class_decls[type_for_class(klass.instance_class)].nil?
|
224
|
+
end
|
225
|
+
|
226
|
+
def reject_returned_doubles!(errors)
|
227
|
+
return unless defined?(::RSpec::Core)
|
228
|
+
|
229
|
+
errors.reject! do |error|
|
230
|
+
case error
|
231
|
+
in RBS::Test::Errors::ReturnTypeError[
|
232
|
+
type:,
|
233
|
+
value: ::RSpec::Mocks::InstanceVerifyingDouble => double
|
234
|
+
]
|
235
|
+
return_class = type.instance_of?(RBS::Types::Bases::Self) ? error.klass : type.name
|
236
|
+
return_type = return_class.to_s.gsub(/^::/, "")
|
237
|
+
double_type = double.instance_variable_get(:@doubled_module).target.to_s
|
238
|
+
|
239
|
+
double_type == return_type
|
240
|
+
in RBS::Test::Errors::ReturnTypeError[
|
241
|
+
value: ::RSpec::Mocks::Double
|
242
|
+
]
|
243
|
+
true
|
244
|
+
else
|
245
|
+
false
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|