Package com.google.gwt.junit

Source Code of com.google.gwt.junit.JUnitShell

/*
* Copyright 2007 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.google.gwt.dev.BootStrapPlatform;
import com.google.gwt.dev.GWTShell;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.ModuleDefLoader;
import com.google.gwt.dev.shell.BrowserWidgetHost;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
import com.google.gwt.junit.benchmarks.BenchmarkReport;
import com.google.gwt.junit.client.Benchmark;
import com.google.gwt.junit.client.TestResults;
import com.google.gwt.junit.client.TimeoutException;
import com.google.gwt.junit.client.Trial;
import com.google.gwt.junit.remote.BrowserManager;
import com.google.gwt.util.tools.ArgHandlerFlag;
import com.google.gwt.util.tools.ArgHandlerString;

import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import junit.framework.TestResult;

import java.io.File;
import java.rmi.Naming;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* This class is responsible for hosting JUnit test case execution. There are
* three main pieces to the JUnit system.
*
* <ul>
* <li>Test environment</li>
* <li>Client classes</li>
* <li>Server classes</li>
* </ul>
*
* <p>
* The test environment consists of this class and the non-translatable version
* of {@link com.google.gwt.junit.client.GWTTestCase}. These two classes
* integrate directly into the real JUnit test process.
* </p>
*
* <p>
* The client classes consist of the translatable version of {@link
* com.google.gwt.junit.client.GWTTestCase}, translatable JUnit classes, and the
* user's own {@link com.google.gwt.junit.client.GWTTestCase}-derived class.
* The client communicates to the server via RPC.
* </p>
*
* <p>
* The server consists of {@link com.google.gwt.junit.server.JUnitHostImpl}, an
* RPC servlet which communicates back to the test environment through a
* {@link JUnitMessageQueue}, thus closing the loop.
* </p>
*/
public class JUnitShell extends GWTShell {

  /**
   * Executes shutdown logic for JUnitShell
   *
   * Sadly, there's no simple way to know when all unit tests have finished
   * executing. So this class is registered as a VM shutdown hook so that work
   * can be done at the end of testing - for example, writing out the reports.
   */
  private class Shutdown implements Runnable {

    public void run() {
      try {
        String reportPath = System.getProperty(Benchmark.REPORT_PATH);
        if (reportPath == null || reportPath.trim().equals("")) {
          reportPath = System.getProperty("user.dir");
        }
        report.generate(reportPath + File.separator + "report-"
            + new Date().getTime() + ".xml");
      } catch (Exception e) {
        // It really doesn't matter how we got here.
        // Regardless of the failure, the VM is shutting down.
        e.printStackTrace();
      }
    }
  }

  /**
   * This is a system property that, when set, emulates command line arguments.
   */
  private static final String PROP_GWT_ARGS = "gwt.args";

  /**
   * This legacy system property, when set, causes us to run in web mode.
   * (Superceded by passing "-web" into gwt.args).
   */
  private static final String PROP_JUNIT_HYBRID_MODE = "gwt.hybrid";

  /**
   * The amount of time to wait for all clients to have contacted the server and
   * begun running the test.
   */
  private static final int TEST_BEGIN_TIMEOUT_MILLIS = 60000;

  /**
   * Singleton object for hosting unit tests. All test case instances executed
   * by the TestRunner will use the single unitTestShell.
   */
  private static JUnitShell unitTestShell;

  static {
    ModuleDefLoader.forceInherit("com.google.gwt.junit.JUnit");
    ModuleDefLoader.setEnableCachingModules(true);
  }

  /**
   * Called by {@link com.google.gwt.junit.server.JUnitHostImpl} to get an
   * interface into the test process.
   *
   * @return The {@link JUnitMessageQueue} interface that belongs to the
   *         singleton {@link JUnitShell}, or <code>null</code> if no such
   *         singleton exists.
   */
  public static JUnitMessageQueue getMessageQueue() {
    if (unitTestShell == null) {
      return null;
    }
    return unitTestShell.messageQueue;
  }

  /**
   * Called by {@link com.google.gwt.junit.rebind.JUnitTestCaseStubGenerator} to
   * add test meta data to the test report.
   *
   * @return The {@link BenchmarkReport} that belongs to the singleton {@link
   *         JUnitShell}, or <code>null</code> if no such singleton exists.
   */
  public static BenchmarkReport getReport() {
    if (unitTestShell == null) {
      return null;
    }
    return unitTestShell.report;
  }

  /**
   * Entry point for {@link com.google.gwt.junit.client.GWTTestCase}. Gets or
   * creates the singleton {@link JUnitShell} and invokes its {@link
   * #runTestImpl(String, TestCase, TestResult)}.
   */
  public static void runTest(String moduleName, TestCase testCase,
      TestResult testResult) throws UnableToCompleteException {
    getUnitTestShell().runTestImpl(moduleName, testCase, testResult);
  }

