package edu.washington.cs.oneswarm.test.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletResponse;
import junit.framework.JUnit4TestAdapter;
import junit.framework.TestResult;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.util.encoders.Base64;
import org.junit.Assert;
import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreComponent;
import com.aelitis.azureus.core.AzureusCoreException;
import com.aelitis.azureus.core.AzureusCoreLifecycleListener;
import com.aelitis.azureus.core.impl.AzureusCoreImpl;
import com.thoughtworks.selenium.Selenium;
import edu.washington.cs.oneswarm.f2f.Friend;
import edu.washington.cs.oneswarm.f2f.OSF2FMain;
import edu.washington.cs.oneswarm.test.integration.oop.LocalOneSwarm;
import edu.washington.cs.oneswarm.test.integration.oop.LocalOneSwarmListener;
/**
* Miscellaneous utility functions for running OneSwarm integration tests.
*
* All methods in this class should be static.
*/
public class TestUtils {
private static Logger logger = Logger.getLogger(TestUtils.class.getName());
/** The web interface port used by the JVM OneSwarm instance. */
public static final int JVM_INSTANCE_WEB_UI_PORT = 4000;
/** The port used by the JVM instance StartServer. */
public static final int JVM_INSTANCE_START_SERVER_PORT = JVM_INSTANCE_WEB_UI_PORT + 2;
/** The URL of the web UI for the JVM test instance. */
public static final String JVM_INSTANCE_WEB_UI = "http://127.0.0.1:" + JVM_INSTANCE_WEB_UI_PORT
+ "/";
// Changed to 8889 to allow running OneSwarm in hosted mode along with the
// community server
public static final String TEST_COMMUNITY_SERVER = "localhost:8889";
/**
* Whether an integration test was started via junit, to determine when
* instances are started.
*/
private static boolean swtTestRunnerMade = false;
/** Checks if a test instance of the community server is running locally. */
public static boolean isLocalCommunityServerRunning() {
try {
HttpURLConnection conn = (HttpURLConnection) new URL("http://" + TEST_COMMUNITY_SERVER
+ "/community").openConnection();
// Expecting BAD_REQUEST since we didn't include a key -- indicates
// that the server is
// up.
if (conn.getResponseCode() == HttpServletResponse.SC_BAD_REQUEST) {
return true;
}
conn.disconnect();
} catch (IOException e) {
System.err.println("Community server check failed: " + e.toString());
return false;
}
return false;
}
/** Blocks until the LocalOneSwarm {@code instance} has started. */
public static void awaitInstanceStart(LocalOneSwarm instance) {
final CountDownLatch latch = new CountDownLatch(1);
/*
* We need to add the listener before checking if we're running to avoid
* an initialization race.
*/
LocalOneSwarmListener listener = new LocalOneSwarmListener() {
@Override
public void instanceStarted(LocalOneSwarm instance) {
latch.countDown();
}
};
instance.addListener(listener);
try {
if (instance.getState() == LocalOneSwarm.State.RUNNING) {
latch.countDown();
}
if (!latch.await(30, TimeUnit.SECONDS)) {
Assert.fail();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
instance.removeListener(listener);
}
}
public static String DEFAULT_NAME = "OneSwarmJVM";
/**
* Starts the a OneSwarm client in the current JVM in a testing
* configuration. This method will never return.
*/
public static void startOneSwarmForTest(boolean useExperimentalConfig) throws IOException {
final String label = DEFAULT_NAME;
// Configure the environment for tests
System.setProperty("MULTI_INSTANCE", "true");
String warPath = new File("./gwt-bin", "war").getAbsolutePath();
System.setProperty("debug.war", warPath);
Map<String, String> scratchPaths = createScratchLocationsForTest(label);
System.setProperty("oneswarm.integration.test", "1");
System.setProperty("oneswarm.integration.user.data", scratchPaths.get("userData"));
System.setProperty("azureus.config.path", scratchPaths.get("userData"));
System.setProperty("oneswarm.integration.web.ui.port", JVM_INSTANCE_WEB_UI_PORT + "");
System.setProperty("oneswarm.integration.start.server.port",
Integer.toString(JVM_INSTANCE_START_SERVER_PORT));
System.setProperty("azureus.security.manager.install", "0");
if (useExperimentalConfig) {
System.setProperty("oneswarm.experimental.config.file",
scratchPaths.get("experimentalConfig"));
}
System.setProperty("nolaunch_startup", "1");
// We use an experimental config to set the instance name.
PrintStream experimentalConfig = new PrintStream(new FileOutputStream(
scratchPaths.get("experimentalConfig")));
experimentalConfig
.println("inject edu.washington.cs.oneswarm.test.integration.oop.LocalOneSwarmExperiment");
experimentalConfig.println("name " + label);
com.aelitis.azureus.ui.Main.main(new String[] {});
}
/** Awaits the start of this JVM's OneSwarm instance. */
public static void awaitJVMOneSwarmStart() {
// Await start of this JVM's instance of OneSwarm.
final CountDownLatch latch = new CountDownLatch(1);
new ConditionWaiter(new ConditionWaiter.Predicate() {
@Override
public boolean satisfied() {
return AzureusCoreImpl.isCoreAvailable();
}
}, 60 * 1000).awaitFail();
AzureusCore core = AzureusCoreImpl.getSingleton();
AzureusCoreLifecycleListener l = new AzureusCoreLifecycleListener() {
@Override
public void componentCreated(AzureusCore core, AzureusCoreComponent component) {
}
@Override
public void started(AzureusCore core) {
latch.countDown();
}
@Override
public void stopping(AzureusCore core) {
}
@Override
public void stopped(AzureusCore core) {
}
@Override
public boolean stopRequested(AzureusCore core) throws AzureusCoreException {
return true;
}
@Override
public boolean restartRequested(AzureusCore core) throws AzureusCoreException {
return true;
}
@Override
public boolean syncInvokeRequired() {
return false;
}
};
core.addLifecycleListener(l);
if (core.isStarted()) {
latch.countDown();
}
try {
if (!latch.await(120, TimeUnit.SECONDS)) {
Assert.fail();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
core.removeLifecycleListener(l);
}
/**
* Creates the set of directories needed for OneSwarm test instances and
* returns a {@code Map} with the paths.
*/
public static Map<String, String> createScratchLocationsForTest(String label)
throws IOException {
Map<String, String> scratchPaths = new HashMap<String, String>();
for (String dir : new String[] { "userData", "workingDir" }) {
File tmpDir = new File(System.getProperty("java.io.tmpdir"), label + "-" + dir);
FileUtils.deleteDirectory(tmpDir);
tmpDir.mkdirs();
scratchPaths.put(dir, tmpDir.getAbsolutePath());
}
scratchPaths.put("experimentalConfig", new File(scratchPaths.get("workingDir"),
"exp.config").getAbsolutePath());
return scratchPaths;
}
/**
* Starts the selenium RC server and returns the associated {@code Process}.
*/
public static Process startSeleniumServer(String rootPath) throws IOException {
// TODO(piatek): Replace /usr/bin/java with something configurable
ProcessBuilder pb = new ProcessBuilder("java", "-jar", rootPath
+ "/build/test-libs/selenium-server.jar");
pb.redirectErrorStream(true);
Process p = pb.start();
new ProcessLogConsumer("SeleniumServer", p).start();
return p;
}
/**
* Asynchronously executes JUnit tests for a particular class in a manner
* suitable for OSX, which requires SWT execution on the main thread.
*/
public static void swtCompatibleTestRunner(final Class<?> testClass) throws IOException {
swtTestRunnerMade = true;
final junit.framework.Test suite = new JUnit4TestAdapter(testClass);
final TestResult[] box = new TestResult[1];
final CountDownLatch latch = new CountDownLatch(1);
new Thread("Off-main TestRunner") {
@Override
public void run() {
box[0] = junit.textui.TestRunner.run(suite);
latch.countDown();
if (box[0].errorCount() + box[0].failureCount() > 0) {
System.exit(-1);
}
// TODO(piatek): [bug] For some reason, the MainWindow$16 inner
// class throws a ClassNotFoundException
// on Windows and Linux that prevents a proper shutdown from the
// test. As a work-around, we quit here
// manually.
System.exit(0);
}
}.start();
TestUtils.startOneSwarmForTest(true);
try {
latch.await();
} catch (final InterruptedException e) {
e.printStackTrace();
System.exit(-1);
}
}
/**
* Blocks while creating a new {@code LocalOneSwarm} instance which has the
* local JVM client added and connected as a friend.
*/
public static LocalOneSwarm spawnOneSwarmInstance(boolean connectToLocalInstance,
boolean experimentalInstance) throws Exception {
final LocalOneSwarm localOneSwarm = new LocalOneSwarm(experimentalInstance);
localOneSwarm.start();
TestUtils.awaitInstanceStart(localOneSwarm);
// Connect the two clients. First, get our key and add it to the remote
// instance.
if (connectToLocalInstance) {
final OSF2FMain f2fMain = OSF2FMain.getSingelton();
String base64Key = new String(Base64.encode(f2fMain.getOverlayManager()
.getOwnPublicKey().getEncoded()));
localOneSwarm.getCoordinator().addCommand("addkey TEST " + base64Key + " true true");
// Wait for the friend connectors to become available (prerequisite
// for friend
// connection)
new ConditionWaiter(new ConditionWaiter.Predicate() {
@Override
public boolean satisfied() {
return f2fMain.getDHTConnector() != null;
}
}, 90 * 1000).awaitFail();
new ConditionWaiter(new ConditionWaiter.Predicate() {
@Override
public boolean satisfied() {
return localOneSwarm.getCoordinator().isFriendConnectorAvailable();
}
}, 90 * 1000).awaitFail();
// Next add remote friend's key to our instance
String remoteKey = localOneSwarm.getPublicKey();
Friend f = new Friend(true, true, new Date(), new Date(), InetAddress.getLocalHost(),
localOneSwarm.getCoordinator().getPort(), localOneSwarm.getLabel(),
Base64.decode(remoteKey), "test", 0, 0, false, true);
f2fMain.getFriendManager().addFriend(f);
f2fMain.getDHTConnector().connectToFriend(f);
// Wait for the connection to be established
localOneSwarm.waitForOnlineFriends(1);
}
return localOneSwarm;
}
/** Blocks until a given selenium {@code elementId} can be found. */
public static void awaitElement(final Selenium selenium, final String elementId) {
new ConditionWaiter(new ConditionWaiter.Predicate() {
@Override
public boolean satisfied() {
boolean isPresent = selenium.isElementPresent(elementId);
if (isPresent) {
logger.info("Found: " + elementId);
} else {
logger.info("Missing: " + elementId);
}
return isPresent;
}
}, 15000).awaitFail();
}
/** Uses special servlet handler for test to flush all server side storage. */
public static void flushCommunityServerState() throws IOException {
sendAction("flush");
logger.info("Server state flushed.");
}
/** Sends a given {@code action} string to the local dev app server. */
private static void sendAction(String action) throws IOException {
String url = "http://" + TEST_COMMUNITY_SERVER + "/test?action=" + action;
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
if (conn.getResponseCode() != HttpServletResponse.SC_OK) {
throw new IOException("Bad status: " + conn.getResponseCode() + " for action: "
+ action);
}
}
/** Awaits the presence of an element and then sends a click event. */
public static void awaitAndClick(Selenium selenium, String elementXpath) {
awaitElement(selenium, elementXpath);
selenium.click(elementXpath);
}
public static boolean swtTestRunnerUsed() {
return swtTestRunnerMade;
}
/**
* Start and leave a test instance of OneSwarm running -- use with the
* selenium IDE to develop tests.
*/
public static void main(String[] args) throws Exception {
TestUtils.startOneSwarmForTest(true);
}
}