flores 0.0.1 → 0.0.2
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 +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
|