Testrunner.java 15.87 KiB
package de.hftstuttgart.dta;
import de.hftstuttgart.dta.model.Result;
import de.hftstuttgart.dta.model.ResultSummary;
import de.hftstuttgart.dta.util.MySummaryGeneratingListener;
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("OpenJDK21 JUnit5/Jupiter Testrunner started");
       if (args.length < 3) {
           LOG.severe("Insufficient arguments provided. Expected: <sourceFolders>:<libraryFolders>:<resultFolder>");
throw new IllegalArgumentException("Missing required arguments."); } LOG.info("Initializing fields..."); sourceFolders = args[0].split(":"); libraryFolders = args[1].split(":"); resultFolder = args[2]; LOG.info(String.format("Source folders: %s", Arrays.toString(sourceFolders))); LOG.info(String.format("Library folders: %s", Arrays.toString(libraryFolders))); LOG.info(String.format("Result folder: %s", resultFolder)); classFolder = Files.createTempDirectory("testrunner").toAbsolutePath().toString(); LOG.info(String.format("Temporary class folder: %s", classFolder)); // Discover source files Set<File> sourceFiles = new HashSet<>(); for (String folder : sourceFolders) { File dir = Paths.get(folder).toFile(); if (!dir.exists() || !dir.isDirectory()) { LOG.warning(String.format("Source folder does not exist or is not a directory: %s", folder)); continue; } sourceFiles.addAll(getAllJavaFilesInFolder(dir)); } LOG.info(String.format("Discovered %d source files.", sourceFiles.size())); if (sourceFiles.isEmpty()) { LOG.severe("No source files found. Compilation cannot proceed."); throw new IllegalStateException("No source files found."); } // Compilation Set<Result> compilationErrors = generateCompileResults(compile(sourceFiles, new File(classFolder))); // run unit tests found in the compiled class files ResultSummary resultSummary = runTests(); // add compilation errors to summary resultSummary.results.addAll(compilationErrors); // serialize result writeResult(resultSummary); } public static String[] buildClassPathItems(boolean runtime) { Set<String> classPathItemsBuild = new HashSet<>(Arrays.asList(libraryFolders)); classPathItemsBuild.add(classFolder); if (runtime) { classPathItemsBuild.addAll(Arrays.stream(sourceFolders).collect(Collectors.toSet())); } 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); if (!pathFile.exists() || !pathFile.isDirectory()) { continue; }
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 { File file = new File(path); if (file.exists()) { files.add(file); } } } return files; } public static List<Diagnostic> compile(Set<File> files, File outputDir) { classPathItems = buildClassPathItems(false); 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 os=System.getProperty("os.name"); final String osSpecificCpDelim=(os.indexOf("Windows")>-1?";":":"); String cp = buildClassPath(classPathItems).stream() .map(f -> f.getPath()) .reduce((s1, s2) -> s1 + osSpecificCpDelim + 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<Result> generateCompileResults(List<Diagnostic> compilationErrors) { return compilationErrors.stream().map(e -> { Result result = new Result(); // 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()); String sourcePath=String.valueOf(e.getSource()); result.packageName=sourcePath.substring(1, sourcePath.lastIndexOf(File.separator)); result.className=sourcePath.substring(sourcePath.lastIndexOf(File.separator)+1, sourcePath.length()-1); result.name=result.className; result.state = Result.State.COMPILATIONERROR.ordinal(); result.failureReason = e.getMessage(Locale.ENGLISH); result.failureType = "Compilation Failed"; result.stacktrace = e.toString(); result.lineNumber = (int) e.getLineNumber(); result.columnNumber = (int) e.getColumnNumber(); result.position = (int) e.getPosition(); return result; }) .collect(Collectors.toCollection(HashSet::new)); } public static ResultSummary 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("-") )); ResultSummary resultSummary = new ResultSummary(); resultSummary.results.addAll(successes.stream().map(s -> { String testParent=s.getParentId().get(); int lastDotIndex=testParent.lastIndexOf('.'); String testPackage=testParent.substring(testParent.lastIndexOf(':')+1, lastDotIndex);
String testClass=testParent.substring(lastDotIndex+1, testParent.length()-1); Result result = new Result(); result.packageName=testPackage; result.className=testClass; result.name = s.getDisplayName(); result.state = Result.State.SUCCESS.ordinal(); return result; }) .collect(Collectors.toCollection(HashSet::new))); resultSummary.results.addAll(summary.getFailures().stream().map(f -> { String testParent=f.getTestIdentifier().getParentId().get(); int lastDotIndex=testParent.lastIndexOf('.'); String testPackage=testParent.substring(testParent.lastIndexOf(':')+1, lastDotIndex-1); String testClass=testParent.substring(lastDotIndex+1, testParent.length()-1); Result result = new Result(); result.packageName=testPackage; result.className=testClass; result.name = f.getTestIdentifier().getDisplayName(); result.state = Result.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))); 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 ResultSummary runTests() throws MalformedURLException {
classPathItems = buildClassPathItems(true); 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(); MySummaryGeneratingListener listener = new MySummaryGeneratingListener(); 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 result summary from junit"); return generateResultSummary(summary, successes); } public static void writeResult(ResultSummary resultSummary) throws IOException { Path fileName = Paths.get(resultFolder, "result.json"); LOG.info(String.format("serializing result as json into %s", fileName.toAbsolutePath().toString())); ObjectMapper objectMapper = new ObjectMapper(); objectMapper .writerWithDefaultPrettyPrinter() .writeValue(fileName.toFile(), resultSummary); } }