moneta 0.7.11 → 0.7.12

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 (72) hide show
  1. data/.travis.yml +2 -0
  2. data/CHANGES +5 -0
  3. data/Gemfile +5 -5
  4. data/Rakefile +2 -2
  5. data/lib/moneta.rb +1 -1
  6. data/lib/moneta/adapters/activerecord.rb +15 -5
  7. data/lib/moneta/adapters/cassandra.rb +1 -2
  8. data/lib/moneta/adapters/datamapper.rb +1 -1
  9. data/lib/moneta/adapters/file.rb +17 -17
  10. data/lib/moneta/adapters/sequel.rb +21 -16
  11. data/lib/moneta/adapters/sqlite.rb +2 -0
  12. data/lib/moneta/transformer/config.rb +1 -1
  13. data/lib/moneta/version.rb +1 -1
  14. data/script/benchmarks +9 -9
  15. data/script/generate-specs +73 -27
  16. data/script/install-bundle +1 -0
  17. data/spec/helper.rb +5 -3
  18. data/spec/moneta/adapter_activerecord_spec.rb +5 -3
  19. data/spec/moneta/adapter_client_spec.rb +2 -0
  20. data/spec/moneta/adapter_datamapper_spec.rb +6 -5
  21. data/spec/moneta/adapter_file_spec.rb +2 -0
  22. data/spec/moneta/adapter_hbase_spec.rb +1 -0
  23. data/spec/moneta/adapter_memcached_dalli_spec.rb +2 -0
  24. data/spec/moneta/adapter_memcached_dalli_with_default_expires_spec.rb +2 -0
  25. data/spec/moneta/adapter_memcached_native_spec.rb +2 -0
  26. data/spec/moneta/adapter_memcached_native_with_default_expires_spec.rb +2 -0
  27. data/spec/moneta/adapter_memcached_spec.rb +2 -0
  28. data/spec/moneta/adapter_memcached_with_default_expires_spec.rb +2 -0
  29. data/spec/moneta/adapter_mongo_spec.rb +2 -0
  30. data/spec/moneta/adapter_mongo_with_default_expires_spec.rb +2 -0
  31. data/spec/moneta/adapter_pstore_spec.rb +2 -0
  32. data/spec/moneta/adapter_redis_spec.rb +2 -0
  33. data/spec/moneta/adapter_redis_with_default_expires_spec.rb +2 -0
  34. data/spec/moneta/adapter_sequel_spec.rb +3 -1
  35. data/spec/moneta/adapter_tokyotyrant_spec.rb +2 -0
  36. data/spec/moneta/adapter_yaml_spec.rb +2 -0
  37. data/spec/moneta/cache_file_memory_spec.rb +2 -0
  38. data/spec/moneta/expires_file_spec.rb +2 -0
  39. data/spec/moneta/pool_spec.rb +2 -0
  40. data/spec/moneta/proxy_redis_spec.rb +2 -0
  41. data/spec/moneta/shared_tcp_spec.rb +2 -0
  42. data/spec/moneta/shared_unix_spec.rb +2 -0
  43. data/spec/moneta/simple_activerecord_spec.rb +3 -1
  44. data/spec/moneta/simple_activerecord_with_expires_spec.rb +3 -1
  45. data/spec/moneta/simple_client_tcp_spec.rb +2 -0
  46. data/spec/moneta/simple_client_unix_spec.rb +2 -0
  47. data/spec/moneta/simple_datamapper_spec.rb +2 -1
  48. data/spec/moneta/simple_datamapper_with_expires_spec.rb +2 -1
  49. data/spec/moneta/simple_datamapper_with_repository_spec.rb +2 -1
  50. data/spec/moneta/simple_file_spec.rb +2 -0
  51. data/spec/moneta/simple_file_with_expires_spec.rb +2 -0
  52. data/spec/moneta/simple_hashfile_spec.rb +2 -0
  53. data/spec/moneta/simple_hashfile_with_expires_spec.rb +2 -0
  54. data/spec/moneta/simple_hbase_spec.rb +1 -0
  55. data/spec/moneta/simple_hbase_with_expires_spec.rb +2 -0
  56. data/spec/moneta/simple_memcached_dalli_spec.rb +2 -0
  57. data/spec/moneta/simple_memcached_native_spec.rb +2 -0
  58. data/spec/moneta/simple_memcached_spec.rb +2 -0
  59. data/spec/moneta/simple_mongo_spec.rb +2 -0
  60. data/spec/moneta/simple_pstore_spec.rb +2 -0
  61. data/spec/moneta/simple_pstore_with_expires_spec.rb +2 -0
  62. data/spec/moneta/simple_redis_spec.rb +2 -0
  63. data/spec/moneta/simple_sequel_spec.rb +3 -1
  64. data/spec/moneta/simple_sequel_with_expires_spec.rb +3 -1
  65. data/spec/moneta/simple_tokyotyrant_spec.rb +2 -0
  66. data/spec/moneta/simple_tokyotyrant_with_expires_spec.rb +2 -0
  67. data/spec/moneta/simple_yaml_spec.rb +2 -0
  68. data/spec/moneta/simple_yaml_with_expires_spec.rb +2 -0
  69. data/spec/moneta/stack_memory_file_spec.rb +2 -0
  70. data/spec/monetaspecs.rb +50 -0
  71. data/spec/rack/moneta_cookies_spec.rb +2 -1
  72. metadata +2 -2
