1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/test/lib/combo/tools/javac/combo/JavacTemplateTestBase.java Wed Apr 27 01:34:52 2016 +0800 1.3 @@ -0,0 +1,362 @@ 1.4 +/* 1.5 + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1.7 + * 1.8 + * This code is free software; you can redistribute it and/or modify it 1.9 + * under the terms of the GNU General Public License version 2 only, as 1.10 + * published by the Free Software Foundation. 1.11 + * 1.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 1.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1.15 + * version 2 for more details (a copy is included in the LICENSE file that 1.16 + * accompanied this code). 1.17 + * 1.18 + * You should have received a copy of the GNU General Public License version 1.19 + * 2 along with this work; if not, write to the Free Software Foundation, 1.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1.21 + * 1.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1.23 + * or visit www.oracle.com if you need additional information or have any 1.24 + * questions. 1.25 + */ 1.26 +package tools.javac.combo; 1.27 + 1.28 +import java.io.File; 1.29 +import java.io.IOException; 1.30 +import java.net.MalformedURLException; 1.31 +import java.net.URI; 1.32 +import java.net.URL; 1.33 +import java.net.URLClassLoader; 1.34 +import java.util.ArrayList; 1.35 +import java.util.Arrays; 1.36 +import java.util.Collections; 1.37 +import java.util.HashMap; 1.38 +import java.util.HashSet; 1.39 +import java.util.List; 1.40 +import java.util.Map; 1.41 +import java.util.Set; 1.42 +import java.util.concurrent.atomic.AtomicInteger; 1.43 +import javax.tools.JavaCompiler; 1.44 +import javax.tools.JavaFileObject; 1.45 +import javax.tools.SimpleJavaFileObject; 1.46 +import javax.tools.StandardJavaFileManager; 1.47 +import javax.tools.StandardLocation; 1.48 +import javax.tools.ToolProvider; 1.49 + 1.50 +import com.sun.source.util.JavacTask; 1.51 +import com.sun.tools.javac.util.Pair; 1.52 +import org.testng.ITestResult; 1.53 +import org.testng.annotations.AfterMethod; 1.54 +import org.testng.annotations.AfterSuite; 1.55 +import org.testng.annotations.BeforeMethod; 1.56 +import org.testng.annotations.Test; 1.57 + 1.58 +import static org.testng.Assert.fail; 1.59 + 1.60 +/** 1.61 + * Base class for template-driven TestNG javac tests that support on-the-fly 1.62 + * source file generation, compilation, classloading, execution, and separate 1.63 + * compilation. 1.64 + * 1.65 + * <p>Manages a set of templates (which have embedded tags of the form 1.66 + * {@code #\{NAME\}}), source files (which are also templates), and compile 1.67 + * options. Test cases can register templates and source files, cause them to 1.68 + * be compiled, validate whether the set of diagnostic messages output by the 1.69 + * compiler is correct, and optionally load and run the compiled classes. 1.70 + * 1.71 + * @author Brian Goetz 1.72 + */ 1.73 +@Test 1.74 +public abstract class JavacTemplateTestBase { 1.75 + private static final Set<String> suiteErrors = Collections.synchronizedSet(new HashSet<>()); 1.76 + private static final AtomicInteger counter = new AtomicInteger(); 1.77 + private static final File root = new File("gen"); 1.78 + private static final File nullDir = new File("empty"); 1.79 + 1.80 + protected final Map<String, Template> templates = new HashMap<>(); 1.81 + protected final Diagnostics diags = new Diagnostics(); 1.82 + protected final List<Pair<String, Template>> sourceFiles = new ArrayList<>(); 1.83 + protected final List<String> compileOptions = new ArrayList<>(); 1.84 + protected final List<File> classpaths = new ArrayList<>(); 1.85 + protected final Template.Resolver defaultResolver = new MapResolver(templates); 1.86 + 1.87 + private Template.Resolver currentResolver = defaultResolver; 1.88 + 1.89 + /** Add a template with a specified name */ 1.90 + protected void addTemplate(String name, Template t) { 1.91 + templates.put(name, t); 1.92 + } 1.93 + 1.94 + /** Add a template with a specified name */ 1.95 + protected void addTemplate(String name, String s) { 1.96 + templates.put(name, new StringTemplate(s)); 1.97 + } 1.98 + 1.99 + /** Add a source file */ 1.100 + protected void addSourceFile(String name, Template t) { 1.101 + sourceFiles.add(new Pair<>(name, t)); 1.102 + } 1.103 + 1.104 + /** Add a File to the class path to be used when loading classes; File values 1.105 + * will generally be the result of a previous call to {@link #compile()}. 1.106 + * This enables testing of separate compilation scenarios if the class path 1.107 + * is set up properly. 1.108 + */ 1.109 + protected void addClassPath(File path) { 1.110 + classpaths.add(path); 1.111 + } 1.112 + 1.113 + /** 1.114 + * Add a set of compilation command-line options 1.115 + */ 1.116 + protected void addCompileOptions(String... opts) { 1.117 + Collections.addAll(compileOptions, opts); 1.118 + } 1.119 + 1.120 + /** Reset the compile options to the default (empty) value */ 1.121 + protected void resetCompileOptions() { compileOptions.clear(); } 1.122 + 1.123 + /** Remove all templates */ 1.124 + protected void resetTemplates() { templates.clear(); } 1.125 + 1.126 + /** Remove accumulated diagnostics */ 1.127 + protected void resetDiagnostics() { diags.reset(); } 1.128 + 1.129 + /** Remove all source files */ 1.130 + protected void resetSourceFiles() { sourceFiles.clear(); } 1.131 + 1.132 + /** Remove registered class paths */ 1.133 + protected void resetClassPaths() { classpaths.clear(); } 1.134 + 1.135 + // Before each test method, reset everything 1.136 + @BeforeMethod 1.137 + public void reset() { 1.138 + resetCompileOptions(); 1.139 + resetDiagnostics(); 1.140 + resetSourceFiles(); 1.141 + resetTemplates(); 1.142 + resetClassPaths(); 1.143 + } 1.144 + 1.145 + // After each test method, if the test failed, capture source files and diagnostics and put them in the log 1.146 + @AfterMethod 1.147 + public void copyErrors(ITestResult result) { 1.148 + if (!result.isSuccess()) { 1.149 + suiteErrors.addAll(diags.errorKeys()); 1.150 + 1.151 + List<Object> list = new ArrayList<>(); 1.152 + Collections.addAll(list, result.getParameters()); 1.153 + list.add("Test case: " + getTestCaseDescription()); 1.154 + for (Pair<String, Template> e : sourceFiles) 1.155 + list.add("Source file " + e.fst + ": " + e.snd); 1.156 + if (diags.errorsFound()) 1.157 + list.add("Compile diagnostics: " + diags.toString()); 1.158 + result.setParameters(list.toArray(new Object[list.size()])); 1.159 + } 1.160 + } 1.161 + 1.162 + @AfterSuite 1.163 + // After the suite is done, dump any errors to output 1.164 + public void dumpErrors() { 1.165 + if (!suiteErrors.isEmpty()) 1.166 + System.err.println("Errors found in test suite: " + suiteErrors); 1.167 + } 1.168 + 1.169 + /** 1.170 + * Get a description of this test case; since test cases may be combinatorially 1.171 + * generated, this should include all information needed to describe the test case 1.172 + */ 1.173 + protected String getTestCaseDescription() { 1.174 + return this.toString(); 1.175 + } 1.176 + 1.177 + /** Assert that all previous calls to compile() succeeded */ 1.178 + protected void assertCompileSucceeded() { 1.179 + if (diags.errorsFound()) 1.180 + fail("Expected successful compilation"); 1.181 + } 1.182 + 1.183 + /** 1.184 + * If the provided boolean is true, assert all previous compiles succeeded, 1.185 + * otherwise assert that a compile failed. 1.186 + * */ 1.187 + protected void assertCompileSucceededIff(boolean b) { 1.188 + if (b) 1.189 + assertCompileSucceeded(); 1.190 + else 1.191 + assertCompileFailed(); 1.192 + } 1.193 + 1.194 + /** Assert that a previous call to compile() failed */ 1.195 + protected void assertCompileFailed() { 1.196 + if (!diags.errorsFound()) 1.197 + fail("Expected failed compilation"); 1.198 + } 1.199 + 1.200 + /** Assert that a previous call to compile() failed with a specific error key */ 1.201 + protected void assertCompileFailed(String message) { 1.202 + if (!diags.errorsFound()) 1.203 + fail("Expected failed compilation: " + message); 1.204 + } 1.205 + 1.206 + /** Assert that a previous call to compile() failed with all of the specified error keys */ 1.207 + protected void assertCompileErrors(String... keys) { 1.208 + if (!diags.errorsFound()) 1.209 + fail("Expected failed compilation"); 1.210 + for (String k : keys) 1.211 + if (!diags.containsErrorKey(k)) 1.212 + fail("Expected compilation error " + k); 1.213 + } 1.214 + 1.215 + /** Convert an object, which may be a Template or a String, into a Template */ 1.216 + protected Template asTemplate(Object o) { 1.217 + if (o instanceof Template) 1.218 + return (Template) o; 1.219 + else if (o instanceof String) 1.220 + return new StringTemplate((String) o); 1.221 + else 1.222 + return new StringTemplate(o.toString()); 1.223 + } 1.224 + 1.225 + /** Compile all registered source files */ 1.226 + protected void compile() throws IOException { 1.227 + compile(false); 1.228 + } 1.229 + 1.230 + /** Compile all registered source files, optionally generating class files 1.231 + * and returning a File describing the directory to which they were written */ 1.232 + protected File compile(boolean generate) throws IOException { 1.233 + List<JavaFileObject> files = new ArrayList<>(); 1.234 + for (Pair<String, Template> e : sourceFiles) 1.235 + files.add(new FileAdapter(e.fst, asTemplate(e.snd))); 1.236 + return compile(classpaths, files, generate); 1.237 + } 1.238 + 1.239 + /** Compile all registered source files, using the provided list of class paths 1.240 + * for finding required classfiles, optionally generating class files 1.241 + * and returning a File describing the directory to which they were written */ 1.242 + protected File compile(List<File> classpaths, boolean generate) throws IOException { 1.243 + List<JavaFileObject> files = new ArrayList<>(); 1.244 + for (Pair<String, Template> e : sourceFiles) 1.245 + files.add(new FileAdapter(e.fst, asTemplate(e.snd))); 1.246 + return compile(classpaths, files, generate); 1.247 + } 1.248 + 1.249 + private File compile(List<File> classpaths, List<JavaFileObject> files, boolean generate) throws IOException { 1.250 + JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler(); 1.251 + StandardJavaFileManager fm = systemJavaCompiler.getStandardFileManager(null, null, null); 1.252 + if (classpaths.size() > 0) 1.253 + fm.setLocation(StandardLocation.CLASS_PATH, classpaths); 1.254 + JavacTask ct = (JavacTask) systemJavaCompiler.getTask(null, fm, diags, compileOptions, null, files); 1.255 + if (generate) { 1.256 + File destDir = new File(root, Integer.toString(counter.incrementAndGet())); 1.257 + // @@@ Assert that this directory didn't exist, or start counter at max+1 1.258 + destDir.mkdirs(); 1.259 + fm.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(destDir)); 1.260 + ct.generate(); 1.261 + return destDir; 1.262 + } 1.263 + else { 1.264 + ct.analyze(); 1.265 + return nullDir; 1.266 + } 1.267 + } 1.268 + 1.269 + /** Load the given class using the provided list of class paths */ 1.270 + protected Class<?> loadClass(String className, File... destDirs) { 1.271 + try { 1.272 + List<URL> list = new ArrayList<>(); 1.273 + for (File f : destDirs) 1.274 + list.add(new URL("file:" + f.toString().replace("\\", "/") + "/")); 1.275 + return Class.forName(className, true, new URLClassLoader(list.toArray(new URL[list.size()]))); 1.276 + } catch (ClassNotFoundException | MalformedURLException e) { 1.277 + throw new RuntimeException("Error loading class " + className, e); 1.278 + } 1.279 + } 1.280 + 1.281 + /** An implementation of Template which is backed by a String */ 1.282 + protected class StringTemplate implements Template { 1.283 + protected final String template; 1.284 + 1.285 + public StringTemplate(String template) { 1.286 + this.template = template; 1.287 + } 1.288 + 1.289 + public String expand(String selector) { 1.290 + return Behavior.expandTemplate(template, currentResolver); 1.291 + } 1.292 + 1.293 + public String toString() { 1.294 + return expand(""); 1.295 + } 1.296 + 1.297 + public StringTemplate with(final String key, final String value) { 1.298 + return new StringTemplateWithResolver(template, new KeyResolver(key, value)); 1.299 + } 1.300 + 1.301 + } 1.302 + 1.303 + /** An implementation of Template which is backed by a String and which 1.304 + * encapsulates a Resolver for resolving embedded tags. */ 1.305 + protected class StringTemplateWithResolver extends StringTemplate { 1.306 + private final Resolver localResolver; 1.307 + 1.308 + public StringTemplateWithResolver(String template, Resolver localResolver) { 1.309 + super(template); 1.310 + this.localResolver = localResolver; 1.311 + } 1.312 + 1.313 + @Override 1.314 + public String expand(String selector) { 1.315 + Resolver saved = currentResolver; 1.316 + currentResolver = new ChainedResolver(currentResolver, localResolver); 1.317 + try { 1.318 + return super.expand(selector); 1.319 + } 1.320 + finally { 1.321 + currentResolver = saved; 1.322 + } 1.323 + } 1.324 + 1.325 + @Override 1.326 + public StringTemplate with(String key, String value) { 1.327 + return new StringTemplateWithResolver(template, new ChainedResolver(localResolver, new KeyResolver(key, value))); 1.328 + } 1.329 + } 1.330 + 1.331 + /** A Resolver which uses a Map to resolve tags */ 1.332 + private class KeyResolver implements Template.Resolver { 1.333 + private final String key; 1.334 + private final String value; 1.335 + 1.336 + public KeyResolver(String key, String value) { 1.337 + this.key = key; 1.338 + this.value = value; 1.339 + } 1.340 + 1.341 + @Override 1.342 + public Template lookup(String k) { 1.343 + return key.equals(k) ? new StringTemplate(value) : null; 1.344 + } 1.345 + } 1.346 + 1.347 + private class FileAdapter extends SimpleJavaFileObject { 1.348 + private final String filename; 1.349 + private final Template template; 1.350 + 1.351 + public FileAdapter(String filename, Template template) { 1.352 + super(URI.create("myfo:/" + filename), Kind.SOURCE); 1.353 + this.template = template; 1.354 + this.filename = filename; 1.355 + } 1.356 + 1.357 + public CharSequence getCharContent(boolean ignoreEncodingErrors) { 1.358 + return toString(); 1.359 + } 1.360 + 1.361 + public String toString() { 1.362 + return Template.Behavior.expandTemplate(template.expand(filename), defaultResolver); 1.363 + } 1.364 + } 1.365 +}