raap 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 507ed156280ed3e0db1f2eb454bb03c57aa473d537d8c6ea5905ad852eba550d
4
- data.tar.gz: 3108fa50c664702f79141a3c0b5e83934d6742468d1d51bfb2febb5b64a00b12
3
+ metadata.gz: 312e27904176673d6962904901cb3e8d31c6b613b8efdf669c877d4670cf9d15
4
+ data.tar.gz: fe940b9d2928a54bfbba8a4842c097cb2a8efe6cf0b873baa383f757de37d017
5
5
  SHA512:
6
- metadata.gz: 269e220d72b9861881de860a71a2adff318105595880fe73e86492e255c9bb32fbeff7ac3685b83ea11d792a02b1509eb969d9956483a9415608530a3f8806bd
7
- data.tar.gz: df4e40973681eb292d7844aa5ed1427b7155f619c6ebea53792149f430ab28e42a0a0f52df9517a3af9af1e08494eaccdd8d4d84313c9d24184f6403a51e1dc5
6
+ metadata.gz: 55070cb0335ae161e3a1f6e62d9faa9d205e0b1c92186d2e81eaea2547d476d092fa90a9c6607d2d4ad11cf1f0a0bab723539e90cc4932d49bc297e069e07632
7
+ data.tar.gz: 771e2f640454bfcd8774ccc0d1ac3079df7e6f1edb7d209327881fea2febb6bbfe04af937481e3e6d81bf9b05046860fc7d861af38b523872d1af8d62a0717da
data/.rubocop.yml ADDED
@@ -0,0 +1,29 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.3
3
+ NewCops: enable
4
+ Include:
5
+ - 'lib/**/*.rb'
6
+
7
+ Lint:
8
+ Enabled: true
9
+ Lint/EmptyBlock:
10
+ Enabled: false
11
+
12
+ Style:
13
+ Enabled: false
14
+ Style/HashSyntax:
15
+ EnforcedShorthandSyntax: always
16
+ Enabled: true
17
+
18
+ Metrics:
19
+ Enabled: false
20
+
21
+ Layout:
22
+ Enabled: true
23
+ Layout/FirstArrayElementIndentation:
24
+ EnforcedStyle: consistent
25
+ Layout/LineLength:
26
+ Max: 150
27
+
28
+ Naming:
29
+ Enabled: false
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # RaaP
2
2
 
