|  | // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | package com.android.tools.r8.naming; | 
|  |  | 
|  | import static org.junit.Assert.assertEquals; | 
|  | import static org.junit.Assert.assertTrue; | 
|  | import static org.junit.Assert.fail; | 
|  |  | 
|  | import com.android.tools.r8.TestBase; | 
|  | import com.android.tools.r8.naming.signature.GenericSignatureAction; | 
|  | import com.android.tools.r8.naming.signature.GenericSignatureParser; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.common.collect.ImmutableSet; | 
|  | import java.lang.reflect.GenericSignatureFormatError; | 
|  | import java.util.List; | 
|  | import java.util.Set; | 
|  | import java.util.function.BiConsumer; | 
|  | import java.util.function.Consumer; | 
|  | import java.util.function.Supplier; | 
|  | import org.junit.Test; | 
|  |  | 
|  | public class GenericSignatureParserTest extends TestBase { | 
|  |  | 
|  | private static class ReGenerateGenericSignatureRewriter | 
|  | implements GenericSignatureAction<String> { | 
|  |  | 
|  | private StringBuilder renamedSignature; | 
|  |  | 
|  | public String getRenamedSignature() { | 
|  | return renamedSignature.toString(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void parsedSymbol(char symbol) { | 
|  | renamedSignature.append(symbol); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void parsedIdentifier(String identifier) { | 
|  | renamedSignature.append(identifier); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String parsedTypeName(String name, ParserPosition parserPosition) { | 
|  | renamedSignature.append(name); | 
|  | return name; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String parsedInnerTypeName(String enclosingType, String name) { | 
|  | renamedSignature.append(name); | 
|  | return name; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void start() { | 
|  | renamedSignature = new StringBuilder(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void stop() { | 
|  | // nothing to do | 
|  | } | 
|  | } | 
|  |  | 
|  | public void parseSimpleError(BiConsumer<GenericSignatureParser<String>, String> parse, | 
|  | Consumer<GenericSignatureFormatError> errorChecker) { | 
|  | try { | 
|  | String signature = "X"; | 
|  | GenericSignatureParser<String> parser = | 
|  | new GenericSignatureParser<>(new ReGenerateGenericSignatureRewriter()); | 
|  | parse.accept(parser, signature); | 
|  | fail("Successfully parsed " + signature); | 
|  | } catch (GenericSignatureFormatError e) { | 
|  | errorChecker.accept(e); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void simpleParseError() { | 
|  | parseSimpleError( | 
|  | GenericSignatureParser::parseClassSignature, | 
|  | e -> assertTrue(e.getMessage().startsWith("Expected L at position 1"))); | 
|  | // TODO(sgjesse): The position 2 reported here is one off. | 
|  | parseSimpleError( | 
|  | GenericSignatureParser::parseFieldSignature, | 
|  | e -> assertTrue(e.getMessage().startsWith("Expected L, [ or T at position 2"))); | 
|  | parseSimpleError(GenericSignatureParser::parseMethodSignature, | 
|  | e -> assertTrue(e.getMessage().startsWith("Expected ( at position 1"))); | 
|  | } | 
|  |  | 
|  | private void parseSignature(String signature, Set<Integer> validPrefixes, | 
|  | Consumer<String> parser, ReGenerateGenericSignatureRewriter rewriter) { | 
|  | for (int i = 0; i < 2; i++) { | 
|  | parser.accept(signature); | 
|  | assertEquals(signature, rewriter.getRenamedSignature()); | 
|  | } | 
|  |  | 
|  | for (int i = 1; i < signature.length(); i++) { | 
|  | try { | 
|  | if (validPrefixes == null || !validPrefixes.contains(i)) { | 
|  | parser.accept(signature.substring(0, i)); | 
|  | fail("Successfully parsed " + signature.substring(0, i) + " (position " + i +")"); | 
|  | } | 
|  | } catch (GenericSignatureFormatError e) { | 
|  | assertTrue(e.getMessage().contains("at position " + (i + 1))); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private void parseClassSignature(String signature, Set<Integer> validPrefixes) { | 
|  | ReGenerateGenericSignatureRewriter rewriter = new ReGenerateGenericSignatureRewriter(); | 
|  | GenericSignatureParser<String> parser = new GenericSignatureParser<>(rewriter); | 
|  |  | 
|  | parseSignature(signature, validPrefixes, parser::parseClassSignature, rewriter); | 
|  | } | 
|  |  | 
|  | private void parseFieldSignature(String signature, Set<Integer> validPrefixes) { | 
|  | ReGenerateGenericSignatureRewriter rewriter = new ReGenerateGenericSignatureRewriter(); | 
|  | GenericSignatureParser<String> parser = new GenericSignatureParser<>(rewriter); | 
|  |  | 
|  | parseSignature(signature, validPrefixes, parser::parseFieldSignature, rewriter); | 
|  | } | 
|  |  | 
|  | private void parseMethodSignature(String signature, Set<Integer> validPrefixes) { | 
|  | ReGenerateGenericSignatureRewriter rewriter = new ReGenerateGenericSignatureRewriter(); | 
|  | GenericSignatureParser<String> parser = new GenericSignatureParser<>(rewriter); | 
|  |  | 
|  | parseSignature(signature, validPrefixes, parser::parseMethodSignature, rewriter); | 
|  | } | 
|  |  | 
|  | public void forClassSignatures(BiConsumer<String, Set<Integer>> consumer) { | 
|  | consumer.accept("Ljava/lang/Object;", null); | 
|  | consumer.accept("LOuter$InnerInterface<TU;>;", null); | 
|  |  | 
|  | consumer.accept("La;", null); | 
|  | consumer.accept("La.i;", null); | 
|  | consumer.accept("La.i.j;", null); | 
|  | consumer.accept("La/b;", null); | 
|  | consumer.accept("La/b.i;", null); | 
|  | consumer.accept("La/b.i.j;", null); | 
|  | consumer.accept("La/b/c;", null); | 
|  | consumer.accept("La/b/c.i;", null); | 
|  | consumer.accept("La/b/c.i.j;", null); | 
|  | consumer.accept("La$b;", null); | 
|  | consumer.accept("La$b.i;", null); | 
|  | consumer.accept("La$b.i.j;", null); | 
|  | consumer.accept("La$b$c;", null); | 
|  | consumer.accept("La$b$c.i;", null); | 
|  | consumer.accept("La$b$c.i.j;", null); | 
|  |  | 
|  | StringBuilder builder = new StringBuilder(); | 
|  | for (int i = 0; i < 3; i++) { | 
|  | builder.append("TT;"); | 
|  | consumer.accept("La<" + builder.toString() + ">;", null); | 
|  | consumer.accept("La<" + builder.toString() + ">.i;", null); | 
|  | consumer.accept("La<" + builder.toString() + ">.i.j;", null); | 
|  | consumer.accept("La/b<" + builder.toString() + ">;", null); | 
|  | consumer.accept("La/b<" + builder.toString() + ">.i;", null); | 
|  | consumer.accept("La/b<" + builder.toString() + ">.i.j;", null); | 
|  | consumer.accept("La/b/c<" + builder.toString() + ">;", null); | 
|  | consumer.accept("La/b/c<" + builder.toString() + ">.i;", null); | 
|  | consumer.accept("La/b/c<" + builder.toString() + ">.i.j;", null); | 
|  | consumer.accept("La$b<" + builder.toString() + ">;", null); | 
|  | consumer.accept("La$b<" + builder.toString() + ">.i;", null); | 
|  | consumer.accept("La$b<" + builder.toString() + ">.i.j;", null); | 
|  | consumer.accept("La$b$c<" + builder.toString() + ">;", null); | 
|  | consumer.accept("La$b$c<" + builder.toString() + ">.i;", null); | 
|  | consumer.accept("La$b$c<" + builder.toString() + ">.i.j;", null); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | public void forBasicTypes(Consumer<String> consumer) { | 
|  | for (char c : "BCDFIJSZ".toCharArray()) { | 
|  | consumer.accept(new String(new char[]{c})); | 
|  | } | 
|  | } | 
|  |  | 
|  | public void forBasicTypesAndVoid(Consumer<String> consumer) { | 
|  | forBasicTypes(consumer); | 
|  | consumer.accept("V"); | 
|  | } | 
|  |  | 
|  | public void forTypeVariableSignatures(BiConsumer<String, Set<Integer>> consumer) { | 
|  | consumer.accept("TT;", null); | 
|  |  | 
|  | consumer.accept("Ta;", null); | 
|  | consumer.accept("Tab;", null); | 
|  | consumer.accept("Tabc;", null); | 
|  | consumer.accept("Ta-b;", null); | 
|  | consumer.accept("Ta-b-c;", null); | 
|  | } | 
|  |  | 
|  | public void forArrayTypeSignatures(BiConsumer<String, Set<Integer>> consumer) { | 
|  | StringBuilder arrayPrefix = new StringBuilder(); | 
|  | for (int i = 0; i < 3; i++) { | 
|  | arrayPrefix.append("["); | 
|  | forBasicTypes(t -> consumer.accept(arrayPrefix.toString() + t, null)); | 
|  | forClassSignatures((x, y) -> { | 
|  | consumer.accept(arrayPrefix.toString() + x, y); | 
|  | }); | 
|  | forTypeVariableSignatures((x, y) -> { | 
|  | consumer.accept(arrayPrefix.toString() + x, y); | 
|  | }); | 
|  | //consumer.accept(); | 
|  | } | 
|  | } | 
|  |  | 
|  | public void forClassTypeSignatures(BiConsumer<String, Set<Integer>> consumer) { | 
|  | // In https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.4 (Java 7) it | 
|  | // says: "If the class bound does not specify a type, it is taken to be Object.". That sentence | 
|  | // is not present for Java 8 or Java 9. We do test it here, and javac from OpenJDK 8 will also | 
|  | // produce that for a class that extends Object and implements at least one interface. | 
|  |  | 
|  | // class C<T> { ... }. | 
|  | consumer.accept("<T:>Ljava/lang/Object;", null); | 
|  | consumer.accept("<T:Ljava/lang/Object;>Ljava/lang/Object;", null); | 
|  |  | 
|  | // class C<T,U> { ... }. | 
|  | consumer.accept("<T:U:>Ljava/lang/Object;", null); | 
|  | consumer.accept("<T:Ljava/lang/Object;U:Ljava/lang/Object;>Ljava/lang/Object;", null); | 
|  |  | 
|  | // class C<T extends AbstractList> { ... }. | 
|  | consumer.accept("<T:Ljava/util/AbstractList;>Ljava/lang/Object;", null); | 
|  | // class C<T extends AbstractList<T>> { ... }. | 
|  | consumer.accept("<T:Ljava/util/AbstractList<TT;>;>Ljava/lang/Object;", null); | 
|  |  | 
|  | // class C<T extends List> { ... }. | 
|  | consumer.accept("<T::Ljava/util/List;>Ljava/lang/Object;", null); | 
|  | // class C<T extends List<T>> { ... }. | 
|  | consumer.accept("<T::Ljava/util/List<TT;>;>Ljava/lang/Object;", null); | 
|  | // class C<T extends List<T[]>> { ... }. | 
|  | consumer.accept("<T::Ljava/util/List<[TT;>;>Ljava/lang/Object;", null); | 
|  | // class C<T extends List<T[][]>> { ... }. | 
|  | consumer.accept("<T::Ljava/util/List<[[TT;>;>Ljava/lang/Object;", null); | 
|  |  | 
|  | // class C<T extends AbstractList & List> { ... }. | 
|  | consumer.accept("<T:Ljava/util/AbstractList;:Ljava/util/List;>Ljava/lang/Object;", null); | 
|  | // class C<T extends AbstractList<T> & List<T>> { ... }. | 
|  | consumer.accept( | 
|  | "<T:Ljava/util/AbstractList<TT;>;:Ljava/util/List<TT;>;>Ljava/lang/Object;", null); | 
|  | // class C<T extends AbstractList<T[]> & List<T[]>> { ... }. | 
|  | consumer.accept( | 
|  | "<T:Ljava/util/AbstractList<[TT;>;:Ljava/util/List<[TT;>;>Ljava/lang/Object;", null); | 
|  | // class C<T extends AbstractList<T[][]> & List<T[][]>> { ... }. | 
|  | consumer.accept( | 
|  | "<T:Ljava/util/AbstractList<[[TT;>;:Ljava/util/List<[[TT;>;>Ljava/lang/Object;", null); | 
|  |  | 
|  | // class C<T extends AbstractList & List & Iterator> { ... }. | 
|  | consumer.accept( | 
|  | "<T:Ljava/util/AbstractList;:Ljava/util/List;:Ljava/util/Iterator;>Ljava/lang/Object;", | 
|  | null); | 
|  | // class C<T extends AbstractList<T> & List<T> & Iterator<T>> { ... }. | 
|  | consumer.accept( | 
|  | "<T:Ljava/util/AbstractList<TT;>;:Ljava/util/List<TT;>;:Ljava/util/Iterator<TT;>;>" | 
|  | + "Ljava/lang/Object;", null); | 
|  |  | 
|  | // class C<T,U> { ... }. | 
|  | consumer.accept("<T:U:>Ljava/lang/Object;", null); | 
|  | consumer.accept("<T:Ljava/lang/Object;U:Ljava/lang/Object;>Ljava/lang/Object;", null); | 
|  |  | 
|  | // class C extends java.util.AbstractList<String> | 
|  | consumer.accept("Ljava/util/AbstractList<Ljava/lang/String;>;", null); | 
|  | // class C extends java.util.AbstractList<String> implements List<String> | 
|  | consumer.accept( | 
|  | "Ljava/util/AbstractList<Ljava/lang/String;>;Ljava/util/List<Ljava/lang/String;>;", | 
|  | ImmutableSet.of(44)); | 
|  | // class C extends java.util.AbstractList<String> implements List<String>, Iterator<String> | 
|  | consumer.accept( | 
|  | "Ljava/util/AbstractList<Ljava/lang/String;>;" | 
|  | + "Ljava/util/List<Ljava/lang/String;>;Ljava/util/Iterator<Ljava/lang/String;>;", | 
|  | ImmutableSet.of(44, 80)); | 
|  |  | 
|  | // class C<T> extends java.util.AbstractList<T> | 
|  | consumer.accept("<T:Ljava/lang/Object;>Ljava/util/AbstractList<TT;>;", null); | 
|  | // class C<T> extends java.util.AbstractList<T> implements List<T> | 
|  | consumer.accept("<T:Ljava/lang/Object;>Ljava/util/AbstractList<TT;>;Ljava/util/List<TT;>;", | 
|  | ImmutableSet.of(51)); | 
|  | // class C<T> extends java.util.AbstractList<T> implements List<T>, Iterator<T> | 
|  | consumer.accept("<T:Ljava/lang/Object;>Ljava/util/AbstractList<TT;>" | 
|  | + ";Ljava/util/List<TT;>;Ljava/util/Iterator<TT;>;", | 
|  | ImmutableSet.of(51, 72)); | 
|  |  | 
|  | // class Outer<T> { | 
|  | //   class Inner { | 
|  | //     class InnerInner { | 
|  | //     } | 
|  | //     class ExtendsInnerInner extends InnerInner { | 
|  | //     } | 
|  | //   } | 
|  | //   class ExtendsInner extends Inner { | 
|  | //   } | 
|  | // } | 
|  | consumer.accept("<T:Ljava/lang/Object;>Ljava/lang/Object;", null);  // Outer signature. | 
|  | // Inner has no signature. | 
|  | // InnerInner has no signature. | 
|  | consumer.accept("LOuter<TT;>.Inner.InnerInner;", null);  // ExtendsInnerInner signature. | 
|  | consumer.accept("LOuter<TT;>.Inner;", null);  // ExtendsInner signature. | 
|  |  | 
|  | // class Outer<T> { | 
|  | //   class Inner<T> { | 
|  | //   } | 
|  | //   interface InnerInterface<T> { | 
|  | //   } | 
|  | //   abstract class ExtendsInner<U> extends Inner implements InnerInterface<U> { | 
|  | //   } | 
|  | // } | 
|  | consumer.accept( | 
|  | "<U:Ljava/lang/Object;>LOuter<TT;>.Inner<TT;>;LOuter$InnerInterface<TU;>;", | 
|  | ImmutableSet.of(45)); | 
|  | } | 
|  |  | 
|  | public void forMethodSignatures(BiConsumer<String, Set<Integer>> consumer) { | 
|  | forBasicTypesAndVoid(t -> consumer.accept("()" + t, null)); | 
|  | forBasicTypesAndVoid(t -> consumer.accept("(BCDFIJSZ)" + t, null)); | 
|  | forBasicTypesAndVoid(t -> consumer.accept("<T:>(BCDFIJSZ)" + t, null)); | 
|  | forBasicTypesAndVoid(t -> consumer.accept("<T:Ljava/util/List;>(BCDFIJSZ)" + t, null)); | 
|  | forBasicTypesAndVoid(t -> consumer.accept("<T:U:>(BCDFIJSZ)" + t, null)); | 
|  | forBasicTypesAndVoid( | 
|  | t -> consumer.accept("<T:Ljava/util/List;U:Ljava/util/List;>(BCDFIJSZ)" + t, null)); | 
|  |  | 
|  | consumer.accept("<T:U:>(Ljava/util/List<TT;>;Ljava/util/Iterator<TT;>;)V", null); | 
|  | consumer.accept( | 
|  | "<T:U:>(Ljava/util/List<TT;>;Ljava/util/Iterator<TT;>;)Ljava/util/List<TT;>;", null); | 
|  |  | 
|  | consumer.accept("<T:U:>(La/b/c<TT;>.i<TT;>;)V", null); | 
|  | consumer.accept("<T:U:>(La/b/c<TT;>.i<TT;>.j<TU;>;)V", null); | 
|  |  | 
|  | consumer.accept("<T:>()La/b/c<TT;>;", null); | 
|  | consumer.accept("<T:>()La/b/c<TT;>.i<TT;>;", null); | 
|  | consumer.accept("<T:>()La/b/c<TT;>.i<TT;>.j<TT;>;", null); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testClassSignatureGenericClass() { | 
|  | forClassTypeSignatures(this::parseClassSignature); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void parseFieldSignature() { | 
|  | forClassSignatures(this::parseFieldSignature); | 
|  | forTypeVariableSignatures(this::parseFieldSignature); | 
|  | forArrayTypeSignatures(this::parseFieldSignature); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void parseMethodSignature() { | 
|  | forMethodSignatures(this::parseMethodSignature); | 
|  | } | 
|  |  | 
|  | private void failingParseAction( | 
|  | Consumer<GenericSignatureParser<String>> parse, String errorMessageType) | 
|  | throws Exception { | 
|  | class ThrowsInParserActionBase<E extends Error> extends ReGenerateGenericSignatureRewriter { | 
|  | protected Supplier<? extends E> exceptionSupplier; | 
|  |  | 
|  | private ThrowsInParserActionBase(Supplier<? extends E> exceptionSupplier) { | 
|  | this.exceptionSupplier = exceptionSupplier; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ThrowsInParsedSymbol<E extends Error> extends ThrowsInParserActionBase<E> { | 
|  | private ThrowsInParsedSymbol(Supplier<? extends E> exceptionSupplier) { | 
|  | super(exceptionSupplier); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void parsedSymbol(char symbol) { | 
|  | throw exceptionSupplier.get(); | 
|  | } | 
|  | } | 
|  |  | 
|  | class ThrowsInParsedIdentifier<E extends Error> extends ThrowsInParserActionBase<E> { | 
|  | private ThrowsInParsedIdentifier(Supplier<? extends E> exceptionSupplier) { | 
|  | super(exceptionSupplier); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void parsedIdentifier(String identifier) { | 
|  | throw exceptionSupplier.get(); | 
|  | } | 
|  | } | 
|  |  | 
|  | class ThrowsInParsedTypeName<E extends Error> extends ThrowsInParserActionBase<E> { | 
|  | private ThrowsInParsedTypeName(Supplier<? extends E> exceptionSupplier) { | 
|  | super(exceptionSupplier); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String parsedTypeName(String name, ParserPosition parserPosition) { | 
|  | throw exceptionSupplier.get(); | 
|  | } | 
|  | } | 
|  |  | 
|  | class ThrowsInParsedInnerTypeName<E extends Error> extends ThrowsInParserActionBase<E> { | 
|  | private ThrowsInParsedInnerTypeName(Supplier<? extends E> exceptionSupplier) { | 
|  | super(exceptionSupplier); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String parsedInnerTypeName(String enclosingType, String name) { | 
|  | throw exceptionSupplier.get(); | 
|  | } | 
|  | } | 
|  |  | 
|  | class ThrowsInStart<E extends Error> extends ThrowsInParserActionBase<E> { | 
|  | private ThrowsInStart(Supplier<? extends E> exceptionSupplier) { | 
|  | super(exceptionSupplier); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void start() { | 
|  | throw exceptionSupplier.get(); | 
|  | } | 
|  | } | 
|  |  | 
|  | class ThrowsInStop<E extends Error> extends ThrowsInParserActionBase<E> { | 
|  | private ThrowsInStop(Supplier<? extends E> exceptionSupplier) { | 
|  | super(exceptionSupplier); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void stop() { | 
|  | throw exceptionSupplier.get(); | 
|  | } | 
|  | } | 
|  |  | 
|  | List<GenericSignatureAction<String>> throwingActions = ImmutableList.of( | 
|  | new ThrowsInParsedSymbol<>(() -> new GenericSignatureFormatError("ERROR")), | 
|  | new ThrowsInParsedSymbol<>(() -> new Error("ERROR")), | 
|  | new ThrowsInParsedIdentifier<>(() -> new GenericSignatureFormatError("ERROR")), | 
|  | new ThrowsInParsedIdentifier<>(() -> new Error("ERROR")), | 
|  | new ThrowsInParsedTypeName<>(() -> new GenericSignatureFormatError("ERROR")), | 
|  | new ThrowsInParsedTypeName<>(() -> new Error("ERROR")), | 
|  | new ThrowsInParsedInnerTypeName<>(() -> new GenericSignatureFormatError("ERROR")), | 
|  | new ThrowsInParsedInnerTypeName<>(() -> new Error("ERROR")), | 
|  | new ThrowsInStart<>(() -> new GenericSignatureFormatError("ERROR")), | 
|  | new ThrowsInStart<>(() -> new Error("ERROR")), | 
|  | new ThrowsInStop<>(() -> new GenericSignatureFormatError("ERROR")), | 
|  | new ThrowsInStop<>(() -> new Error("ERROR"))); | 
|  |  | 
|  | int plainErrorCount = 0; | 
|  | for (GenericSignatureAction<String> action : throwingActions) { | 
|  | GenericSignatureParser<String> parser = new GenericSignatureParser<>(action); | 
|  | try { | 
|  | // This class signature hits all action callbacks. | 
|  | parse.accept(parser); | 
|  | fail("Parse succeeded for " + action.getClass().getSimpleName()); | 
|  | } catch (GenericSignatureFormatError e) { | 
|  | if (e.getSuppressed().length == 0) { | 
|  | assertEquals("ERROR", e.getMessage()); | 
|  | } else { | 
|  | plainErrorCount++; | 
|  | assertEquals("Unknown error parsing " | 
|  | + errorMessageType + " signature: ERROR", e.getMessage()); | 
|  | } | 
|  | } | 
|  | } | 
|  | assertEquals(6, plainErrorCount); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void failingParseAction() throws Exception { | 
|  | // These signatures hits all action callbacks. | 
|  | failingParseAction(parser -> parser.parseClassSignature( | 
|  | "<U:Ljava/lang/Object;>LOuter<TT;>.Inner;Ljava/util/List<TU;>;"), "class"); | 
|  | failingParseAction( | 
|  | parser -> parser.parseFieldSignature("LOuter$InnerInterface<TU;>.Inner;"), "field"); | 
|  | failingParseAction( | 
|  | parser -> parser.parseMethodSignature("(LOuter$InnerInterface<TU;>.Inner;)V"), "method"); | 
|  | } | 
|  | } |