package com.limegroup.gnutella.bootstrap;
import java.io.StringWriter;
import java.io.BufferedReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;
import java.util.Collections;
import java.util.Collection;
import java.util.Set;
import java.util.HashSet;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import junit.framework.Test;
import com.limegroup.gnutella.ExtendedEndpoint;
import com.limegroup.gnutella.UDPService;
import com.limegroup.gnutella.UDPPinger;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.StandardMessageRouter;
import com.limegroup.gnutella.UniqueHostPinger;
import com.limegroup.gnutella.messages.PingRequest;
import com.limegroup.gnutella.messages.PingReply;
import com.limegroup.gnutella.util.BaseTestCase;
import com.limegroup.gnutella.util.PrivilegedAccessor;
import com.limegroup.gnutella.stubs.ActivityCallbackStub;
/**
* Unit tests for UDPHostCache.
*/
public class UDPHostCacheTest extends BaseTestCase {
private StubCache cache;
public UDPHostCacheTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(UDPHostCacheTest.class);
}
public static void globalSetUp() throws Exception {
RouterService rs = new RouterService(new ActivityCallbackStub(),
new StandardMessageRouter());
RouterService.getAcceptor().setAddress(InetAddress.getByName("1.1.1.1"));
DatagramSocket ds = (DatagramSocket)PrivilegedAccessor.invokeMethod(
UDPService.instance(),
"newListeningSocket",
new Object[] { new Integer(7000) },
new Class[] { Integer.TYPE } );
PrivilegedAccessor.invokeMethod(
UDPService.instance(),
"setListeningSocket",
new Object[] { ds } ,
new Class[] { DatagramSocket.class });
RouterService.getMessageRouter().initialize();
}
public void setUp() {
// use a really tiny expiry time
cache = new StubCache();
}
public void testMaximumStored() {
assertEquals(0, cache.getSize());
for(int i = 0; i < 200; i++) {
cache.add(create("1.2.3." + i));
assertEquals(Math.min(i+1, 100), cache.getSize());
}
}
public void testAddAndRemove() {
assertEquals(0, cache.getSize());
for(int i = 0; i < 50; i++) {
cache.add(create("1.2.3." + i));
assertEquals(i+1, cache.getSize());
}
for(int i = 0; i < 50; i++) {
cache.remove(create("1.2.3." + i));
assertEquals(50 - (i+1), cache.getSize());
}
// make sure we can remove stuff that doesn't exist
// with no harm done.
for(int i = 0; i < 50; i++) {
assertEquals(0, cache.getSize());
cache.remove(create("5.4.3.2" + i));
}
}
public void testUsesFiveAtATime() {
assertEquals(0, cache.getSize());
for(int i = 0; i < 23; i++)
cache.add(create("1.2.3." + i));
assertEquals(23, cache.getSize());
assertEquals(-1, cache.amountFetched);
cache.fetchHosts();
assertEquals(5, cache.amountFetched);
cache.fetchHosts();
assertEquals(5, cache.amountFetched);
cache.fetchHosts();
assertEquals(5, cache.amountFetched);
cache.fetchHosts();
assertEquals(5, cache.amountFetched);
cache.fetchHosts();
assertEquals(3, cache.amountFetched);
cache.fetchHosts();
assertEquals(0, cache.amountFetched);
// add newer hosts, should use them.
for(int i = 0; i < 5; i++)
cache.add(create("2.3.4." + i));
assertEquals(28, cache.getSize());
cache.fetchHosts();
assertEquals(5, cache.amountFetched);
cache.fetchHosts();
assertEquals(0, cache.amountFetched);
// add hosts we already added, shouldn't do nothin' with them
for(int i = 0; i < 23; i++)
cache.add(create("1.2.3." + i));
for(int i = 0; i < 5; i++)
cache.add(create("2.3.4." + i));
assertEquals(28, cache.getSize());
cache.fetchHosts();
assertEquals(0, cache.amountFetched);
}
public void testUsesFiveBestAtATime() {
assertEquals(0, cache.getSize());
List endpoints = new LinkedList();
// add 8 hosts with 0 failures, 3 with 1 failure,
// 5 with 2 failures, 4 with 3 failures,
// and 7 with 4 failures -- we should get'm in the right order
// regardless of how they were added.
int i = 0;
for(; i < 8; i++)
endpoints.add(create("1.2.3." + i, 0));
for(; i < 8+3; i++)
endpoints.add(create("1.2.3." + i, 1));
for(; i < 8+3+5; i++)
endpoints.add(create("1.2.3." + i, 2));
for(; i < 8+3+5+4; i++)
endpoints.add(create("1.2.3." + i, 3));
for(; i < 8+3+5+4+7; i++)
endpoints.add(create("1.2.3." + i, 4));
// make sure they're in random order, then add them to the cache.
Collections.shuffle(endpoints);
for(Iterator iter = endpoints.iterator(); iter.hasNext(); )
cache.add((ExtendedEndpoint)iter.next());
assertEquals(-1, cache.amountFetched);
assertEquals(8+3+5+4+7, cache.getSize());
// Fetch until we run out, adding them all to a list
// in order we fetched'm. (Note that we already have
// a test that makes sure we only do 5 at a time --
// this test just ensures we do them in the right order.)
List fetchedHosts = new ArrayList();
while(true) {
cache.fetchHosts();
if(cache.amountFetched == 0)
break;
fetchedHosts.addAll(cache.lastFetched);
}
int max = 8+3+5+4+7;
assertEquals(max, fetchedHosts.size());
for(i = 0; i < 8+3+5+4+7; i++) {
ExtendedEndpoint ep = (ExtendedEndpoint)fetchedHosts.get(i);
if(i < 8)
assertEquals(0, ep.getUDPHostCacheFailures());
else if(i < 8+3)
assertEquals(1, ep.getUDPHostCacheFailures());
else if(i < 8+3+5)
assertEquals(2, ep.getUDPHostCacheFailures());
else if(i < 8+3+5+4)
assertEquals(3, ep.getUDPHostCacheFailures());
else if(i < 8+3+5+4+7)
assertEquals(4, ep.getUDPHostCacheFailures());
else
fail("wrong i: " + i);
}
}
public void testAttemptedExpiresAfterTime() throws Exception {
assertEquals(0, cache.getSize());
for(int i = 0; i < 13; i++)
cache.add(create("1.2.3." + i));
assertEquals(13, cache.getSize());
assertEquals(-1, cache.amountFetched);
cache.fetchHosts();
assertEquals(5, cache.amountFetched);
cache.fetchHosts();
assertEquals(5, cache.amountFetched);
cache.fetchHosts();
assertEquals(3, cache.amountFetched);
cache.fetchHosts();
assertEquals(0, cache.amountFetched);
// Wait the attempted expiry time.
Thread.sleep(StubCache.EXPIRY_TIME + 1000); // +1000 for fudging time.
cache.fetchHosts();
assertEquals(5, cache.amountFetched);
cache.fetchHosts();
assertEquals(5, cache.amountFetched);
cache.fetchHosts();
assertEquals(3, cache.amountFetched);
cache.fetchHosts();
assertEquals(0, cache.amountFetched);
}
public void testResetDataStartsFresh() {
assertEquals(0, cache.getSize());
for(int i = 0; i < 8; i++)
cache.add(create("1.2.3." + i));
assertEquals(8, cache.getSize());
assertEquals(-1, cache.amountFetched);
cache.fetchHosts();
assertEquals(5, cache.amountFetched);
cache.fetchHosts();
assertEquals(3, cache.amountFetched);
cache.fetchHosts();
assertEquals(0, cache.amountFetched);
cache.resetData();
cache.fetchHosts();
assertEquals(5, cache.amountFetched);
cache.fetchHosts();
assertEquals(3, cache.amountFetched);
cache.fetchHosts();
assertEquals(0, cache.amountFetched);
}
public void testWriting() throws Exception {
assertEquals(0, cache.getSize());
StringWriter writer = new StringWriter();
cache.write(writer);
assertEquals("", writer.toString());
for(int i = 0; i < 20; i++)
cache.add(create("1.2.3." + i));
assertEquals(20, cache.getSize());
cache.write(writer);
String written = writer.toString();
Set readEPs = new HashSet();
BufferedReader reader = new BufferedReader(new StringReader(written));
for(int i = 0; i < 20; i++) {
String read = reader.readLine();
assertNotNull(read);
assertNotEquals("", read);
ExtendedEndpoint ep = ExtendedEndpoint.read(read);
assertTrue(ep.isUDPHostCache());
readEPs.add(ep);
}
for(int i = 0; i < 20; i++) {
assertEquals(20-i, readEPs.size());
ExtendedEndpoint ep = create("1.2.3." + i);
assertContains(readEPs, ep);
readEPs.remove(ep);
}
}
public void testWritingPreservesDNSNames() throws Exception {
assertEquals(0, cache.getSize());
StringWriter writer = new StringWriter();
cache.add(create("www.limewire.com"));
cache.add(create("1.2.3.4"));
cache.add(create("www.eff.org"));
cache.write(writer);
String written = writer.toString();
BufferedReader reader = new BufferedReader(new StringReader(written));
List readLines = new LinkedList();
readLines.add(reader.readLine());
readLines.add(reader.readLine());
readLines.add(reader.readLine());
assertStartsWithInList("www.eff.org:6346", readLines);
assertStartsWithInList("1.2.3.4:6346", readLines);
assertStartsWithInList("www.limewire.com:6346", readLines);
assertEquals(readLines.toString(), 0, readLines.size());
assertEquals(null, reader.readLine());
}
public void testRecordingFailuresAndSuccesses() throws Exception {
assertEquals(0, cache.getSize());
UDPPinger.LISTEN_EXPIRE_TIME = 3 * 1000;
cache.doRealFetch = true;
ExtendedEndpoint e1 = create("1.2.3.4");
ExtendedEndpoint e2 = create("2.3.4.5");
ExtendedEndpoint e3 = create("3.4.5.6");
ExtendedEndpoint e4 = create("4.5.6.7");
ExtendedEndpoint e5 = create("5.6.7.8");
cache.add(e1);
cache.add(e2);
cache.add(e3);
cache.add(e4);
cache.add(e5);
assertEquals(5, cache.getSize());
// this will cause UDPHostRanker to send out Pings.
cache.fetchHosts();
Thread.sleep(100);
// Have MessageRouter try and route some replies from some hosts...
routeFor("1.2.3.4", cache.guid);
routeFor("3.4.5.6", cache.guid);
routeFor("5.6.7.8", cache.guid);
// sleep until the MessageListener is unregistered,
// recording the successes & failures.
Thread.sleep(4 * 1000);
assertEquals(0, e1.getUDPHostCacheFailures());
assertEquals(1, e2.getUDPHostCacheFailures());
assertEquals(0, e3.getUDPHostCacheFailures());
assertEquals(1, e4.getUDPHostCacheFailures());
assertEquals(0, e5.getUDPHostCacheFailures());
assertEquals(5, cache.getSize());
// reset data, try again.
cache.resetData();
cache.fetchHosts();
Thread.sleep(100);
routeFor("1.2.3.4", cache.guid);
routeFor("2.3.4.5", cache.guid);
routeFor("5.6.7.8", cache.guid);
Thread.sleep(4 * 1000);
assertEquals(0, e1.getUDPHostCacheFailures());
assertEquals(0, e2.getUDPHostCacheFailures());
assertEquals(1, e3.getUDPHostCacheFailures());
assertEquals(2, e4.getUDPHostCacheFailures());
assertEquals(0, e5.getUDPHostCacheFailures());
assertEquals(5, cache.getSize());
// reset data, try again.
cache.resetData();
cache.fetchHosts();
Thread.sleep(100);
routeFor("3.4.5.6", cache.guid);
Thread.sleep(4 * 1000);
assertEquals(1, e1.getUDPHostCacheFailures());
assertEquals(1, e2.getUDPHostCacheFailures());
assertEquals(0, e3.getUDPHostCacheFailures());
assertEquals(3, e4.getUDPHostCacheFailures());
assertEquals(1, e5.getUDPHostCacheFailures());
assertEquals(5, cache.getSize());
// reset data, try again.
cache.resetData();
cache.fetchHosts();
Thread.sleep(100);
routeFor("1.2.3.4", cache.guid);
routeFor("2.3.4.5", cache.guid);
Thread.sleep(4 * 1000);
assertEquals(0, e1.getUDPHostCacheFailures());
assertEquals(0, e2.getUDPHostCacheFailures());
assertEquals(1, e3.getUDPHostCacheFailures());
assertEquals(4, e4.getUDPHostCacheFailures());
assertEquals(2, e5.getUDPHostCacheFailures());
assertEquals(5, cache.getSize());
// reset data, try again.
cache.resetData();
cache.fetchHosts();
Thread.sleep(100);
routeFor("1.2.3.4", cache.guid);
routeFor("2.3.4.5", cache.guid);
Thread.sleep(4 * 1000);
assertEquals(0, e1.getUDPHostCacheFailures());
assertEquals(0, e2.getUDPHostCacheFailures());
assertEquals(2, e3.getUDPHostCacheFailures());
assertEquals(5, e4.getUDPHostCacheFailures());
assertEquals(3, e5.getUDPHostCacheFailures());
assertEquals(5, cache.getSize());
// reset data, try again.
// e4 should be ejected because it failed over 5 times.
cache.resetData();
cache.fetchHosts();
Thread.sleep(100);
routeFor("1.2.3.4", cache.guid);
routeFor("2.3.4.5", cache.guid);
Thread.sleep(4 * 1000);
assertEquals(0, e1.getUDPHostCacheFailures());
assertEquals(0, e2.getUDPHostCacheFailures());
assertEquals(3, e3.getUDPHostCacheFailures());
assertEquals(6, e4.getUDPHostCacheFailures());
assertEquals(4, e5.getUDPHostCacheFailures());
assertEquals(4, cache.getSize());
// now try a real reset data that decrements failures,
// ensure the caches failures went down
// this includes e4, because we did attempt it.
cache.doRealDecrement = true;
cache.resetData();
assertEquals(0, e1.getUDPHostCacheFailures());
assertEquals(0, e2.getUDPHostCacheFailures());
assertEquals(2, e3.getUDPHostCacheFailures());
assertEquals(5, e4.getUDPHostCacheFailures());
assertEquals(3, e5.getUDPHostCacheFailures());
assertEquals(5, cache.getSize()); // e4 was readded.
}
private void assertStartsWithInList(String find, List list) throws Exception {
boolean found = false;
for(Iterator i = list.iterator(); i.hasNext();) {
String read = (String)i.next();
if(read.startsWith(find)) {
found = true;
i.remove();
break;
}
}
assertTrue("missing: " + find + ", had: " + list, found);
}
private void routeFor(String host, byte[] guid) throws Exception {
PingReply pr = PingReply.create(guid, (byte)1);
InetSocketAddress addr = new InetSocketAddress(InetAddress.getByName(host), 6346);
RouterService.getMessageRouter().handleUDPMessage(pr, addr);
}
private ExtendedEndpoint create(String host) {
return (new ExtendedEndpoint(host, 6346)).setUDPHostCache(true);
}
private ExtendedEndpoint create(String host, int failures) {
ExtendedEndpoint ep = create(host);
for(int i = 0; i < failures; i++)
ep.recordUDPHostCacheFailure();
return ep;
}
private static class StubCache extends UDPHostCache {
private static final int EXPIRY_TIME = 10 * 1000;
private int amountFetched = -1;
private Collection lastFetched;
private boolean doRealFetch = false;
private byte[] guid = null;
private boolean doRealDecrement = false;
public StubCache() {
super(EXPIRY_TIME,new UniqueHostPinger());
}
protected boolean fetch(Collection hosts) {
if(doRealFetch) {
return super.fetch(hosts);
} else {
amountFetched = hosts.size();
lastFetched = hosts;
if(amountFetched == 0)
return false;
else
return true;
}
}
protected PingRequest getPing() {
PingRequest pr = super.getPing();
guid = pr.getGUID();
return pr;
}
protected void decrementFailures() {
if(doRealDecrement) {
super.decrementFailures();
} else {
;
}
}
}
}