right_support 2.6.17 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/.rspec +4 -0
  2. data/CHANGELOG.rdoc +37 -0
  3. data/Gemfile +29 -0
  4. data/Gemfile.lock +111 -0
  5. data/README.rdoc +2 -0
  6. data/Rakefile +62 -0
  7. data/VERSION +1 -0
  8. data/features/balancer_error_handling.feature +34 -0
  9. data/features/balancer_health_check.feature +33 -0
  10. data/features/continuous_integration.feature +51 -0
  11. data/features/continuous_integration_cucumber.feature +28 -0
  12. data/features/continuous_integration_rspec1.feature +28 -0
  13. data/features/continuous_integration_rspec2.feature +28 -0
  14. data/features/http_client_timeout.feature +19 -0
  15. data/features/serialization.feature +95 -0
  16. data/features/step_definitions/http_client_steps.rb +27 -0
  17. data/features/step_definitions/request_balancer_steps.rb +93 -0
  18. data/features/step_definitions/ruby_steps.rb +176 -0
  19. data/features/step_definitions/serialization_steps.rb +96 -0
  20. data/features/step_definitions/server_steps.rb +134 -0
  21. data/features/support/env.rb +138 -0
  22. data/features/support/file_utils_bundler_mixin.rb +45 -0
  23. data/lib/right_support/ci/java_cucumber_formatter.rb +22 -8
  24. data/lib/right_support/ci/java_spec_formatter.rb +26 -8
  25. data/lib/right_support/ci/rake_task.rb +3 -0
  26. data/lib/right_support/ci.rb +24 -0
  27. data/lib/right_support/crypto/signed_hash.rb +22 -0
  28. data/lib/right_support/data/serializer.rb +24 -2
  29. data/lib/right_support/net/address_helper.rb +20 -8
  30. data/lib/right_support/net/dns.rb +20 -8
  31. data/lib/right_support/net/http_client.rb +22 -0
  32. data/lib/right_support/net/request_balancer.rb +27 -21
  33. data/lib/right_support/net/s3_helper.rb +20 -8
  34. data/lib/right_support/net/ssl/open_ssl_patch.rb +22 -0
  35. data/lib/right_support/net/ssl.rb +20 -8
  36. data/lib/right_support/ruby/easy_singleton.rb +22 -0
  37. data/lib/right_support/ruby/object_extensions.rb +22 -0
  38. data/lib/right_support/ruby/string_extensions.rb +1 -1
  39. data/lib/right_support.rb +13 -10
  40. data/right_support.gemspec +180 -18
  41. data/right_support.rconf +8 -0
  42. data/spec/config/feature_set_spec.rb +83 -0
  43. data/spec/crypto/signed_hash_spec.rb +60 -0
  44. data/spec/data/hash_tools_spec.rb +471 -0
  45. data/spec/data/uuid_spec.rb +45 -0
  46. data/spec/db/cassandra_model_part1_spec.rb +84 -0
  47. data/spec/db/cassandra_model_part2_spec.rb +73 -0
  48. data/spec/db/cassandra_model_spec.rb +359 -0
  49. data/spec/fixtures/encrypted_priv_rsa.pem +30 -0
  50. data/spec/fixtures/good_priv_dsa.pem +12 -0
  51. data/spec/fixtures/good_priv_rsa.pem +15 -0
  52. data/spec/fixtures/good_pub_dsa.ssh +1 -0
  53. data/spec/fixtures/good_pub_rsa.pem +5 -0
  54. data/spec/fixtures/good_pub_rsa.ssh +1 -0
  55. data/spec/log/exception_logger_spec.rb +76 -0
  56. data/spec/log/filter_logger_spec.rb +8 -0
  57. data/spec/log/mixin_spec.rb +62 -0
  58. data/spec/log/multiplexer_spec.rb +54 -0
  59. data/spec/log/null_logger_spec.rb +36 -0
  60. data/spec/log/system_logger_spec.rb +92 -0
  61. data/spec/net/address_helper_spec.rb +57 -0
  62. data/spec/net/balancing/health_check_spec.rb +382 -0
  63. data/spec/net/balancing/round_robin_spec.rb +15 -0
  64. data/spec/net/balancing/sticky_policy_spec.rb +92 -0
  65. data/spec/net/dns_spec.rb +152 -0
  66. data/spec/net/http_client_spec.rb +171 -0
  67. data/spec/net/request_balancer_spec.rb +579 -0
  68. data/spec/net/s3_helper_spec.rb +160 -0
  69. data/spec/net/ssl_spec.rb +42 -0
  70. data/spec/net/string_encoder_spec.rb +58 -0
  71. data/spec/rack/log_setter_spec.rb +5 -0
  72. data/spec/rack/request_logger_spec.rb +68 -0
  73. data/spec/rack/request_tracker_spec.rb +5 -0
  74. data/spec/ruby/easy_singleton_spec.rb +72 -0
  75. data/spec/ruby/object_extensions_spec.rb +27 -0
  76. data/spec/ruby/string_extensions_spec.rb +98 -0
  77. data/spec/spec_helper.rb +181 -0
  78. data/spec/stats/activity_spec.rb +193 -0
  79. data/spec/stats/exceptions_spec.rb +123 -0
  80. data/spec/stats/helpers_spec.rb +603 -0
  81. data/spec/validation/openssl_spec.rb +37 -0
  82. data/spec/validation/ssh_spec.rb +39 -0
  83. metadata +218 -19
