radar 0.1.0 → 0.2.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.
- data/.yardopts +1 -0
- data/CHANGELOG.md +21 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +21 -0
- data/README.md +1 -1
- data/docs/user_guide.md +105 -9
- data/lib/radar.rb +5 -0
- data/lib/radar/application.rb +15 -5
- data/lib/radar/config.rb +113 -19
- data/lib/radar/exception_event.rb +5 -3
- data/lib/radar/matchers/backtrace_matcher.rb +31 -0
- data/lib/radar/matchers/class_matcher.rb +30 -0
- data/lib/radar/reporter/file_reporter.rb +44 -2
- data/lib/radar/support.rb +26 -4
- data/lib/radar/version.rb +1 -1
- data/test/radar/application_test.rb +41 -13
- data/test/radar/config_test.rb +100 -5
- data/test/radar/exception_event_test.rb +19 -10
- data/test/radar/matchers/backtrace_matcher_test.rb +27 -0
- data/test/radar/matchers/class_matcher_test.rb +26 -0
- data/test/radar/reporter/file_reporter_test.rb +4 -0
- data/test/radar/support_test.rb +16 -0
- data/test/test_helper.rb +3 -2
- metadata +10 -4
data/.yardopts
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
## 0.2.0 (unreleased)
|
2
|
+
|
3
|
+
- Built in matcher: `:backtrace` (or `BacktraceMatcher`) which checks that
|
4
|
+
the backtrace includes the given text. [GH-18]
|
5
|
+
- Built in matcher: `:class` (or `ClassMatcher`) which checks against the
|
6
|
+
exception class. [GH-17]
|
7
|
+
- Add `config.match` to conditionally match exceptions before reporting
|
8
|
+
them so that exceptions can be better filtered per application. [GH-11]
|
9
|
+
- Changed the way reporters and data extensions are enabled. You must now
|
10
|
+
use methods `use`, `swap`, `insert`, `delete`, etc. See the user guide
|
11
|
+
for details. [GH-16]
|
12
|
+
- Added `prune_time` configuration to `FileReporter` which automatically prunes
|
13
|
+
files older than the specified age. [GH-15]
|
14
|
+
- Extra data in the form of a hash can be passed to `Application#report` now,
|
15
|
+
which is available in `ExceptionEvent#extra`, so that reporters and data
|
16
|
+
extensions can take advantage of this. [GH-14]
|
17
|
+
- Added LICENSE file. Oops!
|
18
|
+
|
19
|
+
## 0.1.0
|
20
|
+
|
21
|
+
- Initial version.
|
data/Gemfile.lock
CHANGED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2010 Mitchell Hashimoto
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
@@ -29,7 +29,7 @@ ask for any more information 90% of the time.
|
|
29
29
|
Then just begin logging exceptions in your application:
|
30
30
|
|
31
31
|
r = Radar::Application.new(:my_application)
|
32
|
-
r.config.
|
32
|
+
r.config.reporters.use Radar::Reporter::FileReporter
|
33
33
|
r.report(exception)
|
34
34
|
|
35
35
|
You can also tell Radar to attach itself to Ruby's `at_exit` hook
|
data/docs/user_guide.md
CHANGED
@@ -32,7 +32,7 @@ remote server, etc.). Radar comes with some built-in reporters. Below, we config
|
|
32
32
|
the application to log errors to a file (by default at `~/.radar/errors/my_application`):
|
33
33
|
|
34
34
|
Radar::Application.new(:my_application) do |app|
|
35
|
-
app.config.
|
35
|
+
app.config.reporters.use Radar::Reporter::FileReporter
|
36
36
|
end
|
37
37
|
|
38
38
|
### Reporting Errors
|
@@ -80,15 +80,15 @@ of what this means with a few examples:
|
|
80
80
|
Reporters are enabled using the appilication configuration:
|
81
81
|
|
82
82
|
Radar::Application.new(:my_application) do |app|
|
83
|
-
app.config.
|
83
|
+
app.config.reporters.use FileReporter
|
84
84
|
end
|
85
85
|
|
86
86
|
And can be configured by passing a block to the reporter, which is yielded with
|
87
87
|
the instance of that reporter:
|
88
88
|
|
89
89
|
Radar::Application.new(:my_application) do |app|
|
90
|
-
app.config.
|
91
|
-
reporter.
|
90
|
+
app.config.reporters.use FileReporter do |reporter|
|
91
|
+
reporter.output_directory = "~/.radar/exceptions"
|
92
92
|
end
|
93
93
|
end
|
94
94
|
|
@@ -96,8 +96,8 @@ Radar also allows multiple reporters to be used, which are then called
|
|
96
96
|
in the order they are defined when an exception occurs:
|
97
97
|
|
98
98
|
Radar::Application.new(:my_application) do |app|
|
99
|
-
app.config.
|
100
|
-
app.config.
|
99
|
+
app.config.reporters.use FileReporter
|
100
|
+
app.config.reporters.use AnotherReporter
|
101
101
|
end
|
102
102
|
|
103
103
|
### Built-in Reporters
|
@@ -112,7 +112,7 @@ where `timestamp` is the time that the exception occurred and `uniquehash` is th
|
|
112
112
|
The directory where these files will be stored is configurable:
|
113
113
|
|
114
114
|
Radar::Application.new(:my_application) do |app|
|
115
|
-
app.config.
|
115
|
+
app.config.reporters.use Radar::Reporter::FileReporter do |reporter|
|
116
116
|
reporter.output_directory = "~/my_application_errors"
|
117
117
|
end
|
118
118
|
end
|
@@ -130,6 +130,9 @@ A few notes:
|
|
130
130
|
JSON and pretty print it if you wish it to be easily human readable. There are
|
131
131
|
[services](http://jsonformatter.curiousconcept.com/) out there to do this.
|
132
132
|
|
133
|
+
For complete documentation on this reporter, please see the actual {Radar::Reporter::FileReporter}
|
134
|
+
page.
|
135
|
+
|
133
136
|
### Custom Reporters
|
134
137
|
|
135
138
|
It is very easy to write custom reporters. A reporter is simply a class which
|
@@ -146,7 +149,7 @@ occurred:
|
|
146
149
|
And then using that reporter is just as easy:
|
147
150
|
|
148
151
|
Radar::Application.new(:my_application) do |app|
|
149
|
-
app.config.
|
152
|
+
app.config.reporters.use StdoutReporter
|
150
153
|
end
|
151
154
|
|
152
155
|
## Data Extensions
|
@@ -181,7 +184,7 @@ Data extensions are enabled via the application configuration like most other
|
|
181
184
|
things:
|
182
185
|
|
183
186
|
Radar::Application.new(:my_application) do |app|
|
184
|
-
app.config.
|
187
|
+
app.config.data_extensions.use UnameExtension
|
185
188
|
end
|
186
189
|
|
187
190
|
### Built-In Data Extensions
|
@@ -195,3 +198,96 @@ Some of these are enabled by default, which are designated by the `*` on the nam
|
|
195
198
|
host such as Ruby version and operating system.
|
196
199
|
|
197
200
|
`*`: Enabled by default on every application.
|
201
|
+
|
202
|
+
## Matchers
|
203
|
+
|
204
|
+
Matchers allow Radar applications to conditionally match exceptions so that
|
205
|
+
a Radar application doesn't catch unwanted exceptions, such as exceptions which
|
206
|
+
may not be caused by the library in question, or perhaps exceptions which aren't
|
207
|
+
really exceptional.
|
208
|
+
|
209
|
+
### Enabling a Matcher
|
210
|
+
|
211
|
+
Matchers are enabled in the application configuration:
|
212
|
+
|
213
|
+
Radar::Application.new(:app) do |app|
|
214
|
+
app.config.match :class, StandardError
|
215
|
+
app.config.match :backtrace, /file.rb$/
|
216
|
+
end
|
217
|
+
|
218
|
+
As you can see, multiple matchers may be enabled. In this case, as long as at
|
219
|
+
least one matches, then the exception will be reported. The first argument to
|
220
|
+
{Radar::Config#match match} is a symbol or class of a matcher. If it is a symbol,
|
221
|
+
the symbol is constantized and expects to exist under the `Radar::Matchers` namespace.
|
222
|
+
If it is a class, that class will be used as the matcher. Any additional arguments
|
223
|
+
are passed directly into the initializer of the matcher. For more information
|
224
|
+
on writing a custom matcher, see the section below.
|
225
|
+
|
226
|
+
If no matchers are specified (the default), then all exceptions are caught.
|
227
|
+
|
228
|
+
### Built-in Matchers
|
229
|
+
|
230
|
+
#### `:backtrace`
|
231
|
+
|
232
|
+
A matcher which matches against the backtrace of the exception. It allows:
|
233
|
+
|
234
|
+
* Match that a string is a substring of a line in the backtrace
|
235
|
+
* Match that a regexp matches a line in the backtrace
|
236
|
+
* Match one of the above up to a maximum depth in the backtrace
|
237
|
+
|
238
|
+
Examples of each are shown below (respective to the above order):
|
239
|
+
|
240
|
+
app.config.match :backtrace, "my_file.rb"
|
241
|
+
app.config.match :backtrace, /.+_test.rb/
|
242
|
+
app.config.match :backtrace, /.+_test.rb/, :depth => 5
|
243
|
+
|
244
|
+
If an exception doesn't have a backtrace (can happen if you don't actually
|
245
|
+
`raise` an exception, but instantiate one) then the matcher always returns
|
246
|
+
`false`.
|
247
|
+
|
248
|
+
#### `:class`
|
249
|
+
|
250
|
+
A matcher which matches against the class of the exception. It is configurable
|
251
|
+
so it can check against:
|
252
|
+
|
253
|
+
* An exact match
|
254
|
+
* Match class or any subclasses
|
255
|
+
* Match a regexp name of a class
|
256
|
+
|
257
|
+
Examples of each are shown below (in the above order):
|
258
|
+
|
259
|
+
app.config.match :class, StandardError
|
260
|
+
app.config.match :class, StandardError, :include_subclasses => true
|
261
|
+
app.config.match :class, /.*Error/
|
262
|
+
|
263
|
+
### Custom Matchers
|
264
|
+
|
265
|
+
Matchers are simply classes which respond to `matches?` which returns a boolean
|
266
|
+
noting if the given {Radar::ExceptionEvent} matches. If true, then the exception
|
267
|
+
is reported, otherwise other matchers are tried, or if there are no other matchers,
|
268
|
+
the exception is ignored.
|
269
|
+
|
270
|
+
Below is a simple custom matcher which only matches exceptions with the
|
271
|
+
configured message:
|
272
|
+
|
273
|
+
class ErrorMessageMatcher
|
274
|
+
def initialize(message)
|
275
|
+
@message = message
|
276
|
+
end
|
277
|
+
|
278
|
+
def matches?(event)
|
279
|
+
event.exception.message == @message
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
And the usage is shown below:
|
284
|
+
|
285
|
+
Radar::Application.new(:app) do |app|
|
286
|
+
app.config.match ErrorMessageMatcher, "sample message"
|
287
|
+
end
|
288
|
+
|
289
|
+
And this results in the following behavior:
|
290
|
+
|
291
|
+
raise "Hello, World" # not reported
|
292
|
+
raise "sample message" # reported since it matches the message
|
293
|
+
|
data/lib/radar.rb
CHANGED
data/lib/radar/application.rb
CHANGED
@@ -56,9 +56,13 @@ module Radar
|
|
56
56
|
#
|
57
57
|
# $app = Radar::Application.new
|
58
58
|
# $app.config do |config|
|
59
|
-
# config.
|
59
|
+
# config.reporters.use Radar::Reporter::FileReporter
|
60
60
|
# end
|
61
61
|
#
|
62
|
+
# You can also just use it without a block:
|
63
|
+
#
|
64
|
+
# $app.config.reporters.use Radar::Reporter::FileReporter
|
65
|
+
#
|
62
66
|
# @yield [Config] Configuration object, only if block is given.
|
63
67
|
# @return [Config]
|
64
68
|
def config
|
@@ -68,14 +72,20 @@ module Radar
|
|
68
72
|
end
|
69
73
|
|
70
74
|
# Reports an exception. This will send the exception on to the
|
71
|
-
# various reporters configured for this application.
|
75
|
+
# various reporters configured for this application. If any
|
76
|
+
# matchers are defined, using {Config#match}, then at least one
|
77
|
+
# must match for the report to go forward to reporters.
|
72
78
|
#
|
73
79
|
# @param [Exception] exception
|
74
|
-
def report(exception)
|
75
|
-
data = ExceptionEvent.new(self, exception)
|
80
|
+
def report(exception, extra=nil)
|
81
|
+
data = ExceptionEvent.new(self, exception, extra)
|
82
|
+
|
83
|
+
# If there are matchers, then verify that at least one matches
|
84
|
+
# before continuing
|
85
|
+
return if !config.matchers.empty? && !config.matchers.values.find { |m| m.matches?(data) }
|
76
86
|
|
77
87
|
# Report the exception to each of the reporters
|
78
|
-
config.reporters.each do |reporter|
|
88
|
+
config.reporters.values.each do |reporter|
|
79
89
|
reporter.report(data)
|
80
90
|
end
|
81
91
|
end
|
data/lib/radar/config.rb
CHANGED
@@ -1,35 +1,129 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
1
3
|
module Radar
|
4
|
+
# The configuration class used for applications. To configure your application
|
5
|
+
# see {Application#config}. This is also where all the examples are.
|
2
6
|
class Config
|
3
7
|
attr_reader :reporters
|
4
8
|
attr_reader :data_extensions
|
9
|
+
attr_reader :matchers
|
5
10
|
|
6
11
|
def initialize
|
7
|
-
@reporters =
|
8
|
-
|
12
|
+
@reporters = UseArray.new do |klass, &block|
|
13
|
+
instance = klass.new
|
14
|
+
block.call(instance) if block
|
15
|
+
[klass, instance]
|
16
|
+
end
|
17
|
+
|
18
|
+
@data_extensions = UseArray.new
|
19
|
+
@data_extensions.use DataExtensions::HostEnvironment
|
20
|
+
|
21
|
+
@matchers = UseArray.new do |matcher, *args|
|
22
|
+
matcher = Support::Inflector.constantize("Radar::Matchers::" + Support::Inflector.camelize(matcher)) if !matcher.is_a?(Class)
|
23
|
+
[matcher, matcher.new(*args)]
|
24
|
+
end
|
9
25
|
end
|
10
26
|
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
27
|
+
# Adds a matcher rule to the application. An application will only
|
28
|
+
# report an exception if the event agrees with at least one of the
|
29
|
+
# matchers.
|
30
|
+
#
|
31
|
+
# To use a matcher, there are two options. The first is to use a
|
32
|
+
# symbol for the name:
|
33
|
+
#
|
34
|
+
# config.match :class, StandardError
|
14
35
|
#
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
36
|
+
# This will cause Radar to search for a class named "ClassMatcher"
|
37
|
+
# under the namespace {Radar::Matchers}.
|
38
|
+
#
|
39
|
+
# A second option is to use a class itself:
|
40
|
+
#
|
41
|
+
# config.match Radar::Matchers::ClassMatcher, StandardError
|
42
|
+
#
|
43
|
+
# Radar will then use the specified class as the matcher.
|
44
|
+
#
|
45
|
+
def match(matcher, *args)
|
46
|
+
@matchers.use(matcher, *args)
|
20
47
|
end
|
48
|
+
end
|
21
49
|
|
22
|
-
|
23
|
-
#
|
24
|
-
# in
|
25
|
-
#
|
50
|
+
class Config
|
51
|
+
# A subclass of Array which allows for slightly different usage, based
|
52
|
+
# on `ActionDispatch::MiddlewareStack` in Rails 3. The main methods are
|
53
|
+
# enumerated below:
|
26
54
|
#
|
27
|
-
#
|
28
|
-
#
|
55
|
+
# - {#use}
|
56
|
+
# - {#insert}
|
57
|
+
# - {#insert_before}
|
58
|
+
# - {#insert_after}
|
59
|
+
# - {#swap}
|
60
|
+
# - {#delete}
|
29
61
|
#
|
30
|
-
|
31
|
-
|
32
|
-
|
62
|
+
class UseArray
|
63
|
+
extend Forwardable
|
64
|
+
def_delegators :@_array, :empty?, :length, :clear
|
65
|
+
|
66
|
+
# Initializes the UseArray with the given block used to generate
|
67
|
+
# the value created for the {#use} method. The block given determines
|
68
|
+
# how the {#use} method stores the key/value.
|
69
|
+
def initialize(*args, &block)
|
70
|
+
@_array = []
|
71
|
+
@_use_block = block || Proc.new { |key, *args| [key, key] }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Use the given key. It is up to the configured use block (given by
|
75
|
+
# the initializer) to generate the actual key/value stored in the array.
|
76
|
+
def use(*args, &block)
|
77
|
+
insert(length, *args, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Insert the given key at the given index or directly before the
|
81
|
+
# given object (by key).
|
82
|
+
def insert(key, *args, &block)
|
83
|
+
@_array.insert(index(key), @_use_block.call(*args, &block))
|
84
|
+
end
|
85
|
+
alias_method :insert_before, :insert
|
86
|
+
|
87
|
+
# Insert after the given key.
|
88
|
+
def insert_after(key, *args, &block)
|
89
|
+
i = index(key)
|
90
|
+
raise ArgumentError.new("No such key found: #{key}") if !i
|
91
|
+
insert(i + 1, *args, &block)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Swaps out the given object at the given index or key with a new
|
95
|
+
# object.
|
96
|
+
def swap(key, *args, &block)
|
97
|
+
i = index(key)
|
98
|
+
raise ArgumentError.new("No such key found: #{key}") if !i
|
99
|
+
delete(i)
|
100
|
+
insert(i, *args, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Delete the object with the given key or index.
|
104
|
+
def delete(key)
|
105
|
+
@_array.delete_at(index(key))
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns the value for the given key. If the key is an integer,
|
109
|
+
# it is returned as-is. Otherwise, do a lookup on the array for the
|
110
|
+
# the given key and return the index of it.
|
111
|
+
def index(key)
|
112
|
+
return key if key.is_a?(Integer)
|
113
|
+
@_array.each_with_index do |data, i|
|
114
|
+
return i if data[0] == key
|
115
|
+
end
|
116
|
+
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns the values of this array.
|
121
|
+
def values
|
122
|
+
@_array.inject([]) do |acc, data|
|
123
|
+
acc << data[1]
|
124
|
+
acc
|
125
|
+
end
|
126
|
+
end
|
33
127
|
end
|
34
128
|
end
|
35
129
|
end
|
@@ -9,16 +9,18 @@ module Radar
|
|
9
9
|
attr_reader :application
|
10
10
|
attr_reader :exception
|
11
11
|
attr_reader :occurred_at
|
12
|
+
attr_reader :extra
|
12
13
|
|
13
|
-
def initialize(application, exception)
|
14
|
+
def initialize(application, exception, extra=nil)
|
14
15
|
@application = application
|
15
16
|
@exception = exception
|
17
|
+
@extra = extra || {}
|
16
18
|
@occurred_at = Time.now
|
17
19
|
end
|
18
20
|
|
19
21
|
# A hash of information about this exception event. This includes
|
20
22
|
# {Application#to_hash} as well as information about the exception.
|
21
|
-
# This also includes any {Config#
|
23
|
+
# This also includes any {Config#data_extensions data_extensions} if
|
22
24
|
# specified.
|
23
25
|
#
|
24
26
|
# @return [Hash]
|
@@ -34,7 +36,7 @@ module Radar
|
|
34
36
|
}
|
35
37
|
|
36
38
|
if !application.config.data_extensions.empty?
|
37
|
-
application.config.data_extensions.each do |extension|
|
39
|
+
application.config.data_extensions.values.each do |extension|
|
38
40
|
Support::Hash.deep_merge!(result, extension.new(self).to_hash)
|
39
41
|
end
|
40
42
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Radar
|
2
|
+
module Matchers
|
3
|
+
# A matcher which matches exceptions which contain a certain
|
4
|
+
# file in their backtrace.
|
5
|
+
#
|
6
|
+
# app.config.match :backtrace, "my_file"
|
7
|
+
# app.config.match :backtrace, %r{lib/my_application}
|
8
|
+
# app.config.match :backtrace, %r{lib/my_application}, :depth => 5
|
9
|
+
#
|
10
|
+
# By default this will search the entire backtrace, unless a depth
|
11
|
+
# is specified.
|
12
|
+
class BacktraceMatcher
|
13
|
+
def initialize(file, opts=nil)
|
14
|
+
@file = file
|
15
|
+
@opts = { :depth => nil }.merge(opts || {})
|
16
|
+
end
|
17
|
+
|
18
|
+
def matches?(event)
|
19
|
+
return false if !event.exception.backtrace
|
20
|
+
|
21
|
+
event.exception.backtrace.each_with_index do |line, depth|
|
22
|
+
return true if @file.is_a?(Regexp) && line =~ @file
|
23
|
+
return true if @file.is_a?(String) && line.include?(@file)
|
24
|
+
return false if @opts[:depth] && @opts[:depth].to_i <= (depth + 1)
|
25
|
+
end
|
26
|
+
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Radar
|
2
|
+
module Matchers
|
3
|
+
# A matcher which matches exceptions with a specific class.
|
4
|
+
#
|
5
|
+
# app.config.match :class, StandardError
|
6
|
+
# app.config.match :class, StandardError, :include_subclasses => true
|
7
|
+
# app.config.match :class, /.*Error/
|
8
|
+
#
|
9
|
+
class ClassMatcher
|
10
|
+
def initialize(klass, opts=nil)
|
11
|
+
@klass = klass
|
12
|
+
@opts = { :include_subclasses => false }.merge(opts || {})
|
13
|
+
end
|
14
|
+
|
15
|
+
def matches?(event)
|
16
|
+
return event.exception.class.to_s =~ @klass if @klass.is_a?(Regexp)
|
17
|
+
return event.exception.class == @klass if !@opts[:include_subclasses]
|
18
|
+
|
19
|
+
# Check for subclass matches
|
20
|
+
current = event.exception.class
|
21
|
+
while current
|
22
|
+
return true if current == @klass
|
23
|
+
current = current.superclass
|
24
|
+
end
|
25
|
+
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -5,25 +5,67 @@ module Radar
|
|
5
5
|
# Reports exceptions by dumping the JSON data out to a file on the
|
6
6
|
# local filesystem. The reporter is configurable:
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# ## Configurable Values
|
9
|
+
#
|
10
|
+
# ### `output_directory`
|
11
|
+
#
|
12
|
+
# Specifies the directory where the outputted files are stored. This value
|
13
|
+
# can be either a string or a lambda which takes an {ExceptionEvent} as its
|
14
|
+
# only parameter. The reporter will automatically attempt to make the configured
|
15
|
+
# directory if it doesn't already exist. Examples of both methods of specifying
|
16
|
+
# the directory are shown below:
|
17
|
+
#
|
18
|
+
# reporter.output_directory = "~/hard/coded/path"
|
19
|
+
#
|
20
|
+
# Or:
|
21
|
+
#
|
22
|
+
# reporter.output_directory = lambda { |event| "~/.radar/errors/#{event.application.name}" }
|
23
|
+
#
|
24
|
+
# ### `prune_time`
|
25
|
+
#
|
26
|
+
# Specifies the maximum age (in seconds) that a previously outputted file
|
27
|
+
# is allowed to reach before being pruned. When an exception is raised, the
|
28
|
+
# FileReporter will automatically prune existing files which are older than
|
29
|
+
# the specified amount. By default this is `nil` (no pruning occurs).
|
30
|
+
#
|
31
|
+
# # One week:
|
32
|
+
# reporter.prune_time = 60 * 60 * 24 * 7
|
9
33
|
#
|
10
34
|
class FileReporter
|
11
35
|
attr_accessor :output_directory
|
36
|
+
attr_accessor :prune_time
|
12
37
|
|
13
38
|
def initialize
|
14
39
|
@output_directory = lambda { |event| "~/.radar/errors/#{event.application.name}" }
|
40
|
+
@prune_time = nil
|
15
41
|
end
|
16
42
|
|
17
43
|
def report(event)
|
18
44
|
output_file = File.join(File.expand_path(output_directory(event)), "#{event.occurred_at.to_i}-#{event.uniqueness_hash}.txt")
|
45
|
+
directory = File.dirname(output_file)
|
19
46
|
|
20
47
|
# Attempt to make the directory if it doesn't exist
|
21
|
-
FileUtils.mkdir_p
|
48
|
+
FileUtils.mkdir_p directory
|
49
|
+
|
50
|
+
# Prune files if enabled
|
51
|
+
prune(directory) if prune_time
|
22
52
|
|
23
53
|
# Write out the JSON to the output file
|
24
54
|
File.open(output_file, 'w') { |f| f.write(event.to_json) }
|
25
55
|
end
|
26
56
|
|
57
|
+
# Prunes the files in the given directory according to the age limit
|
58
|
+
# set by the {#prune_time} variable.
|
59
|
+
#
|
60
|
+
# @param [String] directory Directory to prune
|
61
|
+
def prune(directory)
|
62
|
+
Dir[File.join(directory, "*.txt")].each do |file|
|
63
|
+
next unless File.file?(file)
|
64
|
+
next unless (Time.now.to_i - File.ctime(file).to_i) >= prune_time.to_i
|
65
|
+
File.delete(file)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
27
69
|
# Returns the currently configured output directory. If `event` is given
|
28
70
|
# as a parameter and the currently set directory is a lambda, then the
|
29
71
|
# lambda will be evaluated then returned. If no event is given, the lambda
|
data/lib/radar/support.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
module Radar
|
2
2
|
class Support
|
3
|
+
# Hash support methods:
|
4
|
+
#
|
5
|
+
# * {#deep_merge} and {#deep_merge!} - Does what it says: deep merges a
|
6
|
+
# hash with another hash. Taken from ActiveSupport in Rails 3.
|
7
|
+
#
|
3
8
|
class Hash
|
4
|
-
#----------------------------------------------------------------------
|
5
|
-
# Deep Merging - Taken from Ruby on Rails ActiveSupport
|
6
|
-
#----------------------------------------------------------------------
|
7
|
-
|
8
9
|
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
9
10
|
def self.deep_merge(source, other)
|
10
11
|
deep_merge!(source.dup, other)
|
@@ -21,5 +22,26 @@ module Radar
|
|
21
22
|
source
|
22
23
|
end
|
23
24
|
end
|
25
|
+
|
26
|
+
# Inflector methods:
|
27
|
+
#
|
28
|
+
# * {#camelize} - Convert a string or symbol to UpperCamelCase.
|
29
|
+
# * {#constantize} - Convert a string to a constant.
|
30
|
+
#
|
31
|
+
# Both of these inflector methods are taken directly from ActiveSupport
|
32
|
+
# in Rails 3.
|
33
|
+
class Inflector
|
34
|
+
def self.camelize(string)
|
35
|
+
string.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.constantize(camel_cased_word)
|
39
|
+
names = camel_cased_word.split('::')
|
40
|
+
names.shift if names.empty? || names.first.empty?
|
41
|
+
names.inject(Object) do |acc, name|
|
42
|
+
acc.const_defined?(name) ? acc.const_get(name) : acc.const_missing(name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
24
46
|
end
|
25
47
|
end
|
data/lib/radar/version.rb
CHANGED
@@ -49,13 +49,13 @@ class ApplicationTest < Test::Unit::TestCase
|
|
49
49
|
end
|
50
50
|
|
51
51
|
should "be able to configure an application" do
|
52
|
-
@instance.config.
|
52
|
+
@instance.config.reporters.use @reporter
|
53
53
|
assert !@instance.config.reporters.empty?
|
54
54
|
end
|
55
55
|
|
56
56
|
should "be able to configure using a block" do
|
57
57
|
@instance.config do |config|
|
58
|
-
config.
|
58
|
+
config.reporters.use @reporter
|
59
59
|
end
|
60
60
|
|
61
61
|
assert !@instance.config.reporters.empty?
|
@@ -63,21 +63,13 @@ class ApplicationTest < Test::Unit::TestCase
|
|
63
63
|
end
|
64
64
|
|
65
65
|
context "reporting" do
|
66
|
-
|
67
|
-
# The fake reporter class
|
66
|
+
should "call report on each registered reporter" do
|
68
67
|
reporter = Class.new do
|
69
|
-
def report(environment)
|
70
|
-
raise "success"
|
71
|
-
end
|
68
|
+
def report(environment); raise "success"; end
|
72
69
|
end
|
73
70
|
|
74
|
-
|
75
|
-
@instance.config do |config|
|
76
|
-
config.reporter reporter
|
77
|
-
end
|
78
|
-
end
|
71
|
+
@instance.config.reporters.use reporter
|
79
72
|
|
80
|
-
should "call report on each registered reporter" do
|
81
73
|
assert_raises(RuntimeError) do
|
82
74
|
begin
|
83
75
|
@instance.report(Exception.new)
|
@@ -87,6 +79,42 @@ class ApplicationTest < Test::Unit::TestCase
|
|
87
79
|
end
|
88
80
|
end
|
89
81
|
end
|
82
|
+
|
83
|
+
should "add extra data to the event if given" do
|
84
|
+
reporter = Class.new do
|
85
|
+
def report(event); raise event.extra[:foo]; end
|
86
|
+
end
|
87
|
+
|
88
|
+
@instance.config.reporters.use reporter
|
89
|
+
|
90
|
+
begin
|
91
|
+
@instance.report(Exception.new, :foo => "BAR")
|
92
|
+
rescue => e
|
93
|
+
assert_equal "BAR", e.message
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "with a matcher" do
|
98
|
+
setup do
|
99
|
+
@matcher = Class.new do
|
100
|
+
def matches?(event); event.extra[:foo] == :bar; end
|
101
|
+
end
|
102
|
+
|
103
|
+
@reporter = Class.new
|
104
|
+
@instance.config.reporters.use @reporter
|
105
|
+
@instance.config.match @matcher
|
106
|
+
end
|
107
|
+
|
108
|
+
should "not report if a matcher is specified and doesn't match" do
|
109
|
+
@reporter.any_instance.expects(:report).never
|
110
|
+
@instance.report(Exception.new, :foo => :wrong)
|
111
|
+
end
|
112
|
+
|
113
|
+
should "report if a matcher matches" do
|
114
|
+
@reporter.any_instance.expects(:report).once
|
115
|
+
@instance.report(Exception.new, :foo => :bar)
|
116
|
+
end
|
117
|
+
end
|
90
118
|
end
|
91
119
|
|
92
120
|
context "to_hash" do
|
data/test/radar/config_test.rb
CHANGED
@@ -21,14 +21,14 @@ class ConfigTest < Test::Unit::TestCase
|
|
21
21
|
end
|
22
22
|
|
23
23
|
should "be able to add reporters" do
|
24
|
-
@instance.
|
24
|
+
@instance.reporters.use @reporter_klass
|
25
25
|
assert !@instance.reporters.empty?
|
26
|
-
assert @instance.reporters.first.is_a?(@reporter_klass)
|
26
|
+
assert @instance.reporters.values.first.is_a?(@reporter_klass)
|
27
27
|
end
|
28
28
|
|
29
29
|
should "yield the reporter instance if a block is given" do
|
30
30
|
@reporter_klass.any_instance.expects(:some_method).once
|
31
|
-
@instance.
|
31
|
+
@instance.reporters.use @reporter_klass do |reporter|
|
32
32
|
reporter.some_method
|
33
33
|
end
|
34
34
|
end
|
@@ -47,13 +47,108 @@ class ConfigTest < Test::Unit::TestCase
|
|
47
47
|
end
|
48
48
|
|
49
49
|
should "initially have some data extensions" do
|
50
|
-
assert_equal [Radar::DataExtensions::HostEnvironment], @instance.data_extensions
|
50
|
+
assert_equal [Radar::DataExtensions::HostEnvironment], @instance.data_extensions.values
|
51
51
|
end
|
52
52
|
|
53
53
|
should "be able to add data extensions" do
|
54
|
-
@instance.
|
54
|
+
@instance.data_extensions.use @extension
|
55
55
|
assert !@instance.data_extensions.empty?
|
56
56
|
end
|
57
57
|
end
|
58
|
+
|
59
|
+
context "matchers" do
|
60
|
+
setup do
|
61
|
+
@matcher = Class.new do
|
62
|
+
def matches?(event); false; end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
teardown do
|
67
|
+
@instance.matchers.clear
|
68
|
+
end
|
69
|
+
|
70
|
+
should "initially have no matchers" do
|
71
|
+
assert @instance.matchers.empty?
|
72
|
+
end
|
73
|
+
|
74
|
+
should "be able to add matchers" do
|
75
|
+
@instance.match @matcher
|
76
|
+
assert !@instance.matchers.empty?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "UseArray class" do
|
82
|
+
setup do
|
83
|
+
@klass = Radar::Config::UseArray
|
84
|
+
@instance = @klass.new
|
85
|
+
end
|
86
|
+
|
87
|
+
should "allow inserting objects via use" do
|
88
|
+
assert @instance.empty?
|
89
|
+
@instance.use(:foo)
|
90
|
+
assert !@instance.empty?
|
91
|
+
end
|
92
|
+
|
93
|
+
should "store the length" do
|
94
|
+
assert_equal 0, @instance.length
|
95
|
+
@instance.use(:foo)
|
96
|
+
@instance.use(:bar)
|
97
|
+
assert_equal 2, @instance.length
|
98
|
+
end
|
99
|
+
|
100
|
+
should "allow inserting objects at specific indexes" do
|
101
|
+
@instance.use(:foo)
|
102
|
+
@instance.insert(0, :bar)
|
103
|
+
assert_equal [:bar, :foo], @instance.values
|
104
|
+
end
|
105
|
+
|
106
|
+
should "allow inserting objects at specified key" do
|
107
|
+
@instance.use(:foo)
|
108
|
+
@instance.insert_before(:foo, :bar)
|
109
|
+
assert_equal [:bar, :foo], @instance.values
|
110
|
+
end
|
111
|
+
|
112
|
+
should "allow inserting objects after specified key" do
|
113
|
+
@instance.use(:foo)
|
114
|
+
@instance.insert_after(:foo, :bar)
|
115
|
+
assert_equal [:foo, :bar], @instance.values
|
116
|
+
end
|
117
|
+
|
118
|
+
should "raise an exception if inserting after a nonexistent key" do
|
119
|
+
assert_raises(ArgumentError) {
|
120
|
+
@instance.insert_after(:foo, :bar)
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
should "allow swapping objects" do
|
125
|
+
@instance.use(:foo)
|
126
|
+
@instance.swap(:foo, :bar)
|
127
|
+
assert_equal :bar, @instance.values.first
|
128
|
+
end
|
129
|
+
|
130
|
+
should "allow deleting objects" do
|
131
|
+
@instance.use(:foo)
|
132
|
+
@instance.delete(:foo)
|
133
|
+
assert @instance.empty?
|
134
|
+
end
|
135
|
+
|
136
|
+
should "allow querying for the values in the array" do
|
137
|
+
@instance.use(:foo)
|
138
|
+
@instance.use(:bar)
|
139
|
+
assert_equal [:foo, :bar], @instance.values
|
140
|
+
end
|
141
|
+
|
142
|
+
should "return the index of the given items" do
|
143
|
+
@instance.use(:foo)
|
144
|
+
@instance.use(:bar)
|
145
|
+
|
146
|
+
assert_equal 0, @instance.index(:foo)
|
147
|
+
assert_equal 1, @instance.index(:bar)
|
148
|
+
end
|
149
|
+
|
150
|
+
should "return the numeric index untouched if given" do
|
151
|
+
assert_equal 12, @instance.index(12)
|
152
|
+
end
|
58
153
|
end
|
59
154
|
end
|
@@ -7,6 +7,24 @@ class ExceptionEventTest < Test::Unit::TestCase
|
|
7
7
|
@instance = create_exception_event
|
8
8
|
end
|
9
9
|
|
10
|
+
should "generate a uniqueness hash" do
|
11
|
+
assert @instance.uniqueness_hash, "should have generated a uniqueness hash"
|
12
|
+
end
|
13
|
+
|
14
|
+
should "have a timestamp of when the exception occurred" do
|
15
|
+
assert @instance.occurred_at
|
16
|
+
assert @instance.occurred_at.is_a?(Time)
|
17
|
+
end
|
18
|
+
|
19
|
+
should "not have extra data by default" do
|
20
|
+
assert @instance.extra.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
should "allow for extra data to be present" do
|
24
|
+
@instance = create_exception_event(:foo => :bar)
|
25
|
+
assert_equal :bar, @instance.extra[:foo]
|
26
|
+
end
|
27
|
+
|
10
28
|
context "to_hash" do
|
11
29
|
context "data extensions" do
|
12
30
|
setup do
|
@@ -15,7 +33,7 @@ class ExceptionEventTest < Test::Unit::TestCase
|
|
15
33
|
def to_hash; { :exception => { :foo => :bar } }; end
|
16
34
|
end
|
17
35
|
|
18
|
-
@instance.application.config.
|
36
|
+
@instance.application.config.data_extensions.use @extension
|
19
37
|
@result = @instance.to_hash
|
20
38
|
end
|
21
39
|
|
@@ -35,14 +53,5 @@ class ExceptionEventTest < Test::Unit::TestCase
|
|
35
53
|
assert_equal @instance.to_hash.to_json, @instance.to_json
|
36
54
|
end
|
37
55
|
end
|
38
|
-
|
39
|
-
should "generate a uniqueness hash" do
|
40
|
-
assert @instance.uniqueness_hash, "should have generated a uniqueness hash"
|
41
|
-
end
|
42
|
-
|
43
|
-
should "have a timestamp of when the exception occurred" do
|
44
|
-
assert @instance.occurred_at
|
45
|
-
assert @instance.occurred_at.is_a?(Time)
|
46
|
-
end
|
47
56
|
end
|
48
57
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class BacktraceMatcherTest < Test::Unit::TestCase
|
4
|
+
context "backtrace matcher class" do
|
5
|
+
setup do
|
6
|
+
@klass = Radar::Matchers::BacktraceMatcher
|
7
|
+
end
|
8
|
+
|
9
|
+
should "match if the backtrace matches a regexp" do
|
10
|
+
standard_event = create_exception_event { raise StandardError.new("An error") }
|
11
|
+
assert @klass.new(%r{/matchers/(.+)_test.rb}).matches?(standard_event)
|
12
|
+
assert !@klass.new(%r{/not_matchers/(.+)_test.rb}).matches?(standard_event)
|
13
|
+
end
|
14
|
+
|
15
|
+
should "match if the backtrace includes a substring" do
|
16
|
+
standard_event = create_exception_event { raise StandardError.new("An error") }
|
17
|
+
assert @klass.new("backtrace_matcher_test.rb").matches?(standard_event)
|
18
|
+
assert !@klass.new("not_real_matcher_test.rb").matches?(standard_event)
|
19
|
+
end
|
20
|
+
|
21
|
+
should "match only up to the specified depth" do
|
22
|
+
standard_event = create_exception_event { raise StandardError.new("An error") }
|
23
|
+
assert @klass.new("test_helper.rb", :depth => 5).matches?(standard_event)
|
24
|
+
assert !@klass.new("test_helper.rb", :depth => 1).matches?(standard_event)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ClassMatcherTest < Test::Unit::TestCase
|
4
|
+
context "class matcher class" do
|
5
|
+
setup do
|
6
|
+
@klass = Radar::Matchers::ClassMatcher
|
7
|
+
end
|
8
|
+
|
9
|
+
should "match if the class match exactly" do
|
10
|
+
standard_event = create_exception_event { raise StandardError.new("An error") }
|
11
|
+
assert @klass.new(RuntimeError).matches?(create_exception_event)
|
12
|
+
assert !@klass.new(RuntimeError).matches?(standard_event)
|
13
|
+
end
|
14
|
+
|
15
|
+
should "match regular expressions properly" do
|
16
|
+
standard_event = create_exception_event { raise StandardError.new("An error") }
|
17
|
+
assert @klass.new(/.*Error/).matches?(create_exception_event)
|
18
|
+
assert @klass.new(/.*Error/).matches?(standard_event)
|
19
|
+
end
|
20
|
+
|
21
|
+
should "match subclasses if specified" do
|
22
|
+
assert @klass.new(Exception, :include_subclasses => true).matches?(create_exception_event)
|
23
|
+
assert !@klass.new(Exception).matches?(create_exception_event)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -7,6 +7,10 @@ class FileReporterTest < Test::Unit::TestCase
|
|
7
7
|
@instance = @klass.new
|
8
8
|
end
|
9
9
|
|
10
|
+
should "default prune time to nil" do
|
11
|
+
assert @instance.prune_time.nil?
|
12
|
+
end
|
13
|
+
|
10
14
|
should "allow output directory to be a lambda" do
|
11
15
|
@instance.output_directory = lambda { |event| event.application.name }
|
12
16
|
event = create_exception_event
|
data/test/radar/support_test.rb
CHANGED
@@ -22,5 +22,21 @@ class SupportTest < Test::Unit::TestCase
|
|
22
22
|
assert_equal 2, result[:a][:b]
|
23
23
|
end
|
24
24
|
end
|
25
|
+
|
26
|
+
context "inflector" do
|
27
|
+
setup do
|
28
|
+
@klass = @klass::Inflector
|
29
|
+
end
|
30
|
+
|
31
|
+
should "camelize a string" do
|
32
|
+
assert_equal "ActiveRecord", @klass.camelize("active_record")
|
33
|
+
assert_equal "ActiveRecord::Errors", @klass.camelize("active_record/errors")
|
34
|
+
end
|
35
|
+
|
36
|
+
should "constantize a string" do
|
37
|
+
assert_equal Radar::Application, @klass.constantize("Radar::Application")
|
38
|
+
assert_equal Radar::Reporter::FileReporter, @klass.constantize("Radar::Reporter::FileReporter")
|
39
|
+
end
|
40
|
+
end
|
25
41
|
end
|
26
42
|
end
|
data/test/test_helper.rb
CHANGED
@@ -10,16 +10,17 @@ require "radar"
|
|
10
10
|
class Test::Unit::TestCase
|
11
11
|
# Returns a real {Radar::ExceptionEvent} object with a newly created
|
12
12
|
# {Radar::Application} and a valid (has a backtrace) exception.
|
13
|
-
def create_exception_event
|
13
|
+
def create_exception_event(extra=nil)
|
14
14
|
application = Radar::Application.new(:foo, false)
|
15
15
|
exception = nil
|
16
16
|
|
17
17
|
begin
|
18
|
+
yield if block_given?
|
18
19
|
raise "Something bad happened!"
|
19
20
|
rescue => e
|
20
21
|
exception = e
|
21
22
|
end
|
22
23
|
|
23
|
-
Radar::ExceptionEvent.new(application, exception)
|
24
|
+
Radar::ExceptionEvent.new(application, exception, extra)
|
24
25
|
end
|
25
26
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
7
|
+
- 2
|
8
8
|
- 0
|
9
|
-
version: 0.
|
9
|
+
version: 0.2.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Mitchell Hashimoto
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-08-
|
17
|
+
date: 2010-08-17 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -100,8 +100,10 @@ extra_rdoc_files: []
|
|
100
100
|
files:
|
101
101
|
- .gitignore
|
102
102
|
- .yardopts
|
103
|
+
- CHANGELOG.md
|
103
104
|
- Gemfile
|
104
105
|
- Gemfile.lock
|
106
|
+
- LICENSE
|
105
107
|
- README.md
|
106
108
|
- Rakefile
|
107
109
|
- docs/user_guide.md
|
@@ -111,6 +113,8 @@ files:
|
|
111
113
|
- lib/radar/data_extensions/host_environment.rb
|
112
114
|
- lib/radar/error.rb
|
113
115
|
- lib/radar/exception_event.rb
|
116
|
+
- lib/radar/matchers/backtrace_matcher.rb
|
117
|
+
- lib/radar/matchers/class_matcher.rb
|
114
118
|
- lib/radar/reporter.rb
|
115
119
|
- lib/radar/reporter/file_reporter.rb
|
116
120
|
- lib/radar/support.rb
|
@@ -120,6 +124,8 @@ files:
|
|
120
124
|
- test/radar/config_test.rb
|
121
125
|
- test/radar/data_extensions/host_environment_test.rb
|
122
126
|
- test/radar/exception_event_test.rb
|
127
|
+
- test/radar/matchers/backtrace_matcher_test.rb
|
128
|
+
- test/radar/matchers/class_matcher_test.rb
|
123
129
|
- test/radar/reporter/file_reporter_test.rb
|
124
130
|
- test/radar/reporter_test.rb
|
125
131
|
- test/radar/support_test.rb
|
@@ -138,7 +144,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
138
144
|
requirements:
|
139
145
|
- - ">="
|
140
146
|
- !ruby/object:Gem::Version
|
141
|
-
hash:
|
147
|
+
hash: -4581519538816896341
|
142
148
|
segments:
|
143
149
|
- 0
|
144
150
|
version: "0"
|