raap 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 272cb44584fb297591d356de0c90ecfbcf782951ed4a02432eb4cd87a6849f0f
4
- data.tar.gz: d0776eeeb3f840a1730f16a9df385414e3697631191961b986085773cad49e04
3
+ metadata.gz: 312e27904176673d6962904901cb3e8d31c6b613b8efdf669c877d4670cf9d15
4
+ data.tar.gz: fe940b9d2928a54bfbba8a4842c097cb2a8efe6cf0b873baa383f757de37d017
5
5
  SHA512:
6
- metadata.gz: '09ace505a4973cac5f075a133fc6e13c8fa34bde72adf522cc095ad3accfccec574b3209aaf4bca8deceb398b5b36f0893672722504895d51413c65877efc167'
7
- data.tar.gz: 1fe3eb4b5695bd69b2a30fdd2035a81aa3be1a289dee120f4664f95bb1d6d9a5ab8849f11acb957e8ac97e8dfb174f49536542fc96f29ecfade076e15b76619e
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
 
@@ -78,6 +80,21 @@ For example, an Integer with size zero is `0` and an Array is `[]`.
78
80
 
79
81
  RaaP, like other property-based tests, changes the size 100 times from 0 to 99 by default to generate test data.
80
82
 
83
+ ## Symbolic call
84
+
85
+ You may possibly see the following data structure in the debug logs.
86
+
87
+ ```rb
88
+ [:call, Object, :method, [], {}, nil]
89
+ ```
90
+
91
+ This is a data structure of the state of the method call.
92
+
93
+ Symbolic call is a tuple beginning with `:call`, followed by the receiver, method name, positional arguments, keyword arguments, and block arguments, in that order.
94
+ And the receiver and arguments are nestable.
95
+
96
+ It is used in QuickCheck and Proper to keep as much history of method calls as possible.
97
+
81
98
  ## Options
82
99
 
83
100
  ### `-I PATH` or `--include PATH`
@@ -108,6 +125,10 @@ You can specify size of end.
108
125
 
109
126
  You can specify size of step like `Integer#step: (to: Integer, by: Integer)`.
110
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
+
111
132
  ## Achievements
112
133
 
113
134
  RaaP has already found many RBS mistakes and bug in CRuby during the development phase.
@@ -119,6 +140,11 @@ RaaP has already found many RBS mistakes and bug in CRuby during the development
119
140
  * https://github.com/ruby/rbs/pull/1769
120
141
  * https://github.com/ruby/rbs/pull/1770
121
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
122
148
 
123
149
  ## Development
124
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,
@@ -16,197 +11,264 @@ module RaaP
16
11
  :size_from,
17
12
  :size_to,
18
13
  :size_by,
14
+ :allow_private,
15
+ :skips,
19
16
  keyword_init: true
20
17
  )
21
18
 
22
- # defaults
23
- self.option = Option.new(
24
- dirs: [],
25
- requires: [],
26
- libraries: [],
27
- timeout: 3,
28
- size_from: 0,
29
- size_to: 99,
30
- size_by: 1,
31
- )
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
32
37
 
33
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
+ )
34
51
  @argv = argv
52
+ @skip = DEFAULT_SKIP.dup
53
+ @results = []
35
54
  end
36
55
 
37
56
  def load
38
57
  OptionParser.new do |o|
39
58
  o.on('-I', '--include PATH') do |path|
40
- CLI.option.dirs << path
59
+ @option.dirs << path
41
60
  end
42
61
  o.on('--library lib', 'load rbs library') do |lib|
43
- CLI.option.libraries << lib
62
+ @option.libraries << lib
44
63
  end
45
64
  o.on('--require lib', 'require ruby library') do |lib|
46
- CLI.option.requires << lib
65
+ @option.requires << lib
47
66
  end
48
67
  o.on('--log-level level', "default: warn") do |arg|
49
68
  RaaP.logger.level = arg
50
69
  end
51
- o.on('--timeout sec', Integer, "default: #{CLI.option.timeout}") do |arg|
52
- 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
75
+ end
76
+ o.on('--size-to int', Integer, "default: #{@option.size_to}") do |arg|
77
+ @option.size_to = arg
53
78
  end
54
- o.on('--size-from int', Integer, "default: #{CLI.option.size_from}") do |arg|
55
- CLI.option.size_from = arg
79
+ o.on('--size-by int', Integer, "default: #{@option.size_by}") do |arg|
80
+ @option.size_by = arg
56
81
  end
57
- o.on('--size-to int', Integer, "default: #{CLI.option.size_to}") do |arg|
58
- CLI.option.size_to = arg
82
+ o.on('--allow-private', "default: #{@option.allow_private}") do
83
+ @option.allow_private = true
59
84
  end
