package rabbitescape.render; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.CoreMatchers.*; import static rabbitescape.engine.Tools.*; import java.util.ArrayList; import java.util.List; import rabbitescape.render.androidlike.Bitmap; import org.junit.Test; public class TestBitmapCache { @Test public void Cache_and_scale_calls_load_if_not_loaded_before() { TrackingBitmapLoader loader = new TrackingBitmapLoader(); BitmapCache<FakeBitmap> cache = new BitmapCache<>( loader, null, 5 ); // This is what we are testing - get from the cache and load a bitmap cache.get( "a/b/foo.png", 32 ); // Load was called once assertThat( loader.loadCalls, equalToList( new String[] { "a/b/foo.png" } ) ); } @Test public void Getting_a_bitmap_from_the_cache_returns_the_same_object() { TrackingBitmapLoader loader = new TrackingBitmapLoader(); BitmapCache<FakeBitmap> cache = new BitmapCache<>( loader, null, 5 ); FakeBitmap gotBitmap1 = cache.get( "a/b/foo01.png", 32 ); FakeBitmap gotBitmap2 = cache.get( "a/b/foo02.png", 32 ); // Sanity assertThat( gotBitmap1, not( sameInstance( gotBitmap2 ) ) ); // This is what we are testing - get same instance when call again assertThat( gotBitmap1, sameInstance( cache.get( "a/b/foo01.png", 32 ) ) ); assertThat( gotBitmap1, sameInstance( cache.get( "a/b/foo01.png", 32 ) ) ); assertThat( gotBitmap2, sameInstance( cache.get( "a/b/foo02.png", 32 ) ) ); assertThat( gotBitmap2, sameInstance( cache.get( "a/b/foo02.png", 32 ) ) ); } @Test public void Load_only_called_once_even_if_get_called_multiple_times() { TrackingBitmapLoader loader = new TrackingBitmapLoader(); TrackingBitmapScaler scaler = new TrackingBitmapScaler(); BitmapCache<FakeBitmap> cache = new BitmapCache<>( loader, scaler, 5 ); // This is what we are testing - call get 3 times FakeBitmap bitmap = cache.get( "a/b/foo.png", 30 ); cache.get( "a/b/foo.png", 30 ); cache.get( "a/b/foo.png", 30 ); // Load was still only called once assertThat( loader.loadCalls, equalToList( new String[] { "a/b/foo.png" } ) ); // Scale was only called once assertThat( scaler.scaleCalls.size(), equalTo( 1 ) ); // The bitmap was not recycled assertThat( bitmap.recycled, is( false ) ); } @Test public void Scale_is_not_called_when_we_ask_for_the_loaded_size() { TrackingBitmapLoader loader = new TrackingBitmapLoader(); TrackingBitmapScaler scaler = new TrackingBitmapScaler(); BitmapCache<FakeBitmap> cache = new BitmapCache<>( loader, scaler, 5 ); // This is what we are testing - get it and scale 3 times to // the same size as the loader uses. cache.get( "a/b/foo.png", 32 ); cache.get( "a/b/foo.png", 32 ); cache.get( "a/b/foo.png", 32 ); // Scale was only called once assertThat( scaler.scaleCalls.size(), equalTo( 0 ) ); } @Test public void Scale_is_only_called_the_first_time_we_ask_for_a_size() { TrackingBitmapLoader loader = new TrackingBitmapLoader(); TrackingBitmapScaler scaler = new TrackingBitmapScaler(); BitmapCache<FakeBitmap> cache = new BitmapCache<>( loader, scaler, 5 ); // This is what we are testing - get it and scale 3 times to // the same size, but not the default loader size cache.get( "a/b/foo.png", 19 ); // Scale was called assertThat( scaler.scaleCalls.size(), equalTo( 1 ) ); cache.get( "a/b/foo.png", 19 ); cache.get( "a/b/foo.png", 19 ); // Scale was not called again assertThat( scaler.scaleCalls.size(), equalTo( 1 ) ); } @Test public void Recently_accessed_items_are_not_purged_before_older() { TrackingBitmapLoader loader = new TrackingBitmapLoader(); BitmapCache<FakeBitmap> cache = new BitmapCache<>( loader, null, 5 ); // Get enough to fill the cache FakeBitmap b1 = cache.get( "a/b/foo01.png", 32 ); FakeBitmap b2 = cache.get( "a/b/foo02.png", 32 ); FakeBitmap b3 = cache.get( "a/b/foo03.png", 32 ); FakeBitmap b4 = cache.get( "a/b/foo04.png", 32 ); FakeBitmap b5 = cache.get( "a/b/foo05.png", 32 ); // This is what we are testing: re-access foo02 and it should avoid // being purged cache.get( "a/b/foo02.png", 32 ); // Sanity assertThat( loader.loadCalls.size(), equalTo( 5 ) ); // Get 3 new things - causing 3 things to be purged cache.get( "a/b/foo06.png", 32 ); cache.get( "a/b/foo07.png", 32 ); cache.get( "a/b/foo08.png", 32 ); assertThat( b1.recycled, is( true ) ); assertThat( b2.recycled, is( false ) ); assertThat( b3.recycled, is( true ) ); assertThat( b4.recycled, is( true ) ); assertThat( b5.recycled, is( false ) ); // Sanity assertThat( loader.loadCalls.size(), equalTo( 8 ) ); // Get foo02 - should not need to re-load it cache.get( "a/b/foo02.png", 32 ); // Load was not called again assertThat( loader.loadCalls.size(), equalTo( 8 ) ); // Get foo01 - for this we should need to re-load it cache.get( "a/b/foo01.png", 32 ); // Load was called assertThat( loader.loadCalls.size(), equalTo( 9 ) ); } @Test public void Bitmaps_are_purged_in_order_when_cache_is_full() { TrackingBitmapLoader loader = new TrackingBitmapLoader(); BitmapCache<FakeBitmap> cache = new BitmapCache<>( loader, null, 5 ); // Get enough to fill the cache FakeBitmap b1 = cache.get( "a/b/foo01.png", 32 ); FakeBitmap b2 = cache.get( "a/b/foo02.png", 32 ); FakeBitmap b3 = cache.get( "a/b/foo03.png", 32 ); FakeBitmap b4 = cache.get( "a/b/foo04.png", 32 ); FakeBitmap b5 = cache.get( "a/b/foo05.png", 32 ); // Not recycled yet assertThat( b1.recycled, is( false ) ); // This is what we are testing: get another and the first bitmap // should get purged cache.get( "a/b/foo06.png", 32 ); // Sanity assertThat( loader.loadCalls.size(), equalTo( 6 ) ); assertThat( loader.loadCalls.get( 5 ), equalTo( "a/b/foo06.png" ) ); // The first was recycled, others weren't assertThat( b1.recycled, is( true ) ); assertThat( b2.recycled, is( false ) ); assertThat( b3.recycled, is( false ) ); assertThat( b4.recycled, is( false ) ); assertThat( b5.recycled, is( false ) ); // Try and get the first - it should have to be reloaded cache.get( "a/b/foo01.png", 32 ); // Load was called again for foo01 assertThat( loader.loadCalls.size(), equalTo( 7 ) ); assertThat( loader.loadCalls.get( 6 ), equalTo( "a/b/foo01.png" ) ); } // -- private static class FakeBitmap implements Bitmap { public boolean recycled = false; @Override public String name() { return "fake"; } @Override public int width() { return 32; } @Override public int height() { return 32; } @Override public void recycle() { assertThat( recycled, is( false ) ); recycled = true; } @Override public long getByteCount() { return 1; } } private static class TrackingBitmapLoader implements BitmapLoader<FakeBitmap> { public List<String> loadCalls = new ArrayList<String>(); @Override public FakeBitmap load( String fileName, int tileSize ) { loadCalls.add( fileName ); return new FakeBitmap(); } @Override public int sizeFor( int tileSize ) { return 32; } } private static class TrackingBitmapScaler implements BitmapScaler<FakeBitmap> { public List<String> scaleCalls = new ArrayList<String>(); @Override public FakeBitmap scale( FakeBitmap originalBitmap, double scale ) { scaleCalls.add( String.valueOf( scale ) ); return new FakeBitmap(); } } }