Verified Commit 87de694f authored by Lukas Wiest's avatar Lukas Wiest 🚂
Browse files

initial commit

parents
*.jar
*.class
# maven
/target
# IntelliJ
/.idea
*.iml
from openjdk:11-jdk-slim
env MODOCOT_BASEDIR /modocot
env MODOCOT_TESTDIR /$MODOCOT_BASEDIR/test
env MODOCOT_SOURCEDIR /$MODOCOT_BASEDIR/src
env MODOCOT_RESULTDIR /$MODOCOT_BASEDIR/result
env MODOCOT_LIBSDIR /$MODOCOT_BASEDIR/libs
run mkdir -p $MODOCOT_TESTDIR \
&& mkdir $MODOCOT_SOURCEDIR \
&& mkdir $MODOCOT_RESULTDIR \
&& mkdir -p /$MODOCOT_LIBSDIR/additional
add target/modocot-openjdk11-junit5-runner-jar-with-dependencies.jar /$MODOCOT_BASEDIR/app.jar
add https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-api/5.7.0/junit-jupiter-api-5.7.0.jar /$MODOCOT_LIBSDIR/
add https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-engine/5.7.0/junit-jupiter-engine-5.7.0.jar /$MODOCOT_LIBSDIR/
workdir /modocot
entrypoint java -Djava.security.egd=file:/dev/./urandom -jar /modocot/app.jar "$MODOCOT_SOURCEDIR:$MODOCOT_TESTDIR" "$MODOCOT_LIBSDIR/*" $MODOCOT_RESULTDIR
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.hftstuttgart</groupId>
<artifactId>modocot-openjdk11-junit5-runner</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>8</java.version>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.7.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.3</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<archive>
<manifest>
<mainClass>de.hftstuttgart.modocot.Testrunner</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package de.hftstuttgart.modocot;
import de.hftstuttgart.modocot.model.ModocotResult;
import de.hftstuttgart.modocot.model.ModocotResultSummary;
import de.hftstuttgart.modocot.util.ModocotSummaryGeneratingListener;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.platform.engine.discovery.DiscoverySelectors;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.TestIdentifier;
import org.junit.platform.launcher.TestPlan;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import org.junit.platform.launcher.core.LauncherFactory;
import org.junit.platform.launcher.listeners.TestExecutionSummary;
import javax.tools.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Testrunner
{
static
{
InputStream stream = Testrunner.class.getClassLoader()
.getResourceAsStream("logging.properties");
try
{
LogManager.getLogManager().readConfiguration(stream);
}
catch (IOException e)
{
e.printStackTrace();
}
}
private static final Logger LOG = Logger.getLogger(Testrunner.class.getName());
public static String[] sourceFolders; // folders with Java Files to compile
public static String[] libraryFolders; // folders with libraries in jar-file format
public static String classFolder; // folder to put compiled class files into
public static String[] classPathItems; // items for the classpath
public static String resultFolder; // folder the result file gets serialized to
public static void main(String[] args) throws Exception
{
LOG.info("OpenJDK11 JUnit5/Jupiter Testrunner started");
LOG.info("initializing fields...");
sourceFolders = args[0].split(":");
libraryFolders = args[1].split(":");
resultFolder = args[2];
classFolder = Files.createTempDirectory("modocot-testrunner").toAbsolutePath().toString();
classPathItems = buildClassPathItems();
// finding all source files
Set<File> sourceFiles = new HashSet<>();
for (String folder : sourceFolders)
{
sourceFiles.addAll(getAllJavaFilesInFolder(Paths.get(folder).toFile()));
}
// call compilation and generate Results for failed compiles
Set<ModocotResult> compilationErrors = generateCompileResults(compile(sourceFiles, new File(classFolder)));
// run unit tests found in the compiled class files
ModocotResultSummary resultSummary = runTests();
// add compilation errors to summary
resultSummary.failures.addAll(compilationErrors);
resultSummary.failureCount = resultSummary.failures.size();
// serialize result
writeResult(resultSummary);
}
public static String[] buildClassPathItems()
{
Set<String> classPathItemsBuild = new HashSet<>(Arrays.asList(libraryFolders));
classPathItemsBuild.add(classFolder);
return classPathItemsBuild.toArray(new String[0]);
}
public static Set<File> buildClassPath(String... paths)
{
Set<File> files = new HashSet<>();
for (String path : paths)
{
if (path.endsWith("*"))
{
path = path.substring(0, path.length() - 1);
File pathFile = new File(path);
for (File file : Objects.requireNonNull(pathFile.listFiles()))
{
if (file.isFile() && file.getName().endsWith(".jar"))
{
files.add(file);
} else
{
files.addAll(buildClassPath(Paths.get(file.getPath(), "*").toString()));
}
}
} else
{
files.add(new File(path));
}
}
return files;
}
public static List<Diagnostic> compile(Set<File> files, File outputDir)
{
LOG.info("compilation started");
List<Diagnostic> compilationErrors = new LinkedList<>();
// Create the compiler and add a diagnostic listener to get the compilation errors
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticListener listener = compilationErrors::add;
StandardJavaFileManager fileManager = compiler.getStandardFileManager(listener, null, StandardCharsets.UTF_8);
Iterable<? extends JavaFileObject> fileObjects = fileManager.getJavaFileObjects(files.toArray(new File[0]));
if (!outputDir.exists())
{
outputDir.mkdir();
}
// Set the compiler option for a specific output path
List<String> options = new ArrayList<>();
options.add("-d"); // output dir
options.add(outputDir.getAbsolutePath());
options.add("-cp"); // custom classpath
String cp = buildClassPath(classPathItems).stream()
.map(f -> f.getPath())
.reduce((s1, s2) -> s1 + ":" + s2).orElse("");
LOG.info("classpath for compilation: " + cp);
options.add(cp);
// compile it
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, listener, options, null, fileObjects);
boolean compileResult = task.call();
// If the compilation failed, remove the failed file from the pathsToCompile list and try to compile again without this file
if (!compileResult)
{
File currentFile = new File(((JavaFileObject) compilationErrors.get(compilationErrors.size() - 1).getSource()).toUri().getPath());
LOG.log(Level.WARNING,"compilation of file '" + currentFile.getAbsolutePath() + "' failed");
files.removeIf(file -> file.getAbsolutePath().equalsIgnoreCase(currentFile.getAbsolutePath()));
if (files.size() > 0)
{
LOG.info(String.format("retry compilation without %s", currentFile.getName()));
compile(files, outputDir);
}
} else {
LOG.info("compilation finished");
}
return compilationErrors;
}
public static ClassLoader createCustomClassLoader() throws MalformedURLException
{
LOG.info("creating custom class loader for testing");
URL[] urls = buildClassPath(classPathItems).stream().map(f -> {
try {
return f.toURI().toURL();
} catch (MalformedURLException e) {
LOG.log(Level.SEVERE, e.getMessage(), e);
throw new RuntimeException(e.getMessage(), e);
}
}).toArray(URL[]::new);
LOG.info(String.format("JUnit ClassLoader context classpath: %s", Arrays.deepToString(urls)));
ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
return URLClassLoader.newInstance(urls, parentClassLoader);
}
public static Set<ModocotResult> generateCompileResults(List<Diagnostic> compilationErrors)
{
return compilationErrors.stream().map(e ->
{
ModocotResult result = new ModocotResult();
Pattern pattern = Pattern.compile(String.format("^.*%s(.*\\.java).*$", File.separator));
Matcher matcher = pattern.matcher(String.valueOf(e.getSource()));
result.name = (matcher.matches() && matcher.group(1) != null) ? matcher.group(1) : String.valueOf(e.getSource());
result.state = ModocotResult.State.FAILURE.ordinal();
result.failureReason = e.getMessage(Locale.ENGLISH);
result.failureType = "Compilation Failed";
result.stacktrace = e.toString();
return result;
})
.collect(Collectors.toCollection(HashSet::new));
}
public static ModocotResultSummary generateResultSummary(TestExecutionSummary summary, Set<TestIdentifier> successes)
{
LOG.info("JUnit results:");
LOG.info(String.format(
"Number of Tests: %d, Number of fails: %d, Successful tests: %s, Failed tests: %s",
summary.getTestsFoundCount(),
summary.getTestsFailedCount(),
successes.stream()
.map(s -> s.getDisplayName())
.reduce((s1, s2) -> s1 + ":" + s2).orElse("-"),
summary.getFailures().stream()
.map(f -> f.getTestIdentifier().getDisplayName())
.reduce((s1, s2) -> s1 + ":" + s2).orElse("-")
));
ModocotResultSummary resultSummary = new ModocotResultSummary();
resultSummary.successes = successes.stream().map(s ->
{
ModocotResult result = new ModocotResult();
result.name = s.getDisplayName();
result.state = ModocotResult.State.SUCCESS.ordinal();
return result;
})
.collect(Collectors.toCollection(HashSet::new));
resultSummary.failures = summary.getFailures().stream().map(f ->
{
ModocotResult result = new ModocotResult();
result.name = f.getTestIdentifier().getDisplayName();
result.state = ModocotResult.State.FAILURE.ordinal();
result.failureReason = f.getException().getMessage();
result.failureType = f.getException().getClass().getName();
result.stacktrace = Arrays.stream(f.getException().getStackTrace())
.map(s -> s.toString())
.reduce((s1, s2) -> s1 + "\n" + s2)
.orElse(null);
return result;
})
.collect(Collectors.toCollection(HashSet::new));
resultSummary.timestamp = System.currentTimeMillis() / 1000;
resultSummary.testCount = (int) summary.getTestsStartedCount();
resultSummary.successCount = resultSummary.successes.size();
resultSummary.failureCount = resultSummary.failures.size();
return resultSummary;
}
public static List<File> getAllJavaFilesInFolder(File path)
{
// check if provided path is a directory, otherwise throw a IllegalArgumentException
if (!path.isDirectory())
{
String error = path.getAbsolutePath() + " is not a path";
LOG.severe(error);
throw new IllegalArgumentException(error);
}
List<File> files = new LinkedList<>();
// recursively check for java files
Stream.of(Objects.requireNonNull(path.listFiles()))
.forEach(file ->
{
// if directory, make recursion
if (file.isDirectory())
{
files.addAll(getAllJavaFilesInFolder(file));
}
// if java file add to list
else if (file.getAbsolutePath().endsWith(".java"))
{
files.add(file);
}
});
return files;
}
public static ModocotResultSummary runTests() throws MalformedURLException
{
LOG.info("saving original class loader");
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
// get custom one
ClassLoader customClassLoader = createCustomClassLoader();
TestExecutionSummary summary;
Set<TestIdentifier> successes;
try
{
LOG.info("changing classloader");
Thread.currentThread().setContextClassLoader(customClassLoader);
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
.selectors(DiscoverySelectors.selectClasspathRoots(Collections.singleton(Paths.get(classFolder).toAbsolutePath())))
.build();
ModocotSummaryGeneratingListener listener = new ModocotSummaryGeneratingListener();
Launcher launcher = LauncherFactory.create();
launcher.registerTestExecutionListeners(listener);
LOG.info("discovering UnitTests...");
TestPlan plan = launcher.discover(request);
for (TestIdentifier root : plan.getRoots())
{
for (TestIdentifier test : plan.getChildren(root))
{
LOG.info(String.format("Testclass identified: %s", test.getDisplayName()));
}
}
LOG.info("launching tests");
launcher.execute(plan);
LOG.info("catching test results");
summary = listener.getSummary();
successes = listener.getSuccessfulTestidentifiers();
} finally
{
LOG.info("restore original classloader");
Thread.currentThread().setContextClassLoader(originalClassLoader);
}
LOG.info("generate modocot result summary from junit");
return generateResultSummary(summary, successes);
}
public static void writeResult(ModocotResultSummary resultSummary) throws IOException
{
Path fileName = Paths.get(resultFolder, "result.json");
LOG.info(String.format("serializing modocdot result as json into %s", fileName.toAbsolutePath().toString()));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper
.writerWithDefaultPrettyPrinter()
.writeValue(fileName.toFile(), resultSummary);
}
}
package de.hftstuttgart.modocot.model;
public class ModocotResult
{
public String name;
public int state;
public String failureType;
public String failureReason;
public String stacktrace;
public static enum State
{
SUCCESS,
FAILURE,
UNKNOWN
}
}
package de.hftstuttgart.modocot.model;
import java.util.Set;
public class ModocotResultSummary
{
public long timestamp;
public int testCount;
public int failureCount;
public int successCount;
public String globalStacktrace;
public Set<ModocotResult> successes;
public Set<ModocotResult> failures;
}
package de.hftstuttgart.modocot.util;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.launcher.TestIdentifier;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
import java.util.HashSet;
import java.util.Set;
public class ModocotSummaryGeneratingListener extends SummaryGeneratingListener
{
protected Set<TestIdentifier> successful = new HashSet<>();
@Override
public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult)
{
super.executionFinished(testIdentifier, testExecutionResult);
if (testExecutionResult.getStatus().equals(TestExecutionResult.Status.SUCCESSFUL)
&& testIdentifier.getType().equals(TestDescriptor.Type.TEST))
{
successful.add(testIdentifier);
}
}
public Set<TestIdentifier> getSuccessfulTestidentifiers()
{
return successful;
}
}
package de.hftstuttgart.modocot.util.logging;
import java.util.Date;
import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;
public class CustomFormatter extends SimpleFormatter
{
final String format = "[%1$tFT%1$tT%1$tz] [%2$-7s] [%3$s.%4$s] %5$s%n";
@Override
public synchronized String format(LogRecord lr) {
return String.format(format,
new Date(lr.getMillis()),
lr.getLevel(),
lr.getSourceClassName(),
lr.getSourceMethodName(),
lr.getMessage()
);
}
}
handlers=java.util.logging.ConsoleHandler
.level = INFO
java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter=de.hftstuttgart.modocot.util.logging.CustomFormatter
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment