raap 0.2.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: 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