isolator 0.6.0 → 0.8.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 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