Back to Repositories

Testing Download Index Management in SmartTube ExoPlayer Implementation

This test suite validates the functionality of DefaultDownloadIndex in ExoPlayer, focusing on download tracking and management capabilities. The suite thoroughly tests database operations, state management, and data persistence for media downloads.

Test Coverage Overview

The test suite provides comprehensive coverage of DefaultDownloadIndex functionality:

  • Download CRUD operations (create, read, update, delete)
  • Database state persistence and version management
  • Download status tracking and state transitions
  • Stop reason handling and updates
  • Cursor-based download listing and filtering

Implementation Analysis

The testing approach utilizes JUnit4 with AndroidJUnit4 runner for Android-specific testing. Tests are structured around database operations using SQLite, with careful setup and teardown procedures to ensure test isolation.

Key patterns include builder pattern for test data creation, systematic state verification, and comprehensive equality checking.

Technical Details

Testing tools and configuration:

  • JUnit4 testing framework
  • AndroidJUnit4 test runner
  • SQLiteDatabase for storage operations
  • ExoDatabaseProvider for database management
  • Truth assertion library for verifications
  • ApplicationProvider for context management

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Proper test isolation with @Before and @After hooks
  • Comprehensive edge case coverage
  • Systematic resource cleanup
  • Clear test naming conventions
  • Focused test methods with single responsibilities
  • Helper methods for common assertions

yuliskov/smarttube

exoplayer-amzn-2.10.6/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloadIndexTest.java

            
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.android.exoplayer2.offline;

import static com.google.common.truth.Truth.assertThat;

import android.database.sqlite.SQLiteDatabase;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.database.DatabaseIOException;
import com.google.android.exoplayer2.database.ExoDatabaseProvider;
import com.google.android.exoplayer2.database.VersionTable;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

/** Unit tests for {@link DefaultDownloadIndex}. */
@RunWith(AndroidJUnit4.class)
public class DefaultDownloadIndexTest {

  private static final String EMPTY_NAME = "";

  private ExoDatabaseProvider databaseProvider;
  private DefaultDownloadIndex downloadIndex;

  @Before
  public void setUp() {
    databaseProvider = new ExoDatabaseProvider(ApplicationProvider.getApplicationContext());
    downloadIndex = new DefaultDownloadIndex(databaseProvider);
  }

  @After
  public void tearDown() {
    databaseProvider.close();
  }

  @Test
  public void getDownload_nonExistingId_returnsNull() throws DatabaseIOException {
    assertThat(downloadIndex.getDownload("non existing id")).isNull();
  }

  @Test
  public void addAndGetDownload_nonExistingId_returnsTheSameDownload() throws DatabaseIOException {
    String id = "id";
    Download download = new DownloadBuilder(id).build();

    downloadIndex.putDownload(download);
    Download readDownload = downloadIndex.getDownload(id);

    assertEqual(readDownload, download);
  }

  @Test
  public void addAndGetDownload_existingId_returnsUpdatedDownload() throws DatabaseIOException {
    String id = "id";
    DownloadBuilder downloadBuilder = new DownloadBuilder(id);
    downloadIndex.putDownload(downloadBuilder.build());

    Download download =
        downloadBuilder
            .setType("different type")
            .setUri("different uri")
            .setCacheKey("different cacheKey")
            .setState(Download.STATE_FAILED)
            .setPercentDownloaded(50)
            .setBytesDownloaded(200)
            .setContentLength(400)
            .setFailureReason(Download.FAILURE_REASON_UNKNOWN)
            .setStopReason(0x12345678)
            .setStartTimeMs(10)
            .setUpdateTimeMs(20)
            .setStreamKeys(
                new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 1, /* trackIndex= */ 2),
                new StreamKey(/* periodIndex= */ 3, /* groupIndex= */ 4, /* trackIndex= */ 5))
            .setCustomMetadata(new byte[] {0, 1, 2, 3, 7, 8, 9, 10})
            .build();
    downloadIndex.putDownload(download);
    Download readDownload = downloadIndex.getDownload(id);