@@ -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
+ RightSupport::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("RightSupport::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 = RightSupport::Net::HTTPClient::DEFAULT_OPTIONS[:timeout]
28
+ o = RightSupport::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 = RightSupport::Net::RequestBalancer.new(urls)
42
+ @request_attempts = 0
43
+ @request_t0 = Time.now
44
+ @http_client = RightSupport::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,176 @@
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_support', :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 RightSupport::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_support/ci/rake_task'"
40
+ file.puts "RightSupport::CI::RakeTask.new"
41
+ end
42
+ end
43
+
44
+ Given /^the Rakefile contains a RightSupport::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_support/ci/rake_task'"
49
+ file.puts "RightSupport::CI::RakeTask.new(#{ns})"
50
+ end
51
+ end
52
+
53
+ Given /^a trivial (failing )?RSpec spec$/ do |failing|
54
+ spec_dir = ruby_app_path('spec')
55
+ spec = ruby_app_path('spec', 'trivial_spec.rb')
56
+ FileUtils.mkdir_p(spec_dir)
57
+ unless File.exist?(spec)
58
+ File.open(spec, 'w') do |file|
59
+ file.puts "describe String do"
60
+ file.puts " it 'has a size' do"
61
+ file.puts " 'joe'.size.should == 3"
62
+ file.puts " end"
63
+ file.puts
64
+ file.puts " it 'is stringy' do"
65
+ file.puts " pending"
66
+ file.puts " end"
67
+ unless failing.nil?
68
+ file.puts
69
+ file.puts "it 'meets an impossible ideal' do"
70
+ file.puts " raise NotImplementedError, 'inconceivable!'"
71
+ file.puts "end"
72
+ end
73
+ file.puts "end"
74
+ end
75
+ end
76
+ end
77
+
78
+ Given /^a trivial (failing )?Cucumber feature$/ do |failing|
79
+ features_dir = ruby_app_path('features')
80
+ steps_dir = ruby_app_path('features', 'step_definitions')
81
+ feature = ruby_app_path('features', 'trivial.feature')
82
+ steps = ruby_app_path('features', 'step_definitions', 'trivial_steps.rb')
83
+ FileUtils.mkdir_p(features_dir)
84
+ FileUtils.mkdir_p(steps_dir)
85
+
86
+ unless File.exist?(steps)
87
+ File.open(steps, 'w') do |file|
88
+ file.puts "When /^the night has come and the land is dark$/ do; end"
89
+ file.puts "When /^the moon is the only light we see$/ do; end"
90
+ file.puts "Then /^I won't be afraid.*$/ do; end"
91
+ file.puts "Then /^as long as you stand.*by me$/ do; end"
92
+ file.puts "Then /^you run away as fast as you can$/ do; raise NotImplementedError; end"
93
+ end
94
+ end
95
+ unless File.exist?(feature)
96
+ File.open(feature, 'w') do |file|
97
+ file.puts "Feature: Song Lyrics from the 1950s"
98
+ file.puts
99
+ file.puts " Scenario: Stand By Me"
100
+ file.puts " When the night has come and the land is dark"
101
+ file.puts " And the moon is the only light we see"
102
+ file.puts " Then I won't be afraid, oh, I won't be afraid"
103
+ if failing.nil?
104
+ file.puts " And as long as you stand, stand by me"
105
+ else
106
+ file.puts " And you run away as fast as you can"
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ When /^I install the bundle$/ do
113
+ ruby_app_shell('bundle install')
114
+ end
115
+
116
+ When /^I rake '(.*)'$/ do |task|
117
+ @ruby_app_output = ruby_app_shell("bundle exec rake #{task} --trace ", :ignore_errors => true)
118
+ end
119
+
120
+ When /^I debug the app shell$/ do
121
+ STDOUT.puts "Opening shell in a separate window."
122
+ if RUBY_PLATFORM =~ /darwin/
123
+ ruby_app_shell("open -a Terminal .")
124
+ else
125
+ raise "Don't know how to open an app shell for #{RUBY_PLATFORM}; please contribute your knowledge to #{__FILE__}"
126
+ end
127
+ STDOUT.puts "Press Enter to continue Cucumber execution..."
128
+ STDOUT.flush
129
+ STDIN.readline
130
+ end
131
+
132
+ Then /^the output should contain '(.*)'$/ do |expected_output|
133
+ @ruby_app_output.should include(expected_output)
134
+ end
135
+
136
+ Then /^the output should not contain '(.*)'$/ do |expected_output|
137
+ @ruby_app_output.should_not include(expected_output)
138
+ end
139
+
140
+ Then /^the directory '(.*)' should contain files/ do |dir|
141
+ dir = ruby_app_path(dir)
142
+ File.directory?(dir).should be_true
143
+ Dir[File.join(dir, '*')].should_not be_empty
144
+ end
145
+
146
+ Then /^the file '(.*)' should mention ([0-9]) (passing|failing) test cases?$/ do |file, n, pass_fail|
147
+ file = ruby_app_path(file)
148
+ n = Integer(n)
149
+
150
+ Pathname.new(file).should exist
151
+
152
+ doc = Nokogiri.XML(File.open(file, 'r'))
153
+
154
+ all_testcases = doc.css('testcase').size
155
+ failing_testcases = doc.css('testcase failure').size
156
+ passing_testcases = all_testcases - failing_testcases
157
+
158
+ case pass_fail
159
+ when 'passing'
160
+ passing_testcases.should == n
161
+ when 'failing'
162
+ failing_testcases.should == n
163
+ else
164
+ raise NotImplementedError, "WTF #{pass_fail}"
165
+ end
166
+ end
167
+
168
+ Then /^the command should (succeed|fail)$/ do |success|
169
+ if success == 'succeed'
170
+ $?.exitstatus.should == 0
171
+ elsif success == 'fail'
172
+ $?.exitstatus.should_not == 0
173
+ else
174
+ raise NotImplementedError, "Unknown expectation #{success}"
175
+ end
176
+ 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 = RightSupport::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
+ RightSupport::Data::Serializer::Encoder.load(@serialized_value).should ==
54
+ RightSupport::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
@@ -0,0 +1,138 @@
1
+ #--
2
+ # Copyright: Copyright (c) 2010- 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 'tmpdir'
25
+
26
+ require 'rubygems'
27
+ require 'bundler/setup'
28
+
29
+ $:.unshift(File.expand_path('../../../lib', __FILE__)) #ensure we load THIS project's code, not an installed gem
30
+ require 'right_support'
31
+ require 'right_support/ci'
32
+
33
+ module RandomValueHelper
34
+ RANDOM_KEY_CLASSES = [String, Integer, Float, TrueClass, FalseClass]
35
+ RANDOM_VALUE_CLASSES = RANDOM_KEY_CLASSES + [Array, Hash]
36
+
37
+ def random_value(klass=nil, key_classes=RANDOM_KEY_CLASSES, depth=0)
38
+ if klass.nil?
39
+ if depth < 3 && key_classes.nil?
40
+ klasses = RANDOM_VALUE_CLASSES
41
+ else
42
+ klasses = key_classes
43
+ end
44
+
45
+ klass = klasses[rand(klasses.size)]
46
+ end
47
+
48
+ if klass == String
49
+ result = ''
50
+ io = StringIO.new(result, 'w')
51
+ rand(40).times { io.write(0x61 + rand(26)) }
52
+ io.close
53
+ elsif klass == Integer
54
+ result = rand(0xffffff)
55
+ elsif klass == Float
56
+ result = rand(0xffffff) * rand
57
+ elsif klass == TrueClass
58
+ result = true
59
+ elsif klass == FalseClass
60
+ result = false
61
+ elsif klass == Array
62
+ result = []
63
+ rand(10).times { result << random_value(nil, key_classes,depth+1) }
64
+ elsif klass == Hash
65
+ result = {}
66
+ if string_keys
67
+ key_type = String
68
+ else
69
+ key_type = RANDOM_KEY_CLASSES[rand(RANDOM_KEY_CLASSES.size)]
70
+ end
71
+ rand(10).times { result[random_value(key_type, key_classes, depth+1)] = random_value(nil, key_classes, depth+1) }
72
+ else
73
+ raise ArgumentError, "Unknown random value type #{klass}"
74
+ end
75
+
76
+ result
77
+ end
78
+ end
79
+
80
+ module RubyAppHelper
81
+ def ruby_app_root
82
+ @ruby_app_root ||= Dir.mktmpdir('right_support_cucumber_ruby')
83
+ end
84
+
85
+ def ruby_app_path(*args)
86
+ path = ruby_app_root
87
+ until args.empty?
88
+ item = args.shift
89
+ path = File.join(path, item)
90
+ end
91
+ path
92
+ end
93
+
94
+ # Run a shell command in app_dir, e.g. a rake task
95
+ def ruby_app_shell(cmd, options={})
96
+ ignore_errors = options[:ignore_errors] || false
97
+ log = !!(Cucumber.logger)
98
+
99
+ all_output = ''
100
+ Dir.chdir(ruby_app_root) do
101
+ Cucumber.logger.debug("bash> #{cmd}\n") if log
102
+ Bundler.with_clean_env do
103
+ IO.popen("#{cmd} 2>&1", 'r') do |output|
104
+ output.sync = true
105
+ done = false
106
+ until done
107
+ begin
108
+ line = output.readline + "\n"
109
+ all_output << line
110
+ Cucumber.logger.debug(line) if log
111
+ rescue EOFError
112
+ done = true
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ $?.success?.should(be_true) unless ignore_errors
120
+ all_output
121
+ end
122
+ end
123
+
124
+ module RightSupportWorld
125
+ include RandomValueHelper
126
+ include RubyAppHelper
127
+ end
128
+
129
+ # The Cucumber world
130
+ World(RightSupportWorld)
131
+
132
+ After do
133
+ FileUtils.rm_rf(ruby_app_root) if File.directory?(ruby_app_root)
134
+ end
135
+
136
+ class Object
137
+ include RandomValueHelper
138
+ end
@@ -0,0 +1,45 @@
1
+ # Copyright (c) 2012- RightScale Inc
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ # Magic hooks that is necessary due to Bundler/RVM craziness interfering with
23
+ # our own Cucumbers. This is necessary because our Cucumber scenarios need to create a
24
+ # Ruby app that has a distinct, separate bundle from right_support's own Gemfile, e.g. we
25
+ # need to test what happens when RSpec or Cucumber isn't available. Therefore the subprocesses
26
+ # that RightSupport's Cucumber suite launches, should not inherit our bundle.
27
+ module FileUtilsBundlerMixin
28
+ def self.included(base)
29
+ if base.respond_to?(:sh) && !base.respond_to?(:sh_without_bundler_taint)
30
+ base.instance_eval {
31
+ alias_method :sh_without_bundler_taint, :sh
32
+ alias_method :sh, :sh_with_bundler_taint
33
+ }
34
+ end
35
+ end
36
+
37
+ def sh_with_bundler_taint(*params)
38
+ Bundler.with_clean_env do
39
+ sh_without_bundler_taint(*params)
40
+ end
41
+ end
42
+ end
43
+
44
+ # Install the magic hook.
45
+ Kernel.instance_eval { include(::FileUtilsBundlerMixin) }