statsd-instrument 2.6.0 → 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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +79 -9
  3. data/benchmark/datagram-client +0 -1
  4. data/benchmark/send-metrics-to-dev-null-log +0 -1
  5. data/lib/statsd/instrument.rb +66 -292
  6. data/lib/statsd/instrument/assertions.rb +83 -93
  7. data/lib/statsd/instrument/client.rb +2 -2
  8. data/lib/statsd/instrument/datagram.rb +12 -3
  9. data/lib/statsd/instrument/environment.rb +9 -3
  10. data/lib/statsd/instrument/expectation.rb +93 -0
  11. data/lib/statsd/instrument/helpers.rb +29 -11
  12. data/lib/statsd/instrument/legacy_client.rb +301 -0
  13. data/lib/statsd/instrument/metric.rb +8 -8
  14. data/lib/statsd/instrument/rubocop.rb +18 -0
  15. data/lib/statsd/instrument/rubocop/singleton_configuration.rb +53 -0
  16. data/lib/statsd/instrument/version.rb +1 -1
  17. data/test/assertions_on_legacy_client_test.rb +376 -0
  18. data/test/assertions_test.rb +105 -39
  19. data/test/capture_sink_test.rb +0 -2
  20. data/test/client_test.rb +0 -2
  21. data/test/compatibility/dogstatsd_datagram_compatibility_test.rb +0 -1
  22. data/test/datagram_builder_test.rb +1 -3
  23. data/test/datagram_test.rb +14 -0
  24. data/test/dogstatsd_datagram_builder_test.rb +0 -2
  25. data/test/environment_test.rb +1 -1
  26. data/test/helpers_test.rb +17 -0
  27. data/test/log_sink_test.rb +0 -2
  28. data/test/logger_backend_test.rb +2 -2
  29. data/test/metric_test.rb +2 -2
  30. data/test/null_sink_test.rb +0 -2
  31. data/test/rubocop/singleton_configuration_test.rb +43 -0
  32. data/test/statsd_datagram_builder_test.rb +0 -2
  33. data/test/statsd_instrumentation_test.rb +4 -6
  34. data/test/statsd_test.rb +1 -1
  35. data/test/test_helper.rb +0 -2
  36. data/test/udp_backend_test.rb +1 -1
  37. data/test/udp_sink_test.rb +0 -2
  38. metadata +11 -3
  39. data/lib/statsd/instrument/metric_expectation.rb +0 -82
