/*
* Copyright (c) 2013, Will Szumski
* Copyright (c) 2013, Doug Szumski
*
* This file is part of Cyclismo.
*
* Cyclismo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Cyclismo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cyclismo. If not, see <http://www.gnu.org/licenses/>.
*/
// Copyright 2010 Google Inc. All Rights Reserved.
package org.cowboycoders.cyclismo.io.file;
import android.content.Context;
import android.location.Location;
import android.test.AndroidTestCase;
import android.test.RenamingDelegatingContext;
import android.test.mock.MockContentResolver;
import org.cowboycoders.cyclismo.content.MyTracksProvider;
import org.cowboycoders.cyclismo.content.MyTracksProviderUtils;
import org.cowboycoders.cyclismo.content.MyTracksProviderUtils.Factory;
import org.cowboycoders.cyclismo.content.Track;
import org.cowboycoders.cyclismo.content.Waypoint;
import org.cowboycoders.cyclismo.services.TrackRecordingServiceTest.MockContext;
import org.cowboycoders.cyclismo.testing.TestingProviderUtilsFactory;
import org.easymock.EasyMock;
import org.easymock.IArgumentMatcher;
import org.easymock.IMocksControl;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import static org.easymock.EasyMock.expect;
/**
* Tests for the track writer.
*
* @author Rodrigo Damazio
*/
public class TrackWriterTest extends AndroidTestCase {
/**
* {@link TrackWriterImpl} subclass which mocks out methods called from
* {@link TrackWriterImpl#openFile}.
*/
private static final class OpenFileTrackWriter extends TrackWriterImpl {
private final ByteArrayOutputStream stream;
private final boolean canWrite;
/**
* Constructor.
*
* @param stream the stream to return from
* {@link TrackWriterImpl#newOutputStream}, or null to throw a
* {@link FileNotFoundException}
* @param canWrite the value that {@link TrackWriterImpl#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;
// The directory is set in the canWriteFile. However, this class
// overwrites canWriteFile, thus needs to set it.
setDirectory(new File("/"));
}
@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 TrackWriterImpl} subclass which mocks out methods called from
* {@link TrackWriterImpl#writeTrack}.
*/
private final class WriteTracksTrackWriter extends TrackWriterImpl {
private final boolean openResult;
/**
* Constructor.
*
* @param openResult the return value for {@link TrackWriterImpl#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 EXTENSION = "ext";
private static final String TRACK_NAME = "Swimming across the pacific";
private static final String FULL_TRACK_NAME =
"Swimming across the pacific.ext";
private Track track;
private TrackFormatWriter formatWriter;
private TrackWriterImpl 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();
MockContentResolver mockContentResolver = new MockContentResolver();
RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
getContext(), getContext(), "test.");
Context context = new MockContext(mockContentResolver, targetContext);
MyTracksProvider provider = new MyTracksProvider();
provider.attachInfo(context, null);
mockContentResolver.addProvider(MyTracksProviderUtils.AUTHORITY, provider);
setContext(context);
providerUtils = MyTracksProviderUtils.Factory.get(context);
oldProviderUtilsFactory = TestingProviderUtilsFactory.installWithInstance(providerUtils);
mocksControl = EasyMock.createStrictControl();
formatWriter = mocksControl.createMock(TrackFormatWriter.class);
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);
mocksControl.replay();
writer.writeTrack();
assertEquals(1, writeDocumentCalls);
assertEquals(1, openFileCalls);
mocksControl.verify();
}
public void testWriteTrack_openFails() {
writer = new WriteTracksTrackWriter(getContext(), providerUtils, track,
formatWriter, false);
mocksControl.replay();
writer.writeTrack();
assertEquals(0, writeDocumentCalls);
assertEquals(1, openFileCalls);
mocksControl.verify();
}
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() throws Exception {
writer = new TrackWriterImpl(getContext(), providerUtils, track, formatWriter);
// Set expected mock behavior
formatWriter.writeHeader();
formatWriter.writeBeginTrack(null);
formatWriter.writeEndTrack(null);
formatWriter.writeFooter();
formatWriter.close();
mocksControl.replay();
writer.writeDocument();
assertTrue(writer.wasSuccess());
mocksControl.verify();
}
/**
* Tests when a track only contains invalid locations. Make sure an empty
* track is written.
*/
public void testWriteDocument_oneInvalidLocation() throws Exception {
writer = new TrackWriterImpl(getContext(), providerUtils, track, formatWriter);
Location[] locs = {new Location("fake0")};
fillLocations(locs);
// Make location invalid
locs[0].setLatitude(100);
assertEquals(locs.length, providerUtils.bulkInsertTrackPoint(locs, locs.length, TRACK_ID));
formatWriter.writeHeader();
formatWriter.writeBeginTrack(null);
formatWriter.writeEndTrack(null);
formatWriter.writeFooter();
formatWriter.close();
mocksControl.replay();
writer.writeDocument();
assertTrue(writer.wasSuccess());
mocksControl.verify();
}
public void testWriteDocument() throws Exception {
writer = new TrackWriterImpl(getContext(), providerUtils, track, formatWriter);
final Location[] locs = {
new Location("fake0"),
new Location("fake1"),
new Location("fake2"),
new Location("fake3"),
new Location("fake4"),
new Location("fake5")
};
Waypoint[] wps = {new Waypoint(), new Waypoint(), new Waypoint()};
// Fill locations with valid values
fillLocations(locs);
// Make location 3 invalid
locs[2].setLatitude(100);
assertEquals(locs.length, providerUtils.bulkInsertTrackPoint(locs, locs.length, TRACK_ID));
for (int i = 0; i < wps.length; ++i) {
Waypoint wpt = wps[i];
wpt.setTrackId(TRACK_ID);
assertNotNull(providerUtils.insertWaypoint(wpt));
wpt.setId(i + 1);
}
formatWriter.writeHeader();
// Expect reading/writing of the waypoints (except the first)
formatWriter.writeBeginWaypoints();
formatWriter.writeWaypoint(wptEq(wps[1]));
formatWriter.writeWaypoint(wptEq(wps[2]));
formatWriter.writeEndWaypoints();
// Begin the track
formatWriter.writeBeginTrack(locEq(locs[0]));
// Write locations 1-2
formatWriter.writeOpenSegment();
formatWriter.writeLocation(locEq(locs[0]));
formatWriter.writeLocation(locEq(locs[1]));
formatWriter.writeCloseSegment();
// Location 3 is not written - it's invalid
// Write locations 4-6
formatWriter.writeOpenSegment();
formatWriter.writeLocation(locEq(locs[3]));
formatWriter.writeLocation(locEq(locs[4]));
formatWriter.writeLocation(locEq(locs[5]));
formatWriter.writeCloseSegment();
// End the track
formatWriter.writeEndTrack(locEq(locs[5]));
formatWriter.writeFooter();
formatWriter.close();
mocksControl.replay();
writer.writeDocument();
assertTrue(writer.wasSuccess());
mocksControl.verify();
}
private static Waypoint wptEq(final Waypoint wpt) {
EasyMock.reportMatcher(new IArgumentMatcher() {
@Override
public boolean matches(Object wptObj2) {
if (wptObj2 == null || wpt == null) return wpt == wptObj2;
Waypoint wpt2 = (Waypoint) wptObj2;
return wpt.getId() == wpt2.getId();
}
@Override
public void appendTo(StringBuffer buffer) {
buffer.append("wptEq(");
buffer.append(wpt);
buffer.append(")");
}
});
return null;
}
private static Location locEq(final Location loc) {
EasyMock.reportMatcher(new IArgumentMatcher() {
@Override
public boolean matches(Object locObj2) {
if (locObj2 == null || loc == null) return loc == locObj2;
Location loc2 = (Location) locObj2;
return loc.hasAccuracy() == loc2.hasAccuracy()
&& (!loc.hasAccuracy() || loc.getAccuracy() == loc2.getAccuracy())
&& loc.hasAltitude() == loc2.hasAltitude()
&& (!loc.hasAltitude() || loc.getAltitude() == loc2.getAltitude())
&& loc.hasBearing() == loc2.hasBearing()
&& (!loc.hasBearing() || loc.getBearing() == loc2.getBearing())
&& loc.hasSpeed() == loc2.hasSpeed()
&& (!loc.hasSpeed() || loc.getSpeed() == loc2.getSpeed())
&& loc.getLatitude() == loc2.getLatitude()
&& loc.getLongitude() == loc2.getLongitude()
&& loc.getTime() == loc2.getTime();
}
@Override
public void appendTo(StringBuffer buffer) {
buffer.append("locEq(");
buffer.append(loc);
buffer.append(")");
}
});
return null;
}
private void fillLocations(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);
location.setTime(i + 1000);
}
}
}