diff options
author | ajmichael <ajmichael@google.com> | 2017-09-13 20:37:20 +0200 |
---|---|---|
committer | Ivan Gavrilovic <gavra@google.com> | 2017-09-22 23:28:10 +0100 |
commit | 7f6f08b9b78fd02cdea4157980a42b576ae3c504 (patch) | |
tree | 214beeda0b66023cac62ca9acbd9d2dc8dba6f3d /test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java | |
parent | 8c3292b0ae37edbbc3470d3a3ad34fffd2aef413 (diff) | |
download | desugar-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.java | 414 |
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. + } + } + } +} |