// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.npr.android.news;
import android.content.Context;
import android.content.Intent;
import android.test.InstrumentationTestCase;
import android.util.Log;
import org.npr.android.test.BlockedHttpServer;
import org.npr.android.test.HttpRawResourceServer;
import org.npr.android.test.NullStreamServer;
import org.npr.android.util.PlaylistEntry;
import java.util.ArrayList;
/**
* Sets up a series of tests for the PlaybackService class.
*
* @author jeremywadsack
*/
public class PlaybackServiceTest extends InstrumentationTestCase {
private static final String TAG = PlaybackServiceTest.class.getName();
/*
* Verifies basic functionality - that the player will play an MP3 file
* specified by a URL
*/
public void testShouldPlayRemoteMP3File() {
// create a local server that can post a file out of the res/raw folder
HttpRawResourceServer server = new HttpRawResourceServer(
getInstrumentation().getContext(), false);
PlaybackServiceHelper tester = new PlaybackServiceHelper(
getInstrumentation().getTargetContext());
try {
server.init();
server.start();
// listen to our file
PlaylistEntry entry = new PlaylistEntry(-1L, "http://127.0.0.1:"
+ server.getPort() + "/one_second_silence_mp3", "Silence", false, -1);
tester.listen(entry);
final int maxWait = 30 * 1000; // Wait 30 seconds to finish playing
int timeWaited = 0;
while (!tester.isComplete()) {
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
}
timeWaited += 200;
if (timeWaited > maxWait) {
Log.w(TAG, "Timed out waiting for test song to complete playing");
break;
}
}
} finally {
// Cleanup
server.stop();
}
// assert that the file started and completed
assertNotNull(tester);
assertTrue("Never completed playing song", tester.isComplete());
ArrayList<String> log = tester.getLog();
assertNotNull("File never started playing.", findStringStartsWith(log,
"Prepared"));
// and that the duration and final position are all correct
String durationString = findStringStartsWith(log, "Duration:");
assertNotNull("Media player logged a duration.", durationString);
int pos = durationString.lastIndexOf(' ');
if (pos > -1) {
try {
Integer.parseInt(durationString.substring(pos + 1));
} catch (NumberFormatException ex) {
fail("No duration provided: " + durationString);
}
} else {
fail("No duration provided: " + durationString);
}
String finalPositionString = findStringStartsWith(log, "Final position:");
assertNotNull("Media player logged a position.", finalPositionString);
pos = finalPositionString.lastIndexOf(' ');
if (pos > -1) {
try {
Integer.parseInt(finalPositionString.substring(pos + 1));
} catch (NumberFormatException ex) {
fail("No final position provided: " + finalPositionString);
}
} else {
fail("No final position provided: " + finalPositionString);
}
}
/*
* Verifies that the player can play from a continuous MP3 stream.
*/
public void testShouldPlayRemoteMP3Stream() {
HttpRawResourceServer server = new HttpRawResourceServer(
getInstrumentation().getContext(), true);
PlaybackServiceHelper tester = new PlaybackServiceHelper(
getInstrumentation().getTargetContext());
try {
server.init();
server.start();
// listen to our file
PlaylistEntry entry = new PlaylistEntry(-1L, "http://127.0.0.1:"
+ server.getPort() + "/one_second_silence_mp3", "Silent Stream test",
true, -1);
tester.listen(entry);
final int maxWait = 3 * 1000; // Play stream for 3 seconds
int timeWaited = 0;
while (!tester.isComplete() && timeWaited < maxWait) {
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
}
timeWaited += 200;
}
} finally {
// Cleanup
server.stop();
}
// assert that the file started
assertNotNull(tester);
ArrayList<String> log = tester.getLog();
assertNotNull("File never started playing.", findStringStartsWith(log,
"Prepared"));
}
/*
* If you start the player on the main thread and it fails for any reason,
* then the player never calls isPrepared on the service and the listen loop
* gets locked.
*/
public void testShouldStopPlayerIfMediaFailsAndNoOtherItemsExist() {
final PlaybackServiceHelper tester = new PlaybackServiceHelper(
getInstrumentation().getTargetContext());
// listen to our file
final PlaylistEntry entry = new PlaylistEntry(-1L, "/dev/null",
"not-existent file", false, -1);
Thread t = new Thread(new Runnable() {
@Override
public void run() {
tester.listen(entry);
}
});
t.start();
// Wait 10 seconds to finish the prepared call
long maxWait = 10 * 1000;
long startTime = System.currentTimeMillis();
try {
t.join(maxWait);
} catch (InterruptedException ex) {
}
assertTrue("PlaybackService blocked on listen call.", System
.currentTimeMillis()
- startTime < maxWait);
assertNotNull(tester);
assertTrue("File should complete.", tester.isComplete());
ArrayList<String> log = tester.getLog();
assertNull("File should not play.", findStringStartsWith(log, "Prepared"));
}
/*
* When running on pre-2.2 with an MP3 stream as data, if the data can't be
* streamed and fails to ever prepare it also never errors or completes. This
* should be properly handled so that we don't block the main thread.
*/
public void testShouldStopPlayerIfStreamBlocksAndNoOtherItemsExist() {
BlockedHttpServer server = new BlockedHttpServer();
final PlaybackServiceHelper tester = new PlaybackServiceHelper(
getInstrumentation().getTargetContext());
try {
server.init();
server.start();
// listen to our file
final PlaylistEntry entry = new PlaylistEntry(-1L,
"http://127.0.0.1:" + server.getPort() + "/",
"Invalid stream test",
true, // Don't bother using the StreamProxy
-1);
Thread t = new Thread(new Runnable() {
@Override
public void run() {
tester.listen(entry);
}
});
t.start();
// Wait 5 minutes to finish the prepared call
long maxWait = 30 * 1000;
long startTime = System.currentTimeMillis();
try {
t.join(maxWait);
} catch (InterruptedException ex) {
}
assertTrue("PlaybackService blocked on listen call.", System
.currentTimeMillis()
- startTime < maxWait);
} finally {
// Cleanup
server.stop();
}
assertNotNull(tester);
ArrayList<String> log = tester.getLog();
assertNull("File should not play.", findStringStartsWith(log, "Prepared"));
}
public void testShouldStopPlayerIfStreamFailsAndNoOtherItemsExist() {
NullStreamServer server = new NullStreamServer();
final PlaybackServiceHelper tester = new PlaybackServiceHelper(
getInstrumentation().getTargetContext());
try {
server.init();
server.start();
// listen to our file
final PlaylistEntry entry = new PlaylistEntry(-1L, "http://127.0.0.1:"
+ server.getPort() + "/", "Invalid stream test", false, -1);
Thread t = new Thread(new Runnable() {
@Override
public void run() {
tester.listen(entry);
}
});
t.start();
// Wait 10 seconds to finish the prepared call
long maxWait = 10 * 1000;
long startTime = System.currentTimeMillis();
try {
t.join(maxWait);
} catch (InterruptedException ex) {
}
assertTrue("PlaybackService blocked on listen call.", System
.currentTimeMillis()
- startTime < maxWait);
} finally {
// Cleanup
server.stop();
}
assertNotNull(tester);
assertTrue("File should complete.", tester.isComplete());
ArrayList<String> log = tester.getLog();
assertNull("File should not play.", findStringStartsWith(log, "Prepared"));
}
public void todoShouldPlayNextItemIfMediaFailsAndOtherItemsExist() {
// TODO: Similar to previous test but first add items to the playlist
// provider
// - I can't find examples where anything is ever added
// - Also, mock the PlaylistProvider, if possible
}
// -------------
// Helper functions and classes below
/**
* A helper function to find a string within a list that starts with the given
* prefix
*
* @param list The list of strings to search
* @param prefix The prefix to find in the list
* @return the first string in the list that matches or null if no match is
* found.
*/
private String findStringStartsWith(ArrayList<String> list, String prefix) {
for (String s : list) {
if (s.startsWith(prefix)) {
return s;
}
}
return null;
}
/**
* A stub class that connects to the PlaybackService and instruments it for
* testing and inspection.
*/
private class PlaybackServiceHelper {
private final ArrayList<String> log = new ArrayList<String>();
private boolean isComplete = false;
private Context context = null;
public PlaybackServiceHelper(Context context) {
log.add("Created");
this.context = context;
}
public void listen(PlaylistEntry entry) {
Log.d(TAG, "Listening to " + entry.url);
try {
isComplete = false;
Intent intent = new Intent(context, PlaybackService.class);
intent.setAction(PlaybackService.SERVICE_PLAY_SINGLE);
intent.putExtra(Playable.PLAYABLE_TYPE, Playable.PlayableFactory
.fromPlaylistEntry(entry));
context.startService(intent);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Media play failure", e);
} catch (IllegalStateException e) {
Log.e(TAG, "Media play failure", e);
}
}
public boolean isComplete() {
return isComplete;
}
public ArrayList<String> getLog() {
return log;
}
}
}