flores 0.0.2 → 0.0.3
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/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: []
|