aboutsummaryrefslogtreecommitdiff
path: root/test/lib
diff options
context:
space:
mode:
authoremc <none@none>2013-09-09 17:11:55 -0400
committeremc <none@none>2013-09-09 17:11:55 -0400
commitb5c4d03ed5aef327c17af1f4e46d5300fd5cfa6b (patch)
tree472d03c195f30a7da73f86d6bcb302eda7b13540 /test/lib
parent5f2c84f66927d31f0e1973f7e527f9bc8979e22c (diff)
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
Diffstat (limited to 'test/lib')
-rw-r--r--test/lib/combo/TEST.properties4
-rw-r--r--test/lib/combo/tools/javac/combo/Diagnostics.java82
-rw-r--r--test/lib/combo/tools/javac/combo/JavacTemplateTestBase.java362
-rw-r--r--test/lib/combo/tools/javac/combo/Template.java112
-rw-r--r--test/lib/combo/tools/javac/combo/TemplateTest.java94
5 files changed, 654 insertions, 0 deletions
diff --git a/test/lib/combo/TEST.properties b/test/lib/combo/TEST.properties
new file mode 100644
index 00000000..341ff2af
--- /dev/null
+++ b/test/lib/combo/TEST.properties
@@ -0,0 +1,4 @@
+# This file identifies root(s) of the test-ng hierarchy.
+
+
+TestNG.dirs = .
diff --git a/test/lib/combo/tools/javac/combo/Diagnostics.java b/test/lib/combo/tools/javac/combo/Diagnostics.java
new file mode 100644
index 00000000..6f1774d6
--- /dev/null
+++ b/test/lib/combo/tools/javac/combo/Diagnostics.java
@@ -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;
+ }
+}
diff --git a/test/lib/combo/tools/javac/combo/JavacTemplateTestBase.java b/test/lib/combo/tools/javac/combo/JavacTemplateTestBase.java
new file mode 100644
index 00000000..e2a8bd7c
--- /dev/null
+++ b/test/lib/combo/tools/javac/combo/JavacTemplateTestBase.java
@@ -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);
+ }
+ }
+}
diff --git a/test/lib/combo/tools/javac/combo/Template.java b/test/lib/combo/tools/javac/combo/Template.java
new file mode 100644
index 00000000..8ad4d72e
--- /dev/null
+++ b/test/lib/combo/tools/javac/combo/Template.java
@@ -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;
+ }
+}
diff --git a/test/lib/combo/tools/javac/combo/TemplateTest.java b/test/lib/combo/tools/javac/combo/TemplateTest.java
new file mode 100644
index 00000000..35645687
--- /dev/null
+++ b/test/lib/combo/tools/javac/combo/TemplateTest.java
@@ -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}");
+ }
+}