rbs 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ruby.yml +28 -0
  3. data/.gitignore +12 -0
  4. data/.rubocop.yml +15 -0
  5. data/BSDL +22 -0
  6. data/CHANGELOG.md +9 -0
  7. data/COPYING +56 -0
  8. data/Gemfile +6 -0
  9. data/README.md +93 -0
  10. data/Rakefile +142 -0
  11. data/bin/annotate-with-rdoc +157 -0
  12. data/bin/console +14 -0
  13. data/bin/query-rdoc +103 -0
  14. data/bin/setup +10 -0
  15. data/bin/sort +89 -0
  16. data/bin/test_runner.rb +16 -0
  17. data/docs/CONTRIBUTING.md +97 -0
  18. data/docs/sigs.md +148 -0
  19. data/docs/stdlib.md +152 -0
  20. data/docs/syntax.md +528 -0
  21. data/exe/rbs +7 -0
  22. data/lib/rbs.rb +64 -0
  23. data/lib/rbs/ast/annotation.rb +27 -0
  24. data/lib/rbs/ast/comment.rb +27 -0
  25. data/lib/rbs/ast/declarations.rb +395 -0
  26. data/lib/rbs/ast/members.rb +362 -0
  27. data/lib/rbs/buffer.rb +50 -0
  28. data/lib/rbs/builtin_names.rb +55 -0
  29. data/lib/rbs/cli.rb +558 -0
  30. data/lib/rbs/constant.rb +26 -0
  31. data/lib/rbs/constant_table.rb +150 -0
  32. data/lib/rbs/definition.rb +170 -0
  33. data/lib/rbs/definition_builder.rb +919 -0
  34. data/lib/rbs/environment.rb +281 -0
  35. data/lib/rbs/environment_loader.rb +136 -0
  36. data/lib/rbs/environment_walker.rb +124 -0
  37. data/lib/rbs/errors.rb +187 -0
  38. data/lib/rbs/location.rb +102 -0
  39. data/lib/rbs/method_type.rb +123 -0
  40. data/lib/rbs/namespace.rb +91 -0
  41. data/lib/rbs/parser.y +1344 -0
  42. data/lib/rbs/prototype/rb.rb +553 -0
  43. data/lib/rbs/prototype/rbi.rb +587 -0
  44. data/lib/rbs/prototype/runtime.rb +381 -0
  45. data/lib/rbs/substitution.rb +46 -0
  46. data/lib/rbs/test.rb +26 -0
  47. data/lib/rbs/test/errors.rb +61 -0
  48. data/lib/rbs/test/hook.rb +294 -0
  49. data/lib/rbs/test/setup.rb +58 -0
  50. data/lib/rbs/test/spy.rb +325 -0
  51. data/lib/rbs/test/test_helper.rb +183 -0
  52. data/lib/rbs/test/type_check.rb +254 -0
  53. data/lib/rbs/type_name.rb +70 -0
  54. data/lib/rbs/types.rb +936 -0
  55. data/lib/rbs/variance_calculator.rb +138 -0
  56. data/lib/rbs/vendorer.rb +47 -0
  57. data/lib/rbs/version.rb +3 -0
  58. data/lib/rbs/writer.rb +269 -0
  59. data/lib/ruby/signature.rb +7 -0
  60. data/rbs.gemspec +46 -0
  61. data/stdlib/abbrev/abbrev.rbs +60 -0
  62. data/stdlib/base64/base64.rbs +71 -0
  63. data/stdlib/benchmark/benchmark.rbs +372 -0
  64. data/stdlib/builtin/array.rbs +1997 -0
  65. data/stdlib/builtin/basic_object.rbs +280 -0
  66. data/stdlib/builtin/binding.rbs +177 -0
  67. data/stdlib/builtin/builtin.rbs +45 -0
  68. data/stdlib/builtin/class.rbs +145 -0
  69. data/stdlib/builtin/comparable.rbs +116 -0
  70. data/stdlib/builtin/complex.rbs +400 -0
  71. data/stdlib/builtin/constants.rbs +37 -0
  72. data/stdlib/builtin/data.rbs +5 -0
  73. data/stdlib/builtin/deprecated.rbs +2 -0
  74. data/stdlib/builtin/dir.rbs +413 -0
  75. data/stdlib/builtin/encoding.rbs +607 -0
  76. data/stdlib/builtin/enumerable.rbs +404 -0
  77. data/stdlib/builtin/enumerator.rbs +260 -0
  78. data/stdlib/builtin/errno.rbs +781 -0
  79. data/stdlib/builtin/errors.rbs +582 -0
  80. data/stdlib/builtin/exception.rbs +194 -0
  81. data/stdlib/builtin/false_class.rbs +40 -0
  82. data/stdlib/builtin/fiber.rbs +68 -0
  83. data/stdlib/builtin/fiber_error.rbs +12 -0
  84. data/stdlib/builtin/file.rbs +1076 -0
  85. data/stdlib/builtin/file_test.rbs +59 -0
  86. data/stdlib/builtin/float.rbs +696 -0
  87. data/stdlib/builtin/gc.rbs +243 -0
  88. data/stdlib/builtin/hash.rbs +1029 -0
  89. data/stdlib/builtin/integer.rbs +707 -0
  90. data/stdlib/builtin/io.rbs +683 -0
  91. data/stdlib/builtin/kernel.rbs +576 -0
  92. data/stdlib/builtin/marshal.rbs +161 -0
  93. data/stdlib/builtin/match_data.rbs +271 -0
  94. data/stdlib/builtin/math.rbs +369 -0
  95. data/stdlib/builtin/method.rbs +185 -0
  96. data/stdlib/builtin/module.rbs +1104 -0
  97. data/stdlib/builtin/nil_class.rbs +82 -0
  98. data/stdlib/builtin/numeric.rbs +409 -0
  99. data/stdlib/builtin/object.rbs +824 -0
  100. data/stdlib/builtin/proc.rbs +429 -0
  101. data/stdlib/builtin/process.rbs +1227 -0
  102. data/stdlib/builtin/random.rbs +267 -0
  103. data/stdlib/builtin/range.rbs +226 -0
  104. data/stdlib/builtin/rational.rbs +424 -0
  105. data/stdlib/builtin/rb_config.rbs +57 -0
  106. data/stdlib/builtin/regexp.rbs +1083 -0
  107. data/stdlib/builtin/ruby_vm.rbs +14 -0
  108. data/stdlib/builtin/signal.rbs +55 -0
  109. data/stdlib/builtin/string.rbs +1901 -0
  110. data/stdlib/builtin/string_io.rbs +284 -0
  111. data/stdlib/builtin/struct.rbs +40 -0
  112. data/stdlib/builtin/symbol.rbs +228 -0
  113. data/stdlib/builtin/thread.rbs +1108 -0
  114. data/stdlib/builtin/thread_group.rbs +23 -0
  115. data/stdlib/builtin/time.rbs +1047 -0
  116. data/stdlib/builtin/trace_point.rbs +290 -0
  117. data/stdlib/builtin/true_class.rbs +46 -0
  118. data/stdlib/builtin/unbound_method.rbs +153 -0
  119. data/stdlib/builtin/warning.rbs +17 -0
  120. data/stdlib/coverage/coverage.rbs +62 -0
  121. data/stdlib/csv/csv.rbs +773 -0
  122. data/stdlib/erb/erb.rbs +392 -0
  123. data/stdlib/find/find.rbs +40 -0
  124. data/stdlib/ipaddr/ipaddr.rbs +247 -0
  125. data/stdlib/json/json.rbs +335 -0
  126. data/stdlib/pathname/pathname.rbs +1093 -0
  127. data/stdlib/prime/integer-extension.rbs +23 -0
  128. data/stdlib/prime/prime.rbs +188 -0
  129. data/stdlib/securerandom/securerandom.rbs +9 -0
  130. data/stdlib/set/set.rbs +301 -0
  131. data/stdlib/tmpdir/tmpdir.rbs +53 -0
  132. metadata +292 -0
