blob: 4bd61e195c8e64915c2c480fcffd88f507e5fbe1 [file] [log] [blame]
// Copyright (c) 2018, 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.kotlin;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class R8KotlinDataClassTest extends AbstractR8KotlinTestBase {
private static final KotlinDataClass TEST_DATA_CLASS = new KotlinDataClass("dataclass.Person")
.addProperty("name", "java.lang.String")
.addProperty("age", "int");
private static final MethodSignature NAME_GETTER_METHOD =
TEST_DATA_CLASS.getGetterForProperty("name");
private static final MethodSignature AGE_GETTER_METHOD =
TEST_DATA_CLASS.getGetterForProperty("age");
private static final MethodSignature COMPONENT1_METHOD =
TEST_DATA_CLASS.getComponentNFunctionForProperty("name");
private static final MethodSignature COMPONENT2_METHOD =
TEST_DATA_CLASS.getComponentNFunctionForProperty("age");
private static final MethodSignature COPY_METHOD = TEST_DATA_CLASS.getCopySignature();
private static final MethodSignature COPY_DEFAULT_METHOD =
TEST_DATA_CLASS.getCopyDefaultSignature();
public R8KotlinDataClassTest(boolean allowAccessModification) {
super(allowAccessModification);
}
@Parameters(name = "{0}")
public static Collection<Object> data() {
return ImmutableList.of(Boolean.TRUE, Boolean.FALSE);
}
@Test
public void test_dataclass_gettersOnly() throws Exception {
final String mainClassName = "dataclass.MainGettersOnlyKt";
final MethodSignature testMethodSignature =
new MethodSignature("testDataClassGetters", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
runTest("dataclass", mainClassName, extraRules, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject dataClass = checkClassExists(dexInspector, TEST_DATA_CLASS.getClassName());
// Getters should be removed after inlining, which is possible only if access is relaxed.
final boolean areGetterPresent = !allowAccessModification;
Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
.put(NAME_GETTER_METHOD, areGetterPresent)
.put(AGE_GETTER_METHOD, areGetterPresent)
// ComponentN and copy methods are not used.
.put(COMPONENT1_METHOD, false)
.put(COMPONENT2_METHOD, false)
.put(COPY_METHOD, false)
.put(COPY_DEFAULT_METHOD, false)
.build();
checkMethodsPresence(dataClass, presenceMap);
ClassSubject classSubject = checkClassExists(dexInspector, mainClassName);
MethodSubject testMethod = checkMethodIsPresent(classSubject, testMethodSignature);
DexCode dexCode = getDexCode(testMethod);
if (allowAccessModification) {
// Both getters should be inlined
checkMethodIsNeverInvoked(dexCode, NAME_GETTER_METHOD, AGE_GETTER_METHOD);
} else {
checkMethodIsInvokedAtLeastOnce(dexCode, NAME_GETTER_METHOD, AGE_GETTER_METHOD);
}
});
}
@Test
public void test_dataclass_componentOnly() throws Exception {
final String mainClassName = "dataclass.MainComponentOnlyKt";
final MethodSignature testMethodSignature =
new MethodSignature("testAllDataClassComponentFunctions", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
runTest("dataclass", mainClassName, extraRules, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject dataClass = checkClassExists(dexInspector, TEST_DATA_CLASS.getClassName());
// ComponentN functions should be removed after inlining, which is possible only if access
// is relaxed.
final boolean areComponentMethodsPresent = !allowAccessModification;
Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
.put(NAME_GETTER_METHOD, false)
.put(AGE_GETTER_METHOD, false)
// ComponentN and copy methods are not used.
.put(COMPONENT1_METHOD, areComponentMethodsPresent)
.put(COMPONENT2_METHOD, areComponentMethodsPresent)
.put(COPY_METHOD, false)
.put(COPY_DEFAULT_METHOD, false)
.build();
checkMethodsPresence(dataClass, presenceMap);
ClassSubject classSubject = checkClassExists(dexInspector, mainClassName);
MethodSubject testMethod = checkMethodIsPresent(classSubject, testMethodSignature);
DexCode dexCode = getDexCode(testMethod);
if (allowAccessModification) {
checkMethodIsNeverInvoked(dexCode, COMPONENT1_METHOD, COMPONENT2_METHOD);
} else {
checkMethodIsInvokedAtLeastOnce(dexCode, COMPONENT1_METHOD, COMPONENT2_METHOD);
}
});
}
@Test
public void test_dataclass_componentPartial() throws Exception {
final String mainClassName = "dataclass.MainComponentPartialKt";
final MethodSignature testMethodSignature =
new MethodSignature("testSomeDataClassComponentFunctions", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
runTest("dataclass", mainClassName, extraRules, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject dataClass = checkClassExists(dexInspector, TEST_DATA_CLASS.getClassName());
boolean component2IsPresent = !allowAccessModification;
Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
.put(NAME_GETTER_METHOD, false)
.put(AGE_GETTER_METHOD, false)
// ComponentN and copy methods are not used.
.put(COMPONENT1_METHOD, false)
.put(COMPONENT2_METHOD, component2IsPresent)
.put(COPY_METHOD, false)
.put(COPY_DEFAULT_METHOD, false)
.build();
checkMethodsPresence(dataClass, presenceMap);
ClassSubject classSubject = checkClassExists(dexInspector, mainClassName);
MethodSubject testMethod = checkMethodIsPresent(classSubject, testMethodSignature);
DexCode dexCode = getDexCode(testMethod);
if (allowAccessModification) {
checkMethodIsNeverInvoked(dexCode, COMPONENT2_METHOD);
} else {
checkMethodIsInvokedAtLeastOnce(dexCode, COMPONENT2_METHOD);
}
});
}
@Test
public void test_dataclass_copy() throws Exception {
final String mainClassName = "dataclass.MainCopyKt";
runTest("dataclass", mainClassName, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject dataClass = checkClassExists(dexInspector, TEST_DATA_CLASS.getClassName());
// Copy method is small enough that it is always inlined.
final boolean copyMethodIsPresent = false;
Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
.put(COPY_METHOD, copyMethodIsPresent)
.put(COPY_DEFAULT_METHOD, false)
.build();
checkMethodsPresence(dataClass, presenceMap);
});
}
@Test
public void test_dataclass_copyDefault() throws Exception {
final String mainClassName = "dataclass.MainCopyWithDefaultKt";
final MethodSignature testMethodSignature =
new MethodSignature("testDataClassCopyWithDefault", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
runTest("dataclass", mainClassName, extraRules, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject dataClass = checkClassExists(dexInspector, TEST_DATA_CLASS.getClassName());
// Copy$default method is inlined if access is relaxed.
final boolean copyDefaultMethodIsPresent = !allowAccessModification;
// copy$default is a wrapper around copy to deal with default values. If it's inlined thus
// copy is inlined as well.
final boolean copyMethodIsPresent = copyDefaultMethodIsPresent;
Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
.put(COPY_DEFAULT_METHOD, copyDefaultMethodIsPresent)
.put(COPY_METHOD, copyMethodIsPresent)
.build();
checkMethodsPresence(dataClass, presenceMap);
if (copyDefaultMethodIsPresent) {
ClassSubject classSubject = checkClassExists(dexInspector, mainClassName);
MethodSubject testMethod = checkMethodIsPresent(classSubject, testMethodSignature);
DexCode dexCode = getDexCode(testMethod);
checkMethodIsInvokedAtLeastOnce(dexCode, COPY_DEFAULT_METHOD);
}
if (copyMethodIsPresent) {
MethodSubject testMethod = checkMethod(dataClass, COPY_DEFAULT_METHOD, true);
DexCode dexCode = getDexCode(testMethod);
checkMethodIsInvokedAtLeastOnce(dexCode, COPY_METHOD);
}
});
}
}