Added feed generators, basic rss tests

This commit is contained in:
daniel oeh 2014-03-31 19:45:03 +02:00
parent b2de7512f4
commit 5a6c7ef43a
7 changed files with 428 additions and 147 deletions

View File

@ -17,7 +17,7 @@ import java.io.Reader;
public class TypeGetter {
private static final String TAG = "TypeGetter";
enum Type {
public enum Type {
RSS20, RSS091, ATOM, INVALID
}

View File

@ -79,7 +79,7 @@ public class SyndDateUtils {
second = date.substring(date.indexOf("-"));
}
}
date = first + second;
}
if (isLocal) {
@ -131,4 +131,23 @@ public class SyndDateUtils {
result += (Float.valueOf(parts[idx])) * 1000L;
return result;
}
public static String formatRFC822Date(Date date) {
SimpleDateFormat format = RFC822Formatter.get();
return format.format(date);
}
public static String formatRFC3339Local(Date date) {
SimpleDateFormat format = RFC3339Formatter.get();
format.applyPattern(RFC3339LOCAL);
String result = format.format(date);
format.applyPattern(RFC3339UTC);
return result;
}
public static String formatRFC3339UTC(Date date) {
SimpleDateFormat format = RFC3339Formatter.get();
format.applyPattern(RFC3339UTC);
return format.format(date);
}
}

View File