data/.travis.yml CHANGED
@@ -14,6 +14,8 @@ before_install:
14
14
  - script/install-bundle
15
15
  - script/upload-bundle
16
16
  install: 'echo "Bundle installed"'
17
+ before_script:
18
+ - mysql -e 'create database moneta;'
17
19
  env:
18
20
  global:
19
21
  - secure: "B0vx1g1CB1A6mM3B/iy2ATicfS4OXT80bb2RVe8mSRsPzez1B4q4Q4hJcaMI\nrMARONN8Krtnti+IqvmDnB0Z0AKYMEyIc+zT37zJOCjLdkLJl+x/thuU/MbC\nvlLVwjMf6JE2EUzTfORDRFYc5ycCqfsfgNk1Go0D2CPT6P9u9uQ="
data/CHANGES CHANGED
@@ -1,3 +1,8 @@
1
+ 0.7.12
2
+
3
+ * Concurrency tests added
4
+ * Bugfixes for File, Sqlite, Sequel and Datamapper, ActiveRecord
5
+
1
6
  0.7.11
2
7
 
3
8
  * Logger: Add option :file
data/Gemfile CHANGED
@@ -29,9 +29,9 @@ gem 'faraday'
29
29
  gem 'daybreak'
30
30
  gem 'dm-core'
31
31
  gem 'dm-migrations'
32
- gem 'dm-sqlite-adapter'
32
+ gem 'dm-mysql-adapter'
33
33
  gem 'fog'
34
- gem 'activerecord', '>= 3.2.9'
34
+ gem 'activerecord', '>= 3.2.11'
35
35
  gem 'redis'
36
36
  gem 'mongo'
37
37
  gem 'sequel'
@@ -53,9 +53,9 @@ end
53
53
  gem 'memcached', :platforms => :ruby
54
54
  gem 'jruby-memcached', :platforms => :jruby
55
55
  gem 'sqlite3', :platforms => :ruby
56
- gem 'jdbc-sqlite3', :platforms => :jruby
57
56
  gem 'activerecord-jdbc-adapter', :platforms => :jruby
58
- gem 'activerecord-jdbcsqlite3-adapter', :platforms => :jruby
57
+ gem 'activerecord-jdbcmysql-adapter', :platforms => :jruby
58
+ gem 'mysql2', '>= 0.3.12b5', :platforms => :ruby
59
59
  # gdbm for jruby needs ffi
60
60
  gem 'ffi', :platforms => :jruby
61
61
  gem 'gdbm', :platforms => :jruby
@@ -65,5 +65,5 @@ gem 'rack'
65
65
  gem 'rack-cache'
66
66
 
67
67
  # Rails integration testing
68
- gem 'actionpack'
68
+ gem 'actionpack', '>= 3.2.11'
69
69
  gem 'minitest'
data/Rakefile CHANGED
@@ -33,8 +33,8 @@ task :test do
33
33
  # QuickLZ is also not maintained on Github, but on Bitbucket
34
34
  # and I don't know where the issue tracker is.
35
35
  #
36
- # * action_dispatch cannot be required for an unknown reason
37
- unstable = specs.select {|s| s =~ /quicklz|action_dispatch/ }
36
+ # * Cassandra fails spuriously (An expert has to check the adapter!)
37
+ unstable = specs.select {|s| s =~ /quicklz|cassandra/ }
38
38
  specs -= unstable
39
39
 
40
40
  if group =~ /^(\d+)\/(\d+)$/
data/lib/moneta.rb CHANGED
@@ -103,7 +103,7 @@ module Moneta
103
103
  raise ArgumentError, 'Name must be Symbol' unless Symbol === name
104
104
  case name
105
105
  when :Sequel, :ActiveRecord, :Couch, :DataMapper
106
- # Sequel accept only base64 keys and values
106
+ # Sequel, DataMapper and AR accept only base64 keys and values
107
107
  # FIXME: Couch should work only with :marshal but this raises an error on 1.9
