flores 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c0fb409bcd13936e24dae9b9f932577b83321cfa
4
- data.tar.gz: 0168c710951ccd26e4bff91434d4e811526cabe3
3
+ metadata.gz: 5628419d75d7725ac7a85dfb625142a1607b162e
4
+ data.tar.gz: 79027be03b9a83aca2573c3f05b17838bfb218f9
5
5
  SHA512:
6
- metadata.gz: 9da3201822ace87268b2cd900d75c95ee7b9d02ac3b7801b5456039d3fc55e8a35739e8f88ff40524f4db7ba3c6bf6036967d652d2d94f19181eb4e67fdb3891
7
- data.tar.gz: 1d79f887fbbeeb0cd4f641b31382b074fc20a3e7e2b6a3da55bfde4d35dbb491ef51463869981e9e53c862b506e4785182359cb2c084d64ca6d7ecedde0c603a
6
+ metadata.gz: 3898ab4b5e26cdd2cd8b1bb01284b0d525f0a141cc211dcfa0804c20f217b06c8e9a2ef37429bede123feb439d6a69a3b38a9e77956aae164776b42dd3e2a885
7
+ data.tar.gz: 058ddd9a151b86d5728eddcafb880144e7578d673b007b2f8389fc8511aee1f45ffe47620f85beb07251af68c5a80e6f960792bd0bf04f4bd62e5ebcc636bdc0
data/Gemfile CHANGED
@@ -3,5 +3,10 @@ source "https://rubygems.org"
3
3
  group "development" do
4
4
  gem "rspec", ">= 3.0.0"
5
5
  gem "fuubar"
6
+ gem "stud"
6
7
  gem "pry"
7
8
  end
9
+
10
+ group "test" do
11
+ gem 'simplecov', :require => false
12
+ end
@@ -8,6 +8,7 @@ GEM
8
8
  rspec (~> 3.0)
9
9
  ruby-progressbar (~> 1.4)
10
10
  method_source (0.8.2)
11
+ multi_json (1.11.1)
11
12
  pry (0.10.1)
12
13
  coderay (~> 1.1.0)
13
14
  method_source (~> 0.8.1)
@@ -31,9 +32,14 @@ GEM
31
32
  rspec-support (~> 3.2.0)
32
33
  rspec-support (3.2.1)
33
34
  ruby-progressbar (1.7.1)
35
+ simplecov (0.6.4)
36
+ multi_json (~> 1.0)
37
+ simplecov-html (~> 0.5.3)
38
+ simplecov-html (0.5.3)
34
39
  slop (3.6.0)
35
40
  spoon (0.0.4)
36
41
  ffi
42
+ stud (0.0.19)
37
43
 
38
44
  PLATFORMS
39
45
  java
@@ -43,3 +49,5 @@ DEPENDENCIES
43
49
  fuubar
44
50
  pry
45
51
  rspec (>= 3.0.0)
52
+ simplecov
53
+ stud
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Flores - a stress testing library
2
2
 
3
+ This library is named in loving memory of Carlo Flores.
4
+
5
+ ---
6
+
3
7
  When writing tests, it is often good to test a wide variety of inputs to ensure
4
8
  your entire input range behaves correctly.
5
9
 
@@ -14,12 +18,17 @@ to find common patterns in failures!
14
18
  Let's look at a sample situation. Ruby's TCPServer. Let's write a spec to cover a spec covering port binding:
15
19
 
16
20
  ```ruby
21
+ require "flores/rspec"
22
+ RSpec.configure do |config|
23
+ Flores::RSpec.configure(config)
24
+ end
25
+
17
26
  describe TCPServer do
18
27
  subject(:socket) { Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) }
19
28
  let(:port) { 5000 }
20
29
  let(:sockaddr) { Socket.sockaddr_in(port, "127.0.0.1") }
21
30
 
22
- after { socket.close}
31
+ after { socket.close }
23
32
 
24
33
  it "should bind successfully" do
25
34
  socket.bind(sockaddr)
@@ -46,7 +55,7 @@ Let's assume I don't know anything about tcp port ranges and test randomly in th
46
55
 
47
56
  ```ruby
48
57
  describe TCPServer do
49
- let(:port) { Randomized.integer(-100_000..100_000) }
58
+ let(:port) { Flores::Random.integer(-100_000..100_000) }
50
59
  ...
51
60
  end
52
61
  ```
@@ -61,15 +70,19 @@ Failures:
61
70
 
62
71
  1) TCPServer should bind successfully
63
72
  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)>'
73
+ expected: == 70144
74
+ got: 4608
75
+ # ./tcpserver_spec.rb:18:in `block (2 levels) in <top (required)>'
67
76
 
68
- Finished in 0.00155 seconds (files took 0.10221 seconds to load)
77
+ Finished in 0.00163 seconds (files took 0.09982 seconds to load)
69
78
  1 example, 1 failure
79
+
80
+ Failed examples:
81
+
82
+ rspec ./tcpserver_spec.rb:16 # TCPServer should bind successfully
70
83
  ```
71
84
 
72
- Well that's weird. Binding port 83359 actually made it bind on port 17823!
85
+ Well that's weird. Binding port 70144 actually made it bind on port 4608!
73
86
 
74
87
  If we run it more times, we'll see all kinds of different results:
75
88
 
@@ -104,74 +117,66 @@ we introduced randomness to our test inputs.
104
117
  We can go further and run a given spec example many times and group the
105
118
  failures by similarity and include context (what the inputs were, etc)
106
119
 
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!
120
+ This library provides an `stress_it` helper which behaves similarly to rspec's
121
+ `it` except that the spec is copied (and run) many times.
110
122
 
111
- The result is grouped by failure and includes context. Let's see how it works:
123
+ The result is grouped by failure and includes context (`let` and `subject`).
124
+ Let's see how it works:
112
125
 
113
- We'll change `it` to use `analyze_it` instead:
126
+ We'll change `it` to use `stress_it` instead, and also add `analyze_results`:
114
127
 
115
128
  ```diff
