isolator 0.1.0.pre2 → 0.1.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.
- 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
|