flores 0.0.1

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 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: []