package org.checkerframework.framework.test; import java.io.File; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import org.checkerframework.framework.test.diagnostics.JavaDiagnosticReader; import org.checkerframework.framework.test.diagnostics.TestDiagnostic; import org.checkerframework.framework.util.PluginUtil; /** Used by the Checker Framework test suite to run the framework and generate a test result. */ public class TypecheckExecutor { public TypecheckExecutor() {} /** Runs a typechecking test using the given configuration and returns the test result */ public TypecheckResult runTest(TestConfiguration configuration) { CompilationResult result = compile(configuration); return interpretResults(configuration, result); } /** * Using the settings from the input configuration, compile all source files in the * configuration, and return place the result in a CompilationResult */ public CompilationResult compile(TestConfiguration configuration) { TestUtilities.ensureDirectoryExists(new File(configuration.getOptions().get("-d"))); final StringWriter javacOutput = new StringWriter(); DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>(); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); Iterable<? extends JavaFileObject> javaFiles = fileManager.getJavaFileObjects( configuration.getTestSourceFiles().toArray(new File[] {})); // Even though the method compiler.getTask takes a list of processors, it fails if processors are passed this way // with the message: // error: Class names, 'org.checkerframework.checker.interning.InterningChecker', are only accepted if // annotation processing is explicitly requested // Therefore, we now add them to the beginning of the options list final List<String> options = new ArrayList<String>(); options.add("-processor"); options.add(PluginUtil.join(",", configuration.getProcessors())); List<String> nonJvmOptions = new ArrayList<String>(); for (String option : configuration.getFlatOptions()) { if (!option.startsWith("-J-")) { nonJvmOptions.add(option); } } nonJvmOptions.add("-Xmaxerrs"); nonJvmOptions.add("100000"); nonJvmOptions.add("-Xmaxwarns"); nonJvmOptions.add("100000"); options.addAll(nonJvmOptions); if (configuration.shouldEmitDebugInfo()) { System.out.println("Running test using the following invocation:"); System.out.println( "javac " + PluginUtil.join(" ", options) + " " + PluginUtil.join(" ", configuration.getTestSourceFiles())); } JavaCompiler.CompilationTask task = compiler.getTask( javacOutput, fileManager, diagnostics, options, new ArrayList<String>(), javaFiles); /* * In Eclipse, std out and std err for multiple tests appear as one * long stream. When selecting a specific failed test, one sees the * expected/unexpected messages, but not the std out/err messages from * that particular test. Can we improve this somehow? */ final Boolean compiledWithoutError = task.call(); javacOutput.flush(); return new CompilationResult( compiledWithoutError, javacOutput.toString(), javaFiles, diagnostics.getDiagnostics()); } /** * Reads the expected diagnostics for the given configuration and creates a TypecheckResult * which contains all of the missing and expected diagnostics */ public TypecheckResult interpretResults( TestConfiguration config, CompilationResult compilationResult) { List<TestDiagnostic> expectedDiagnostics = readDiagnostics(config, compilationResult); return TypecheckResult.fromCompilationResults( config, compilationResult, expectedDiagnostics); } /** * Added in case a subclass wishes to filter out errors or add new expected errors. This method * is called immediately before results are checked. */ protected List<TestDiagnostic> readDiagnostics( TestConfiguration config, CompilationResult compilationResult) { List<TestDiagnostic> expectedDiagnostics; if (config.getDiagnosticFiles() == null || config.getDiagnosticFiles().isEmpty()) { expectedDiagnostics = JavaDiagnosticReader.readExpectedDiagnosticsJfo( compilationResult.getJavaFileObjects(), true); } else { expectedDiagnostics = JavaDiagnosticReader.readDiagnosticFiles(config.getDiagnosticFiles(), true); } return expectedDiagnostics; } }