with_advisory_lock 4.0.0 → 5.0.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.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +80 -0
  3. data/.gitignore +3 -0
  4. data/.tool-versions +1 -0
  5. data/Appraisals +37 -11
  6. data/CHANGELOG.md +131 -0
  7. data/Gemfile +0 -12
  8. data/README.md +85 -171
  9. data/gemfiles/{activerecord_5.1.gemfile → activerecord_6.1.gemfile} +4 -2
  10. data/gemfiles/{activerecord_5.2.gemfile → activerecord_7.0.gemfile} +4 -2
  11. data/gemfiles/activerecord_7.1.gemfile +14 -0
  12. data/lib/with_advisory_lock/base.rb +16 -2
  13. data/lib/with_advisory_lock/concern.rb +13 -2
  14. data/lib/with_advisory_lock/database_adapter_support.rb +10 -3
  15. data/lib/with_advisory_lock/failed_to_acquire_lock.rb +7 -0
  16. data/lib/with_advisory_lock/flock.rb +4 -3
  17. data/lib/with_advisory_lock/mysql.rb +6 -16
  18. data/lib/with_advisory_lock/postgresql.rb +9 -7
  19. data/lib/with_advisory_lock/version.rb +3 -1
  20. data/lib/with_advisory_lock.rb +1 -1
  21. data/test/concern_test.rb +23 -10
  22. data/test/lock_test.rb +61 -28
  23. data/test/nesting_test.rb +14 -46
  24. data/test/options_test.rb +35 -33
  25. data/test/parallelism_test.rb +35 -37
  26. data/test/shared_test.rb +93 -90
  27. data/test/test_helper.rb +52 -0
  28. data/test/test_models.rb +9 -7
  29. data/test/thread_test.rb +23 -22
  30. data/test/transaction_test.rb +34 -36
  31. data/with_advisory_lock.gemspec +24 -23
  32. metadata +27 -41
  33. data/.travis.install-mysql-5.7.sh +0 -11
  34. data/.travis.yml +0 -32
  35. data/gemfiles/activerecord_4.2.gemfile +0 -19
  36. data/gemfiles/activerecord_5.0.gemfile +0 -19
  37. data/lib/with_advisory_lock/nested_advisory_lock_error.rb +0 -14
  38. data/test/database.yml +0 -17
  39. data/test/minitest_helper.rb +0 -40
  40. data/tests.sh +0 -11
@@ -2,12 +2,14 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activerecord", "~> 5.1.0"
5
+ gem "activerecord", "~> 7.0.0"
6
6
 
7
7
  platforms :ruby do
8
+ gem "sqlite3"
8
9
  gem "mysql2"
10
+ gem "trilogy"
11
+ gem "activerecord-trilogy-adapter"
9
12
  gem "pg"
10
- gem "sqlite3"
11
13
  end
12
14
 
13
15
  platforms :jruby do
@@ -0,0 +1,14 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 7.1.0"
6
+
7
+ platforms :ruby do
8
+ gem "sqlite3"
9
+ gem "mysql2"
10
+ gem "trilogy"
11
+ gem "pg"
12
+ end
13
+
14
+ gemspec path: "../"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'zlib'
2
4
 
3
5
  module WithAdvisoryLock
@@ -19,17 +21,18 @@ module WithAdvisoryLock
19
21
  LockStackItem = Struct.new(:name, :shared)
20
22
 
21
23
  class Base
22
- attr_reader :connection, :lock_name, :timeout_seconds, :shared, :transaction
24
+ attr_reader :connection, :lock_name, :timeout_seconds, :shared, :transaction, :disable_query_cache
23
25
 
24
26
  def initialize(connection, lock_name, options)
25
27
  options = { timeout_seconds: options } unless options.respond_to?(:fetch)
26
- options.assert_valid_keys :timeout_seconds, :shared, :transaction
28
+ options.assert_valid_keys :timeout_seconds, :shared, :transaction, :disable_query_cache
27
29
 
28
30
  @connection = connection
29
31
  @lock_name = lock_name
30
32
  @timeout_seconds = options.fetch(:timeout_seconds, nil)
31
33
  @shared = options.fetch(:shared, false)
32
34
  @transaction = options.fetch(:transaction, false)
35
+ @disable_query_cache = options.fetch(:disable_query_cache, false)
33
36
  end
34
37
 
35
38
  def lock_str
@@ -51,6 +54,16 @@ module WithAdvisoryLock
51
54
  end
52
55
 
