/*
* Copyright 2012 the hegemon authors.
*
* 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.cueup.hegemon.testing.server;
import com.cueup.hegemon.LoadPath;
import com.cueup.hegemon.testing.HegemonRunner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.runner.notification.RunNotifier;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
/**
* Test server for Hegemon.
*/
public class HegemonTestServer extends AbstractHandler {
private final LoadPath loadPath;
/**
* Creates a test server loading sources with 'loadPath'.
*/
public HegemonTestServer(LoadPath loadPath) {
this.loadPath = loadPath;
}
private static class AllClassesList {
private static final Set<String> TEST_CLASS_NAMES;
private static final Map<String, String> TEST_CLASS_SHORT_NAMES;
private static final Map<String, String> JS_NAME_TO_CLASS_NAME;
private static void addClass(String className) {
try {
Class<?> c = Class.forName(className, false, AllClassesList.class.getClassLoader());
if (c.isAnnotationPresent(HegemonRunner.TestScript.class)) {
TEST_CLASS_NAMES.add(className);
TEST_CLASS_SHORT_NAMES.put(c.getSimpleName(), className);
JS_NAME_TO_CLASS_NAME.put(c.getAnnotation(HegemonRunner.TestScript.class).filename(), className);
} else {
System.err.println("No annotation: " + className);
}
} catch (ClassNotFoundException e) {
System.err.println("Could not load: " + className);
}
}
private static void addClassesFromJar(File jarFile) throws IOException {
JarInputStream is = new JarInputStream(new FileInputStream(jarFile));
JarEntry entry;
while ((entry = is.getNextJarEntry()) != null) {
String name = entry.getName();
if (name.endsWith("Test.class")) {
addClass(FilenameUtils.removeExtension(name).replaceAll("/", "."));
}
}
}
private static void addClassesFromPath(File root) throws IOException {
Collection files = FileUtils.listFiles(root, new String[]{"class"}, true);
for (Object file : files) {
String name = root.toURI().relativize(((File) file).toURI()).getPath();
if (name.endsWith("Test.class")) {
addClass(name.substring(0, name.length() - 6).replaceAll("/", "."));
}
}
}
static {
TEST_CLASS_NAMES = Sets.newHashSet();
TEST_CLASS_SHORT_NAMES = Maps.newHashMap();
JS_NAME_TO_CLASS_NAME = Maps.newHashMap();
try {
String classPath = System.getProperty("java.class.path");
String separator = System.getProperty("path.separator");
for (String classpathEntry : classPath.split(separator)) {
System.err.println("Adding class path: " + classpathEntry);
if (classpathEntry.endsWith(".jar")) {
addClassesFromJar(new File(classpathEntry));
} else {
addClassesFromPath(new File(classpathEntry));
}
}
ClassLoader loader = AllClassesList.class.getClassLoader();
if (loader instanceof URLClassLoader) {
for (URL base : ((URLClassLoader) loader).getURLs()) {
if ("file".equals(base.getProtocol())) {
System.err.println("Adding class path: " + base);
if (base.getPath().endsWith(".jar")) {
addClassesFromJar(new File(base.getPath()));
} else {
addClassesFromPath(new File(base.getPath()));
}
} else {
System.err.println("Ignoring class path: " + base);
}
}
}
} catch (Throwable t) { // lint: disable=IllegalCatchCheck
t.printStackTrace();
}
}
}
private static Class loadClass(String name) {
try {
return Class.forName(name);
} catch (ClassNotFoundException ex) {
return null;
}
}
private static Class loadClassByReference(String name) {
Class result = loadClass(name);
if (result != null) {
return result;
}
List<String> toTry = Lists.newArrayList();
toTry.add(AllClassesList.TEST_CLASS_SHORT_NAMES.get(name));
toTry.add(AllClassesList.JS_NAME_TO_CLASS_NAME.get(name));
for (String option : toTry) {
if (option != null) {
result = loadClass(option);
if (result != null) {
return result;
}
}
}
return null;
}
private static void markHandled(Request baseRequest, HttpServletResponse response, int status, String contentType) {
response.setContentType(contentType + ";charset=utf-8");
response.setStatus(status);
baseRequest.setHandled(true);
}
/**
* Hook for adding custom test output handling.
* @return the custom test output handler, or null to use no custom output.
*/
protected CustomTestOutput createCustomTestOutputHandler() {
return null;
}
/**
* Hook for adding custom handling for each test class run.
*/
protected void startTestClass() {
}
/**
* The request handler translates an HTTP request to a test class and method to run.
*/
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
if ("/favicon.ico".equals(target)) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
baseRequest.setHandled(true);
return;
}
if (target.length() <= 1) {
String[] names = AllClassesList.TEST_CLASS_NAMES.toArray(new String[AllClassesList.TEST_CLASS_NAMES.size()]);
Arrays.sort(names);
markHandled(baseRequest, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "text/html");
for (String name : names) {
response.getWriter().println("<p><a href=\"" + name + "\">" + name + "</a></p>");
}
return;
}
try {
Class c = loadClassByReference(target.substring(1));
if (c == null) {
markHandled(baseRequest, response, HttpServletResponse.SC_NOT_FOUND, "text/plain");
response.getWriter().println("Could not find class with name or reference to " + target.substring(1));
return;
}
HegemonRunner runner = new HegemonRunner(c, baseRequest.getQueryString(), this.loadPath);
RunNotifier notifier = new RunNotifier();
notifier.addListener(new ResponseListener(response, createCustomTestOutputHandler()));
startTestClass();
long start = System.currentTimeMillis();
response.setContentType("text/html;charset=utf-8");
response.getWriter().print(
"<style>.ok {color:green} .fail {color:red} .ignore {color:orange} pre {margin-left: 24px}</style>");
runner.run(notifier);
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("<br>Finished in " + ((System.currentTimeMillis() - start) / 1000.0) + " seconds.");
baseRequest.setHandled(true);
} catch (Throwable t) { // lint: disable=IllegalCatchCheck
markHandled(baseRequest, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "text/plain");
t.printStackTrace(response.getWriter());
}
}
/**
* Starts an http server on a port that responds to a passed handler.
*/
public static void run(AbstractHandler handler, int port) throws Exception {
Server server = new Server(port);
server.setHandler(handler);
server.start();
server.join();
}
}