Example #1
0
/** Helper functions to run tests. */
public class Helpers implements play.mvc.Http.Status, play.mvc.Http.HeaderNames {

  public static String GET = "GET";
  public static String POST = "POST";
  public static String PUT = "PUT";
  public static String DELETE = "DELETE";
  public static String HEAD = "HEAD";

  // --

  public static Class<? extends WebDriver> HTMLUNIT = HtmlUnitDriver.class;
  public static Class<? extends WebDriver> FIREFOX = FirefoxDriver.class;

  // --
  @SuppressWarnings(value = "unchecked")
  private static Result invokeHandler(
      play.api.mvc.Handler handler, FakeRequest fakeRequest, long timeout) {
    if (handler instanceof play.core.j.JavaAction) {
      play.api.mvc.Action action = (play.api.mvc.Action) handler;
      return wrapScalaResult(action.apply(fakeRequest.getWrappedRequest()), timeout);
    } else {
      throw new RuntimeException("This is not a JavaAction and can't be invoked this way.");
    }
  }

  /**
   * Default Timeout (milliseconds) for fake requests issued by these Helpers. This value is
   * determined from System property <b>test.timeout</b>. The default value is <b>30000</b> (30
   * seconds).
   */
  public static final long DEFAULT_TIMEOUT = Long.getLong("test.timeout", 30000L);

  private static Result wrapScalaResult(
      scala.concurrent.Future<play.api.mvc.Result> result, long timeout) {
    if (result == null) {
      return null;
    } else {
      final play.api.mvc.Result scalaResult = Promise.wrap(result).get(timeout);
      return new Result() {
        public play.api.mvc.Result toScala() {
          return scalaResult;
        }
      };
    }
  }

  // --

  /** Call an action method while decorating it with the right @With interceptors. */
  public static Result callAction(HandlerRef actionReference) {
    return callAction(actionReference, DEFAULT_TIMEOUT);
  }

  public static Result callAction(HandlerRef actionReference, long timeout) {
    return callAction(actionReference, fakeRequest(), timeout);
  }

  /** Call an action method while decorating it with the right @With interceptors. */
  public static Result callAction(HandlerRef actionReference, FakeRequest fakeRequest) {
    return callAction(actionReference, fakeRequest, DEFAULT_TIMEOUT);
  }

  public static Result callAction(
      HandlerRef actionReference, FakeRequest fakeRequest, long timeout) {
    play.api.mvc.HandlerRef handlerRef = (play.api.mvc.HandlerRef) actionReference;
    return invokeHandler(handlerRef.handler(), fakeRequest, timeout);
  }

  /** Build a new GET / fake request. */
  public static FakeRequest fakeRequest() {
    return new FakeRequest();
  }

  /** Build a new fake request. */
  public static FakeRequest fakeRequest(String method, String uri) {
    return new FakeRequest(method, uri);
  }

  /** Build a new fake request corresponding to a given route call */
  public static FakeRequest fakeRequest(Call call) {
    return fakeRequest(call.method(), call.url());
  }

  /** Build a new fake application. */
  public static FakeApplication fakeApplication() {
    return new FakeApplication(
        new java.io.File("."),
        Helpers.class.getClassLoader(),
        new HashMap<String, Object>(),
        new ArrayList<String>(),
        null);
  }

  /** Build a new fake application. */
  public static FakeApplication fakeApplication(GlobalSettings global) {
    return new FakeApplication(
        new java.io.File("."),
        Helpers.class.getClassLoader(),
        new HashMap<String, Object>(),
        new ArrayList<String>(),
        global);
  }

  /** A fake Global */
  public static GlobalSettings fakeGlobal() {
    return new GlobalSettings();
  }

  /** Constructs a in-memory (h2) database configuration to add to a FakeApplication. */
  public static Map<String, String> inMemoryDatabase() {
    return inMemoryDatabase("default");
  }

  /** Constructs a in-memory (h2) database configuration to add to a FakeApplication. */
  public static Map<String, String> inMemoryDatabase(String name) {
    return inMemoryDatabase(name, Collections.<String, String>emptyMap());
  }

  /** Constructs a in-memory (h2) database configuration to add to a FakeApplication. */
  public static Map<String, String> inMemoryDatabase(String name, Map<String, String> options) {
    return Scala.asJava(play.api.test.Helpers.inMemoryDatabase(name, Scala.asScala(options)));
  }

  /** Build a new fake application. */
  public static FakeApplication fakeApplication(
      Map<String, ? extends Object> additionalConfiguration) {
    return new FakeApplication(
        new java.io.File("."),
        Helpers.class.getClassLoader(),
        additionalConfiguration,
        new ArrayList<String>(),
        null);
  }

  /** Build a new fake application. */
  public static FakeApplication fakeApplication(
      Map<String, ? extends Object> additionalConfiguration, GlobalSettings global) {
    return new FakeApplication(
        new java.io.File("."),
        Helpers.class.getClassLoader(),
        additionalConfiguration,
        new ArrayList<String>(),
        global);
  }

  /** Build a new fake application. */
  public static FakeApplication fakeApplication(
      Map<String, ? extends Object> additionalConfiguration, List<String> additionalPlugin) {
    return new FakeApplication(
        new java.io.File("."),
        Helpers.class.getClassLoader(),
        additionalConfiguration,
        additionalPlugin,
        null);
  }

  /** Build a new fake application. */
  public static FakeApplication fakeApplication(
      Map<String, ? extends Object> additionalConfiguration,
      List<String> additionalPlugin,
      GlobalSettings global) {
    return new FakeApplication(
        new java.io.File("."),
        Helpers.class.getClassLoader(),
        additionalConfiguration,
        additionalPlugin,
        global);
  }

  /** Build a new fake application. */
  public static FakeApplication fakeApplication(
      Map<String, ? extends Object> additionalConfiguration,
      List<String> additionalPlugins,
      List<String> withoutPlugins) {
    return new FakeApplication(
        new java.io.File("."),
        Helpers.class.getClassLoader(),
        additionalConfiguration,
        additionalPlugins,
        withoutPlugins,
        null);
  }

  /** Build a new fake application. */
  public static FakeApplication fakeApplication(
      Map<String, ? extends Object> additionalConfiguration,
      List<String> additionalPlugins,
      List<String> withoutPlugins,
      GlobalSettings global) {
    return new FakeApplication(
        new java.io.File("."),
        Helpers.class.getClassLoader(),
        additionalConfiguration,
        additionalPlugins,
        withoutPlugins,
        global);
  }

  /** Extracts the Status code of this Result value. */
  public static int status(Result result) {
    return result.toScala().header().status();
  }

  /** Extracts the Location header of this Result value if this Result is a Redirect. */
  public static String redirectLocation(Result result) {
    return header(LOCATION, result);
  }

  /** Extracts the Flash values of this Result value. */
  public static Flash flash(Result result) {
    return JavaResultExtractor.getFlash(result);
  }

  /** Extracts the Session of this Result value. */
  public static Session session(Result result) {
    return JavaResultExtractor.getSession(result);
  }

  /** Extracts a Cookie value from this Result value */
  public static Cookie cookie(String name, Result result) {
    return JavaResultExtractor.getCookies(result).get(name);
  }

  /** Extracts the Cookies (an iterator) from this result value. */
  public static Cookies cookies(Result result) {
    return play.core.j.JavaResultExtractor.getCookies(result);
  }

  /** Extracts an Header value of this Result value. */
  public static String header(String header, Result result) {
    return JavaResultExtractor.getHeaders(result).get(header);
  }

  /** Extracts all Headers of this Result value. */
  public static Map<String, String> headers(Result result) {
    return JavaResultExtractor.getHeaders(result);
  }

  /** Extracts the Content-Type of this Content value. */
  public static String contentType(Content content) {
    return content.contentType();
  }

  /** Extracts the Content-Type of this Result value. */
  public static String contentType(Result result) {
    String h = header(CONTENT_TYPE, result);
    if (h == null) return null;
    if (h.contains(";")) {
      return h.substring(0, h.indexOf(";")).trim();
    } else {
      return h.trim();
    }
  }

  /** Extracts the Charset of this Result value. */
  public static String charset(Result result) {
    String h = header(CONTENT_TYPE, result);
    if (h == null) return null;
    if (h.contains("; charset=")) {
      return h.substring(h.indexOf("; charset=") + 10, h.length()).trim();
    } else {
      return null;
    }
  }

  /** Extracts the content as bytes. */
  public static byte[] contentAsBytes(Result result) {
    return contentAsBytes(result, DEFAULT_TIMEOUT);
  }

  public static byte[] contentAsBytes(Result result, long timeout) {
    return JavaResultExtractor.getBody(result, timeout);
  }

  /** Extracts the content as bytes. */
  public static byte[] contentAsBytes(Content content) {
    return content.body().getBytes();
  }

  /** Extracts the content as String. */
  public static String contentAsString(Content content) {
    return content.body();
  }

  /** Extracts the content as String. */
  public static String contentAsString(Result result) {
    return contentAsString(result, DEFAULT_TIMEOUT);
  }

