rgot 1.1.0 → 1.3.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: edabddcdf99d28070fbe94f391d49d7c034cb1b6d467629c76fc671f7d27956b
4
- data.tar.gz: 642606670788ef496178d91b9450f6c08bb97e8e4f9e14442e0868ddf0c2acbd
3
+ metadata.gz: e2f0075689755b0a44db0674a5069769d1ee4c2567774d7bb7422ba5b27c8960
4
+ data.tar.gz: 9ddd64091ca0daeff256fcc5c305aad378a92472f4522b875512fb35915d74aa
5
5
  SHA512:
6
- metadata.gz: eb8824c1730be5395ffb22b3c075239b612a987db000367c804201394305b080834bb246c7c198da556e26090ae7a8424830b6253f7c10fd0183662ff6d490d0
7
- data.tar.gz: 4064bdb31930c1cc0a072ab6c7113f93f66ce8cd3bafd4208824c36a5c591685a0e82f7e09758ff32a4d342eefee64efeb92910e8b45c75a48175571f84e03f7
6
+ metadata.gz: 2e24662ccc2f6f4644eec95f9764975b86d2ea40f49c3c8ae569f428809ca08df79e6517d2bbcf0ec7d08e610867972133a1a9ab5df245cf36896afb8ace7e63
7
+ data.tar.gz: ac5c041deb124812d911d0af697a06a398ced2c79bc54f6058ec4cb2499af9dd814007f3e0c8951c50448892ba0fbc0a6846dfd879c3497601b4aebf5d00116e
@@ -14,13 +14,13 @@ jobs:
14
14
  strategy:
15
15
  matrix:
16
16
  ruby:
17
- - '2.6.10'
18
- - '2.7.6'
19
- - '3.0.4'
20
- - '3.1.2'
17
+ - '2.7.7'
18
+ - '3.0.5'
19
+ - '3.1.3'
20
+ - '3.2.0'
21
21
 
22
22
  steps:
23
- - uses: actions/checkout@v2
23
+ - uses: actions/checkout@v3
24
24
  - name: Set up Ruby
25
25
  uses: ruby/setup-ruby@v1
26
26
  with:
data/.gitignore CHANGED
@@ -1,3 +1,3 @@
1
- Gemfile.lock
2
1
  pkg
3
2
  tmp