    assertThat(readDownload).isNotNull();
    assertEqual(readDownload, download);
  }

  @Test
  public void releaseAndRecreateDownloadIndex_returnsTheSameDownload() throws DatabaseIOException {
    String id = "id";
    Download download = new DownloadBuilder(id).build();
    downloadIndex.putDownload(download);

    downloadIndex = new DefaultDownloadIndex(databaseProvider);
    Download readDownload = downloadIndex.getDownload(id);
    assertThat(readDownload).isNotNull();
    assertEqual(readDownload, download);
  }

  @Test
  public void removeDownload_nonExistingId_doesNotFail() throws DatabaseIOException {
    downloadIndex.removeDownload("non existing id");
  }

  @Test
  public void removeDownload_existingId_getDownloadReturnsNull() throws DatabaseIOException {
    String id = "id";
    Download download = new DownloadBuilder(id).build();
    downloadIndex.putDownload(download);
    downloadIndex.removeDownload(id);

    Download readDownload = downloadIndex.getDownload(id);
    assertThat(readDownload).isNull();
  }

  @Test
  public void getDownloads_emptyDownloadIndex_returnsEmptyArray() throws DatabaseIOException {
    assertThat(downloadIndex.getDownloads().getCount()).isEqualTo(0);
  }

  @Test
  public void getDownloads_noState_returnsAllDownloadStatusSortedByStartTime()
      throws DatabaseIOException {
    Download download1 = new DownloadBuilder("id1").setStartTimeMs(1).build();
    downloadIndex.putDownload(download1);
    Download download2 = new DownloadBuilder("id2").setStartTimeMs(0).build();
    downloadIndex.putDownload(download2);

    try (DownloadCursor cursor = downloadIndex.getDownloads()) {
      assertThat(cursor.getCount()).isEqualTo(2);
      cursor.moveToNext();
      assertEqual(cursor.getDownload(), download2);
      cursor.moveToNext();
      assertEqual(cursor.getDownload(), download1);
    }
  }

  @Test
  public void getDownloads_withStates_returnsAllDownloadStatusWithTheSameStates()
      throws DatabaseIOException {
    Download download1 =
        new DownloadBuilder("id1").setStartTimeMs(0).setState(Download.STATE_REMOVING).build();
    downloadIndex.putDownload(download1);
    Download download2 =
        new DownloadBuilder("id2").setStartTimeMs(1).setState(Download.STATE_STOPPED).build();
    downloadIndex.putDownload(download2);
    Download download3 =
        new DownloadBuilder("id3").setStartTimeMs(2).setState(Download.STATE_COMPLETED).build();
    downloadIndex.putDownload(download3);

    try (DownloadCursor cursor =
        downloadIndex.getDownloads(Download.STATE_REMOVING, Download.STATE_COMPLETED)) {
      assertThat(cursor.getCount()).isEqualTo(2);
      cursor.moveToNext();
      assertEqual(cursor.getDownload(), download1);
      cursor.moveToNext();
      assertEqual(cursor.getDownload(), download3);
    }
  }

  @Test
  public void putDownload_setsVersion() throws DatabaseIOException {
    SQLiteDatabase readableDatabase = databaseProvider.getReadableDatabase();
    assertThat(VersionTable.getVersion(readableDatabase, VersionTable.FEATURE_OFFLINE, EMPTY_NAME))
        .isEqualTo(VersionTable.VERSION_UNSET);

    downloadIndex.putDownload(new DownloadBuilder("id1").build());

    assertThat(VersionTable.getVersion(readableDatabase, VersionTable.FEATURE_OFFLINE, EMPTY_NAME))
        .isEqualTo(DefaultDownloadIndex.TABLE_VERSION);
  }

  @Test
  public void downloadIndex_versionDowngradeWipesData() throws DatabaseIOException {
    Download download1 = new DownloadBuilder("id1").build();
    downloadIndex.putDownload(download1);
    DownloadCursor cursor = downloadIndex.getDownloads();
    assertThat(cursor.getCount()).isEqualTo(1);
    cursor.close();

    SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
    VersionTable.setVersion(
        writableDatabase, VersionTable.FEATURE_OFFLINE, EMPTY_NAME, Integer.MAX_VALUE);

    downloadIndex = new DefaultDownloadIndex(databaseProvider);

    cursor = downloadIndex.getDownloads();
    assertThat(cursor.getCount()).isEqualTo(0);
    cursor.close();
    assertThat(VersionTable.getVersion(writableDatabase, VersionTable.FEATURE_OFFLINE, EMPTY_NAME))
        .isEqualTo(DefaultDownloadIndex.TABLE_VERSION);
  }

  @Test
  public void setStopReason_setReasonToNone() throws Exception {
    String id = "id";
    DownloadBuilder downloadBuilder =
        new DownloadBuilder(id).setState(Download.STATE_COMPLETED).setStopReason(0x12345678);
    Download download = downloadBuilder.build();
    downloadIndex.putDownload(download);

    downloadIndex.setStopReason(Download.STOP_REASON_NONE);

    Download readDownload = downloadIndex.getDownload(id);
    Download expectedDownload = downloadBuilder.setStopReason(Download.STOP_REASON_NONE).build();
    assertEqual(readDownload, expectedDownload);
  }

  @Test
  public void setStopReason_setReason() throws Exception {
    String id = "id";
    DownloadBuilder downloadBuilder =
        new DownloadBuilder(id)
            .setState(Download.STATE_FAILED)
            .setFailureReason(Download.FAILURE_REASON_UNKNOWN);
    Download download = downloadBuilder.build();
    downloadIndex.putDownload(download);
    int stopReason = 0x12345678;

    downloadIndex.setStopReason(stopReason);

    Download readDownload = downloadIndex.getDownload(id);
    Download expectedDownload = downloadBuilder.setStopReason(stopReason).build();
    assertEqual(readDownload, expectedDownload);
  }

  @Test
  public void setStopReason_notTerminalState_doesNotSetStopReason() throws Exception {
    String id = "id";
    DownloadBuilder downloadBuilder = new DownloadBuilder(id).setState(Download.STATE_DOWNLOADING);
    Download download = downloadBuilder.build();
    downloadIndex.putDownload(download);
    int notMetRequirements = 0x12345678;

    downloadIndex.setStopReason(notMetRequirements);

    Download readDownload = downloadIndex.getDownload(id);
    assertEqual(readDownload, download);
  }

  @Test
  public void setSingleDownloadStopReason_setReasonToNone() throws Exception {
    String id = "id";
    DownloadBuilder downloadBuilder =
        new DownloadBuilder(id).setState(Download.STATE_COMPLETED).setStopReason(0x12345678);
    Download download = downloadBuilder.build();
    downloadIndex.putDownload(download);

    downloadIndex.setStopReason(id, Download.STOP_REASON_NONE);

    Download readDownload = downloadIndex.getDownload(id);
    Download expectedDownload = downloadBuilder.setStopReason(Download.STOP_REASON_NONE).build();
    assertEqual(readDownload, expectedDownload);
  }

  @Test
  public void setSingleDownloadStopReason_setReason() throws Exception {
    String id = "id";
    DownloadBuilder downloadBuilder =
        new DownloadBuilder(id)
            .setState(Download.STATE_FAILED)
            .setFailureReason(Download.FAILURE_REASON_UNKNOWN);
    Download download = downloadBuilder.build();
    downloadIndex.putDownload(download);
    int stopReason = 0x12345678;

    downloadIndex.setStopReason(id, stopReason);

    Download readDownload = downloadIndex.getDownload(id);
    Download expectedDownload = downloadBuilder.setStopReason(stopReason).build();
    assertEqual(readDownload, expectedDownload);
  }

  @Test
  public void setSingleDownloadStopReason_notTerminalState_doesNotSetStopReason() throws Exception {
    String id = "id";
    DownloadBuilder downloadBuilder = new DownloadBuilder(id).setState(Download.STATE_DOWNLOADING);
    Download download = downloadBuilder.build();
    downloadIndex.putDownload(download);
    int notMetRequirements = 0x12345678;

    downloadIndex.setStopReason(id, notMetRequirements);

    Download readDownload = downloadIndex.getDownload(id);
    assertEqual(readDownload, download);
  }

  private static void assertEqual(Download download, Download that) {
    assertThat(download.request).isEqualTo(that.request);
    assertThat(download.state).isEqualTo(that.state);
    assertThat(download.startTimeMs).isEqualTo(that.startTimeMs);
    assertThat(download.updateTimeMs).isEqualTo(that.updateTimeMs);
    assertThat(download.contentLength).isEqualTo(that.contentLength);
    assertThat(download.stopReason).isEqualTo(that.stopReason);
    assertThat(download.failureReason).isEqualTo(that.failureReason);
    assertThat(download.getPercentDownloaded()).isEqualTo(that.getPercentDownloaded());
    assertThat(download.getBytesDownloaded()).isEqualTo(that.getBytesDownloaded());
  }
}