@@ -0,0 +1,301 @@
1
+ # frozen_string_literal: true
2
+
3
+ class StatsD::Instrument::LegacyClient
4
+ def self.singleton
5
+ @singleton ||= new
6
+ end
7
+
8
+ attr_accessor :default_sample_rate, :prefix
9
+ attr_writer :backend
10
+ attr_reader :default_tags
11
+
12
+ def default_tags=(tags)
13
+ @default_tags = StatsD::Instrument::Metric.normalize_tags(tags)
14
+ end
15
+
16
+ def backend
17
+ @backend ||= StatsD::Instrument::Environment.default_backend
18
+ end
19
+
20
+ # @!method measure(name, value = nil, sample_rate: nil, tags: nil, &block)
21
+ #
22
+ # Emits a timing metric
23
+ #
24
+ # @param [String] key The name of the metric.
25
+ # @param sample_rate (see #increment)
26
+ # @param tags (see #increment)
27
+ #
28
+ # @example Providing a value directly
29
+ # start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
30
+ # do_something
31
+ # stop = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
32
+ # http_response = StatsD.measure('HTTP.call.duration', stop - start)
33
+ #
34
+ # @example Providing a block to measure the duration of its execution
35
+ # http_response = StatsD.measure('HTTP.call.duration') do
36
+ # Net::HTTP.get(url)
37
+ # end
38
+ #
39
+ # @overload measure(key, value, sample_rate: nil, tags: nil)
40
+ # Emits a timing metric, by providing a duration in milliseconds.
41
+ #
42
+ # @param [Float] value The measured duration in milliseconds
43
+ # @return [void]
44
+ #
45
+ # @overload measure(key, sample_rate: nil, tags: nil, &block)
46
+ # Emits a timing metric, after measuring the execution duration of the
47
+ # block passed to this method.
48
+ #
49
+ # @yield `StatsD.measure` will yield the block and measure the duration. After the block
50
+ # returns, the duration in millisecond will be emitted as metric.
51
+ # @return The value that was returned by the block passed through.
52
+ def measure(
53
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
54
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
55
+ prefix: self.prefix, no_prefix: false, as_dist: false,
56
+ &block
57
+ )
58
+ # TODO: in the next version, hardcode this to :ms when the as_dist argument is dropped.
59
+ type = as_dist ? :d : :ms
60
+ prefix = nil if no_prefix
61
+ if block_given?
62
+ measure_latency(type, key, sample_rate: sample_rate, tags: tags, prefix: prefix, &block)
63
+ else
64
+ collect_metric(type, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
65
+ end
66
+ end
67
+
68
+ # @!method increment(name, value = 1, sample_rate: nil, tags: nil)
69
+ #
70
+ # Emits a counter metric.
71
+ #
72
+ # @param key [String] The name of the metric.
73
+ # @param value [Integer] The value to increment the counter by.
74
+ #
75
+ # You should not compensate for the sample rate using the counter increment. E.g., if
76
+ # your sample rate is 0.01, you should <b>not</b> use 100 as increment to compensate for it.
77
+ # The sample rate is part of the packet that is being sent to the server, and the server
78
+ # should know how to handle it.
79
+ #
80
+ # @param sample_rate [Float] (default: `StatsD.default_sample_rate`) The rate at which to sample
81
+ # this metric call. This value should be between 0 and 1. This value can be used to reduce
82
+ # the amount of network I/O (and CPU cycles) used for very frequent metrics.
83
+ #
84
+ # - A value of `0.1` means that only 1 out of 10 calls will be emitted; the other 9 will
85
+ # be short-circuited.
86
+ # - When set to `1`, every metric will be emitted.
87
+ # - If this parameter is not set, the default sample rate for this client will be used.
88
+ # @param tags [Array<String>, Hash<Symbol, String>] The tags to associate with this measurement.
89
+ # They can be provided as an array of strings, or a hash of key/value pairs.
90
+ # _Note:_ Tags are not supported by all implementations.
91
+ # @return [void]
92
+ def increment(
93
+ key, value_arg = 1, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
94
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
95
+ prefix: self.prefix, no_prefix: false
96
+ )
97
+ prefix = nil if no_prefix
98
+ collect_metric(:c, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
99
+ end
100
+
101
+ # @!method gauge(name, value, sample_rate: nil, tags: nil)
102
+ #
103
+ # Emits a gauge metric.
104
+ #
105
+ # @param key The name of the metric.
106
+ # @param value [Numeric] The current value to record.
107
+ # @param sample_rate (see #increment)
108
+ # @param tags (see #increment)
109
+ # @return [void]
110
+ def gauge(
111
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
112
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
113
+ prefix: self.prefix, no_prefix: false
114
+ )
115
+ prefix = nil if no_prefix
116
+ collect_metric(:g, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
117
+ end
118
+
119
+ # @!method set(name, value, sample_rate: nil, tags: nil)
120
+ #
121
+ # Emits a set metric, which counts the number of distinct values that have occurred.
122
+ #
123
+ # @example Couning the number of unique visitors
124
+ # StatsD.set('visitors.unique', Current.user.id)
125
+ #
126
+ # @param key [String] The name of the metric.
127
+ # @param value [Numeric] The value to record.
128
+ # @param sample_rate (see #increment)
129
+ # @param tags (see #increment)
130
+ # @return [void]
131
+ def set(
132
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
133
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
134
+ prefix: self.prefix, no_prefix: false
135
+ )
136
+ prefix = nil if no_prefix
137
+ collect_metric(:s, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
138
+ end
139
+
140
+ # @!method histogram(name, value, sample_rate: nil, tags: nil)
141
+ #
142
+ # Emits a histogram metric.
143
+ #
144
+ # @param key The name of the metric.
145
+ # @param value [Numeric] The value to record.
146
+ # @param sample_rate (see #increment)
147
+ # @param tags (see #increment)
148
+ # @return (see #collect_metric)
149
+ # @note Supported by the datadog implementation only.
150
+ def histogram(
151
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
152
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
153
+ prefix: self.prefix, no_prefix: false
154
+ )
155
+ prefix = nil if no_prefix
156
+ collect_metric(:h, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
157
+ end
158
+
159
+ # @!method distribution(name, value = nil, sample_rate: nil, tags: nil, &block)
160
+ #
161
+ # Emits a distribution metric.
162
+ #
163
+ # @param [String] key The name of the metric.
164
+ # @param sample_rate (see #increment)
165
+ # @param tags (see #increment)
166
+ #
167
+ # @note Supported by the datadog implementation only.
168
+ # @example
169
+ # http_response = StatsD.distribution('HTTP.call.duration') do
170
+ # Net::HTTP.get(url)
171
+ # end
172
+ #
173
+ # @overload distribution(name, value, sample_rate: nil, tags: nil)
174
+ #
175
+ # Emits a distribution metric, given a provided value to record.
176
+ #
177
+ # @param [Numeric] value The value to record.
178
+ # @return [void]
179
+ #
180
+ # @overload distribution(key, metric_options = {}, &block)
181
+ #
182
+ # Emits a distribution metric for the duration of the provided block, in milliseconds.
183
+ #
184
+ # @yield `StatsD.distribution` will yield the block and measure the duration. After
185
+ # the block returns, the duration in millisecond will be emitted as metric.
186
+ # @return The value that was returned by the block passed through.
187
+ def distribution(
188
+ key, value_arg = nil, deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
189
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
190
+ prefix: self.prefix, no_prefix: false,
191
+ &block
192
+ )
193
+ prefix = nil if no_prefix
194
+ if block_given?
195
+ measure_latency(:d, key, sample_rate: sample_rate, tags: tags, prefix: prefix, &block)
196
+ else
197
+ collect_metric(:d, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
198
+ end
199
+ end
200
+
201
+ # @!method key_value(name, value)
202
+ #
203
+ # Emits a key/value metric.
204
+ #
205
+ # @param key [String] The name of the metric.
206
+ # @param value [Numeric] The value to record.
207
+ # @return [void]
208
+ #
209
+ # @note Supported by the statsite implementation only.
210
+ def key_value(
211
+ key, value_arg = nil, deprecated_sample_rate_arg = nil,
212
+ value: value_arg, sample_rate: deprecated_sample_rate_arg, no_prefix: false
213
+ )
214
+ prefix = nil if no_prefix
215
+ collect_metric(:kv, key, value, sample_rate: sample_rate, prefix: prefix)
216
+ end
217
+
218
+ # @!method event(title, text, tags: nil, hostname: nil, timestamp: nil, aggregation_key: nil, priority: nil, source_type_name: nil, alert_type: nil) # rubocop:disable Metrics/LineLength
219
+ #
220
+ # Emits an event.
221
+ #
222
+ # @param title [String] Title of the event. A configured prefix may be applied to this title.
223
+ # @param text [String] Body of the event. Can contain newlines.
224
+ # @param [String] hostname The hostname to associate with the event.
225
+ # @param [Time] timestamp The moment the status of the service was checkes. Defaults to now.
226
+ # @param [String] aggregation_key A key to aggregate similar events into groups.
227
+ # @param [String] priority The event's priority, either `"low"` or `"normal"` (default).
228
+ # @param [String] source_type_name The source type.
229
+ # @param [String] alert_type The type of alert. Either `"info"` (default), `"warning"`, `"error"`, or `"success"`.
230
+ # @param tags (see #increment)
231
+ # @return [void]
232
+ #
233
+ # @note Supported by the Datadog implementation only.
234
+ def event(
235
+ title, text,
236
+ deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
237
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
238
+ prefix: self.prefix, no_prefix: false,
239
+ hostname: nil, date_happened: nil, timestamp: date_happened,
240
+ aggregation_key: nil, priority: nil, source_type_name: nil, alert_type: nil,
241
+ **_ignored
242
+ )
243
+ prefix = nil if no_prefix
244
+ collect_metric(:_e, title, text, sample_rate: sample_rate, tags: tags, prefix: prefix, metadata: {
245
+ hostname: hostname, timestamp: timestamp, aggregation_key: aggregation_key,
246
+ priority: priority, source_type_name: source_type_name, alert_type: alert_type
247
+ })
248
+ end
249
+
250
+ # @!method service_check(name, status, tags: nil, hostname: nil, timestamp: nil, message: nil)
251
+ #
252
+ # Emits a service check.
253
+ #
254
+ # @param [String] name Name of the service. A configured prefix may be applied to this title.
255
+ # @param [Symbol] status Current status of the service. Either `:ok`, `:warning`, `:critical`, or `:unknown`.
256
+ # @param [String] hostname The hostname to associate with the event.
257
+ # @param [Time] timestamp The moment the status of the service was checkes. Defaults to now.
258
+ # @param [String] message A message that describes the current status.
259
+ # @param tags (see #increment)
260
+ # @return [void]
261
+ #
262
+ # @note Supported by the Datadog implementation only.
263
+ def service_check(
264
+ name, status,
265
+ deprecated_sample_rate_arg = nil, deprecated_tags_arg = nil,
266
+ sample_rate: deprecated_sample_rate_arg, tags: deprecated_tags_arg,
267
+ prefix: self.prefix, no_prefix: false,
268
+ hostname: nil, timestamp: nil, message: nil, **_ignored
269
+ )
270
+ prefix = nil if no_prefix
271
+ collect_metric(:_sc, name, status, sample_rate: sample_rate, prefix: prefix, tags: tags, metadata: {
272
+ hostname: hostname, timestamp: timestamp, message: message
273
+ })
274
+ end
275
+
276
+ private
277
+
278
+ def measure_latency(type, key, sample_rate:, tags:, prefix:)
279
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
280
+ begin
281
+ yield
282
+ ensure
283
+ # Ensure catches both a raised exception and a return in the invoked block
284
+ value = 1000.0 * (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
285
+ collect_metric(type, key, value, sample_rate: sample_rate, tags: tags, prefix: prefix)
286
+ end
287
+ end
288
+
289
+ # Instantiates a metric, and sends it to the backend for further processing.
290
+ # @param options (see StatsD::Instrument::Metric#initialize)
291
+ # @return [void]
292
+ def collect_metric(type, name, value, sample_rate:, tags: nil, prefix:, metadata: nil)
293
+ sample_rate ||= default_sample_rate
294
+ name = "#{prefix}.#{name}" if prefix
295
+
296
+ metric = StatsD::Instrument::Metric.new(type: type, name: name, value: value,
297
+ sample_rate: sample_rate, tags: tags, metadata: metadata)
298
+ backend.collect_metric(metric)
299
+ metric # TODO: return `nil` in the next major version
300
+ end
301
+ end
@@ -43,9 +43,9 @@ class StatsD::Instrument::Metric
43
43
  using RubyBackports
44
44
  end
45
45
 
46
- def self.new(
47
- type:, name:, value: default_value(type), sample_rate: StatsD.default_sample_rate, tags: nil, metadata: nil
48
- )
46
+ def self.new(type:, name:, value: default_value(type), tags: nil, metadata: nil,
47
+ sample_rate: StatsD.legacy_singleton_client.default_sample_rate)
48
+
49
49
  # pass keyword arguments as positional arguments for performance reasons,
50
50
  # since MRI's C implementation of new turns keyword arguments into a hash
51
51
  super(type, name, value, sample_rate, tags, metadata)
@@ -92,8 +92,8 @@ class StatsD::Instrument::Metric
92
92
  @value = value
93
93
  @sample_rate = sample_rate
94
94
  @tags = StatsD::Instrument::Metric.normalize_tags(tags)
95
- if StatsD.default_tags
96
- @tags = Array(@tags) + StatsD.default_tags
95
+ if StatsD.legacy_singleton_client.default_tags
96
+ @tags = Array(@tags) + StatsD.legacy_singleton_client.default_tags
97
97
  end
98
98
  @metadata = metadata
99
99
  end
@@ -101,9 +101,9 @@ class StatsD::Instrument::Metric
101
101
  # @private
102
102
  # @return [String]
103
103
  def to_s
104
- str = +"#{TYPES[type]} #{name}:#{value}"
105
- str << " @#{sample_rate}" if sample_rate != 1.0
106
- tags&.each { |tag| str << " ##{tag}" }
104
+ str = +"#{name}:#{value}|#{type}"
105
+ str << "|@#{sample_rate}" if sample_rate && sample_rate != 1.0
106
+ str << "|#" << tags.join(',') if tags && !tags.empty?
107
107
  str
108
108
  end
109
109
 
@@ -21,6 +21,17 @@ module RuboCop
21
21
  statsd_count
22
22
  }
23
23
 
24
+ SINGLETON_CONFIGURATION_METHODS = %i{
25
+ backend
26
+ backend=
27
+ prefix
28
+ prefix=
29
+ default_tags
30
+ default_tags=
31
+ default_sample_rate
32
+ default_sample_rate=
33
+ }
34
+
24
35
  private
25
36
 
26
37
  def metaprogramming_method?(node)
@@ -33,6 +44,12 @@ module RuboCop
33
44
  METRIC_METHODS.include?(node.method_name)
34
45
  end
35
46
 
47
+ def singleton_configuration_method?(node)
48
+ node.receiver&.type == :const &&
49
+ node.receiver&.const_name == "StatsD" &&
50
+ SINGLETON_CONFIGURATION_METHODS.include?(node.method_name)
51
+ end
52
+
36
53
  def has_keyword_argument?(node, sym)
37
54
  if (kwargs = keyword_arguments(node))
38
55
  kwargs.child_nodes.detect do |pair|
@@ -62,3 +79,4 @@ require_relative 'rubocop/positional_arguments'
62
79
  require_relative 'rubocop/splat_arguments'
63
80
  require_relative 'rubocop/measure_as_dist_argument'
64
81
  require_relative 'rubocop/metric_prefix_argument'
82
+ require_relative 'rubocop/singleton_configuration'
@@ -0,0 +1,53 @@
1
+ # frozen-string-literal: true
2
+
3
+ require_relative '../rubocop' unless defined?(RuboCop::Cop::StatsD)
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module StatsD
8
+ # This Rubocop will check for calls to StatsD singleton congfiguration methods
9
+ # (e.g. `StatsD.prefix`). The library is moving away from having just a single
10
+ # singleton client, so these methods are deprecated.
11
+ #
12
+ # Use the following Rubocop invocation to check your project's codebase:
13
+ #
14
+ # rubocop --require `bundle show statsd-instrument`/lib/statsd/instrument/rubocop.rb \
15
+ # --only StatsD/SingletonConfiguration
16
+ #
17
+ # This cop will not autocorrect violations. There are several ways of fixing the violation.
18
+ #
19
+ # - The best option is to configure the library using environment variables, like
20
+ # `STATSD_ADDR`, `STATSD_IMPLEMENTATION`, `STATSD_PREFIX`, and `STATSD_DEFAULT_TAGS`.
21
+ # Metric methods called on the StatsD singleton (e.g. `StatsD.increment`) will by default
22
+ # be delegated to a client that is configured using these environment variables.
23
+ # - Alternatively, you can instantiate your own client using `StatsD::Instrument::Client.new`,
24
+ # and assign it to `StatsD.singleton_client`. The client constructor accepts many of the
25
+ # same options.
26
+ # - If you have to, you can call the old methods on `StatsD.legacy_singleton_client`. Note
27
+ # that this option will go away in the next major version.
28
+ class SingletonConfiguration < Cop
29
+ include RuboCop::Cop::StatsD
30
+
31
+ MSG = <<~MESSAGE
32
+ Singleton methods to configure StatsD are deprecated.
33
+
34
+ - The best option is to configure the library using environment variables, like
35
+ `STATSD_ADDR`, `STATSD_IMPLEMENTATION`, `STATSD_PREFIX`, and `STATSD_DEFAULT_TAGS`.
36
+ Metric methods called on the StatsD singleton (e.g. `StatsD.increment`) will by default
37
+ be delegated to a client that is configured using these environment variables.
38
+ - Alternatively, you can instantiate your own client using `StatsD::Instrument::Client.new`,
39
+ and assign it to `StatsD.singleton_client`. The client constructor accepts many of the
40
+ same options.
41
+ - If you have to, you can call the old methods on `StatsD.legacy_singleton_client`. Note
42
+ that this option will go away in the next major version.
43
+ MESSAGE
44
+
45
+ def on_send(node)
46
+ if singleton_configuration_method?(node)
47
+ add_offense(node)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module StatsD
4
4
  module Instrument
5
- VERSION = "2.6.0"
5
+ VERSION = "2.7.0"
6
6
  end
7
7
  end
@@ -0,0 +1,376 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class AssertionsOnLegacyClientTest < Minitest::Test
6
+ def setup
7
+ @old_client = StatsD.singleton_client
8
+ StatsD.singleton_client = StatsD.legacy_singleton_client
9
+
10
+ test_class = Class.new(Minitest::Test)
11
+ test_class.send(:include, StatsD::Instrument::Assertions)
12
+ @test_case = test_class.new('fake')
13
+ end
14
+
15
+ def teardown
16
+ StatsD.singleton_client = @old_client
17
+ end
18
+
19
+ def test_assert_no_statsd_calls
20
+ @test_case.assert_no_statsd_calls('counter') do
21
+ # noop
22
+ end
23
+
24
+ @test_case.assert_no_statsd_calls('counter') do
25
+ StatsD.increment('other')
26
+ end
27
+
28
+ assertion = assert_raises(Minitest::Assertion) do
29
+ @test_case.assert_no_statsd_calls('counter') do
30
+ StatsD.increment('counter')
31
+ end
32
+ end
33
+ assert_equal assertion.message, "No StatsD calls for metric counter expected."
34
+
35
+ assertion = assert_raises(Minitest::Assertion) do
36
+ @test_case.assert_no_statsd_calls do
37
+ StatsD.increment('other')
38
+ end
39
+ end
40
+ assert_equal assertion.message, "No StatsD calls for metric other expected."
41
+
42
+ assertion = assert_raises(Minitest::Assertion) do
43
+ @test_case.assert_no_statsd_calls do
44
+ StatsD.increment('other')
45
+ StatsD.increment('another')
46
+ end
47
+ end
48
+ assert_equal assertion.message, "No StatsD calls for metric other, another expected."
49
+ end
50
+
51
+ def test_assert_statsd_call
52
+ @test_case.assert_statsd_increment('counter') do
53
+ StatsD.increment('counter')
54
+ end
55
+
56
+ @test_case.assert_statsd_increment('counter') do
57
+ StatsD.increment('counter')
58
+ StatsD.increment('other')
59
+ end
60
+
61
+ assert_raises(Minitest::Assertion) do
62
+ @test_case.assert_statsd_increment('counter') do
63
+ StatsD.increment('other')
64
+ end
65
+ end
66
+
67
+ assert_raises(Minitest::Assertion) do
68
+ @test_case.assert_statsd_increment('counter') do
69
+ StatsD.gauge('counter', 42)
70
+ end
71
+ end
72
+
73
+ assert_raises(Minitest::Assertion) do
74
+ @test_case.assert_statsd_increment('counter') do
75
+ StatsD.increment('counter')
76
+ StatsD.increment('counter')
77
+ end
78
+ end
79
+
80
+ @test_case.assert_statsd_increment('counter', times: 2) do
81
+ StatsD.increment('counter')
82
+ StatsD.increment('counter')
83
+ end
84
+
85
+ @test_case.assert_statsd_increment('counter', times: 2, tags: ['foo:1']) do
86
+ StatsD.increment('counter', tags: { foo: 1 })
87
+ StatsD.increment('counter', tags: { foo: 1 })
88
+ end
89
+
90
+ assert_raises(Minitest::Assertion) do
91
+ @test_case.assert_statsd_increment('counter', times: 2, tags: ['foo:1']) do
92
+ StatsD.increment('counter', tags: { foo: 1 })
93
+ StatsD.increment('counter', tags: { foo: 2 })
94
+ end
95
+ end
96
+
97
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a', 'b']) do
98
+ StatsD.increment('counter', sample_rate: 0.5, tags: ['a', 'b'])
99
+ end
100
+
101
+ assert_raises(Minitest::Assertion) do
102
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a', 'b'], ignore_tags: ['b']) do
103
+ StatsD.increment('counter', sample_rate: 0.5, tags: ['a'])
104
+ end
105
+ end
106
+
107
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a'], ignore_tags: ['b']) do
108
+ StatsD.increment('counter', sample_rate: 0.5, tags: ['a', 'b'])
109
+ end
110
+
111
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a'], ignore_tags: ['b']) do
112
+ StatsD.increment('counter', sample_rate: 0.5, tags: ['a'])
113
+ end
114
+
115
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1 }, ignore_tags: { b: 2 }) do
116
+ StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2 })
117
+ end
118
+
119
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1 }, ignore_tags: { b: 2 }) do
120
+ StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 3 })
121
+ end
122
+
123
+ assert_raises(Minitest::Assertion) do
124
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1, b: 3 }, ignore_tags: ['b']) do
125
+ StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2 })
126
+ end
127
+ end
128
+
129
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1 }, ignore_tags: ['b']) do
130
+ StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2 })
131
+ end
132
+
133
+ assert_raises(Minitest::Assertion) do
134
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: ['a', 'b']) do
135
+ StatsD.increment('counter', sample_rate: 0.2, tags: ['c'])
136
+ end
137
+ end
138
+ end
139
+
140
+ def test_assert_statsd_gauge_call_with_numeric_value
141
+ @test_case.assert_statsd_gauge('gauge', value: 42) do
142
+ StatsD.gauge('gauge', 42)
143
+ end
144
+
145
+ @test_case.assert_statsd_gauge('gauge', value: '42') do
146
+ StatsD.gauge('gauge', 42)
147
+ end
148
+
149
+ assert_raises(Minitest::Assertion) do
150
+ @test_case.assert_statsd_gauge('gauge', value: 42) do
151
+ StatsD.gauge('gauge', 45)
152
+ end
153
+ end
154
+ end
155
+
156
+ def test_assert_statsd_set_call_with_string_value
157
+ @test_case.assert_statsd_set('set', value: 12345) do
158
+ StatsD.set('set', '12345')
159
+ end
160
+
161
+ @test_case.assert_statsd_set('set', value: '12345') do
162
+ StatsD.set('set', '12345')
163
+ end
164
+
165
+ @test_case.assert_statsd_set('set', value: 12345) do
166
+ StatsD.set('set', 12345)
167
+ end
168
+
169
+ @test_case.assert_statsd_set('set', value: '12345') do
170
+ StatsD.set('set', 12345)
171
+ end
172
+
173
+ assert_raises(Minitest::Assertion) do
174
+ @test_case.assert_statsd_set('set', value: '42') do
175
+ StatsD.set('set', 45)
176
+ end
177
+ end
178
+ end
179
+
180
+ def test_tags_will_match_subsets
181
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1 }) do
182
+ StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2 })
183
+ end
184
+
185
+ assert_raises(Minitest::Assertion) do
186
+ @test_case.assert_statsd_increment('counter', sample_rate: 0.5, tags: { a: 1, b: 3 }) do
187
+ StatsD.increment('counter', sample_rate: 0.5, tags: { a: 1, b: 2, c: 4 })
188
+ end
189
+ end
190
+ end
191
+
192
+ def test_tags_friendly_error
193
+ assertion = assert_raises(Minitest::Assertion) do
194
+ @test_case.assert_statsd_increment('counter', tags: { class: "AnotherJob" }) do
195
+ StatsD.increment('counter', tags: { class: "MyJob" })
196
+ end
197
+ end
198
+
199
+ assert_includes assertion.message, "Captured metrics with the same key"
200
+ assert_includes assertion.message, "MyJob"
201
+ end
202
+
203
+ def test_multiple_metrics_are_not_order_dependent
204
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:1'])
205
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
206
+ @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
207
+ StatsD.increment('counter', tags: { foo: 1 })
208
+ StatsD.increment('counter', tags: { foo: 2 })
209
+ end
210
+
211
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:1'])
212
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
213
+ @test_case.assert_statsd_calls([foo_2_metric, foo_1_metric]) do
214
+ StatsD.increment('counter', tags: { foo: 1 })
215
+ StatsD.increment('counter', tags: { foo: 2 })
216
+ end
217
+
218
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
219
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
220
+ @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
221
+ StatsD.increment('counter', tags: { foo: 1 })
222
+ StatsD.increment('counter', tags: { foo: 1 })
223
+ StatsD.increment('counter', tags: { foo: 2 })
224
+ end
225
+
226
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
227
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
228
+ @test_case.assert_statsd_calls([foo_2_metric, foo_1_metric]) do
229
+ StatsD.increment('counter', tags: { foo: 1 })
230
+ StatsD.increment('counter', tags: { foo: 1 })
231
+ StatsD.increment('counter', tags: { foo: 2 })
232
+ end
233
+
234
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
235
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
236
+ @test_case.assert_statsd_calls([foo_2_metric, foo_1_metric]) do
237
+ StatsD.increment('counter', tags: { foo: 1 })
238
+ StatsD.increment('counter', tags: { foo: 2 })
239
+ StatsD.increment('counter', tags: { foo: 1 })
240
+ end
241
+ end
242
+
243
+ def test_assert_multiple_statsd_calls
244
+ assert_raises(Minitest::Assertion) do
245
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
246
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
247
+ @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
248
+ StatsD.increment('counter', tags: { foo: 1 })
249
+ StatsD.increment('counter', tags: { foo: 2 })
250
+ end
251
+ end
252
+
253
+ assert_raises(Minitest::Assertion) do
254
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
255
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
256
+ @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
257
+ StatsD.increment('counter', tags: { foo: 1 })
258
+ StatsD.increment('counter', tags: { foo: 1 })
259
+ StatsD.increment('counter', tags: { foo: 2 })
260
+ StatsD.increment('counter', tags: { foo: 2 })
261
+ end
262
+ end
263
+
264
+ foo_1_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 2, tags: ['foo:1'])
265
+ foo_2_metric = StatsD::Instrument::MetricExpectation.new(type: :c, name: 'counter', times: 1, tags: ['foo:2'])
266
+ @test_case.assert_statsd_calls([foo_1_metric, foo_2_metric]) do
267
+ StatsD.increment('counter', tags: { foo: 1 })
268
+ StatsD.increment('counter', tags: { foo: 1 })
269
+ StatsD.increment('counter', tags: { foo: 2 })
270
+ end
271
+ end
272
+
273
+ def test_assert_statsd_call_with_tags
274
+ @test_case.assert_statsd_increment('counter', tags: ['a:b', 'c:d']) do
275
+ StatsD.increment('counter', tags: { a: 'b', c: 'd' })
276
+ end
277
+
278
+ @test_case.assert_statsd_increment('counter', tags: { a: 'b', c: 'd' }) do
279
+ StatsD.increment('counter', tags: ['a:b', 'c:d'])
280
+ end
281
+ end
282
+
283
+ def test_nested_assertions
284
+ @test_case.assert_statsd_increment('counter1') do
285
+ @test_case.assert_statsd_increment('counter2') do
286
+ StatsD.increment('counter1')
287
+ StatsD.increment('counter2')
288
+ end
289
+ end
290
+
291
+ @test_case.assert_statsd_increment('counter1') do
292
+ StatsD.increment('counter1')
293
+ @test_case.assert_statsd_increment('counter2') do
294
+ StatsD.increment('counter2')
295
+ end
296
+ end
297
+
298
+ assert_raises(Minitest::Assertion) do
299
+ @test_case.assert_statsd_increment('counter1') do
300
+ @test_case.assert_statsd_increment('counter2') do
301
+ StatsD.increment('counter2')
302
+ end
303
+ end
304
+ end
305
+
306
+ assert_raises(Minitest::Assertion) do
307
+ @test_case.assert_statsd_increment('counter1') do
308
+ @test_case.assert_statsd_increment('counter2') do
309
+ StatsD.increment('counter1')
310
+ end
311
+ StatsD.increment('counter2')
312
+ end
313
+ end
314
+ end
315
+
316
+ def test_assertion_block_with_expected_exceptions
317
+ @test_case.assert_statsd_increment('expected_happened') do
318
+ @test_case.assert_raises(RuntimeError) do
319
+ begin
320
+ raise "expected"
321
+ rescue
322
+ StatsD.increment('expected_happened')
323
+ raise
324
+ end
325
+ end
326
+ end
327
+
328
+ assertion = assert_raises(Minitest::Assertion) do
329
+ @test_case.assert_statsd_increment('counter') do
330
+ @test_case.assert_raises(RuntimeError) do
331
+ raise "expected"
332
+ end
333
+ end
334
+ end
335
+ assert_includes assertion.message, "No StatsD calls for metric counter of type c were made"
336
+ end
337
+
338
+ def test_assertion_block_with_unexpected_exceptions
339
+ assertion = assert_raises(Minitest::Assertion) do
340
+ @test_case.assert_statsd_increment('counter') do
341
+ StatsD.increment('counter')
342
+ raise "unexpected"
343
+ end
344
+ end
345
+ assert_includes assertion.message, "An exception occurred in the block provided to the StatsD assertion"
346
+
347
+ assertion = assert_raises(Minitest::Assertion) do
348
+ @test_case.assert_raises(RuntimeError) do
349
+ @test_case.assert_statsd_increment('counter') do
350
+ StatsD.increment('counter')
351
+ raise "unexpected"
352
+ end
353
+ end
354
+ end
355
+ assert_includes assertion.message, "An exception occurred in the block provided to the StatsD assertion"
356
+
357
+ assertion = assert_raises(Minitest::Assertion) do
358
+ @test_case.assert_raises(RuntimeError) do
359
+ @test_case.assert_no_statsd_calls do
360
+ raise "unexpected"
361
+ end
362
+ end
363
+ end
364
+ assert_includes assertion.message, "An exception occurred in the block provided to the StatsD assertion"
365
+ end
366
+
367
+ def test_assertion_block_with_other_assertion_failures
368
+ # If another assertion failure happens inside the block, that failrue should have priority
369
+ assertion = assert_raises(Minitest::Assertion) do
370
+ @test_case.assert_statsd_increment('counter') do
371
+ @test_case.flunk('other assertion failure')
372
+ end
373
+ end
374
+ assert_equal "other assertion failure", assertion.message
375
+ end
376
+ end