108
108
  transformer[:key] << :base64
109
109
  transformer[:value] << :base64
@@ -78,11 +78,21 @@ module Moneta
78
78
 
79
79
  # (see Proxy#increment)
80
80
  def increment(key, amount = 1, options = {})
81
- record = @table.where(:k => key).lock.first_or_initialize
82
- value = Utils.to_int(record.v) + amount
83
- record.v = value.to_s
84
- record.save
85
- value
81
+ @table.transaction do
82
+ if record = @table.where(:k => key).lock.first
83
+ value = Utils.to_int(record.v) + amount
84
+ record.v = value.to_s
85
+ record.save
86
+ value
87
+ elsif create(key, amount.to_s, options)
88
+ amount
89
+ else
90
+ raise 'Concurrent modification'
91
+ end
92
+ end
93
+ rescue
94
+ tries ||= 0
95
+ (tries += 1) < 10 ? retry : raise
86
96
  end
87
97
 
88
98
  # (see Proxy#create)
@@ -62,8 +62,7 @@ module Moneta
62
62
 
63
63
  # (see Proxy#load)
64
64
  def load(key, options = {})
65
- value = @backend.get(@cf, key)
66
- if value
65
+ if value = @backend.get(@cf, key)
67
66
  expires = expires_value(options, nil)
68
67
  @backend.insert(@cf, key, {'value' => value['value'] }, :ttl => expires || nil) if expires != nil
69
68
  value['value']
@@ -12,7 +12,7 @@ module Moneta
12
12
 
13
13
  class Store
14
14
  include ::DataMapper::Resource
15
- property :k, Text, :key => true
15
+ property :k, String, :key => true, :length => 255
16
16
  property :v, Text, :lazy => false
17
17
  self.raise_on_save_failure = true
18
18
  end
@@ -6,9 +6,7 @@ module Moneta
6
6
  # @api public
7
7
  class File
8
8
  include Defaults
9
- include IncrementSupport
10
-
11
- supports :create
9
+ supports :create, :increment
12
10
 
13
11
  # @param [Hash] options
14
12
  # @option options [String] :dir Directory where files will be stored
@@ -64,7 +62,22 @@ module Moneta
64
62
 
65
63
  # (see Proxy#increment)
66
64
  def increment(key, amount = 1, options = {})
67
- lock(key) { super }
65
+ path = store_path(key)
66
+ ::File.open(path, 'rb+') do |f|
67
+ Thread.pass until f.flock(::File::LOCK_EX)
68
+ value = Utils.to_int(f.read) + amount
69
+ f.truncate(0)
70
+ f.pos = 0
71
+ f.write(value.to_s)
72
+ value
73
+ end
74
+ rescue Errno::ENOENT
75
+ if create(key, amount.to_s, options)
76
+ amount
77
+ else
78
+ # Concurrent modification
79
+ retry
80
+ end
68
81
  end
69
82
 
70
83
  # (see Proxy#create)
@@ -82,19 +95,6 @@ module Moneta
82
95
 
83
96
  protected
84
97
 
85
- def lock(key, &block)
86
- path = store_path(key)
87
- return yield unless ::File.exist?(path)
88
- ::File.open(path, 'r+') do |f|
89
- begin
90
- Thread.pass until f.flock(::File::LOCK_EX)
91
- yield
92
- ensure
93
- f.flock(::File::LOCK_UN)
94
- end
95
- end
96
- end
97
-
98
98
  def store_path(key)
99
99
  ::File.join(@dir, key)
100
100
  end
@@ -16,7 +16,7 @@ module Moneta
16
16
  # @option options All other options passed to `Sequel#connect`
17
17
  # @option options [Sequel connection] :backend Use existing backend instance
18
18
  def initialize(options = {})
19
- table = options.delete(:table) || :moneta
19
+ table = (options.delete(:table) || :moneta).to_sym
20
20
  @backend = options[:backend] ||
21
21
  begin
22
22
  raise ArgumentError, 'Option :db is required' unless db = options.delete(:db)
@@ -42,12 +42,10 @@ module Moneta
42
42
 
43
43
  # (see Proxy#store)
44
44
  def store(key, value, options = {})
45
- @backend.transaction do
46
- begin
47
- @table.insert(:k => key, :v => value)
48
- rescue ::Sequel::DatabaseError
49
- @table.update(:k => key, :v => value)
50
- end
45
+ begin
46
+ @table.insert(:k => key, :v => value)
47
+ rescue ::Sequel::DatabaseError
48
+ @table.where(:k => key).update(:v => value)
51
49
  end
52
50
  value
53
51
  rescue ::Sequel::DatabaseError
