package com.arellomobile.mvp.compiler; import com.google.common.collect.HashBasedTable; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Table; import com.google.common.io.Files; import com.google.common.io.Resources; import com.google.common.truth.Truth; import com.google.testing.compile.CompileTester; import com.google.testing.compile.JavaSourcesSubjectFactory; import org.junit.Before; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.net.URL; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Scanner; import java.util.regex.Pattern; import javax.annotation.processing.Processor; import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.StandardLocation; import javax.tools.ToolProvider; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** * Date: 25.02.2016 * Time: 11:40 * * @author Savin Mikhail */ public abstract class CompilerTest { private JavaCompiler javac; private DiagnosticCollector<JavaFileObject> diagnosticCollector; private StandardJavaFileManager fileManager; private File tmpDir; @Before public void setUp() { javac = ToolProvider.getSystemJavaCompiler(); diagnosticCollector = new DiagnosticCollector<>(); fileManager = javac.getStandardFileManager(diagnosticCollector, null, null); tmpDir = Files.createTempDir(); } protected CompileTester getThat(final JavaFileObject... target) { return getThat(Collections.singletonList(new MvpCompiler()), target); } protected CompileTester getThat(Collection<? extends Processor> processors, final JavaFileObject... target) { return Truth.assert_().about(JavaSourcesSubjectFactory.javaSources()) .that(Arrays.asList(target)) .processedWith(processors); } protected String getString(String filePath) throws IOException { URL resource = Resources.getResource(filePath); Scanner scanner = new Scanner(new ByteArrayInputStream(Resources.toByteArray(resource))); StringBuilder stringBuilder = new StringBuilder(); while (scanner.hasNextLine()) { stringBuilder.append(scanner.nextLine()); stringBuilder.append("\n"); } return stringBuilder.toString(); } protected void assertCompilationResultIs(Table<Diagnostic.Kind, Integer, Pattern> expectedDiagnostics, Collection<String> resources) throws IOException { assertCompilationResultIs(expectedDiagnostics, resources, MvpCompiler.class); } //For more info see https://github.com/google/auto/blob/master/value/src/test/java/com/google/auto/value/processor/CompilationErrorsTest.java protected void assertCompilationResultIs(Table<Diagnostic.Kind, Integer, Pattern> expectedDiagnostics, Collection<String> resources, final Class<? extends Processor> processor) throws IOException { StringWriter compilerOut = new StringWriter(); List<String> options = ImmutableList.of( "-sourcepath", tmpDir.getPath(), "-d", tmpDir.getPath(), "-processor", processor.getName(), "-Xlint"); javac.getTask(compilerOut, fileManager, diagnosticCollector, options, null, null); // This doesn't compile anything but communicates the paths to the JavaFileManager. // Convert the strings containing the source code of the test classes into files that we // can feed to the compiler. List<String> classNames = Lists.newArrayList(); List<JavaFileObject> sourceFiles = Lists.newArrayList(); for (String source : resources) { ClassName className = ClassName.extractFromSource(source); File dir = new File(tmpDir, className.sourceDirectoryName()); dir.mkdirs(); assertTrue(dir.isDirectory()); // True if we just made it, or it was already there. String sourceName = className.simpleName + ".java"; Files.write(source, new File(dir, sourceName), Charset.forName("UTF-8")); classNames.add(className.fullName()); JavaFileObject sourceFile = fileManager.getJavaFileForInput( StandardLocation.SOURCE_PATH, className.fullName(), JavaFileObject.Kind.SOURCE); sourceFiles.add(sourceFile); } assertEquals(classNames.size(), sourceFiles.size()); // Compile the classes. JavaCompiler.CompilationTask javacTask = javac.getTask( compilerOut, fileManager, diagnosticCollector, options, classNames, sourceFiles); boolean compiledOk = javacTask.call(); // Check that there were no compilation errors unless we were expecting there to be. // We ignore "notes", typically debugging output from the annotation processor // when that is enabled. Table<Diagnostic.Kind, Integer, String> diagnostics = HashBasedTable.create(); for (Diagnostic<?> diagnostic : diagnosticCollector.getDiagnostics()) { boolean ignore = (diagnostic.getKind() == Diagnostic.Kind.NOTE || (diagnostic.getKind() == Diagnostic.Kind.WARNING && diagnostic.getMessage(null).contains( "No processor claimed any of these annotations"))); if (!ignore) { diagnostics.put( diagnostic.getKind(), (int) diagnostic.getLineNumber(), diagnostic.getMessage(null)); } } assertEquals(diagnostics.containsRow(Diagnostic.Kind.ERROR), !compiledOk); assertEquals("Diagnostic kinds should match: " + diagnostics, expectedDiagnostics.rowKeySet(), diagnostics.rowKeySet()); for (Table.Cell<Diagnostic.Kind, Integer, Pattern> expectedDiagnostic : expectedDiagnostics.cellSet()) { boolean match = false; for (Table.Cell<Diagnostic.Kind, Integer, String> diagnostic : diagnostics.cellSet()) { if (expectedDiagnostic.getValue().matcher(diagnostic.getValue()).find()) { int expectedLine = expectedDiagnostic.getColumnKey(); if (expectedLine != 0) { int actualLine = diagnostic.getColumnKey(); if (actualLine != expectedLine) { fail("Diagnostic matched pattern but on line " + actualLine + " not line " + expectedLine + ": " + diagnostic.getValue()); } } match = true; break; } } assertTrue("Diagnostics should contain " + expectedDiagnostic + ": " + diagnostics, match); } } private static class ClassName { final String packageName; // Package name with trailing dot. May be empty but not null. final String simpleName; private ClassName(String packageName, String simpleName) { this.packageName = packageName; this.simpleName = simpleName; } // Extract the package and simple name of the top-level class defined in the given string, // which is a Java sourceUnit unit. static ClassName extractFromSource(String sourceUnit) { String pkg; if (sourceUnit.contains("package ")) { // (?s) means that . matches everything including \n pkg = sourceUnit.replaceAll("(?s).*?package ([a-z.]+);.*", "$1") + "."; } else { pkg = ""; } String cls = sourceUnit.replaceAll("(?s).*?(class|interface|enum) ([A-Za-z0-9_$]+).*", "$2"); assertTrue(cls, cls.matches("[A-Za-z0-9_$]+")); return new ClassName(pkg, cls); } String fullName() { return packageName + simpleName; } String sourceDirectoryName() { return packageName.replace('.', '/'); } } }