Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enqueue fixes: 1) option to enqueue after currently playing 2) respect download start order #2714

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ba27ec6
refactor - DBWriter.addQueueItem() : refactor enqueue position calcul…
orionlee May 18, 2018
bfde3c7
refactor - DBWriterTest: parametrize the set of tests
orionlee May 18, 2018
30f104f
#2652 (part of): The in-progress podcast at the front of the queue
orionlee May 19, 2018
17e6133
#2652 (part of): Expose keep in-progress at front as a preference
orionlee May 19, 2018
0973efa
refactor test - break ItemEnqueuePositionCalculatorTest to be more mo…
orionlee May 23, 2018
97905e5
#2448: make podcast episode enqueue position respect download start o…
orionlee May 23, 2018
fb824b5
Test cases readability: change expected format from position to the a…
orionlee May 23, 2018
ce5aa26
refactoring test - factor out common operations of calc position,
orionlee May 24, 2018
820b0b0
test case bug fix: Bulk download 2nd item position should be 1
orionlee May 28, 2018
fb7fb05
test case tweak: preserve download order test, fix test case name
orionlee May 28, 2018
2d1ee52
fix imports post androidX migration
orionlee Oct 4, 2019
cd3d20d
refactor - move ItemEnqueuePositionCalculator to top-level per review.
orionlee Oct 4, 2019
2f82a5d
refactor - rename FeedFileDownloadStatusRequesterInterface to a more …
orionlee Oct 4, 2019
fb6fa01
Enqueue tweaks - replace custom stub DownloadStateProvider with mocki…
orionlee Oct 4, 2019
418d4fa
bugfix respect download order - obey user settings "Enqueue Downloaded"
orionlee Oct 11, 2019
69c0022
code style fixes - naming, indentation, etc.
orionlee Oct 4, 2019
d24669d
refactor extract common FeedItem List to IDs method
orionlee Oct 27, 2019
406f1cc
refactor move generic Collection helpers to CollectionTestUtil
orionlee Oct 27, 2019
52521ec
#2652 the UI of a new setting enqueue location
orionlee Oct 28, 2019
bddd2bf
enqueue location: use the new 3-value settings
orionlee Oct 28, 2019
e233398
code style fixes: naming, indentation.
orionlee Oct 29, 2019
52f6a12
AFTER_CURRENTLY_PLAYING enqueue location option - test boundary condi…
orionlee Oct 29, 2019
f8fbc8e
test fix: ensure test is not dependent on UserPreferences's enqueueLo…
orionlee Oct 31, 2019
b80973b
refactor - make enqueue position logic more readable per review.
orionlee Oct 31, 2019
6e019f7
code style / comment tweak per review
orionlee Nov 5, 2019
9d6db7b
enqueue respect download order: add test case for download failures.
orionlee Nov 5, 2019
9e8904b
code style fixes
orionlee Nov 5, 2019
89d7670
code style - reduce nested ifs
orionlee Nov 5, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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