@@ -57,9 +55,7 @@ module Moneta
57
55
 
58
56
  # (see Proxy#store)
59
57
  def create(key, value, options = {})
60
- @backend.transaction do
61
- @table.insert(:k => key, :v => value)
62
- end
58
+ @table.insert(:k => key, :v => value)
63
59
  true
64
60
  rescue ::Sequel::DatabaseError
65
61
  # FIXME: This catches too many errors
@@ -73,22 +69,25 @@ module Moneta
73
69
  locked_table = @table.for_update
74
70
  if record = locked_table[:k => key]
75
71
  value = Utils.to_int(record[:v]) + amount
76
- locked_table.update(:k => key, :v => value.to_s)
72
+ locked_table.where(:k => key).update(:v => value.to_s)
77
73
  value
78
74
  else
79
75
  locked_table.insert(:k => key, :v => amount.to_s)
80
76
  amount
81
77
  end
82
78
  end
79
+ rescue ::Sequel::DatabaseError
80
+ # FIXME: This catches too many errors
81
+ # it should only catch a not-unique-exception
82
+ tries ||= 0
83
+ (tries += 1) < 10 ? retry : raise
83
84
  end
84
85
 
85
86
  # (see Proxy#delete)
86
87
  def delete(key, options = {})
87
- @backend.transaction do
88
- if value = load(key, options)
89
- @table.filter(:k => key).delete
90
- value
91
- end
88
+ if value = load(key, options)
89
+ @table.filter(:k => key).delete
90
+ value
92
91
  end
93
92
  end
94
93
 
@@ -97,6 +96,12 @@ module Moneta
97
96
  @table.delete
98
97
  self
99
98
  end
99
+
100
+ # (see Proxy#close)
101
+ def close
102
+ @backend.disconnect
103
+ nil
104
+ end
100
105
  end
101
106
  end
102
107
  end
@@ -14,6 +14,7 @@ module Moneta
14
14
  # @param [Hash] options
15
15
  # @option options [String] :file Database file
16
16
  # @option options [String] :table ('moneta') Table name
17
+ # @option options [Fixnum] :busy_timeout (1000) Sqlite timeout if database is busy
17
18
  # @option options [::Sqlite3::Database] :backend Use existing backend instance
18
19
  def initialize(options = {})
19
20
  table = options[:table] || 'moneta'
@@ -22,6 +23,7 @@ module Moneta
22
23
  raise ArgumentError, 'Option :file is required' unless options[:file]
23
24
  ::SQLite3::Database.new(options[:file])
24
25
  end
26
+ @backend.busy_timeout(options[:busy_timeout] || 1000)
25
27
  @backend.execute("create table if not exists #{table} (k blob not null primary key, v blob)")
26
28
  @stmts =
27
29
  [@select = @backend.prepare("select v from #{table} where k = ?"),
@@ -25,7 +25,7 @@ module Moneta
25
25
  [ :encode, "%s.unpack('m').first", "[%s].pack('m').gsub(\"\n\", '')" ],
26
26
  :escape => [ :encode, 'Helper.unescape(%s)', 'Helper.escape(%s)' ],
27
27
  :hmac => [ :hmac, 'Helper.hmacverify(%s, options[:secret] || @secret)',
28
- 'Helper.hmacsign(%s, options[:secret] || @secret)', 'openssl' ],
28
+ 'Helper.hmacsign(%s, options[:secret] || @secret)', 'openssl' ],
29
29
  :truncate => [ :truncate, nil, 'Helper.truncate(%s, @maxlen)', 'digest/md5' ],
30
30
  :md5 => [ :digest, nil, '::Digest::MD5.hexdigest(%s)', 'digest/md5' ],
31
31
  :rmd160 => [ :digest, nil, '::Digest::RMD160.hexdigest(%s)', 'digest/rmd160' ],
@@ -1,5 +1,5 @@
1
1
  module Moneta
2
2
  # Moneta version number
3
3
  # @api public
4
- VERSION = '0.7.11'
4
+ VERSION = '0.7.12'
5
5
  end
data/script/benchmarks CHANGED
@@ -34,18 +34,18 @@ class MonetaBenchmarks
34
34
  # SDBM is unstable, YAML is too slow
35
35
  # :SDBM => { :file => "#{DIR}/sdbm" },
36
36
  # :YAML => { :file => "#{DIR}/yaml" },