@ -1,170 +1,168 @@
package instrumentationTest.de.test.antennapod.syndication.handler;
import java.io.BufferedOutputStream;
import android.content.Context;
import android.test.InstrumentationTestCase;
import de.danoeh.antennapod.feed.*;
import de.danoeh.antennapod.syndication.handler.FeedHandler;
import de.danoeh.antennapod.syndication.handler.UnsupportedFeedtypeException;
import instrumentationTest.de.test.antennapod.util.syndication.feedgenerator.FeedGenerator;
import instrumentationTest.de.test.antennapod.util.syndication.feedgenerator.RSS2Generator;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import android.test.AndroidTestCase;
import android.util.Log;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.syndication.handler.FeedHandler;
/**
* Tests for FeedHandler
*/
public class FeedHandlerTest extends InstrumentationTestCase {
private static final String FEEDS_DIR = "testfeeds";
/** Enqueues a list of Feeds and tests if they are parsed correctly */
public class FeedHandlerTest extends AndroidTestCase {
private static final String TAG = "FeedHandlerTest";
private static final String FEEDS_DIR = "testfeeds";
File file = null;
OutputStream outputStream = null;
private ArrayList<Feed> feeds;
protected void setUp() throws Exception {
super.setUp();
Context context = getInstrumentation().getContext();
File destDir = context.getExternalFilesDir(FEEDS_DIR);
assertNotNull(destDir);
protected void setUp() throws Exception {
super.setUp();
feeds = new ArrayList<Feed>();
for (int i = 0; i < TestFeeds.urls.length; i++) {
Feed f = new Feed(TestFeeds.urls[i], new Date());
f.setFile_url(new File(getContext().getExternalFilesDir(FEEDS_DIR)
.getAbsolutePath(), "R" + i).getAbsolutePath());
feeds.add(f);
}
}
file = new File(destDir, "feed.xml");
file.delete();
private InputStream getInputStream(String url)
throws MalformedURLException, IOException {
HttpURLConnection connection = (HttpURLConnection) (new URL(url))
.openConnection();
int rc = connection.getResponseCode();
if (rc == HttpURLConnection.HTTP_OK) {
return connection.getInputStream();
} else {
return null;
}
}
assertNotNull(file);
assertFalse(file.exists());
private boolean downloadFeed(Feed feed) throws IOException {
int num_retries = 20;
boolean successful = false;
outputStream = new FileOutputStream(file);
}
for (int i = 0; i < num_retries; i++) {
InputStream in = null;
BufferedOutputStream out = null;
try {
in = getInputStream(feed.getDownload_url());
if (in == null) {
return false;
}
out = new BufferedOutputStream(new FileOutputStream(
feed.getFile_url()));
byte[] buffer = new byte[8 * 1024];
int count = 0;
while ((count = in.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
out.flush();
successful = true;
return true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
if (successful) {
break;
}
}
}
if (!successful) {
Log.e(TAG, "Download failed after " + num_retries + " retries");
throw new IOException();
} else {
return true;
@Override
protected void tearDown() throws Exception {
super.tearDown();
file.delete();
file = null;
outputStream.close();
outputStream = null;
}
private Feed runFeedTest(Feed feed, FeedGenerator g, String encoding, long flags) throws IOException, UnsupportedFeedtypeException, SAXException, ParserConfigurationException {
g.writeFeed(feed, outputStream, encoding, flags);
FeedHandler handler = new FeedHandler();
Feed parsedFeed = new Feed(feed.getDownload_url(), feed.getLastUpdate());
parsedFeed.setFile_url(file.getAbsolutePath());
parsedFeed.setDownloaded(true);
handler.parseFeed(parsedFeed);
return parsedFeed;
}
private void feedValid(Feed feed, Feed parsedFeed, String feedType) {
assertEquals(feed.getTitle(), parsedFeed.getTitle());
if (feedType.equals(Feed.TYPE_ATOM1)) {
assertEquals(feed.getFeedIdentifier(), parsedFeed.getFeedIdentifier());
assertEquals(feed.getAuthor(), parsedFeed.getAuthor());
}
}
assertEquals(feed.getLink(), parsedFeed.getLink());
assertEquals(feed.getDescription(), parsedFeed.getDescription());
assertEquals(feed.getLanguage(), parsedFeed.getLanguage());
assertEquals(feed.getPaymentLink(), parsedFeed.getPaymentLink());
private boolean isFeedValid(Feed feed) {
Log.i(TAG, "Checking if " + feed.getDownload_url() + " is valid");
boolean result = false;
if (feed.getTitle() == null) {
Log.e(TAG, "Feed has no title");
return false;
}
if (!hasValidFeedItems(feed)) {
Log.e(TAG, "Feed has invalid items");
return false;
}
if (feed.getLink() == null) {
Log.e(TAG, "Feed has no link");
return false;
}
if (feed.getLink() != null && feed.getLink().length() == 0) {
Log.e(TAG, "Feed has empty link");
return false;
}
if (feed.getIdentifyingValue() == null) {
Log.e(TAG, "Feed has no identifying value");
return false;
}
if (feed.getIdentifyingValue() != null
&& feed.getIdentifyingValue().length() == 0) {
Log.e(TAG, "Feed has empty identifying value");
return false;
}
return true;
}
if (feed.getImage() != null) {
FeedImage image = feed.getImage();
FeedImage parsedImage = parsedFeed.getImage();
assertNotNull(parsedImage);
private boolean hasValidFeedItems(Feed feed) {
for (FeedItem item : feed.getItems()) {
if (item.getTitle() == null) {
Log.e(TAG, "Item has no title");
return false;
}
}
return true;
}
assertEquals(image.getTitle(), parsedImage.getTitle());
assertEquals(image.getDownload_url(), parsedImage.getDownload_url());
}
public void testParseFeeds() {
Log.i(TAG, "Testing RSS feeds");
while (!feeds.isEmpty()) {
Feed feed = feeds.get(0);
parseFeed(feed);
feeds.remove(0);
}
if (feed.getItems() != null) {
assertNotNull(parsedFeed.getItems());
assertEquals(feed.getItems().size(), parsedFeed.getItems().size());
Log.i(TAG, "RSS Test completed");
}
for (int i = 0; i < feed.getItems().size(); i++) {
FeedItem item = feed.getItems().get(i);
FeedItem parsedItem = parsedFeed.getItems().get(i);
private void parseFeed(Feed feed) {
try {
Log.i(TAG, "Testing feed with url " + feed.getDownload_url());
FeedHandler handler = new FeedHandler();
if (downloadFeed(feed)) {
handler.parseFeed(feed);
assertTrue(isFeedValid(feed));
if (item.getItemIdentifier() != null)
assertEquals(item.getItemIdentifier(), parsedItem.getItemIdentifier());
assertEquals(item.getTitle(), parsedItem.getTitle());
assertEquals(item.getDescription(), parsedItem.getDescription());
assertEquals(item.getContentEncoded(), parsedItem.getContentEncoded());
assertEquals(item.getLink(), parsedItem.getLink());
assertEquals(item.getPubDate().getTime(), parsedItem.getPubDate().getTime());
assertEquals(item.getPaymentLink(), parsedItem.getPaymentLink());
if (item.hasMedia()) {
assertTrue(parsedItem.hasMedia());
FeedMedia media = item.getMedia();
FeedMedia parsedMedia = parsedItem.getMedia();
assertEquals(media.getDownload_url(), parsedMedia.getDownload_url());
assertEquals(media.getSize(), parsedMedia.getSize());
assertEquals(media.getMime_type(), parsedMedia.getMime_type());
}
if (item.hasItemImage()) {
assertTrue(parsedItem.hasItemImage());
FeedImage image = item.getImage();
FeedImage parsedImage = parsedItem.getImage();
assertEquals(image.getTitle(), parsedImage.getTitle());
assertEquals(image.getDownload_url(), parsedImage.getDownload_url());
}
if (item.getChapters() != null) {
assertNotNull(parsedItem.getChapters());
assertEquals(item.getChapters().size(), parsedItem.getChapters().size());
List<Chapter> chapters = item.getChapters();
List<Chapter> parsedChapters = parsedItem.getChapters();
for (int j = 0; j < chapters.size(); j++) {
Chapter chapter = chapters.get(j);
Chapter parsedChapter = parsedChapters.get(j);
assertEquals(chapter.getTitle(), parsedChapter.getTitle());
assertEquals(chapter.getLink(), parsedChapter.getLink());
}
}
}
} catch (Exception e) {
Log.e(TAG, "Error when trying to test " + feed.getDownload_url());
e.printStackTrace();
}
}
}
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
for (Feed feed : feeds) {
File f = new File(feed.getFile_url());
f.delete();
}
}
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);
}
private Feed createTestFeed(int numItems, boolean withImage, boolean withFeedMedia, boolean withChapters) {
FeedImage image = null;
if (withImage) {
image = new FeedImage(0, "image", null, "http://example.com/picture", false);
}
Feed feed = new Feed(0, new Date(), "title", "http://example.com", "This is the description",
"http://example.com/payment", "Daniel", "en", null, "http://example.com/feed", image, file.getAbsolutePath(),
"http://example.com/feed", true);
feed.setItems(new ArrayList<FeedItem>());
for (int i = 0; i < numItems; i++) {
FeedItem item = new FeedItem(0, "item-" + i, "http://example.com/item-" + i,
"http://example.com/items/" + i, new Date(i), false, feed);
feed.getItems().add(item);
if (withFeedMedia) {
item.setMedia(new FeedMedia(0, item, 4711, 0, 100, "audio/mp3", null, "http://example.com/media-" + i,
false, null, 0));
}
}
return feed;
}
}

View File

@ -0,0 +1,106 @@
package instrumentationTest.de.test.antennapod.util.syndication.feedgenerator;
import android.util.Xml;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.syndication.util.SyndDateUtils;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.OutputStream;
/**
* Creates Atom feeds. See FeedGenerator for more information.
*/
public class AtomGenerator implements FeedGenerator{
private static final String NS_ATOM = "http://www.w3.org/2005/Atom";
public static final long FEATURE_USE_RFC3339LOCAL = 1;
@Override
public void writeFeed(Feed feed, OutputStream outputStream, String encoding, long flags) throws IOException {
if (feed == null) throw new IllegalArgumentException("feed = null");
if (outputStream == null) throw new IllegalArgumentException("outputStream = null");
if (encoding == null) throw new IllegalArgumentException("encoding = null");
XmlSerializer xml = Xml.newSerializer();
xml.setOutput(outputStream, encoding);
xml.startDocument(encoding, null);
xml.startTag(null, "feed");
xml.attribute(null, "xmlns", NS_ATOM);
// Write Feed data
if (feed.getIdentifyingValue() != null) {
xml.startTag(null, "id");
xml.text(feed.getIdentifyingValue());
xml.endTag(null, "id");
}
if (feed.getTitle() != null) {
xml.startTag(null, "title");
xml.text(feed.getTitle());
xml.endTag(null, "title");
}
if (feed.getLink() != null) {
xml.startTag(null, "link");
xml.attribute(null, "rel", "alternate");
xml.attribute(null, "href", feed.getLink());
xml.endTag(null, "link");
}
if (feed.getDescription() != null) {
xml.startTag(null, "subtitle");
xml.text(feed.getDescription());
xml.endTag(null, "subtitle");
}
// Write FeedItem data
if (feed.getItems() != null) {
for (FeedItem item : feed.getItems()) {
if (item.getIdentifyingValue() != null) {
xml.startTag(null, "id");
xml.text(item.getIdentifyingValue());
xml.endTag(null, "id");
}
if (item.getTitle() != null) {
xml.startTag(null, "title");
xml.text(item.getTitle());
xml.endTag(null, "title");
}
if (item.getLink() != null) {
xml.startTag(null, "link");
xml.attribute(null, "rel", "alternate");
xml.attribute(null, "href", item.getLink());
xml.endTag(null, "link");
}
if (item.getPubDate() != null) {
xml.startTag(null, "published");
if ((flags & FEATURE_USE_RFC3339LOCAL) != 0) {
xml.text(SyndDateUtils.formatRFC3339Local(item.getPubDate()));
} else {
xml.text(SyndDateUtils.formatRFC3339UTC(item.getPubDate()));
}
}
if (item.getDescription() != null) {
xml.startTag(null, "content");
xml.text(item.getDescription());
xml.endTag(null, "content");
}
if (item.getMedia() != null) {
FeedMedia media = item.getMedia();
xml.startTag(null, "link");
xml.attribute(null, "rel", "enclosure");
xml.attribute(null, "href", media.getDownload_url());
xml.attribute(null, "type", media.getMime_type());
xml.attribute(null, "length", String.valueOf(media.getSize()));
xml.endTag(null, "link");
}
}
}
xml.endTag(null, "feed");
xml.endDocument();
}
}

View File

@ -0,0 +1,28 @@
package instrumentationTest.de.test.antennapod.util.syndication.feedgenerator;
import de.danoeh.antennapod.feed.Feed;
import java.io.IOException;
import java.io.OutputStream;
/**
* Generates a machine-readable, platform-independent representation of a Feed object.
*/
public interface FeedGenerator {
/**
* Creates a machine-readable, platform-independent representation of a given
* Feed object and writes it to the given OutputStream.
* <p/>
* The representation might not be compliant with its specification if the feed
* is missing certain attribute values. This is intentional because the FeedGenerator is
* used for creating test data.
*
* @param feed The feed that should be written. Must not be null.
* @param outputStream The output target that the feed will be written to. The outputStream is not closed after
* the method's execution Must not be null.
* @param encoding The encoding to use. Must not be null.
* @param flags Optional argument for enabling implementation-dependent features.
*/
public void writeFeed(Feed feed, OutputStream outputStream, String encoding, long flags) throws IOException;
}

View File

@ -0,0 +1,20 @@
package instrumentationTest.de.test.antennapod.util.syndication.feedgenerator;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
/**
* Utility methods for FeedGenerator
*/
public class GeneratorUtil {
public static void addPaymentLink(XmlSerializer xml, String paymentLink) throws IOException {
xml.startTag("http://www.w3.org/2005/Atom", "link");
xml.attribute(null, "rel", "payment");
xml.attribute(null, "title", "Flattr this!");
xml.attribute(null, "href", paymentLink);
xml.attribute(null, "type", "text/html");
xml.endTag("http://www.w3.org/2005/Atom", "link");
}
}

View File

@ -0,0 +1,110 @@
package instrumentationTest.de.test.antennapod.util.syndication.feedgenerator;
import android.util.Xml;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.syndication.util.SyndDateUtils;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.OutputStream;
/**
* Creates RSS 2.0 feeds. See FeedGenerator for more information.
*/
public class RSS2Generator implements FeedGenerator{
public static final long FEATURE_WRITE_GUID = 1;
@Override
public void writeFeed(Feed feed, OutputStream outputStream, String encoding, long flags) throws IOException {
if (feed == null) throw new IllegalArgumentException("feed = null");
if (outputStream == null) throw new IllegalArgumentException("outputStream = null");
if (encoding == null) throw new IllegalArgumentException("encoding = null");
XmlSerializer xml = Xml.newSerializer();
xml.setOutput(outputStream, encoding);
xml.startDocument(encoding, null);
xml.setPrefix("atom", "http://www.w3.org/2005/Atom");
xml.startTag(null, "rss");
xml.attribute(null, "version", "2.0");
xml.startTag(null, "channel");
// Write Feed data
if (feed.getTitle() != null) {
xml.startTag(null, "title");
xml.text(feed.getTitle());
xml.endTag(null, "title");
}
if (feed.getDescription() != null) {
xml.startTag(null, "description");
xml.text(feed.getDescription());
xml.endTag(null, "description");
}
if (feed.getLink() != null) {
xml.startTag(null, "link");
xml.text(feed.getLink());
xml.endTag(null, "link");
}
if (feed.getLanguage() != null) {
xml.startTag(null, "language");
xml.text(feed.getLanguage());
xml.endTag(null, "language");
}
if (feed.getPaymentLink() != null) {
GeneratorUtil.addPaymentLink(xml, feed.getPaymentLink());
}
// Write FeedItem data
if (feed.getItems() != null) {
for (FeedItem item : feed.getItems()) {
xml.startTag(null, "item");
if (item.getTitle() != null) {
xml.startTag(null, "title");
xml.text(item.getTitle());
xml.endTag(null, "title");
}
if (item.getDescription() != null) {
xml.startTag(null, "description");
xml.text(item.getDescription());
xml.endTag(null, "description");
}
if (item.getLink() != null) {
xml.startTag(null, "link");
xml.text(item.getLink());
xml.endTag(null, "link");
}
if (item.getPubDate() != null) {
xml.startTag(null, "pubDate");
xml.text(SyndDateUtils.formatRFC822Date(item.getPubDate()));
xml.endTag(null, "pubDate");
}
if ((flags & FEATURE_WRITE_GUID) != 0) {
xml.startTag(null, "guid");
xml.text(item.getItemIdentifier());
xml.endTag(null, "guid");
}
if (item.getMedia() != null) {
xml.startTag(null, "enclosure");
xml.attribute(null, "url", item.getMedia().getDownload_url());
xml.attribute(null, "length", String.valueOf(item.getMedia().getSize()));
xml.attribute(null, "type", item.getMedia().getMime_type());
xml.endTag(null, "enclosure");
}
if (item.getPaymentLink() != null) {
GeneratorUtil.addPaymentLink(xml, item.getPaymentLink());
}
xml.endTag(null, "item");
}
}
xml.endTag(null, "channel");
xml.endTag(null, "rss");
xml.endDocument();
}
}