
#
# Specifying rufus-tokyo
#
# Fri Jul 17 15:37:16 JST 2009 #rubykaigi2009
#

shared 'an abstract structure' do

  it 'should be empty initially' do

    @db.size.should.equal(0)
    @db['pillow'].should.be.nil
  end

  it 'should accept values' do

    @db['pillow'] = 'Shonagon'
    @db.size.should.equal(1)
  end

  it 'should restitute values' do

    @db['pillow'] = 'Shonagon'
    @db['pillow'].should.equal('Shonagon')
  end

  it 'should delete values' do

    @db['pillow'] = 'Shonagon'
    @db.delete('pillow').should.equal('Shonagon')
    @db.size.should.equal(0)
  end

  it 'should accept and restitute \\0 strings' do

    s = "toto#{0.chr}nada"
    @db[s] = s
    @db[s].should.equal(s)
  end
  
  it 'should stringify keys and values' do
    @db[123] = 456
    @db["123".to_sym].should.equal("456")
  end

  it 'should reply to #keys and #values' do

    keys = %w{ alpha bravo charly delta echo foxtrott }
    keys.each_with_index { |k, i| @db[k] = i.to_s }
    @db.keys.should.equal(keys)
    @db.values.should.equal(%w{ 0 1 2 3 4 5 })
  end

  it 'should reply to #keys when there are keys containing \0' do

    s = "toto#{0.chr}nada"
    @db[s] = s
    @db.keys.should.equal([ s ])
  end

  it 'should return a Ruby hash on merge' do

    @db['a'] = 'A'

    @db.merge({ 'b' => 'B', 'c' => 'C' }).should.equal(
      { 'a' => 'A', 'b' => 'B', 'c' => 'C' })

    @db['b'].should.be.nil

    @db.size.should.equal(1)
  end

  it 'should have more values in case of merge!' do

    @db['a'] = 'A'

    @db.merge!({ 'b' => 'B', 'c' => 'C' })

    @db.size.should.equal(3)
    @db['b'].should.equal('B')
  end
end

shared 'abstract structure #keys' do

  it 'should return a Ruby Array by default' do

    @db.keys.class.should.equal(::Array)
  end

  it 'should retrieve keys that contain \0' do

    @db.keys.include?("toto#{0.chr}5").should.be.true
  end

  it 'should retrieve forward matching keys when key contains \0' do

    @db.keys(:prefix => 'toto').should.equal([ "toto#{0.chr}5" ])
  end

  it 'should return a limited number of keys when :limit is set' do

    @db.keys(:limit => 20).size.should.equal(20)
  end

  it 'should delete_keys_with_prefix' do

    @db.delete_keys_with_prefix('animal')
    @db.size.should.equal(@n + 1)
    @db.keys(:prefix => 'animal').size.should.equal(0)
  end

  unless @db.class.name.match(/^Rufus::Edo/)

    it 'should return a Cabinet List when :native => true' do

      l = @db.keys(:native => true)
      l.class.should.equal(Rufus::Tokyo::List)
      l.size.should.equal(2 * @n + 1)
      l.free
    end

    it 'should retrieve forward matching keys when :prefix => "prefix-"' do

      @db.keys(:prefix => 'person').size.should.equal(@n)

      l = @db.keys(:prefix => 'animal', :native => true)
      l.size.should.equal(@n)
      l.free
    end
  end
end

shared 'abstract structure with transactions' do

  #before do
  #  @db = Rufus::Tokyo::Cabinet.new('tmp/cabinet_spec.tch')
  #  @db.clear
  #end
  #after do
  #  @db.close
  #end

  it 'should correctly abort transactions' do

    @db.transaction {
      @db['pk0'] = 'value0'
      @db.abort
    }
    @db.size.should.be.zero
  end

  it 'should rollback transactions with errors, and bubble exceptions' do

    begin
      @db.transaction {
        @db['pk0'] = 'value0'
        raise 'something goes wrong'
      }
    rescue RuntimeError
    end
    @db.size.should.be.zero
  end

  it 'should rollback transactions with Abort exceptions, and consume exceptions' do

    @db.transaction {
      @db['pk0'] = 'value0'
      raise Rufus::Tokyo::Transactions::Abort
    }
    @db.size.should.be.zero
  end

  it 'should commit successful transactions' do

    @db.transaction do
      @db['pk0'] = 'showanojidaietaminamishima'
    end
    @db['pk0'].should.equal('showanojidaietaminamishima')
  end

  it 'should abort low level transactions' do

    @db.tranbegin
    @db['pk0'] = 'shikataganai'
    @db.tranabort
    @db.size.should.be.zero
  end

  it 'should commit low level transactions' do

    @db.tranbegin
    @db['pk0'] = 'shikataganai'
    @db.trancommit
    @db['pk0'].should.equal('shikataganai')
  end
end

shared 'abstract structure #lget/lput/ldelete' do

  it 'should get multiple values' do

    @db.lget(%w{ 0 1 2 }).should.equal({"0"=>"val0", "1"=>"val1", "2"=>"val2"})
    @db.mget(%w{ 0 1 2 }).should.equal({"0"=>"val0", "1"=>"val1", "2"=>"val2"})
    @db.mget(*%w{ 0 1 2 }).should.equal({"0"=>"val0", "1"=>"val1", "2"=>"val2"})
  end

  it 'should put multiple values' do

    @db.lput('3' => 'val3', '4' => 'val4')
    @db.lget(%w{ 2 3 }).should.equal({"2"=>"val2", "3"=>"val3"})
  end

  it 'should delete multiple values' do

    @db.ldelete(%w{ 2 3 })
    @db.lget(%w{ 0 1 2 }).should.equal({"0"=>"val0", "1"=>"val1"})
  end

  it 'should delete multiple values' do

    @db.ldelete(*%w{ 2 3 })
    @db.lget(%w{ 0 1 2 }).should.equal({"0"=>"val0", "1"=>"val1"})
  end