37
- :ActiveRecord => { :connection => { :adapter => 'sqlite3', :database => ':memory:' } },
37
+ :ActiveRecord => { :table => 'activerecord', :connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql2'), :database => 'moneta' } },
38
38
  :Cassandra => {},
39
39
  :Client => {},
40
40
  :Couch => {},
41
41
  :DBM => { :file => "#{DIR}/dbm" },
42
- :DataMapper => { :setup => "sqlite3:#{DIR}/datamapper" },
42
+ :DataMapper => { :setup => 'mysql://root:@localhost/moneta', :table => 'datamapper' },
43
43
  :Daybreak => { :file => "#{DIR}/daybreak" },
44
44
  :File => { :dir => "#{DIR}/file" },
45
45
  :GDBM => { :file => "#{DIR}/gdbm" },
46
46
  :HBase => {},
47
47
  :HashFile => { :dir => "#{DIR}/hashfile" },
48
- :KyotoCabinet => { :file => 'kyotocabinet.kch' },
48
+ :KyotoCabinet => { :file => "#{DIR}/kyotocabinet.kch" },
49
49
  :LRUHash => {},
50
50
  :LevelDB => { :dir => "#{DIR}/leveldb" },
51
51
  :LocalMemCache => { :file => "#{DIR}/lmc" },
@@ -57,10 +57,10 @@ class MonetaBenchmarks
57
57
  :Redis => {},
58
58
  :RestClient => { :url => 'http://localhost:8808/' },
59
59
  :Riak => {},
60
- :Sequel => { :db => 'sqlite:/' },
60
+ :Sequel => { :db => (defined?(JRUBY_VERSION) ? 'jdbc:mysql' : 'mysql2') + '://root:@localhost/moneta', :table => 'sequel' },
61
61
  :Sqlite => { :file => ':memory:' },
62
62
  :TDB => { :file => "#{DIR}/tdb" },
63
- :TokyoCabinet => { :file => 'tokyocabinet' },
63
+ :TokyoCabinet => { :file => "#{DIR}/tokyocabinet" },
64
64
  :TokyoTyrant => {},
65
65
  }
66
66
 
@@ -79,7 +79,7 @@ class MonetaBenchmarks
79
79
  :runs => 3,
80
80
  :keys => 1000,
81
81
  :min_key_len => 3,
82
- :max_key_len => 200,
82
+ :max_key_len => 128,
83
83
  :key_dist => :uniform,
84
84
  :min_val_len => 0,
85
85
  :max_val_len => 1024,
@@ -89,7 +89,7 @@ class MonetaBenchmarks
89
89
  :runs => 3,
90
90
  :keys => 100,
91
91
  :min_key_len => 3,
92
- :max_key_len => 200,
92
+ :max_key_len => 128,
93
93
  :key_dist => :uniform,
94
94
  :min_val_len => 0,
95
95
  :max_val_len => 10240,
@@ -109,7 +109,7 @@ class MonetaBenchmarks
109
109
  :runs => 3,
110
110
  :keys => 1000,
111
111
  :min_key_len => 3,
112
- :max_key_len => 200,
112
+ :max_key_len => 128,
113
113
  :key_dist => :normal,
114
114
  :min_val_len => 0,
115
115
  :max_val_len => 1024,
@@ -119,7 +119,7 @@ class MonetaBenchmarks
119
119
  :runs => 3,
120
120
  :keys => 100,
121
121
  :min_key_len => 3,
122
- :max_key_len => 200,
122
+ :max_key_len => 128,
123
123
  :key_dist => :normal,
124
124
  :min_val_len => 0,
125
125
  :max_val_len => 10240,
@@ -38,15 +38,19 @@ class Specs
38
38
  end
39
39
 
40
40
  def without_increment
41
- new(:specs => specs - [:increment] + [:not_increment])
41
+ new(:specs => specs - [:increment, :concurrent_increment] + [:not_increment])
42
+ end
43
+
44
+ def without_concurrent
45
+ new(:specs => specs - [:concurrent_increment, :concurrent_create])
42
46
  end
43
47
 
44
48
  def without_persist
45
- new(:specs => specs - [:persist, :multiprocess] + [:not_persist])
49
+ new(:specs => specs - [:persist, :multiprocess, :concurrent_increment, :concurrent_create] + [:not_persist])
46
50
  end
47
51
 
48
52
  def without_multiprocess
49
- new(:specs => specs - [:multiprocess])
53
+ new(:specs => specs - [:multiprocess, :concurrent_increment, :concurrent_create])
50
54
  end
51
55
 
52
56
  def with_expires
@@ -95,12 +99,12 @@ class Specs
95
99
  end
96
100
 
97
101
  def without_create
98
- new(:specs => specs - [:create, :create_expires] + [:not_create])
102
+ new(:specs => specs - [:create, :concurrent_create, :create_expires] + [:not_create])
99
103
  end
100
104
  end
101
105
 
