org.androidannotations.testutils.ProcessorTestHelper Maven / Gradle / Ivy
Show all versions of androidannotations-testutils Show documentation
/**
 * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
 * Copyright (C) 2016-2020 the AndroidAnnotations project
 *
 * 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> diagnostics;
		public CompileResult(List> 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 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 compilerOptions = new ArrayList<>();
	private final List> 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.
	 * 
	 * 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 files = new ArrayList<>();
		addCollection(files, compilationUnits);
		return compileFiles(files);
	}
	public CompileResult compileFiles(Object... elements) {
		assert elements != null;
		List 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));
			} else if (element instanceof String[]) {
				for (String subElement : (String[]) element) {
					files.add(new File(subElement));
				}
			}
		}
		return compileFiles(files);
	}
	public CompileResult compileFiles(File... compilationUnits) {
		return compileFiles(Arrays.asList(compilationUnits));
	}
	public CompileResult compileFiles(Collection compilationUnits) {
		DiagnosticCollector 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 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  void addCollection(List files, Collection compilationUnits) {
		if (compilationUnits == null) {
			return;
		}
		addCollection(files, compilationUnits.toArray(new Type[] {}));
	}
	private  void addCollection(List files, T... compilationUnits) {
		if (compilationUnits == null) {
			return;
		}
		for (T element : compilationUnits) {
			addCollection(files, element);
		}
	}
	private  void addCollection(List 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");
	}
}