blob: f4a779f7ac22a389159119d59720cab6692cfc9d [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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.android.build.gradle.internal.incremental;
import static com.google.common.truth.Truth.assertThat;
import com.android.utils.ILogger;
import com.android.utils.NullLogger;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
/**
* tests for the {@link IncrementalVisitor}
*/
public class IncrementalVisitorTest {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
public void testClassEligibility() throws IOException {
File fooDotBar = temporaryFolder.newFile("foo.bar");
assertThat(IncrementalVisitor.isClassEligibleForInstantRun(fooDotBar)).isFalse();
File RDotClass = temporaryFolder.newFile("R.class");
assertThat(IncrementalVisitor.isClassEligibleForInstantRun(RDotClass)).isFalse();
File RdimenDotClass = temporaryFolder.newFile("R$dimen.class");
assertThat(IncrementalVisitor.isClassEligibleForInstantRun(RdimenDotClass)).isFalse();
File someClass = temporaryFolder.newFile("Some.class");
assertThat(IncrementalVisitor.isClassEligibleForInstantRun(someClass)).isTrue();
File RSomethingClass = temporaryFolder.newFile("Rsomething.class");
assertThat(IncrementalVisitor.isClassEligibleForInstantRun(RSomethingClass)).isTrue();
File RSomethingWithInner = temporaryFolder.newFile("Rsome$dimen.class");
assertThat(IncrementalVisitor.isClassEligibleForInstantRun(RSomethingWithInner)).isTrue();
}
/**
* It's possible for us to be given a class to instument for instant run that inherits from a
* class that does not exist. The canonical use case is when a class inherits from a class that
* exists in some API level, but we're compiling against an API lower than that. In these
* situations we want to skip instrumenting these classes because they're not going to be used
* anyway (if they do get used the app will crash anyway).
*
* <p>Another use-case is when a library has a compileOnly dependency on something, and a class
* inherits from something in that compileOnly dependency.
*
* <p>See https://issuetracker.google.com/72811718 for discussions about this problem.
*/
@Test
public void testClassWithNonExistentSuperClass() throws Exception {
ClassWriter cw;
// Class A: this class inherits from Object and is perfectly valid, and should be
// instrumented for instant run.
cw = new ClassWriter(0);
cw.visit(Opcodes.V1_6, Opcodes.ACC_SUPER, "A", null, "java/lang/Object", null);
cw.visitEnd();
File a = temporaryFolder.newFile("A.class");
Files.write(a.toPath(), cw.toByteArray());
// Class B: this class inherits from a non-existent class, and is not valid, and thus
// should not be instrumented for instant run.
cw = new ClassWriter(0);
cw.visit(Opcodes.V1_6, Opcodes.ACC_SUPER, "B", null, "NonExistentSuperClass", null);
cw.visitEnd();
File b = temporaryFolder.newFile("B.class");
Files.write(b.toPath(), cw.toByteArray());
int apiLevel = 24;
File root = temporaryFolder.getRoot();
File outDir = temporaryFolder.newFolder();
ILogger logger = new NullLogger();
// Instrument class A, producing a new file that should not contain the same bytecode as
// A originally did (because we have instrumented it).
File aOut =
IncrementalVisitor.instrumentClass(
apiLevel,
root,
a,
outDir,
IncrementalSupportVisitor.VISITOR_BUILDER,
logger);
// Instrument class B, producing a new file that should contain the same bytecode as B
// originally did (because we have not instrumented it).
File bOut =
IncrementalVisitor.instrumentClass(
apiLevel,
root,
b,
outDir,
IncrementalSupportVisitor.VISITOR_BUILDER,
logger);
// instrumentClass() _can_ return null, but we shouldn't hit any of the situations in which
// that happens. If we do, something has gone wrong.
assertThat(aOut).isNotNull();
assertThat(bOut).isNotNull();
assertThat(Files.readAllBytes(a.toPath())).isNotEqualTo(Files.readAllBytes(aOut.toPath()));
assertThat(Files.readAllBytes(b.toPath())).isEqualTo(Files.readAllBytes(bOut.toPath()));
}
interface NoDefault {
String func();
}
interface WithDefault {
default String func() {
return "func";
}
}
private static class ImplWithDefault implements WithDefault {
public void anotherFunc() {}
}
@Test
public void testHasDefaultMethods() throws IOException {
ClassNode interfaceNode =
AsmUtils.readClass(
NoDefault.class.getClassLoader(),
Type.getType(NoDefault.class).getInternalName());
assertThat(IncrementalVisitor.hasDefaultMethods(interfaceNode)).isFalse();
interfaceNode =
AsmUtils.readClass(
WithDefault.class.getClassLoader(),
Type.getType(WithDefault.class).getInternalName());
assertThat(IncrementalVisitor.hasDefaultMethods(interfaceNode)).isTrue();
}
@Test
public void testGetMethodByNameInClass() throws IOException {
AsmUtils.ClassNodeProvider classReaderProvider = new ClassNodeProviderForTests();
ILogger iLogger = new NullLogger();
AsmClassNode classAndInterfacesNode =
AsmUtils.readClassAndInterfaces(
classReaderProvider,
Type.getType(ImplWithDefault.class).getInternalName(),
"Object",
21,
iLogger);
MethodNode method =
IncrementalVisitor.getMethodByNameInClass(
"func", "()Ljava/lang/String;", classAndInterfacesNode);
assertThat(method).isNotNull();
assertThat(method.name).isEqualTo("func");
method =
IncrementalVisitor.getMethodByNameInClass(
"anotherFunc", "()V", classAndInterfacesNode);
assertThat(method).isNotNull();
assertThat(method.name).isEqualTo("anotherFunc");
}
}