102
- ADAPTER_SPECS = Specs.new(:specs => [:null, :store, :returndifferent, :increment, :persist, :multiprocess, :create, :features], :key => %w(string), :value => %w(string))
103
- STANDARD_SPECS = Specs.new(:specs => [:null, :store, :returndifferent, :marshallable_key, :marshallable_value, :transform_value, :increment, :persist, :multiprocess, :create, :features])
106
+ ADAPTER_SPECS = Specs.new(:specs => [:null, :store, :returndifferent, :increment, :concurrent_increment, :concurrent_create, :persist, :multiprocess, :create, :features], :key => %w(string), :value => %w(string))
107
+ STANDARD_SPECS = Specs.new(:specs => [:null, :store, :returndifferent, :marshallable_key, :marshallable_value, :transform_value, :increment, :concurrent_increment, :concurrent_create, :persist, :multiprocess, :create, :features])
104
108
  TRANSFORMER_SPECS = Specs.new(:specs => [:null, :store, :returndifferent, :transform_value, :increment, :create, :features])
105
109
 
106
110
  header = "# Generated by #{File.basename(__FILE__)}\n"
@@ -352,12 +356,12 @@ end
352
356
  'simple_sqlite' => {
353
357
  :store => :Sqlite,
354
358
  :options => ':file => File.join(make_tempdir, "simple_sqlite")',
355
- :specs => STANDARD_SPECS
359
+ :specs => STANDARD_SPECS.without_concurrent
356
360
  },
357
361
  'simple_sqlite_with_expires' => {
358
362
  :store => :Sqlite,
359
363
  :options => ':file => File.join(make_tempdir, "simple_sqlite_with_expires"), :expires => true',
360
- :specs => STANDARD_SPECS.with_expires
364
+ :specs => STANDARD_SPECS.with_expires.without_concurrent
361
365
  },
362
366
  'simple_redis' => {
363
367
  :store => :Redis,
@@ -419,27 +423,27 @@ end
419
423
  },
420
424
  'simple_sequel' => {
421
425
  :store => :Sequel,
422
- :options => ':db => (defined?(JRUBY_VERSION) ? "jdbc:sqlite:" : "sqlite:") + File.join(make_tempdir, "simple_sequel")',
426
+ :options => ':db => (defined?(JRUBY_VERSION) ? "jdbc:mysql" : "mysql2") + "://root:@localhost/moneta", :table => "simple_sequel"',
423
427
  :load_value => '::Marshal.load(value.unpack(\'m\').first)',
424
428
  :specs => STANDARD_SPECS
425
429
  },
426
430
  'simple_sequel_with_expires' => {
427
431
  :store => :Sequel,
428
- :options => ':db => (defined?(JRUBY_VERSION) ? "jdbc:sqlite:" : "sqlite:") + File.join(make_tempdir, "simple_sequel_with_expires"), :expires => true',
432
+ :options => ':db => (defined?(JRUBY_VERSION) ? "jdbc:mysql" : "mysql2") + "://root:@localhost/moneta", :table => "simple_sequel_with_expires", :expires => true',
429
433
  :specs => STANDARD_SPECS.with_expires,
430
434
  :load_value => '::Marshal.load(value.unpack(\'m\').first)'
431
435
  },
432
436
  'simple_datamapper' => {
433
437
  :store => :DataMapper,
434
438
  :specs => STANDARD_SPECS.without_increment,
435
- :options => ':setup => "sqlite3://#{make_tempdir}/simple_datamapper"',
439
+ :options => ':setup => "mysql://root:@localhost/moneta", :table => "simple_datamapper"',
436
440
  # DataMapper needs default repository to be setup
437
441
  :preamble => "require 'dm-core'\nDataMapper.setup(:default, :adapter => :in_memory)\n",
438
442
  :load_value => '::Marshal.load(value.unpack(\'m\').first)'
439
443
  },