  /**
   * Retrieves the JUnitShell. This should only be invoked during TestRunner
   * execution of JUnit tests.
   */
  private static JUnitShell getUnitTestShell() {
    if (unitTestShell == null) {
      BootStrapPlatform.go();
      JUnitShell shell = new JUnitShell();
      String[] args = shell.synthesizeArgs();
      if (!shell.processArgs(args)) {
        throw new RuntimeException("Invalid shell arguments");
      }

      shell.messageQueue = new JUnitMessageQueue(shell.numClients);

      if (!shell.startUp()) {
        throw new RuntimeException("Shell failed to start");
      }

      shell.report = new BenchmarkReport(shell.getTopLogger());
      unitTestShell = shell;

      Runtime.getRuntime().addShutdownHook(new Thread(shell.new Shutdown()));
    }

    return unitTestShell;
  }

  /**
   * When headless, all logging goes to the console.
   */
  private PrintWriterTreeLogger consoleLogger;

  /**
   * Name of the module containing the current/last module to run.
   */
  private String currentModuleName;

  /**
   * If true, the last attempt to launch failed.
   */
  private boolean lastLaunchFailed;

  /**
   * Portal to interact with the servlet.
   */
  private JUnitMessageQueue messageQueue;

  /**
   * The number of test clients executing in parallel. With -remoteweb, users
   * can specify a number of parallel test clients, but by default we only have
   * 1.
   */
  private int numClients = 1;

  /**
   * The result of benchmark runs.
   */
  private BenchmarkReport report;

  /**
   * What type of test we're running; Local hosted, local web, or remote web.
   */
  private RunStyle runStyle = new RunStyleLocalHosted(this);

  /**
   * The time at which the current test will fail if the client has not yet
   * started the test.
   */
  private long testBeginTimeout;

  /**
   * Class name of the current/last test case to run.
   */
  private String testCaseClassName;

  /**
   * Enforce the singleton pattern. The call to {@link GWTShell}'s ctor forces
   * server mode and disables processing extra arguments as URLs to be shown.
   */
  private JUnitShell() {
    super(true, true);

    registerHandler(new ArgHandlerFlag() {

      public String getPurpose() {
        return "Causes your test to run in web (compiled) mode (defaults to hosted mode)";
      }

      public String getTag() {
        return "-web";
      }

      public boolean setFlag() {
        runStyle = new RunStyleLocalWeb(JUnitShell.this);
        return true;
      }

    });

    registerHandler(new ArgHandlerString() {

      public String getPurpose() {
        return "Runs web mode via RMI to a BrowserManagerServer; e.g. rmi://localhost/ie6";
      }

      public String getTag() {
        return "-remoteweb";
      }

      public String[] getTagArgs() {
        return new String[] {"rmiUrl"};
      }

      public boolean isUndocumented() {
        return true;
      }

      public boolean setString(String str) {
        try {
          String[] urls = str.split(",");
          numClients = urls.length;
          BrowserManager[] browserManagers = new BrowserManager[numClients];
          for (int i = 0; i < numClients; ++i) {
            browserManagers[i] = (BrowserManager) Naming.lookup(urls[i]);
          }
          runStyle = new RunStyleRemoteWeb(JUnitShell.this, browserManagers);
        } catch (Exception e) {
          System.err.println("Error connecting to browser manager at " + str);
          e.printStackTrace();
          System.exit(1);
          return false;
        }
        return true;
      }
    });

    registerHandler(new ArgHandlerFlag() {

      public String getPurpose() {
        return "Causes the log window and browser windows to be displayed; useful for debugging";
      }

      public String getTag() {
        return "-notHeadless";
      }

      public boolean setFlag() {
        setHeadless(false);
        return true;
      }
    });

    setRunTomcat(true);
    setHeadless(true);

    // Legacy: -Dgwt.hybrid runs web mode
    if (System.getProperty(PROP_JUNIT_HYBRID_MODE) != null) {
      runStyle = new RunStyleLocalWeb(this);
    }
  }

  public TreeLogger getTopLogger() {
    if (consoleLogger != null) {
      return consoleLogger;
    } else {
      return super.getTopLogger();
    }
  }

  protected String doGetDefaultLogLevel() {
    return "WARN";
  }

  /**
   * Overrides the default module loading behavior. Clears any existing entry
   * points and adds an entry point for the class being tested. This test class
   * is then rebound to a generated subclass.
   */
  protected ModuleDef doLoadModule(TreeLogger logger, String moduleName)
      throws UnableToCompleteException {

    ModuleDef module = super.doLoadModule(logger, moduleName);

    // Tweak the module for JUnit support
    //
    if (moduleName.equals(currentModuleName)) {
      module.clearEntryPoints();
      module.addEntryPointTypeName(testCaseClassName);
    }
    return module;
  }

