require 'test/unit'
require 'unicache'

class TestableUniCache < UniCache
    attr_reader :cb
    attr_accessor :cb_table
end

class UniCacheTest < Test::Unit::TestCase
    
    def test_put_get
        c = UniCache.new( 2 )

        assert_equal( 2, c.size )

        c.put( 'foo', 'bar' )

        assert_equal( 1, c.length )

        c.put( 'dii', 'duu' )

        assert_equal( 2, c.length )

        assert_equal( 'duu', c.get( 'dii' ) )
        assert_equal( 'bar', c.get( 'foo' ) )

        c[ 'foo2' ] = 'bar'

        assert_equal( 2, c.length )

        c[ 'dii2' ] = 'duu'

        assert_equal( 2, c.length )

        assert_equal( 'duu', c[ 'dii2' ] )
        assert_equal( 'bar', c[ 'foo2' ] )
        
        assert_equal( nil, c.get( 'dii' ) )
        assert_equal( nil, c.get( 'foo' ) )
    end


    def test_eviction
        c = UniCache.new( 2 )
        c[0] = 0
        c[1] = 1
        c[2] = 2

        # 0 evicted.
        assert_equal( nil, c[0] )
        assert_equal( 1, c[1] )
        assert_equal( 2, c[2] )

        # 1 latest.
        c[1]
        c[0] = 0

        # 2 evicted as oldest.
        assert_equal( nil, c[2] )
        assert_equal( 0, c[0] )
        assert_equal( 1, c[1] )

        assert_equal( nil, c[3] )

    end


    def test_resize
        c = UniCache.new( 2 )

        assert_equal( 2, c.size )

        c.put( 'foo', 'bar' )
        c.put( 'dii', 'duu' )

        assert_equal( 2, c.length )
        
        c.resize( 1 )

        assert_equal( 1, c.length )

        exception = false
        begin
            c.resize( 0 )
        rescue UniCache::SizeError
            exception = true
        end
        assert_equal( true, exception )

        c.put( 'foo', 'bar' )
        c.put( 'dii', 'duu' )

        assert_equal( 'duu', c.get( 'dii' ) )
        assert_equal( nil, c.get( 'foo' ) )

    end


    def test_existance

        c = UniCache.new( 2 )
        c[0] = 0
        c[2] = 2
        c[1] = 1

        assert_equal( nil, c.peek(0) )
        assert_equal( 1, c.peek(1) )
        assert_equal( 2, c.peek(2) )

        assert_equal( false, c.exist?(0) )
        assert_equal( true, c.exist?(1) )
        assert_equal( true, c.exist?(2) )

        # 2 should be evicted.
        c[0] = 0
        assert_equal( true, c.exist?(0) )
        assert_equal( true, c.exist?(1) )
        assert_equal( false, c.exist?(2) )

        c.remove( 0 )

        assert_equal( 1, c.length )

        assert_equal( true, c.exist?(1) )

        assert_equal( 1, c.length )
        c[2] = 2
        assert_equal( 2, c.length )
        oldest = c.remove
        assert_equal( [1,1], oldest )

        c.clear

        exception = false
        begin
            c.remove( 0 )
        rescue UniCache::RemoveError
            exception = true
        end
        assert_equal( true, exception )

        assert_equal( 0, c.length )
        assert_equal( 2, c.size )
        assert_equal( false, c.exist?(1) )
        assert_equal( false, c.exist?(0) )

        # Clear for empty cache.
        c.clear

    end
        

    def test_callbacks
        c = TestableUniCache.new( 2 )
        c.cb_table = {}
        
        # Register dummy callbacks.
        c.cb.ids.each do |type|
            eval "c.registerCallback( :#{type.to_s}, Proc.new{ |k,v,o| o.cb_table[ :#{type.to_s} ] = [ k, v ] } )"
        end

        c.removeCallback( :getdata )
        c.registerCallback( :getdata, Proc.new{ |k| k+1 } )
       
        # Wrong type.
        exception = false
        begin
            c.registerCallback( :my_callback, Proc.new {} )
        rescue UniCache::CallbackError
            exception = true
        end
        assert_equal( true, exception )
       
        # Invalid proc.
        exception = false
        begin
            c.registerCallback( :add, true )
        rescue UniCache::CallbackError
            exception = true
        end
        assert_equal( true, exception )


        c[0] = 0
        assert_equal( {:add=>[0, 0], :put=>[0, 0]}, c.cb_table )
        c.cb_table.clear

        c[0] = 1
        assert_equal( {:overwrite=>[0, 1], :valueremove=>[0, 0], :put=>[0, 1]}, c.cb_table )
        c.cb_table.clear

        c[0]
        assert_equal( {:hit=>[0, 1]}, c.cb_table )
        c.cb_table.clear

        c[1]
        assert_equal( {:miss=>[1, nil], :add=>[1, 2], :put=>[1, 2]}, c.cb_table )
        c.cb_table.clear

        c.removeCallback
        c.resize( 1 )
        c[0] = 0
        c[1]
        assert_equal( {}, c.cb_table )
        c.cb_table.clear
        
    end


    def test_thread

        c = UniCache.new( 2 )

        # Trying to destroy entry.
        t1 = Thread.new do
            sleep( 1 )
            c.remove( 0 )
        end
                
        c.registerCallback( :getdata, Proc.new do 'data' end )
    
        gotValue = nil

        # Block value through get.
        t2 = Thread.new do
            c.get( 0 ) do |val|
                sleep( 4 )
                gotValue = val
            end
        end

        # Distractions.
        t3 = Thread.new do
            sleep( 1 )
            100.times do
                c[0]
                c[0] = 2
            end
        end

        [t1, t2, t3].each do |t| t.join end

        assert_equal( 'data', gotValue )
    end

end