scout_apm 3.0.0.pre8 → 3.0.0.pre9

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 63c9b4319eecbefb59fdb10fb0fbc0d264dfb7a0
4
- data.tar.gz: 821271be9ef95416e72b659a825895513d80017d
3
+ metadata.gz: ed6963870425b875415255f3aa5bb51144b5fabd
4
+ data.tar.gz: f31e6e8780992531d3a87733b3f4ef5bc3e11ab8
5
5
  SHA512:
6
- metadata.gz: 330d8721be955d8676285665d7db211919b1867da0720b1b24c663119f434cb7aaffa9a55cec27d8286633d40d924b337283fea0c45195d872900f54a3c6c041
7
- data.tar.gz: 0eef9b024471d03e689aaceffd56402c32a5f0904e6c9124209bb60e4d06173f281fc25b3562225303b5c8292be7daab2f5233618a098454c646da597d4913fa
6
+ metadata.gz: bb4b61f29fc72f0c1eeec3c9bc2ac86b57a66605a5d8dba43612dc686d95b75c6658665d8c2106ae637c794f2a7af689796163aa31e4c8e561610a16f2064778
7
+ data.tar.gz: d7a9137fcdd64016a75cb74f1c4f02c653c38dff5621b6d23baaefa1c4a5657fbd13dc8d7ccd25844ee0c7ab44771becb4982754e6a48589c92624cfad012919
data/CHANGELOG.markdown CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  * ScoutProf BETA
4
4
 
5
+ # 2.1.22
6
+
7
+ * Add DevTrace support for newest 4.2.x and 5.x versions of Rails
8
+
9
+ # 2.1.21
10
+
11
+ * Fix edge case, causing DevTrace to fail
12
+ * Add debug tooling, allowing custom functions to be inserted into the agent at
13
+ key points.
14
+
15
+ # 2.1.20
16
+
17
+ * Add a `detailed_middleware` boolean configuration option to capture
18
+ per-middleware data, as opposed to the default of aggregating all middleware
19
+ together. This has a small amount of additional overhead, approximately
20
+ 10-15ms per request.
21
+
5
22
  # 2.1.19
6
23
 
7
24
  * Log all configuration settings at start when log level is debug
@@ -72,7 +72,7 @@ module ScoutApm
72
72
  log_deliver(metrics, slow_transactions, metadata, slow_jobs, histograms)
73
73
 
74
74
  payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms)
75
- # logger.debug("Payload: #{payload}")
75
+ logger.debug("Sending payload w/ Headers: #{headers.inspect}")
76
76
 
77
77
  reporter.report(payload, headers)
78
78
  rescue => e
@@ -265,6 +265,7 @@ module ScoutApm
265
265
  @background_worker = ScoutApm::BackgroundWorker.new
266
266
  @background_worker_thread = Thread.new do
267
267
  @background_worker.start {
268
+ ScoutApm::Debug.instance.call_periodic_hooks
268
269
  ScoutApm::Agent.instance.process_metrics
269
270
  clean_old_percentiles
270
271
  }
@@ -293,8 +294,13 @@ module ScoutApm
293
294
  install_instrument(ScoutApm::Instruments::ActionControllerRails2)
294
295
  when :rails3_or_4 then
295
296
  install_instrument(ScoutApm::Instruments::ActionControllerRails3Rails4)
296
- install_instrument(ScoutApm::Instruments::MiddlewareSummary)
297
297
  install_instrument(ScoutApm::Instruments::RailsRouter)
298
+
299
+ if config.value("detailed_middleware")
300
+ install_instrument(ScoutApm::Instruments::MiddlewareDetailed)
301
+ else
302
+ install_instrument(ScoutApm::Instruments::MiddlewareSummary)
303
+ end
298
304
  end
299
305
 
300
306
  install_instrument(ScoutApm::Instruments::ActiveRecord)
@@ -37,6 +37,7 @@ module ScoutApm
37
37
  'compress_payload',
38
38
  'config_file',
39
39
  'data_file',
