| // Copyright (c) 2019, 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.desugar.backports; |
| |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; |
| import static java.util.stream.Collectors.toList; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.Assert.assertEquals; |
| |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestBuilder; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.InstructionSubject; |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; |
| import it.unimi.dsi.fastutil.ints.Int2IntAVLTreeMap; |
| import it.unimi.dsi.fastutil.ints.Int2IntSortedMap; |
| import java.io.IOException; |
| import java.nio.file.Path; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import org.junit.Assert; |
| import org.junit.Test; |
| |
| abstract class AbstractBackportTest extends TestBase { |
| protected final TestParameters parameters; |
| private final Class<?> targetClass; |
| private final Class<?> testClass; |
| private final Path testJar; |
| private final String testClassName; |
| private final Int2IntSortedMap invokeStaticCounts = new Int2IntAVLTreeMap(); |
| private final Set<String> ignoredInvokes = new HashSet<>(); |
| |
| AbstractBackportTest(TestParameters parameters, Class<?> targetClass, |
| Class<?> testClass) { |
| this(parameters, targetClass, testClass, null, null); |
| } |
| |
| AbstractBackportTest(TestParameters parameters, Class<?> targetClass, |
| Path testJar, String testClassName) { |
| this(parameters, targetClass, null, testJar, testClassName); |
| } |
| |
| private AbstractBackportTest(TestParameters parameters, Class<?> targetClass, |
| Class<?> testClass, Path testJar, String testClassName) { |
| this.parameters = parameters; |
| this.targetClass = targetClass; |
| this.testClass = testClass; |
| this.testJar = testJar; |
| |
| if (testClass != null) { |
| assert testJar == null; |
| assert testClassName == null; |
| this.testClassName = testClass.getName(); |
| } else { |
| assert testJar != null; |
| assert testClassName != null; |
| this.testClassName = testClassName; |
| } |
| |
| // Assume all method calls will be rewritten on the lowest API level. |
| invokeStaticCounts.put(AndroidApiLevel.B.getLevel(), 0); |
| } |
| |
| void registerTarget(AndroidApiLevel apiLevel, int invokeStaticCount) { |
| invokeStaticCounts.put(apiLevel.getLevel(), invokeStaticCount); |
| } |
| |
| private int getTargetInvokesCount(AndroidApiLevel apiLevel) { |
| int key = invokeStaticCounts.headMap(apiLevel.getLevel() + 1).lastIntKey(); |
| return invokeStaticCounts.get(key); |
| } |
| |
| void ignoreInvokes(String methodName) { |
| ignoredInvokes.add(methodName); |
| } |
| |
| private void configureProgram(TestBuilder<?, ?> builder) throws IOException { |
| builder.addProgramClasses(MiniAssert.class, IgnoreInvokes.class); |
| if (testClass != null) { |
| builder.addProgramClassesAndInnerClasses(testClass); |
| } else { |
| builder.addProgramFiles(testJar); |
| } |
| } |
| |
| @Test |
| public void desugaring() throws Exception { |
| if (parameters.isCfRuntime()) { |
| testForJvm() |
| .apply(this::configureProgram) |
| .run(parameters.getRuntime(), testClassName) |
| .assertSuccess(); |
| } else { |
| testForD8() |
| .setMinApi(parameters.getApiLevel()) |
| .apply(this::configureProgram) |
| .setIncludeClassesChecksum(true) |
| .compile() |
| .run(parameters.getRuntime(), testClassName) |
| .assertSuccess() |
| .inspect(this::assertDesugaring); |
| } |
| } |
| |
| private void assertDesugaring(CodeInspector inspector) { |
| ClassSubject testSubject = inspector.clazz(testClassName); |
| assertThat(testSubject, isPresent()); |
| |
| List<InstructionSubject> javaInvokeStatics = testSubject.allMethods() |
| .stream() |
| // Do not count @IgnoreInvokes-annotated methods. |
| .filter(i -> !i.annotation(IgnoreInvokes.class.getName()).isPresent()) |
| .flatMap(MethodSubject::streamInstructions) |
| .filter(InstructionSubject::isInvoke) |
| .filter(is -> is.getMethod().holder.toSourceString().equals(targetClass.getName())) |
| // Do not count invokes if explicitly ignored. |
| .filter(is -> !ignoredInvokes.contains(is.getMethod().name.toString())) |
| .collect(toList()); |
| |
| AndroidApiLevel apiLevel = parameters.getApiLevel(); |
| long expectedTargetInvokes = getTargetInvokesCount(apiLevel); |
| long actualTargetInvokes = javaInvokeStatics.size(); |
| assertEquals("Expected " |
| + expectedTargetInvokes |
| + " invokes on " |
| + targetClass.getName() |
| + " but found " |
| + actualTargetInvokes |
| + ": " |
| + javaInvokeStatics, expectedTargetInvokes, actualTargetInvokes); |
| } |
| |
| /** JUnit {@link Assert} isn't available in the VM runtime. This is a mini mirror of its API. */ |
| static abstract class MiniAssert { |
| static void assertTrue(boolean value) { |
| assertEquals(true, value); |
| } |
| |
| static void assertFalse(boolean value) { |
| assertEquals(false, value); |
| } |
| |
| static void assertEquals(boolean expected, boolean actual) { |
| if (expected != actual) { |
| throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>'); |
| } |
| } |
| |
| static void assertEquals(int expected, int actual) { |
| if (expected != actual) { |
| throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>'); |
| } |
| } |
| |
| static void assertEquals(long expected, long actual) { |
| if (expected != actual) { |
| throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>'); |
| } |
| } |
| |
| static void assertEquals(float expected, float actual) { |
| if (Float.compare(expected, actual) != 0) { |
| throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>'); |
| } |
| } |
| |
| static void assertEquals(double expected, double actual) { |
| if (Double.compare(expected, actual) != 0) { |
| throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>'); |
| } |
| } |
| |
| static void assertEquals(Object expected, Object actual) { |
| if (expected != actual && (expected == null || !expected.equals(actual))) { |
| throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>'); |
| } |
| } |
| |
| static void assertSame(Object expected, Object actual) { |
| if (expected != actual) { |
| throw new AssertionError( |
| "Expected <" + expected + "> to be same instance as <" + actual + '>'); |
| } |
| } |
| } |
| } |