raap 0.3.0 → 0.5.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: 507ed156280ed3e0db1f2eb454bb03c57aa473d537d8c6ea5905ad852eba550d
4
- data.tar.gz: 3108fa50c664702f79141a3c0b5e83934d6742468d1d51bfb2febb5b64a00b12
3
+ metadata.gz: 94f0ec4ff924c5089f8e307002dd5f3cd778f04e686acd8b46466139c3c1aebd
4
+ data.tar.gz: e9a06fb3c71cfc6cd605031fe8cb899a123dc82a94ef00586d39e8329c45beac
5
5
  SHA512:
6
- metadata.gz: 269e220d72b9861881de860a71a2adff318105595880fe73e86492e255c9bb32fbeff7ac3685b83ea11d792a02b1509eb969d9956483a9415608530a3f8806bd
7
- data.tar.gz: df4e40973681eb292d7844aa5ed1427b7155f619c6ebea53792149f430ab28e42a0a0f52df9517a3af9af1e08494eaccdd8d4d84313c9d24184f6403a51e1dc5
6
+ metadata.gz: ed0d647fefa8591c0a03981232c84d64bf7c70bcf9207d2dd6471a9f3369747b5dd3adcc3cd60a8e1d65f0e1543ed7a5f5108eef6523c7d306cfc399d0bc63e4
7
+ data.tar.gz: 537c5dfbb65bb12064e46c47fb75a60321ee0f16296f1272ef27d68927f4f9f3d5875ea6f3ac2225c76a2d274fbe393bff20f573b8d138b52a6ced8027528e1f
data/.rubocop.yml ADDED
@@ -0,0 +1,31 @@
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
+ Style/FrozenStringLiteralComment:
18
+ Enabled: true
19
+
20
+ Metrics:
21
+ Enabled: false
22
+
23
+ Layout:
24
+ Enabled: true
25
+ Layout/FirstArrayElementIndentation:
26
+ EnforcedStyle: consistent
27
+ Layout/LineLength:
28
+ Max: 150
29
+
30
+ Naming:
31
+ Enabled: false
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # RaaP
2
2
 
3
+ <img src="https://raw.githubusercontent.com/ksss/raap/main/public/jacket.webp" width=400/>
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
 
@@ -68,6 +70,15 @@ $ raap 'MyClass' # Run only RBS of MyClass
68
70
  $ raap 'MyClass::*' # Run each class under MyClass
69
71
  $ raap 'MyClass.singleton_method' # Run only MyClass.singleton_method
70
72
  $ raap 'MyClass#instance_method' # Run only MyClass#instance_method
73
+ $ raap 'MyClass' '!MyClass#skip' # Run method MyClass without #skip
74
+ ```
75
+
76
+ ```
77
+ $ cat test/raap.txt
78
+ MyClass
79
+ !MyClass#skip
80
+
81
+ $ raap $(cat test/raap.txt) # You can manage the methods to be tested in a file
71
82
  ```
72
83
 
73
84
  ## Size
@@ -101,11 +112,15 @@ You can specify to load specify PATH as RBS.
101
112
 
102
113
  ### `--library lib`
103
114
 
104
- You can specify to load RBS library
115
+ You can specify to load RBS library.
105
116
 
106
117
  ### `--require lib`
107
118
 
108
- You can specify require Ruby library
119
+ You can specify require Ruby library.
120
+
121
+ ### `--log-level level`
122
+
123
+ You can specify log level (debug, info, warn or error).
109
124
 
110
125
  ### `--timeout sec`
111
126
 
@@ -123,6 +138,15 @@ You can specify size of end.
123
138
 
124
139
  You can specify size of step like `Integer#step: (to: Integer, by: Integer)`.
125
140
 
141
+ ### `--allow-private`
142
+
143
+ By default, raap only validates public methods.
144
+ However, by setting this option, it is possible to intentionally validate private methods in the RBS.
145
+
146
+ ## First support is CLI
147
+
148
+ 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.
149
+
126
150
  ## Achievements
