right_support 2.6.17 → 2.7.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.
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) }