60
- o.on('--size-by int', Integer, "default: #{CLI.option.size_by}") do |arg|
61
- CLI.option.size_by = arg
85
+ o.on('--skip tag', "skip case (e.g. `Foo#meth`)") do |tag|
86
+ @option.skips << tag
62
87
  end
63
88
  end.parse!(@argv)
64
89
 
65
- CLI.option.dirs.each do |dir|
90
+ @option.dirs.each do |dir|
66
91
  RaaP::RBS.loader.add(path: Pathname(dir))
67
92
  end
68
- CLI.option.libraries.each do |lib|
93
+ @option.libraries.each do |lib|
69
94
  RaaP::RBS.loader.add(library: lib, version: nil)
70
95
  end
71
- CLI.option.requires.each do |lib|
96
+ @option.requires.each do |lib|
72
97
  require lib
73
98
  end
99
+ @option.skips.each do |skip|
100
+ @skip << skip
101
+ end
102
+ @skip.freeze
74
103
 
75
104
  self
76
105
  end
77
106
 
78
107
  def run
108
+ Signal.trap(:INT) do
109
+ puts "Interrupted by SIGINT"
110
+ report
111
+ exit 1
112
+ end
113
+
79
114
  @argv.map do |tag|
80
115
  case
81
116
  when tag.include?('#')
82
- run_by_instance(tag:)
117
+ run_by(kind: :instance, tag:)
83
118
  when tag.include?('.')
84
- run_by_singleton(tag:)
119
+ run_by(kind: :singleton, tag:)
85
120
  when tag.end_with?('*')
86
121
  run_by_type_name_with_search(tag:)
87
122
  else
88
123
  run_by_type_name(tag:)
89
124
  end
90
- end.each do |ret|
91
- ret.each do |methods|
92
- methods.each do |status, method_name, method_type|
93
- if status == 1
94
- puts "Fail:"
95
- puts "def #{method_name}: #{method_type}"
96
- end
97
- end
98
- end
99
125
  end
100
126
 
101
- self
127
+ report
102
128
  end
103
129
 
104
- def run_by_instance(tag:)
105
- t, m = tag.split('#', 2)
106
- t or raise
107
- m or raise
108
- type = RBS.parse_type(t)
109
- type = __skip__ = type
110
- raise "cannot specified #{type}" unless type.respond_to?(:name)
111
- receiver_type = Type.new(type.to_s)
112
- method_name = m.to_sym
113
- definition = RBS.builder.build_instance(type.name)
114
- type_params_decl = definition.type_params_decl
115
- method = definition.methods[method_name]
116
- raise "`#{tag}` is not found" unless method
117
- puts "# #{type.to_s}"
118
- puts
119
- [
120
- method.method_types.map do |method_type|
121
- 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
122
157
  end
123
- ]
158
+ end
159
+ exit_status
124
160
  end
125
161
 
126
- def run_by_singleton(tag:)
127
- t, m = tag.split('.', 2)
162
+ def run_by(kind:, tag:)
163
+ split = kind == :instance ? '#' : '.'
164
+ t, m = tag.split(split, 2)
128
165
  t or raise
129
166
  m or raise
130
167
  type = RBS.parse_type(t)
131
168
  raise "cannot specified #{type.class}" unless type.respond_to?(:name)
169
+
132
170
  type = __skip__ = type
133
- 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
134
178
  method_name = m.to_sym
135
- 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
+
136
185
  method = definition.methods[method_name]
137
- type_params_decl = definition.type_params_decl
138
186
  raise "`#{tag}` not found" unless method
139
- puts "# #{type}"
140
- puts
141
- [
142
- method.method_types.map do |method_type|
143
- 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:)
144
200
  end
145
- ]
201
+ }
146
202
  end
147
203
 
148
204
  def run_by_type_name_with_search(tag:)
149
205
  first, _last = tag.split('::')
