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,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