/** * Copyright (C) 2010-2016 eBusiness Information, Excilys Group * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed To in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.androidannotations.testutils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; import javax.annotation.processing.Processor; import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; /** * Based on http://code.google.com/p/acris/wiki/AnnotationProcessing_Testing */ public class ProcessorTestHelper { public static class CompileResult { private final List<Diagnostic<? extends JavaFileObject>> diagnostics; public CompileResult(List<Diagnostic<? extends JavaFileObject>> diagnostics) { this.diagnostics = diagnostics; } } private static final String TEST_SOURCE_FOLDER = "src/test/java"; private static final String MAIN_SOURCE_FOLDER = "src/main/java"; protected static final String SOURCE_FILE_SUFFIX = ".java"; protected static final String OUTPUT_DIRECTORY = "target/generated-test"; public static void assertGeneratedClassMatches(File output, String value) { String[] outputContent = getContents(output); for (String line : outputContent) { if (line.matches(value)) { return; } } fail("Expected value \"" + value + "\" couldn't be found in file " + output.getAbsolutePath()); } public static void assertGeneratedClassDoesntMatches(File output, String value) { String[] outputContent = getContents(output); for (String line : outputContent) { if (line.matches(value)) { fail("Value \"" + value + "\" shouldn't be in file " + output.getAbsolutePath()); } } } public static void assertGeneratedClassContains(File output, String[] codeFragment) { assertTrue("Code fragment \"" + join(codeFragment) + "\" should be in file " + output.getAbsolutePath(), Collections.indexOfSubList(Arrays.asList(getContents(output)), Arrays.asList(codeFragment)) != -1); } public static void assertGeneratedClassDoesNotContain(File output, String[] codeFragment) { assertTrue("Code fragment \"" + join(codeFragment) + "\" should not be in file " + output.getAbsolutePath(), Collections.indexOfSubList(Arrays.asList(getContents(output)), Arrays.asList(codeFragment)) == -1); } public static void assertOutput(File expectedResult, File output) { String[] expectedContent = getContents(expectedResult); String[] outputContent = getContents(output); assertEquals(expectedContent.length, outputContent.length); for (int i = 0; i < expectedContent.length; i++) { assertEquals(expectedContent[i].trim(), outputContent[i].trim()); } } public static void assertClassSourcesGeneratedToOutput(Class<?> clazz) { String canonicalName = clazz.getCanonicalName(); String filePath = canonicalName.replace(".", "/").concat(".java"); File generatedSourcesDir = new File(OUTPUT_DIRECTORY); File generatedSourceFile = new File(generatedSourcesDir, filePath); File sourcesDir = new File(MAIN_SOURCE_FOLDER); File expectedResult = new File(sourcesDir, filePath); assertOutput(expectedResult, generatedSourceFile); } public static void assertClassSourcesNotGeneratedToOutput(Class<?> clazz) { String canonicalName = clazz.getCanonicalName(); String filePath = canonicalName.replace(".", "/").concat(".java"); File generatedSourcesDir = new File(OUTPUT_DIRECTORY); File output = new File(generatedSourcesDir, filePath); assertFalse(output.exists()); } public static void assertCompilationSuccessful(CompileResult result) { for (Diagnostic<? extends JavaFileObject> diagnostic : result.diagnostics) { assertFalse("Expected no errors, found " + diagnostic, diagnostic.getKind().equals(Kind.ERROR)); } } public static void assertCompilationError(CompileResult result) { for (Diagnostic<? extends JavaFileObject> diagnostic : result.diagnostics) { if (diagnostic.getKind() == Kind.ERROR) { return; } } fail("Expected a compilation error, diagnostics: " + result.diagnostics); } public static void assertCompilationErrorWithNoSource(CompileResult result) { for (Diagnostic<? extends JavaFileObject> diagnostic : result.diagnostics) { if (diagnostic.getKind() == Kind.ERROR && diagnostic.getSource() == null) { return; } } fail("Expected a compilation error with no source, diagnostics: " + result.diagnostics); } public static void assertCompilationErrorCount(int expectedErrorCount, CompileResult result) { int errorCount = 0; for (Diagnostic<? extends JavaFileObject> diagnostic : result.diagnostics) { if (diagnostic.getKind() == Kind.ERROR) { errorCount++; } } if (errorCount != expectedErrorCount) { fail("Expected " + expectedErrorCount + " compilation error, found " + errorCount + " diagnostics: " + result.diagnostics); } } public static void assertCompilationErrorOn(File expectedErrorClassFile, String expectedContentInError, CompileResult result) throws IOException { assertCompilationDiagnostingOn(Kind.ERROR, expectedErrorClassFile, expectedContentInError, result); } public static void assertCompilationErrorOn(String expectedClassName, String expectedContentInError, CompileResult result) throws IOException { assertCompilationDiagnostingOn(Kind.ERROR, new File(expectedClassName + ".java"), expectedContentInError, result); } public static void assertCompilationWarningOn(File expectedErrorClassFile, String expectedContentInError, CompileResult result) throws IOException { assertCompilationDiagnostingOn(Kind.WARNING, expectedErrorClassFile, expectedContentInError, result); } private static void assertCompilationDiagnostingOn(Kind expectedDiagnosticKind, File expectedErrorClassFile, String expectedContentInError, CompileResult result) throws IOException { String expectedErrorPath; boolean fileNameOnly = expectedErrorClassFile.getPath().split(Pattern.quote(File.separator)).length == 1; if (fileNameOnly) { // this is just the filename expectedErrorPath = expectedErrorClassFile.getPath(); } else { expectedErrorPath = expectedErrorClassFile.toURI().toString(); } for (Diagnostic<? extends JavaFileObject> diagnostic : result.diagnostics) { if (diagnostic.getKind() == expectedDiagnosticKind) { JavaFileObject source = diagnostic.getSource(); if (source != null) { if (expectedErrorPath.endsWith(source.toUri().toString()) || fileNameOnly && source.toUri().toString().endsWith(expectedErrorPath)) { CharSequence sourceContent = source.getCharContent(true); if (diagnostic.getPosition() != Diagnostic.NOPOS) { CharSequence contentInError = sourceContent.subSequence((int) diagnostic.getStartPosition(), (int) diagnostic.getEndPosition()); if (contentInError.toString().contains(expectedContentInError)) { return; } } } } } } fail("Expected a compilation " + expectedDiagnosticKind + " in " + expectedErrorClassFile.toString() + " on " + expectedContentInError + ", diagnostics: " + result.diagnostics); } private static String[] getContents(File file) { List<String> content = new ArrayList<>(); try (BufferedReader input = new BufferedReader(new FileReader(file))) { String line = null; // not declared within while loop while ((line = input.readLine()) != null) { content.add(line); } } catch (IOException ex) { ex.printStackTrace(); } return content.toArray(new String[] {}); } private final List<String> compilerOptions = new ArrayList<>(); private final List<Class<? extends Processor>> processorsClasses = new ArrayList<>(); public ProcessorTestHelper() { compilerOptions.add("-classpath"); compilerOptions.add(getClassPath()); compilerOptions.add("-s"); String outputPath = ensureOutputDirectory().getAbsolutePath(); compilerOptions.add(outputPath); compilerOptions.add("-d"); compilerOptions.add(outputPath); } public File getOuputDirectory() { return ensureOutputDirectory(); } public void addProcessor(Class<? extends Processor> processorClass) { processorsClasses.add(processorClass); } public void addProcessorParameter(String key, String value) { addCompilerOptions("-A" + key + "=" + value); } public final void addCompilerOptions(String... compilerOptions) { for (String compilerOption : compilerOptions) { this.compilerOptions.add(compilerOption); } } public String toPath(Package packageName) { return toPath(packageName.getName()); } public String toPath(String packageName) { return packageName.replace(".", "/"); } /** * Attempts to compile the given compilation units using the Java Compiler * API. * <p> * The compilation units and all their dependencies are expected to be on * the classpath. * * @param compilationUnits * the classes to compile * @return the {@link Diagnostic diagnostics} returned by the compilation, * as demonstrated in the documentation for {@link JavaCompiler} */ public CompileResult compileFiles(Type... compilationUnits) { assert compilationUnits != null; List<File> files = new ArrayList<>(); addCollection(files, compilationUnits); return compileFiles(files); } public CompileResult compileFiles(Object... elements) { assert elements != null; List<File> files = new ArrayList<>(); for (Object element : elements) { if (element instanceof Type) { addCollection(files, (Type) element); } else if (element instanceof String) { files.add(new File((String) element)); } } return compileFiles(files); } public CompileResult compileFiles(File... compilationUnits) { return compileFiles(Arrays.asList(compilationUnits)); } public CompileResult compileFiles(Collection<File> compilationUnits) { DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>(); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnosticCollector, null, null)) { CompilationTask task = compiler.getTask(null, fileManager, diagnosticCollector, compilerOptions, null, fileManager.getJavaFileObjectsFromFiles(compilationUnits)); List<Processor> processors = new ArrayList<>(); for (Class<? extends Processor> processorClass : processorsClasses) { try { processors.add(processorClass.newInstance()); } catch (Exception e) { throw new RuntimeException(e); } } task.setProcessors(processors); task.call(); } catch (IOException e) { // we should always be able to close the manager } return new CompileResult(diagnosticCollector.getDiagnostics()); } public void assertCompilationErrorOn(Class<?> expectedErrorClass, String expectedContentInError, CompileResult result) throws IOException { assertCompilationErrorOn(toFile(expectedErrorClass), expectedContentInError, result); } public void assertCompilationWarningOn(Class<?> expectedErrorClass, String expectedContentInError, CompileResult result) throws IOException { assertCompilationWarningOn(toFile(expectedErrorClass), expectedContentInError, result); } private File ensureOutputDirectory() { File outputDir = new File(OUTPUT_DIRECTORY); if (!outputDir.exists()) { outputDir.mkdirs(); } return outputDir; } public void ensureOutputDirectoryIsEmpty() { File outputDir = new File(OUTPUT_DIRECTORY); String[] childs = outputDir.list(); if (childs != null && childs.length > 0) { deleteDirectoryRecursively(outputDir); outputDir.mkdirs(); } } private void deleteDirectoryRecursively(File directory) { File[] childs = directory.listFiles(); if (childs != null) { for (File file : childs) { if (file.isDirectory()) { deleteDirectoryRecursively(file); } else { file.delete(); } } } directory.delete(); } private <T extends AnnotatedElement> void addCollection(List<File> files, Collection<T> compilationUnits) { if (compilationUnits == null) { return; } addCollection(files, compilationUnits.toArray(new Type[] {})); } private <T extends Type> void addCollection(List<File> files, T... compilationUnits) { if (compilationUnits == null) { return; } for (T element : compilationUnits) { addCollection(files, element); } } private <T extends Type> void addCollection(List<File> files, T element) { assert element != null; if (element instanceof Class<?>) { File file = toFile((Class<?>) element); if (file != null) { files.add(file); } else { // These are innerclasses, etc ... that should not be // defined in this way } } else if (element instanceof Package) { ClassFinder classFinder = new ClassFinder(); addCollection(files, classFinder.findClassesInPackage(((Package) element).getName())); } } private String convertClassNameToResourcePath(String name) { return name.replace(".", File.separator); } public File toFile(Class<?> clazz) { File file = new File(TEST_SOURCE_FOLDER + File.separator + convertClassNameToResourcePath(clazz.getCanonicalName()) + SOURCE_FILE_SUFFIX); if (!file.exists()) { file = new File(MAIN_SOURCE_FOLDER + File.separator + convertClassNameToResourcePath(clazz.getCanonicalName()) + SOURCE_FILE_SUFFIX); if (!file.exists()) { return null; } } return file; } protected String getClassPath() { String classPath = System.getProperty("maven.test.class.path"); if (classPath == null || classPath.length() == 0) { return System.getProperty("java.class.path"); } classPath = classPath.replaceAll(", ", isWindows() ? ";" : ":").trim(); return "\"" + classPath.substring(1, classPath.length() - 2).trim() + ";" + new File("target\\classes").getAbsolutePath() + "\""; } private String getOsName() { return System.getProperty("os.name"); } private boolean isWindows() { return getOsName().startsWith("Windows"); } private static String join(String[] array) { return Arrays.toString(array).replaceAll(",", "\n"); } }