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,181 @@
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
+ require 'rubygems'
23
+ require 'bundler/setup'
24
+ require 'flexmock'
25
+ require 'ruby-debug'
26
+ require 'syntax'
27
+ require 'right_develop'
28
+
29
+ $:.unshift(File.expand_path('../../../lib', __FILE__)) #ensure we load THIS project's code, not an installed gem
30
+ require 'right_support'
31
+
32
+ RSpec.configure do |config|
33
+ config.mock_with :flexmock
34
+ end
35
+
36
+ def read_fixture(fn)
37
+ basedir = File.expand_path('../..', __FILE__)
38
+ fixtures_dir = File.join(basedir, 'spec', 'fixtures')
39
+ File.read(File.join(fixtures_dir, fn))
40
+ end
41
+
42
+ def corrupt(key, factor=4)
43
+ d = key.size / 2
44
+
45
+ key[0..(d-factor)] + key[d+factor..-1]
46
+ end
47
+
48
+ RSpec::Matchers.define :have_green_endpoint do |endpoint|
49
+ match do |balancer|
50
+ stack = balancer.instance_variable_get(:@stack)
51
+ state = stack.instance_variable_get(:@endpoints)
52
+ state = state[endpoint] if state.respond_to?(:[])
53
+ unless stack && state && state.key?(:n_level)
54
+ raise ArgumentError, "Custom matcher is incompatible with new HealthCheck implementation!"
55
+ end
56
+ state[:n_level] == 0
57
+ end
58
+ end
59
+
60
+ RSpec::Matchers.define :have_yellow_endpoint do |endpoint, n|
61
+ match do |balancer|
62
+ stack = balancer.instance_variable_get(:@stack)
63
+ max_n = stack.instance_variable_get(:@yellow_states)
64
+ state = stack.instance_variable_get(:@endpoints)
65
+ state = state[endpoint] if state.respond_to?(:[])
66
+ unless max_n && stack && state && state.key?(:n_level)
67
+ raise ArgumentError, "Custom matcher is incompatible with new HealthCheck implementation!"
68
+ end
69
+
70
+ if n
71
+ state[:n_level].should == n
72
+ else
73
+ (1...max_n).should include(state[:n_level])
74
+ end
75
+ end
76
+ end
77
+
78
+ RSpec::Matchers.define :have_red_endpoint do |endpoint|
79
+ match do |balancer|
80
+ stack = balancer.instance_variable_get(:@stack)
81
+ max_n = stack.instance_variable_get(:@yellow_states)
82
+ state = stack.instance_variable_get(:@endpoints)
83
+ state = state[endpoint] if state.respond_to?(:[])
84
+ unless max_n && stack && state && state.key?(:n_level)
85
+ raise ArgumentError, "Custom matcher is incompatible with new HealthCheck implementation!"
86
+ end
87
+ min = 1
88
+ state[:n_level].should == max_n
89
+ end
90
+ end
91
+
92
+ def find_empirical_distribution(trials=2500, list=[1,2,3,4,5])
93
+ seen = {}
94
+
95
+ trials.times do
96
+ value = yield
97
+ seen[value] ||= 0
98
+ seen[value] += 1
99
+ end
100
+
101
+ seen
102
+ end
103
+
104
+ def test_random_distribution(trials=25000, list=[1,2,3,4,5], &block)
105
+ seen = find_empirical_distribution(trials, &block)
106
+ should_be_chosen_fairly(seen,trials,list.size)
107
+ end
108
+
109
+ def should_be_chosen_fairly(seen, trials, size)
110
+ #Load should be evenly distributed
111
+ chance = 1.0 / size
112
+ seen.each_pair do |_, count|
113
+ (Float(count) / Float(trials)).should be_within(0.025).of(chance) #allow 5% margin of error
114
+ end
115
+ end
116
+
117
+ RANDOM_KEY_CLASSES = [String, Integer, Float, TrueClass, FalseClass]
118
+ RANDOM_VALUE_CLASSES = RANDOM_KEY_CLASSES + [Array, Hash]
119
+
120
+ def random_value(klass=nil, depth=0)
121
+ if klass.nil?
122
+ if depth < 5
123
+ klasses = RANDOM_VALUE_CLASSES
124
+ else
125
+ klasses = RANDOM_KEY_CLASSES
126
+ end
127
+
128
+ klass = klasses[rand(klasses.size)]
129
+ end
130
+
131
+ if klass == String
132
+ result = ''
133
+ io = StringIO.new(result, 'w')
134
+ rand(40).times { io.write(0x61 + rand(26)) }
135
+ io.close
136
+ elsif klass == Integer
137
+ result = rand(0xffffff)
138
+ elsif klass == Float
139
+ result = rand(0xffffff) * rand
140
+ elsif klass == TrueClass
141
+ result = true
142
+ elsif klass == FalseClass
143
+ result = false
144
+ elsif klass == Array
145
+ result = []
146
+ rand(10).times { result << random_value(nil, depth+1) }
147
+ elsif klass == Hash
148
+ result = {}
149
+ key_type = RANDOM_KEY_CLASSES[rand(RANDOM_KEY_CLASSES.size)]
150
+ rand(10).times { result[random_value(key_type, depth+1)] = random_value(nil, depth+1) }
151
+ else
152
+ raise ArgumentError, "Unknown random value type #{klass}"
153
+ end
154
+
155
+ result
156
+ end
157
+
158
+ def mock_logger
159
+ logger = flexmock(Logger.new(StringIO.new))
160
+ end
161
+
162
+ module SpecHelper
163
+ module SocketMocking
164
+ def mock_getaddrinfo(hostname, result, times=1)
165
+ boring_args = [nil, Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP]
166
+
167
+ if result.is_a?(Class) && result.ancestors.include?(Exception)
168
+ e = result.new("Mock exception raised by getaddrinfo")
169
+ flexmock(Socket).should_receive(:getaddrinfo).with(hostname, *boring_args).times(times).ordered.and_raise(e)
170
+ else
171
+ infos = []
172
+
173
+ result.each do |addr|
174
+ infos << [0,0,0,addr]
175
+ end
176
+
177
+ flexmock(Socket).should_receive(:getaddrinfo).with(hostname, *boring_args).times(times).ordered.and_return(infos)
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,193 @@
1
+ #
2
+ # Copyright (c) 2009-2012 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
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
24
+
25
+ describe RightSupport::Stats::Activity do
26
+
27
+ include FlexMock::ArgumentTypes
28
+
29
+ before(:all) do
30
+ @original_recent_size = RightSupport::Stats::Activity::RECENT_SIZE
31
+ RightSupport::Stats::Activity.instance_eval { remove_const(:RECENT_SIZE) }
32
+ RightSupport::Stats::Activity.const_set(:RECENT_SIZE, 10)
33
+ end
34
+
35
+ after(:all) do
36
+ RightSupport::Stats::Activity.instance_eval { remove_const(:RECENT_SIZE) }
37
+ RightSupport::Stats::Activity.const_set(:RECENT_SIZE, @original_recent_size)
38
+ end
39
+
40
+ before(:each) do
41
+ @now = 1000000
42
+ flexmock(Time).should_receive(:now).and_return(@now).by_default
43
+ @stats = RightSupport::Stats::Activity.new
44
+ end
45
+
46
+ it "initializes stats data" do
47
+ @stats.instance_variable_get(:@interval).should == 0.0
48
+ @stats.instance_variable_get(:@last_start_time).should == @now
49
+ @stats.instance_variable_get(:@avg_duration).should be_nil
50
+ @stats.instance_variable_get(:@total).should == 0
51
+ @stats.instance_variable_get(:@count_per_type).should == {}
52
+ end
53
+
54
+ it "updates count and interval information" do
55
+ flexmock(Time).should_receive(:now).and_return(1000010)
56
+ @stats.update
57
+ @stats.instance_variable_get(:@interval).should == 1.0
58
+ @stats.instance_variable_get(:@last_start_time).should == @now + 10
59
+ @stats.instance_variable_get(:@avg_duration).should be_nil
60
+ @stats.instance_variable_get(:@total).should == 1
61
+ @stats.instance_variable_get(:@count_per_type).should == {}
62
+ end
63
+
64
+ it "updates weight the average interval toward recent activity" do
65
+ end
66
+
67
+ it "updates counts per type when type provided" do
68
+ flexmock(Time).should_receive(:now).and_return(1000010)
69
+ @stats.update("test")
70
+ @stats.instance_variable_get(:@interval).should == 1.0
71
+ @stats.instance_variable_get(:@last_start_time).should == @now + 10
72
+ @stats.instance_variable_get(:@avg_duration).should be_nil
73
+ @stats.instance_variable_get(:@total).should == 1
74
+ @stats.instance_variable_get(:@count_per_type).should == {"test" => 1}
75
+ end
76
+
77
+ it "doesn't update counts when type contains 'stats'" do
78
+ flexmock(Time).should_receive(:now).and_return(1000010)
79
+ @stats.update("my stats")
80
+ @stats.instance_variable_get(:@interval).should == 0.0
81
+ @stats.instance_variable_get(:@last_start_time).should == @now
82
+ @stats.instance_variable_get(:@avg_duration).should be_nil
83
+ @stats.instance_variable_get(:@total).should == 0
84
+ @stats.instance_variable_get(:@count_per_type).should == {}
85
+ end
86
+
87
+ it "limits length of type string when submitting update" do
88
+ flexmock(Time).should_receive(:now).and_return(1000010)
89
+ @stats.update("test 12345678901234567890123456789012345678901234567890123456789")
90
+ @stats.instance_variable_get(:@total).should == 1
91
+ @stats.instance_variable_get(:@count_per_type).should ==
92
+ {"test 1234567890123456789012345678901234567890123456789012..." => 1}
93
+ end
94
+
95
+ it "doesn't convert symbol or boolean to string when submitting update" do
96
+ flexmock(Time).should_receive(:now).and_return(1000010)
97
+ @stats.update(:test)
98
+ @stats.update(true)
99
+ @stats.update(false)
100
+ @stats.instance_variable_get(:@total).should == 3
101
+ @stats.instance_variable_get(:@count_per_type).should == {:test => 1, true => 1, false => 1}
102
+ end
103
+
104
+ it "converts arbitrary type value to limited-length string when submitting update" do
105
+ flexmock(Time).should_receive(:now).and_return(1000010)
106
+ @stats.update({1 => 11, 2 => 22})
107
+ @stats.update({1 => 11, 2 => 22, 3 => 12345678901234567890123456789012345678901234567890123456789})
108
+ @stats.instance_variable_get(:@total).should == 2
109
+ @stats.instance_variable_get(:@count_per_type).should == {"{1=>11, 2=>22}" => 1,
110
+ "{1=>11, 2=>22, 3=>123456789012345678901234567890123456789..." => 1}
111
+ end
112
+
113
+ it "doesn't measure rate if disabled" do
114
+ @stats = RightSupport::Stats::Activity.new(false)
115
+ flexmock(Time).should_receive(:now).and_return(1000010)
116
+ @stats.update
117
+ @stats.instance_variable_get(:@interval).should == 0.0
118
+ @stats.instance_variable_get(:@last_start_time).should == @now + 10
119
+ @stats.instance_variable_get(:@avg_duration).should be_nil
120
+ @stats.instance_variable_get(:@total).should == 1
121
+ @stats.instance_variable_get(:@count_per_type).should == {}
122
+ @stats.all.should == {"last" => {"elapsed"=>0}, "total" => 1}
123
+ end
124
+
125
+ it "updates duration when finish using internal start time by default" do
126
+ flexmock(Time).should_receive(:now).and_return(1000010)
127
+ @stats.finish
128
+ @stats.instance_variable_get(:@interval).should == 0.0
129
+ @stats.instance_variable_get(:@last_start_time).should == @now
130
+ @stats.instance_variable_get(:@avg_duration).should == 1.0
131
+ @stats.instance_variable_get(:@total).should == 0
132
+ @stats.instance_variable_get(:@count_per_type).should == {}
133
+ end
134
+
135
+ it "updates duration when finish using specified start time" do
136
+ flexmock(Time).should_receive(:now).and_return(1000030)
137
+ @stats.avg_duration.should be_nil
138
+ @stats.finish(1000010)
139
+ @stats.instance_variable_get(:@interval).should == 0.0
140
+ @stats.instance_variable_get(:@last_start_time).should == @now
141
+ @stats.instance_variable_get(:@avg_duration).should == 2.0
142
+ @stats.instance_variable_get(:@total).should == 0
143
+ @stats.instance_variable_get(:@count_per_type).should == {}
144
+ end
145
+
146
+ it "converts interval to rate" do
147
+ flexmock(Time).should_receive(:now).and_return(1000020)
148
+ @stats.avg_rate.should be_nil
149
+ @stats.update
150
+ @stats.instance_variable_get(:@interval).should == 2.0
151
+ @stats.avg_rate.should == 0.5
152
+ end
153
+
154
+ it "reports number of seconds since last update or nil if no updates" do
155
+ flexmock(Time).should_receive(:now).and_return(1000010)
156
+ @stats.last.should be_nil
157
+ @stats.update
158
+ @stats.last.should == {"elapsed" => 0}
159
+ end
160
+
161
+ it "reports number of seconds since last update and last type" do
162
+ @stats.update("test")
163
+ flexmock(Time).should_receive(:now).and_return(1000010)
164
+ @stats.last.should == {"elapsed" => 10, "type" => "test"}
165
+ end
166
+
167
+ it "reports whether last activity is still active" do
168
+ @stats.update("test", "token")
169
+ flexmock(Time).should_receive(:now).and_return(1000010)
170
+ @stats.last.should == {"elapsed" => 10, "type" => "test", "active" => true}
171
+ @stats.finish(@now - 10, "token")
172
+ @stats.last.should == {"elapsed" => 10, "type" => "test", "active" => false}
173
+ @stats.instance_variable_get(:@avg_duration).should == 2.0
174
+ end
175
+
176
+ it "converts count per type to percentages" do
177
+ flexmock(Time).should_receive(:now).and_return(1000010)
178
+ @stats.update("foo")
179
+ @stats.instance_variable_get(:@total).should == 1
180
+ @stats.instance_variable_get(:@count_per_type).should == {"foo" => 1}
181
+ @stats.percentage.should == {"total" => 1, "percent" => {"foo" => 100.0}}
182
+ @stats.update("bar")
183
+ @stats.instance_variable_get(:@total).should == 2
184
+ @stats.instance_variable_get(:@count_per_type).should == {"foo" => 1, "bar" => 1}
185
+ @stats.percentage.should == {"total" => 2, "percent" => {"foo" => 50.0, "bar" => 50.0}}
186
+ @stats.update("foo")
187
+ @stats.update("foo")
188
+ @stats.instance_variable_get(:@total).should == 4
189
+ @stats.instance_variable_get(:@count_per_type).should == {"foo" => 3, "bar" => 1}
190
+ @stats.percentage.should == {"total" => 4, "percent" => {"foo" => 75.0, "bar" => 25.0}}
191
+ end
192
+
193
+ end # RightSupport::Stats::Activity
@@ -0,0 +1,123 @@
1
+ #
2
+ # Copyright (c) 2009-2012 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
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
24
+
25
+ describe RightSupport::Stats::Exceptions do
26
+
27
+ include FlexMock::ArgumentTypes
28
+
29
+ before(:each) do
30
+ @now = 1000000
31
+ flexmock(Time).should_receive(:now).and_return(@now).by_default
32
+ @stats = RightSupport::Stats::Exceptions.new
33
+ @exception = Exception.new("Test error")
34
+ end
35
+
36
+ it "initializes stats data" do
37
+ @stats.stats.should be_nil
38
+ @stats.instance_variable_get(:@callback).should be_nil
39
+ end
40
+
41
+ it "tracks submitted exception information by category" do
42
+ @stats.track("testing", @exception)
43
+ @stats.stats.should == {"testing" => {"total" => 1,
44
+ "recent" => [{"count" => 1, "type" => "Exception", "message" => "Test error",
45
+ "when" => @now, "where" => nil}]}}
46
+ end
47
+
48
+ it "recognizes and counts repeated exceptions" do
49
+ @stats.track("testing", @exception)
50
+ @stats.stats.should == {"testing" => {"total" => 1,
51
+ "recent" => [{"count" => 1, "type" => "Exception", "message" => "Test error",
52
+ "when" => @now, "where" => nil}]}}
53
+ flexmock(Time).should_receive(:now).and_return(1000010)
54
+ category = "another"
55
+ backtrace = ["here", "and", "there"]
56
+ 4.times do |i|
57
+ begin
58
+ raise ArgumentError, "badarg"
59
+ rescue Exception => e
60
+ flexmock(e).should_receive(:backtrace).and_return(backtrace)
61
+ @stats.track(category, e)
62
+ backtrace.shift(2) if i == 1
63
+ category = "testing" if i == 2
64
+ end
65
+ end
66
+ @stats.stats.should == {"testing" => {"total" => 2,
67
+ "recent" => [{"count" => 1, "type" => "Exception", "message" => "Test error",
68
+ "when" => @now, "where" => nil},
69
+ {"count" => 1, "type" => "ArgumentError", "message" => "badarg",
70
+ "when" => @now + 10, "where" => "there"}]},
71
+ "another" => {"total" => 3,
72
+ "recent" => [{"count" => 2, "type" => "ArgumentError", "message" => "badarg",
73
+ "when" => @now + 10, "where" => "here"},
74
+ {"count" => 1, "type" => "ArgumentError", "message" => "badarg",
75
+ "when" => @now + 10, "where" => "there"}]}}
76
+ end
77
+
78
+ it "limits the number of exceptions stored by eliminating older exceptions" do
79
+ (RightSupport::Stats::Exceptions::MAX_RECENT_EXCEPTIONS + 1).times do |i|
80
+ begin
81
+ raise ArgumentError, "badarg"
82
+ rescue Exception => e
83
+ flexmock(e).should_receive(:backtrace).and_return([i.to_s])
84
+ @stats.track("testing", e)
85
+ end
86
+ end
87
+ stats = @stats.stats
88
+ stats["testing"]["total"].should == RightSupport::Stats::Exceptions::MAX_RECENT_EXCEPTIONS + 1
89
+ stats["testing"]["recent"].size.should == RightSupport::Stats::Exceptions::MAX_RECENT_EXCEPTIONS
90
+ stats["testing"]["recent"][0]["where"].should == "1"
91
+ end
92
+
93
+ it "makes callback if callback and message defined" do
94
+ called = 0
95
+ callback = lambda do |exception, message, server|
96
+ called += 1
97
+ exception.should == @exception
98
+ message.should == "message"
99
+ server.should == "server"
100
+ end
101
+ @stats = RightSupport::Stats::Exceptions.new("server", callback)
102
+ @stats.track("testing", @exception, "message")
103
+ @stats.stats.should == {"testing" => {"total" => 1,
104
+ "recent" => [{"count" => 1, "type" => "Exception", "message" => "Test error",
105
+ "when" => @now, "where" => nil}]}}
106
+ called.should == 1
107
+ end
108
+
109
+ it "catches any exceptions raised internally and log them" do
110
+ begin
111
+ logger = flexmock("logger")
112
+ logger.should_receive(:error).with(/Failed to track exception 'Test error' \(Exception: bad IN/).once
113
+ RightSupport::Log::Mixin.default_logger = logger
114
+ flexmock(@exception).should_receive(:backtrace).and_raise(Exception.new("bad"))
115
+ @stats = RightSupport::Stats::Exceptions.new
116
+ @stats.track("testing", @exception, "message")
117
+ @stats.stats["testing"]["total"].should == 1
118
+ ensure
119
+ RightSupport::Log::Mixin.default_logger = nil
120
+ end
121
+ end
122
+
123
+ end # RightSupport::Stats::Exceptions