flores 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/Gemfile.lock +22 -0
- data/Makefile +12 -0
- data/README.md +177 -0
- data/examples/analyze_number.rb +12 -9
- data/examples/socket_acceptance_spec.rb +7 -8
- data/examples/socket_analyze_spec.rb +12 -10
- data/examples/socket_stress_spec.rb +8 -7
- data/flores.gemspec +1 -1
- data/lib/flores/namespace.rb +23 -0
- data/lib/{randomized.rb → flores/random.rb} +12 -7
- data/lib/{rspec/stress_it.rb → flores/rspec/analyze.rb} +44 -74
- data/lib/flores/rspec/formatters/analyze.rb +61 -0
- data/lib/flores/rspec/stress.rb +106 -0
- data/lib/flores/rspec.rb +45 -0
- data/spec/{randomized_spec.rb → flores/random_spec.rb} +33 -33
- data/spec/flores/rspec/stress_spec.rb +87 -0
- data/spec/spec_init.rb +23 -0
- metadata +21 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0fb409bcd13936e24dae9b9f932577b83321cfa
|
4
|
+
data.tar.gz: 0168c710951ccd26e4bff91434d4e811526cabe3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9da3201822ace87268b2cd900d75c95ee7b9d02ac3b7801b5456039d3fc55e8a35739e8f88ff40524f4db7ba3c6bf6036967d652d2d94f19181eb4e67fdb3891
|
7
|
+
data.tar.gz: 1d79f887fbbeeb0cd4f641b31382b074fc20a3e7e2b6a3da55bfde4d35dbb491ef51463869981e9e53c862b506e4785182359cb2c084d64ca6d7ecedde0c603a
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,22 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
+
coderay (1.1.0)
|
4
5
|
diff-lcs (1.2.5)
|
6
|
+
ffi (1.9.6-java)
|
7
|
+
fuubar (2.0.0)
|
8
|
+
rspec (~> 3.0)
|
9
|
+
ruby-progressbar (~> 1.4)
|
10
|
+
method_source (0.8.2)
|
11
|
+
pry (0.10.1)
|
12
|
+
coderay (~> 1.1.0)
|
13
|
+
method_source (~> 0.8.1)
|
14
|
+
slop (~> 3.4)
|
15
|
+
pry (0.10.1-java)
|
16
|
+
coderay (~> 1.1.0)
|
17
|
+
method_source (~> 0.8.1)
|
18
|
+
slop (~> 3.4)
|
19
|
+
spoon (~> 0.0)
|
5
20
|
rspec (3.2.0)
|
6
21
|
rspec-core (~> 3.2.0)
|
7
22
|
rspec-expectations (~> 3.2.0)
|
@@ -15,9 +30,16 @@ GEM
|
|
15
30
|
diff-lcs (>= 1.2.0, < 2.0)
|
16
31
|
rspec-support (~> 3.2.0)
|
17
32
|
rspec-support (3.2.1)
|
33
|
+
ruby-progressbar (1.7.1)
|
34
|
+
slop (3.6.0)
|
35
|
+
spoon (0.0.4)
|
36
|
+
ffi
|
18
37
|
|
19
38
|
PLATFORMS
|
39
|
+
java
|
20
40
|
ruby
|
21
41
|
|
22
42
|
DEPENDENCIES
|
43
|
+
fuubar
|
44
|
+
pry
|
23
45
|
rspec (>= 3.0.0)
|
data/Makefile
CHANGED
@@ -29,3 +29,15 @@ install: $(GEM)
|
|
29
29
|
.PHONY: clean
|
30
30
|
clean:
|
31
31
|
-rm -rf .yardoc $(GEM) $(NAME)-$(VERSION)/
|
32
|
+
|
33
|
+
.PHONY: rubocop
|
34
|
+
rubocop:
|
35
|
+
rubocop -D
|
36
|
+
|
37
|
+
.PHONY: test
|
38
|
+
test: rubocop
|
39
|
+
rspec -f Flores::RSpec::Formatters::Analyze
|
40
|
+
|
41
|
+
.PHONY: test
|
42
|
+
test-fast: rubocop
|
43
|
+
ITERATIONS=10 rspec -f Flores::RSpec::Formatters::Analyze
|
data/README.md
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
# Flores - a stress testing library
|
2
|
+
|
3
|
+
When writing tests, it is often good to test a wide variety of inputs to ensure
|
4
|
+
your entire input range behaves correctly.
|
5
|
+
|
6
|
+
Further, adding a bit of randomness in your tests can help find bugs.
|
7
|
+
|
8
|
+
## Why Flores?
|
9
|
+
|
10
|
+
Randomization helps you cover a wider range of inputs to your tests to find bugs. Stress
|
11
|
+
testing (run a test repeatedly) helps you find bugs faster. We can use stress testing results
|
12
|
+
to find common patterns in failures!
|
13
|
+
|
14
|
+
Let's look at a sample situation. Ruby's TCPServer. Let's write a spec to cover a spec covering port binding:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
describe TCPServer do
|
18
|
+
subject(:socket) { Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) }
|
19
|
+
let(:port) { 5000 }
|
20
|
+
let(:sockaddr) { Socket.sockaddr_in(port, "127.0.0.1") }
|
21
|
+
|
22
|
+
after { socket.close}
|
23
|
+
|
24
|
+
it "should bind successfully" do
|
25
|
+
socket.bind(sockaddr)
|
26
|
+
expect(socket.local_address.ip_port).to(be == port)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
Running it:
|
32
|
+
|
33
|
+
```
|
34
|
+
% rspec tcpserver_spec.rb
|
35
|
+
.
|
36
|
+
|
37
|
+
Finished in 0.00248 seconds (files took 0.16294 seconds to load)
|
38
|
+
1 example, 0 failures
|
39
|
+
```
|
40
|
+
|
41
|
+
That's cool. We now have some confidence that TCPServer on port 5000 will bind successfully.
|
42
|
+
|
43
|
+
What about the other ports? What ranges of values should work? What shouldn't?
|
44
|
+
|
45
|
+
Let's assume I don't know anything about tcp port ranges and test randomly in the range -100,000 to +100,000:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
describe TCPServer do
|
49
|
+
let(:port) { Randomized.integer(-100_000..100_000) }
|
50
|
+
...
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
Running it:
|
55
|
+
|
56
|
+
```
|
57
|
+
% rspec tcpserver_spec.rb
|
58
|
+
F
|
59
|
+
|
60
|
+
Failures:
|
61
|
+
|
62
|
+
1) TCPServer should bind successfully
|
63
|
+
Failure/Error: expect(socket.local_address.ip_port).to(be == port)
|
64
|
+
expected: == 83359
|
65
|
+
got: 17823
|
66
|
+
# ./tcpserver_spec.rb:12:in `block (2 levels) in <top (required)>'
|
67
|
+
|
68
|
+
Finished in 0.00155 seconds (files took 0.10221 seconds to load)
|
69
|
+
1 example, 1 failure
|
70
|
+
```
|
71
|
+
|
72
|
+
Well that's weird. Binding port 83359 actually made it bind on port 17823!
|
73
|
+
|
74
|
+
If we run it more times, we'll see all kinds of different results:
|
75
|
+
|
76
|
+
* Run 1:
|
77
|
+
```
|
78
|
+
Failure/Error: expect(socket.local_address.ip_port).to(be == port)
|
79
|
+
expected: == 83359
|
80
|
+
got: 17823
|
81
|
+
```
|
82
|
+
* Run 2:
|
83
|
+
```
|
84
|
+
Failure/Error: let(:sockaddr) { Socket.sockaddr_in(port, "127.0.0.1") }
|
85
|
+
SocketError:
|
86
|
+
getaddrinfo: nodename nor servname provided, or not known
|
87
|
+
```
|
88
|
+
* Run 3:
|
89
|
+
```
|
90
|
+
Errno::EACCES:
|
91
|
+
Permission denied - bind(2) for 127.0.0.1:615
|
92
|
+
```
|
93
|
+
* Run 4:
|
94
|
+
```
|
95
|
+
Finished in 0.00161 seconds (files took 0.10356 seconds to load)
|
96
|
+
1 example, 0 failures
|
97
|
+
```
|
98
|
+
|
99
|
+
## Analyze the results
|
100
|
+
|
101
|
+
The above example showed that there were many different kinds of failures when
|
102
|
+
we introduced randomness to our test inputs.
|
103
|
+
|
104
|
+
We can go further and run a given spec example many times and group the
|
105
|
+
failures by similarity and include context (what the inputs were, etc)
|
106
|
+
|
107
|
+
This library provides an `analyze_it` helper which behaves similarly to rspec's
|
108
|
+
`it` except that it runs the block a random number of times and clears the `let` cache
|
109
|
+
each time. This lets you run a given test many times with many random inputs!
|
110
|
+
|
111
|
+
The result is grouped by failure and includes context. Let's see how it works:
|
112
|
+
|
113
|
+
We'll change `it` to use `analyze_it` instead:
|
114
|
+
|
115
|
+
```diff
|
116
|
+
- it "should bind successfully" do
|
117
|
+
+ analyze_it "should bind successfully", [:port] do
|
118
|
+
```
|
119
|
+
|
120
|
+
Now rerunning the test. With barely any spec changes from the original, we have
|
121
|
+
now enough randomness and stress testing to identify many different failure cases
|
122
|
+
and input ranges for those failures.
|
123
|
+
|
124
|
+
```
|
125
|
+
Failures:
|
126
|
+
|
127
|
+
1) TCPServer should bind successfully
|
128
|
+
Failure/Error: raise StandardError, Analysis.new(results) if results.any? { |k, _| k != :success }
|
129
|
+
StandardError:
|
130
|
+
31.14% tests successful of 2563 tests
|
131
|
+
Failure analysis:
|
132
|
+
50.57% -> [1296] SocketError
|
133
|
+
Sample exception for {:port=>-94900}
|
134
|
+
getaddrinfo: nodename nor servname provided, or not known
|
135
|
+
Samples causing SocketError:
|
136
|
+
{:port=>-49441}
|
137
|
+
{:port=>-1991}
|
138
|
+
{:port=>-54074}
|
139
|
+
{:port=>-1733}
|
140
|
+
{:port=>-21868}
|
141
|
+
16.89% -> [433] RSpec::Expectations::ExpectationNotMetError
|
142
|
+
Sample exception for {:port=>93844}
|
143
|
+
expected: == 93844
|
144
|
+
got: 28308
|
145
|
+
Samples causing RSpec::Expectations::ExpectationNotMetError:
|
146
|
+
{:port=>89451}
|
147
|
+
{:port=>95627}
|
148
|
+
{:port=>95225}
|
149
|
+
{:port=>73106}
|
150
|
+
{:port=>77167}
|
151
|
+
1.01% -> [26] Errno::EACCES
|
152
|
+
Sample exception for {:port=>65649}
|
153
|
+
Permission denied - bind(2) for 127.0.0.1:113
|
154
|
+
Samples causing Errno::EACCES:
|
155
|
+
{:port=>913}
|
156
|
+
{:port=>141}
|
157
|
+
{:port=>66194}
|
158
|
+
{:port=>66217}
|
159
|
+
{:port=>66408}
|
160
|
+
0.39% -> [10] Errno::EADDRINUSE
|
161
|
+
Sample exception for {:port=>34402}
|
162
|
+
Address already in use - bind(2) for 127.0.0.1:34402
|
163
|
+
Samples causing Errno::EADDRINUSE:
|
164
|
+
{:port=>50905}
|
165
|
+
{:port=>71202}
|
166
|
+
{:port=>34402}
|
167
|
+
{:port=>28235}
|
168
|
+
{:port=>85641}
|
169
|
+
# ./lib/rspec/stress_it.rb:103:in `block in analyze_it'
|
170
|
+
|
171
|
+
Finished in 0.0735 seconds (files took 0.10247 seconds to load)
|
172
|
+
1 example, 1 failure
|
173
|
+
|
174
|
+
Failed examples:
|
175
|
+
|
176
|
+
rspec ./tcpserver_spec.rb:8 # TCPServer should bind successfully
|
177
|
+
```
|
data/examples/analyze_number.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
# This file is part of ruby-flores.
|
2
3
|
# Copyright (C) 2015 Jordan Sissel
|
3
4
|
#
|
@@ -13,18 +14,20 @@
|
|
13
14
|
#
|
14
15
|
# You should have received a copy of the GNU Affero General Public License
|
15
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
-
|
17
|
-
|
18
|
-
require "randomized"
|
19
|
-
require "rspec/stress_it"
|
17
|
+
require "flores/rspec"
|
18
|
+
require "flores/random"
|
20
19
|
|
21
20
|
RSpec.configure do |c|
|
22
|
-
c
|
21
|
+
Flores::RSpec.configure(c)
|
22
|
+
c.add_formatter("Flores::RSpec::Formatters::Analyze")
|
23
23
|
end
|
24
24
|
|
25
|
-
describe "number" do
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
describe "a random number" do
|
26
|
+
context "between 0 and 200 inclusive" do
|
27
|
+
let(:number) { Flores::Random.number(0..200) }
|
28
|
+
analyze_results
|
29
|
+
stress_it "should be less than 100" do
|
30
|
+
expect(number).to(be < 100)
|
31
|
+
end
|
29
32
|
end
|
30
33
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
# This file is part of ruby-flores.
|
2
3
|
# Copyright (C) 2015 Jordan Sissel
|
3
4
|
#
|
@@ -13,14 +14,12 @@
|
|
13
14
|
#
|
14
15
|
# You should have received a copy of the GNU Affero General Public License
|
15
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
-
|
17
|
-
# encoding: utf-8
|
18
|
-
require "randomized"
|
17
|
+
require "flores/random"
|
19
18
|
require "socket"
|
20
|
-
require "rspec
|
19
|
+
require "flores/rspec"
|
21
20
|
|
22
21
|
RSpec.configure do |c|
|
23
|
-
c
|
22
|
+
Flores::RSpec.configure(c)
|
24
23
|
end
|
25
24
|
|
26
25
|
# A factory for encapsulating behavior of a tcp server and client for the
|
@@ -62,12 +61,12 @@ class TCPIntegrationTestFactory
|
|
62
61
|
end
|
63
62
|
|
64
63
|
describe "TCPServer+TCPSocket" do
|
65
|
-
let(:port) {
|
66
|
-
let(:text) {
|
64
|
+
let(:port) { Flores::Random.integer(1024..65535) }
|
65
|
+
let(:text) { Flores::Random.text(1..2000) }
|
67
66
|
subject { TCPIntegrationTestFactory.new(port) }
|
68
67
|
|
69
68
|
describe "using stress_it" do
|
70
|
-
|
69
|
+
stress_it2 "should send data correctly", [:port, :text] do
|
71
70
|
begin
|
72
71
|
subject.setup
|
73
72
|
rescue Errno::EADDRINUSE
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
# This file is part of ruby-flores.
|
2
3
|
# Copyright (C) 2015 Jordan Sissel
|
3
4
|
#
|
@@ -13,26 +14,27 @@
|
|
13
14
|
#
|
14
15
|
# You should have received a copy of the GNU Affero General Public License
|
15
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
-
|
17
|
-
# encoding: utf-8
|
18
|
-
require "randomized"
|
17
|
+
require "flores/random"
|
19
18
|
require "socket"
|
20
|
-
require "rspec
|
19
|
+
require "flores/rspec"
|
21
20
|
|
22
21
|
RSpec.configure do |c|
|
23
|
-
c
|
22
|
+
Flores::RSpec.configure(c)
|
24
23
|
end
|
25
24
|
|
26
25
|
describe TCPServer do
|
27
26
|
subject(:socket) { Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) }
|
28
27
|
let(:sockaddr) { Socket.sockaddr_in(port, "127.0.0.1") }
|
29
|
-
after { socket.close }
|
30
28
|
|
31
29
|
context "on a random port" do
|
32
|
-
let(:port) {
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
let(:port) { Flores::Random.integer(-100_000..100_000) }
|
31
|
+
stress_it2 "should bind successfully", [:port] do
|
32
|
+
begin
|
33
|
+
socket.bind(sockaddr)
|
34
|
+
expect(socket.local_address.ip_port).to(be == port)
|
35
|
+
ensure
|
36
|
+
socket.close
|
37
|
+
end
|
36
38
|
end
|
37
39
|
end
|
38
40
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
# This file is part of ruby-flores.
|
2
3
|
# Copyright (C) 2015 Jordan Sissel
|
3
4
|
#
|
@@ -13,17 +14,17 @@
|
|
13
14
|
#
|
14
15
|
# You should have received a copy of the GNU Affero General Public License
|
15
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
-
|
17
|
-
# encoding: utf-8
|
18
|
-
require "randomized"
|
17
|
+
require "flores/random"
|
19
18
|
require "socket"
|
20
|
-
require "rspec
|
19
|
+
require "flores/rspec"
|
21
20
|
|
22
21
|
RSpec.configure do |c|
|
23
|
-
c
|
22
|
+
Flores::RSpec.configure(c)
|
24
23
|
end
|
25
24
|
|
26
25
|
describe TCPServer do
|
26
|
+
analyze
|
27
|
+
|
27
28
|
subject(:socket) { Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) }
|
28
29
|
let(:sockaddr) { Socket.sockaddr_in(port, "127.0.0.1") }
|
29
30
|
let(:ignore_eaddrinuse) do
|
@@ -41,14 +42,14 @@ describe TCPServer do
|
|
41
42
|
end
|
42
43
|
|
43
44
|
context "on privileged ports" do
|
44
|
-
let(:port) {
|
45
|
+
let(:port) { Flores::Random.integer(1..1023) }
|
45
46
|
stress_it "should raise Errno::EACCESS" do
|
46
47
|
expect { socket.bind(sockaddr) }.to(raise_error(Errno::EACCES))
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
50
51
|
context "on unprivileged ports" do
|
51
|
-
let(:port) {
|
52
|
+
let(:port) { Flores::Random.integer(1025..65535) }
|
52
53
|
stress_it "should bind on a port" do
|
53
54
|
# EADDRINUSE is expected since we are picking ports at random
|
54
55
|
# Let's ignore this specific exception
|
data/flores.gemspec
CHANGED
@@ -2,7 +2,7 @@ Gem::Specification.new do |spec|
|
|
2
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.3"
|
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.
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# This file is part of ruby-flores.
|
3
|
+
# Copyright (C) 2015 Jordan Sissel
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as
|
7
|
+
# published by the Free Software Foundation, either version 3 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
# :nodoc:
|
18
|
+
module Flores # rubocop:disable Style/ClassAndModuleChildren
|
19
|
+
module RSpec # rubocop:disable Style/ClassAndModuleChildren
|
20
|
+
module Formatters # rubocop:disable Style/ClassAndModuleChildren
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
# This file is part of ruby-flores.
|
2
3
|
# Copyright (C) 2015 Jordan Sissel
|
3
4
|
#
|
@@ -13,11 +14,11 @@
|
|
13
14
|
#
|
14
15
|
# You should have received a copy of the GNU Affero General Public License
|
15
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
-
|
17
|
-
|
17
|
+
|
18
|
+
require "flores/namespace"
|
18
19
|
|
19
20
|
# A collection of methods intended for use in randomized testing.
|
20
|
-
module
|
21
|
+
module Flores::Random
|
21
22
|
# A selection of UTF-8 characters
|
22
23
|
#
|
23
24
|
# I'd love to generate this, but I don't yet know enough about how unicode
|
@@ -43,7 +44,7 @@ module Randomized
|
|
43
44
|
# * Negative lengths are not permitted and will raise an ArgumentError
|
44
45
|
#
|
45
46
|
# @param length [Fixnum or Range] the length of text to generate
|
46
|
-
# @return [String] the
|
47
|
+
# @return [String] the generated text
|
47
48
|
def self.text(length)
|
48
49
|
return text_range(length) if length.is_a?(Range)
|
49
50
|
|
@@ -51,6 +52,10 @@ module Randomized
|
|
51
52
|
length.times.collect { character }.join
|
52
53
|
end # def text
|
53
54
|
|
55
|
+
# Generate text with random characters of a length within the given range.
|
56
|
+
#
|
57
|
+
# @param range [Range] the range of length to generate, inclusive
|
58
|
+
# @return [String] the generated text
|
54
59
|
def self.text_range(range)
|
55
60
|
raise ArgumentError, "Requires ascending range, you gave #{range}." if range.end < range.begin
|
56
61
|
raise ArgumentError, "A negative range values are not permitted, I received range #{range}" if range.begin < 0
|
@@ -77,9 +82,9 @@ module Randomized
|
|
77
82
|
# @param range [Range]
|
78
83
|
def self.number(range)
|
79
84
|
raise ArgumentError, "Range not given, got #{range.class}: #{range.inspect}" if !range.is_a?(Range)
|
80
|
-
#
|
81
|
-
#
|
82
|
-
rand * (range.
|
85
|
+
# Ruby 1.9.3 and below do not have Enumerable#size, so we have to compute the size of the range
|
86
|
+
# ourselves.
|
87
|
+
rand * (range.end - range.begin) + range.begin
|
83
88
|
end # def number
|
84
89
|
|
85
90
|
# Run a block a random number of times.
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
# This file is part of ruby-flores.
|
2
3
|
# Copyright (C) 2015 Jordan Sissel
|
3
4
|
#
|
@@ -13,9 +14,8 @@
|
|
13
14
|
#
|
14
15
|
# You should have received a copy of the GNU Affero General Public License
|
15
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
-
|
17
|
-
|
18
|
-
require "rspec/core"
|
17
|
+
require "flores/namespace"
|
18
|
+
require "flores/rspec"
|
19
19
|
|
20
20
|
# RSpec helpers for stress testing examples
|
21
21
|
#
|
@@ -26,83 +26,53 @@ require "rspec/core"
|
|
26
26
|
# end
|
27
27
|
#
|
28
28
|
# TODO(sissel): Show an example of stress_it and analyze_it
|
29
|
-
module RSpec::
|
30
|
-
|
31
|
-
|
32
|
-
# Wraps `it` and runs the block many times. Each run has will clear the `let` cache.
|
29
|
+
module Flores::RSpec::Analyze
|
30
|
+
# Save state after each example so it can be used in analysis after specs are completed.
|
33
31
|
#
|
34
|
-
#
|
35
|
-
#
|
32
|
+
# If you use this, you'll want to set your RSpec formatter to
|
33
|
+
# Flores::RSpec::Formatter::Analyze
|
36
34
|
#
|
37
|
-
#
|
38
|
-
def stress_it(name, options = {}, &block)
|
39
|
-
stress__iterations = Randomized.iterations(options.delete(:stress_iterations) || DEFAULT_ITERATIONS)
|
40
|
-
it(name, options) do
|
41
|
-
# Run the block of an example many times
|
42
|
-
stress__iterations.each do
|
43
|
-
# Run the block within 'it' scope
|
44
|
-
instance_eval(&block)
|
45
|
-
|
46
|
-
# clear the internal rspec `let` cache this lets us run a test
|
47
|
-
# repeatedly with fresh `let` evaluations.
|
48
|
-
# Reference: https://github.com/rspec/rspec-core/blob/5fc29a15b9af9dc1c9815e278caca869c4769767/lib/rspec/core/memoized_helpers.rb#L124-L127
|
49
|
-
__memoized.clear
|
50
|
-
end
|
51
|
-
end # it ...
|
52
|
-
end # def stress_it
|
53
|
-
|
54
|
-
# Generate a random number of copies of a given example.
|
55
|
-
# The idea is to take 1 `it` and run it N times to help tease out failures.
|
56
|
-
# Of course, the teasing requires you have randomized `let` usage, for example:
|
35
|
+
# Let's show an example that fails sometimes.
|
57
36
|
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
37
|
+
# describe "Addition of two numbers" do
|
38
|
+
# context "positive numbers" do
|
39
|
+
# analyze_results
|
40
|
+
# let(:a) { Flores::Random.number(1..1000) }
|
41
|
+
#
|
42
|
+
# # Here we make negative numbers possible to cause failure in our test.
|
43
|
+
# let(:b) { Flores::Random.number(-200..1000) }
|
44
|
+
# subject { a + b }
|
45
|
+
#
|
46
|
+
# stress_it "should be positive" do
|
47
|
+
# expect(subject).to(be > 0)
|
48
|
+
# end
|
49
|
+
# end
|
61
50
|
# end
|
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
|
66
|
-
instance_eval(&block)
|
67
|
-
end # it ...
|
68
|
-
end # .times
|
69
|
-
end
|
70
|
-
|
71
|
-
# Perform analysis on failure scenarios of a given example
|
72
|
-
#
|
73
|
-
# This will run the given example a random number of times and aggregate the
|
74
|
-
# results. If any failures occur, the spec will fail and a report will be
|
75
|
-
# given on that test.
|
76
51
|
#
|
77
|
-
#
|
78
|
-
#
|
79
|
-
# let(:number) { Randomized.number(0..200) }
|
80
|
-
# fuzz "should be less than 100" do
|
81
|
-
# expect(number).to(be < 100)
|
82
|
-
# end
|
52
|
+
# And running it:
|
83
53
|
#
|
84
|
-
#
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
54
|
+
# % rspec -f Flores::RSpec::Formatter::Analyze
|
55
|
+
# Addition of two numbers positive numbers should be positive
|
56
|
+
# 98.20% tests successful of 3675 tests
|
57
|
+
# Failure analysis:
|
58
|
+
# 1.80% -> [66] RSpec::Expectations::ExpectationNotMetError
|
59
|
+
# Sample exception for {:a=>126.21705882478048, :b=>-139.54814492675024, :subject=>-13.33108610196976}
|
60
|
+
# expected: > 0
|
61
|
+
# got: -13.33108610196976
|
62
|
+
# Samples causing RSpec::Expectations::ExpectationNotMetError:
|
63
|
+
# {:a=>90.67298249206425, :b=>-136.6237821353908, :subject=>-45.95079964332655}
|
64
|
+
# {:a=>20.35865155878871, :b=>-39.592417377658876, :subject=>-19.233765818870165}
|
65
|
+
# {:a=>158.07905166101787, :b=>-177.5864470909581, :subject=>-19.50739542994023}
|
66
|
+
# {:a=>31.80445518715138, :b=>-188.51942190504894, :subject=>-156.71496671789757}
|
67
|
+
# {:a=>116.1479954937354, :b=>-146.18477887927958, :subject=>-30.036783385544183}
|
68
|
+
def analyze_results
|
69
|
+
# TODO(sissel): Would be lovely to figure out how to inject an 'after' for
|
70
|
+
# all examples if we are using the Analyze formatter.
|
71
|
+
# Then this method could be implied by using the right formatter, or something.
|
72
|
+
after do |example|
|
73
|
+
example.metadata[:values] = __memoized.clone
|
104
74
|
end
|
105
|
-
end
|
75
|
+
end
|
106
76
|
|
107
77
|
# A formatter to show analysis of an `analyze_it` example.
|
108
78
|
class Analysis < StandardError
|
@@ -170,4 +140,4 @@ module RSpec::StressIt
|
|
170
140
|
]
|
171
141
|
end # def error_sample_states
|
172
142
|
end # class Analysis
|
173
|
-
end #
|
143
|
+
end # Flores::RSpec::Analyze
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# This file is part of ruby-flores.
|
3
|
+
# Copyright (C) 2015 Jordan Sissel
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as
|
7
|
+
# published by the Free Software Foundation, either version 3 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
require "flores/namespace"
|
18
|
+
require "rspec/core/formatters/base_text_formatter"
|
19
|
+
|
20
|
+
Flores::RSpec::Formatters::Analyze = Class.new(RSpec::Core::Formatters::BaseTextFormatter) do
|
21
|
+
RSpec::Core::Formatters.register self, :dump_failures, :dump_summary
|
22
|
+
|
23
|
+
def dump_summary(event)
|
24
|
+
# The event is an RSpec::Core::Notifications::SummaryNotification
|
25
|
+
# Let's mimic the BaseTextFormatter but without the failing test report
|
26
|
+
output.puts
|
27
|
+
output.puts "Finished in #{event.formatted_duration}"
|
28
|
+
output.puts "#{event.colorized_totals_line}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def dump_failures(event)
|
32
|
+
output.puts
|
33
|
+
group = event.examples.each_with_object(Hash.new { |h, k| h[k] = [] }) do |e, m|
|
34
|
+
m[e.metadata[:full_description]] << e
|
35
|
+
m
|
36
|
+
end
|
37
|
+
group.each { |description, examples| dump_example_summary(description, examples) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def dump_example_summary(description, examples)
|
41
|
+
output.puts description
|
42
|
+
analysis = Flores::RSpec::Analyze::Analysis.new(group_by_result(examples))
|
43
|
+
output.puts(analysis.to_s.gsub(/^/, " "))
|
44
|
+
end
|
45
|
+
|
46
|
+
def group_by_result(examples) # rubocop:disable Metrics/AbcSize
|
47
|
+
examples.each_with_object(Hash.new { |h, k| h[k] = [] }) do |example, results|
|
48
|
+
if example.metadata[:execution_result].status == :passed
|
49
|
+
results[:success] << [example.metadata[:values], nil]
|
50
|
+
else
|
51
|
+
exception = example.metadata[:execution_result].exception
|
52
|
+
results[exception.class] << [example.metadata[:values], exception]
|
53
|
+
end
|
54
|
+
results
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_missing(m, *args)
|
59
|
+
p m => args
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# This file is part of ruby-flores.
|
3
|
+
# Copyright (C) 2015 Jordan Sissel
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as
|
7
|
+
# published by the Free Software Foundation, either version 3 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
require "flores/namespace"
|
18
|
+
require "flores/rspec"
|
19
|
+
require "flores/random"
|
20
|
+
|
21
|
+
# This module adds helpers useful in doing stress testing within rspec.
|
22
|
+
#
|
23
|
+
# The number of iterations in a stress test is random.
|
24
|
+
#
|
25
|
+
# By way of example, let's have a silly test for adding two positive numbers and expecting
|
26
|
+
# the result to not be negative:
|
27
|
+
#
|
28
|
+
# describe "Addition" do
|
29
|
+
# context "of two positive numbers" do
|
30
|
+
# let(:a) { Flores::Random.number(1..10000) }
|
31
|
+
# let(:b) { Flores::Random.number(1..10000) }
|
32
|
+
# subject { a + b }
|
33
|
+
#
|
34
|
+
# # Note the use of 'stress_it' here!
|
35
|
+
# stress_it "should be greater than zero" do
|
36
|
+
# expect(subject).to(be > 0)
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# Running this:
|
42
|
+
#
|
43
|
+
# % rspec
|
44
|
+
# <lots of dots>
|
45
|
+
#
|
46
|
+
# Finished in 0.45412 seconds (files took 0.32963 seconds to load)
|
47
|
+
# 4795 examples, 0 failures
|
48
|
+
#
|
49
|
+
# In this way, instead of testing 1 fixed case or 1 randomized case, we test
|
50
|
+
# *many* cases in one rspec run.
|
51
|
+
module Flores::RSpec::Stress
|
52
|
+
# Wraps `it` and runs the block many times. Each run has will clear the `let` cache.
|
53
|
+
#
|
54
|
+
# The implementation of this is roughly that the given block will be run N times within an `it`:
|
55
|
+
#
|
56
|
+
# stress_it_internal "my test" do
|
57
|
+
# expect(...)
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# is roughly equivalent to
|
61
|
+
#
|
62
|
+
# it "my test" do
|
63
|
+
# 1000.times do
|
64
|
+
# expect(...)
|
65
|
+
# __memoized.clear
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# The intent of this is to allow randomized testing for fuzzing and stress testing
|
70
|
+
# of APIs to help find edge cases and weird behavior.
|
71
|
+
#
|
72
|
+
# The default number of iterations is randomly selected between 1 and 1000 inclusive
|
73
|
+
def stress_it_internal(name, options = {}, &block)
|
74
|
+
stress__iterations = Flores::Random.iterations(options.delete(:stress_iterations) || Flores::RSpec::DEFAULT_ITERATIONS)
|
75
|
+
it(name, options) do
|
76
|
+
# Run the block of an example many times
|
77
|
+
stress__iterations.each do
|
78
|
+
# Run the block within 'it' scope
|
79
|
+
instance_eval(&block)
|
80
|
+
|
81
|
+
# clear the internal rspec `let` cache this lets us run a test
|
82
|
+
# repeatedly with fresh `let` evaluations.
|
83
|
+
# Reference: https://github.com/rspec/rspec-core/blob/5fc29a15b9af9dc1c9815e278caca869c4769767/lib/rspec/core/memoized_helpers.rb#L124-L127
|
84
|
+
__memoized.clear
|
85
|
+
end
|
86
|
+
end # it ...
|
87
|
+
end # def stress_it_internal
|
88
|
+
|
89
|
+
# Generate a random number of copies of a given example.
|
90
|
+
# The idea is to take 1 `it` and run it N times to help tease out failures.
|
91
|
+
# Of course, the teasing requires you have randomized `let` usage, for example:
|
92
|
+
#
|
93
|
+
# let(:number) { Flores::Random.number(0..200) }
|
94
|
+
# it "should be less than 100" do
|
95
|
+
# expect(number).to(be < 100)
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# This creates N (random) copies of your spec example. Using `stress_it` is
|
99
|
+
# preferred instead of `stress_it_internal` because this method will cause
|
100
|
+
# before, after, and around clauses to be invoked correctly.
|
101
|
+
def stress_it(name, *args, &block)
|
102
|
+
Flores::Random.iterations(Flores::RSpec.iterations).each do
|
103
|
+
it(name, *args, &block)
|
104
|
+
end # each
|
105
|
+
end # def stress_it
|
106
|
+
end # Flores::RSpec::Stress
|
data/lib/flores/rspec.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# This file is part of ruby-flores.
|
3
|
+
# Copyright (C) 2015 Jordan Sissel
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as
|
7
|
+
# published by the Free Software Foundation, either version 3 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
# :nodoc:
|
18
|
+
require "flores/namespace"
|
19
|
+
|
20
|
+
# The root of the rspec helpers the Flores library provides
|
21
|
+
module Flores::RSpec
|
22
|
+
DEFAULT_ITERATIONS = 1..1000
|
23
|
+
|
24
|
+
# Sets up rspec with the Flores RSpec helpers. Usage looks like this:
|
25
|
+
#
|
26
|
+
# RSpec.configure do |config|
|
27
|
+
# Flores::RSpec.configure(config)
|
28
|
+
# end
|
29
|
+
def self.configure(rspec_configuration)
|
30
|
+
require "flores/rspec/stress"
|
31
|
+
require "flores/rspec/analyze"
|
32
|
+
rspec_configuration.extend(Flores::RSpec::Stress)
|
33
|
+
rspec_configuration.extend(Flores::RSpec::Analyze)
|
34
|
+
end # def self.configure
|
35
|
+
|
36
|
+
def self.iterations
|
37
|
+
return @iterations if @iterations
|
38
|
+
if ENV["ITERATIONS"]
|
39
|
+
@iterations = 0..ENV["ITERATIONS"].to_i
|
40
|
+
else
|
41
|
+
@iterations = DEFAULT_ITERATIONS
|
42
|
+
end
|
43
|
+
@iterations
|
44
|
+
end
|
45
|
+
end # def Flores::RSpec
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
# This file is part of ruby-flores.
|
2
3
|
# Copyright (C) 2015 Jordan Sissel
|
3
4
|
#
|
@@ -13,25 +14,20 @@
|
|
13
14
|
#
|
14
15
|
# You should have received a copy of the GNU Affero General Public License
|
15
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
-
|
17
|
-
# encoding: utf-8
|
18
|
-
require "randomized"
|
19
|
-
require "rspec/stress_it"
|
17
|
+
require "spec_init"
|
20
18
|
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
shared_examples_for String do |variables|
|
26
|
-
analyze_it "should be a String", variables do
|
19
|
+
shared_examples_for String do
|
20
|
+
stress_it "should be a String" do
|
27
21
|
expect(subject).to(be_a(String))
|
28
22
|
end
|
29
|
-
|
23
|
+
stress_it "have valid encoding" do
|
30
24
|
expect(subject).to(be_valid_encoding)
|
31
25
|
end
|
32
26
|
end
|
33
27
|
|
34
|
-
describe
|
28
|
+
describe Flores::Random do
|
29
|
+
analyze_results
|
30
|
+
|
35
31
|
describe "#text" do
|
36
32
|
context "with no arguments" do
|
37
33
|
stress_it "should raise ArgumentError" do
|
@@ -43,30 +39,30 @@ describe Randomized do
|
|
43
39
|
subject { described_class.text(length) }
|
44
40
|
|
45
41
|
context "that is positive" do
|
46
|
-
let(:length) {
|
42
|
+
let(:length) { Flores::Random.integer(1..1000) }
|
47
43
|
it_behaves_like String, [:length]
|
48
|
-
|
44
|
+
stress_it "has correct length" do
|
49
45
|
expect(subject.length).to(eq(length))
|
50
46
|
end
|
51
47
|
end
|
52
48
|
|
53
49
|
context "that is negative" do
|
54
|
-
let(:length) { -1 *
|
55
|
-
|
50
|
+
let(:length) { -1 * Flores::Random.integer(1..1000) }
|
51
|
+
stress_it "should raise ArgumentError" do
|
56
52
|
expect { subject }.to(raise_error(ArgumentError))
|
57
53
|
end
|
58
54
|
end
|
59
55
|
end
|
60
56
|
|
61
57
|
context "with 1 range argument" do
|
62
|
-
let(:start) {
|
63
|
-
let(:length) {
|
58
|
+
let(:start) { Flores::Random.integer(2..1000) }
|
59
|
+
let(:length) { Flores::Random.integer(1..1000) }
|
64
60
|
subject { described_class.text(range) }
|
65
61
|
|
66
62
|
context "that is ascending" do
|
67
63
|
let(:range) { start..(start + length) }
|
68
64
|
it_behaves_like String, [:range]
|
69
|
-
|
65
|
+
stress_it "should give a string within that length range" do
|
70
66
|
expect(subject).to(be_a(String))
|
71
67
|
expect(range).to(include(subject.length))
|
72
68
|
end
|
@@ -74,7 +70,7 @@ describe Randomized do
|
|
74
70
|
|
75
71
|
context "that is descending" do
|
76
72
|
let(:range) { start..(start - length) }
|
77
|
-
|
73
|
+
stress_it "should raise ArgumentError" do
|
78
74
|
expect { subject }.to(raise_error(ArgumentError))
|
79
75
|
end
|
80
76
|
end
|
@@ -84,49 +80,53 @@ describe Randomized do
|
|
84
80
|
describe "#character" do
|
85
81
|
subject { described_class.character }
|
86
82
|
it_behaves_like String, [:subject]
|
87
|
-
|
83
|
+
stress_it "has length == 1" do
|
88
84
|
expect(subject.length).to(be == 1)
|
89
85
|
end
|
90
86
|
end
|
91
87
|
|
92
88
|
shared_examples_for Numeric do |type|
|
93
|
-
let(:start) {
|
94
|
-
let(:length) {
|
89
|
+
let(:start) { Flores::Random.integer(-100_000..100_000) }
|
90
|
+
let(:length) { Flores::Random.integer(1..100_000) }
|
95
91
|
let(:range) { start..(start + length) }
|
96
92
|
|
97
|
-
|
93
|
+
stress_it "should be a #{type}" do
|
98
94
|
expect(subject).to(be_a(type))
|
99
95
|
end
|
100
96
|
|
101
|
-
|
97
|
+
stress_it "should be within the bounds of the given range" do
|
102
98
|
expect(range).to(include(subject))
|
103
99
|
end
|
104
100
|
end
|
105
101
|
|
106
102
|
describe "#integer" do
|
107
103
|
it_behaves_like Numeric, Fixnum do
|
108
|
-
subject {
|
104
|
+
subject { Flores::Random.integer(range) }
|
109
105
|
end
|
110
106
|
end
|
111
107
|
|
112
108
|
describe "#number" do
|
113
109
|
it_behaves_like Numeric, Float do
|
114
|
-
subject {
|
110
|
+
subject { Flores::Random.number(range) }
|
115
111
|
end
|
116
112
|
end
|
117
113
|
|
118
114
|
describe "#iterations" do
|
119
|
-
let(:start) {
|
120
|
-
let(:length) {
|
115
|
+
let(:start) { Flores::Random.integer(1..1000) }
|
116
|
+
let(:length) { Flores::Random.integer(1..1000) }
|
121
117
|
let(:range) { start..(start + length) }
|
122
|
-
subject {
|
118
|
+
subject { Flores::Random.iterations(range) }
|
123
119
|
|
124
|
-
|
120
|
+
stress_it "should return an Enumerable" do
|
125
121
|
expect(subject).to(be_a(Enumerable))
|
126
122
|
end
|
127
123
|
|
128
|
-
|
129
|
-
|
124
|
+
stress_it "should have a size within the expected range" do
|
125
|
+
# Ruby 2.0 added Enumerable#size, so we can't use it here.
|
126
|
+
# Meaning `123.times.size` doesn't work. So for this test,
|
127
|
+
# we use small ranges because Enumerable#count actually
|
128
|
+
# counts (via iteration) and is slow on large numbers.
|
129
|
+
expect(range).to(include(subject.count))
|
130
130
|
end
|
131
131
|
end
|
132
132
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# This file is part of ruby-flores.
|
3
|
+
# Copyright (C) 2015 Jordan Sissel
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as
|
7
|
+
# published by the Free Software Foundation, either version 3 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
require "spec_init"
|
18
|
+
|
19
|
+
Counter = Class.new do
|
20
|
+
attr_reader :value
|
21
|
+
def initialize
|
22
|
+
@value = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def incr
|
26
|
+
@value += 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def decr
|
30
|
+
@value -= 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe Flores::RSpec::Stress do
|
35
|
+
subject { Counter.new }
|
36
|
+
before do
|
37
|
+
expect(subject.value).to(be == 0)
|
38
|
+
subject.incr
|
39
|
+
expect(subject.value).to(be == 1)
|
40
|
+
end
|
41
|
+
|
42
|
+
after do
|
43
|
+
expect(subject.value).to(be == 1)
|
44
|
+
subject.decr
|
45
|
+
expect(subject.value).to(be == 0)
|
46
|
+
end
|
47
|
+
|
48
|
+
stress_it "should call all before and after hooks" do
|
49
|
+
expect(subject.value).to(be == 1)
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "level 1" do
|
53
|
+
before do
|
54
|
+
expect(subject.value).to(be == 1)
|
55
|
+
subject.incr
|
56
|
+
expect(subject.value).to(be == 2)
|
57
|
+
end
|
58
|
+
|
59
|
+
after do
|
60
|
+
expect(subject.value).to(be == 2)
|
61
|
+
subject.decr
|
62
|
+
expect(subject.value).to(be == 1)
|
63
|
+
end
|
64
|
+
|
65
|
+
stress_it "should call all before and after hooks" do
|
66
|
+
expect(subject.value).to(be == 2)
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "level 2" do
|
70
|
+
before do
|
71
|
+
expect(subject.value).to(be == 2)
|
72
|
+
subject.incr
|
73
|
+
expect(subject.value).to(be == 3)
|
74
|
+
end
|
75
|
+
|
76
|
+
after do
|
77
|
+
expect(subject.value).to(be == 3)
|
78
|
+
subject.decr
|
79
|
+
expect(subject.value).to(be == 2)
|
80
|
+
end
|
81
|
+
|
82
|
+
stress_it "should call all before and after hooks" do
|
83
|
+
expect(subject.value).to(be == 3)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/spec/spec_init.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# This file is part of ruby-flores.
|
3
|
+
# Copyright (C) 2015 Jordan Sissel
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as
|
7
|
+
# published by the Free Software Foundation, either version 3 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
require "flores/random"
|
18
|
+
require "flores/rspec"
|
19
|
+
|
20
|
+
RSpec.configure do |config|
|
21
|
+
Kernel.srand config.seed
|
22
|
+
Flores::RSpec.configure(config)
|
23
|
+
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.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jordan Sissel
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2
|
14
14
|
Add fuzzing, randomization, and stress to your tests.
|
@@ -23,42 +23,49 @@ executables: []
|
|
23
23
|
extensions: []
|
24
24
|
extra_rdoc_files: []
|
25
25
|
files:
|
26
|
-
-
|
26
|
+
- .rubocop.yml
|
27
27
|
- Gemfile
|
28
28
|
- Gemfile.lock
|
29
29
|
- LICENSE.txt
|
30
30
|
- Makefile
|
31
|
+
- README.md
|
31
32
|
- examples/analyze_number.rb
|
32
33
|
- examples/socket_acceptance_spec.rb
|
33
34
|
- examples/socket_analyze_spec.rb
|
34
35
|
- examples/socket_stress_spec.rb
|
35
36
|
- flores.gemspec
|
36
|
-
- lib/
|
37
|
-
- lib/
|
38
|
-
-
|
39
|
-
|
37
|
+
- lib/flores/namespace.rb
|
38
|
+
- lib/flores/random.rb
|
39
|
+
- lib/flores/rspec.rb
|
40
|
+
- lib/flores/rspec/analyze.rb
|
41
|
+
- lib/flores/rspec/formatters/analyze.rb
|
42
|
+
- lib/flores/rspec/stress.rb
|
43
|
+
- spec/flores/random_spec.rb
|
44
|
+
- spec/flores/rspec/stress_spec.rb
|
45
|
+
- spec/spec_init.rb
|
46
|
+
homepage:
|
40
47
|
licenses:
|
41
48
|
- AGPL 3.0 - http://www.gnu.org/licenses/agpl-3.0.html
|
42
49
|
metadata: {}
|
43
|
-
post_install_message:
|
50
|
+
post_install_message:
|
44
51
|
rdoc_options: []
|
45
52
|
require_paths:
|
46
53
|
- lib
|
47
54
|
- lib
|
48
55
|
required_ruby_version: !ruby/object:Gem::Requirement
|
49
56
|
requirements:
|
50
|
-
- -
|
57
|
+
- - '>='
|
51
58
|
- !ruby/object:Gem::Version
|
52
59
|
version: '0'
|
53
60
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
61
|
requirements:
|
55
|
-
- -
|
62
|
+
- - '>='
|
56
63
|
- !ruby/object:Gem::Version
|
57
64
|
version: '0'
|
58
65
|
requirements: []
|
59
|
-
rubyforge_project:
|
60
|
-
rubygems_version: 2.4.
|
61
|
-
signing_key:
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 2.4.5
|
68
|
+
signing_key:
|
62
69
|
specification_version: 4
|
63
70
|
summary: Fuzz, randomize, and stress your tests
|
64
71
|
test_files: []
|