isolator 0.1.0.pre2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +15 -0
- data/isolator.gemspec +2 -0
- data/lib/isolator.rb +33 -10
- data/lib/isolator/adapter_builder.rb +4 -3
- data/lib/isolator/adapters.rb +1 -0
- data/lib/isolator/adapters/after_commit.rb +7 -0
- data/lib/isolator/adapters/background_jobs/active_job.rb +4 -2
- data/lib/isolator/adapters/background_jobs/sidekiq.rb +4 -2
- data/lib/isolator/adapters/base.rb +23 -4
- data/lib/isolator/adapters/http.rb +2 -17
- data/lib/isolator/adapters/http/sniffer.rb +21 -0
- data/lib/isolator/adapters/http/webmock.rb +7 -0
- data/lib/isolator/adapters/mailers/mail.rb +2 -2
- data/lib/isolator/callbacks.rb +2 -0
- data/lib/isolator/isolate.rb +2 -2
- data/lib/isolator/notifier.rb +10 -6
- data/lib/isolator/railtie.rb +3 -2
- data/lib/isolator/version.rb +1 -1
- metadata +35 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a15f425ab4ff004a59412c6c5f74da93ac998f90c50d5131ed244baa3b631174
|
4
|
+
data.tar.gz: 511c6210a02513d4c922dc7b00b89ea887be9601b9cf2892b913fc59e81ff74a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6f2b7ca96011162f5436593bb42b60da26d720cfef028c7edee29abc703fb31a67d69d270c276a7c156265fb00e6f828b752f6957bec54f72440fafd762348d
|
7
|
+
data.tar.gz: c4ee00bd87e118a2d7c66ae90854f5cd3fc39a448cb30bb1a49aabd2eb11a75dc1579b47d9f95a11ccaabf37cc341a39b45906cd45d77d31159c5329d9e146af
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,14 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.1.0 (2018-02-19)
|
6
|
+
|
7
|
+
- Add `test_after_commit` patch. ([@palkan][])
|
8
|
+
|
9
|
+
- [PR [#7](https://github.com/palkan/isolator/pull/7)] Add WebMock adapter. ([@palkan][])
|
10
|
+
|
11
|
+
- Add `ignore_if` modifier to adapter. ([@palkan][])
|
12
|
+
|
5
13
|
- [PR [#5](https://github.com/palkan/isolator/pull/5)] Add `mail` adapter. ([@alexshgov][])
|
6
14
|
|
7
15
|
- Initial version. ([@palkan][], [@TheSmartnik][], [@alexshgov][])
|
data/README.md
CHANGED
@@ -109,6 +109,7 @@ Isolator has a bunch of built-in adapters:
|
|
109
109
|
- `:active_job`
|
110
110
|
- `:sidekiq`
|
111
111
|
- `:mailer`
|
112
|
+
- `:webmock` – track mocked HTTP requests (unseen by Sniffer) in tests
|
112
113
|
|
113
114
|
You can dynamically enable/disable adapters, e.g.:
|
114
115
|
|
@@ -121,6 +122,20 @@ Isolator.adapters.http.disable!
|
|
121
122
|
Isolator.adapters.http.enable!
|
122
123
|
```
|
123
124
|
|
125
|
+
### Ignore Offenses
|
126
|
+
|
127
|
+
Since Isolator adapter is just a wrapper over original code, it may lead to false positives when there is another library patching the same behaviour. In that case you might want to ignore some offenses.
|
128
|
+
|
129
|
+
Consider an example: we use Sidekiq along with [`sidekiq-postpone`](https://github.com/marshall-lee/sidekiq-postpone)–gem that patches `Sidekiq::Client#raw_push` and allows you to postpone jobs enqueueing (e.g. to enqueue everything when a transaction is commited–we don't want to raise exceptions in such situation).
|
130
|
+
|
131
|
+
To ignore offenses when `sidekiq-postpone` is active, you can add an ignore `proc`:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
Isolator.adapters.sidekiq.ignore_if { Thread.current[:sidekiq_postpone] }
|
135
|
+
```
|
136
|
+
|
137
|
+
You can add as many _ignores_ as you want, the offense is registered iff all of them return false.
|
138
|
+
|
124
139
|
## Custom Adapters
|
125
140
|
|
126
141
|
An adapter is just a combination of a _method wrapper_ and lifecycle hooks.
|
data/isolator.gemspec
CHANGED
@@ -32,4 +32,6 @@ Gem::Specification.new do |spec|
|
|
32
32
|
|
33
33
|
spec.add_development_dependency "uniform_notifier", "~> 1.11"
|
34
34
|
spec.add_development_dependency "sidekiq", "~> 5.0"
|
35
|
+
spec.add_development_dependency "webmock", "~> 3.1"
|
36
|
+
spec.add_development_dependency "test_after_commit", "~> 1.1"
|
35
37
|
end
|
data/lib/isolator.rb
CHANGED
@@ -37,6 +37,32 @@ module Isolator
|
|
37
37
|
Thread.current[:isolator_disabled] = true
|
38
38
|
end
|
39
39
|
|
40
|
+
# Accepts block and disable Isolator within
|
41
|
+
def disable
|
42
|
+
return yield if disabled?
|
43
|
+
res = nil
|
44
|
+
begin
|
45
|
+
disable!
|
46
|
+
res = yield
|
47
|
+
ensure
|
48
|
+
enable!
|
49
|
+
end
|
50
|
+
res
|
51
|
+
end
|
52
|
+
|
53
|
+
# Accepts block and enable Isolator within
|
54
|
+
def enable
|
55
|
+
return yield if enabled?
|
56
|
+
res = nil
|
57
|
+
begin
|
58
|
+
enable!
|
59
|
+
res = yield
|
60
|
+
ensure
|
61
|
+
disable!
|
62
|
+
end
|
63
|
+
res
|
64
|
+
end
|
65
|
+
|
40
66
|
def transactions_threshold
|
41
67
|
Thread.current.fetch(:isolator_threshold)
|
42
68
|
end
|
@@ -46,14 +72,12 @@ module Isolator
|
|
46
72
|
end
|
47
73
|
|
48
74
|
def incr_transactions!
|
49
|
-
return unless enabled?
|
50
75
|
Thread.current[:isolator_transactions] =
|
51
76
|
Thread.current.fetch(:isolator_transactions, 0) + 1
|
52
77
|
start! if Thread.current.fetch(:isolator_transactions) == transactions_threshold
|
53
78
|
end
|
54
79
|
|
55
80
|
def decr_transactions!
|
56
|
-
return unless enabled?
|
57
81
|
Thread.current[:isolator_transactions] =
|
58
82
|
Thread.current.fetch(:isolator_transactions) - 1
|
59
83
|
finish! if Thread.current.fetch(:isolator_transactions) == (transactions_threshold - 1)
|
@@ -68,7 +92,11 @@ module Isolator
|
|
68
92
|
end
|
69
93
|
|
70
94
|
def enabled?
|
71
|
-
|
95
|
+
!disabled?
|
96
|
+
end
|
97
|
+
|
98
|
+
def disabled?
|
99
|
+
Thread.current[:isolator_disabled] == true
|
72
100
|
end
|
73
101
|
|
74
102
|
def adapters
|
@@ -84,10 +112,5 @@ end
|
|
84
112
|
|
85
113
|
require "isolator/orm_adapters"
|
86
114
|
|
87
|
-
|
88
|
-
|
89
|
-
if defined?(Rails)
|
90
|
-
require "isolator/railtie"
|
91
|
-
else
|
92
|
-
require "isolator/adapters"
|
93
|
-
end
|
115
|
+
require "isolator/adapters"
|
116
|
+
require "isolator/railtie" if defined?(Rails)
|
@@ -5,7 +5,7 @@ require "isolator/adapters/base"
|
|
5
5
|
module Isolator
|
6
6
|
# Builds adapter from provided params
|
7
7
|
module AdapterBuilder
|
8
|
-
def self.call(target, method_name, **options)
|
8
|
+
def self.call(target: nil, method_name: nil, **options)
|
9
9
|
adapter = Module.new do
|
10
10
|
extend Isolator::Adapters::Base
|
11
11
|
|
@@ -13,14 +13,15 @@ module Isolator
|
|
13
13
|
self.exception_message = options[:exception_message] if options.key?(:exception_message)
|
14
14
|
end
|
15
15
|
|
16
|
-
add_patch_method
|
16
|
+
add_patch_method(adapter, target, method_name) if
|
17
|
+
target && method_name
|
17
18
|
adapter
|
18
19
|
end
|
19
20
|
|
20
21
|
def self.add_patch_method(adapter, base, method_name)
|
21
22
|
mod = Module.new do
|
22
23
|
define_method method_name do |*args, &block|
|
23
|
-
adapter.notify(caller)
|
24
|
+
adapter.notify(caller, *args)
|
24
25
|
super(*args, &block)
|
25
26
|
end
|
26
27
|
end
|
data/lib/isolator/adapters.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
Isolator.isolate :active_job,
|
4
|
-
:
|
3
|
+
Isolator.isolate :active_job,
|
4
|
+
target: ActiveJob::Base,
|
5
|
+
method_name: :enqueue,
|
6
|
+
exception_class: Isolator::BackgroundJobError
|
@@ -15,17 +15,36 @@ module Isolator
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def enabled?
|
18
|
-
|
18
|
+
!disabled?
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
21
|
+
def disabled?
|
22
|
+
@disabled == true
|
23
|
+
end
|
24
|
+
|
25
|
+
def notify(backtrace, *args)
|
26
|
+
return unless notify?(*args)
|
22
27
|
Isolator.notify(exception: build_exception, backtrace: backtrace)
|
23
28
|
end
|
24
29
|
|
25
|
-
def
|
26
|
-
enabled? && Isolator.within_transaction?
|
30
|
+
def notify?(*args)
|
31
|
+
enabled? && Isolator.enabled? && Isolator.within_transaction? && !ignored?(*args)
|
27
32
|
end
|
28
33
|
|
34
|
+
def ignore_if
|
35
|
+
ignores << Proc.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def ignores
|
39
|
+
@ignores ||= []
|
40
|
+
end
|
41
|
+
|
42
|
+
def ignored?(*args)
|
43
|
+
ignores.any? { |block| block.call(*args) }
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
29
48
|
def build_exception
|
30
49
|
klass = exception_class || Isolator::UnsafeOperationError
|
31
50
|
klass.new(exception_message)
|
@@ -1,20 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "sniffer"
|
3
|
+
require "isolator/adapters/http/sniffer"
|
4
4
|
|
5
|
-
|
6
|
-
# Disable Sniffer logger
|
7
|
-
c.logger = Logger.new(IO::NULL)
|
8
|
-
end
|
9
|
-
|
10
|
-
Isolator.isolate :http, Sniffer.singleton_class,
|
11
|
-
:store, exception_class: Isolator::HTTPError
|
12
|
-
|
13
|
-
Isolator.before_isolate do
|
14
|
-
Sniffer.enable!
|
15
|
-
end
|
16
|
-
|
17
|
-
Isolator.after_isolate do
|
18
|
-
Sniffer.clear!
|
19
|
-
Sniffer.disable!
|
20
|
-
end
|
5
|
+
require "isolator/adapters/http/webmock" if defined?(::WebMock)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sniffer"
|
4
|
+
|
5
|
+
# Disable Sniffer logger
|
6
|
+
Sniffer::Config.defaults["logger"] = Logger.new(IO::NULL)
|
7
|
+
|
8
|
+
Isolator.isolate :http, target: Sniffer.singleton_class,
|
9
|
+
method_name: :store,
|
10
|
+
exception_class: Isolator::HTTPError
|
11
|
+
|
12
|
+
Isolator.before_isolate do
|
13
|
+
next if Isolator.adapters.http.disabled?
|
14
|
+
Sniffer.enable!
|
15
|
+
end
|
16
|
+
|
17
|
+
Isolator.after_isolate do
|
18
|
+
next if Isolator.adapters.http.disabled?
|
19
|
+
Sniffer.clear!
|
20
|
+
Sniffer.disable!
|
21
|
+
end
|
data/lib/isolator/callbacks.rb
CHANGED
data/lib/isolator/isolate.rb
CHANGED
@@ -3,9 +3,9 @@
|
|
3
3
|
module Isolator
|
4
4
|
# Add .isolate function to build and register adapters
|
5
5
|
module Isolate
|
6
|
-
def isolate(id,
|
6
|
+
def isolate(id, **options)
|
7
7
|
raise "Adapter already registered: #{id}" if Isolator.adapters.key?(id.to_s)
|
8
|
-
adapter = AdapterBuilder.call(
|
8
|
+
adapter = AdapterBuilder.call(**options)
|
9
9
|
Isolator.adapters[id.to_s] = adapter
|
10
10
|
end
|
11
11
|
end
|
data/lib/isolator/notifier.rb
CHANGED
@@ -28,11 +28,15 @@ module Isolator
|
|
28
28
|
|
29
29
|
def log_exception
|
30
30
|
return unless Isolator.config.logger
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
|
32
|
+
offense_line = filtered_backtrace.first
|
33
|
+
|
34
|
+
msg = "[ISOLATOR EXCEPTION]\n" \
|
35
|
+
"#{exception.message}"
|
36
|
+
|
37
|
+
msg += "\n ↳ #{offense_line}" if offense_line
|
38
|
+
|
39
|
+
Isolator.config.logger.warn(msg)
|
36
40
|
end
|
37
41
|
|
38
42
|
def send_notifications
|
@@ -44,7 +48,7 @@ module Isolator
|
|
44
48
|
end
|
45
49
|
|
46
50
|
def filtered_backtrace
|
47
|
-
backtrace.reject { |line| line =~
|
51
|
+
backtrace.reject { |line| line =~ /\/(gems|ruby)/ }.take_while { |line| line !~ /ruby/ }
|
48
52
|
end
|
49
53
|
|
50
54
|
def uniform_notifier_loaded?
|
data/lib/isolator/railtie.rb
CHANGED
@@ -3,8 +3,9 @@
|
|
3
3
|
module Isolator
|
4
4
|
class Railtie < ::Rails::Railtie # :nodoc:
|
5
5
|
config.after_initialize do
|
6
|
-
#
|
7
|
-
|
6
|
+
# Forec load adapters after application initialization
|
7
|
+
# (when all deps are likely to be loaded).
|
8
|
+
load File.join(__dir__, "adapters.rb")
|
8
9
|
|
9
10
|
next unless Rails.env.test?
|
10
11
|
|
data/lib/isolator/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: isolator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Dementyev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-02-
|
11
|
+
date: 2018-02-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sniffer
|
@@ -150,6 +150,34 @@ dependencies:
|
|
150
150
|
- - "~>"
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '5.0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: webmock
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '3.1'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '3.1'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: test_after_commit
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '1.1'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '1.1'
|
153
181
|
description: Detect non-atomic interactions within DB transactions
|
154
182
|
email:
|
155
183
|
- dementiev.vm@gmail.com
|
@@ -174,11 +202,14 @@ files:
|
|
174
202
|
- lib/isolator.rb
|
175
203
|
- lib/isolator/adapter_builder.rb
|
176
204
|
- lib/isolator/adapters.rb
|
205
|
+
- lib/isolator/adapters/after_commit.rb
|
177
206
|
- lib/isolator/adapters/background_jobs.rb
|
178
207
|
- lib/isolator/adapters/background_jobs/active_job.rb
|
179
208
|
- lib/isolator/adapters/background_jobs/sidekiq.rb
|
180
209
|
- lib/isolator/adapters/base.rb
|
181
210
|
- lib/isolator/adapters/http.rb
|
211
|
+
- lib/isolator/adapters/http/sniffer.rb
|
212
|
+
- lib/isolator/adapters/http/webmock.rb
|
182
213
|
- lib/isolator/adapters/mailers.rb
|
183
214
|
- lib/isolator/adapters/mailers/mail.rb
|
184
215
|
- lib/isolator/callbacks.rb
|
@@ -209,9 +240,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
209
240
|
version: 2.3.0
|
210
241
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
211
242
|
requirements:
|
212
|
-
- - "
|
243
|
+
- - ">="
|
213
244
|
- !ruby/object:Gem::Version
|
214
|
-
version:
|
245
|
+
version: '0'
|
215
246
|
requirements: []
|
216
247
|
rubyforge_project:
|
217
248
|
rubygems_version: 2.7.4
|