| // 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.desugaredlibrary; |
| |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestParametersCollection; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.ZipUtils; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.FoundClassSubject; |
| import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; |
| import com.google.common.collect.Sets; |
| import com.google.common.collect.Sets.SetView; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.util.Collections; |
| import java.util.IdentityHashMap; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| import org.junit.Assume; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| @RunWith(Parameterized.class) |
| public class DesugaredLibraryDeterminismTest extends DesugaredLibraryTestBase { |
| private final TestParameters parameters; |
| |
| @Parameterized.Parameters(name = "{0}") |
| public static TestParametersCollection data() { |
| return getTestParameters().withDexRuntimes().build(); |
| } |
| |
| public DesugaredLibraryDeterminismTest(TestParameters parameters) { |
| this.parameters = parameters; |
| } |
| |
| @Test |
| public void testDeterminism() throws Exception { |
| AndroidApiLevel minApiLevel = parameters.getRuntime().asDex().getMinApiLevel(); |
| Assume.assumeTrue(minApiLevel.isLessThan(AndroidApiLevel.O)); |
| Path libDexFile1 = buildDesugaredLibraryToBytes(minApiLevel); |
| Path libDexFile2 = buildDesugaredLibraryToBytes(minApiLevel); |
| assertIdenticalInspectors(libDexFile1, libDexFile2); |
| assertArrayEquals(getDexBytes(libDexFile1), getDexBytes(libDexFile2)); |
| } |
| |
| private void assertIdenticalInspectors(Path libDexFile1, Path libDexFile2) throws IOException { |
| CodeInspector i1 = new CodeInspector(libDexFile1.resolve("classes.dex")); |
| CodeInspector i2 = new CodeInspector(libDexFile2.resolve("classes.dex")); |
| assertIdenticalInspectors(i1, i2); |
| } |
| |
| public static void assertIdenticalInspectors(CodeInspector i1, CodeInspector i2) { |
| assertEquals(i1.allClasses().size(), i2.allClasses().size()); |
| Map<DexEncodedMethod, DexEncodedMethod> diffs = new IdentityHashMap<>(); |
| for (FoundClassSubject clazz1 : i1.allClasses()) { |
| ClassSubject clazz = i2.clazz(clazz1.getOriginalName()); |
| assertTrue(clazz.isPresent()); |
| FoundClassSubject clazz2 = clazz.asFoundClassSubject(); |
| Set<String> methods1 = |
| clazz1.allMethods().stream() |
| .map(FoundMethodSubject::toString) |
| .collect(Collectors.toSet()); |
| Set<String> methods2 = |
| clazz2.allMethods().stream() |
| .map(FoundMethodSubject::toString) |
| .collect(Collectors.toSet()); |
| SetView<String> union = Sets.union(methods1, methods2); |
| assertEquals( |
| "Inspector 1 contains more methods", |
| Collections.emptySet(), |
| Sets.difference(union, methods1)); |
| assertEquals( |
| "Inspector 2 contains more methods", |
| Collections.emptySet(), |
| Sets.difference(union, methods2)); |
| assertEquals(clazz1.allMethods().size(), clazz2.allMethods().size()); |
| for (FoundMethodSubject method1 : clazz1.allMethods()) { |
| MethodSubject method = clazz2.method(method1.asMethodReference()); |
| assertTrue(method.isPresent()); |
| FoundMethodSubject method2 = method.asFoundMethodSubject(); |
| if (method1.hasCode()) { |
| assertTrue(method2.hasCode()); |
| if (!(method1 |
| .getMethod() |
| .getCode() |
| .toString() |
| .equals(method2.getMethod().getCode().toString()))) { |
| diffs.put(method1.getMethod(), method2.getMethod()); |
| } |
| } |
| } |
| } |
| assertTrue(printDiffs(diffs), diffs.isEmpty()); |
| } |
| |
| private static String printDiffs(Map<DexEncodedMethod, DexEncodedMethod> diffs) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("The following methods had differences from one dex file to the other (") |
| .append(diffs.size()) |
| .append("):\n"); |
| diffs.forEach( |
| (m1, m2) -> { |
| sb.append(m1.toSourceString()).append("\n"); |
| String[] lines1 = m1.getCode().toString().split("\n"); |
| String[] lines2 = m2.getCode().toString().split("\n"); |
| if (lines1.length != lines2.length) { |
| sb.append("Different number of lines."); |
| sb.append("\n"); |
| } else { |
| for (int i = 0; i < lines1.length; i++) { |
| if (!lines1[i].equals(lines2[i])) { |
| sb.append(lines1[i]); |
| sb.append("\n"); |
| sb.append(lines2[i]); |
| sb.append("\n"); |
| return; |
| } |
| } |
| } |
| }); |
| return sb.toString(); |
| } |
| |
| private byte[] getDexBytes(Path libDexFile) throws IOException { |
| return Files.readAllBytes(libDexFile.resolve("classes.dex")); |
| } |
| |
| private Path buildDesugaredLibraryToBytes(AndroidApiLevel minApiLevel) throws IOException { |
| Path lib1 = buildDesugaredLibrary(minApiLevel); |
| Path unzipped1 = temp.newFolder().toPath(); |
| ZipUtils.unzip(lib1.toString(), unzipped1.toFile()); |
| return unzipped1; |
| } |
| } |