rgot 1.1.0 → 1.3.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: 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)