116
129
  - it "should bind successfully" do
117
- + analyze_it "should bind successfully", [:port] do
130
+ + analyze_results # track the `let` and `subject` values in our tests.
131
+ + stress_it "should bind successfully" do
118
132
  ```
119
133
 
134
+ The `analyze_results` method just adds an `after` hook to capture the `let` and
135
+ `subject` values used in each example.
136
+
137
+ The final step is to use a custom formatter provided with this library to do the analysis.
138
+
120
139
  Now rerunning the test. With barely any spec changes from the original, we have
121
140
  now enough randomness and stress testing to identify many different failure cases
122
141
  and input ranges for those failures.
123
142
 
124
143
  ```
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
144
+ % rspec -f Flores::RSpec::Formatters::Analyze tcpserver_spec.rb
145
+
146
+ TCPServer should bind successfully
147
+ 33.96% (of 742 total) tests are successful
148
+ Failure analysis:
149
+ 46.90% -> [348] SocketError
150
+ Sample exception for {:socket=>#<Socket:(closed)>, :port=>-74235}
151
+ getaddrinfo: nodename nor servname provided, or not known
152
+ Samples causing SocketError:
153
+ {:socket=>#<Socket:(closed)>, :port=>-60170}
154
+ {:socket=>#<Socket:(closed)>, :port=>-73159}
155
+ {:socket=>#<Socket:(closed)>, :port=>-84648}
156
+ {:socket=>#<Socket:(closed)>, :port=>-5936}
157
+ {:socket=>#<Socket:(closed)>, :port=>-78195}
158
+ 18.33% -> [136] RSpec::Expectations::ExpectationNotMetError
159
+ Sample exception for {:socket=>#<Socket:(closed)>, :port=>72849, :sockaddr=>"\x10\x02\x1C\x91\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"}
160
+ expected: == 72849
161
+ got: 7313
162
+ Samples causing RSpec::Expectations::ExpectationNotMetError:
163
+ {:socket=>#<Socket:(closed)>, :port=>74072, :sockaddr=>"\x10\x02!X\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"}
164
+ {:socket=>#<Socket:(closed)>, :port=>77973, :sockaddr=>"\x10\x020\x95\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"}
165
+ {:socket=>#<Socket:(closed)>, :port=>88867, :sockaddr=>"\x10\x02[#\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"}
166
+ {:socket=>#<Socket:(closed)>, :port=>87710, :sockaddr=>"\x10\x02V\x9E\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"}
167
+ {:socket=>#<Socket:(closed)>, :port=>95690, :sockaddr=>"\x10\x02u\xCA\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"}
168
+ 0.81% -> [6] Errno::EACCES
169
+ Sample exception for {:socket=>#<Socket:(closed)>, :port=>65897, :sockaddr=>"\x10\x02\x01i\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"}
170
+ Permission denied - bind(2) for 127.0.0.1:361
171
+ Samples causing Errno::EACCES:
172
+ {:socket=>#<Socket:(closed)>, :port=>879, :sockaddr=>"\x10\x02\x03o\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"}
173
+ {:socket=>#<Socket:(closed)>, :port=>66258, :sockaddr=>"\x10\x02\x02\xD2\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"}
174
+ {:socket=>#<Socket:(closed)>, :port=>65829, :sockaddr=>"\x10\x02\x01%\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"}
175
+ {:socket=>#<Socket:(closed)>, :port=>66044, :sockaddr=>"\x10\x02\x01\xFC\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"}
176
+ {:socket=>#<Socket:(closed)>, :port=>65897, :sockaddr=>"\x10\x02\x01i\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"}
177
+
178
+ Finished in 0.10509 seconds
179
+ 742 examples, 490 failures
177
180
  ```
181
+
182
+ Now we can see a wide variety of failure cases all found through randomization. Nice!
@@ -17,15 +17,19 @@
17
17
  require "flores/rspec"
18
18
  require "flores/random"
19
19
 
20
- RSpec.configure do |c|
21
- Flores::RSpec.configure(c)
22
- c.add_formatter("Flores::RSpec::Formatters::Analyze")
20
+ RSpec.configure do |config|
21
+ Flores::RSpec.configure(config)
22
+ Kernel.srand config.seed
23
+
24
+ # Demonstrate the wonderful Analyze formatter
25
+ config.add_formatter("Flores::RSpec::Formatters::Analyze")
23
26
  end
24
27
 
25
28
  describe "a random number" do
29
+ analyze_results
30
+
26
31
  context "between 0 and 200 inclusive" do
27
32
  let(:number) { Flores::Random.number(0..200) }
28
- analyze_results
29
33
  stress_it "should be less than 100" do
30
34
  expect(number).to(be < 100)
31
35
  end
@@ -14,12 +14,16 @@
14
14
  #
15
15
  # You should have received a copy of the GNU Affero General Public License
16
16
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ require "flores/rspec"
17
18
  require "flores/random"
18
19
  require "socket"
19
- require "flores/rspec"
20
20
 
21
- RSpec.configure do |c|
22
- Flores::RSpec.configure(c)
21
+ RSpec.configure do |config|
22
+ Kernel.srand config.seed
23
+ Flores::RSpec.configure(config)
24
+
25
+ # Demonstrate the wonderful Analyze formatter
26
+ config.add_formatter("Flores::RSpec::Formatters::Analyze")
23
27
  end
24
28
 
25
29
  # A factory for encapsulating behavior of a tcp server and client for the
@@ -53,7 +57,9 @@ class TCPIntegrationTestFactory
53
57
 
54
58
  @client.syswrite(text)
55
59
  @client.close
56
- server.read
60
+ data = server.read
61
+ data.force_encoding(Encoding.default_external).encoding
62
+ data
57
63
  ensure
58
64
  @client.close unless @client.closed?
59
65
  server.close unless server.nil? || server.closed?
@@ -61,25 +67,27 @@ class TCPIntegrationTestFactory
61
67
  end
62
68
 
63
69
  describe "TCPServer+TCPSocket" do
70
+ analyze_results
71
+
64
72
  let(:port) { Flores::Random.integer(1024..65535) }
65
73
  let(:text) { Flores::Random.text(1..2000) }
66
74
  subject { TCPIntegrationTestFactory.new(port) }
67
-
68
- describe "using stress_it" do
69
- stress_it2 "should send data correctly", [:port, :text] do
70
- begin
71
- subject.setup
72
- rescue Errno::EADDRINUSE
73
- next # Skip port bindings that are in use
74
- end
75
75
 
76
- begin
77
- received = subject.send_and_receive(text)
78
- expect(received.encoding).to(be == text.encoding)
79
- expect(received).to(be == text)
80
- ensure
81
- subject.teardown
82
- end
76
+ before do
77
+ begin
78
+ subject.setup
79
+ rescue Errno::EADDRINUSE
80
+ skip "Port #{port} was in use. Skipping!"
81
+ end
82
+ end
83
+
84
+ stress_it "should send data correctly", [:port, :text] do
85
+ begin
86
+ received = subject.send_and_receive(text)
87
+ expect(received.encoding).to(be == text.encoding)
88
+ expect(received).to(be == text)
89
+ ensure
90
+ subject.teardown
83
91
  end
84
92
  end
85
93
  end
@@ -14,31 +14,33 @@
14
14
  #
15
15
  # You should have received a copy of the GNU Affero General Public License
16
16
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ require "flores/rspec"
17
18
  require "flores/random"
18
19
  require "socket"
19
- require "flores/rspec"
20
20
 
21
- RSpec.configure do |c|
22
- Flores::RSpec.configure(c)
21
+ RSpec.configure do |config|
22
+ Flores::RSpec.configure(config)
23
+ Kernel.srand(config.seed)
24
+
25
+ # Demonstrate the wonderful Analyze formatter
26
+ config.add_formatter("Flores::RSpec::Formatters::Analyze")
23
27
  end
24
28
 
25
29
  describe TCPServer do
26
- analyze
27
-
30
+ analyze_results
28
31
  subject(:socket) { Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) }
29
32
  let(:sockaddr) { Socket.sockaddr_in(port, "127.0.0.1") }
30
- let(:ignore_eaddrinuse) do
31
- proc do |m, *args|
32
- begin
33
- m.call(*args)
34
- rescue Errno::EADDRINUSE # rubocop:disable Lint/HandleExceptions
35
- # ignore
36
- end
37
- end
38
- end
39
33
 
40
34
  after do
41
- socket.close
35
+ socket.close unless socket.closed?
36
+ end
37
+
38
+ context "on a random port" do
39
+ let(:port) { Flores::Random.integer(-100_000..100_000) }
40
+ stress_it "should bind successfully", [:port] do
41
+ socket.bind(sockaddr)
42
+ expect(socket.local_address.ip_port).to(be == port)
43
+ end
42
44
  end
43
45
 
44
46
  context "on privileged ports" do
@@ -53,7 +55,13 @@ describe TCPServer do
53
55
  stress_it "should bind on a port" do
54
56
  # EADDRINUSE is expected since we are picking ports at random
55
57
  # Let's ignore this specific exception
56
- allow(socket).to(receive(:bind).and_wrap_original(&ignore_eaddrinuse))
58
+ allow(socket).to(receive(:bind).and_wrap_original do |original, *args|
59
+ begin
60
+ original.call(*args)
61
+ rescue Errno::EADDRINUSE # rubocop:disable Lint/HandleExceptions
62
+ # Ignore
63
+ end
64
+ end)
57
65
  expect { socket.bind(sockaddr) }.to_not(raise_error)
58
66
  end
59
67
  end
@@ -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.3"
5
+ spec.version = "0.0.4"
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,254 @@
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
+
18
+ require "flores/namespace"
19
+ require "flores/random"
20
+ require "English"
21
+ require "openssl"
22
+
23
+ module Flores::PKI
24
+ # Generate a random serial number for a certificate.
25
+ def self.random_serial
26
+ # RFC5280 (X509) says:
27
+ # > 4.1.2.2. Serial Number
28
+ # > Certificate users MUST be able to handle serialNumber values up to 20 octets
29
+ Flores::Random.integer(1..9).to_s + Flores::Random.iterations(0..19).collect { Flores::Random.integer(0..9) }.join
30
+ end
31
+
32
+ # A certificate signing request.
33
+ #
34
+ # From here, you can configure a certificate to be created based on your
35
+ # desired configuration.
36
+ #
37
+ # Example making a root CA:
38
+ #
39
+ # key = OpenSSL::PKey::RSA.generate(4096, 65537)
40
+ # csr = Flores::PKI::CertificateSigningRequest.new
41
+ # csr.subject = "OU=Fancy Pants Inc."
42
+ # certificate = csr.create_root(key)
43
+ #
44
+ # Example making an intermediate CA:
45
+ #
46
+ # root_key = OpenSSL::PKey::RSA.generate(4096, 65537)
47
+ # root_csr = Flores::PKI::CertificateSigningRequest.new
48
+ # root_csr.subject = "OU=Fancy Pants Inc."
49
+ # root_csr.public_key = root_key.public
50
+ # root_certificate = csr.create_root(root_key)
51
+ #
52
+ # intermediate_key = OpenSSL::PKey::RSA.generate(4096, 65537)
53
+ # intermediate_csr = Flores::PKI::CertificateSigningRequest.new
54
+ # intermediate_csr.public_key = intermediate_key.public
55
+ # intermediate_csr.subject = "OU=Fancy Pants Inc. Intermediate 1"
56
+ # intermediate_certificate = csr.create_intermediate(root_certificate, root_key)
57
+ class CertificateSigningRequest
58
+ # raised when an invalid signing configuration is given
59
+ class InvalidRequest < StandardError; end
60
+
61
+ # raised when invalid data is present in a certificate request
62
+ class InvalidData < StandardError; end
63
+
64
+ # raised when an invalid subject (format, or whatever) is given in a certificate request
65
+ class InvalidSubject < InvalidData; end
66
+
67
+ # raised when an invalid time value is given for a certificate request
68
+ class InvalidTime < InvalidData; end
69
+
70
+ def initialize
71
+ self.serial = Flores::PKI.random_serial
72
+ self.digest_method = default_digest_method
73
+ end
74
+
75
+ private
76
+
77
+ def validate_subject(value)
78
+ OpenSSL::X509::Name.parse(value)
79
+ rescue OpenSSL::X509::NameError => e
80
+ raise InvalidSubject, "Invalid subject '#{value}'. (#{e})"
81
+ rescue TypeError => e
82
+ # Bug(?) in MRI 2.1.6(?)
83
+ raise InvalidSubject, "Invalid subject '#{value}'. (#{e})"
84
+ end
85
+
86
+ def subject=(value)
87
+ @subject = validate_subject(value)
88
+ end
89
+
90
+ attr_reader :subject
91
+
92
+ def subject_alternates=(values)
93
+ @subject_alternates = values
94
+ end
95
+
96
+ attr_reader :subject_alternates
97
+
98
+ def public_key=(value)
99
+ @public_key = validate_public_key(value)
100
+ end
101
+
102
+ def validate_public_key(value)
103
+ raise InvalidData, "public key must be a OpenSSL::PKey::PKey" unless value.is_a? OpenSSL::PKey::PKey
104
+ value
105
+ end
106
+
107
+ attr_reader :public_key
108
+
109
+ def start_time=(value)
110
+ @start_time = validate_time(value)
111
+ end
112
+
113
+ attr_reader :start_time
114
+
115
+ def expire_time=(value)
116
+ @expire_time = validate_time(value)
117
+ end
118
+
119
+ attr_reader :expire_time
120
+
121
+ def validate_time(value)
122
+ raise InvalidTime, "#{value.inspect} (class #{value.class.name})" unless value.is_a?(Time)
123
+ value
124
+ end
125
+
126
+ def certificate
127
+ return @certificate if @certificate
128
+ @certificate = OpenSSL::X509::Certificate.new
129
+
130
+ # RFC5280
131
+ # > 4.1.2.1. Version
132
+ # > version MUST be 3 (value is 2).
133
+ #
134
+ # Version value of '2' means a v3 certificate.
135
+ @certificate.version = 2
136
+
137
+ @certificate.subject = subject
138
+ @certificate.not_before = start_time
139
+ @certificate.not_after = expire_time
140
+ @certificate.public_key = public_key
141
+ @certificate
142
+ end
143
+
144
+ def default_digest_method
145
+ OpenSSL::Digest::SHA256.new
146
+ end
147
+
148
+ def self_signed?
149
+ @signing_certificate.nil?
150
+ end
151
+
152
+ def validate!
153
+ if self_signed?
154
+ if @signing_key.nil?
155
+ raise InvalidRequest, "No signing_key given. Cannot sign key."
156
+ end
157
+ elsif @signing_certificate.nil? && @signing_key
158
+ raise InvalidRequest, "signing_key given, but no signing_certificate is set"
159
+ elsif @signing_certificate && @signing_key.nil?
160
+ raise InvalidRequest, "signing_certificate given, but no signing_key is set"
161
+ end
162
+ end
163
+
164
+ def create
165
+ validate!
166
+ extensions = OpenSSL::X509::ExtensionFactory.new
167
+ extensions.subject_certificate = certificate
168
+ extensions.issuer_certificate = self_signed? ? certificate : signing_certificate
169
+
170
+ certificate.issuer = extensions.issuer_certificate.subject
171
+ certificate.add_extension(extensions.create_extension("subjectKeyIdentifier", "hash", true))
172
+
173
+ # RFC 5280 4.2.1.1. Authority Key Identifier
174
+ # This is "who signed this key"
175
+ certificate.add_extension(extensions.create_extension("authorityKeyIdentifier", "keyid:always,issuer", true))
176
+
177
+ if want_signature_ability?
178
+ # Create a CA.
179
+ certificate.add_extension(extensions.create_extension("basicConstraints", "CA:TRUE", true))
180
+ # Rough googling seems to indicate at least keyCertSign is required for CA and intermediate certs.
181
+ certificate.add_extension(extensions.create_extension("keyUsage", "keyCertSign, cRLSign, digitalSignature", true))
182
+ else
183
+ # Create a client+server certificate
184
+ #
185
+ # It feels weird to create a certificate that's valid as both server and client, but a brief inspection of major
186
+ # web properties (apple.com, google.com, yahoo.com, github.com, fastly.com, mozilla.com, amazon.com) reveals that
187
+ # major web properties have certificates with both clientAuth and serverAuth extended key usages. Further,
188
+ # these major server certificates all have digitalSignature and keyEncipherment for key usage.
189
+ #
190
+ # Here's the command I used to check this:
191
+ # echo mozilla.com apple.com github.com google.com yahoo.com fastly.com elastic.co amazon.com \
192
+ # | xargs -n1 sh -c 'openssl s_client -connect $1:443 \
193
+ # | sed -ne "/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p" \
194
+ # | openssl x509 -text -noout | sed -ne "/X509v3 extensions/,/Signature Algorithm/p" | sed -e "s/^/$1 /"' - \
195
+ # | grep -A2 'Key Usage'
196
+ certificate.add_extension(extensions.create_extension("keyUsage", "digitalSignature, keyEncipherment", true))
197
+ certificate.add_extension(extensions.create_extension("extendedKeyUsage", "clientAuth, serverAuth", false))
198
+ end
199
+ certificate.serial = OpenSSL::BN.new(serial)
200
+ certificate.sign(signing_key, digest_method)
201
+ certificate
202
+ end
203
+
204
+ # Set the certificate which is going to be signing this request.
205
+ def signing_certificate=(certificate)
206
+ raise InvalidData, "signing_certificate must be an OpenSSL::X509::Certificate" unless certificate.is_a?(OpenSSL::X509::Certificate)
207
+ @signing_certificate = certificate
208
+ end
209
+ attr_reader :signing_certificate
210
+
211
+ attr_reader :signing_key
212
+ def signing_key=(private_key)
213
+ raise InvalidData, "signing_key must be an OpenSSL::PKey::PKey (or a subclass)" unless private_key.is_a?(OpenSSL::PKey::PKey)
214
+ @signing_key = private_key
215
+ end
216
+
217
+ def want_signature_ability=(value)
218
+ raise InvalidData, "want_signature_ability must be a boolean" unless value == true || value == false
219
+ @want_signature_ability = value
220
+ end
221
+
222
+ def want_signature_ability?
223
+ @want_signature_ability == true
224
+ end
225
+
226
+ attr_reader :digest_method
227
+ def digest_method=(value)
228
+ raise InvalidData, "digest_method must be a OpenSSL::Digest (or a subclass)" unless value.is_a?(OpenSSL::Digest)
229
+ @digest_method = value
230
+ end
231
+
232
+ attr_reader :serial
233
+ def serial=(value)
234
+ begin
235
+ Integer(value)
236
+ rescue
237
+ raise InvalidData, "Invalid serial value. Must be a number (or a String containing only nubers)"
238
+ end
239
+ @serial = value
240
+ end
241
+
242
+ public(:serial, :serial=)
243
+ public(:subject, :subject=)
244
+ public(:subject_alternates, :subject_alternates=)
245
+ public(:public_key, :public_key=)
246
+ public(:start_time, :start_time=)
247
+ public(:expire_time, :expire_time=)
248
+ public(:digest_method, :digest_method=)
249
+ public(:want_signature_ability?, :want_signature_ability=)
250
+ public(:signing_key, :signing_key=)
251
+ public(:signing_certificate, :signing_certificate=)
252
+ public(:create)
253
+ end # class CertificateSigningRequest
254
+ end # Flores::PKI
@@ -16,6 +16,7 @@
16
16
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
 
18
18
  require "flores/namespace"
19
+ autoload :Socket, "socket"
19
20
 
20
21
  # A collection of methods intended for use in randomized testing.
21
22
  module Flores::Random
@@ -26,7 +27,7 @@ module Flores::Random
26
27
  # characters.
27
28
  CHARACTERS = [
28
29
  # Basic Latin
29
- *(32..126).map(&:chr),
30
+ *(32..126).map(&:chr).map { |c| c.force_encoding(Encoding.default_external) },
30
31
 
31
32
  # hand-selected CJK Unified Ideographs Extension A
32
33
  "㐤", "㐨", "㐻", "㑐",
@@ -99,4 +100,97 @@ module Flores::Random
99
100
  integer(range).times
100
101
  end
101
102
  end # def iterations
102
- end # module Randomized
103
+
104
+ # Return a random element from an array
105
+ def self.item(array)
106
+ array[integer(0...array.size)]
107
+ end
108
+
109
+ # Return a random IPv4 address as a string
110
+ def self.ipv4
111
+ # TODO(sissel): Support CIDR range restriction?
112
+ # TODO(sissel): Support netmask restriction?
113
+ [integer(0..IPV4_MAX)].pack("N").unpack("C4").join(".")
114
+ end
115
+
116
+ # Return a random IPv6 address as a string
117
+ #
118
+ # The address may be in abbreviated form (ABCD::01EF):w
119
+ def self.ipv6
120
+ # TODO(sissel): Support CIDR range restriction?
121
+ # TODO(sissel): Support netmask restriction?
122
+ length = integer(2..8)
123
+ if length == 8
124
+ # Full address; nothing to abbreviate
125
+ ipv6_pack(length)
126
+ else
127
+ abbreviation = ipv6_abbreviation(length)
128
+ if length == 2
129
+ first = 1
130
+ second = 1
131
+ else
132
+ first = integer(2...length)
133
+ second = length - first
134
+ end
135
+ ipv6_pack(first) + abbreviation + ipv6_pack(second)
136
+ end
137
+ end
138
+
139
+ # Get a TCP socket bound and listening on a random port.
140
+ #
141
+ # You are responsible for closing the socket.
142
+ #
143
+ # Returns [socket, address, port]
144
+ def self.tcp_listener(host = "127.0.0.1")
145
+ socket_listener(Socket::SOCK_STREAM, host)
146
+ end
147
+
148
+ # Get a UDP socket bound and listening on a random port.
149
+ #
150
+ # You are responsible for closing the socket.
151
+ #
152
+ # Returns [socket, address, port]
153
+ def self.udp_listener(host = "127.0.0.1")
154
+ socket_listener(Socket::SOCK_DGRAM, host)
155
+ end
156
+
157
+ private
158
+
159
+ IPV4_MAX = 1 << 32
160
+ IPV6_SEGMENT = 1 << 16
161
+
162
+ def self.ipv6_pack(length)
163
+ length.times.collect { integer(0..IPV6_SEGMENT).to_s(16) }.join(":")
164
+ end
165
+
166
+ def self.ipv6_abbreviation(length)
167
+ abbreviate = (integer(0..1) == 0)
168
+ if abbreviate
169
+ "::"
170
+ else
171
+ ":" + (8 - length).times.collect { "0" }.join(":") + ":"
172
+ end
173
+ end
174
+
175
+ LISTEN_BACKLOG = 5
176
+ def self.socket_listener(type, host)
177
+ socket = server_socket_class.new(Socket::AF_INET, type)
178
+ socket.bind(Socket.pack_sockaddr_in(0, host))
179
+ if type == Socket::SOCK_STREAM || type == Socket::SOCK_SEQPACKET
180
+ socket.listen(LISTEN_BACKLOG)
181
+ end
182
+
183
+ port = socket.local_address.ip_port
184
+ address = socket.local_address.ip_address
185
+ [socket, address, port]
186
+ end
187
+
188
+ def self.server_socket_class
189
+ if RUBY_ENGINE == 'jruby'
190
+ # https://github.com/jruby/jruby/wiki/ServerSocket
191
+ ServerSocket
192
+ else
193
+ Socket
194
+ end
195
+ end
196
+ end # module Flores::Random
@@ -85,13 +85,21 @@ module Flores::RSpec::Analyze
85
85
  end # def total
86
86
 
87
87
  def success_count
88
- if @results.include?(:success)
89
- @results[:success].length
88
+ if @results.include?(:passed)
89
+ @results[:passed].length
90
90
  else
91
91
  0
92
92
  end
93
93
  end # def success_count
94
94
 
95
+ def success_and_pending_count
96
+ count = 0
97
+ [:passed, :pending].each do |group|
98
+ count += @results[group].length
99
+ end
100
+ count
101
+ end # def success_count
102
+
95
103
  def percent(count)
96
104
  return (count + 0.0) / total
97
105
  end # def percent
@@ -100,10 +108,16 @@ module Flores::RSpec::Analyze
100
108
  return format("%.2f%%", percent(count) * 100)
101
109
  end # def percent_s
102
110
 
103
- def to_s
111
+ def to_s # rubocop:disable Metrics/AbcSize
104
112
  # This method is crazy complex for a formatter. Should refactor this significantly.
105
- report = ["#{percent_s(success_count)} tests successful of #{total} tests"]
106
- report += failure_summary if success_count < total
113
+ report = []
114
+ if @results[:pending].any?
115
+ # We have pending examples, put a clear message.
116
+ report << "#{percent_s(success_and_pending_count)} (of #{total} total) tests are successful or pending"
117
+ else
118
+ report << "#{percent_s(success_count)} (of #{total} total) tests are successful"
119
+ end
120
+ report += failure_summary if success_and_pending_count < total
107
121
  report.join("\n")
108
122
  end # def to_s
109
123
 
@@ -112,7 +126,8 @@ module Flores::RSpec::Analyze
112
126
  def failure_summary
113
127
  report = ["Failure analysis:"]
114
128
  report += @results.sort_by { |_, v| -v.length }.collect do |group, instances|
115
- next if group == :success
129
+ next if group == :passed
130
+ next if group == :pending
116
131
  error_report(group, instances)
117
132
  end.reject(&:nil?).flatten
118
133
  report
@@ -18,18 +18,62 @@ require "flores/namespace"
18
18
  require "rspec/core/formatters/base_text_formatter"
19
19
 
20
20
  Flores::RSpec::Formatters::Analyze = Class.new(RSpec::Core::Formatters::BaseTextFormatter) do
21
- RSpec::Core::Formatters.register self, :dump_failures, :dump_summary
21
+ RSpec::Core::Formatters.register self, :dump_failures, :dump_summary, :start, :example_passed, :example_failed, :example_pending
22
+
23
+ SPINNER = %w(▘ ▝ ▗ ▖)
24
+
25
+ def example_passed(_event)
26
+ increment(:pass)
27
+ end
28
+
29
+ def example_failed(_event)
30
+ increment(:failed)
31
+ end
32
+
33
+ def example_pending(_event)
34
+ increment(:pending)
35
+ end
36
+
37
+ def increment(status)
38
+ return unless output.tty?
39
+ now = Time.new
40
+ if status == :failed
41
+ output.write("F")
42
+ elsif status == :pending
43
+ output.write("P")
44
+ end
45
+
46
+ update_status if now - @last_update > 0.200
47
+ end
48
+
49
+ def update_status
50
+ glyph = SPINNER[@count]
51
+ output.write("#{glyph} ")
52
+ @last_update = Time.new
53
+ @count += 1
54
+ @count = 0 if @count >= SPINNER.size
55
+ end
56
+
57
+ def start(event)
58
+ @last_update = Time.now
59
+ @total = event.count
60
+ @count = 0
61
+ end
22
62
 
23
63
  def dump_summary(event)
64
+ output.write("\r") if output.tty?
24
65
  # The event is an RSpec::Core::Notifications::SummaryNotification
25
66
  # Let's mimic the BaseTextFormatter but without the failing test report
26
- output.puts
27
67
  output.puts "Finished in #{event.formatted_duration}"
28
68
  output.puts "#{event.colorized_totals_line}"
29
69
  end
30
70
 
71
+ def failures?(examples)
72
+ return examples.select { |e| e.metadata[:execution_result].status == :failed }.any?
73
+ end
74
+
31
75
  def dump_failures(event)
32
- output.puts
76
+ return unless failures?(event.examples)
33
77
  group = event.examples.each_with_object(Hash.new { |h, k| h[k] = [] }) do |e, m|
34
78
  m[e.metadata[:full_description]] << e
35
79
  m
@@ -45,8 +89,10 @@ Flores::RSpec::Formatters::Analyze = Class.new(RSpec::Core::Formatters::BaseText
45
89
 
46
90
  def group_by_result(examples) # rubocop:disable Metrics/AbcSize
47
91
  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]
92
+ status = example.metadata[:execution_result].status
93
+ case status
94
+ when :passed, :pending
95
+ results[status] << [example.metadata[:values], nil]
50
96
  else
51
97
  exception = example.metadata[:execution_result].exception
52
98
  results[exception.class] << [example.metadata[:values], exception]
@@ -0,0 +1,74 @@
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
+ require "flores/pki"
19
+
20
+ describe Flores::PKI::CertificateSigningRequest do
21
+ let(:csr) { Flores::PKI::CertificateSigningRequest.new }
22
+
23
+ # Here, I use a 512-bit key for faster tests.
24
+ # Please do not use 512-bit keys in production.
25
+ let(:key_bits) { 512 }
26
+
27
+ let(:key) { OpenSSL::PKey::RSA.generate(key_bits, 65537) }
28
+ let(:certificate_duration) { Flores::Random.number(1..86400) }
29
+
30
+ #before do
31
+ #csr.subject = "OU=Fancy Pants Co."
32
+ #csr.public_key = root_key.public_key
33
+ #csr.start_time = Time.now
34
+ #csr.expire_time = csr.start_time + certificate_duration
35
+ #end
36
+
37
+ shared_examples_for "a certificate" do
38
+ it "returns a valid certificate" do
39
+ expect(certificate).to(be_a(OpenSSL::X509::Certificate))
40
+ end
41
+ end
42
+
43
+ context "#subject=" do
44
+ context "with an invalid subject" do
45
+ let(:certificate_subject) { Flores::Random.text(1..20) }
46
+ it "fails" do
47
+ expect { csr.subject = certificate_subject }.to(raise_error(Flores::PKI::CertificateSigningRequest::InvalidSubject))
48
+ end
49
+ end
50
+ end
51
+
52
+ context "a self-signed client/server certificate" do
53
+ let(:certificate_subject) { "CN=server.example.com" }
54
+ before do
55
+ csr.subject = certificate_subject
56
+ csr.public_key = key.public_key
57
+ csr.start_time = Time.now
58
+ csr.expire_time = csr.start_time + certificate_duration
59
+ csr.signing_key = key
60
+ end
61
+ let(:certificate) { csr.create }
62
+ it_behaves_like "a certificate"
63
+ end
64
+ end
65
+
66
+ describe Flores::PKI do
67
+ context "#random_serial" do
68
+ let(:serial) { Flores::PKI.random_serial }
69
+ stress_it "generates a valid OpenSSL::BN value" do
70
+ OpenSSL::BN.new(serial)
71
+ Integer(serial)
72
+ end
73
+ end
74
+ end
@@ -20,11 +20,27 @@ shared_examples_for String do
20
20
  stress_it "should be a String" do
21
21
  expect(subject).to(be_a(String))
22
22
  end
23
+ stress_it "have expected encoding" do
24
+ expect(subject.encoding).to(be == Encoding.default_external)
25
+ end
23
26
  stress_it "have valid encoding" do
24
27
  expect(subject).to(be_valid_encoding)
25
28
  end
26
29
  end
27
30
 
31
+ shared_examples_for "network address" do
32
+ before { require "socket" }
33
+ stress_it "should be a valid ipv6 address according to Socket.pack_sockaddr_in" do
34
+ expect { Socket.pack_sockaddr_in(0, subject) }.not_to(raise_error)
35
+ end
36
+ end
37
+
38
+ shared_examples_for Socket do
39
+ stress_it "should be a Socket" do
40
+ expect(socket).to(be_a(Socket))
41
+ end
42
+ end
43
+
28
44
  describe Flores::Random do
29
45
  analyze_results
30
46
 
@@ -44,6 +60,10 @@ describe Flores::Random do
44
60
  stress_it "has correct length" do
45
61
  expect(subject.length).to(eq(length))
46
62
  end
63
+
64
+ stress_it "has correct encoding" do
65
+ expect(subject.encoding).to(be == Encoding.default_external)
66
+ end
47
67
  end
48
68
 
49
69
  context "that is negative" do
@@ -128,5 +148,101 @@ describe Flores::Random do
128
148
  # counts (via iteration) and is slow on large numbers.
129
149
  expect(range).to(include(subject.count))
130
150
  end
151
+
152
+ context "{ ... }" do
153
+ stress_it "should invoke a given block for each iteration" do
154
+ count = 0
155
+ Flores::Random.iterations(range) do
156
+ count += 1
157
+ end
158
+ expect(count).to(be > 0)
159
+ expect(range).to(include(count))
160
+ end
161
+ end
162
+ end
163
+
164
+ describe "#items" do
165
+ let(:start) { Flores::Random.integer(1..1000) }
166
+ let(:length) { Flores::Random.integer(1..1000) }
167
+ let(:range) { start..(start + length) }
168
+ let(:items) { Flores::Random.iterations(range).collect { Flores::Random.number(1..1000) } }
169
+ subject { Flores::Random.item(items) }
170
+
171
+ stress_it "should choose a random item from the list" do
172
+ expect(items).to(include(subject))
173
+ end
174
+
175
+ context "with a list of numbers" do
176
+ stress_it "should be return a number" do
177
+ expect(subject).to(be_a(Numeric))
178
+ end
179
+ end
180
+ end
181
+
182
+ describe "#ipv6" do
183
+ subject { Flores::Random.ipv6 }
184
+ it_behaves_like "network address"
185
+ end
186
+
187
+ describe "#ipv4" do
188
+ subject { Flores::Random.ipv4 }
189
+ it_behaves_like "network address"
190
+ end
191
+
192
+ describe "networking" do
193
+ let(:socket) { subject[0] }
194
+ let(:host) { subject[1] }
195
+ let(:port) { subject[2] }
196
+ after do
197
+ socket.close
198
+ end
199
+
200
+ describe "#udp_listener" do
201
+ let(:text) { Flores::Random.text(1..100) }
202
+ subject { Flores::Random.udp_listener }
203
+ it_behaves_like Socket
204
+
205
+ context "#recvfrom" do
206
+ let(:payload) do
207
+ data, _ = socket.recvfrom(65536)
208
+ data.force_encoding(text.encoding)
209
+ end
210
+ let(:client) { UDPSocket.new }
211
+
212
+ before do
213
+ client.send(text, 0, host, port)
214
+ end
215
+
216
+ after do
217
+ client.close
218
+ end
219
+
220
+ it "receives udp packets" do
221
+ expect(payload).to(be == text)
222
+ end
223
+ end
224
+ end
225
+
226
+ describe "#tcp_listener" do
227
+ subject { Flores::Random.tcp_listener }
228
+ it_behaves_like Socket
229
+
230
+ context "#accept" do
231
+ let(:client) { TCPSocket.new(host, port) }
232
+
233
+ before do
234
+ client
235
+ end
236
+
237
+ after do
238
+ client.close
239
+ end
240
+
241
+ it "returns a socket" do
242
+ connection, _address = socket.accept
243
+ expect(connection).to(be_a(Socket))
244
+ end
245
+ end
246
+ end
131
247
  end
132
248
  end
@@ -14,6 +14,8 @@
14
14
  #
15
15
  # You should have received a copy of the GNU Affero General Public License
16
16
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ require "simplecov"
18
+ SimpleCov.start
17
19
  require "flores/random"
18
20
  require "flores/rspec"
19
21
 
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.3
4
+ version: 0.0.4
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-24 00:00:00.000000000 Z
11
+ date: 2015-06-23 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Add fuzzing, randomization, and stress to your tests.
@@ -23,7 +23,7 @@ executables: []
23
23
  extensions: []
24
24
  extra_rdoc_files: []
25
25
  files:
26
- - .rubocop.yml
26
+ - ".rubocop.yml"
27
27
  - Gemfile
28
28
  - Gemfile.lock
29
29
  - LICENSE.txt
@@ -31,41 +31,43 @@ files:
31
31
  - README.md
32
32
  - examples/analyze_number.rb
33
33
  - examples/socket_acceptance_spec.rb
34
- - examples/socket_analyze_spec.rb
35
34
  - examples/socket_stress_spec.rb
36
35
  - flores.gemspec
37
36
  - lib/flores/namespace.rb
37
+ - lib/flores/pki.rb
38
38
  - lib/flores/random.rb
39
39
  - lib/flores/rspec.rb
40
40
  - lib/flores/rspec/analyze.rb
41
41
  - lib/flores/rspec/formatters/analyze.rb
42
42
  - lib/flores/rspec/stress.rb
43
+ - spec/flores/pki_spec.rb
43
44
  - spec/flores/random_spec.rb
44
45
  - spec/flores/rspec/stress_spec.rb
45
46
  - spec/spec_init.rb
46
- homepage:
47
+ homepage:
47
48
  licenses:
48
49
  - AGPL 3.0 - http://www.gnu.org/licenses/agpl-3.0.html
49
50
  metadata: {}
50
- post_install_message:
51
+ post_install_message:
51
52
  rdoc_options: []
52
53
  require_paths:
53
54
  - lib
54
55
  - lib
55
56
  required_ruby_version: !ruby/object:Gem::Requirement
56
57
  requirements:
57
- - - '>='
58
+ - - ">="
58
59
  - !ruby/object:Gem::Version
59
60
  version: '0'
60
61
  required_rubygems_version: !ruby/object:Gem::Requirement
61
62
  requirements:
62
- - - '>='
63
+ - - ">="
63
64
  - !ruby/object:Gem::Version
64
65
  version: '0'
65
66
  requirements: []
66
- rubyforge_project:
67
- rubygems_version: 2.4.5
68
- signing_key:
67
+ rubyforge_project:
68
+ rubygems_version: 2.4.6
69
+ signing_key:
69
70
  specification_version: 4
70
71
  summary: Fuzz, randomize, and stress your tests
71
72
  test_files: []
73
+ has_rdoc:
@@ -1,40 +0,0 @@
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 "socket"
19
- require "flores/rspec"
20
-
21
- RSpec.configure do |c|
22
- Flores::RSpec.configure(c)
23
- end
24
-
25
- describe TCPServer do
26
- subject(:socket) { Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) }
27
- let(:sockaddr) { Socket.sockaddr_in(port, "127.0.0.1") }
28
-
29
- context "on a random port" do
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
38
- end
39
- end
40
- end