/* * Copyright 2016 Red Hat, Inc. and/or its affiliates. * * 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.uberfire.annotations.processors; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; 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; import org.junit.Test; import static org.junit.Assert.*; /** * Base miscfeatures to generate source code with an Annotation Processor */ public abstract class AbstractProcessorTest { private static final String SOURCE_FILETYPE = ".java"; /** * Compile a unit of source code with the specified annotation processor * @param annotationProcessor * @param compilationUnits * @return */ public List<Diagnostic<? extends JavaFileObject>> compile(final Processor annotationProcessor, final String... compilationUnits) { final DiagnosticCollector<JavaFileObject> diagnosticListener = new DiagnosticCollector<JavaFileObject>(); try { final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnosticListener, null, null); String[] convertedCompilationUnits = convertCompilationUnitToFilePaths(compilationUnits); final Iterable<? extends JavaFileObject> compilationUnitsJavaObjects = fileManager.getJavaFileObjects(convertedCompilationUnits); //Compile with provide annotation processor final CompilationTask task = compiler.getTask(null, fileManager, diagnosticListener, null, null, compilationUnitsJavaObjects); task.setProcessors(Arrays.asList(annotationProcessor)); task.call(); fileManager.close(); } catch (IOException ioe) { fail(ioe.getMessage()); } return diagnosticListener.getDiagnostics().stream().filter(p -> p.getKind() != Kind.NOTE).collect(Collectors.toList()); } private String[] convertCompilationUnitToFilePaths(String[] compilationUnits) { List<String> convertedCompilationUnits = new ArrayList<String>(); for (String compilationUnit : compilationUnits) { convertedCompilationUnits.add(this.getClass().getResource("/" + compilationUnit + SOURCE_FILETYPE).getPath()); } return convertedCompilationUnits.toArray(new String[convertedCompilationUnits.size()]); } /** * Retrieve the expected source code for a compilation unit * @param compilationUnit * @return * @throws FileNotFoundException */ public String getExpectedSourceCode(final String compilationUnit) throws FileNotFoundException { StringBuilder sb = new StringBuilder(); try { final String path = this.getClass().getResource("/" + compilationUnit).getPath(); final FileReader fr = new FileReader(path); final BufferedReader input = new BufferedReader(fr); try { String line = null; while ((line = input.readLine()) != null) { sb.append(line); sb.append(System.getProperty("line.separator")); } } finally { input.close(); } } catch (FileNotFoundException fnfe) { throw fnfe; } catch (IOException ioe) { fail(ioe.getMessage()); } return sb.toString(); } /** * Assert that compilation was successful * @param diagnostics */ public void assertSuccessfulCompilation(final List<Diagnostic<? extends JavaFileObject>> diagnostics) { assertFalse(diagnostics.toString(), hasErrors(diagnostics)); } /** * Assert that compilation failed * @param diagnostics */ public void assertFailedCompilation(final List<Diagnostic<? extends JavaFileObject>> diagnostics) { assertTrue(hasErrors(diagnostics)); } private boolean hasErrors(final List<Diagnostic<? extends JavaFileObject>> diagnostics) { for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) { if (diagnostic.getKind().equals(Kind.ERROR)) { return true; } } return false; } /** * Assert that the given error message is contained in the compilation * diagnostics. * @param diagnostics the list of diagnostic messages from the compiler. Must not be null. * @param kind the kind of message to search for, or null to search messages of * any kind. * @param line the line number that must be attached to the message, or * {@link Diagnostic#NOPOS} if line number is not important. * @param col the column number that must be attached to the message, or * {@link Diagnostic#NOPOS} if column number is not important. * @param message the message to search for. If any otherwise matching message in * the given list contains this string, the assertion passes. Must not be null. */ public void assertCompilationMessage(List<Diagnostic<? extends JavaFileObject>> diagnostics, Kind kind, long line, long col, final String message) { StringBuilder sb = new StringBuilder(100); for (Diagnostic<? extends JavaFileObject> msg : diagnostics) { sb.append(msg.getKind()) .append(" ") .append(msg.getLineNumber()) .append(":") .append(msg.getColumnNumber()) .append(": ") .append(msg.getMessage(null)) .append("\n"); if ((kind == null || msg.getKind().equals(kind)) && (line == Diagnostic.NOPOS || msg.getLineNumber() == line) && (col == Diagnostic.NOPOS || msg.getColumnNumber() == col) && msg.getMessage(null).contains(message)) { return; } } fail("Compiler diagnostics did not contain " + kind + " message " + line + ":" + col + ": " + message + "\n" + "Dump of all " + diagnostics.size() + " actual messages:\n" + sb); } /** * Returns the annotation processor being tested by the current test. This processor should be * created with a GenerationCompleteCallback that will capture the output of the processor so it can be examined * by test assertions. */ protected abstract AbstractErrorAbsorbingProcessor getProcessorUnderTest(); /** * Regression test for UF-44: Annotation Processors can put eclipse into an infinite loop. */ @Test public void shouldNotAllowClassNotFoundExceptionThrough() throws Exception { AbstractErrorAbsorbingProcessor processorUnderTest = null; try { AbstractGenerator.FAIL_FOR_TESTING = true; processorUnderTest = getProcessorUnderTest(); } catch (Throwable t) { t.printStackTrace(); fail("The annotation processor's constructor threw an exception. This is bad for Eclipse!"); } finally { AbstractGenerator.FAIL_FOR_TESTING = false; } // ensure the error message was preserved so the user has a hope of tracking down the problem! List<Diagnostic<? extends JavaFileObject>> messages = compile(processorUnderTest, "org/uberfire/annotations/processors/AnnotatedWithEverything"); assertCompilationMessage(messages, Kind.ERROR, Diagnostic.NOPOS, Diagnostic.NOPOS, "Failing for testing purposes"); } /** * Container for miscfeatures results. */ public class Result { private String expectedCode; private String actualCode; public String getExpectedCode() { return expectedCode; } public void setExpectedCode(final String expectedCode) { this.expectedCode = expectedCode; } public String getActualCode() { return actualCode; } public void setActualCode(final String actualCode) { this.actualCode = actualCode; } } }