blob: dfce8c7dada1ae18fabfff67c9e4799d3655c811 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2017 BEA Systems, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* wharley@bea.com - initial API and implementation
* IBM Corporation - fix for 342936
* het@google.com - Bug 415274 - Annotation processing throws a NPE in getElementsAnnotatedWith()
*******************************************************************************/
package org.eclipse.jdt.compiler.apt.tests;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.ServiceLoader;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Platform;
/**
* Helper class to support compilation and results checking for tests running in batch mode.
* @since 3.3
*/
public class BatchTestUtils {
private static final String RESOURCES_DIR = "resources";
// relative to plugin directory
private static final String PROCESSOR_JAR_NAME = "lib/apttestprocessors.jar";
private static final String JLS8_PROCESSOR_JAR_NAME = "lib/apttestprocessors8.jar";
public static String _processorJarPath;
public static String _jls8ProcessorJarPath;
// locations to copy and generate files
private static String _tmpFolder;
private static JavaCompiler _eclipseCompiler;
private static String _tmpSrcFolderName;
private static File _tmpSrcDir;
private static String _tmpBinFolderName;
private static File _tmpBinDir;
public static String _tmpGenFolderName;
private static File _tmpGenDir;
/**
* Create a class that contains an annotation that generates another class,
* and compile it. Verify that generation and compilation succeeded.
*/
public static void compileOneClass(JavaCompiler compiler, List<String> options, File inputFile) {
compileOneClass(compiler, options, inputFile, false);
}
public static void compileOneClass(JavaCompiler compiler, List<String> options, File inputFile, boolean useJLS8Processors) {
StandardJavaFileManager manager = compiler.getStandardFileManager(null, Locale.getDefault(), Charset.defaultCharset());
// create new list containing inputfile
List<File> files = new ArrayList<File>();
files.add(inputFile);
Iterable<? extends JavaFileObject> units = manager.getJavaFileObjectsFromFiles(files);
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
options.add("-d");
options.add(_tmpBinFolderName);
options.add("-s");
options.add(_tmpGenFolderName);
addProcessorPaths(options, useJLS8Processors, true);
options.add("-XprintRounds");
options.add("-XprintProcessorInfo");
CompilationTask task = compiler.getTask(printWriter, manager, null, options, null, units);
Boolean result = task.call();
if (!result.booleanValue()) {
String errorOutput = stringWriter.getBuffer().toString();
System.err.println("Compilation failed: " + errorOutput);
junit.framework.TestCase.assertTrue("Compilation failed : " + errorOutput, false);
}
try {
manager.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void compileTree(JavaCompiler compiler, List<String> options, File targetFolder) {
compileTree(compiler, options, targetFolder, false);
}
public static void compileTree(JavaCompiler compiler, List<String> options, File targetFolder,
DiagnosticListener<? super JavaFileObject> listener) {
compileTree(compiler, options, targetFolder, false, listener);
}
public static void compileTree(JavaCompiler compiler, List<String> options, File targetFolder, boolean useJLS8Processors) {
compileTree(compiler, options, targetFolder, useJLS8Processors, null);
}
public static void compileInModuleMode(JavaCompiler compiler, List<String> options, String processor,
File targetFolder, DiagnosticListener<? super JavaFileObject> listener, boolean multiModule) {
StandardJavaFileManager manager = compiler.getStandardFileManager(null, Locale.getDefault(), Charset.defaultCharset());
Iterable<? extends File> location = manager.getLocation(StandardLocation.CLASS_PATH);
// create new list containing inputfile
List<File> files = new ArrayList<File>();
findFilesUnder(targetFolder, files);
Iterable<? extends JavaFileObject> units = manager.getJavaFileObjectsFromFiles(files);
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
List<String> copyOptions = new ArrayList<>();
copyOptions.add("-processor");
copyOptions.add(processor);
copyOptions.add("-A" + processor);
copyOptions.add("-d");
copyOptions.add(_tmpBinFolderName);
copyOptions.add("-s");
copyOptions.add(_tmpGenFolderName);
addModuleProcessorPath(copyOptions, getSrcFolderName(), multiModule);
copyOptions.add("-XprintRounds");
CompilationTask task = compiler.getTask(printWriter, manager, listener, copyOptions, null, units);
Boolean result = task.call();
if (!result.booleanValue()) {
String errorOutput = stringWriter.getBuffer().toString();
System.err.println("Compilation failed: " + errorOutput);
junit.framework.TestCase.assertTrue("Compilation failed : " + errorOutput, false);
}
List<String> classes = new ArrayList<>();
try {
System.clearProperty(processor);
copyOptions = new ArrayList<>();
copyOptions.addAll(options);
copyOptions.add("-cp");
copyOptions.add(_jls8ProcessorJarPath + File.pathSeparator + _tmpGenFolderName);
copyOptions.add("--processor-module-path");
copyOptions.add(_jls8ProcessorJarPath);
copyOptions.add("--module-path");
copyOptions.add(_tmpBinFolderName);
classes.add("java.base/java.lang.Object"); // This is required to make sure BTB for Object is fully populated.
findClassesUnderModules(Paths.get(_tmpBinFolderName), classes);
manager.setLocation(StandardLocation.CLASS_PATH, location);
task = compiler.getTask(printWriter, manager, listener, copyOptions, classes, null);
result = task.call();
if (!result.booleanValue()) {
String errorOutput = stringWriter.getBuffer().toString();
System.err.println("Compilation failed: " + errorOutput);
junit.framework.TestCase.assertTrue("Compilation failed : " + errorOutput, false);
}
} catch (IOException e) {
// print the stack just in case.
e.printStackTrace();
}
try {
manager.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void compileTree(JavaCompiler compiler, List<String> options,
File targetFolder, boolean useJLS8Processors,
DiagnosticListener<? super JavaFileObject> listener) {
StandardJavaFileManager manager = compiler.getStandardFileManager(null, Locale.getDefault(), Charset.defaultCharset());
// create new list containing inputfile
List<File> files = new ArrayList<File>();
findFilesUnder(targetFolder, files);
Iterable<? extends JavaFileObject> units = manager.getJavaFileObjectsFromFiles(files);
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
options.add("-d");
options.add(_tmpBinFolderName);
options.add("-s");
options.add(_tmpGenFolderName);
addProcessorPaths(options, useJLS8Processors, true);
options.add("-XprintRounds");
CompilationTask task = compiler.getTask(printWriter, manager, listener, options, null, units);
Boolean result = task.call();
if (!result.booleanValue()) {
String errorOutput = stringWriter.getBuffer().toString();
System.err.println("Compilation failed: " + errorOutput);
junit.framework.TestCase.assertTrue("Compilation failed : " + errorOutput, false);
}
try {
manager.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/*
* First compiles the given files without processor, then processes them
* with the just compiled binaries.
*/
public static void compileTreeAndProcessBinaries(JavaCompiler compiler, List<String> options, String processor,
File targetFolder, DiagnosticListener<? super JavaFileObject> listener) {
StandardJavaFileManager manager = compiler.getStandardFileManager(null, Locale.getDefault(), Charset.defaultCharset());
Iterable<? extends File> location = manager.getLocation(StandardLocation.CLASS_PATH);
// create new list containing inputfile
List<File> files = new ArrayList<File>();
findFilesUnder(targetFolder, files);
Iterable<? extends JavaFileObject> units = manager.getJavaFileObjectsFromFiles(files);
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
List<String> copyOptions = new ArrayList<>();
copyOptions.addAll(options);
copyOptions.add("-d");
copyOptions.add(_tmpBinFolderName);
copyOptions.add("-s");
copyOptions.add(_tmpGenFolderName);
addProcessorPaths(copyOptions, true, true);
options.add("-XprintRounds");
CompilationTask task = compiler.getTask(printWriter, manager, listener, copyOptions, null, units);
Boolean result = task.call();
if (!result.booleanValue()) {
String errorOutput = stringWriter.getBuffer().toString();
System.err.println("Compilation failed: " + errorOutput);
junit.framework.TestCase.assertTrue("Compilation failed : " + errorOutput, false);
}
List<String> classes = new ArrayList<>();
try {
System.clearProperty(processor);
copyOptions = new ArrayList<>();
copyOptions.addAll(options);
copyOptions.add("-cp");
copyOptions.add(_tmpBinFolderName + File.pathSeparator + _jls8ProcessorJarPath + File.pathSeparator + _tmpGenFolderName);
copyOptions.add("-processorpath");
copyOptions.add(_jls8ProcessorJarPath);
classes.add("java.lang.Object"); // This is required to make sure BTB for Object is fully populated.
findClassesUnder(Paths.get(_tmpBinFolderName), null, classes, null);
manager.setLocation(StandardLocation.CLASS_PATH, location);
task = compiler.getTask(printWriter, manager, listener, copyOptions, classes, null);
result = task.call();
if (!result.booleanValue()) {
String errorOutput = stringWriter.getBuffer().toString();
System.err.println("Compilation failed: " + errorOutput);
junit.framework.TestCase.assertTrue("Compilation failed : " + errorOutput, false);
}
} catch (IOException e) {
// print the stack just in case.
e.printStackTrace();
}
}
/**
* Compile the contents of a directory tree, collecting errors so that they can be
* compared with expected errors.
* @param compiler the system compiler or Eclipse compiler
* @param options will be passed to the compiler
* @param targetFolder the folder to compile
* @param errors a StringWriter into which compiler output will be written
* @return true if the compilation was successful
*/
public static boolean compileTreeWithErrors(
JavaCompiler compiler,
List<String> options,
File targetFolder,
DiagnosticListener<? super JavaFileObject> diagnosticListener) {
return compileTreeWithErrors(compiler, options, targetFolder, diagnosticListener, false, true);
}
public static boolean compileTreeWithErrors(
JavaCompiler compiler,
List<String> options,
File targetFolder,
DiagnosticListener<? super JavaFileObject> diagnosticListener,
boolean addProcessorsToClasspath) {
return compileTreeWithErrors(compiler, options, targetFolder, diagnosticListener, false, addProcessorsToClasspath);
}
public static boolean compileTreeWithErrors(
JavaCompiler compiler,
List<String> options,
File targetFolder,
DiagnosticListener<? super JavaFileObject> diagnosticListener,
boolean useJLS8Processors, boolean addProcessorsToClasspath) {
StandardJavaFileManager manager = compiler.getStandardFileManager(null, Locale.getDefault(), Charset.defaultCharset());
// create new list containing inputfile
List<File> files = new ArrayList<File>();
findFilesUnder(targetFolder, files);
Iterable<? extends JavaFileObject> units = manager.getJavaFileObjectsFromFiles(files);
options.add("-d");
options.add(_tmpBinFolderName);
options.add("-s");
options.add(_tmpGenFolderName);
addProcessorPaths(options, useJLS8Processors, addProcessorsToClasspath);
// use writer to prevent System.out/err to be polluted with problems
StringWriter writer = new StringWriter();
CompilationTask task = compiler.getTask(writer, manager, diagnosticListener, options, null, units);
Boolean result = task.call();
try {
manager.close();
} catch (IOException e) {
e.printStackTrace();
}
return result.booleanValue();
}
/**
* Recursively collect all the files under some root. Ignore directories named "CVS".
* Used when compiling multiple source files.
* @param files a List<File> to which all the files found will be added
* @return the set of Files under a root folder.
*/
public static void findFilesUnder(File rootFolder, List<File> files) {
for (File child : rootFolder.listFiles()) {
if ("CVS".equals(child.getName())) {
continue;
}
if (child.isDirectory()) {
findFilesUnder(child, files);
}
else {
files.add(child);
}
}
}
protected static void findClassesUnderModules(Path modulePath, List<String> classes) throws IOException {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(modulePath)) {
for (Path entry : stream) {
if (Files.isDirectory(entry)) {
findClassesUnder(entry, entry, classes, entry.getFileName().toString());
}
}
}
}
protected static void findClassesUnder(Path root, Path folder, List<String> classes, String moduleName) throws IOException {
if (folder == null)
folder = root;
try (DirectoryStream<Path> stream = Files.newDirectoryStream(folder)) {
for (Path entry : stream) {
if (Files.isDirectory(entry)) {
findClassesUnder(root, entry, classes, moduleName);
} else {
String fileName = entry.getFileName().toString();
if (fileName.endsWith(".class") && !fileName.startsWith("module-info")) {
String className = root.relativize(entry).toString();
className = className.substring(0, className.indexOf(".class"));
className = className.replace(File.separatorChar, '.');
if (moduleName != null) {
className = moduleName + "/" + className;
}
classes.add(className);
}
}
}
}
}
/** @return the name of the folder where class files will be saved */
public static String getBinFolderName() {
return _tmpBinFolderName;
}
public static JavaCompiler getEclipseCompiler() {
return _eclipseCompiler;
}
/** @return the name of the folder where generated files will be placed */
public static String getGenFolderName() {
return _tmpGenFolderName;
}
/** @return the name of the folder where source files will be found during compilation */
public static String getSrcFolderName() {
return _tmpSrcFolderName;
}
public static String getResourceFolderName() {
return RESOURCES_DIR;
}
/**
* Load Eclipse compiler and create temporary directories on disk
*/
public static void init()
{
_tmpFolder = System.getProperty("java.io.tmpdir");
if (_tmpFolder.endsWith(File.separator)) {
_tmpFolder += "eclipse-temp";
} else {
_tmpFolder += (File.separator + "eclipse-temp");
}
_tmpBinFolderName = _tmpFolder + File.separator + "bin";
_tmpBinDir = new File(_tmpBinFolderName);
BatchTestUtils.deleteTree(_tmpBinDir); // remove existing contents
_tmpBinDir.mkdirs();
assert _tmpBinDir.exists() : "couldn't mkdirs " + _tmpBinFolderName;
_tmpGenFolderName = _tmpFolder + File.separator + "gen-src";
_tmpGenDir = new File(_tmpGenFolderName);
BatchTestUtils.deleteTree(_tmpGenDir); // remove existing contents
_tmpGenDir.mkdirs();
assert _tmpGenDir.exists() : "couldn't mkdirs " + _tmpGenFolderName;
_tmpSrcFolderName = _tmpFolder + File.separator + "src";
_tmpSrcDir = new File(_tmpSrcFolderName);
BatchTestUtils.deleteTree(_tmpSrcDir); // remove existing contents
_tmpSrcDir.mkdirs();
assert _tmpSrcDir.exists() : "couldn't mkdirs " + _tmpSrcFolderName;
try {
_processorJarPath = setupProcessorJar(PROCESSOR_JAR_NAME, _tmpFolder);
_jls8ProcessorJarPath = setupProcessorJar(JLS8_PROCESSOR_JAR_NAME, _tmpFolder);
} catch (IOException e) {
e.printStackTrace();
}
junit.framework.TestCase.assertNotNull("No processor jar path set", _processorJarPath);
File processorJar = new File(_processorJarPath);
junit.framework.TestCase.assertTrue("Couldn't find processor jar at " + processorJar.getAbsolutePath(), processorJar.exists());
ServiceLoader<JavaCompiler> javaCompilerLoader = ServiceLoader.load(JavaCompiler.class);//, EclipseCompiler.class.getClassLoader());
Class<?> c = null;
try {
c = Class.forName("org.eclipse.jdt.internal.compiler.tool.EclipseCompiler");
} catch (ClassNotFoundException e) {
// ignore
}
if (c == null) {
junit.framework.TestCase.assertTrue("Eclipse compiler is not available", false);
}
for (JavaCompiler javaCompiler : javaCompilerLoader) {
if (c.isInstance(javaCompiler)) {
_eclipseCompiler = javaCompiler;
}
}
junit.framework.TestCase.assertNotNull("No Eclipse compiler found", _eclipseCompiler);
}
private static void addProcessorPaths(List<String> options, boolean useJLS8Processors, boolean addToNormalClasspath) {
String path = useJLS8Processors ? _jls8ProcessorJarPath : _processorJarPath;
if (addToNormalClasspath) {
options.add("-cp");
options.add(_tmpSrcFolderName + File.pathSeparator + _tmpGenFolderName + File.pathSeparator + path);
}
options.add("-processorpath");
options.add(path);
}
private static void addModuleProcessorPath(List<String> options, String srcFolderName, boolean multiModule) {
options.add("--processor-module-path");
options.add(_jls8ProcessorJarPath);
options.add("--module-path");
options.add(_jls8ProcessorJarPath);
if (multiModule) {
options.add("--module-source-path");
options.add(srcFolderName);
}
}
public static void tearDown() {
new File(_processorJarPath).deleteOnExit();
new File(_jls8ProcessorJarPath).deleteOnExit();
BatchTestUtils.deleteTree(new File(_tmpFolder));
}
protected static String getPluginDirectoryPath() {
try {
if (Platform.isRunning()) {
URL platformURL = Platform.getBundle("org.eclipse.jdt.compiler.apt.tests").getEntry("/");
return new File(FileLocator.toFileURL(platformURL).getFile()).getAbsolutePath();
}
return new File(System.getProperty("user.dir")).getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static byte[] read(java.io.File file) throws java.io.IOException {
int fileLength;
byte[] fileBytes = new byte[fileLength = (int) file.length()];
java.io.FileInputStream stream = null;
try {
stream = new java.io.FileInputStream(file);
int bytesRead = 0;
int lastReadSize = 0;
while ((lastReadSize != -1) && (bytesRead != fileLength)) {
lastReadSize = stream.read(fileBytes, bytesRead, fileLength - bytesRead);
bytesRead += lastReadSize;
}
} finally {
if (stream != null) {
stream.close();
}
}
return fileBytes;
}
/**
* @return true if this file's end-of-line delimiters should be replaced with
* a platform-independent value, e.g. for compilation.
*/
public static boolean shouldConvertToIndependentLineDelimiter(File file) {
return file.getName().endsWith(".java");
}
/**
* Copy a file from one location to another, unless the destination file already exists and has
* the same timestamp and file size. Create the destination location if necessary. Convert line
* delimiters according to {@link #shouldConvertToIndependentLineDelimiter(File)}.
*
* @param src
* the full path to the resource location.
* @param destFolder
* the full path to the destination location.
* @throws IOException
*/
public static void copyResource(File src, File dest) throws IOException {
if (dest.exists() &&
src.lastModified() < dest.lastModified() &&
src.length() == dest.length())
{
return;
}
// read source bytes
byte[] srcBytes = null;
srcBytes = read(src);
if (shouldConvertToIndependentLineDelimiter(src)) {
String contents = new String(srcBytes);
contents = TestUtils.convertToIndependentLineDelimiter(contents);
srcBytes = contents.getBytes();
}
writeFile(dest, srcBytes);
}
public static void writeFile(File dest, byte[] srcBytes) throws IOException {
File destFolder = dest.getParentFile();
if (!destFolder.exists()) {
if (!destFolder.mkdirs()) {
throw new IOException("Unable to create directory " + destFolder);
}
}
// write bytes to dest
FileOutputStream out = null;
try {
out = new FileOutputStream(dest);
out.write(srcBytes);
out.flush();
} finally {
if (out != null) {
out.close();
}
}
}
/**
* Copy a resource that is located under the <code>resources</code> folder of the plugin to a
* corresponding location under the specified target folder. Convert line delimiters according
* to {@link #shouldConvertToIndependentLineDelimiter(File)}.
*
* @param resourcePath
* the relative path under <code>[plugin-root]/resources</code> of the resource to
* be copied
* @param targetFolder
* the absolute path of the folder under which the resource will be copied. Folder
* and subfolders will be created if necessary.
* @return a file representing the copied resource
* @throws IOException
*/
public static File copyResource(String resourcePath, File targetFolder) throws IOException {
File resDir = new File(getPluginDirectoryPath(), RESOURCES_DIR);
File resourceFile = new File(resDir, resourcePath);
File targetFile = new File(targetFolder, resourcePath);
copyResource(resourceFile, targetFile);
return targetFile;
}
/**
* Copy all the files under the directory specified by src to the directory
* specified by dest. The src and dest directories must exist; child directories
* under dest will be created as required. Existing files in dest will be
* overwritten. Newlines will be converted according to
* {@link #shouldConvertToIndependentLineDelimiter(File)}. Directories
* named "CVS" will be ignored.
* @param resourceFolderName the name of the source folder, relative to
* <code>[plugin-root]/resources</code>
* @param the absolute path of the destination folder
* @throws IOException
*/
public static void copyResources(String resourceFolderName, File destFolder) throws IOException {
File resDir = new File(getPluginDirectoryPath(), RESOURCES_DIR);
File resourceFolder = new File(resDir, resourceFolderName);
copyResources(resourceFolder, destFolder);
}
private static void copyResources(File resourceFolder, File destFolder) throws IOException {
if (resourceFolder == null) {
return;
}
// Copy all resources in this folder
String[] children = resourceFolder.list();
if (null == children) {
return;
}
// if there are any children, (recursively) copy them
for (String child : children) {
if ("CVS".equals(child)) {
continue;
}
File childRes = new File(resourceFolder, child);
File childDest = new File(destFolder, child);
if (childRes.isDirectory()) {
copyResources(childRes, childDest);
}
else {
copyResource(childRes, childDest);
}
}
}
public static String setupProcessorJar(String processorJar, String tmpDir) throws IOException {
File libDir = new File(getPluginDirectoryPath());
File libFile = new File(libDir, processorJar);
File destinationDir = new File(tmpDir);
File destinationFile = new File(destinationDir, processorJar);
copyResource(libFile, destinationFile);
return destinationFile.getCanonicalPath();
}
/**
* Recursively delete the contents of a directory, including any subdirectories.
* This is not optimized to handle very large or deep directory trees efficiently.
* @param f is either a normal file (which will be deleted) or a directory
* (which will be emptied and then deleted).
*/
public static void deleteTree(File f)
{
if (null == f) {
return;
}
File[] children = f.listFiles();
if (null != children) {
// if f has any children, (recursively) delete them
for (File child : children) {
deleteTree(child);
}
}
// At this point f is either a normal file or an empty directory
f.delete();
}
/**
* Check the contents of a file.
* @return true if the contents of <code>genTextFile</code> exactly match <code>string</code>
*/
public static boolean fileContentsEqualText(File genTextFile, String input) {
long length = genTextFile.length();
if (length != input.length()) {
return false;
}
char[] contents = new char[512];
Reader r = null;
try {
r = new BufferedReader(new FileReader(genTextFile));
int ofs = 0;
while (length > 0) {
int read = r.read(contents);
String match = input.substring(ofs, ofs + read);
if (!match.contentEquals(new String(contents, 0, read))) {
return false;
}
ofs += read;
length -= read;
}
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
if (r != null)
try {
r.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}