package com.limegroup.gnutella.bootstrap;
import java.net.URLEncoder;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import junit.framework.Test;
import com.limegroup.gnutella.Endpoint;
import com.limegroup.gnutella.HostCatcher;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.util.BaseTestCase;
import com.limegroup.gnutella.util.CommonUtils;
import com.limegroup.gnutella.util.PrivilegedAccessor;
/**
* Unit tests for BootstrapServerManager.
*/
public class BootstrapServerManagerTest extends BaseTestCase {
public BootstrapServerManagerTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(BootstrapServerManagerTest.class);
}
////////////////////////////////////////////////////////////////////////////
final int RESPONSES_PER_SERVER=12;
final static int SERVER_PORT=6700;
final static String DIRECTORY="/path/to/script.php";
final static String COMMON_PARAMS="client=LIME&version="
+URLEncoder.encode(CommonUtils.getLimeWireVersion());
/** Our backend. */
TestBootstrapServerManager bman;
TestHostCatcher catcher;
/**
* Our servers. They're stored in the backend in order s3-s1.
* For urlfile retrievals, we try them in order s3-s2-s1 (done by
* BootstrapServerManager), but for all others we force it to
* "randomly" try them s3-s1; see TestBootstrapServerManager.randServer()*/
BootstrapServer url1, url2, url3;
TestBootstrapServer s1, s2, s3;
public void setUp() throws Exception {
url1=new BootstrapServer("http://127.0.0.1:"+(SERVER_PORT)+DIRECTORY);
url2=new BootstrapServer("http://127.0.0.1:"+(SERVER_PORT+1)+DIRECTORY);
url3=new BootstrapServer("http://127.0.0.1:"+(SERVER_PORT+2)+DIRECTORY);
//Prepare backend
catcher = new TestHostCatcher();
PrivilegedAccessor.setValue(RouterService.class, "catcher", catcher);
bman = new TestBootstrapServerManager();
bman.addBootstrapServer(url3);
bman.addBootstrapServer(url2);
bman.addBootstrapServer(url1);
//bman.bootstrapServersAdded(); NOT CALLED BECAUSE IT SHUFFLES
//Prepare servers
s1=new TestBootstrapServer(SERVER_PORT);
s3=new TestBootstrapServer(SERVER_PORT+2);
s2=new TestBootstrapServer(SERVER_PORT+1);
StringBuffer response=new StringBuffer();
for (int i=0; i<RESPONSES_PER_SERVER; i++)
response.append("1.2.3."+i+":6346"+(i<5 ? "\r\n" : "\n"));
s1.setResponseData(response.toString());
s2.setResponseData(response.toString());
s3.setResponseData(response.toString());
}
public void tearDown() {
s1.shutdown();
s2.shutdown();
s3.shutdown();
try { Thread.sleep(200); } catch (InterruptedException e) { }
}
///////////////////////////////////////////////////////////////////////
/** Checks hostfile=1 request. Also checks that unreachable hosts are
* visited as needed. */
public void testFetchEndpointsAsync() {
//Make first server unreachable. (Remember try s3, s2, then s1.)
s3.shutdown();
//Connect. Wait for data.
bman.fetchEndpointsAsync();
sleep();
//Check that backend sent right requests. Only the second host should
//have been contacted.
assertNull(s3.getRequest()); //wasn't reachable
assertEquals("GET "+DIRECTORY+"?"+COMMON_PARAMS+"&hostfile=1 HTTP/1.1",
s2.getRequest());
assertNull( s1.getRequest()); //wasn't contacted
//...and got right results.
for (int i=0; i<RESPONSES_PER_SERVER; i++)
assertEquals(new Endpoint("1.2.3."+i+":6346"),
catcher.list.get(i));
}
/** Checks urlfile=1 request. Also checks that multiple hosts are visited
* as needed. */
public void testFetchBootstrapServersAsync() throws ParseException {
final int SIZE=12;
//Prepare server.
StringBuffer response=new StringBuffer();
for (int i=0; i<SIZE/2; i++)
response.append("http://1.2.3."+i+"/script.php\r\n");
s3.setResponseData(response.toString());
response=new StringBuffer();
for (int i=SIZE/2; i<SIZE; i++)
response.append("http://1.2.3."+i+"/script.php\r\n");
s2.setResponseData(response.toString());
//Connect. Wait for data.
bman.fetchBootstrapServersAsync();
sleep();
//Check that backend sent right request. We had to contact the second
//host because the first didn't send enough data.
assertEquals("GET "+DIRECTORY+"?"+COMMON_PARAMS+"&urlfile=1 HTTP/1.1",
s3.getRequest());
assertEquals("GET "+DIRECTORY+"?"+COMMON_PARAMS+"&urlfile=1 HTTP/1.1",
s2.getRequest());
assertEquals(null, s1.getRequest()); //wasn't contacted
//Check that we got the right results. First make sure we kept the old
//server list...
Iterator iter=bman.getBootstrapServers();
assertEquals(url3, iter.next());
assertEquals(url2, iter.next());
assertEquals(url1, iter.next());
//...and added new servers.
for (int i=0; i<RESPONSES_PER_SERVER; i++) {
assertTrue(iter.hasNext());
assertEquals(iter.next(),
new BootstrapServer("http://1.2.3."+i+"/script.php"));
}
assertTrue(! iter.hasNext());
}
public void testRemoveUnreachableHosts() {
//Make first server unreachable.
s3.shutdown();
bman.fetchEndpointsAsync();
sleep();
Iterator iter=bman.getBootstrapServers();
assertEquals(url2, iter.next());
assertEquals(url1, iter.next());
if(iter.hasNext())
fail("had another: " + iter.next());
//We could also check sX.getRequest() and catcher.list, but that's
//covered by previous tests.
}
public void testRemoveBadHTTPHosts() {
//Make first server unreachable.
s3.setResponse("HTTP/1.0 503 Service Unavailable");
bman.fetchEndpointsAsync();
sleep();
Iterator iter=bman.getBootstrapServers();
assertEquals(url2, iter.next());
assertEquals(url1, iter.next());
if(iter.hasNext())
fail("had another: " + iter.next());
}
public void testRemoveErrorHosts() {
//Make first server error.
s3.setResponseData("ERROR\r\n");
bman.fetchEndpointsAsync();
sleep();
Iterator iter=bman.getBootstrapServers();
assertEquals(url2, iter.next());
assertEquals(url1, iter.next());
if(iter.hasNext())
fail("had another: " + iter.next());
}
public void testRemoveBadIPHosts() {
//Make first server error.
s3.setResponseData("18.239.0.155:ABC\r\n");
bman.fetchEndpointsAsync();
sleep();
Iterator iter=bman.getBootstrapServers();
assertEquals(url2, iter.next());
assertEquals(url1, iter.next());
if(iter.hasNext())
fail("had another: " + iter.next());
}
/** Test servers that send HTML for HOSTFILE requestss. */
public void testRemoveBadIPHosts2() {
//Make first server error.
s3.setResponseData("<html>This\r\nis bad\r\ndata>\r\n");
bman.fetchEndpointsAsync();
sleep();
Iterator iter=bman.getBootstrapServers();
assertEquals(url2, iter.next());
assertEquals(url1, iter.next());
if(iter.hasNext())
fail("had another: " + iter.next());
assertEquals(RESPONSES_PER_SERVER, catcher.list.size());
for (int i=0; i<RESPONSES_PER_SERVER; i++)
assertEquals(new Endpoint("1.2.3."+i+":6346"),
catcher.list.get(i));
}
public void testRemoveBadURLHosts() {
//Make first server unreachable.
s3.setResponseData("improper.url\r\n");
//Make second server have data.
StringBuffer response=new StringBuffer();
for (int i=0; i<RESPONSES_PER_SERVER; i++)
response.append("http://1.2.3."+i+"/script.php\r\n");
s2.setResponseData(response.toString());
bman.fetchBootstrapServersAsync();
sleep();
Iterator iter=bman.getBootstrapServers();
assertEquals(url2, iter.next());
assertEquals(url1, iter.next());
assertTrue(! url3.equals(iter.next()));
}
public void testSendUpdatesAsyncNoURL() {
s3.setResponseData("OK\r\n");
bman.sendUpdatesAsync(new Endpoint("18.239.0.144", 6347));
sleep();
assertEquals("GET "+DIRECTORY+"?"+COMMON_PARAMS
+"&ip=18.239.0.144:6347 HTTP/1.1",
s3.getRequest());
assertNull(s2.getRequest());
assertNull(s1.getRequest());
}
public void testSendUpdatesAsyncURL() {
//Force _lastConnectable to be url3
bman.fetchEndpointsAsync();
sleep();
bman.randomServer(); // pretend we contacted s3 so we can contact s2
s2.setResponseData("Ok\r\n"); //Test funny case; not required by spec
bman.sendUpdatesAsync(new Endpoint("18.239.0.145", 6348));
sleep();
assertEquals("GET "+DIRECTORY+"?"+COMMON_PARAMS
+"&ip=18.239.0.145:6348&url="
+"http%3A%2F%2F127.0.0.1%3A6702%2Fpath%2Fto%2Fscript.php"
+" HTTP/1.1",
s2.getRequest());
assertNull(s1.getRequest());
}
public void testSendUpdatesAsyncNoOK() {
s3.setResponseData("");
s2.setResponseData("OK\r\n");
bman.sendUpdatesAsync(new Endpoint("18.239.0.144", 6347));
sleep();
assertEquals("GET "+DIRECTORY+"?"+COMMON_PARAMS
+"&ip=18.239.0.144:6347 HTTP/1.1",
s3.getRequest());
//Yes, url3 is sent to s2 even though url3 never sent OK. TODO: fix
//this.
assertEquals("GET "+DIRECTORY+"?"+COMMON_PARAMS
+"&ip=18.239.0.144:6347&url="
+"http%3A%2F%2F127.0.0.1%3A6702%2Fpath%2Fto%2Fscript.php"
+" HTTP/1.1",
s2.getRequest());
assertNull(s1.getRequest());
}
public void testGiveUp() {
int old = BootstrapServerManager.MAX_HOSTS_PER_REQUEST;
try {
BootstrapServerManager.MAX_HOSTS_PER_REQUEST = 2;
s3.shutdown();
s2.shutdown();
bman.fetchEndpointsAsync();
sleep();
assertEquals(null, s1.getRequest());
assertEquals(0, catcher.list.size());
Iterator iter=bman.getBootstrapServers();
assertEquals(url1, iter.next());
if(iter.hasNext())
fail("had another: " + iter.next());
} finally {
BootstrapServerManager.MAX_HOSTS_PER_REQUEST = old;
}
}
public void testAddBootstrapServer() {
for (int i=0; i<5000; i++) {
try {
BootstrapServer server=new BootstrapServer(
"http://18.239.0.144:"+(i+80)+"/script.jsp");
bman.addBootstrapServer(server);
} catch (ParseException e) {
fail("Bad exception");
}
}
int count=0;
for (Iterator iter=bman.getBootstrapServers();
iter.hasNext();
iter.next()) {
count++;
}
assertEquals(1000, count); //e.g., BootstrapServer.MAX_BOOTSTRAP_SERVERS
//We could also test the replacement policy, but that's a bit of a pain.
}
public void testDefaults() {
//Clear out the list of urls.
s3.shutdown();
s2.shutdown();
s1.shutdown();
bman.fetchEndpointsAsync();
sleep();
assertTrue(! bman.getBootstrapServers().hasNext());
//Now second call should load defaults...and actually go on the network!
bman.fetchEndpointsAsync();
try { Thread.sleep(3000); } catch (InterruptedException e) { }
//Make sure defaults were loaded
int count=0;
for (Iterator iter=bman.getBootstrapServers();
iter.hasNext();
iter.next()) {
count++;
}
// start with our defaults, add 3 for s3->s1, subtract failed hosts.
assertEquals(DefaultBootstrapServers.urls.length + 3 -
bman.failed, count);
//Make sure this actually got some endpoints. Note: this requires a
//network connection, as it actually uses GWebCache.
//assertTrue("Only got "+catcher.list.size()+" endpoints",
// catcher.list.size()>10);
}
public void testRedirect() {
// we should not follow redirects!!!
s3.setResponse("HTTP/1.1 303 Redirect\r\nLocation: "+url1);
s3.setResponseData("You have been redirected.");
bman.fetchEndpointsAsync();
sleep();
assertNotNull(s3.getRequest()); //original location
assertNull(s1.getRequest()); //was redirected here, but shouldn't go
assertNotNull(s2.getRequest()); //should go here since s3 was crap
assertEquals("invalid responses, got: " + catcher.list,
RESPONSES_PER_SERVER, catcher.list.size());
}
private void sleep() {
//Must be long enough for socket connect timeouts. On Linux
//it appears that a much shorter value will work.
try { Thread.sleep(5000); } catch (InterruptedException e) { }
}
}
/** Overrides the add(Endpoint, boolean) method) */
class TestHostCatcher extends HostCatcher {
List /* of Endpoint */ list=new ArrayList(20);
public TestHostCatcher() {
super();
}
public boolean add(Endpoint e, boolean forceHighPriority) {
list.add(e);
return true;
}
public boolean add(Endpoint e, int priority) {
list.add(e);
return true;
}
}
/** A BootstrapServerManager that tries host in a round-robin fashion. */
class TestBootstrapServerManager extends BootstrapServerManager {
public int failed = 0;
public TestBootstrapServerManager() {
super();
}
/** The LAST value given out, or -1 if none. Starts with s3 and works down
* to avoid problems when unreachable ers are removed from list by
* BootstrapServerManager. */
private int i = -1;
public int randomServer() {
if (i >= size() || i < 0)
return (i = 0);
else
return ++i;
}
protected void removeServer(BootstrapServer server) {
super.removeServer(server);
i--;
failed++;
}
}