53
56
  def with_advisory_lock_if_needed(&block)
57
+ if disable_query_cache
58
+ return lock_and_yield do
59
+ ActiveRecord::Base.uncached(&block)
60
+ end
61
+ end
62
+
63
+ lock_and_yield(&block)
64
+ end
65
+
66
+ def lock_and_yield(&block)
54
67
  if already_locked?
55
68
  Result.new(true, yield)
56
69
  elsif timeout_seconds == 0
@@ -75,6 +88,7 @@ module WithAdvisoryLock
75
88
  while @timeout_seconds.nil? || Time.now < give_up_at
76
89
  r = yield_with_lock(&block)
77
90
  return r if r.lock_was_acquired?
91
+
78
92
  # Randomizing sleep time may help reduce contention.
79
93
  sleep(rand(0.05..0.15))
80
94
  end
@@ -1,16 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/concern'
2
4
 
3
5
  module WithAdvisoryLock
4
6
  module Concern
5
7
  extend ActiveSupport::Concern
6
- delegate :with_advisory_lock, :advisory_lock_exists?, to: 'self.class'
8
+ delegate :with_advisory_lock, :with_advisory_lock!, :advisory_lock_exists?, to: 'self.class'
7
9
 
8
- module ClassMethods
10
+ class_methods do
9
11
  def with_advisory_lock(lock_name, options = {}, &block)
10
12
  result = with_advisory_lock_result(lock_name, options, &block)
11
13
  result.lock_was_acquired? ? result.result : false
12
14
  end
13
15
 
16
+ def with_advisory_lock!(lock_name, options = {}, &block)
17
+ result = with_advisory_lock_result(lock_name, options, &block)
18
+ unless result.lock_was_acquired?
19
+ raise WithAdvisoryLock::FailedToAcquireLock, lock_name
20
+ end
21
+
22
+ result.result
23
+ end
24
+
14
25
  def with_advisory_lock_result(lock_name, options = {}, &block)
15
26
  impl = impl_class.new(connection, lock_name, options)
16
27
  impl.with_advisory_lock_if_needed(&block)
@@ -1,11 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WithAdvisoryLock
2
4
  class DatabaseAdapterSupport
5
+ # Caches nested lock support by MySQL reported version
6
+ @@mysql_nl_cache = {}
7
+ @@mysql_nl_cache_mutex = Mutex.new
8
+
3
9
  def initialize(connection)
4
- @sym_name = connection.adapter_name.downcase.to_sym
10
+ @connection = connection
11
+ @sym_name = connection.adapter_name.downcase.to_sym
5
12
  end
6
13
 
7
14
  def mysql?
8
- %i[mysql mysql2].include? @sym_name
15
+ %i[mysql2 trilogy].include? @sym_name
9
16
  end
10
17
 
11
18
  def postgresql?
@@ -13,7 +20,7 @@ module WithAdvisoryLock
13
20
  end
14
21
 
15
22
  def sqlite?
16
- :sqlite3 == @sym_name
23
+ @sym_name == :sqlite3
17
24
  end
18
25
  end
19
26
  end
@@ -0,0 +1,7 @@
1
+ module WithAdvisoryLock
2
+ class FailedToAcquireLock < StandardError
3
+ def initialize(lock_name)
4
+ super("Failed to acquire lock #{lock_name}")
5
+ end
6
+ end
7
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fileutils'
2
4
 
3
5
  module WithAdvisoryLock
@@ -19,9 +21,8 @@ module WithAdvisoryLock
19
21
  end
20
22
 
21
23
  def try_lock
22
- if transaction
23
- raise ArgumentError, 'transaction level locks are not supported on SQLite'
24
- end
24
+ raise ArgumentError, 'transaction level locks are not supported on SQLite' if transaction
25
+
25
26
  0 == file_io.flock((shared ? File::LOCK_SH : File::LOCK_EX) | File::LOCK_NB)
26
27
  end
27
28
 
@@ -1,17 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WithAdvisoryLock
2
4
  class MySQL < Base
3
- # See http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock
5
+ # See https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
4
6
  def try_lock
5
- unless lock_stack.empty?
6
- raise NestedAdvisoryLockError.new(
7
- "MySQL doesn't support nested Advisory Locks",
8
- lock_stack.dup
9
- )
10
- end
11
7
  raise ArgumentError, 'shared locks are not supported on MySQL' if shared
