rbs 0.2.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.
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