right_develop 1.1.0

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.
@@ -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