12
- if transaction
13
- raise ArgumentError, 'transaction level locks are not supported on MySQL'
14
- end
8
+ raise ArgumentError, 'transaction level locks are not supported on MySQL' if transaction
9
+
15
10
  execute_successful?("GET_LOCK(#{quoted_lock_str}, 0)")
16
11
  end
17
12
 
@@ -21,12 +16,7 @@ module WithAdvisoryLock
21
16
 
22
17
  def execute_successful?(mysql_function)
23
18
  sql = "SELECT #{mysql_function} AS #{unique_column_name}"
24
- connection.select_value(sql).to_i > 0
25
- end
26
-
27
- # MySQL doesn't support nested locks:
28
- def already_locked?
29
- lock_stack.last == lock_stack_item
19
+ connection.select_value(sql).to_i.positive?
30
20
  end
31
21
 
32
22
  # MySQL wants a string as the lock key.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WithAdvisoryLock
2
4
  class PostgreSQL < Base
3
5
  # See http://www.postgresql.org/docs/9.1/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
@@ -8,10 +10,12 @@ module WithAdvisoryLock
8
10
 
9
11
  def release_lock
10
12
  return if transaction
13
+
11
14
  pg_function = "pg_advisory_unlock#{shared ? '_shared' : ''}"
12
15
  execute_successful?(pg_function)
13
16
  rescue ActiveRecord::StatementInvalid => e
14
17
  raise unless e.message =~ / ERROR: +current transaction is aborted,/
18
+
15
19
  begin
16
20
  connection.rollback_db_transaction
17
21
  execute_successful?(pg_function)
@@ -21,20 +25,18 @@ module WithAdvisoryLock
21
25
  end
22
26
 
23
27
  def execute_successful?(pg_function)
24
- comment = lock_name.gsub(/(\/\*)|(\*\/)/, '--')
28
+ comment = lock_name.to_s.gsub(%r{(/\*)|(\*/)}, '--')
25
29
  sql = "SELECT #{pg_function}(#{lock_keys.join(',')}) AS #{unique_column_name} /* #{comment} */"
26
30
  result = connection.select_value(sql)
27
31
  # MRI returns 't', jruby returns true. YAY!
28
- (result == 't' || result == true)
32
+ ['t', true].include?(result)
29
33
  end
30
34
 
31
35
  # PostgreSQL wants 2 32bit integers as the lock key.
32
36
  def lock_keys
33
- @lock_keys ||= begin
34
- [stable_hashcode(lock_name), ENV['WITH_ADVISORY_LOCK_PREFIX']].map do |ea|
35
- # pg advisory args must be 31 bit ints
36
- ea.to_i & 0x7fffffff
37
- end
37
+ @lock_keys ||= [stable_hashcode(lock_name), ENV['WITH_ADVISORY_LOCK_PREFIX']].map do |ea|
38
+ # pg advisory args must be 31 bit ints
39
+ ea.to_i & 0x7fffffff
38
40
  end
39
41
  end
40
42
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WithAdvisoryLock
2
- VERSION = Gem::Version.new('4.0.0')
4
+ VERSION = Gem::Version.new('5.0.0')
3
5
  end
@@ -1,5 +1,6 @@
1
1
  require 'with_advisory_lock/version'
2
2
  require 'active_support'
3
+ require_relative 'with_advisory_lock/failed_to_acquire_lock'
3
4
 
4
5
  module WithAdvisoryLock
5
6
  extend ActiveSupport::Autoload
@@ -9,7 +10,6 @@ module WithAdvisoryLock
9
10
  autoload :DatabaseAdapterSupport
10
11
  autoload :Flock
11
12
  autoload :MySQL, 'with_advisory_lock/mysql'
12
- autoload :NestedAdvisoryLockError
13
13
  autoload :PostgreSQL, 'with_advisory_lock/postgresql'
14
14
  end
15
15
 
data/test/concern_test.rb CHANGED
@@ -1,20 +1,33 @@
1
- require 'minitest_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe "with_advisory_lock.concern" do
4
- it "adds with_advisory_lock to ActiveRecord classes" do
5
- assert Tag.respond_to?(:with_advisory_lock)
3
+ require 'test_helper'
4
+
5
+ class WithAdvisoryLockConcernTest < GemTestCase
6
+ test 'adds with_advisory_lock to ActiveRecord classes' do
7
+ assert_respond_to(Tag, :with_advisory_lock)
6
8
  end
7
9
 
