From c29b0ce8c760b75b4c61781acaf56542530ca3cc Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Sun, 11 Aug 2019 21:21:28 +0200 Subject: Fixed app integration tests --- .../java/de/test/antennapod/EspressoTestUtils.java | 102 ++ .../antennapod/entities/ExternalMediaTest.java | 19 +- .../de/test/antennapod/feed/FeedFilterTest.java | 16 +- .../java/de/test/antennapod/feed/FeedItemTest.java | 11 +- .../test/antennapod/handler/FeedHandlerTest.java | 26 +- .../service/download/HttpDownloaderTest.java | 37 +- .../service/playback/CancelablePSMPCallback.java | 124 ++ .../service/playback/DefaultPSMPCallback.java | 74 + .../playback/PlaybackServiceMediaPlayerTest.java | 203 ++- .../playback/PlaybackServiceTaskManagerTest.java | 8 +- .../de/test/antennapod/storage/DBCleanupTests.java | 49 +- .../storage/DBNullCleanupAlgorithmTest.java | 29 +- .../storage/DBQueueCleanupAlgorithmTest.java | 12 +- .../de/test/antennapod/storage/DBReaderTest.java | 50 +- .../de/test/antennapod/storage/DBTasksTest.java | 38 +- .../de/test/antennapod/storage/DBTestUtils.java | 8 +- .../de/test/antennapod/storage/DBWriterTest.java | 54 +- .../de/test/antennapod/ui/MainActivityTest.java | 242 +--- .../test/antennapod/ui/NavigationDrawerTest.java | 253 ++++ .../de/test/antennapod/ui/PlaybackSonicTest.java | 52 +- .../java/de/test/antennapod/ui/PlaybackTest.java | 52 +- .../de/test/antennapod/ui/PreferencesTest.java | 294 ++-- .../java/de/test/antennapod/ui/UITestUtils.java | 7 +- .../de/test/antennapod/ui/UITestUtilsTest.java | 31 +- .../antennapod/ui/VideoplayerActivityTest.java | 42 +- .../antennapod/util/FilenameGeneratorTest.java | 29 +- .../de/test/antennapod/util/URLCheckerTest.java | 25 +- .../antennapod/util/playback/TimelineTest.java | 32 +- .../antennapod/util/service/download/HTTPBin.java | 1 + .../util/service/download/NanoHTTPD.java | 1433 -------------------- .../util/syndication/FeedDiscovererTest.java | 30 +- 31 files changed, 1258 insertions(+), 2125 deletions(-) create mode 100644 app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java create mode 100644 app/src/androidTest/java/de/test/antennapod/service/playback/CancelablePSMPCallback.java create mode 100644 app/src/androidTest/java/de/test/antennapod/service/playback/DefaultPSMPCallback.java create mode 100644 app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java delete mode 100644 app/src/androidTest/java/de/test/antennapod/util/service/download/NanoHTTPD.java (limited to 'app/src/androidTest/java/de/test/antennapod') diff --git a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java new file mode 100644 index 000000000..50109c71a --- /dev/null +++ b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java @@ -0,0 +1,102 @@ +package de.test.antennapod; + +import android.content.Context; +import android.support.annotation.StringRes; +import android.support.test.InstrumentationRegistry; +import android.support.test.espresso.PerformException; +import android.support.test.espresso.UiController; +import android.support.test.espresso.ViewAction; +import android.support.test.espresso.contrib.DrawerActions; +import android.support.test.espresso.contrib.RecyclerViewActions; +import android.support.test.espresso.util.HumanReadables; +import android.support.test.espresso.util.TreeIterables; +import android.view.View; +import de.danoeh.antennapod.R; +import org.hamcrest.Matcher; + +import java.io.File; +import java.util.concurrent.TimeoutException; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; +import static android.support.test.espresso.matcher.ViewMatchers.isRoot; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; + +public class EspressoTestUtils { + /** + * Perform action of waiting for a specific view id. + * https://stackoverflow.com/a/49814995/ + * @param viewMatcher The view to wait for. + * @param millis The timeout of until when to wait for. + */ + public static ViewAction waitForView(final Matcher viewMatcher, final long millis) { + return new ViewAction() { + @Override + public Matcher getConstraints() { + return isRoot(); + } + + @Override + public String getDescription() { + return "wait for a specific view for" + millis + " millis."; + } + + @Override + public void perform(final UiController uiController, final View view) { + uiController.loopMainThreadUntilIdle(); + final long startTime = System.currentTimeMillis(); + final long endTime = startTime + millis; + + do { + for (View child : TreeIterables.breadthFirstViewTraversal(view)) { + // found view with required ID + if (viewMatcher.matches(child)) { + return; + } + } + + uiController.loopMainThreadForAtLeast(50); + } + while (System.currentTimeMillis() < endTime); + + // timeout happens + throw new PerformException.Builder() + .withActionDescription(this.getDescription()) + .withViewDescription(HumanReadables.describe(view)) + .withCause(new TimeoutException()) + .build(); + } + }; + } + + /** + * Clear all app databases + */ + public static void clearAppData() { + File root = InstrumentationRegistry.getTargetContext().getFilesDir().getParentFile(); + String[] sharedPreferencesFileNames = new File(root, "shared_prefs").list(); + for (String fileName : sharedPreferencesFileNames) { + System.out.println("Cleared database: " + fileName); + InstrumentationRegistry.getTargetContext(). + getSharedPreferences(fileName.replace(".xml", ""), Context.MODE_PRIVATE).edit().clear().commit(); + } + } + + public static void clickPreference(@StringRes int title) { + onView(withId(R.id.list)).perform( + RecyclerViewActions.actionOnItem(hasDescendant(withText(title)), + click())); + } + + public static void openNavDrawer() { + onView(isRoot()).perform(waitForView(withId(R.id.drawer_layout), 1000)); + onView(withId(R.id.drawer_layout)).perform(DrawerActions.open()); + } + + public static void closeNavDrawer() { + onView(isRoot()).perform(waitForView(withId(R.id.drawer_layout), 1000)); + onView(withId(R.id.drawer_layout)).perform(DrawerActions.close()); + } +} diff --git a/app/src/androidTest/java/de/test/antennapod/entities/ExternalMediaTest.java b/app/src/androidTest/java/de/test/antennapod/entities/ExternalMediaTest.java index 80dded59f..e8a87ecf1 100644 --- a/app/src/androidTest/java/de/test/antennapod/entities/ExternalMediaTest.java +++ b/app/src/androidTest/java/de/test/antennapod/entities/ExternalMediaTest.java @@ -3,21 +3,27 @@ package de.test.antennapod.entities; import android.annotation.SuppressLint; import android.content.SharedPreferences; import android.preference.PreferenceManager; -import android.test.InstrumentationTestCase; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.util.playback.ExternalMedia; +import org.junit.After; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; /** * Tests for {@link ExternalMedia} entity. */ -public class ExternalMediaTest extends InstrumentationTestCase { +@SmallTest +public class ExternalMediaTest { private static final int NOT_SET = -1; - @Override - protected void tearDown() throws Exception { - super.tearDown(); + @After + public void tearDown() throws Exception { clearSharedPrefs(); } @@ -30,9 +36,10 @@ public class ExternalMediaTest extends InstrumentationTestCase { } private SharedPreferences getDefaultSharedPrefs() { - return PreferenceManager.getDefaultSharedPreferences(getInstrumentation().getTargetContext()); + return PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getTargetContext()); } + @Test public void testSaveCurrentPositionUpdatesPreferences() { final int POSITION = 50; final int LAST_PLAYED_TIME = 1650; diff --git a/app/src/androidTest/java/de/test/antennapod/feed/FeedFilterTest.java b/app/src/androidTest/java/de/test/antennapod/feed/FeedFilterTest.java index 23a05ac8c..8cd9b0fa1 100644 --- a/app/src/androidTest/java/de/test/antennapod/feed/FeedFilterTest.java +++ b/app/src/androidTest/java/de/test/antennapod/feed/FeedFilterTest.java @@ -1,12 +1,17 @@ package de.test.antennapod.feed; -import android.test.AndroidTestCase; - +import android.support.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.FeedFilter; import de.danoeh.antennapod.core.feed.FeedItem; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; -public class FeedFilterTest extends AndroidTestCase { +@SmallTest +public class FeedFilterTest { + @Test public void testNullFilter() throws Exception { FeedFilter filter = new FeedFilter(); FeedItem item = new FeedItem(); @@ -19,6 +24,7 @@ public class FeedFilterTest extends AndroidTestCase { assertTrue(filter.shouldAutoDownload(item)); } + @Test public void testBasicIncludeFilter() throws Exception { String includeFilter = "Hello"; FeedFilter filter = new FeedFilter(includeFilter, ""); @@ -36,6 +42,7 @@ public class FeedFilterTest extends AndroidTestCase { assertTrue(!filter.shouldAutoDownload(item2)); } + @Test public void testBasicExcludeFilter() throws Exception { String excludeFilter = "Hello"; FeedFilter filter = new FeedFilter("", excludeFilter); @@ -53,6 +60,7 @@ public class FeedFilterTest extends AndroidTestCase { assertTrue(filter.shouldAutoDownload(item2)); } + @Test public void testComplexIncludeFilter() throws Exception { String includeFilter = "Hello \n\"Two words\""; FeedFilter filter = new FeedFilter(includeFilter, ""); @@ -74,6 +82,7 @@ public class FeedFilterTest extends AndroidTestCase { assertTrue(filter.shouldAutoDownload(item3)); } + @Test public void testComplexExcludeFilter() throws Exception { String excludeFilter = "Hello \"Two words\""; FeedFilter filter = new FeedFilter("", excludeFilter); @@ -95,6 +104,7 @@ public class FeedFilterTest extends AndroidTestCase { assertTrue(!filter.shouldAutoDownload(item3)); } + @Test public void testComboFilter() throws Exception { String includeFilter = "Hello world"; String excludeFilter = "dislike"; diff --git a/app/src/androidTest/java/de/test/antennapod/feed/FeedItemTest.java b/app/src/androidTest/java/de/test/antennapod/feed/FeedItemTest.java index ced0d7a28..9779b32a5 100644 --- a/app/src/androidTest/java/de/test/antennapod/feed/FeedItemTest.java +++ b/app/src/androidTest/java/de/test/antennapod/feed/FeedItemTest.java @@ -1,16 +1,20 @@ package de.test.antennapod.feed; -import android.test.AndroidTestCase; - +import android.support.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.FeedItem; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; -public class FeedItemTest extends AndroidTestCase { +@SmallTest +public class FeedItemTest { private static final String TEXT_LONG = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; private static final String TEXT_SHORT = "Lorem ipsum"; /** * If one of `description` or `content:encoded` is null, use the other one. */ + @Test public void testShownotesNullValues() throws Exception { testShownotes(null, TEXT_LONG); testShownotes(TEXT_LONG, null); @@ -19,6 +23,7 @@ public class FeedItemTest extends AndroidTestCase { /** * If `description` is reasonably longer than `content:encoded`, use `description`. */ + @Test public void testShownotesLength() throws Exception { testShownotes(TEXT_SHORT, TEXT_LONG); testShownotes(TEXT_LONG, TEXT_SHORT); diff --git a/app/src/androidTest/java/de/test/antennapod/handler/FeedHandlerTest.java b/app/src/androidTest/java/de/test/antennapod/handler/FeedHandlerTest.java index 9419d2318..dfb78d5a9 100644 --- a/app/src/androidTest/java/de/test/antennapod/handler/FeedHandlerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/handler/FeedHandlerTest.java @@ -2,14 +2,17 @@ package de.test.antennapod.handler; import android.content.Context; import android.support.test.InstrumentationRegistry; -import android.test.InstrumentationTestCase; - +import android.support.test.filters.SmallTest; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import org.xml.sax.SAXException; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -26,17 +29,23 @@ import de.test.antennapod.util.syndication.feedgenerator.AtomGenerator; import de.test.antennapod.util.syndication.feedgenerator.FeedGenerator; import de.test.antennapod.util.syndication.feedgenerator.RSS2Generator; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + /** * Tests for FeedHandler */ -public class FeedHandlerTest extends InstrumentationTestCase { +@SmallTest +public class FeedHandlerTest { private static final String FEEDS_DIR = "testfeeds"; private File file = null; private OutputStream outputStream = null; - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { Context context = InstrumentationRegistry.getTargetContext(); File destDir = context.getExternalFilesDir(FEEDS_DIR); assertNotNull(destDir); @@ -51,9 +60,8 @@ public class FeedHandlerTest extends InstrumentationTestCase { } - @Override - protected void tearDown() throws Exception { - super.tearDown(); + @After + public void tearDown() throws Exception { file.delete(); file = null; @@ -130,12 +138,14 @@ public class FeedHandlerTest extends InstrumentationTestCase { } } + @Test public void testRSS2Basic() throws IOException, UnsupportedFeedtypeException, SAXException, ParserConfigurationException { Feed f1 = createTestFeed(10, false, true, true); Feed f2 = runFeedTest(f1, new RSS2Generator(), "UTF-8", RSS2Generator.FEATURE_WRITE_GUID); feedValid(f1, f2, Feed.TYPE_RSS2); } + @Test public void testAtomBasic() throws IOException, UnsupportedFeedtypeException, SAXException, ParserConfigurationException { Feed f1 = createTestFeed(10, false, true, true); Feed f2 = runFeedTest(f1, new AtomGenerator(), "UTF-8", 0); diff --git a/app/src/androidTest/java/de/test/antennapod/service/download/HttpDownloaderTest.java b/app/src/androidTest/java/de/test/antennapod/service/download/HttpDownloaderTest.java index 4a5883c64..967bf431c 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/download/HttpDownloaderTest.java +++ b/app/src/androidTest/java/de/test/antennapod/service/download/HttpDownloaderTest.java @@ -1,6 +1,7 @@ package de.test.antennapod.service.download; -import android.test.InstrumentationTestCase; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; import android.util.Log; import java.io.File; @@ -14,8 +15,17 @@ import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.service.download.HttpDownloader; import de.danoeh.antennapod.core.util.DownloadError; import de.test.antennapod.util.service.download.HTTPBin; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; -public class HttpDownloaderTest extends InstrumentationTestCase { +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@LargeTest +public class HttpDownloaderTest { private static final String TAG = "HttpDownloaderTest"; private static final String DOWNLOAD_DIR = "testdownloads"; @@ -29,9 +39,8 @@ public class HttpDownloaderTest extends InstrumentationTestCase { super(); } - @Override - protected void tearDown() throws Exception { - super.tearDown(); + @After + public void tearDown() throws Exception { File[] contents = destDir.listFiles(); for (File f : contents) { assertTrue(f.delete()); @@ -40,11 +49,10 @@ public class HttpDownloaderTest extends InstrumentationTestCase { httpServer.stop(); } - @Override - protected void setUp() throws Exception { - super.setUp(); - UserPreferences.init(getInstrumentation().getTargetContext()); - destDir = getInstrumentation().getTargetContext().getExternalFilesDir(DOWNLOAD_DIR); + @Before + public void setUp() throws Exception { + UserPreferences.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); + destDir = InstrumentationRegistry.getTargetContext().getExternalFilesDir(DOWNLOAD_DIR); assertNotNull(destDir); assertTrue(destDir.exists()); httpServer = new HTTPBin(); @@ -84,22 +92,27 @@ public class HttpDownloaderTest extends InstrumentationTestCase { private static final String URL_404 = HTTPBin.BASE_URL + "/status/404"; private static final String URL_AUTH = HTTPBin.BASE_URL + "/basic-auth/user/passwd"; + @Test public void testPassingHttp() { download(HTTPBin.BASE_URL + "/status/200", "test200", true); } + @Test public void testRedirect() { download(HTTPBin.BASE_URL + "/redirect/4", "testRedirect", true); } + @Test public void testGzip() { download(HTTPBin.BASE_URL + "/gzip/100", "testGzip", true); } + @Test public void test404() { download(URL_404, "test404", false); } + @Test public void testCancel() { final String url = HTTPBin.BASE_URL + "/delay/3"; FeedFileImpl feedFile = setupFeedFile(url, "delay", true); @@ -124,11 +137,13 @@ public class HttpDownloaderTest extends InstrumentationTestCase { assertFalse(new File(feedFile.getFile_url()).exists()); } + @Test public void testDeleteOnFailShouldDelete() { Downloader downloader = download(URL_404, "testDeleteOnFailShouldDelete", false, true, null, null, true); assertFalse(new File(downloader.getDownloadRequest().getDestination()).exists()); } + @Test public void testDeleteOnFailShouldNotDelete() throws IOException { String filename = "testDeleteOnFailShouldDelete"; File dest = new File(destDir, filename); @@ -138,10 +153,12 @@ public class HttpDownloaderTest extends InstrumentationTestCase { assertTrue(new File(downloader.getDownloadRequest().getDestination()).exists()); } + @Test public void testAuthenticationShouldSucceed() throws InterruptedException { download(URL_AUTH, "testAuthSuccess", true, true, "user", "passwd", true); } + @Test public void testAuthenticationShouldFail() { Downloader downloader = download(URL_AUTH, "testAuthSuccess", false, true, "user", "Wrong passwd", true); assertEquals(DownloadError.ERROR_UNAUTHORIZED, downloader.getResult().getReason()); diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/CancelablePSMPCallback.java b/app/src/androidTest/java/de/test/antennapod/service/playback/CancelablePSMPCallback.java new file mode 100644 index 000000000..085e2c479 --- /dev/null +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/CancelablePSMPCallback.java @@ -0,0 +1,124 @@ +package de.test.antennapod.service.playback; + +import android.support.annotation.NonNull; +import de.danoeh.antennapod.core.feed.MediaType; +import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer; +import de.danoeh.antennapod.core.util.playback.Playable; + +public class CancelablePSMPCallback implements PlaybackServiceMediaPlayer.PSMPCallback { + + private final PlaybackServiceMediaPlayer.PSMPCallback originalCallback; + private boolean isCancelled = false; + + public CancelablePSMPCallback(PlaybackServiceMediaPlayer.PSMPCallback originalCallback) { + this.originalCallback = originalCallback; + } + + public void cancel() { + isCancelled = true; + } + + @Override + public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + if (isCancelled) { + return; + } + originalCallback.statusChanged(newInfo); + } + + @Override + public void shouldStop() { + if (isCancelled) { + return; + } + originalCallback.shouldStop(); + } + + @Override + public void playbackSpeedChanged(float s) { + if (isCancelled) { + return; + } + originalCallback.playbackSpeedChanged(s); + } + + @Override + public void setSpeedAbilityChanged() { + if (isCancelled) { + return; + } + originalCallback.setSpeedAbilityChanged(); + } + + @Override + public void onBufferingUpdate(int percent) { + if (isCancelled) { + return; + } + originalCallback.onBufferingUpdate(percent); + } + + @Override + public void onMediaChanged(boolean reloadUI) { + if (isCancelled) { + return; + } + originalCallback.onMediaChanged(reloadUI); + } + + @Override + public boolean onMediaPlayerInfo(int code, int resourceId) { + if (isCancelled) { + return true; + } + return originalCallback.onMediaPlayerInfo(code, resourceId); + } + + @Override + public boolean onMediaPlayerError(Object inObj, int what, int extra) { + if (isCancelled) { + return true; + } + return originalCallback.onMediaPlayerError(inObj, what, extra); + } + + @Override + public void onPostPlayback(@NonNull Playable media, boolean ended, boolean skipped, boolean playingNext) { + if (isCancelled) { + return; + } + originalCallback.onPostPlayback(media, ended, skipped, playingNext); + } + + @Override + public void onPlaybackStart(@NonNull Playable playable, int position) { + if (isCancelled) { + return; + } + originalCallback.onPlaybackStart(playable, position); + } + + @Override + public void onPlaybackPause(Playable playable, int position) { + if (isCancelled) { + return; + } + originalCallback.onPlaybackPause(playable, position); + } + + @Override + public Playable getNextInQueue(Playable currentMedia) { + if (isCancelled) { + return null; + } + return originalCallback.getNextInQueue(currentMedia); + } + + @Override + public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) { + if (isCancelled) { + return; + } + originalCallback.onPlaybackEnded(mediaType, stopPlaying); + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/DefaultPSMPCallback.java b/app/src/androidTest/java/de/test/antennapod/service/playback/DefaultPSMPCallback.java new file mode 100644 index 000000000..113239908 --- /dev/null +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/DefaultPSMPCallback.java @@ -0,0 +1,74 @@ +package de.test.antennapod.service.playback; + +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import de.danoeh.antennapod.core.feed.MediaType; +import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer; +import de.danoeh.antennapod.core.util.playback.Playable; + +public class DefaultPSMPCallback implements PlaybackServiceMediaPlayer.PSMPCallback { + @Override + public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + + } + + @Override + public void shouldStop() { + + } + + @Override + public void playbackSpeedChanged(float s) { + + } + + @Override + public void setSpeedAbilityChanged() { + + } + + @Override + public void onBufferingUpdate(int percent) { + + } + + @Override + public void onMediaChanged(boolean reloadUI) { + + } + + @Override + public boolean onMediaPlayerInfo(int code, @StringRes int resourceId) { + return false; + } + + @Override + public boolean onMediaPlayerError(Object inObj, int what, int extra) { + return false; + } + + @Override + public void onPostPlayback(@NonNull Playable media, boolean ended, boolean skipped, boolean playingNext) { + + } + + @Override + public void onPlaybackStart(@NonNull Playable playable, int position) { + + } + + @Override + public void onPlaybackPause(Playable playable, int position) { + + } + + @Override + public Playable getNextInQueue(Playable currentMedia) { + return null; + } + + @Override + public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) { + + } + } \ No newline at end of file diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java index 2f53ea8a6..7a9ede8c5 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java @@ -1,10 +1,10 @@ package de.test.antennapod.service.playback; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.StringRes; -import android.test.InstrumentationTestCase; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.MediumTest; +import de.test.antennapod.EspressoTestUtils; import junit.framework.AssertionFailedError; import org.apache.commons.io.IOUtils; @@ -22,42 +22,49 @@ 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.feed.FeedPreferences; -import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.service.playback.LocalPSMP; import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer; import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.core.util.playback.Playable; import de.test.antennapod.util.service.download.HTTPBin; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static android.support.test.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * Test class for LocalPSMP */ -public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { - private static final String TAG = "PlaybackServiceMediaPlayerTest"; - +@MediumTest +public class PlaybackServiceMediaPlayerTest { private static final String PLAYABLE_FILE_URL = "http://127.0.0.1:" + HTTPBin.PORT + "/files/0"; private static final String PLAYABLE_DEST_URL = "psmptestfile.mp3"; private String PLAYABLE_LOCAL_URL = null; - private static final int LATCH_TIMEOUT_SECONDS = 10; + private static final int LATCH_TIMEOUT_SECONDS = 3; private HTTPBin httpServer; private volatile AssertionFailedError assertionError; - @Override - protected void tearDown() throws Exception { - super.tearDown(); + @After + public void tearDown() throws Exception { PodDBAdapter.deleteDatabase(); httpServer.stop(); } - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { assertionError = null; - - final Context context = getInstrumentation().getTargetContext(); + EspressoTestUtils.clearAppData(); + final Context context = InstrumentationRegistry.getTargetContext(); // create new database PodDBAdapter.init(context); @@ -78,14 +85,14 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertTrue(cacheDir.canWrite()); assertTrue(cacheDir.canRead()); if (!dest.exists()) { - InputStream i = getInstrumentation().getContext().getAssets().open("testfile.mp3"); + InputStream i = InstrumentationRegistry.getContext().getAssets().open("testfile.mp3"); OutputStream o = new FileOutputStream(new File(cacheDir, PLAYABLE_DEST_URL)); IOUtils.copy(i, o); o.flush(); o.close(); i.close(); } - PLAYABLE_LOCAL_URL = "file://" + dest.getAbsolutePath(); + PLAYABLE_LOCAL_URL = dest.getAbsolutePath(); assertEquals(0, httpServer.serveFile(dest)); } @@ -102,10 +109,9 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertNotNull(info.playable); break; case STOPPED: - assertNull(info.playable); - break; case ERROR: assertNull(info.playable); + break; } } catch (AssertionFailedError e) { if (assertionError == null) @@ -113,6 +119,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } } + @Test public void testInit() { final Context c = getInstrumentation().getTargetContext(); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, new DefaultPSMPCallback()); @@ -137,11 +144,11 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { return media; } - + @Test public void testPlayMediaObjectStreamNoStartNoPrepare() throws InterruptedException { final Context c = getInstrumentation().getTargetContext(); final CountDownLatch countDownLatch = new CountDownLatch(2); - PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() { + CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() { @Override public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { @@ -162,7 +169,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertionError = e; } } - }; + }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null); psmp.playMediaObject(p, true, false, false); @@ -173,13 +180,15 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertTrue(psmp.getPSMPInfo().playerStatus == PlayerStatus.INITIALIZED); assertFalse(psmp.isStartWhenPrepared()); + callback.cancel(); psmp.shutdown(); } + @Test public void testPlayMediaObjectStreamStartNoPrepare() throws InterruptedException { final Context c = getInstrumentation().getTargetContext(); final CountDownLatch countDownLatch = new CountDownLatch(2); - PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() { + CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() { @Override public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { @@ -200,7 +209,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertionError = e; } } - }; + }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null); psmp.playMediaObject(p, true, true, false); @@ -212,13 +221,15 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertTrue(psmp.getPSMPInfo().playerStatus == PlayerStatus.INITIALIZED); assertTrue(psmp.isStartWhenPrepared()); + callback.cancel(); psmp.shutdown(); } + @Test public void testPlayMediaObjectStreamNoStartPrepare() throws InterruptedException { final Context c = getInstrumentation().getTargetContext(); final CountDownLatch countDownLatch = new CountDownLatch(4); - PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() { + CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() { @Override public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { @@ -242,7 +253,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertionError = e; } } - }; + }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null); psmp.playMediaObject(p, true, false, true); @@ -251,14 +262,16 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { throw assertionError; assertTrue(res); assertTrue(psmp.getPSMPInfo().playerStatus == PlayerStatus.PREPARED); + callback.cancel(); psmp.shutdown(); } + @Test public void testPlayMediaObjectStreamStartPrepare() throws InterruptedException { final Context c = getInstrumentation().getTargetContext(); final CountDownLatch countDownLatch = new CountDownLatch(5); - PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() { + CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() { @Override public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { @@ -285,7 +298,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertionError = e; } } - }; + }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null); psmp.playMediaObject(p, true, true, true); @@ -294,13 +307,15 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { throw assertionError; assertTrue(res); assertTrue(psmp.getPSMPInfo().playerStatus == PlayerStatus.PLAYING); + callback.cancel(); psmp.shutdown(); } + @Test public void testPlayMediaObjectLocalNoStartNoPrepare() throws InterruptedException { final Context c = getInstrumentation().getTargetContext(); final CountDownLatch countDownLatch = new CountDownLatch(2); - PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() { + CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() { @Override public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { @@ -321,7 +336,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertionError = e; } } - }; + }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL); psmp.playMediaObject(p, false, false, false); @@ -331,13 +346,15 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertTrue(res); assertTrue(psmp.getPSMPInfo().playerStatus == PlayerStatus.INITIALIZED); assertFalse(psmp.isStartWhenPrepared()); + callback.cancel(); psmp.shutdown(); } + @Test public void testPlayMediaObjectLocalStartNoPrepare() throws InterruptedException { final Context c = getInstrumentation().getTargetContext(); final CountDownLatch countDownLatch = new CountDownLatch(2); - PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() { + CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() { @Override public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { @@ -358,7 +375,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertionError = e; } } - }; + }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL); psmp.playMediaObject(p, false, true, false); @@ -368,13 +385,15 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertTrue(res); assertTrue(psmp.getPSMPInfo().playerStatus == PlayerStatus.INITIALIZED); assertTrue(psmp.isStartWhenPrepared()); + callback.cancel(); psmp.shutdown(); } + @Test public void testPlayMediaObjectLocalNoStartPrepare() throws InterruptedException { final Context c = getInstrumentation().getTargetContext(); final CountDownLatch countDownLatch = new CountDownLatch(4); - PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() { + CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() { @Override public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { @@ -398,7 +417,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertionError = e; } } - }; + }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL); psmp.playMediaObject(p, false, false, true); @@ -407,13 +426,15 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { throw assertionError; assertTrue(res); assertTrue(psmp.getPSMPInfo().playerStatus == PlayerStatus.PREPARED); + callback.cancel(); psmp.shutdown(); } + @Test public void testPlayMediaObjectLocalStartPrepare() throws InterruptedException { final Context c = getInstrumentation().getTargetContext(); final CountDownLatch countDownLatch = new CountDownLatch(5); - PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() { + CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() { @Override public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { @@ -441,7 +462,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { countDownLatch.countDown(); } } - }; + }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL); psmp.playMediaObject(p, false, true, true); @@ -450,6 +471,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { throw assertionError; assertTrue(res); assertTrue(psmp.getPSMPInfo().playerStatus == PlayerStatus.PLAYING); + callback.cancel(); psmp.shutdown(); } @@ -458,7 +480,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { final int latchCount = (stream && reinit) ? 2 : 1; final CountDownLatch countDownLatch = new CountDownLatch(latchCount); - PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() { + CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() { @Override public void statusChanged(LocalPSMP.PSMPInfo newInfo) { checkPSMPInfo(newInfo); @@ -503,7 +525,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError"); return false; } - }; + }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL); if (initialState == PlayerStatus.PLAYING) { @@ -514,41 +536,51 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { if (assertionError != null) throw assertionError; assertTrue(res || initialState != PlayerStatus.PLAYING); + callback.cancel(); psmp.shutdown(); } + @Test public void testPauseDefaultState() throws InterruptedException { pauseTestSkeleton(PlayerStatus.STOPPED, false, false, false, 1); } + @Test public void testPausePlayingStateNoAbandonNoReinitNoStream() throws InterruptedException { pauseTestSkeleton(PlayerStatus.PLAYING, false, false, false, LATCH_TIMEOUT_SECONDS); } + @Test public void testPausePlayingStateNoAbandonNoReinitStream() throws InterruptedException { pauseTestSkeleton(PlayerStatus.PLAYING, true, false, false, LATCH_TIMEOUT_SECONDS); } + @Test public void testPausePlayingStateAbandonNoReinitNoStream() throws InterruptedException { pauseTestSkeleton(PlayerStatus.PLAYING, false, true, false, LATCH_TIMEOUT_SECONDS); } + @Test public void testPausePlayingStateAbandonNoReinitStream() throws InterruptedException { pauseTestSkeleton(PlayerStatus.PLAYING, true, true, false, LATCH_TIMEOUT_SECONDS); } + @Test public void testPausePlayingStateNoAbandonReinitNoStream() throws InterruptedException { pauseTestSkeleton(PlayerStatus.PLAYING, false, false, true, LATCH_TIMEOUT_SECONDS); } + @Test public void testPausePlayingStateNoAbandonReinitStream() throws InterruptedException { pauseTestSkeleton(PlayerStatus.PLAYING, true, false, true, LATCH_TIMEOUT_SECONDS); } + @Test public void testPausePlayingStateAbandonReinitNoStream() throws InterruptedException { pauseTestSkeleton(PlayerStatus.PLAYING, false, true, true, LATCH_TIMEOUT_SECONDS); } + @Test public void testPausePlayingStateAbandonReinitStream() throws InterruptedException { pauseTestSkeleton(PlayerStatus.PLAYING, true, true, true, LATCH_TIMEOUT_SECONDS); } @@ -559,7 +591,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { (initialState == PlayerStatus.PREPARED) ? 1 : 0; final CountDownLatch countDownLatch = new CountDownLatch(latchCount); - PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() { + CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() { @Override public void statusChanged(LocalPSMP.PSMPInfo newInfo) { checkPSMPInfo(newInfo); @@ -584,7 +616,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } return false; } - }; + }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); if (initialState == PlayerStatus.PREPARED || initialState == PlayerStatus.PLAYING || initialState == PlayerStatus.PAUSED) { boolean startWhenPrepared = (initialState != PlayerStatus.PREPARED); @@ -598,17 +630,21 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { if (assertionError != null) throw assertionError; assertTrue(res || (initialState != PlayerStatus.PAUSED && initialState != PlayerStatus.PREPARED)); + callback.cancel(); psmp.shutdown(); } + @Test public void testResumePausedState() throws InterruptedException { resumeTestSkeleton(PlayerStatus.PAUSED, LATCH_TIMEOUT_SECONDS); } + @Test public void testResumePreparedState() throws InterruptedException { resumeTestSkeleton(PlayerStatus.PREPARED, LATCH_TIMEOUT_SECONDS); } + @Test public void testResumePlayingState() throws InterruptedException { resumeTestSkeleton(PlayerStatus.PLAYING, 1); } @@ -617,7 +653,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { final Context c = getInstrumentation().getTargetContext(); final int latchCount = 1; final CountDownLatch countDownLatch = new CountDownLatch(latchCount); - PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() { + CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() { @Override public void statusChanged(LocalPSMP.PSMPInfo newInfo) { checkPSMPInfo(newInfo); @@ -639,7 +675,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError"); return false; } - }; + }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL); if (initialState == PlayerStatus.INITIALIZED @@ -663,21 +699,26 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { if (assertionError != null) throw assertionError; assertTrue(res); + callback.cancel(); psmp.shutdown(); } + @Test public void testPrepareInitializedState() throws InterruptedException { prepareTestSkeleton(PlayerStatus.INITIALIZED, LATCH_TIMEOUT_SECONDS); } + @Test public void testPreparePlayingState() throws InterruptedException { prepareTestSkeleton(PlayerStatus.PLAYING, 1); } + @Test public void testPreparePausedState() throws InterruptedException { prepareTestSkeleton(PlayerStatus.PAUSED, 1); } + @Test public void testPreparePreparedState() throws InterruptedException { prepareTestSkeleton(PlayerStatus.PREPARED, 1); } @@ -686,7 +727,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { final Context c = getInstrumentation().getTargetContext(); final int latchCount = 2; final CountDownLatch countDownLatch = new CountDownLatch(latchCount); - PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() { + CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() { @Override public void statusChanged(LocalPSMP.PSMPInfo newInfo) { checkPSMPInfo(newInfo); @@ -708,7 +749,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError"); return false; } - }; + }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL); boolean prepareImmediately = initialState != PlayerStatus.INITIALIZED; @@ -722,21 +763,26 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { if (assertionError != null) throw assertionError; assertTrue(res); + callback.cancel(); psmp.shutdown(); } + @Test public void testReinitPlayingState() throws InterruptedException { reinitTestSkeleton(PlayerStatus.PLAYING, LATCH_TIMEOUT_SECONDS); } + @Test public void testReinitPausedState() throws InterruptedException { reinitTestSkeleton(PlayerStatus.PAUSED, LATCH_TIMEOUT_SECONDS); } + @Test public void testPreparedPlayingState() throws InterruptedException { reinitTestSkeleton(PlayerStatus.PREPARED, LATCH_TIMEOUT_SECONDS); } + @Test public void testReinitInitializedState() throws InterruptedException { reinitTestSkeleton(PlayerStatus.INITIALIZED, LATCH_TIMEOUT_SECONDS); } @@ -746,71 +792,4 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { super("Unexpected state change: " + status); } } - - private class DefaultPSMPCallback implements PlaybackServiceMediaPlayer.PSMPCallback { - @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { - - } - - @Override - public void shouldStop() { - - } - - @Override - public void playbackSpeedChanged(float s) { - - } - - @Override - public void setSpeedAbilityChanged() { - - } - - @Override - public void onBufferingUpdate(int percent) { - - } - - @Override - public void onMediaChanged(boolean reloadUI) { - - } - - @Override - public boolean onMediaPlayerInfo(int code, @StringRes int resourceId) { - return false; - } - - @Override - public boolean onMediaPlayerError(Object inObj, int what, int extra) { - return false; - } - - @Override - public void onPostPlayback(@NonNull Playable media, boolean ended, boolean skipped, boolean playingNext) { - - } - - @Override - public void onPlaybackStart(@NonNull Playable playable, int position) { - - } - - @Override - public void onPlaybackPause(Playable playable, int position) { - - } - - @Override - public Playable getNextInQueue(Playable currentMedia) { - return null; - } - - @Override - public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) { - - } - } } diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java index c8222b376..9c0e90929 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java @@ -3,8 +3,7 @@ package de.test.antennapod.service.playback; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.annotation.UiThreadTest; -import android.support.test.runner.AndroidJUnit4; -import android.test.InstrumentationTestCase; +import android.support.test.filters.LargeTest; import java.util.ArrayList; import java.util.Date; @@ -23,9 +22,8 @@ import org.greenrobot.eventbus.EventBus; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import static junit.framework.Assert.assertFalse; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -33,7 +31,7 @@ import static org.junit.Assert.fail; /** * Test class for PlaybackServiceTaskManager */ -@RunWith(AndroidJUnit4.class) +@LargeTest public class PlaybackServiceTaskManagerTest { @After diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java b/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java index 5cd4e9906..e78894e59 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java @@ -3,8 +3,6 @@ package de.test.antennapod.storage; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; -import android.test.FlakyTest; -import android.test.InstrumentationTestCase; import java.io.File; import java.io.IOException; @@ -12,41 +10,46 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.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.DBTasks; import de.danoeh.antennapod.core.storage.PodDBAdapter; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import static de.test.antennapod.storage.DBTestUtils.saveFeedlist; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; /** * Test class for DBTasks */ -public class DBCleanupTests extends InstrumentationTestCase { - - private static final String TAG = "DBTasksTest"; +@SmallTest +public class DBCleanupTests { static final int EPISODE_CACHE_SIZE = 5; - private final int cleanupAlgorithm; + private int cleanupAlgorithm; Context context; private File destFolder; public DBCleanupTests() { - this.cleanupAlgorithm = UserPreferences.EPISODE_CLEANUP_DEFAULT; + setCleanupAlgorithm(UserPreferences.EPISODE_CLEANUP_DEFAULT); } - public DBCleanupTests(int cleanupAlgorithm) { + protected void setCleanupAlgorithm(int cleanupAlgorithm) { this.cleanupAlgorithm = cleanupAlgorithm; } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - + @After + public void tearDown() throws Exception { assertTrue(PodDBAdapter.deleteDatabase()); cleanupDestFolder(destFolder); @@ -59,11 +62,11 @@ public class DBCleanupTests extends InstrumentationTestCase { } } - @Override - protected void setUp() throws Exception { - super.setUp(); - context = getInstrumentation().getTargetContext(); - destFolder = context.getExternalCacheDir(); + @Before + public void setUp() throws Exception { + context = InstrumentationRegistry.getTargetContext(); + destFolder = new File(context.getCacheDir(), "DDCleanupTests"); + destFolder.mkdir(); cleanupDestFolder(destFolder); assertNotNull(destFolder); assertTrue(destFolder.exists()); @@ -84,7 +87,7 @@ public class DBCleanupTests extends InstrumentationTestCase { UserPreferences.init(context); } - @FlakyTest(tolerance = 3) + @Test public void testPerformAutoCleanupShouldDelete() throws IOException { final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2; @@ -140,7 +143,7 @@ public class DBCleanupTests extends InstrumentationTestCase { } } - @FlakyTest(tolerance = 3) + @Test public void testPerformAutoCleanupHandleUnplayed() throws IOException { final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2; @@ -156,7 +159,7 @@ public class DBCleanupTests extends InstrumentationTestCase { } } - @FlakyTest(tolerance = 3) + @Test public void testPerformAutoCleanupShouldNotDeleteBecauseInQueue() throws IOException { final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2; @@ -177,7 +180,7 @@ public class DBCleanupTests extends InstrumentationTestCase { * call to DBWriter.deleteFeedMediaOfItem instead of the ID of the FeedMedia. This would cause the wrong item to be deleted. * @throws IOException */ - @FlakyTest(tolerance = 3) + @Test public void testPerformAutoCleanupShouldNotDeleteBecauseInQueue_withFeedsWithNoMedia() throws IOException { // add feed with no enclosures so that item ID != media ID saveFeedlist(1, 10, false); @@ -195,7 +198,7 @@ public class DBCleanupTests extends InstrumentationTestCase { testPerformAutoCleanupShouldNotDeleteBecauseInQueue(); } - @FlakyTest(tolerance = 3) + @Test public void testPerformAutoCleanupShouldNotDeleteBecauseFavorite() throws IOException { final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2; diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java index 7925941ec..fe5091eb9 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java @@ -3,8 +3,6 @@ package de.test.antennapod.storage; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; -import android.test.FlakyTest; -import android.test.InstrumentationTestCase; import java.io.File; import java.io.IOException; @@ -12,17 +10,27 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.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.DBTasks; import de.danoeh.antennapod.core.storage.PodDBAdapter; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; /** * Tests that the APNullCleanupAlgorithm is working correctly. */ -public class DBNullCleanupAlgorithmTest extends InstrumentationTestCase { +@SmallTest +public class DBNullCleanupAlgorithmTest { private static final String TAG = "DBNullCleanupAlgorithmTest"; private static final int EPISODE_CACHE_SIZE = 5; @@ -31,10 +39,8 @@ public class DBNullCleanupAlgorithmTest extends InstrumentationTestCase { private File destFolder; - @Override - protected void tearDown() throws Exception { - super.tearDown(); - + @After + public void tearDown() throws Exception { assertTrue(PodDBAdapter.deleteDatabase()); cleanupDestFolder(destFolder); @@ -47,10 +53,9 @@ public class DBNullCleanupAlgorithmTest extends InstrumentationTestCase { } } - @Override - protected void setUp() throws Exception { - super.setUp(); - context = getInstrumentation().getTargetContext(); + @Before + public void setUp() throws Exception { + context = InstrumentationRegistry.getTargetContext(); destFolder = context.getExternalCacheDir(); cleanupDestFolder(destFolder); assertNotNull(destFolder); @@ -77,7 +82,7 @@ public class DBNullCleanupAlgorithmTest extends InstrumentationTestCase { * The null algorithm should never delete any items, even if they're played and not in the queue. * @throws IOException */ - @FlakyTest(tolerance = 3) + @Test public void testPerformAutoCleanupShouldNotDelete() throws IOException { final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2; diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java index d602d150b..3320aa545 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java @@ -1,33 +1,37 @@ package de.test.antennapod.storage; -import android.test.FlakyTest; - import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import android.support.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBTasks; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * Tests that the APQueueCleanupAlgorithm is working correctly. */ +@SmallTest public class DBQueueCleanupAlgorithmTest extends DBCleanupTests { private static final String TAG = "DBQueueCleanupAlgorithmTest"; public DBQueueCleanupAlgorithmTest() { - super(UserPreferences.EPISODE_CLEANUP_QUEUE); + setCleanupAlgorithm(UserPreferences.EPISODE_CLEANUP_QUEUE); } /** * For APQueueCleanupAlgorithm we expect even unplayed episodes to be deleted if needed * if they aren't in the queue */ - @FlakyTest(tolerance = 3) + @Test public void testPerformAutoCleanupHandleUnplayed() throws IOException { final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2; diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java index 0c5cf10cb..d99a409d3 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java @@ -1,13 +1,15 @@ package de.test.antennapod.storage; import android.content.Context; -import android.test.InstrumentationTestCase; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Random; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; @@ -15,32 +17,39 @@ import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.FeedItemStatistics; import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.core.util.LongList; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import static de.test.antennapod.storage.DBTestUtils.saveFeedlist; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; /** * Test class for DBReader */ -public class DBReaderTest extends InstrumentationTestCase { +@SmallTest +public class DBReaderTest { - @Override - protected void tearDown() throws Exception { - super.tearDown(); + @After + public void tearDown() throws Exception { assertTrue(PodDBAdapter.deleteDatabase()); } - @Override - protected void setUp() throws Exception { - super.setUp(); - + @Before + public void setUp() throws Exception { // create new database - PodDBAdapter.init(getInstrumentation().getTargetContext()); + PodDBAdapter.init(InstrumentationRegistry.getTargetContext()); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); adapter.close(); } + @Test public void testGetFeedList() { List feeds = saveFeedlist(10, 0, false); List savedFeeds = DBReader.getFeedList(); @@ -51,6 +60,7 @@ public class DBReaderTest extends InstrumentationTestCase { } } + @Test public void testGetFeedListSortOrder() { PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -80,6 +90,7 @@ public class DBReaderTest extends InstrumentationTestCase { assertEquals("Wrong id of feed 4: ", feed4.getId(), saved.get(3).getId()); } + @Test public void testFeedListDownloadUrls() { List feeds = saveFeedlist(10, 0, false); List urls = DBReader.getFeedListDownloadUrls(); @@ -90,8 +101,9 @@ public class DBReaderTest extends InstrumentationTestCase { } } + @Test public void testLoadFeedDataOfFeedItemlist() { - final Context context = getInstrumentation().getTargetContext(); + final Context context = InstrumentationRegistry.getTargetContext(); final int numFeeds = 10; final int numItems = 1; List feeds = saveFeedlist(numFeeds, numItems, false); @@ -114,6 +126,7 @@ public class DBReaderTest extends InstrumentationTestCase { } } + @Test public void testGetFeedItemList() { final int numFeeds = 1; final int numItems = 10; @@ -153,6 +166,7 @@ public class DBReaderTest extends InstrumentationTestCase { return queue; } + @Test public void testGetQueueIDList() { final int numItems = 10; List queue = saveQueue(numItems); @@ -165,6 +179,7 @@ public class DBReaderTest extends InstrumentationTestCase { } } + @Test public void testGetQueue() { final int numItems = 10; List queue = saveQueue(numItems); @@ -205,6 +220,7 @@ public class DBReaderTest extends InstrumentationTestCase { return downloaded; } + @Test public void testGetDownloadedItems() { final int numItems = 10; List downloaded = saveDownloadedItems(numItems); @@ -242,6 +258,7 @@ public class DBReaderTest extends InstrumentationTestCase { return newItems; } + @Test public void testGetNewItemIds() { final int numItems = 10; @@ -266,6 +283,7 @@ public class DBReaderTest extends InstrumentationTestCase { } } + @Test public void testGetPlaybackHistory() { final int numItems = (DBReader.PLAYBACK_HISTORY_SIZE + 1) * 2; final int playedItems = DBReader.PLAYBACK_HISTORY_SIZE + 1; @@ -295,6 +313,7 @@ public class DBReaderTest extends InstrumentationTestCase { } } + @Test public void testGetFeedStatisticsCheckOrder() { final int NUM_FEEDS = 10; final int NUM_ITEMS = 10; @@ -307,6 +326,7 @@ public class DBReaderTest extends InstrumentationTestCase { } } + @Test public void testGetNavDrawerDataQueueEmptyNoUnreadItems() { final int NUM_FEEDS = 10; final int NUM_ITEMS = 10; @@ -317,6 +337,7 @@ public class DBReaderTest extends InstrumentationTestCase { assertEquals(0, navDrawerData.queueSize); } + @Test public void testGetNavDrawerDataQueueNotEmptyWithUnreadItems() { final int NUM_FEEDS = 10; final int NUM_ITEMS = 10; @@ -345,8 +366,9 @@ public class DBReaderTest extends InstrumentationTestCase { assertEquals(NUM_QUEUE, navDrawerData.queueSize); } + @Test public void testGetFeedItemlistCheckChaptersFalse() throws Exception { - Context context = getInstrumentation().getTargetContext(); + Context context = InstrumentationRegistry.getTargetContext(); List feeds = DBTestUtils.saveFeedlist(10, 10, false, false, 0); for (Feed feed : feeds) { for (FeedItem item : feed.getItems()) { @@ -355,6 +377,7 @@ public class DBReaderTest extends InstrumentationTestCase { } } + @Test public void testGetFeedItemlistCheckChaptersTrue() throws Exception { List feeds = saveFeedlist(10, 10, false, true, 10); for (Feed feed : feeds) { @@ -364,6 +387,7 @@ public class DBReaderTest extends InstrumentationTestCase { } } + @Test public void testLoadChaptersOfFeedItemNoChapters() throws Exception { List feeds = saveFeedlist(1, 3, false, false, 0); saveFeedlist(1, 3, false, true, 3); @@ -377,6 +401,7 @@ public class DBReaderTest extends InstrumentationTestCase { } } + @Test public void testLoadChaptersOfFeedItemWithChapters() throws Exception { final int NUM_CHAPTERS = 3; DBTestUtils.saveFeedlist(1, 3, false, false, 0); @@ -392,6 +417,7 @@ public class DBReaderTest extends InstrumentationTestCase { } } + @Test public void testGetItemWithChapters() throws Exception { final int NUM_CHAPTERS = 3; List feeds = saveFeedlist(1, 1, false, true, NUM_CHAPTERS); diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java index 9cd7689ba..6af12315c 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java @@ -1,14 +1,15 @@ package de.test.antennapod.storage; import android.content.Context; -import android.test.FlakyTest; -import android.test.InstrumentationTestCase; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.filters.SmallTest; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; @@ -16,28 +17,30 @@ 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.PodDBAdapter; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import static java.util.Collections.singletonList; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; /** * Test class for DBTasks */ -public class DBTasksTest extends InstrumentationTestCase { - - private static final String TAG = "DBTasksTest"; - +@SmallTest +public class DBTasksTest { private Context context; - @Override - protected void tearDown() throws Exception { - super.tearDown(); + @After + public void tearDown() throws Exception { assertTrue(PodDBAdapter.deleteDatabase()); } - @Override - protected void setUp() throws Exception { - super.setUp(); - context = getInstrumentation().getTargetContext(); + @Before + public void setUp() throws Exception { + context = InstrumentationRegistry.getTargetContext(); // create new database PodDBAdapter.init(context); @@ -49,7 +52,7 @@ public class DBTasksTest extends InstrumentationTestCase { UserPreferences.init(context); } - @FlakyTest(tolerance = 3) + @Test public void testUpdateFeedNewFeed() { final int NUM_ITEMS = 10; @@ -69,6 +72,7 @@ public class DBTasksTest extends InstrumentationTestCase { } /** Two feeds with the same title, but different download URLs should be treated as different feeds. */ + @Test public void testUpdateFeedSameTitle() { Feed feed1 = new Feed("url1", null, "title"); @@ -83,6 +87,7 @@ public class DBTasksTest extends InstrumentationTestCase { assertTrue(savedFeed1.getId() != savedFeed2.getId()); } + @Test public void testUpdateFeedUpdatedFeed() { final int NUM_ITEMS_OLD = 10; final int NUM_ITEMS_NEW = 10; @@ -123,6 +128,7 @@ public class DBTasksTest extends InstrumentationTestCase { updatedFeedTest(feedFromDB, feedID, itemIDs, NUM_ITEMS_OLD, NUM_ITEMS_NEW); } + @Test public void testUpdateFeedMediaUrlResetState() { final Feed feed = new Feed("url", null, "title"); FeedItem item = new FeedItem(0, "item", "id", "link", new Date(), FeedItem.PLAYED, feed); @@ -139,7 +145,9 @@ public class DBTasksTest extends InstrumentationTestCase { FeedMedia media = new FeedMedia(item, "url", 1024, "mime/type"); item.setMedia(media); - feed.setItems(singletonList(item)); + List list = new ArrayList<>(); + list.add(item); + feed.setItems(list); final Feed newFeed = DBTasks.updateFeed(context, feed)[0]; assertTrue(feed != newFeed); diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java b/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java index f58008172..09a8466e6 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java @@ -1,7 +1,5 @@ package de.test.antennapod.storage; -import junit.framework.Assert; - import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -15,6 +13,8 @@ import de.danoeh.antennapod.core.feed.SimpleChapter; import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator; +import static org.junit.Assert.assertTrue; + /** * Utility methods for DB* tests. */ @@ -65,9 +65,9 @@ class DBTestUtils { } Collections.sort(f.getItems(), new FeedItemPubdateComparator()); adapter.setCompleteFeed(f); - Assert.assertTrue(f.getId() != 0); + assertTrue(f.getId() != 0); for (FeedItem item : f.getItems()) { - Assert.assertTrue(item.getId() != 0); + assertTrue(item.getId() != 0); } feeds.add(f); } diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java index 27d76116d..7663bb3c9 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java @@ -4,7 +4,9 @@ import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; import android.preference.PreferenceManager; -import android.test.InstrumentationTestCase; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.filters.MediumTest; import android.util.Log; import org.awaitility.Awaitility; @@ -27,23 +29,32 @@ import de.danoeh.antennapod.core.storage.DBReader; 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 static android.support.test.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; /** * Test class for DBWriter */ -public class DBWriterTest extends InstrumentationTestCase { +@MediumTest +public class DBWriterTest { private static final String TAG = "DBWriterTest"; private static final String TEST_FOLDER = "testDBWriter"; private static final long TIMEOUT = 5L; - @Override - protected void tearDown() throws Exception { - super.tearDown(); - + @After + public void tearDown() throws Exception { assertTrue(PodDBAdapter.deleteDatabase()); - final Context context = getInstrumentation().getTargetContext(); + final Context context = InstrumentationRegistry.getTargetContext(); File testDir = context.getExternalFilesDir(TEST_FOLDER); assertNotNull(testDir); for (File f : testDir.listFiles()) { @@ -51,24 +62,23 @@ public class DBWriterTest extends InstrumentationTestCase { } } - @Override - protected void setUp() throws Exception { - super.setUp(); - + @Before + public void setUp() throws Exception { // create new database - PodDBAdapter.init(getInstrumentation().getTargetContext()); + PodDBAdapter.init(InstrumentationRegistry.getTargetContext()); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); adapter.close(); - Context context = getInstrumentation().getTargetContext(); + Context context = InstrumentationRegistry.getTargetContext(); SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()).edit(); prefEdit.putBoolean(UserPreferences.PREF_DELETE_REMOVES_FROM_QUEUE, true).commit(); UserPreferences.init(context); } + @Test public void testSetFeedMediaPlaybackInformation() throws IOException, ExecutionException, InterruptedException, TimeoutException { final int POSITION = 50; @@ -101,6 +111,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertEquals(DURATION, mediaFromDb.getDuration()); } + @Test public void testDeleteFeedMediaOfItemFileExists() throws IOException, ExecutionException, InterruptedException, TimeoutException { File dest = new File(getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER), "testFile"); @@ -133,6 +144,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertNull(media.getFile_url()); } + @Test public void testDeleteFeedMediaOfItemRemoveFromQueue() throws IOException, ExecutionException, InterruptedException, TimeoutException { assertTrue(UserPreferences.shouldDeleteRemoveFromQueue()); @@ -174,6 +186,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertTrue(queue.size() == 0); } + @Test public void testDeleteFeed() throws ExecutionException, InterruptedException, IOException, TimeoutException { File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER); assertNotNull(destFolder); @@ -229,6 +242,7 @@ public class DBWriterTest extends InstrumentationTestCase { adapter.close(); } + @Test public void testDeleteFeedNoItems() throws IOException, ExecutionException, InterruptedException, TimeoutException { File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER); assertNotNull(destFolder); @@ -254,6 +268,7 @@ public class DBWriterTest extends InstrumentationTestCase { adapter.close(); } + @Test public void testDeleteFeedNoFeedMedia() throws IOException, ExecutionException, InterruptedException, TimeoutException { File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER); assertNotNull(destFolder); @@ -296,6 +311,7 @@ public class DBWriterTest extends InstrumentationTestCase { adapter.close(); } + @Test public void testDeleteFeedWithQueueItems() throws ExecutionException, InterruptedException, TimeoutException { File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER); assertNotNull(destFolder); @@ -360,6 +376,7 @@ public class DBWriterTest extends InstrumentationTestCase { adapter.close(); } + @Test public void testDeleteFeedNoDownloadedFiles() throws ExecutionException, InterruptedException, TimeoutException { File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER); assertNotNull(destFolder); @@ -427,6 +444,7 @@ public class DBWriterTest extends InstrumentationTestCase { return media; } + @Test public void testAddItemToPlaybackHistoryNotPlayedYet() throws ExecutionException, InterruptedException, TimeoutException { FeedMedia media = playbackHistorySetup(null); @@ -440,6 +458,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertNotNull(media.getPlaybackCompletionDate()); } + @Test public void testAddItemToPlaybackHistoryAlreadyPlayed() throws ExecutionException, InterruptedException, TimeoutException { final long OLD_DATE = 0; @@ -483,6 +502,7 @@ public class DBWriterTest extends InstrumentationTestCase { return feed; } + @Test public void testAddQueueItemSingleItem() throws InterruptedException, ExecutionException, TimeoutException { final Context context = getInstrumentation().getTargetContext(); Feed feed = new Feed("url", null, "title"); @@ -507,6 +527,7 @@ public class DBWriterTest extends InstrumentationTestCase { adapter.close(); } + @Test public void testAddQueueItemSingleItemAlreadyInQueue() throws InterruptedException, ExecutionException, TimeoutException { final Context context = getInstrumentation().getTargetContext(); Feed feed = new Feed("url", null, "title"); @@ -541,6 +562,7 @@ public class DBWriterTest extends InstrumentationTestCase { adapter.close(); } + @Test public void testAddQueueItemMultipleItems() throws InterruptedException, ExecutionException, TimeoutException { final Context context = getInstrumentation().getTargetContext(); final int NUM_ITEMS = 10; @@ -559,6 +581,7 @@ public class DBWriterTest extends InstrumentationTestCase { adapter.close(); } + @Test public void testClearQueue() throws InterruptedException, ExecutionException, TimeoutException { final int NUM_ITEMS = 10; @@ -572,6 +595,7 @@ public class DBWriterTest extends InstrumentationTestCase { adapter.close(); } + @Test public void testRemoveQueueItem() throws InterruptedException, ExecutionException, TimeoutException { final int NUM_ITEMS = 10; final Context context = getInstrumentation().getTargetContext(); @@ -604,6 +628,7 @@ public class DBWriterTest extends InstrumentationTestCase { } } + @Test public void testRemoveQueueItemMultipleItems() throws InterruptedException, ExecutionException, TimeoutException { // Setup test data // @@ -641,6 +666,7 @@ public class DBWriterTest extends InstrumentationTestCase { } + @Test public void testMoveQueueItem() throws InterruptedException, ExecutionException, TimeoutException { final int NUM_ITEMS = 10; Feed feed = new Feed("url", null, "title"); @@ -687,6 +713,7 @@ public class DBWriterTest extends InstrumentationTestCase { } } + @Test public void testMarkFeedRead() throws InterruptedException, ExecutionException, TimeoutException { final int NUM_ITEMS = 10; Feed feed = new Feed("url", null, "title"); @@ -713,6 +740,7 @@ public class DBWriterTest extends InstrumentationTestCase { } } + @Test public void testMarkAllItemsReadSameFeed() throws InterruptedException, ExecutionException, TimeoutException { final int NUM_ITEMS = 10; Feed feed = new Feed("url", null, "title"); diff --git a/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java b/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java index 8e0064079..d013ab9cf 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java @@ -1,28 +1,22 @@ package de.test.antennapod.ui; +import android.app.Activity; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; -import android.support.test.espresso.contrib.DrawerActions; +import android.support.test.InstrumentationRegistry; import android.support.test.espresso.intent.Intents; -import android.support.test.filters.FlakyTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; -import android.view.Gravity; -import android.widget.ListView; import com.robotium.solo.Solo; import com.robotium.solo.Timeout; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; -import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.dialog.RatingDialog; -import de.danoeh.antennapod.fragment.DownloadsFragment; -import de.danoeh.antennapod.fragment.EpisodesFragment; -import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; -import de.danoeh.antennapod.fragment.QueueFragment; +import de.test.antennapod.EspressoTestUtils; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -30,23 +24,18 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import static android.support.test.InstrumentationRegistry.getInstrumentation; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.action.ViewActions.longClick; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.contrib.DrawerMatchers.isClosed; -import static android.support.test.espresso.intent.Intents.intended; -import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent; +import static android.support.test.espresso.contrib.ActivityResultMatchers.hasResultCode; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; -import static de.test.antennapod.NthMatcher.first; +import static de.test.antennapod.EspressoTestUtils.clickPreference; +import static de.test.antennapod.EspressoTestUtils.openNavDrawer; import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; /** * User interface tests for MainActivity @@ -59,10 +48,19 @@ public class MainActivityTest { private SharedPreferences prefs; @Rule - public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class); + public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false); @Before public void setUp() throws IOException { + // override first launch preference + // do this BEFORE calling getActivity()! + EspressoTestUtils.clearAppData(); + prefs = InstrumentationRegistry.getContext() + .getSharedPreferences(MainActivity.PREF_NAME, Context.MODE_PRIVATE); + prefs.edit().putBoolean(MainActivity.PREF_IS_FIRST_LAUNCH, false).commit(); + + mActivityRule.launchActivity(new Intent()); + Intents.init(); Context context = mActivityRule.getActivity(); uiTestUtils = new UITestUtils(context); @@ -75,11 +73,6 @@ public class MainActivityTest { adapter.open(); adapter.close(); - // override first launch preference - // do this BEFORE calling getActivity()! - prefs = context.getSharedPreferences(MainActivity.PREF_NAME, Context.MODE_PRIVATE); - prefs.edit().putBoolean(MainActivity.PREF_IS_FIRST_LAUNCH, false).commit(); - RatingDialog.init(context); RatingDialog.saveRated(); @@ -95,19 +88,13 @@ public class MainActivityTest { prefs.edit().clear().commit(); } - private void openNavDrawer() { - onView(withId(R.id.drawer_layout)) - .check(matches(isClosed(Gravity.LEFT))) - .perform(DrawerActions.open()); - } - @Test public void testAddFeed() throws Exception { uiTestUtils.addHostedFeedData(); final Feed feed = uiTestUtils.hostedFeeds.get(0); openNavDrawer(); solo.clickOnText(solo.getString(R.string.add_feed_label)); - solo.enterText(0, feed.getDownload_url()); + solo.enterText(1, feed.getDownload_url()); solo.clickOnButton(solo.getString(R.string.confirm_label)); solo.waitForActivity(OnlineFeedViewActivity.class); solo.waitForView(R.id.butSubscribe); @@ -116,154 +103,23 @@ public class MainActivityTest { assertTrue(solo.waitForText(solo.getString(R.string.open_podcast), 0, Timeout.getLargeTimeout(), false)); } - - @Test - @FlakyTest - public void testClickNavDrawer() throws Exception { - uiTestUtils.addLocalFeedData(false); - - UserPreferences.setHiddenDrawerItems(new ArrayList<>()); - - // queue - openNavDrawer(); - solo.clickOnText(solo.getString(R.string.queue_label)); - solo.waitForView(android.R.id.list); - assertEquals(solo.getString(R.string.queue_label), getActionbarTitle()); - - // episodes - openNavDrawer(); - solo.clickOnText(solo.getString(R.string.episodes_label)); - solo.waitForView(android.R.id.list); - assertEquals(solo.getString(R.string.episodes_label), getActionbarTitle()); - - // Subscriptions - openNavDrawer(); - solo.clickOnText(solo.getString(R.string.subscriptions_label)); - solo.waitForView(R.id.subscriptions_grid); - assertEquals(solo.getString(R.string.subscriptions_label), getActionbarTitle()); - - // downloads - openNavDrawer(); - solo.clickOnText(solo.getString(R.string.downloads_label)); - solo.waitForView(android.R.id.list); - assertEquals(solo.getString(R.string.downloads_label), getActionbarTitle()); - - // playback history - openNavDrawer(); - solo.clickOnText(solo.getString(R.string.playback_history_label)); - solo.waitForView(android.R.id.list); - assertEquals(solo.getString(R.string.playback_history_label), getActionbarTitle()); - - // add podcast - openNavDrawer(); - solo.clickOnText(solo.getString(R.string.add_feed_label)); - solo.waitForView(R.id.txtvFeedurl); - assertEquals(solo.getString(R.string.add_feed_label), getActionbarTitle()); - - // podcasts - ListView list = (ListView) solo.getView(R.id.nav_list); - for (int i = 0; i < uiTestUtils.hostedFeeds.size(); i++) { - Feed f = uiTestUtils.hostedFeeds.get(i); - openNavDrawer(); - solo.scrollListToLine(list, i); - solo.clickOnText(f.getTitle()); - solo.waitForView(android.R.id.list); - assertEquals("", getActionbarTitle()); - } - } - private String getActionbarTitle() { return ((MainActivity) solo.getCurrentActivity()).getSupportActionBar().getTitle().toString(); } - @Test - @FlakyTest - public void testGoToPreferences() { + public void testBackButtonBehaviorGoToPage() { openNavDrawer(); onView(withText(R.string.settings_label)).perform(click()); - intended(hasComponent(PreferenceActivity.class.getName())); - } + clickPreference(R.string.user_interface_label); + clickPreference(R.string.pref_back_button_behavior_title); - @Test - public void testDrawerPreferencesHideSomeElements() { - UserPreferences.setHiddenDrawerItems(new ArrayList<>()); - openNavDrawer(); - onView(first(withText(R.string.queue_label))).perform(longClick()); - onView(withText(R.string.episodes_label)).perform(click()); - onView(withText(R.string.playback_history_label)).perform(click()); - onView(withText(R.string.confirm_label)).perform(click()); - - List hidden = UserPreferences.getHiddenDrawerItems(); - assertEquals(2, hidden.size()); - assertTrue(hidden.contains(EpisodesFragment.TAG)); - assertTrue(hidden.contains(PlaybackHistoryFragment.TAG)); - } - - @Test - public void testDrawerPreferencesUnhideSomeElements() { - List hidden = Arrays.asList(PlaybackHistoryFragment.TAG, DownloadsFragment.TAG); - UserPreferences.setHiddenDrawerItems(hidden); - openNavDrawer(); - onView(first(withText(R.string.queue_label))).perform(longClick()); - - onView(withText(R.string.downloads_label)).perform(click()); - onView(withText(R.string.queue_label)).perform(click()); - onView(withText(R.string.confirm_label)).perform(click()); - - hidden = UserPreferences.getHiddenDrawerItems(); - assertEquals(2, hidden.size()); - assertTrue(hidden.contains(QueueFragment.TAG)); - assertTrue(hidden.contains(PlaybackHistoryFragment.TAG)); - } - - - @Test - public void testDrawerPreferencesHideAllElements() { - UserPreferences.setHiddenDrawerItems(new ArrayList<>()); - String[] titles = mActivityRule.getActivity().getResources().getStringArray(R.array.nav_drawer_titles); - - openNavDrawer(); - onView(first(withText(R.string.queue_label))).perform(longClick()); - for (String title : titles) { - onView(first(withText(title))).perform(click()); - } + onView(withText(R.string.back_button_go_to_page)).perform(click()); + onView(withText(R.string.subscriptions_label)).perform(click()); onView(withText(R.string.confirm_label)).perform(click()); - List hidden = UserPreferences.getHiddenDrawerItems(); - assertEquals(titles.length, hidden.size()); - for (String tag : MainActivity.NAV_DRAWER_TAGS) { - assertTrue(hidden.contains(tag)); - } - } - - @Test - public void testDrawerPreferencesHideCurrentElement() { - UserPreferences.setHiddenDrawerItems(new ArrayList<>()); - openNavDrawer(); - onView(withText(R.string.downloads_label)).perform(click()); - openNavDrawer(); - - onView(first(withText(R.string.queue_label))).perform(longClick()); - onView(first(withText(R.string.downloads_label))).perform(click()); - onView(withText(R.string.confirm_label)).perform(click()); - - List hidden = UserPreferences.getHiddenDrawerItems(); - assertEquals(1, hidden.size()); - assertTrue(hidden.contains(DownloadsFragment.TAG)); - } - - @Test - public void testBackButtonBehaviorGoToPage() { - openNavDrawer(); - solo.clickOnText(solo.getString(R.string.settings_label)); - solo.clickOnText(solo.getString(R.string.user_interface_label)); - solo.clickOnText(solo.getString(R.string.pref_back_button_behavior_title)); - solo.clickOnText(solo.getString(R.string.back_button_go_to_page)); - solo.waitForDialogToOpen(); - solo.clickOnText(solo.getString(R.string.subscriptions_label)); - solo.clickOnText(solo.getString(R.string.confirm_label)); solo.goBackToActivity(MainActivity.class.getSimpleName()); + solo.goBack(); // Close nav drawer solo.goBack(); assertEquals(solo.getString(R.string.subscriptions_label), getActionbarTitle()); } @@ -271,11 +127,12 @@ public class MainActivityTest { @Test public void testBackButtonBehaviorOpenDrawer() { openNavDrawer(); - solo.clickOnText(solo.getString(R.string.settings_label)); - solo.clickOnText(solo.getString(R.string.user_interface_label)); - solo.clickOnText(solo.getString(R.string.pref_back_button_behavior_title)); - solo.clickOnText(solo.getString(R.string.back_button_open_drawer)); + onView(withText(R.string.settings_label)).perform(click()); + clickPreference(R.string.user_interface_label); + clickPreference(R.string.pref_back_button_behavior_title); + onView(withText(R.string.back_button_open_drawer)).perform(click()); solo.goBackToActivity(MainActivity.class.getSimpleName()); + solo.goBack(); // Close nav drawer solo.goBack(); assertTrue(((MainActivity)solo.getCurrentActivity()).isDrawerOpen()); } @@ -283,39 +140,42 @@ public class MainActivityTest { @Test public void testBackButtonBehaviorDoubleTap() { openNavDrawer(); - solo.clickOnText(solo.getString(R.string.settings_label)); - solo.clickOnText(solo.getString(R.string.user_interface_label)); - solo.clickOnText(solo.getString(R.string.pref_back_button_behavior_title)); - solo.clickOnText(solo.getString(R.string.back_button_double_tap)); + onView(withText(R.string.settings_label)).perform(click()); + clickPreference(R.string.user_interface_label); + clickPreference(R.string.pref_back_button_behavior_title); + onView(withText(R.string.back_button_double_tap)).perform(click()); solo.goBackToActivity(MainActivity.class.getSimpleName()); + solo.goBack(); // Close nav drawer solo.goBack(); solo.goBack(); - assertTrue(solo.getCurrentActivity().isFinishing()); + assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED)); } @Test - public void testBackButtonBehaviorPrompt() { + public void testBackButtonBehaviorPrompt() throws Exception { openNavDrawer(); - solo.clickOnText(solo.getString(R.string.settings_label)); - solo.clickOnText(solo.getString(R.string.user_interface_label)); - solo.clickOnText(solo.getString(R.string.pref_back_button_behavior_title)); - solo.clickOnText(solo.getString(R.string.back_button_show_prompt)); + onView(withText(R.string.settings_label)).perform(click()); + clickPreference(R.string.user_interface_label); + clickPreference(R.string.pref_back_button_behavior_title); + onView(withText(R.string.back_button_show_prompt)).perform(click()); solo.goBackToActivity(MainActivity.class.getSimpleName()); + solo.goBack(); // Close nav drawer solo.goBack(); - solo.clickOnText(solo.getString(R.string.yes)); - solo.waitForDialogToClose(); - assertTrue(solo.getCurrentActivity().isFinishing()); + onView(withText(R.string.yes)).perform(click()); + Thread.sleep(100); + assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED)); } @Test public void testBackButtonBehaviorDefault() { openNavDrawer(); - solo.clickOnText(solo.getString(R.string.settings_label)); - solo.clickOnText(solo.getString(R.string.user_interface_label)); - solo.clickOnText(solo.getString(R.string.pref_back_button_behavior_title)); - solo.clickOnText(solo.getString(R.string.back_button_default)); + onView(withText(R.string.settings_label)).perform(click()); + clickPreference(R.string.user_interface_label); + clickPreference(R.string.pref_back_button_behavior_title); + onView(withText(R.string.back_button_default)).perform(click()); solo.goBackToActivity(MainActivity.class.getSimpleName()); + solo.goBack(); // Close nav drawer solo.goBack(); - assertTrue(solo.getCurrentActivity().isFinishing()); + assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED)); } } diff --git a/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java b/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java new file mode 100644 index 000000000..0ed62010b --- /dev/null +++ b/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java @@ -0,0 +1,253 @@ +package de.test.antennapod.ui; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.support.test.InstrumentationRegistry; +import android.support.test.espresso.contrib.DrawerActions; +import android.support.test.espresso.intent.Intents; +import android.support.test.filters.FlakyTest; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.widget.ListView; +import com.robotium.solo.Solo; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.activity.PreferenceActivity; +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.storage.PodDBAdapter; +import de.danoeh.antennapod.dialog.RatingDialog; +import de.danoeh.antennapod.fragment.DownloadsFragment; +import de.danoeh.antennapod.fragment.EpisodesFragment; +import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; +import de.danoeh.antennapod.fragment.QueueFragment; +import de.test.antennapod.EspressoTestUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static android.support.test.InstrumentationRegistry.getInstrumentation; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.longClick; +import static android.support.test.espresso.intent.Intents.intended; +import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent; +import static android.support.test.espresso.matcher.ViewMatchers.isRoot; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static de.test.antennapod.NthMatcher.first; +import static de.test.antennapod.EspressoTestUtils.waitForView; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * User interface tests for MainActivity drawer + */ +@RunWith(AndroidJUnit4.class) +public class NavigationDrawerTest { + + private Solo solo; + private UITestUtils uiTestUtils; + private SharedPreferences prefs; + + @Rule + public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false); + + @Before + public void setUp() throws IOException { + // override first launch preference + // do this BEFORE calling getActivity()! + EspressoTestUtils.clearAppData(); + prefs = InstrumentationRegistry.getContext() + .getSharedPreferences(MainActivity.PREF_NAME, Context.MODE_PRIVATE); + prefs.edit().putBoolean(MainActivity.PREF_IS_FIRST_LAUNCH, false).commit(); + + mActivityRule.launchActivity(new Intent()); + + Intents.init(); + Context context = mActivityRule.getActivity(); + uiTestUtils = new UITestUtils(context); + uiTestUtils.setup(); + + // create new database + PodDBAdapter.init(context); + PodDBAdapter.deleteDatabase(); + PodDBAdapter adapter = PodDBAdapter.getInstance(); + adapter.open(); + adapter.close(); + + RatingDialog.init(context); + RatingDialog.saveRated(); + + solo = new Solo(getInstrumentation(), mActivityRule.getActivity()); + } + + @After + public void tearDown() throws Exception { + uiTestUtils.tearDown(); + solo.finishOpenedActivities(); + Intents.release(); + PodDBAdapter.deleteDatabase(); + prefs.edit().clear().commit(); + } + + private void openNavDrawer() { + onView(isRoot()).perform(waitForView(withId(R.id.drawer_layout), 1000)); + onView(withId(R.id.drawer_layout)).perform(DrawerActions.open()); + } + + @Test + @FlakyTest + public void testClickNavDrawer() throws Exception { + uiTestUtils.addLocalFeedData(false); + + setHiddenDrawerItems(new ArrayList<>()); + + // queue + openNavDrawer(); + solo.clickOnText(solo.getString(R.string.queue_label)); + solo.waitForView(R.id.recyclerView); + assertEquals(solo.getString(R.string.queue_label), getActionbarTitle()); + + // episodes + openNavDrawer(); + solo.clickOnText(solo.getString(R.string.episodes_label)); + solo.waitForView(android.R.id.list); + assertEquals(solo.getString(R.string.episodes_label), getActionbarTitle()); + + // Subscriptions + openNavDrawer(); + solo.clickOnText(solo.getString(R.string.subscriptions_label)); + solo.waitForView(R.id.subscriptions_grid); + assertEquals(solo.getString(R.string.subscriptions_label), getActionbarTitle()); + + // downloads + openNavDrawer(); + solo.clickOnText(solo.getString(R.string.downloads_label)); + solo.waitForView(android.R.id.list); + assertEquals(solo.getString(R.string.downloads_label), getActionbarTitle()); + + // playback history + openNavDrawer(); + solo.clickOnText(solo.getString(R.string.playback_history_label)); + solo.waitForView(android.R.id.list); + assertEquals(solo.getString(R.string.playback_history_label), getActionbarTitle()); + + // add podcast + openNavDrawer(); + solo.clickOnText(solo.getString(R.string.add_feed_label)); + solo.waitForView(R.id.txtvFeedurl); + assertEquals(solo.getString(R.string.add_feed_label), getActionbarTitle()); + + // podcasts + ListView list = (ListView) solo.getView(R.id.nav_list); + for (int i = 0; i < uiTestUtils.hostedFeeds.size(); i++) { + Feed f = uiTestUtils.hostedFeeds.get(i); + openNavDrawer(); + solo.scrollListToLine(list, i); + solo.clickOnText(f.getTitle()); + solo.waitForView(android.R.id.list); + assertEquals("", getActionbarTitle()); + } + } + + private String getActionbarTitle() { + return ((MainActivity) solo.getCurrentActivity()).getSupportActionBar().getTitle().toString(); + } + + + @Test + @FlakyTest + public void testGoToPreferences() { + openNavDrawer(); + onView(withText(R.string.settings_label)).perform(click()); + intended(hasComponent(PreferenceActivity.class.getName())); + } + + @Test + public void testDrawerPreferencesHideSomeElements() { + setHiddenDrawerItems(new ArrayList<>()); + openNavDrawer(); + onView(first(withText(R.string.queue_label))).perform(longClick()); + onView(withText(R.string.episodes_label)).perform(click()); + onView(withText(R.string.playback_history_label)).perform(click()); + onView(withText(R.string.confirm_label)).perform(click()); + + List hidden = UserPreferences.getHiddenDrawerItems(); + assertEquals(2, hidden.size()); + assertTrue(hidden.contains(EpisodesFragment.TAG)); + assertTrue(hidden.contains(PlaybackHistoryFragment.TAG)); + } + + @Test + public void testDrawerPreferencesUnhideSomeElements() { + List hidden = Arrays.asList(PlaybackHistoryFragment.TAG, DownloadsFragment.TAG); + setHiddenDrawerItems(hidden); + openNavDrawer(); + onView(first(withText(R.string.queue_label))).perform(longClick()); + + onView(withText(R.string.downloads_label)).perform(click()); + onView(withText(R.string.queue_label)).perform(click()); + onView(withText(R.string.confirm_label)).perform(click()); + + hidden = UserPreferences.getHiddenDrawerItems(); + assertEquals(2, hidden.size()); + assertTrue(hidden.contains(QueueFragment.TAG)); + assertTrue(hidden.contains(PlaybackHistoryFragment.TAG)); + } + + + @Test + public void testDrawerPreferencesHideAllElements() { + setHiddenDrawerItems(new ArrayList<>()); + String[] titles = mActivityRule.getActivity().getResources().getStringArray(R.array.nav_drawer_titles); + + openNavDrawer(); + onView(first(withText(R.string.queue_label))).perform(longClick()); + for (String title : titles) { + onView(first(withText(title))).perform(click()); + } + onView(withText(R.string.confirm_label)).perform(click()); + + List hidden = UserPreferences.getHiddenDrawerItems(); + assertEquals(titles.length, hidden.size()); + for (String tag : MainActivity.NAV_DRAWER_TAGS) { + assertTrue(hidden.contains(tag)); + } + } + + @Test + public void testDrawerPreferencesHideCurrentElement() { + setHiddenDrawerItems(new ArrayList<>()); + openNavDrawer(); + onView(withText(R.string.downloads_label)).perform(click()); + openNavDrawer(); + + onView(first(withText(R.string.queue_label))).perform(longClick()); + onView(first(withText(R.string.downloads_label))).perform(click()); + onView(withText(R.string.confirm_label)).perform(click()); + + List hidden = UserPreferences.getHiddenDrawerItems(); + assertEquals(1, hidden.size()); + assertTrue(hidden.contains(DownloadsFragment.TAG)); + } + + private void setHiddenDrawerItems(List items) { + UserPreferences.setHiddenDrawerItems(items); + try { + mActivityRule.runOnUiThread(() -> mActivityRule.getActivity().updateNavDrawer()); + } catch (Throwable throwable) { + throwable.printStackTrace(); + fail(); + } + } +} diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PlaybackSonicTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PlaybackSonicTest.java index 55ed998bb..6b41ad0ea 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PlaybackSonicTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PlaybackSonicTest.java @@ -1,13 +1,12 @@ package de.test.antennapod.ui; -import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.os.Build; import android.preference.PreferenceManager; -import android.test.ActivityInstrumentationTestCase2; -import android.test.FlakyTest; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.rule.ActivityTestRule; import android.view.View; import android.widget.ListView; @@ -25,14 +24,21 @@ import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.PodDBAdapter; +import de.test.antennapod.EspressoTestUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import static android.support.test.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * test cases for starting and ending playback from the MainActivity and AudioPlayerActivity */ -@TargetApi(Build.VERSION_CODES.JELLY_BEAN) -public class PlaybackSonicTest extends ActivityInstrumentationTestCase2 { - - private static final String TAG = PlaybackTest.class.getSimpleName(); +@LargeTest +public class PlaybackSonicTest { private static final int EPISODES_DRAWER_LIST_INDEX = 1; private static final int QUEUE_DRAWER_LIST_INDEX = 0; @@ -41,15 +47,13 @@ public class PlaybackSonicTest extends ActivityInstrumentationTestCase2 activityTestRule = new ActivityTestRule<>(MainActivity.class, false, false); - @Override + @Before public void setUp() throws Exception { - super.setUp(); - - context = getInstrumentation().getTargetContext(); + EspressoTestUtils.clearAppData(); + context = InstrumentationRegistry.getTargetContext(); PodDBAdapter.init(context); PodDBAdapter.deleteDatabase(); @@ -62,7 +66,8 @@ public class PlaybackSonicTest extends ActivityInstrumentationTestCase2 { - - private static final String TAG = PlaybackTest.class.getSimpleName(); +@LargeTest +public class PlaybackTest { private static final int EPISODES_DRAWER_LIST_INDEX = 1; private static final int QUEUE_DRAWER_LIST_INDEX = 0; + @Rule + public ActivityTestRule activityTestRule = new ActivityTestRule<>(MainActivity.class); + private Solo solo; private UITestUtils uiTestUtils; - private Context context; - public PlaybackTest() { - super(MainActivity.class); - } - - @Override + @Before public void setUp() throws Exception { - super.setUp(); - - context = getInstrumentation().getTargetContext(); + context = InstrumentationRegistry.getTargetContext(); PodDBAdapter.init(context); PodDBAdapter.deleteDatabase(); @@ -58,7 +61,7 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2 .putBoolean(UserPreferences.PREF_PAUSE_ON_HEADSET_DISCONNECT, false) .commit(); - solo = new Solo(getInstrumentation(), getActivity()); + solo = new Solo(InstrumentationRegistry.getInstrumentation(), getActivity()); uiTestUtils = new UITestUtils(context); uiTestUtils.setup(); @@ -69,7 +72,7 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2 adapter.close(); } - @Override + @After public void tearDown() throws Exception { solo.finishOpenedActivities(); uiTestUtils.tearDown(); @@ -77,12 +80,15 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2 // shut down playback service skipEpisode(); context.sendBroadcast(new Intent(PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); + } - super.tearDown(); + private MainActivity getActivity() { + return activityTestRule.getActivity(); } + private void openNavDrawer() { solo.clickOnImageButton(0); - getInstrumentation().waitForIdleSync(); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } private void setContinuousPlaybackPreference(boolean value) { @@ -150,12 +156,14 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2 assertTrue(playing); } + @Test public void testStartLocal() throws Exception { uiTestUtils.addLocalFeedData(true); DBWriter.clearQueue().get(); startLocalPlayback(); } + @Test public void testContinousPlaybackOffSingleEpisode() throws Exception { setContinuousPlaybackPreference(false); uiTestUtils.addLocalFeedData(true); @@ -163,7 +171,7 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2 startLocalPlayback(); } - @FlakyTest(tolerance = 3) + @Test public void testContinousPlaybackOffMultipleEpisodes() throws Exception { setContinuousPlaybackPreference(false); uiTestUtils.addLocalFeedData(true); @@ -187,7 +195,7 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2 assertFalse(status.equals(PlayerStatus.PLAYING)); } - @FlakyTest(tolerance = 3) + @Test public void testContinuousPlaybackOnMultipleEpisodes() throws Exception { setContinuousPlaybackPreference(true); uiTestUtils.addLocalFeedData(true); @@ -252,13 +260,13 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2 assertTrue(startedReplay); } + @Test public void testReplayEpisodeContinuousPlaybackOn() throws Exception { replayEpisodeCheck(true); } + @Test public void testReplayEpisodeContinuousPlaybackOff() throws Exception { replayEpisodeCheck(false); } - - } diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java index d934bf3e2..4b2dc75a9 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java @@ -1,22 +1,19 @@ package de.test.antennapod.ui; +import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.preference.PreferenceManager; -import android.support.test.espresso.contrib.RecyclerViewActions; -import android.support.test.rule.ActivityTestRule; -import android.support.test.runner.AndroidJUnit4; -import android.view.View; +import android.support.test.filters.LargeTest; +import android.support.test.rule.ActivityTestRule; import com.robotium.solo.Solo; import com.robotium.solo.Timeout; -import org.hamcrest.Matcher; -import org.junit.After; +import de.test.antennapod.EspressoTestUtils; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.runner.RunWith; import java.util.Arrays; import java.util.concurrent.TimeUnit; @@ -35,38 +32,33 @@ import de.danoeh.antennapod.fragment.SubscriptionFragment; import static android.support.test.InstrumentationRegistry.getInstrumentation; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; -import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static de.test.antennapod.EspressoTestUtils.clickPreference; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; -@RunWith(AndroidJUnit4.class) +@LargeTest public class PreferencesTest { private Solo solo; private Resources res; - private SharedPreferences prefs; @Rule - public ActivityTestRule mActivityRule = new ActivityTestRule<>(PreferenceActivity.class); + public ActivityTestRule mActivityRule = new ActivityTestRule<>(PreferenceActivity.class, false, false); @Before public void setUp() { + EspressoTestUtils.clearAppData(); + mActivityRule.launchActivity(new Intent()); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mActivityRule.getActivity()); + prefs.edit().putBoolean(UserPreferences.PREF_ENABLE_AUTODL, true).commit(); + solo = new Solo(getInstrumentation(), mActivityRule.getActivity()); Timeout.setSmallTimeout(500); Timeout.setLargeTimeout(1000); res = mActivityRule.getActivity().getResources(); UserPreferences.init(mActivityRule.getActivity()); - - prefs = PreferenceManager.getDefaultSharedPreferences(mActivityRule.getActivity()); - prefs.edit().clear(); - prefs.edit().putBoolean(UserPreferences.PREF_ENABLE_AUTODL, true).commit(); - } - - @After - public void tearDown() { - solo.finishOpenedActivities(); - prefs.edit().clear(); } @Test @@ -78,8 +70,8 @@ public class PreferencesTest { } else { otherTheme = R.string.pref_theme_title_light; } - clickPreference(withText(R.string.user_interface_label)); - clickPreference(withText(R.string.pref_set_theme_title)); + clickPreference(R.string.user_interface_label); + clickPreference(R.string.pref_set_theme_title); onView(withText(otherTheme)).perform(click()); assertTrue(solo.waitForCondition(() -> UserPreferences.getTheme() != theme, Timeout.getLargeTimeout())); } @@ -93,8 +85,8 @@ public class PreferencesTest { } else { otherTheme = R.string.pref_theme_title_light; } - clickPreference(withText(R.string.user_interface_label)); - clickPreference(withText(R.string.pref_set_theme_title)); + clickPreference(R.string.user_interface_label); + clickPreference(R.string.pref_set_theme_title); onView(withText(otherTheme)).perform(click()); assertTrue(solo.waitForCondition(() -> UserPreferences.getTheme() != theme, Timeout.getLargeTimeout())); } @@ -102,34 +94,31 @@ public class PreferencesTest { @Test public void testEnablePersistentPlaybackControls() { final boolean persistNotify = UserPreferences.isPersistNotify(); - clickPreference(withText(R.string.user_interface_label)); - clickPreference(withText(R.string.pref_persistNotify_title)); + clickPreference(R.string.user_interface_label); + clickPreference(R.string.pref_persistNotify_title); assertTrue(solo.waitForCondition(() -> persistNotify != UserPreferences.isPersistNotify(), Timeout.getLargeTimeout())); - clickPreference(withText(R.string.pref_persistNotify_title)); + clickPreference(R.string.pref_persistNotify_title); assertTrue(solo.waitForCondition(() -> persistNotify == UserPreferences.isPersistNotify(), Timeout.getLargeTimeout())); } @Test public void testSetLockscreenButtons() { - solo.clickOnText(solo.getString(R.string.user_interface_label)); + onView(withText(R.string.user_interface_label)).perform(click()); solo.scrollDown(); String[] buttons = res.getStringArray(R.array.compact_notification_buttons_options); - solo.clickOnText(solo.getString(R.string.pref_compact_notification_buttons_title)); + onView(withText(R.string.pref_compact_notification_buttons_title)).perform(click()); solo.waitForDialogToOpen(1000); - // First uncheck every checkbox - for (String button : buttons) { - assertTrue(solo.searchText(button)); - if (solo.isTextChecked(button)) { - solo.clickOnText(button); - } - } + // First uncheck checkbox + onView(withText(buttons[2])).perform(click()); + // Now try to check all checkboxes - solo.clickOnText(buttons[0]); - solo.clickOnText(buttons[1]); - solo.clickOnText(buttons[2]); + onView(withText(buttons[0])).perform(click()); + onView(withText(buttons[1])).perform(click()); + onView(withText(buttons[2])).perform(click()); + // Make sure that the third checkbox is unchecked assertTrue(!solo.isTextChecked(buttons[2])); - solo.clickOnText(solo.getString(R.string.confirm_label)); + onView(withText(R.string.confirm_label)).perform(click()); solo.waitForDialogToClose(1000); assertTrue(solo.waitForCondition(UserPreferences::showRewindOnCompactNotification, Timeout.getLargeTimeout())); assertTrue(solo.waitForCondition(UserPreferences::showFastForwardOnCompactNotification, Timeout.getLargeTimeout())); @@ -138,107 +127,109 @@ public class PreferencesTest { @Test public void testEnqueueAtFront() { - solo.clickOnText(solo.getString(R.string.playback_pref)); + onView(withText(R.string.playback_pref)).perform(click()); final boolean enqueueAtFront = UserPreferences.enqueueAtFront(); solo.scrollDown(); solo.scrollDown(); - solo.clickOnText(solo.getString(R.string.pref_queueAddToFront_title)); + onView(withText(R.string.pref_queueAddToFront_title)).perform(click()); assertTrue(solo.waitForCondition(() -> enqueueAtFront != UserPreferences.enqueueAtFront(), Timeout.getLargeTimeout())); - solo.clickOnText(solo.getString(R.string.pref_queueAddToFront_title)); + onView(withText(R.string.pref_queueAddToFront_title)).perform(click()); assertTrue(solo.waitForCondition(() -> enqueueAtFront == UserPreferences.enqueueAtFront(), Timeout.getLargeTimeout())); } @Test public void testHeadPhonesDisconnect() { - solo.clickOnText(solo.getString(R.string.playback_pref)); + onView(withText(R.string.playback_pref)).perform(click()); final boolean pauseOnHeadsetDisconnect = UserPreferences.isPauseOnHeadsetDisconnect(); - solo.clickOnText(solo.getString(R.string.pref_pauseOnHeadsetDisconnect_title)); + onView(withText(R.string.pref_pauseOnHeadsetDisconnect_title)).perform(click()); assertTrue(solo.waitForCondition(() -> pauseOnHeadsetDisconnect != UserPreferences.isPauseOnHeadsetDisconnect(), Timeout.getLargeTimeout())); - solo.clickOnText(solo.getString(R.string.pref_pauseOnHeadsetDisconnect_title)); + onView(withText(R.string.pref_pauseOnHeadsetDisconnect_title)).perform(click()); assertTrue(solo.waitForCondition(() -> pauseOnHeadsetDisconnect == UserPreferences.isPauseOnHeadsetDisconnect(), Timeout.getLargeTimeout())); } @Test public void testHeadPhonesReconnect() { - solo.clickOnText(solo.getString(R.string.playback_pref)); + onView(withText(R.string.playback_pref)).perform(click()); if(UserPreferences.isPauseOnHeadsetDisconnect() == false) { - solo.clickOnText(solo.getString(R.string.pref_pauseOnHeadsetDisconnect_title)); + onView(withText(R.string.pref_pauseOnHeadsetDisconnect_title)).perform(click()); assertTrue(solo.waitForCondition(UserPreferences::isPauseOnHeadsetDisconnect, Timeout.getLargeTimeout())); } final boolean unpauseOnHeadsetReconnect = UserPreferences.isUnpauseOnHeadsetReconnect(); - solo.clickOnText(solo.getString(R.string.pref_unpauseOnHeadsetReconnect_title)); + onView(withText(R.string.pref_unpauseOnHeadsetReconnect_title)).perform(click()); assertTrue(solo.waitForCondition(() -> unpauseOnHeadsetReconnect != UserPreferences.isUnpauseOnHeadsetReconnect(), Timeout.getLargeTimeout())); - solo.clickOnText(solo.getString(R.string.pref_unpauseOnHeadsetReconnect_title)); + onView(withText(R.string.pref_unpauseOnHeadsetReconnect_title)).perform(click()); assertTrue(solo.waitForCondition(() -> unpauseOnHeadsetReconnect == UserPreferences.isUnpauseOnHeadsetReconnect(), Timeout.getLargeTimeout())); } @Test public void testBluetoothReconnect() { - solo.clickOnText(solo.getString(R.string.playback_pref)); + onView(withText(R.string.playback_pref)).perform(click()); if(UserPreferences.isPauseOnHeadsetDisconnect() == false) { - solo.clickOnText(solo.getString(R.string.pref_pauseOnHeadsetDisconnect_title)); + onView(withText(R.string.pref_pauseOnHeadsetDisconnect_title)).perform(click()); assertTrue(solo.waitForCondition(UserPreferences::isPauseOnHeadsetDisconnect, Timeout.getLargeTimeout())); } final boolean unpauseOnBluetoothReconnect = UserPreferences.isUnpauseOnBluetoothReconnect(); - solo.clickOnText(solo.getString(R.string.pref_unpauseOnBluetoothReconnect_title)); + onView(withText(R.string.pref_unpauseOnBluetoothReconnect_title)).perform(click()); assertTrue(solo.waitForCondition(() -> unpauseOnBluetoothReconnect != UserPreferences.isUnpauseOnBluetoothReconnect(), Timeout.getLargeTimeout())); - solo.clickOnText(solo.getString(R.string.pref_unpauseOnBluetoothReconnect_title)); + onView(withText(R.string.pref_unpauseOnBluetoothReconnect_title)).perform(click()); assertTrue(solo.waitForCondition(() -> unpauseOnBluetoothReconnect == UserPreferences.isUnpauseOnBluetoothReconnect(), Timeout.getLargeTimeout())); } @Test public void testContinuousPlayback() { - solo.clickOnText(solo.getString(R.string.playback_pref)); + onView(withText(R.string.playback_pref)).perform(click()); final boolean continuousPlayback = UserPreferences.isFollowQueue(); solo.scrollDown(); solo.scrollDown(); - solo.clickOnText(solo.getString(R.string.pref_followQueue_title)); + onView(withText(R.string.pref_followQueue_title)).perform(click()); assertTrue(solo.waitForCondition(() -> continuousPlayback != UserPreferences.isFollowQueue(), Timeout.getLargeTimeout())); - solo.clickOnText(solo.getString(R.string.pref_followQueue_title)); + onView(withText(R.string.pref_followQueue_title)).perform(click()); assertTrue(solo.waitForCondition(() -> continuousPlayback == UserPreferences.isFollowQueue(), Timeout.getLargeTimeout())); } @Test public void testAutoDelete() { - solo.clickOnText(solo.getString(R.string.storage_pref)); + onView(withText(R.string.storage_pref)).perform(click()); final boolean autoDelete = UserPreferences.isAutoDelete(); - solo.clickOnText(solo.getString(R.string.pref_auto_delete_title)); + onView(withText(R.string.pref_auto_delete_title)).perform(click()); assertTrue(solo.waitForCondition(() -> autoDelete != UserPreferences.isAutoDelete(), Timeout.getLargeTimeout())); - solo.clickOnText(solo.getString(R.string.pref_auto_delete_title)); + onView(withText(R.string.pref_auto_delete_title)).perform(click()); assertTrue(solo.waitForCondition(() -> autoDelete == UserPreferences.isAutoDelete(), Timeout.getLargeTimeout())); } @Test public void testPlaybackSpeeds() { - clickPreference(withText(R.string.playback_pref)); - clickPreference(withText(R.string.pref_playback_speed_title)); - assertTrue(solo.searchText(res.getStringArray(R.array.playback_speed_values)[0])); + clickPreference(R.string.playback_pref); + clickPreference(R.string.media_player); + onView(withText(R.string.media_player_exoplayer)).perform(click()); + clickPreference(R.string.pref_playback_speed_title); + solo.waitForDialogToOpen(); + onView(withText("0.50")).check(matches(isDisplayed())); onView(withText(R.string.cancel_label)).perform(click()); } @Test public void testPauseForInterruptions() { - solo.clickOnText(solo.getString(R.string.playback_pref)); + onView(withText(R.string.playback_pref)).perform(click()); final boolean pauseForFocusLoss = UserPreferences.shouldPauseForFocusLoss(); - solo.clickOnText(solo.getString(R.string.pref_pausePlaybackForFocusLoss_title)); + onView(withText(R.string.pref_pausePlaybackForFocusLoss_title)).perform(click()); assertTrue(solo.waitForCondition(() -> pauseForFocusLoss != UserPreferences.shouldPauseForFocusLoss(), Timeout.getLargeTimeout())); - solo.clickOnText(solo.getString(R.string.pref_pausePlaybackForFocusLoss_title)); + onView(withText(R.string.pref_pausePlaybackForFocusLoss_title)).perform(click()); assertTrue(solo.waitForCondition(() -> pauseForFocusLoss == UserPreferences.shouldPauseForFocusLoss(), Timeout.getLargeTimeout())); } @Test public void testDisableUpdateInterval() { - solo.clickOnText(solo.getString(R.string.network_pref)); - solo.clickOnText(solo.getString(R.string.pref_autoUpdateIntervallOrTime_sum)); - solo.waitForDialogToOpen(); - solo.clickOnText(solo.getString(R.string.pref_autoUpdateIntervallOrTime_Disable)); + onView(withText(R.string.network_pref)).perform(click()); + onView(withText(R.string.pref_autoUpdateIntervallOrTime_title)).perform(click()); + onView(withText(R.string.pref_autoUpdateIntervallOrTime_Disable)).perform(click()); assertTrue(solo.waitForCondition(() -> UserPreferences.getUpdateInterval() == 0, 1000)); } @Test public void testSetUpdateInterval() { - clickPreference(withText(R.string.network_pref)); - clickPreference(withText(R.string.pref_autoUpdateIntervallOrTime_title)); + clickPreference(R.string.network_pref); + clickPreference(R.string.pref_autoUpdateIntervallOrTime_title); onView(withText(R.string.pref_autoUpdateIntervallOrTime_Interval)).perform(click()); String search = "12 " + solo.getString(R.string.pref_update_interval_hours_plural); onView(withText(search)).perform(click()); @@ -246,42 +237,32 @@ public class PreferencesTest { TimeUnit.HOURS.toMillis(12), Timeout.getLargeTimeout())); } - @Test - public void testMobileUpdates() { - clickPreference(withText(R.string.network_pref)); - final boolean mobileUpdates = UserPreferences.isAllowMobileUpdate(); - clickPreference(withText(R.string.pref_mobileUpdate_title)); - assertTrue(solo.waitForCondition(() -> mobileUpdates != UserPreferences.isAllowMobileUpdate(), Timeout.getLargeTimeout())); - clickPreference(withText(R.string.pref_mobileUpdate_title)); - assertTrue(solo.waitForCondition(() -> mobileUpdates == UserPreferences.isAllowMobileUpdate(), Timeout.getLargeTimeout())); - } - @Test public void testSetSequentialDownload() { - clickPreference(withText(R.string.network_pref)); - clickPreference(withText(R.string.pref_parallel_downloads_title)); + clickPreference(R.string.network_pref); + clickPreference(R.string.pref_parallel_downloads_title); solo.waitForDialogToOpen(); solo.clearEditText(0); solo.enterText(0, "1"); - solo.clickOnText(solo.getString(android.R.string.ok)); + onView(withText(android.R.string.ok)).perform(click()); assertTrue(solo.waitForCondition(() -> UserPreferences.getParallelDownloads() == 1, Timeout.getLargeTimeout())); } @Test public void testSetParallelDownloads() { - clickPreference(withText(R.string.network_pref)); - clickPreference(withText(R.string.pref_parallel_downloads_title)); + clickPreference(R.string.network_pref); + clickPreference(R.string.pref_parallel_downloads_title); solo.waitForDialogToOpen(); solo.clearEditText(0); solo.enterText(0, "10"); - solo.clickOnText(solo.getString(android.R.string.ok)); + onView(withText(android.R.string.ok)).perform(click()); assertTrue(solo.waitForCondition(() -> UserPreferences.getParallelDownloads() == 10, Timeout.getLargeTimeout())); } @Test public void testSetParallelDownloadsInvalidInput() { - clickPreference(withText(R.string.network_pref)); - clickPreference(withText(R.string.pref_parallel_downloads_title)); + clickPreference(R.string.network_pref); + clickPreference(R.string.pref_parallel_downloads_title); solo.waitForDialogToOpen(); solo.clearEditText(0); solo.enterText(0, "0"); @@ -297,9 +278,9 @@ public class PreferencesTest { String[] values = res.getStringArray(R.array.episode_cache_size_values); String entry = entries[entries.length/2]; final int value = Integer.valueOf(values[values.length/2]); - clickPreference(withText(R.string.network_pref)); - clickPreference(withText(R.string.pref_automatic_download_title)); - clickPreference(withText(R.string.pref_episode_cache_title)); + clickPreference(R.string.network_pref); + clickPreference(R.string.pref_automatic_download_title); + clickPreference(R.string.pref_episode_cache_title); solo.waitForDialogToOpen(); solo.clickOnText(entry); assertTrue(solo.waitForCondition(() -> UserPreferences.getEpisodeCacheSize() == value, Timeout.getLargeTimeout())); @@ -312,12 +293,11 @@ public class PreferencesTest { String minEntry = entries[0]; final int minValue = Integer.valueOf(values[0]); - clickPreference(withText(R.string.network_pref)); - clickPreference(withText(R.string.pref_automatic_download_title)); - clickPreference(withText(R.string.pref_episode_cache_title)); - solo.waitForDialogToOpen(1000); + clickPreference(R.string.network_pref); + clickPreference(R.string.pref_automatic_download_title); + clickPreference(R.string.pref_episode_cache_title); solo.scrollUp(); - solo.clickOnText(minEntry); + onView(withText(minEntry)).perform(click()); assertTrue(solo.waitForCondition(() -> UserPreferences.getEpisodeCacheSize() == minValue, Timeout.getLargeTimeout())); } @@ -327,46 +307,44 @@ public class PreferencesTest { String[] values = res.getStringArray(R.array.episode_cache_size_values); String maxEntry = entries[entries.length-1]; final int maxValue = Integer.valueOf(values[values.length-1]); - solo.clickOnText(solo.getString(R.string.network_pref)); - solo.clickOnText(solo.getString(R.string.pref_automatic_download_title)); - solo.waitForText(solo.getString(R.string.pref_automatic_download_title)); - solo.clickOnText(solo.getString(R.string.pref_episode_cache_title)); - solo.waitForDialogToOpen(); - solo.clickOnText(maxEntry); + onView(withText(R.string.network_pref)).perform(click()); + onView(withText(R.string.pref_automatic_download_title)).perform(click()); + onView(withText(R.string.pref_episode_cache_title)).perform(click()); + onView(withText(maxEntry)).perform(click()); assertTrue(solo.waitForCondition(() -> UserPreferences.getEpisodeCacheSize() == maxValue, Timeout.getLargeTimeout())); } @Test public void testAutomaticDownload() { final boolean automaticDownload = UserPreferences.isEnableAutodownload(); - clickPreference(withText(R.string.network_pref)); - clickPreference(withText(R.string.pref_automatic_download_title)); - clickPreference(withText(R.string.pref_automatic_download_title)); + clickPreference(R.string.network_pref); + clickPreference(R.string.pref_automatic_download_title); + clickPreference(R.string.pref_automatic_download_title); assertTrue(solo.waitForCondition(() -> automaticDownload != UserPreferences.isEnableAutodownload(), Timeout.getLargeTimeout())); if(UserPreferences.isEnableAutodownload() == false) { - clickPreference(withText(R.string.pref_automatic_download_title)); + clickPreference(R.string.pref_automatic_download_title); } assertTrue(solo.waitForCondition(() -> UserPreferences.isEnableAutodownload() == true, Timeout.getLargeTimeout())); final boolean enableAutodownloadOnBattery = UserPreferences.isEnableAutodownloadOnBattery(); - solo.clickOnText(solo.getString(R.string.pref_automatic_download_on_battery_title)); + onView(withText(R.string.pref_automatic_download_on_battery_title)).perform(click()); assertTrue(solo.waitForCondition(() -> enableAutodownloadOnBattery != UserPreferences.isEnableAutodownloadOnBattery(), Timeout.getLargeTimeout())); - solo.clickOnText(solo.getString(R.string.pref_automatic_download_on_battery_title)); + onView(withText(R.string.pref_automatic_download_on_battery_title)).perform(click()); assertTrue(solo.waitForCondition(() -> enableAutodownloadOnBattery == UserPreferences.isEnableAutodownloadOnBattery(), Timeout.getLargeTimeout())); final boolean enableWifiFilter = UserPreferences.isEnableAutodownloadWifiFilter(); - solo.clickOnText(solo.getString(R.string.pref_autodl_wifi_filter_title)); + onView(withText(R.string.pref_autodl_wifi_filter_title)).perform(click()); assertTrue(solo.waitForCondition(() -> enableWifiFilter != UserPreferences.isEnableAutodownloadWifiFilter(), Timeout.getLargeTimeout())); - solo.clickOnText(solo.getString(R.string.pref_autodl_wifi_filter_title)); + onView(withText(R.string.pref_autodl_wifi_filter_title)).perform(click()); assertTrue(solo.waitForCondition(() -> enableWifiFilter == UserPreferences.isEnableAutodownloadWifiFilter(), Timeout.getLargeTimeout())); } @Test public void testEpisodeCleanupQueueOnly() { - solo.clickOnText(solo.getString(R.string.network_pref)); - solo.clickOnText(solo.getString(R.string.pref_automatic_download_title)); - solo.clickOnText(solo.getString(R.string.pref_episode_cleanup_title)); + onView(withText(R.string.network_pref)).perform(click()); + onView(withText(R.string.pref_automatic_download_title)).perform(click()); + onView(withText(R.string.pref_episode_cleanup_title)).perform(click()); solo.waitForText(solo.getString(R.string.episode_cleanup_queue_removal)); - solo.clickOnText(solo.getString(R.string.episode_cleanup_queue_removal)); + onView(withText(R.string.episode_cleanup_queue_removal)).perform(click()); assertTrue(solo.waitForCondition(() -> { EpisodeCleanupAlgorithm alg = UserPreferences.getEpisodeCleanupAlgorithm(); return alg instanceof APQueueCleanupAlgorithm; @@ -376,11 +354,11 @@ public class PreferencesTest { @Test public void testEpisodeCleanupNeverAlg() { - solo.clickOnText(solo.getString(R.string.network_pref)); - solo.clickOnText(solo.getString(R.string.pref_automatic_download_title)); - solo.clickOnText(solo.getString(R.string.pref_episode_cleanup_title)); + onView(withText(R.string.network_pref)).perform(click()); + onView(withText(R.string.pref_automatic_download_title)).perform(click()); + onView(withText(R.string.pref_episode_cleanup_title)).perform(click()); solo.waitForText(solo.getString(R.string.episode_cleanup_never)); - solo.clickOnText(solo.getString(R.string.episode_cleanup_never)); + onView(withText(R.string.episode_cleanup_never)).perform(click()); assertTrue(solo.waitForCondition(() -> { EpisodeCleanupAlgorithm alg = UserPreferences.getEpisodeCleanupAlgorithm(); return alg instanceof APNullCleanupAlgorithm; @@ -390,11 +368,11 @@ public class PreferencesTest { @Test public void testEpisodeCleanupClassic() { - solo.clickOnText(solo.getString(R.string.network_pref)); - solo.clickOnText(solo.getString(R.string.pref_automatic_download_title)); - solo.clickOnText(solo.getString(R.string.pref_episode_cleanup_title)); + onView(withText(R.string.network_pref)).perform(click()); + onView(withText(R.string.pref_automatic_download_title)).perform(click()); + onView(withText(R.string.pref_episode_cleanup_title)).perform(click()); solo.waitForText(solo.getString(R.string.episode_cleanup_after_listening)); - solo.clickOnText(solo.getString(R.string.episode_cleanup_after_listening)); + onView(withText(R.string.episode_cleanup_after_listening)).perform(click()); assertTrue(solo.waitForCondition(() -> { EpisodeCleanupAlgorithm alg = UserPreferences.getEpisodeCleanupAlgorithm(); if (alg instanceof APCleanupAlgorithm) { @@ -408,9 +386,9 @@ public class PreferencesTest { @Test public void testEpisodeCleanupNumDays() { - clickPreference(withText(R.string.network_pref)); - clickPreference(withText(R.string.pref_automatic_download_title)); - clickPreference(withText(R.string.pref_episode_cleanup_title)); + clickPreference(R.string.network_pref); + clickPreference(R.string.pref_automatic_download_title); + clickPreference(R.string.pref_episode_cleanup_title); solo.waitForDialogToOpen(); String search = res.getQuantityString(R.plurals.episode_cleanup_days_after_listening, 5, 5); onView(withText(search)).perform(click()); @@ -430,9 +408,8 @@ public class PreferencesTest { int seconds = UserPreferences.getRewindSecs(); int deltas[] = res.getIntArray(R.array.seek_delta_values); - clickPreference(withText(R.string.playback_pref)); - clickPreference(withText(R.string.pref_rewind)); - solo.waitForDialogToOpen(); + clickPreference(R.string.playback_pref); + clickPreference(R.string.pref_rewind); int currentIndex = Arrays.binarySearch(deltas, seconds); assertTrue(currentIndex >= 0 && currentIndex < deltas.length); // found? @@ -442,20 +419,18 @@ public class PreferencesTest { onView(withText(String.valueOf(deltas[newIndex]) + " seconds")).perform(click()); onView(withText("Confirm")).perform(click()); - solo.waitForDialogToClose(); assertTrue(solo.waitForCondition(() -> UserPreferences.getRewindSecs() == deltas[newIndex], Timeout.getLargeTimeout())); } @Test public void testFastForwardChange() { - clickPreference(withText(R.string.playback_pref)); + clickPreference(R.string.playback_pref); for (int i = 2; i > 0; i--) { // repeat twice to catch any error where fastforward is tracking rewind int seconds = UserPreferences.getFastForwardSecs(); int deltas[] = res.getIntArray(R.array.seek_delta_values); - clickPreference(withText(R.string.pref_fast_forward)); - solo.waitForDialogToOpen(); + clickPreference(R.string.pref_fast_forward); int currentIndex = Arrays.binarySearch(deltas, seconds); assertTrue(currentIndex >= 0 && currentIndex < deltas.length); // found? @@ -463,7 +438,7 @@ public class PreferencesTest { // Find next value (wrapping around to next) int newIndex = (currentIndex + 1) % deltas.length; - onView(withText(String.valueOf(deltas[newIndex]) + " seconds")).perform(click()); + onView(withText(deltas[newIndex] + " seconds")).perform(click()); onView(withText("Confirm")).perform(click()); solo.waitForDialogToClose(); @@ -474,33 +449,27 @@ public class PreferencesTest { @Test public void testBackButtonBehaviorGoToPageSelector() { - clickPreference(withText(R.string.user_interface_label)); - clickPreference(withText(R.string.pref_back_button_behavior_title)); - solo.waitForDialogToOpen(); - solo.clickOnText(solo.getString(R.string.back_button_go_to_page)); - solo.waitForDialogToOpen(); - solo.clickOnText(solo.getString(R.string.queue_label)); - solo.clickOnText(solo.getString(R.string.confirm_label)); + clickPreference(R.string.user_interface_label); + clickPreference(R.string.pref_back_button_behavior_title); + onView(withText(R.string.back_button_go_to_page)).perform(click()); + onView(withText(R.string.queue_label)).perform(click()); + onView(withText(R.string.confirm_label)).perform(click()); assertTrue(solo.waitForCondition(() -> UserPreferences.getBackButtonBehavior() == UserPreferences.BackButtonBehavior.GO_TO_PAGE, Timeout.getLargeTimeout())); assertTrue(solo.waitForCondition(() -> UserPreferences.getBackButtonGoToPage().equals(QueueFragment.TAG), Timeout.getLargeTimeout())); - clickPreference(withText(R.string.pref_back_button_behavior_title)); - solo.waitForDialogToOpen(); - solo.clickOnText(solo.getString(R.string.back_button_go_to_page)); - solo.waitForDialogToOpen(); - solo.clickOnText(solo.getString(R.string.episodes_label)); - solo.clickOnText(solo.getString(R.string.confirm_label)); + clickPreference(R.string.pref_back_button_behavior_title); + onView(withText(R.string.back_button_go_to_page)).perform(click()); + onView(withText(R.string.episodes_label)).perform(click()); + onView(withText(R.string.confirm_label)).perform(click()); assertTrue(solo.waitForCondition(() -> UserPreferences.getBackButtonBehavior() == UserPreferences.BackButtonBehavior.GO_TO_PAGE, Timeout.getLargeTimeout())); assertTrue(solo.waitForCondition(() -> UserPreferences.getBackButtonGoToPage().equals(EpisodesFragment.TAG), Timeout.getLargeTimeout())); - clickPreference(withText(R.string.pref_back_button_behavior_title)); - solo.waitForDialogToOpen(); - solo.clickOnText(solo.getString(R.string.back_button_go_to_page)); - solo.waitForDialogToOpen(); - solo.clickOnText(solo.getString(R.string.subscriptions_label)); - solo.clickOnText(solo.getString(R.string.confirm_label)); + clickPreference(R.string.pref_back_button_behavior_title); + onView(withText(R.string.back_button_go_to_page)).perform(click()); + onView(withText(R.string.subscriptions_label)).perform(click()); + onView(withText(R.string.confirm_label)).perform(click()); assertTrue(solo.waitForCondition(() -> UserPreferences.getBackButtonBehavior() == UserPreferences.BackButtonBehavior.GO_TO_PAGE, Timeout.getLargeTimeout())); assertTrue(solo.waitForCondition(() -> UserPreferences.getBackButtonGoToPage().equals(SubscriptionFragment.TAG), @@ -509,20 +478,15 @@ public class PreferencesTest { @Test public void testDeleteRemovesFromQueue() { - clickPreference(withText(R.string.storage_pref)); + clickPreference(R.string.storage_pref); if (!UserPreferences.shouldDeleteRemoveFromQueue()) { - clickPreference(withText(R.string.pref_delete_removes_from_queue_title)); + clickPreference(R.string.pref_delete_removes_from_queue_title); assertTrue(solo.waitForCondition(UserPreferences::shouldDeleteRemoveFromQueue, Timeout.getLargeTimeout())); } final boolean deleteRemovesFromQueue = UserPreferences.shouldDeleteRemoveFromQueue(); - solo.clickOnText(solo.getString(R.string.pref_delete_removes_from_queue_title)); + onView(withText(R.string.pref_delete_removes_from_queue_title)).perform(click()); assertTrue(solo.waitForCondition(() -> deleteRemovesFromQueue != UserPreferences.shouldDeleteRemoveFromQueue(), Timeout.getLargeTimeout())); - solo.clickOnText(solo.getString(R.string.pref_delete_removes_from_queue_title)); + onView(withText(R.string.pref_delete_removes_from_queue_title)).perform(click()); assertTrue(solo.waitForCondition(() -> deleteRemovesFromQueue == UserPreferences.shouldDeleteRemoveFromQueue(), Timeout.getLargeTimeout())); } - - private void clickPreference(Matcher matcher) { - onView(withId(R.id.list)) - .perform(RecyclerViewActions.actionOnItem(hasDescendant(matcher), click())); - } } diff --git a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java index 263790c2f..20c54cb15 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java @@ -4,8 +4,6 @@ import android.content.Context; import android.graphics.Bitmap; import android.util.Log; -import junit.framework.Assert; - import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -30,6 +28,7 @@ import de.danoeh.antennapod.fragment.ExternalPlayerFragment; import de.test.antennapod.util.service.download.HTTPBin; import de.test.antennapod.util.syndication.feedgenerator.RSS2Generator; import org.greenrobot.eventbus.EventBus; +import org.junit.Assert; /** * Utility methods for UI tests. @@ -39,8 +38,6 @@ class UITestUtils { private static final String TAG = UITestUtils.class.getSimpleName(); - private static final String DATA_FOLDER = "test/UITestUtils"; - private static final int NUM_FEEDS = 5; private static final int NUM_ITEMS_PER_FEED = 10; @@ -61,7 +58,7 @@ class UITestUtils { public void setup() throws IOException { - destDir = context.getExternalFilesDir(DATA_FOLDER); + destDir = new File(context.getFilesDir(), "test/UITestUtils"); destDir.mkdir(); hostedFeedDir = new File(destDir, "hostedFeeds"); hostedFeedDir.mkdir(); diff --git a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtilsTest.java b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtilsTest.java index 45ba472ff..7555bb69a 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtilsTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtilsTest.java @@ -1,35 +1,44 @@ package de.test.antennapod.ui; -import android.test.InstrumentationTestCase; - import java.io.File; import java.net.HttpURLConnection; import java.net.URL; import java.util.List; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.filters.MediumTest; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; /** * Test for the UITestUtils. Makes sure that all URLs are reachable and that the class does not cause any crashes. */ -public class UITestUtilsTest extends InstrumentationTestCase { +@MediumTest +public class UITestUtilsTest { private UITestUtils uiTestUtils; - @Override - protected void setUp() throws Exception { - super.setUp(); - uiTestUtils = new UITestUtils(getInstrumentation().getTargetContext()); + @Before + public void setUp() throws Exception { + uiTestUtils = new UITestUtils(InstrumentationRegistry.getTargetContext()); uiTestUtils.setup(); } - @Override + @After public void tearDown() throws Exception { - super.tearDown(); uiTestUtils.tearDown(); } + @Test public void testAddHostedFeeds() throws Exception { uiTestUtils.addHostedFeedData(); final List feeds = uiTestUtils.hostedFeeds; @@ -46,7 +55,7 @@ public class UITestUtilsTest extends InstrumentationTestCase { } } - private void testUrlReachable(String strUtl) throws Exception { + public void testUrlReachable(String strUtl) throws Exception { URL url = new URL(strUtl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); @@ -78,10 +87,12 @@ public class UITestUtilsTest extends InstrumentationTestCase { } } + @Test public void testAddLocalFeedDataNoDownload() throws Exception { addLocalFeedDataCheck(false); } + @Test public void testAddLocalFeedDataDownload() throws Exception { addLocalFeedDataCheck(true); } diff --git a/app/src/androidTest/java/de/test/antennapod/ui/VideoplayerActivityTest.java b/app/src/androidTest/java/de/test/antennapod/ui/VideoplayerActivityTest.java index da6f07cab..c4dd032d7 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/VideoplayerActivityTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/VideoplayerActivityTest.java @@ -1,38 +1,36 @@ package de.test.antennapod.ui; -import android.test.ActivityInstrumentationTestCase2; - -import com.robotium.solo.Solo; +import android.content.Intent; +import android.support.test.filters.MediumTest; +import android.support.test.rule.ActivityTestRule; +import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.VideoplayerActivity; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; /** * Test class for VideoplayerActivity */ -public class VideoplayerActivityTest extends ActivityInstrumentationTestCase2 { - - private Solo solo; - - public VideoplayerActivityTest() { - super(VideoplayerActivity.class); - } - - @Override - public void setUp() throws Exception { - super.setUp(); - solo = new Solo(getInstrumentation(), getActivity()); - } +@MediumTest +@Ignore +public class VideoplayerActivityTest { - @Override - public void tearDown() throws Exception { - solo.finishOpenedActivities(); - super.tearDown(); - } + @Rule + public ActivityTestRule activityTestRule = new ActivityTestRule<>(VideoplayerActivity.class, false, false); /** * Test if activity can be started. */ + @Test public void testStartActivity() throws Exception { - solo.waitForActivity(VideoplayerActivity.class); + activityTestRule.launchActivity(new Intent()); + onView(withId(R.id.videoframe)).check(matches(isDisplayed())); } } diff --git a/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java b/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java index a36b3b65a..ac98d2802 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java @@ -1,14 +1,22 @@ package de.test.antennapod.util; -import android.test.AndroidTestCase; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.filters.SmallTest; import android.text.TextUtils; import java.io.File; import java.io.IOException; import de.danoeh.antennapod.core.util.FileNameGenerator; +import org.junit.After; +import org.junit.Test; -public class FilenameGeneratorTest extends AndroidTestCase { +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@SmallTest +public class FilenameGeneratorTest { private static final String VALID1 = "abc abc"; private static final String INVALID1 = "ab/c: chapters, String shownotes, int duration) { @@ -40,6 +48,7 @@ public class TimelineTest extends InstrumentationTestCase { return media; } + @Test public void testProcessShownotesAddTimecodeHHMMSSNoChapters() throws Exception { final String timeStr = "10:11:12"; final long time = 3600 * 1000 * 10 + 60 * 1000 * 11 + 12 * 1000; @@ -50,6 +59,7 @@ public class TimelineTest extends InstrumentationTestCase { checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } + @Test public void testProcessShownotesAddTimecodeHHMMSSMoreThen24HoursNoChapters() throws Exception { final String timeStr = "25:00:00"; final long time = 25 * 60 * 60 * 1000; @@ -60,6 +70,7 @@ public class TimelineTest extends InstrumentationTestCase { checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } + @Test public void testProcessShownotesAddTimecodeHHMMNoChapters() throws Exception { final String timeStr = "10:11"; final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; @@ -70,6 +81,7 @@ public class TimelineTest extends InstrumentationTestCase { checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } + @Test public void testProcessShownotesAddTimecodeMMSSNoChapters() throws Exception { final String timeStr = "10:11"; final long time = 10 * 60 * 1000 + 11 * 1000; @@ -80,6 +92,7 @@ public class TimelineTest extends InstrumentationTestCase { checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } + @Test public void testProcessShownotesAddTimecodeHMMSSNoChapters() throws Exception { final String timeStr = "2:11:12"; final long time = 2 * 60 * 60 * 1000 + 11 * 60 * 1000 + 12 * 1000; @@ -90,6 +103,7 @@ public class TimelineTest extends InstrumentationTestCase { checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } + @Test public void testProcessShownotesAddTimecodeMSSNoChapters() throws Exception { final String timeStr = "1:12"; final long time = 60 * 1000 + 12 * 1000; @@ -100,6 +114,7 @@ public class TimelineTest extends InstrumentationTestCase { checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } + @Test public void testProcessShownotesAddTimecodeMultipleFormatsNoChapters() throws Exception { final String[] timeStrings = new String[]{ "10:12", "1:10:12" }; @@ -109,6 +124,7 @@ public class TimelineTest extends InstrumentationTestCase { checkLinkCorrect(res, new long[]{ 10 * 60 * 1000 + 12 * 1000, 60 * 60 * 1000 + 10 * 60 * 1000 + 12 * 1000 }, timeStrings); } + @Test public void testProcessShownotesAddTimecodeMultipleShortFormatNoChapters() throws Exception { // One of these timecodes fits as HH:MM and one does not so both should be parsed as MM:SS. @@ -120,6 +136,7 @@ public class TimelineTest extends InstrumentationTestCase { checkLinkCorrect(res, new long[]{ 10 * 60 * 1000 + 12 * 1000, 2 * 60 * 1000 + 12 * 1000 }, timeStrings); } + @Test public void testProcessShownotesAddTimecodeParentheses() throws Exception { final String timeStr = "10:11"; final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; @@ -130,6 +147,7 @@ public class TimelineTest extends InstrumentationTestCase { checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } + @Test public void testProcessShownotesAddTimecodeBrackets() throws Exception { final String timeStr = "10:11"; final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; @@ -140,6 +158,7 @@ public class TimelineTest extends InstrumentationTestCase { checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } + @Test public void testProcessShownotesAddTimecodeAngleBrackets() throws Exception { final String timeStr = "10:11"; final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; @@ -150,6 +169,7 @@ public class TimelineTest extends InstrumentationTestCase { checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } + @Test public void testProcessShownotesAndInvalidTimecode() throws Exception { final String[] timeStrs = new String[] {"2:1", "0:0", "000", "00", "00:000"}; @@ -183,6 +203,7 @@ public class TimelineTest extends InstrumentationTestCase { assertEquals(timecodes.length, countedLinks); } + @Test public void testIsTimecodeLink() throws Exception { assertFalse(Timeline.isTimecodeLink(null)); assertFalse(Timeline.isTimecodeLink("http://antennapod/timecode/123123")); @@ -193,6 +214,7 @@ public class TimelineTest extends InstrumentationTestCase { assertTrue(Timeline.isTimecodeLink("antennapod://timecode/1")); } + @Test public void testGetTimecodeLinkTime() throws Exception { assertEquals(-1, Timeline.getTimecodeLinkTime(null)); assertEquals(-1, Timeline.getTimecodeLinkTime("http://timecode/123")); diff --git a/app/src/androidTest/java/de/test/antennapod/util/service/download/HTTPBin.java b/app/src/androidTest/java/de/test/antennapod/util/service/download/HTTPBin.java index cde93fd7e..3d8417bf6 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/service/download/HTTPBin.java +++ b/app/src/androidTest/java/de/test/antennapod/util/service/download/HTTPBin.java @@ -3,6 +3,7 @@ package de.test.antennapod.util.service.download; import android.util.Base64; import android.util.Log; +import fi.iki.elonen.NanoHTTPD; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; diff --git a/app/src/androidTest/java/de/test/antennapod/util/service/download/NanoHTTPD.java b/app/src/androidTest/java/de/test/antennapod/util/service/download/NanoHTTPD.java deleted file mode 100644 index 8d9cedbd1..000000000 --- a/app/src/androidTest/java/de/test/antennapod/util/service/download/NanoHTTPD.java +++ /dev/null @@ -1,1433 +0,0 @@ -package de.test.antennapod.util.service.download; - -import android.support.v4.util.ArrayMap; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.PushbackInputStream; -import java.io.RandomAccessFile; -import java.io.UnsupportedEncodingException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.net.SocketTimeoutException; -import java.net.URLDecoder; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.TimeZone; - -/** - * A simple, tiny, nicely embeddable HTTP server in Java - *

- *

- * NanoHTTPD - *

Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, 2010 by Konstantinos Togias

- *

- *

- * Features + limitations: - *

    - *

    - *

  • Only one Java file
  • - *
  • Java 5 compatible
  • - *
  • Released as open source, Modified BSD licence
  • - *
  • No fixed config files, logging, authorization etc. (Implement yourself if you need them.)
  • - *
  • Supports parameter parsing of GET and POST methods (+ rudimentary PUT support in 1.25)
  • - *
  • Supports both dynamic content and file serving
  • - *
  • Supports file upload (since version 1.2, 2010)
  • - *
  • Supports partial content (streaming)
  • - *
  • Supports ETags
  • - *
  • Never caches anything
  • - *
  • Doesn't limit bandwidth, request time or simultaneous connections
  • - *
  • Default code serves files and shows all HTTP parameters and headers
  • - *
  • File server supports directory listing, index.html and index.htm
  • - *
  • File server supports partial content (streaming)
  • - *
  • File server supports ETags
  • - *
  • File server does the 301 redirection trick for directories without '/'
  • - *
  • File server supports simple skipping for files (continue download)
  • - *
  • File server serves also very long files without memory overhead
  • - *
  • Contains a built-in list of most common mime types
  • - *
  • All header names are converted lowercase so they don't vary between browsers/clients
  • - *

    - *

- *

- *

- * How to use: - *

    - *

    - *

  • Subclass and implement serve() and embed to your own program
  • - *

    - *

- *

- * See the separate "LICENSE.md" file for the distribution license (Modified BSD licence) - */ -public abstract class NanoHTTPD { - /** - * Maximum time to wait on Socket.getInputStream().read() (in milliseconds) - * This is required as the Keep-Alive HTTP connections would otherwise - * block the socket reading thread forever (or as long the browser is open). - */ - private static final int SOCKET_READ_TIMEOUT = 5000; - /** - * Common mime type for dynamic content: plain text - */ - private static final String MIME_PLAINTEXT = "text/plain"; - /** - * Common mime type for dynamic content: html - */ - private static final String MIME_HTML = "text/html"; - /** - * Pseudo-Parameter to use to store the actual query string in the parameters map for later re-processing. - */ - private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING"; - private final String hostname; - private final int myPort; - private ServerSocket myServerSocket; - private final Set openConnections = new HashSet<>(); - private Thread myThread; - /** - * Pluggable strategy for asynchronously executing requests. - */ - private AsyncRunner asyncRunner; - /** - * Pluggable strategy for creating and cleaning up temporary files. - */ - private TempFileManagerFactory tempFileManagerFactory; - - /** - * Constructs an HTTP server on given port. - */ - NanoHTTPD(int port) { - this(null, port); - } - - /** - * Constructs an HTTP server on given hostname and port. - */ - private NanoHTTPD(String hostname, int port) { - this.hostname = hostname; - this.myPort = port; - setTempFileManagerFactory(new DefaultTempFileManagerFactory()); - setAsyncRunner(new DefaultAsyncRunner()); - } - - private static final void safeClose(Closeable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (IOException e) { - } - } - } - - private static final void safeClose(Socket closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (IOException e) { - } - } - } - - private static final void safeClose(ServerSocket closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (IOException e) { - } - } - } - - /** - * Start the server. - * - * @throws IOException if the socket is in use. - */ - public void start() throws IOException { - myServerSocket = new ServerSocket(); - myServerSocket.bind((hostname != null) ? new InetSocketAddress(hostname, myPort) : new InetSocketAddress(myPort)); - - myThread = new Thread(() -> { - do { - try { - final Socket finalAccept = myServerSocket.accept(); - registerConnection(finalAccept); - finalAccept.setSoTimeout(SOCKET_READ_TIMEOUT); - final InputStream inputStream = finalAccept.getInputStream(); - asyncRunner.exec(() -> { - OutputStream outputStream = null; - try { - outputStream = finalAccept.getOutputStream(); - TempFileManager tempFileManager = tempFileManagerFactory.create(); - HTTPSession session = new HTTPSession(tempFileManager, inputStream, outputStream, finalAccept.getInetAddress()); - while (!finalAccept.isClosed()) { - session.execute(); - } - } catch (Exception e) { - // When the socket is closed by the client, we throw our own SocketException - // to break the "keep alive" loop above. - if (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage()))) { - e.printStackTrace(); - } - } finally { - safeClose(outputStream); - safeClose(inputStream); - safeClose(finalAccept); - unRegisterConnection(finalAccept); - } - }); - } catch (IOException e) { - } - } while (!myServerSocket.isClosed()); - }); - myThread.setDaemon(true); - myThread.setName("NanoHttpd Main Listener"); - myThread.start(); - } - - /** - * Stop the server. - */ - public void stop() { - try { - safeClose(myServerSocket); - closeAllConnections(); - if (myThread != null) { - myThread.join(); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Registers that a new connection has been set up. - * - * @param socket the {@link Socket} for the connection. - */ - private synchronized void registerConnection(Socket socket) { - openConnections.add(socket); - } - - /** - * Registers that a connection has been closed - * - * @param socket - * the {@link Socket} for the connection. - */ - private synchronized void unRegisterConnection(Socket socket) { - openConnections.remove(socket); - } - - /** - * Forcibly closes all connections that are open. - */ - private synchronized void closeAllConnections() { - for (Socket socket : openConnections) { - safeClose(socket); - } - } - - public final int getListeningPort() { - return myServerSocket == null ? -1 : myServerSocket.getLocalPort(); - } - - private boolean wasStarted() { - return myServerSocket != null && myThread != null; - } - - public final boolean isAlive() { - return wasStarted() && !myServerSocket.isClosed() && myThread.isAlive(); - } - - /** - * Override this to customize the server. - *

- *

- * (By default, this delegates to serveFile() and allows directory listing.) - * - * @param uri Percent-decoded URI without parameters, for example "/index.cgi" - * @param method "GET", "POST" etc. - * @param parms Parsed, percent decoded parameters from URI and, in case of POST, data. - * @param headers Header entries, percent decoded - * @return HTTP response, see class Response for details - */ - @Deprecated - public Response serve(String uri, Method method, Map headers, Map parms, - Map files) { - return new Response(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found"); - } - - /** - * Override this to customize the server. - *

- *

- * (By default, this delegates to serveFile() and allows directory listing.) - * - * @param session The HTTP session - * @return HTTP response, see class Response for details - */ - Response serve(IHTTPSession session) { - Map files = new ArrayMap<>(); - Method method = session.getMethod(); - if (Method.PUT.equals(method) || Method.POST.equals(method)) { - try { - session.parseBody(files); - } catch (IOException ioe) { - return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); - } catch (ResponseException re) { - return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage()); - } - } - - Map parms = session.getParms(); - parms.put(QUERY_STRING_PARAMETER, session.getQueryParameterString()); - return serve(session.getUri(), method, session.getHeaders(), parms, files); - } - - /** - * Decode percent encoded String values. - * - * @param str the percent encoded String - * @return expanded form of the input, for example "foo%20bar" becomes "foo bar" - */ - private String decodePercent(String str) { - String decoded = null; - try { - decoded = URLDecoder.decode(str, "UTF8"); - } catch (UnsupportedEncodingException ignored) { - } - return decoded; - } - - /** - * Decode parameters from a URL, handing the case where a single parameter name might have been - * supplied several times, by return lists of values. In general these lists will contain a single - * element. - * - * @param parms original NanoHttpd parameters values, as passed to the serve() method. - * @return a map of String (parameter name) to List<String> (a list of the values supplied). - */ - protected Map> decodeParameters(Map parms) { - return this.decodeParameters(parms.get(QUERY_STRING_PARAMETER)); - } - - /** - * Decode parameters from a URL, handing the case where a single parameter name might have been - * supplied several times, by return lists of values. In general these lists will contain a single - * element. - * - * @param queryString a query string pulled from the URL. - * @return a map of String (parameter name) to List<String> (a list of the values supplied). - */ - private Map> decodeParameters(String queryString) { - Map> parms = new ArrayMap<>(); - if (queryString != null) { - StringTokenizer st = new StringTokenizer(queryString, "&"); - while (st.hasMoreTokens()) { - String e = st.nextToken(); - int sep = e.indexOf('='); - String propertyName = (sep >= 0) ? decodePercent(e.substring(0, sep)).trim() : decodePercent(e).trim(); - if (!parms.containsKey(propertyName)) { - parms.put(propertyName, new ArrayList<>()); - } - String propertyValue = (sep >= 0) ? decodePercent(e.substring(sep + 1)) : null; - if (propertyValue != null) { - parms.get(propertyName).add(propertyValue); - } - } - } - return parms; - } - - // ------------------------------------------------------------------------------- // - // - // Threading Strategy. - // - // ------------------------------------------------------------------------------- // - - /** - * Pluggable strategy for asynchronously executing requests. - * - * @param asyncRunner new strategy for handling threads. - */ - private void setAsyncRunner(AsyncRunner asyncRunner) { - this.asyncRunner = asyncRunner; - } - - // ------------------------------------------------------------------------------- // - // - // Temp file handling strategy. - // - // ------------------------------------------------------------------------------- // - - /** - * Pluggable strategy for creating and cleaning up temporary files. - * - * @param tempFileManagerFactory new strategy for handling temp files. - */ - private void setTempFileManagerFactory(TempFileManagerFactory tempFileManagerFactory) { - this.tempFileManagerFactory = tempFileManagerFactory; - } - - /** - * HTTP Request methods, with the ability to decode a String back to its enum value. - */ - public enum Method { - GET, PUT, POST, DELETE, HEAD, OPTIONS; - - static Method lookup(String method) { - for (Method m : Method.values()) { - if (m.toString().equalsIgnoreCase(method)) { - return m; - } - } - return null; - } - } - - /** - * Pluggable strategy for asynchronously executing requests. - */ - public interface AsyncRunner { - void exec(Runnable code); - } - - /** - * Factory to create temp file managers. - */ - public interface TempFileManagerFactory { - TempFileManager create(); - } - - // ------------------------------------------------------------------------------- // - - /** - * Temp file manager. - *

- *

Temp file managers are created 1-to-1 with incoming requests, to create and cleanup - * temporary files created as a result of handling the request.

- */ - public interface TempFileManager { - TempFile createTempFile() throws Exception; - - void clear(); - } - - /** - * A temp file. - *

- *

Temp files are responsible for managing the actual temporary storage and cleaning - * themselves up when no longer needed.

- */ - public interface TempFile { - OutputStream open(); - - void delete(); - - String getName(); - } - - /** - * Default threading strategy for NanoHttpd. - *

- *

By default, the server spawns a new Thread for every incoming request. These are set - * to daemon status, and named according to the request number. The name is - * useful when profiling the application.

- */ - public static class DefaultAsyncRunner implements AsyncRunner { - private long requestCount; - - @Override - public void exec(Runnable code) { - ++requestCount; - Thread t = new Thread(code); - t.setDaemon(true); - t.setName("NanoHttpd Request Processor (#" + requestCount + ")"); - t.start(); - } - } - - /** - * Default strategy for creating and cleaning up temporary files. - *

- *

This class stores its files in the standard location (that is, - * wherever java.io.tmpdir points to). Files are added - * to an internal list, and deleted when no longer needed (that is, - * when clear() is invoked at the end of processing a - * request).

- */ - public static class DefaultTempFileManager implements TempFileManager { - private final String tmpdir; - private final List tempFiles; - - public DefaultTempFileManager() { - tmpdir = System.getProperty("java.io.tmpdir"); - tempFiles = new ArrayList<>(); - } - - @Override - public TempFile createTempFile() throws Exception { - DefaultTempFile tempFile = new DefaultTempFile(tmpdir); - tempFiles.add(tempFile); - return tempFile; - } - - @Override - public void clear() { - for (TempFile file : tempFiles) { - try { - file.delete(); - } catch (Exception ignored) { - } - } - tempFiles.clear(); - } - } - - /** - * Default strategy for creating and cleaning up temporary files. - *

- *

By default, files are created by File.createTempFile() in - * the directory specified.

- */ - public static class DefaultTempFile implements TempFile { - private File file; - private OutputStream fstream; - - public DefaultTempFile(String tempdir) throws IOException { - file = File.createTempFile("NanoHTTPD-", "", new File(tempdir)); - fstream = new FileOutputStream(file); - } - - @Override - public OutputStream open() { - return fstream; - } - - @Override - public void delete() { - safeClose(fstream); - file.delete(); - } - - @Override - public String getName() { - return file.getAbsolutePath(); - } - } - - /** - * HTTP response. Return one of these from serve(). - */ - public static class Response { - /** - * HTTP status code after processing, e.g. "200 OK", HTTP_OK - */ - private IStatus status; - /** - * MIME type of content, e.g. "text/html" - */ - private String mimeType; - /** - * Data of the response, may be null. - */ - private InputStream data; - /** - * Headers for the HTTP response. Use addHeader() to add lines. - */ - private final Map header = new ArrayMap<>(); - /** - * The request method that spawned this response. - */ - private Method requestMethod; - /** - * Use chunkedTransfer - */ - private boolean chunkedTransfer; - - /** - * Default constructor: response = HTTP_OK, mime = MIME_HTML and your supplied message - */ - public Response(String msg) { - this(Status.OK, MIME_HTML, msg); - } - - /** - * Basic constructor. - */ - public Response(IStatus status, String mimeType, InputStream data) { - this.status = status; - this.mimeType = mimeType; - this.data = data; - } - - /** - * Convenience method that makes an InputStream out of given text. - */ - public Response(IStatus status, String mimeType, String txt) { - this.status = status; - this.mimeType = mimeType; - try { - this.data = txt != null ? new ByteArrayInputStream(txt.getBytes("UTF-8")) : null; - } catch (java.io.UnsupportedEncodingException uee) { - uee.printStackTrace(); - } - } - - /** - * Adds given line to the header. - */ - public void addHeader(String name, String value) { - header.put(name, value); - } - - public String getHeader(String name) { - return header.get(name); - } - - /** - * Sends given response to the socket. - */ - void send(OutputStream outputStream) { - String mime = mimeType; - SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US); - gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT")); - - try { - if (status == null) { - throw new Error("sendResponse(): Status can't be null."); - } - PrintWriter pw = new PrintWriter(outputStream); - pw.print("HTTP/1.1 " + status.getDescription() + " \r\n"); - - if (mime != null) { - pw.print("Content-Type: " + mime + "\r\n"); - } - - if (header == null || header.get("Date") == null) { - pw.print("Date: " + gmtFrmt.format(new Date()) + "\r\n"); - } - - if (header != null) { - for (String key : header.keySet()) { - String value = header.get(key); - pw.print(key + ": " + value + "\r\n"); - } - } - - sendConnectionHeaderIfNotAlreadyPresent(pw, header); - - if (requestMethod != Method.HEAD && chunkedTransfer) { - sendAsChunked(outputStream, pw); - } else { - int pending = data != null ? data.available() : 0; - sendContentLengthHeaderIfNotAlreadyPresent(pw, header, pending); - pw.print("\r\n"); - pw.flush(); - sendAsFixedLength(outputStream, pending); - } - outputStream.flush(); - safeClose(data); - } catch (IOException ioe) { - // Couldn't write? No can do. - } - } - - void sendContentLengthHeaderIfNotAlreadyPresent(PrintWriter pw, Map header, int size) { - if (!headerAlreadySent(header, "content-length")) { - pw.print("Content-Length: "+ size +"\r\n"); - } - } - - void sendConnectionHeaderIfNotAlreadyPresent(PrintWriter pw, Map header) { - if (!headerAlreadySent(header, "connection")) { - pw.print("Connection: keep-alive\r\n"); - } - } - - private boolean headerAlreadySent(Map header, String name) { - boolean alreadySent = false; - for (String headerName : header.keySet()) { - alreadySent |= headerName.equalsIgnoreCase(name); - } - return alreadySent; - } - - private void sendAsChunked(OutputStream outputStream, PrintWriter pw) throws IOException { - pw.print("Transfer-Encoding: chunked\r\n"); - pw.print("\r\n"); - pw.flush(); - int BUFFER_SIZE = 16 * 1024; - byte[] CRLF = "\r\n".getBytes(); - byte[] buff = new byte[BUFFER_SIZE]; - int read; - while ((read = data.read(buff)) > 0) { - outputStream.write(String.format("%x\r\n", read).getBytes()); - outputStream.write(buff, 0, read); - outputStream.write(CRLF); - } - outputStream.write("0\r\n\r\n".getBytes()); - } - - private void sendAsFixedLength(OutputStream outputStream, int pending) throws IOException { - if (requestMethod != Method.HEAD && data != null) { - int BUFFER_SIZE = 16 * 1024; - byte[] buff = new byte[BUFFER_SIZE]; - while (pending > 0) { - int read = data.read(buff, 0, ((pending > BUFFER_SIZE) ? BUFFER_SIZE : pending)); - if (read <= 0) { - break; - } - outputStream.write(buff, 0, read); - pending -= read; - } - } - } - - public IStatus getStatus() { - return status; - } - - public void setStatus(Status status) { - this.status = status; - } - - public String getMimeType() { - return mimeType; - } - - public void setMimeType(String mimeType) { - this.mimeType = mimeType; - } - - public InputStream getData() { - return data; - } - - public void setData(InputStream data) { - this.data = data; - } - - public Method getRequestMethod() { - return requestMethod; - } - - public void setRequestMethod(Method requestMethod) { - this.requestMethod = requestMethod; - } - - public void setChunkedTransfer(boolean chunkedTransfer) { - this.chunkedTransfer = chunkedTransfer; - } - - public interface IStatus { - int getRequestStatus(); - String getDescription(); - } - - /** - * Some HTTP response status codes - */ - public enum Status implements IStatus { - SWITCH_PROTOCOL(101, "Switching Protocols"), OK(200, "OK"), CREATED(201, "Created"), ACCEPTED(202, "Accepted"), NO_CONTENT(204, "No Content"), PARTIAL_CONTENT(206, "Partial Content"), REDIRECT(301, - "Moved Permanently"), NOT_MODIFIED(304, "Not Modified"), BAD_REQUEST(400, "Bad Request"), UNAUTHORIZED(401, - "Unauthorized"), FORBIDDEN(403, "Forbidden"), NOT_FOUND(404, "Not Found"), METHOD_NOT_ALLOWED(405, "Method Not Allowed"), RANGE_NOT_SATISFIABLE(416, - "Requested Range Not Satisfiable"), INTERNAL_ERROR(500, "Internal Server Error"); - private final int requestStatus; - private final String description; - - Status(int requestStatus, String description) { - this.requestStatus = requestStatus; - this.description = description; - } - - @Override - public int getRequestStatus() { - return this.requestStatus; - } - - @Override - public String getDescription() { - return "" + this.requestStatus + " " + description; - } - } - } - - public static final class ResponseException extends Exception { - private static final long serialVersionUID = 1L; - - private final Response.Status status; - - public ResponseException(Response.Status status, String message) { - super(message); - this.status = status; - } - - public ResponseException(Response.Status status, String message, Exception e) { - super(message, e); - this.status = status; - } - - public Response.Status getStatus() { - return status; - } - } - - /** - * Default strategy for creating and cleaning up temporary files. - */ - private class DefaultTempFileManagerFactory implements TempFileManagerFactory { - @Override - public TempFileManager create() { - return new DefaultTempFileManager(); - } - } - - /** - * Handles one session, i.e. parses the HTTP request and returns the response. - */ - public interface IHTTPSession { - void execute() throws IOException; - - Map getParms(); - - Map getHeaders(); - - /** - * @return the path part of the URL. - */ - String getUri(); - - String getQueryParameterString(); - - Method getMethod(); - - InputStream getInputStream(); - - CookieHandler getCookies(); - - /** - * Adds the files in the request body to the files map. - * @arg files - map to modify - */ - void parseBody(Map files) throws IOException, ResponseException; - } - - protected class HTTPSession implements IHTTPSession { - public static final int BUFSIZE = 8192; - private final TempFileManager tempFileManager; - private final OutputStream outputStream; - private final PushbackInputStream inputStream; - private int splitbyte; - private int rlen; - private String uri; - private Method method; - private Map parms; - private Map headers; - private CookieHandler cookies; - private String queryParameterString; - - public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) { - this.tempFileManager = tempFileManager; - this.inputStream = new PushbackInputStream(inputStream, BUFSIZE); - this.outputStream = outputStream; - } - - public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) { - this.tempFileManager = tempFileManager; - this.inputStream = new PushbackInputStream(inputStream, BUFSIZE); - this.outputStream = outputStream; - String remoteIp = inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() ? "127.0.0.1" : inetAddress.getHostAddress().toString(); - headers = new ArrayMap<>(); - - headers.put("remote-addr", remoteIp); - headers.put("http-client-ip", remoteIp); - } - - @Override - public void execute() throws IOException { - try { - // Read the first 8192 bytes. - // The full header should fit in here. - // Apache's default header limit is 8KB. - // Do NOT assume that a single read will get the entire header at once! - byte[] buf = new byte[BUFSIZE]; - splitbyte = 0; - rlen = 0; - { - int read = -1; - try { - read = inputStream.read(buf, 0, BUFSIZE); - } catch (Exception e) { - safeClose(inputStream); - safeClose(outputStream); - throw new SocketException("NanoHttpd Shutdown"); - } - if (read == -1) { - // socket was been closed - safeClose(inputStream); - safeClose(outputStream); - throw new SocketException("NanoHttpd Shutdown"); - } - while (read > 0) { - rlen += read; - splitbyte = findHeaderEnd(buf, rlen); - if (splitbyte > 0) - break; - read = inputStream.read(buf, rlen, BUFSIZE - rlen); - } - } - - if (splitbyte < rlen) { - inputStream.unread(buf, splitbyte, rlen - splitbyte); - } - - parms = new ArrayMap<>(); - if(null == headers) { - headers = new ArrayMap<>(); - } - - // Create a BufferedReader for parsing the header. - BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, rlen))); - - // Decode the header into parms and header java properties - Map pre = new ArrayMap<>(); - decodeHeader(hin, pre, parms, headers); - - method = Method.lookup(pre.get("method")); - if (method == null) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error."); - } - - uri = pre.get("uri"); - - cookies = new CookieHandler(headers); - - // Ok, now do the serve() - Response r = serve(this); - if (r == null) { - throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response."); - } else { - cookies.unloadQueue(r); - r.setRequestMethod(method); - r.send(outputStream); - } - } catch (SocketException e) { - // throw it out to close socket object (finalAccept) - throw e; - } catch (SocketTimeoutException ste) { - throw ste; - } catch (IOException ioe) { - Response r = new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); - r.send(outputStream); - safeClose(outputStream); - } catch (ResponseException re) { - Response r = new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage()); - r.send(outputStream); - safeClose(outputStream); - } finally { - tempFileManager.clear(); - } - } - - @Override - public void parseBody(Map files) throws IOException, ResponseException { - RandomAccessFile randomAccessFile = null; - BufferedReader in = null; - try { - - randomAccessFile = getTmpBucket(); - - long size; - if (headers.containsKey("content-length")) { - size = Integer.parseInt(headers.get("content-length")); - } else if (splitbyte < rlen) { - size = rlen - splitbyte; - } else { - size = 0; - } - - // Now read all the body and write it to f - byte[] buf = new byte[512]; - while (rlen >= 0 && size > 0) { - rlen = inputStream.read(buf, 0, (int)Math.min(size, 512)); - size -= rlen; - if (rlen > 0) { - randomAccessFile.write(buf, 0, rlen); - } - } - - // Get the raw body as a byte [] - ByteBuffer fbuf = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, randomAccessFile.length()); - randomAccessFile.seek(0); - - // Create a BufferedReader for easily reading it as string. - InputStream bin = new FileInputStream(randomAccessFile.getFD()); - in = new BufferedReader(new InputStreamReader(bin)); - - // If the method is POST, there may be parameters - // in data section, too, read it: - if (Method.POST.equals(method)) { - String contentType = ""; - String contentTypeHeader = headers.get("content-type"); - - StringTokenizer st = null; - if (contentTypeHeader != null) { - st = new StringTokenizer(contentTypeHeader, ",; "); - if (st.hasMoreTokens()) { - contentType = st.nextToken(); - } - } - - if ("multipart/form-data".equalsIgnoreCase(contentType)) { - // Handle multipart/form-data - if (!st.hasMoreTokens()) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html"); - } - - String boundaryStartString = "boundary="; - int boundaryContentStart = contentTypeHeader.indexOf(boundaryStartString) + boundaryStartString.length(); - String boundary = contentTypeHeader.substring(boundaryContentStart, contentTypeHeader.length()); - if (boundary.startsWith("\"") && boundary.endsWith("\"")) { - boundary = boundary.substring(1, boundary.length() - 1); - } - - decodeMultipartData(boundary, fbuf, in, parms, files); - } else { - String postLine = ""; - StringBuilder postLineBuffer = new StringBuilder(); - char pbuf[] = new char[512]; - int read = in.read(pbuf); - while (read >= 0 && !postLine.endsWith("\r\n")) { - postLine = String.valueOf(pbuf, 0, read); - postLineBuffer.append(postLine); - read = in.read(pbuf); - } - postLine = postLineBuffer.toString().trim(); - // Handle application/x-www-form-urlencoded - if ("application/x-www-form-urlencoded".equalsIgnoreCase(contentType)) { - decodeParms(postLine, parms); - } else if (postLine.length() != 0) { - // Special case for raw POST data => create a special files entry "postData" with raw content data - files.put("postData", postLine); - } - } - } else if (Method.PUT.equals(method)) { - files.put("content", saveTmpFile(fbuf, 0, fbuf.limit())); - } - } finally { - safeClose(randomAccessFile); - safeClose(in); - } - } - - /** - * Decodes the sent headers and loads the data into Key/value pairs - */ - private void decodeHeader(BufferedReader in, Map pre, Map parms, Map headers) - throws ResponseException { - try { - // Read the request line - String inLine = in.readLine(); - if (inLine == null) { - return; - } - - StringTokenizer st = new StringTokenizer(inLine); - if (!st.hasMoreTokens()) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html"); - } - - pre.put("method", st.nextToken()); - - if (!st.hasMoreTokens()) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html"); - } - - String uri = st.nextToken(); - - // Decode parameters from the URI - int qmi = uri.indexOf('?'); - if (qmi >= 0) { - decodeParms(uri.substring(qmi + 1), parms); - uri = decodePercent(uri.substring(0, qmi)); - } else { - uri = decodePercent(uri); - } - - // If there's another token, it's protocol version, - // followed by HTTP headers. Ignore version but parse headers. - // NOTE: this now forces header names lowercase since they are - // case insensitive and vary by client. - if (st.hasMoreTokens()) { - String line = in.readLine(); - while (line != null && line.trim().length() > 0) { - int p = line.indexOf(':'); - if (p >= 0) - headers.put(line.substring(0, p).trim().toLowerCase(Locale.US), line.substring(p + 1).trim()); - line = in.readLine(); - } - } - - pre.put("uri", uri); - } catch (IOException ioe) { - throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe); - } - } - - /** - * Decodes the Multipart Body data and put it into Key/Value pairs. - */ - private void decodeMultipartData(String boundary, ByteBuffer fbuf, BufferedReader in, Map parms, - Map files) throws ResponseException { - try { - int[] bpositions = getBoundaryPositions(fbuf, boundary.getBytes()); - int boundarycount = 1; - String mpline = in.readLine(); - while (mpline != null) { - if (!mpline.contains(boundary)) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html"); - } - boundarycount++; - Map item = new ArrayMap<>(); - mpline = in.readLine(); - while (mpline != null && mpline.trim().length() > 0) { - int p = mpline.indexOf(':'); - if (p != -1) { - item.put(mpline.substring(0, p).trim().toLowerCase(Locale.US), mpline.substring(p + 1).trim()); - } - mpline = in.readLine(); - } - if (mpline != null) { - String contentDisposition = item.get("content-disposition"); - if (contentDisposition == null) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html"); - } - StringTokenizer st = new StringTokenizer(contentDisposition, ";"); - Map disposition = new ArrayMap<>(); - while (st.hasMoreTokens()) { - String token = st.nextToken().trim(); - int p = token.indexOf('='); - if (p != -1) { - disposition.put(token.substring(0, p).trim().toLowerCase(Locale.US), token.substring(p + 1).trim()); - } - } - String pname = disposition.get("name"); - pname = pname.substring(1, pname.length() - 1); - - String value = ""; - if (item.get("content-type") == null) { - StringBuilder tmp = new StringBuilder(); - while (mpline != null && !mpline.contains(boundary)) { - mpline = in.readLine(); - if (mpline != null) { - int d = mpline.indexOf(boundary); - if (d == -1) { - tmp.append(mpline); - } else { - tmp.append(mpline.substring(0, d - 2)); - } - } - } - value = tmp.toString(); - } else { - if (boundarycount > bpositions.length) { - throw new ResponseException(Response.Status.INTERNAL_ERROR, "Error processing request"); - } - int offset = stripMultipartHeaders(fbuf, bpositions[boundarycount - 2]); - String path = saveTmpFile(fbuf, offset, bpositions[boundarycount - 1] - offset - 4); - files.put(pname, path); - value = disposition.get("filename"); - value = value.substring(1, value.length() - 1); - do { - mpline = in.readLine(); - } while (mpline != null && !mpline.contains(boundary)); - } - parms.put(pname, value); - } - } - } catch (IOException ioe) { - throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe); - } - } - - /** - * Find byte index separating header from body. It must be the last byte of the first two sequential new lines. - */ - private int findHeaderEnd(final byte[] buf, int rlen) { - int splitbyte = 0; - while (splitbyte + 3 < rlen) { - if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') { - return splitbyte + 4; - } - splitbyte++; - } - return 0; - } - - /** - * Find the byte positions where multipart boundaries start. - */ - private int[] getBoundaryPositions(ByteBuffer b, byte[] boundary) { - int matchcount = 0; - int matchbyte = -1; - List matchbytes = new ArrayList<>(); - for (int i = 0; i < b.limit(); i++) { - if (b.get(i) == boundary[matchcount]) { - if (matchcount == 0) - matchbyte = i; - matchcount++; - if (matchcount == boundary.length) { - matchbytes.add(matchbyte); - matchcount = 0; - matchbyte = -1; - } - } else { - i -= matchcount; - matchcount = 0; - matchbyte = -1; - } - } - int[] ret = new int[matchbytes.size()]; - for (int i = 0; i < ret.length; i++) { - ret[i] = matchbytes.get(i); - } - return ret; - } - - /** - * Retrieves the content of a sent file and saves it to a temporary file. The full path to the saved file is returned. - */ - private String saveTmpFile(ByteBuffer b, int offset, int len) { - String path = ""; - if (len > 0) { - FileOutputStream fileOutputStream = null; - try { - TempFile tempFile = tempFileManager.createTempFile(); - ByteBuffer src = b.duplicate(); - fileOutputStream = new FileOutputStream(tempFile.getName()); - FileChannel dest = fileOutputStream.getChannel(); - src.position(offset).limit(offset + len); - dest.write(src.slice()); - path = tempFile.getName(); - } catch (Exception e) { // Catch exception if any - throw new Error(e); // we won't recover, so throw an error - } finally { - safeClose(fileOutputStream); - } - } - return path; - } - - private RandomAccessFile getTmpBucket() { - try { - TempFile tempFile = tempFileManager.createTempFile(); - return new RandomAccessFile(tempFile.getName(), "rw"); - } catch (Exception e) { - throw new Error(e); // we won't recover, so throw an error - } - } - - /** - * It returns the offset separating multipart file headers from the file's data. - */ - private int stripMultipartHeaders(ByteBuffer b, int offset) { - int i; - for (i = offset; i < b.limit(); i++) { - if (b.get(i) == '\r' && b.get(++i) == '\n' && b.get(++i) == '\r' && b.get(++i) == '\n') { - break; - } - } - return i + 1; - } - - /** - * Decodes parameters in percent-encoded URI-format ( e.g. "name=Jack%20Daniels&pass=Single%20Malt" ) and - * adds them to given Map. NOTE: this doesn't support multiple identical keys due to the simplicity of Map. - */ - private void decodeParms(String parms, Map p) { - if (parms == null) { - queryParameterString = ""; - return; - } - - queryParameterString = parms; - StringTokenizer st = new StringTokenizer(parms, "&"); - while (st.hasMoreTokens()) { - String e = st.nextToken(); - int sep = e.indexOf('='); - if (sep >= 0) { - p.put(decodePercent(e.substring(0, sep)).trim(), - decodePercent(e.substring(sep + 1))); - } else { - p.put(decodePercent(e).trim(), ""); - } - } - } - - @Override - public final Map getParms() { - return parms; - } - - public String getQueryParameterString() { - return queryParameterString; - } - - @Override - public final Map getHeaders() { - return headers; - } - - @Override - public final String getUri() { - return uri; - } - - @Override - public final Method getMethod() { - return method; - } - - @Override - public final InputStream getInputStream() { - return inputStream; - } - - @Override - public CookieHandler getCookies() { - return cookies; - } - } - - public static class Cookie { - private final String n; - private final String v; - private final String e; - - public Cookie(String name, String value, String expires) { - n = name; - v = value; - e = expires; - } - - public Cookie(String name, String value) { - this(name, value, 30); - } - - public Cookie(String name, String value, int numDays) { - n = name; - v = value; - e = getHTTPTime(numDays); - } - - public String getHTTPHeader() { - String fmt = "%s=%s; expires=%s"; - return String.format(fmt, n, v, e); - } - - public static String getHTTPTime(int days) { - Calendar calendar = Calendar.getInstance(); - SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); - dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); - calendar.add(Calendar.DAY_OF_MONTH, days); - return dateFormat.format(calendar.getTime()); - } - } - - /** - * Provides rudimentary support for cookies. - * Doesn't support 'path', 'secure' nor 'httpOnly'. - * Feel free to improve it and/or add unsupported features. - * - * @author LordFokas - */ - public class CookieHandler implements Iterable { - private final ArrayMap cookies = new ArrayMap<>(); - private final ArrayList queue = new ArrayList<>(); - - public CookieHandler(Map httpHeaders) { - String raw = httpHeaders.get("cookie"); - if (raw != null) { - String[] tokens = raw.split(";"); - for (String token : tokens) { - String[] data = token.trim().split("="); - if (data.length == 2) { - cookies.put(data[0], data[1]); - } - } - } - } - - @Override public Iterator iterator() { - return cookies.keySet().iterator(); - } - - /** - * Read a cookie from the HTTP Headers. - * - * @param name The cookie's name. - * @return The cookie's value if it exists, null otherwise. - */ - public String read(String name) { - return cookies.get(name); - } - - /** - * Sets a cookie. - * - * @param name The cookie's name. - * @param value The cookie's value. - * @param expires How many days until the cookie expires. - */ - public void set(String name, String value, int expires) { - queue.add(new Cookie(name, value, Cookie.getHTTPTime(expires))); - } - - public void set(Cookie cookie) { - queue.add(cookie); - } - - /** - * Set a cookie with an expiration date from a month ago, effectively deleting it on the client side. - * - * @param name The cookie name. - */ - public void delete(String name) { - set(name, "-delete-", -30); - } - - /** - * Internally used by the webserver to add all queued cookies into the Response's HTTP Headers. - * - * @param response The Response object to which headers the queued cookies will be added. - */ - public void unloadQueue(Response response) { - for (Cookie cookie : queue) { - response.addHeader("Set-Cookie", cookie.getHTTPHeader()); - } - } - } -} diff --git a/app/src/androidTest/java/de/test/antennapod/util/syndication/FeedDiscovererTest.java b/app/src/androidTest/java/de/test/antennapod/util/syndication/FeedDiscovererTest.java index 809b9769a..d4837ef60 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/syndication/FeedDiscovererTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/syndication/FeedDiscovererTest.java @@ -1,38 +1,43 @@ package de.test.antennapod.util.syndication; -import android.test.InstrumentationTestCase; - +import android.support.test.InstrumentationRegistry; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import java.io.File; import java.io.FileOutputStream; +import java.nio.file.Files; import java.util.Map; import de.danoeh.antennapod.core.util.syndication.FeedDiscoverer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; /** * Test class for FeedDiscoverer */ -public class FeedDiscovererTest extends InstrumentationTestCase { +public class FeedDiscovererTest { private FeedDiscoverer fd; private File testDir; - @Override + @Before public void setUp() throws Exception { - super.setUp(); fd = new FeedDiscoverer(); - testDir = getInstrumentation().getTargetContext().getExternalFilesDir("FeedDiscovererTest"); + testDir = new File(InstrumentationRegistry.getTargetContext().getFilesDir(), "FeedDiscovererTest"); testDir.mkdir(); assertTrue(testDir.exists()); } - @Override - protected void tearDown() throws Exception { + @After + public void tearDown() throws Exception { FileUtils.deleteDirectory(testDir); - super.tearDown(); } private String createTestHtmlString(String rel, String type, String href, String title) { @@ -81,30 +86,37 @@ public class FeedDiscovererTest extends InstrumentationTestCase { } } + @Test public void testAlternateRSSWithTitleAbsolute() throws Exception { checkFindUrls(true, true, true, true, true); } + @Test public void testAlternateRSSWithTitleRelative() throws Exception { checkFindUrls(true, true, true, false, true); } + @Test public void testAlternateRSSNoTitleAbsolute() throws Exception { checkFindUrls(true, true, false, true, true); } + @Test public void testAlternateRSSNoTitleRelative() throws Exception { checkFindUrls(true, true, false, false, true); } + @Test public void testAlternateAtomWithTitleAbsolute() throws Exception { checkFindUrls(true, false, true, true, true); } + @Test public void testFeedAtomWithTitleAbsolute() throws Exception { checkFindUrls(false, false, true, true, true); } + @Test public void testAlternateRSSWithTitleAbsoluteFromFile() throws Exception { checkFindUrls(true, true, true, true, false); } -- cgit v1.2.3