end

shared 'abstract structure #add{int|double}' do

  it 'should increment (int)' do

    @db.addint('counter', 1).should.equal(1)
    @db.incr('counter', 1).should.equal(2)
    @db.addint('counter', 2).should.equal(4)
    @db.incr('counter').should.equal(5)

    @db.counter_value('counter').should.equal(5)
  end

  it 'should fail gracefully if counter has already a [string] value (int)' do

    @db['counter'] = 'a'

    lambda { @db.addint('counter', 1) }.should.raise(
      @db.class.name.match(/^Rufus::Edo/) ?
        Rufus::Edo::EdoError : Rufus::Tokyo::TokyoError)

    @db['counter'].should.equal('a')
  end

  it 'should increment (double)' do

    @db.adddouble('counter', 1.0).should.equal(1.0)
    @db.incr('counter', 1.5).should.equal(2.5)
    @db.adddouble('counter', 2.2).should.equal(4.7)

    @db.counter_value('counter').should.equal(4.7)
  end

  it 'should fail gracefully if counter has already a [string] value (double)' do

    @db['counter'] = 'a'

    lambda {
      @db.adddouble('counter', 1.0)
    }.should.raise(
      @db.class.name.match(/^Rufus::Edo/) ?
        Rufus::Edo::EdoError : Rufus::Tokyo::TokyoError)

    @db['counter'].should.equal('a')
  end
end

shared 'abstract structure #putkeep' do

  it 'should accept values' do

    @db.putkeep('pillow', 'Shonagon')
    @db.size.should.equal(1)
  end

  it 'should restitute values' do

    @db.putkeep('pillow', 'Shonagon')
    @db['pillow'].should.equal('Shonagon')
  end

  it 'should not overwrite values if already set' do

    @db['pillow'] = 'Shonagon'
    @db['pillow'].should.equal('Shonagon')

    @db.putkeep('pillow', 'Ruby')
    @db['pillow'].should.equal('Shonagon')
  end

  it 'should return true if not yet set' do

    @db.putkeep('pillow', 'Shonagon').should.equal(true)
  end

  it 'should return false if already set' do

    @db['pillow'] = 'Shonagon'
    @db.putkeep('pillow', 'Ruby').should.equal(false)
  end

  it 'should accept binary data \0' do

    s = "Sei#{0.chr}Shonagon"

    @db.putkeep(s, s).should.be.true
    @db[s].should.equal(s)
  end
end

shared 'abstract structure #putcat' do

  it 'should append' do

    @db['heian'] = 'Shonagon'
    @db.putcat('heian', ', Shikibu')
    @db.putcat('heian', ', Michitsuna')

    @db['heian'].should.equal('Shonagon, Shikibu, Michitsuna')
  end

  it 'should create when not present' do

    @db.putcat('regent', 'Fujiwara no Michinaga')

    @db['regent'].should.equal('Fujiwara no Michinaga')
  end
end

shared 'tyrant with embedded lua' do

  it 'should call lua extensions' do

    @db['toto'] = '0'
    3.times { @db.ext(:incr, 'toto', '1') }
    @db.ext('incr', 'toto', 2) # lax

    @db['toto'].should.equal('5')
  end

  it 'should return nil when function is missing' do

    @db.ext(:missing, 'nada', 'forever').should.equal(nil)
  end
end

shared 'an abstract structure flattening keys and values' do

  it 'should to_s keys and values when #[]=' do

    @db[:hello] = :world
    @db['hello'].should.equal('world')
    @db[:hello].should.equal('world')
    @db.size.should.equal(1)
  end

  it 'should to_s keys and values when #putkeep' do

    @db.putkeep(:pillow, :shonagon)
    @db[:pillow].should.equal('shonagon')
    @db.size.should.equal(1)
  end

  it 'should to_s keys when #delete' do

    @db['hello'] = 'world'
    @db.delete(:hello).should.equal('world')
    @db.size.should.equal(0)
  end

  it 'should to_s keys when #lget' do

    writers = %w[ shikibu shonagon ]
    writers.each { |s| @db[s] = s }
    @db.lget(writers.collect { |w| w.to_sym }).keys.sort.should.equal(writers)
  end

  it 'should to_s keys when #ldelete' do

    writers = %w[ shonagon shikibu ]
    writers.each { |s| @db[s] = s }

    @db.ldelete(writers.collect { |w| w.to_sym })

    @db.size.should.equal(0)
  end

  it 'should to_s keys when #incr' do

    @db.incr(:genji_count, 1).should.equal(1)
    @db.incr(:genji_count, 1).should.equal(2)
  end

  it 'should to_s keys and values when #merge!' do

    @db.merge!(:genji => 1, :to_no_chujo => 2)

    @db['genji'].should.equal('1')
    @db['to_no_chujo'].should.equal('2')
  end
end

shared 'an abstract structure with a default value' do

  it 'should restitute the default value' do

    @db['known'].should.equal('Ulysse')
    @db['unknown'].should.equal('Nemo')
  end
end

shared 'an abstract structure with a default_proc' do

  it 'should restitute the value computed by the default_proc' do

    @db['known'].should.equal('Ulysse')
    @db['unknown'].should.equal('default:unknown')
  end
end

