isolator 0.6.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5c027f6c2e795b8aeb009340efd8a920556cfb47553a06d93f4feab8d7c79de
4
- data.tar.gz: 3ae481ffdf7eba4d080402d44c7355c558363b0228053091ca96edc55bd26847
3
+ metadata.gz: 5c40700b0b2f7e5025ed7cbfb80580ac9ee1516a1295146bf0fa9db1de6e2a7f
4
+ data.tar.gz: 7d605d3790733c0b1bcd44e6188c30050900879042ef604950cc38b36d6214d0
5
5
  SHA512:
6
- metadata.gz: '008f26a3b4e3d059332e25a0b193dcec4586a17b6d6df9259b8b13e94a85e214e2c16aa37197a5490e92ba3cc288ce656c3ae1e1061e7ae7968ff4b967d327f1'
7
- data.tar.gz: 2a4237282734486458d04f45474eee2a75367724808f8079801645ffe978981725f6d46e6db1de041d8c36d2cd1a5832b7f09f44e2773c609013560bd9ee5904
6
+ metadata.gz: d3a05d4ccad1f80171a67f0f124cb2d37548803a5451338cb9d821c16042dc94c407f9a4a155597742268cbf8ee939594a166c49835f35f5f4d2ec4d1d9a0567
7
+ data.tar.gz: a287d291ca6465f3c8acfc8cb8c1c5ba898315c6aae80156855153d13a3c7c763864d97127ef38b04331bb7c99b23b417783eada86afdf8c26282c694dfe22bd
data/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.8.0 (2021-12-29)
6
+
7
+ - Drop Ruby 2.5 support.
8
+
9
+ - Add .isolator_ignore.yml configuration file for Rails application.
10
+
11
+ ## 0.7.0 (2020-09-25)
12
+
13
+ - Add debug mode. ([@palkan][])
14
+
15
+ Use `ISOLATOR_DEBUG=true` to turn on debug mode, which prints some useful information: when a transaction is tracked,
16
+ thresholds are changed, etc.
17
+
18
+ - Track transactions for different connections independently. ([@mquan][], [@palkan][])
19
+
20
+ This, for example, makes Isolator compatible with Rails multi-database apps.
21
+
22
+ - Allow custom ignorer usage. ([@iiwo][])
23
+
24
+ - `Isolator.load_ignore_config` is deprecated in favor of `Isolator::Ignorer.prepare`. ([@iiwo][])
25
+
26
+ ## 0.6.2 (2020-03-20)
27
+
28
+ - Make Sniffer version requirement open-ended. ([@palkan][])
29
+
30
+ - **Support Ruby 2.5+** ([@palkan][])
31
+
32
+ ## 0.6.1 (2019-09-06)
33
+
34
+ - Fix Sniffer integration. ([@palkan][])
35
+
36
+ Fixes [#21](https://github.com/palkan/isolator/issues/21).
37
+
5
38
  ## 0.6.0 (2019-04-12) 🚀
6
39
 
7
40
  - Add support for exceptions message details. ([@palkan][])
@@ -64,3 +97,5 @@
64
97
  [@Envek]: https://github.com/Envek
65
98
  [@DmitryTsepelev]: https://github.com/DmitryTsepelev
66
99
  [@shivanshgaur]: https://github.com/shivanshgaur
100
+ [@iiwo]: https://github.com/iiwo
101
+ [@mquan]: https://github.com/mquan
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018 Vladimir Dementyev
3
+ Copyright (c) 2018-2020 Vladimir Dementyev
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  [![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](http://cultofmartians.com/tasks/isolator.html)
2
2
  [![Gem Version](https://badge.fury.io/rb/isolator.svg)](https://badge.fury.io/rb/isolator)
3
- [![Build Status](https://travis-ci.org/palkan/isolator.svg?branch=master)](https://travis-ci.org/palkan/isolator)
3
+ ![Build](https://github.com/palkan/isolator/workflows/Build/badge.svg)
4
4
 
5
5
  # Isolator
6
6
 
@@ -79,6 +79,8 @@ However, there are some potential caveats:
79
79
 
80
80
  3) Isolator tries to detect the `test` environment and slightly change its behavior: first, it respect _transactional tests_; secondly, error raising is turned on by default (see [below](#configuration)).
81
81
 
82
+ 4) Experimental [multiple databases](https://guides.rubyonrails.org/active_record_multiple_databases.html) has been added in v0.7.0. Please, let us know if you encounter any issues.
83
+
82
84
  ### Configuration
83
85
 
84
86
  ```ruby
@@ -95,10 +97,14 @@ Isolator.configure do |config|
95
97
  # Customize backtrace filtering (provide a callable)
96
98
  # By default, just takes the top-5 lines
97
99
  config.backtrace_filter = ->(backtrace) { backtrace.take(5) }
100
+
101
+ # Define a custom ignorer class (must implement .prepare)
102
+ # uses a row number based list from the .isolator_todo.yml file
103
+ config.ignorer = Isolator::Ignorer
98
104
  end
99
105
  ```
100
106
 
101
- Isolator relys on [uniform_notifier][] to send custom notifications.
107
+ Isolator relies on [uniform_notifier][] to send custom notifications.
102
108
 
103
109
  **NOTE:** `uniform_notifier` should be installed separately (i.e., added to Gemfile).
104
110
 
@@ -109,13 +115,13 @@ Isolator relys on [uniform_notifier][] to send custom notifications.
109
115
 
110
116
  ### Supported ORMs
111
117
 
112
- - `ActiveRecord` >= 4.1
113
- - `ROM::SQL` (only if Active Support instrumentation extenstion is loaded)
118
+ - `ActiveRecord` >= 5.1 (4.2 likely till works, but we do not test against it anymore)
119
+ - `ROM::SQL` (only if Active Support instrumentation extension is loaded)
114
120
 
115
121
  ### Adapters
116
122
 
117
123
  Isolator has a bunch of built-in adapters:
118
- - `:http` – built on top of [Sniffer][]
124
+ - `:http` – built on top of [Sniffer][]
119
125
  - `:active_job`
120
126
  - `:sidekiq`
121
127
  - `:resque`
@@ -155,7 +161,7 @@ You can add as many _ignores_ as you want, the offense is registered iff all of
155
161
 
156
162
  ### Using with legacy Rails codebases
157
163
 
158
- If you already have a huge Rails project it can be a tricky to turn Isolator on because you'll immediately get a lot of failed specs. If you want to fix detected issues one by one, you can list all of them in the special file `.isolator_todo.yml` in a following way:
164
+ If you already have a huge Rails project it can be tricky to turn Isolator on because you'll immediately get a lot of failed specs. If you want to fix detected issues one by one, you can list all of them in the special files `.isolator_todo.yml` and `.isolator_ignore.yml` in the following way:
159
165
 
160
166
  ```
161
167
  sidekiq:
@@ -165,9 +171,11 @@ sidekiq:
165
171
 
166
172
  All the exceptions raised in the listed lines will be ignored.
167
173
 
174
+ The `.isolator_todo.yml` file is intended to point to the code that should be fixed later, and `.isolator_ignore.yml` points to the code that for some reasons is not expected to be fixed. (See https://github.com/palkan/isolator/issues/40)
175
+
168
176
  ### Using with legacy Ruby codebases
169
177
 
170
- If you are not using Rails, you'll have to load ignores from file manually, using `Isolator#load_ignore_config`, for instance `Isolator.load_ignore_config("./config/.isolator_todo.yml")`
178
+ If you are not using Rails, you'll have to load ignores from file manually, using `Isolator::Ignorer.prepare(path:)`, for instance `Isolator::Ignorer.prepare(path: "./config/.isolator_todo.yml")`
171
179
 
172
180
  ## Custom Adapters
173
181
 
@@ -183,23 +191,23 @@ Suppose that you have a class `Danger` with a method `#explode`, which is not sa
183
191
  # the third one is a method name.
184
192
  Isolator.isolate :danger, Danger, :explode, options
185
193
 
186
- # NOTE: if you want to isolate a class method, use signleton_class instead
194
+ # NOTE: if you want to isolate a class method, use singleton_class instead
187
195
  Isolator.isolate :danger, Danger.singleton_class, :explode, options
188
196
  ```
189
197
 
190
198
  Possible `options` are:
191
199
  - `exception_class` – an exception class to raise in case of offense
192
200
  - `exception_message` – custom exception message (could be specified without a class)
193
- - `details_message` – a block to generate additional exceptin message information:
201
+ - `details_message` – a block to generate additional exception message information:
194
202
 
195
203
  ```ruby
196
204
  Isolator.isolate :active_job,
197
- target: ActiveJob::Base,
198
- method_name: :enqueue,
199
- exception_class: Isolator::BackgroundJobError,
200
- details_message: ->(obj, _args) {
201
- "#{obj.class.name}(#{obj.arguments})"
202
- }
205
+ target: ActiveJob::Base,
206
+ method_name: :enqueue,
207
+ exception_class: Isolator::BackgroundJobError,
208
+ details_message: ->(obj, _args) {
209
+ "#{obj.class.name}(#{obj.arguments})"
210
+ }
203
211
  ```
204
212
 
205
213
  You can also add some callbacks to be run before and after the transaction:
@@ -210,10 +218,22 @@ Isolator.before_isolate do
210
218
  end
211
219
 
212
220
  Isolator.after_isolate do
213
- # right after the transaction has been committed/rollbacked
221
+ # right after the transaction has been committed/rolled back
214
222
  end
215
223
  ```
216
224
 
225
+ ## Troubleshooting
226
+
227
+ ### Verbose output
228
+
229
+ In most cases, turning on verbose output for Isolator helps to identify the issue. To do that, you can either specify `ISOLATOR_DEBUG=true` environment variable or set `Isolator.debug_enabled` manually.
230
+
231
+ ### Tests failing after upgrading to Rails 6.0.3 while using [Combustion](https://github.com/pat/combustion)
232
+
233
+ The reason is that Rails started using a [separate connection pool for advisory locks](https://github.com/rails/rails/pull/38235) since 6.0.3. Since Combustion usually applies migrations for every test run, this pool becomse visible to [test fixtures](https://github.com/rails/rails/blob/b738f1930f3c82f51741ef7241c1fee691d7deb2/activerecord/lib/active_record/test_fixtures.rb#L123-L127), which resulted in 2 transactional commits tracked by Isolator, which only expects one. That leads to false negatives.
234
+
235
+ To fix this disable migrations advisory locks by adding `advisory_locks: false` to your database configuration in `(spec|test)/internal/config/database.yml`.
236
+
217
237
  ## Contributing
218
238
 
219
239
  Bug reports and pull requests are welcome on GitHub at https://github.com/palkan/isolator.
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Isolator.isolate :active_job,
4
- target: ActiveJob::Base,
5
- method_name: :enqueue,
6
- exception_class: Isolator::BackgroundJobError,
7
- details_message: ->(obj, _args) {
8
- "#{obj.class.name}" \
9
- "#{obj.arguments.any? ? " (#{obj.arguments.join(', ')})" : ''}"
10
- }
4
+ target: ActiveJob::Base,
5
+ method_name: :enqueue,
6
+ exception_class: Isolator::BackgroundJobError,
7
+ details_message: ->(obj, _args) {
8
+ "#{obj.class.name}" \
9
+ "#{obj.arguments.any? ? " (#{obj.arguments.join(", ")})" : ""}"
10
+ }
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Isolator.isolate :resque,
4
- target: Resque.singleton_class,
5
- method_name: :enqueue,
6
- exception_class: Isolator::BackgroundJobError,
7
- details_message: ->(_obj, args) {
8
- args.join(", ")
9
- }
4
+ target: Resque.singleton_class,
5
+ method_name: :enqueue,
6
+ exception_class: Isolator::BackgroundJobError,
7
+ details_message: ->(_obj, args) {
8
+ args.join(", ")
9
+ }
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Isolator.isolate :resque_scheduler,
4
- target: Resque.singleton_class,
5
- method_name: :enqueue_at,
6
- exception_class: Isolator::BackgroundJobError,
7
- details_message: ->(_obj, (ts, *args)) {
8
- "#{args.join(', ')} (at #{ts})"
9
- }
4
+ target: Resque.singleton_class,
5
+ method_name: :enqueue_at,
6
+ exception_class: Isolator::BackgroundJobError,
7
+ details_message: ->(_obj, (ts, *args)) {
8
+ "#{args.join(", ")} (at #{ts})"
9
+ }
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Isolator.isolate :sidekiq,
4
- target: Sidekiq::Client,
5
- method_name: :raw_push,
6
- exception_class: Isolator::BackgroundJobError,
7
- details_message: ->(_obj, args) {
8
- args.first.map do |job|
9
- "#{job['class']} (#{job['args']})"
10
- end.join("\n")
11
- }
4
+ target: Sidekiq::Client,
5
+ method_name: :raw_push,
6
+ exception_class: Isolator::BackgroundJobError,
7
+ details_message: ->(_obj, args) {
8
+ args.first.map do |job|
9
+ "#{job["class"]} (#{job["args"]})"
10
+ end.join("\n")
11
+ }
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Isolator.isolate :sucker_punch,
4
- target: SuckerPunch::Queue.singleton_class,
5
- method_name: :find_or_create,
6
- exception_class: Isolator::BackgroundJobError,
7
- details_message: ->(_obj, args) {
8
- args.compact.join(", ")
9
- }
4
+ target: SuckerPunch::Queue.singleton_class,
5
+ method_name: :find_or_create,
6
+ exception_class: Isolator::BackgroundJobError,
7
+ details_message: ->(_obj, args) {
8
+ args.compact.join(", ")
9
+ }
@@ -31,8 +31,8 @@ module Isolator
31
31
  enabled? && Isolator.enabled? && Isolator.within_transaction? && !ignored?(*args)
32
32
  end
33
33
 
34
- def ignore_if
35
- ignores << Proc.new
34
+ def ignore_if(&block)
35
+ ignores << block
36
36
  end
37
37
 
38
38
  def ignores
@@ -10,7 +10,7 @@ Isolator.isolate :http, target: Sniffer.singleton_class,
10
10
  exception_class: Isolator::HTTPError,
11
11
  details_message: ->(_obj, args) {
12
12
  req = args.first.request
13
- "#{req[:method]} #{req[:host]}/#{req[:query]}"
13
+ "#{req.method} #{req.host}:#{req.port}#{req.query}"
14
14
  }
15
15
 
16
16
  Isolator.before_isolate do
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  adapter = Isolator.isolate :webmock,
4
- exception_class: Isolator::HTTPError,
5
- details_message: ->(obj, _args) {
6
- "#{obj.method.to_s.upcase} #{obj.uri}"
7
- }
4
+ exception_class: Isolator::HTTPError,
5
+ details_message: ->(obj, _args) {
6
+ "#{obj.method.to_s.upcase} #{obj.uri}"
7
+ }
8
8
 
9
9
  WebMock.after_request do |*args|
10
10
  adapter.notify(caller, *args)
@@ -5,25 +5,31 @@ module Isolator
5
5
  #
6
6
  # - `raise_exceptions` - whether to raise an exception in case of offense;
7
7
  # defaults to true in test env and false otherwise.
8
- # NOTE: env is infered from RACK_ENV and RAILS_ENV.
8
+ # NOTE: env is inferred from RACK_ENV and RAILS_ENV.
9
9
  #
10
10
  # - `logger` - logger instance (nil by default)
11
11
  #
12
12
  # - `send_notifications` - whether to send notifications (through uniform_notifier);
13
- # defauls to false
13
+ # defaults to false
14
+ #
15
+ # - `backtrace_filter` - define a custom backtrace filtering (provide a callable)
16
+ #
17
+ # - `ignorer` - define a custom ignorer (must implement .prepare)
18
+ #
14
19
  class Configuration
15
20
  attr_accessor :raise_exceptions, :logger, :send_notifications,
16
- :backtrace_filter
21
+ :backtrace_filter, :ignorer
17
22
 
18
23
  def initialize
19
24
  @logger = nil
20
25
  @raise_exceptions = test_env?
21
26
  @send_notifications = false
22
27
  @backtrace_filter = ->(backtrace) { backtrace.take(5) }
28
+ @ignorer = Isolator::Ignorer
23
29
  end
24
30
 
25
- alias raise_exceptions? raise_exceptions
26
- alias send_notifications? send_notifications
31
+ alias_method :raise_exceptions?, :raise_exceptions
32
+ alias_method :send_notifications?, :send_notifications
27
33
 
28
34
  def test_env?
29
35
  ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test"
@@ -1,39 +1,59 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Isolator
4
- # Add .load_ignore_config function for ignoring patterns from file
5
- module Ignorer
6
- def load_ignore_config(path)
7
- return unless File.exist?(path)
4
+ # Handle ignoring isolator errors using a yml file
5
+ class Ignorer
6
+ TODO_PATH = ".isolator_todo.yml"
8
7
 
9
- todos = YAML.load_file(path)
8
+ class << self
9
+ def prepare(path: TODO_PATH, regex_string: "^.*(#ignores#):.*$")
10
+ return unless File.exist?(path)
10
11
 
11
- adapters.each do |id, adapter|
12
- ignored_paths = todos.fetch(id, [])
13
- configure_adapter(adapter, ignored_paths)
12
+ todos = YAML.load_file(path)
13
+
14
+ Isolator.adapters.each do |id, adapter|
15
+ ignored_paths = todos.fetch(id, [])
16
+ AdapterIgnore.new(adapter: adapter, ignored_paths: ignored_paths, regex_string: regex_string).prepare
17
+ end
14
18
  end
15
19
  end
16
20
 
17
21
  private
18
22
 
19
- def configure_adapter(adapter, ignored_paths)
20
- ignores = build_ignore_list(ignored_paths)
21
- return if ignores.blank?
23
+ class AdapterIgnore
24
+ def initialize(adapter:, ignored_paths:, regex_string:)
25
+ self.adapter = adapter
26
+ self.ignored_paths = ignored_paths
27
+ self.regex_string = regex_string
28
+ end
29
+
30
+ def prepare
31
+ return if ignores.blank?
22
32
 
23
- regex = Regexp.new("^.*(#{ignores.join('|')}):.*$")
24
- adapter.ignore_if { caller.any? { |row| regex =~ row } }
25
- end
33
+ adapter.ignore_if { caller.any? { |row| regex =~ row } }
34
+ end
26
35
 
27
- def build_ignore_list(ignored_paths)
28
- ignored_paths.each_with_object([]) do |path, result|
29
- ignored_files = Dir[path]
36
+ private
30
37
 
31
- if ignored_files.blank?
32
- result << path.to_s
33
- else
34
- result.concat(ignored_files)
38
+ attr_accessor :adapter, :ignored_paths, :regex_string
39
+
40
+ def ignores
41
+ return @ignores if defined? @ignores
42
+
43
+ @ignores = ignored_paths.each_with_object([]) do |path, result|
44
+ ignored_files = Dir[path]
45
+
46
+ if ignored_files.blank?
47
+ result << path.to_s
48
+ else
49
+ result.concat(ignored_files)
50
+ end
35
51
  end
36
52
  end
53
+
54
+ def regex
55
+ Regexp.new(regex_string.gsub("#ignores#", ignores.join("|")))
56
+ end
37
57
  end
38
58
  end
39
59
  end
@@ -5,7 +5,7 @@ module Isolator
5
5
  module Isolate
6
6
  def isolate(id, **options)
7
7
  raise "Adapter already registered: #{id}" if Isolator.adapters.key?(id.to_s)
8
- adapter = AdapterBuilder.call(options)
8
+ adapter = AdapterBuilder.call(**options)
9
9
  Isolator.adapters[id.to_s] = adapter
10
10
  end
11
11
  end
@@ -9,8 +9,9 @@ module Isolator
9
9
 
10
10
  def self.subscribe!(event)
11
11
  ::ActiveSupport::Notifications.subscribe(event) do |_name, _start, _finish, _id, query|
12
- Isolator.incr_transactions! if query[:sql] =~ START_PATTERN
13
- Isolator.decr_transactions! if query[:sql] =~ FINISH_PATTERN
12
+ connection_id = query[:connection_id] || query[:connection]&.object_id || 0
13
+ Isolator.incr_transactions!(connection_id) if START_PATTERN.match?(query[:sql])
14
+ Isolator.decr_transactions!(connection_id) if FINISH_PATTERN.match?(query[:sql])
14
15
  end
15
16
  end
16
17
  end
@@ -2,12 +2,21 @@
2
2
 
3
3
  module Isolator
4
4
  class Railtie < ::Rails::Railtie # :nodoc:
5
+ initializer "isolator.backtrace_cleaner" do
6
+ ActiveSupport.on_load(:active_record) do
7
+ Isolator.backtrace_cleaner = lambda do |locations|
8
+ ::Rails.backtrace_cleaner.clean(locations)
9
+ end
10
+ end
11
+ end
12
+
5
13
  config.after_initialize do
6
14
  # Forec load adapters after application initialization
7
15
  # (when all deps are likely to be loaded).
8
16
  load File.join(__dir__, "adapters.rb")
9
17
 
10
- Isolator.load_ignore_config(Rails.root.join(".isolator_todo.yml"))
18
+ Isolator.config.ignorer&.prepare
19
+ Isolator.config.ignorer&.prepare(path: ".isolator_ignore.yml")
11
20
 
12
21
  next unless Rails.env.test?
13
22
 
@@ -18,14 +27,12 @@ module Isolator
18
27
  super
19
28
  return unless run_in_transaction?
20
29
 
21
- open_count = ActiveRecord::Base.connection.open_transactions
22
- Isolator.transactions_threshold += open_count
30
+ Isolator.incr_thresholds!
23
31
  end
24
32
 
25
33
  def teardown_fixtures(*)
26
34
  if run_in_transaction?
27
- open_count = ActiveRecord::Base.connection.open_transactions
28
- Isolator.transactions_threshold -= open_count
35
+ Isolator.decr_thresholds!
29
36
  end
30
37
  super
31
38
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Isolator
4
- VERSION = "0.6.0"
4
+ VERSION = "0.8.0"
5
5
  end
data/lib/isolator.rb CHANGED
@@ -17,7 +17,25 @@ require "isolator/ext/thread_fetch"
17
17
  module Isolator
18
18
  using Isolator::ThreadFetch
19
19
 
20
+ class ThreadStateProxy
21
+ attr_reader :prefix
22
+
23
+ def initilize(prefix = "isolator_")
24
+ @prefix = prefix
25
+ end
26
+
27
+ def [](key)
28
+ Thread.current[:"#{prefix}#{key}"]
29
+ end
30
+
31
+ def []=(key, value)
32
+ Thread.current[:"#{prefix}#{key}"] = value
33
+ end
34
+ end
35
+
20
36
  class << self
37
+ attr_accessor :default_threshold, :default_connection_id
38
+
21
39
  def config
22
40
  @config ||= Configuration.new
23
41
  end
@@ -31,11 +49,11 @@ module Isolator
31
49
  end
32
50
 
33
51
  def enable!
34
- Thread.current[:isolator_disabled] = false
52
+ state[:disabled] = false
35
53
  end
36
54
 
37
55
  def disable!
38
- Thread.current[:isolator_disabled] = true
56
+ state[:disabled] = true
39
57
  end
40
58
 
41
59
  # Accepts block and disable Isolator within
@@ -64,32 +82,84 @@ module Isolator
64
82
  res
65
83
  end
66
84
 
67
- def transactions_threshold
68
- Thread.current.fetch(:isolator_threshold, 1)
85
+ def transactions_threshold=(val)
86
+ set_connection_threshold(val)
69
87
  end
70
88
 
71
- def transactions_threshold=(val)
72
- Thread.current[:isolator_threshold] = val
89
+ def transactions_threshold(connection_id = default_connection_id.call)
90
+ connection_threshold(connection_id)
73
91
  end
74
92
 
75
- def incr_transactions!
76
- Thread.current[:isolator_transactions] =
77
- Thread.current.fetch(:isolator_transactions, 0) + 1
78
- start! if Thread.current.fetch(:isolator_transactions) == transactions_threshold
93
+ def current_transactions(connection_id = default_connection_id.call)
94
+ state[:transactions]&.[](connection_id) || 0
95
+ end
96
+
97
+ def set_connection_threshold(val, connection_id = default_connection_id.call)
98
+ state[:thresholds] ||= Hash.new { |h, k| h[k] = Isolator.default_threshold }
99
+ state[:thresholds][connection_id] = val
100
+
101
+ debug!("Threshold value was changed for connection #{connection_id}: #{val}")
102
+ end
103
+
104
+ def incr_thresholds!
105
+ self.default_threshold += 1
106
+ return unless state[:thresholds]
107
+
108
+ state[:thresholds].transform_values!(&:succ)
109
+
110
+ debug!("Thresholds were incremented")
111
+ end
112
+
113
+ def decr_thresholds!
114
+ self.default_threshold -= 1
115
+ return unless state[:thresholds]
116
+
117
+ state[:thresholds].transform_values!(&:pred)
118
+
119
+ debug!("Thresholds were decremented")
120
+ end
121
+
122
+ def incr_transactions!(connection_id = default_connection_id.call)
123
+ state[:transactions] ||= Hash.new { |h, k| h[k] = 0 }
124
+ state[:transactions][connection_id] += 1
125
+
126
+ # Workaround to track threshold changes made before opening a connection
127
+ pending_threshold = state[:thresholds]&.delete(0)
128
+ if pending_threshold
129
+ state[:thresholds][connection_id] = pending_threshold
130
+ end
131
+
132
+ debug!("Transaction opened for connection #{connection_id} (total: #{state[:transactions][connection_id]}, threshold: #{state[:thresholds]&.fetch(connection_id, default_threshold)})")
133
+
134
+ start! if current_transactions(connection_id) == connection_threshold(connection_id)
79
135
  end
80
136
 
81
- def decr_transactions!
82
- Thread.current[:isolator_transactions] =
83
- Thread.current.fetch(:isolator_transactions) - 1
84
- finish! if Thread.current.fetch(:isolator_transactions) == (transactions_threshold - 1)
137
+ def decr_transactions!(connection_id = default_connection_id.call)
138
+ current = state[:transactions]&.[](connection_id) || 0
139
+
140
+ if current <= 0
141
+ warn "Trying to finalize an untracked transaction"
142
+ return
143
+ end
144
+
145
+ state[:transactions][connection_id] -= 1
146
+
147
+ finish! if current_transactions(connection_id) == (connection_threshold(connection_id) - 1)
148
+
149
+ state[:transactions].delete(connection_id) if state[:transactions][connection_id].zero?
150
+
151
+ debug!("Transaction closed for connection #{connection_id} (total: #{state[:transactions][connection_id]}, threshold: #{state[:thresholds]&.[](connection_id) || default_threshold})")
85
152
  end
86
153
 
87
154
  def clear_transactions!
88
- Thread.current[:isolator_transactions] = 0
155
+ state[:transactions]&.clear
89
156
  end
90
157
 
91
158
  def within_transaction?
92
- Thread.current.fetch(:isolator_transactions, 0) >= transactions_threshold
159
+ state[:transactions]&.each do |connection_id, transaction_count|
160
+ return true if transaction_count >= connection_threshold(connection_id)
161
+ end
162
+ false
93
163
  end
94
164
 
95
165
  def enabled?
@@ -97,17 +167,61 @@ module Isolator
97
167
  end
98
168
 
99
169
  def disabled?
100
- Thread.current[:isolator_disabled] == true
170
+ state[:disabled] == true
101
171
  end
102
172
 
103
173
  def adapters
104
174
  @adapters ||= Isolator::SimpleHashie.new
105
175
  end
106
176
 
177
+ def load_ignore_config(path)
178
+ warn "[DEPRECATION] `load_ignore_config` is deprecated. Please use `Isolator::Ignorer.prepare` instead."
179
+ Isolator::Ignorer.prepare(path: path)
180
+ end
181
+
107
182
  include Isolator::Isolate
108
183
  include Isolator::Callbacks
109
- include Isolator::Ignorer
184
+
185
+ attr_accessor :debug_enabled, :backtrace_cleaner, :backtrace_length
186
+
187
+ private
188
+
189
+ attr_accessor :state
190
+
191
+ def connection_threshold(connection_id)
192
+ state[:thresholds]&.[](connection_id) || default_threshold
193
+ end
194
+
195
+ def debug!(msg)
196
+ return unless debug_enabled
197
+ msg = "[ISOLATOR DEBUG] #{msg}"
198
+
199
+ if backtrace_cleaner && backtrace_length.positive?
200
+ source = extract_source_location(caller)
201
+
202
+ msg = "#{msg}\n ↳ #{source.join("\n")}" unless source.empty?
203
+ end
204
+
205
+ $stdout.puts(colorize_debug(msg))
206
+ end
207
+
208
+ def extract_source_location(locations)
209
+ backtrace_cleaner.call(locations.lazy)
210
+ .take(backtrace_length).to_a
211
+ end
212
+
213
+ def colorize_debug(msg)
214
+ return msg unless $stdout.tty?
215
+
216
+ "\u001b[31;1m#{msg}\u001b[0m"
217
+ end
110
218
  end
219
+
220
+ self.state = ThreadStateProxy.new
221
+ self.default_threshold = 1
222
+ self.default_connection_id = -> { ActiveRecord::Base.connected? ? ActiveRecord::Base.connection.object_id : 0 }
223
+ self.debug_enabled = ENV["ISOLATOR_DEBUG"] == "true"
224
+ self.backtrace_length = ENV.fetch("ISOLATOR_BACKTRACE_LENGTH", 1).to_i
111
225
  end
112
226
 
113
227
  require "isolator/orm_adapters"
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: isolator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-13 00:00:00.000000000 Z
11
+ date: 2021-12-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sniffer
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 0.3.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.3.1
27
27
  - !ruby/object:Gem::Dependency
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '10.0'
47
+ version: '13.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '10.0'
54
+ version: '13.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -70,14 +70,14 @@ dependencies:
70
70
  name: rspec-rails
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '3.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '3.0'
83
83
  - !ruby/object:Gem::Dependency
@@ -95,77 +95,77 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: 5.10.0
97
97
  - !ruby/object:Gem::Dependency
98
- name: rubocop
98
+ name: sidekiq
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 0.56.0
103
+ version: '5.0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 0.56.0
110
+ version: '5.0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: rubocop-md
112
+ name: webmock
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '0.2'
117
+ version: '3.1'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '0.2'
124
+ version: '3.1'
125
125
  - !ruby/object:Gem::Dependency
126
- name: sidekiq
126
+ name: test_after_commit
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '5.0'
131
+ version: '1.1'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '5.0'
138
+ version: '1.1'
139
139
  - !ruby/object:Gem::Dependency
140
- name: webmock
140
+ name: resque
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - "~>"
143
+ - - ">="
144
144
  - !ruby/object:Gem::Version
145
- version: '3.1'
145
+ version: '0'
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
- - - "~>"
150
+ - - ">="
151
151
  - !ruby/object:Gem::Version
152
- version: '3.1'
152
+ version: '0'
153
153
  - !ruby/object:Gem::Dependency
154
- name: test_after_commit
154
+ name: fakeredis
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
- - - "~>"
157
+ - - ">="
158
158
  - !ruby/object:Gem::Version
159
- version: '1.1'
159
+ version: '0'
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
- - - "~>"
164
+ - - ">="
165
165
  - !ruby/object:Gem::Version
166
- version: '1.1'
166
+ version: '0'
167
167
  - !ruby/object:Gem::Dependency
168
- name: resque
168
+ name: resque-scheduler
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
171
  - - ">="
@@ -179,7 +179,7 @@ dependencies:
179
179
  - !ruby/object:Gem::Version
180
180
  version: '0'
181
181
  - !ruby/object:Gem::Dependency
182
- name: fakeredis
182
+ name: sucker_punch
183
183
  requirement: !ruby/object:Gem::Requirement
184
184
  requirements:
185
185
  - - ">="
@@ -193,7 +193,7 @@ dependencies:
193
193
  - !ruby/object:Gem::Version
194
194
  version: '0'
195
195
  - !ruby/object:Gem::Dependency
196
- name: resque-scheduler
196
+ name: database_cleaner
197
197
  requirement: !ruby/object:Gem::Requirement
198
198
  requirements:
199
199
  - - ">="
@@ -207,7 +207,7 @@ dependencies:
207
207
  - !ruby/object:Gem::Version
208
208
  version: '0'
209
209
  - !ruby/object:Gem::Dependency
210
- name: sucker_punch
210
+ name: database_cleaner-active_record
211
211
  requirement: !ruby/object:Gem::Requirement
212
212
  requirements:
213
213
  - - ">="
@@ -221,7 +221,49 @@ dependencies:
221
221
  - !ruby/object:Gem::Version
222
222
  version: '0'
223
223
  - !ruby/object:Gem::Dependency
224
- name: database_cleaner
224
+ name: after_commit_everywhere
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
237
+ - !ruby/object:Gem::Dependency
238
+ name: uniform_notifier
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ type: :development
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - ">="
249
+ - !ruby/object:Gem::Version
250
+ version: '0'
251
+ - !ruby/object:Gem::Dependency
252
+ name: webrick
253
+ requirement: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - ">="
256
+ - !ruby/object:Gem::Version
257
+ version: '0'
258
+ type: :development
259
+ prerelease: false
260
+ version_requirements: !ruby/object:Gem::Requirement
261
+ requirements:
262
+ - - ">="
263
+ - !ruby/object:Gem::Version
264
+ version: '0'
265
+ - !ruby/object:Gem::Dependency
266
+ name: net-smtp
225
267
  requirement: !ruby/object:Gem::Requirement
226
268
  requirements:
227
269
  - - ">="
@@ -241,22 +283,9 @@ executables: []
241
283
  extensions: []
242
284
  extra_rdoc_files: []
243
285
  files:
244
- - ".gitignore"
245
- - ".rubocop.yml"
246
- - ".travis.yml"
247
286
  - CHANGELOG.md
248
- - Gemfile
249
287
  - LICENSE.txt
250
288
  - README.md
251
- - Rakefile
252
- - bin/console
253
- - bin/setup
254
- - gemfiles/activerecord42.gemfile
255
- - gemfiles/jruby.gemfile
256
- - gemfiles/rails6.gemfile
257
- - gemfiles/railsmaster.gemfile
258
- - gemfiles/ruby_2.2.2.gemfile
259
- - isolator.gemspec
260
289
  - lib/isolator.rb
261
290
  - lib/isolator/adapter_builder.rb
262
291
  - lib/isolator/adapters.rb
@@ -291,8 +320,13 @@ files:
291
320
  homepage: https://github.com/palkan/isolator
292
321
  licenses:
293
322
  - MIT
294
- metadata: {}
295
- post_install_message:
323
+ metadata:
324
+ bug_tracker_uri: http://github.com/palkan/isolator/issues
325
+ changelog_uri: https://github.com/palkan/isolator/blob/master/CHANGELOG.md
326
+ documentation_uri: http://github.com/palkan/isolator
327
+ homepage_uri: http://github.com/palkan/isolator
328
+ source_code_uri: http://github.com/palkan/isolator
329
+ post_install_message:
296
330
  rdoc_options: []
297
331
  require_paths:
298
332
  - lib
@@ -300,15 +334,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
300
334
  requirements:
301
335
  - - ">="
302
336
  - !ruby/object:Gem::Version
303
- version: 2.2.2
337
+ version: 2.6.0
304
338
  required_rubygems_version: !ruby/object:Gem::Requirement
305
339
  requirements:
306
340
  - - ">="
307
341
  - !ruby/object:Gem::Version
308
342
  version: '0'
309
343
  requirements: []
310
- rubygems_version: 3.0.2
311
- signing_key:
344
+ rubygems_version: 3.2.22
345
+ signing_key:
312
346
  specification_version: 4
313
347
  summary: Detect non-atomic interactions within DB transactions
314
348
  test_files: []
data/.gitignore DELETED
@@ -1,13 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- *.gem
11
- gemfiles/*.lock
12
- Gemfile.local
13
- .rspec_status
data/.rubocop.yml DELETED
@@ -1,70 +0,0 @@
1
- require:
2
- - rubocop-md
3
-
4
- AllCops:
5
- Include:
6
- - 'lib/**/*.rb'
7
- - 'lib/**/*.rake'
8
- - 'spec/**/*.rb'
9
- Exclude:
10
- - 'bin/**/*'
11
- - 'gemfiles/**/*'
12
- - 'spec/dummy/**/*'
13
- - 'vendor/**/*'
14
- - 'tmp/**/*'
15
- - 'Rakefile'
16
- - 'Gemfile'
17
- - '*.gemspec'
18
- DisplayCopNames: true
19
- StyleGuideCopsOnly: false
20
- TargetRubyVersion: 2.2.2
21
-
22
- Rails:
23
- Enabled: false
24
-
25
- Bundler/OrderedGems:
26
- Enabled: false
27
-
28
- Naming/UncommunicativeMethodParamName:
29
- Enabled: false
30
-
31
- Style/SymbolArray:
32
- Enabled: false
33
-
34
- Style/Documentation:
35
- Exclude:
36
- - 'spec/**/*.rb'
37
-
38
- Style/StringLiterals:
39
- EnforcedStyle: double_quotes
40
-
41
- Style/RegexpLiteral:
42
- Enabled: false
43
-
44
- Style/Lambda:
45
- Enabled: false
46
-
47
- Style/BlockDelimiters:
48
- Exclude:
49
- - 'spec/**/*.rb'
50
-
51
- Style/NumericPredicate:
52
- Enabled: false
53
-
54
- Layout/SpaceInsideStringInterpolation:
55
- EnforcedStyle: no_space
56
-
57
- Lint/AmbiguousRegexpLiteral:
58
- Enabled: false
59
-
60
- Metrics/LineLength:
61
- Max: 100
62
- Exclude:
63
- - 'spec/**/*.rb'
64
-
65
- Metrics/BlockLength:
66
- Exclude:
67
- - 'spec/**/*.rb'
68
-
69
- Style/MutableConstant:
70
- Enabled: false
data/.travis.yml DELETED
@@ -1,35 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.5.3
6
-
7
- notifications:
8
- email: false
9
-
10
- before_install:
11
- - (ruby -v | grep '2.2.2') || gem update --system
12
- - gem install bundler -v '< 2'
13
-
14
- matrix:
15
- fast_finish: true
16
- include:
17
- - rvm: ruby-head
18
- gemfile: gemfiles/railsmaster.gemfile
19
- - rvm: jruby-9.1.0.0
20
- gemfile: gemfiles/jruby.gemfile
21
- - rvm: 2.6.1
22
- gemfile: gemfiles/rails6.gemfile
23
- - rvm: 2.5.0
24
- gemfile: Gemfile
25
- - rvm: 2.4.3
26
- gemfile: Gemfile
27
- - rvm: 2.3.1
28
- gemfile: gemfiles/activerecord42.gemfile
29
- - rvm: 2.2.2
30
- gemfile: gemfiles/ruby_2.2.2.gemfile
31
- allow_failures:
32
- - rvm: ruby-head
33
- gemfile: gemfiles/railsmaster.gemfile
34
- - rvm: jruby-9.1.0.0
35
- gemfile: gemfiles/jruby.gemfile
data/Gemfile DELETED
@@ -1,20 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
-
5
- # Specify your gem's dependencies in isolator.gemspec
6
- gemspec
7
-
8
- gem "pry-byebug"
9
-
10
- gem "sqlite3", "~> 1.3.0"
11
-
12
- local_gemfile = File.join(__dir__, "Gemfile.local")
13
-
14
- if File.exist?(local_gemfile)
15
- eval(File.read(local_gemfile)) # rubocop:disable Security/Eval
16
- else
17
- gem "rails", "~> 5.0"
18
- end
19
-
20
- gem 'uniform_notifier', '~> 1.11'
data/Rakefile DELETED
@@ -1,8 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
- require "rubocop/rake_task"
4
-
5
- RSpec::Core::RakeTask.new(:spec)
6
- RuboCop::RakeTask.new
7
-
8
- task default: [:rubocop, :spec]
data/bin/console DELETED
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "isolator"
5
-
6
- require "pry"
7
- Pry.start
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
@@ -1,7 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "rails", "~> 4.2"
4
- gem "sqlite3", "~> 1.3.0"
5
- gem 'uniform_notifier', '~> 1.11'
6
-
7
- gemspec path: ".."
@@ -1,8 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "activerecord-jdbcsqlite3-adapter", "~> 50.0"
4
- gem "jdbc-sqlite3"
5
- gem "rails", "~> 5.0"
6
- gem 'uniform_notifier', '~> 1.11'
7
-
8
- gemspec path: ".."
@@ -1,7 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "rails", "6.0.0.beta3"
4
- gem "sqlite3", "~> 1.3"
5
- gem 'uniform_notifier', '~> 1.11'
6
-
7
- gemspec path: ".."
@@ -1,7 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "rails", github: "rails/rails"
4
- gem "sqlite3"
5
- gem 'uniform_notifier', '~> 1.11'
6
-
7
- gemspec path: ".."
@@ -1,8 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "rails", "~> 4.2"
4
- gem "sqlite3", "~> 1.3.0"
5
- gem 'uniform_notifier', '1.11'
6
- gem 'anyway_config', '1.2'
7
-
8
- gemspec path: ".."
data/isolator.gemspec DELETED
@@ -1,41 +0,0 @@
1
- lib = File.expand_path("../lib", __FILE__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require "isolator/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "isolator"
7
- spec.version = Isolator::VERSION
8
- spec.authors = ["Vladimir Dementyev"]
9
- spec.email = ["dementiev.vm@gmail.com"]
10
-
11
- spec.summary = "Detect non-atomic interactions within DB transactions"
12
- spec.description = "Detect non-atomic interactions within DB transactions"
13
- spec.homepage = "https://github.com/palkan/isolator"
14
- spec.license = "MIT"
15
-
16
- spec.required_ruby_version = ">= 2.2.2"
17
-
18
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
- f.match(%r{^(test|spec|features)/})
20
- end
21
- spec.require_paths = ["lib"]
22
-
23
- spec.add_runtime_dependency "sniffer", "~> 0.3.1"
24
-
25
- spec.add_development_dependency "bundler", ">= 1.16"
26
- spec.add_development_dependency "rake", "~> 10.0"
27
- spec.add_development_dependency "rspec", "~> 3.0"
28
- spec.add_development_dependency "rspec-rails", "~> 3.0"
29
- spec.add_development_dependency "minitest", "~> 5.10.0"
30
- spec.add_development_dependency "rubocop", "~> 0.56.0"
31
- spec.add_development_dependency "rubocop-md", "~> 0.2"
32
-
33
- spec.add_development_dependency "sidekiq", "~> 5.0"
34
- spec.add_development_dependency "webmock", "~> 3.1"
35
- spec.add_development_dependency "test_after_commit", "~> 1.1"
36
- spec.add_development_dependency "resque"
37
- spec.add_development_dependency "fakeredis"
38
- spec.add_development_dependency "resque-scheduler"
39
- spec.add_development_dependency "sucker_punch"
40
- spec.add_development_dependency "database_cleaner"
41
- end