test/lib/combo/tools/javac/combo/JavacTemplateTestBase.java

Mon, 09 Sep 2013 17:11:55 -0400

author
emc
date
Mon, 09 Sep 2013 17:11:55 -0400
changeset 2017
67c5110c60fe
parent 0
959103a6100f
permissions
-rw-r--r--

8015322: Javac template test framework
Summary: Putback of the javac template test framework from the Lambda repository
Reviewed-by: jjg
Contributed-by: brian.goetz@oracle.com

aoqi@0 1 /*
aoqi@0 2 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
aoqi@0 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
aoqi@0 4 *
aoqi@0 5 * This code is free software; you can redistribute it and/or modify it
aoqi@0 6 * under the terms of the GNU General Public License version 2 only, as
aoqi@0 7 * published by the Free Software Foundation.
aoqi@0 8 *
aoqi@0 9 * This code is distributed in the hope that it will be useful, but WITHOUT
aoqi@0 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
aoqi@0 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
aoqi@0 12 * version 2 for more details (a copy is included in the LICENSE file that
aoqi@0 13 * accompanied this code).
aoqi@0 14 *
aoqi@0 15 * You should have received a copy of the GNU General Public License version
aoqi@0 16 * 2 along with this work; if not, write to the Free Software Foundation,
aoqi@0 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
aoqi@0 18 *
aoqi@0 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
aoqi@0 20 * or visit www.oracle.com if you need additional information or have any
aoqi@0 21 * questions.
aoqi@0 22 */
aoqi@0 23 package tools.javac.combo;
aoqi@0 24
aoqi@0 25 import java.io.File;
aoqi@0 26 import java.io.IOException;
aoqi@0 27 import java.net.MalformedURLException;
aoqi@0 28 import java.net.URI;
aoqi@0 29 import java.net.URL;
aoqi@0 30 import java.net.URLClassLoader;
aoqi@0 31 import java.util.ArrayList;
aoqi@0 32 import java.util.Arrays;
aoqi@0 33 import java.util.Collections;
aoqi@0 34 import java.util.HashMap;
aoqi@0 35 import java.util.HashSet;
aoqi@0 36 import java.util.List;
aoqi@0 37 import java.util.Map;
aoqi@0 38 import java.util.Set;
aoqi@0 39 import java.util.concurrent.atomic.AtomicInteger;
aoqi@0 40 import javax.tools.JavaCompiler;
aoqi@0 41 import javax.tools.JavaFileObject;
aoqi@0 42 import javax.tools.SimpleJavaFileObject;
aoqi@0 43 import javax.tools.StandardJavaFileManager;
aoqi@0 44 import javax.tools.StandardLocation;
aoqi@0 45 import javax.tools.ToolProvider;
aoqi@0 46
aoqi@0 47 import com.sun.source.util.JavacTask;
aoqi@0 48 import com.sun.tools.javac.util.Pair;
aoqi@0 49 import org.testng.ITestResult;
aoqi@0 50 import org.testng.annotations.AfterMethod;
aoqi@0 51 import org.testng.annotations.AfterSuite;
aoqi@0 52 import org.testng.annotations.BeforeMethod;
aoqi@0 53 import org.testng.annotations.Test;
aoqi@0 54
aoqi@0 55 import static org.testng.Assert.fail;
aoqi@0 56
aoqi@0 57 /**
aoqi@0 58 * Base class for template-driven TestNG javac tests that support on-the-fly
aoqi@0 59 * source file generation, compilation, classloading, execution, and separate
aoqi@0 60 * compilation.
aoqi@0 61 *
aoqi@0 62 * <p>Manages a set of templates (which have embedded tags of the form
aoqi@0 63 * {@code #\{NAME\}}), source files (which are also templates), and compile
aoqi@0 64 * options. Test cases can register templates and source files, cause them to
aoqi@0 65 * be compiled, validate whether the set of diagnostic messages output by the
aoqi@0 66 * compiler is correct, and optionally load and run the compiled classes.
aoqi@0 67 *
aoqi@0 68 * @author Brian Goetz
aoqi@0 69 */
aoqi@0 70 @Test
aoqi@0 71 public abstract class JavacTemplateTestBase {
aoqi@0 72 private static final Set<String> suiteErrors = Collections.synchronizedSet(new HashSet<>());
aoqi@0 73 private static final AtomicInteger counter = new AtomicInteger();
aoqi@0 74 private static final File root = new File("gen");
aoqi@0 75 private static final File nullDir = new File("empty");
aoqi@0 76
aoqi@0 77 protected final Map<String, Template> templates = new HashMap<>();
aoqi@0 78 protected final Diagnostics diags = new Diagnostics();
aoqi@0 79 protected final List<Pair<String, Template>> sourceFiles = new ArrayList<>();
aoqi@0 80 protected final List<String> compileOptions = new ArrayList<>();
aoqi@0 81 protected final List<File> classpaths = new ArrayList<>();
aoqi@0 82 protected final Template.Resolver defaultResolver = new MapResolver(templates);
aoqi@0 83
aoqi@0 84 private Template.Resolver currentResolver = defaultResolver;
aoqi@0 85
aoqi@0 86 /** Add a template with a specified name */
aoqi@0 87 protected void addTemplate(String name, Template t) {
aoqi@0 88 templates.put(name, t);
aoqi@0 89 }
aoqi@0 90
aoqi@0 91 /** Add a template with a specified name */
aoqi@0 92 protected void addTemplate(String name, String s) {
aoqi@0 93 templates.put(name, new StringTemplate(s));
aoqi@0 94 }
aoqi@0 95
aoqi@0 96 /** Add a source file */
aoqi@0 97 protected void addSourceFile(String name, Template t) {
aoqi@0 98 sourceFiles.add(new Pair<>(name, t));
aoqi@0 99 }
aoqi@0 100
aoqi@0 101 /** Add a File to the class path to be used when loading classes; File values
aoqi@0 102 * will generally be the result of a previous call to {@link #compile()}.
aoqi@0 103 * This enables testing of separate compilation scenarios if the class path
aoqi@0 104 * is set up properly.
aoqi@0 105 */
aoqi@0 106 protected void addClassPath(File path) {
aoqi@0 107 classpaths.add(path);
aoqi@0 108 }
aoqi@0 109
aoqi@0 110 /**
aoqi@0 111 * Add a set of compilation command-line options
aoqi@0 112 */
aoqi@0 113 protected void addCompileOptions(String... opts) {
aoqi@0 114 Collections.addAll(compileOptions, opts);
aoqi@0 115 }
aoqi@0 116
aoqi@0 117 /** Reset the compile options to the default (empty) value */
aoqi@0 118 protected void resetCompileOptions() { compileOptions.clear(); }
aoqi@0 119
aoqi@0 120 /** Remove all templates */
aoqi@0 121 protected void resetTemplates() { templates.clear(); }
aoqi@0 122
aoqi@0 123 /** Remove accumulated diagnostics */
aoqi@0 124 protected void resetDiagnostics() { diags.reset(); }
aoqi@0 125
aoqi@0 126 /** Remove all source files */
aoqi@0 127 protected void resetSourceFiles() { sourceFiles.clear(); }
aoqi@0 128
aoqi@0 129 /** Remove registered class paths */
aoqi@0 130 protected void resetClassPaths() { classpaths.clear(); }
aoqi@0 131
aoqi@0 132 // Before each test method, reset everything
aoqi@0 133 @BeforeMethod
aoqi@0 134 public void reset() {
aoqi@0 135 resetCompileOptions();
aoqi@0 136 resetDiagnostics();
aoqi@0 137 resetSourceFiles();
aoqi@0 138 resetTemplates();
aoqi@0 139 resetClassPaths();
aoqi@0 140 }
aoqi@0 141
aoqi@0 142 // After each test method, if the test failed, capture source files and diagnostics and put them in the log
aoqi@0 143 @AfterMethod
aoqi@0 144 public void copyErrors(ITestResult result) {
aoqi@0 145 if (!result.isSuccess()) {
aoqi@0 146 suiteErrors.addAll(diags.errorKeys());
aoqi@0 147
aoqi@0 148 List<Object> list = new ArrayList<>();
aoqi@0 149 Collections.addAll(list, result.getParameters());
aoqi@0 150 list.add("Test case: " + getTestCaseDescription());
aoqi@0 151 for (Pair<String, Template> e : sourceFiles)
aoqi@0 152 list.add("Source file " + e.fst + ": " + e.snd);
aoqi@0 153 if (diags.errorsFound())
aoqi@0 154 list.add("Compile diagnostics: " + diags.toString());
aoqi@0 155 result.setParameters(list.toArray(new Object[list.size()]));
aoqi@0 156 }
aoqi@0 157 }
aoqi@0 158
aoqi@0 159 @AfterSuite
aoqi@0 160 // After the suite is done, dump any errors to output
aoqi@0 161 public void dumpErrors() {
aoqi@0 162 if (!suiteErrors.isEmpty())
aoqi@0 163 System.err.println("Errors found in test suite: " + suiteErrors);
aoqi@0 164 }
aoqi@0 165
aoqi@0 166 /**
aoqi@0 167 * Get a description of this test case; since test cases may be combinatorially
aoqi@0 168 * generated, this should include all information needed to describe the test case
aoqi@0 169 */
aoqi@0 170 protected String getTestCaseDescription() {
aoqi@0 171 return this.toString();
aoqi@0 172 }
aoqi@0 173
aoqi@0 174 /** Assert that all previous calls to compile() succeeded */
aoqi@0 175 protected void assertCompileSucceeded() {
aoqi@0 176 if (diags.errorsFound())
aoqi@0 177 fail("Expected successful compilation");
aoqi@0 178 }
aoqi@0 179
aoqi@0 180 /**
aoqi@0 181 * If the provided boolean is true, assert all previous compiles succeeded,
aoqi@0 182 * otherwise assert that a compile failed.
aoqi@0 183 * */
aoqi@0 184 protected void assertCompileSucceededIff(boolean b) {
aoqi@0 185 if (b)
aoqi@0 186 assertCompileSucceeded();
aoqi@0 187 else
aoqi@0 188 assertCompileFailed();
aoqi@0 189 }
aoqi@0 190
aoqi@0 191 /** Assert that a previous call to compile() failed */
aoqi@0 192 protected void assertCompileFailed() {
aoqi@0 193 if (!diags.errorsFound())
aoqi@0 194 fail("Expected failed compilation");
aoqi@0 195 }
aoqi@0 196
aoqi@0 197 /** Assert that a previous call to compile() failed with a specific error key */
aoqi@0 198 protected void assertCompileFailed(String message) {
aoqi@0 199 if (!diags.errorsFound())
aoqi@0 200 fail("Expected failed compilation: " + message);
aoqi@0 201 }
aoqi@0 202
aoqi@0 203 /** Assert that a previous call to compile() failed with all of the specified error keys */
aoqi@0 204 protected void assertCompileErrors(String... keys) {
aoqi@0 205 if (!diags.errorsFound())
aoqi@0 206 fail("Expected failed compilation");
aoqi@0 207 for (String k : keys)
aoqi@0 208 if (!diags.containsErrorKey(k))
aoqi@0 209 fail("Expected compilation error " + k);
aoqi@0 210 }
aoqi@0 211
aoqi@0 212 /** Convert an object, which may be a Template or a String, into a Template */
aoqi@0 213 protected Template asTemplate(Object o) {
aoqi@0 214 if (o instanceof Template)
aoqi@0 215 return (Template) o;
aoqi@0 216 else if (o instanceof String)
aoqi@0 217 return new StringTemplate((String) o);
aoqi@0 218 else
aoqi@0 219 return new StringTemplate(o.toString());
aoqi@0 220 }
aoqi@0 221
aoqi@0 222 /** Compile all registered source files */
aoqi@0 223 protected void compile() throws IOException {
aoqi@0 224 compile(false);
aoqi@0 225 }
aoqi@0 226
aoqi@0 227 /** Compile all registered source files, optionally generating class files
aoqi@0 228 * and returning a File describing the directory to which they were written */
aoqi@0 229 protected File compile(boolean generate) throws IOException {
aoqi@0 230 List<JavaFileObject> files = new ArrayList<>();
aoqi@0 231 for (Pair<String, Template> e : sourceFiles)
aoqi@0 232 files.add(new FileAdapter(e.fst, asTemplate(e.snd)));
aoqi@0 233 return compile(classpaths, files, generate);
aoqi@0 234 }
aoqi@0 235
aoqi@0 236 /** Compile all registered source files, using the provided list of class paths
aoqi@0 237 * for finding required classfiles, optionally generating class files
aoqi@0 238 * and returning a File describing the directory to which they were written */
aoqi@0 239 protected File compile(List<File> classpaths, boolean generate) throws IOException {
aoqi@0 240 List<JavaFileObject> files = new ArrayList<>();
aoqi@0 241 for (Pair<String, Template> e : sourceFiles)
aoqi@0 242 files.add(new FileAdapter(e.fst, asTemplate(e.snd)));
aoqi@0 243 return compile(classpaths, files, generate);
aoqi@0 244 }
aoqi@0 245
aoqi@0 246 private File compile(List<File> classpaths, List<JavaFileObject> files, boolean generate) throws IOException {
aoqi@0 247 JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();
aoqi@0 248 StandardJavaFileManager fm = systemJavaCompiler.getStandardFileManager(null, null, null);
aoqi@0 249 if (classpaths.size() > 0)
aoqi@0 250 fm.setLocation(StandardLocation.CLASS_PATH, classpaths);
aoqi@0 251 JavacTask ct = (JavacTask) systemJavaCompiler.getTask(null, fm, diags, compileOptions, null, files);
aoqi@0 252 if (generate) {
aoqi@0 253 File destDir = new File(root, Integer.toString(counter.incrementAndGet()));
aoqi@0 254 // @@@ Assert that this directory didn't exist, or start counter at max+1
aoqi@0 255 destDir.mkdirs();
aoqi@0 256 fm.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(destDir));
aoqi@0 257 ct.generate();
aoqi@0 258 return destDir;
aoqi@0 259 }
aoqi@0 260 else {
aoqi@0 261 ct.analyze();
aoqi@0 262 return nullDir;
aoqi@0 263 }
aoqi@0 264 }
aoqi@0 265
aoqi@0 266 /** Load the given class using the provided list of class paths */
aoqi@0 267 protected Class<?> loadClass(String className, File... destDirs) {
aoqi@0 268 try {
aoqi@0 269 List<URL> list = new ArrayList<>();
aoqi@0 270 for (File f : destDirs)
aoqi@0 271 list.add(new URL("file:" + f.toString().replace("\\", "/") + "/"));
aoqi@0 272 return Class.forName(className, true, new URLClassLoader(list.toArray(new URL[list.size()])));
aoqi@0 273 } catch (ClassNotFoundException | MalformedURLException e) {
aoqi@0 274 throw new RuntimeException("Error loading class " + className, e);
aoqi@0 275 }
aoqi@0 276 }
aoqi@0 277
aoqi@0 278 /** An implementation of Template which is backed by a String */
aoqi@0 279 protected class StringTemplate implements Template {
aoqi@0 280 protected final String template;
aoqi@0 281
aoqi@0 282 public StringTemplate(String template) {
aoqi@0 283 this.template = template;
aoqi@0 284 }
aoqi@0 285
aoqi@0 286 public String expand(String selector) {
aoqi@0 287 return Behavior.expandTemplate(template, currentResolver);
aoqi@0 288 }
aoqi@0 289
aoqi@0 290 public String toString() {
aoqi@0 291 return expand("");
aoqi@0 292 }
aoqi@0 293
aoqi@0 294 public StringTemplate with(final String key, final String value) {
aoqi@0 295 return new StringTemplateWithResolver(template, new KeyResolver(key, value));
aoqi@0 296 }
aoqi@0 297
aoqi@0 298 }
aoqi@0 299
aoqi@0 300 /** An implementation of Template which is backed by a String and which
aoqi@0 301 * encapsulates a Resolver for resolving embedded tags. */
aoqi@0 302 protected class StringTemplateWithResolver extends StringTemplate {
aoqi@0 303 private final Resolver localResolver;
aoqi@0 304
aoqi@0 305 public StringTemplateWithResolver(String template, Resolver localResolver) {
aoqi@0 306 super(template);
aoqi@0 307 this.localResolver = localResolver;
aoqi@0 308 }
aoqi@0 309
aoqi@0 310 @Override
aoqi@0 311 public String expand(String selector) {
aoqi@0 312 Resolver saved = currentResolver;
aoqi@0 313 currentResolver = new ChainedResolver(currentResolver, localResolver);
aoqi@0 314 try {
aoqi@0 315 return super.expand(selector);
aoqi@0 316 }
aoqi@0 317 finally {
aoqi@0 318 currentResolver = saved;
aoqi@0 319 }
aoqi@0 320 }
aoqi@0 321
aoqi@0 322 @Override
aoqi@0 323 public StringTemplate with(String key, String value) {
aoqi@0 324 return new StringTemplateWithResolver(template, new ChainedResolver(localResolver, new KeyResolver(key, value)));
aoqi@0 325 }
aoqi@0 326 }
aoqi@0 327
aoqi@0 328 /** A Resolver which uses a Map to resolve tags */
aoqi@0 329 private class KeyResolver implements Template.Resolver {
aoqi@0 330 private final String key;
aoqi@0 331 private final String value;
aoqi@0 332
aoqi@0 333 public KeyResolver(String key, String value) {
aoqi@0 334 this.key = key;
aoqi@0 335 this.value = value;
aoqi@0 336 }
aoqi@0 337
aoqi@0 338 @Override
aoqi@0 339 public Template lookup(String k) {
aoqi@0 340 return key.equals(k) ? new StringTemplate(value) : null;
aoqi@0 341 }
aoqi@0 342 }
aoqi@0 343
aoqi@0 344 private class FileAdapter extends SimpleJavaFileObject {
aoqi@0 345 private final String filename;
aoqi@0 346 private final Template template;
aoqi@0 347
aoqi@0 348 public FileAdapter(String filename, Template template) {
aoqi@0 349 super(URI.create("myfo:/" + filename), Kind.SOURCE);
aoqi@0 350 this.template = template;
aoqi@0 351 this.filename = filename;
aoqi@0 352 }
aoqi@0 353
aoqi@0 354 public CharSequence getCharContent(boolean ignoreEncodingErrors) {
aoqi@0 355 return toString();
aoqi@0 356 }
aoqi@0 357
aoqi@0 358 public String toString() {
aoqi@0 359 return Template.Behavior.expandTemplate(template.expand(filename), defaultResolver);
aoqi@0 360 }
aoqi@0 361 }
aoqi@0 362 }

mercurial