airbrake-ruby 4.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/lib/airbrake-ruby.rb +515 -0
  3. data/lib/airbrake-ruby/async_sender.rb +80 -0
  4. data/lib/airbrake-ruby/backtrace.rb +196 -0
  5. data/lib/airbrake-ruby/benchmark.rb +39 -0
  6. data/lib/airbrake-ruby/code_hunk.rb +51 -0
  7. data/lib/airbrake-ruby/config.rb +229 -0
  8. data/lib/airbrake-ruby/config/validator.rb +91 -0
  9. data/lib/airbrake-ruby/deploy_notifier.rb +36 -0
  10. data/lib/airbrake-ruby/file_cache.rb +54 -0
  11. data/lib/airbrake-ruby/filter_chain.rb +95 -0
  12. data/lib/airbrake-ruby/filters/context_filter.rb +29 -0
  13. data/lib/airbrake-ruby/filters/dependency_filter.rb +31 -0
  14. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +46 -0
  15. data/lib/airbrake-ruby/filters/gem_root_filter.rb +33 -0
  16. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +92 -0
  17. data/lib/airbrake-ruby/filters/git_repository_filter.rb +64 -0
  18. data/lib/airbrake-ruby/filters/git_revision_filter.rb +66 -0
  19. data/lib/airbrake-ruby/filters/keys_blacklist.rb +49 -0
  20. data/lib/airbrake-ruby/filters/keys_filter.rb +140 -0
  21. data/lib/airbrake-ruby/filters/keys_whitelist.rb +48 -0
  22. data/lib/airbrake-ruby/filters/root_directory_filter.rb +28 -0
  23. data/lib/airbrake-ruby/filters/sql_filter.rb +125 -0
  24. data/lib/airbrake-ruby/filters/system_exit_filter.rb +23 -0
  25. data/lib/airbrake-ruby/filters/thread_filter.rb +92 -0
  26. data/lib/airbrake-ruby/hash_keyable.rb +37 -0
  27. data/lib/airbrake-ruby/ignorable.rb +44 -0
  28. data/lib/airbrake-ruby/inspectable.rb +39 -0
  29. data/lib/airbrake-ruby/loggable.rb +34 -0
  30. data/lib/airbrake-ruby/monotonic_time.rb +43 -0
  31. data/lib/airbrake-ruby/nested_exception.rb +38 -0
  32. data/lib/airbrake-ruby/notice.rb +162 -0
  33. data/lib/airbrake-ruby/notice_notifier.rb +134 -0
  34. data/lib/airbrake-ruby/performance_breakdown.rb +46 -0
  35. data/lib/airbrake-ruby/performance_notifier.rb +155 -0
  36. data/lib/airbrake-ruby/promise.rb +109 -0
  37. data/lib/airbrake-ruby/query.rb +54 -0
  38. data/lib/airbrake-ruby/request.rb +46 -0
  39. data/lib/airbrake-ruby/response.rb +74 -0
  40. data/lib/airbrake-ruby/stashable.rb +15 -0
  41. data/lib/airbrake-ruby/stat.rb +73 -0
  42. data/lib/airbrake-ruby/sync_sender.rb +113 -0
  43. data/lib/airbrake-ruby/tdigest.rb +393 -0
  44. data/lib/airbrake-ruby/thread_pool.rb +128 -0
  45. data/lib/airbrake-ruby/time_truncate.rb +17 -0
  46. data/lib/airbrake-ruby/timed_trace.rb +58 -0
  47. data/lib/airbrake-ruby/truncator.rb +115 -0
  48. data/lib/airbrake-ruby/version.rb +6 -0
  49. data/spec/airbrake_spec.rb +324 -0
  50. data/spec/async_sender_spec.rb +72 -0
  51. data/spec/backtrace_spec.rb +427 -0
  52. data/spec/benchmark_spec.rb +33 -0
  53. data/spec/code_hunk_spec.rb +115 -0
  54. data/spec/config/validator_spec.rb +184 -0
  55. data/spec/config_spec.rb +154 -0
  56. data/spec/deploy_notifier_spec.rb +48 -0
  57. data/spec/file_cache_spec.rb +34 -0
  58. data/spec/filter_chain_spec.rb +92 -0
  59. data/spec/filters/context_filter_spec.rb +23 -0
  60. data/spec/filters/dependency_filter_spec.rb +12 -0
  61. data/spec/filters/exception_attributes_filter_spec.rb +50 -0
  62. data/spec/filters/gem_root_filter_spec.rb +41 -0
  63. data/spec/filters/git_last_checkout_filter_spec.rb +46 -0
  64. data/spec/filters/git_repository_filter.rb +61 -0
  65. data/spec/filters/git_revision_filter_spec.rb +126 -0
  66. data/spec/filters/keys_blacklist_spec.rb +225 -0
  67. data/spec/filters/keys_whitelist_spec.rb +194 -0
  68. data/spec/filters/root_directory_filter_spec.rb +39 -0
  69. data/spec/filters/sql_filter_spec.rb +262 -0
  70. data/spec/filters/system_exit_filter_spec.rb +14 -0
  71. data/spec/filters/thread_filter_spec.rb +277 -0
  72. data/spec/fixtures/notroot.txt +7 -0
  73. data/spec/fixtures/project_root/code.rb +221 -0
  74. data/spec/fixtures/project_root/empty_file.rb +0 -0
  75. data/spec/fixtures/project_root/long_line.txt +1 -0
  76. data/spec/fixtures/project_root/short_file.rb +3 -0
  77. data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +5 -0
  78. data/spec/helpers.rb +9 -0
  79. data/spec/ignorable_spec.rb +14 -0
  80. data/spec/inspectable_spec.rb +45 -0
  81. data/spec/monotonic_time_spec.rb +12 -0
  82. data/spec/nested_exception_spec.rb +73 -0
  83. data/spec/notice_notifier/options_spec.rb +259 -0
  84. data/spec/notice_notifier_spec.rb +356 -0
  85. data/spec/notice_spec.rb +296 -0
  86. data/spec/performance_breakdown_spec.rb +12 -0
  87. data/spec/performance_notifier_spec.rb +491 -0
  88. data/spec/promise_spec.rb +197 -0
  89. data/spec/query_spec.rb +11 -0
  90. data/spec/request_spec.rb +11 -0
  91. data/spec/response_spec.rb +88 -0
  92. data/spec/spec_helper.rb +100 -0
  93. data/spec/stashable_spec.rb +23 -0
  94. data/spec/stat_spec.rb +47 -0
  95. data/spec/sync_sender_spec.rb +133 -0
  96. data/spec/tdigest_spec.rb +230 -0
  97. data/spec/thread_pool_spec.rb +158 -0
  98. data/spec/time_truncate_spec.rb +13 -0
  99. data/spec/timed_trace_spec.rb +125 -0
  100. data/spec/truncator_spec.rb +238 -0
  101. metadata +216 -0
