Testrunner.java 13.7 KB
Newer Older
Lukas Wiest's avatar
Lukas Wiest committed
1
package de.hftstuttgart.dtt;
Lukas Wiest's avatar
Lukas Wiest committed
2

Lukas Wiest's avatar
Lukas Wiest committed
3
4
5
import de.hftstuttgart.dtt.model.Result;
import de.hftstuttgart.dtt.model.ResultSummary;
import de.hftstuttgart.dtt.util.MySummaryGeneratingListener;
Lukas Wiest's avatar
Lukas Wiest committed
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

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];
Lukas Wiest's avatar
Lukas Wiest committed
74
        classFolder = Files.createTempDirectory("testrunner").toAbsolutePath().toString();
Lukas Wiest's avatar
Lukas Wiest committed
75
76
77
78
79
80
81
82
83

        // 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
Lukas Wiest's avatar
Lukas Wiest committed
84
        Set<Result> compilationErrors = generateCompileResults(compile(sourceFiles, new File(classFolder)));
Lukas Wiest's avatar
Lukas Wiest committed
85
86

        // run unit tests found in the compiled class files
Lukas Wiest's avatar
Lukas Wiest committed
87
        ResultSummary resultSummary = runTests();
Lukas Wiest's avatar
Lukas Wiest committed
88
89

        // add compilation errors to summary
Lukas Wiest's avatar
Lukas Wiest committed
90
        resultSummary.results.addAll(compilationErrors);
Lukas Wiest's avatar
Lukas Wiest committed
91
92
93
94
95

        // serialize result
        writeResult(resultSummary);
    }

96
    public static String[] buildClassPathItems(boolean runtime)
Lukas Wiest's avatar
Lukas Wiest committed
97
98
99
    {
        Set<String> classPathItemsBuild = new HashSet<>(Arrays.asList(libraryFolders));
        classPathItemsBuild.add(classFolder);
100
101
102
103
        if (runtime)
        {
            classPathItemsBuild.addAll(Arrays.stream(sourceFolders).collect(Collectors.toSet()));
        }
Lukas Wiest's avatar
Lukas Wiest committed
104
105
106
107
108
109
110
111
112
113
114
115
116
        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);
117
118
119
120
121
                if (!pathFile.exists() || !pathFile.isDirectory())
                {
                    continue;
                }

Lukas Wiest's avatar
Lukas Wiest committed
122
123
124
125
126
127
128
129
130
131
132
133
                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
            {
134
135
136
137
138
                File file = new File(path);
                if (file.exists())
                {
                    files.add(file);
                }
Lukas Wiest's avatar
Lukas Wiest committed
139
140
141
142
143
144
145
            }
        }
        return files;
    }

    public static List<Diagnostic> compile(Set<File> files, File outputDir)
    {
146
        classPathItems = buildClassPathItems(false);
Lukas Wiest's avatar
Lukas Wiest committed
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
        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);
    }

Lukas Wiest's avatar
Lukas Wiest committed
211
    public static Set<Result> generateCompileResults(List<Diagnostic> compilationErrors)
Lukas Wiest's avatar
Lukas Wiest committed
212
213
214
    {
        return compilationErrors.stream().map(e ->
        {
Lukas Wiest's avatar
Lukas Wiest committed
215
            Result result = new Result();
Lukas Wiest's avatar
Lukas Wiest committed
216
217
218
219
            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());
Lukas Wiest's avatar
Lukas Wiest committed
220
            result.state = Result.State.COMPILATIONERROR.ordinal();
Lukas Wiest's avatar
Lukas Wiest committed
221
222
223
224
            result.failureReason = e.getMessage(Locale.ENGLISH);
            result.failureType = "Compilation Failed";
            result.stacktrace = e.toString();

225
226
227
228
            result.lineNumber = (int) e.getLineNumber();
            result.columnNumber = (int) e.getColumnNumber();
            result.position = (int) e.getPosition();

Lukas Wiest's avatar
Lukas Wiest committed
229
230
231
232
233
            return result;
        })
        .collect(Collectors.toCollection(HashSet::new));
    }

Lukas Wiest's avatar
Lukas Wiest committed
234
    public static ResultSummary generateResultSummary(TestExecutionSummary summary, Set<TestIdentifier> successes)
Lukas Wiest's avatar
Lukas Wiest committed
235
236
237
238
239
240
241
242
243
244
245
246
247
248
    {
        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("-")
        ));

Lukas Wiest's avatar
Lukas Wiest committed
249
        ResultSummary resultSummary = new ResultSummary();
Lukas Wiest's avatar
Lukas Wiest committed
250
        resultSummary.results.addAll(successes.stream().map(s ->
Lukas Wiest's avatar
Lukas Wiest committed
251
        {
Lukas Wiest's avatar
Lukas Wiest committed
252
            Result result = new Result();
Lukas Wiest's avatar
Lukas Wiest committed
253
            result.name = s.getDisplayName();
Lukas Wiest's avatar
Lukas Wiest committed
254
            result.state = Result.State.SUCCESS.ordinal();
Lukas Wiest's avatar
Lukas Wiest committed
255
256
257

            return result;
        })
Lukas Wiest's avatar
Lukas Wiest committed
258
        .collect(Collectors.toCollection(HashSet::new)));
Lukas Wiest's avatar
Lukas Wiest committed
259

Lukas Wiest's avatar
Lukas Wiest committed
260
        resultSummary.results.addAll(summary.getFailures().stream().map(f ->
Lukas Wiest's avatar
Lukas Wiest committed
261
        {
Lukas Wiest's avatar
Lukas Wiest committed
262
            Result result = new Result();
Lukas Wiest's avatar
Lukas Wiest committed
263
            result.name = f.getTestIdentifier().getDisplayName();
Lukas Wiest's avatar
Lukas Wiest committed
264
            result.state = Result.State.FAILURE.ordinal();
Lukas Wiest's avatar
Lukas Wiest committed
265
266
267
268
269
270
271
272
273
274

            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;
        })
Lukas Wiest's avatar
Lukas Wiest committed
275
        .collect(Collectors.toCollection(HashSet::new)));
Lukas Wiest's avatar
Lukas Wiest committed
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310

        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;
    }

Lukas Wiest's avatar
Lukas Wiest committed
311
    public static ResultSummary runTests() throws MalformedURLException
Lukas Wiest's avatar
Lukas Wiest committed
312
    {
313
        classPathItems = buildClassPathItems(true);
Lukas Wiest's avatar
Lukas Wiest committed
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
        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();

Lukas Wiest's avatar
Lukas Wiest committed
330
            MySummaryGeneratingListener listener = new MySummaryGeneratingListener();
Lukas Wiest's avatar
Lukas Wiest committed
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
            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);
        }

Lukas Wiest's avatar
Lukas Wiest committed
357
        LOG.info("generate result summary from junit");
Lukas Wiest's avatar
Lukas Wiest committed
358
359
360
        return generateResultSummary(summary, successes);
    }

Lukas Wiest's avatar
Lukas Wiest committed
361
    public static void writeResult(ResultSummary resultSummary) throws IOException
Lukas Wiest's avatar
Lukas Wiest committed
362
363
    {
        Path fileName = Paths.get(resultFolder, "result.json");
Lukas Wiest's avatar
Lukas Wiest committed
364
        LOG.info(String.format("serializing result as json into %s", fileName.toAbsolutePath().toString()));
Lukas Wiest's avatar
Lukas Wiest committed
365
366
367
368
369
370
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper
            .writerWithDefaultPrettyPrinter()
            .writeValue(fileName.toFile(), resultSummary);
    }
}