  /**
   * Never check for updates in JUnit mode.
   */
  protected boolean doShouldCheckForUpdates() {
    return false;
  }

  protected ArgHandlerPort getArgHandlerPort() {
    return new ArgHandlerPort() {
      public String[] getDefaultArgs() {
        return new String[] {"-port", "auto"};
      }
    };
  }

  protected void initializeLogger() {
    if (isHeadless()) {
      consoleLogger = new PrintWriterTreeLogger();
      consoleLogger.setMaxDetail(getLogLevel());
    } else {
      super.initializeLogger();
    }
  }

  /**
   * Overrides {@link GWTShell#notDone()} to wait for the currently-running test
   * to complete.
   */
  protected boolean notDone() {
    if (!messageQueue.haveAllClientsRetrievedCurrentTest()
        && testBeginTimeout < System.currentTimeMillis()) {
      throw new TimeoutException(
          "The browser did not contact the server within "
              + TEST_BEGIN_TIMEOUT_MILLIS + "ms.");
    }

    if (messageQueue.hasResult(testCaseClassName)) {
      return false;
    }

    return !runStyle.wasInterrupted();
  }

  void compileForWebMode(String moduleName) throws UnableToCompleteException {
    BrowserWidgetHost browserHost = getBrowserHost();
    assert (browserHost != null);
    browserHost.compile(new String[] {moduleName});
  }

  /**
   * Runs a particular test case.
   */
  private void runTestImpl(String moduleName, TestCase testCase,
      TestResult testResult) throws UnableToCompleteException {

    String newTestCaseClassName = testCase.getClass().getName();
    boolean sameTest = newTestCaseClassName.equals(testCaseClassName);
    if (sameTest && lastLaunchFailed) {
      throw new UnableToCompleteException();
    }

    messageQueue.setNextTestName(newTestCaseClassName, testCase.getName());

    try {
      lastLaunchFailed = false;
      testCaseClassName = newTestCaseClassName;
      currentModuleName = moduleName;
      runStyle.maybeLaunchModule(moduleName, !sameTest);
    } catch (UnableToCompleteException e) {
      lastLaunchFailed = true;
      testResult.addError(testCase, e);
      return;
    }

    // Wait for test to complete
    try {
      // Set a timeout period to automatically fail if the servlet hasn't been
      // contacted; something probably went wrong (the module failed to load?)
      testBeginTimeout = System.currentTimeMillis() + TEST_BEGIN_TIMEOUT_MILLIS;
      pumpEventLoop();
    } catch (TimeoutException e) {
      lastLaunchFailed = true;
      testResult.addError(testCase, e);
      return;
    }

    List/* JUnitMessageQueue.TestResult */results
        = messageQueue.getResults(testCaseClassName);

    if (results == null) {
      return;
    }

    boolean parallelTesting = numClients > 1;

    for (int i = 0; i < results.size(); ++i) {
      TestResults result = (TestResults) results.get(i);
      Trial firstTrial = (Trial) result.getTrials().get(0);
      Throwable exception = firstTrial.getException();

      // In the case that we're running multiple clients at once, we need to
      // let the user know the browser in which the failure happened
      if (parallelTesting && exception != null) {
        String msg = "Remote test failed at " + result.getHost() + " on "
            + result.getAgent();
        if (exception instanceof AssertionFailedError) {
          AssertionFailedError newException = new AssertionFailedError(msg
              + "\n" + exception.getMessage());
          newException.setStackTrace(exception.getStackTrace());
          exception = newException;
        } else {
          exception = new RuntimeException(msg, exception);
        }
      }

      // A "successful" failure
      if (exception instanceof AssertionFailedError) {
        testResult.addFailure(testCase, (AssertionFailedError) exception);
      } else if (exception != null) {
        // A real failure
        testResult.addError(testCase, exception);
      }

      if (testCase instanceof Benchmark) {
        report.addBenchmarkResults(testCase, result);
      }
    }
  }

  /**
   * Synthesize command line arguments from a system property.
   */
  private String[] synthesizeArgs() {
    ArrayList argList = new ArrayList();

    String args = System.getProperty(PROP_GWT_ARGS);
    if (args != null) {
      // Match either a non-whitespace, non start of quoted string, or a
      // quoted string that can have embedded, escaped quoting characters
      //
      Pattern pattern = Pattern.compile("[^\\s\"]+|\"[^\"\\\\]*(\\\\.[^\"\\\\]*)*\"");
      Matcher matcher = pattern.matcher(args);
      while (matcher.find()) {
        argList.add(matcher.group());
      }
    }

    return (String[]) argList.toArray(new String[argList.size()]);
  }
}
TOP

Related Classes of com.google.gwt.junit.JUnitShell

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.