flores 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +70 -0
- data/LICENSE.txt +662 -0
- data/Makefile +0 -19
- data/examples/analyze_number.rb +17 -0
- data/examples/socket_acceptance_spec.rb +25 -24
- data/examples/socket_analyze_spec.rb +18 -1
- data/examples/socket_stress_spec.rb +20 -3
- data/flores.gemspec +2 -3
- data/lib/randomized.rb +47 -17
- data/lib/rspec/stress_it.rb +82 -38
- data/spec/randomized_spec.rb +61 -24
- metadata +4 -2
data/Makefile
CHANGED
@@ -3,25 +3,6 @@ VERSION=$(shell awk -F\" '/spec.version/ { print $$2 }' $(GEMSPEC))
|
|
3
3
|
NAME=$(shell awk -F\" '/spec.name/ { print $$2 }' $(GEMSPEC))
|
4
4
|
GEM=$(NAME)-$(VERSION).gem
|
5
5
|
|
6
|
-
.PHONY: test
|
7
|
-
test:
|
8
|
-
sh notify-failure.sh ruby test/all.rb
|
9
|
-
|
10
|
-
.PHONY: testloop
|
11
|
-
testloop:
|
12
|
-
while true; do \
|
13
|
-
$(MAKE) test; \
|
14
|
-
$(MAKE) wait-for-changes; \
|
15
|
-
done
|
16
|
-
|
17
|
-
.PHONY: serve-coverage
|
18
|
-
serve-coverage:
|
19
|
-
cd coverage; python -mSimpleHTTPServer
|
20
|
-
|
21
|
-
.PHONY: wait-for-changes
|
22
|
-
wait-for-changes:
|
23
|
-
-inotifywait --exclude '\.swp' -e modify $$(find $(DIRS) -name '*.rb'; find $(DIRS) -type d)
|
24
|
-
|
25
6
|
.PHONY: package
|
26
7
|
package: | $(GEM)
|
27
8
|
|
data/examples/analyze_number.rb
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
# This file is part of ruby-flores.
|
2
|
+
# Copyright (C) 2015 Jordan Sissel
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU Affero General Public License as
|
6
|
+
# published by the Free Software Foundation, either version 3 of the
|
7
|
+
# License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU Affero General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Affero General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
#
|
17
|
+
# encoding: utf-8
|
1
18
|
require "randomized"
|
2
19
|
require "rspec/stress_it"
|
3
20
|
|
@@ -1,3 +1,20 @@
|
|
1
|
+
# This file is part of ruby-flores.
|
2
|
+
# Copyright (C) 2015 Jordan Sissel
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU Affero General Public License as
|
6
|
+
# published by the Free Software Foundation, either version 3 of the
|
7
|
+
# License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU Affero General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Affero General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
#
|
17
|
+
# encoding: utf-8
|
1
18
|
require "randomized"
|
2
19
|
require "socket"
|
3
20
|
require "rspec/stress_it"
|
@@ -6,6 +23,10 @@ RSpec.configure do |c|
|
|
6
23
|
c.extend RSpec::StressIt
|
7
24
|
end
|
8
25
|
|
26
|
+
# A factory for encapsulating behavior of a tcp server and client for the
|
27
|
+
# purposes of testing.
|
28
|
+
#
|
29
|
+
# This is probably not really a "factory" in a purist-sense, but whatever.
|
9
30
|
class TCPIntegrationTestFactory
|
10
31
|
def initialize(port)
|
11
32
|
@listener = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
@@ -33,7 +54,6 @@ class TCPIntegrationTestFactory
|
|
33
54
|
|
34
55
|
@client.syswrite(text)
|
35
56
|
@client.close
|
36
|
-
#expect(client.syswrite(text)).to(be == text.bytesize)
|
37
57
|
server.read
|
38
58
|
ensure
|
39
59
|
@client.close unless @client.closed?
|
@@ -42,32 +62,12 @@ class TCPIntegrationTestFactory
|
|
42
62
|
end
|
43
63
|
|
44
64
|
describe "TCPServer+TCPSocket" do
|
45
|
-
let(:port) { Randomized.
|
46
|
-
let(:text) { Randomized.text(1..
|
65
|
+
let(:port) { Randomized.integer(1024..65535) }
|
66
|
+
let(:text) { Randomized.text(1..2000) }
|
47
67
|
subject { TCPIntegrationTestFactory.new(port) }
|
48
|
-
|
49
|
-
#describe "using before/after and stress_it2" do
|
50
|
-
#before do
|
51
|
-
#begin
|
52
|
-
#subject.setup
|
53
|
-
#rescue Errno::EADDRINUSE
|
54
|
-
## We chose a random port that was already in use, let's skip this test.
|
55
|
-
#skip("Port #{port} is in use by another process, skipping")
|
56
|
-
#end
|
57
|
-
#end
|
58
|
-
|
59
|
-
#after do
|
60
|
-
#subject.teardown
|
61
|
-
#end
|
62
|
-
|
63
|
-
#stress_it2 "should send data correctly" do
|
64
|
-
#received = subject.send_and_receive(text)
|
65
|
-
#expect(received).to(be == text)
|
66
|
-
#end
|
67
|
-
#end
|
68
68
|
|
69
69
|
describe "using stress_it" do
|
70
|
-
|
70
|
+
analyze_it "should send data correctly", [:port, :text] do
|
71
71
|
begin
|
72
72
|
subject.setup
|
73
73
|
rescue Errno::EADDRINUSE
|
@@ -76,6 +76,7 @@ describe "TCPServer+TCPSocket" do
|
|
76
76
|
|
77
77
|
begin
|
78
78
|
received = subject.send_and_receive(text)
|
79
|
+
expect(received.encoding).to(be == text.encoding)
|
79
80
|
expect(received).to(be == text)
|
80
81
|
ensure
|
81
82
|
subject.teardown
|
@@ -1,3 +1,20 @@
|
|
1
|
+
# This file is part of ruby-flores.
|
2
|
+
# Copyright (C) 2015 Jordan Sissel
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU Affero General Public License as
|
6
|
+
# published by the Free Software Foundation, either version 3 of the
|
7
|
+
# License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU Affero General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Affero General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
#
|
17
|
+
# encoding: utf-8
|
1
18
|
require "randomized"
|
2
19
|
require "socket"
|
3
20
|
require "rspec/stress_it"
|
@@ -12,7 +29,7 @@ describe TCPServer do
|
|
12
29
|
after { socket.close }
|
13
30
|
|
14
31
|
context "on a random port" do
|
15
|
-
let(:port) { Randomized.
|
32
|
+
let(:port) { Randomized.integer(-100_000..100_000) }
|
16
33
|
analyze_it "should bind successfully", [:port] do
|
17
34
|
socket.bind(sockaddr)
|
18
35
|
expect(socket.local_address.ip_port).to(be == port)
|
@@ -1,3 +1,20 @@
|
|
1
|
+
# This file is part of ruby-flores.
|
2
|
+
# Copyright (C) 2015 Jordan Sissel
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU Affero General Public License as
|
6
|
+
# published by the Free Software Foundation, either version 3 of the
|
7
|
+
# License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU Affero General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Affero General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
#
|
17
|
+
# encoding: utf-8
|
1
18
|
require "randomized"
|
2
19
|
require "socket"
|
3
20
|
require "rspec/stress_it"
|
@@ -13,7 +30,7 @@ describe TCPServer do
|
|
13
30
|
proc do |m, *args|
|
14
31
|
begin
|
15
32
|
m.call(*args)
|
16
|
-
rescue Errno::EADDRINUSE
|
33
|
+
rescue Errno::EADDRINUSE # rubocop:disable Lint/HandleExceptions
|
17
34
|
# ignore
|
18
35
|
end
|
19
36
|
end
|
@@ -24,14 +41,14 @@ describe TCPServer do
|
|
24
41
|
end
|
25
42
|
|
26
43
|
context "on privileged ports" do
|
27
|
-
let(:port) { Randomized.
|
44
|
+
let(:port) { Randomized.integer(1..1023) }
|
28
45
|
stress_it "should raise Errno::EACCESS" do
|
29
46
|
expect { socket.bind(sockaddr) }.to(raise_error(Errno::EACCES))
|
30
47
|
end
|
31
48
|
end
|
32
49
|
|
33
50
|
context "on unprivileged ports" do
|
34
|
-
let(:port) { Randomized.
|
51
|
+
let(:port) { Randomized.integer(1025..65535) }
|
35
52
|
stress_it "should bind on a port" do
|
36
53
|
# EADDRINUSE is expected since we are picking ports at random
|
37
54
|
# Let's ignore this specific exception
|
data/flores.gemspec
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
|
-
files = %x
|
2
|
+
files = %x(git ls-files).split("\n")
|
3
3
|
|
4
4
|
spec.name = "flores"
|
5
|
-
spec.version = "0.0.
|
5
|
+
spec.version = "0.0.2"
|
6
6
|
spec.summary = "Fuzz, randomize, and stress your tests"
|
7
7
|
spec.description = <<-DESCRIPTION
|
8
8
|
Add fuzzing, randomization, and stress to your tests.
|
@@ -20,4 +20,3 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.authors = ["Jordan Sissel"]
|
21
21
|
spec.email = ["jls@semicomplete.com"]
|
22
22
|
end
|
23
|
-
|
data/lib/randomized.rb
CHANGED
@@ -1,5 +1,42 @@
|
|
1
|
+
# This file is part of ruby-flores.
|
2
|
+
# Copyright (C) 2015 Jordan Sissel
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU Affero General Public License as
|
6
|
+
# published by the Free Software Foundation, either version 3 of the
|
7
|
+
# License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU Affero General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Affero General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
#
|
17
|
+
# encoding: utf-8
|
18
|
+
|
1
19
|
# A collection of methods intended for use in randomized testing.
|
2
20
|
module Randomized
|
21
|
+
# A selection of UTF-8 characters
|
22
|
+
#
|
23
|
+
# I'd love to generate this, but I don't yet know enough about how unicode
|
24
|
+
# blocks are allocated to do that. For now, hardcode a set of possible
|
25
|
+
# characters.
|
26
|
+
CHARACTERS = [
|
27
|
+
# Basic Latin
|
28
|
+
*(32..126).map(&:chr),
|
29
|
+
|
30
|
+
# hand-selected CJK Unified Ideographs Extension A
|
31
|
+
"㐤", "㐨", "㐻", "㑐",
|
32
|
+
|
33
|
+
# hand-selected Hebrew
|
34
|
+
"א", "ב", "ג", "ד", "ה",
|
35
|
+
|
36
|
+
# hand-selected Cyrillic
|
37
|
+
"Є", "Б", "Р", "н", "я"
|
38
|
+
]
|
39
|
+
|
3
40
|
# Generates text with random characters of a given length (or within a length range)
|
4
41
|
#
|
5
42
|
# * The length can be a number or a range `x..y`. If a range, it must be ascending (x < y)
|
@@ -8,30 +45,23 @@ module Randomized
|
|
8
45
|
# @param length [Fixnum or Range] the length of text to generate
|
9
46
|
# @return [String] the
|
10
47
|
def self.text(length)
|
11
|
-
if length.is_a?(Range)
|
12
|
-
raise ArgumentError, "Requires ascending range, you gave #{length}." if length.end < length.begin
|
13
|
-
raise ArgumentError, "A negative length is not permitted, I received range #{length}" if length.begin < 0
|
14
|
-
|
15
|
-
length = integer(length)
|
16
|
-
else
|
17
|
-
raise ArgumentError, "A negative length is not permitted, I received #{length}" if length < 0
|
18
|
-
end
|
48
|
+
return text_range(length) if length.is_a?(Range)
|
19
49
|
|
50
|
+
raise ArgumentError, "A negative length is not permitted, I received #{length}" if length < 0
|
20
51
|
length.times.collect { character }.join
|
21
52
|
end # def text
|
22
53
|
|
54
|
+
def self.text_range(range)
|
55
|
+
raise ArgumentError, "Requires ascending range, you gave #{range}." if range.end < range.begin
|
56
|
+
raise ArgumentError, "A negative range values are not permitted, I received range #{range}" if range.begin < 0
|
57
|
+
text(integer(range))
|
58
|
+
end
|
59
|
+
|
23
60
|
# Generates a random character (A string of length 1)
|
24
61
|
#
|
25
62
|
# @return [String]
|
26
63
|
def self.character
|
27
|
-
|
28
|
-
# Unicode 7 (http://www.unicode.org/versions/Unicode7.0.0/) and after much
|
29
|
-
# reading, I realized I wasn't in my house anymore but had somehow lost
|
30
|
-
# track of time and was alone in a field. Civilization had fallen centuries
|
31
|
-
# ago. :P
|
32
|
-
|
33
|
-
# Until UTF-8 is supported, just return a random lower ASCII character
|
34
|
-
integer(32..127).chr
|
64
|
+
return CHARACTERS[integer(0...CHARACTERS.length)]
|
35
65
|
end # def character
|
36
66
|
|
37
67
|
# Return a random integer value within a given range.
|
@@ -64,4 +94,4 @@ module Randomized
|
|
64
94
|
integer(range).times
|
65
95
|
end
|
66
96
|
end # def iterations
|
67
|
-
end
|
97
|
+
end # module Randomized
|
data/lib/rspec/stress_it.rb
CHANGED
@@ -1,6 +1,31 @@
|
|
1
|
+
# This file is part of ruby-flores.
|
2
|
+
# Copyright (C) 2015 Jordan Sissel
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU Affero General Public License as
|
6
|
+
# published by the Free Software Foundation, either version 3 of the
|
7
|
+
# License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU Affero General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Affero General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
#
|
1
17
|
# encoding: utf-8
|
2
18
|
require "rspec/core"
|
3
19
|
|
20
|
+
# RSpec helpers for stress testing examples
|
21
|
+
#
|
22
|
+
# Setting it up in rspec:
|
23
|
+
#
|
24
|
+
# RSpec.configure do |c|
|
25
|
+
# c.extend RSpec::StressIt
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# TODO(sissel): Show an example of stress_it and analyze_it
|
4
29
|
module RSpec::StressIt
|
5
30
|
DEFAULT_ITERATIONS = 1..5000
|
6
31
|
|
@@ -10,11 +35,11 @@ module RSpec::StressIt
|
|
10
35
|
# of APIs to help find edge cases and weird behavior.
|
11
36
|
#
|
12
37
|
# The default number of iterations is randomly selected between 1 and 1000 inclusive
|
13
|
-
def stress_it(name, options={}, &block)
|
14
|
-
|
38
|
+
def stress_it(name, options = {}, &block)
|
39
|
+
stress__iterations = Randomized.iterations(options.delete(:stress_iterations) || DEFAULT_ITERATIONS)
|
15
40
|
it(name, options) do
|
16
41
|
# Run the block of an example many times
|
17
|
-
|
42
|
+
stress__iterations.each do
|
18
43
|
# Run the block within 'it' scope
|
19
44
|
instance_eval(&block)
|
20
45
|
|
@@ -34,10 +59,10 @@ module RSpec::StressIt
|
|
34
59
|
# it "should be less than 100" do
|
35
60
|
# expect(number).to(be < 100)
|
36
61
|
# end
|
37
|
-
def stress_it2(name, options={}, &block)
|
38
|
-
|
39
|
-
|
40
|
-
it(
|
62
|
+
def stress_it2(name, options = {}, &block)
|
63
|
+
stress__iterations = Randomized.iterations(options.delete(:stress_iterations) || DEFAULT_ITERATIONS)
|
64
|
+
stress__iterations.each do |i|
|
65
|
+
it(name + " [#{i}]", *args) do
|
41
66
|
instance_eval(&block)
|
42
67
|
end # it ...
|
43
68
|
end # .times
|
@@ -57,18 +82,17 @@ module RSpec::StressIt
|
|
57
82
|
# end
|
58
83
|
#
|
59
84
|
# Example report:
|
60
|
-
def analyze_it(name, variables, &block)
|
85
|
+
def analyze_it(name, variables, &block) # rubocop:disable Metrics/AbcSize
|
61
86
|
it(name) do
|
62
|
-
results = Hash.new { |h,k| h[k] = [] }
|
63
|
-
|
64
|
-
iterations.each do |i|
|
87
|
+
results = Hash.new { |h, k| h[k] = [] }
|
88
|
+
Randomized.iterations(DEFAULT_ITERATIONS).each do
|
65
89
|
state = Hash[variables.collect { |l| [l, __send__(l)] }]
|
66
90
|
begin
|
67
91
|
instance_eval(&block)
|
68
92
|
results[:success] << [state, nil]
|
69
93
|
rescue => e
|
70
94
|
results[e.class] << [state, e]
|
71
|
-
rescue Exception => e
|
95
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
72
96
|
results[e.class] << [state, e]
|
73
97
|
end
|
74
98
|
|
@@ -76,20 +100,19 @@ module RSpec::StressIt
|
|
76
100
|
__memoized.clear
|
77
101
|
end
|
78
102
|
|
79
|
-
if results
|
80
|
-
raise Analysis.new(results)
|
81
|
-
end
|
103
|
+
raise StandardError, Analysis.new(results) if results.any? { |k, _| k != :success }
|
82
104
|
end
|
83
|
-
end
|
105
|
+
end # def analyze_it
|
84
106
|
|
107
|
+
# A formatter to show analysis of an `analyze_it` example.
|
85
108
|
class Analysis < StandardError
|
86
109
|
def initialize(results)
|
87
110
|
@results = results
|
88
|
-
end
|
111
|
+
end # def initialize
|
89
112
|
|
90
113
|
def total
|
91
|
-
@results.reduce(0) { |m, (
|
92
|
-
end
|
114
|
+
@results.reduce(0) { |m, (_, v)| m + v.length }
|
115
|
+
end # def total
|
93
116
|
|
94
117
|
def success_count
|
95
118
|
if @results.include?(:success)
|
@@ -97,33 +120,54 @@ module RSpec::StressIt
|
|
97
120
|
else
|
98
121
|
0
|
99
122
|
end
|
100
|
-
end
|
123
|
+
end # def success_count
|
101
124
|
|
102
125
|
def percent(count)
|
103
126
|
return (count + 0.0) / total
|
104
|
-
end
|
127
|
+
end # def percent
|
105
128
|
|
106
129
|
def percent_s(count)
|
107
|
-
return
|
108
|
-
end
|
130
|
+
return format("%.2f%%", percent(count) * 100)
|
131
|
+
end # def percent_s
|
109
132
|
|
110
133
|
def to_s
|
111
134
|
# This method is crazy complex for a formatter. Should refactor this significantly.
|
112
135
|
report = ["#{percent_s(success_count)} tests successful of #{total} tests"]
|
113
|
-
if success_count < total
|
114
|
-
report << "Failure analysis:"
|
115
|
-
report += @results.sort_by { |k,v| -v.length }.reject { |k,v| k == :success }.collect do |k, v|
|
116
|
-
sample = v.sample(5).collect { |v| v.first }.join(", ")
|
117
|
-
[
|
118
|
-
" #{percent_s(v.length)} -> [#{v.length}] #{k}",
|
119
|
-
" Sample exception:",
|
120
|
-
v.sample(1).first[1].to_s.gsub(/^/, " "),
|
121
|
-
" Samples causing #{k}:",
|
122
|
-
*v.sample(5).collect { |state, _exception| " #{state}" }
|
123
|
-
]
|
124
|
-
end.flatten
|
125
|
-
end
|
136
|
+
report += failure_summary if success_count < total
|
126
137
|
report.join("\n")
|
127
|
-
end
|
128
|
-
|
138
|
+
end # def to_s
|
139
|
+
|
140
|
+
# TODO(sissel): All these report/summary/to_s things are an indication that the
|
141
|
+
# report formatting belongs in a separate class.
|
142
|
+
def failure_summary
|
143
|
+
report = ["Failure analysis:"]
|
144
|
+
report += @results.sort_by { |_, v| -v.length }.collect do |group, instances|
|
145
|
+
next if group == :success
|
146
|
+
error_report(group, instances)
|
147
|
+
end.reject(&:nil?).flatten
|
148
|
+
report
|
149
|
+
end # def failure_summary
|
150
|
+
|
151
|
+
def error_report(error, instances)
|
152
|
+
report = error_summary(error, instances)
|
153
|
+
report += error_sample_states(error, instances) if instances.size > 1
|
154
|
+
report
|
155
|
+
end # def error_report
|
156
|
+
|
157
|
+
def error_summary(error, instances)
|
158
|
+
sample = instances.sample(1)
|
159
|
+
[
|
160
|
+
" #{percent_s(instances.length)} -> [#{instances.length}] #{error}",
|
161
|
+
" Sample exception for #{sample.first[0]}",
|
162
|
+
sample.first[1].to_s.gsub(/^/, " ")
|
163
|
+
]
|
164
|
+
end # def error_summary
|
165
|
+
|
166
|
+
def error_sample_states(error, instances)
|
167
|
+
[
|
168
|
+
" Samples causing #{error}:",
|
169
|
+
*instances.sample(5).collect { |state, _exception| " #{state}" }
|
170
|
+
]
|
171
|
+
end # def error_sample_states
|
172
|
+
end # class Analysis
|
129
173
|
end # module RSpec::StressIt
|
data/spec/randomized_spec.rb
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
# This file is part of ruby-flores.
|
2
|
+
# Copyright (C) 2015 Jordan Sissel
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU Affero General Public License as
|
6
|
+
# published by the Free Software Foundation, either version 3 of the
|
7
|
+
# License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU Affero General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Affero General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
#
|
17
|
+
# encoding: utf-8
|
1
18
|
require "randomized"
|
2
19
|
require "rspec/stress_it"
|
3
20
|
|
@@ -5,10 +22,19 @@ RSpec.configure do |c|
|
|
5
22
|
c.extend RSpec::StressIt
|
6
23
|
end
|
7
24
|
|
25
|
+
shared_examples_for String do |variables|
|
26
|
+
analyze_it "should be a String", variables do
|
27
|
+
expect(subject).to(be_a(String))
|
28
|
+
end
|
29
|
+
analyze_it "have valid encoding", variables do
|
30
|
+
expect(subject).to(be_valid_encoding)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
8
34
|
describe Randomized do
|
9
35
|
describe "#text" do
|
10
36
|
context "with no arguments" do
|
11
|
-
|
37
|
+
stress_it "should raise ArgumentError" do
|
12
38
|
expect { subject.text }.to(raise_error(ArgumentError))
|
13
39
|
end
|
14
40
|
end
|
@@ -18,15 +44,15 @@ describe Randomized do
|
|
18
44
|
|
19
45
|
context "that is positive" do
|
20
46
|
let(:length) { rand(1..1000) }
|
21
|
-
|
22
|
-
|
47
|
+
it_behaves_like String, [:length]
|
48
|
+
analyze_it "has correct length", [:length] do
|
23
49
|
expect(subject.length).to(eq(length))
|
24
50
|
end
|
25
51
|
end
|
26
52
|
|
27
53
|
context "that is negative" do
|
28
54
|
let(:length) { -1 * rand(1..1000) }
|
29
|
-
|
55
|
+
analyze_it "should raise ArgumentError", [:length] do
|
30
56
|
expect { subject }.to(raise_error(ArgumentError))
|
31
57
|
end
|
32
58
|
end
|
@@ -38,16 +64,17 @@ describe Randomized do
|
|
38
64
|
subject { described_class.text(range) }
|
39
65
|
|
40
66
|
context "that is ascending" do
|
41
|
-
let(:range) { start
|
42
|
-
|
67
|
+
let(:range) { start..(start + length) }
|
68
|
+
it_behaves_like String, [:range]
|
69
|
+
analyze_it "should give a string within that length range", [:range] do
|
43
70
|
expect(subject).to(be_a(String))
|
44
71
|
expect(range).to(include(subject.length))
|
45
72
|
end
|
46
73
|
end
|
47
74
|
|
48
75
|
context "that is descending" do
|
49
|
-
let(:range) { start
|
50
|
-
|
76
|
+
let(:range) { start..(start - length) }
|
77
|
+
analyze_it "should raise ArgumentError", [:range] do
|
51
78
|
expect { subject }.to(raise_error(ArgumentError))
|
52
79
|
end
|
53
80
|
end
|
@@ -56,40 +83,50 @@ describe Randomized do
|
|
56
83
|
|
57
84
|
describe "#character" do
|
58
85
|
subject { described_class.character }
|
59
|
-
|
86
|
+
it_behaves_like String, [:subject]
|
87
|
+
analyze_it "has length == 1", [:subject] do
|
60
88
|
expect(subject.length).to(be == 1)
|
61
89
|
end
|
62
90
|
end
|
63
91
|
|
64
|
-
shared_examples_for
|
65
|
-
let(:start) { Randomized.integer(-
|
66
|
-
let(:length) { Randomized.integer(1
|
67
|
-
let(:range) { start
|
92
|
+
shared_examples_for Numeric do |type|
|
93
|
+
let(:start) { Randomized.integer(-100_000..100_000) }
|
94
|
+
let(:length) { Randomized.integer(1..100_000) }
|
95
|
+
let(:range) { start..(start + length) }
|
68
96
|
|
69
|
-
|
70
|
-
expect(subject).to(be_a(
|
97
|
+
analyze_it "should be a #{type}", [:range] do
|
98
|
+
expect(subject).to(be_a(type))
|
71
99
|
end
|
72
100
|
|
73
|
-
|
101
|
+
analyze_it "should be within the bounds of the given range", [:range] do
|
74
102
|
expect(range).to(include(subject))
|
75
103
|
end
|
76
104
|
end
|
77
105
|
|
78
106
|
describe "#integer" do
|
79
|
-
it_behaves_like
|
107
|
+
it_behaves_like Numeric, Fixnum do
|
80
108
|
subject { Randomized.integer(range) }
|
81
|
-
stress_it "is a Fixnum" do
|
82
|
-
expect(subject).to(be_a(Fixnum))
|
83
|
-
end
|
84
109
|
end
|
85
110
|
end
|
111
|
+
|
86
112
|
describe "#number" do
|
87
|
-
it_behaves_like
|
113
|
+
it_behaves_like Numeric, Float do
|
88
114
|
subject { Randomized.number(range) }
|
115
|
+
end
|
116
|
+
end
|
89
117
|
|
90
|
-
|
91
|
-
|
92
|
-
|
118
|
+
describe "#iterations" do
|
119
|
+
let(:start) { Randomized.integer(1..100_000) }
|
120
|
+
let(:length) { Randomized.integer(1..100_000) }
|
121
|
+
let(:range) { start..(start + length) }
|
122
|
+
subject { Randomized.iterations(range) }
|
123
|
+
|
124
|
+
analyze_it "should return an Enumerable", [:range] do
|
125
|
+
expect(subject).to(be_a(Enumerable))
|
126
|
+
end
|
127
|
+
|
128
|
+
analyze_it "should have a size within the expected range", [:range] do
|
129
|
+
expect(range).to(include(subject.size))
|
93
130
|
end
|
94
131
|
end
|
95
132
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flores
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jordan Sissel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-21 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Add fuzzing, randomization, and stress to your tests.
|
@@ -23,8 +23,10 @@ executables: []
|
|
23
23
|
extensions: []
|
24
24
|
extra_rdoc_files: []
|
25
25
|
files:
|
26
|
+
- ".rubocop.yml"
|
26
27
|
- Gemfile
|
27
28
|
- Gemfile.lock
|
29
|
+
- LICENSE.txt
|
28
30
|
- Makefile
|
29
31
|
- examples/analyze_number.rb
|
30
32
|
- examples/socket_acceptance_spec.rb
|