3
+ /.gem_rbs_collection
data/Gemfile CHANGED
@@ -1,3 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ gem "rbs"
6
+ gem "steep"
7
+ gem "debug"
data/Gemfile.lock ADDED
@@ -0,0 +1,73 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rgot (1.3.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ activesupport (7.0.4)
10
+ concurrent-ruby (~> 1.0, >= 1.0.2)
11
+ i18n (>= 1.6, < 2)
12
+ minitest (>= 5.1)
13
+ tzinfo (~> 2.0)
14
+ ast (2.4.2)
15
+ concurrent-ruby (1.1.10)
16
+ csv (3.2.6)
17
+ debug (1.7.1)
18
+ ffi (1.15.5)
19
+ fileutils (1.7.0)
20
+ i18n (1.12.0)
21
+ concurrent-ruby (~> 1.0)
22
+ json (2.6.3)
23
+ language_server-protocol (3.17.0.2)
24
+ listen (3.7.1)
25
+ rb-fsevent (~> 0.10, >= 0.10.3)
26
+ rb-inotify (~> 0.9, >= 0.9.10)
27
+ logger (1.5.3)
28
+ minitest (5.17.0)
29
+ parallel (1.22.1)
30
+ parser (3.2.0.0)
31
+ ast (~> 2.4.1)
32
+ rainbow (3.1.1)
33
+ rake (13.0.6)
34
+ rb-fsevent (0.11.2)
35
+ rb-inotify (0.10.1)
36
+ ffi (~> 1.0)
37
+ rbs (2.8.2)
38
+ securerandom (0.2.2)
39
+ steep (1.3.0)
40
+ activesupport (>= 5.1)
41
+ csv (>= 3.0.9)
42
+ fileutils (>= 1.1.0)
43
+ json (>= 2.1.0)
44
+ language_server-protocol (>= 3.15, < 4.0)
45
+ listen (~> 3.0)
46
+ logger (>= 1.3.0)
47
+ parallel (>= 1.0.0)
48
+ parser (>= 3.1)
49
+ rainbow (>= 2.2.2, < 4.0)
50
+ rbs (>= 2.8.0)
51
+ securerandom (>= 0.1)
52
+ strscan (>= 1.0.0)
53
+ terminal-table (>= 2, < 4)
54
+ strscan (3.0.5)
55
+ terminal-table (3.0.2)
56
+ unicode-display_width (>= 1.1.1, < 3)
57
+ tzinfo (2.0.5)
58
+ concurrent-ruby (~> 1.0)
59
+ unicode-display_width (2.4.2)
60
+
61
+ PLATFORMS
62
+ ruby
63
+
64
+ DEPENDENCIES
65
+ bundler
66
+ debug
67
+ rake
68
+ rbs
69
+ rgot!
70
+ steep
71
+
72
+ BUNDLED WITH
73
+ 2.4.2
data/README.md CHANGED
@@ -93,6 +93,33 @@ ok FooTest 2.782s
93
93
 
94
94
  `b.n` is automatically adjusted.
95
95
 
96
+ ## Fuzzing
97
+
98
+ ```
99
+ $ rgot target_file_test.rb --fuzz . --fuzztime 1
100
+ ```
101
+
102
+ Fuzzing tests are also supported.
103
+ Please refer to the gloang documentation for details.
104
+
105
+ https://go.dev/security/fuzz/
106
+
107
+ ```ruby
108
+ module FooTest
109
+ # To enable fuzzing, the method name
110
+ # should be prefixed with `fuzz`.
111
+ def fuzz_any_func(f)
112
+ f.add(5, "hello")
113
+ f.fuzz do |t, i, s|
114
+ out, err = foo(i, s)
115
+ if err != nil && out != ""
116
+ t.errorf("%s, %s", out, err)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ ```
122
+
96
123
  ## Example
97
124
 
98
125
  Rgot's example feature is the best and if you want to write the sample code of your library.
@@ -189,6 +216,8 @@ Method name should be set `test_*` for testing.
189
216
 
190
217
  And benchmark method should be set `benchmark_*`.
191
218
 
219
+ And fuzz method should be set `fuzz_*`.
220
+
192
221
  And example method should be set `example_*`.
193
222
 
194
223
  ```ruby
@@ -199,6 +228,9 @@ module XxxTest
199
228
  def benchmark_any_name(b)
200
229
  end
201
230
 
231
+ def fuzz_any_name(f)
232
+ end
233
+
202
234
  def example_any_name
203
235
  end
204
236
  end
@@ -218,6 +250,8 @@ Usage: rgot [options]
218
250
  --thread [count,...] set thread counts of comma split
219
251
  --require [path] load some code before running
220
252
  --load-path [path] Specify $LOAD_PATH directory
253
+ --fuzz [regexp] run the fuzz test matching `regexp`
254
+ --fuzztime [sec] time to spend fuzzing; default is to run indefinitely
221
255
  ```
222
256
 
223
257
  ## Basic
@@ -341,7 +375,7 @@ And this is default virtual main code.
341
375
  ```ruby
342
376
  module TestSomeCode
343
377
  def test_main(m)
344
- exit m.run
378
+ m.run
345
379
  end
346
380
  end
347
381
  ```
@@ -362,7 +396,7 @@ module TestSomeCode
362
396
  the_before_running_some_code
363
397
  code = m.run
364
398
  the_after_running_some_code
365
- exit code
399
+ code
366
400
  end
367
401
  end
368
402
  ```
@@ -515,3 +549,39 @@ def benchmark_foo(b)
515
549
  end
516
550
  end
517
551
  ```
552
+
553
+ ## Rgot::F (Fuzzing)
554
+
555
+ ### Rgot::F#add
556
+
557
+ Set the sample value with `#add`. This value is also used as a test. It guesses the type from the value and generates a random value.
558
+
559
+ ### Rgot::F#fuzz
560
+
561
+ Generate the random value generated by `#fuzz` and execute the code.
562
+ The `t` becomes an instance of `Rgot::T` and the test can be run as usual.
563
+
564
+ ```ruby
565
+ def fuzz_foo(f)
566
+ f.add(100, "hello")
567
+ f.fuzz do |t, i, s|
568
+ i #=> 100, 84, 17, 9, 66, ...
569
+ s #=> "hello", "Y\xD5\xAB\xBA\x8E", "r\x95D\xA5\xF7", "\xCEj=\x9C\xBD", ...
570
+ if !foo(i, s)
571
+ t.error("fail with i=#{i}, s=#{s}")
572
+ end
573
+ end
574
+ end
575
+ ```
576
+
577
+ # TODO
578
+
579
+ - [ ] Support to save and load fuzzing data
580
+
581
+ ## v2
582
+
583
+ - [ ] Support sub testing
584
+ - [ ] Fix duration argument unit
585
+ - [ ] Refactoring
586
+ - [ ] Fix M#initialize argument
587
+ - [ ] Fix internal class API
data/Rakefile CHANGED
@@ -5,6 +5,9 @@ task :test do |t|
5
5
  targets = [
6
6
  "test/rgot_common_test.rb",
7
7
  "test/rgot_test.rb",
8
+ "test/rgot_benchmark_test.rb",
9
+ "test/rgot_example_test.rb",
10
+ "test/rgot_fuzzing_test.rb",
8
11
  ]
9
12
  ruby "bin/rgot -v #{targets.join(' ')}"
10
13
  end
data/Steepfile ADDED
@@ -0,0 +1,4 @@
1
+ target :lib do
2
+ check "lib"
3
+ signature "sig"
4
+ end
data/bin/rgot CHANGED
@@ -1,4 +1,4 @@
1
1
  #! /usr/bin/env ruby
2
2
  require 'rgot/cli'
3
3
 
4
- Rgot::Cli.new(ARGV).run
4
+ exit Rgot::Cli.new(ARGV).run
data/lib/rgot/b.rb CHANGED
@@ -1,12 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rgot
2
4
  class B < Common
3
- Options = Struct.new(
5
+ Options = _ = Struct.new(
4
6
  :procs,
5
7
  :threads,
6
8
  :benchtime,
7
9
  )
8
10
 
11
+ # @dynamic n, n=
9
12
  attr_accessor :n
13
+
10
14
  def initialize(benchmark_module, name, opts = Options.new)
11
15
  super()
12
16
  @n = 1
@@ -14,7 +18,7 @@ module Rgot
14
18
  @name = name
15
19
  @opts = opts
16
20
  @timer_on = false
17
- @duration = 0
21
+ @duration = 0.0
18
22
  @module.extend @module if @module
19
23
  end
20
24
 
@@ -36,34 +40,34 @@ module Rgot
36
40
  if @timer_on
37
41
  @start = Rgot.now
38
42
  end
39
- @duration = 0
43
+ @duration = 0.0
40
44
  end
41
45
 
42
46
  def run(&block)
43
47
  n = 1
44
48
  benchtime = (@opts.benchtime || 1).to_f
45
49
  catch(:skip) do
46
- run_n(n.to_i, block)
47
- while !failed? && @duration < benchtime && @n < 1e9
50
+ run_n(n, block)
51
+ while !failed? && @duration < benchtime && n < 1e9
48
52
  if @duration < (benchtime / 100.0)
49
- @n *= 100
53
+ n *= 100
50
54
  elsif @duration < (benchtime / 10.0)
51
- @n *= 10
55
+ n *= 10
52
56
  elsif @duration < (benchtime / 5.0)
53
- @n *= 5
57
+ n *= 5
54
58
  elsif @duration < (benchtime / 2.0)
55
- @n *= 2
59
+ n *= 2
56
60
  else
57
- if @n.to_i == 1
61
+ if n == 1
58
62
  break
59
63
  end
60
- @n *= 1.2
64
+ n = [(n * 1.2).to_i, n + 1].max || raise
61
65
  end
62
- run_n(@n.to_i, block)
66
+ run_n(n, block)
63
67
  end
64
68
  end
65
69
 
66
- BenchmarkResult.new(n: @n, t: @duration)
70
+ BenchmarkResult.new(n: n, t: @duration)
67
71
  end
68
72
 
69
73
  def run_parallel
@@ -81,7 +85,6 @@ module Rgot
81
85
  }.each(&:join)
82
86
  end
83
87
  end
84
- @n *= procs * threads
85
88
  Process.waitall
86
89
  end
87
90
 
@@ -1,5 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rgot
2
4
  class BenchmarkResult
5
+ # @dynamic n, t
6
+ attr_reader :n
7
+ attr_reader :t
8
+
3
9
  def initialize(n:, t:)
4
10
  @n = n
5
11
  @t = t
data/lib/rgot/cli.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
4
 
3
5
  require_relative '../rgot'
@@ -11,7 +13,7 @@ module Rgot
11
13
  def run
12
14
  opts = Rgot::M::Options.new
13
15
  parse_option(opts)
14
- process_start(opts)
16
+ main_process(opts)
15
17
  end
16
18
 
17
19
  private
@@ -50,6 +52,15 @@ module Rgot
50
52
  o.on '--load-path [path]', "Specify $LOAD_PATH directory" do |arg|
51
53
  $LOAD_PATH.unshift(arg)
52
54
  end
55
+ o.on '--fuzz [regexp]', "run the fuzz test matching `regexp`" do |arg|
56
+ unless arg
57
+ raise Rgot::OptionError, "missing argument for flag --fuzz"
58
+ end
59
+ opts.fuzz = arg
60
+ end
61
+ o.on '--fuzztime [sec]', "time to spend fuzzing; default is to run indefinitely" do |arg|
62
+ opts.fuzztime = arg
63
+ end
53
64
  end
54
65
  parser.parse!(@argv)
55
66
 
@@ -74,81 +85,97 @@ module Rgot
74
85
  end
75
86
  end
76
87
 
77
- def process_start(opts)
88
+ def main_process(opts)
78
89
  code = 0
79
- testing_files.map do |testing_file|
80
- begin
81
- pid = fork do
82
- require testing_file
83
-
84
- modules = Object.constants.select { |c|
85
- next if c == :FileTest
86
- /.*Test\z/ =~ c
87
- }.map { |c|
88
- Object.const_get(c)
89
- }
90
-
91
- modules.each do |test_module|
92
- tests = []
93
- benchmarks = []
94
- examples = []
95
- main = nil
96
- methods = test_module.instance_methods
97
- methods.grep(/\Atest_/).each do |m|
98
- if m == :test_main && main.nil?
99
- main = Rgot::InternalTest.new(test_module, m)
100
- else
101
- tests << Rgot::InternalTest.new(test_module, m)
102
- end
103
- end
104
-
105
- methods.grep(/\Abenchmark_/).each do |m|
106
- benchmarks << Rgot::InternalBenchmark.new(test_module, m)
107
- end
108
-
109
- methods.grep(/\Aexample_?/).each do |m|
110
- examples << Rgot::InternalExample.new(test_module, m)
111
- end
112
-
113
- duration = Rgot.now
114
- at_exit do
115
- template = "%s\t%s\t%.3fs"
116
-
117
- case $!
118
- when SystemExit
119
- if $!.success?
120
- # exit 0
121
- puts sprintf(template, "ok ", test_module, Rgot.now - duration)
122
- else
123
- # exit 1
124
- puts "exit status #{$!.status}"
125
- puts sprintf(template, "FAIL", test_module, Rgot.now - duration)
126
- end
127
- when NilClass
128
- # not raise, not exit
129
- else
130
- # any exception
131
- puts sprintf(template, "FAIL", test_module, Rgot.now - duration)
132
- end
133
- end
134
- m = Rgot::M.new(tests: tests, benchmarks: benchmarks, examples: examples, opts: opts)
135
- if main
136
- main.module.extend main.module
137
- main.module.instance_method(main.name).bind(main.module).call(m)
138
- else
139
- exit m.run
140
- end
141
- end
142
- end
143
- ensure
144
- _, status = Process.waitpid2(pid)
145
- unless status.success?
146
- code = 1
147
- end
90
+
91
+ testing_files.each do |testing_file|
92
+ result = child_process(opts, testing_file)
93
+ unless result == 0
94
+ code = 1
95
+ end
96
+ end
97
+
98
+ code
99
+ end
100
+
101
+ def child_process(opts, testing_file)
102
+ node = RubyVM::AbstractSyntaxTree.parse_file(testing_file).children[2]
103
+ test_module_name = find_toplevel_name(node)
104
+
105
+ if opts.fuzz
106
+ # fuzzing observes changes in coverage.
107
+ require 'coverage'
108
+ Coverage.start(oneshot_lines: true)
109
+ end
110
+ load testing_file
111
+
112
+ test_module = Object.const_get(test_module_name)
113
+ tests = []
114
+ benchmarks = []
115
+ examples = []
116
+ fuzz_targets = []
117
+ main = nil
118
+ methods = test_module.public_instance_methods
119
+ methods.grep(/\Atest_/).each do |m|
120
+ if m == :test_main && main.nil?
121
+ main = Rgot::InternalTest.new(test_module, m)
122
+ else
123
+ tests << Rgot::InternalTest.new(test_module, m)
148
124
  end
149
125
  end
150
126
 
151
- exit code
127
+ methods.grep(/\Abenchmark_/).each do |m|
128
+ benchmarks << Rgot::InternalBenchmark.new(test_module, m)
129
+ end
130
+
131
+ methods.grep(/\Aexample_?/).each do |m|
132
+ examples << Rgot::InternalExample.new(test_module, m)
133
+ end
134
+
135
+ methods.grep(/\Afuzz_/).each do |m|
136
+ fuzz_targets << Rgot::InternalFuzzTarget.new(test_module, m)
137
+ end
138
+
139
+ m = Rgot::M.new(
140
+ test_module: test_module,
141
+ tests: tests,
142
+ benchmarks: benchmarks,
143
+ examples: examples,
144
+ fuzz_targets: fuzz_targets,
145
+ opts: opts
146
+ )
147
+ if main
148
+ main.module.extend main.module
149
+ main.module.instance_method(:test_main).bind(main.module).call(m)
150
+ else
151
+ m.run
152
+ end
153
+ end
154
+
155
+ def find_toplevel_name(node)
156
+ case node.type
157
+ when :MODULE
158
+ find_toplevel_name(node.children.first)
159
+ when :CONST, :COLON3
160
+ node.children.first
161
+ when :COLON2
162
+ case node.children
163
+ in [nil, sym]
164
+ # module Foo
165
+ sym
166
+ in [namespace, sym]
167
+ # module Foo::Bar
168
+ find_toplevel_name(namespace)
169
+ end
170
+ when :BLOCK
171
+ module_node = node.children.find { |c| c.type == :MODULE }
172
+ unless module_node
173
+ raise "no module found"
174
+ end
175
+ find_toplevel_name(module_node)
176
+ else
177
+ raise node.type.to_s
178
+ end
152
179
  end
153
180
  end
154
181
  end
data/lib/rgot/common.rb CHANGED
@@ -1,17 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thread'
2
4
  require 'pathname'
3
5
 
4
6
  module Rgot
5
7
  class Common
8
+ # @dynamic output, output=
6
9
  attr_accessor :output
7
10
 
8
11
  def initialize
9
- @output = ""
12
+ @output = "".dup
10
13
  @failed = false
11
14
  @skipped = false
12
15
  @finished = false
13
16
  @start = Rgot.now
14
- @mutex = Mutex.new
17
+ @mutex = Thread::Mutex.new
15
18
  end
16
19
 
17
20
  def failed?
@@ -98,9 +101,10 @@ module Rgot
98
101
  # internal_log -> synchronize -> internal_log -> other log -> running method
99
102
  c = caller[4]
100
103
  path = c.sub(/:.*/, '')
101
- line = c.match(/:(\d+?):/)[1]
104
+ line = c.match(/:(\d+?):/)&.[](1)
102
105
  relative_path = Pathname.new(path).relative_path_from(Pathname.new(Dir.pwd)).to_s
103
- "\t#{relative_path}:#{line}: #{str}\n"
106
+ # Every line is indented at least 4 spaces.
107
+ " #{relative_path}:#{line}: #{str}\n"
104
108
  end
105
109
 
106
110
  def internal_log(msg)
@@ -1,14 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ripper'
2
4
 
3
5
  module Rgot
4
6
  class ExampleParser < Ripper
7
+
8
+ # @dynamic examples, examples=
5
9
  attr_accessor :examples
10
+
6
11
  def initialize(code)
7
12
  super
8
13
  @examples = []
9
14
  @in_def = false
10
15
  @has_output = false
11
- @output = ""
16
+ @output = "".dup
12
17
  end
13
18
 
14
19
  def on_def(method, args, body)