127
151
 
128
152
  RaaP has already found many RBS mistakes and bug in CRuby during the development phase.
@@ -134,6 +158,11 @@ RaaP has already found many RBS mistakes and bug in CRuby during the development
134
158
  * https://github.com/ruby/rbs/pull/1769
135
159
  * https://github.com/ruby/rbs/pull/1770
136
160
  * https://github.com/ruby/rbs/pull/1771
161
+ * https://github.com/ruby/rbs/pull/1779
162
+ * https://github.com/ruby/rbs/pull/1783
163
+ * https://github.com/ruby/rbs/pull/1789
164
+ * https://github.com/ruby/rbs/pull/1790
165
+ * https://github.com/ruby/rbs/pull/1793
137
166
 
138
167
  ## Development
139
168
 
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,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
1
4
  require 'raap'
2
5
 
3
- RaaP::CLI.new(ARGV).load.run
6
+ exit RaaP::CLI.new(ARGV).load.run
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RaaP
2
4
  module BindCall
3
5
  class << self
4
- def define_singleton_method(...) = ::Object.instance_method(:define_singleton_method).bind_call(...)
6
+ def define_method(...) = ::Module.instance_method(:define_method).bind_call(...)
5
7
  def respond_to?(...) = ::Kernel.instance_method(:respond_to?).bind_call(...)
6
8
  def instance_of?(...) = ::Kernel.instance_method(:instance_of?).bind_call(...)
7
9
  def is_a?(...) = ::Kernel.instance_method(:is_a?).bind_call(...)
@@ -11,19 +13,31 @@ module RaaP
11
13
  def public_send(...) = ::Kernel.instance_method(:public_send).bind_call(...)
12
14
 
13
15
  def class(obj)
14
- if respond_to?(obj, :class)
15
- obj.class
16
- else
16
+ if instance_of?(obj, BasicObject)
17
17
  ::Kernel.instance_method(:class).bind_call(obj)
18
+ else
19
+ obj.class
18
20
  end
19
21
  end
20
22
 
21
23
  def inspect(obj)
22
- if respond_to?(obj, :inspect)
23
- obj.inspect
24
- else
24
+ if instance_of?(obj, BasicObject)
25
25
  ::Kernel.instance_method(:inspect).bind_call(obj)
26
+ else
27
+ case obj
28
+ when Hash
29
+ body = obj.map do |k, v|
30
+ "#{inspect(k)} => #{inspect(v)}"
31
+ end
32
+ "{#{body.join(', ')}}"
33
+ when Array
34
+ "[#{obj.map { |o| inspect(o) }.join(', ')}]"
35
+ else
36
+ obj.inspect
37
+ end
26
38
  end
39
+ rescue NoMethodError
40
+ "#<#{self.class(obj)}>"
27
41
  end
28
42
  end
29
43
  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,
@@ -20,60 +15,80 @@ module RaaP
20
15
  keyword_init: true
21
16
  )
