with_advisory_lock 4.0.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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