airbrake-ruby 4.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 (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