예제 #1
0
  private WebServer createServer(HttpHandler handler) {
    int port = PortProber.findFreePort();
    final WebServer server = WebServers.createWebServer(newFixedThreadPool(5), port);
    server.add(handler);

    tearDowns.add(
        new Callable<Object>() {
          @Override
          public Object call() {
            server.stop();
            return null;
          }
        });

    try {
      server.start().get(10, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new RuntimeException("Interrupted waiting for server to start", e);
    } catch (ExecutionException e) {
      throw new RuntimeException("Server failed to start", e);
    } catch (java.util.concurrent.TimeoutException e) {
      throw new TimeoutException("Timed out waiting for the server to start", e);
    }
    return server;
  }
예제 #2
0
  @BeforeClass
  public static void setup() throws Exception {
    registry = Registry.getNewInstanceForTestOnly();
    hub = Hub.getNewInstanceForTest(PortProber.findFreePort(), registry);

    status = new URL("http://" + hub.getHost() + ":" + hub.getPort() + "/grid/status");
    host = new HttpHost(hub.getHost(), hub.getPort());

    hub.start();

    p1 = RemoteProxyFactory.getNewBasicRemoteProxy("app1", "http://machine1:4444/");
    p2 = RemoteProxyFactory.getNewBasicRemoteProxy("app1", "http://machine2:4444/");
    p3 = RemoteProxyFactory.getNewBasicRemoteProxy("app1", "http://machine3:4444/");
    p4 = RemoteProxyFactory.getNewBasicRemoteProxy("app1", "http://machine4:4444/");

    RegistrationRequest req = new RegistrationRequest();
    Map<String, Object> capability = new HashMap<String, Object>();
    capability.put("applicationName", "custom app");
    req.addDesiredCapabilitiy(capability);

    Map<String, Object> config = new HashMap<String, Object>();
    config.put("url", "http://machine5:4444/");
    req.setConfiguration(config);
    customProxy = new MyCustomProxy(req);

    registry.add(p1);
    registry.add(p2);
    registry.add(p3);
    registry.add(p4);
    registry.add(customProxy);
  }
  // start a small grid that only has 1 testing slot : firefox
  @BeforeClass(alwaysRun = true)
  public void prepare() throws Exception {

    hub.start();
    hubURL = new URL("http://" + hub.getHost() + ":" + hub.getPort());

    SelfRegisteringRemote remote =
        SelfRegisteringRemote.create(
            SeleniumProtocol.WebDriver, PortProber.findFreePort(), hub.getRegistrationURL());
    remote.addFirefoxSupport(null);
    remote.setMaxConcurrentSession(1);
    remote.setTimeout(-1, -1);
    remote.launchRemoteServer();
    remote.registerToHub();

    // assigning a priority rule where requests with the flag "important"
    // go first.
    hub.getRegistry()
        .setPrioritizer(
            new Prioritizer() {
              public int compareTo(Map<String, Object> a, Map<String, Object> b) {
                boolean aImportant =
                    a.get("important") == null
                        ? false
                        : Boolean.parseBoolean(a.get("important").toString());
                boolean bImportant =
                    b.get("important") == null
                        ? false
                        : Boolean.parseBoolean(b.get("important").toString());
                if (aImportant == bImportant) {
                  return 0;
                }
                if (aImportant && !bImportant) {
                  return -1;
                } else {
                  return 1;
                }
              }
            });
  }
        /**
         * Creates a new service. Before creating a new service, the builder will find a port for
         * the server to listen to.
         *
         * @return The new service object.
         */
        public PhantomJSDriverService build() {
            // Find a port to listen on, if not already decided
            port = port == 0 ? PortProber.findFreePort() : port;

            // Few final checks
            checkState(phantomjs != null, "Path to PhantomJS executable not specified");

            try {
                // Build a list of command line arguments for the executable
                ImmutableList.Builder<String> argsBuilder = ImmutableList.builder();

                // Add command line proxy configuration for PhantomJS
                if (proxy != null) {
                    switch (proxy.getProxyType()) {
                        case MANUAL:
                            if (proxy.getHttpProxy() != null && !proxy.getHttpProxy().isEmpty()) {          //< HTTP proxy
                                argsBuilder.add("--proxy-type=http");
                                argsBuilder.add(String.format("--proxy=%s", proxy.getHttpProxy()));
                            } else if (proxy.getSocksProxy() != null && !proxy.getSocksProxy().isEmpty()) {  //< SOCKS5 proxy
                                argsBuilder.add("--proxy-type=socks5");
                                argsBuilder.add(String.format("--proxy=%s", proxy.getSocksProxy()));
                                if (proxy.getSocksUsername() != null && !proxy.getSocksUsername().isEmpty()
                                        && proxy.getSocksPassword() != null && !proxy.getSocksPassword().isEmpty()) {
	                                argsBuilder.add(String.format("--proxy-auth=%s:%s", proxy.getSocksUsername(),
	                                        proxy.getSocksPassword()));
                                }
                            } else {
                                // TODO Not supported yet by PhantomJS
                                checkArgument(true, "PhantomJS supports only HTTP and Socks5 Proxy currently");
                            }
                            break;
                        case PAC:
                            // TODO Not supported yet by PhantomJS
                            checkArgument(true, "PhantomJS doesn't support Proxy PAC files");
                            break;
                        case SYSTEM:
                            argsBuilder.add("--proxy-type=system");
                            break;
                        case AUTODETECT:
                            // TODO Not supported yet by PhantomJS
                            checkArgument(true, "PhantomJS doesn't support Proxy Auto-configuration");
                            break;
                        case DIRECT:
                        default:
                            argsBuilder.add("--proxy-type=none");
                            break;
                    }
                }

                if (this.acceptSslCerts) {
                    argsBuilder.add("--web-security=false");
                    argsBuilder.add("--ssl-protocol=any");
                    argsBuilder.add("--ignore-ssl-errors=true");
                }

                // Additional command line arguments (if provided)
                if (this.commandLineArguments != null) {
                    argsBuilder.add(this.commandLineArguments);
                }

                // Should use an external GhostDriver?
                if (ghostdriver != null) { //< Path to GhostDriver provided: use it simply as a PhantomJS script
                    // Add the canonical path to GhostDriver
                    argsBuilder.add(ghostdriver.getCanonicalPath());

                    // Add the port to listen on (if not specified in command line args)
                    if (!argsContains(this.ghostdriverCommandLineArguments, "port")) {
                        argsBuilder.add(String.format("--port=%d", port));
                    }

                    // Add Log File (if not specified in command line args)
                    if (logFile != null && !argsContains(this.ghostdriverCommandLineArguments, "logFile")) {
                        argsBuilder.add(String.format("--logFile=%s", logFile.getAbsolutePath()));
                    }

                    // Additional GhostDriver command line arguments (if provided)
                    if (this.ghostdriverCommandLineArguments!= null) {
                        argsBuilder.add(this.ghostdriverCommandLineArguments);
                    }
                } else { //< Path to GhostDriver not provided: use PhantomJS's internal GhostDriver (default behaviour)

                    // Append required parameters (if not specified in command line args)
                    if (!argsContains(this.commandLineArguments, "webdriver")) {
                        argsBuilder.add(String.format("--webdriver=%d", port));
                    }

                    // Add Log File (if not specified in command line args)
                    if (logFile != null && !argsContains(this.commandLineArguments, "webdriver-logfile")) {
                        argsBuilder.add(String.format("--webdriver-logfile=%s", logFile.getAbsolutePath()));
                    }
                }

                // Create a new service
                return new PhantomJSDriverService(phantomjs, port, argsBuilder.build(), environment);
            } catch (IOException e) {
                throw new WebDriverException(e);
            }
        }
/** how to setup a grid that does not use FIFO for the requests. */
public class WebDriverPriorityDemo {

  private Hub hub =
      Hub.getNewInstanceForTest(PortProber.findFreePort(), Registry.getNewInstanceForTestOnly());
  private URL hubURL = hub.getUrl();

  // start a small grid that only has 1 testing slot : firefox
  @BeforeClass(alwaysRun = true)
  public void prepare() throws Exception {

    hub.start();
    hubURL = new URL("http://" + hub.getHost() + ":" + hub.getPort());

    SelfRegisteringRemote remote =
        SelfRegisteringRemote.create(
            SeleniumProtocol.WebDriver, PortProber.findFreePort(), hub.getRegistrationURL());
    remote.addFirefoxSupport(null);
    remote.setMaxConcurrentSession(1);
    remote.setTimeout(-1, -1);
    remote.launchRemoteServer();
    remote.registerToHub();

    // assigning a priority rule where requests with the flag "important"
    // go first.
    hub.getRegistry()
        .setPrioritizer(
            new Prioritizer() {
              public int compareTo(Map<String, Object> a, Map<String, Object> b) {
                boolean aImportant =
                    a.get("important") == null
                        ? false
                        : Boolean.parseBoolean(a.get("important").toString());
                boolean bImportant =
                    b.get("important") == null
                        ? false
                        : Boolean.parseBoolean(b.get("important").toString());
                if (aImportant == bImportant) {
                  return 0;
                }
                if (aImportant && !bImportant) {
                  return -1;
                } else {
                  return 1;
                }
              }
            });
  }

  WebDriver runningOne;

  // mark the grid 100% busy = having 1 firefox test running.
  @Test
  public void test() throws MalformedURLException, InterruptedException {
    DesiredCapabilities ff = DesiredCapabilities.firefox();
    runningOne = new RemoteWebDriver(new URL(hubURL + "/grid/driver"), ff);
    runningOne.get(hubURL + "/grid/console");
    Assert.assertEquals(runningOne.getTitle(), "Grid overview");
  }

  // queuing 5 requests on the grid.
  @Test(dependsOnMethods = "test")
  public void sendMoreRequests() throws MalformedURLException {
    for (int i = 0; i < 5; i++) {
      new Thread(
              new Runnable() {
                public void run() {
                  DesiredCapabilities ff = DesiredCapabilities.firefox();
                  try {
                    new RemoteWebDriver(new URL(hubURL + "/grid/driver"), ff);
                  } catch (MalformedURLException e) {
                    e.printStackTrace();
                  }
                }
              })
          .start();
    }
  }

  WebDriver importantOne;
  boolean importantOneStarted = false;

  // adding a request with high priority at the end of the queue
  @Test(dependsOnMethods = "sendMoreRequests", timeOut = 30000)
  public void sendTheImportantOne() throws MalformedURLException, InterruptedException {
    while (hub.getRegistry().getNewSessionRequests().size() != 5) {
      Thread.sleep(250);
    }
    Assert.assertEquals(hub.getRegistry().getNewSessionRequests().size(), 5);
    Assert.assertEquals(hub.getRegistry().getActiveSessions().size(), 1);

    final DesiredCapabilities ff = DesiredCapabilities.firefox();
    ff.setCapability("important", true);

    new Thread(
            new Runnable() {
              public void run() {
                try {
                  importantOne = new RemoteWebDriver(new URL(hubURL + "/grid/driver"), ff);
                  importantOneStarted = true;
                } catch (MalformedURLException e) {
                  throw new RuntimeException("bug", e);
                }
              }
            })
        .start();
  }

  // then 5 more non-important requests
  @Test(dependsOnMethods = "sendTheImportantOne")
  public void sendMoreRequests2() throws MalformedURLException {
    for (int i = 0; i < 5; i++) {
      new Thread(
              new Runnable() {
                public void run() {
                  DesiredCapabilities ff = DesiredCapabilities.firefox();
                  try {
                    new RemoteWebDriver(new URL(hubURL + "/grid/driver"), ff);
                  } catch (MalformedURLException e) {
                    e.printStackTrace();
                  }
                }
              })
          .start();
    }
  }

  @Test(dependsOnMethods = "sendMoreRequests2", timeOut = 20000)
  public void validateStateAndPickTheImportantOne() throws InterruptedException {
    try {
      while (hub.getRegistry().getNewSessionRequests().size() != 11) {
        Thread.sleep(500);
      }
      // queue = 5 + 1 important + 5.
      Assert.assertEquals(hub.getRegistry().getNewSessionRequests().size(), 11);

      // 1 firefox still running
      Assert.assertEquals(hub.getRegistry().getActiveSessions().size(), 1);

      // closing the running test.
      runningOne.quit();

      // validating new expected state
      while (!(hub.getRegistry().getActiveSessions().size() == 1
          && hub.getRegistry().getNewSessionRequests().size() == 10)) {
        Thread.sleep(250);
        Reporter.log("waiting for correct state.");
      }

      // TODO freynaud : sometines does not start. FF pops up, but address bar remains empty.
      while (!importantOneStarted) {
        Thread.sleep(250);
        Reporter.log("waiting for browser to start");
      }
      importantOne.get(hubURL + "/grid/console");
      Assert.assertEquals(importantOne.getTitle(), "Grid overview");
    } finally {
      // cleaning the queue to avoid having some browsers left over after
      // the test
      hub.getRegistry().getNewSessionRequests().clear();
      importantOne.quit();
    }
  }

  @AfterClass(alwaysRun = true)
  public void stop() throws Exception {
    hub.stop();
  }
}
/**
 * OperaLauncherRunner implements an interface in C++ with a Java API for controlling the Opera
 * binary.
 */
public class OperaLauncherRunner extends OperaRunner
    implements com.opera.core.systems.runner.interfaces.OperaRunner {

  public static final String LAUNCHER_ENV_VAR = "OPERA_LAUNCHER";
  public static final String LAUNCHER_NAME = launcherNameForOS();
  public static final File LAUNCHER_DIRECTORY =
      new File(System.getProperty("user.home"), ".launcher");
  public static final File LAUNCHER_DEFAULT_LOCATION = new File(LAUNCHER_DIRECTORY, LAUNCHER_NAME);

  private final int launcherPort = PortProber.findFreePort();
  private final List<String> arguments;

  private OperaLauncherBinary binary = null;
  private OperaLauncherProtocol protocol = null;

  private String crashlog = null;

  public OperaLauncherRunner() {
    this(new OperaSettings());
  }

  public OperaLauncherRunner(OperaSettings s) {
    super(s);

    // Locate the bundled launcher from OperaLaunchers project and copy it to its default location
    // on users system if it's not there or outdated
    URL bundledLauncher =
        OperaLaunchers.class.getClassLoader().getResource("launchers/" + LAUNCHER_NAME);

    if (bundledLauncher == null) {
      throw new OperaRunnerException("Not able to locate bundled launcher: " + bundledLauncher);
    }

    File launcher = settings.getLauncher();
    try {
      if (launcher.getCanonicalPath().equals(LAUNCHER_DEFAULT_LOCATION.getCanonicalPath())
          && (!launcher.exists() || isLauncherOutdated(launcher))) {
        extractLauncher(bundledLauncher, launcher);
      }
    } catch (IOException e) {
      throw new OperaRunnerException(e);
    }

    if (!launcher.canExecute()) {
      if (!launcher.setExecutable(true)) {
        throw new OperaRunnerException("Unable to make launcher executable: " + launcher.getPath());
      }
    }

    // Find a suitable Opera executable based on requested product if no binary has already been
    // specified
    if (settings.getBinary() == null) {
      // Do check for null here since OperaBinary's sanitization throws a cryptic null pointer
      File binary = OperaBinary.find(settings.getProduct());
      if (binary == null) {
        throw new OperaRunnerException(
            String.format("Unable to find executable for product %s", settings.getProduct()));
      }

      // Calls new OperaBinary(b) which will check that the binary is executable and that it's not a
      // directory
      settings.setBinary(binary);
    }

    // Create list of arguments for launcher binary
    arguments = buildArguments();
    logger.config("launcher arguments: " + arguments);

    init();
  }

  private void init() {
    try {
      binary =
          new OperaLauncherBinary(
              settings.getLauncher().getPath(), arguments.toArray(new String[arguments.size()]));
    } catch (IOException e) {
      throw new OperaRunnerException("Unable to start launcher: " + e.getMessage());
    }

    logger.fine("Waiting for launcher connection on port " + launcherPort);

    ServerSocket listenerServer = null;
    try {
      // Setup listener server
      listenerServer = new ServerSocket(launcherPort);
      // TODO(andreastt): Unsafe int cast
      listenerServer.setSoTimeout((int) OperaIntervals.LAUNCHER_CONNECT_TIMEOUT.getMs());

      // Try to connect
      protocol = new OperaLauncherProtocol(listenerServer.accept());

      // We did it!
      logger.fine("Connected with launcher on port " + launcherPort);

      // Do the handshake!
      LauncherHandshakeRequest.Builder request = LauncherHandshakeRequest.newBuilder();
      ResponseEncapsulation res =
          protocol.sendRequest(MessageType.MSG_HELLO, request.build().toByteArray());

      // Are we happy?
      if (res.isSuccess()) {
        logger.finer("Got launcher handshake: " + res.getResponse().toString());
      } else {
        throw new OperaRunnerException(
            "Did not get launcher handshake: " + res.getResponse().toString());
      }
    } catch (SocketTimeoutException e) {
      throw new OperaRunnerException(
          "Timeout waiting for launcher to connect on port " + launcherPort, e);
    } catch (IOException e) {
      throw new OperaRunnerException("Unable to listen to launcher port " + launcherPort, e);
    } finally {
      Closeables.closeQuietly(listenerServer);
    }
  }

  protected List<String> buildArguments() {
    ImmutableList.Builder<String> builder = ImmutableList.builder();

    builder.add("-host").add(settings.getHost());
    builder.add("-port").add(String.format("%s", launcherPort));
    if (settings.getDisplay() != null && settings.getDisplay() > 0) {
      builder.add("-display").add(String.format(":%s", settings.getDisplay()));
    }
    if (settings.logging().getLevel() != Level.OFF) {
      builder.add("-console"); // TODO(andreastt): Allow for file logging
      builder
          .add("-verbosity")
          .add(toLauncherLoggingLevel(settings.logging().getLevel()).toString());
    }
    if (settings.getProduct() != OperaProduct.ALL) {
      builder.add("-profile").add(settings.getProduct().getDescriptionString());
    }
    if (settings.getBackend() != null && !settings.getBackend().isEmpty()) {
      builder.add("-backend").add(settings.getBackend());
    }
    if (settings.hasDetach()) {
      builder.add("-noquit");
    }
    builder.add("-bin").add(settings.getBinary().getAbsolutePath());

    // The launcher will pass on any extra arguments to Opera
    for (OperaArgument argument : settings.arguments()) {
      builder.add(settings.arguments().sign() + argument.getArgument());
      if (argument.getValue() != null && !argument.getValue().isEmpty()) {
        builder.add(argument.getValue());
      }
    }

    return builder.build();
  }

  /**
   * {@inheritDoc}
   *
   * @throws OperaRunnerException if launcher is shut down or not running
   */
  @Override
  public void startOpera() throws OperaRunnerException {
    assertLauncherAlive();

    try {
      byte[] request = LauncherStartRequest.newBuilder().build().toByteArray();

      ResponseEncapsulation res = protocol.sendRequest(MessageType.MSG_START, request);

      if (handleStatusMessage(res.getResponse()) != StatusType.RUNNING) {
        throw new IOException("launcher unable to start binary");
      }

      // Check Opera hasn't immediately exited (e.g. due to unknown arguments)
      try {
        Thread.sleep(OperaIntervals.PROCESS_START_SLEEP.getMs());
      } catch (InterruptedException e) {
        // nothing
      }

      res = protocol.sendRequest(MessageType.MSG_STATUS, request);

      if (handleStatusMessage(res.getResponse()) != StatusType.RUNNING) {
        throw new IOException(
            "Opera exited immediately; possibly incorrect arguments?  Command: "
                + binary.getCommands());
      }
    } catch (IOException e) {
      throw new OperaRunnerException("Could not start Opera: " + e.getMessage());
    }
  }

  /**
   * {@inheritDoc}
   *
   * @throws OperaRunnerException if launcher is shut down or not running
   */
  @Override
  public void stopOpera() throws OperaRunnerException {
    assertLauncherAlive();

    if (!isOperaRunning()) {
      return;
    }

    try {
      LauncherStopRequest.Builder request = LauncherStopRequest.newBuilder();

      ResponseEncapsulation res =
          protocol.sendRequest(MessageType.MSG_STOP, request.build().toByteArray());

      if (handleStatusMessage(res.getResponse()) == StatusType.RUNNING) {
        throw new IOException("launcher unable to stop binary");
      }
    } catch (IOException e) {
      throw new OperaRunnerException("Could not stop Opera: " + e.getMessage());
    }
  }

  @Override
  public boolean isOperaRunning() {
    return isOperaRunning(0);
  }

  public boolean isOperaRunning(int processId) {
    if (!isLauncherRunning()) {
      return false;
    }

    try {
      LauncherStatusRequest.Builder request = LauncherStatusRequest.newBuilder();
      if (processId > 0) {
        request.setProcessid(processId);
      }

      ResponseEncapsulation res =
          protocol.sendRequest(MessageType.MSG_STATUS, request.build().toByteArray());
      logger.finer("Getting Opera's status from launcher: " + res.getResponse().toString());

      return handleStatusMessage(res.getResponse()) == StatusType.RUNNING;
    } catch (IOException e) {
      logger.fine("Could not get state of Opera, assuming launcher has shut down");
      return false;
    }
  }

  @Override
  public boolean hasOperaCrashed() {
    return crashlog != null;
  }

  @Override
  public String getOperaCrashlog() {
    return crashlog;
  }

  @Override
  public void shutdown() {
    if (!isLauncherRunning()) {
      return;
    }

    try {
      // Send a shutdown command to the launcher
      protocol.sendRequestWithoutResponse(MessageType.MSG_SHUTDOWN, null);
    } catch (IOException e) {
      // If launcher has already been shutdown, this shouldn't create an exception, all we want to
      // do is to make sure the protocol is down
    }

    try {
      // Then shutdown the protocol connection
      protocol.shutdown();
    } catch (IOException e) {
      throw new OperaRunnerException("Unable to shut down launcher", e);
    } finally {
      binary.shutdown();
      protocol = null;
      binary = null;
    }
  }

  /**
   * Take screenshot using external program. Will not trigger a screen repaint.
   *
   * @throws OperaRunnerException if launcher is shutdown or not running
   */
  @Override
  public ScreenShotReply saveScreenshot(long timeout, String... hashes)
      throws OperaRunnerException {
    assertLauncherAlive();

    String resultMd5;
    byte[] resultBytes;
    boolean blank = false;

    try {
      LauncherScreenshotRequest.Builder request = LauncherScreenshotRequest.newBuilder();
      for (String hash : hashes) {
        request.addKnownMD5S(hash);
      }
      request.setKnownMD5STimeoutMs((int) timeout);

      ResponseEncapsulation res =
          protocol.sendRequest(MessageType.MSG_SCREENSHOT, request.build().toByteArray());
      LauncherScreenshotResponse response = (LauncherScreenshotResponse) res.getResponse();

      resultMd5 = response.getMd5();
      resultBytes = response.getImagedata().toByteArray();

      if (response.hasBlank()) {
        blank = response.getBlank();
      }

    } catch (SocketTimeoutException e) {
      throw new OperaRunnerException("Could not get screenshot from launcher", e);
    } catch (IOException e) {
      throw new OperaRunnerException("Could not get screenshot from launcher", e);
    }

    ScreenShotReply screenshotreply = new ScreenShotReply(resultMd5, resultBytes);
    screenshotreply.setBlank(blank);
    screenshotreply.setCrashed(this.hasOperaCrashed());

    return screenshotreply;
  }

  /** Handle status message, and updates state. */
  private StatusType handleStatusMessage(GeneratedMessage msg) {
    LauncherStatusResponse response = (LauncherStatusResponse) msg;

    // LOG RESULT!
    logger.finest("[LAUNCHER] Status: " + response.getStatus().toString());

    if (response.hasExitcode()) {
      logger.finest("[LAUNCHER] Status: exitCode=" + response.getExitcode());
    }

    if (response.hasCrashlog()) {
      logger.finest("[LAUNCHER] Status: crashLog=yes");
    } else {
      logger.finest("[LAUNCHER] Status: crashLog=no");
    }

    if (response.getLogmessagesCount() > 0) {
      for (String message : response.getLogmessagesList()) {
        logger.finest("[LAUNCHER LOG] " + message);
      }
    } else {
      logger.finest("[LAUNCHER LOG] No log...");
    }

    // Handle state
    StatusType status = response.getStatus();
    if (status == StatusType.CRASHED) {
      if (response.hasCrashlog()) {
        crashlog = response.getCrashlog().toStringUtf8();
      } else {
        crashlog = ""; // != NULL :-|
      }
    } else {
      crashlog = null;
    }

    // TODO: send something to the operalistener....
    // if(launcherLastKnowStatus == StatusType.RUNNING && status !=
    // StatusType.RUNNING){
    // if(operaListener != null)
    // operaListener.operaBinaryStopped(response.getExitcode());
    // }

    return status;
  }

  private void extractLauncher(URL sourceLauncher, File targetLauncher) {
    checkNotNull(sourceLauncher);
    checkNotNull(targetLauncher);

    InputStream is = null;
    OutputStream os = null;

    try {
      targetLauncher.getParentFile().mkdirs();

      if (!targetLauncher.exists()) {
        Files.touch(targetLauncher);
      }

      is = sourceLauncher.openStream();
      os = new FileOutputStream(targetLauncher);

      ByteStreams.copy(is, os);
    } catch (IOException e) {
      throw new OperaRunnerException("Cannot write file to disk: " + e.getMessage());
    } finally {
      Closeables.closeQuietly(is);
      Closeables.closeQuietly(os);
    }

    logger.fine("New launcher copied to " + targetLauncher.getPath());
  }

  private boolean isLauncherOutdated(File launcher) {
    try {
      return !md5sum(md5(launcher)).equals(LAUNCHER_CHECKSUMS.get(launcher.getName()));
    } catch (NoSuchAlgorithmException e) {
      throw new OperaRunnerException(
          "Algorithm is not available in your environment: " + e.getMessage());
    } catch (IOException e) {
      throw new OperaRunnerException("Unable to open stream or file: " + e.getMessage());
    }
  }

  private void assertLauncherAlive() {
    if (!isLauncherRunning()) {
      throw new OperaRunnerException("launcher was shutdown");
    }
  }

  private boolean isLauncherRunning() {
    return binary != null && binary.isRunning();
  }

  /**
   * Asserts whether given launcher exists, is a file and that it's executable.
   *
   * @param launcher the launcher to assert
   * @throws IOException if there is a problem with the provided launcher
   */
  public static void assertLauncherGood(File launcher) throws IOException {
    if (!launcher.exists()) {
      throw new IOException("Unknown file: " + launcher.getPath());
    }

    if (!launcher.isFile()) {
      throw new IOException("Not a real file: " + launcher.getPath());
    }

    if (!FileHandler.canExecute(launcher)) {
      throw new IOException("Not executable: " + launcher.getPath());
    }
  }

  /**
   * Get the launcher's binary file name based on what flavour of operating system and what kind of
   * architecture the user is using.
   *
   * @return the launcher's binary file name
   */
  private static String launcherNameForOS() {
    Platform currentPlatform = Platform.getCurrent();

    switch (currentPlatform) {
      case LINUX:
      case UNIX:
        return "64".equals(System.getProperty("sun.arch.data.model"))
            ? "launcher-linux-amd64"
            : "launcher-linux-ia32";
      case MAC:
        return "launcher-mac";
      case WINDOWS:
      case VISTA:
      case XP:
        return "launcher-win32-i86pc.exe";
      default:
        throw new WebDriverException(
            "Could not find a platform that supports bundled launchers, please set it manually");
    }
  }

  /*
   * Return the HEX sum of an MD5 byte array.
   *
   * @param b the md5 byte array to hex
   * @return hex version of the byte array
   */
  private static String md5sum(byte[] bytes) {
    String result = "";
    for (byte b : bytes) {
      result += Integer.toString((b & 0xff) + 0x100, 16).substring(1);
    }
    return result;
  }

  /**
   * Get the MD5 hash of the given stream.
   *
   * @param fis the input stream to use
   * @return a byte array of the MD5 hash
   * @throws java.security.NoSuchAlgorithmException if MD5 is not available
   * @throws IOException if an I/O error occurs
   */
  private static byte[] md5(InputStream fis) throws NoSuchAlgorithmException, IOException {
    return ByteStreams.hash(
            ByteStreams.newInputStreamSupplier(ByteStreams.toByteArray(fis)), Hashing.md5())
        .asBytes();
  }

  /**
   * Get the MD5 hash of the given file.
   *
   * @param file file to compute a hash on
   * @return a byte array of the MD5 hash
   * @throws IOException if file cannot be found
   * @throws NoSuchAlgorithmException if MD5 is not available
   */
  private static byte[] md5(File file) throws NoSuchAlgorithmException, IOException {
    return Files.hash(file, Hashing.md5()).asBytes();
  }

  /**
   * The launcher allows for the following logging levels: "FINEST", "FINE", "INFO", "WARNING",
   * "SEVERE". Since the launcher is unusually chatty, we don't want it to use the same logging
   * level as OperaDriver. Besides, it doesn't have the same logging levels as Java. This method
   * accepts a Java logging level and converts it to something sensible to pass on to the launcher.
   *
   * @param level the Java logging level
   * @return a sensible, non-chatty logging level
   */
  protected static Level toLauncherLoggingLevel(Level level) {
    // ALL -2147483648
    // FINEST 300
    // FINER 400
    // FINE 500
    // CONFIG 700
    // INFO 800
    // WARNING 900
    // SEVERE 1000
    // OFF 2147483647

    switch (level.intValue()) {
      case 1000: // SEVERE
        return Level.SEVERE;

      case 900: // WARNING
        return Level.WARNING;

      case 800: // INFO
      case 700: // CONFIG
      case 500: // FINE
      case 400: // FINER
        return Level.FINE;

      case 300: // FINEST
      case -2147483648: // ALL
        return Level.FINEST;

      default: // OFF
        return Level.OFF;
    }
  }
}