blob: f23619a93e25359bdf4433dcdcdcc52b780c447d [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.junit;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.SeleniumException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Runs in web mode via browsers managed by Selenium.
*/
public class RunStyleSelenium extends RunStyle {
private static class RCSelenium {
final String browser;
final String host;
final int port;
Selenium selenium;
public RCSelenium(String browser, String host, int port) {
this.browser = browser;
this.host = host;
this.port = port;
}
public void createSelenium(String domain) {
this.selenium = new DefaultSelenium(host, port, browser, domain);
}
public String getBrowser() {
return browser;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public Selenium getSelenium() {
return selenium;
}
}
private RCSelenium remotes[];
/**
* Whether one of the remote browsers was interrupted.
*/
private boolean wasInterrupted;
/**
* A separate lock to control access to {@link #wasInterrupted}. This keeps
* the main thread calls into {@link #wasInterrupted()} from having to be
* synchronized on the containing instance and potentially block on RPC calls.
* It is okay to take the {@link #wasInterruptedLock} while locking the
* containing instance; it is NOT okay to do the opposite or deadlock could
* occur.
*/
private final Object wasInterruptedLock = new Object();
public RunStyleSelenium(final JUnitShell shell) {
super(shell);
}
@Override
public boolean initialize(String args) {
if (args == null || args.length() == 0) {
throw new JUnitFatalLaunchException(
"Selenium runstyle requires comma-separated Selenium-RC targets");
}
String[] targetsIn = args.split(",");
RCSelenium targets[] = new RCSelenium[targetsIn.length];
Pattern pattern = Pattern.compile("([\\w\\.-]+):([\\d]+)/([\\w\\s\\*(/\\w+)*]+)");
for (int i = 0; i < targets.length; ++i) {
Matcher matcher = pattern.matcher(targetsIn[i]);
if (!matcher.matches()) {
throw new JUnitFatalLaunchException("Unable to parse Selenium target "
+ targetsIn[i] + " (expected format is [host]:[port]/[browser])");
}
RCSelenium instance = new RCSelenium(matcher.group(3), matcher.group(1),
Integer.parseInt(matcher.group(2)));
targets[i] = instance;
}
this.remotes = targets;
shell.setNumClients(targets.length);
// Install a shutdown hook that will close all of our outstanding Selenium
// sessions.
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
for (RCSelenium remote : remotes) {
if (remote.getSelenium() != null) {
try {
remote.getSelenium().stop();
} catch (SeleniumException se) {
shell.getTopLogger().log(TreeLogger.WARN,
"Error stopping selenium session", se);
}
}
}
}
});
start();
return true;
}
@Override
public synchronized void launchModule(String moduleName) throws UnableToCompleteException {
// Get the localhost address.
String domain;
try {
String localhost = InetAddress.getLocalHost().getHostAddress();
domain = "http://" + localhost + ":" + shell.getPort() + "/";
} catch (UnknownHostException e) {
throw new RuntimeException("Unable to determine my ip address", e);
}
// Startup all the selenia and point them at the module url.
for (RCSelenium remote : remotes) {
try {
String url = shell.getModuleUrl(moduleName);
shell.getTopLogger().log(TreeLogger.TRACE,
"Starting with domain: " + domain + " Opening URL: " + url);
remote.createSelenium(domain);
remote.getSelenium().start();
remote.getSelenium().open(url);
} catch (Exception e) {
shell.getTopLogger().log(TreeLogger.ERROR,
"Error launching browser via Selenium-RC at " + remote.getHost(), e);
}
}
}
@Override
public boolean wasInterrupted() {
synchronized (wasInterruptedLock) {
return wasInterrupted;
}
}
/**
* Create the keep-alive thread.
*/
protected void start() {
// This will periodically check for failure of the Selenium session and stop
// the test if something goes wrong.
Thread keepAliveThread = new Thread() {
@Override
public void run() {
do {
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
} while (doKeepAlives());
}
};
keepAliveThread.setDaemon(true);
keepAliveThread.start();
}
private synchronized boolean doKeepAlives() {
if (remotes != null) {
for (RCSelenium remote : remotes) {
// Use getTitle() as a cheap way to see if the Selenium server's still
// responding (Selenium seems to provide no way to check the server
// status directly).
try {
if (remote.getSelenium() != null) {
remote.getSelenium().getTitle();
}
} catch (Throwable e) {
setWasInterrupted(true);
}
}
}
return !wasInterrupted();
}
private void setWasInterrupted(boolean wasInterrupted) {
synchronized (wasInterruptedLock) {
this.wasInterrupted = wasInterrupted;
}
}
}