440
444
  'simple_datamapper_with_expires' => {
441
445
  :store => :DataMapper,
442
- :options => ':setup => "sqlite3://#{make_tempdir}/simple_datamapper_with_expires", :expires => true',
446
+ :options => ':setup => "mysql://root:@localhost/moneta", :table => "simple_datamapper_with_expires", :expires => true',
443
447
  # DataMapper needs default repository to be setup
444
448
  :preamble => "require 'dm-core'\nDataMapper.setup(:default, :adapter => :in_memory)\n",
445
449
  :specs => STANDARD_SPECS.without_increment.with_expires,
@@ -448,7 +452,7 @@ end
448
452
  'simple_datamapper_with_repository' => {
449
453
  :store => :DataMapper,
450
454
  :specs => STANDARD_SPECS.without_increment,
451
- :options => ':repository => :repo, :setup => "sqlite3://#{make_tempdir}/simple_datamapper_with_repository"',
455
+ :options => ':repository => :repo, :setup => "mysql://root:@localhost/moneta", :table => "simple_datamapper_with_repository"',
452
456
  # DataMapper needs default repository to be setup
453
457
  :preamble => "require 'dm-core'\nDataMapper.setup(:default, :adapter => :in_memory)\n",
454
458
  :load_value => '::Marshal.load(value.unpack(\'m\').first)'
@@ -456,12 +460,12 @@ end
456
460
  'simple_activerecord' => {
457
461
  :store => :ActiveRecord,
458
462
  :specs => STANDARD_SPECS,
459
- :options => ":connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'), :database => File.join(make_tempdir, 'simple_activerecord') }",
463
+ :options => ":table => 'simple_activerecord', :connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql2'), :database => 'moneta' }",
460
464
  :load_value => '::Marshal.load(value.unpack(\'m\').first)'
461
465
  },
462
466
  'simple_activerecord_with_expires' => {
463
467
  :store => :ActiveRecord,
464
- :options => ":connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'), :database => File.join(make_tempdir, 'simple_activerecord_with_expires') }, :expires => true",
468
+ :options => ":table => 'simple_activerecord_with_expires', :connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql2'), :database => 'moneta' }, :expires => true",
465
469
  :specs => STANDARD_SPECS.with_expires,
466
470
  :load_value => '::Marshal.load(value.unpack(\'m\').first)'
467
471
  },
@@ -491,14 +495,14 @@ end
491
495
  use :WeakCreate
492
496
  adapter :Couch, :db => 'weak_create'
493
497
  end},
494
- :specs => ADAPTER_SPECS.without_increment
498
+ :specs => ADAPTER_SPECS.without_increment.without_concurrent
495
499
  },
496
500
  'weak_increment' => {
497
501
  :build => %{Moneta.build do
498
502
  use :WeakIncrement
499
503
  adapter :Couch, :db => 'weak_increment'
500
504
  end},
501
- :specs => ADAPTER_SPECS.without_create
505
+ :specs => ADAPTER_SPECS.without_create.without_concurrent
502
506
  },
503
507
  'expires_memory' => {
504
508
  :build => %{Moneta.build do
@@ -1097,7 +1101,7 @@ it 'compile transformer class' do
1097
1101
  end}
1098
1102
  },