150
- ret = []
151
- RBS.env.class_decls.each do |name, entry|
206
+ RBS.env.class_decls.each do |name, _entry|
152
207
  if ['', '::'].any? { |pre| name.to_s.match?(/\A#{pre}#{first}\b/) }
153
- ret << run_by_type_name(tag: name.to_s)
208
+ run_by_type_name(tag: name.to_s)
154
209
  end
155
210
  end
156
- ret.flatten(1)
157
211
  end
158
212
 
159
213
  def run_by_type_name(tag:)
160
214
  type = RBS.parse_type(tag)
161
215
  type = __skip__ = type
162
216
  raise "cannot specified #{type.class}" unless type.respond_to?(:name)
163
- type_name = type.name.absolute!
164
217
 
165
- ret = []
218
+ type_name = type.name.absolute!
219
+ type_args = type.args
166
220
 
167
221
  definition = RBS.builder.build_singleton(type_name)
168
222
  type_params_decl = definition.type_params_decl
169
223
  definition.methods.filter_map do |method_name, method|
224
+ next if @skip.include?("#{type_name.absolute!}.#{method_name}")
170
225
  next unless method.accessibility == :public
171
226
  next if method.defined_in != type_name
172
- next if method_name == :fork || method_name == :spawn # TODO: skip solution
173
- puts "# #{type_name}.#{method_name}"
174
- puts
175
- ret << method.method_types.map do |method_type|
176
- property(receiver_type: Type.new("singleton(#{type})"), type_params_decl:, method_type:, method_name:)
177
- 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
+ }
178
235
  end
179
236
 
180
237
  definition = RBS.builder.build_instance(type_name)
181
238
  type_params_decl = definition.type_params_decl
182
239
  definition.methods.filter_map do |method_name, method|
240
+ next if @skip.include?("#{type_name.absolute!}##{method_name}")
183
241
  next unless method.accessibility == :public
184
242
  next if method.defined_in != type_name
185
- next if method_name == :fork || method_name == :spawn # TODO: skip solution
186
- puts "# #{type_name}##{method_name}"
187
- puts
188
- ret << method.method_types.map do |method_type|
189
- property(receiver_type: Type.new(type.to_s), type_params_decl:, method_type:, method_name:)
190
- end
191
- end
192
243
 
193
- 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
194
252
  end
195
253
 
196
- 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:)
197
255
  rtype = __skip__ = receiver_type.type
198
256
  if receiver_type.type.instance_of?(::RBS::Types::ClassSingleton)
199
257
  prefix = 'self.'
200
- type_args = []
201
258
  else
202
259
  prefix = ''
203
- type_args = rtype.args
204
260
  end
205
- 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}")
206
267
  status = 0
207
- stats = MethodProperty.new(
268
+ reason = nil
269
+ prop = MethodProperty.new(
208
270
  receiver_type:,
209
- method_name: method_name,
271
+ method_name:,
210
272
  method_type: MethodType.new(
211
273
  method_type,
212
274
  type_params_decl:,
@@ -215,39 +277,59 @@ module RaaP
215
277
  instance_type: ::RBS::Types::ClassInstance.new(name: rtype.name, args: type_args, location: nil),
216
278
  class_type: ::RBS::Types::ClassSingleton.new(name: rtype.name, location: nil),
217
279
  ),
218
- size_step: CLI.option.size_from.step(to: CLI.option.size_to, by: CLI.option.size_by),
219
- timeout: CLI.option.timeout,
220
- ).run do |called|
280
+ size_step: @option.size_from.step(to: @option.size_to, by: @option.size_by),
281
+ timeout: @option.timeout,
282
+ allow_private: true,
283
+ )
284
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
285
+ stats = prop.run do |called|
221
286
  case called
222
287
  in Result::Success => s
223
288
  print '.'
224
289
  RaaP.logger.debug { "Success: #{s.called_str}" }
225
290
  in Result::Failure => f
226
291
  puts 'F'
227
- puts "Failed in case of `#{f.called_str}`"
228
- puts
292
+ if (e = f.exception)
293
+ RaaP.logger.debug { "Failure: [#{e.class}] #{e.message}" }
294
+ RaaP.logger.debug { e.backtrace.join("\n") }
295
+ end
229
296
  RaaP.logger.debug { PP.pp(f.symbolic_call, ''.dup) }
230
- puts "### call stack:"
231
- puts
232
- puts "```"
233
- puts SymbolicCaller.new(f.symbolic_call).to_lines.join("\n")
234
- 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 "```"
235
305
  status = 1
236
306
  throw :break
237
307
  in Result::Skip => s
238
308
  print 'S'
239
- 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}")
311
+ RaaP.logger.debug(s.exception.backtrace.join("\n"))
240
312
  in Result::Exception => e
241
313
  print 'E'
242
- RaaP.logger.info("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}")
243
316
  RaaP.logger.debug(e.exception.backtrace.join("\n"))
244
317
  end
245
318
  end
319
+ end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
246
320
  puts
247
- puts "success: #{stats.success}, skip: #{stats.skip}, exception: #{stats.exception}"
248
- 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
249
331
 
250
- [status, method_name, method_type]
332
+ [status, method_name, method_type, reason]
251
333
  end
252
334
  end
253
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