Skip to content

Commit

Permalink
Merge pull request #2714 from orionlee/enqueue_keep_inprogress_front_…
Browse files Browse the repository at this point in the history
…2652_respect_download_start_order_2448

Enqueue fixes: keep inprogress front, respect download start order
  • Loading branch information
ByteHamster committed Nov 5, 2019
2 parents 363c361 + 89d7670 commit 808f273
Show file tree
Hide file tree
Showing 21 changed files with 943 additions and 101 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package de.test.antennapod.storage;

import android.content.Context;
import android.content.Intent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;

import org.awaitility.Awaitility;
import org.awaitility.core.ConditionTimeoutException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.List;

import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.DBTasksCallbacks;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.playback.Playable;
import de.test.antennapod.ui.UITestUtils;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class AutoDownloadTest {

private Context context;
private UITestUtils stubFeedsServer;

private DBTasksCallbacks dbTasksCallbacksOrig;

@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();

stubFeedsServer = new UITestUtils(context);;
stubFeedsServer.setup();

dbTasksCallbacksOrig = ClientConfig.dbTasksCallbacks;

// create new database
PodDBAdapter.init(context);
PodDBAdapter.deleteDatabase();
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.close();
}

@After
public void tearDown() throws Exception {
stubFeedsServer.tearDown();

context.sendBroadcast(new Intent(PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
Awaitility.await().until(() -> !PlaybackService.isRunning);

ClientConfig.dbTasksCallbacks = dbTasksCallbacksOrig;
}

/**
* A cross-functional test, ensuring playback's behavior works with Auto Download in boundary condition.
*
* Scenario:
* - For setting enqueue location AFTER_CURRENTLY_PLAYING
* - when playback of an episode is complete and the app advances to the next episode (continuous playback on)
* - when automatic download kicks in,
* - ensure the next episode is the current playing one, needed for AFTER_CURRENTLY_PLAYING enqueue location.
*/
@Test
public void downloadsEnqueuedToAfterCurrent_CurrentAdvancedToNextOnPlaybackComplete() throws Exception {
UserPreferences.setFollowQueue(true); // continuous playback

// Setup: feeds and queue
// downloads 3 of them, leave some in new state (auto-downloadable)
stubFeedsServer.addLocalFeedData(false);
List<FeedItem> queue = DBReader.getQueue();
assertTrue(queue.size() > 1);
FeedItem item0 = queue.get(0);
FeedItem item1 = queue.get(1);

// Setup: enable automatic download
// it is not needed, as the actual automatic download is stubbed.
StubDownloadAlgorithm stubDownloadAlgorithm = new StubDownloadAlgorithm();
useDownloadAlgorithm(stubDownloadAlgorithm);

// Actual test
// Play the first one in the queue
playEpisode(item0);

try {
// when playback is complete, advances to the next one, and auto download kicks in,
// ensure that currently playing has been advanced to the next one by this point.
Awaitility.await("advanced to the next episode")
.atMost(6000, MILLISECONDS) // the test mp3 media is 3-second long. twice should be enough
.until(() -> item1.equals(stubDownloadAlgorithm.getCurrentlyPlayingAtDownload()));
} catch (ConditionTimeoutException cte) {
FeedItem actual = stubDownloadAlgorithm.getCurrentlyPlayingAtDownload();
fail("when auto download is triggered, the next episode should be playing: ("
+ item1.getId() + ", " + item1.getTitle() + ") . "
+ "Actual playing: ("
+ (actual == null ? "" : actual.getId() + ", " + actual.getTitle()) + ")"
);
}

}

private void playEpisode(@NonNull FeedItem item) {
FeedMedia media = item.getMedia();
DBTasks.playMedia(context, media, false, true, true);
Awaitility.await("episode is playing")
.atMost(1000, MILLISECONDS)
.until(() -> item.equals(getCurrentlyPlaying()));
}

private FeedItem getCurrentlyPlaying() {
Playable playable = Playable.PlayableUtils.createInstanceFromPreferences(context);
if (playable == null) {
return null;
}
return ((FeedMedia)playable).getItem();
}

private void useDownloadAlgorithm(final AutomaticDownloadAlgorithm downloadAlgorithm) {
DBTasksCallbacks dbTasksCallbacksStub = new DBTasksCallbacks() {
@Override
public AutomaticDownloadAlgorithm getAutomaticDownloadAlgorithm() {
return downloadAlgorithm;
}

@Override
public EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm() {
return dbTasksCallbacksOrig.getEpisodeCacheCleanupAlgorithm();
}
};
ClientConfig.dbTasksCallbacks = dbTasksCallbacksStub;
}

private class StubDownloadAlgorithm implements AutomaticDownloadAlgorithm {
@Nullable
private FeedItem currentlyPlaying;

@Override
public Runnable autoDownloadUndownloadedItems(Context context) {
return () -> {
if (currentlyPlaying == null) {
currentlyPlaying = getCurrentlyPlaying();
} else {
throw new AssertionError("Stub automatic download should be invoked once and only once");
}
};
}

@Nullable
FeedItem getCurrentlyPlayingAtDownload() {
return currentlyPlaying;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,31 @@

import android.content.Context;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import static de.danoeh.antennapod.core.util.FeedItemUtil.getIdList;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
Expand Down Expand Up @@ -179,4 +184,82 @@ private void updatedFeedTest(final Feed newFeed, long feedID, List<Long> itemIDs
lastDate = item.getPubDate();
}
}

@Test
public void testAddQueueItemsInDownload_EnqueueEnabled() throws Exception {
// Setup test data / environment
UserPreferences.setEnqueueDownloadedEpisodes(true);
UserPreferences.setEnqueueLocation(UserPreferences.EnqueueLocation.BACK);

List<FeedItem> fis1 = createSavedFeed("Feed 1", 2).getItems();
List<FeedItem> fis2 = createSavedFeed("Feed 2", 3).getItems();

DBWriter.addQueueItem(context, fis1.get(0), fis2.get(0)).get();
// the first item fis1.get(0) is already in the queue
FeedItem[] itemsToDownload = new FeedItem[]{ fis1.get(0), fis1.get(1), fis2.get(2), fis2.get(1) };

// Expectations:
List<FeedItem> expectedEnqueued = Arrays.asList(fis1.get(1), fis2.get(2), fis2.get(1));
List<FeedItem> expectedQueue = new ArrayList<>();
expectedQueue.addAll(DBReader.getQueue());
expectedQueue.addAll(expectedEnqueued);

// Run actual test and assert results
List<? extends FeedItem> actualEnqueued =
DBTasks.enqueueFeedItemsToDownload(context, itemsToDownload);

assertEqualsByIds("Only items not in the queue are enqueued", expectedEnqueued, actualEnqueued);
assertEqualsByIds("Queue has new items appended", expectedQueue, DBReader.getQueue());
}

@Test
public void testAddQueueItemsInDownload_EnqueueDisabled() throws Exception {
// Setup test data / environment
UserPreferences.setEnqueueDownloadedEpisodes(false);

List<FeedItem> fis1 = createSavedFeed("Feed 1", 2).getItems();
List<FeedItem> fis2 = createSavedFeed("Feed 2", 3).getItems();

DBWriter.addQueueItem(context, fis1.get(0), fis2.get(0)).get();
FeedItem[] itemsToDownload = new FeedItem[]{ fis1.get(0), fis1.get(1), fis2.get(2), fis2.get(1) };

// Expectations:
List<FeedItem> expectedEnqueued = Collections.emptyList();
List<FeedItem> expectedQueue = DBReader.getQueue();

// Run actual test and assert results
List<? extends FeedItem> actualEnqueued =
DBTasks.enqueueFeedItemsToDownload(context, itemsToDownload);

assertEqualsByIds("No item is enqueued", expectedEnqueued, actualEnqueued);
assertEqualsByIds("Queue is unchanged", expectedQueue, DBReader.getQueue());
}

private void assertEqualsByIds(String msg, List<? extends FeedItem> expected, List<? extends FeedItem> actual) {
// assert only the IDs, so that any differences are easily to spot.
List<Long> expectedIds = getIdList(expected);
List<Long> actualIds = getIdList(actual);
assertEquals(msg, expectedIds, actualIds);
}

private Feed createSavedFeed(String title, int numFeedItems) {
final Feed feed = new Feed("url", null, title);

if (numFeedItems > 0) {
List<FeedItem> items = new ArrayList<>(numFeedItems);
for (int i = 1; i <= numFeedItems; i++) {
FeedItem item = new FeedItem(0, "item " + i + " of " + title, "id", "link",
new Date(), FeedItem.UNPLAYED, feed);
items.add(item);
}
feed.setItems(items);
}

PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setCompleteFeed(feed);
adapter.close();
return feed;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
import android.content.SharedPreferences;
import android.database.Cursor;
import android.preference.PreferenceManager;
import android.util.Log;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import android.util.Log;

import org.awaitility.Awaitility;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
Expand All @@ -29,9 +32,7 @@
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.Consumer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import de.danoeh.antennapod.core.util.FeedItemUtil;

import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
Expand Down Expand Up @@ -475,11 +476,12 @@ public void testAddItemToPlaybackHistoryAlreadyPlayed()
assertFalse(OLD_DATE == media.getPlaybackCompletionDate().getTime());
}

private Feed queueTestSetupMultipleItems(final int NUM_ITEMS) throws InterruptedException, ExecutionException, TimeoutException {
private Feed queueTestSetupMultipleItems(final int numItems) throws InterruptedException, ExecutionException, TimeoutException {
final Context context = getInstrumentation().getTargetContext();
UserPreferences.setEnqueueLocation(UserPreferences.EnqueueLocation.BACK);
Feed feed = new Feed("url", null, "title");
feed.setItems(new ArrayList<>());
for (int i = 0; i < NUM_ITEMS; i++) {
for (int i = 0; i < numItems; i++) {
FeedItem item = new FeedItem(0, "title " + i, "id " + i, "link " + i, new Date(), FeedItem.PLAYED, feed);
feed.getItems().add(item);
}
Expand Down Expand Up @@ -573,12 +575,16 @@ public void testAddQueueItemMultipleItems() throws InterruptedException, Executi
Cursor cursor = adapter.getQueueIDCursor();
assertTrue(cursor.moveToFirst());
assertTrue(cursor.getCount() == NUM_ITEMS);
List<Long> expectedIds = FeedItemUtil.getIdList(feed.getItems());
List<Long> actualIds = new ArrayList<>();
for (int i = 0; i < NUM_ITEMS; i++) {
assertTrue(cursor.moveToPosition(i));
assertTrue(cursor.getLong(0) == feed.getItems().get(i).getId());
actualIds.add(cursor.getLong(0));
}
cursor.close();
adapter.close();
assertEquals("Bulk add to queue: result order should be the same as the order given",
expectedIds, actualIds);
}

@Test
Expand Down
Loading

0 comments on commit 808f273

Please sign in to comment.