1099
1103
  'adapter_activerecord' => {
1100
- :build => "Moneta::Adapters::ActiveRecord.new(:connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'), :database => File.join(make_tempdir, 'adapter_activerecord') })",
1104
+ :build => "Moneta::Adapters::ActiveRecord.new(:table => 'adapter_activerecord', :connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql2'), :database => 'moneta' })",
1101
1105
  :specs => ADAPTER_SPECS,
1102
1106
  :tests => %{
1103
1107
  it 'updates an existing key/value' do
@@ -1108,9 +1112,9 @@ it 'updates an existing key/value' do
1108
1112
  end
1109
1113
 
1110
1114
  it 'uses an existing connection' do
1111
- ActiveRecord::Base.establish_connection :adapter => (defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'), :database => File.join(make_tempdir, 'activerecord-existing')
1115
+ ActiveRecord::Base.establish_connection :adapter => (defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql2'), :database => 'moneta'
1112
1116
 
1113
- store = Moneta::Adapters::ActiveRecord.new
1117
+ store = Moneta::Adapters::ActiveRecord.new(:table => 'activerecord_existing')
1114
1118
  store.table.should be_table_exists
1115
1119
  end
1116
1120
  }
@@ -1146,16 +1150,16 @@ end
1146
1150
  :specs => ADAPTER_SPECS.without_increment.without_create
1147
1151
  },
1148
1152
  'adapter_datamapper' => {
1149
- :build => 'Moneta::Adapters::DataMapper.new(:setup => "sqlite3://#{make_tempdir}/adapter_datamapper")',
1153
+ :build => 'Moneta::Adapters::DataMapper.new(:setup => "mysql://root:@localhost/moneta", :table => "adapter_datamapper")',
1150
1154
  # DataMapper needs default repository to be setup
1151
1155
  :preamble => "require 'dm-core'\nDataMapper.setup(:default, :adapter => :in_memory)\n",
1152
1156
  :specs => ADAPTER_SPECS.without_increment,
1153
1157
  :tests => %q{
1154
1158
  it 'does not cross contaminate when storing' do
1155
- first = Moneta::Adapters::DataMapper.new(:setup => "sqlite3://#{make_tempdir}/datamapper-first")
1159
+ first = Moneta::Adapters::DataMapper.new(:setup => "mysql://root:@localhost/moneta", :table => "datamapper_first")
1156
1160
  first.clear
1157
1161
 
1158
- second = Moneta::Adapters::DataMapper.new(:repository => :sample, :setup => "sqlite3://#{make_tempdir}/datamapper-second")
1162
+ second = Moneta::Adapters::DataMapper.new(:repository => :sample, :setup => "mysql://root:@localhost/moneta", :table => "datamapper_second")
1159
1163
  second.clear
1160
1164
 
1161
1165
  first['key'] = 'value'
@@ -1166,10 +1170,10 @@ it 'does not cross contaminate when storing' do
1166
1170
  end
1167
1171
 
1168
1172
  it 'does not cross contaminate when deleting' do
1169
- first = Moneta::Adapters::DataMapper.new(:setup => "sqlite3://#{make_tempdir}/datamapper-first")
1173
+ first = Moneta::Adapters::DataMapper.new(:setup => "mysql://root:@localhost/moneta", :table => "datamapper_first")
1170
1174
  first.clear
1171
1175
 
1172
- second = Moneta::Adapters::DataMapper.new(:repository => :sample, :setup => "sqlite3://#{make_tempdir}/datamapper-second")
1176
+ second = Moneta::Adapters::DataMapper.new(:repository => :sample, :setup => "mysql://root:@localhost/moneta", :table => "datamapper_second")
1173
1177
  second.clear
1174
1178
 
1175
1179
  first['key'] = 'value'
@@ -1304,12 +1308,12 @@ end}
1304
1308
  :specs => ADAPTER_SPECS.without_multiprocess
1305
1309
  },
1306
1310
  'adapter_sequel' => {
1307
- :build => 'Moneta::Adapters::Sequel.new(:db => (defined?(JRUBY_VERSION) ? "jdbc:sqlite:" : "sqlite:") + File.join(make_tempdir, "adapter_sequel"))',
1311
+ :build => 'Moneta::Adapters::Sequel.new(:db => (defined?(JRUBY_VERSION) ? "jdbc:mysql" : "mysql2") + "://root:@localhost/moneta", :table => "adapter_sequel")',
1308
1312
  :specs => ADAPTER_SPECS
1309
1313
  },
1310
1314
  'adapter_sqlite' => {
1311
1315
  :build => 'Moneta::Adapters::Sqlite.new(:file => File.join(make_tempdir, "adapter_sqlite"))',
1312
- :specs => ADAPTER_SPECS
1316
+ :specs => ADAPTER_SPECS.without_concurrent
1313
1317
  },
1314
1318
  'adapter_kyotocabinet' => {
1315
1319
  :build => 'Moneta::Adapters::KyotoCabinet.new(:file => File.join(make_tempdir, "adapter_kyotocabinet.kch"))',
@@ -1966,6 +1970,48 @@ it 'does not support #decrement' do
1966
1970
  end.to raise_error(NotImplementedError)
1967
1971
  end}
1968
1972
 
1973
+ SPECS['concurrent_increment'] = %{def increment_thread(name)
1974
+ Thread.new do
1975
+ s = new_store
1976
+ 1000.times do |i|
1977
+ s.increment('counter')
1978
+ s.store("\#{name}\#{i}", i.to_s, :expires => false)
1979
+ sleep 0.01 if i % 100 == 0
1980
+ end
1981
+ s.close
1982
+ end
1983
+ end
1984
+
1985
+ it 'have atomic increment across multiple processes' do
1986
+ a = increment_thread('a')
1987
+ b = increment_thread('b')
1988
+ a.join
1989
+ b.join
1990
+ 1000.times do |i|
1991
+ store["a\#{i}"].should == i.to_s
1992
+ store["b\#{i}"].should == i.to_s
1993
+ end
1994
+ store.raw['counter'].should == 2000.to_s
1995
+ end}
1996
+
1997
+ SPECS['concurrent_create'] = %{def create_thread(name)
1998
+ a = Thread.new do
1999
+ s = new_store
2000
+ 1000.times do |i|
2001
+ s[i.to_s].should == name if s.create(i.to_s, name, :expires => false)
2002
+ sleep 0.01 if i % 100 == 0
2003
+ end
2004
+ s.close
2005
+ end
2006
+ end
2007
+
2008
+ it 'have atomic create across multiple processes' do
2009
+ a = create_thread('a')
2010
+ b = create_thread('b')
2011
+ a.join
2012
+ b.join
2013
+ end}
2014
+
1969
2015
  SPECS['increment'] = %{it 'initializes in #increment with 1' do
1970
2016
  store.key?('inckey').should be_false
1971
2017
  store.increment('inckey').should == 1