fuzzbert 1.0.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.
- data/LICENSE +20 -0
- data/README.md +204 -0
- data/bin/fuzzbert +9 -0
- data/lib/fuzzbert.rb +36 -0
- data/lib/fuzzbert/autorun.rb +65 -0
- data/lib/fuzzbert/autorun.rb~ +33 -0
- data/lib/fuzzbert/container.rb +21 -0
- data/lib/fuzzbert/dsl.rb +9 -0
- data/lib/fuzzbert/error_handler.rb +19 -0
- data/lib/fuzzbert/executor.rb +153 -0
- data/lib/fuzzbert/generation.rb +8 -0
- data/lib/fuzzbert/generator.rb +18 -0
- data/lib/fuzzbert/generators.rb +70 -0
- data/lib/fuzzbert/mutator.rb +19 -0
- data/lib/fuzzbert/rake_task.rb +81 -0
- data/lib/fuzzbert/template.rb +154 -0
- data/lib/fuzzbert/test.rb +13 -0
- data/lib/fuzzbert/test_suite.rb +25 -0
- data/lib/fuzzbert/version.rb +4 -0
- data/spec/autorun_spec.rb +39 -0
- data/spec/dsl_spec.rb +88 -0
- data/spec/executor_spec.rb +113 -0
- data/spec/generator_spec.rb +25 -0
- data/spec/mutator_spec.rb +32 -0
- data/spec/template_spec.rb +84 -0
- data/spec/test_spec.rb +20 -0
- metadata +80 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
class FuzzBert::Generator
|
3
|
+
include FuzzBert::Generation
|
4
|
+
|
5
|
+
attr_reader :description
|
6
|
+
|
7
|
+
def initialize(desc, generator=nil, &blk)
|
8
|
+
@description = desc
|
9
|
+
@generator = generator || blk
|
10
|
+
raise RuntimeError.new("No generator given") unless @generator
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_data
|
14
|
+
@generator.call
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module FuzzBert::Generators
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def random(limit=1024)
|
8
|
+
-> { random_bytes(limit) { |data| data } }
|
9
|
+
end
|
10
|
+
|
11
|
+
def random_b64(limit=1024)
|
12
|
+
-> { random_bytes(b64_len(limit)) { |data| Base64.encode64(data) } }
|
13
|
+
end
|
14
|
+
|
15
|
+
def random_hex(limit=1024)
|
16
|
+
-> { random_bytes(hex_len(limit)) { |data| data.unpack("H*")[0] } }
|
17
|
+
end
|
18
|
+
|
19
|
+
def random_fixlen(len)
|
20
|
+
-> { random_bytes_fixlen(len) { |data| data } }
|
21
|
+
end
|
22
|
+
|
23
|
+
def random_b64_fixlen(len)
|
24
|
+
-> { random_bytes_fixlen(b64_len(len)) { |data| Base64.encode(data) } }
|
25
|
+
end
|
26
|
+
|
27
|
+
def random_hex_fixlen(len)
|
28
|
+
-> { random_bytes_fixlen(hex_len(len)) { |data| data.unpack("H*")[0] } }
|
29
|
+
end
|
30
|
+
|
31
|
+
def cycle(range)
|
32
|
+
ary = range.to_a
|
33
|
+
i = 0
|
34
|
+
lambda do
|
35
|
+
ret = ary[i]
|
36
|
+
i = (i + 1) % ary.size
|
37
|
+
ret
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def fixed(data)
|
42
|
+
-> { data }
|
43
|
+
end
|
44
|
+
|
45
|
+
def sample(ary)
|
46
|
+
-> { ary.sample }
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def hex_len(len)
|
52
|
+
len / 2
|
53
|
+
end
|
54
|
+
|
55
|
+
def b64_len(len)
|
56
|
+
len * 3 / 4
|
57
|
+
end
|
58
|
+
|
59
|
+
def random_bytes(limit)
|
60
|
+
len = FuzzBert::PRNG.rand(1..limit)
|
61
|
+
yield FuzzBert::PRNG.bytes(len)
|
62
|
+
end
|
63
|
+
|
64
|
+
def random_bytes_fixlen(len)
|
65
|
+
yield FuzzBert::PRNG.bytes(len)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
class FuzzBert::Mutator < FuzzBert::Generator
|
3
|
+
|
4
|
+
def initialize(value)
|
5
|
+
orig = value.dup
|
6
|
+
orig.force_encoding(Encoding::BINARY)
|
7
|
+
super("Mutator") do
|
8
|
+
#select a byte
|
9
|
+
i = FuzzBert::PRNG.rand(value.size)
|
10
|
+
old = orig[i].ord
|
11
|
+
#map a random value from 0..254 to 0..255 excluding the current value
|
12
|
+
b = FuzzBert::PRNG.rand(255)
|
13
|
+
b = b < old ? b : b + 1
|
14
|
+
orig.dup.tap { |s| s.setbyte(i, b) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'fuzzbert'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/tasklib'
|
4
|
+
|
5
|
+
class FuzzBert::RakeTask < ::Rake::TaskLib
|
6
|
+
include ::Rake::DSL if defined?(::Rake::DSL)
|
7
|
+
|
8
|
+
# Name of task.
|
9
|
+
#
|
10
|
+
# default:
|
11
|
+
# :fuzz
|
12
|
+
attr_accessor :name
|
13
|
+
|
14
|
+
# Glob pattern to match files.
|
15
|
+
#
|
16
|
+
# default:
|
17
|
+
# 'fuzz/**/fuzz_*.rb'
|
18
|
+
attr_accessor :pattern
|
19
|
+
|
20
|
+
# Command line options to pass to ruby.
|
21
|
+
#
|
22
|
+
# default:
|
23
|
+
# nil
|
24
|
+
attr_accessor :ruby_opts
|
25
|
+
|
26
|
+
# Path to FuzzBert
|
27
|
+
#
|
28
|
+
# default:
|
29
|
+
# 'fuzzbert'
|
30
|
+
attr_accessor :fuzzbert_path
|
31
|
+
|
32
|
+
# Command line options to pass to fuzzbert.
|
33
|
+
#
|
34
|
+
# default:
|
35
|
+
# nil
|
36
|
+
attr_accessor :fuzzbert_opts
|
37
|
+
|
38
|
+
def initialize(*args)
|
39
|
+
#configure the rake task
|
40
|
+
setup_ivars(args)
|
41
|
+
yield self if block_given?
|
42
|
+
|
43
|
+
desc "Run FuzzBert random test suite" unless ::Rake.application.last_comment
|
44
|
+
|
45
|
+
task name do
|
46
|
+
run_task
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def setup_ivars(*args)
|
51
|
+
@name = args.shift || :fuzz
|
52
|
+
@ruby_opts, @fuzzbert_opts = nil, nil
|
53
|
+
@fuzzbert_path = 'fuzzbert'
|
54
|
+
@pattern = 'fuzz/**/fuzz_*.rb'
|
55
|
+
end
|
56
|
+
|
57
|
+
def run_task
|
58
|
+
begin
|
59
|
+
system(command)
|
60
|
+
rescue
|
61
|
+
#silent, user could have interrupted a permanent session
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def command
|
68
|
+
cmd_parts = []
|
69
|
+
cmd_parts << RUBY
|
70
|
+
cmd_parts << ruby_opts
|
71
|
+
cmd_parts << "-S" << fuzzbert_path
|
72
|
+
cmd_parts << fuzzbert_opts
|
73
|
+
cmd_parts << pattern
|
74
|
+
cmd_parts.flatten.reject(&blank).join(" ")
|
75
|
+
end
|
76
|
+
|
77
|
+
def blank
|
78
|
+
lambda {|s| s.nil? || s == ""}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
class FuzzBert::Template
|
4
|
+
include FuzzBert::Generation
|
5
|
+
|
6
|
+
def initialize(template)
|
7
|
+
@template = Parser.new(template).parse
|
8
|
+
@callbacks = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def set(name, &blk)
|
12
|
+
@callbacks[name] = blk
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_data
|
16
|
+
"".tap do |buf|
|
17
|
+
@template.each { |t| buf << t.to_data(@callbacks) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
class Parser
|
24
|
+
|
25
|
+
def initialize(template)
|
26
|
+
@io = StringIO.new(template)
|
27
|
+
@template = []
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse
|
31
|
+
@state = determine_state
|
32
|
+
while token = parse_token
|
33
|
+
@template << token
|
34
|
+
end
|
35
|
+
@template
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_token
|
39
|
+
case @state
|
40
|
+
when :TEXT
|
41
|
+
parse_text
|
42
|
+
when :IDENTIFIER
|
43
|
+
parse_identifier
|
44
|
+
else
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def determine_state
|
50
|
+
begin
|
51
|
+
@buf = @io.readchar
|
52
|
+
|
53
|
+
case @buf
|
54
|
+
when '$'
|
55
|
+
c = @io.readchar
|
56
|
+
if c == "{"
|
57
|
+
@buf = ""
|
58
|
+
:IDENTIFIER
|
59
|
+
else
|
60
|
+
@buf << c
|
61
|
+
:TEXT
|
62
|
+
end
|
63
|
+
when '\\'
|
64
|
+
@buf = ""
|
65
|
+
:TEXT
|
66
|
+
else
|
67
|
+
:TEXT
|
68
|
+
end
|
69
|
+
rescue EOFError
|
70
|
+
:EOF
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse_identifier
|
75
|
+
name = ""
|
76
|
+
begin
|
77
|
+
until (c = @io.readchar) == '}'
|
78
|
+
name << c
|
79
|
+
end
|
80
|
+
|
81
|
+
if name.empty?
|
82
|
+
raise RuntimeError.new("No identifier name given")
|
83
|
+
end
|
84
|
+
|
85
|
+
@state = determine_state
|
86
|
+
Identifier.new(name)
|
87
|
+
rescue EOFError
|
88
|
+
raise RuntimeError.new("Unclosed identifier")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_text
|
93
|
+
text = @buf
|
94
|
+
begin
|
95
|
+
loop do
|
96
|
+
until (c = @io.readchar) == '$'
|
97
|
+
if c == '\\'
|
98
|
+
text << parse_escape
|
99
|
+
else
|
100
|
+
text << c
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
d = @io.readchar
|
105
|
+
if d == "{"
|
106
|
+
@state = :IDENTIFIER
|
107
|
+
return Text.new(text)
|
108
|
+
else
|
109
|
+
text << c
|
110
|
+
text << d
|
111
|
+
end
|
112
|
+
end
|
113
|
+
rescue EOFError
|
114
|
+
@state = :EOF
|
115
|
+
Text.new(text)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def parse_escape
|
120
|
+
begin
|
121
|
+
@io.readchar
|
122
|
+
rescue EOFError
|
123
|
+
'\\'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
class Text
|
130
|
+
|
131
|
+
def initialize(text)
|
132
|
+
@text = text
|
133
|
+
end
|
134
|
+
|
135
|
+
def to_data(callbacks)
|
136
|
+
@text
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
class Identifier
|
142
|
+
|
143
|
+
def initialize(name)
|
144
|
+
@name = name.to_sym
|
145
|
+
end
|
146
|
+
|
147
|
+
def to_data(callbacks)
|
148
|
+
callbacks[@name].call
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
class FuzzBert::TestSuite
|
3
|
+
|
4
|
+
attr_reader :description, :test, :generators
|
5
|
+
|
6
|
+
def initialize(desc)
|
7
|
+
@description = desc
|
8
|
+
@generators = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def deploy(&blk)
|
12
|
+
@test = FuzzBert::Test.new(blk)
|
13
|
+
end
|
14
|
+
|
15
|
+
def data(desc, &blk)
|
16
|
+
@generators << FuzzBert::Generator.new(desc, blk.call)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.create(desc, &blk)
|
20
|
+
obj = self.new(desc)
|
21
|
+
obj.instance_eval(&blk) if blk
|
22
|
+
obj
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'fuzzbert'
|
3
|
+
|
4
|
+
describe FuzzBert::AutoRun do
|
5
|
+
|
6
|
+
let(:handler) do
|
7
|
+
c = Class.new
|
8
|
+
def c.handle(id, data, pid, status)
|
9
|
+
raise RuntimeError.new
|
10
|
+
end
|
11
|
+
c
|
12
|
+
end
|
13
|
+
|
14
|
+
value = "test"
|
15
|
+
called = false
|
16
|
+
|
17
|
+
fuzz "autorun" do
|
18
|
+
|
19
|
+
deploy do |data|
|
20
|
+
data.should == value
|
21
|
+
end
|
22
|
+
|
23
|
+
data "1" do
|
24
|
+
called = true
|
25
|
+
-> { value }
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
it "has added a TestSuite to AutoRun" do
|
31
|
+
suite = FuzzBert::AutoRun::TEST_CASES
|
32
|
+
suite.size.should == 1
|
33
|
+
FuzzBert::Executor.new(suite, pool_size: 1, limit: 1, handler: handler).run
|
34
|
+
called.should == true
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
|
data/spec/dsl_spec.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'fuzzbert'
|
3
|
+
|
4
|
+
describe FuzzBert::TestSuite do
|
5
|
+
describe "fuzz" do
|
6
|
+
context "with one generator" do
|
7
|
+
let(:suite) do
|
8
|
+
FuzzBert::TestSuite.create "test" do
|
9
|
+
deploy { |data| data }
|
10
|
+
|
11
|
+
data("1") { FuzzBert::Generators.random }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns an instance of TestSuite" do
|
16
|
+
suite.should be_an_instance_of(FuzzBert::TestSuite)
|
17
|
+
suite.description.should == "test"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "defines a test that executes the block in deploy" do
|
21
|
+
suite.test.should_not be_nil
|
22
|
+
suite.test.should be_an_instance_of(FuzzBert::Test)
|
23
|
+
["a", 10, true, Object.new].each { |o| suite.test.run(o).should == o }
|
24
|
+
end
|
25
|
+
|
26
|
+
it "defines one generator" do
|
27
|
+
suite.generators.size.should == 1
|
28
|
+
gen = suite.generators.first
|
29
|
+
gen.should be_an_instance_of(FuzzBert::Generator)
|
30
|
+
gen.description.should == "1"
|
31
|
+
gen.to_data.should be_an_instance_of(String)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
context "with two generators" do
|
37
|
+
let(:suite) do
|
38
|
+
FuzzBert::TestSuite.create "test" do
|
39
|
+
deploy { |data| data }
|
40
|
+
|
41
|
+
data("1") { FuzzBert::Generators.fixed(1) }
|
42
|
+
data("2") { FuzzBert::Generators.fixed(2) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "defines two generators" do
|
47
|
+
suite.generators.size.should == 2
|
48
|
+
gen = suite.generators.first
|
49
|
+
gen.should be_an_instance_of(FuzzBert::Generator)
|
50
|
+
gen.description.should == "1"
|
51
|
+
gen.to_data.should == 1
|
52
|
+
|
53
|
+
gen = suite.generators[1]
|
54
|
+
gen.should be_an_instance_of(FuzzBert::Generator)
|
55
|
+
gen.description.should == "2"
|
56
|
+
gen.to_data.should == 2
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with complex data" do
|
62
|
+
let(:suite) do
|
63
|
+
FuzzBert::TestSuite.create "test" do
|
64
|
+
deploy { |data| data }
|
65
|
+
|
66
|
+
data "1" do
|
67
|
+
c = FuzzBert::Container.new
|
68
|
+
c << FuzzBert::Generators.fixed("1")
|
69
|
+
c << FuzzBert::Generators.fixed("2")
|
70
|
+
c << FuzzBert::Generators.fixed("3")
|
71
|
+
c.generator
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it "applies the container generators in sequence" do
|
78
|
+
suite.generators.size.should == 1
|
79
|
+
gen = suite.generators.first
|
80
|
+
gen.should be_an_instance_of(FuzzBert::Generator)
|
81
|
+
gen.description.should == "1"
|
82
|
+
gen.to_data.should == "123"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|