#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

PATH = File.expand_path(File.join(__FILE__, '..', '..', 'spec'))

class Specs
  attr_reader :key, :value, :specs, :features

  def initialize(options = {})
    @specs = options.delete(:specs).to_a
    @key = options.delete(:key)     || %w(object string binary hash boolean nil integer number)
    @value = options.delete(:value) || %w(object string binary hash boolean nil integer number)
    @features = []
    [:expires, :expires_native, :increment, :create].each do |feature|
      @features << feature if @specs.include?(feature)
    end
    @features.sort_by!(&:to_s)
    @features.uniq!
  end

  def new(options)
    Specs.new({:specs => specs, :key => key, :value => value}.merge(options))
  end

  def without_path
    new(:key => key - %w(path))
  end

  def stringvalues_only
    new(:value => %w(string))
  end

  def simplekeys_only
    new(:key => %w(string hash integer))
  end

  def simplevalues_only
    new(:value => %w(string hash integer))
  end

  def without_increment
    new(:specs => specs - [:increment, :concurrent_increment] + [:not_increment])
  end

  def without_large
    new(:specs => specs - [:store_large])
  end

  def without_concurrent
    new(:specs => specs - [:concurrent_increment, :concurrent_create])
  end

  def without_persist
    new(:specs => specs - [:persist, :multiprocess, :concurrent_increment, :concurrent_create] + [:not_persist])
  end

  def without_multiprocess
    new(:specs => specs - [:multiprocess, :concurrent_increment, :concurrent_create])
  end

  def with_expires
    a = specs.dup
    if a.include?(:transform_value)
      a.delete(:transform_value)
      a << :transform_value_expires
    end
    a << :create_expires if a.include?(:create)
    a << :expires
    new(:specs => a)
  end

  def with_native_expires
    a = specs.dup
    a << :create_expires if a.include?(:create)
    new(:specs => a + [:expires])
  end

  def without_marshallable
    new(:specs => specs - [:marshallable_value, :marshallable_key])
  end

  def without_transform
    new(:specs => specs - [:marshallable_value, :marshallable_key, :transform_value])
  end

  def returnsame
    new(:specs => specs - [:returndifferent] + [:returnsame])
  end

  def without_marshallable_key
    new(:specs => specs - [:marshallable_key])
  end

  def without_marshallable_value
    new(:specs => specs - [:marshallable_value])
  end

  def without_store
    new(:specs => specs - [:store, :store_large, :transform_value, :marshallable_value])
  end

  def with_default_expires
    new(:specs => specs + [:default_expires])
  end

  def without_create
    new(:specs => specs - [:create, :concurrent_create, :create_expires] + [:not_create])
  end
end

ADAPTER_SPECS = Specs.new(:specs => [:null, :store, :returndifferent, :increment, :concurrent_increment, :concurrent_create, :persist, :multiprocess, :create, :features, :store_large], :key => %w(string path), :value => %w(string path))
STANDARD_SPECS = Specs.new(:specs => [:null, :store, :returndifferent, :marshallable_key, :marshallable_value, :transform_value, :increment, :concurrent_increment, :concurrent_create, :persist, :multiprocess, :create, :features, :store_large])
TRANSFORMER_SPECS = Specs.new(:specs => [:null, :store, :returndifferent, :transform_value, :increment, :create, :features, :store_large])

header = "# coding: binary\n# Generated by #{File.basename(__FILE__)}\n"

