right_develop 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ When /^a client makes a (buggy )?request to '(.*)'$/ do |buggy, path|
2
+ t = RightDevelop::Net::HTTPClient::DEFAULT_OPTIONS[:timeout]
3
+ o = RightDevelop::Net::HTTPClient::DEFAULT_OPTIONS[:open_timeout]
4
+ When "a client makes a #{buggy}request to '#{path}' with timeout #{t} and open_timeout #{o}"
5
+ end
6
+
7
+
8
+ When /^a client makes a (buggy )?request to '(.*)' with timeout (\d+) and open_timeout (\d+)$/ do |buggy, path, timeout, open_timeout|
9
+ buggy = !(buggy.nil? || buggy.empty?)
10
+
11
+ @mock_servers.should_not be_nil
12
+ @mock_servers.size.should == 1
13
+
14
+ timeout = timeout.to_i
15
+ open_timeout = open_timeout.to_i
16
+ url = @mock_servers.first.url
17
+
18
+ @http_client = RightDevelop::Net::HTTPClient.new(:timeout=>timeout, :open_timeout=>open_timeout)
19
+ @request_t0 = Time.now
20
+ begin
21
+ raise ArgumentError, "Fall down go boom!" if buggy
22
+ @http_client.get("#{url}#{path}", {:timeout => timeout, :open_timeout => open_timeout})
23
+ rescue Exception => e
24
+ @request_error = e
25
+ end
26
+ @request_t1 = Time.now
27
+ end
@@ -0,0 +1,93 @@
1
+ # Rails' constantize method to convert passed in policy to the actual class
2
+ def constantize(camel_cased_word)
3
+ names = camel_cased_word.split('::')
4
+ names.shift if names.empty? || names.first.empty?
5
+
6
+ constant = Object
7
+ names.each do |name|
8
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
9
+ end
10
+ constant
11
+ end
12
+
13
+ Given /^(\w+) balancing policy$/ do |policy|
14
+ @health_check = Proc.new do |endpoint|
15
+ begin
16
+ RightDevelop::Net::HTTPClient.new.get(endpoint, {:timeout => 1, :open_timeout => 1})
17
+ true
18
+ rescue Exception => e
19
+ false
20
+ end
21
+ end
22
+
23
+ @options ||= { :policy => constantize("RightDevelop::Net::LB::" + policy), :health_check => @health_check }
24
+ end
25
+
26
+ When /^a client makes a (buggy )?load-balanced request to '(.*)'$/ do |buggy, path|
27
+ t = RightDevelop::Net::HTTPClient::DEFAULT_OPTIONS[:timeout]
28
+ o = RightDevelop::Net::HTTPClient::DEFAULT_OPTIONS[:open_timeout]
29
+ step "a client makes a #{buggy}load-balanced request to '#{path}' with timeout #{t} and open_timeout #{o}"
30
+ end
31
+
32
+ When /^a client makes a (buggy )?load-balanced request to '(.*)' with timeout (\d+) and open_timeout (\d+)$/ do |buggy, path, timeout, open_timeout|
33
+ buggy = !(buggy.nil? || buggy.empty?)
34
+
35
+ @mock_servers.should_not be_nil
36
+ @mock_servers.empty?.should be_false
37
+
38
+ timeout = timeout.to_i
39
+ open_timeout = open_timeout.to_i
40
+ urls = @mock_servers.map { |s| s.url }
41
+ @request_balancer = RightDevelop::Net::RequestBalancer.new(urls)
42
+ @request_attempts = 0
43
+ @request_t0 = Time.now
44
+ @http_client = RightDevelop::Net::HTTPClient.new
45
+ begin
46
+ @request_balancer.request do |url|
47
+ @request_attempts += 1
48
+ raise ArgumentError, "Fall down go boom!" if buggy
49
+ @http_client.get("#{url}#{path}", {:timeout => timeout, :open_timeout => open_timeout})
50
+ end
51
+ rescue Exception => e
52
+ @request_error = e
53
+ end
54
+ @request_t1 = Time.now
55
+ end
56
+
57
+ Then /^the request should (\w+ ?\w*)$/ do |behavior|
58
+ case behavior
59
+ when 'complete'
60
+ error_expected = false
61
+ when 'raise'
62
+ error_expected = true
63
+ when /raise (\w+)/
64
+ error_expected = true
65
+ error_class_expected = /raise (\w+)/.match(behavior)[1]
66
+ else
67
+ raise ArgumentError, "Unknown request behavior #{behavior}"
68
+ end
69
+
70
+ if !error_expected && @request_error
71
+ puts '!' * 80
72
+ puts @request_error.class.inspect
73
+ puts @request_error.class.superclass.inspect
74
+ puts '!' * 80
75
+ end
76
+ @request_error.should be_nil unless error_expected
77
+ @request_error.should_not be_nil if error_expected
78
+ @request_error.class.name.should =~ Regexp.new(error_class_expected) if error_class_expected
79
+ end
80
+
81
+ Then /^the request should (\w+ ?\w*) in less than (\d+) seconds?$/ do |behavior, time|
82
+ step "the request should #{behavior}"
83
+ #allow 10% margin of error due to Ruby/OS scheduler variance
84
+ (@request_t1.to_f - @request_t0.to_f).should <= (time.to_f * 1.10)
85
+ end
86
+
87
+ Then /^the request should be attempted once$/ do
88
+ @request_attempts.should == 1
89
+ end
90
+
91
+ Then /^the request should be attempted ([0-9]+) times$/ do |n|
92
+ @request_attempts.should == n.to_i
93
+ end
@@ -0,0 +1,206 @@
1
+ require 'nokogiri'
2
+
3
+ Given /^a Ruby application$/ do
4
+ ruby_app_root.should_not be_nil
5
+ end
6
+
7
+ Given /^a Gemfile$/ do
8
+ gemfile = ruby_app_path('Gemfile')
9
+ unless File.exist?(gemfile)
10
+ basedir = File.expand_path('../../..', __FILE__)
11
+ File.open(gemfile, 'w') do |file|
12
+ file.puts "gem 'right_develop', :path=>'#{basedir}'"
13
+ end
14
+ end
15
+ end
16
+
17
+ Given /^a gem dependency on '(.*)'$/ do |dependency|
18
+ step 'a Gemfile'
19
+ gem, version = dependency.split(/\s+/, 2)
20
+ gemfile = ruby_app_path('Gemfile')
21
+ File.open(gemfile, 'a') do |file|
22
+ file.puts "gem '#{gem}', '#{version}'"
23
+ end
24
+ end
25
+
26
+ Given /^a Rakefile$/ do
27
+ rakefile = ruby_app_path('Rakefile')
28
+ unless File.exist?(rakefile)
29
+ File.open(rakefile, 'w') do |file|
30
+ file.puts "# Auto-generated by #{__FILE__}"
31
+ end
32
+ end
33
+ end
34
+
35
+ Given /^the Rakefile contains a RightDevelop::CI::RakeTask$/ do
36
+ step 'a Rakefile'
37
+ rakefile = ruby_app_path('Rakefile')
38
+ File.open(rakefile, 'w') do |file|
39
+ file.puts "require 'right_develop'"
40
+ file.puts "RightDevelop::CI::RakeTask.new"
41
+ end
42
+ end
43
+
44
+ Given /^the Rakefile contains a RightDevelop::CI::RakeTask with parameter '(.*)'$/ do |ns|
45
+ step 'a Rakefile'
46
+ rakefile = ruby_app_path('Rakefile')
47
+ File.open(rakefile, 'w') do |file|
48
+ file.puts "require 'right_develop'"
49
+ file.puts "RightDevelop::CI::RakeTask.new(#{ns})"
50
+ end
51
+ end
52
+
53
+ Given /^the Rakefile contains:$/ do |content|
54
+ step 'a Rakefile'
55
+ rakefile = ruby_app_path('Rakefile')
56
+ File.open(rakefile, 'w') do |file|
57
+ file.puts "require 'right_develop'"
58
+ content.split("\n").each do |line|
59
+ file.puts line
60
+ end
61
+ end
62
+ end
63
+
64
+ Given /^a trivial (failing )?RSpec spec$/ do |failing|
65
+ spec_dir = ruby_app_path('spec')
66
+ spec = ruby_app_path('spec', 'trivial_spec.rb')
67
+ FileUtils.mkdir_p(spec_dir)
68
+ File.open(spec, 'w') do |file|
69
+ file.puts "describe String do"
70
+ file.puts " it 'has a size' do"
71
+ file.puts " 'joe'.size.should == 3"
72
+ file.puts " end"
73
+ file.puts
74
+ file.puts " it 'is stringy' do"
75
+ file.puts " pending"
76
+ file.puts " end"
77
+ unless failing.nil?
78
+ file.puts
79
+ file.puts "it 'meets an impossible ideal' do"
80
+ file.puts " raise NotImplementedError, 'inconceivable!'"
81
+ file.puts "end"
82
+ end
83
+ file.puts "end"
84
+ end
85
+ end
86
+
87
+ Given /^an RSpec spec named '([A-Za-z0-9_.]+)' with content:$/ do |name, content|
88
+ spec_dir = ruby_app_path('spec')
89
+ spec = ruby_app_path('spec', name)
90
+ FileUtils.mkdir_p(spec_dir)
91
+ File.open(spec, 'w') do |file|
92
+ content.split("\n").each do |line|
93
+ file.puts line
94
+ end
95
+ end
96
+ end
97
+
98
+ Given /^a trivial (failing )?Cucumber feature$/ do |failing|
99
+ features_dir = ruby_app_path('features')
100
+ steps_dir = ruby_app_path('features', 'step_definitions')
101
+ feature = ruby_app_path('features', 'trivial.feature')
102
+ steps = ruby_app_path('features', 'step_definitions', 'trivial_steps.rb')
103
+ FileUtils.mkdir_p(features_dir)
104
+ FileUtils.mkdir_p(steps_dir)
105
+
106
+ unless File.exist?(steps)
107
+ File.open(steps, 'w') do |file|
108
+ file.puts "When /^the night has come and the land is dark$/ do; end"
109
+ file.puts "When /^the moon is the only light we see$/ do; end"
110
+ file.puts "Then /^I won't be afraid.*$/ do; end"
111
+ file.puts "Then /^as long as you stand.*by me$/ do; end"
112
+ file.puts "Then /^you run away as fast as you can$/ do; raise NotImplementedError; end"
113
+ end
114
+ end
115
+ unless File.exist?(feature)
116
+ File.open(feature, 'w') do |file|
117
+ file.puts "Feature: Song Lyrics from the 1950s"
118
+ file.puts
119
+ file.puts " Scenario: Stand By Me"
120
+ file.puts " When the night has come and the land is dark"
121
+ file.puts " And the moon is the only light we see"
122
+ file.puts " Then I won't be afraid, oh, I won't be afraid"
123
+ if failing.nil?
124
+ file.puts " And as long as you stand, stand by me"
125
+ else
126
+ file.puts " And you run away as fast as you can"
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ When /^I install the bundle$/ do
133
+ ruby_app_shell('bundle install')
134
+ end
135
+
136
+ When /^I rake '(.*)'$/ do |task|
137
+ @ruby_app_output = ruby_app_shell("bundle exec rake #{task} --trace ", :ignore_errors => true)
138
+ end
139
+
140
+ When /^I debug the app shell$/ do
141
+ STDOUT.puts "Opening shell in a separate window."
142
+ if RUBY_PLATFORM =~ /darwin/
143
+ ruby_app_shell("open -a Terminal .")
144
+ else
145
+ raise "Don't know how to open an app shell for #{RUBY_PLATFORM}; please contribute your knowledge to #{__FILE__}"
146
+ end
147
+ STDOUT.puts "Press Enter to continue Cucumber execution..."
148
+ STDOUT.flush
149
+ STDIN.readline
150
+ end
151
+
152
+ Then /^the output should contain '(.*)'$/ do |expected_output|
153
+ @ruby_app_output.should include(expected_output)
154
+ end
155
+
156
+ Then /^the output should not contain '(.*)'$/ do |expected_output|
157
+ @ruby_app_output.should_not include(expected_output)
158
+ end
159
+
160
+ Then /^the directory '(.*)' should contain files/ do |dir|
161
+ dir = ruby_app_path(dir)
162
+ File.directory?(dir).should be_true
163
+ Dir[File.join(dir, '*')].should_not be_empty
164
+ end
165
+
166
+ Then /^the file '(.*)' should (not )?exist?$/ do |file, negatory|
167
+ file = ruby_app_path(file)
168
+
169
+ if negatory.nil? || negatory.empty?
170
+ Pathname.new(file).should exist
171
+ else
172
+ Pathname.new(file).should_not exist
173
+ end
174
+ end
175
+
176
+ Then /^the file '(.*)' should mention ([0-9]) (passing|failing) test cases?$/ do |file, n, pass_fail|
177
+ file = ruby_app_path(file)
178
+ n = Integer(n)
179
+
180
+ Pathname.new(file).should exist
181
+
182
+ doc = Nokogiri.XML(File.open(file, 'r'))
183
+
184
+ all_testcases = doc.css('testcase').size
185
+ failing_testcases = doc.css('testcase failure').size
186
+ passing_testcases = all_testcases - failing_testcases
187
+
188
+ case pass_fail
189
+ when 'passing'
190
+ passing_testcases.should == n
191
+ when 'failing'
192
+ failing_testcases.should == n
193
+ else
194
+ raise NotImplementedError, "WTF #{pass_fail}"
195
+ end
196
+ end
197
+
198
+ Then /^the command should (succeed|fail)$/ do |success|
199
+ if success == 'succeed'
200
+ $?.exitstatus.should == 0
201
+ elsif success == 'fail'
202
+ $?.exitstatus.should_not == 0
203
+ else
204
+ raise NotImplementedError, "Unknown expectation #{success}"
205
+ end
206
+ end
@@ -0,0 +1,96 @@
1
+ Given /^a serializer named '(.+)'$/ do |klass|
2
+ @serializer = klass.to_const
3
+ end
4
+
5
+ Given /^a stateful Ruby class named '(.*)'$/ do |name|
6
+ name = name.to_sym
7
+ ivars = 1 + rand(10)
8
+
9
+
10
+ Kernel.__send__(:remove_const, name) if Kernel.const_defined?(name)
11
+
12
+ @stateful_ruby_class = Class.new(Object)
13
+ Kernel.__send__(:const_set, name, @stateful_ruby_class)
14
+
15
+ @stateful_ruby_class.instance_eval do
16
+ define_method(:initialize) do
17
+ ivars.times do
18
+ self.instance_variable_set("@instance_variable_#{random_value(Integer)}", random_value(nil, [String]))
19
+ end
20
+ end
21
+
22
+ define_method(:==) do |other|
23
+ result = (Set.new(self.instance_variables) == Set.new(other.instance_variables))
24
+
25
+ self.instance_variables.each do |ivar|
26
+ result &&= (self.instance_variable_get(ivar) == other.instance_variable_get(ivar))
27
+ end
28
+
29
+ result
30
+ end
31
+ end
32
+ end
33
+
34
+ When /^I serialize the Ruby value: (.*)$/ do |expression|
35
+ @ruby_value = eval(expression)
36
+ @serialized_value = @serializer.dump(@ruby_value)
37
+ end
38
+
39
+ When /^I serialize a complex random data structure$/ do
40
+ @ruby_value = random_value(nil, [String])
41
+ @serialized_value = @serializer.dump(@ruby_value)
42
+ end
43
+
44
+ When /^an eldritch force deletes a key from the serialized value$/ do
45
+ hash = RightDevelop::Data::Serializer::Encoder.load(@serialized_value)
46
+ hash.delete(hash.keys[rand(hash.keys.size)])
47
+ @serialized_value = @serializer.dump(hash)
48
+ end
49
+
50
+ Then /^the serialized value should be: (.*)$/ do |expression|
51
+ if (@serialized_value =~ /^\{/) || (expression =~ /^\{/)
52
+ # Hash: ordering of JSON representation is unimportant; load as pure JSON and compare values
53
+ RightDevelop::Data::Serializer::Encoder.load(@serialized_value).should ==
54
+ RightDevelop::Data::Serializer::Encoder.load(expression)
55
+ else
56
+ # Any other data: exact comparison
57
+ @serialized_value.should == expression
58
+ end
59
+ end
60
+
61
+ Then /^the serialized value should round-trip cleanly$/ do
62
+ case @ruby_value
63
+ when Float
64
+ # Floating-point numbers lose some precision due to truncation
65
+ @serializer.load(@serialized_value).should be_within(0.000001).of(@ruby_value)
66
+ when Time
67
+ # Times are stored with accuracy ~ 1 sec
68
+ @serializer.load(@serialized_value).to_i.should == @ruby_value.to_i
69
+ else
70
+ # Everything else should compare identical
71
+ @serializer.load(@serialized_value).should == @ruby_value
72
+ end
73
+ end
74
+
75
+ When /^the serialized value should fail to round\-trip$/ do
76
+ @serializer.load(@serialized_value).should_not == @ruby_value
77
+ end
78
+
79
+ Then /^the serialized value should be a JSON (.*)$/ do |json_type|
80
+ case json_type
81
+ when 'object'
82
+ @serialized_value.should =~ /^\{.*\}$/
83
+ when 'string'
84
+ @serialized_value.should =~ /^".*"$/
85
+ when 'number'
86
+ @serialized_value.should =~ /^".*"$/
87
+ when 'true', 'false', 'null'
88
+ @serialized_value.should == json_type
89
+ else
90
+ raise NotImplementedError, "Unknown JSON type"
91
+ end
92
+ end
93
+
94
+ Then /^the serialized value should have a suitable _ruby_class/ do
95
+ @serialized_value.should =~ /"_ruby_class":"#{Regexp.escape(@ruby_value.class.name)}"/
96
+ end
@@ -0,0 +1,134 @@
1
+ #-- -*- mode: ruby; encoding: utf-8 -*-
2
+ # Copyright: Copyright (c) 2011 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'webrick'
25
+ require 'webrick/httpservlet'
26
+
27
+ # Mimic a generic HTTP server with various configurable behaviors
28
+ class MockServer < WEBrick::HTTPServer
29
+ attr_accessor :thread, :port, :url
30
+
31
+ # Overridden factory method that will tolerate Errno::EADDRINUSE in case a certain
32
+ # TCP port is already used
33
+ def self.new(*args)
34
+ tries ||= 0
35
+ super
36
+ rescue Errno::EADDRINUSE => e
37
+ tries += 1
38
+ if tries > 5
39
+ raise
40
+ else
41
+ retry
42
+ end
43
+ end
44
+
45
+ def initialize(options={})
46
+ @port = options[:port] || (4096 + rand(32768-4096))
47
+ @url = "http://localhost:#{@port}"
48
+
49
+ logger = WEBrick::Log.new(STDERR, WEBrick::Log::FATAL)
50
+ super(options.merge(:Port => @port, :AccessLog => [], :Logger=>logger))
51
+
52
+ # mount servlets via callback
53
+ yield(self)
54
+
55
+ #Start listening for HTTP in a separate thread
56
+ @thread = Thread.new do
57
+ self.start()
58
+ end
59
+ end
60
+ end
61
+
62
+ # Mimic a server that exists but hangs without providing any response, not even
63
+ # ICMP signals e.g. host unreachable or network unreachable. We simulate this
64
+ # by pointing at a port of the RightScale (my) load balancer that is not allowed
65
+ # by the security group, which causes the TCP SYN packets to be dropped with no
66
+ # acknowledgement.
67
+ class BlackholedServer
68
+ attr_accessor :port, :url
69
+
70
+ def initialize(options={})
71
+ @port = options[:port] || (4096 + rand(4096))
72
+ @url = "my.rightscale.com:#{@port}"
73
+ end
74
+ end
75
+
76
+ Before do
77
+ @mock_servers = []
78
+ end
79
+
80
+ # Kill running reposes after test finishes.
81
+ After do
82
+ @mock_servers.each do |server|
83
+ if server.is_a?(MockServer)
84
+ server.thread.kill
85
+ end
86
+ end
87
+ end
88
+
89
+ Given /^(an?|\d+)? (overloaded|blackholed) servers?$/ do |number, behavior|
90
+ number = 0 if number =~ /no/
91
+ number = 1 if number =~ /an?/
92
+ number = number.to_i
93
+
94
+ number.times do
95
+ case behavior
96
+ when 'overloaded'
97
+ proc = Proc.new do
98
+ sleep(10)
99
+ 'Hi there! I am overloaded.'
100
+ end
101
+ server = MockServer.new do |s|
102
+ s.mount('/', WEBrick::HTTPServlet::ProcHandler.new(proc))
103
+ end
104
+ when 'blackholed'
105
+ server = BlackholedServer.new()
106
+ else
107
+ raise ArgumentError, "Unknown server behavior #{behavior}"
108
+ end
109
+
110
+ @mock_servers << server
111
+ end
112
+ end
113
+
114
+ Given /^(an?|\d+)? servers? that responds? with ([0-9]+)$/ do |number, status_code|
115
+ number = 0 if number =~ /no/
116
+ number = 1 if number =~ /an?/
117
+ number = number.to_i
118
+
119
+ status_code = status_code.to_i
120
+
121
+ proc = Proc.new do
122
+ klass = WEBrick::HTTPStatus::CodeToError[status_code]
123
+ klass.should_not be_nil
124
+ raise klass, "Simulated #{status_code} response"
125
+ end
126
+
127
+ number.times do
128
+ server = MockServer.new do |s|
129
+ s.mount('/', WEBrick::HTTPServlet::ProcHandler.new(proc))
130
+ end
131
+
132
+ @mock_servers << server
133
+ end
134
+ end