test/tools/javac/lambda/bridge/template_tests/BridgeMethodTestCase.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 */
aoqi@0 24 import java.io.File;
aoqi@0 25 import java.io.IOException;
aoqi@0 26 import java.lang.reflect.Method;
aoqi@0 27 import java.util.ArrayList;
aoqi@0 28 import java.util.Arrays;
aoqi@0 29 import java.util.HashMap;
aoqi@0 30 import java.util.List;
aoqi@0 31 import java.util.Map;
aoqi@0 32 import java.util.StringJoiner;
aoqi@0 33
aoqi@0 34 import org.testng.annotations.BeforeMethod;
aoqi@0 35 import org.testng.annotations.Test;
aoqi@0 36
aoqi@0 37 import tools.javac.combo.*;
aoqi@0 38
aoqi@0 39 import static org.testng.Assert.fail;
aoqi@0 40
aoqi@0 41 /**
aoqi@0 42 * BridgeMethodTestCase -- used for asserting linkage to bridges under separate compilation.
aoqi@0 43 *
aoqi@0 44 * Example test case:
aoqi@0 45 * public void test1() throws IOException, ReflectiveOperationException {
aoqi@0 46 * compileSpec("C(Bc1(A))");
aoqi@0 47 * assertLinkage("C", LINKAGE_ERROR, "B1");
aoqi@0 48 * recompileSpec("C(Bc1(Ac0))", "A");
aoqi@0 49 * assertLinkage("C", "A0", "B1");
aoqi@0 50 * }
aoqi@0 51 *
aoqi@0 52 * This compiles A, B, and C, asserts that C.m()Object does not exist, asserts
aoqi@0 53 * that C.m()Number eventually invokes B.m()Number, recompiles B, and then asserts
aoqi@0 54 * that the result of calling C.m()Object now arrives at A.
aoqi@0 55 *
aoqi@0 56 * @author Brian Goetz
aoqi@0 57 */
aoqi@0 58
aoqi@0 59 @Test
aoqi@0 60 public abstract class BridgeMethodTestCase extends JavacTemplateTestBase {
aoqi@0 61
aoqi@0 62 private static final String TYPE_LETTERS = "ABCDIJK";
aoqi@0 63
aoqi@0 64 private static final String BASE_INDEX_CLASS = "class C0 {\n" +
aoqi@0 65 " int val;\n" +
aoqi@0 66 " C0(int val) { this.val = val; }\n" +
aoqi@0 67 " public int getVal() { return val; }\n" +
aoqi@0 68 "}\n";
aoqi@0 69 private static final String INDEX_CLASS_TEMPLATE = "class C#ID extends C#PREV {\n" +
aoqi@0 70 " C#ID(int val) { super(val); }\n" +
aoqi@0 71 "}\n";
aoqi@0 72
aoqi@0 73
aoqi@0 74
aoqi@0 75 protected static String LINKAGE_ERROR = "-1";
aoqi@0 76
aoqi@0 77 private List<File> compileDirs = new ArrayList<>();
aoqi@0 78
aoqi@0 79 /**
aoqi@0 80 * Compile all the classes in a class spec, and put them on the classpath.
aoqi@0 81 *
aoqi@0 82 * The spec is the specification for a nest of classes, using the following notation
aoqi@0 83 * A, B represent abstract classes
aoqi@0 84 * C represents a concrete class
aoqi@0 85 * I, J, K represent interfaces
aoqi@0 86 * Lowercase 'c' following a class means that the method m() is concrete
aoqi@0 87 * Lowercase 'a' following a class or interface means that the method m() is abstract
aoqi@0 88 * Lowercase 'd' following an interface means that the method m() is default
aoqi@0 89 * A number 0, 1, or 2 following the lowercase letter indicates the return type of that method
aoqi@0 90 * 0 = Object, 1 = Number, 2 = Integer (these form an inheritance chain so bridges are generated)
aoqi@0 91 * A classes supertypes follow its method spec, in parentheses
aoqi@0 92 * Examples:
aoqi@0 93 * C(Ia0, Jd0) -- C extends I and J, I has abstract m()Object, J has default m()Object
aoqi@0 94 * Cc1(Ia0) -- C has concrete m()Number, extends I with abstract m()Object
aoqi@0 95 * If a type must appear multiple times, its full spec must be in the first occurrence
aoqi@0 96 * Example:
aoqi@0 97 * C(I(Kd0), J(K))
aoqi@0 98 */
aoqi@0 99 protected void compileSpec(String spec) throws IOException {
aoqi@0 100 compileSpec(spec, false);
aoqi@0 101 }
aoqi@0 102
aoqi@0 103 /**
aoqi@0 104 * Compile all the classes in a class spec, and assert that there were compilation errors.
aoqi@0 105 */
aoqi@0 106 protected void compileSpec(String spec, String... errorKeys) throws IOException {
aoqi@0 107 compileSpec(spec, false, errorKeys);
aoqi@0 108 }
aoqi@0 109
aoqi@0 110 protected void compileSpec(String spec, boolean debug, String... errorKeys) throws IOException {
aoqi@0 111 ClassModel cm = new Parser(spec).parseClassModel();
aoqi@0 112 for (int i = 0; i <= cm.maxIndex() ; i++) {
aoqi@0 113 if (debug) System.out.println(indexClass(i));
aoqi@0 114 addSourceFile(String.format("C%d.java", i), new StringTemplate(indexClass(i)));
aoqi@0 115 }
aoqi@0 116 for (Map.Entry<String, ClassModel> e : classes(cm).entrySet()) {
aoqi@0 117 if (debug) System.out.println(e.getValue().toSource());
aoqi@0 118 addSourceFile(e.getKey() + ".java", new StringTemplate(e.getValue().toSource()));
aoqi@0 119 }
aoqi@0 120 compileDirs.add(compile(true));
aoqi@0 121 resetSourceFiles();
aoqi@0 122 if (errorKeys.length == 0)
aoqi@0 123 assertCompileSucceeded();
aoqi@0 124 else
aoqi@0 125 assertCompileErrors(errorKeys);
aoqi@0 126 }
aoqi@0 127
aoqi@0 128 /**
aoqi@0 129 * Recompile only a subset of classes in the class spec, as named by names,
aoqi@0 130 * and put them on the classpath such that they shadow earlier versions of that class.
aoqi@0 131 */
aoqi@0 132 protected void recompileSpec(String spec, String... names) throws IOException {
aoqi@0 133 List<String> nameList = Arrays.asList(names);
aoqi@0 134 ClassModel cm = new Parser(spec).parseClassModel();
aoqi@0 135 for (int i = 0; i <= cm.maxIndex() ; i++) {
aoqi@0 136 addSourceFile(String.format("C%d.java", i), new StringTemplate(indexClass(i)));
aoqi@0 137 }
aoqi@0 138 for (Map.Entry<String, ClassModel> e : classes(cm).entrySet())
aoqi@0 139 if (nameList.contains(e.getKey()))
aoqi@0 140 addSourceFile(e.getKey() + ".java", new StringTemplate(e.getValue().toSource()));
aoqi@0 141 compileDirs.add(compile(Arrays.asList(classPaths()), true));
aoqi@0 142 resetSourceFiles();
aoqi@0 143 assertCompileSucceeded();
aoqi@0 144 }
aoqi@0 145
aoqi@0 146 protected void assertLinkage(String name, String... expected) throws ReflectiveOperationException {
aoqi@0 147 for (int i=0; i<expected.length; i++) {
aoqi@0 148 String e = expected[i];
aoqi@0 149 if (e.equals(LINKAGE_ERROR)) {
aoqi@0 150 try {
aoqi@0 151 int actual = invoke(name, i);
aoqi@0 152 fail("Expected linkage error, got" + fromNum(actual));
aoqi@0 153 }
aoqi@0 154 catch (LinkageError x) {
aoqi@0 155 // success
aoqi@0 156 }
aoqi@0 157 }
aoqi@0 158 else {
aoqi@0 159 if (e.length() == 1)
aoqi@0 160 e += "0";
aoqi@0 161 int expectedInt = toNum(e);
aoqi@0 162 int actual = invoke(name, i);
aoqi@0 163 if (expectedInt != actual)
aoqi@0 164 fail(String.format("Expected %s but found %s for %s.m()%d", fromNum(expectedInt), fromNum(actual), name, i));
aoqi@0 165 }
aoqi@0 166 }
aoqi@0 167 }
aoqi@0 168
aoqi@0 169 private Map<String, ClassModel> classes(ClassModel cm) {
aoqi@0 170 HashMap<String, ClassModel> m = new HashMap<>();
aoqi@0 171 classesHelper(cm, m);
aoqi@0 172 return m;
aoqi@0 173 }
aoqi@0 174
aoqi@0 175 private String indexClass(int index) {
aoqi@0 176 if (index == 0) {
aoqi@0 177 return BASE_INDEX_CLASS;
aoqi@0 178 } else {
aoqi@0 179 return INDEX_CLASS_TEMPLATE
aoqi@0 180 .replace("#ID", String.valueOf(index))
aoqi@0 181 .replace("#PREV", String.valueOf(index - 1));
aoqi@0 182 }
aoqi@0 183 }
aoqi@0 184
aoqi@0 185 private static String overrideName(int index) {
aoqi@0 186 return "C" + index;
aoqi@0 187 }
aoqi@0 188
aoqi@0 189 private void classesHelper(ClassModel cm, Map<String, ClassModel> m) {
aoqi@0 190 if (!m.containsKey(cm.name))
aoqi@0 191 m.put(cm.name, cm);
aoqi@0 192 for (ClassModel s : cm.supertypes)
aoqi@0 193 classesHelper(s, m);
aoqi@0 194 }
aoqi@0 195
aoqi@0 196 private static String fromNum(int num) {
aoqi@0 197 return String.format("%c%d", TYPE_LETTERS.charAt(num / 10), num % 10);
aoqi@0 198 }
aoqi@0 199
aoqi@0 200 private static int toNum(String name, int index) {
aoqi@0 201 return 10*(TYPE_LETTERS.indexOf(name.charAt(0))) + index;
aoqi@0 202 }
aoqi@0 203
aoqi@0 204 private static int toNum(String string) {
aoqi@0 205 return 10*(TYPE_LETTERS.indexOf(string.charAt(0))) + Integer.parseInt(string.substring(1, 2));
aoqi@0 206 }
aoqi@0 207
aoqi@0 208 private int invoke(String name, int index) throws ReflectiveOperationException {
aoqi@0 209 File[] files = classPaths();
aoqi@0 210 Class clazz = loadClass(name, files);
aoqi@0 211 Method[] ms = clazz.getMethods();
aoqi@0 212 for (Method m : ms) {
aoqi@0 213 if (m.getName().equals("m") && m.getReturnType().getName().equals(overrideName(index))) {
aoqi@0 214 m.setAccessible(true);
aoqi@0 215 Object instance = clazz.newInstance();
aoqi@0 216 Object c0 = m.invoke(instance);
aoqi@0 217 Method getVal = c0.getClass().getMethod("getVal");
aoqi@0 218 getVal.setAccessible(true);
aoqi@0 219 return (int)getVal.invoke(c0);
aoqi@0 220 }
aoqi@0 221 }
aoqi@0 222 throw new NoSuchMethodError("cannot find method m()" + index + " in class " + name);
aoqi@0 223 }
aoqi@0 224
aoqi@0 225 private File[] classPaths() {
aoqi@0 226 File[] files = new File[compileDirs.size()];
aoqi@0 227 for (int i=0; i<files.length; i++)
aoqi@0 228 files[files.length - i - 1] = compileDirs.get(i);
aoqi@0 229 return files;
aoqi@0 230 }
aoqi@0 231
aoqi@0 232 @BeforeMethod
aoqi@0 233 @Override
aoqi@0 234 public void reset() {
aoqi@0 235 compileDirs.clear();
aoqi@0 236 super.reset();
aoqi@0 237 }
aoqi@0 238
aoqi@0 239 private static class ClassModel {
aoqi@0 240
aoqi@0 241 enum MethodType {
aoqi@0 242 ABSTRACT('a'), CONCRETE('c'), DEFAULT('d');
aoqi@0 243
aoqi@0 244 public final char designator;
aoqi@0 245
aoqi@0 246 MethodType(char designator) {
aoqi@0 247 this.designator = designator;
aoqi@0 248 }
aoqi@0 249
aoqi@0 250 public static MethodType find(char c) {
aoqi@0 251 for (MethodType m : values())
aoqi@0 252 if (m.designator == c)
aoqi@0 253 return m;
aoqi@0 254 throw new IllegalArgumentException();
aoqi@0 255 }
aoqi@0 256 }
aoqi@0 257
aoqi@0 258 private final String name;
aoqi@0 259 private final boolean isInterface;
aoqi@0 260 private final List<ClassModel> supertypes;
aoqi@0 261 private final MethodType methodType;
aoqi@0 262 private final int methodIndex;
aoqi@0 263
aoqi@0 264 private ClassModel(String name,
aoqi@0 265 boolean anInterface,
aoqi@0 266 List<ClassModel> supertypes,
aoqi@0 267 MethodType methodType,
aoqi@0 268 int methodIndex) {
aoqi@0 269 this.name = name;
aoqi@0 270 isInterface = anInterface;
aoqi@0 271 this.supertypes = supertypes;
aoqi@0 272 this.methodType = methodType;
aoqi@0 273 this.methodIndex = methodIndex;
aoqi@0 274 }
aoqi@0 275
aoqi@0 276 @Override
aoqi@0 277 public String toString() {
aoqi@0 278 StringBuilder sb = new StringBuilder();
aoqi@0 279 sb.append(name);
aoqi@0 280 if (methodType != null) {
aoqi@0 281 sb.append(methodType.designator);
aoqi@0 282 sb.append(methodIndex);
aoqi@0 283 }
aoqi@0 284 if (!supertypes.isEmpty()) {
aoqi@0 285 sb.append("(");
aoqi@0 286 for (int i=0; i<supertypes.size(); i++) {
aoqi@0 287 if (i > 0)
aoqi@0 288 sb.append(",");
aoqi@0 289 sb.append(supertypes.get(i).toString());
aoqi@0 290 }
aoqi@0 291 sb.append(")");
aoqi@0 292 }
aoqi@0 293 return sb.toString();
aoqi@0 294 }
aoqi@0 295
aoqi@0 296 int maxIndex() {
aoqi@0 297 int maxSoFar = methodIndex;
aoqi@0 298 for (ClassModel cm : supertypes) {
aoqi@0 299 maxSoFar = Math.max(cm.maxIndex(), maxSoFar);
aoqi@0 300 }
aoqi@0 301 return maxSoFar;
aoqi@0 302 }
aoqi@0 303
aoqi@0 304 public String toSource() {
aoqi@0 305 String extendsClause = "";
aoqi@0 306 String implementsClause = "";
aoqi@0 307 String methodBody = "";
aoqi@0 308 boolean isAbstract = "AB".contains(name);
aoqi@0 309
aoqi@0 310 for (ClassModel s : supertypes) {
aoqi@0 311 if (!s.isInterface) {
aoqi@0 312 extendsClause = String.format("extends %s", s.name);
aoqi@0 313 break;
aoqi@0 314 }
aoqi@0 315 }
aoqi@0 316
aoqi@0 317 StringJoiner sj = new StringJoiner(", ");
aoqi@0 318 for (ClassModel s : supertypes)
aoqi@0 319 if (s.isInterface)
aoqi@0 320 sj.add(s.name);
aoqi@0 321 if (sj.length() > 0) {
aoqi@0 322 if (isInterface)
aoqi@0 323 implementsClause = "extends " + sj.toString();
aoqi@0 324 else
aoqi@0 325 implementsClause = "implements " + sj.toString();
aoqi@0 326 }
aoqi@0 327 if (methodType != null) {
aoqi@0 328 switch (methodType) {
aoqi@0 329 case ABSTRACT:
aoqi@0 330 methodBody = String.format("public abstract %s m();", overrideName(methodIndex));
aoqi@0 331 break;
aoqi@0 332 case CONCRETE:
aoqi@0 333 methodBody = String.format("public %s m() { return new %s(%d); };",
aoqi@0 334 overrideName(methodIndex), overrideName(methodIndex), toNum(name, methodIndex));
aoqi@0 335 break;
aoqi@0 336 case DEFAULT:
aoqi@0 337 methodBody = String.format("public default %s m() { return new %s(%d); };",
aoqi@0 338 overrideName(methodIndex), overrideName(methodIndex), toNum(name, methodIndex));
aoqi@0 339 break;
aoqi@0 340
aoqi@0 341 }
aoqi@0 342 }
aoqi@0 343
aoqi@0 344 return String.format("public %s %s %s %s %s { %s }", isAbstract ? "abstract" : "",
aoqi@0 345 isInterface ? "interface" : "class",
aoqi@0 346 name, extendsClause, implementsClause, methodBody);
aoqi@0 347 }
aoqi@0 348 }
aoqi@0 349
aoqi@0 350 private static class Parser {
aoqi@0 351 private final String input;
aoqi@0 352 private final char[] chars;
aoqi@0 353 private int index;
aoqi@0 354
aoqi@0 355 private Parser(String s) {
aoqi@0 356 input = s;
aoqi@0 357 chars = s.toCharArray();
aoqi@0 358 }
aoqi@0 359
aoqi@0 360 private char peek() {
aoqi@0 361 return index < chars.length ? chars[index] : 0;
aoqi@0 362 }
aoqi@0 363
aoqi@0 364 private boolean peek(String validChars) {
aoqi@0 365 return validChars.indexOf(peek()) >= 0;
aoqi@0 366 }
aoqi@0 367
aoqi@0 368 private char advanceIf(String validChars) {
aoqi@0 369 if (peek(validChars))
aoqi@0 370 return chars[index++];
aoqi@0 371 else
aoqi@0 372 return 0;
aoqi@0 373 }
aoqi@0 374
aoqi@0 375 private char advanceIfDigit() {
aoqi@0 376 return advanceIf("0123456789");
aoqi@0 377 }
aoqi@0 378
aoqi@0 379 private int index() {
aoqi@0 380 StringBuilder buf = new StringBuilder();
aoqi@0 381 char c = advanceIfDigit();
aoqi@0 382 while (c != 0) {
aoqi@0 383 buf.append(c);
aoqi@0 384 c = advanceIfDigit();
aoqi@0 385 }
aoqi@0 386 return Integer.valueOf(buf.toString());
aoqi@0 387 }
aoqi@0 388
aoqi@0 389 private char advance() {
aoqi@0 390 return chars[index++];
aoqi@0 391 }
aoqi@0 392
aoqi@0 393 private char expect(String validChars) {
aoqi@0 394 char c = advanceIf(validChars);
aoqi@0 395 if (c == 0)
aoqi@0 396 throw new IllegalArgumentException(String.format("Expecting %s at position %d of %s", validChars, index, input));
aoqi@0 397 return c;
aoqi@0 398 }
aoqi@0 399
aoqi@0 400 public ClassModel parseClassModel() {
aoqi@0 401 List<ClassModel> supers = new ArrayList<>();
aoqi@0 402 char name = expect(TYPE_LETTERS);
aoqi@0 403 boolean isInterface = "IJK".indexOf(name) >= 0;
aoqi@0 404 ClassModel.MethodType methodType = peek(isInterface ? "ad" : "ac") ? ClassModel.MethodType.find(advance()) : null;
aoqi@0 405 int methodIndex = 0;
aoqi@0 406 if (methodType != null) {
aoqi@0 407 methodIndex = index();
aoqi@0 408 }
aoqi@0 409 if (peek() == '(') {
aoqi@0 410 advance();
aoqi@0 411 supers.add(parseClassModel());
aoqi@0 412 while (peek() == ',') {
aoqi@0 413 advance();
aoqi@0 414 supers.add(parseClassModel());
aoqi@0 415 }
aoqi@0 416 expect(")");
aoqi@0 417 }
aoqi@0 418 return new ClassModel(new String(new char[]{ name }), isInterface, supers, methodType, methodIndex);
aoqi@0 419 }
aoqi@0 420 }
aoqi@0 421 }

mercurial