22
17
 
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
- )
18
+ # Should skip methods has side effects
19
+ DEFAULT_SKIP = Set.new
20
+ %i[
21
+ fork system exec spawn `
22
+ abort exit exit! raise fail
23
+ load require require_relative
24
+ gem
25
+ ].each do |kernel_method|
26
+ DEFAULT_SKIP << "::Kernel##{kernel_method}"
27
+ DEFAULT_SKIP << "::Kernel.#{kernel_method}"
28
+ end
29
+ %i[
30
+ delete unlink chmod lchmod chown lchown
31
+ link mkfifo new open rename truncate
32
+ ].each { |m| DEFAULT_SKIP << "::File.#{m}" }
33
+ %i[flock truncate].each { |m| DEFAULT_SKIP << "::File##{m}" }
34
+
35
+ attr_accessor :option, :argv, :skip, :results
34
36
 
35
37
  def initialize(argv)
38
+ # defaults
39
+ @option = Option.new(
40
+ dirs: [],
41
+ requires: [],
42
+ libraries: [],
43
+ timeout: 3,
44
+ size_from: 0,
45
+ size_to: 99,
46
+ size_by: 1,
47
+ allow_private: false,
48
+ )
36
49
  @argv = argv
50
+ @skip = DEFAULT_SKIP.dup
51
+ @results = []
37
52
  end
38
53
 
39
54
  def load
40
55
  OptionParser.new do |o|
41
56
  o.on('-I', '--include PATH') do |path|
42
- CLI.option.dirs << path
57
+ @option.dirs << path
43
58
  end
44
59
  o.on('--library lib', 'load rbs library') do |lib|
45
- CLI.option.libraries << lib
60
+ @option.libraries << lib
46
61
  end
47
62
  o.on('--require lib', 'require ruby library') do |lib|
48
- CLI.option.requires << lib
63
+ @option.requires << lib
49
64
  end
50
- o.on('--log-level level', "default: warn") do |arg|
65
+ o.on('--log-level level', "default: info") do |arg|
51
66
  RaaP.logger.level = arg
52
67
  end
53
- o.on('--timeout sec', Integer, "default: #{CLI.option.timeout}") do |arg|
54
- CLI.option.timeout = arg
68
+ o.on('--timeout sec', Float, "default: #{@option.timeout}") do |arg|
69
+ @option.timeout = arg
55
70
  end
56
- o.on('--size-from int', Integer, "default: #{CLI.option.size_from}") do |arg|
57
- CLI.option.size_from = arg
71
+ o.on('--size-from int', Integer, "default: #{@option.size_from}") do |arg|
72
+ @option.size_from = arg
58
73
  end
59
- o.on('--size-to int', Integer, "default: #{CLI.option.size_to}") do |arg|
60
- CLI.option.size_to = arg
74
+ o.on('--size-to int', Integer, "default: #{@option.size_to}") do |arg|
75
+ @option.size_to = arg
61
76
  end
62
- o.on('--size-by int', Integer, "default: #{CLI.option.size_by}") do |arg|
63
- CLI.option.size_by = arg
77
+ o.on('--size-by int', Integer, "default: #{@option.size_by}") do |arg|
78
+ @option.size_by = arg
64
79
  end
65
- o.on('--allow-private', "default: #{CLI.option.allow_private}") do
66
- CLI.option.allow_private = true
80
+ o.on('--allow-private', "default: #{@option.allow_private}") do
81
+ @option.allow_private = true
67
82
  end
68
83
  end.parse!(@argv)
69
84
 
70
- CLI.option.dirs.each do |dir|
85
+ @option.dirs.each do |dir|
71
86
  RaaP::RBS.loader.add(path: Pathname(dir))
72
87
  end
73
- CLI.option.libraries.each do |lib|
88
+ @option.libraries.each do |lib|
74
89
  RaaP::RBS.loader.add(library: lib, version: nil)
75
90
  end
76
- CLI.option.requires.each do |lib|
91
+ @option.requires.each do |lib|
77
92
  require lib
78
93
  end
79
94
 
@@ -81,137 +96,195 @@ module RaaP
81
96
  end
82
97
 
83
98
  def run
84
- @argv.map do |tag|
99
+ Signal.trap(:INT) do
100
+ puts "Interrupted by SIGINT"
101
+ report
102
+ exit 1
103
+ end
104
+
105
+ # Search skip tag
106
+ @argv.each do |tag|
107
+ if tag.start_with?('!') && (tag.include?('#') || tag.include?('.'))
108
+ t = tag[1..] or raise
109
+ t = "::#{t}" unless t.start_with?('::')
110
+ t or raise
111
+ @skip << t
112
+ end
113
+ end
114
+ @skip.freeze
115
+
116
+ @argv.each do |tag|
117
+ next if tag.start_with?('!')
118
+
85
119
  case
86
120
  when tag.include?('#')
87
- run_by_instance(tag:)
121
+ run_by(kind: :instance, tag:)
88
122
  when tag.include?('.')
89
- run_by_singleton(tag:)
123
+ run_by(kind: :singleton, tag:)
90
124
  when tag.end_with?('*')
91
125
  run_by_type_name_with_search(tag:)
92
126
  else
93
127
  run_by_type_name(tag:)
94
128
  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
129
  end
105
130
 
106
- self
131
+ report
107
132
  end
108
133
 
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:)
134
+ private
135
+
136
+ def report
137
+ i = 0
138
+ exit_status = 0
139
+ @results.each do |ret|
140
+ ret => { method:, properties: }
141
+ properties.select { |status,| status == 1 }.each do |_, method_name, method_type, reason|
142
+ i += 1
143
+ location = if method.alias_of
144
+ alias_decl = RBS.find_alias_decl(method.defined_in, method_name) or raise "alias decl not found: #{method_name}"
145
+ alias_decl.location
146
+ else
147
+ method_type.location
148
+ end
149
+ prefix = method.defs.first.member.kind == :instance ? '' : 'self.'
150
+
151
+ puts "\e[41m\e[1m#\e[m\e[1m #{i}) Failure:\e[m"
152
+ puts
153
+ puts "def #{prefix}#{method_name}: #{method_type}"
154
+ puts " in #{location}"
155
+ puts
156
+ puts "## Reason"
157
+ puts
158
+ puts reason&.string
159
+ puts
160
+ exit_status = 1
127
161
  end
128
- ]
162
+ end
163
+ exit_status
129
164
  end
130
165
 
131
- def run_by_singleton(tag:)
132
- t, m = tag.split('.', 2)
166
+ def run_by(kind:, tag:)
167
+ split = kind == :instance ? '#' : '.'
168
+ t, m = tag.split(split, 2)
133
169
  t or raise
134
170
  m or raise
135
171
  type = RBS.parse_type(t)
136
172
  raise "cannot specified #{type.class}" unless type.respond_to?(:name)
173
+
137
174
  type = __skip__ = type
138
- receiver_type = Type.new("singleton(#{type.name})")
175
+ type_name = type.name.absolute!
176
+ type_to_s = type.to_s.start_with?('::') ? type.to_s : "::#{type}"
177
+ receiver_type = if kind == :instance
178
+ Type.new(type_to_s)
179
+ else
180
+ Type.new("singleton(#{type_name})")
181
+ end
139
182
  method_name = m.to_sym
140
- definition = RBS.builder.build_singleton(type.name)
183
+ definition = if kind == :instance
184
+ RBS.builder.build_instance(type_name)
185
+ else
186
+ RBS.builder.build_singleton(type_name)
187
+ end
188
+
141
189
  method = definition.methods[method_name]
142
- type_params_decl = definition.type_params_decl
143
190
  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:)
191
+
192
+ if @skip.include?("#{type_name}#{split}#{method_name}")
193
+ raise "`#{type_name}#{split}#{method_name}` is a method to be skipped"
194
+ end
195
+
196
+ type_params_decl = definition.type_params_decl
197
+ type_args = type.args
198
+
199
+ RaaP.logger.info("# #{type}")
200
+ @results << {
201
+ method:,
202
+ properties: method.method_types.map do |method_type|
203
+ property(receiver_type:, type_params_decl:, type_args:, method_type:, method_name:)
149
204
  end
150
- ]
205
+ }
151
206
  end
