Back to Repositories

Testing Disk Cache External Deletion Handling in Glide

This test suite validates the robustness of Glide’s disk cache implementation when the cache directory is externally cleared or deleted. It specifically addresses issue #2465 by testing cache behavior under unexpected directory modifications.

Test Coverage Overview

The test suite covers critical disk cache operations under external directory deletion scenarios.

Key areas tested include:
  • Cache clearing operations after external directory deletion
  • Cache retrieval functionality post-deletion
  • Image loading resilience with compromised cache states
  • Cache initialization and recovery mechanisms

Implementation Analysis

The testing approach employs JUnit4 with AndroidJUnit4 runner for Android-specific testing context. It utilizes mock objects for cache keys and implements systematic test isolation through @Before and @After annotations.

Notable patterns include:
  • Concurrent execution handling via ConcurrencyHelper
  • Systematic resource cleanup
  • Controlled cache directory manipulation

Technical Details

Testing infrastructure includes:
  • JUnit4 test framework
  • Mockito for object mocking
  • DiskLruCacheWrapper for cache implementation
  • Custom TearDownGlide rule for cleanup
  • ApplicationProvider for context management

Best Practices Demonstrated

The test suite exemplifies robust testing practices through proper test isolation, thorough cleanup procedures, and comprehensive error case coverage.

Notable practices include:
  • Systematic resource cleanup after each test
  • Proper test case isolation
  • Explicit error condition testing
  • Comprehensive edge case coverage

bumptech/glide

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

            
package com.bumptech.glide;

import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;

import android.content.Context;
import android.graphics.drawable.Drawable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.cache.DiskCache;
import com.bumptech.glide.load.engine.cache.DiskCache.Factory;
import com.bumptech.glide.load.engine.cache.DiskLruCacheWrapper;
import com.bumptech.glide.test.ResourceIds;
import com.bumptech.glide.test.ResourceIds.raw;
import com.bumptech.glide.testutil.ConcurrencyHelper;
import com.bumptech.glide.testutil.TearDownGlide;
import java.io.File;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

// Tests #2465.
@RunWith(AndroidJUnit4.class)
public class ExternallyClearedDiskCacheTest {
  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();
  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();
  private Context context;
  private File cacheDir;

  @Before
  public void setUp() {
    context = ApplicationProvider.getApplicationContext();
    cacheDir = context.getCacheDir();
  }

  @After
  public void tearDown() {
    // Force us to wait until Glide's threads shut down.
    Glide.tearDown();
    deleteRecursively(cacheDir);
  }

  @Test
  public void clearDiskCache_afterOpeningDiskCache_andDeleteDirectoryOutsideGlide_doesNotThrow() {
    DiskCache cache = DiskLruCacheWrapper.create(cacheDir, 1024 * 1024);
    cache.get(mock(Key.class));
    deleteRecursively(cacheDir);
    cache.clear();
  }

  @Test
  public void get_afterDeleteDirectoryOutsideGlideAndClose_doesNotThrow() {
    DiskCache cache = DiskLruCacheWrapper.create(cacheDir, 1024 * 1024);
    cache.get(mock(Key.class));
    deleteRecursively(cacheDir);
    cache.clear();

    cache.get(mock(Key.class));
  }

  @Test
  public void loadFromCache_afterDiskCacheDeletedAndCleared_doesNotFail() {
    final DiskCache cache = DiskLruCacheWrapper.create(cacheDir, 1024 * 1024);
    cache.get(mock(Key.class));
    deleteRecursively(cacheDir);
    cache.clear();

    Glide.init(
        context,
        new GlideBuilder()
            .setDiskCache(
                new Factory() {
                  @Override
                  public DiskCache build() {
                    return cache;
                  }
                }));

    Drawable drawable =
        concurrency.get(Glide.with(context).load(ResourceIds.raw.canonical).submit());
    assertThat(drawable).isNotNull();
  }

  @Test
  public void loadFromCache_afterDiskCacheDeleted_doesNotFail() {
    final DiskCache cache = DiskLruCacheWrapper.create(cacheDir, 1024 * 1024);
    cache.get(mock(Key.class));
    deleteRecursively(cacheDir);

    Glide.init(
        context,
        new GlideBuilder()
            .setDiskCache(
                new Factory() {
                  @Override
                  public DiskCache build() {
                    return cache;
                  }
                }));

    Drawable drawable = concurrency.get(Glide.with(context).load(raw.canonical).submit());
    assertThat(drawable).isNotNull();
  }

  private static void deleteRecursively(File file) {
    if (file.isDirectory()) {
      File[] files = file.listFiles();
      if (files != null) {
        for (File f : files) {
          deleteRecursively(f);
        }
      }
    }
    if (!file.delete() && file.exists()) {
      throw new RuntimeException("Failed to delete: " + file);
    }
  }
}