40
+ 'detailed_middleware',
40
41
  'dev_trace',
41
42
  'direct_host',
42
43
  'disabled_instruments',
@@ -45,8 +46,8 @@ module ScoutApm
45
46
  'hostname',
46
47
  'ignore',
47
48
  'key',
48
- 'log_level',
49
49
  'log_file_path',
50
+ 'log_level',
50
51
  'monitor',
51
52
  'name',
52
53
  'profile',
@@ -129,6 +130,7 @@ module ScoutApm
129
130
  "monitor" => BooleanCoercion.new,
130
131
  "enable_background_jobs" => BooleanCoercion.new,
131
132
  "dev_trace" => BooleanCoercion.new,
133
+ "detailed_middleware" => BooleanCoercion.new,
132
134
  "ignore" => JsonCoercion.new,
133
135
  }
134
136
 
@@ -203,6 +205,7 @@ module ScoutApm
203
205
  class ConfigDefaults
204
206
  DEFAULTS = {
205
207
  'compress_payload' => true,
208
+ 'detailed_middleware' => false,
206
209
  'dev_trace' => false,
207
210
  'direct_host' => 'https://apm.scoutapp.com',
208
211
  'disabled_instruments' => [],
@@ -0,0 +1,37 @@
1
+ module ScoutApm
2
+ class Debug
3
+ # see self.instance
4
+ @@instance = nil
5
+
6
+ def self.instance
7
+ @@instance ||= new
8
+ end
9
+
10
+ def register_periodic_hook(&hook)
11
+ @periodic_hooks << hook
12
+ end
13
+
14
+ def call_periodic_hooks
15
+ @periodic_hooks.each do |hook|
16
+ begin
17
+ hook.call
18
+ rescue => e
19
+ logger.info("Periodic debug hook failed to run: #{e}\n\t#{e.backtrace.join("\n\t")}")
20
+ end
21
+ end
22
+ rescue
23
+ # Something went super wrong for the inner rescue to not catch this. Just
24
+ # swallow the error. The debug tool should never crash the app.
25
+ end
26
+
27
+ private
28
+
29
+ def initialize
30
+ @periodic_hooks = []
31
+ end
32
+
33
+ def logger
34
+ ScoutApm::Agent.instance.logger
35
+ end
36
+ end
37
+ end
@@ -3,7 +3,7 @@
3
3
  (function(){var open=window.XMLHttpRequest.prototype.open;var send=window.XMLHttpRequest.prototype.send;function openReplacement(method,url,async,user,password){this._url=url;return open.apply(this,arguments);}
4
4
  function sendReplacement(data){if(this.onload){this._onload=this.onload;}
5
5
  this.onload=onLoadReplacement;return send.apply(this,arguments);}
6
- function onLoadReplacement(){if(this._url.startsWith(window.location.protocol+"//"+window.location.host)||!this._url.startsWith("http")){try{traceText=this.getResponseHeader("X-scoutapminstant");if(traceText){setTimeout(function(){window.scoutInstant("addTrace",traceText)},0);}}catch(e){console.debug("Problem getting X-scoutapminstant header");}}
6
+ function onLoadReplacement(){if(this._url.startsWith(window.location.protocol+"//"+window.location.host)||!this._url.startsWith("http")){try{var traceText=this.getResponseHeader("X-scoutapminstant");if(traceText){setTimeout(function(){window.scoutInstant("addTrace",traceText)},0);}}catch(e){console.debug("Problem getting X-scoutapminstant header");}}
7
7
  if(this._onload){return this._onload.apply(this,arguments);}}
8
8
  window.XMLHttpRequest.prototype.open=openReplacement;window.XMLHttpRequest.prototype.send=sendReplacement;})();
9
- </script>
9
+ </script>
@@ -5,6 +5,11 @@ module ScoutApm
5
5
  class Page
6
6
  def initialize(html)
7
7
  @html = html
8
+
9
+ if html.is_a?(Array)
10
+ @html = html.inject("") { |memo, str| memo + str }
11
+ end
12
+
8
13
  @to_add_to_head = []
9
14
  @to_add_to_body = []
10
15
  end
@@ -45,66 +50,200 @@ module ScoutApm
45
50
  end
46
51
 
47
52
  def call(env)
48
- status, headers, response = @app.call(env)
49
- path, content_type = env['PATH_INFO'], headers['Content-Type']
50
- if ScoutApm::Agent.instance.config.value('dev_trace')
51
- if response.respond_to?(:body)
52
- req = ScoutApm::RequestManager.lookup
53
-
54
- return [status, headers, response] if req.ignoring_request?
55
-
56
- slow_converter = LayerConverters::SlowRequestConverter.new(req)
57
- trace = slow_converter.call
58
- if trace
59
- metadata = {
60
- :app_root => ScoutApm::Environment.instance.root.to_s,
61
- :unique_id => env['action_dispatch.request_id'], # note, this is a different unique_id than what "normal" payloads use
62
- :agent_version => ScoutApm::VERSION,
63
- :platform => "ruby",
64
- }
65
- hash = ScoutApm::Serializers::PayloadSerializerToJson.rearrange_slow_transaction(trace)
66
- hash.merge!(:metadata => metadata)
67
- payload = ScoutApm::Serializers::PayloadSerializerToJson.jsonify_hash(hash)
68
-
69
- if env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' || content_type.include?("application/json")
70
- ScoutApm::Agent.instance.logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body. This is either AJAX or JSON. Path=#{path}; ContentType=#{content_type}")
71
- # Add the payload as a header if it's an AJAX call or JSON
72
- headers['X-scoutapminstant'] = payload
73
- [status, headers, response]
74
- else
75
- # otherwise, attempt to add it inline in the page, along with the appropriate JS & CSS. Note, if page doesn't have a head or body,
76
- #duration = (req.root_layer.total_call_time*1000).to_i
77
- apm_host=ScoutApm::Agent.instance.config.value("direct_host")
78
- page = ScoutApm::Instant::Page.new(response.body)
79
- page.add_to_head(ScoutApm::Instant::Util.read_asset("xmlhttp_instrumentation.html")) # This monkey-patches XMLHttpRequest. It could possibly be part of the main scout_instant.js too. Putting it here so it runs as soon as possible.
80
- page.add_to_head("<link href='#{apm_host}/instant/scout_instant.css?cachebust=#{Time.now.to_i}' media='all' rel='stylesheet' />")
81
- page.add_to_body("<script src='#{apm_host}/instant/scout_instant.js?cachebust=#{Time.now.to_i}'></script>")
82
- page.add_to_body("<script>var scoutInstantPageTrace=#{payload};window.scoutInstant=window.scoutInstant('#{apm_host}', scoutInstantPageTrace)</script>")
83
-
84
- if response.is_a?(ActionDispatch::Response)
85
- ScoutApm::Agent.instance.logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body. This appears to be an HTML page and an ActionDispatch::Response. Path=#{path}; ContentType=#{content_type}")
86
- # preserve the ActionDispatch::Response when applicable
87
- response.body=[page.res]
88
- [status, headers, response]
89
- else
90
- ScoutApm::Agent.instance.logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body. This appears to be an HTML page but not an ActionDispatch::Response. Path=#{path}; ContentType=#{content_type}")
91
- # otherwise, just return an array
92
- [status, headers, [page.res]]
93
- end
94
- end
95
- else
96
- ScoutApm::Agent.instance.logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body, but no trace was found. Path=#{path}; ContentType=#{content_type}")
97
- [status, headers, response]
98
- end
99
- else
100
- # don't log anything here - this is the path for all assets served in development, and the log would get noisy
101
- [status, headers, response]
102
- end
53
+ rack_response = @app.call(env)
54
+ begin
55
+ DevTraceResponseManipulator.new(env, rack_response).call
56
+ rescue Exception => e
57
+ # If anything went wrong at all, just bail out and return the unmodified response.
58
+ ScoutApm::Agent.instance.logger.debug("DevTrace: Raised an exception: #{e.message}, #{e.backtrace}")
59
+ rack_response
60
+ end
61
+ end
62
+ end
63
+
64
+ class DevTraceResponseManipulator
65
+ attr_reader :rack_response
66
+ attr_reader :rack_status, :rack_headers, :rack_body
67
+ attr_reader :env
68
+
69
+ def initialize(env, rack_response)
70
+ @env = env
71
+ @rack_response = rack_response
72
+
73
+ @rack_status = rack_response[0]
74
+ @rack_headers = rack_response[1]
75
+ @rack_body = rack_response[2]
76
+ end
77
+
78
+ def call
79
+ return rack_response unless preconditions_met?
80
+
81
+ if ajax_request?
82
+ ScoutApm::Agent.instance.logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body. This is either AJAX or JSON. Path=#{path}; ContentType=#{content_type}")
83
+ adjust_ajax_header
84
+ else
85
+ adjust_html_response
86
+ end
87
+
88
+ rebuild_rack_response
89
+ end
90
+
91
+ ###########################
92
+ # Precondition checking #
93
+ ###########################
94
+
95
+ def preconditions_met?
96
+ if dev_trace_disabled?
97
+ logger.debug("DevTrace: isn't activated via config. Try: SCOUT_DEV_TRACE=true rails server")
98
+ return false
99
+ end
100
+
101
+ # Don't attempt to instrument assets.
102
+ # Don't log this case, since it would be very noisy
103
+ logger.debug("DevTrace: dev asset ignored") and return false if development_asset?
104
+
105
+ # If we didn't have a tracked_request object, or we explicitly ignored
106
+ # this request, don't do any work.
107
+ logger.debug("DevTrace: no tracked request") and return false if tracked_request.nil? || tracked_request.ignoring_request?
108
+
109
+ # If we didn't get a trace, we can't show a trace...
110
+ if trace.nil?
111
+ logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body, but no trace was found. Path=#{path}; ContentType=#{content_type}")
112
+ return false
113
+ end
114
+
115
+ true
116
+ end
117
+
118
+ def dev_trace_disabled?
119
+ ! ScoutApm::Agent.instance.config.value('dev_trace')
120
+ end
121
+
122
+ ########################
123
+ # Response Injection #
124
+ ########################
125
+
126
+ def rebuild_rack_response
127
+ [rack_status, rack_headers, rack_body]
128
+ end
129
+
130
+ def adjust_ajax_header
131
+ rack_headers['X-scoutapminstant'] = payload
132
+ end
133
+
134
+ def adjust_html_response
135
+ case true
136
+ when older_rails_response? then adjust_older_rails_response
137
+ when newer_rails_response? then adjust_newer_rails_response
138
+ when rack_proxy_response? then adjust_rack_proxy_response
103
139
  else
104
- ScoutApm::Agent.instance.logger.debug("DevTrace: isn't activated via config. Try: SCOUT_DEV_TRACE=true rails server")
105
- [status, headers, response]
140
+ # No action taken, we only adjust if we know exactly what we have.
141
+ end
142
+ end
143
+
144
+ def older_rails_response?
145
+ if defined?(ActionDispatch::Response)
146
+ return true if rack_body.is_a?(ActionDispatch::Response)
147
+ end
148
+ end
149
+
150
+ def newer_rails_response?
151
+ if defined?(ActionDispatch::Response::RackBody)
152
+ return true if rack_body.is_a?(ActionDispatch::Response::RackBody)
106
153
  end
107
154
  end
155
+
156
+ def rack_proxy_response?
157
+ rack_body.is_a?(Rack::BodyProxy)
158
+ end
159
+
160
+ def adjust_older_rails_response
161
+ logger.debug("DevTrace: in middleware, dev_trace is active, and response has a (older) body. This appears to be an HTML page and an ActionDispatch::Response. Path=#{path}; ContentType=#{content_type}")
162
+ rack_body.body = [ html_manipulator.res ]
163
+ end
164
+
165
+ # Preserve the ActionDispatch::Response object we're working with
166
+ def adjust_newer_rails_response
167
+ logger.debug("DevTrace: in middleware, dev_trace is active, and response has a (newer) body. This appears to be an HTML page and an ActionDispatch::Response. Path=#{path}; ContentType=#{content_type}")
168
+ @rack_body = [ html_manipulator.res ]
169
+ end
170
+
171
+ def adjust_rack_proxy_response
172
+ logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body. This appears to be an HTML page and an Rack::BodyProxy. Path=#{path}; ContentType=#{content_type}")
173
+ @rack_body = [ html_manipulator.res ]
174
+ @rack_headers.delete("Content-Length")
175
+ end
176
+
177
+ def html_manipulator
178
+ @html_manipulator ||=
179
+ begin
180
+ page = ScoutApm::Instant::Page.new(rack_body.body)
181
+
182
+ # This monkey-patches XMLHttpRequest. It could possibly be part of the main scout_instant.js too. Putting it here so it runs as soon as possible.
183
+ page.add_to_head(ScoutApm::Instant::Util.read_asset("xmlhttp_instrumentation.html"))
184
+
185
+ # Add a link to CSS, then JS
186
+ page.add_to_head("<link href='#{apm_host}/instant/scout_instant.css?cachebust=#{Time.now.to_i}' media='all' rel='stylesheet' />")
187
+ page.add_to_body("<script src='#{apm_host}/instant/scout_instant.js?cachebust=#{Time.now.to_i}'></script>")
188
+ page.add_to_body("<script>var scoutInstantPageTrace=#{payload};window.scoutInstant=window.scoutInstant('#{apm_host}', scoutInstantPageTrace)</script>")
189
+
190
+ page
191
+ end
192
+ end
193
+
194
+ def ajax_request?
195
+ env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' || content_type.include?("application/json")
196
+ end
197
+
198
+ def development_asset?
199
+ !rack_body.respond_to?(:body)
200
+ end
201
+
202
+ def path
203
+ env['PATH_INFO']
204
+ end
205
+
206
+ def content_type
207
+ rack_headers['Content-Type']
208
+ end
209
+
210
+ ##############################
211
+ # APM Helpers & Shorthands #
212
+ ##############################
213
+
214
+ def logger
215
+ ScoutApm::Agent.instance.logger
216
+ end
217
+
218
+ def tracked_request
219
+ @tracked_request ||= ScoutApm::RequestManager.lookup
220
+ end
221
+
222
+ def apm_host
223
+ ScoutApm::Agent.instance.config.value("direct_host")
224
+ end
225
+
226
+ def trace
227
+ @trace ||= LayerConverters::SlowRequestConverter.new(tracked_request).call
228
+ end
229
+
230
+ def payload
231
+ @payload ||=
232
+ begin
233
+ metadata = {
234
+ :app_root => ScoutApm::Environment.instance.root.to_s,
235
+ :unique_id => env['action_dispatch.request_id'], # note, this is a different unique_id than what "normal" payloads use
236
+ :agent_version => ScoutApm::VERSION,
237
+ :platform => "ruby",
238
+ }
239
+
240
+ hash = ScoutApm::Serializers::PayloadSerializerToJson.
241
+ rearrange_slow_transaction(trace).
242
+ merge!(:metadata => metadata)
243
+ ScoutApm::Serializers::PayloadSerializerToJson.jsonify_hash(hash)
244
+ end
245
+ end
246
+
108
247
  end
109
248
  end
110
249
  end
@@ -1,13 +1,11 @@
1
1
  # Inserts a new middleware between each actual middleware in the application,
2
2
  # so as to trace the time for each one.
3
3
  #
4
- # Currently disabled due to the overhead of this approach (~10-15ms per request
5
- # in practice). Instead, middleware as a whole are instrumented via the
6
- # MiddlewareSummary class.
4
+ # Currently disabled by default due to the overhead of this approach (~10-15ms
5
+ # per request in practice). Instead, middleware as a whole are instrumented
6
+ # via the MiddlewareSummary class.
7
7
  #
8
- # There will likely be a configuration flag to turn this on in favor of the
9
- # summary tracing in a future version of the agent, but this is not yet
10
- # implemented.
8
+ # Turn this on with the configuration setting `detailed_middleware` set to true
11
9
  module ScoutApm
12
10
  module Instruments
13
11
  class MiddlewareDetailed
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "3.0.0.pre8"
2
+ VERSION = "3.0.0.pre9"
3
3
  end
data/lib/scout_apm.rb CHANGED
@@ -26,6 +26,7 @@ require 'rusage'
26
26
  #####################################
27
27
  require 'scout_apm/version'
28
28
 
29
+ require 'scout_apm/debug'
29
30
  require 'scout_apm/tracked_request'
30
31
  require 'scout_apm/layer'
31
32
  require 'scout_apm/limited_layer'
@@ -76,7 +77,7 @@ require 'scout_apm/instruments/active_record'
76
77
  require 'scout_apm/instruments/action_controller_rails_2'
77
78
  require 'scout_apm/instruments/action_controller_rails_3_rails4'
78
79
  require 'scout_apm/instruments/middleware_summary'
79
- # require 'scout_apm/instruments/middleware_detailed' # Currently disabled functionality, see the file for details.
80
+ require 'scout_apm/instruments/middleware_detailed' # Disabled by default, see the file for more details
80
81
  require 'scout_apm/instruments/rails_router'
81
82
  require 'scout_apm/instruments/grape'
82
83
  require 'scout_apm/instruments/sinatra'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.pre8
4
+ version: 3.0.0.pre9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-12-29 00:00:00.000000000 Z
12
+ date: 2017-02-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -148,6 +148,7 @@ files:
148
148
  - lib/scout_apm/call_set.rb
149
149
  - lib/scout_apm/config.rb
150
150
  - lib/scout_apm/context.rb
151
+ - lib/scout_apm/debug.rb
151
152
  - lib/scout_apm/environment.rb
152
153
  - lib/scout_apm/fake_store.rb
153
154
  - lib/scout_apm/framework_integrations/rails_2.rb
@@ -292,34 +293,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
292
293
  version: 1.3.1
293
294
  requirements: []
294
295
  rubyforge_project: scout_apm
295
- rubygems_version: 2.5.2
296
+ rubygems_version: 2.2.2
296
297
  signing_key:
297
298
  specification_version: 4
298
299
  summary: Ruby application performance monitoring
299
- test_files:
300
- - test/data/config_test_1.yml
301
- - test/test_helper.rb
302
- - test/unit/agent_test.rb
303
- - test/unit/background_job_integrations/sidekiq_test.rb
304
- - test/unit/config_test.rb
305
- - test/unit/context_test.rb
306
- - test/unit/environment_test.rb
307
- - test/unit/git_revision_test.rb
308
- - test/unit/histogram_test.rb
309
- - test/unit/ignored_uris_test.rb
310
- - test/unit/instruments/active_record_instruments_test.rb
311
- - test/unit/instruments/net_http_test.rb
312
- - test/unit/instruments/percentile_sampler_test.rb
313
- - test/unit/layaway_test.rb
314
- - test/unit/layer_children_set_test.rb
315
- - test/unit/limited_layer_test.rb
316
- - test/unit/metric_set_test.rb
317
- - test/unit/scored_item_set_test.rb
318
- - test/unit/serializers/payload_serializer_test.rb
319
- - test/unit/slow_job_policy_test.rb
320
- - test/unit/slow_request_policy_test.rb
321
- - test/unit/sql_sanitizer_test.rb
322
- - test/unit/store_test.rb
323
- - test/unit/utils/active_record_metric_name_test.rb
324
- - test/unit/utils/backtrace_parser_test.rb
325
- - test/unit/utils/numbers_test.rb
300
+ test_files: []