8015322: Javac template test framework
Putback of the javac template test framework from the Lambda repository Reviewed-by: jjg
This commit is contained in:
parent
5b76a0d216
commit
b03e5fc290
@ -32,7 +32,7 @@ tests that the compiler performs according to the specifications in
|
||||
JLS and JVMS.
|
||||
|
||||
In addition, there is a substantial collection of regression and unit
|
||||
tests for all the tools in the maain langtools test/ directory.
|
||||
tests for all the tools in the main langtools test/ directory.
|
||||
|
||||
Finally, there is a small set of tests to do basic validation of a build
|
||||
of the langtools workspace for use by JDK. These tests check the contents
|
||||
|
4
langtools/test/lib/combo/TEST.properties
Normal file
4
langtools/test/lib/combo/TEST.properties
Normal file
@ -0,0 +1,4 @@
|
||||
# This file identifies root(s) of the test-ng hierarchy.
|
||||
|
||||
|
||||
TestNG.dirs = .
|
82
langtools/test/lib/combo/tools/javac/combo/Diagnostics.java
Normal file
82
langtools/test/lib/combo/tools/javac/combo/Diagnostics.java
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package tools.javac.combo;
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
/**
|
||||
* A container for compiler diagnostics, separated into errors and warnings,
|
||||
* used by JavacTemplateTestBase.
|
||||
*
|
||||
* @author Brian Goetz
|
||||
*/
|
||||
public class Diagnostics implements javax.tools.DiagnosticListener<JavaFileObject> {
|
||||
|
||||
protected List<Diagnostic<? extends JavaFileObject>> diags = new ArrayList<>();
|
||||
protected boolean foundErrors = false;
|
||||
|
||||
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
|
||||
diags.add(diagnostic);
|
||||
foundErrors = foundErrors || diagnostic.getKind() == Diagnostic.Kind.ERROR;
|
||||
}
|
||||
|
||||
/** Were there any errors found? */
|
||||
public boolean errorsFound() {
|
||||
return foundErrors;
|
||||
}
|
||||
|
||||
/** Get all diagnostic keys */
|
||||
public List<String> keys() {
|
||||
return diags.stream()
|
||||
.map(Diagnostic::getCode)
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
/** Do the diagnostics contain the specified error key? */
|
||||
public boolean containsErrorKey(String key) {
|
||||
return diags.stream()
|
||||
.filter(d -> d.getKind() == Diagnostic.Kind.ERROR)
|
||||
.anyMatch(d -> d.getCode().equals(key));
|
||||
}
|
||||
|
||||
/** Get the error keys */
|
||||
public List<String> errorKeys() {
|
||||
return diags.stream()
|
||||
.filter(d -> d.getKind() == Diagnostic.Kind.ERROR)
|
||||
.map(Diagnostic::getCode)
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
public String toString() { return keys().toString(); }
|
||||
|
||||
/** Clear all diagnostic state */
|
||||
public void reset() {
|
||||
diags.clear();
|
||||
foundErrors = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,362 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package tools.javac.combo;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
import javax.tools.StandardLocation;
|
||||
import javax.tools.ToolProvider;
|
||||
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.tools.javac.util.Pair;
|
||||
import org.testng.ITestResult;
|
||||
import org.testng.annotations.AfterMethod;
|
||||
import org.testng.annotations.AfterSuite;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
/**
|
||||
* Base class for template-driven TestNG javac tests that support on-the-fly
|
||||
* source file generation, compilation, classloading, execution, and separate
|
||||
* compilation.
|
||||
*
|
||||
* <p>Manages a set of templates (which have embedded tags of the form
|
||||
* {@code #\{NAME\}}), source files (which are also templates), and compile
|
||||
* options. Test cases can register templates and source files, cause them to
|
||||
* be compiled, validate whether the set of diagnostic messages output by the
|
||||
* compiler is correct, and optionally load and run the compiled classes.
|
||||
*
|
||||
* @author Brian Goetz
|
||||
*/
|
||||
@Test
|
||||
public abstract class JavacTemplateTestBase {
|
||||
private static final Set<String> suiteErrors = Collections.synchronizedSet(new HashSet<>());
|
||||
private static final AtomicInteger counter = new AtomicInteger();
|
||||
private static final File root = new File("gen");
|
||||
private static final File nullDir = new File("empty");
|
||||
|
||||
protected final Map<String, Template> templates = new HashMap<>();
|
||||
protected final Diagnostics diags = new Diagnostics();
|
||||
protected final List<Pair<String, Template>> sourceFiles = new ArrayList<>();
|
||||
protected final List<String> compileOptions = new ArrayList<>();
|
||||
protected final List<File> classpaths = new ArrayList<>();
|
||||
protected final Template.Resolver defaultResolver = new MapResolver(templates);
|
||||
|
||||
private Template.Resolver currentResolver = defaultResolver;
|
||||
|
||||
/** Add a template with a specified name */
|
||||
protected void addTemplate(String name, Template t) {
|
||||
templates.put(name, t);
|
||||
}
|
||||
|
||||
/** Add a template with a specified name */
|
||||
protected void addTemplate(String name, String s) {
|
||||
templates.put(name, new StringTemplate(s));
|
||||
}
|
||||
|
||||
/** Add a source file */
|
||||
protected void addSourceFile(String name, Template t) {
|
||||
sourceFiles.add(new Pair<>(name, t));
|
||||
}
|
||||
|
||||
/** Add a File to the class path to be used when loading classes; File values
|
||||
* will generally be the result of a previous call to {@link #compile()}.
|
||||
* This enables testing of separate compilation scenarios if the class path
|
||||
* is set up properly.
|
||||
*/
|
||||
protected void addClassPath(File path) {
|
||||
classpaths.add(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a set of compilation command-line options
|
||||
*/
|
||||
protected void addCompileOptions(String... opts) {
|
||||
Collections.addAll(compileOptions, opts);
|
||||
}
|
||||
|
||||
/** Reset the compile options to the default (empty) value */
|
||||
protected void resetCompileOptions() { compileOptions.clear(); }
|
||||
|
||||
/** Remove all templates */
|
||||
protected void resetTemplates() { templates.clear(); }
|
||||
|
||||
/** Remove accumulated diagnostics */
|
||||
protected void resetDiagnostics() { diags.reset(); }
|
||||
|
||||
/** Remove all source files */
|
||||
protected void resetSourceFiles() { sourceFiles.clear(); }
|
||||
|
||||
/** Remove registered class paths */
|
||||
protected void resetClassPaths() { classpaths.clear(); }
|
||||
|
||||
// Before each test method, reset everything
|
||||
@BeforeMethod
|
||||
public void reset() {
|
||||
resetCompileOptions();
|
||||
resetDiagnostics();
|
||||
resetSourceFiles();
|
||||
resetTemplates();
|
||||
resetClassPaths();
|
||||
}
|
||||
|
||||
// After each test method, if the test failed, capture source files and diagnostics and put them in the log
|
||||
@AfterMethod
|
||||
public void copyErrors(ITestResult result) {
|
||||
if (!result.isSuccess()) {
|
||||
suiteErrors.addAll(diags.errorKeys());
|
||||
|
||||
List<Object> list = new ArrayList<>();
|
||||
Collections.addAll(list, result.getParameters());
|
||||
list.add("Test case: " + getTestCaseDescription());
|
||||
for (Pair<String, Template> e : sourceFiles)
|
||||
list.add("Source file " + e.fst + ": " + e.snd);
|
||||
if (diags.errorsFound())
|
||||
list.add("Compile diagnostics: " + diags.toString());
|
||||
result.setParameters(list.toArray(new Object[list.size()]));
|
||||
}
|
||||
}
|
||||
|
||||
@AfterSuite
|
||||
// After the suite is done, dump any errors to output
|
||||
public void dumpErrors() {
|
||||
if (!suiteErrors.isEmpty())
|
||||
System.err.println("Errors found in test suite: " + suiteErrors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a description of this test case; since test cases may be combinatorially
|
||||
* generated, this should include all information needed to describe the test case
|
||||
*/
|
||||
protected String getTestCaseDescription() {
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
/** Assert that all previous calls to compile() succeeded */
|
||||
protected void assertCompileSucceeded() {
|
||||
if (diags.errorsFound())
|
||||
fail("Expected successful compilation");
|
||||
}
|
||||
|
||||
/**
|
||||
* If the provided boolean is true, assert all previous compiles succeeded,
|
||||
* otherwise assert that a compile failed.
|
||||
* */
|
||||
protected void assertCompileSucceededIff(boolean b) {
|
||||
if (b)
|
||||
assertCompileSucceeded();
|
||||
else
|
||||
assertCompileFailed();
|
||||
}
|
||||
|
||||
/** Assert that a previous call to compile() failed */
|
||||
protected void assertCompileFailed() {
|
||||
if (!diags.errorsFound())
|
||||
fail("Expected failed compilation");
|
||||
}
|
||||
|
||||
/** Assert that a previous call to compile() failed with a specific error key */
|
||||
protected void assertCompileFailed(String message) {
|
||||
if (!diags.errorsFound())
|
||||
fail("Expected failed compilation: " + message);
|
||||
}
|
||||
|
||||
/** Assert that a previous call to compile() failed with all of the specified error keys */
|
||||
protected void assertCompileErrors(String... keys) {
|
||||
if (!diags.errorsFound())
|
||||
fail("Expected failed compilation");
|
||||
for (String k : keys)
|
||||
if (!diags.containsErrorKey(k))
|
||||
fail("Expected compilation error " + k);
|
||||
}
|
||||
|
||||
/** Convert an object, which may be a Template or a String, into a Template */
|
||||
protected Template asTemplate(Object o) {
|
||||
if (o instanceof Template)
|
||||
return (Template) o;
|
||||
else if (o instanceof String)
|
||||
return new StringTemplate((String) o);
|
||||
else
|
||||
return new StringTemplate(o.toString());
|
||||
}
|
||||
|
||||
/** Compile all registered source files */
|
||||
protected void compile() throws IOException {
|
||||
compile(false);
|
||||
}
|
||||
|
||||
/** Compile all registered source files, optionally generating class files
|
||||
* and returning a File describing the directory to which they were written */
|
||||
protected File compile(boolean generate) throws IOException {
|
||||
List<JavaFileObject> files = new ArrayList<>();
|
||||
for (Pair<String, Template> e : sourceFiles)
|
||||
files.add(new FileAdapter(e.fst, asTemplate(e.snd)));
|
||||
return compile(classpaths, files, generate);
|
||||
}
|
||||
|
||||
/** Compile all registered source files, using the provided list of class paths
|
||||
* for finding required classfiles, optionally generating class files
|
||||
* and returning a File describing the directory to which they were written */
|
||||
protected File compile(List<File> classpaths, boolean generate) throws IOException {
|
||||
List<JavaFileObject> files = new ArrayList<>();
|
||||
for (Pair<String, Template> e : sourceFiles)
|
||||
files.add(new FileAdapter(e.fst, asTemplate(e.snd)));
|
||||
return compile(classpaths, files, generate);
|
||||
}
|
||||
|
||||
private File compile(List<File> classpaths, List<JavaFileObject> files, boolean generate) throws IOException {
|
||||
JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();
|
||||
StandardJavaFileManager fm = systemJavaCompiler.getStandardFileManager(null, null, null);
|
||||
if (classpaths.size() > 0)
|
||||
fm.setLocation(StandardLocation.CLASS_PATH, classpaths);
|
||||
JavacTask ct = (JavacTask) systemJavaCompiler.getTask(null, fm, diags, compileOptions, null, files);
|
||||
if (generate) {
|
||||
File destDir = new File(root, Integer.toString(counter.incrementAndGet()));
|
||||
// @@@ Assert that this directory didn't exist, or start counter at max+1
|
||||
destDir.mkdirs();
|
||||
fm.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(destDir));
|
||||
ct.generate();
|
||||
return destDir;
|
||||
}
|
||||
else {
|
||||
ct.analyze();
|
||||
return nullDir;
|
||||
}
|
||||
}
|
||||
|
||||
/** Load the given class using the provided list of class paths */
|
||||
protected Class<?> loadClass(String className, File... destDirs) {
|
||||
try {
|
||||
List<URL> list = new ArrayList<>();
|
||||
for (File f : destDirs)
|
||||
list.add(new URL("file:" + f.toString().replace("\\", "/") + "/"));
|
||||
return Class.forName(className, true, new URLClassLoader(list.toArray(new URL[list.size()])));
|
||||
} catch (ClassNotFoundException | MalformedURLException e) {
|
||||
throw new RuntimeException("Error loading class " + className, e);
|
||||
}
|
||||
}
|
||||
|
||||
/** An implementation of Template which is backed by a String */
|
||||
protected class StringTemplate implements Template {
|
||||
protected final String template;
|
||||
|
||||
public StringTemplate(String template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
public String expand(String selector) {
|
||||
return Behavior.expandTemplate(template, currentResolver);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return expand("");
|
||||
}
|
||||
|
||||
public StringTemplate with(final String key, final String value) {
|
||||
return new StringTemplateWithResolver(template, new KeyResolver(key, value));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** An implementation of Template which is backed by a String and which
|
||||
* encapsulates a Resolver for resolving embedded tags. */
|
||||
protected class StringTemplateWithResolver extends StringTemplate {
|
||||
private final Resolver localResolver;
|
||||
|
||||
public StringTemplateWithResolver(String template, Resolver localResolver) {
|
||||
super(template);
|
||||
this.localResolver = localResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String expand(String selector) {
|
||||
Resolver saved = currentResolver;
|
||||
currentResolver = new ChainedResolver(currentResolver, localResolver);
|
||||
try {
|
||||
return super.expand(selector);
|
||||
}
|
||||
finally {
|
||||
currentResolver = saved;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringTemplate with(String key, String value) {
|
||||
return new StringTemplateWithResolver(template, new ChainedResolver(localResolver, new KeyResolver(key, value)));
|
||||
}
|
||||
}
|
||||
|
||||
/** A Resolver which uses a Map to resolve tags */
|
||||
private class KeyResolver implements Template.Resolver {
|
||||
private final String key;
|
||||
private final String value;
|
||||
|
||||
public KeyResolver(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Template lookup(String k) {
|
||||
return key.equals(k) ? new StringTemplate(value) : null;
|
||||
}
|
||||
}
|
||||
|
||||
private class FileAdapter extends SimpleJavaFileObject {
|
||||
private final String filename;
|
||||
private final Template template;
|
||||
|
||||
public FileAdapter(String filename, Template template) {
|
||||
super(URI.create("myfo:/" + filename), Kind.SOURCE);
|
||||
this.template = template;
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
|
||||
return toString();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return Template.Behavior.expandTemplate(template.expand(filename), defaultResolver);
|
||||
}
|
||||
}
|
||||
}
|
112
langtools/test/lib/combo/tools/javac/combo/Template.java
Normal file
112
langtools/test/lib/combo/tools/javac/combo/Template.java
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package tools.javac.combo;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A template into which tags of the form {@code #\{KEY\}} or
|
||||
* {@code #\{KEY.SUBKEY\}} can be expanded.
|
||||
*/
|
||||
public interface Template {
|
||||
String expand(String selector);
|
||||
|
||||
interface Resolver {
|
||||
public Template lookup(String key);
|
||||
}
|
||||
|
||||
public static class Behavior {
|
||||
/* Looks for expandable keys. An expandable key can take the form:
|
||||
* #{MAJOR}
|
||||
* #{MAJOR.MINOR}
|
||||
* where MAJOR can be IDENTIFIER or IDENTIFIER[NUMERIC_INDEX]
|
||||
* and MINOR can be an identifier
|
||||
*/
|
||||
private static final Pattern pattern = Pattern.compile("#\\{([A-Z_][A-Z0-9_]*(?:\\[\\d+\\])?)(?:\\.([A-Z_][A-Z0-9_]*))?\\}");
|
||||
|
||||
public static String expandTemplate(String template, final Map<String, Template> vars) {
|
||||
return expandTemplate(template, new MapResolver(vars));
|
||||
}
|
||||
|
||||
public static String expandTemplate(String template, Resolver res) {
|
||||
CharSequence in = template;
|
||||
StringBuffer out = new StringBuffer();
|
||||
while (true) {
|
||||
boolean more = false;
|
||||
Matcher m = pattern.matcher(in);
|
||||
while (m.find()) {
|
||||
String major = m.group(1);
|
||||
String minor = m.group(2);
|
||||
Template key = res.lookup(major);
|
||||
if (key == null)
|
||||
throw new IllegalStateException("Unknown major key " + major);
|
||||
|
||||
String replacement = key.expand(minor == null ? "" : minor);
|
||||
more |= pattern.matcher(replacement).find();
|
||||
m.appendReplacement(out, replacement);
|
||||
}
|
||||
m.appendTail(out);
|
||||
if (!more)
|
||||
return out.toString();
|
||||
else {
|
||||
in = out;
|
||||
out = new StringBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class MapResolver implements Template.Resolver {
|
||||
private final Map<String, Template> vars;
|
||||
|
||||
public MapResolver(Map<String, Template> vars) {this.vars = vars;}
|
||||
|
||||
public Template lookup(String key) {
|
||||
return vars.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
class ChainedResolver implements Template.Resolver {
|
||||
private final Template.Resolver upstreamResolver, thisResolver;
|
||||
|
||||
public ChainedResolver(Template.Resolver upstreamResolver, Template.Resolver thisResolver) {
|
||||
this.upstreamResolver = upstreamResolver;
|
||||
this.thisResolver = thisResolver;
|
||||
}
|
||||
|
||||
public Template.Resolver getUpstreamResolver() {
|
||||
return upstreamResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Template lookup(String key) {
|
||||
Template result = thisResolver.lookup(key);
|
||||
if (result == null)
|
||||
result = upstreamResolver.lookup(key);
|
||||
return result;
|
||||
}
|
||||
}
|
94
langtools/test/lib/combo/tools/javac/combo/TemplateTest.java
Normal file
94
langtools/test/lib/combo/tools/javac/combo/TemplateTest.java
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
package tools.javac.combo;
|
||||
|
||||
import org.testng.annotations.BeforeTest;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* TemplateTest
|
||||
*/
|
||||
@Test
|
||||
public class TemplateTest {
|
||||
Map<String, Template> vars = new HashMap<>();
|
||||
|
||||
@BeforeTest
|
||||
void before() { vars.clear(); }
|
||||
|
||||
private void assertTemplate(String expected, String template) {
|
||||
String result = Template.Behavior.expandTemplate(template, vars);
|
||||
assertEquals(result, expected, "for " + template);
|
||||
}
|
||||
|
||||
private String dotIf(String s) {
|
||||
return s == null || s.isEmpty() ? "" : "." + s;
|
||||
}
|
||||
|
||||
public void testTemplateExpansion() {
|
||||
vars.put("A", s -> "a" + dotIf(s));
|
||||
vars.put("B", s -> "b" + dotIf(s));
|
||||
vars.put("C", s -> "#{A}#{B}");
|
||||
vars.put("D", s -> "#{A" + dotIf(s) + "}#{B" + dotIf(s) + "}");
|
||||
vars.put("_D", s -> "d");
|
||||
|
||||
assertTemplate("", "");
|
||||
assertTemplate("foo", "foo");
|
||||
assertTemplate("a", "#{A}");
|
||||
assertTemplate("a", "#{A.}");
|
||||
assertTemplate("a.FOO", "#{A.FOO}");
|
||||
assertTemplate("aa", "#{A}#{A}");
|
||||
assertTemplate("ab", "#{C}");
|
||||
assertTemplate("ab", "#{C.FOO}");
|
||||
assertTemplate("ab", "#{C.}");
|
||||
assertTemplate("a.FOOb.FOO", "#{D.FOO}");
|
||||
assertTemplate("ab", "#{D}");
|
||||
assertTemplate("d", "#{_D}");
|
||||
assertTemplate("#{A", "#{A");
|
||||
}
|
||||
|
||||
public void testIndexedTemplate() {
|
||||
vars.put("A[0]", s -> "a" );
|
||||
vars.put("A[1]", s -> "b" );
|
||||
vars.put("A[2]", s -> "c" );
|
||||
vars.put("X", s -> "0");
|
||||
assertTemplate("a", "#{A[0]}");
|
||||
assertTemplate("b", "#{A[1]}");
|
||||
assertTemplate("c", "#{A[2]}");
|
||||
}
|
||||
|
||||
public void testAngleBrackets() {
|
||||
vars.put("X", s -> "xyz");
|
||||
assertTemplate("List<String> ls = xyz;", "List<String> ls = #{X};");
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = IllegalStateException.class )
|
||||
public void testUnknownKey() {
|
||||
assertTemplate("#{Q}", "#{Q}");
|
||||
}
|
||||
}
|
@ -0,0 +1,421 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import tools.javac.combo.*;
|
||||
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
/**
|
||||
* BridgeMethodTestCase -- used for asserting linkage to bridges under separate compilation.
|
||||
*
|
||||
* Example test case:
|
||||
* public void test1() throws IOException, ReflectiveOperationException {
|
||||
* compileSpec("C(Bc1(A))");
|
||||
* assertLinkage("C", LINKAGE_ERROR, "B1");
|
||||
* recompileSpec("C(Bc1(Ac0))", "A");
|
||||
* assertLinkage("C", "A0", "B1");
|
||||
* }
|
||||
*
|
||||
* This compiles A, B, and C, asserts that C.m()Object does not exist, asserts
|
||||
* that C.m()Number eventually invokes B.m()Number, recompiles B, and then asserts
|
||||
* that the result of calling C.m()Object now arrives at A.
|
||||
*
|
||||
* @author Brian Goetz
|
||||
*/
|
||||
|
||||
@Test
|
||||
public abstract class BridgeMethodTestCase extends JavacTemplateTestBase {
|
||||
|
||||
private static final String TYPE_LETTERS = "ABCDIJK";
|
||||
|
||||
private static final String BASE_INDEX_CLASS = "class C0 {\n" +
|
||||
" int val;\n" +
|
||||
" C0(int val) { this.val = val; }\n" +
|
||||
" public int getVal() { return val; }\n" +
|
||||
"}\n";
|
||||
private static final String INDEX_CLASS_TEMPLATE = "class C#ID extends C#PREV {\n" +
|
||||
" C#ID(int val) { super(val); }\n" +
|
||||
"}\n";
|
||||
|
||||
|
||||
|
||||
protected static String LINKAGE_ERROR = "-1";
|
||||
|
||||
private List<File> compileDirs = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Compile all the classes in a class spec, and put them on the classpath.
|
||||
*
|
||||
* The spec is the specification for a nest of classes, using the following notation
|
||||
* A, B represent abstract classes
|
||||
* C represents a concrete class
|
||||
* I, J, K represent interfaces
|
||||
* Lowercase 'c' following a class means that the method m() is concrete
|
||||
* Lowercase 'a' following a class or interface means that the method m() is abstract
|
||||
* Lowercase 'd' following an interface means that the method m() is default
|
||||
* A number 0, 1, or 2 following the lowercase letter indicates the return type of that method
|
||||
* 0 = Object, 1 = Number, 2 = Integer (these form an inheritance chain so bridges are generated)
|
||||
* A classes supertypes follow its method spec, in parentheses
|
||||
* Examples:
|
||||
* C(Ia0, Jd0) -- C extends I and J, I has abstract m()Object, J has default m()Object
|
||||
* Cc1(Ia0) -- C has concrete m()Number, extends I with abstract m()Object
|
||||
* If a type must appear multiple times, its full spec must be in the first occurrence
|
||||
* Example:
|
||||
* C(I(Kd0), J(K))
|
||||
*/
|
||||
protected void compileSpec(String spec) throws IOException {
|
||||
compileSpec(spec, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile all the classes in a class spec, and assert that there were compilation errors.
|
||||
*/
|
||||
protected void compileSpec(String spec, String... errorKeys) throws IOException {
|
||||
compileSpec(spec, false, errorKeys);
|
||||
}
|
||||
|
||||
protected void compileSpec(String spec, boolean debug, String... errorKeys) throws IOException {
|
||||
ClassModel cm = new Parser(spec).parseClassModel();
|
||||
for (int i = 0; i <= cm.maxIndex() ; i++) {
|
||||
if (debug) System.out.println(indexClass(i));
|
||||
addSourceFile(String.format("C%d.java", i), new StringTemplate(indexClass(i)));
|
||||
}
|
||||
for (Map.Entry<String, ClassModel> e : classes(cm).entrySet()) {
|
||||
if (debug) System.out.println(e.getValue().toSource());
|
||||
addSourceFile(e.getKey() + ".java", new StringTemplate(e.getValue().toSource()));
|
||||
}
|
||||
compileDirs.add(compile(true));
|
||||
resetSourceFiles();
|
||||
if (errorKeys.length == 0)
|
||||
assertCompileSucceeded();
|
||||
else
|
||||
assertCompileErrors(errorKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recompile only a subset of classes in the class spec, as named by names,
|
||||
* and put them on the classpath such that they shadow earlier versions of that class.
|
||||
*/
|
||||
protected void recompileSpec(String spec, String... names) throws IOException {
|
||||
List<String> nameList = Arrays.asList(names);
|
||||
ClassModel cm = new Parser(spec).parseClassModel();
|
||||
for (int i = 0; i <= cm.maxIndex() ; i++) {
|
||||
addSourceFile(String.format("C%d.java", i), new StringTemplate(indexClass(i)));
|
||||
}
|
||||
for (Map.Entry<String, ClassModel> e : classes(cm).entrySet())
|
||||
if (nameList.contains(e.getKey()))
|
||||
addSourceFile(e.getKey() + ".java", new StringTemplate(e.getValue().toSource()));
|
||||
compileDirs.add(compile(Arrays.asList(classPaths()), true));
|
||||
resetSourceFiles();
|
||||
assertCompileSucceeded();
|
||||
}
|
||||
|
||||
protected void assertLinkage(String name, String... expected) throws ReflectiveOperationException {
|
||||
for (int i=0; i<expected.length; i++) {
|
||||
String e = expected[i];
|
||||
if (e.equals(LINKAGE_ERROR)) {
|
||||
try {
|
||||
int actual = invoke(name, i);
|
||||
fail("Expected linkage error, got" + fromNum(actual));
|
||||
}
|
||||
catch (LinkageError x) {
|
||||
// success
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (e.length() == 1)
|
||||
e += "0";
|
||||
int expectedInt = toNum(e);
|
||||
int actual = invoke(name, i);
|
||||
if (expectedInt != actual)
|
||||
fail(String.format("Expected %s but found %s for %s.m()%d", fromNum(expectedInt), fromNum(actual), name, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, ClassModel> classes(ClassModel cm) {
|
||||
HashMap<String, ClassModel> m = new HashMap<>();
|
||||
classesHelper(cm, m);
|
||||
return m;
|
||||
}
|
||||
|
||||
private String indexClass(int index) {
|
||||
if (index == 0) {
|
||||
return BASE_INDEX_CLASS;
|
||||
} else {
|
||||
return INDEX_CLASS_TEMPLATE
|
||||
.replace("#ID", String.valueOf(index))
|
||||
.replace("#PREV", String.valueOf(index - 1));
|
||||
}
|
||||
}
|
||||
|
||||
private static String overrideName(int index) {
|
||||
return "C" + index;
|
||||
}
|
||||
|
||||
private void classesHelper(ClassModel cm, Map<String, ClassModel> m) {
|
||||
if (!m.containsKey(cm.name))
|
||||
m.put(cm.name, cm);
|
||||
for (ClassModel s : cm.supertypes)
|
||||
classesHelper(s, m);
|
||||
}
|
||||
|
||||
private static String fromNum(int num) {
|
||||
return String.format("%c%d", TYPE_LETTERS.charAt(num / 10), num % 10);
|
||||
}
|
||||
|
||||
private static int toNum(String name, int index) {
|
||||
return 10*(TYPE_LETTERS.indexOf(name.charAt(0))) + index;
|
||||
}
|
||||
|
||||
private static int toNum(String string) {
|
||||
return 10*(TYPE_LETTERS.indexOf(string.charAt(0))) + Integer.parseInt(string.substring(1, 2));
|
||||
}
|
||||
|
||||
private int invoke(String name, int index) throws ReflectiveOperationException {
|
||||
File[] files = classPaths();
|
||||
Class clazz = loadClass(name, files);
|
||||
Method[] ms = clazz.getMethods();
|
||||
for (Method m : ms) {
|
||||
if (m.getName().equals("m") && m.getReturnType().getName().equals(overrideName(index))) {
|
||||
m.setAccessible(true);
|
||||
Object instance = clazz.newInstance();
|
||||
Object c0 = m.invoke(instance);
|
||||
Method getVal = c0.getClass().getMethod("getVal");
|
||||
getVal.setAccessible(true);
|
||||
return (int)getVal.invoke(c0);
|
||||
}
|
||||
}
|
||||
throw new NoSuchMethodError("cannot find method m()" + index + " in class " + name);
|
||||
}
|
||||
|
||||
private File[] classPaths() {
|
||||
File[] files = new File[compileDirs.size()];
|
||||
for (int i=0; i<files.length; i++)
|
||||
files[files.length - i - 1] = compileDirs.get(i);
|
||||
return files;
|
||||
}
|
||||
|
||||
@BeforeMethod
|
||||
@Override
|
||||
public void reset() {
|
||||
compileDirs.clear();
|
||||
super.reset();
|
||||
}
|
||||
|
||||
private static class ClassModel {
|
||||
|
||||
enum MethodType {
|
||||
ABSTRACT('a'), CONCRETE('c'), DEFAULT('d');
|
||||
|
||||
public final char designator;
|
||||
|
||||
MethodType(char designator) {
|
||||
this.designator = designator;
|
||||
}
|
||||
|
||||
public static MethodType find(char c) {
|
||||
for (MethodType m : values())
|
||||
if (m.designator == c)
|
||||
return m;
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final boolean isInterface;
|
||||
private final List<ClassModel> supertypes;
|
||||
private final MethodType methodType;
|
||||
private final int methodIndex;
|
||||
|
||||
private ClassModel(String name,
|
||||
boolean anInterface,
|
||||
List<ClassModel> supertypes,
|
||||
MethodType methodType,
|
||||
int methodIndex) {
|
||||
this.name = name;
|
||||
isInterface = anInterface;
|
||||
this.supertypes = supertypes;
|
||||
this.methodType = methodType;
|
||||
this.methodIndex = methodIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(name);
|
||||
if (methodType != null) {
|
||||
sb.append(methodType.designator);
|
||||
sb.append(methodIndex);
|
||||
}
|
||||
if (!supertypes.isEmpty()) {
|
||||
sb.append("(");
|
||||
for (int i=0; i<supertypes.size(); i++) {
|
||||
if (i > 0)
|
||||
sb.append(",");
|
||||
sb.append(supertypes.get(i).toString());
|
||||
}
|
||||
sb.append(")");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
int maxIndex() {
|
||||
int maxSoFar = methodIndex;
|
||||
for (ClassModel cm : supertypes) {
|
||||
maxSoFar = Math.max(cm.maxIndex(), maxSoFar);
|
||||
}
|
||||
return maxSoFar;
|
||||
}
|
||||
|
||||
public String toSource() {
|
||||
String extendsClause = "";
|
||||
String implementsClause = "";
|
||||
String methodBody = "";
|
||||
boolean isAbstract = "AB".contains(name);
|
||||
|
||||
for (ClassModel s : supertypes) {
|
||||
if (!s.isInterface) {
|
||||
extendsClause = String.format("extends %s", s.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
StringJoiner sj = new StringJoiner(", ");
|
||||
for (ClassModel s : supertypes)
|
||||
if (s.isInterface)
|
||||
sj.add(s.name);
|
||||
if (sj.length() > 0) {
|
||||
if (isInterface)
|
||||
implementsClause = "extends " + sj.toString();
|
||||
else
|
||||
implementsClause = "implements " + sj.toString();
|
||||
}
|
||||
if (methodType != null) {
|
||||
switch (methodType) {
|
||||
case ABSTRACT:
|
||||
methodBody = String.format("public abstract %s m();", overrideName(methodIndex));
|
||||
break;
|
||||
case CONCRETE:
|
||||
methodBody = String.format("public %s m() { return new %s(%d); };",
|
||||
overrideName(methodIndex), overrideName(methodIndex), toNum(name, methodIndex));
|
||||
break;
|
||||
case DEFAULT:
|
||||
methodBody = String.format("public default %s m() { return new %s(%d); };",
|
||||
overrideName(methodIndex), overrideName(methodIndex), toNum(name, methodIndex));
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return String.format("public %s %s %s %s %s { %s }", isAbstract ? "abstract" : "",
|
||||
isInterface ? "interface" : "class",
|
||||
name, extendsClause, implementsClause, methodBody);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Parser {
|
||||
private final String input;
|
||||
private final char[] chars;
|
||||
private int index;
|
||||
|
||||
private Parser(String s) {
|
||||
input = s;
|
||||
chars = s.toCharArray();
|
||||
}
|
||||
|
||||
private char peek() {
|
||||
return index < chars.length ? chars[index] : 0;
|
||||
}
|
||||
|
||||
private boolean peek(String validChars) {
|
||||
return validChars.indexOf(peek()) >= 0;
|
||||
}
|
||||
|
||||
private char advanceIf(String validChars) {
|
||||
if (peek(validChars))
|
||||
return chars[index++];
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
private char advanceIfDigit() {
|
||||
return advanceIf("0123456789");
|
||||
}
|
||||
|
||||
private int index() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
char c = advanceIfDigit();
|
||||
while (c != 0) {
|
||||
buf.append(c);
|
||||
c = advanceIfDigit();
|
||||
}
|
||||
return Integer.valueOf(buf.toString());
|
||||
}
|
||||
|
||||
private char advance() {
|
||||
return chars[index++];
|
||||
}
|
||||
|
||||
private char expect(String validChars) {
|
||||
char c = advanceIf(validChars);
|
||||
if (c == 0)
|
||||
throw new IllegalArgumentException(String.format("Expecting %s at position %d of %s", validChars, index, input));
|
||||
return c;
|
||||
}
|
||||
|
||||
public ClassModel parseClassModel() {
|
||||
List<ClassModel> supers = new ArrayList<>();
|
||||
char name = expect(TYPE_LETTERS);
|
||||
boolean isInterface = "IJK".indexOf(name) >= 0;
|
||||
ClassModel.MethodType methodType = peek(isInterface ? "ad" : "ac") ? ClassModel.MethodType.find(advance()) : null;
|
||||
int methodIndex = 0;
|
||||
if (methodType != null) {
|
||||
methodIndex = index();
|
||||
}
|
||||
if (peek() == '(') {
|
||||
advance();
|
||||
supers.add(parseClassModel());
|
||||
while (peek() == ',') {
|
||||
advance();
|
||||
supers.add(parseClassModel());
|
||||
}
|
||||
expect(")");
|
||||
}
|
||||
return new ClassModel(new String(new char[]{ name }), isInterface, supers, methodType, methodIndex);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,7 @@
|
||||
# This file identifies root(s) of the test-ng hierarchy.
|
||||
|
||||
|
||||
|
||||
TestNG.dirs = .
|
||||
|
||||
lib.dirs = /lib/combo
|
Loading…
x
Reference in New Issue
Block a user