radar 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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"
|