#!/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_mongo_moped' => {
    store: :MongoMoped,
    options: "db: 'standard_mongo_moped'",
    specs: STANDARD_SPECS.with_native_expires
  },
  'standard_mongo_official' => {
    store: :MongoOfficial,
    options: "db: 'standard_mongo_official'",
    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
  },
  'adapter_mongo_with_default_expires' => {
    build: %{Moneta::Adapters::Mongo.new(expires: 1)},
    specs: ADAPTER_SPECS.with_expires.with_default_expires.simplevalues_only
  },
  'adapter_mongo_moped' => {
    build: 'Moneta::Adapters::MongoMoped.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('_id' => ::Moped::BSON::Binary.new(:generic, 'key')).one.should_not be_nil
  sleep 70 # Mongo needs up to 60 seconds
  store.instance_variable_get(:@collection).find('_id' => ::Moped::BSON::Binary.new(:generic, 'key')).one.should be_nil
end}
  },
  'adapter_mongo_moped_with_default_expires' => {
    build: %{Moneta::Adapters::MongoMoped.new(expires: 1)},
    specs: ADAPTER_SPECS.with_expires.with_default_expires.simplevalues_only
  },
  'adapter_mongo_official' => {
    build: 'Moneta::Adapters::MongoOfficial.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_official_with_default_expires' => {
    build: %{Moneta::Adapters::MongoOfficial.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