@@ -0,0 +1,61 @@
1
+ module RBS
2
+ module Test
3
+ module Errors
4
+ ArgumentTypeError =
5
+ Struct.new(:klass, :method_name, :method_type, :param, :value, keyword_init: true)
6
+ BlockArgumentTypeError =
7
+ Struct.new(:klass, :method_name, :method_type, :param, :value, keyword_init: true)
8
+ ArgumentError =
9
+ Struct.new(:klass, :method_name, :method_type, keyword_init: true)
10
+ BlockArgumentError =
11
+ Struct.new(:klass, :method_name, :method_type, keyword_init: true)
12
+ ReturnTypeError =
13
+ Struct.new(:klass, :method_name, :method_type, :type, :value, keyword_init: true)
14
+ BlockReturnTypeError =
15
+ Struct.new(:klass, :method_name, :method_type, :type, :value, keyword_init: true)
16
+
17
+ UnexpectedBlockError = Struct.new(:klass, :method_name, :method_type, keyword_init: true)
18
+ MissingBlockError = Struct.new(:klass, :method_name, :method_type, keyword_init: true)
19
+
20
+ UnresolvedOverloadingError = Struct.new(:klass, :method_name, :method_types, keyword_init: true)
21
+
22
+ def self.format_param(param)
23
+ if param.name
24
+ "`#{param.type}` (#{param.name})"
25
+ else
26
+ "`#{param.type}`"
27
+ end
28
+ end
29
+
30
+ def self.inspect_(obj)
31
+ Hook.inspect_(obj)
32
+ end
33
+
34
+ def self.to_string(error)
35
+ method = "#{error.klass.name}#{error.method_name}"
36
+ case error
37
+ when ArgumentTypeError
38
+ "[#{method}] ArgumentTypeError: expected #{format_param error.param} but given `#{inspect_(error.value)}`"
39
+ when BlockArgumentTypeError
40
+ "[#{method}] BlockArgumentTypeError: expected #{format_param error.param} but given `#{inspect_(error.value)}`"
41
+ when ArgumentError
42
+ "[#{method}] ArgumentError: expected method type #{error.method_type}"
43
+ when BlockArgumentError
44
+ "[#{method}] BlockArgumentError: expected method type #{error.method_type}"
45
+ when ReturnTypeError
46
+ "[#{method}] ReturnTypeError: expected `#{error.type}` but returns `#{inspect_(error.value)}`"
47
+ when BlockReturnTypeError
48
+ "[#{method}] BlockReturnTypeError: expected `#{error.type}` but returns `#{inspect_(error.value)}`"
49
+ when UnexpectedBlockError
50
+ "[#{method}] UnexpectedBlockError: unexpected block is given for `#{error.method_type}`"
51
+ when MissingBlockError
52
+ "[#{method}] MissingBlockError: required block is missing for `#{error.method_type}`"
53
+ when UnresolvedOverloadingError
54
+ "[#{method}] UnresolvedOverloadingError: couldn't find a suitable overloading"
55
+ else
56
+ raise "Unexpected error: #{inspect_(error)}"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,294 @@
1
+ require "rbs"
2
+ require "pp"
3
+
4
+ module RBS
5
+ module Test
6
+ class Hook
7
+ class Error < Exception
8
+ attr_reader :errors
9
+
10
+ def initialize(errors)
11
+ @errors = errors
12
+ super "Type error detected: [#{errors.map {|e| Errors.to_string(e) }.join(", ")}]"
13
+ end
14
+ end
15
+
16
+ attr_reader :env
17
+ attr_reader :logger
18
+
19
+ attr_reader :instance_module
20
+ attr_reader :instance_methods
21
+ attr_reader :singleton_module
22
+ attr_reader :singleton_methods
23
+
24
+ attr_reader :klass
25
+ attr_reader :errors
26
+
27
+ def builder
28
+ @builder ||= DefinitionBuilder.new(env: env)
29
+ end
30
+
31
+ def typecheck
32
+ @typecheck ||= TypeCheck.new(self_class: klass, builder: builder)
33
+ end
34
+
35
+ def initialize(env, klass, logger:, raise_on_error: false)
36
+ @env = env
37
+ @logger = logger
38
+ @klass = klass
39
+
40
+ @instance_module = Module.new
41
+ @instance_methods = []
42
+
43
+ @singleton_module = Module.new
44
+ @singleton_methods = []
45
+
46
+ @errors = []
47
+
48
+ @raise_on_error = raise_on_error
49
+ end
50
+
51
+ def raise_on_error!(error = true)
52
+ @raise_on_error = error
53
+ self
54
+ end
55
+
56
+ def raise_on_error?
57
+ @raise_on_error
58
+ end
59
+
60
+ def prepend!
61
+ klass.prepend @instance_module
62
+ klass.singleton_class.prepend @singleton_module
63
+
64
+ if block_given?
65
+ yield
66
+ disable
67
+ end
68
+
69
+ self
70
+ end
71
+
72
+ def self.install(env, klass, logger:)
73
+ new(env, klass, logger: logger).prepend!
74
+ end
75
+
76
+ def refinement
77
+ klass = self.klass
78
+ instance_module = self.instance_module
79
+ singleton_module = self.singleton_module
80
+
81
+ Module.new do
82
+ refine klass do
83
+ prepend instance_module
84
+ end
85
+
86
+ refine klass.singleton_class do
87
+ prepend singleton_module
88
+ end
89
+ end
90
+ end
91
+
92
+ def verify_all
93
+ type_name = Namespace.parse(klass.name).to_type_name.absolute!
94
+
95
+ builder.build_instance(type_name).tap do |definition|
96
+ definition.methods.each do |name, method|
97
+ if method.defined_in.name.absolute! == type_name
98
+ unless method.annotations.any? {|a| a.string == "rbs:test:skip" }
99
+ logger.info "Installing a hook on #{type_name}##{name}: #{method.method_types.join(" | ")}"
100
+ verify instance_method: name, types: method.method_types
101
+ else
102
+ logger.info "Skipping test of #{type_name}##{name}"
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ builder.build_singleton(type_name).tap do |definition|
109
+ definition.methods.each do |name, method|
110
+ if method.defined_in&.name&.absolute! == type_name || name == :new
111
+ unless method.annotations.any? {|a| a.string == "rbs:test:skip" }
112
+ logger.info "Installing a hook on #{type_name}.#{name}: #{method.method_types.join(" | ")}"
113
+ verify singleton_method: name, types: method.method_types
114
+ else
115
+ logger.info "Skipping test of #{type_name}.#{name}"
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ self
122
+ end
123
+
124
+ def delegation(name, method_types, method_name)
125
+ hook = self
126
+
127
+ -> (*args, &block) do
128
+ hook.logger.debug { "#{method_name} receives arguments: #{hook.inspect_(args)}" }
129
+
130
+ block_calls = []
131
+
132
+ if block
133
+ original_block = block
134
+
135
+ block = hook.call(Object.new, INSTANCE_EVAL) do |fresh_obj|
136
+ ->(*as) do
137
+ hook.logger.debug { "#{method_name} receives block arguments: #{hook.inspect_(as)}" }
138
+
139
+ ret = if self.equal?(fresh_obj)
140
+ original_block[*as]
141
+ else
142
+ hook.call(self, INSTANCE_EXEC, *as, &original_block)
143
+ end
144
+
145
+ block_calls << ArgumentsReturn.new(
146
+ arguments: as,
147
+ return_value: ret,
148
+ exception: nil
149
+ )
150
+
151
+ hook.logger.debug { "#{method_name} returns from block: #{hook.inspect_(ret)}" }
152
+
153
+ ret
154
+ end.ruby2_keywords
155
+ end
156
+ end
157
+
158
+ method = hook.call(self, METHOD, name)
159
+ klass = hook.call(self, CLASS)
160
+ singleton_klass = begin
161
+ hook.call(self, SINGLETON_CLASS)
162
+ rescue TypeError
163
+ nil
164
+ end
165
+ prepended = klass.ancestors.include?(hook.instance_module) || singleton_klass&.ancestors&.include?(hook.singleton_module)
166
+ exception = nil
167
+ result = begin
168
+ if prepended
169
+ method.super_method.call(*args, &block)
170
+ else
171
+ # Using refinement
172
+ method.call(*args, &block)
173
+ end
174
+ rescue Exception => e
175
+ exception = e
176
+ nil
177
+ end
178
+
179
+ hook.logger.debug { "#{method_name} returns: #{hook.inspect_(result)}" }
180
+
181
+ call = CallTrace.new(method_call: ArgumentsReturn.new(arguments: args, return_value: result, exception: exception),
182
+ block_calls: block_calls,
183
+ block_given: block != nil)
184
+
185
+ method_type_errors = method_types.map do |method_type|
186
+ hook.typecheck.method_call(method_name, method_type, call, errors: [])
187
+ end
188
+
189
+ new_errors = []
190
+
191
+ if method_type_errors.none?(&:empty?)
192
+ if (best_errors = hook.find_best_errors(method_type_errors))
193
+ new_errors.push(*best_errors)
194
+ else
195
+ new_errors << Errors::UnresolvedOverloadingError.new(
196
+ klass: hook.klass,
197
+ method_name: method_name,
198
+ method_types: method_types
199
+ )
200
+ end
201
+ end
202
+
203
+ unless new_errors.empty?
204
+ new_errors.each do |error|
205
+ hook.logger.error Errors.to_string(error)
206
+ end
207
+
208
+ hook.errors.push(*new_errors)
209
+
210
+ if hook.raise_on_error?
211
+ raise Error.new(new_errors)
212
+ end
213
+ end
214
+
215
+ result
216
+ end.ruby2_keywords
217
+ end
218
+
219
+ def verify(instance_method: nil, singleton_method: nil, types:)
220
+ method_types = types.map do |type|
221
+ case type
222
+ when String
223
+ Parser.parse_method_type(type)
224
+ else
225
+ type
226
+ end
227
+ end
228
+
229
+ case
230
+ when instance_method
231
+ instance_methods << instance_method
232
+ call(self.instance_module, DEFINE_METHOD, instance_method, &delegation(instance_method, method_types, "##{instance_method}"))
233
+ when singleton_method
234
+ call(self.singleton_module, DEFINE_METHOD, singleton_method, &delegation(singleton_method, method_types, ".#{singleton_method}"))
235
+ end
236
+
237
+ self
238
+ end
239
+
240
+ def find_best_errors(errorss)
241
+ if errorss.size == 1
242
+ errorss[0]
243
+ else
244
+ no_arity_errors = errorss.select do |errors|
245
+ errors.none? do |error|
246
+ error.is_a?(Errors::ArgumentError) ||
247
+ error.is_a?(Errors::BlockArgumentError) ||
248
+ error.is_a?(Errors::MissingBlockError) ||
249
+ error.is_a?(Errors::UnexpectedBlockError)
250
+ end
251
+ end
252
+
253
+ unless no_arity_errors.empty?
254
+ # Choose a error set which doesn't include arity error
255
+ return no_arity_errors[0] if no_arity_errors.size == 1
256
+ end
257
+ end
258
+ end
259
+
260
+ def self.backtrace(skip: 2)
261
+ raise
262
+ rescue => exn
263
+ exn.backtrace.drop(skip)
264
+ end
265
+
266
+ def run
267
+ yield
268
+ self
269
+ ensure
270
+ disable
271
+ end
272
+
273
+ def call(receiver, method, *args, &block)
274
+ method.bind(receiver).call(*args, &block)
275
+ end
276
+
277
+ def inspect_(obj)
278
+ Hook.inspect_(obj)
279
+ end
280
+
281
+ def self.inspect_(obj)
282
+ obj.inspect
283
+ rescue
284
+ INSPECT.bind(obj).call()
285
+ end
286
+
287
+ def disable
288
+ self.instance_module.remove_method(*instance_methods)
289
+ self.singleton_module.remove_method(*singleton_methods)
290
+ self
291
+ end
292
+ end
293
+ end
294
+ end
@@ -0,0 +1,58 @@
1
+ require "rbs"
2
+ require "rbs/test"
3
+
4
+ require "optparse"
5
+ require "shellwords"
6
+
7
+ logger = Logger.new(STDERR)
8
+
9
+ begin
10
+ opts = Shellwords.shellsplit(ENV["RBS_TEST_OPT"] || "-I sig")
11
+ filter = ENV.fetch("RBS_TEST_TARGET").split(",")
12
+ skips = (ENV["RBS_TEST_SKIP"] || "").split(",")
13
+ logger.level = (ENV["RBS_TEST_LOGLEVEL"] || "info")
14
+ raise_on_error = ENV["RBS_TEST_RAISE"]
15
+ rescue
16
+ STDERR.puts "rbs/test/setup handles the following environment variables:"
17
+ STDERR.puts " [REQUIRED] RBS_TEST_TARGET: test target class name, `Foo::Bar,Foo::Baz` for each class or `Foo::*` for all classes under `Foo`"
18
+ STDERR.puts " [OPTIONAL] RBS_TEST_SKIP: skip testing classes"
19
+ STDERR.puts " [OPTIONAL] RBS_TEST_OPT: options for signatures (`-r` for libraries or `-I` for signatures)"
20
+ STDERR.puts " [OPTIONAL] RBS_TEST_LOGLEVEL: one of debug|info|warn|error|fatal (defaults to info)"
21
+ STDERR.puts " [OPTIONAL] RBS_TEST_RAISE: specify any value to raise an exception when type error is detected"
22
+ exit 1
23
+ end
24
+
25
+ hooks = []
26
+
27
+ env = RBS::Environment.new
28
+
29
+ loader = RBS::EnvironmentLoader.new
30
+ OptionParser.new do |opts|
31
+ opts.on("-r [LIB]") do |name| loader.add(library: name) end
32
+ opts.on("-I [DIR]") do |dir| loader.add(path: Pathname(dir)) end
33
+ end.parse!(opts)
34
+ loader.load(env: env)
35
+
36
+ def match(filter, name)
37
+ if filter.end_with?("*")
38
+ name.start_with?(filter[0, filter.size - 1]) || name == filter[0, filter.size-3]
39
+ else
40
+ filter == name
41
+ end
42
+ end
43
+
44
+ TracePoint.trace :end do |tp|
45
+ class_name = tp.self.name
46
+
47
+ if class_name
48
+ if filter.any? {|f| match(f, class_name) } && skips.none? {|f| match(f, class_name) }
49
+ type_name = RBS::Namespace.parse(class_name).absolute!.to_type_name
50
+ if hooks.none? {|hook| hook.klass == tp.self }
51
+ if env.find_class(type_name)
52
+ logger.info "Setting up hooks for #{class_name}"
53
+ hooks << RBS::Test::Hook.install(env, tp.self, logger: logger).verify_all.raise_on_error!(raise_on_error)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,325 @@
1
+ module RBS
2
+ module Test
3
+ module Spy
4
+ def self.singleton_method(object, method_name)
5
+ spy = SingletonSpy.new(object: object, method_name: method_name)
6
+
7
+ if block_given?
8
+ begin
9
+ spy.setup
10
+ yield spy
11
+ ensure
12
+ spy.reset
13
+ end
14
+ else
15
+ spy
16
+ end
17
+ end
18
+
19
+ def self.instance_method(mod, method_name)
20
+ spy = InstanceSpy.new(mod: mod, method_name: method_name)
21
+
22
+ if block_given?
23
+ begin
24
+ spy.setup
25
+ yield spy
26
+ ensure
27
+ spy.reset
28
+ end
29
+ else
30
+ spy
31
+ end
32
+ end
33
+
34
+ def self.wrap(object, method_name)
35
+ spy = WrapSpy.new(object: object, method_name: method_name)
36
+
37
+ if block_given?
38
+ begin
39
+ yield spy, spy.wrapped_object
40
+ end
41
+ else
42
+ spy
43
+ end
44
+ end
45
+
46
+ class SingletonSpy
47
+ attr_accessor :callback
48
+ attr_reader :method_name
49
+ attr_reader :object
50
+
51
+ def initialize(object:, method_name:)
52
+ @object = object
53
+ @method_name = method_name
54
+ @callback = -> (_) { }
55
+ end
56
+
57
+ def setup
58
+ spy = self
59
+
60
+ object.singleton_class.class_eval do
61
+ remove_method spy.method_name
62
+ define_method spy.method_name, spy.spy()
63
+ end
64
+ end
65
+
66
+ def spy()
67
+ spy = self
68
+
69
+ -> (*args, &block) do
70
+ return_value = nil
71
+ exception = nil
72
+ block_calls = []
73
+
74
+ spy_block = if block
75
+ Object.new.instance_eval do |fresh|
76
+ -> (*block_args) do
77
+ block_exn = nil
78
+ block_return = nil
79
+
80
+ begin
81
+ block_return = if self.equal?(fresh)
82
+ # no instance eval
83
+ block.call(*block_args)
84
+ else
85
+ self.instance_exec(*block_args, &block)
86
+ end
87
+ rescue Exception => exn
88
+ block_exn = exn
89
+ end
90
+
91
+ block_calls << ArgumentsReturn.new(
92
+ arguments: block_args,
93
+ return_value: block_return,
94
+ exception: block_exn
95
+ )
96
+
97
+ if block_exn
98
+ raise block_exn
99
+ else
100
+ block_return
101
+ end
102
+ end.ruby2_keywords
103
+ end
104
+ end
105
+
106
+ begin
107
+ return_value = super(*args, &spy_block)
108
+ rescue Exception => exn
109
+ exception = exn
110
+ end
111
+
112
+ trace = CallTrace.new(
113
+ method_name: spy.method_name,
114
+ method_call: ArgumentsReturn.new(
115
+ arguments: args,
116
+ return_value: return_value,
117
+ exception: exception,
118
+ ),
119
+ block_calls: block_calls,
120
+ block_given: block != nil
121
+ )
122
+
123
+ spy.callback.call(trace)
124
+
125
+ if exception
126
+ raise exception
127
+ else
128
+ return_value
129
+ end
130
+ end.ruby2_keywords
131
+ end
132
+
133
+ def reset
134
+ if object.singleton_class.methods.include?(method_name)
135
+ object.singleton_class.remove_method method_name
136
+ end
137
+ end
138
+ end
139
+
140
+ class InstanceSpy
141
+ attr_accessor :callback
142
+ attr_reader :mod
143
+ attr_reader :method_name
144
+ attr_reader :original_method
145
+
146
+ def initialize(mod:, method_name:)
147
+ @mod = mod
148
+ @method_name = method_name
149
+ @original_method = mod.instance_method(method_name)
150
+ @callback = -> (_) { }
151
+ end
152
+
153
+ def setup
154
+ spy = self
155
+
156
+ mod.class_eval do
157
+ remove_method spy.method_name
158
+ define_method spy.method_name, spy.spy()
159
+ end
160
+ end
161
+
162
+ def reset
163
+ spy = self
164
+
165
+ mod.class_eval do
166
+ remove_method spy.method_name
167
+ define_method spy.method_name, spy.original_method
168
+ end
169
+ end
170
+
171
+ def spy
172
+ spy = self
173
+
174
+ -> (*args, &block) do
175
+ return_value = nil
176
+ exception = nil
177
+ block_calls = []
178
+
179
+ spy_block = if block
180
+ Object.new.instance_eval do |fresh|
181
+ -> (*block_args) do
182
+ block_exn = nil
183
+ block_return = nil
184
+
185
+ begin
186
+ block_return = if self.equal?(fresh)
187
+ # no instance eval
188
+ block.call(*block_args)
189
+ else
190
+ self.instance_exec(*block_args, &block)
191
+ end
192
+ rescue Exception => exn
193
+ block_exn = exn
194
+ end
195
+
196
+ block_calls << ArgumentsReturn.new(
197
+ arguments: block_args,
198
+ return_value: block_return,
199
+ exception: block_exn
200
+ )
201
+
202
+ if block_exn
203
+ raise block_exn
204
+ else
205
+ block_return
206
+ end
207
+ end.ruby2_keywords
208
+ end
209
+ end
210
+
211
+ begin
212
+ return_value = spy.original_method.bind_call(self, *args, &spy_block)
213
+ rescue Exception => exn
214
+ exception = exn
215
+ end
216
+
217
+ trace = CallTrace.new(
218
+ method_name: spy.method_name,
219
+ method_call: ArgumentsReturn.new(
220
+ arguments: args,
221
+ return_value: return_value,
222
+ exception: exception,
223
+ ),
224
+ block_calls: block_calls,
225
+ block_given: block != nil
226
+ )
227
+
228
+ spy.callback.call(trace)
229
+
230
+ if exception
231
+ raise exception
232
+ else
233
+ return_value
234
+ end
235
+ end.ruby2_keywords
236
+ end
237
+ end
238
+
239
+ class WrapSpy
240
+ attr_accessor :callback
241
+ attr_reader :object
242
+ attr_reader :method_name
243
+
244
+ def initialize(object:, method_name:)
245
+ @callback = -> (_) { }
246
+ @object = object
247
+ @method_name = method_name
248
+ end
249
+
250
+ def wrapped_object
251
+ spy = self
252
+
253
+ Class.new(BasicObject) do
254
+ define_method(:method_missing) do |name, *args, &block|
255
+ spy.object.__send__(name, *args, &block)
256
+ end
257
+
258
+ define_method(spy.method_name, -> (*args, &block) {
259
+ return_value = nil
260
+ exception = nil
261
+ block_calls = []
262
+
263
+ spy_block = if block
264
+ Object.new.instance_eval do |fresh|
265
+ -> (*block_args) do
266
+ block_exn = nil
267
+ block_return = nil
268
+
269
+ begin
270
+ block_return = if self.equal?(fresh)
271
+ # no instance eval
272
+ block.call(*block_args)
273
+ else
274
+ self.instance_exec(*block_args, &block)
275
+ end
276
+ rescue Exception => exn
277
+ block_exn = exn
278
+ end
279
+
280
+ block_calls << ArgumentsReturn.new(
281
+ arguments: block_args,
282
+ return_value: block_return,
283
+ exception: block_exn
284
+ )
285
+
286
+ if block_exn
287
+ raise block_exn
288
+ else
289
+ block_return
290
+ end
291
+ end.ruby2_keywords
292
+ end
293
+ end
294
+
295
+ begin
296
+ return_value = spy.object.__send__(spy.method_name, *args, &spy_block)
297
+ rescue ::Exception => exn
298
+ exception = exn
299
+ end
300
+
301
+ trace = CallTrace.new(
302
+ method_name: spy.method_name,
303
+ method_call: ArgumentsReturn.new(
304
+ arguments: args,
305
+ return_value: return_value,
306
+ exception: exception,
307
+ ),
308
+ block_calls: block_calls,
309
+ block_given: block != nil
310
+ )
311
+
312
+ spy.callback.call(trace)
313
+
314
+ if exception
315
+ spy.object.__send__(:raise, exception)
316
+ else
317
+ return_value
318
+ end
319
+ }.ruby2_keywords)
320
+ end.new()
321
+ end
322
+ end
323
+ end
324
+ end
325
+ end