Back to Repositories

Testing Drawable Loading and Resource Management in Glide

This test suite validates Glide’s drawable loading functionality, focusing on bitmap handling, memory management, and cache behavior. The tests ensure proper resource lifecycle management and verify that drawables are handled correctly across different loading scenarios.

Test Coverage Overview

The test suite provides comprehensive coverage of Glide’s drawable loading capabilities, particularly focusing on bitmap recycling and cache behavior. Key areas tested include:
  • Bitmap resource cleanup during clear operations
  • Transformation handling with loaded BitmapDrawables
  • Memory cache interaction patterns
  • Disk cache strategy verification

Implementation Analysis

The testing approach utilizes JUnit4 with AndroidX test extensions for Android-specific components. The implementation employs mock executors and concurrency helpers to ensure reliable test execution. Tests verify both RequestManager and RequestBuilder workflows with various configuration combinations.

Notable patterns include systematic verification of bitmap recycling behavior and careful management of resource lifecycle states.

Technical Details

Testing tools and configuration:
  • JUnit4 with AndroidX test runner
  • Mockito for dependency mocking
  • Custom ConcurrencyHelper for thread management
  • MockGlideExecutor for controlled execution
  • Custom TearDownGlide rule for cleanup
  • LruResourceCache and LruBitmapPool for memory management testing

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Proper test isolation using @Before setup
  • Systematic resource cleanup
  • Controlled execution environment
  • Comprehensive assertion coverage
  • Clear test method naming conventions
  • Effective use of mock objects and verification

bumptech/glide

instrumentation/src/androidTest/java/com/bumptech/glide/LoadDrawableTest.java

            
package com.bumptech.glide;

import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter;
import com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool;
import com.bumptech.glide.load.engine.cache.LruResourceCache;
import com.bumptech.glide.load.engine.cache.MemoryCacheAdapter;
import com.bumptech.glide.load.engine.executor.GlideExecutor;
import com.bumptech.glide.load.engine.executor.MockGlideExecutor;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.test.GlideApp;
import com.bumptech.glide.test.ResourceIds;
import com.bumptech.glide.testutil.ConcurrencyHelper;
import com.bumptech.glide.testutil.TearDownGlide;
import com.bumptech.glide.util.Util;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@RunWith(AndroidJUnit4.class)
public class LoadDrawableTest {
  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();
  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();

  @Mock private RequestListener<Drawable> listener;

  private Context context;
  private GlideExecutor executor;
  private GlideBuilder glideBuilder;

  @Before
  public void setUp() {
    MockitoAnnotations.initMocks(this);

    executor = MockGlideExecutor.newMainThreadExecutor();

    context = ApplicationProvider.getApplicationContext();
    glideBuilder =
        new GlideBuilder()
            .setAnimationExecutor(executor)
            .setSourceExecutor(executor)
            .setDiskCacheExecutor(executor);
  }

  @Test
  public void clear_withLoadedBitmapDrawable_doesNotRecycleBitmap() {
    Glide.init(
        context,
        glideBuilder
            .setMemoryCache(new MemoryCacheAdapter())
            .setBitmapPool(new BitmapPoolAdapter()));
    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);
    BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);
    Target<Drawable> target =
        concurrency.wait(GlideApp.with(context).load(drawable).submit(100, 100));
    Glide.with(context).clear(target);

    // Allow Glide's resource recycler to run on the main thread.
    concurrency.pokeMainThread();

    assertThat(bitmap.isRecycled()).isFalse();
  }

  @Test
  public void transform_withLoadedBitmapDrawable_doesNotRecycleBitmap() {
    Glide.init(
        context,
        glideBuilder
            .setMemoryCache(new MemoryCacheAdapter())
            .setBitmapPool(new BitmapPoolAdapter()));
    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);
    BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);
    concurrency.wait(GlideApp.with(context).load(drawable).centerCrop().submit(100, 100));

    assertThat(bitmap.isRecycled()).isFalse();
  }

  @Test
  public void loadFromRequestManager_withBitmap_doesNotLoadFromDiskCache() {
    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);
    BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);
    Glide.init(
        context,
        glideBuilder
            .setMemoryCache(new LruResourceCache(Util.getBitmapByteSize(bitmap) * 10))
            .setBitmapPool(new LruBitmapPool(Util.getBitmapByteSize(bitmap) * 10)));
    Target<Drawable> target =
        concurrency.wait(GlideApp.with(context).load(drawable).centerCrop().submit(100, 100));
    Glide.with(context).clear(target);

    assertThat(bitmap.isRecycled()).isFalse();

    concurrency.runOnMainThread(
        new Runnable() {
          @Override
          public void run() {
            Glide.get(context).clearMemory();
          }
        });

    concurrency.wait(
        GlideApp.with(context).load(drawable).centerCrop().listener(listener).submit(100, 100));

    verify(listener)
        .onResourceReady(
            ArgumentMatchers.<Drawable>any(),
            any(),
            ArgumentMatchers.<Target<Drawable>>any(),
            eq(DataSource.LOCAL),
            anyBoolean());
  }

  @Test
  public void loadFromRequestBuilder_asDrawable_withBitmap_doesNotLoadFromDiskCache() {
    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);
    BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);
    Glide.init(
        context,
        glideBuilder
            .setMemoryCache(new LruResourceCache(Util.getBitmapByteSize(bitmap) * 10))
            .setBitmapPool(new LruBitmapPool(Util.getBitmapByteSize(bitmap) * 10)));
    Target<Drawable> target =
        concurrency.wait(
            GlideApp.with(context).asDrawable().load(drawable).centerCrop().submit(100, 100));
    Glide.with(context).clear(target);

    assertThat(bitmap.isRecycled()).isFalse();

    concurrency.runOnMainThread(
        new Runnable() {
          @Override
          public void run() {
            Glide.get(context).clearMemory();
          }
        });

    concurrency.wait(
        GlideApp.with(context).load(drawable).centerCrop().listener(listener).submit(100, 100));

    verify(listener)
        .onResourceReady(
            ArgumentMatchers.<Drawable>any(),
            any(),
            ArgumentMatchers.<Target<Drawable>>any(),
            eq(DataSource.LOCAL),
            anyBoolean());
  }

  @Test
  public void loadFromRequestBuilder_asDrawable_withBitmapAndStrategyBeforeLoad_notFromCache() {
    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResourceIds.raw.canonical);
    BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);
    Glide.init(
        context,
        glideBuilder
            .setMemoryCache(new LruResourceCache(Util.getBitmapByteSize(bitmap) * 10))
            .setBitmapPool(new LruBitmapPool(Util.getBitmapByteSize(bitmap) * 10)));
    Target<Drawable> target =
        concurrency.wait(
            GlideApp.with(context)
                .asDrawable()
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .load(drawable)
                .centerCrop()
                .submit(100, 100));
    Glide.with(context).clear(target);

    assertThat(bitmap.isRecycled()).isFalse();

    concurrency.runOnMainThread(
        new Runnable() {
          @Override
          public void run() {
            Glide.get(context).clearMemory();
          }
        });

    concurrency.wait(
        GlideApp.with(context).load(drawable).centerCrop().listener(listener).submit(100, 100));

    verify(listener)
        .onResourceReady(
            ArgumentMatchers.<Drawable>any(),
            any(),
            ArgumentMatchers.<Target<Drawable>>any(),
            eq(DataSource.LOCAL),
            anyBoolean());
  }
}