package com.limegroup.gnutella.downloader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import junit.framework.Test;
import com.limegroup.gnutella.DownloadManager;
import com.limegroup.gnutella.Downloader;
import com.limegroup.gnutella.ForMeReplyHandler;
import com.limegroup.gnutella.GUID;
import com.limegroup.gnutella.ManagedConnection;
import com.limegroup.gnutella.RemoteFileDesc;
import com.limegroup.gnutella.ReplyHandler;
import com.limegroup.gnutella.Response;
import com.limegroup.gnutella.RouteTable;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.messages.QueryReply;
import com.limegroup.gnutella.messages.QueryRequest;
import com.limegroup.gnutella.settings.ConnectionSettings;
import com.limegroup.gnutella.stubs.ActivityCallbackStub;
import com.limegroup.gnutella.stubs.ConnectionManagerStub;
import com.limegroup.gnutella.stubs.MessageRouterStub;
import com.limegroup.gnutella.util.PrivilegedAccessor;
import com.limegroup.gnutella.util.StringUtils;
import com.limegroup.gnutella.xml.LimeXMLDocument;
/**
* This test makes sure that LimeWire does the following things:
* 1) Does NOT requery ever.
* 2) Wakes up from the WAITING_FROM_RESULTS state when a new, valid query comes
* in.
* 3) Wakes up from the GAVE_UP state when a new, valid query comes in.
*/
public class RequeryDownloadTest
extends com.limegroup.gnutella.util.BaseTestCase {
/** The main test fixture. Contains the incomplete file and hash below. */
private DownloadManager _mgr;
/** Where to send and receive messages */
private TestMessageRouter _router;
/** The simulated downloads.dat file. Used only to build _mgr. */
private File _snapshot;
/** The name of the completed file. */
private String _filename="some file.txt";
/** The incomplete file to resume from. */
private File _incompleteFile;
/** The hash of file when complete. */
private URN _hash = TestFile.hash();
/** The uploader */
private TestUploader _uploader;
/** The TestMessageRouter's queryRouteTable. */
private RouteTable _queryRouteTable;
/** The TestMessageRouter's FOR_ME_REPLY_HANDLER. */
private final ReplyHandler _ourReplyHandler = ForMeReplyHandler.instance();
private static final int PORT = 6939;
class TestMessageRouter extends MessageRouterStub {
List /* of QueryMessage */ broadcasts=new LinkedList();
public void sendDynamicQuery(QueryRequest query) {
broadcasts.add(query);
super.sendDynamicQuery(query); //add GUID to route table
}
}
//////////////////////////// Fixtures /////////////////////////
public RequeryDownloadTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(RequeryDownloadTest.class);
}
public void setUp() throws Exception {
ManagedDownloader.NO_DELAY = true;
ConnectionSettings.NUM_CONNECTIONS.setValue(0);
ConnectionSettings.CONNECT_ON_STARTUP.setValue(false);
ConnectionSettings.LOCAL_IS_PRIVATE.setValue(false);
ConnectionSettings.PORT.setValue(PORT);
_router=new TestMessageRouter();
new RouterService(new ActivityCallbackStub(), _router);
RouterService.setListeningPort(ConnectionSettings.PORT.getValue());
PrivilegedAccessor.setValue(
RouterService.class, "manager", new ConnectionManagerStub());
_router.initialize();
_queryRouteTable =
(RouteTable) PrivilegedAccessor.getValue(_router,
"_queryRouteTable");
createSnapshot();
_mgr=RouterService.getDownloadManager();
_mgr.initialize();
_mgr.scheduleWaitingPump();
boolean ok=_mgr.readSnapshot(_snapshot);
assertTrue("Couldn't read snapshot file", ok);
_uploader=new TestUploader("uploader 6666", 6666);
_uploader.setRate(Integer.MAX_VALUE);
RouterService.getDownloadManager().clearAllDownloads();
new File( getSaveDirectory(), _filename).delete();
}
/** Creates a downloads.dat file named SNAPSHOT with a faked up
* IncompleteFileManager in it. All of this because we can't access
* DownloadManager.incompleteFileManager. */
private void createSnapshot() throws Exception {
try {
//Make IncompleteFileManager with appropriate entries...
IncompleteFileManager ifm=createIncompleteFile();
//...and write it to downloads.dat.
_snapshot = File.createTempFile(
"ResumeByHashTest", ".dat"
);
ObjectOutputStream out =
new ObjectOutputStream(new FileOutputStream(_snapshot));
out.writeObject(new ArrayList()); //downloads
out.writeObject(ifm);
out.close();
} catch (IOException e) {
fail("Couldn't create temp file", e);
}
}
/** Creates the incomplete file and returns an IncompleteFileManager with
* info for that file. */
public IncompleteFileManager createIncompleteFile() throws Exception {
IncompleteFileManager ifm=new IncompleteFileManager();
Set urns=new HashSet(1);
urns.add(_hash);
RemoteFileDesc rfd = new RemoteFileDesc("1.2.3.4", PORT, 13l,
_filename, TestFile.length(),
new byte[16], 56, false, 4,
true, null, urns, false,
false,"",0,null, -1);
//Create incompleteFile, write a few bytes
_incompleteFile=ifm.getFile(rfd);
try {
_incompleteFile.delete();
_incompleteFile.createNewFile();
OutputStream out=new FileOutputStream(_incompleteFile);
out.write((byte)TestFile.getByte(0));
out.write((byte)TestFile.getByte(1));
out.close();
} catch (IOException e) {
fail("Couldn't create incomplete file", e);
}
//Record information in IncompleteFileManager.
VerifyingFile vf=new VerifyingFile(TestFile.length());
vf.addInterval(new Interval(0, 1)); //inclusive
ifm.addEntry(_incompleteFile, vf);
return ifm;
}
public void tearDown() {
_uploader.stopThread();
if (_incompleteFile != null )
_incompleteFile.delete();
if (_snapshot != null)
_snapshot.delete();
new File(getSaveDirectory(), _filename).delete();
}
/////////////////////////// Actual Tests /////////////////////////////
/** Gets response with exact match, starts downloading. */
public void testExactMatch() throws Exception {
doTest(_filename, _hash, true);
}
/** Gets response with same hash, different name, starts downloading. */
public void testHashMatch() throws Exception {
doTest("different name.txt", _hash, true);
}
/** Gets a response that doesn't match--can't download. */
public void testNoMatch() throws Exception {
doTest("some other file.txt", null, false);
}
/**
* Skeleton method for all tests.
* @param responseName the file name to send in responses
* @param responseURN the SHA1 urn to send in responses, or null for none
* @param shouldDownload true if the downloader should actually start
* the download. False if the response shouldn't satisfy it.
*/
private void doTest(String responseName,
URN responseURN,
boolean shouldDownload) throws Exception {
// we need to seed the MessageRouter with a GUID that it will recognize
byte[] guidToUse = GUID.makeGuid();
_queryRouteTable.routeReply(guidToUse, _ourReplyHandler);
//Start a download for the given incomplete file. Give the thread time
//to start up and send its requery.
Downloader downloader = null;
downloader = _mgr.download(_incompleteFile);
assertTrue(downloader instanceof ResumeDownloader);
assertEquals(Downloader.QUEUED,downloader.getState());
int counts = 0;
// Make sure that you are through the QUEUED state.
while (downloader.getState() == Downloader.QUEUED) {
Thread.sleep(100);
if(counts++ > 50)
fail("took too long, state: " + downloader.getState());
}
//give the downloading thread time to change states
Thread.sleep(1000);
assertEquals("downloader isn't waiting for results",
Downloader.WAITING_FOR_RESULTS, downloader.getState());
// no need to do a dldr.resume() cuz ResumeDownloaders spawn the query
// automatically
//Check that we can get query of right type.
//TODO: try resume without URN
assertEquals("unexpected router.broadcasts size", 1,
_router.broadcasts.size());
Object m=_router.broadcasts.get(0);
assertInstanceof("m should be a query request", QueryRequest.class, m);
QueryRequest qr=(QueryRequest)m;
// no more requeries
assertTrue((GUID.isLimeGUID(qr.getGUID())) &&
!(GUID.isLimeRequeryGUID(qr.getGUID())));
// since filename is the first thing ever submitted it should always
// query for allFiles[0].getFileName()
String qString =StringUtils.createQueryString(_filename);
assertEquals("should have queried for filename", qString,
qr.getQuery());
assertNotNull("should have some requested urn types",
qr.getRequestedUrnTypes() );
assertEquals("unexpected amount of requested urn types",
1, qr.getRequestedUrnTypes().size() );
Set urns=qr.getQueryUrns();
assertNotNull("urns shouldn't be null", urns);
assertEquals("should only have NO urn", 0, urns.size());
// not relevant anymore, we don't send URN queries
// assertTrue("urns should contain the hash", urns.contains(_hash));
//Send a response to the query.
Set responseURNs = null;
if (responseURN != null) {
responseURNs = new HashSet(1);
responseURNs.add(responseURN);
}
Response response = newResponse(0l, TestFile.length(),
responseName, responseURNs);
byte[] ip = {(byte)127, (byte)0, (byte)0, (byte)1};
QueryReply reply = new QueryReply(guidToUse,
(byte)6, 6666, ip, 0l,
new Response[] { response }, new byte[16],
false, false, //needs push, is busy
true, false, //finished upload, measured speed
false, false);//supports chat, is multicast response....
_router.handleQueryReply(reply, new ManagedConnection("1.2.3.4", PORT));
//Make sure the downloader does the right thing with the response.
Thread.sleep(2000);
counts = 0;
if (shouldDownload) {
//a) Match: wait for download to start, then complete.
while (downloader.getState()!=Downloader.COMPLETE) {
if ( downloader.getState() != Downloader.CONNECTING &&
downloader.getState() != Downloader.HASHING &&
downloader.getState() != Downloader.SAVING )
assertEquals(Downloader.DOWNLOADING, downloader.getState());
Thread.sleep(500);
if(counts++ > 60)
fail("took too long, state: " + downloader.getState());
}
}
else {
//b) No match: keep waiting for results
assertEquals("downloader should wait for user",
Downloader.WAITING_FOR_RESULTS, downloader.getState());
downloader.stop();
}
}
/**
* Utility method to create a new response.
*/
private Response newResponse(long index, long size, String name, Set urns)
throws Exception {
Class gc = PrivilegedAccessor.getClass(Response.class, "GGEPContainer");
return (Response)PrivilegedAccessor.invokeConstructor(
Response.class, new Object[] {
new Long(index), new Long(size), name,
urns, null, null, null },
new Class[] {
Long.TYPE, Long.TYPE, String.class,
Set.class, LimeXMLDocument.class,
gc, byte[].class } );
}
}