@@ -0,0 +1,128 @@
1
+ module Airbrake
2
+ # ThreadPool implements a simple thread pool that can configure the number of
3
+ # worker threads and the size of the queue to process.
4
+ #
5
+ # @example
6
+ # # Initialize a new thread pool with 5 workers and a queue size of 100. Set
7
+ # # the block to be run concurrently.
8
+ # thread_pool = ThreadPool.new(
9
+ # worker_size: 5,
10
+ # queue_size: 100,
11
+ # block: proc { |message| print "ECHO: #{message}..."}
12
+ # )
13
+ #
14
+ # # Send work.
15
+ # 10.times { |i| thread_pool << i }
16
+ # #=> ECHO: 0...ECHO: 1...ECHO: 2...
17
+ #
18
+ # @api private
19
+ # @since v4.6.1
20
+ class ThreadPool
21
+ include Loggable
22
+
23
+ # @return [ThreadGroup] the list of workers
24
+ # @note This is exposed for eaiser unit testing
25
+ attr_reader :workers
26
+
27
+ def initialize(worker_size:, queue_size:, block:)
28
+ @worker_size = worker_size
29
+ @queue_size = queue_size
30
+ @block = block
31
+
32
+ @queue = SizedQueue.new(queue_size)
33
+ @workers = ThreadGroup.new
34
+ @mutex = Mutex.new
35
+ @pid = nil
36
+ @closed = false
37
+
38
+ has_workers?
39
+ end
40
+
41
+ # Adds a new message to the thread pool. Rejects messages if the queue is at
42
+ # its capacity.
43
+ #
44
+ # @param [Object] message The message that gets passed to the block
45
+ # @return [Boolean] true if the message was successfully sent to the pool,
46
+ # false if the queue is full
47
+ def <<(message)
48
+ return false if backlog >= @queue_size
49
+ @queue << message
50
+ true
51
+ end
52
+
53
+ # @return [Integer] how big the queue is at the moment
54
+ def backlog
55
+ @queue.size
56
+ end
57
+
58
+ # Checks if a thread pool has any workers. A thread pool doesn't have any
59
+ # workers only in two cases: when it was closed or when all workers
60
+ # crashed. An *active* thread pool doesn't have any workers only when
61
+ # something went wrong.
62
+ #
63
+ # Workers are expected to crash when you +fork+ the process the workers are
64
+ # living in. In this case we detect a +fork+ and try to revive them here.
65
+ #
66
+ # Another possible scenario that crashes workers is when you close the
67
+ # instance on +at_exit+, but some other +at_exit+ hook prevents the process
68
+ # from exiting.
69
+ #
70
+ # @return [Boolean] true if an instance wasn't closed, but has no workers
71
+ # @see https://goo.gl/oydz8h Example of at_exit that prevents exit
72
+ def has_workers?
73
+ @mutex.synchronize do
74
+ return false if @closed
75
+
76
+ if @pid != Process.pid && @workers.list.empty?
77
+ @pid = Process.pid
78
+ spawn_workers
79
+ end
80
+
81
+ !@closed && @workers.list.any?
82
+ end
83
+ end
84
+
85
+ # Closes the thread pool making it a no-op (it shut downs all worker
86
+ # threads). Before closing, waits on all unprocessed tasks to be processed.
87
+ #
88
+ # @return [void]
89
+ # @raise [Airbrake::Error] when invoked more than one time
90
+ def close
91
+ threads = @mutex.synchronize do
92
+ raise Airbrake::Error, 'this thread pool is closed already' if @closed
93
+
94
+ unless @queue.empty?
95
+ msg = "#{LOG_LABEL} waiting to process #{@queue.size} task(s)..."
96
+ logger.debug(msg + ' (Ctrl-C to abort)')
97
+ end
98
+
99
+ @worker_size.times { @queue << :stop }
100
+ @closed = true
101
+ @workers.list.dup
102
+ end
103
+
104
+ threads.each(&:join)
105
+ logger.debug("#{LOG_LABEL} thread pool closed")
106
+ end
107
+
108
+ def closed?
109
+ @closed
110
+ end
111
+
112
+ def spawn_workers
113
+ @worker_size.times { @workers.add(spawn_worker) }
114
+ @workers.enclose
115
+ end
116
+
117
+ private
118
+
119
+ def spawn_worker
120
+ Thread.new do
121
+ while (message = @queue.pop)
122
+ break if message == :stop
123
+ @block.call(message)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,17 @@
1
+ module Airbrake
2
+ # TimeTruncate contains methods for truncating time.
3
+ #
4
+ # @api private
5
+ # @since v3.2.0
6
+ module TimeTruncate
7
+ # Truncate +time+ to floor minute and turn it into an RFC3339 timestamp.
8
+ #
9
+ # @param [Time] time
10
+ # @return [String]
11
+ def self.utc_truncate_minutes(time)
12
+ tm = time.getutc
13
+
14
+ Time.utc(tm.year, tm.month, tm.day, tm.hour, tm.min).to_datetime.rfc3339
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,58 @@
1
+ module Airbrake
2
+ # TimedTrace represents a chunk of code performance of which was measured and
3
+ # stored under a label. The chunk is called a "span".
4
+ #
5
+ # @example
6
+ # timed_trace = TimedTrace.new
7
+ # timed_trace.span('http request') do
8
+ # http.get('example.com')
9
+ # end
10
+ # timed_trace.spans #=> { 'http request' => 0.123 }
11
+ #
12
+ # @api public
13
+ # @since v4.3.0
14
+ class TimedTrace
15
+ # @param [String] label
16
+ # @return [Airbrake::TimedTrace]
17
+ def self.span(label, &block)
18
+ new.tap { |timed_trace| timed_trace.span(label, &block) }
19
+ end
20
+
21
+ def initialize
22
+ @spans = {}
23
+ end
24
+
25
+ # @param [String] label
26
+ # @return [Boolean]
27
+ def span(label)
28
+ start_span(label)
29
+ yield
30
+ stop_span(label)
31
+ end
32
+
33
+ # @param [String] label
34
+ # @return [Boolean]
35
+ def start_span(label)
36
+ return false if @spans.key?(label)
37
+
38
+ @spans[label] = Airbrake::Benchmark.new
39
+ true
40
+ end
41
+
42
+ # @param [String] label
43
+ # @return [Boolean]
44
+ def stop_span(label)
45
+ return false unless @spans.key?(label)
46
+
47
+ @spans[label].stop
48
+ true
49
+ end
50
+
51
+ # @return [Hash<String=>Float>]
52
+ def spans
53
+ @spans.each_with_object({}) do |(label, benchmark), new_spans|
54
+ new_spans[label] = benchmark.duration
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,115 @@
1
+ module Airbrake
2
+ # This class is responsible for truncation of too big objects. Mainly, you
3
+ # should use it for simple objects such as strings, hashes, & arrays.
4
+ #
5
+ # @api private
6
+ # @since v1.0.0
7
+ class Truncator
8
+ # @return [Hash] the options for +String#encode+
9
+ ENCODING_OPTIONS = { invalid: :replace, undef: :replace }.freeze
10
+
11
+ # @return [String] the temporary encoding to be used when fixing invalid
12
+ # strings with +ENCODING_OPTIONS+
13
+ TEMP_ENCODING = 'utf-16'.freeze
14
+
15
+ # @return [String] what to append when something is a circular reference
16
+ CIRCULAR = '[Circular]'.freeze
17
+
18
+ # @return [String] what to append when something is truncated
19
+ TRUNCATED = '[Truncated]'.freeze
20
+
21
+ # @return [Array<Class>] The types that can contain references to itself
22
+ CIRCULAR_TYPES = [Array, Hash, Set].freeze
23
+
24
+ # @param [Integer] max_size maximum size of hashes, arrays and strings
25
+ def initialize(max_size)
26
+ @max_size = max_size
27
+ end
28
+
29
+ # Performs deep truncation of arrays, hashes, sets & strings. Uses a
30
+ # placeholder for recursive objects (`[Circular]`).
31
+ #
32
+ # @param [Object] object The object to truncate
33
+ # @param [Set] seen The cache that helps to detect recursion
34
+ # @return [Object] truncated object
35
+ def truncate(object, seen = Set.new)
36
+ if seen.include?(object.object_id)
37
+ return CIRCULAR if CIRCULAR_TYPES.any? { |t| object.is_a?(t) }
38
+ return object
39
+ end
40
+ truncate_object(object, seen << object.object_id)
41
+ end
42
+
43
+ # Reduces maximum allowed size of hashes, arrays, sets & strings by half.
44
+ # @return [Integer] current +max_size+ value
45
+ def reduce_max_size
46
+ @max_size /= 2
47
+ end
48
+
49
+ private
50
+
51
+ def truncate_object(object, seen)
52
+ case object
53
+ when Hash then truncate_hash(object, seen)
54
+ when Array then truncate_array(object, seen)
55
+ when Set then truncate_set(object, seen)
56
+ when String then truncate_string(object)
57
+ when Numeric, TrueClass, FalseClass, Symbol, NilClass then object
58
+ else
59
+ truncate_string(stringify_object(object))
60
+ end
61
+ end
62
+
63
+ def truncate_string(str)
64
+ fixed_str = replace_invalid_characters(str)
65
+ return fixed_str if fixed_str.length <= @max_size
66
+ (fixed_str.slice(0, @max_size) + TRUNCATED).freeze
67
+ end
68
+
69
+ def stringify_object(object)
70
+ object.to_json
71
+ rescue *Notice::JSON_EXCEPTIONS
72
+ object.to_s
73
+ end
74
+
75
+ def truncate_hash(hash, seen)
76
+ truncated_hash = {}
77
+ hash.each_with_index do |(key, val), idx|
78
+ break if idx + 1 > @max_size
79
+ truncated_hash[key] = truncate(val, seen)
80
+ end
81
+
82
+ truncated_hash.freeze
83
+ end
84
+
85
+ def truncate_array(array, seen)
86
+ array.slice(0, @max_size).map! { |elem| truncate(elem, seen) }.freeze
87
+ end
88
+
89
+ def truncate_set(set, seen)
90
+ truncated_set = Set.new
91
+
92
+ set.each do |elem|
93
+ truncated_set << truncate(elem, seen)
94
+ break if truncated_set.size >= @max_size
95
+ end
96
+
97
+ truncated_set.freeze
98
+ end
99
+
100
+ # Replaces invalid characters in a string with arbitrary encoding.
101
+ #
102
+ # @param [String] str The string to replace characters
103
+ # @return [String] a UTF-8 encoded string
104
+ # @see https://github.com/flori/json/commit/3e158410e81f94dbbc3da6b7b35f4f64983aa4e3
105
+ def replace_invalid_characters(str)
106
+ encoding = str.encoding
107
+ utf8_string = (encoding == Encoding::UTF_8 || encoding == Encoding::ASCII)
108
+ return str if utf8_string && str.valid_encoding?
109
+
110
+ temp_str = str.dup
111
+ temp_str.encode!(TEMP_ENCODING, ENCODING_OPTIONS) if utf8_string
112
+ temp_str.encode!('utf-8', ENCODING_OPTIONS)
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,6 @@
1
+ # We use Semantic Versioning v2.0.0
2
+ # More information: http://semver.org/
3
+ module Airbrake
4
+ # @return [String] the library version
5
+ AIRBRAKE_RUBY_VERSION = '4.7.0'.freeze
6
+ end
@@ -0,0 +1,324 @@
1
+ RSpec.describe Airbrake do
2
+ it "gets initialized with a performance notifier" do
3
+ expect(described_class.performance_notifier).not_to be_nil
4
+ end
5
+
6
+ it "gets initialized with a notice notifier" do
7
+ expect(described_class.notice_notifier).not_to be_nil
8
+ end
9
+
10
+ it "gets initialized with a deploy notifier" do
11
+ expect(described_class.deploy_notifier).not_to be_nil
12
+ end
13
+
14
+ describe ".configure" do
15
+ before do
16
+ Airbrake::Config.instance = Airbrake::Config.new
17
+ described_class.reset
18
+ end
19
+
20
+ after { described_class.reset }
21
+
22
+ it "yields the config" do
23
+ expect do |b|
24
+ begin
25
+ described_class.configure(&b)
26
+ rescue Airbrake::Error
27
+ nil
28
+ end
29
+ end.to yield_with_args(Airbrake::Config)
30
+ end
31
+
32
+ it "sets logger to Airbrake::Loggable" do
33
+ logger = Logger.new(File::NULL)
34
+ described_class.configure do |c|
35
+ c.project_id = 1
36
+ c.project_key = '123'
37
+ c.logger = logger
38
+ end
39
+
40
+ expect(Airbrake::Loggable.instance).to eql(logger)
41
+ end
42
+
43
+ it "makes Airbrake configured" do
44
+ expect(described_class).not_to be_configured
45
+
46
+ described_class.configure do |c|
47
+ c.project_id = 1
48
+ c.project_key = '2'
49
+ end
50
+
51
+ expect(described_class).to be_configured
52
+ end
53
+
54
+ context "when a notifier was configured" do
55
+ before do
56
+ expect(described_class).to receive(:configured?).and_return(true)
57
+ end
58
+
59
+ it "closes previously configured notice notifier" do
60
+ expect(described_class).to receive(:close)
61
+ described_class.configure {}
62
+ end
63
+ end
64
+
65
+ context "when a notifier wasn't configured" do
66
+ before do
67
+ expect(described_class).to receive(:configured?).and_return(false)
68
+ end
69
+
70
+ it "doesn't close previously configured notice notifier" do
71
+ expect(described_class).not_to receive(:close)
72
+ described_class.configure {}
73
+ end
74
+ end
75
+
76
+ context "when called multiple times" do
77
+ it "doesn't overwrite performance notifier" do
78
+ described_class.configure {}
79
+ performance_notifier = described_class.performance_notifier
80
+
81
+ described_class.configure {}
82
+ expect(described_class.performance_notifier).to eql(performance_notifier)
83
+ end
84
+
85
+ it "doesn't overwrite notice notifier" do
86
+ described_class.configure {}
87
+ notice_notifier = described_class.notice_notifier
88
+
89
+ described_class.configure {}
90
+ expect(described_class.notice_notifier).to eql(notice_notifier)
91
+ end
92
+
93
+ it "doesn't overwrite deploy notifier" do
94
+ described_class.configure {}
95
+ deploy_notifier = described_class.deploy_notifier
96
+
97
+ described_class.configure {}
98
+ expect(described_class.deploy_notifier).to eql(deploy_notifier)
99
+ end
100
+ end
101
+
102
+ context "when blacklist_keys gets configured" do
103
+ before { allow(Airbrake.notice_notifier).to receive(:add_filter) }
104
+
105
+ it "adds blacklist filter" do
106
+ expect(Airbrake.notice_notifier).to receive(:add_filter)
107
+ .with(an_instance_of(Airbrake::Filters::KeysBlacklist))
108
+ described_class.configure { |c| c.blacklist_keys = %w[password] }
109
+ end
110
+
111
+ it "initializes blacklist with specified parameters" do
112
+ expect(Airbrake::Filters::KeysBlacklist).to receive(:new).with(%w[password])
113
+ described_class.configure { |c| c.blacklist_keys = %w[password] }
114
+ end
115
+ end
116
+
117
+ context "when whitelist_keys gets configured" do
118
+ before { allow(Airbrake.notice_notifier).to receive(:add_filter) }
119
+
120
+ it "adds whitelist filter" do
121
+ expect(Airbrake.notice_notifier).to receive(:add_filter)
122
+ .with(an_instance_of(Airbrake::Filters::KeysWhitelist))
123
+ described_class.configure { |c| c.whitelist_keys = %w[banana] }
124
+ end
125
+
126
+ it "initializes whitelist with specified parameters" do
127
+ expect(Airbrake::Filters::KeysWhitelist).to receive(:new).with(%w[banana])
128
+ described_class.configure { |c| c.whitelist_keys = %w[banana] }
129
+ end
130
+ end
131
+
132
+ context "when root_directory gets configured" do
133
+ before { allow(Airbrake.notice_notifier).to receive(:add_filter) }
134
+
135
+ it "adds root directory filter" do
136
+ expect(Airbrake.notice_notifier).to receive(:add_filter)
137
+ .with(an_instance_of(Airbrake::Filters::RootDirectoryFilter))
138
+ described_class.configure { |c| c.root_directory = '/my/path' }
139
+ end
140
+
141
+ it "initializes root directory filter with specified path" do
142
+ expect(Airbrake::Filters::RootDirectoryFilter)
143
+ .to receive(:new).with('/my/path')
144
+ described_class.configure { |c| c.root_directory = '/my/path' }
145
+ end
146
+
147
+ it "adds git revision filter" do
148
+ expect(Airbrake.notice_notifier).to receive(:add_filter)
149
+ .with(an_instance_of(Airbrake::Filters::GitRevisionFilter))
150
+ described_class.configure { |c| c.root_directory = '/my/path' }
151
+ end
152
+
153
+ it "initializes git revision filter with correct root directory" do
154
+ expect(Airbrake::Filters::GitRevisionFilter)
155
+ .to receive(:new).with('/my/path')
156
+ described_class.configure { |c| c.root_directory = '/my/path' }
157
+ end
158
+
159
+ it "adds git repository filter" do
160
+ expect(Airbrake.notice_notifier).to receive(:add_filter)
161
+ .with(an_instance_of(Airbrake::Filters::GitRepositoryFilter))
162
+ described_class.configure { |c| c.root_directory = '/my/path' }
163
+ end
164
+
165
+ it "initializes git repository filter with correct root directory" do
166
+ expect(Airbrake::Filters::GitRepositoryFilter)
167
+ .to receive(:new).with('/my/path')
168
+ described_class.configure { |c| c.root_directory = '/my/path' }
169
+ end
170
+
171
+ it "adds git last checkout filter" do
172
+ expect(Airbrake.notice_notifier).to receive(:add_filter)
173
+ .with(an_instance_of(Airbrake::Filters::GitLastCheckoutFilter))
174
+ described_class.configure { |c| c.root_directory = '/my/path' }
175
+ end
176
+
177
+ it "initializes git last checkout filter with correct root directory" do
178
+ expect(Airbrake::Filters::GitLastCheckoutFilter)
179
+ .to receive(:new).with('/my/path')
180
+ described_class.configure { |c| c.root_directory = '/my/path' }
181
+ end
182
+ end
183
+ end
184
+
185
+ describe "#reset" do
186
+ context "when Airbrake was previously configured" do
187
+ before do
188
+ expect(described_class).to receive(:configured?).and_return(true)
189
+ end
190
+
191
+ it "closes notice notifier" do
192
+ expect(described_class).to receive(:close)
193
+ subject.reset
194
+ end
195
+ end
196
+ end
197
+
198
+ describe "#notify_request" do
199
+ context "when :stash key is not provided" do
200
+ it "doesn't add anything to the stash of the request" do
201
+ expect(described_class.performance_notifier).to receive(:notify) do |request|
202
+ expect(request.stash).to be_empty
203
+ end
204
+
205
+ described_class.notify_request(
206
+ method: 'GET',
207
+ route: '/',
208
+ status_code: 200,
209
+ start_time: Time.now
210
+ )
211
+ end
212
+ end
213
+
214
+ context "when :stash key is provided" do
215
+ it "adds the value as the stash of the request" do
216
+ expect(described_class.performance_notifier).to receive(:notify) do |request|
217
+ expect(request.stash).to eq(request_id: 1)
218
+ end
219
+
220
+ described_class.notify_request(
221
+ {
222
+ method: 'GET',
223
+ route: '/',
224
+ status_code: 200,
225
+ start_time: Time.now
226
+ },
227
+ request_id: 1
228
+ )
229
+ end
230
+ end
231
+ end
232
+
233
+ describe "#notify_query" do
234
+ context "when :stash key is not provided" do
235
+ it "doesn't add anything to the stash of the query" do
236
+ expect(described_class.performance_notifier).to receive(:notify) do |query|
237
+ expect(query.stash).to be_empty
238
+ end
239
+
240
+ described_class.notify_query(
241
+ method: 'GET',
242
+ route: '/',
243
+ query: '',
244
+ start_time: Time.now
245
+ )
246
+ end
247
+ end
248
+
249
+ context "when :stash key is provided" do
250
+ it "adds the value as the stash of the query" do
251
+ expect(described_class.performance_notifier).to receive(:notify) do |query|
252
+ expect(query.stash).to eq(request_id: 1)
253
+ end
254
+
255
+ described_class.notify_query(
256
+ {
257
+ method: 'GET',
258
+ route: '/',
259
+ query: '',
260
+ start_time: Time.now
261
+ },
262
+ request_id: 1
263
+ )
264
+ end
265
+ end
266
+ end
267
+
268
+ describe "#notify_performance_breakdown" do
269
+ context "when :stash key is not provided" do
270
+ it "doesn't add anything to the stash of the performance breakdown" do
271
+ expect(described_class.performance_notifier).to receive(:notify) do |query|
272
+ expect(query.stash).to be_empty
273
+ end
274
+
275
+ described_class.notify_query(
276
+ method: 'GET',
277
+ route: '/',
278
+ query: '',
279
+ start_time: Time.now
280
+ )
281
+ end
282
+ end
283
+
284
+ context "when :stash key is provided" do
285
+ it "adds the value as the stash of the performance breakdown" do
286
+ expect(
287
+ described_class.performance_notifier
288
+ ).to receive(:notify) do |performance_breakdown|
289
+ expect(performance_breakdown.stash).to eq(request_id: 1)
290
+ end
291
+
292
+ described_class.notify_performance_breakdown(
293
+ {
294
+ method: 'GET',
295
+ route: '/',
296
+ response_type: :html,
297
+ groups: {},
298
+ start_time: Time.now
299
+ },
300
+ request_id: 1
301
+ )
302
+ end
303
+ end
304
+ end
305
+
306
+ describe ".performance_notifier" do
307
+ it "returns a performance notifier" do
308
+ expect(described_class.performance_notifier)
309
+ .to be_an(Airbrake::PerformanceNotifier)
310
+ end
311
+ end
312
+
313
+ describe ".notice_notifier" do
314
+ it "returns a notice notifier" do
315
+ expect(described_class.notice_notifier).to be_an(Airbrake::NoticeNotifier)
316
+ end
317
+ end
318
+
319
+ describe ".deploy_notifier" do
320
+ it "returns a deploy notifier" do
321
+ expect(described_class.deploy_notifier).to be_an(Airbrake::DeployNotifier)
322
+ end
323
+ end
324
+ end