152
207
 
153
208
  def run_by_type_name_with_search(tag:)
154
209
  first, _last = tag.split('::')
155
- ret = []
156
- RBS.env.class_decls.each do |name, entry|
157
- if ['', '::'].any? { |pre| name.to_s.match?(/\A#{pre}#{first}\b/) }
158
- ret << run_by_type_name(tag: name.to_s)
210
+ RBS.env.class_decls.each do |name, _entry|
211
+ if ['', '::'].any? { |pre| name.to_s.start_with?("#{pre}#{first}") }
212
+ if @skip.include?(name.to_s)
213
+ RaaP.logger.info("Skip #{name}")
214
+ next
215
+ end
216
+ run_by_type_name(tag: name.to_s)
159
217
  end
160
218
  end
161
- ret.flatten(1)
162
219
  end
163
220
 
164
221
  def run_by_type_name(tag:)
165
222
  type = RBS.parse_type(tag)
166
223
  type = __skip__ = type
167
224
  raise "cannot specified #{type.class}" unless type.respond_to?(:name)
168
- type_name = type.name.absolute!
169
225
 
170
- ret = []
226
+ type_name = type.name.absolute!
227
+ type_args = type.args
171
228
 
172
229
  definition = RBS.builder.build_singleton(type_name)
173
230
  type_params_decl = definition.type_params_decl
174
- definition.methods.filter_map do |method_name, method|
231
+ definition.methods.each do |method_name, method|
232
+ if @skip.include?("#{type_name.absolute!}.#{method_name}")
233
+ RaaP.logger.info("Skip #{"#{type_name.absolute!}.#{method_name}"}")
234
+ next
235
+ end
175
236
  next unless method.accessibility == :public
176
237
  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
238
+
239
+ RaaP.logger.info("# #{type_name}.#{method_name}")
240
+ @results << {
241
+ method:,
242
+ properties: method.method_types.map do |method_type|
243
+ property(receiver_type: Type.new("singleton(#{type.name})"), type_params_decl:, type_args:, method_type:, method_name:)
244
+ end
245
+ }
183
246
  end
184
247
 
185
248
  definition = RBS.builder.build_instance(type_name)
186
249
  type_params_decl = definition.type_params_decl
187
- definition.methods.filter_map do |method_name, method|
250
+ definition.methods.each do |method_name, method|
251
+ if @skip.include?("#{type_name.absolute!}##{method_name}")
252
+ RaaP.logger.info("Skip #{"#{type_name.absolute!}.#{method_name}"}")
253
+ next
254
+ end
188
255
  next unless method.accessibility == :public
189
256
  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
257
 
198
- ret
258
+ RaaP.logger.info("# #{type_name}##{method_name}")
259
+ @results << {
260
+ method:,
261
+ properties: method.method_types.map do |method_type|
262
+ property(receiver_type: Type.new(type.name), type_params_decl:, type_args:, method_type:, method_name:)
263
+ end
264
+ }
265
+ end
199
266
  end
200
267
 
201
- def property(receiver_type:, type_params_decl:, method_type:, method_name:)
268
+ def property(receiver_type:, type_params_decl:, type_args:, method_type:, method_name:)
202
269
  rtype = __skip__ = receiver_type.type
203
270
  if receiver_type.type.instance_of?(::RBS::Types::ClassSingleton)
204
271
  prefix = 'self.'
205
- type_args = []
206
272
  else
207
273
  prefix = ''
208
- type_args = rtype.args
209
274
  end
210
- puts "## def #{prefix}#{method_name}: #{method_type}"
275
+
276
+ # type_args delegate to self_type
277
+ type_params_decl.each_with_index do |param, i|
278
+ if rtype.instance_of?(::RBS::Types::ClassInstance)
279
+ rtype.args[i] = type_args[i] || param.upper_bound || ::RBS::Types::Bases::Any.new(location: nil)
280
+ end
281
+ end
282
+ RaaP.logger.info("## def #{prefix}#{method_name}: #{method_type}")
211
283
  status = 0
212
- stats = MethodProperty.new(
284
+ reason = nil
285
+ prop = MethodProperty.new(
213
286
  receiver_type:,
214
- method_name: method_name,
287
+ method_name:,
215
288
  method_type: MethodType.new(
216
289
  method_type,
217
290
  type_params_decl:,
@@ -220,46 +293,59 @@ module RaaP
220
293
  instance_type: ::RBS::Types::ClassInstance.new(name: rtype.name, args: type_args, location: nil),
221
294
  class_type: ::RBS::Types::ClassSingleton.new(name: rtype.name, location: nil),
222
295
  ),
223
- size_step: CLI.option.size_from.step(to: CLI.option.size_to, by: CLI.option.size_by),
224
- timeout: CLI.option.timeout,
225
- allow_private: true,
226
- ).run do |called|
296
+ size_step: @option.size_from.step(to: @option.size_to, by: @option.size_by),
297
+ timeout: @option.timeout,
298
+ allow_private: @option.allow_private,
299
+ )
300
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
301
+ stats = prop.run do |called|
227
302
  case called
228
303
  in Result::Success => s
229
304
  print '.'
230
305
  RaaP.logger.debug { "Success: #{s.called_str}" }
231
306
  in Result::Failure => f
232
307
  puts 'F'
233
- puts "Failed in case of `#{f.called_str}`"
234
- if e = f.exception
235
- RaaP.logger.debug { "Failure: [#{e.class}] #{e.message}" }
308
+ if (e = f.exception)
309
+ RaaP.logger.info { "Failure: [#{e.class}] #{e.message}" }
310
+ RaaP.logger.debug { e.backtrace.join("\n") }
236
311
  end
237
- puts
238
312
  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 "```"
313
+ reason = StringIO.new
314
+ reason.puts "Failed in case of `#{f.called_str}`"
315
+ reason.puts
316
+ reason.puts "### Repro"
317
+ reason.puts
318
+ reason.puts "```rb"
319
+ reason.puts SymbolicCaller.new(f.symbolic_call).to_lines.join("\n")
320
+ reason.puts "```"
244
321
  status = 1
245
322
  throw :break
246
323
  in Result::Skip => s
247
324
  print 'S'
248
- RaaP.logger.debug { PP.pp(s.symbolic_call, ''.dup) }
249
- RaaP.logger.debug("Skip: [#{s.exception.class}] #{s.exception.message}")
325
+ RaaP.logger.debug { "\n```\n#{SymbolicCaller.new(s.symbolic_call).to_lines.join("\n")}\n```" }
326
+ RaaP.logger.debug("Skip: #{s.exception.detailed_message}")
250
327
  RaaP.logger.debug(s.exception.backtrace.join("\n"))
251
328
  in Result::Exception => e
252
329
  print 'E'
253
- RaaP.logger.debug { PP.pp(e.symbolic_call, ''.dup) }
254
- RaaP.logger.debug("Exception: [#{e.exception.class}] #{e.exception.message}")
330
+ RaaP.logger.debug { "\n```\n#{SymbolicCaller.new(e.symbolic_call).to_lines.join("\n")}\n```" }
331
+ RaaP.logger.debug("Exception: #{e.exception.detailed_message}")
255
332
  RaaP.logger.debug(e.exception.backtrace.join("\n"))
256
333
  end
257
334
  end
335
+ end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
258
336
  puts
259
- puts "success: #{stats.success}, skip: #{stats.skip}, exception: #{stats.exception}"
260
- puts
337
+ time_diff = end_time - start_time
338
+ time = ", time: #{(time_diff * 1000).round}ms"
339
+ stats_log = "success: #{stats.success}, skip: #{stats.skip}, exception: #{stats.exception}#{time}"
340
+ RaaP.logger.info(stats_log)
341
+
342
+ if status == 0 && stats.success.zero? && !stats.break
343
+ status = 1
344
+ reason = StringIO.new
345
+ reason.puts "Never succeeded => #{stats_log}"
346
+ end
261
347
 
262
- [status, method_name, method_type]
348
+ [status, method_name, method_type, reason]
263
349
  end
264
350
  end
265
351
  end