package com.limegroup.gnutella;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import junit.framework.Test;
import com.limegroup.gnutella.messages.BadPacketException;
import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.messages.QueryReply;
import com.limegroup.gnutella.messages.QueryRequest;
import com.limegroup.gnutella.messages.vendor.MessagesSupportedVendorMessage;
import com.limegroup.gnutella.messages.vendor.QueryStatusResponse;
import com.limegroup.gnutella.search.HostData;
import com.limegroup.gnutella.search.SearchResultHandler;
import com.limegroup.gnutella.settings.SearchSettings;
import com.limegroup.gnutella.spam.SpamManager;
import com.limegroup.gnutella.stubs.ActivityCallbackStub;
import com.limegroup.gnutella.util.DataUtils;
/**
* Checks whether (multi)leaves avoid forwarding messages to ultrapeers, do
* redirects properly, etc. The test includes a leaf attached to 3
* Ultrapeers.
*/
public class ClientSideLeafGuidanceTest extends ClientSideTestCase {
private final int REPORT_INTERVAL = SearchResultHandler.REPORT_INTERVAL;
private final int MAX_RESULTS = SearchResultHandler.MAX_RESULTS;
public ClientSideLeafGuidanceTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(ClientSideLeafGuidanceTest.class);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
/** @return The first QueyrRequest received from this connection. If null
* is returned then it was never recieved (in a timely fashion).
*/
private static QueryStatusResponse getFirstQueryStatus(Connection c)
throws BadPacketException, IOException {
return (QueryStatusResponse)
getFirstInstanceOfMessageType(c, QueryStatusResponse.class, TIMEOUT);
}
static MyActivityCallback myCallback = new MyActivityCallback();
///////////////////////// Actual Tests ////////////////////////////
// THIS TEST SHOULD BE RUN FIRST!!
public void testBasicGuidance() throws Exception {
for (int i = 0; i < testUP.length; i++)
// send a MessagesSupportedMessage
testUP[i].send(MessagesSupportedVendorMessage.instance());
// spawn a query and make sure all UPs get it
GUID queryGuid = new GUID(RouterService.newQueryGUID());
RouterService.query(queryGuid.bytes(), "susheel");
Thread.sleep(250);
for (int i = 0; i < testUP.length; i++) {
QueryRequest qr = getFirstQueryRequest(testUP[i]);
assertNotNull(qr);
assertEquals(new GUID(qr.getGUID()), queryGuid);
}
// now send back results and make sure that we get a QueryStatus
// from the leaf
Message m = null;
// ensure that we'll get a QueryStatusResponse from the Responses
// we're sending.
assertGreaterThan(REPORT_INTERVAL, 6*testUP.length);
for (int i = 0; i < testUP.length; i++) {
Response[] res = new Response[] {
// Only the 'susheel' Responses will pass the
// ResponseVerifier.matchesQuery() check and
// the others wont
new Response(10, 10, "susheel"+i),
new Response(10, 10, "susheel smells good"+i),
new Response(10, 10, "anita is sweet"+i),
new Response(10, 10, "anita is prety"+i),
new Response(10, 10, "susheel smells bad" + i),
new Response(10, 10, "renu is sweet " + i),
new Response(10, 10, "prety is spelled pretty " + i),
new Response(10, 10, "go susheel go" + i),
new Response(10, 10, "susheel runs fast" + i),
new Response(10, 10, "susheel jumps high" + i),
new Response(10, 10, "sleepy susheel" + i),
};
m = new QueryReply(queryGuid.bytes(), (byte) 1, 6355, myIP(), 0, res,
GUID.makeGuid(), new byte[0], false, false, true,
true, false, false, null);
testUP[i].send(m);
testUP[i].flush();
}
// all UPs should get a QueryStatusResponse
for (int i = 0; i < testUP.length; i++) {
QueryStatusResponse stat = getFirstQueryStatus(testUP[i]);
assertNotNull("up: " + i + " failed", stat);
assertEquals("up: " + i + " failed", new GUID(stat.getGUID()), queryGuid);
assertEquals("up: " + i + " failed", 5, stat.getNumResults());
}
// shut off the query....
RouterService.stopQuery(queryGuid);
// all UPs should get a QueryStatusResponse with 65535
for (int i = 0; i < testUP.length; i++) {
QueryStatusResponse stat = getFirstQueryStatus(testUP[i]);
assertNotNull("up: " + i + " failed", stat);
assertEquals("up: " + i + " failed", new GUID(stat.getGUID()), queryGuid);
assertEquals("up: " + i + " failed", 65535, stat.getNumResults());
}
}
public void testAdvancedGuidance1() throws Exception {
for (int i = 0; i < testUP.length; i++)
drain(testUP[i]);
// spawn a query and make sure all UPs get it
GUID queryGuid = new GUID(RouterService.newQueryGUID());
RouterService.query(queryGuid.bytes(), "susheel daswanu");
for (int i = 0; i < testUP.length; i++) {
QueryRequest qr = getFirstQueryRequest(testUP[i]);
assertNotNull(qr);
assertEquals(new GUID(qr.getGUID()), queryGuid);
}
// now send back results and make sure that we get a QueryStatus
// from the leaf
Message m = null;
for (int i = 0; i < testUP.length; i++) {
//send enough responses per ultrapeer to shut off querying.
Response[] res = new Response[150/testUP.length + 10];
for (int j = 0; j < res.length; j++)
res[j] = new Response(10, 10, "susheel good"+i+j);
m = new QueryReply(queryGuid.bytes(), (byte) 1, 6355, myIP(), 0, res,
GUID.makeGuid(), new byte[0], false, false, true,
true, false, false, null);
testUP[i].send(m);
testUP[i].flush();
}
// all UPs should get a QueryStatusResponse
boolean maxResultsEncountered = false;
for (int i = 0; i < testUP.length; i++) {
for (int j = 0; j < testUP.length; j++) {
QueryStatusResponse stat = getFirstQueryStatus(testUP[j]);
assertNotNull(stat);
assertEquals(new GUID(stat.getGUID()), queryGuid);
// depending on how far along the query is we could have a
// number or 65535 - the number 11 depends on settings such as
// REPORT_INTERVAL
if (stat.getNumResults() == MAX_RESULTS) {
assertEquals(testUP.length-1, i);
maxResultsEncountered = true;
} else {
//assertEquals(11*(i+1), stat.getNumResults());
// there is no sane way this can be asserted.
}
}
}
assertTrue(maxResultsEncountered);
// now, even though we send more responses, we shoudl NOT get any more
// leaf guidance...
Response[] res = new Response[REPORT_INTERVAL*4];
for (int j = 0; j < res.length; j++)
res[j] = new Response(10, 10, "anita is pretty"+j);
m = new QueryReply(queryGuid.bytes(), (byte) 1, 6355, myIP(), 0, res,
GUID.makeGuid(), new byte[0], false, false, true,
true, false, false, null);
testUP[0].send(m);
testUP[0].flush();
// no UPs should get a QueryStatusResponse
drainQSRespones();
}
public void testAdvancedGuidance2() throws Exception {
Message m = null;
for (int i = 0; i < testUP.length; i++)
drain(testUP[i]);
// spawn a query and make sure all UPs get it
GUID queryGuid = new GUID(RouterService.newQueryGUID());
RouterService.query(queryGuid.bytes(), "anita kesavan");
for (int i = 0; i < testUP.length; i++) {
QueryRequest qr = getFirstQueryRequest(testUP[i]);
assertNotNull(qr);
assertEquals(new GUID(qr.getGUID()), queryGuid);
}
// now send back results and make sure that we get a QueryStatus
// from the leaf
Response[] res = new Response[REPORT_INTERVAL*4];
for (int j = 0; j < res.length; j++)
res[j] = new Response(10, 10, "anita is pretty"+j);
m = new QueryReply(queryGuid.bytes(), (byte) 1, 6355, myIP(), 0, res,
GUID.makeGuid(), new byte[0], false, false, true,
true, false, false, null);
testUP[0].send(m);
testUP[0].flush();
// all UPs should get a QueryStatusResponse
for (int i = 0; i < testUP.length; i++) {
QueryStatusResponse stat = getFirstQueryStatus(testUP[i]);
assertNotNull(stat);
assertEquals(new GUID(stat.getGUID()), queryGuid);
assertEquals(REPORT_INTERVAL, stat.getNumResults());
}
// now send just a few responses - less than the number of
// REPORT_INTERVAL - and confirm we don't get messages
res = new Response[REPORT_INTERVAL-1];
for (int j = 0; j < res.length; j++)
res[j] = new Response(10, 10, "anita is sweet"+j);
m = new QueryReply(queryGuid.bytes(), (byte) 1, 6355, myIP(), 0, res,
GUID.makeGuid(), new byte[0], false, false, true,
true, false, false, null);
testUP[2].send(m);
testUP[2].flush();
// no UPs should get a QueryStatusResponse
for (int i = 0; i < testUP.length; i++) {
QueryStatusResponse stat = getFirstQueryStatus(testUP[i]);
assertNull(stat);
}
// simply send 2 more responses....
res = new Response[2];
for (int j = 0; j < res.length; j++)
res[j] = new Response(10, 10, "anita is young"+j);
m = new QueryReply(queryGuid.bytes(), (byte) 1, 6355, myIP(), 0, res,
GUID.makeGuid(), new byte[0], false, false, true,
true, false, false, null);
testUP[1].send(m);
testUP[1].flush();
// and all UPs should get a QueryStatusResponse
for (int i = 0; i < testUP.length; i++) {
QueryStatusResponse stat = getFirstQueryStatus(testUP[i]);
assertNotNull(stat);
assertEquals(new GUID(stat.getGUID()), queryGuid);
assertEquals(REPORT_INTERVAL+((REPORT_INTERVAL+1)/4),
stat.getNumResults());
}
// shut off the query....
RouterService.stopQuery(queryGuid);
// all UPs should get a QueryStatusResponse with 65535
for (int i = 0; i < testUP.length; i++) {
QueryStatusResponse stat = getFirstQueryStatus(testUP[i]);
assertNotNull(stat);
assertEquals(new GUID(stat.getGUID()), queryGuid);
assertEquals(65535, stat.getNumResults());
}
// more results should not result in more status messages...
res = new Response[REPORT_INTERVAL*2];
for (int j = 0; j < res.length; j++)
res[j] = new Response(10, 10, "anita is pretty"+j);
m = new QueryReply(queryGuid.bytes(), (byte) 1, 6355, myIP(), 0, res,
GUID.makeGuid(), new byte[0], false, false, true,
true, false, false, null);
testUP[0].send(m);
testUP[0].flush();
// no UPs should get a QueryStatusResponse
drainQSRespones();
}
/**
* tests that spammy results are shown to the gui but are not counted
* in leaf guidance
*/
public void testSpamFiltering() throws Exception {
SearchSettings.ENABLE_SPAM_FILTER.setValue(true);
SearchSettings.FILTER_SPAM_RESULTS.setValue(0.4f);
myCallback.responses.clear();
Message m = null;
for (int i = 0; i < testUP.length; i++)
drain(testUP[i]);
// spawn a query and make sure all UPs get it
GUID queryGuid = new GUID(RouterService.newQueryGUID());
RouterService.query(queryGuid.bytes(), "anita kesavan");
for (int i = 0; i < testUP.length; i++) {
QueryRequest qr = getFirstQueryRequest(testUP[i]);
assertNotNull(qr);
assertEquals(new GUID(qr.getGUID()), queryGuid);
}
// mark anita as spammy
RemoteFileDesc anita =new RemoteFileDesc("127.0.0.1", 6355, 1, "anita kasevan",
1000, DataUtils.EMPTY_GUID, 3,
false, 3, false,
null, Collections.EMPTY_SET,
false,false,
"ALT",0l,
Collections.EMPTY_SET, 0l);
SpamManager.instance().handleUserMarkedSpam(new RemoteFileDesc[]{anita});
assertTrue(SpamManager.instance().isSpam(anita));
// now send back results and make sure that we do not get a QueryStatus
// from the leaf
Response[] res = new Response[REPORT_INTERVAL*4];
for (int j = 0; j < res.length; j++)
res[j] = new Response(10, 10, "anita kasevan "+j);
m = new QueryReply(queryGuid.bytes(), (byte) 1, 6355, myIP(), 0, res,
GUID.makeGuid(), new byte[0], false, false, true,
true, false, false, null);
testUP[0].send(m);
testUP[0].flush();
Thread.sleep(1000);
// the gui should be informed about the results
assertEquals(REPORT_INTERVAL * 4, myCallback.responses.size());
// We should get a QueryStatusResponse from the UP even if all
// Responses are spam. See SearchResultHandler.handleQueryReply(QueryReply)
// for more info!
int numGoodSentToFrontEnd = 0;
int numBadSentToFrontEnd = (int)Math.ceil(res.length * SearchSettings.SPAM_RESULT_RATIO.getValue());
int numToReport = numGoodSentToFrontEnd + numBadSentToFrontEnd;
// Make sure the spam ratio is not 1 as we'd no longer
// be able to distinguish between spam and not spam!
assertNotEquals(SearchSettings.SPAM_RESULT_RATIO.getValue(), 1.0f);
assertGreaterThan(REPORT_INTERVAL, numToReport);
QueryStatusResponse qsr
= (QueryStatusResponse)getFirstInstanceOfMessageType(testUP[0], QueryStatusResponse.class, TIMEOUT);
assertEquals(numToReport/4, qsr.getNumResults());
}
/**
* drains the ultrapeers for any QueryStatusResponses and fails if one is received.
* @throws Exception
*/
private static void drainQSRespones() throws Exception {
failIfAnyArrive(testUP,QueryStatusResponse.class);
}
private static byte[] myIP() {
return new byte[] { (byte)127, (byte)0, 0, 1 };
}
public static Integer numUPs() {
return new Integer(3);
}
public static ActivityCallback getActivityCallback() {
return myCallback;
}
public static class MyActivityCallback extends ActivityCallbackStub {
public List responses = new ArrayList();
public void handleQueryResult(RemoteFileDesc rfdParam,
HostData data,
Set locs) {
responses.add(rfdParam);
}
}
}