TESTS = {
  'standard_client_tcp' => {
    :preamble => "start_server(Moneta::Adapters::Memory.new)\n",
    :store => :Client,
    :specs => STANDARD_SPECS,
    :tests => %{
it 'supports multiple clients' do
  client = Moneta.new(:Client)
  client['shared_key'] = 'shared_val'
  (1..100).each do |i|
    Thread.new do
      client = Moneta.new(:Client)
      (1.100).each do |j|
        client['shared_key'].should == 'shared_val'
        client["key-\#{j}-\#{i}"] = "val-\#{j}-\#{i}"
        client["key-\#{j}-\#{i}"].should == "val-\#{j}-\#{i}"
      end
    end
  end
end
}
  },
  'standard_client_unix' => {
    :preamble => "start_server(Moneta::Adapters::Memory.new, :socket => File.join(make_tempdir, 'standard_client_unix'))\n",
    :store => :Client,
    :options => ":socket => File.join(make_tempdir, 'standard_client_unix')",
    :specs => STANDARD_SPECS
  },
  'standard_restclient' => {
    :preamble => "start_restserver\n",
    :store => :RestClient,
    :options => ":url => 'http://localhost:8808/moneta/'",
    :specs => STANDARD_SPECS.without_increment.without_create
  },
  'standard_memory' => {
    :store => :Memory,
    :specs => STANDARD_SPECS.without_persist
  },
  'standard_memory_with_expires' => {
    :store => :Memory,
    :options => ':expires => true',
    :specs => STANDARD_SPECS.with_expires.without_persist
  },
  'standard_memory_with_compress' => {
    :store => :Memory,
    :options => ':compress => true',
    :load_value => 'Marshal.load(::Zlib::Inflate.inflate(value))',
    :specs => STANDARD_SPECS.without_persist
  },
  'standard_memory_with_prefix' => {
    :store => :Memory,
    :options => ':prefix => "moneta"',
    :specs => STANDARD_SPECS.without_persist
  },
  'standard_memory_with_json_serializer' => {
    :store => :Memory,
    :options => ':serializer => :json',
    :load_value => '::MultiJson.load(value)',
    :specs => STANDARD_SPECS.without_marshallable.simplekeys_only.simplevalues_only.without_persist
  },
  'standard_memory_with_json_key_serializer' => {
    :store => :Memory,
    :options => ':key_serializer => :json',
    :specs => STANDARD_SPECS.without_marshallable_key.simplekeys_only.without_persist,
  },
  'standard_memory_with_json_value_serializer' => {
    :store => :Memory,
    :options => ':value_serializer => :json',
    :specs => STANDARD_SPECS.without_marshallable_value.simplevalues_only.without_persist,
    :load_value => '::MultiJson.load(value)'
  },
  'standard_memory_with_snappy_compress' => {
    :store => :Memory,
    :options => ':compress => :snappy',
    :load_value => 'Marshal.load(::Snappy.inflate(value))',
    :specs => STANDARD_SPECS.without_persist
  },
  'standard_lruhash' => {
    :store => :LRUHash,
    :specs => STANDARD_SPECS.without_persist
  },
  'standard_lruhash_with_expires' => {
    :store => :LRUHash,
    :options => ':expires => true',
    :specs => STANDARD_SPECS.with_expires.without_persist,
  },
  'standard_file' => {
    :store => :File,
    :options => ':dir => File.join(make_tempdir, "simple_file")',
    :specs => STANDARD_SPECS
  },
  'standard_file_with_expires' => {
    :store => :File,
    :options => ':dir => File.join(make_tempdir, "simple_file_with_expires"), :expires => true',
    :specs => STANDARD_SPECS.with_expires
  },
  'standard_hashfile' => {
    :store => :HashFile,
    :options => ':dir => File.join(make_tempdir, "simple_hashfile")',
    :specs => STANDARD_SPECS
  },
  'standard_hashfile_with_expires' => {
    :store => :HashFile,
    :options => ':dir => File.join(make_tempdir, "simple_hashfile_with_expires"), :expires => true',
    :specs => STANDARD_SPECS.with_expires
  },
  'standard_cassandra' => {
    :store => :Cassandra,
    :options => ':keyspace => "simple_cassandra"',
    :specs => STANDARD_SPECS.without_increment.without_create.with_native_expires,
  },
  'standard_hbase' => {
    :store => :HBase,
    :options => ':table => "simple_hbase"',
    :specs => STANDARD_SPECS.without_create
  },
  'standard_hbase_with_expires' => {
    :store => :HBase,
    :options => ':table => "simple_hbase", :expires => true',
    :specs => STANDARD_SPECS.with_expires,
  },
  'standard_dbm' => {
    :store => :DBM,
    :options => ':file => File.join(make_tempdir, "simple_dbm")',
    :specs => STANDARD_SPECS.without_multiprocess
  },
  'standard_dbm_with_expires' => {
    :store => :DBM,
    :options => ':file => File.join(make_tempdir, "simple_dbm_with_expires"), :expires => true',
    :specs => STANDARD_SPECS.without_multiprocess.with_expires
  },
  'standard_tdb' => {
    :store => :TDB,
    :options => ':file => File.join(make_tempdir, "simple_tdb")',
    :specs => STANDARD_SPECS.without_multiprocess
  },
  'standard_tdb_with_expires' => {
    :store => :TDB,
    :options => ':file => File.join(make_tempdir, "simple_tdb_with_expires"), :expires => true',
    :specs => STANDARD_SPECS.without_multiprocess.with_expires
  },
  'standard_daybreak' => {
    :store => :Daybreak,
    :options => ':file => File.join(make_tempdir, "simple_daybreak")',
    :specs => STANDARD_SPECS.without_multiprocess
  },
  'standard_daybreak_with_expires' => {
    :store => :Daybreak,
    :options => ':file => File.join(make_tempdir, "simple_daybreak_with_expires"), :expires => true',
    :specs => STANDARD_SPECS.without_multiprocess.with_expires
  },
  'standard_gdbm' => {
    :store => :GDBM,
    :options => ':file => File.join(make_tempdir, "simple_gdbm")',
    :specs => STANDARD_SPECS.without_multiprocess
  },
  'standard_gdbm_with_expires' => {
    :store => :GDBM,
    :options => ':file => File.join(make_tempdir, "simple_gdbm_with_expires"), :expires => true',
    :specs => STANDARD_SPECS.without_multiprocess.with_expires
  },
  'standard_sdbm' => {
    :store => :SDBM,
    :options => ':file => File.join(make_tempdir, "simple_sdbm")',
    :specs => STANDARD_SPECS.without_multiprocess.without_large
  },
  'standard_sdbm_with_expires' => {
    :store => :SDBM,
    :options => ':file => File.join(make_tempdir, "simple_sdbm_with_expires"), :expires => true',
    :specs => STANDARD_SPECS.without_multiprocess.with_expires.without_large
  },
  'standard_leveldb' => {
    :store => :LevelDB,
    :options => ':dir => File.join(make_tempdir, "simple_leveldb")',
    :specs => STANDARD_SPECS.without_multiprocess
  },
  'standard_leveldb_with_expires' => {
    :store => :LevelDB,
    :options => ':dir => File.join(make_tempdir, "simple_leveldb_with_expires"), :expires => true',
    :specs => STANDARD_SPECS.without_multiprocess.with_expires
  },
  'standard_lmdb' => {
    :store => :LMDB,
    :options => ':dir => File.join(make_tempdir, "simple_lmdb")',
    :specs => STANDARD_SPECS.without_concurrent
  },
  'standard_lmdb_with_expires' => {
    :store => :LMDB,
    :options => ':dir => File.join(make_tempdir, "simple_lmdb_with_expires"), :expires => true',
    :specs => STANDARD_SPECS.without_concurrent.with_expires
  },
  'standard_pstore' => {
    :store => :PStore,
    :options => ':file => File.join(make_tempdir, "simple_pstore")',
    :load_value => 'value',
    :specs => STANDARD_SPECS
  },
  'standard_pstore_with_expires' => {
    :store => :PStore,
    :options => ':file => File.join(make_tempdir, "simple_pstore_with_expires"), :expires => true',
    :load_value => 'value',
    :specs => STANDARD_SPECS.with_expires
  },
  'standard_yaml' => {
    :store => :YAML,
    :options => ':file => File.join(make_tempdir, "simple_yaml")',
    :specs => STANDARD_SPECS.without_marshallable_value.without_concurrent,
    :load_value => 'value'
  },
  'standard_yaml_with_expires' => {
    :store => :YAML,
    :options => ':file => File.join(make_tempdir, "simple_yaml_with_expires"), :expires => true',
    :specs => STANDARD_SPECS.without_marshallable_value.with_expires.without_concurrent,
    :load_value => 'value'
  },
  'standard_localmemcache' => {
    :store => :LocalMemCache,
    :options => ':file => File.join(make_tempdir, "simple_localmemcache")',
    :specs => STANDARD_SPECS.without_increment.without_create
  },
  'standard_localmemcache_with_expires' => {
    :store => :LocalMemCache,
    :options => ':file => File.join(make_tempdir, "simple_localmemcache_with_expires"), :expires => true',
    :specs => STANDARD_SPECS.without_increment.without_create.with_expires
  },
  'standard_tokyocabinet' => {
    :store => :TokyoCabinet,
    :options => ':file => File.join(make_tempdir, "simple_tokyocabinet")',
    :specs => STANDARD_SPECS.without_multiprocess
  },
  'standard_tokyocabinet_with_expires' => {
    :store => :TokyoCabinet,
    :options => ':file => File.join(make_tempdir, "simple_tokyocabinet_with_expires"), :expires => true',
    :specs => STANDARD_SPECS.without_multiprocess.with_expires
  },
  'standard_tokyotyrant' => {
    :store => :TokyoTyrant,
    :specs => STANDARD_SPECS
  },
  'standard_tokyotyrant_with_expires' => {
    :store => :TokyoTyrant,
    :options => ':expires => true',
    :specs => STANDARD_SPECS.with_expires
  },
  'standard_kyotocabinet' => {
    :store => :KyotoCabinet,
    :options => ':file => File.join(make_tempdir, "simple_kyotocabinet.kch")',
    :specs => STANDARD_SPECS.without_multiprocess
  },
  'standard_kyotocabinet_with_expires' => {
    :store => :KyotoCabinet,
    :options => ':file => File.join(make_tempdir, "simple_kyotocabinet_with_expires.kch"), :expires => true',
    :specs => STANDARD_SPECS.without_multiprocess.with_expires
  },
  'standard_sqlite' => {
    :store => :Sqlite,
    :options => ':file => File.join(make_tempdir, "simple_sqlite")',
    :specs => STANDARD_SPECS.without_concurrent
  },
  'standard_sqlite_with_expires' => {
    :store => :Sqlite,
    :options => ':file => File.join(make_tempdir, "simple_sqlite_with_expires"), :expires => true',
    :specs => STANDARD_SPECS.with_expires.without_concurrent
  },
  'standard_redis' => {
    :store => :Redis,
    :specs => STANDARD_SPECS.with_native_expires,
  },
  'standard_memcached' => {
    :store => :Memcached,
    :specs => STANDARD_SPECS.with_native_expires,
    :options => ':namespace => "simple_memcached"'
  },
  'standard_memcached_dalli' => {
    :store => :MemcachedDalli,
    :specs => STANDARD_SPECS.with_native_expires,
    :options => ':namespace => "simple_memcached_dalli"'
  },
  'standard_memcached_native' => {
    :store => :MemcachedNative,
    :specs => STANDARD_SPECS.with_native_expires,
    :options => ':namespace => "simple_memcached_native"'
  },
  'standard_riak' => {
    :store => :Riak,
    :options => ":bucket => 'standard_riak'",
    # We don't want Riak warnings in tests
    :preamble => "require 'riak'\n\nRiak.disable_list_keys_warnings = true\n\n",
    :specs => STANDARD_SPECS.without_increment.without_create
  },
  'standard_riak_with_expires' => {
    :store => :Riak,
    :options => ":bucket => 'standard_riak_with_expires', :expires => true",
    # We don't want Riak warnings in tests
    :preamble => "require 'riak'\n\nRiak.disable_list_keys_warnings = true\n\n",
    :specs => STANDARD_SPECS.without_increment.with_expires.without_create
  },
  'standard_couch' => {
    :store => :Couch,
    :options => ":db => 'standard_couch'",
    :load_value => '::Marshal.load(value.unpack(\'m\').first)',
    :specs => STANDARD_SPECS.without_increment
  },
  'standard_couch_with_expires' => {
    :store => :Couch,
    :options => ":db => 'standard_couch_with_expires', :expires => true",
    :specs => STANDARD_SPECS.without_increment.with_expires,
    :load_value => '::Marshal.load(value.unpack(\'m\').first)'
  },
  'standard_mongo' => {
    :store => :Mongo,
    :options => ":db => 'standard_mongo'",
    :specs => STANDARD_SPECS.with_native_expires
  },
  'standard_null' => {
    :store => :Null,
    :specs => STANDARD_SPECS.without_increment.without_create.without_store.without_persist
  },
  'null_adapter' => {
    :build => 'Moneta::Adapters::Null.new',
    :specs => Specs.new(:specs => [:null, :not_increment, :not_create, :not_persist])
  },
  'standard_sequel' => {
    :store => :Sequel,
    :options => ':db => (defined?(JRUBY_VERSION) ? "jdbc:mysql://localhost/moneta?user=root" : "mysql2://root:@localhost/moneta"), :table => "simple_sequel"',
    :load_value => '::Marshal.load(value)',
    :specs => STANDARD_SPECS
  },
  'standard_sequel_with_expires' => {
    :store => :Sequel,
    :options => ':db => (defined?(JRUBY_VERSION) ? "jdbc:mysql://localhost/moneta?user=root" : "mysql2://root:@localhost/moneta"), :table => "simple_sequel_with_expires", :expires => true',
    :specs => STANDARD_SPECS.with_expires,
    :load_value => '::Marshal.load(value)'
  },
  'standard_datamapper' => {
    :store => :DataMapper,
    :specs => STANDARD_SPECS.without_increment,
    :options => ':setup => "mysql://root:@localhost/moneta", :table => "simple_datamapper"',
    # DataMapper needs default repository to be setup
    :preamble => "require 'dm-core'\nDataMapper.setup(:default, :adapter => :in_memory)\n",
    :load_value => '::Marshal.load(value.unpack(\'m\').first)'
  },
  'standard_datamapper_with_expires' => {
    :store => :DataMapper,
    :options => ':setup => "mysql://root:@localhost/moneta", :table => "simple_datamapper_with_expires", :expires => true',
    # DataMapper needs default repository to be setup
    :preamble => "require 'dm-core'\nDataMapper.setup(:default, :adapter => :in_memory)\n",
    :specs => STANDARD_SPECS.without_increment.with_expires,
    :load_value => '::Marshal.load(value.unpack(\'m\').first)'
  },
  'standard_datamapper_with_repository' => {
    :store => :DataMapper,
    :specs => STANDARD_SPECS.without_increment,
    :options => ':repository => :repo, :setup => "mysql://root:@localhost/moneta", :table => "simple_datamapper_with_repository"',
    # DataMapper needs default repository to be setup
    :preamble => "require 'dm-core'\nDataMapper.setup(:default, :adapter => :in_memory)\n",
    :load_value => '::Marshal.load(value.unpack(\'m\').first)'
  },
  'standard_activerecord' => {
    :store => :ActiveRecord,
    :specs => STANDARD_SPECS,
    :options => ":table => 'standard_activerecord', :connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql2'), :database => 'moneta', :username => 'root' }",
    :load_value => '::Marshal.load(value.unpack(\'m\').first)'
  },
  'standard_activerecord_with_expires' => {
    :store => :ActiveRecord,
    :options => ":table => 'standard_activerecord_with_expires', :connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql2'), :database => 'moneta', :username => 'root' }, :expires => true",
    :specs => STANDARD_SPECS.with_expires,
    :load_value => '::Marshal.load(value.unpack(\'m\').first)'
  },
  'standard_fog' => {
    :store                  => :Fog,
    :specs                  => STANDARD_SPECS.without_increment.without_create,
    :options => ":aws_access_key_id => 'fake_access_key_id',
    :aws_secret_access_key  => 'fake_secret_access_key',
    :provider               => 'AWS',
    :dir                    => 'standard_fog'",
    # Put Fog into testing mode
    :preamble               => "require 'fog'\nFog.mock!\n"
  },
  'standard_fog_with_expires' => {
    :store                  => :Fog,
    :options => ":aws_access_key_id => 'fake_access_key_id',
    :aws_secret_access_key  => 'fake_secret_access_key',
    :provider               => 'AWS',
    :dir                    => 'standard_fog_with_expires',
    :expires                => true",
    # Put Fog into testing mode
    :preamble               => "require 'fog'\nFog.mock!\n",
    :specs => STANDARD_SPECS.without_increment.without_create.with_expires
  },
  'weak_create' => {
    # Put Fog into testing mode
    :preamble               => "require 'fog'\nFog.mock!\n",
    :build => %{Moneta.build do
  use :WeakCreate
  adapter :Fog,
    :aws_access_key_id => 'fake_access_key_id',
    :aws_secret_access_key  => 'fake_secret_access_key',
    :provider               => 'AWS',
    :dir                    => 'weak_create'
end},
    :specs => ADAPTER_SPECS.without_increment.without_concurrent.returnsame
  },
  'weak_increment' => {
    # Put Fog into testing mode
    :preamble               => "require 'fog'\nFog.mock!\n",
    :build => %{Moneta.build do
  use :WeakIncrement
  adapter :Fog,
    :aws_access_key_id => 'fake_access_key_id',
    :aws_secret_access_key  => 'fake_secret_access_key',
    :provider               => 'AWS',
    :dir                    => 'weak_increment'
end},
    :specs => ADAPTER_SPECS.without_create.without_concurrent.returnsame
  },
  'expires_memory' => {
    :build => %{Moneta.build do
  use :Expires
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_transform.with_expires.without_persist.returnsame
  },
  'expires_memory_with_default_expires' => {
    :build => %{Moneta.build do
  use :Expires, :expires => 1
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_transform.with_expires.with_default_expires.without_persist.returnsame
  },
  'expires_file' => {
    :build => %{Moneta.build do
  use :Expires
  use :Transformer, :key => [:marshal, :escape], :value => :marshal
  adapter :File, :dir => File.join(make_tempdir, "expires-file")
end},
    :specs => STANDARD_SPECS.with_expires.stringvalues_only,
    :tests => %{
it 'deletes expired value in underlying file storage' do
  store.store('foo', 'bar', :expires => 2)
  store['foo'].should == 'bar'
  sleep 1
  store['foo'].should == 'bar'
  sleep 2
  store['foo'].should be_nil
  store.adapter['foo'].should be_nil
  store.adapter.adapter['foo'].should be_nil
end
}
  },
  'proxy_redis' => {
    :build => %{Moneta.build do
  use :Proxy
  use :Proxy
  adapter :Redis
end},
    :specs => ADAPTER_SPECS.with_expires
  },
  'proxy_expires_memory' => {
    :build => %{Moneta.build do
  use :Proxy
  use :Expires
  use :Proxy
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_transform.with_expires.returnsame.without_persist
  },
  'cache_file_memory' => {
    :build => %{Moneta.build do
  use(:Cache) do
    adapter { adapter :File, :dir => File.join(make_tempdir, "cache_file_memory") }
    cache { adapter :Memory }
  end
end},
    :specs => ADAPTER_SPECS.returnsame,
    :tests => %{
it 'stores loaded values in cache' do
  store.adapter['foo'] = 'bar'
  store.cache['foo'].should be_nil
  store['foo'].should == 'bar'
  store.cache['foo'].should == 'bar'
  store.adapter.delete('foo')
  store['foo'].should == 'bar'
  store.delete('foo')
  store['foo'].should be_nil
end
}
  },
  'cache_memory_null' => {
    :build => %{Moneta.build do
  use(:Cache) do
    adapter(Moneta::Adapters::Memory.new)
    cache(Moneta::Adapters::Null.new)
  end
end},
    :specs => ADAPTER_SPECS.without_persist.returnsame
  },
  'shared_tcp' => {
    :build => %{Moneta.build do
  use(:Shared, :port => 9001) do
    adapter :PStore, :file => File.join(make_tempdir, 'shared_tcp')
  end
end},
    :specs => ADAPTER_SPECS,
    :tests => %{
it 'shares values' do
  store['shared_key'] = 'shared_value'
  second = new_store
  second.key?('shared_key').should be_true
  second['shared_key'].should == 'shared_value'
  second.close
end
}
  },
  'shared_unix' => {
    :build => %{Moneta.build do
  use(:Shared, :socket => File.join(make_tempdir, 'shared_unix.socket')) do
    adapter :PStore, :file => File.join(make_tempdir, 'shared_unix')
  end
end},
    :specs => ADAPTER_SPECS,
    :tests => %{
it 'shares values' do
  store['shared_key'] = 'shared_value'
  second = new_store
  second.key?('shared_key').should be_true
  second['shared_key'].should == 'shared_value'
  second.close
end
}
  },
  'stack_file_memory' => {
    :build => %{Moneta.build do
  use(:Stack) do
    add(Moneta.new(:Null))
    add(Moneta::Adapters::Null.new)
    add { adapter :File, :dir => File.join(make_tempdir, "stack_file_memory") }
    add { adapter :Memory }
  end
end},
    :specs => ADAPTER_SPECS.without_increment.without_create
  },
  'stack_memory_file' => {
    :build => %{Moneta.build do
  use(:Stack) do
    add { adapter :Memory }
    add { adapter :File, :dir => File.join(make_tempdir, "stack_memory_file") }
  end
end},
    :specs => ADAPTER_SPECS.returnsame
  },
  'lock' => {
    :build => %{Moneta.build do
  use :Lock
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_transform.returnsame.without_persist
  },
  'pool' => {
    :build => %{Moneta.build do
  use :Pool do
    adapter :File, :dir => File.join(make_tempdir, "pool")
  end
end},
    :specs => ADAPTER_SPECS
  },
  'transformer_zlib' => {
    :build => %{Moneta.build do
  use :Transformer, :value => :zlib
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.stringvalues_only,
    :load_value => '::Zlib::Inflate.inflate(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::ZlibValue.should_not be_nil
end}
  },
  'transformer_bzip2' => {
    :build => %{Moneta.build do
  use :Transformer, :value => :bzip2
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.stringvalues_only,
    :load_value => '::Bzip2.uncompress(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::Bzip2Value.should_not be_nil
end}
  },
  'transformer_lzo' => {
    :build => %{Moneta.build do
  use :Transformer, :value => :lzo
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.stringvalues_only,
    :load_value => '::LZO.decompress(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::LzoValue.should_not be_nil
end}
  },
  'transformer_lz4' => {
    :build => %{Moneta.build do
  use :Transformer, :value => :lz4
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.stringvalues_only,
    :load_value => '::LZ4.uncompress(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::Lz4Value.should_not be_nil
end}
  },
  'transformer_lzma' => {
    :build => %{Moneta.build do
  use :Transformer, :value => :lzma
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.stringvalues_only,
    :load_value => '::LZMA.decompress(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::LzmaValue.should_not be_nil
end}
  },
  'transformer_snappy' => {
    :build => %{Moneta.build do
  use :Transformer, :value => :snappy
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.stringvalues_only,
    :load_value => '::Snappy.inflate(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::SnappyValue.should_not be_nil
end}
  },
  'transformer_quicklz' => {
    :build => %{Moneta.build do
  use :Transformer, :value => :quicklz
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.stringvalues_only,
    :load_value => '::QuickLZ.decompress(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::QuicklzValue.should_not be_nil
end}
  },
  'transformer_json' => {
    :build => %{Moneta.build do
  use :Transformer, :key => :json, :value => :json
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.simplekeys_only.simplevalues_only,
    :load_value => '::MultiJson.load(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::JsonKeyJsonValue.should_not be_nil
end}
  },
  'transformer_bert' => {
    :build => %{Moneta.build do
  use :Transformer, :key => :bert, :value => :bert
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.simplekeys_only.simplevalues_only,
    :load_value => '::BERT.decode(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::BertKeyBertValue.should_not be_nil
end}
  },
  'transformer_bencode' => {
    :build => %{Moneta.build do
  use :Transformer, :key => :bencode, :value => :bencode
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.simplekeys_only.simplevalues_only,
    :load_value => '::BEncode.load(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::BencodeKeyBencodeValue.should_not be_nil
end}

  },
  'transformer_bson' => {
    :build => %{Moneta.build do
  use :Transformer, :key => :bson, :value => :bson
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.simplekeys_only.simplevalues_only,
    :load_value => "::BSON.deserialize(value)['v']",
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::BsonKeyBsonValue.should_not be_nil
end}
  },
  'transformer_ox' => {
    :build => %{Moneta.build do
  use :Transformer, :key => :ox, :value => :ox
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS,
    :load_value => '::Ox.parse_obj(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::OxKeyOxValue.should_not be_nil
end}
  },
  'transformer_php' => {
    :build => %{Moneta.build do
  use :Transformer, :key => :php, :value => :php
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.simplekeys_only.simplevalues_only,
    :load_value => '::PHP.unserialize(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::PhpKeyPhpValue.should_not be_nil
end}
  },
  'transformer_tnet' => {
    :build => %{Moneta.build do
 use :Transformer, :key => :tnet, :value => :tnet
 adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.simplekeys_only.simplevalues_only,
    :load_value => '::TNetstring.parse(value).first',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::TnetKeyTnetValue.should_not be_nil
end}
  },
  'transformer_msgpack' => {
    :build => %{Moneta.build do
  use :Transformer, :key => :msgpack, :value => :msgpack
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.simplekeys_only.simplevalues_only,
    :load_value => '::MessagePack.unpack(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MsgpackKeyMsgpackValue.should_not be_nil
end}
  },
  'transformer_marshal' => {
    :build => %{Moneta.build do
  use :Transformer, :key => :marshal, :value => :marshal
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS,
    :load_value => '::Marshal.load(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalKeyMarshalValue.should_not be_nil
end}
  },
  'transformer_key_marshal' => {
    :build => %{Moneta.build do
  use :Transformer, :key => :marshal
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.returnsame,
    :load_value => 'value',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalKey.should_not be_nil
end}
  },
  'transformer_key_to_s' => {
    :build => %{Moneta.build do
  use :Transformer, :key => :to_s
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.returnsame.simplekeys_only,
    :load_value => 'value',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::To_sKey.should_not be_nil
end}
  },
  'transformer_key_inspect' => {
    :build => %{Moneta.build do
  use :Transformer, :key => :inspect
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.returnsame.simplekeys_only,
    :load_value => 'value',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::InspectKey.should_not be_nil
end}
  },
  'transformer_value_marshal' => {
    :build => %{Moneta.build do
  use :Transformer, :value => :marshal
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS,
    :load_value => '::Marshal.load(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalValue.should_not be_nil
end}
  },
  'transformer_yaml' => {
    :build => %{Moneta.build do
  use :Transformer, :key => :yaml, :value => :yaml
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS,
    :load_value => '::YAML.load(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::YamlKeyYamlValue.should_not be_nil
end}
  },
  'transformer_key_yaml' => {
    :build => %{Moneta.build do
  use :Transformer, :key => :yaml
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS.returnsame,
    :load_value => 'value',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::YamlKey.should_not be_nil
end}
  },
  'transformer_value_yaml' => {
    :build => %{Moneta.build do
  use :Transformer, :value => :yaml
  adapter :Memory
end},
    :specs => TRANSFORMER_SPECS,
    :load_value => '::YAML.load(value)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::YamlValue.should_not be_nil
end}
  },
  'transformer_marshal_hmac' => {
    :build => %{Moneta.build do
  use :Transformer, :key => :marshal, :value => [:marshal, :hmac], :secret => 'secret'
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :load_value => '::Marshal.load(::Moneta::Transformer::Helper.hmacverify(value, \'secret\'))',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalKeyMarshalHmacValue.should_not be_nil
end}
  },
  'transformer_marshal_base64' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :base64], :value => [:marshal, :base64]
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :load_value => '::Marshal.load(value.unpack(\'m\').first)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalBase64KeyMarshalBase64Value.should_not be_nil
end}
  },
  'transformer_marshal_hex' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :hex], :value => [:marshal, :hex]
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :load_value => '::Marshal.load([value].pack(\'H*\'))',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalHexKeyMarshalHexValue.should_not be_nil
end}
  },
  'transformer_marshal_prefix' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :prefix], :value => :marshal, :prefix => 'moneta'
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalPrefixKeyMarshalValue.should_not be_nil
end}
  },
  'transformer_marshal_uuencode' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :uuencode], :value => [:marshal, :uuencode]
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :load_value => '::Marshal.load(value.unpack(\'u\').first)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalUuencodeKeyMarshalUuencodeValue.should_not be_nil
end}
  },
  'transformer_marshal_qp' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :qp], :value => [:marshal, :qp]
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :load_value => '::Marshal.load(value.unpack(\'M\').first)',
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalQpKeyMarshalQpValue.should_not be_nil
end}
  },
  'transformer_marshal_escape' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :escape], :value => :marshal
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalEscapeKeyMarshalValue.should_not be_nil
end}
  },
  'transformer_marshal_md5' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :md5], :value => :marshal
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalMd5KeyMarshalValue.should_not be_nil
end}
  },
  'transformer_marshal_sha1' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :sha1], :value => :marshal
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalSha1KeyMarshalValue.should_not be_nil
end}
  },
  'transformer_marshal_sha256' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :sha256], :value => :marshal
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalSha256KeyMarshalValue.should_not be_nil
end}
  },
  'transformer_marshal_sha384' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :sha384], :value => :marshal
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalSha384KeyMarshalValue.should_not be_nil
end}
  },
  'transformer_marshal_sha512' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :sha512], :value => :marshal
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalSha512KeyMarshalValue.should_not be_nil
end}
  },
  'transformer_marshal_rmd160' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :rmd160], :value => :marshal
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalRmd160KeyMarshalValue.should_not be_nil
end}
  },
  'transformer_marshal_md5_spread' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :md5, :spread], :value => :marshal
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalMd5SpreadKeyMarshalValue.should_not be_nil
end}
  },
  'transformer_marshal_city32' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :city32], :value => :marshal
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalCity32KeyMarshalValue.should_not be_nil
end}
  },
  'transformer_marshal_city64' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :city64], :value => :marshal
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalCity64KeyMarshalValue.should_not be_nil
end}
  },
  'transformer_marshal_city128' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :city128], :value => :marshal
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalCity128KeyMarshalValue.should_not be_nil
end}
  },
  'transformer_marshal_truncate' => {
    :build => %{Moneta.build do
  use :Transformer, :key => [:marshal, :truncate], :value => :marshal, :maxlen => 64
  adapter :Memory
end},
    :specs => STANDARD_SPECS.without_persist,
    :tests => %{
it 'compile transformer class' do
  store.should_not be_nil
  Moneta::Transformer::MarshalTruncateKeyMarshalValue.should_not be_nil
end}
  },
  'adapter_activerecord' => {
    :build => "Moneta::Adapters::ActiveRecord.new(:table => 'adapter_activerecord', :connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql2'), :database => 'moneta', :username => 'root' })",
    :specs => ADAPTER_SPECS,
    :tests => %{
it 'updates an existing key/value' do
  store['foo/bar'] = '1'
  store['foo/bar'] = '2'
  store.table.where(:k => 'foo/bar').count.should == 1
end

it 'supports different tables same database' do
  store1 = Moneta::Adapters::ActiveRecord.new(:table => 'adapter_activerecord1', :connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql2'), :database => 'moneta', :username => 'root' })
  store2 = Moneta::Adapters::ActiveRecord.new(:table => 'adapter_activerecord2', :connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql2'), :database => 'moneta', :username => 'root' })

  store1['key'] = 'value1'
  store2['key'] = 'value2'
  store1['key'].should == 'value1'
  store2['key'].should == 'value2'

  store1.close
  store2.close
end

it 'supports different databases same table' do
  store1 = Moneta::Adapters::ActiveRecord.new(:table => 'adapter_activerecord', :connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql2'), :database => 'moneta_activerecord1', :username => 'root' })
  store2 = Moneta::Adapters::ActiveRecord.new(:table => 'adapter_activerecord', :connection => { :adapter => (defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql2'), :database => 'moneta_activerecord2', :username => 'root' })

  store1['key'] = 'value1'
  store2['key'] = 'value2'
  store1['key'].should == 'value1'
  store2['key'].should == 'value2'

  store1.close
  store2.close
end}
  },
  'adapter_activerecord_exisiting_connection' => {
    :preamble => "require 'active_record'\nActiveRecord::Base.establish_connection :adapter => (defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql2'), :database => 'moneta', :username => 'root'\n",
    :build => "Moneta::Adapters::ActiveRecord.new(:table => 'adapter_activerecord_existing_connection')",
    :specs => ADAPTER_SPECS
  },
  'adapter_client' => {
    :preamble => "start_server(Moneta::Adapters::Memory.new)\n",
    :build => "Moneta::Adapters::Client.new",
    :specs => ADAPTER_SPECS
  },
  'adapter_restclient' => {
    :preamble => "start_restserver\n",
    :build => "Moneta::Adapters::RestClient.new(:url => 'http://localhost:8808/moneta/')",
    :specs => ADAPTER_SPECS.without_increment.without_create
  },
  'adapter_cassandra' => {
    :build => "Moneta::Adapters::Cassandra.new(:keyspace => 'adapter_cassandra')",
    :specs => ADAPTER_SPECS.without_increment.without_create.with_native_expires
  },
  'adapter_cassandra_with_default_expires' => {
    :build => %{Moneta::Adapters::Cassandra.new(:keyspace => 'adapter_cassandra_with_default_expires', :expires => 1)},
    :specs => ADAPTER_SPECS.without_increment.without_create.with_native_expires.with_default_expires
  },
  'adapter_hbase' => {
    :build => "Moneta::Adapters::HBase.new(:table => 'adapter_hbase')",
    :specs => ADAPTER_SPECS.without_create
  },
  'adapter_cookie' => {
    :build => 'Moneta::Adapters::Cookie.new',
    :specs => ADAPTER_SPECS.without_persist.returnsame
  },
  'adapter_couch' => {
    :build => "Moneta::Adapters::Couch.new(:db => 'adapter_couch')",
    :specs => ADAPTER_SPECS.without_increment.simplevalues_only.without_path
  },
  'adapter_datamapper' => {
    :build => 'Moneta::Adapters::DataMapper.new(:setup => "mysql://root:@localhost/moneta", :table => "adapter_datamapper")',
    # DataMapper needs default repository to be setup
    :preamble => "require 'dm-core'\nDataMapper.setup(:default, :adapter => :in_memory)\n",
    :specs => ADAPTER_SPECS.without_increment,
    :tests => %q{
it 'does not cross contaminate when storing' do
  first = Moneta::Adapters::DataMapper.new(:setup => "mysql://root:@localhost/moneta", :table => "datamapper_first")
  first.clear

  second = Moneta::Adapters::DataMapper.new(:repository => :sample, :setup => "mysql://root:@localhost/moneta", :table => "datamapper_second")
  second.clear

  first['key'] = 'value'
  second['key'] = 'value2'

  first['key'].should == 'value'
  second['key'].should == 'value2'
end

it 'does not cross contaminate when deleting' do
  first = Moneta::Adapters::DataMapper.new(:setup => "mysql://root:@localhost/moneta", :table => "datamapper_first")
  first.clear

  second = Moneta::Adapters::DataMapper.new(:repository => :sample, :setup => "mysql://root:@localhost/moneta", :table => "datamapper_second")
  second.clear

  first['key'] = 'value'
  second['key'] = 'value2'

  first.delete('key').should == 'value'
  first.key?('key').should be_false
  second['key'].should == 'value2'
end
}
  },
  'adapter_dbm' => {
    :build => 'Moneta::Adapters::DBM.new(:file => File.join(make_tempdir, "adapter_dbm"))',
    :specs => ADAPTER_SPECS.without_multiprocess
  },
  'adapter_tdb' => {
    :build => 'Moneta::Adapters::TDB.new(:file => File.join(make_tempdir, "adapter_tdb"))',
    :specs => ADAPTER_SPECS.without_multiprocess
  },
  'adapter_daybreak' => {
    :build => 'Moneta::Adapters::Daybreak.new(:file => File.join(make_tempdir, "adapter_daybreak"))',
    :specs => ADAPTER_SPECS.without_multiprocess.returnsame
  },
  'adapter_file' => {
    :build => 'Moneta::Adapters::File.new(:dir => File.join(make_tempdir, "adapter_file"))',
    :specs => ADAPTER_SPECS
  },
  'adapter_fog' => {
    :build => "Moneta::Adapters::Fog.new(:aws_access_key_id => 'fake_access_key_id',
    :aws_secret_access_key  => 'fake_secret_access_key',
    :provider               => 'AWS',
    :dir                    => 'adapter_fog')",
    # Put Fog into testing mode
    :preamble               => "require 'fog'\nFog.mock!\n",
    # Fog returns same object in mocking mode (in-memory store)
    :specs => ADAPTER_SPECS.without_increment.without_create.returnsame
  },
  'adapter_gdbm' => {
    :build => 'Moneta::Adapters::GDBM.new(:file => File.join(make_tempdir, "adapter_gdbm"))',
    :specs => ADAPTER_SPECS.without_multiprocess
  },
  'adapter_localmemcache' => {
    :build => 'Moneta::Adapters::LocalMemCache.new(:file => File.join(make_tempdir, "adapter_localmemcache"))',
    :specs => ADAPTER_SPECS.without_increment.without_create
  },
  'adapter_memcached_dalli' => {
    :build => 'Moneta::Adapters::MemcachedDalli.new(:namespace => "adapter_memcached_dalli")',
    :specs => ADAPTER_SPECS.with_native_expires
  },
  'adapter_memcached_dalli_with_default_expires' => {
    :build => %{Moneta::Adapters::MemcachedDalli.new(:expires => 1)},
    :specs => ADAPTER_SPECS.with_native_expires.with_default_expires
  },
  'adapter_memcached_native' => {
    :build => 'Moneta::Adapters::MemcachedNative.new(:namespace => "adapter_memcached_native")',
    :specs => ADAPTER_SPECS.with_native_expires
  },
  'adapter_memcached_native_with_default_expires' => {
    :build => %{Moneta::Adapters::MemcachedNative.new(:expires => 1)},
    :specs => ADAPTER_SPECS.with_native_expires.with_default_expires
  },
  'adapter_memcached' => {
    :build => 'Moneta::Adapters::Memcached.new(:namespace => "adapter_memcached")',
    :specs => ADAPTER_SPECS.with_native_expires
  },
  'adapter_memcached_with_default_expires' => {
    :build => %{Moneta::Adapters::Memcached.new(:expires => 1)},
    :specs => ADAPTER_SPECS.with_native_expires.with_default_expires
  },
  'adapter_memory' => {
    :build => 'Moneta::Adapters::Memory.new',
    :specs => STANDARD_SPECS.without_transform.returnsame.without_persist
  },
  'adapter_lruhash' => {
    :build => 'Moneta::Adapters::LRUHash.new',
    :specs => ADAPTER_SPECS.without_persist.returnsame,
    :tests => %{
it 'deletes oldest' do
  store = Moneta::Adapters::LRUHash.new(:max_size => 10)
  store[0]  = 'y'
  (1..1000).each do |i|
    store[i] = 'x'
    store[0].should == 'y'
    store.instance_variable_get(:@entry).size.should == [10, i+1].min
    (0...[9, i-1].min).each do |j|
      store.instance_variable_get(:@entry)[i-j].should_not be_nil
    end
    store.key?(i-9).should be_false if i > 9
  end
end}
  },
  'adapter_mongo' => {
    :build => 'Moneta::Adapters::Mongo.new(:db => "adapter_mongo")',
    :specs => ADAPTER_SPECS.with_native_expires.simplevalues_only,
    :tests => %{
it 'automatically deletes expired document' do
  store.store('key', 'val', :expires => 5)
  store.instance_variable_get(:@collection).find_one('_id' => ::BSON::Binary.new('key')).should_not be_nil
  sleep 70 # Mongo needs up to 60 seconds
  store.instance_variable_get(:@collection).find_one('_id' => ::BSON::Binary.new('key')).should be_nil
end}
  },
  'adapter_mongo_with_default_expires' => {
    :build => %{Moneta::Adapters::Mongo.new(:expires => 1)},
    :specs => ADAPTER_SPECS.with_expires.with_default_expires.simplevalues_only
  },
  'adapter_pstore' => {
    :build => 'Moneta::Adapters::PStore.new(:file => File.join(make_tempdir, "adapter_pstore"))',
    :specs => STANDARD_SPECS.without_transform
  },
  'adapter_redis' => {
    :build => 'Moneta::Adapters::Redis.new',
    :specs => ADAPTER_SPECS.with_native_expires
  },
  'adapter_redis_with_default_expires' => {
    :build => %{Moneta::Adapters::Redis.new(:expires => 1)},
    :specs => ADAPTER_SPECS.with_native_expires.with_default_expires
  },
  'adapter_riak' => {
    :build => 'Moneta::Adapters::Riak.new',
    :options => ":bucket => 'adapter_riak'",
    :specs => ADAPTER_SPECS.without_increment.without_create,
    # We don't want Riak warnings in tests
    :preamble => "require 'riak'\n\nRiak.disable_list_keys_warnings = true\n\n"
  },
  'adapter_sdbm' => {
    :build => 'Moneta::Adapters::SDBM.new(:file => File.join(make_tempdir, "adapter_sdbm"))',
    :specs => ADAPTER_SPECS.without_multiprocess.without_large
  },
  'adapter_lmdb' => {
    :build => 'Moneta::Adapters::LMDB.new(:dir => File.join(make_tempdir, "adapter_lmdb"))',
    :specs => ADAPTER_SPECS.without_concurrent
  },
  'adapter_lmdb_with_db' => {
    :build => 'Moneta::Adapters::LMDB.new(:dir => File.join(make_tempdir, "adapter_lmdb"), :db => "adapter_lmdb_with_db")',
    :specs => ADAPTER_SPECS.without_concurrent
  },
  'adapter_leveldb' => {
    :build => 'Moneta::Adapters::LevelDB.new(:dir => File.join(make_tempdir, "adapter_leveldb"))',
    :specs => ADAPTER_SPECS.without_multiprocess
  },
  'adapter_sequel' => {
    :build => 'Moneta::Adapters::Sequel.new(:db => (defined?(JRUBY_VERSION) ? "jdbc:mysql://localhost/moneta?user=root" : "mysql2://root:@localhost/moneta"), :table => "adapter_sequel")',
    :specs => ADAPTER_SPECS
  },
  'adapter_sqlite' => {
    :build => 'Moneta::Adapters::Sqlite.new(:file => File.join(make_tempdir, "adapter_sqlite"))',
    :specs => ADAPTER_SPECS.without_concurrent
  },
  'adapter_kyotocabinet' => {
    :build => 'Moneta::Adapters::KyotoCabinet.new(:file => File.join(make_tempdir, "adapter_kyotocabinet.kch"))',
    :specs => ADAPTER_SPECS.without_multiprocess
  },
  'adapter_tokyocabinet_bdb' => {
    :build => 'Moneta::Adapters::TokyoCabinet.new(:file => File.join(make_tempdir, "adapter_tokyocabinet_bdb"), :type => :bdb)',
    :specs => ADAPTER_SPECS.without_multiprocess
  },
  'adapter_tokyocabinet_hdb' => {
    :build => 'Moneta::Adapters::TokyoCabinet.new(:file => File.join(make_tempdir, "adapter_tokyocabinet_hdb"), :type => :hdb)',
    :specs => ADAPTER_SPECS.without_multiprocess
  },
  'adapter_tokyotyrant' => {
    :build => 'Moneta::Adapters::TokyoTyrant.new',
    :specs => ADAPTER_SPECS
  },
  'adapter_yaml' => {
    :build => 'Moneta::Adapters::YAML.new(:file => File.join(make_tempdir, "adapter_yaml"))',
    :specs => STANDARD_SPECS.simplevalues_only.simplekeys_only.without_transform.without_concurrent
  },
  'mutex' => {
    :store => :Memory,
    :specs => Specs.new,
    :tests => %{
it 'should have #lock' do
  mutex = Moneta::Mutex.new(store, 'mutex')
  mutex.lock.should be_true
  mutex.locked?.should be_true
  expect do
   mutex.lock
  end.to raise_error(RuntimeError)
  expect do
   mutex.try_lock
  end.to raise_error(RuntimeError)
  mutex.unlock.should be_nil
  mutex.locked?.should be_false
end

it 'should have #enter' do
  mutex = Moneta::Mutex.new(store, 'mutex')
  mutex.enter.should be_true
  mutex.locked?.should be_true
  expect do
   mutex.enter
  end.to raise_error(RuntimeError)
  expect do
   mutex.try_enter
  end.to raise_error(RuntimeError)
  mutex.leave.should be_nil
  mutex.locked?.should be_false
end

it 'should lock with #lock' do
  a = Moneta::Mutex.new(store, 'mutex')
  b = Moneta::Mutex.new(store, 'mutex')
  a.lock.should be_true
  b.try_lock.should be_false
  a.unlock.should be_nil
end

it 'should have lock timeout' do
  a = Moneta::Mutex.new(store, 'mutex')
  b = Moneta::Mutex.new(store, 'mutex')
  a.lock.should be_true
  b.lock(1).should be_false
  a.unlock.should be_nil
end

it 'should have #synchronize' do
  mutex = Moneta::Mutex.new(store, 'mutex')
  mutex.synchronize do
    mutex.locked?.should be_true
  end
  mutex.locked?.should be_false
end
  }
  },
  'semaphore' => {
    :store => :Memory,
    :specs => Specs.new,
    :tests => %{
it 'should have #lock' do
  mutex = Moneta::Semaphore.new(store, 'semaphore')
  mutex.lock.should be_true
  mutex.locked?.should be_true
  expect do
   mutex.lock
  end.to raise_error(RuntimeError)
  expect do
   mutex.try_lock
  end.to raise_error(RuntimeError)
  mutex.unlock.should be_nil
  mutex.locked?.should be_false
end

it 'should have #enter' do
  mutex = Moneta::Semaphore.new(store, 'semaphore')
  mutex.enter.should be_true
  mutex.locked?.should be_true
  expect do
   mutex.enter
  end.to raise_error(RuntimeError)
  expect do
   mutex.try_enter
  end.to raise_error(RuntimeError)
  mutex.leave.should be_nil
  mutex.locked?.should be_false
end

it 'should lock with #lock' do
  a = Moneta::Semaphore.new(store, 'semaphore')
  b = Moneta::Semaphore.new(store, 'semaphore')
  a.lock.should be_true
  b.try_lock.should be_false
  a.unlock.should be_nil
end

it 'should have lock timeout' do
  a = Moneta::Semaphore.new(store, 'semaphore')
  b = Moneta::Semaphore.new(store, 'semaphore')
  a.lock.should be_true
  b.lock(1).should be_false
  a.unlock.should be_nil
end

it 'should count concurrent accesses' do
  a = Moneta::Semaphore.new(store, 'semaphore', 2)
  b = Moneta::Semaphore.new(store, 'semaphore', 2)
  c = Moneta::Semaphore.new(store, 'semaphore', 2)
  a.synchronize do
    a.locked?.should be_true
    b.synchronize do
      b.locked?.should be_true
      c.try_lock.should be_false
    end
  end
end

it 'should have #synchronize' do
  semaphore = Moneta::Semaphore.new(store, 'semaphore')
  semaphore.synchronize do
    semaphore.locked?.should be_true
  end
  semaphore.locked?.should be_false
end
  }
  },
  'optionmerger' => {
    :store => :Memory,
    :specs => Specs.new,
    :tests => %{
it '#with should return OptionMerger' do
  options = {:optionname => :optionvalue}
  merger = store.with(options)
  merger.should be_instance_of(Moneta::OptionMerger)
end

it 'saves default options' do
  options = {:optionname => :optionvalue}
  merger = store.with(options)
  Moneta::OptionMerger::METHODS.each do |method|
    merger.default_options[method].should equal(options)
  end
end

PREFIX = [['alpha', nil], ['beta', nil], ['alpha', 'beta']]

it 'merges options' do
  merger = store.with(:opt1 => :val1, :opt2 => :val2).with(:opt2 => :overwrite, :opt3 => :val3)
  Moneta::OptionMerger::METHODS.each do |method|
    merger.default_options[method].should == {:opt1 => :val1, :opt2 => :overwrite, :opt3 => :val3}
  end
end

it 'merges options only for some methods' do
  PREFIX.each do |(alpha,beta)|
    options = {:opt1 => :val1, :opt2 => :val2, :prefix => alpha}
    merger = store.with(options).with(:opt2 => :overwrite, :opt3 => :val3, :prefix => beta, :only => :clear)
    (Moneta::OptionMerger::METHODS - [:clear]).each do |method|
      merger.default_options[method].should equal(options)
    end
    merger.default_options[:clear].should == {:opt1 => :val1, :opt2 => :overwrite, :opt3 => :val3, :prefix => "\#{alpha}\#{beta}"}

    merger = store.with(options).with(:opt2 => :overwrite, :opt3 => :val3, :prefix => beta, :only => [:load, :store])
    (Moneta::OptionMerger::METHODS - [:load, :store]).each do |method|
      merger.default_options[method].should equal(options)
    end
    merger.default_options[:load].should == {:opt1 => :val1, :opt2 => :overwrite, :opt3 => :val3, :prefix => "\#{alpha}\#{beta}"}
    merger.default_options[:store].should == {:opt1 => :val1, :opt2 => :overwrite, :opt3 => :val3, :prefix => "\#{alpha}\#{beta}"}
  end
end

it 'merges options except for some methods' do
  PREFIX.each do |(alpha,beta)|
    options = {:opt1 => :val1, :opt2 => :val2, :prefix => alpha}
    merger = store.with(options).with(:opt2 => :overwrite, :opt3 => :val3, :except => :clear, :prefix => beta)
    (Moneta::OptionMerger::METHODS - [:clear]).each do |method|
      merger.default_options[method].should == {:opt1 => :val1, :opt2 => :overwrite, :opt3 => :val3, :prefix => "\#{alpha}\#{beta}"}
    end
    merger.default_options[:clear].should equal(options)

    merger = store.with(options).with(:opt2 => :overwrite, :opt3 => :val3, :prefix => beta, :except => [:load, :store])
    (Moneta::OptionMerger::METHODS - [:load, :store]).each do |method|
      merger.default_options[method].should == {:opt1 => :val1, :opt2 => :overwrite, :opt3 => :val3, :prefix => "\#{alpha}\#{beta}"}
    end
    merger.default_options[:load].should equal(options)
    merger.default_options[:store].should equal(options)
  end
end

it 'has method #raw' do
  store.raw.default_options.should == {:store=>{:raw=>true},:create=>{:raw=>true},:load=>{:raw=>true},:delete=>{:raw=>true}}
  store.raw.should equal(store.raw.raw)
end

it 'has method #expires' do
  store.expires(10).default_options.should == {:store=>{:expires=>10},:create=>{:expires=>10},:increment=>{:expires=>10}}
end

it 'has method #prefix' do
  store.prefix('a').default_options.should == {:store=>{:prefix=>'a'},:load=>{:prefix=>'a'},:create=>{:prefix=>'a'},
                                               :delete=>{:prefix=>'a'},:key? => {:prefix=>'a'},:increment=>{:prefix=>'a'}}

  store.prefix('a').prefix('b').default_options.should == {:store=>{:prefix=>'ab'},:load=>{:prefix=>'ab'},:create=>{:prefix=>'ab'},
                                                           :delete=>{:prefix=>'ab'},:key? => {:prefix=>'ab'},:increment=>{:prefix=>'ab'}}

  store.raw.prefix('b').default_options.should == {:store=>{:raw=>true,:prefix=>'b'},:load=>{:raw=>true,:prefix=>'b'},:create=>{:raw=>true,:prefix=>'b'},:delete=>{:raw=>true,:prefix=>'b'},:key? => {:prefix=>'b'},:increment=>{:prefix=>'b'}}

  store.prefix('a').raw.default_options.should == {:store=>{:raw=>true,:prefix=>'a'},:load=>{:raw=>true,:prefix=>'a'},:create=>{:raw=>true,:prefix=>'a'},:delete=>{:raw=>true,:prefix=>'a'},:key? => {:prefix=>'a'},:increment=>{:prefix=>'a'}}
end

it 'supports adding proxis using #with' do
  compressed_store = store.with(:prefix => 'compressed') do
    use :Transformer, :value => :zlib
  end
  store['key'] = 'uncompressed value'
  compressed_store['key'] = 'compressed value'
  store['key'].should == 'uncompressed value'
  compressed_store['key'].should == 'compressed value'
  store.key?('compressedkey').should be_true
  # Check if value is compressed
  compressed_store['key'].should_not == store['compressedkey']
end}
  },
}

SPECS = {}

KEYS = {
  'nil' => ['nil', 0],
  'integer' => [-10, 42],
  'number' => [0.5, -0.3, 1<<127, 99],
  'boolean' => [true, false],
  'string' => %w(strkey1 strkey2).map(&:inspect),
  'path' => %w(bar/foo/baz foo/bar).map(&:inspect),
  'binary' => ["\xC3\xBCber", "\xAA\xBB\xCC"].map(&:inspect),
  'object' => ['Value.new(:objkey1)', 'Value.new(:objkey2)'],
  'hash' => [{'hashkey1' => 'hashkey2'}, {'hashkey3' => 'hashkey4'}].map(&:inspect)
}

VALUES = {
  'nil' => ["''", 'nil', 0, false],
  'integer' => [41, -12],
  'number' => [123.456, -98.7, 1<<128, 33],
  'boolean' => [true, false],
  'string' => %w(strval1 strval2).map(&:inspect),
  'binary' => ["\xC3\xBCber", "\xAA\xBB\xCC"].map(&:inspect),
  'hash' => [{'hashval1' => ['array1', 1]}, {'hashval3' => ['array2', {'hashval4' => 42}]}].map(&:inspect),
  'object' => ['Value.new(:objval1)', 'Value.new(:objval2)'],
}

KEYS.each do |key_type, keys|
  VALUES.each do |val_type, vals|
    (keys.size/2).times do |k|
      keypair = keys[2*k,2]
      (vals.size/2).times do |v|
        valpair = vals[2*v,2]
        4.times do |i|
          key1, key2 = i % 2 == 0 ? keypair : keypair.reverse
          val1, val2 = i < 2 ? valpair : valpair.reverse

          code = %{it 'reads from keys like a Hash' do
  store[#{key1}].should be_nil
  store.load(#{key1}).should be_nil
end

it 'guarantees that the same value is returned when setting a key' do
  value = #{val1}
  (store[#{key1}] = value).should equal(value)
end

it 'returns false from #key? if a key is not available' do
  store.key?(#{key1}).should be_false
end

it 'returns nil from delete if a value for a key does not exist' do
  store.delete(#{key1}).should be_nil
end

it 'removes all keys from the store with clear' do
  store[#{key1}] = #{val1}
  store[#{key2}] = #{val2}
  store.clear.should equal(store)
  store.key?(#{key1}).should be_false
  store.key?(#{key2}).should be_false
end

it 'fetches a key with a default value with fetch, if the key is not available' do
  store.fetch(#{key1}, #{val1}).should == #{val1}
end

it 'fetches a key with a block with fetch, if the key is not available' do
  key = #{key1}
  value = #{val1}
  store.fetch(key) do |k|
    k.should equal(key)
    value
  end.should equal(value)
end

it 'accepts frozen options' do
  options = {:option1 => 1, :options2 => 2}
  options.freeze
  store.key?(#{key1}, options).should be_false
  store.load(#{key1}, options).should be_nil
  store.fetch(#{key1}, 42, options).should == 42
  store.fetch(#{key1}, options) { 42 }.should == 42
  store.delete(#{key1}, options).should be_nil
  store.clear(options).should equal(store)
  store.store(#{key1}, #{val1}, options).should == #{val1}
end}
          (SPECS["null_#{key_type}key_#{val_type}value"] ||= []) << code

          code = %{it 'writes values to keys that like a Hash' do
  store[#{key1}] = #{val1}
  store[#{key1}].should == #{val1}
  store.load(#{key1}).should == #{val1}
end

it 'returns true from #key? if a key is available' do
  store[#{key1}] = #{val1}
  store.key?(#{key1}).should be_true
end

it 'stores values with #store' do
  value = #{val1}
  store.store(#{key1}, value).should equal(value)
  store[#{key1}].should == #{val1}
  store.load(#{key1}).should == #{val1}
end

it 'stores values after clear' do
  store[#{key1}] = #{val1}
  store[#{key2}] = #{val2}
  store.clear.should equal(store)
  store[#{key1}] = #{val1}
  store[#{key1}].should == #{val1}
  store[#{key2}].should be_nil
end

it 'removes and returns a value from the backing store via delete if it exists' do
  store[#{key1}] = #{val1}
  store.delete(#{key1}).should == #{val1}
  store.key?(#{key1}).should be_false
end

it 'overwrites existing values' do
  store[#{key1}] = #{val1}
  store[#{key1}].should == #{val1}
  store[#{key1}] = #{val2}
  store[#{key1}].should == #{val2}
end

it 'stores frozen values' do
  value = #{val1}.freeze
  (store[#{key1}] = value).should equal(value)
  store[#{key1}].should == #{val1}
end

it 'stores frozen keys' do
  key = #{key1}.freeze
  store[key] = #{val1}
  store[#{key1}].should == #{val1}
end}

          if val_type != 'nil'
            code << %{
it 'fetches a key with a default value with fetch, if the key is available' do
  store[#{key1}] = #{val1}
  store.fetch(#{key1}, #{val2}).should == #{val1}
end

it 'does not run the block in fetch if the key is available' do
  store[#{key1}] = #{val1}
  unaltered = 'unaltered'
  store.fetch(#{key1}) { unaltered = 'altered' }
  unaltered.should == 'unaltered'
end}
          end

          (SPECS["store_#{key_type}key_#{val_type}value"] ||= []) << code

          if val_type != 'boolean' && val_type != 'nil' && val_type != 'integer' && val_type != 'number'
        (SPECS["returndifferent_#{key_type}key_#{val_type}value"] ||= []) << %{it 'guarantees that a different value is retrieved' do
  value = #{val1}
  store[#{key1}] = value
  store[#{key1}].should_not be_equal(value)
end}
        (SPECS["returnsame_#{key_type}key_#{val_type}value"] ||= []) << %{it 'guarantees that the same value is retrieved' do
  value = #{val1}
  store[#{key1}] = value
  store[#{key1}].should be_equal(value)
end}
          end

          code = %{it 'persists values' do
  store[#{key1}] = #{val1}
  store.close
  @store = nil
  store[#{key1}].should == #{val1}
end}
          (SPECS["persist_#{key_type}key_#{val_type}value"] ||= []) << code
        end
      end
    end
  end
end

SPECS['store_large'] = %{it 'should store values up to 32k' do
  value = 'x' * (32 * 1024)
  store['large'] = value
  store['large'].should == value
end

it 'should store keys up to 128 bytes' do
  key = 'x' * 128
  store[key] = 'value'
  store[key].should == 'value'
end}

SPECS['not_persist'] = %{it 'does not persist values' do
  store['key'] = 'val'
  store.close
  @store = nil

  store['key'].should be_nil
end}

SPECS['multiprocess'] = %{it 'supports access by multiple instances/processes' do
  store['key'] = 'val'
  store2 = new_store
  store2['key'].should == 'val'
  store2.close
end}

SPECS['expires'] = %{it 'supports expires on store and []', :retry => 3 do
  store.store('key1', 'val1', :expires => 3)
  store['key1'].should == 'val1'
  sleep 1
  store['key1'].should == 'val1'
  sleep 3
  store['key1'].should be_nil
end

it 'supports strict expires on store and []' do
  store.store('key1', 'val1', :expires => 2)
  store['key1'].should == 'val1'
  sleep 3 # Sleep 3 seconds because after 2 seconds the value can still exist!
  store['key1'].should be_nil
end

it 'supports expires on store and fetch', :retry => 3 do
  store.store('key1', 'val1', :expires => 3)
  store.fetch('key1').should == 'val1'
  sleep 1
  store.fetch('key1').should == 'val1'
  sleep 3
  store.fetch('key1').should be_nil
end

it 'supports strict expires on store and fetch' do
  store.store('key1', 'val1', :expires => 2)
  store.fetch('key1').should == 'val1'
  sleep 3 # Sleep 3 seconds because after 2 seconds the value can still exist!
  store.fetch('key1').should be_nil
end

it 'supports 0 as no-expires on store and []' do
  store.store('key1', 'val1', :expires => 0)
  store['key1'].should == 'val1'
  sleep 2
  store['key1'].should == 'val1'
end

it 'supports false as no-expires on store and []' do
  store.store('key1', 'val1', :expires => false)
  store['key1'].should == 'val1'
  sleep 2
  store['key1'].should == 'val1'
end

it 'supports expires on store and load', :retry => 3 do
  store.store('key1', 'val1', :expires => 3)
  store.load('key1').should == 'val1'
  sleep 1
  store.load('key1').should == 'val1'
  sleep 3
  store.load('key1').should be_nil
end

it 'supports strict expires on store and load' do
  store.store('key1', 'val1', :expires => 2)
  store.load('key1').should == 'val1'
  sleep 3 # Sleep 3 seconds because after 2 seconds the value can still exist!
  store.load('key1').should be_nil
end

it 'supports expires on store and #key?', :retry => 3 do
  store.store('key1', 'val1', :expires => 3)
  store.key?('key1').should be_true
  sleep 1
  store.key?('key1').should be_true
  sleep 3
  store.key?('key1').should be_false
end

it 'supports strict expires on store and #key?' do
  store.store('key1', 'val1', :expires => 2)
  store.key?('key1').should be_true
  sleep 3 # Sleep 3 seconds because after 2 seconds the value can still exist!
  store.key?('key1').should be_false
end

it 'supports updating the expiration time in load', :retry => 3 do
  store.store('key2', 'val2', :expires => 3)
  store['key2'].should == 'val2'
  sleep 1
  store.load('key2', :expires => 5).should == 'val2'
  store['key2'].should == 'val2'
  sleep 3
  store['key2'].should == 'val2'
  sleep 3
  store['key2'].should be_nil
end

it 'supports 0 as no-expires in load' do
  store.store('key1', 'val1', :expires => 2)
  store.load('key1', :expires => 0).should == 'val1'
  sleep 3
  store.load('key1').should == 'val1'
end

it 'supports false as no-expires in load' do
  store.store('key1', 'val1', :expires => 2)
  store.load('key1', :expires => false).should == 'val1'
  sleep 3
  store.load('key1').should == 'val1'
end

it 'supports updating the expiration time in #key?', :retry => 3 do
  store.store('key2', 'val2', :expires => 3)
  store['key2'].should == 'val2'
  sleep 1
  store.key?('key2', :expires => 5).should be_true
  store['key2'].should == 'val2'
  sleep 3
  store['key2'].should == 'val2'
  sleep 3
  store['key2'].should be_nil
end

it 'supports 0 as no-expires in #key?' do
  store.store('key1', 'val1', :expires => 2)
  store.key?('key1', :expires => 0).should be_true
  sleep 3
  store['key1'].should == 'val1'
end

it 'supports false as no-expires in #key?' do
  store.store('key1', 'val1', :expires => 2)
  store.key?('key1', :expires => false ).should be_true
  sleep 3
  store['key1'].should == 'val1'
end

it 'supports updating the expiration time in fetch', :retry => 3 do
  store.store('key1', 'val1', :expires => 3)
  store['key1'].should == 'val1'
  sleep 1
  store.fetch('key1', nil, :expires => 5).should == 'val1'
  store['key1'].should == 'val1'
  sleep 3
  store['key1'].should == 'val1'
  sleep 3
  store['key1'].should be_nil
end

it 'supports 0 as no-expires in fetch' do
  store.store('key1', 'val1', :expires => 2)
  store.fetch('key1', nil, :expires => 0).should == 'val1'
  sleep 3
  store.load('key1').should == 'val1'
end

it 'supports false as no-expires in fetch' do
  store.store('key1', 'val1', :expires => 2)
  store.fetch('key1', nil, :expires => false).should == 'val1'
  sleep 3
  store.load('key1').should == 'val1'
end

it 'strictly respects expires in delete' do
  store.store('key2', 'val2', :expires => 2)
  store['key2'].should == 'val2'
  sleep 3 # Sleep 3 seconds because after 2 seconds the value can still exist!
  store.delete('key2').should be_nil
end

it 'respects expires in delete', :retry => 3 do
  store.store('key2', 'val2', :expires => 3)
  store['key2'].should == 'val2'
  sleep 1
  store['key2'].should == 'val2'
  sleep 3
  store.delete('key2').should be_nil
end

it 'supports the #expires syntactic sugar', :retry => 3 do
  store.store('persistent_key', 'persistent_value', :expires => 0)
  store.expires(1).store('key2', 'val2')
  store['key2'].should == 'val2'
  sleep 2
  store.delete('key2').should be_nil
  store['persistent_key'].should == 'persistent_value'
end

it 'supports false as no-expires on store and []' do
  store.store('key1', 'val1', :expires => false)
  store['key1'].should == 'val1'
  sleep 2
  store['key1'].should == 'val1'
end

it 'does not update the expiration time in #key? when not asked to do so', :retry => 3 do
  store.store('key1', 'val1', :expires => 1)
  store.key?('key1').should be_true
  store.key?('key1', :expires => nil).should be_true
  sleep 2
  store.key?('key1').should be_false
end

it 'does not update the expiration time in fetch when not asked to do so', :retry => 3 do
  store.store('key1', 'val1', :expires => 1)
  store.fetch('key1').should == 'val1'
  store.fetch('key1', :expires => nil).should == 'val1'
  sleep 2
  store.fetch('key1').should be_nil
end

it 'does not update the expiration time in load when not asked to do so', :retry => 3 do
  store.store('key1', 'val1', :expires => 1)
  store.load('key1').should == 'val1'
  store.load('key1', :expires => nil).should == 'val1'
  sleep 2
  store.load('key1').should be_nil
end}

SPECS['default_expires'] = %{it 'does set default expiration time' do
  store['key1'] = 'val1'
  store.key?('key1').should be_true
  store.fetch('key1').should == 'val1'
  store.load('key1').should == 'val1'
  sleep 2
  store.key?('key1').should be_false
  store.fetch('key1').should be_nil
  store.load('key1').should be_nil
end}

SPECS['not_increment'] = %{it 'does not support #increment' do
  expect do
    store.increment('inckey')
  end.to raise_error(NotImplementedError)
end

it 'does not support #decrement' do
  expect do
    store.increment('inckey')
  end.to raise_error(NotImplementedError)
end}

SPECS['concurrent_increment'] = %{def increment_thread(name)
  Thread.new do
    s = new_store
    100.times do |i|
      100.times do |j|
        s.increment("counter\#{j}", 1, :expires => false)
        Thread.pass if rand(1000) >= 995
      end
      s.store("\#{name}\#{i}", i.to_s, :expires => false)
    end
    s.close
  end
end

it 'have atomic increment across multiple processes' do
  a = increment_thread('a')
  b = increment_thread('b')
  c = increment_thread('c')
  a.join
  b.join
  c.join
  100.times do |i|
    store["a\#{i}"].should == i.to_s
    store["b\#{i}"].should == i.to_s
    store["c\#{i}"].should == i.to_s
  end
  100.times do |j|
    store.raw["counter\#{j}"].should == 300.to_s
  end
end}

SPECS['concurrent_create'] = %{def create_thread(name)
  Thread.new do
    s = new_store
    1000.times do |i|
      s[i.to_s].should == name if s.create(i.to_s, name, :expires => false)
      Thread.pass if rand(100) >= 99
    end
    s.close
  end
end

it 'have atomic create across multiple processes' do
  a = create_thread('a')
  b = create_thread('b')
  c = create_thread('c')
  a.join
  b.join
  c.join
end}

SPECS['increment'] = %{it 'initializes in #increment with 1' do
  store.key?('inckey').should be_false
  store.increment('inckey').should == 1
  store.key?('inckey').should be_true
  store.raw['inckey'].should == '1'
  store.raw.load('inckey').should == '1'
  store.load('inckey', :raw => true).should == '1'

  store.delete('inckey', :raw => true).should == '1'
  store.key?('inckey').should be_false
end

it 'initializes in #increment with higher value' do
  store.increment('inckey', 42).should == 42
  store.key?('inckey').should be_true
  store.raw['inckey'].should == '42'
  store.delete('inckey', :raw => true).should == '42'
end

it 'initializes in #increment with 0' do
  store.increment('inckey', 0).should == 0
  store.key?('inckey').should be_true
  store.raw['inckey'].should == '0'
  store.delete('inckey', :raw => true).should == '0'
end

it 'initializes in #decrement with 0' do
  store.decrement('inckey', 0).should == 0
  store.raw['inckey'].should == '0'
end

it 'initializes in #decrement with negative value' do
  store.decrement('inckey', -42).should == 42
  store.raw['inckey'].should == '42'
end

it 'supports incrementing existing value by value' do
  store.increment('inckey').should == 1
  store.increment('inckey', 42).should == 43
  store.raw['inckey'].should == '43'
end

it 'supports decrementing existing value by value' do
  store.increment('inckey').should == 1
  store.decrement('inckey').should == 0
  store.increment('inckey', 42).should == 42
  store.decrement('inckey', 2).should == 40
  store.raw['inckey'].should == '40'
end

it 'supports incrementing existing value by 0' do
  store.increment('inckey').should == 1
  store.increment('inckey', 0).should == 1
  store.raw['inckey'].should == '1'
end

it 'supports decrementing existing value' do
  store.increment('inckey', 10).should == 10
  store.increment('inckey', -5).should == 5
  store.raw['inckey'].should == '5'
  store.increment('inckey', -5).should == 0
  store.raw['inckey'].should == '0'
end

it 'interprets raw value as integer' do
  store.store('inckey', '42', :raw => true)
  store.increment('inckey').should == 43
  store.raw['inckey'].should == '43'
end

it 'raises error in #increment on non integer value' do
  store['strkey'] = 'value'
  expect do
    store.increment('strkey')
  end.to raise_error
end

it 'raises error in #decrement on non integer value' do
  store['strkey'] = 'value'
  expect do
    store.decrement('strkey')
  end.to raise_error
end

it 'supports Semaphore' do
  a = Moneta::Semaphore.new(store, 'semaphore', 2)
  b = Moneta::Semaphore.new(store, 'semaphore', 2)
  c = Moneta::Semaphore.new(store, 'semaphore', 2)
  a.synchronize do
    a.locked?.should be_true
    b.synchronize do
      b.locked?.should be_true
      c.try_lock.should be_false
    end
  end
end
}

SPECS['create'] = %{it 'creates the given key' do
  store.create('key','value').should be_true
  store['key'].should == 'value'
end

it 'creates raw value with the given key' do
  store.raw.create('key','value').should be_true
  store.raw['key'].should == 'value'
end

it 'does not create a key if it exists' do
  store['key'] = 'value'
  store.create('key','another value').should be_false
  store['key'].should == 'value'
end

it 'supports Mutex' do
  a = Moneta::Mutex.new(store, 'mutex')
  b = Moneta::Mutex.new(store, 'mutex')
  a.lock.should be_true
  b.try_lock.should be_false
  a.unlock.should be_nil
end
}

SPECS['not_create'] = %{it 'does not support #create' do
  expect do
    store.create('key','value')
  end.to raise_error(NotImplementedError)
end}

SPECS['create_expires'] = %{it 'creates the given key and expires it' do
  store.create('key','value', :expires => 1).should be_true
  store['key'].should == 'value'
  sleep 2
  store.key?('key').should be_false
end

it 'does not change expires if the key exists' do
  store.store('key', 'value', :expires => false).should == 'value'
  store.create('key','another value', :expires => 1).should be_false
  store['key'].should == 'value'
  sleep 2
  store['key'].should == 'value'
  store.key?('key').should be_true
end}

SPECS['marshallable_key']  = %{it 'refuses to #[] from keys that cannot be marshalled' do
  expect do
    store[Struct.new(:foo).new(:bar)]
  end.to raise_error(marshal_error)
end

it 'refuses to load from keys that cannot be marshalled' do
  expect do
    store.load(Struct.new(:foo).new(:bar))
  end.to raise_error(marshal_error)
end

it 'refuses to fetch from keys that cannot be marshalled' do
  expect do
    store.fetch(Struct.new(:foo).new(:bar), true)
  end.to raise_error(marshal_error)
end

it 'refuses to #[]= to keys that cannot be marshalled' do
  expect do
    store[Struct.new(:foo).new(:bar)] = 'value'
  end.to raise_error(marshal_error)
end

it 'refuses to store to keys that cannot be marshalled' do
  expect do
    store.store Struct.new(:foo).new(:bar), 'value'
  end.to raise_error(marshal_error)
end

it 'refuses to check for #key? if the key cannot be marshalled' do
  expect do
    store.key? Struct.new(:foo).new(:bar)
  end.to raise_error(marshal_error)
end

it 'refuses to delete a key if the key cannot be marshalled' do
  expect do
    store.delete Struct.new(:foo).new(:bar)
  end.to raise_error(marshal_error)
end}

SPECS['marshallable_value']  = %{it 'refuses to store values that cannot be marshalled' do
  expect do
    store.store 'key', Struct.new(:foo).new(:bar)
  end.to raise_error(marshal_error)
end}

SPECS['transform_value']  = %{it 'allows to bypass transformer with :raw' do
  store['key'] = 'value'
  load_value(store.load('key', :raw => true)).should == 'value'

  store.store('key', 'value', :raw => true)
  store.load('key', :raw => true).should == 'value'
  store.delete('key', :raw => true).should == 'value'
end

it 'allows to bypass transformer with raw syntactic sugar' do
  store['key'] = 'value'
  load_value(store.raw.load('key')).should == 'value'

  store.raw.store('key', 'value')
  store.raw['key'].should == 'value'
  store.raw.load('key').should == 'value'
  store.raw.delete('key').should == 'value'

  store.raw['key'] = 'value2'
  store.raw['key'].should == 'value2'
end

it 'returns unmarshalled value' do
  store.store('key', 'unmarshalled value', :raw => true)
  store.load('key', :raw => true).should == 'unmarshalled value'
end

it 'might raise exception on invalid value' do
  store.store('key', 'unmarshalled value', :raw => true)

  begin
    store['key'].should == load_value('unmarshalled value')
    store.delete('key').should == load_value('unmarshalled value')
  rescue Exception => ex
    expect do
      store['key']
    end.to raise_error
    expect do
      store.delete('key')
    end.to raise_error
  end
end}

SPECS['transform_value_expires']  = %{it 'allows to bypass transformer with :raw' do
  store['key'] = 'value'
  load_value(store.load('key', :raw => true)).should == 'value'
  store['key'] = [1,2,3]
  load_value(store.load('key', :raw => true)).should == [[1,2,3]]
  store['key'] = nil
  load_value(store.load('key', :raw => true)).should == [nil]
  store['key'] = false
  load_value(store.load('key', :raw => true)).should be_false

  store.store('key', 'value', :expires => 10)
  load_value(store.load('key', :raw => true)).first.should == 'value'
  load_value(store.load('key', :raw => true)).last.should respond_to(:to_int)

  store.store('key', 'value', :raw => true)
  store.load('key', :raw => true).should == 'value'
  store.delete('key', :raw => true).should == 'value'
end

it 'returns unmarshalled value' do
  store.store('key', 'unmarshalled value', :raw => true)
  store.load('key', :raw => true).should == 'unmarshalled value'
end

it 'might raise exception on invalid value' do
  store.store('key', 'unmarshalled value', :raw => true)

  begin
    store['key'].should == load_value('unmarshalled value')
    store.delete('key').should == load_value('unmarshalled value')
  rescue Exception => ex
    expect do
      store['key']
    end.to raise_error
    expect do
      store.delete('key')
    end.to raise_error
  end
end}

SPECS['features'] = %{it 'should report correct features' do
  store.features.sort_by(&:to_s).should == features
end

it 'should have frozen features' do
  store.features.frozen?.should be_true
end

it 'should have #supports?' do
  features.each do |f|
    store.supports?(f).should be_true
  end
  store.supports?(:unknown).should be_false
end}

specs_code = "#{header}\n"
SPECS.each do |key, code|
  specs_code << "#################### #{key} ####################\n\n" <<
    "shared_examples_for '#{key}' do\n  " << [code].flatten.join("\n\n").gsub("\n", "\n  ") << "\nend\n\n"
end
specs_code.gsub!(/\n +\n/, "\n\n")
File.open(File.join(PATH, 'monetaspecs.rb'), 'w') {|out| out << specs_code }

TESTS.each do |name, options|
  build = options.delete(:build)
  store = options.delete(:store)

  load_value = options.delete(:load_value) || 'Marshal.load(value)'

  specs_code = []
  specs = options.delete(:specs)
  specs.specs.sort.each do |s|
    specs_code << "  it_should_behave_like '#{s}'" if SPECS[s.to_s]
    specs.key.each do |k|
      specs.value.each do |v|
        x = "#{s}_#{k}key_#{v}value"
        specs_code << "  it_should_behave_like '#{x}'" if SPECS[x]
      end
    end
  end

  preamble = options.delete(:preamble).to_s.gsub("\n", "\n  ")
  opts = options.delete(:options)
  opts = ', ' << opts if opts

  build ||= "Moneta.new(#{store.inspect}#{opts}, :logger => {:file => File.join(make_tempdir, '#{name}.log')})"

  code = %{#{header}require 'helper'

describe_moneta #{name.inspect} do
  #{preamble}def features
    #{specs.features.to_a.inspect}
  end

  def new_store
    #{build.gsub("\n", "\n    ")}
  end

  def load_value(value)
    #{load_value}
  end

  include_context 'setup_store'
#{specs_code.join("\n")}#{options[:tests].to_s.gsub("\n", "\n  ")}
end
}

  code.gsub!(/\n +\n/, "\n\n")
  File.open(File.join(PATH, 'moneta', "#{name}_spec.rb"), 'w') {|out| out << code }
end
