summaryrefslogtreecommitdiff
path: root/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java
diff options
context:
space:
mode:
authorajmichael <ajmichael@google.com>2017-09-13 20:37:20 +0200
committerIvan Gavrilovic <gavra@google.com>2017-09-22 23:28:10 +0100
commit7f6f08b9b78fd02cdea4157980a42b576ae3c504 (patch)
tree214beeda0b66023cac62ca9acbd9d2dc8dba6f3d /test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java
parent8c3292b0ae37edbbc3470d3a3ad34fffd2aef413 (diff)
downloaddesugar-7f6f08b9b78fd02cdea4157980a42b576ae3c504.tar.gz
Open source tests for Android desugarer.
These tests will fail with a helpful error message if you do not have android_sdk_repository set up. They currently require that platform 25 be installed in your SDK. RELNOTES: None PiperOrigin-RevId: 168570577 GitOrigin-RevId: d60e0d02eb56e27f98086d764c6d9f88898d920d Change-Id: I27f71669816c963111a92762a9f3dffddabfa0f6
Diffstat (limited to 'test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java')
-rw-r--r--test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java414
1 files changed, 414 insertions, 0 deletions
diff --git a/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java b/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java
new file mode 100644
index 0000000..587d4f7
--- /dev/null
+++ b/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java
@@ -0,0 +1,414 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.desugar;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.getStrategyClassName;
+import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.getTwrStrategyClassNameSpecifiedInSystemProperty;
+import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.isMimicStrategy;
+import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.isNullStrategy;
+import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.isReuseStrategy;
+import static org.junit.Assert.fail;
+import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
+import static org.objectweb.asm.Opcodes.ASM5;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.google.devtools.build.android.desugar.runtime.ThrowableExtension;
+import com.google.devtools.build.android.desugar.testdata.ClassUsingTryWithResources;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/** This is the unit test for {@link TryWithResourcesRewriter} */
+@RunWith(JUnit4.class)
+public class TryWithResourcesRewriterTest {
+
+ private final DesugaringClassLoader classLoader =
+ new DesugaringClassLoader(ClassUsingTryWithResources.class.getName());
+ private Class<?> desugaredClass;
+
+ @Before
+ public void setup() {
+ try {
+ desugaredClass = classLoader.findClass(ClassUsingTryWithResources.class.getName());
+ } catch (ClassNotFoundException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Test
+ public void testMethodsAreDesugared() {
+ // verify whether the desugared class is indeed desugared.
+ DesugaredThrowableMethodCallCounter origCounter =
+ countDesugaredThrowableMethodCalls(ClassUsingTryWithResources.class);
+ DesugaredThrowableMethodCallCounter desugaredCounter =
+ countDesugaredThrowableMethodCalls(classLoader.classContent, classLoader);
+ /**
+ * In java9, javac creates a helper method {@code $closeResource(Throwable, AutoCloseable)
+ * to close resources. So, the following number 3 is highly dependant on the version of javac.
+ */
+ assertThat(hasAutoCloseable(classLoader.classContent)).isFalse();
+ assertThat(classLoader.numOfTryWithResourcesInvoked.intValue()).isAtLeast(2);
+ assertThat(classLoader.visitedExceptionTypes)
+ .containsExactly(
+ "java/lang/Exception", "java/lang/Throwable", "java/io/UnsupportedEncodingException");
+ assertDesugaringBehavior(origCounter, desugaredCounter);
+ }
+
+ @Test
+ public void testCheckSuppressedExceptionsReturningEmptySuppressedExceptions() {
+ {
+ Throwable[] suppressed = ClassUsingTryWithResources.checkSuppressedExceptions(false);
+ assertThat(suppressed).isEmpty();
+ }
+ try {
+ Throwable[] suppressed =
+ (Throwable[])
+ desugaredClass
+ .getMethod("checkSuppressedExceptions", boolean.class)
+ .invoke(null, Boolean.FALSE);
+ assertThat(suppressed).isEmpty();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new AssertionError(e);
+ }
+ }
+
+ @Test
+ public void testPrintStackTraceOfCaughtException() {
+ {
+ String trace = ClassUsingTryWithResources.printStackTraceOfCaughtException();
+ assertThat(trace.toLowerCase()).contains("suppressed");
+ }
+ try {
+ String trace =
+ (String) desugaredClass.getMethod("printStackTraceOfCaughtException").invoke(null);
+
+ if (isMimicStrategy()) {
+ assertThat(trace.toLowerCase()).contains("suppressed");
+ } else if (isReuseStrategy()) {
+ assertThat(trace.toLowerCase()).contains("suppressed");
+ } else if (isNullStrategy()) {
+ assertThat(trace.toLowerCase()).doesNotContain("suppressed");
+ } else {
+ fail("unexpected desugaring strategy " + ThrowableExtension.getStrategy());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new AssertionError(e);
+ }
+ }
+
+ @Test
+ public void testCheckSuppressedExceptionReturningOneSuppressedException() {
+ {
+ Throwable[] suppressed = ClassUsingTryWithResources.checkSuppressedExceptions(true);
+ assertThat(suppressed).hasLength(1);
+ }
+ try {
+ Throwable[] suppressed =
+ (Throwable[])
+ desugaredClass
+ .getMethod("checkSuppressedExceptions", boolean.class)
+ .invoke(null, Boolean.TRUE);
+
+ if (isMimicStrategy()) {
+ assertThat(suppressed).hasLength(1);
+ } else if (isReuseStrategy()) {
+ assertThat(suppressed).hasLength(1);
+ } else if (isNullStrategy()) {
+ assertThat(suppressed).isEmpty();
+ } else {
+ fail("unexpected desugaring strategy " + ThrowableExtension.getStrategy());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new AssertionError(e);
+ }
+ }
+
+ @Test
+ public void testSimpleTryWithResources() throws Throwable {
+ {
+ try {
+ ClassUsingTryWithResources.simpleTryWithResources();
+ fail("Expected RuntimeException");
+ } catch (RuntimeException expected) {
+ assertThat(expected.getClass()).isEqualTo(RuntimeException.class);
+ assertThat(expected.getSuppressed()).hasLength(1);
+ assertThat(expected.getSuppressed()[0].getClass()).isEqualTo(IOException.class);
+ }
+ }
+
+ try {
+ try {
+ desugaredClass.getMethod("simpleTryWithResources").invoke(null);
+ fail("Expected RuntimeException");
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ } catch (RuntimeException expected) {
+ String expectedStrategyName = getTwrStrategyClassNameSpecifiedInSystemProperty();
+ assertThat(getStrategyClassName()).isEqualTo(expectedStrategyName);
+ if (isMimicStrategy()) {
+ assertThat(expected.getSuppressed()).isEmpty();
+ assertThat(ThrowableExtension.getSuppressed(expected)).hasLength(1);
+ assertThat(ThrowableExtension.getSuppressed(expected)[0].getClass())
+ .isEqualTo(IOException.class);
+ } else if (isReuseStrategy()) {
+ assertThat(expected.getSuppressed()).hasLength(1);
+ assertThat(expected.getSuppressed()[0].getClass()).isEqualTo(IOException.class);
+ assertThat(ThrowableExtension.getSuppressed(expected)[0].getClass())
+ .isEqualTo(IOException.class);
+ } else if (isNullStrategy()) {
+ assertThat(expected.getSuppressed()).isEmpty();
+ assertThat(ThrowableExtension.getSuppressed(expected)).isEmpty();
+ } else {
+ fail("unexpected desugaring strategy " + ThrowableExtension.getStrategy());
+ }
+ }
+ }
+
+ private static void assertDesugaringBehavior(
+ DesugaredThrowableMethodCallCounter orig, DesugaredThrowableMethodCallCounter desugared) {
+ assertThat(desugared.countThrowableGetSuppressed()).isEqualTo(orig.countExtGetSuppressed());
+ assertThat(desugared.countThrowableAddSuppressed()).isEqualTo(orig.countExtAddSuppressed());
+ assertThat(desugared.countThrowablePrintStackTrace()).isEqualTo(orig.countExtPrintStackTrace());
+ assertThat(desugared.countThrowablePrintStackTracePrintStream())
+ .isEqualTo(orig.countExtPrintStackTracePrintStream());
+ assertThat(desugared.countThrowablePrintStackTracePrintWriter())
+ .isEqualTo(orig.countExtPrintStackTracePrintWriter());
+
+ assertThat(orig.countThrowableGetSuppressed()).isEqualTo(desugared.countExtGetSuppressed());
+ // $closeResource is rewritten to ThrowableExtension.closeResource, so addSuppressed() is called
+ // in the runtime library now.
+ assertThat(orig.countThrowableAddSuppressed())
+ .isAtLeast(desugared.countThrowableAddSuppressed());
+ assertThat(orig.countThrowablePrintStackTrace()).isEqualTo(desugared.countExtPrintStackTrace());
+ assertThat(orig.countThrowablePrintStackTracePrintStream())
+ .isEqualTo(desugared.countExtPrintStackTracePrintStream());
+ assertThat(orig.countThrowablePrintStackTracePrintWriter())
+ .isEqualTo(desugared.countExtPrintStackTracePrintWriter());
+
+ assertThat(desugared.countThrowablePrintStackTracePrintStream()).isEqualTo(0);
+ assertThat(desugared.countThrowablePrintStackTracePrintStream()).isEqualTo(0);
+ assertThat(desugared.countThrowablePrintStackTracePrintWriter()).isEqualTo(0);
+ assertThat(desugared.countThrowableAddSuppressed()).isEqualTo(0);
+ assertThat(desugared.countThrowableGetSuppressed()).isEqualTo(0);
+ }
+
+ private static DesugaredThrowableMethodCallCounter countDesugaredThrowableMethodCalls(
+ Class<?> klass) {
+ try {
+ ClassReader reader = new ClassReader(klass.getName());
+ DesugaredThrowableMethodCallCounter counter =
+ new DesugaredThrowableMethodCallCounter(klass.getClassLoader());
+ reader.accept(counter, 0);
+ return counter;
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail(e.toString());
+ return null;
+ }
+ }
+
+ private static DesugaredThrowableMethodCallCounter countDesugaredThrowableMethodCalls(
+ byte[] content, ClassLoader loader) {
+ ClassReader reader = new ClassReader(content);
+ DesugaredThrowableMethodCallCounter counter = new DesugaredThrowableMethodCallCounter(loader);
+ reader.accept(counter, 0);
+ return counter;
+ }
+
+ /** Check whether java.lang.AutoCloseable is used as arguments of any method. */
+ private static boolean hasAutoCloseable(byte[] classContent) {
+ ClassReader reader = new ClassReader(classContent);
+ final AtomicInteger counter = new AtomicInteger();
+ ClassVisitor visitor =
+ new ClassVisitor(Opcodes.ASM5) {
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String desc, String signature, String[] exceptions) {
+ for (Type argumentType : Type.getArgumentTypes(desc)) {
+ if ("Ljava/lang/AutoCloseable;".equals(argumentType.getDescriptor())) {
+ counter.incrementAndGet();
+ }
+ }
+ return null;
+ }
+ };
+ reader.accept(visitor, 0);
+ return counter.get() > 0;
+ }
+
+ private static class DesugaredThrowableMethodCallCounter extends ClassVisitor {
+ private final ClassLoader classLoader;
+ private final Map<String, AtomicInteger> counterMap;
+
+ public DesugaredThrowableMethodCallCounter(ClassLoader loader) {
+ super(ASM5);
+ classLoader = loader;
+ counterMap = new HashMap<>();
+ TryWithResourcesRewriter.TARGET_METHODS
+ .entries()
+ .forEach(entry -> counterMap.put(entry.getKey() + entry.getValue(), new AtomicInteger()));
+ TryWithResourcesRewriter.TARGET_METHODS
+ .entries()
+ .forEach(
+ entry ->
+ counterMap.put(
+ entry.getKey()
+ + TryWithResourcesRewriter.METHOD_DESC_MAP.get(entry.getValue()),
+ new AtomicInteger()));
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String desc, String signature, String[] exceptions) {
+ return new InvokeCounter();
+ }
+
+ private class InvokeCounter extends MethodVisitor {
+
+ public InvokeCounter() {
+ super(ASM5);
+ }
+
+ private boolean isAssignableToThrowable(String owner) {
+ try {
+ Class<?> ownerClass = classLoader.loadClass(owner.replace('/', '.'));
+ return Throwable.class.isAssignableFrom(ownerClass);
+ } catch (ClassNotFoundException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ String signature = name + desc;
+ if ((opcode == INVOKEVIRTUAL && isAssignableToThrowable(owner))
+ || (opcode == INVOKESTATIC
+ && Type.getInternalName(ThrowableExtension.class).equals(owner))) {
+ AtomicInteger counter = counterMap.get(signature);
+ if (counter == null) {
+ return;
+ }
+ counter.incrementAndGet();
+ }
+ }
+ }
+
+ public int countThrowableAddSuppressed() {
+ return counterMap.get("addSuppressed(Ljava/lang/Throwable;)V").get();
+ }
+
+ public int countThrowableGetSuppressed() {
+ return counterMap.get("getSuppressed()[Ljava/lang/Throwable;").get();
+ }
+
+ public int countThrowablePrintStackTrace() {
+ return counterMap.get("printStackTrace()V").get();
+ }
+
+ public int countThrowablePrintStackTracePrintStream() {
+ return counterMap.get("printStackTrace(Ljava/io/PrintStream;)V").get();
+ }
+
+ public int countThrowablePrintStackTracePrintWriter() {
+ return counterMap.get("printStackTrace(Ljava/io/PrintWriter;)V").get();
+ }
+
+ public int countExtAddSuppressed() {
+ return counterMap.get("addSuppressed(Ljava/lang/Throwable;Ljava/lang/Throwable;)V").get();
+ }
+
+ public int countExtGetSuppressed() {
+ return counterMap.get("getSuppressed(Ljava/lang/Throwable;)[Ljava/lang/Throwable;").get();
+ }
+
+ public int countExtPrintStackTrace() {
+ return counterMap.get("printStackTrace(Ljava/lang/Throwable;)V").get();
+ }
+
+ public int countExtPrintStackTracePrintStream() {
+ return counterMap.get("printStackTrace(Ljava/lang/Throwable;Ljava/io/PrintStream;)V").get();
+ }
+
+ public int countExtPrintStackTracePrintWriter() {
+ return counterMap.get("printStackTrace(Ljava/lang/Throwable;Ljava/io/PrintWriter;)V").get();
+ }
+ }
+
+ private static class DesugaringClassLoader extends ClassLoader {
+
+ private final String targetedClassName;
+ private Class<?> klass;
+ private byte[] classContent;
+ private final Set<String> visitedExceptionTypes = new HashSet<>();
+ private final AtomicInteger numOfTryWithResourcesInvoked = new AtomicInteger();
+
+ public DesugaringClassLoader(String targetedClassName) {
+ super(DesugaringClassLoader.class.getClassLoader());
+ this.targetedClassName = targetedClassName;
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ if (name.equals(targetedClassName)) {
+ if (klass != null) {
+ return klass;
+ }
+ // desugar the class, and return the desugared one.
+ classContent = desugarTryWithResources(name);
+ klass = defineClass(name, classContent, 0, classContent.length);
+ return klass;
+ } else {
+ return super.findClass(name);
+ }
+ }
+
+ private byte[] desugarTryWithResources(String className) {
+ try {
+ ClassReader reader = new ClassReader(className);
+ ClassWriter writer = new ClassWriter(reader, COMPUTE_MAXS);
+ TryWithResourcesRewriter rewriter =
+ new TryWithResourcesRewriter(
+ writer,
+ TryWithResourcesRewriterTest.class.getClassLoader(),
+ visitedExceptionTypes,
+ numOfTryWithResourcesInvoked);
+ reader.accept(rewriter, 0);
+ return writer.toByteArray();
+ } catch (IOException e) {
+ fail(e.toString());
+ return null; // suppress compiler error.
+ }
+ }
+ }
+}