From 870dda9b8b1f31aef4dc984a58d61654647f30bb Mon Sep 17 00:00:00 2001 From: Rodrigo Damazio Date: Wed, 19 May 2010 17:35:08 -0700 Subject: [PATCH] Importing tests source --- .../apps/mytracks/io/CsvTrackWriterTest.java | 12 + .../apps/mytracks/io/GpxTrackWriterTest.java | 86 ++++ .../apps/mytracks/io/KmlTrackWriterTest.java | 113 ++++++ .../mytracks/io/TrackFormatWriterTest.java | 196 ++++++++++ .../apps/mytracks/io/TrackWriterTest.java | 367 ++++++++++++++++++ .../apps/mytracks/stats/DoubleBufferTest.java | 117 ++++++ .../mytracks/stats/ExtremityMonitorTest.java | 63 +++ .../mytracks/stats/TripStatisticsTest.java | 180 +++++++++ .../testing/TestingProviderUtilsFactory.java | 38 ++ .../mytracks/util/ChartURLGeneratorTest.java | 61 +++ 10 files changed, 1233 insertions(+) create mode 100644 MyTracksTest/src/com/google/android/apps/mytracks/io/CsvTrackWriterTest.java create mode 100644 MyTracksTest/src/com/google/android/apps/mytracks/io/GpxTrackWriterTest.java create mode 100644 MyTracksTest/src/com/google/android/apps/mytracks/io/KmlTrackWriterTest.java create mode 100644 MyTracksTest/src/com/google/android/apps/mytracks/io/TrackFormatWriterTest.java create mode 100644 MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java create mode 100644 MyTracksTest/src/com/google/android/apps/mytracks/stats/DoubleBufferTest.java create mode 100644 MyTracksTest/src/com/google/android/apps/mytracks/stats/ExtremityMonitorTest.java create mode 100644 MyTracksTest/src/com/google/android/apps/mytracks/stats/TripStatisticsTest.java create mode 100644 MyTracksTest/src/com/google/android/apps/mytracks/testing/TestingProviderUtilsFactory.java create mode 100644 MyTracksTest/src/com/google/android/apps/mytracks/util/ChartURLGeneratorTest.java diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/CsvTrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/CsvTrackWriterTest.java new file mode 100644 index 000000000..fa0d70deb --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/CsvTrackWriterTest.java @@ -0,0 +1,12 @@ +// Copyright 2010 Google Inc. All Rights Reserved. + +package com.google.android.apps.mytracks.io; + +/** + * Tests for the CSV track exporter. + * + * @author Rodrigo Damazio + */ +public class CsvTrackWriterTest extends TrackFormatWriterTest { + +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/GpxTrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/GpxTrackWriterTest.java new file mode 100644 index 000000000..c72924e22 --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/GpxTrackWriterTest.java @@ -0,0 +1,86 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +package com.google.android.apps.mytracks.io; + +import com.google.android.apps.mytracks.content.Waypoint; + +import android.location.Location; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.Date; +import java.util.List; + +/** + * Tests for the GPX track exporter. + * + * @author Rodrigo Damazio + */ +public class GpxTrackWriterTest extends TrackFormatWriterTest { + + public void testXmlOutput() throws Exception { + TrackFormatWriter writer = new GpxTrackWriter(); + String result = writeTrack(writer); + Document doc = parseXmlDocument(result); + + Element gpxTag = getChildElement(doc, "gpx"); + Element trackTag = getChildElement(gpxTag, "trk"); + assertEquals(TRACK_NAME, getChildTextValue(trackTag, "name")); + assertEquals(TRACK_DESCRIPTION, getChildTextValue(trackTag, "desc")); + assertEquals(Long.toString(TRACK_ID), + getChildTextValue(trackTag, "number")); + List segmentTags = getChildElements(trackTag, "trkseg", 2); + Element segment1Tag = segmentTags.get(0); + Element segment2Tag = segmentTags.get(1); + List seg1PointTags = getChildElements(segment1Tag, "trkpt", 2); + List seg2PointTags = getChildElements(segment2Tag, "trkpt", 2); + assertTagsMatchPoints(seg1PointTags, location1, location2); + assertTagsMatchPoints(seg2PointTags, location3, location4); + List waypointTags = getChildElements(gpxTag, "wpt", 2); + assertTagsMatchWaypoints(waypointTags, wp1, wp2); + } + + /** + * Asserts that the given tags describe the given waypoints, in the same + * order. + */ + protected void assertTagsMatchWaypoints(List tags, Waypoint... wps) { + assertEquals(wps.length, tags.size()); + for (int i = 0; i < wps.length; i++) { + Element tag = tags.get(i); + Waypoint wp = wps[i]; + Location loc = wp.getLocation(); + + assertTagMatchesLocation(tag, loc); + + assertEquals(wp.getName(), getChildTextValue(tag, "name")); + assertEquals(wp.getDescription(), getChildTextValue(tag, "desc")); + } + } + + /** + * Asserts that the given tags describe the given points, in the same order. + */ + protected void assertTagsMatchPoints(List tags, Location... locs) { + assertEquals(locs.length, tags.size()); + for (int i = 0; i < locs.length; i++) { + Element tag = tags.get(i); + Location loc = locs[i]; + + assertTagMatchesLocation(tag, loc); + } + } + + /** + * Asserts that the given tag describes the given location. + */ + private void assertTagMatchesLocation(Element tag, Location loc) { + assertEquals(Double.toString(loc.getLatitude()), tag.getAttribute("lat")); + assertEquals(Double.toString(loc.getLongitude()), tag.getAttribute("lon")); + assertEquals( + GpxTrackWriter.TIMESTAMP_FORMAT.format(new Date(loc.getTime())), + getChildTextValue(tag, "time")); + assertEquals(Double.toString(loc.getAltitude()), + getChildTextValue(tag, "ele")); + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/KmlTrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/KmlTrackWriterTest.java new file mode 100644 index 000000000..e3e735f2d --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/KmlTrackWriterTest.java @@ -0,0 +1,113 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +package com.google.android.apps.mytracks.io; + +import com.google.android.apps.mytracks.content.Track; +import com.google.android.apps.mytracks.util.StringUtils; + +import android.location.Location; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.List; +import java.util.Vector; + +/** + * Tests for the KML track exporter. + * + * @author Rodrigo Damazio + */ +public class KmlTrackWriterTest extends TrackFormatWriterTest { + private static final String FULL_TRACK_DESCRIPTION = "full track description"; + + /** + * A fake version of {@link StringUtils} which returns a fixed track + * description, thus not depending on the context. + */ + private class FakeStringUtils extends StringUtils { + public FakeStringUtils() { + super(null); + } + + @Override + public String generateTrackDescription(Track track, + Vector distances, Vector elevations) { + assertSame(KmlTrackWriterTest.super.track, track); + assertTrue(distances.isEmpty()); + assertTrue(elevations.isEmpty()); + return FULL_TRACK_DESCRIPTION; + } + } + + public void testXmlOutput() throws Exception { + KmlTrackWriter writer = new KmlTrackWriter(new FakeStringUtils()); + String result = writeTrack(writer); + Document doc = parseXmlDocument(result); + + Element kmlTag = getChildElement(doc, "kml"); + Element docTag = getChildElement(kmlTag, "Document"); + assertEquals(TRACK_NAME, getChildTextValue(docTag, "name")); + assertEquals(TRACK_DESCRIPTION, getChildTextValue(docTag, "description")); + + // There are 5 placemarks - start, segments, end, waypoint 1, waypoint 2 + List placemarkTags = getChildElements(docTag, "Placemark", 5); + assertTagIsPlacemark(placemarkTags.get(0), + "(Start)", TRACK_DESCRIPTION, location1); + assertTagIsPlacemark(placemarkTags.get(2), + "(End)", FULL_TRACK_DESCRIPTION, location4); + assertTagIsPlacemark(placemarkTags.get(3), + WAYPOINT1_NAME, WAYPOINT1_DESCRIPTION, location2); + assertTagIsPlacemark(placemarkTags.get(4), + WAYPOINT2_NAME, WAYPOINT2_DESCRIPTION, location3); + + Element trackPlacemarkTag = placemarkTags.get(1); + assertEquals(TRACK_NAME, getChildTextValue(trackPlacemarkTag, "name")); + assertEquals(TRACK_DESCRIPTION, + getChildTextValue(trackPlacemarkTag, "description")); + Element geometryTag = getChildElement(trackPlacemarkTag, "MultiGeometry"); + List segmentTags = getChildElements(geometryTag, "LineString", 2); + assertTagHasPoints(segmentTags.get(0), location1, location2); + assertTagHasPoints(segmentTags.get(1), location3, location4); + } + + /** + * Asserts that the given XML tag is a placemark with the given properties. + * + * @param tag the tag to analyze + * @param name the expected name for the placemark + * @param description the expected description for the placemark + * @param location the expected location of the placemark + */ + private void assertTagIsPlacemark(Element tag, String name, + String description, Location location) { + assertEquals(name, getChildTextValue(tag, "name")); + assertEquals(description, getChildTextValue(tag, "description")); + Element pointTag = getChildElement(tag, "Point"); + String expectedCoords = + location.getLongitude() + "," + location.getLatitude(); + String actualCoords = getChildTextValue(pointTag, "coordinates"); + assertEquals(expectedCoords, actualCoords); + } + + /** + * Asserts that the given tag has a "coordinates" subtag with the given + * locations. + * + * @param tag the tag to analyze + * @param locs the locations to expect in the coordinates + */ + private void assertTagHasPoints(Element tag, Location... locs) { + StringBuilder expectedBuilder = new StringBuilder(); + for (Location loc : locs) { + expectedBuilder.append(loc.getLongitude()); + expectedBuilder.append(','); + expectedBuilder.append(loc.getLatitude()); + expectedBuilder.append(','); + expectedBuilder.append(loc.getAltitude()); + expectedBuilder.append(' '); + } + String expectedCoordinates = expectedBuilder.toString().trim(); + String actualCoordinates = getChildTextValue(tag, "coordinates").trim(); + assertEquals(expectedCoordinates, actualCoordinates); + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackFormatWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackFormatWriterTest.java new file mode 100644 index 000000000..8e4bd0e44 --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackFormatWriterTest.java @@ -0,0 +1,196 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +package com.google.android.apps.mytracks.io; + +import com.google.android.apps.mytracks.content.Track; +import com.google.android.apps.mytracks.content.Waypoint; + +import android.location.Location; +import android.test.AndroidTestCase; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.FactoryConfigurationError; +import javax.xml.parsers.ParserConfigurationException; + +/** + * Base class for track format writer tests, which sets up a fake track and + * gives auxiliary methods for verifying XML output. + * + * @author Rodrigo Damazio + */ +public abstract class TrackFormatWriterTest extends AndroidTestCase { + + // All the user-provided strings have "]]>" to ensure that proper escaping is + // being done. + protected static final String TRACK_NAME = "Home]]>"; + protected static final String TRACK_DESCRIPTION = "The long ]]> journey home"; + protected static final String WAYPOINT1_NAME = "point]]>1"; + protected static final String WAYPOINT1_DESCRIPTION = "point 1]]>description"; + protected static final String WAYPOINT2_NAME = "point]]>2"; + protected static final String WAYPOINT2_DESCRIPTION = "point 2]]>description"; + private static final int BUFFER_SIZE = 10240; + protected static final long TRACK_ID = 12345L; + protected Track track; + protected Location location1, location2, location3, location4; + protected Waypoint wp1, wp2; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + track = new Track(); + track.setId(TRACK_ID); + track.setName(TRACK_NAME); + track.setDescription(TRACK_DESCRIPTION); + + location1 = new Location("mock"); + location2 = new Location("mock"); + location3 = new Location("mock"); + location4 = new Location("mock"); + populateLocations(location1, location2, location3, location4); + + wp1 = new Waypoint(); + wp2 = new Waypoint(); + wp1.setLocation(location2); + wp1.setName(WAYPOINT1_NAME); + wp1.setDescription(WAYPOINT1_DESCRIPTION); + wp2.setLocation(location3); + wp2.setName(WAYPOINT2_NAME); + wp2.setDescription(WAYPOINT2_DESCRIPTION); + } + + /** + * Populates the given locations with coordinates and time. + */ + protected void populateLocations(Location... locs) { + for (int i = 0; i < locs.length; i++) { + Location loc = locs[i]; + loc.setAltitude(i * 1000); + loc.setLatitude(i); + loc.setLongitude(-i); + loc.setTime(10000000 + i * 1000); + } + } + + /** + * Makes the right sequence of calls to the writer in order to write the fake + * track in {@link #track}. + * + * @param writer the writer to write to + * @return the written contents + */ + protected String writeTrack(TrackFormatWriter writer) { + OutputStream output = new ByteArrayOutputStream(BUFFER_SIZE); + writer.prepare(track, output); + writer.writeHeader(); + writer.writeBeginTrack(location1); + writer.writeOpenSegment(); + writer.writeLocation(location1); + writer.writeLocation(location2); + writer.writeCloseSegment(); + writer.writeOpenSegment(); + writer.writeLocation(location3); + writer.writeLocation(location4); + writer.writeCloseSegment(); + writer.writeEndTrack(location4); + writer.writeWaypoint(wp1); + writer.writeWaypoint(wp2); + writer.writeFooter(); + writer.close(); + return output.toString(); + } + + /** + * Gets the text data contained inside a tag. + * + * @param parent the parent of the tag containing the text + * @param elementName the name of the tag containing the text + * @return the text contents + */ + protected String getChildTextValue(Element parent, String elementName) { + Element child = getChildElement(parent, elementName); + assertTrue(child.hasChildNodes()); + NodeList children = child.getChildNodes(); + int length = children.getLength(); + assertTrue(length > 0); + + // The children may be a sucession of text elements, just concatenate them + String result = ""; + for (int i = 0; i < length; i++) { + Text textNode = (Text) children.item(i); + result += textNode.getNodeValue(); + } + return result; + } + + /** + * Returns all child elements of a given parent which gave the given name. + * + * @param parent the parent to get children from + * @param elementName the element name to look for + * @param expectedChildren the number of children we're expected to find + * @return a list of the found elements + */ + protected List getChildElements(Node parent, String elementName, + int expectedChildren) { + assertTrue(parent.hasChildNodes()); + NodeList children = parent.getChildNodes(); + int length = children.getLength(); + List result = new ArrayList(); + for (int i = 0; i < length; i++) { + Node childNode = children.item(i); + if (childNode.getNodeType() == Node.ELEMENT_NODE + && childNode.getNodeName().equalsIgnoreCase(elementName)) { + result.add((Element) childNode); + } + } + assertTrue(children.toString(), result.size() == expectedChildren); + return result; + } + + /** + * Returns the single child element of the given parent with the given type. + * + * @param parent the parent to get a child from + * @param elementName the name of the child to look for + * @return the child element + */ + protected Element getChildElement(Node parent, String elementName) { + return getChildElements(parent, elementName, 1).get(0); + } + + /** + * Parses the given XML contents and returns a DOM {@link Document} for it. + */ + protected Document parseXmlDocument(String contents) + throws FactoryConfigurationError, ParserConfigurationException, + SAXException, IOException { + DocumentBuilderFactory builderFactory = + DocumentBuilderFactory.newInstance(); + builderFactory.setCoalescing(true); + // TODO: Somehow do XML validation on Android + // builderFactory.setValidating(true); + builderFactory.setNamespaceAware(true); + builderFactory.setIgnoringComments(true); + builderFactory.setIgnoringElementContentWhitespace(true); + DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder(); + Document doc = documentBuilder.parse( + new InputSource(new StringReader(contents))); + return doc; + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java new file mode 100644 index 000000000..05de8e7d3 --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java @@ -0,0 +1,367 @@ +// Copyright 2010 Google Inc. All Rights Reserved. + +package com.google.android.apps.mytracks.io; + +import com.google.android.apps.mytracks.MyTracksConstants; +import com.google.android.apps.mytracks.content.MyTracksProviderUtils; +import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory; +import com.google.android.apps.mytracks.content.Track; +import com.google.android.apps.mytracks.content.TrackBuffer; +import com.google.android.apps.mytracks.content.Waypoint; +import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory; + +import android.content.Context; +import android.database.Cursor; +import android.location.Location; +import android.test.AndroidTestCase; + +import org.easymock.EasyMock; +import org.easymock.IAnswer; + +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.isA; +import static org.easymock.EasyMock.same; +import org.easymock.IMocksControl; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.OutputStream; + +/** + * Tests for the track writer. + * + * @author Rodrigo Damazio + */ +public class TrackWriterTest extends AndroidTestCase { + /** + * {@link TrackWriter} subclass which mocks out methods called from + * {@link TrackWriter#openFile}. + */ + private static final class OpenFileTrackWriter extends TrackWriter { + private final ByteArrayOutputStream stream; + private final boolean canWrite; + + /** + * Constructor. + * + * @param stream the stream to return from + * {@link TrackWriter#newOutputStream}, or null to throw a + * {@link FileNotFoundException} + * @param canWrite the value that {@link TrackWriter#canWriteFile} will + * return + */ + private OpenFileTrackWriter(Context context, + MyTracksProviderUtils providerUtils, Track track, + TrackFormatWriter writer, ByteArrayOutputStream stream, + boolean canWrite) { + super(context, providerUtils, track, writer); + + this.stream = stream; + this.canWrite = canWrite; + } + + @Override + protected boolean canWriteFile() { + return canWrite; + } + + @Override + protected OutputStream newOutputStream(String fileName) + throws FileNotFoundException { + assertEquals(FULL_TRACK_NAME, fileName); + + if (stream == null) { + throw new FileNotFoundException(); + } + + return stream; + } + } + + /** + * {@link TrackWriter} subclass which mocks out methods called from + * {@link TrackWriter#writeTrack}. + */ + private final class WriteTracksTrackWriter extends TrackWriter { + private final boolean openResult; + + /** + * Constructor. + * + * @param openResult the return value for {@link TrackWriter#openFile} + */ + private WriteTracksTrackWriter(Context context, + MyTracksProviderUtils providerUtils, Track track, + TrackFormatWriter writer, boolean openResult) { + super(context, providerUtils, track, writer); + this.openResult = openResult; + } + + @Override + protected boolean openFile() { + openFileCalls++; + return openResult; + } + + @Override + void writeDocument() { + writeDocumentCalls++; + } + + @Override + protected void runOnUiThread(Runnable runnable) { + runnable.run(); + } + } + + private static final long TRACK_ID = 1234567L; + private static final String DEFAULT_DIR = "default/dir"; + private static final String EXTENSION = "ext"; + private static final String TRACK_NAME = "Swimming across the pacific"; + private static final String TRACK_SANITIZED_NAME = "Swimmingacrossthepacific"; + private static final String FULL_TRACK_NAME = "Swimmingacrossthepacific.ext"; + + private Track track; + private TrackFormatWriter formatWriter; + private TrackWriter writer; + private IMocksControl mocksControl; + private MyTracksProviderUtils providerUtils; + private Factory oldProviderUtilsFactory; + + // State used in specific tests + private int writeDocumentCalls; + private int openFileCalls; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mocksControl = EasyMock.createStrictControl(); + formatWriter = mocksControl.createMock(TrackFormatWriter.class); + providerUtils = mocksControl.createMock(MyTracksProviderUtils.class); + oldProviderUtilsFactory = + TestingProviderUtilsFactory.installWithInstance(providerUtils); + + expect(formatWriter.getDefaultDirectory()).andStubReturn(DEFAULT_DIR); + expect(formatWriter.getExtension()).andStubReturn(EXTENSION); + + track = new Track(); + track.setName(TRACK_NAME); + track.setId(TRACK_ID); + } + + @Override + protected void tearDown() throws Exception { + TestingProviderUtilsFactory.restoreOldFactory(oldProviderUtilsFactory); + super.tearDown(); + } + + public void testWriteTrack() { + writer = new WriteTracksTrackWriter(getContext(), providerUtils, track, + formatWriter, true); + + // Expect the completion callback to be run + Runnable completionCallback = mocksControl.createMock(Runnable.class); + completionCallback.run(); + + mocksControl.replay(); + writer.setOnCompletion(completionCallback); + writer.writeTrack(); + + assertEquals(1, writeDocumentCalls); + assertEquals(1, openFileCalls); + mocksControl.verify(); + } + + public void testWriteTrack_openFails() { + writer = new WriteTracksTrackWriter(getContext(), providerUtils, track, + formatWriter, false); + + // Expect the completion callback to be run + Runnable completionCallback = mocksControl.createMock(Runnable.class); + completionCallback.run(); + + mocksControl.replay(); + writer.setOnCompletion(completionCallback); + writer.writeTrack(); + + assertEquals(0, writeDocumentCalls); + assertEquals(1, openFileCalls); + mocksControl.verify(); + } + + public void testSanitizeName() { + assertEquals(TRACK_SANITIZED_NAME, TrackWriter.sanitizeName(TRACK_NAME)); + } + + public void testOpenFile() { + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + writer = new OpenFileTrackWriter( + getContext(), providerUtils, track, formatWriter, stream, true); + + formatWriter.prepare(track, stream); + + mocksControl.replay(); + assertTrue(writer.openFile()); + mocksControl.verify(); + } + + public void testOpenFile_cantWrite() { + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + writer = new OpenFileTrackWriter( + getContext(), providerUtils, track, formatWriter, stream, false); + + mocksControl.replay(); + assertFalse(writer.openFile()); + mocksControl.verify(); + } + + public void testOpenFile_streamError() { + writer = new OpenFileTrackWriter( + getContext(), providerUtils, track, formatWriter, null, true); + + mocksControl.replay(); + assertFalse(writer.openFile()); + mocksControl.verify(); + } + + public void testWriteDocument_emptyTrack() { + writer = new TrackWriter(getContext(), providerUtils, track, formatWriter); + + // Don't let it write any waypoints + expect(providerUtils.getWaypointsCursor( + TRACK_ID, 0, MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS)) + .andStubReturn(null); + + // Set expected mock behavior + formatWriter.writeHeader(); + formatWriter.writeFooter(); + formatWriter.close(); + + mocksControl.replay(); + writer.writeDocument(); + + assertTrue(writer.wasSuccess()); + mocksControl.verify(); + } + + public void testWriteDocument() { + writer = new TrackWriter(getContext(), providerUtils, track, formatWriter); + + Location l1 = new Location("fake1"); + Location l2 = new Location("fake2"); + Location l3 = new Location("fake3"); + Location l4 = new Location("fake4"); + Location l5 = new Location("fake5"); + Location l6 = new Location("fake6"); + Waypoint p1 = new Waypoint(); + Waypoint p2 = new Waypoint(); + + addLocations(l1, l2, l3, l4, l5, l6); + stubBufferFill( + new Location[] { l1, l2, l3, l4 }, + new Location[] { l5, l6 }); + + track.setStopId(6L); + + // Make location 3 invalid + l3.setLatitude(100); + + // Begin the track + formatWriter.writeHeader(); + formatWriter.writeBeginTrack(l1); + + // Write locations 1-2 + formatWriter.writeOpenSegment(); + formatWriter.writeLocation(l1); + formatWriter.writeLocation(l2); + formatWriter.writeCloseSegment(); + + // Location 3 is not written - it's invalid + + // Write locations 4-6 + formatWriter.writeOpenSegment(); + formatWriter.writeLocation(l4); + formatWriter.writeLocation(l5); + formatWriter.writeLocation(l6); + formatWriter.writeCloseSegment(); + + // End the track + formatWriter.writeEndTrack(l6); + + // Expect reading/writing of the waypoints + Cursor cursor = mocksControl.createMock(Cursor.class); + expect(providerUtils.getWaypointsCursor( + TRACK_ID, 0, MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS)) + .andStubReturn(cursor); + expect(cursor.moveToFirst()).andReturn(true); + expect(cursor.moveToNext()).andReturn(true); + expect(providerUtils.createWaypoint(cursor)).andReturn(p1); + formatWriter.writeWaypoint(p1); + expect(cursor.moveToNext()).andReturn(true); + expect(providerUtils.createWaypoint(cursor)).andReturn(p2); + formatWriter.writeWaypoint(p2); + expect(cursor.moveToNext()).andReturn(false).anyTimes(); + cursor.close(); + + formatWriter.writeFooter(); + formatWriter.close(); + + mocksControl.replay(); + writer.writeDocument(); + + assertTrue(writer.wasSuccess()); + mocksControl.verify(); + } + + private void addLocations(Location... locs) { + assertTrue(locs.length < 90); + for (int i = 0; i < locs.length; i++) { + Location location = locs[i]; + location.setLatitude(i + 1); + location.setLongitude(i + 1); + } + } + + /** + * Defines the behaviour of filling the track buffer when a read is + * requested. + * The IDs of the locations will be their sequential number. + * + * @param feeds is a list of location arrays, each element of which + * will be fed into the track buffer on each call + */ + private void stubBufferFill(final Location[]... feeds) { + providerUtils.getTrackPoints(same(track), isA(TrackBuffer.class)); + EasyMock.expectLastCall().andStubAnswer(new IAnswer() { + private int lastId = 1; + private int reads = 0; + + @Override + public Void answer() throws Throwable { + // Get the buffer from the arguments + Object[] args = EasyMock.getCurrentArguments(); + assertEquals(2, args.length); + TrackBuffer buffer = (TrackBuffer) args[1]; + assertNotNull(buffer); + + // Check that we still have data to feed to the buffer + if (reads >= feeds.length) { + fail("More buffer reads than expected"); + } + + // Fill the buffer + buffer.reset(); + Location[] locations = feeds[reads]; + for (int i = 0; i < locations.length; i++) { + buffer.add(locations[i], lastId + i); + } + + // Update internal state + lastId += locations.length; + reads++; + return null; + } + }); + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/stats/DoubleBufferTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/stats/DoubleBufferTest.java new file mode 100644 index 000000000..0af9196e5 --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/stats/DoubleBufferTest.java @@ -0,0 +1,117 @@ +/* + * Copyright 2009 Google Inc. All Rights Reserved. + */ + +package com.google.android.apps.mytracks.stats; + +import junit.framework.TestCase; + +/** + * Test for the DoubleBuffer class. + * + * @author Sandor Dornbush + */ +public class DoubleBufferTest extends TestCase { + + /** + * Tests that the constructor leaves the buffer in a valid state. + */ + public void testConstructor() { + DoubleBuffer buffer = new DoubleBuffer(10); + assertFalse(buffer.isFull()); + assertEquals(0.0, buffer.getAverage()); + double[] averageAndVariance = buffer.getAverageAndVariance(); + assertEquals(0.0, averageAndVariance[0]); + assertEquals(0.0, averageAndVariance[1]); + } + + /** + * Simple test with 10 of the same values. + */ + public void testBasic() { + DoubleBuffer buffer = new DoubleBuffer(10); + + for (int i = 0; i < 9; i++) { + buffer.setNext(1.0); + assertFalse(buffer.isFull()); + assertEquals(1.0, buffer.getAverage()); + double[] averageAndVariance = buffer.getAverageAndVariance(); + assertEquals(1.0, averageAndVariance[0]); + assertEquals(0.0, averageAndVariance[1]); + } + buffer.setNext(1); + assertTrue(buffer.isFull()); + assertEquals(1.0, buffer.getAverage()); + double[] averageAndVariance = buffer.getAverageAndVariance(); + assertEquals(1.0, averageAndVariance[0]); + assertEquals(0.0, averageAndVariance[1]); + } + + /** + * Tests with 5 entries of -10 and 5 entries of 10. + */ + public void testSplit() { + DoubleBuffer buffer = new DoubleBuffer(10); + + for (int i = 0; i < 5; i++) { + buffer.setNext(-10); + assertFalse(buffer.isFull()); + assertEquals(-10.0, buffer.getAverage()); + double[] averageAndVariance = buffer.getAverageAndVariance(); + assertEquals(-10.0, averageAndVariance[0]); + assertEquals(0.0, averageAndVariance[1]); + } + + for (int i = 1; i < 5; i++) { + buffer.setNext(10); + assertFalse(buffer.isFull()); + double expectedAverage = ((i * 10.0) - 50.0) / (i + 5); + assertEquals(buffer.toString(), + expectedAverage, buffer.getAverage(), 0.01); + double[] averageAndVariance = buffer.getAverageAndVariance(); + assertEquals(expectedAverage, averageAndVariance[0]); + } + buffer.setNext(10); + assertTrue(buffer.isFull()); + assertEquals(0.0, buffer.getAverage()); + double[] averageAndVariance = buffer.getAverageAndVariance(); + assertEquals(0.0, averageAndVariance[0]); + assertEquals(100.0, averageAndVariance[1]); + } + + /** + * Tests that reset leaves the buffer in a valid state. + */ + public void testReset() { + DoubleBuffer buffer = new DoubleBuffer(10); + + for (int i = 0; i < 100; i++) { + buffer.setNext(i); + } + assertTrue(buffer.isFull()); + + buffer.reset(); + assertFalse(buffer.isFull()); + assertEquals(0.0, buffer.getAverage()); + double[] averageAndVariance = buffer.getAverageAndVariance(); + assertEquals(0.0, averageAndVariance[0]); + assertEquals(0.0, averageAndVariance[1]); + } + + + /** + * Tests that if a lot of items are inserted the smoothing and looping works. + */ + public void testLoop() { + DoubleBuffer buffer = new DoubleBuffer(10); + + for (int i = 0; i < 1000; i++) { + buffer.setNext(i); + assertEquals(i >= 9, buffer.isFull()); + if (i > 10) { + assertEquals(i - 4.5, buffer.getAverage()); + } + } + } + +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/stats/ExtremityMonitorTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/stats/ExtremityMonitorTest.java new file mode 100644 index 000000000..d5cba3126 --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/stats/ExtremityMonitorTest.java @@ -0,0 +1,63 @@ +/** + * Copyright 2009 Google Inc. All Rights Reserved. + */ +package com.google.android.apps.mytracks.stats; + +import junit.framework.TestCase; +import java.util.Random; + +/** + * This class test the ExtremityMonitor class. + * + * @author Sandor Dornbush + */ +public class ExtremityMonitorTest extends TestCase { + + public ExtremityMonitorTest(String name) { + super(name); + } + + public void testInitialize() { + ExtremityMonitor monitor = new ExtremityMonitor(); + assertEquals(Double.POSITIVE_INFINITY, monitor.getMin()); + assertEquals(Double.NEGATIVE_INFINITY, monitor.getMax()); + } + + public void testSimple() { + ExtremityMonitor monitor = new ExtremityMonitor(); + assertTrue(monitor.update(0)); + assertTrue(monitor.update(1)); + assertEquals(0.0, monitor.getMin()); + assertEquals(1.0, monitor.getMax()); + assertFalse(monitor.update(1)); + assertFalse(monitor.update(0.5)); + } + + /** + * Throws a bunch of random numbers between [0,1] at the monitor. + */ + public void testRandom() { + ExtremityMonitor monitor = new ExtremityMonitor(); + Random random = new Random(42); + for (int i = 0; i < 1000; i++) { + monitor.update(random.nextDouble()); + } + assertTrue(monitor.getMin() < 0.1); + assertTrue(monitor.getMax() < 1.0); + assertTrue(monitor.getMin() >= 0.0); + assertTrue(monitor.getMax() > 0.9); + } + + public void testReset() { + ExtremityMonitor monitor = new ExtremityMonitor(); + assertTrue(monitor.update(0)); + assertTrue(monitor.update(1)); + monitor.reset(); + assertEquals(Double.POSITIVE_INFINITY, monitor.getMin()); + assertEquals(Double.NEGATIVE_INFINITY, monitor.getMax()); + assertTrue(monitor.update(0)); + assertTrue(monitor.update(1)); + assertEquals(0.0, monitor.getMin()); + assertEquals(1.0, monitor.getMax()); + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/stats/TripStatisticsTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/stats/TripStatisticsTest.java new file mode 100644 index 000000000..f8bc1b64a --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/stats/TripStatisticsTest.java @@ -0,0 +1,180 @@ +// Copyright 2009 Google Inc. All Rights Reserved. + +package com.google.android.apps.mytracks.stats; + +import com.google.android.apps.mytracks.MyTracksConstants; + +import android.location.Location; + +import junit.framework.TestCase; + +/** + * Test the the function of the TripStatistics class. + * + * @author Sandor Dornbush + */ +public class TripStatisticsTest extends TestCase { + + private TripStatistics stats = null; + + @Override + protected void setUp() throws Exception { + super.setUp(); + stats = new TripStatistics(); + } + + public void testAddLocationSimple() throws Exception { + stats = new TripStatistics(1000); + + assertEquals(0.0, stats.getSmoothedElevation()); + assertEquals(Double.POSITIVE_INFINITY, stats.getMinElevation()); + assertEquals(Double.NEGATIVE_INFINITY, stats.getMaxElevation()); + assertEquals(0.0, stats.getMaxSpeed()); + assertEquals(Double.POSITIVE_INFINITY, stats.getMinGrade()); + assertEquals(Double.NEGATIVE_INFINITY, stats.getMaxGrade()); + assertEquals(0.0, stats.getTotalElevationGain()); + assertEquals(0, stats.getMovingTime()); + assertEquals(0.0, stats.getTotalDistance()); + for (int i = 0; i < 100; i++) { + Location l = new Location("test"); + l.setAccuracy(1.0f); + l.setLongitude(45.0); + + // Going up by 5 meters each time. + l.setAltitude(i); + // Moving by .1% of a degree latitude. + l.setLatitude(i * .001); + l.setSpeed(11.1f); + // Each time slice is 10 seconds. + long time = 1000 + 10000 * i; + l.setTime(time); + boolean moving = stats.addLocation(l, time); + assertEquals((i != 0), moving); + + assertEquals(10000 * i, stats.getTotalTime()); + System.out.println("i: " + i + "\nLocation: " + l + "\nStats: " + stats); + assertEquals(10000 * i, stats.getMovingTime()); + assertEquals(i, stats.getSmoothedElevation(), + MyTracksConstants.ELEVATION_SMOOTHING_FACTOR / 2); + assertEquals(0.0, stats.getMinElevation()); + assertEquals(i , stats.getMaxElevation(), + MyTracksConstants.ELEVATION_SMOOTHING_FACTOR / 2); + assertEquals(i, stats.getTotalElevationGain(), + MyTracksConstants.ELEVATION_SMOOTHING_FACTOR); + if (i > MyTracksConstants.SPEED_SMOOTHING_FACTOR) { + assertEquals(11.1f, stats.getMaxSpeed(), 0.1); + } + if ((i > MyTracksConstants.GRADE_SMOOTHING_FACTOR) + && (i > MyTracksConstants.ELEVATION_SMOOTHING_FACTOR)) { + assertEquals(0.009, stats.getMinGrade(), 0.0001); + assertEquals(0.009, stats.getMaxGrade(), 0.0001); + } + // 1 degree = 111 km + // 1 timeslice = 0.001 degree = 111 m + assertEquals(111.0 * i, stats.getTotalDistance(), 100); + } + } + + /** + * Test that elevation works if the user is stable. + */ + public void testElevationSimple() throws Exception { + for (double elevation = 0; elevation < 1000; elevation += 10) { + stats = new TripStatistics(); + for (int j = 0; j < 100; j++) { + assertEquals(0.0, stats.updateElevation(elevation)); + assertEquals(elevation, stats.getSmoothedElevation()); + assertEquals(elevation, stats.getMinElevation()); + assertEquals(elevation, stats.getMaxElevation()); + assertEquals(0.0, stats.getTotalElevationGain()); + } + } + } + + public void testElevationGain() throws Exception { + for (double i = 0; i < 1000; i++) { + double expectedGain; + if (i < (MyTracksConstants.ELEVATION_SMOOTHING_FACTOR - 1)) { + expectedGain = 0; + } else if (i < MyTracksConstants.ELEVATION_SMOOTHING_FACTOR) { + expectedGain = 0.5; + } else { + expectedGain = 1.0; + } + assertEquals(expectedGain, + stats.updateElevation(i)); + assertEquals(i, stats.getSmoothedElevation(), 20); + assertEquals(0.0, stats.getMinElevation(), 0.0); + assertEquals(i, stats.getMaxElevation(), + MyTracksConstants.ELEVATION_SMOOTHING_FACTOR); + assertEquals(i, stats.getTotalElevationGain(), + MyTracksConstants.ELEVATION_SMOOTHING_FACTOR); + } + } + + public void testGradeSimple() throws Exception { + for (double i = 0; i < 1000; i++) { + // The value of the elevation does not matter. This is just to fill the + // buffer. + stats.updateElevation(i); + stats.updateGrade(100, 100); + if ((i > MyTracksConstants.GRADE_SMOOTHING_FACTOR) + && (i > MyTracksConstants.ELEVATION_SMOOTHING_FACTOR)) { + assertEquals(1.0, stats.getMaxGrade()); + assertEquals(1.0, stats.getMinGrade()); + } + } + for (double i = 0; i < 1000; i++) { + // The value of the elevation does not matter. This is just to fill the + // buffer. + stats.updateElevation(i); + stats.updateGrade(100, -100); + if ((i > MyTracksConstants.GRADE_SMOOTHING_FACTOR) + && (i > MyTracksConstants.ELEVATION_SMOOTHING_FACTOR)) { + assertEquals(1.0, stats.getMaxGrade()); + assertEquals(-1.0, stats.getMinGrade()); + } + } + } + + public void testGradeIgnoreShort() throws Exception { + for (double i = 0; i < 100; i++) { + // The value of the elevation does not matter. This is just to fill the + // buffer. + stats.updateElevation(i); + stats.updateGrade(1, 100); + assertEquals(Double.NEGATIVE_INFINITY, stats.getMaxGrade()); + assertEquals(Double.POSITIVE_INFINITY, stats.getMinGrade()); + } + } + + public void testUpdateSpeedIncludeZero() { + for (int i = 0; i < 1000; i++) { + stats.updateSpeed(i + 1000, 0.0, i, 4.0); + assertEquals(0.0, stats.getMaxSpeed()); + assertEquals((i + 1) * 1000, stats.getMovingTime()); + } + } + + public void testUpdateSpeedIngoreErrorCode() { + stats.updateSpeed(12345000, 128.0, 12344000, 0.0); + assertEquals(0.0, stats.getMaxSpeed()); + assertEquals(1000, stats.getMovingTime()); + } + + public void testUpdateSpeedIngoreLargeAcceleration() { + stats.updateSpeed(12345000, 100.0, 12344000, 1.0); + assertEquals(0.0, stats.getMaxSpeed()); + assertEquals(1000, stats.getMovingTime()); + } + + public void testUpdateSpeed() { + for (int i = 0; i < 1000; i++) { + stats.updateSpeed(i + 1000, 4.0, i, 4.0); + assertEquals((i + 1) * 1000, stats.getMovingTime()); + if (i > MyTracksConstants.SPEED_SMOOTHING_FACTOR) { + assertEquals(4.0, stats.getMaxSpeed()); + } + } + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/testing/TestingProviderUtilsFactory.java b/MyTracksTest/src/com/google/android/apps/mytracks/testing/TestingProviderUtilsFactory.java new file mode 100644 index 000000000..64705dce1 --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/testing/TestingProviderUtilsFactory.java @@ -0,0 +1,38 @@ +// Copyright 2010 Google Inc. All Rights Reserved. + +package com.google.android.apps.mytracks.testing; + +import com.google.android.apps.mytracks.content.MyTracksProviderUtils; +import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory; + +import android.content.Context; + +/** + * A fake factory for {@link MyTracksProviderUtils} which always returns a + * predefined instance. + * + * @author Rodrigo Damazio + */ +public class TestingProviderUtilsFactory extends Factory { + private MyTracksProviderUtils instance; + + public TestingProviderUtilsFactory(MyTracksProviderUtils instance) { + this.instance = instance; + } + + @Override + protected MyTracksProviderUtils newForContext(Context context) { + return instance; + } + + public static Factory installWithInstance(MyTracksProviderUtils instance) { + Factory oldFactory = Factory.getInstance(); + Factory factory = new TestingProviderUtilsFactory(instance); + MyTracksProviderUtils.Factory.overrideInstance(factory); + return oldFactory; + } + + public static void restoreOldFactory(Factory factory) { + MyTracksProviderUtils.Factory.overrideInstance(factory); + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/util/ChartURLGeneratorTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/util/ChartURLGeneratorTest.java new file mode 100644 index 000000000..ceb714783 --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/util/ChartURLGeneratorTest.java @@ -0,0 +1,61 @@ +// Copyright 2009 Google Inc. All Rights Reserved. + +package com.google.android.apps.mytracks.util; + +import com.google.android.apps.mytracks.content.Track; + +import junit.framework.TestCase; + +import java.util.Vector; + +/** + * Tests for the Chart URL generator. + * + * @author Sandor Dornbush + */ +public class ChartURLGeneratorTest extends TestCase { + + public void testgetChartUrl() { + Vector distances = new Vector(); + Vector elevations = new Vector(); + Track t = new Track(); + t.setMinElevation(0); + t.setMaxElevation(2000); + t.setTotalDistance(100); + + distances.add(0.0); + elevations.add(10.0); + + distances.add(10.0); + elevations.add(300.0); + + distances.add(20.0); + elevations.add(800.0); + + distances.add(50.0); + elevations.add(1900.0); + + distances.add(75.0); + elevations.add(1200.0); + + distances.add(90.0); + elevations.add(700.0); + + distances.add(100.0); + elevations.add(70.0); + + String chart = ChartURLGenerator.getChartUrl(distances, + elevations, + t, + "Title", + true, + false); + + assertEquals( + "http://chart.apis.google.com/chart?&chs=600x350&cht=lxy&" + + "chtt=Title&chxt=x,y&chxr=0,0,0,0|1,0.0,2100.0,300&chco=009A00&" + + "chm=B,00AA00,0,0,0&chg=100000,14.285714285714286,1,0&" + + "chd=e:AAGZMzf.v.5l..,ATJJYY55kkVVCI", + chart); + } +}