Back to Repositories

Validating RequestManager Lifecycle Operations in Glide

This test suite validates RequestManager functionality in Glide, focusing on resource cleanup and thread safety. It ensures proper handling of image loading requests and resource management across different Android lifecycle states.

Test Coverage Overview

The test suite covers critical RequestManager operations in Glide, particularly focusing on resource cleanup scenarios.

Key areas tested include:
  • Request cleanup after RequestManager destruction
  • Background thread handling for clear operations
  • Cross-thread request management
  • Lifecycle-aware resource management

Implementation Analysis

The testing approach utilizes AndroidJUnit4 with Mockito for dependency isolation. The implementation leverages ConcurrencyHelper for thread management and custom lifecycle simulation.

Notable patterns include:
  • Mock lifecycle implementation for controlled testing
  • Concurrent operation validation
  • Resource cleanup verification

Technical Details

Testing infrastructure includes:
  • AndroidJUnit4 test runner
  • Mockito for mocking dependencies
  • TearDownGlide rule for cleanup
  • ConcurrencyHelper for thread management
  • Custom RequestManagerTreeNode implementation

Best Practices Demonstrated

The test suite exemplifies robust testing practices for Android image loading libraries.

Key practices include:
  • Proper test isolation using @Before setup
  • Comprehensive thread safety validation
  • Explicit lifecycle management
  • Edge case coverage for resource cleanup
  • Clear test case documentation with issue references

bumptech/glide

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

            
package com.bumptech.glide;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.bumptech.glide.manager.Lifecycle;
import com.bumptech.glide.manager.LifecycleListener;
import com.bumptech.glide.manager.RequestManagerTreeNode;
import com.bumptech.glide.request.target.Target;
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 org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@RunWith(AndroidJUnit4.class)
public class RequestManagerTest {
  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();
  @Mock private RequestManagerTreeNode treeNode;

  private final ConcurrencyHelper concurrency = new ConcurrencyHelper();
  private RequestManager requestManager;
  private Context context;

  @Before
  public void setUp() {
    MockitoAnnotations.initMocks(this);
    context = ApplicationProvider.getApplicationContext();
    Glide glide = Glide.get(context);
    requestManager =
        new RequestManager(
            glide,
            new Lifecycle() {
              @Override
              public void addListener(@NonNull LifecycleListener listener) {
                listener.onStart();
              }

              @Override
              public void removeListener(@NonNull LifecycleListener listener) {
                // Do nothing.
              }
            },
            treeNode,
            context);
  }

  /** Tests #2262. */
  @Test
  public void clear_withNonOwningRequestManager_afterOwningManagerIsDestroyed_doesNotThrow() {
    // First destroy our Fragment/Activity RequestManager.
    requestManager.onDestroy();

    final ImageView imageView = new ImageView(context);
    imageView.measure(100, 100);
    imageView.layout(0, 0, 100, 100);
    // Then start a new load with our now destroyed RequestManager.
    concurrency.loadOnMainThread(requestManager.load(ResourceIds.raw.canonical), imageView);

    // Finally clear our new load with any RequestManager other than the one we used to start it.
    concurrency.runOnMainThread(
        new Runnable() {
          @Override
          public void run() {
            Glide.with(context).clear(imageView);
          }
        });
  }

  /** Tests b/69361054. */
  @Test
  public void clear_withNonOwningRequestManager_onBackgroundThread_doesNotThrow() {
    concurrency.runOnMainThread(
        new Runnable() {
          @Override
          public void run() {
            requestManager.onDestroy();
          }
        });

    final Target<Drawable> target = concurrency.wait(requestManager.load(raw.canonical).submit());

    concurrency.runOnMainThread(
        new Runnable() {
          @Override
          public void run() {
            Glide.with(context).clear(target);
          }
        });
  }
}