flores 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f3e39f321f6d9a2ab60eeb8b478e8c140137b54c
4
+ data.tar.gz: a46b1752bbc387b7007b212a6b84388d69cd75c8
5
+ SHA512:
6
+ metadata.gz: 25aabaf1ef95b9fa0c0ddca1a137edc2c3677420bebae35b6f3c697b5dd298ce4a3246b69210111f9de2ade5a1b80adc21bf01420bcaa8f0783a8c26bdd3391c
7
+ data.tar.gz: 6e4ce72dd9e879c63b66aa12a6585c11b1c9011fcbb9afb2121870c52be7a866996f8bedbdec949112310974f0280c969662ad78cc7ae4a80d35f82af68f987b
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ group "development" do
4
+ gem "rspec", ">= 3.0.0"
5
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,23 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.2.5)
5
+ rspec (3.2.0)
6
+ rspec-core (~> 3.2.0)
7
+ rspec-expectations (~> 3.2.0)
8
+ rspec-mocks (~> 3.2.0)
9
+ rspec-core (3.2.0)
10
+ rspec-support (~> 3.2.0)
11
+ rspec-expectations (3.2.0)
12
+ diff-lcs (>= 1.2.0, < 2.0)
13
+ rspec-support (~> 3.2.0)
14
+ rspec-mocks (3.2.0)
15
+ diff-lcs (>= 1.2.0, < 2.0)
16
+ rspec-support (~> 3.2.0)
17
+ rspec-support (3.2.1)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ rspec (>= 3.0.0)
data/Makefile ADDED
@@ -0,0 +1,50 @@
1
+ GEMSPEC=$(shell ls *.gemspec)
2
+ VERSION=$(shell awk -F\" '/spec.version/ { print $$2 }' $(GEMSPEC))
3
+ NAME=$(shell awk -F\" '/spec.name/ { print $$2 }' $(GEMSPEC))
4
+ GEM=$(NAME)-$(VERSION).gem
5
+
6
+ .PHONY: test
7
+ test:
8
+ sh notify-failure.sh ruby test/all.rb
9
+
10
+ .PHONY: testloop
11
+ testloop:
12
+ while true; do \
13
+ $(MAKE) test; \
14
+ $(MAKE) wait-for-changes; \
15
+ done
16
+
17
+ .PHONY: serve-coverage
18
+ serve-coverage:
19
+ cd coverage; python -mSimpleHTTPServer
20
+
21
+ .PHONY: wait-for-changes
22
+ wait-for-changes:
23
+ -inotifywait --exclude '\.swp' -e modify $$(find $(DIRS) -name '*.rb'; find $(DIRS) -type d)
24
+
25
+ .PHONY: package
26
+ package: | $(GEM)
27
+
28
+ .PHONY: gem
29
+ gem: $(GEM)
30
+
31
+ $(GEM):
32
+ gem build $(GEMSPEC)
33
+
34
+ .PHONY: test-package
35
+ test-package: $(GEM)
36
+ # Sometimes 'gem build' makes a faulty gem.
37
+ gem unpack $(GEM)
38
+ rm -rf $(NAME)-$(VERSION)/
39
+
40
+ .PHONY: publish
41
+ publish: test-package
42
+ gem push $(GEM)
43
+
44
+ .PHONY: install
45
+ install: $(GEM)
46
+ gem install $(GEM)
47
+
48
+ .PHONY: clean
49
+ clean:
50
+ -rm -rf .yardoc $(GEM) $(NAME)-$(VERSION)/
@@ -0,0 +1,13 @@
1
+ require "randomized"
2
+ require "rspec/stress_it"
3
+
4
+ RSpec.configure do |c|
5
+ c.extend RSpec::StressIt
6
+ end
7
+
8
+ describe "number" do
9
+ let(:number) { Randomized.number(0..200) }
10
+ analyze_it "should be less than 100", [:number] do
11
+ expect(number).to(be < 100)
12
+ end
13
+ end
@@ -0,0 +1,85 @@
1
+ require "randomized"
2
+ require "socket"
3
+ require "rspec/stress_it"
4
+
5
+ RSpec.configure do |c|
6
+ c.extend RSpec::StressIt
7
+ end
8
+
9
+ class TCPIntegrationTestFactory
10
+ def initialize(port)
11
+ @listener = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
12
+ @client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
13
+ @port = port
14
+ end
15
+
16
+ def teardown
17
+ @listener.close unless @listener.closed?
18
+ @client.close unless @listener.closed?
19
+ end
20
+
21
+ def sockaddr
22
+ Socket.sockaddr_in(@port, "127.0.0.1")
23
+ end
24
+
25
+ def setup
26
+ @listener.bind(sockaddr)
27
+ @listener.listen(5)
28
+ end
29
+
30
+ def send_and_receive(text)
31
+ @client.connect(sockaddr)
32
+ server, _ = @listener.accept
33
+
34
+ @client.syswrite(text)
35
+ @client.close
36
+ #expect(client.syswrite(text)).to(be == text.bytesize)
37
+ server.read
38
+ ensure
39
+ @client.close unless @client.closed?
40
+ server.close unless server.nil? || server.closed?
41
+ end
42
+ end
43
+
44
+ describe "TCPServer+TCPSocket" do
45
+ let(:port) { Randomized.number(1024..65535) }
46
+ let(:text) { Randomized.text(1..10000) }
47
+ subject { TCPIntegrationTestFactory.new(port) }
48
+
49
+ #describe "using before/after and stress_it2" do
50
+ #before do
51
+ #begin
52
+ #subject.setup
53
+ #rescue Errno::EADDRINUSE
54
+ ## We chose a random port that was already in use, let's skip this test.
55
+ #skip("Port #{port} is in use by another process, skipping")
56
+ #end
57
+ #end
58
+
59
+ #after do
60
+ #subject.teardown
61
+ #end
62
+
63
+ #stress_it2 "should send data correctly" do
64
+ #received = subject.send_and_receive(text)
65
+ #expect(received).to(be == text)
66
+ #end
67
+ #end
68
+
69
+ describe "using stress_it" do
70
+ stress_it "should send data correctly" do
71
+ begin
72
+ subject.setup
73
+ rescue Errno::EADDRINUSE
74
+ next # Skip port bindings that are in use
75
+ end
76
+
77
+ begin
78
+ received = subject.send_and_receive(text)
79
+ expect(received).to(be == text)
80
+ ensure
81
+ subject.teardown
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,21 @@
1
+ require "randomized"
2
+ require "socket"
3
+ require "rspec/stress_it"
4
+
5
+ RSpec.configure do |c|
6
+ c.extend RSpec::StressIt
7
+ end
8
+
9
+ describe TCPServer do
10
+ subject(:socket) { Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) }
11
+ let(:sockaddr) { Socket.sockaddr_in(port, "127.0.0.1") }
12
+ after { socket.close }
13
+
14
+ context "on a random port" do
15
+ let(:port) { Randomized.number(-100000..100000) }
16
+ analyze_it "should bind successfully", [:port] do
17
+ socket.bind(sockaddr)
18
+ expect(socket.local_address.ip_port).to(be == port)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ require "randomized"
2
+ require "socket"
3
+ require "rspec/stress_it"
4
+
5
+ RSpec.configure do |c|
6
+ c.extend RSpec::StressIt
7
+ end
8
+
9
+ describe TCPServer do
10
+ subject(:socket) { Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) }
11
+ let(:sockaddr) { Socket.sockaddr_in(port, "127.0.0.1") }
12
+ let(:ignore_eaddrinuse) do
13
+ proc do |m, *args|
14
+ begin
15
+ m.call(*args)
16
+ rescue Errno::EADDRINUSE
17
+ # ignore
18
+ end
19
+ end
20
+ end
21
+
22
+ after do
23
+ socket.close
24
+ end
25
+
26
+ context "on privileged ports" do
27
+ let(:port) { Randomized.number(1..1023) }
28
+ stress_it "should raise Errno::EACCESS" do
29
+ expect { socket.bind(sockaddr) }.to(raise_error(Errno::EACCES))
30
+ end
31
+ end
32
+
33
+ context "on unprivileged ports" do
34
+ let(:port) { Randomized.number(1025..65535) }
35
+ stress_it "should bind on a port" do
36
+ # EADDRINUSE is expected since we are picking ports at random
37
+ # Let's ignore this specific exception
38
+ allow(socket).to(receive(:bind).and_wrap_original(&ignore_eaddrinuse))
39
+ expect { socket.bind(sockaddr) }.to_not(raise_error)
40
+ end
41
+ end
42
+
43
+ context "on port 0" do
44
+ let(:port) { 0 }
45
+ stress_it "should bind successfully" do
46
+ expect { socket.bind(sockaddr) }.to_not(raise_error)
47
+ end
48
+ end
49
+ end
data/flores.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ Gem::Specification.new do |spec|
2
+ files = %x{git ls-files}.split("\n")
3
+
4
+ spec.name = "flores"
5
+ spec.version = "0.0.1"
6
+ spec.summary = "Fuzz, randomize, and stress your tests"
7
+ spec.description = <<-DESCRIPTION
8
+ Add fuzzing, randomization, and stress to your tests.
9
+
10
+ This library is an exploration to build the tools to let you write tests
11
+ that find bugs.
12
+
13
+ In memory of Carlo Flores.
14
+ DESCRIPTION
15
+ spec.license = "AGPL 3.0 - http://www.gnu.org/licenses/agpl-3.0.html"
16
+
17
+ spec.files = files
18
+ spec.require_paths << "lib"
19
+
20
+ spec.authors = ["Jordan Sissel"]
21
+ spec.email = ["jls@semicomplete.com"]
22
+ end
23
+
data/lib/randomized.rb ADDED
@@ -0,0 +1,67 @@
1
+ # A collection of methods intended for use in randomized testing.
2
+ module Randomized
3
+ # Generates text with random characters of a given length (or within a length range)
4
+ #
5
+ # * The length can be a number or a range `x..y`. If a range, it must be ascending (x < y)
6
+ # * Negative lengths are not permitted and will raise an ArgumentError
7
+ #
8
+ # @param length [Fixnum or Range] the length of text to generate
9
+ # @return [String] the
10
+ def self.text(length)
11
+ if length.is_a?(Range)
12
+ raise ArgumentError, "Requires ascending range, you gave #{length}." if length.end < length.begin
13
+ raise ArgumentError, "A negative length is not permitted, I received range #{length}" if length.begin < 0
14
+
15
+ length = integer(length)
16
+ else
17
+ raise ArgumentError, "A negative length is not permitted, I received #{length}" if length < 0
18
+ end
19
+
20
+ length.times.collect { character }.join
21
+ end # def text
22
+
23
+ # Generates a random character (A string of length 1)
24
+ #
25
+ # @return [String]
26
+ def self.character
27
+ # TODO(sissel): Add support to generate valid UTF-8. I started reading
28
+ # Unicode 7 (http://www.unicode.org/versions/Unicode7.0.0/) and after much
29
+ # reading, I realized I wasn't in my house anymore but had somehow lost
30
+ # track of time and was alone in a field. Civilization had fallen centuries
31
+ # ago. :P
32
+
33
+ # Until UTF-8 is supported, just return a random lower ASCII character
34
+ integer(32..127).chr
35
+ end # def character
36
+
37
+ # Return a random integer value within a given range.
38
+ #
39
+ # @param range [Range]
40
+ def self.integer(range)
41
+ raise ArgumentError, "Range not given, got #{range.class}: #{range.inspect}" if !range.is_a?(Range)
42
+ rand(range)
43
+ end # def integer
44
+
45
+ # Return a random number within a given range.
46
+ #
47
+ # @param range [Range]
48
+ def self.number(range)
49
+ raise ArgumentError, "Range not given, got #{range.class}: #{range.inspect}" if !range.is_a?(Range)
50
+ # Range#size returns the number of elements in the range, not the length of the range.
51
+ # This makes #size return (abs(range.begin - range.end) + 1), and we want the length, so subtract 1.
52
+ rand * (range.size - 1) + range.begin
53
+ end # def number
54
+
55
+ # Run a block a random number of times.
56
+ #
57
+ # @param range [Fixnum of Range] same meaning as #integer(range)
58
+ def self.iterations(range, &block)
59
+ range = 0..range if range.is_a?(Numeric)
60
+ if block_given?
61
+ integer(range).times(&block)
62
+ nil
63
+ else
64
+ integer(range).times
65
+ end
66
+ end # def iterations
67
+ end
@@ -0,0 +1,129 @@
1
+ # encoding: utf-8
2
+ require "rspec/core"
3
+
4
+ module RSpec::StressIt
5
+ DEFAULT_ITERATIONS = 1..5000
6
+
7
+ # Wraps `it` and runs the block many times. Each run has will clear the `let` cache.
8
+ #
9
+ # The intent of this is to allow randomized testing for fuzzing and stress testing
10
+ # of APIs to help find edge cases and weird behavior.
11
+ #
12
+ # The default number of iterations is randomly selected between 1 and 1000 inclusive
13
+ def stress_it(name, options={}, &block)
14
+ __iterations = Randomized.iterations(options.delete(:stress_iterations) || DEFAULT_ITERATIONS)
15
+ it(name, options) do
16
+ # Run the block of an example many times
17
+ __iterations.each do |i|
18
+ # Run the block within 'it' scope
19
+ instance_eval(&block)
20
+
21
+ # clear the internal rspec `let` cache this lets us run a test
22
+ # repeatedly with fresh `let` evaluations.
23
+ # Reference: https://github.com/rspec/rspec-core/blob/5fc29a15b9af9dc1c9815e278caca869c4769767/lib/rspec/core/memoized_helpers.rb#L124-L127
24
+ __memoized.clear
25
+ end
26
+ end # it ...
27
+ end # def stress_it
28
+
29
+ # Generate a random number of copies of a given example.
30
+ # The idea is to take 1 `it` and run it N times to help tease out failures.
31
+ # Of course, the teasing requires you have randomized `let` usage, for example:
32
+ #
33
+ # let(:number) { Randomized.number(0..200) }
34
+ # it "should be less than 100" do
35
+ # expect(number).to(be < 100)
36
+ # end
37
+ def stress_it2(name, options={}, &block)
38
+ __iterations = Randomized.iterations(options.delete(:stress_iterations) || DEFAULT_ITERATIONS)
39
+ __iterations.each do |i|
40
+ it(example_name + " [#{i}]", *args) do
41
+ instance_eval(&block)
42
+ end # it ...
43
+ end # .times
44
+ end
45
+
46
+ # Perform analysis on failure scenarios of a given example
47
+ #
48
+ # This will run the given example a random number of times and aggregate the
49
+ # results. If any failures occur, the spec will fail and a report will be
50
+ # given on that test.
51
+ #
52
+ # Example spec:
53
+ #
54
+ # let(:number) { Randomized.number(0..200) }
55
+ # fuzz "should be less than 100" do
56
+ # expect(number).to(be < 100)
57
+ # end
58
+ #
59
+ # Example report:
60
+ def analyze_it(name, variables, &block)
61
+ it(name) do
62
+ results = Hash.new { |h,k| h[k] = [] }
63
+ iterations = Randomized.iterations(DEFAULT_ITERATIONS)
64
+ iterations.each do |i|
65
+ state = Hash[variables.collect { |l| [l, __send__(l)] }]
66
+ begin
67
+ instance_eval(&block)
68
+ results[:success] << [state, nil]
69
+ rescue => e
70
+ results[e.class] << [state, e]
71
+ rescue Exception => e
72
+ results[e.class] << [state, e]
73
+ end
74
+
75
+ # Clear `let` memoizations
76
+ __memoized.clear
77
+ end
78
+
79
+ if results[:success] != iterations
80
+ raise Analysis.new(results)
81
+ end
82
+ end
83
+ end
84
+
85
+ class Analysis < StandardError
86
+ def initialize(results)
87
+ @results = results
88
+ end
89
+
90
+ def total
91
+ @results.reduce(0) { |m, (k,v)| m + v.length }
92
+ end
93
+
94
+ def success_count
95
+ if @results.include?(:success)
96
+ @results[:success].length
97
+ else
98
+ 0
99
+ end
100
+ end
101
+
102
+ def percent(count)
103
+ return (count + 0.0) / total
104
+ end
105
+
106
+ def percent_s(count)
107
+ return sprintf("%.2f%%", percent(count) * 100)
108
+ end
109
+
110
+ def to_s
111
+ # This method is crazy complex for a formatter. Should refactor this significantly.
112
+ report = ["#{percent_s(success_count)} tests successful of #{total} tests"]
113
+ if success_count < total
114
+ report << "Failure analysis:"
115
+ report += @results.sort_by { |k,v| -v.length }.reject { |k,v| k == :success }.collect do |k, v|
116
+ sample = v.sample(5).collect { |v| v.first }.join(", ")
117
+ [
118
+ " #{percent_s(v.length)} -> [#{v.length}] #{k}",
119
+ " Sample exception:",
120
+ v.sample(1).first[1].to_s.gsub(/^/, " "),
121
+ " Samples causing #{k}:",
122
+ *v.sample(5).collect { |state, _exception| " #{state}" }
123
+ ]
124
+ end.flatten
125
+ end
126
+ report.join("\n")
127
+ end
128
+ end
129
+ end # module RSpec::StressIt
@@ -0,0 +1,95 @@
1
+ require "randomized"
2
+ require "rspec/stress_it"
3
+
4
+ RSpec.configure do |c|
5
+ c.extend RSpec::StressIt
6
+ end
7
+
8
+ describe Randomized do
9
+ describe "#text" do
10
+ context "with no arguments" do
11
+ it "should raise ArgumentError" do
12
+ expect { subject.text }.to(raise_error(ArgumentError))
13
+ end
14
+ end
15
+
16
+ context "with 1 length argument" do
17
+ subject { described_class.text(length) }
18
+
19
+ context "that is positive" do
20
+ let(:length) { rand(1..1000) }
21
+ stress_it "should give a string with that length" do
22
+ expect(subject).to(be_a(String))
23
+ expect(subject.length).to(eq(length))
24
+ end
25
+ end
26
+
27
+ context "that is negative" do
28
+ let(:length) { -1 * rand(1..1000) }
29
+ stress_it "should raise ArgumentError" do
30
+ expect { subject }.to(raise_error(ArgumentError))
31
+ end
32
+ end
33
+ end
34
+
35
+ context "with 1 range argument" do
36
+ let(:start) { rand(1..1000) }
37
+ let(:length) { rand(1..1000) }
38
+ subject { described_class.text(range) }
39
+
40
+ context "that is ascending" do
41
+ let(:range) { start .. (start + length) }
42
+ stress_it "should give a string within that length range" do
43
+ expect(subject).to(be_a(String))
44
+ expect(range).to(include(subject.length))
45
+ end
46
+ end
47
+
48
+ context "that is descending" do
49
+ let(:range) { start .. (start - length) }
50
+ stress_it "should raise ArgumentError" do
51
+ expect { subject }.to(raise_error(ArgumentError))
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "#character" do
58
+ subject { described_class.character }
59
+ stress_it "returns a string of length 1" do
60
+ expect(subject.length).to(be == 1)
61
+ end
62
+ end
63
+
64
+ shared_examples_for "random numbers within a range" do
65
+ let(:start) { Randomized.integer(-100000 .. 100000) }
66
+ let(:length) { Randomized.integer(1 .. 100000) }
67
+ let(:range) { start .. (start + length) }
68
+
69
+ stress_it "should be a Numeric" do
70
+ expect(subject).to(be_a(Numeric))
71
+ end
72
+
73
+ stress_it "should be within the bounds of the given range" do
74
+ expect(range).to(include(subject))
75
+ end
76
+ end
77
+
78
+ describe "#integer" do
79
+ it_behaves_like "random numbers within a range" do
80
+ subject { Randomized.integer(range) }
81
+ stress_it "is a Fixnum" do
82
+ expect(subject).to(be_a(Fixnum))
83
+ end
84
+ end
85
+ end
86
+ describe "#number" do
87
+ it_behaves_like "random numbers within a range" do
88
+ subject { Randomized.number(range) }
89
+
90
+ stress_it "is a Float" do
91
+ expect(subject).to(be_a(Float))
92
+ end
93
+ end
94
+ end
95
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flores
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jordan Sissel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |2
14
+ Add fuzzing, randomization, and stress to your tests.
15
+
16
+ This library is an exploration to build the tools to let you write tests
17
+ that find bugs.
18
+
19
+ In memory of Carlo Flores.
20
+ email:
21
+ - jls@semicomplete.com
22
+ executables: []
23
+ extensions: []
24
+ extra_rdoc_files: []
25
+ files:
26
+ - Gemfile
27
+ - Gemfile.lock
28
+ - Makefile
29
+ - examples/analyze_number.rb
30
+ - examples/socket_acceptance_spec.rb
31
+ - examples/socket_analyze_spec.rb
32
+ - examples/socket_stress_spec.rb
33
+ - flores.gemspec
34
+ - lib/randomized.rb
35
+ - lib/rspec/stress_it.rb
36
+ - spec/randomized_spec.rb
37
+ homepage:
38
+ licenses:
39
+ - AGPL 3.0 - http://www.gnu.org/licenses/agpl-3.0.html
40
+ metadata: {}
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 2.4.3
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: Fuzz, randomize, and stress your tests
62
+ test_files: []