3
+ ![Jacket](https://raw.githubusercontent.com/ksss/raap/main/public/DALL%C2%B7E%202024-03-23%2000.02.29%20-%20Imagine%20a%20scene%20where%20the%20abstract%20concepts%20of%20Ruby%20programming%20and%20property-based%20testing%20blend%20harmoniously.%20Picture%20a%20large%2C%20glowing%20ruby%20crystal%2C%20.webp)
4
+
3
5
  ## RBS as a Property
4
6
 
5
7
  RaaP is a property based testing tool.
@@ -55,7 +57,7 @@ Finally, you get the perfect RBS!
55
57
 
56
58
  Install the gem and add to the application's Gemfile by executing:
57
59
 
58
- $ bundle add raap
60
+ $ bundle add raap --require false
59
61
 
60
62
  If bundler is not being used to manage dependencies, install the gem by executing:
61
63
 
@@ -123,6 +125,10 @@ You can specify size of end.
123
125
 
124
126
  You can specify size of step like `Integer#step: (to: Integer, by: Integer)`.
125
127
 
128
+ ## First support is CLI
129
+
130
+ In RaaP, usage through the CLI is the primary support focus, and efforts are made to maintain compatibility. The use of the library's code (such as `RaaP::Type`) does not guarantee compatibility.
131
+
126
132
  ## Achievements
127
133
 
128
134
  RaaP has already found many RBS mistakes and bug in CRuby during the development phase.
@@ -134,6 +140,11 @@ RaaP has already found many RBS mistakes and bug in CRuby during the development
134
140
  * https://github.com/ruby/rbs/pull/1769
135
141
  * https://github.com/ruby/rbs/pull/1770
136
142
  * https://github.com/ruby/rbs/pull/1771
143
+ * https://github.com/ruby/rbs/pull/1779
144
+ * https://github.com/ruby/rbs/pull/1783
145
+ * https://github.com/ruby/rbs/pull/1789
146
+ * https://github.com/ruby/rbs/pull/1790
147
+ * https://github.com/ruby/rbs/pull/1793
137
148
 
138
149
  ## Development
139
150
 
data/Rakefile CHANGED
@@ -2,7 +2,16 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "minitest/test_task"
5
+ require "rubocop/rake_task"
5
6
 
6
7
  Minitest::TestTask.create
7
8
 
8
- task default: :test
9
+ RuboCop::RakeTask.new
10
+
11
+ namespace :steep do
12
+ task :check do
13
+ sh "bundle exec steep check"
14
+ end
15
+ end
16
+
17
+ task default: [:test, :rubocop, 'steep:check']
data/exe/raap CHANGED
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
1
3
  require 'raap'
2
4
 
3
- RaaP::CLI.new(ARGV).load.run
5
+ exit RaaP::CLI.new(ARGV).load.run
@@ -11,19 +11,31 @@ module RaaP
11
11
  def public_send(...) = ::Kernel.instance_method(:public_send).bind_call(...)
12
12
 
13
13
  def class(obj)
14
- if respond_to?(obj, :class)
15
- obj.class
16
- else
14
+ if instance_of?(obj, BasicObject)
17
15
  ::Kernel.instance_method(:class).bind_call(obj)
16
+ else
17
+ obj.class
18
18
  end
19
19
  end
20
20
 
21
21
  def inspect(obj)
22
- if respond_to?(obj, :inspect)
23
- obj.inspect
24
- else
22
+ if instance_of?(obj, BasicObject)
25
23
  ::Kernel.instance_method(:inspect).bind_call(obj)
24
+ else
25
+ case obj
26
+ when Hash
27
+ body = obj.map do |k, v|
28
+ "#{inspect(k)} => #{inspect(v)}"
29
+ end
30
+ "{#{body.join(', ')}}"
31
+ when Array
32
+ "[#{obj.map { |o| inspect(o) }.join(', ')}]"
33
+ else
34
+ obj.inspect
35
+ end
26
36
  end
37
+ rescue NoMethodError
38
+ "#<#{self.class(obj)}>"
27
39
  end
28
40
  end
29
41
  end
data/lib/raap/cli.rb CHANGED
@@ -2,12 +2,7 @@
2
2
 
3
3
  module RaaP
4
4
  # $ raap Integer#pow
5
- # $ raap -I sig RaaP::Type
6
5
  class CLI
7
- class << self
8
- attr_accessor :option
9
- end
10
-
11
6
  Option = Struct.new(
12
7
  :dirs,
13
8
  :requires,
@@ -17,201 +12,263 @@ module RaaP
17
12
  :size_to,
18
13
  :size_by,
19
14
  :allow_private,
15
+ :skips,
20
16
  keyword_init: true
21
17
  )
22
18
 
23
- # defaults
24
- self.option = Option.new(
25
- dirs: [],
26
- requires: [],
27
- libraries: [],
28
- timeout: 3,
29
- size_from: 0,
30
- size_to: 99,
31
- size_by: 1,
32
- allow_private: false,
33
- )
19
+ # Should skip methods has side effects
20
+ DEFAULT_SKIP = Set.new
21
+ %i[
22
+ fork system exec spawn `
23
+ abort exit exit! raise fail
24
+ load require require_relative
25
+ gem
26
+ ].each do |kernel_method|
27
+ DEFAULT_SKIP << "::Kernel##{kernel_method}"
28
+ DEFAULT_SKIP << "::Kernel.#{kernel_method}"
29
+ end
30
+ %i[
31
+ delete unlink chmod lchmod chown lchown
32
+ link mkfifo new open rename truncate
33
+ ].each { |m| DEFAULT_SKIP << "::File.#{m}" }
34
+ %i[flock truncate].each { |m| DEFAULT_SKIP << "::File##{m}" }
35
+
36
+ attr_accessor :option, :argv, :skip, :results
34
37
 
35
38
  def initialize(argv)
39
+ # defaults
40
+ @option = Option.new(
41
+ dirs: [],
42
+ requires: [],
43
+ libraries: [],
44
+ timeout: 3,
45
+ size_from: 0,
46
+ size_to: 99,
47
+ size_by: 1,
48
+ skips: [],
49
+ allow_private: false,
50
+ )
36
51
  @argv = argv
52
+ @skip = DEFAULT_SKIP.dup
53
+ @results = []
37
54
  end
38
55
 
39
56
  def load
40
57
  OptionParser.new do |o|
41
58
  o.on('-I', '--include PATH') do |path|
42
- CLI.option.dirs << path
59
+ @option.dirs << path
43
60
  end
44
61
  o.on('--library lib', 'load rbs library') do |lib|
45
- CLI.option.libraries << lib
62
+ @option.libraries << lib
46
63
  end
47
64
  o.on('--require lib', 'require ruby library') do |lib|
48
- CLI.option.requires << lib
65
+ @option.requires << lib
49
66
  end
50
67
  o.on('--log-level level', "default: warn") do |arg|
51
68
  RaaP.logger.level = arg
52
69
  end
53
- o.on('--timeout sec', Integer, "default: #{CLI.option.timeout}") do |arg|
54
- CLI.option.timeout = arg
70
+ o.on('--timeout sec', Float, "default: #{@option.timeout}") do |arg|
71
+ @option.timeout = arg
72
+ end
73
+ o.on('--size-from int', Integer, "default: #{@option.size_from}") do |arg|
74
+ @option.size_from = arg
55
75
  end
56
- o.on('--size-from int', Integer, "default: #{CLI.option.size_from}") do |arg|
57
- CLI.option.size_from = arg
76
+ o.on('--size-to int', Integer, "default: #{@option.size_to}") do |arg|
77
+ @option.size_to = arg
58
78
  end
59
- o.on('--size-to int', Integer, "default: #{CLI.option.size_to}") do |arg|
60
- CLI.option.size_to = arg
79
+ o.on('--size-by int', Integer, "default: #{@option.size_by}") do |arg|
80
+ @option.size_by = arg
61
81
  end
62
- o.on('--size-by int', Integer, "default: #{CLI.option.size_by}") do |arg|
63
- CLI.option.size_by = arg
82
+ o.on('--allow-private', "default: #{@option.allow_private}") do
83
+ @option.allow_private = true
64
84
  end
65
- o.on('--allow-private', "default: #{CLI.option.allow_private}") do
66
- CLI.option.allow_private = true
85
+ o.on('--skip tag', "skip case (e.g. `Foo#meth`)") do |tag|
86
+ @option.skips << tag
67
87
  end
68
88
  end.parse!(@argv)
69
89
 
70
- CLI.option.dirs.each do |dir|
90
+ @option.dirs.each do |dir|
71
91
  RaaP::RBS.loader.add(path: Pathname(dir))
72
92
  end
73
- CLI.option.libraries.each do |lib|
93
+ @option.libraries.each do |lib|
74
94
  RaaP::RBS.loader.add(library: lib, version: nil)
75
95
  end
76
- CLI.option.requires.each do |lib|
96
+ @option.requires.each do |lib|
77
97
  require lib
78
98
  end
99
+ @option.skips.each do |skip|
100
+ @skip << skip
101
+ end
102
+ @skip.freeze
79
103
 
80
104
  self
81
105
  end
82
106
 
83
107
  def run
108
+ Signal.trap(:INT) do
109
+ puts "Interrupted by SIGINT"
110
+ report
111
+ exit 1
112
+ end
113
+
84
114
  @argv.map do |tag|
85
115
  case
86
116
  when tag.include?('#')
87
- run_by_instance(tag:)
117
+ run_by(kind: :instance, tag:)
88
118
  when tag.include?('.')
89
- run_by_singleton(tag:)
119
+ run_by(kind: :singleton, tag:)
90
120
  when tag.end_with?('*')
91
121
  run_by_type_name_with_search(tag:)
92
122
  else
93
123
  run_by_type_name(tag:)
94
124
  end
95
- end.each do |ret|
96
- ret.each do |methods|
97
- methods.each do |status, method_name, method_type|
98
- if status == 1
99
- puts "Fail:"
100
- puts "def #{method_name}: #{method_type}"
101
- end
102
- end
103
- end
104
125
  end
105
126
 
106
- self
127
+ report
107
128
  end
108
129
 
109
- def run_by_instance(tag:)
110
- t, m = tag.split('#', 2)
111
- t or raise
112
- m or raise
113
- type = RBS.parse_type(t)
114
- type = __skip__ = type
115
- raise "cannot specified #{type}" unless type.respond_to?(:name)
116
- receiver_type = Type.new(type.to_s)
117
- method_name = m.to_sym
118
- definition = RBS.builder.build_instance(type.name)
119
- type_params_decl = definition.type_params_decl
120
- method = definition.methods[method_name]
121
- raise "`#{tag}` is not found" unless method
122
- puts "# #{type.to_s}"
123
- puts
124
- [
125
- method.method_types.map do |method_type|
126
- property(receiver_type:, type_params_decl:, method_type:, method_name:)
130
+ private
131
+
132
+ def report
133
+ i = 0
134
+ exit_status = 0
135
+ @results.each do |ret|
136
+ ret => { method:, properties: }
137
+ properties.select { |status,| status == 1 }.each do |_, method_name, method_type, reason|
138
+ i += 1
139
+ location = if method.alias_of
140
+ alias_decl = RBS.find_alias_decl(method.defined_in, method_name) or raise "alias decl not found: #{method_name}"
141
+ alias_decl.location
142
+ else
143
+ method_type.location
144
+ end
145
+ prefix = method.defs.first.member.kind == :instance ? '' : 'self.'
146
+
147
+ puts "\e[41m\e[1m#\e[m\e[1m #{i}) Failure:\e[m"
148
+ puts
149
+ puts "def #{prefix}#{method_name}: #{method_type}"
150
+ puts " in #{location}"
151
+ puts
152
+ puts "## Reason"
153
+ puts
154
+ puts reason&.string
155
+ puts
156
+ exit_status = 1
127
157
  end
128
- ]
158
+ end
159
+ exit_status
129
160
  end
130
161
 
131
- def run_by_singleton(tag:)
132
- t, m = tag.split('.', 2)
162
+ def run_by(kind:, tag:)
163
+ split = kind == :instance ? '#' : '.'
164
+ t, m = tag.split(split, 2)
133
165
  t or raise
134
166
  m or raise
135
167
  type = RBS.parse_type(t)
136
168
  raise "cannot specified #{type.class}" unless type.respond_to?(:name)
169
+
137
170
  type = __skip__ = type
138
- receiver_type = Type.new("singleton(#{type.name})")
171
+ type_name = type.name.absolute!
172
+ type_to_s = type.to_s.start_with?('::') ? type.to_s : "::#{type}"
173
+ receiver_type = if kind == :instance
174
+ Type.new(type_to_s)
175
+ else
176
+ Type.new("singleton(#{type_name})")
177
+ end
139
178
  method_name = m.to_sym
140
- definition = RBS.builder.build_singleton(type.name)
179
+ definition = if kind == :instance
180
+ RBS.builder.build_instance(type_name)
181
+ else
182
+ RBS.builder.build_singleton(type_name)
183
+ end
184
+
141
185
  method = definition.methods[method_name]
142
- type_params_decl = definition.type_params_decl
143
186
  raise "`#{tag}` not found" unless method
144
- puts "# #{type}"
145
- puts
146
- [
147
- method.method_types.map do |method_type|
148
- property(receiver_type:, type_params_decl:, method_type:, method_name:)
187
+
188
+ if @skip.include?("#{type_name}#{split}#{method_name}")
189
+ raise "`#{type_name}#{split}#{method_name}` is a method to be skipped"
190
+ end
191
+
192
+ type_params_decl = definition.type_params_decl
193
+ type_args = type.args
194
+
195
+ RaaP.logger.info("# #{type}")
196
+ @results << {
197
+ method:,
198
+ properties: method.method_types.map do |method_type|
199
+ property(receiver_type:, type_params_decl:, type_args:, method_type:, method_name:)
149
200
  end
150
- ]
201
+ }
151
202
  end
152
203
 
153
204
  def run_by_type_name_with_search(tag:)
154
205
  first, _last = tag.split('::')
155
- ret = []
156
- RBS.env.class_decls.each do |name, entry|
206
+ RBS.env.class_decls.each do |name, _entry|
157
207
  if ['', '::'].any? { |pre| name.to_s.match?(/\A#{pre}#{first}\b/) }
158
- ret << run_by_type_name(tag: name.to_s)
208
+ run_by_type_name(tag: name.to_s)
159
209
  end
160
210
  end
161
- ret.flatten(1)
162
211
  end
163
212
 
164
213
  def run_by_type_name(tag:)
165
214
  type = RBS.parse_type(tag)
166
215
  type = __skip__ = type
167
216
  raise "cannot specified #{type.class}" unless type.respond_to?(:name)
168
- type_name = type.name.absolute!
169
217
 
170
- ret = []
218
+ type_name = type.name.absolute!
219
+ type_args = type.args
171
220
 
172
221
  definition = RBS.builder.build_singleton(type_name)
173
222
  type_params_decl = definition.type_params_decl
174
223
  definition.methods.filter_map do |method_name, method|
224
+ next if @skip.include?("#{type_name.absolute!}.#{method_name}")
175
225
  next unless method.accessibility == :public
176
226
  next if method.defined_in != type_name
177
- next if method_name == :fork || method_name == :spawn # TODO: skip solution
178
- puts "# #{type_name}.#{method_name}"
179
- puts
180
- ret << method.method_types.map do |method_type|
181
- property(receiver_type: Type.new("singleton(#{type})"), type_params_decl:, method_type:, method_name:)
182
- end
227
+
228
+ RaaP.logger.info("# #{type_name}.#{method_name}")
229
+ @results << {
230
+ method:,
231
+ properties: method.method_types.map do |method_type|
232
+ property(receiver_type: Type.new("singleton(#{type.name})"), type_params_decl:, type_args:, method_type:, method_name:)
233
+ end
234
+ }
183
235
  end
184
236
 
185
237
  definition = RBS.builder.build_instance(type_name)
186
238
  type_params_decl = definition.type_params_decl
187
239
  definition.methods.filter_map do |method_name, method|
240
+ next if @skip.include?("#{type_name.absolute!}##{method_name}")
188
241
  next unless method.accessibility == :public
189
242
  next if method.defined_in != type_name
190
- next if method_name == :fork || method_name == :spawn # TODO: skip solution
191
- puts "# #{type_name}##{method_name}"
192
- puts
193
- ret << method.method_types.map do |method_type|
194
- property(receiver_type: Type.new(type.to_s), type_params_decl:, method_type:, method_name:)
195
- end
196
- end
197
243
 
198
- ret
244
+ RaaP.logger.info("# #{type_name}##{method_name}")
245
+ @results << {
246
+ method:,
247
+ properties: method.method_types.map do |method_type|
248
+ property(receiver_type: Type.new(type.name), type_params_decl:, type_args:, method_type:, method_name:)
249
+ end
250
+ }
251
+ end
199
252
  end
200
253
 
201
- def property(receiver_type:, type_params_decl:, method_type:, method_name:)
254
+ def property(receiver_type:, type_params_decl:, type_args:, method_type:, method_name:)
202
255
  rtype = __skip__ = receiver_type.type
203
256
  if receiver_type.type.instance_of?(::RBS::Types::ClassSingleton)
204
257
  prefix = 'self.'
205
- type_args = []
206
258
  else
207
259
  prefix = ''
208
- type_args = rtype.args
209
260
  end
210
- puts "## def #{prefix}#{method_name}: #{method_type}"
261
+ type_params_decl.each_with_index do |_, i|
262
+ if rtype.instance_of?(::RBS::Types::ClassInstance)
263
+ rtype.args[i] = type_args[i] || ::RBS::Types::Bases::Any.new(location: nil)
264
+ end
265
+ end
266
+ RaaP.logger.info("## def #{prefix}#{method_name}: #{method_type}")
211
267
  status = 0
212
- stats = MethodProperty.new(
268
+ reason = nil
269
+ prop = MethodProperty.new(
213
270
  receiver_type:,
214
- method_name: method_name,
271
+ method_name:,
215
272
  method_type: MethodType.new(
216
273
  method_type,
217
274
  type_params_decl:,
@@ -220,46 +277,59 @@ module RaaP
220
277
  instance_type: ::RBS::Types::ClassInstance.new(name: rtype.name, args: type_args, location: nil),
221
278
  class_type: ::RBS::Types::ClassSingleton.new(name: rtype.name, location: nil),
222
279
  ),
223
- size_step: CLI.option.size_from.step(to: CLI.option.size_to, by: CLI.option.size_by),
224
- timeout: CLI.option.timeout,
280
+ size_step: @option.size_from.step(to: @option.size_to, by: @option.size_by),
281
+ timeout: @option.timeout,
225
282
  allow_private: true,
226
- ).run do |called|
283
+ )
284
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
285
+ stats = prop.run do |called|
227
286
  case called
228
287
  in Result::Success => s
229
288
  print '.'
230
289
  RaaP.logger.debug { "Success: #{s.called_str}" }
231
290
  in Result::Failure => f
232
291
  puts 'F'
233
- puts "Failed in case of `#{f.called_str}`"
234
- if e = f.exception
292
+ if (e = f.exception)
235
293
  RaaP.logger.debug { "Failure: [#{e.class}] #{e.message}" }
294
+ RaaP.logger.debug { e.backtrace.join("\n") }
236
295
  end
237
- puts
238
296
  RaaP.logger.debug { PP.pp(f.symbolic_call, ''.dup) }
239
- puts "### call stack:"
240
- puts
241
- puts "```"
242
- puts SymbolicCaller.new(f.symbolic_call).to_lines.join("\n")
243
- puts "```"
297
+ reason = StringIO.new
298
+ reason.puts "Failed in case of `#{f.called_str}`"
299
+ reason.puts
300
+ reason.puts "### Repro"
301
+ reason.puts
302
+ reason.puts "```rb"
303
+ reason.puts SymbolicCaller.new(f.symbolic_call).to_lines.join("\n")
304
+ reason.puts "```"
244
305
  status = 1
245
306
  throw :break
246
307
  in Result::Skip => s
247
308
  print 'S'
248
- RaaP.logger.debug { PP.pp(s.symbolic_call, ''.dup) }
249
- RaaP.logger.debug("Skip: [#{s.exception.class}] #{s.exception.message}")
309
+ RaaP.logger.debug { "\n```\n#{SymbolicCaller.new(s.symbolic_call).to_lines.join("\n")}\n```" }
310
+ RaaP.logger.debug("Skip: #{s.exception.detailed_message}")
250
311
  RaaP.logger.debug(s.exception.backtrace.join("\n"))
251
312
  in Result::Exception => e
252
313
  print 'E'
253
- RaaP.logger.debug { PP.pp(e.symbolic_call, ''.dup) }
254
- RaaP.logger.debug("Exception: [#{e.exception.class}] #{e.exception.message}")
314
+ RaaP.logger.debug { "\n```\n#{SymbolicCaller.new(e.symbolic_call).to_lines.join("\n")}\n```" }
315
+ RaaP.logger.debug("Exception: #{e.exception.detailed_message}")
255
316
  RaaP.logger.debug(e.exception.backtrace.join("\n"))
256
317
  end
257
318
  end
319
+ end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
258
320
  puts
259
- puts "success: #{stats.success}, skip: #{stats.skip}, exception: #{stats.exception}"
260
- puts
321
+ time_diff = end_time - start_time
322
+ time = ", time: #{(time_diff * 1000).round}ms"
323
+ stats_log = "success: #{stats.success}, skip: #{stats.skip}, exception: #{stats.exception}#{time}"
324
+ RaaP.logger.info(stats_log)
325
+
326
+ if status == 0 && stats.success.zero? && !stats.break
327
+ status = 1
328
+ reason = StringIO.new
329
+ reason.puts "Never succeeded => #{stats_log}"
330
+ end
261
331
 
262
- [status, method_name, method_type]
332
+ [status, method_name, method_type, reason]
263
333
  end
264
334
  end
265
335
  end
@@ -6,35 +6,39 @@ module RaaP
6
6
  @fun = fun
7
7
  end
8
8
 
9
- def pick_arguments(size: 10, eval: true)
10
- a = recursive_pick(build_args_type, size:, eval:)
11
- k = recursive_pick(build_kwargs_type, size:, eval:)
9
+ def pick_arguments(size: 10)
10
+ SymbolicCaller.new(arguments_to_symbolic_call(size:)).eval
11
+ end
12
+
13
+ def arguments_to_symbolic_call(size: 10)
14
+ a = to_symbolic_call_recursive(build_args_type, size:)
15
+ k = to_symbolic_call_recursive(build_kwargs_type, size:)
12
16
 
13
17
  [a, k]
14
18
  end
15
19
 
16
20
  private
17
21
 
18
- def recursive_pick(type, size:, eval:)
22
+ def to_symbolic_call_recursive(type, size:)
19
23
  case
20
24
  when type.nil?
21
25
  nil
22
26
  when type.respond_to?(:each_pair)
23
- type.each_pair.to_h { |k, v| [k, recursive_pick(v, size:, eval:)] }
27
+ type.each_pair.to_h { |k, v| [k, to_symbolic_call_recursive(v, size:)] }
24
28
  when type.respond_to?(:each)
25
- type.each.map { |v| recursive_pick(v, size:, eval:) }
29
+ type.each.map { |v| to_symbolic_call_recursive(v, size:) }
26
30
  else
27
- type.pick(size:, eval:)
31
+ type.to_symbolic_call(size:)
28
32
  end
29
33
  end
30
34
 
31
35
  def build_args_type
32
36
  reqs = @fun.required_positionals.map { |param| Type.new(param.type) }
33
37
  tras = @fun.trailing_positionals.map { |param| Type.new(param.type) }
34
- sampled_optional_positionals = @fun.optional_positionals.sample(Random.rand(@fun.optional_positionals.length + 1))
38
+ sampled_optional_positionals = @fun.optional_positionals.take(Random.rand(@fun.optional_positionals.length + 1))
35
39
  opts = sampled_optional_positionals.map { |param| Type.new(param.type) }
36
40
  rest = []
37
- if param = @fun.rest_positionals
41
+ if (param = @fun.rest_positionals)
38
42
  rest = Array.new(Random.rand(0..3)) { Type.new(param.type) }
39
43
  end
40
44
  [reqs, opts, rest, tras].flatten
@@ -45,7 +49,7 @@ module RaaP
45
49
  rand = Random.rand(@fun.optional_keywords.length + 1)
46
50
  opts = @fun.optional_keywords.to_a.sample(rand).to_h { |name, param| [name, Type.new(param.type)] }
47
51
  kwargs = reqs.to_h.merge(opts)
48
- if param = @fun.rest_keywords
52
+ if (param = @fun.rest_keywords)
49
53
  keys = Array.new(Random.rand(0..3)) do
50
54
  random_key = nil
51
55
  loop do