blob: 9623e7caba2050d65f41fba4a839c95dbbad8c0e [file] [log] [blame]
// 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;
}
}