  public static String contentAsString(Result result, long timeout) {
    try {
      String charset = charset(result);
      if (charset == null) {
        charset = "utf-8";
      }
      return new String(contentAsBytes(result, timeout), charset);
    } catch (RuntimeException e) {
      throw e;
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
  }

  @SuppressWarnings(value = "unchecked")
  public static Result routeAndCall(FakeRequest fakeRequest, long timeout) {
    try {
      return routeAndCall(
          (Class<? extends Routes>) FakeRequest.class.getClassLoader().loadClass("Routes"),
          fakeRequest,
          timeout);
    } catch (RuntimeException e) {
      throw e;
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
  }

  public static Result routeAndCall(
      Class<? extends Routes> router, FakeRequest fakeRequest, long timeout) {
    try {
      Routes routes =
          (Routes)
              router
                  .getClassLoader()
                  .loadClass(router.getName() + "$")
                  .getDeclaredField("MODULE$")
                  .get(null);
      if (routes.routes().isDefinedAt(fakeRequest.getWrappedRequest())) {
        return invokeHandler(
            routes.routes().apply(fakeRequest.getWrappedRequest()), fakeRequest, timeout);
      } else {
        return null;
      }
    } catch (RuntimeException e) {
      throw e;
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
  }

  public static Result route(FakeRequest fakeRequest) {
    return route(fakeRequest, DEFAULT_TIMEOUT);
  }

  public static Result route(FakeRequest fakeRequest, long timeout) {
    return route(play.Play.application(), fakeRequest, timeout);
  }

  public static Result route(Application app, FakeRequest fakeRequest) {
    return route(app, fakeRequest, DEFAULT_TIMEOUT);
  }

  public static Result route(Application app, FakeRequest fakeRequest, long timeout) {
    final scala.Option<scala.concurrent.Future<play.api.mvc.Result>> opt =
        play.api.test.Helpers.jRoute(app.getWrappedApplication(), fakeRequest.fake);
    return wrapScalaResult(Scala.orNull(opt), timeout);
  }

  public static Result route(Application app, FakeRequest fakeRequest, byte[] body) {
    return route(app, fakeRequest, body, DEFAULT_TIMEOUT);
  }

  public static Result route(Application app, FakeRequest fakeRequest, byte[] body, long timeout) {
    return wrapScalaResult(
        Scala.orNull(
            play.api.test.Helpers.jRoute(
                app.getWrappedApplication(), fakeRequest.getWrappedRequest(), body)),
        timeout);
  }

  public static Result route(FakeRequest fakeRequest, byte[] body) {
    return route(fakeRequest, body, DEFAULT_TIMEOUT);
  }

  public static Result route(FakeRequest fakeRequest, byte[] body, long timeout) {
    return route(play.Play.application(), fakeRequest, body, timeout);
  }

  /** Starts a new application. */
  public static void start(FakeApplication fakeApplication) {

    play.api.Play.start(fakeApplication.getWrappedApplication());
  }

  /** Stops an application. */
  public static void stop(FakeApplication fakeApplication) {
    play.api.Play.stop();
  }

  /** Executes a block of code in a running application. */
  public static void running(FakeApplication fakeApplication, final Runnable block) {
    synchronized (PlayRunners$.MODULE$.mutex()) {
      try {
        start(fakeApplication);
        block.run();
      } finally {
        stop(fakeApplication);
      }
    }
  }

  /**
   * Creates a new Test server listening on port defined by configuration setting "testserver.port"
   * (defaults to 19001).
   */
  public static TestServer testServer() {
    return testServer(play.api.test.Helpers.testServerPort());
  }

  /**
   * Creates a new Test server listening on port defined by configuration setting "testserver.port"
   * (defaults to 19001) and using the given FakeApplication.
   */
  public static TestServer testServer(FakeApplication app) {
    return testServer(play.api.test.Helpers.testServerPort(), app);
  }

  /** Creates a new Test server. */
  public static TestServer testServer(int port) {
    return new TestServer(port, fakeApplication());
  }

  /** Creates a new Test server. */
  public static TestServer testServer(int port, FakeApplication app) {
    return new TestServer(port, app);
  }

  /** Starts a Test server. */
  public static void start(TestServer server) {
    server.start();
  }

  /** Stops a Test server. */
  public static void stop(TestServer server) {
    server.stop();
  }

  /** Executes a block of code in a running server. */
  public static void running(TestServer server, final Runnable block) {
    synchronized (PlayRunners$.MODULE$.mutex()) {
      try {
        start(server);
        block.run();
      } finally {
        stop(server);
      }
    }
  }

  /** Executes a block of code in a running server, with a test browser. */
  public static void running(
      TestServer server, Class<? extends WebDriver> webDriver, final Callback<TestBrowser> block) {
    running(server, play.api.test.WebDriverFactory.apply(webDriver), block);
  }

  /** Executes a block of code in a running server, with a test browser. */
  public static void running(
      TestServer server, WebDriver webDriver, final Callback<TestBrowser> block) {
    synchronized (PlayRunners$.MODULE$.mutex()) {
      TestBrowser browser = null;
      TestServer startedServer = null;
      try {
        start(server);
        startedServer = server;
        browser = testBrowser(webDriver);
        block.invoke(browser);
      } catch (Error e) {
        throw e;
      } catch (RuntimeException re) {
        throw re;
      } catch (Throwable t) {
        throw new RuntimeException(t);
      } finally {
        if (browser != null) {
          browser.quit();
        }
        if (startedServer != null) {
          stop(startedServer);
        }
      }
    }
  }

  /** Creates a Test Browser. */
  public static TestBrowser testBrowser() {
    return testBrowser(HTMLUNIT);
  }

  /** Creates a Test Browser. */
  public static TestBrowser testBrowser(int port) {
    return testBrowser(HTMLUNIT, port);
  }

  /** Creates a Test Browser. */
  public static TestBrowser testBrowser(Class<? extends WebDriver> webDriver) {
    return testBrowser(webDriver, Helpers$.MODULE$.testServerPort());
  }

  /** Creates a Test Browser. */
  public static TestBrowser testBrowser(Class<? extends WebDriver> webDriver, int port) {
    try {
      return new TestBrowser(webDriver, "http://localhost:" + port);
    } catch (RuntimeException e) {
      throw e;
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
  }

  /** Creates a Test Browser. */
  public static TestBrowser testBrowser(WebDriver of, int port) {
    return new TestBrowser(of, "http://localhost:" + port);
  }

  /** Creates a Test Browser. */
  public static TestBrowser testBrowser(WebDriver of) {
    return testBrowser(of, Helpers$.MODULE$.testServerPort());
  }
}
Example #2
0
/**
 * PoliciesCache retains PolicyDocument objects for inserted Files, and reloads them if file
 * modification time changes.
 *
 * @author Greg Schueler <a href="mailto:[email protected]">[email protected]</a>
 */
public class PoliciesCache implements Iterable<PolicyCollection> {
  static final long DIR_LIST_CHECK_DELAY =
      Long.getLong(PoliciesCache.class.getName() + ".DirListCheckDelay", 60000);
  static final long FILE_CHECK_DELAY =
      Long.getLong(PoliciesCache.class.getName() + ".FileCheckDelay", 60000);
  private static final Logger logger = Logger.getLogger(PoliciesCache.class);

  static final FilenameFilter filenameFilter =
      new FilenameFilter() {
        public boolean accept(File dir, String name) {
          return name.endsWith(".aclpolicy");
        }
      };

  private Set<File> warned = new HashSet<File>();
  private Map<File, CacheItem> cache = new HashMap<File, CacheItem>();
  private DocumentBuilder builder;
  private File rootDir;

  public PoliciesCache(File rootDir) throws ParserConfigurationException {
    this.rootDir = rootDir;
    DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
    domFactory.setNamespaceAware(true);
    builder = domFactory.newDocumentBuilder();
    builder.setErrorHandler(null);
  }

  private static class CacheItem {
    PolicyCollection policyCollection;
    Long cacheTime;
    Long modTime;

    private CacheItem(PolicyCollection policyCollection, Long modTime) {
      this.policyCollection = policyCollection;
      this.modTime = modTime;
      this.cacheTime = System.currentTimeMillis();
    }

    public void touch(Long time) {
      this.cacheTime = time;
    }
  }

  long lastDirListCheckTime = 0;
  private File[] lastDirList;

  private File[] listDirFiles() {
    if (System.currentTimeMillis() - lastDirListCheckTime > DIR_LIST_CHECK_DELAY) {
      doListDir();
    }
    return lastDirList;
  }

  private void doListDir() {
    lastDirList = rootDir.listFiles(filenameFilter);
    lastDirListCheckTime = System.currentTimeMillis();
  }

  public synchronized void add(final File file) throws PoliciesParseException {
    getDocument(file);
  }

  private PolicyCollection createEntry(final File file) throws PoliciesParseException {
    try {
      return new YamlPolicyCollection(file);
    } catch (ParserException e1) {
      throw new PoliciesParseException("YAML syntax error: " + e1.toString(), e1);
    } catch (Exception e1) {
      throw new PoliciesParseException(e1);
    }
  }

  public synchronized PolicyCollection getDocument(final File file) throws PoliciesParseException {
    //        cacheTotal++;
    CacheItem entry = cache.get(file);

    long checkTime = System.currentTimeMillis();
    if (null == entry || ((checkTime - entry.cacheTime) > FILE_CHECK_DELAY)) {
      final long lastmod = file.lastModified();
      if (null == entry || lastmod > entry.modTime) {
        if (!file.exists()) {
          CacheItem remove = cache.remove(file);
          entry = null;
          //                        cacheRemove++;
        } else {
          //                        cacheMiss++;
          PolicyCollection entry1 = createEntry(file);
          if (null != entry1) {
            entry = new CacheItem(entry1, lastmod);
            cache.put(file, entry);
          } else {
            cache.remove(file);
            entry = null;
          }
        }
      } else {
        //                cacheUnmodifiedHit++;
        entry.touch(checkTime);
      }
    } else {
      //            cacheHit++;
    }
    return null != entry ? entry.policyCollection : null;
  }

  public Iterator<PolicyCollection> iterator() {
    final File[] files = listDirFiles();
    return new cacheIterator(
        null != files ? Arrays.asList(files).iterator() : new ArrayList<File>().iterator());
  }

  private Map<File, Long> cooldownset = Collections.synchronizedMap(new HashMap<File, Long>());
  /**
   * Iterator over the PoliciesDocuments for the cache's files. It skips files that cannot be
   * loaded.
   */
  private class cacheIterator implements Iterator<PolicyCollection> {
    Iterator<File> intIter;
    private File nextFile;
    private PolicyCollection nextDocument;

    public cacheIterator(final Iterator<File> intIter) {
      this.intIter = intIter;
      nextFile = this.intIter.hasNext() ? this.intIter.next() : null;
      loadNextDocument();
    }

    private void loadNextDocument() {
      while (hasNextFile() && null == nextDocument) {
        File nextFile2 = getNextFile();
        Long aLong = cooldownset.get(nextFile2);
        if (null != aLong && nextFile2.lastModified() == aLong.longValue()) {
          logger.debug(
              "Skip parsing of: " + nextFile2 + ". Reason: parse error cooldown until modified");
          continue;
        } else if (null != aLong) {
          // clear
          cooldownset.remove(nextFile2);
        }
        try {
          nextDocument = getDocument(nextFile2);
        } catch (PoliciesParseException e) {
          logger.error(
              "ERROR unable to parse aclpolicy: " + nextFile2 + ". Reason: " + e.getMessage());
          logger.debug(
              "ERROR unable to parse aclpolicy: " + nextFile2 + ". Reason: " + e.getMessage(), e);
          cache.remove(nextFile2);
          cooldownset.put(nextFile2, nextFile2.lastModified());
        }
      }
    }

    private File getNextFile() {
      File next = nextFile;
      nextFile = intIter.hasNext() ? intIter.next() : null;
      return next;
    }

    private PolicyCollection getNextDocument() {
      PolicyCollection doc = nextDocument;
      nextDocument = null;
      loadNextDocument();
      return doc;
    }

    public boolean hasNextFile() {
      return null != nextFile;
    }

    public boolean hasNext() {
      return null != nextDocument;
    }

    public PolicyCollection next() {
      return getNextDocument();
    }

    public void remove() {}
  }
}
Example #3
0
/** Helper functions to run tests. */
public class Helpers implements play.mvc.Http.Status, play.mvc.Http.HeaderNames {

  public static String GET = "GET";
  public static String POST = "POST";
  public static String PUT = "PUT";
  public static String DELETE = "DELETE";
  public static String HEAD = "HEAD";

  // --

  public static Class<? extends WebDriver> HTMLUNIT = HtmlUnitDriver.class;
  public static Class<? extends WebDriver> FIREFOX = FirefoxDriver.class;

  // --
  @SuppressWarnings(value = "unchecked")
  private static Result invokeHandler(
      play.api.mvc.Handler handler, Request requestBuilder, long timeout) {
    if (handler instanceof play.api.mvc.Action) {
      play.api.mvc.Action action = (play.api.mvc.Action) handler;
      return wrapScalaResult(action.apply(requestBuilder._underlyingRequest()), timeout);
    } else if (handler instanceof JavaHandler) {
      return invokeHandler(
          ((JavaHandler) handler)
              .withComponents(
                  Play.application().injector().instanceOf(JavaHandlerComponents.class)),
          requestBuilder,
          timeout);
    } else {
      throw new RuntimeException("This is not a JavaAction and can't be invoked this way.");
    }
  }

  /**
   * Default Timeout (milliseconds) for fake requests issued by these Helpers. This value is
   * determined from System property <b>test.timeout</b>. The default value is <b>30000</b> (30
   * seconds).
   */
  public static final long DEFAULT_TIMEOUT = Long.getLong("test.timeout", 30000L);

  private static Result wrapScalaResult(
      scala.concurrent.Future<play.api.mvc.Result> result, long timeout) {
    if (result == null) {
      return null;
    } else {
      final play.api.mvc.Result scalaResult = Promise.wrap(result).get(timeout);
      return scalaResult.asJava();
    }
  }

  // --

  /** Calls a Callable which invokes a Controller or some other method with a Context */
  public static <V> V invokeWithContext(RequestBuilder requestBuilder, Callable<V> callable) {
    try {
      Context.current.set(new Context(requestBuilder));
      return callable.call();
    } catch (Exception e) {
      throw new RuntimeException(e);
    } finally {
      Context.current.remove();
    }
  }

  /** Build a new GET / fake request. */
  public static RequestBuilder fakeRequest() {
    return fakeRequest("GET", "/");
  }

  /** Build a new fake request. */
  public static RequestBuilder fakeRequest(String method, String uri) {
    return new RequestBuilder().method(method).uri(uri);
  }

  /** Build a new fake request corresponding to a given route call */
  public static RequestBuilder fakeRequest(Call call) {
    return fakeRequest(call.method(), call.url());
  }

  /** Build a new fake application. */
  public static FakeApplication fakeApplication() {
    return new FakeApplication(
        new java.io.File("."), Helpers.class.getClassLoader(), new HashMap<String, Object>(), null);
  }

  /**
   * Build a new fake application.
   *
   * @deprecated Use dependency injection (since 2.5.0)
   */
  @Deprecated
  public static FakeApplication fakeApplication(GlobalSettings global) {
    return new FakeApplication(
        new java.io.File("."),
        Helpers.class.getClassLoader(),
        new HashMap<String, Object>(),
        global);
  }

  /**
   * A fake Global.
   *
   * @deprecated Use dependency injection (since 2.5.0)
   */
  @Deprecated
  public static GlobalSettings fakeGlobal() {
    return new GlobalSettings();
  }

  /** Constructs a in-memory (h2) database configuration to add to a FakeApplication. */
  public static Map<String, String> inMemoryDatabase() {
    return inMemoryDatabase("default");
  }

  /** Constructs a in-memory (h2) database configuration to add to a FakeApplication. */
  public static Map<String, String> inMemoryDatabase(String name) {
    return inMemoryDatabase(name, Collections.<String, String>emptyMap());
  }

  /** Constructs a in-memory (h2) database configuration to add to a FakeApplication. */
  public static Map<String, String> inMemoryDatabase(String name, Map<String, String> options) {
    return Scala.asJava(play.api.test.Helpers.inMemoryDatabase(name, Scala.asScala(options)));
  }

  /** Build a new fake application. */
  public static FakeApplication fakeApplication(
      Map<String, ? extends Object> additionalConfiguration) {
    return new FakeApplication(
        new java.io.File("."), Helpers.class.getClassLoader(), additionalConfiguration);
  }

  /**
   * Build a new fake application.
   *
   * @deprecated Use the version without GlobalSettings (since 2.5.0)
   */
  @Deprecated
  public static FakeApplication fakeApplication(
      Map<String, ? extends Object> additionalConfiguration, GlobalSettings global) {
    return new FakeApplication(
        new java.io.File("."), Helpers.class.getClassLoader(), additionalConfiguration, global);
  }

  /**
   * Extracts the content as a {@link akka.util.ByteString}.
   *
   * <p>This method is only capable of extracting the content of results with strict entities. To
   * extract the content of results with streamed entities, use {@link #contentAsBytes(Result,
   * Materializer)}.
   *
   * @param result The result to extract the content from.
   * @return The content of the result as a ByteString.
   * @throws UnsupportedOperationException if the result does not have a strict entity.
   */
  public static ByteString contentAsBytes(Result result) {
    if (result.body() instanceof HttpEntity.Strict) {
      return ((HttpEntity.Strict) result.body()).data();
    } else {
      throw new UnsupportedOperationException(
          "Tried to extract body from a non strict HTTP entity without a materializer, use the version of this method that accepts a materializer instead");
    }
  }

  /**
   * Extracts the content as a {@link akka.util.ByteString}.
   *
   * @param result The result to extract the content from.
   * @param mat The materialiser to use to extract the body from the result stream.
   * @return The content of the result as a ByteString.
   */
  public static ByteString contentAsBytes(Result result, Materializer mat) {
    return contentAsBytes(result, mat, DEFAULT_TIMEOUT);
  }

  /**
   * Extracts the content as a {@link akka.util.ByteString}.
   *
   * @param result The result to extract the content from.
   * @param mat The materialiser to use to extract the body from the result stream.
   * @param timeout The amount of time, in milliseconds, to wait for the body to be produced.
   * @return The content of the result as a ByteString.
   */
  public static ByteString contentAsBytes(Result result, Materializer mat, long timeout) {
    try {
      return result
          .body()
          .consumeData(mat)
          .thenApply(Function.identity())
          .toCompletableFuture()
          .get(timeout, TimeUnit.MILLISECONDS);
    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /** Extracts the content as bytes. */
  public static ByteString contentAsBytes(Content content) {
    return ByteString.fromString(content.body());
  }

  /** Extracts the content as a String. */
  public static String contentAsString(Content content) {
    return content.body();
  }

  /**
   * Extracts the content as a String.
   *
   * <p>This method is only capable of extracting the content of results with strict entities. To
   * extract the content of results with streamed entities, use {@link #contentAsString(Result,
   * Materializer)}.
   *
   * @param result The result to extract the content from.
   * @return The content of the result as a String.
   * @throws UnsupportedOperationException if the result does not have a strict entity.
   */
  public static String contentAsString(Result result) {
    return contentAsBytes(result).decodeString(result.charset().orElse("utf-8"));
  }

  /**
   * Extracts the content as a String.
   *
   * @param result The result to extract the content from.
   * @param mat The materialiser to use to extract the body from the result stream.
   * @return The content of the result as a String.
   */
  public static String contentAsString(Result result, Materializer mat) {
    return contentAsBytes(result, mat, DEFAULT_TIMEOUT)
        .decodeString(result.charset().orElse("utf-8"));
  }

  /**
   * Extracts the content as a String.
   *
   * @param result The result to extract the content from.
   * @param mat The materialiser to use to extract the body from the result stream.
   * @param timeout The amount of time, in milliseconds, to wait for the body to be produced.
   * @return The content of the result as a String.
   */
  public static String contentAsString(Result result, Materializer mat, long timeout) {
    return contentAsBytes(result, mat, timeout).decodeString(result.charset().orElse("utf-8"));
  }

  @SuppressWarnings(value = "unchecked")
  public static Result routeAndCall(RequestBuilder requestBuilder, long timeout) {
    try {
      return routeAndCall(
          (Class<? extends Router>) RequestBuilder.class.getClassLoader().loadClass("Routes"),
          requestBuilder,
          timeout);
    } catch (RuntimeException e) {
      throw e;
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
  }

  public static Result routeAndCall(
      Class<? extends Router> router, RequestBuilder requestBuilder, long timeout) {
    try {
      Request request = requestBuilder.build();
      Router routes =
          (Router)
              router
                  .getClassLoader()
                  .loadClass(router.getName() + "$")
                  .getDeclaredField("MODULE$")
                  .get(null);
      if (routes.routes().isDefinedAt(request._underlyingRequest())) {
        return invokeHandler(routes.routes().apply(request._underlyingRequest()), request, timeout);
      } else {
        return null;
      }
    } catch (RuntimeException e) {
      throw e;
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
  }

  public static Result routeAndCall(Router router, RequestBuilder requestBuilder) {
    return routeAndCall(router, requestBuilder, DEFAULT_TIMEOUT);
  }

  public static Result routeAndCall(Router router, RequestBuilder requestBuilder, long timeout) {
    try {
      Request request = requestBuilder.build();
      if (router.routes().isDefinedAt(request._underlyingRequest())) {
        return invokeHandler(router.routes().apply(request._underlyingRequest()), request, timeout);
      } else {
        return null;
      }
    } catch (RuntimeException e) {
      throw e;
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
  }

  public static Result route(Call call) {
    return route(fakeRequest(call));
  }

  public static Result route(Call call, long timeout) {
    return route(fakeRequest(call), timeout);
  }

  public static Result route(Application app, Call call) {
    return route(app, fakeRequest(call));
  }

  public static Result route(Application app, Call call, long timeout) {
    return route(app, fakeRequest(call), timeout);
  }

  public static Result route(RequestBuilder requestBuilder) {
    return route(requestBuilder, DEFAULT_TIMEOUT);
  }

  public static Result route(RequestBuilder requestBuilder, long timeout) {
    return route(play.Play.application(), requestBuilder, timeout);
  }

  public static Result route(Application app, RequestBuilder requestBuilder) {
    return route(app, requestBuilder, DEFAULT_TIMEOUT);
  }

  @SuppressWarnings("unchecked")
  public static Result route(Application app, RequestBuilder requestBuilder, long timeout) {
    final scala.Option<scala.concurrent.Future<play.api.mvc.Result>> opt =
        play.api.test.Helpers.jRoute(
            app.getWrappedApplication(),
            requestBuilder.build()._underlyingRequest(),
            requestBuilder.body());
    return wrapScalaResult(Scala.orNull(opt), timeout);
  }

  /** Starts a new application. */
  public static void start(Application application) {
    play.api.Play.start(application.getWrappedApplication());
  }

  /** Stops an application. */
  public static void stop(Application application) {
    play.api.Play.stop(application.getWrappedApplication());
  }

  /** Executes a block of code in a running application. */
  public static void running(Application application, final Runnable block) {
    synchronized (PlayRunners$.MODULE$.mutex()) {
      try {
        start(application);
        block.run();
      } finally {
        stop(application);
      }
    }
  }

  /**
   * Creates a new Test server listening on port defined by configuration setting "testserver.port"
   * (defaults to 19001).
   */
  public static TestServer testServer() {
    return testServer(play.api.test.Helpers.testServerPort());
  }

  /**
   * Creates a new Test server listening on port defined by configuration setting "testserver.port"
   * (defaults to 19001) and using the given FakeApplication.
   */
  public static TestServer testServer(Application app) {
    return testServer(play.api.test.Helpers.testServerPort(), app);
  }

  /** Creates a new Test server. */
  public static TestServer testServer(int port) {
    return new TestServer(port, fakeApplication());
  }

  /** Creates a new Test server. */
  public static TestServer testServer(int port, Application app) {
    return new TestServer(port, app);
  }

  /** Starts a Test server. */
  public static void start(TestServer server) {
    server.start();
  }

  /** Stops a Test server. */
  public static void stop(TestServer server) {
    server.stop();
  }

  /** Executes a block of code in a running server. */
  public static void running(TestServer server, final Runnable block) {
    synchronized (PlayRunners$.MODULE$.mutex()) {
      try {
        start(server);
        block.run();
      } finally {
        stop(server);
      }
    }
  }

  /** Executes a block of code in a running server, with a test browser. */
  public static void running(
      TestServer server, Class<? extends WebDriver> webDriver, final Consumer<TestBrowser> block) {
    running(server, play.api.test.WebDriverFactory.apply(webDriver), block);
  }

  /** Executes a block of code in a running server, with a test browser. */
  public static void running(
      TestServer server, WebDriver webDriver, final Consumer<TestBrowser> block) {
    synchronized (PlayRunners$.MODULE$.mutex()) {
      TestBrowser browser = null;
      TestServer startedServer = null;
      try {
        start(server);
        startedServer = server;
        browser = testBrowser(webDriver, server.port());
        block.accept(browser);
      } finally {
        if (browser != null) {
          browser.quit();
        }
        if (startedServer != null) {
          stop(startedServer);
        }
      }
    }
  }

  /** Creates a Test Browser. */
  public static TestBrowser testBrowser() {
    return testBrowser(HTMLUNIT);
  }

  /** Creates a Test Browser. */
  public static TestBrowser testBrowser(int port) {
    return testBrowser(HTMLUNIT, port);
  }

  /** Creates a Test Browser. */
  public static TestBrowser testBrowser(Class<? extends WebDriver> webDriver) {
    return testBrowser(webDriver, Helpers$.MODULE$.testServerPort());
  }

  /** Creates a Test Browser. */
  public static TestBrowser testBrowser(Class<? extends WebDriver> webDriver, int port) {
    try {
      return new TestBrowser(webDriver, "http://localhost:" + port);
    } catch (RuntimeException e) {
      throw e;
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
  }

  /** Creates a Test Browser. */
  public static TestBrowser testBrowser(WebDriver of, int port) {
    return new TestBrowser(of, "http://localhost:" + port);
  }

  /** Creates a Test Browser. */
  public static TestBrowser testBrowser(WebDriver of) {
    return testBrowser(of, Helpers$.MODULE$.testServerPort());
  }
}
public class SGTestNGListener extends TestListenerAdapter {

  protected String testMethodName;
  protected String suiteName;
  protected String buildNumber;
  protected String version;
  protected ScriptingContainer container;
  private Process process = null;
  private Process process2 = null;
  private String logstashLogPath;
  private String logstashLogPath2;
  private static final boolean enableLogstash =
      Boolean.parseBoolean(System.getProperty("iTests.enableLogstash", "false"));
  protected static final String CREDENTIALS_FOLDER =
      System.getProperty(
          "iTests.credentialsFolder",
          SGTestHelper.getSGTestRootDir() + "/src/main/resources/credentials");
  private static File propsFile = new File(CREDENTIALS_FOLDER + "/logstash/logstash.properties");
  protected String logstashHost;

  private static long testInvocationCounter = 1;
  private static final long maxCount = Long.getLong("sgtest.webui.numberOfTestRetries", 3) + 1;

  public SGTestNGListener() {
    if (enableLogstash) {
      LogUtils.log("in SGTestNGListener constructor");
    }
  }

  @Override
  public void onStart(ITestContext iTestContext) {
    suiteName = System.getProperty("iTests.suiteName", "sgtest");
    LogUtils.log("suite number is now (on start) - " + suiteName);

    if (enableLogstash) {
      buildNumber = System.getProperty("iTests.buildNumber");
      LogUtils.log("build number is now (on start) - " + buildNumber);
      version = System.getProperty("cloudifyVersion");
    }
  }

  private void initLogstash2(ITestResult tr) {

    initLogstashHost();

    String simpleClassName = tr.getTestClass().getRealClass().getSimpleName();
    String pathToLogstash =
        SGTestHelper.getSGTestRootDir().replace("\\", "/") + "/src/main/resources/logstash";
    String confFilePath2 = pathToLogstash + "/logstash-shipper-client-2.conf";
    String backupFilePath2 =
        pathToLogstash + "/logstash-shipper-client-2-" + simpleClassName + ".conf";
    File backupFile2 = new File(backupFilePath2);

    LogUtils.log(
        "trying to start logstash agent number 2. simple class name is " + simpleClassName);
    if (backupFile2.exists()) {
      LogUtils.log("the file " + backupFilePath2 + " already exists. not starting logstash");
    }

    if (!isAfter(tr) && !backupFile2.exists()) {

      try {
        //                backupFilePath2 = IOUtils.backupFile(confFilePath2);
        LogUtils.log("copying file " + confFilePath2 + " to " + backupFilePath2);
        IOUtils.copyFile(confFilePath2, backupFilePath2);
        IOUtils.replaceTextInFile(backupFilePath2, "<path_to_build>", SGTestHelper.getBuildDir());
        IOUtils.replaceTextInFile(
            backupFilePath2,
            "<suite_number>",
            "suite_" + System.getProperty("iTests.suiteId", "0"));
        IOUtils.replaceTextInFile(
            backupFilePath2,
            "<path_to_test_class_folder>",
            SGTestHelper.getSGTestRootDir().replace("\\", "/")
                + "/../"
                + suiteName
                + "/"
                + tr.getTestClass().getName());
        IOUtils.replaceTextInFile(backupFilePath2, "<suite_name>", suiteName);
        IOUtils.replaceTextInFile(backupFilePath2, "<test_name>", simpleClassName);
        IOUtils.replaceTextInFile(backupFilePath2, "<build_number>", buildNumber);
        IOUtils.replaceTextInFile(backupFilePath2, "<version>", version);
        IOUtils.replaceTextInFile(backupFilePath2, "<host>", logstashHost);

        String logstashJarPath =
            DeploymentUtils.getLocalRepository() + "net/logstash/1.2.2/logstash-1.2.2.jar";
        logstashLogPath2 = pathToLogstash + "/logstash-" + simpleClassName + "-2.txt";
        String cmdLine =
            "java -jar "
                + logstashJarPath
                + " agent -f "
                + backupFilePath2
                + " -l "
                + logstashLogPath2;

        final String[] parts = cmdLine.split(" ");
        final ProcessBuilder pb = new ProcessBuilder(parts);
        LogUtils.log("Executing Command line: " + cmdLine);

        process2 = pb.start();

      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  private void initLogstash(String testName) {

    initLogstashHost();

    String pathToLogstash =
        SGTestHelper.getSGTestRootDir().replace("\\", "/") + "/src/main/resources/logstash";
    String confFilePath = pathToLogstash + "/logstash-shipper-client.conf";
    String fixedTestName = testName.substring(0, testName.length() - 2);
    String backupFilePath = pathToLogstash + "/logstash-shipper-client-" + fixedTestName + ".conf";

    if (process == null) {

      try {

        LogUtils.log("copying file " + confFilePath + " to " + backupFilePath);
        IOUtils.copyFile(confFilePath, backupFilePath);
        //                backupFilePath = IOUtils.backupFile(confFilePath);
        IOUtils.replaceTextInFile(
            backupFilePath,
            "<path_to_test_folder>",
            SGTestHelper.getSGTestRootDir().replace("\\", "/")
                + "/../"
                + suiteName
                + "/"
                + testName);
        IOUtils.replaceTextInFile(backupFilePath, "<suite_name>", suiteName);
        IOUtils.replaceTextInFile(backupFilePath, "<test_name>", testName);
        IOUtils.replaceTextInFile(backupFilePath, "<build_number>", buildNumber);
        IOUtils.replaceTextInFile(backupFilePath, "<version>", version);
        IOUtils.replaceTextInFile(backupFilePath, "<host>", logstashHost);

        String logstashJarPath =
            DeploymentUtils.getLocalRepository() + "net/logstash/1.2.2/logstash-1.2.2.jar";
        logstashLogPath = pathToLogstash + "/logstash-" + fixedTestName + ".txt";
        String cmdLine =
            "java -jar "
                + logstashJarPath
                + " agent -f "
                + backupFilePath
                + " -l "
                + logstashLogPath;

        final String[] parts = cmdLine.split(" ");
        final ProcessBuilder pb = new ProcessBuilder(parts);
        LogUtils.log("Executing Command line: " + cmdLine);

        TimeUnit.SECONDS.sleep(1);
        process = pb.start();

      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  @Override
  public void beforeConfiguration(ITestResult tr) {
    if (enableLogstash) {
      super.beforeConfiguration(tr);
      if (suiteName
          == null) { // this is in case the suite has a @BeforeSuite method. which is invoked before
                     // the onStart is.
        suiteName = System.getProperty("iTests.suiteName", "sgtest");
        buildNumber = System.getProperty("iTests.buildNumber");
        LogUtils.log("build number is now - " + buildNumber);
        version = System.getProperty("cloudifyVersion");
      }
    }
  }

  @Override
  public void onConfigurationSuccess(ITestResult iTestResult) {
    super.onConfigurationSuccess(iTestResult);
    String testName = iTestResult.getTestClass().getName();
    String configurationName = iTestResult.getMethod().toString().split("\\(|\\)")[0];
    if (isAfter(iTestResult) && !enableLogstash) {
      DumpUtils.copyBeforeConfigurationsLogToTestDir(testName, suiteName);
      testName = testMethodName;
    }
    if (suiteName
        == null) { // this is in case the suite has a @BeforeSuite method. which is invoked before
                   // the onStart is.
      suiteName = System.getProperty("iTests.suiteName", "sgtest");
    }
    LogUtils.log("Configuration Succeeded: " + configurationName);

    if (enableLogstash && iTestResult.getMethod().isBeforeClassConfiguration()) {
      initLogstash2(iTestResult);
    }

    String newmanTestFolder = System.getProperty("newman.test.path");
    if (newmanTestFolder != null) {
      File testFolder = new File(newmanTestFolder);
      ZipUtils.unzipArchive(testFolder);
      try {
        copyAllFilesToLogDir(testFolder, testFolder);
      } catch (IOException e) {
        LogUtils.log("Failed to copy all log files - caught " + e, e);
      }
    } else {
      ZipUtils.unzipArchive(testMethodName, suiteName);
    }

    if (enableLogstash
        && isAfter(iTestResult)
        && !iTestResult.getMethod().isAfterClassConfiguration()
        && !iTestResult.getMethod().isAfterSuiteConfiguration()) {
      testName = testMethodName;
    }

    write2LogFile(iTestResult, DumpUtils.createTestFolder(testName, suiteName));

    if (isAfter(iTestResult)) {
      if (enableLogstash) {
        if (process != null) {
          killLogstashAgent(1, logstashLogPath);
        }
        if (process2 != null && iTestResult.getMethod().isAfterClassConfiguration()) {
          killLogstashAgent(2, logstashLogPath2);
        }
      }
    }
  }

  @Override
  public void onConfigurationFailure(ITestResult iTestResult) {
    super.onConfigurationFailure(iTestResult);
    String testName = iTestResult.getTestClass().getName();
    String configurationName = iTestResult.getMethod().toString().split("\\(|\\)")[0];
    if (!enableLogstash && isAfter(iTestResult)) {
      DumpUtils.copyBeforeConfigurationsLogToTestDir(testName, suiteName);
      testName = testMethodName;
    }
    if (suiteName
        == null) { // this is in case the suite has a @BeforeSuite method. which is invoked before
                   // the onStart is.
      suiteName = System.getProperty("iTests.suiteName", "sgtest");
    }
    LogUtils.log("Configuration Failed: " + configurationName, iTestResult.getThrowable());

    if (enableLogstash && iTestResult.getMethod().isBeforeClassConfiguration()) {
      initLogstash2(iTestResult);
    }

    String newmanTestFolder = System.getProperty("newman.test.path");
    if (newmanTestFolder != null) {
      File testFolder = new File(newmanTestFolder);
      ZipUtils.unzipArchive(testFolder);
      try {
        copyAllFilesToLogDir(testFolder, testFolder);
      } catch (IOException e) {
        LogUtils.log("Failed to copy all log files");
      }
    } else {
      ZipUtils.unzipArchive(testMethodName, suiteName);
    }

    if (enableLogstash
        && isAfter(iTestResult)
        && !iTestResult.getMethod().isAfterClassConfiguration()
        && !iTestResult.getMethod().isAfterSuiteConfiguration()) {
      testName = testMethodName;
    }
    File testFolder = DumpUtils.createTestFolder(testName, suiteName);
    write2LogFile(iTestResult, testFolder);
    write2ErrorTxtFile(iTestResult, testFolder);

    if (isAfter(iTestResult)) {
      if (enableLogstash) {
        if (process != null) {
          killLogstashAgent(1, logstashLogPath);
        }
        if (process2 != null && iTestResult.getMethod().isAfterClassConfiguration()) {
          killLogstashAgent(2, logstashLogPath2);
        }
      }
    }
  }

  @Override
  public void onConfigurationSkip(ITestResult iTestResult) {
    super.onConfigurationFailure(iTestResult);
    String testName = iTestResult.getTestClass().getName();
    String configurationName = iTestResult.getMethod().toString().split("\\(|\\)")[0];
    if (!enableLogstash && isAfter(iTestResult)) {
      DumpUtils.copyBeforeConfigurationsLogToTestDir(testName, suiteName);
      testName = testMethodName;
    }
    LogUtils.log("Configuration Skipped: " + configurationName, iTestResult.getThrowable());

    if (enableLogstash && iTestResult.getMethod().isBeforeClassConfiguration()) {
      initLogstash2(iTestResult);
    }

    String newmanTestFolder = System.getProperty("newman.test.path");
    if (newmanTestFolder != null) {
      File testFolder = new File(newmanTestFolder);
      ZipUtils.unzipArchive(testFolder);
      try {
        copyAllFilesToLogDir(testFolder, testFolder);
      } catch (IOException e) {
        LogUtils.log("Failed to copy all log files");
      }
    } else {
      ZipUtils.unzipArchive(testMethodName, suiteName);
    }

    if (enableLogstash
        && isAfter(iTestResult)
        && !iTestResult.getMethod().isAfterClassConfiguration()
        && !iTestResult.getMethod().isAfterSuiteConfiguration()) {
      testName = testMethodName;
    }
    write2LogFile(iTestResult, DumpUtils.createTestFolder(testName, suiteName));

    if (isAfter(iTestResult)) {
      if (enableLogstash) {
        if (process != null) {
          killLogstashAgent(1, logstashLogPath);
        }
        if (process2 != null && iTestResult.getMethod().isAfterClassConfiguration()) {
          killLogstashAgent(2, logstashLogPath2);
        }
      }
    }
  }

  @Override
  public void onTestStart(ITestResult iTestResult) {
    super.onTestStart(iTestResult);
    String testName = TestNGUtils.constructTestMethodName(iTestResult);
    LogUtils.log("Test Start: " + testName);
    if (enableLogstash) {
      initLogstash(testName);
    }
  }

  @Override
  public void onTestFailure(ITestResult iTestResult) {
    if (suiteName.toLowerCase().contains("webui")) {
      if (testInvocationCounter < maxCount) {
        testInvocationCounter++;
        iTestResult.setAttribute("retry", true);
      } else {
        LogUtils.log("Number of retries expired.");
        iTestResult.setStatus(ITestResult.FAILURE);
        // reset count
        testInvocationCounter = 1;
        testMethodName = TestNGUtils.constructTestMethodName(iTestResult);
        LogUtils.log("Test Failed: " + testMethodName, iTestResult.getThrowable());
        File testFolder = DumpUtils.createTestFolder(testMethodName, suiteName);
        write2LogFile(iTestResult, testFolder);
        write2ErrorTxtFile(iTestResult, testFolder);
      }
    } else {
      testMethodName = TestNGUtils.constructTestMethodName(iTestResult);
      LogUtils.log("Test Failed: " + testMethodName, iTestResult.getThrowable());
      File testFolder = DumpUtils.createTestFolder(testMethodName, suiteName);
      write2LogFile(iTestResult, testFolder);
      write2ErrorTxtFile(iTestResult, testFolder);
    }
    super.onTestFailure(iTestResult);
  }

  @Override
  public void onTestSkipped(ITestResult iTestResult) {
    super.onTestSkipped(iTestResult);
    testMethodName = TestNGUtils.constructTestMethodName(iTestResult);
    LogUtils.log("Test Skipped: " + testMethodName, iTestResult.getThrowable());
    write2LogFile(iTestResult, DumpUtils.createTestFolder(testMethodName, suiteName));
  }

  @Override
  public void onTestSuccess(ITestResult iTestResult) {
    super.onTestSuccess(iTestResult);
    if (suiteName.toLowerCase().contains("webui")) {
      testInvocationCounter = 1;
    }
    testMethodName = TestNGUtils.constructTestMethodName(iTestResult);
    LogUtils.log("Test Passed: " + testMethodName);
    write2LogFile(iTestResult, DumpUtils.createTestFolder(testMethodName, suiteName));
  }

  @Override
  public void onFinish(ITestContext testContext) {
    super.onFinish(testContext);
    if (suiteName == null) suiteName = System.getProperty("iTests.suiteName", "sgtest");
    LogUtils.log("Finishing Suite: " + suiteName.toLowerCase());
    if (suiteName.toLowerCase().contains("webui")) {
      onFinishWebUITests(testContext);
    }
    try {
      SGTestNGReporter.reset();
    } catch (Exception e) {
      // ignore
    }
  }

  private void onFinishWebUITests(ITestContext testContext) {
    // List of test results which we will delete later because of duplication or because the test
    // eventually passed
    List<ITestResult> testsToBeRemoved = new ArrayList<ITestResult>();

    // collect all id's from passed test
    Set<Integer> passedTestIds = new HashSet<Integer>();
    for (ITestResult passedTest : testContext.getPassedTests().getAllResults()) {
      passedTestIds.add(getTestId(passedTest));
    }

    Set<Integer> failedTestIds = new HashSet<Integer>();
    for (ITestResult failedTest : testContext.getFailedTests().getAllResults()) {

      int failedTestId = getTestId(failedTest);
      // if this test failed before mark as to be deleted
      // or delete this failed test if there is at least one passed version
      if (failedTestIds.contains(failedTestId) || passedTestIds.contains(failedTestId)) {
        testsToBeRemoved.add(failedTest);
      } else {
        failedTestIds.add(failedTestId);
      }
    }
    // finally delete all tests that are marked
    for (Iterator<ITestResult> iterator = testContext.getFailedTests().getAllResults().iterator();
        iterator.hasNext(); ) {
      ITestResult testResult = iterator.next();
      if (testsToBeRemoved.contains(testResult)) {
        iterator.remove();
      }
    }
  }

  // returns an ID for each test result
  private int getTestId(ITestResult result) {
    int id = result.getTestClass().getName().hashCode();
    id = 31 * id + result.getMethod().getMethodName().hashCode();
    id = 31 * id + (result.getParameters() != null ? Arrays.hashCode(result.getParameters()) : 0);
    return id;
  }

  private void write2LogFile(ITestResult iTestResult, File testFolder) {
    BufferedWriter out = null;
    try {
      if (testFolder == null) {
        LogUtils.log("Can not write to file test folder is null");
        return;
      }
      String output = SGTestNGReporter.getOutput();
      if (StringUtils.isEmpty(output)) {
        LogUtils.log("nothing to write to log file");
        return;
      }
      String parameters = TestNGUtils.extractParameters(iTestResult);
      File testLogFile =
          new File(
              testFolder.getAbsolutePath()
                  + "/"
                  + iTestResult.getName()
                  + "("
                  + parameters
                  + ").log");
      if (!testLogFile.createNewFile()) {
        LogUtils.log(
            "Failed to create log file ["
                + testLogFile
                + "];\n log output: "
                + Reporter.getOutput());
        return;
      }
      FileWriter fstream = new FileWriter(testLogFile);
      out = new BufferedWriter(fstream);
      out.write(output);
    } catch (Exception e) {
      LogUtils.log("Failed to write to log file result - " + iTestResult, e);
    } finally {
      SGTestNGReporter.reset();
      if (out != null) {
        try {
          out.close();
        } catch (IOException e) {
          LogUtils.log("Failed closing stream", e);
          // ignore
        }
      }
    }
  }

  private void write2ErrorTxtFile(ITestResult iTestResult, File testFolder) {
    if (testFolder == null) {
      LogUtils.log("Can not write error.txt - test folder is null");
      return;
    }
    //noinspection ThrowableResultOfMethodCallIgnored
    if (iTestResult.getThrowable() == null) {
      LogUtils.log("nothing to write to error.txt - throwable is null");
      return;
    }
    // trim if too long
    //noinspection ThrowableResultOfMethodCallIgnored
    String errorMsg = iTestResult.getThrowable().toString();
    if (errorMsg.length() > 120) {
      errorMsg = errorMsg.substring(0, 120 - 3) + "...";
    }

    File errorTxtFile = new File(testFolder.getAbsolutePath(), "error.txt");
    PrintWriter out = null;
    try {
      out = new PrintWriter(new BufferedWriter(new FileWriter(errorTxtFile, true)));
      out.println(errorMsg);
    } catch (IOException ioe) {
      LogUtils.log("Failed to write contents into error.txt file", ioe);
    } finally {
      if (out != null) {
        out.close();
      }
    }
  }

  private boolean isAfter(ITestResult iTestResult) {
    ITestNGMethod method = iTestResult.getMethod();
    return (method.isAfterClassConfiguration()
        || method.isAfterMethodConfiguration()
        || method.isAfterSuiteConfiguration()
        || method.isAfterTestConfiguration());
  }

  private void killLogstashAgent(int logAgentNumber, String logstashLogPath) {

    FileObject listendir;
    CustomFileListener listener = new CustomFileListener();
    long TIMEOUT_BETWEEN_FILE_QUERYING = 1000;
    long LOOP_TIMEOUT_IN_MILLIS = 10 * 1000;

    try {
      FileSystemManager fileSystemManager = VFS.getManager();
      listendir = fileSystemManager.resolveFile(logstashLogPath);
    } catch (FileSystemException e) {
      e.printStackTrace();
      return;
    }

    DefaultFileMonitor fm = new DefaultFileMonitor(listener);
    fm.setRecursive(true);
    fm.addFile(listendir);
    fm.setDelay(TIMEOUT_BETWEEN_FILE_QUERYING);
    fm.start();

    LogUtils.log("waiting to destroy logger");
    long startTimeMillis = System.currentTimeMillis();

    while (true) {

      if (!listener.isProcessUp()) {
        break;
      }

      listener.setProcessUp(false);

      try {
        TimeUnit.MILLISECONDS.sleep(LOOP_TIMEOUT_IN_MILLIS);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }

    long endTimeMillis = System.currentTimeMillis();
    LogUtils.log("destroying logstash agent " + logAgentNumber);
    LogUtils.log("waited " + (endTimeMillis - startTimeMillis) / 1000 + " seconds");
    fm.stop();

    //        File logstashOutputFile = new File(logstashLogPath);

    if (logAgentNumber == 1) {

      process.destroy();
      process = null;
    } else {

      process2.destroy();
      process2 = null;
    }

    //        try {
    //            TimeUnit.SECONDS.sleep(5);
    //
    //            LogUtils.log("returning logstash config file to initial state");
    //            if(logAgentNumber == 1){
    //                IOUtils.replaceFileWithMove(new File(confFilePath), new File(backupFilePath));
    //                FileUtils.deleteQuietly(new File(backupFilePath));
    //            }
    //            else{
    //                IOUtils.replaceFileWithMove(new File(confFilePath2), new
    // File(backupFilePath2));
    //                FileUtils.deleteQuietly(new File(backupFilePath2));
    //
    //            }
    //        } catch (IOException e) {
    //            e.printStackTrace();
    //        } catch (InterruptedException e) {
    //            e.printStackTrace();
    //        }

    //        if(logstashOutputFile.exists()){
    //            FileUtils.deleteQuietly(logstashOutputFile);
    //        }
  }

  private void initLogstashHost() {

    if (logstashHost != null) {
      return;
    }

    Properties props;
    try {
      props = IOUtils.readPropertiesFromFile(propsFile);
    } catch (final Exception e) {
      throw new IllegalStateException("Failed reading properties file : " + e.getMessage());
    }
    logstashHost = props.getProperty("logstash_server_host");
  }

  private void copyAllFilesToLogDir(File node, File parent) throws IOException {
    if (!node.getAbsoluteFile().equals(parent.getAbsoluteFile())
        && node.isFile()
        && !node.getParentFile().equals(parent)) {
      String fileNamePrefix = node.getName().substring(0, node.getName().lastIndexOf('.'));
      String fileNameSuffix = node.getName().substring(node.getName().lastIndexOf('.'));
      String newFilePath =
          node.getParentFile().getAbsolutePath()
              + File.separator
              + fileNamePrefix.replace(".", "_")
              + fileNameSuffix;

      File newNode = new File(newFilePath);
      if (node.renameTo(newNode)) {
        FileUtils.copyFileToDirectory(newNode, parent);
      }
    }
    if (node.isDirectory()) {
      String[] subNote = node.list();
      for (String filename : subNote) {
        copyAllFilesToLogDir(new File(node, filename), parent);
      }
      if (!node.equals(parent)) {
        FileUtils.deleteDirectory(node);
      }
    }
  }
}
/**
 * Test suite for distributing JUnit3 tests. Simply add tests to this suite just like you would for
 * regular JUnit3 suites, and these tests will be executed in parallel on the grid. Note that if
 * there are no other grid nodes, this suite will still ensure parallel test execution within single
 * JVM.
 *
 * <p>Below is an example of distributed JUnit3 test suite:
 *
 * <pre name="code" class="java">
 * public class GridJunit3ExampleTestSuite {
 *     // Standard JUnit3 static suite method.
 *     public static TestSuite suite() {
 *         TestSuite suite = new GridJunit3TestSuite("Example Grid Test Suite");
 *
 *         // Add tests.
 *         suite.addTestSuite(TestA.class);
 *         suite.addTestSuite(TestB.class);
 *         suite.addTestSuite(TestC.class);
 *
 *         return suite;
 *     }
 * }
 * </pre>
 *
 * If you have four tests A, B, C, and D, and if you need to run A and B sequentially, then you
 * should create a nested test suite with test A and B as follows:
 *
 * <pre name="code" class="java">
 * public class GridJunit3ExampleTestSuite {
 *     // Standard JUnit3 static suite method.
 *     public static TestSuite suite() {
 *         TestSuite suite = new GridJunit3TestSuite("Example Grid Test Suite");
 *
 *         // Nested test suite to run tests A and B sequentially.
 *         TestSuite nested = new TestSuite("Example Nested Sequential Suite");
 *
 *         nested.addTestSuite(TestA.class);
 *         nested.addTestSuite(TestB.class);
 *
 *         // Add tests A and B.
 *         suite.addTest(nested);
 *
 *         // Add other tests.
 *         suite.addTestSuite(TestC.class);
 *
 *         return suite;
 *     }
 * }
 * </pre>
 *
 * <p>Note that you can also grid-enable existing JUnit3 tests using {@link
 * GridifyTest @GridifyTest} annotation which you can attach to your {@code suite()} methods of
 * existing test suite. Refer to {@link GridifyTest @GridifyTest} documentation for more
 * information.
 *
 * <p>Also note that some tests can only be executed locally mostly due to some environment issues.
 * However they still can benefit from parallel execution with other tests. GridGain supports it via
 * {@link GridJunit3LocalTestSuite} suites that can be added to {@code GridJunit3TestSuite}. Refer
 * to {@link GridJunit3LocalTestSuite} documentation for more information.
 *
 * <h1 class="header">Logging</h1>
 *
 * When running distributed JUnit, all the logging that is done to {@link System#out} or {@link
 * System#err} is preserved. GridGain will accumulate all logging that is done on remote nodes, send
 * them back to originating node and associate all log statements with their corresponding tests.
 * This way, for example, if you are running tests from and IDEA or Eclipse (or any other IDE) you
 * would still see the logs as if it was a local run. However, since remote nodes keep all log
 * statements done within a single individual test case in memory, you must make sure that enough
 * memory is allocated on every node and that individual test cases do not spit out gigabytes of log
 * statements. Also note, that logs will be sent back to originating node upon completion of every
 * test, so don't be alarmed if you don't see any log statements for a while and then all of them
 * appear at once.
 *
 * <p>GridGain achieves such log transparency via reassigning {@link System#out} or {@link
 * System#err} to internal {@link PrintStream} implementation. However, when using {@code Log4J} (or
 * any other logging framework) within your tests you must make sure that it is configured with
 * {@link ConsoleAppender} and that {@link ConsoleAppender#setFollow(boolean)} attribute is set to
 * {@code true}. Logging to files is not supported yet and is planned for next point release.
 *
 * <p>
 *
 * <h1 class="header">Test Suite Nesting</h1>
 *
 * {@code GridJunit3TestSuite} instances can be nested within each other as deep as needed. However
 * all nested distributed test suites will be treated just like regular JUnit test suites, and not
 * as distributed test suites. This approach becomes convenient when you have several distributed
 * test suites that you would like to be able to execute separately in distributed fashion, but at
 * the same time you would like to be able to execute them as a part of larger distributed suites.
 *
 * <p>
 *
 * <h1 class="header">Configuration</h1>
 *
 * To run distributed JUnit tests you need to start other instances of GridGain. You can do so by
 * running {@code GRIDGAIN_HOME/bin/ggjunit.{sh|bat}} script, which will start default
 * configuration. If configuration other than default is required, then use regular {@code
 * GRIDGAIN_HOME/bin/ggstart.{sh|bat}} script and pass your own Spring XML configuration file as a
 * parameter to the script.
 *
 * <p>You can use the following configuration parameters to configure distributed test suite
 * locally. Note that many parameters can be overridden by setting corresponding VM parameters
 * defined in {@link GridTestVmParameters} at VM startup.
 *
 * <table class="doctable">
 *   <tr>
 *     <th>GridConfiguration Method</th>
 *     <th>Default Value</th>
 *     <th>Description</th>
 *   </tr>
 *   <tr>
 *     <td>{@link #setDisabled(boolean) setDisabled(boolean)}</td>
 *     <td>{@code false}</td>
 *     <td>
 *       If {@code true} then GridGain will be turned off and suite will run locally.
 *       This value can be overridden by setting {@link GridTestVmParameters#GRIDGAIN_DISABLED} VM
 *       parameter to {@code true}. This parameter comes handy when you would like to
 *       turn off GridGain without changing the actual code.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td>{@link #setConfigurationPath(String) setConfigurationPath(String)}</td>
 *     <td>{@link #DFLT_JUNIT_CONFIG DFLT_JUNIT_CONFIG}</td>
 *     <td>
 *       Optional path to GridGain Spring XML configuration file for running JUnit tests. This
 *       property can be overridden by setting {@link GridTestVmParameters#GRIDGAIN_CONFIG} VM
 *       parameter. Note that the value can be either absolute value or relative to
 *       ${GRIDGAIN_HOME} installation folder.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td>{@link #setRouterClassName(String) setRouterClassName(String)}</td>
 *     <td>{@link #DFLT_JUNIT_ROUTER DFLT_JUNIT_ROUTER}</td>
 *     <td>
 *       Optional name of test router class that implements {@link GridTestRouter} interface.
 *       If not provided, then tests will be routed in round-robin fashion using default
 *       {@link GridTestRouterAdapter}. The value of this parameter can be overridden by setting
 *       {@link GridTestVmParameters#GRIDGAIN_TEST_ROUTER} VM parameter to the name of your
 *       own customer router class.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td>{@link #setRouterClass(Class) setRouterClass(Class)}</td>
 *     <td>{@code null}</td>
 *     <td>
 *       Same as {@link #setRouterClassName(String) setRouterClassName(String)}, but sets the
 *       actual class instead of the name.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td>{@link #setTimeout(long) setTimeout(long)}</td>
 *     <td>{@code 0} which means that tests will never timeout.</td>
 *     <td>
 *       Maximum timeout value in milliseconds after which test suite will return without
 *       waiting for the remaining tests to complete. This value can be overridden by setting
 *       {@link GridTestVmParameters#GRIDGAIN_TEST_TIMEOUT} VM parameter to the timeout value
 *       for the tests.
 *     </td>
 *   </tr>
 * </table>
 *
 * @author 2005-2011 Copyright (C) GridGain Systems, Inc.
 * @version 3.1.1c.19062011
 */
public class GridJunit3TestSuite extends TestSuite {
  /**
   * Default GridGain configuration file for JUnits (value is {@code
   * config/junit/junit-spring.xml}).
   */
  public static final String DFLT_JUNIT_CONFIG = "config/junit/junit-spring.xml";

  /**
   * Default JUnit test router (value is {@link GridTestRouterAdapter
   * GridTestRouterAdapter.class.getName()}).
   */
  public static final String DFLT_JUNIT_ROUTER = GridTestRouterAdapter.class.getName();

  /** */
  private final Collection<String> locTests = new HashSet<String>();

  /** JUnit3 JavaAssist proxy. */
  private final GridJunit3ProxyFactory factory = new GridJunit3ProxyFactory();

  /** Flag indicating whether grid was started in this suite. */
  private boolean selfStarted;

  /** Junit3 Spring configuration path. */
  private String cfgPath =
      System.getProperty(GRIDGAIN_CONFIG.name()) == null
          ? DFLT_JUNIT_CONFIG
          : System.getProperty(GRIDGAIN_CONFIG.name());

  /**
   * Check if GridGain is disabled by checking {@link GridTestVmParameters#GRIDGAIN_DISABLED} system
   * property.
   */
  private boolean isDisabled = Boolean.getBoolean(GRIDGAIN_DISABLED.name());

  /** JUnit test router class name. */
  private String routerClsName =
      System.getProperty(GRIDGAIN_TEST_ROUTER.name()) == null
          ? DFLT_JUNIT_ROUTER
          : System.getProperty(GRIDGAIN_TEST_ROUTER.name());

  /** JUnit test router class. */
  private Class<? extends GridTestRouter> routerCls;

  /**
   * Local suite in case if grid is disabled or if this is a nested suite within other distributed
   * suite.
   */
  private TestSuite copy;

  /** JUnit grid name. */
  private String gridName;

  /** Test timeout. */
  private long timeout =
      Long.getLong(GRIDGAIN_TEST_TIMEOUT.name()) == null
          ? 0
          : Long.getLong(GRIDGAIN_TEST_TIMEOUT.name());

  /** */
  private ClassLoader clsLdr;

  /** Empty test suite. */
  public GridJunit3TestSuite() {
    if (copy == null) {
      copy = new TestSuite();
    }
  }

  /** @param name Test suite name. */
  public GridJunit3TestSuite(String name) {
    super(name);

    if (copy == null) {
      copy = new TestSuite(name);
    }
  }

  /**
   * Test suite for one class.
   *
   * @param cls Class for test suite.
   */
  public GridJunit3TestSuite(Class<? extends TestCase> cls) {
    super(cls);

    if (copy == null) {
      copy = new TestSuite(cls);
    }
  }

  /**
   * Test suite for a given test class with specified test name.
   *
   * @param cls Test class.
   * @param name Test name.
   */
  public GridJunit3TestSuite(Class<? extends TestCase> cls, String name) {
    super(cls, name);

    if (copy == null) {
      copy = new TestSuite(cls, name);
    }
  }

  /**
   * Copies non-distributed test suite into distributed one.
   *
   * @param suite Test suite to copy.
   */
  public GridJunit3TestSuite(TestSuite suite) {
    super(suite.getName());

    if (copy == null) {
      copy = new TestSuite(suite.getName());
    }

    for (int i = 0; i < suite.testCount(); i++) {
      addTest(suite.testAt(i));
    }
  }

  /**
   * Empty test suite with given class loader.
   *
   * @param clsLdr Tests class loader.
   */
  public GridJunit3TestSuite(ClassLoader clsLdr) {
    this();

    assert clsLdr != null;

    this.clsLdr = clsLdr;
  }

  /**
   * @param name Test suite name.
   * @param clsLdr Tests class loader.
   */
  public GridJunit3TestSuite(String name, ClassLoader clsLdr) {
    this(name);

    assert clsLdr != null;

    this.clsLdr = clsLdr;
  }

  /**
   * Test suite for one class.
   *
   * @param cls Class for test suite.
   * @param clsLdr Tests class loader.
   */
  public GridJunit3TestSuite(Class<? extends TestCase> cls, ClassLoader clsLdr) {
    this(cls);

    assert clsLdr != null;

    this.clsLdr = clsLdr;
  }

  /**
   * Test suite for a given test class with specified test name.
   *
   * @param cls Test class.
   * @param name Test name.
   * @param clsLdr Tests class loader.
   */
  public GridJunit3TestSuite(Class<? extends TestCase> cls, String name, ClassLoader clsLdr) {
    this(cls, name);

    assert clsLdr != null;

    this.clsLdr = clsLdr;
  }

  /**
   * Copies non-distributed test suite into distributed one.
   *
   * @param suite Test suite to copy.
   * @param clsLdr Tests class loader.
   */
  public GridJunit3TestSuite(TestSuite suite, ClassLoader clsLdr) {
    this(suite);

    assert clsLdr != null;

    this.clsLdr = clsLdr;
  }

  /**
   * Sets path to GridGain configuration file. By default {@code
   * {GRIDGAIN_HOME}/config/junit/junit-spring.xml} is used.
   *
   * @param cfgPath Path to GridGain configuration file.
   */
  public void setConfigurationPath(String cfgPath) {
    this.cfgPath = cfgPath;
  }

  /**
   * Gets path to GridGain configuration file. By default {@code
   * {GRIDGAIN_HOME}/config/junit/junit-spring.xml} is used.
   *
   * @return Path to GridGain configuration file.
   */
  public String getConfigurationPath() {
    return cfgPath;
  }

  /**
   * Disables GridGain. If set to {@code true} then this suite will execute locally as if GridGain
   * was not in a picture at all.
   *
   * @param disabled If set to {@code true} then this suite will execute locally as if GridGain was
   *     not in a picture at all.
   */
  public void setDisabled(boolean disabled) {
    isDisabled = disabled;
  }

  /**
   * Gets flag indicating whether GridGain should be enabled or not. If set to {@code true} then
   * this suite will execute locally as if GridGain was not in a picture at all.
   *
   * @return Flag indicating whether GridGain should be enabled or not. If set to {@code true} then
   *     this suite will execute locally as if GridGain was not in a picture at all.
   */
  public boolean isDisabled() {
    return isDisabled;
  }

  /**
   * Sets name of class for routing JUnit tests. By default {@link #DFLT_JUNIT_ROUTER} class name is
   * used.
   *
   * @param routerClsName Junit test router class name.
   */
  public void setRouterClassName(String routerClsName) {
    this.routerClsName = routerClsName;
  }

  /**
   * Gets JUnit test router class name.
   *
   * @return JUnit test router class name.
   */
  public String getRouterClassName() {
    return routerClsName;
  }

  /**
   * Sets router class. By default {@link GridTestRouterAdapter} is used.
   *
   * @param routerCls Router class to use for test routing.
   */
  public void setRouterClass(Class<? extends GridTestRouter> routerCls) {
    this.routerCls = routerCls;
  }

  /**
   * Gets router class used for test routing.
   *
   * @return Router class used for test routing.
   */
  public Class<? extends GridTestRouter> getRouterClass() {
    return routerCls;
  }

  /**
   * Gets identical suite for local (non-distributed) execution.
   *
   * @return Local suite.
   */
  public TestSuite getLocalCopy() {
    return copy;
  }

  /** {@inheritDoc} */
  @Override
  public void setName(String name) {
    if (copy != null) {
      copy.setName(name);
    }

    super.setName(name);
  }

  /**
   * Gets timeout for running distributed test suite.
   *
   * @return Timeout for tests.
   */
  public long getTimeout() {
    return timeout;
  }

  /**
   * Sets timeout for running distributed test suite. By default, test execution does not expire.
   *
   * @param timeout Timeout for tests.
   */
  public void setTimeout(long timeout) {
    this.timeout = timeout;
  }

  /** {@inheritDoc} */
  @Override
  public Test testAt(int index) {
    return isDisabled ? copy.testAt(index) : super.testAt(index);
  }

  /** {@inheritDoc} */
  @Override
  public int testCount() {
    return isDisabled ? copy.testCount() : super.testCount();
  }

  /** {@inheritDoc} */
  @Override
  public Enumeration<Test> tests() {
    return isDisabled ? copy.tests() : super.tests();
  }

  /**
   * The added suite will be always executed locally, but in parallel with other locally or remotely
   * running tests. This comes handy for tests that cannot be distributed for some environmental
   * reasons, but still would benefit from parallel execution.
   *
   * <p>Note, that local suites will be executed on local node even if grid topology only allows
   * remote nodes.
   *
   * @param localSuite Test to execute locally in parallel with other local or distributed tests.
   */
  @SuppressWarnings({"TypeMayBeWeakened"})
  public void addTest(GridJunit3LocalTestSuite localSuite) {
    if (!locTests.contains(localSuite.getName())) {
      locTests.add(localSuite.getName());
    }

    addTest((Test) localSuite);
  }

  /**
   * Adds a test to be executed on the grid. In case of test suite, all tests inside of test suite
   * will be executed sequentially on some remote node.
   *
   * @param test Test to add.
   */
  @Override
  public void addTest(Test test) {
    if (copy == null) {
      copy = new TestSuite(getName());
    }

    // Add test to the list of local ones.
    if (test instanceof GridJunit3LocalTestSuite) {
      String testName = ((TestSuite) test).getName();

      if (!locTests.contains(testName)) {
        locTests.add(testName);
      }
    }

    if (test instanceof GridJunit3TestSuite) {
      copy.addTest(((GridJunit3TestSuite) test).copy);

      super.addTest(new GridJunit3TestSuiteProxy(((GridJunit3TestSuite) test).copy, factory));
    } else if (test instanceof GridJunit3TestSuiteProxy) {
      copy.addTest(((GridJunit3TestSuiteProxy) test).getOriginal());

      super.addTest(test);
    } else if (test instanceof GridJunit3TestCaseProxy) {
      copy.addTest(((GridJunit3TestCaseProxy) test).getGridGainJunit3OriginalTestCase());

      super.addTest(test);
    } else if (test instanceof TestSuite) {
      copy.addTest(test);

      super.addTest(new GridJunit3TestSuiteProxy((TestSuite) test, factory));
    } else {
      assert test instanceof TestCase
          : "Test must be either instance of TestSuite or TestCase: " + test;

      copy.addTest(test);

      super.addTest(factory.createProxy((TestCase) test));
    }
  }

  /**
   * Creates JUnit test router. Note that router must have a no-arg constructor.
   *
   * @return JUnit router instance.
   */
  @SuppressWarnings({"unchecked"})
  private GridTestRouter createRouter() {
    try {
      if (routerCls == null) {
        routerCls = (Class<? extends GridTestRouter>) Class.forName(routerClsName);
      } else {
        routerClsName = routerCls.getName();
      }

      return routerCls.newInstance();
    } catch (ClassNotFoundException e) {
      throw new GridRuntimeException("Failed to initialize JUnit router: " + routerClsName, e);
    } catch (IllegalAccessException e) {
      throw new GridRuntimeException("Failed to initialize JUnit router: " + routerClsName, e);
    } catch (InstantiationException e) {
      throw new GridRuntimeException("Failed to initialize JUnit router: " + routerClsName, e);
    }
  }

  /**
   * Runs all tests belonging to this test suite on the grid.
   *
   * @param result Test result collector.
   */
  @Override
  public void run(TestResult result) {
    if (isDisabled) {
      copy.run(result);
    } else {
      GridTestRouter router = createRouter();

      Grid grid = startGrid();

      try {
        List<GridTaskFuture<?>> futs = new ArrayList<GridTaskFuture<?>>(testCount());

        List<GridJunit3SerializableTest> tests =
            new ArrayList<GridJunit3SerializableTest>(testCount());

        for (int i = 0; i < testCount(); i++) {
          Test junit = testAt(i);

          GridJunit3SerializableTest test;

          if (junit instanceof TestSuite) {
            test = new GridJunit3SerializableTestSuite((TestSuite) junit);
          } else {
            assert junit instanceof TestCase
                : "Test must be either TestSuite or TestCase: " + junit;

            test = new GridJunit3SerializableTestCase((TestCase) junit);
          }

          tests.add(test);

          if (clsLdr == null) {
            clsLdr = U.detectClassLoader(junit.getClass());
          }

          futs.add(
              grid.execute(
                  new GridJunit3Task(junit.getClass(), clsLdr),
                  new GridJunit3Argument(router, test, locTests.contains(test.getName())),
                  timeout));
        }

        for (int i = 0; i < testCount(); i++) {
          GridTaskFuture<?> fut = futs.get(i);

          GridJunit3SerializableTest origTest = tests.get(i);

          try {
            GridJunit3SerializableTest resTest = (GridJunit3SerializableTest) fut.get();

            origTest.setResult(resTest);

            origTest.getTest().run(result);
          } catch (GridException e) {
            handleFail(result, origTest, e);
          }
        }
      } finally {
        stopGrid();
      }
    }
  }

  /**
   * Handles test fail.
   *
   * @param result Test result.
   * @param origTest Original JUnit test.
   * @param e Exception thrown from grid.
   */
  private void handleFail(TestResult result, GridJunit3SerializableTest origTest, Throwable e) {
    // Simulate that all tests were run.
    origTest.getTest().run(result);

    // For the tests suite we assume that all tests failed because
    // entire test suite execution failed and there is no way to get
    // broken tests.
    if (origTest.getTest() instanceof GridJunit3TestSuiteProxy) {
      TestSuite suite = (((TestSuite) origTest.getTest()));

      for (int j = 0; j < suite.testCount(); j++) {
        result.addError(suite.testAt(j), e);
      }
    } else if (origTest.getTest() instanceof GridJunit3TestCaseProxy) {
      result.addError(origTest.getTest(), e);
    }
  }

  /**
   * Starts Grid instance. Note that if grid is already started, then it will be looked up and
   * returned from this method.
   *
   * @return Started grid.
   */
  private Grid startGrid() {
    Properties props = System.getProperties();

    gridName = props.getProperty(GRIDGAIN_NAME.name());

    if (!props.containsKey(GRIDGAIN_NAME.name()) || G.state(gridName) != GridFactoryState.STARTED) {
      selfStarted = true;

      // Set class loader for the spring.
      ClassLoader curCl = Thread.currentThread().getContextClassLoader();

      // Add no-op logger to remove no-appender warning.
      Appender app = new NullAppender();

      Logger.getRootLogger().addAppender(app);

      try {
        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());

        Grid grid = G.start(cfgPath);

        gridName = grid.name();

        System.setProperty(GRIDGAIN_NAME.name(), grid.name());

        return grid;
      } catch (GridException e) {
        throw new GridRuntimeException("Failed to start grid: " + cfgPath, e);
      } finally {
        Logger.getRootLogger().removeAppender(app);

        Thread.currentThread().setContextClassLoader(curCl);
      }
    }

    return G.grid(gridName);
  }

  /** Stops grid only if it was started by this test suite. */
  private void stopGrid() {
    // Only stop grid if it was started here.
    if (selfStarted) {
      G.stop(gridName, true);
    }
  }
}