8
- it "adds with_advisory_lock to ActiveRecord instances" do
9
- assert Label.new.respond_to?(:with_advisory_lock)
10
+ test 'adds with_advisory_lock to ActiveRecord instances' do
11
+ assert_respond_to(Label.new, :with_advisory_lock)
10
12
  end
11
13
 
12
- it "adds advisory_lock_exists? to ActiveRecord classes" do
13
- assert Tag.respond_to?(:advisory_lock_exists?)
14
+ test 'adds advisory_lock_exists? to ActiveRecord classes' do
15
+ assert_respond_to(Tag, :advisory_lock_exists?)
14
16
  end
15
17
 
16
- it "adds advisory_lock_exists? to ActiveRecord classes" do
17
- assert Label.new.respond_to?(:advisory_lock_exists?)
18
+ test 'adds advisory_lock_exists? to ActiveRecord instances' do
19
+ assert_respond_to(Label.new, :advisory_lock_exists?)
18
20
  end
21
+ end
19
22
 
23
+ class ActiveRecordQueryCacheTest < GemTestCase
24
+ test 'does not disable quary cache by default' do
25
+ ActiveRecord::Base.expects(:uncached).never
26
+ Tag.with_advisory_lock('lock') { Tag.first }
27
+ end
28
+
29
+ test 'can disable ActiveRecord query cache' do
30
+ ActiveRecord::Base.expects(:uncached).once
31
+ Tag.with_advisory_lock('a-lock', disable_query_cache: true) { Tag.first }
32
+ end
20
33
  end
data/test/lock_test.rb CHANGED
@@ -1,47 +1,80 @@
1
- require 'minitest_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe 'class methods' do
4
- let(:lock_name) { 'test lock' }
3
+ require 'test_helper'
5
4
 
6
- describe '.current_advisory_lock' do
7
- it 'returns nil outside an advisory lock request' do
8
- Tag.current_advisory_lock.must_be_nil
5
+ class LockTest < GemTestCase
6
+ setup do
7
+ @lock_name = 'test lock'
8
+ @return_val = 1900
9
+ end
10
+
11
+ test 'returns nil outside an advisory lock request' do
12
+ assert_nil(Tag.current_advisory_lock)
13
+ end
14
+
15
+ test 'returns the name of the last lock acquired' do
16
+ Tag.with_advisory_lock(@lock_name) do
17
+ assert_match(/#{@lock_name}/, Tag.current_advisory_lock)
9
18
  end
19
+ end
10
20
 
11
- it 'returns the name of the last lock acquired' do
12
- Tag.with_advisory_lock(lock_name) do
13
- # The lock name may have a prefix if WITH_ADVISORY_LOCK_PREFIX env is set
14
- Tag.current_advisory_lock.must_match(/#{lock_name}/)
15
- end
21
+ test 'can obtain a lock with a name that attempts to disrupt a SQL comment' do
22
+ dangerous_lock_name = 'test */ lock /*'
23
+ Tag.with_advisory_lock(dangerous_lock_name) do
24
+ assert_match(/#{Regexp.escape(dangerous_lock_name)}/, Tag.current_advisory_lock)
16
25
  end
26
+ end
17
27
 
18
- it 'can obtain a lock with a name that attempts to disrupt a SQL comment' do
19
- dangerous_lock_name = 'test */ lock /*'
20
- Tag.with_advisory_lock(dangerous_lock_name) do
21
- Tag.current_advisory_lock.must_match(/#{Regexp.escape(dangerous_lock_name)}/)
22
- end
28
+ test 'returns false for an unacquired lock' do
29
+ refute(Tag.advisory_lock_exists?(@lock_name))
30
+ end
23
31
 
32
+ test 'returns true for an acquired lock' do
33
+ Tag.with_advisory_lock(@lock_name) do
34
+ assert(Tag.advisory_lock_exists?(@lock_name))
24
35
  end
25
36
  end
26
37
 
27
- describe '.advisory_lock_exists?' do
28
- it 'returns false for an unacquired lock' do
29
- Tag.advisory_lock_exists?(lock_name).must_be_false
38
+ test 'returns block return value if lock successful' do
39
+ assert_equal(@return_val, Tag.with_advisory_lock!(@lock_name) { @return_val })
40
+ end
41
+
42
+ test 'returns false on lock acquisition failure' do
43
+ thread_with_lock = Thread.new do
44
+ Tag.with_advisory_lock(@lock_name, timeout_seconds: 0) do
45
+ @locked_elsewhere = true
46
+ loop { sleep 0.01 }
47
+ end
30
48
  end
31
49
 
32
- it 'returns the name of the last lock acquired' do
33
- Tag.with_advisory_lock(lock_name) do
34
- Tag.advisory_lock_exists?(lock_name).must_be_true
50
+ sleep 0.01 until @locked_elsewhere
51
+ assert_not(Tag.with_advisory_lock(@lock_name, timeout_seconds: 0) { @return_val })
52
+
53
+ thread_with_lock.kill
54
+ end
55
+
56
+ test 'raises an error on lock acquisition failure' do
57
+ thread_with_lock = Thread.new do
58
+ Tag.with_advisory_lock(@lock_name, timeout_seconds: 0) do
59
+ @locked_elsewhere = true
60
+ loop { sleep 0.01 }
35
61
  end
36
62
  end
63
+
64
+ sleep 0.01 until @locked_elsewhere
65
+ assert_raises(WithAdvisoryLock::FailedToAcquireLock) do
66
+ Tag.with_advisory_lock!(@lock_name, timeout_seconds: 0) { @return_val }
67
+ end
68
+
69
+ thread_with_lock.kill
37
70
  end
38
71
 
39
- describe 'zero timeout_seconds' do
40
- it 'attempts the lock exactly once with no timeout' do
41
- expected = SecureRandom.base64
42
- Tag.with_advisory_lock(lock_name, 0) do
43
- expected
44
- end.must_equal expected
72
+ test 'attempts the lock exactly once with no timeout' do
73
+ expected = SecureRandom.base64
74
+ actual = Tag.with_advisory_lock(@lock_name, 0) do
75
+ expected
45
76
  end
77
+
78
+ assert_equal(expected, actual)
46
79
  end
47
80
  end
data/test/nesting_test.rb CHANGED
@@ -1,60 +1,28 @@
1
- require 'minitest_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe "lock nesting" do
4
- # This simplifies what we expect from the lock name:
5
- before :each do
3
+ require 'test_helper'
4
+
5
+ class LockNestingTest < GemTestCase
6
+ setup do
6
7
  @prior_prefix = ENV['WITH_ADVISORY_LOCK_PREFIX']
7
8
  ENV['WITH_ADVISORY_LOCK_PREFIX'] = nil
8
9
  end
9
10
 
10
- after :each do
11
+ teardown do
11
12
  ENV['WITH_ADVISORY_LOCK_PREFIX'] = @prior_prefix
12
13
  end
13
14
 
14
- it "doesn't request the same lock twice" do
15
+ test "doesn't request the same lock twice" do
15
16
  impl = WithAdvisoryLock::Base.new(nil, nil, nil)
16
- impl.lock_stack.must_be_empty
17
- Tag.with_advisory_lock("first") do
18
- impl.lock_stack.map(&:name).must_equal %w(first)
17
+ assert_empty(impl.lock_stack)
18
+ Tag.with_advisory_lock('first') do
19
+ assert_equal(%w[first], impl.lock_stack.map(&:name))
19
20
  # Even MySQL should be OK with this:
20
- Tag.with_advisory_lock("first") do
21
- impl.lock_stack.map(&:name).must_equal %w(first)
22
- end
23
- impl.lock_stack.map(&:name).must_equal %w(first)
24
- end
25
- impl.lock_stack.must_be_empty
26
- end
27
-
28
- it "raises errors with MySQL when acquiring nested lock" do
29
- skip unless env_db == :mysql
30
- exc = proc {
31
- Tag.with_advisory_lock("first") do
32
- Tag.with_advisory_lock("second") do
33
- end
34
- end
35
- }.must_raise WithAdvisoryLock::NestedAdvisoryLockError
36
- exc.lock_stack.map(&:name).must_equal %w(first)
37
- end
38
-
39
- it "supports nested advisory locks with !MySQL" do
40
- skip if env_db == :mysql
41
- impl = WithAdvisoryLock::Base.new(nil, nil, nil)
42
- impl.lock_stack.must_be_empty
43
- Tag.with_advisory_lock("first") do
44
- impl.lock_stack.map(&:name).must_equal %w(first)
45
- Tag.with_advisory_lock("second") do
46
- impl.lock_stack.map(&:name).must_equal %w(first second)
47
- Tag.with_advisory_lock("first") do
48
- # Shouldn't ask for another lock:
49
- impl.lock_stack.map(&:name).must_equal %w(first second)
50
- Tag.with_advisory_lock("second") do
51
- # Shouldn't ask for another lock:
52
- impl.lock_stack.map(&:name).must_equal %w(first second)
53
- end
54
- end
21
+ Tag.with_advisory_lock('first') do
22
+ assert_equal(%w[first], impl.lock_stack.map(&:name))
55
23
  end
56
- impl.lock_stack.map(&:name).must_equal %w(first)
24
+ assert_equal(%w[first], impl.lock_stack.map(&:name))
57
25
  end
58
- impl.lock_stack.must_be_empty
26
+ assert_empty(impl.lock_stack)
59
27
  end
60
28
  end
data/test/options_test.rb CHANGED
@@ -1,64 +1,66 @@
1
- require 'minitest_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe 'options parsing' do
3
+ require 'test_helper'
4
+
5
+ class OptionsParsingTest < GemTestCase
4
6
  def parse_options(options)
5
7
  WithAdvisoryLock::Base.new(mock, mock, options)
6
8
  end
7
9
 
8
- specify 'defaults (empty hash)' do
10
+ test 'defaults (empty hash)' do
9
11
  impl = parse_options({})
10
- impl.timeout_seconds.must_be_nil
11
- impl.shared.must_equal false
12
- impl.transaction.must_equal false
12
+ assert_nil(impl.timeout_seconds)
13
+ assert_not(impl.shared)
14
+ assert_not(impl.transaction)
13
15
  end
14
16
 
15
- specify 'nil sets timeout to nil' do
17
+ test 'nil sets timeout to nil' do
16
18
  impl = parse_options(nil)
17
- impl.timeout_seconds.must_be_nil
18
- impl.shared.must_equal false
19
- impl.transaction.must_equal false
19
+ assert_nil(impl.timeout_seconds)
20
+ assert_not(impl.shared)
21
+ assert_not(impl.transaction)
20
22
  end
21
23
 
22
- specify 'integer sets timeout to value' do
24
+ test 'integer sets timeout to value' do
23
25
  impl = parse_options(42)
24
- impl.timeout_seconds.must_equal 42
25
- impl.shared.must_equal false
26
- impl.transaction.must_equal false
26
+ assert_equal(42, impl.timeout_seconds)
27
+ assert_not(impl.shared)
28
+ assert_not(impl.transaction)
27
29
  end
28
30
 
29
- specify 'hash with invalid key errors' do
30
- proc {
31
+ test 'hash with invalid key errors' do
32
+ assert_raises(ArgumentError) do
31
33
  parse_options(foo: 42)
32
- }.must_raise ArgumentError
34
+ end
33
35
  end
34
36
 
35
- specify 'hash with timeout_seconds sets timeout to value' do
37
+ test 'hash with timeout_seconds sets timeout to value' do
36
38
  impl = parse_options(timeout_seconds: 123)
37
- impl.timeout_seconds.must_equal 123
38
- impl.shared.must_equal false
39
- impl.transaction.must_equal false
39
+ assert_equal(123, impl.timeout_seconds)
40
+ assert_not(impl.shared)
41
+ assert_not(impl.transaction)
40
42
  end
41
43
 
42
- specify 'hash with shared option sets shared to true' do
44
+ test 'hash with shared option sets shared to true' do
43
45
  impl = parse_options(shared: true)
44
- impl.timeout_seconds.must_be_nil
45
- impl.shared.must_equal true
46
- impl.transaction.must_equal false
46
+ assert_nil(impl.timeout_seconds)
47
+ assert(impl.shared)
48
+ assert_not(impl.transaction)
47
49
  end
48
50
 
49
- specify 'hash with transaction option set transaction to true' do
51
+ test 'hash with transaction option set transaction to true' do
50
52
  impl = parse_options(transaction: true)
51
- impl.timeout_seconds.must_be_nil
52
- impl.shared.must_equal false
53
- impl.transaction.must_equal true
53
+ assert_nil(impl.timeout_seconds)
54
+ assert_not(impl.shared)
55
+ assert(impl.transaction)
54
56
  end
55
57
 
56
- specify 'hash with multiple keys sets options' do
58
+ test 'hash with multiple keys sets options' do
57
59
  foo = mock
58
60
  bar = mock
59
61
  impl = parse_options(timeout_seconds: foo, shared: bar)
60
- impl.timeout_seconds.must_equal foo
61
- impl.shared.must_equal bar
62
- impl.transaction.must_equal false
62
+ assert_equal(foo, impl.timeout_seconds)
63
+ assert_equal(bar, impl.shared)
64
+ assert_not(impl.transaction)
63
65
  end
64
66
  end