| // 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.shaking; |
| |
| import static com.android.tools.r8.references.Reference.classFromClass; |
| import static com.android.tools.r8.references.Reference.methodFromMethod; |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.hamcrest.core.IsNot.not; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.tools.r8.DataEntryResource; |
| import com.android.tools.r8.NeverPropagateValue; |
| import com.android.tools.r8.R8TestRunResult; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.graph.AppServices; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.references.ClassReference; |
| import com.android.tools.r8.references.MethodReference; |
| import com.android.tools.r8.utils.BooleanUtils; |
| import com.android.tools.r8.utils.DataResourceConsumerForTesting; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.graphinspector.GraphInspector; |
| import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode; |
| import com.google.common.collect.Lists; |
| import java.util.List; |
| import java.util.ServiceLoader; |
| 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 ServiceLoaderTest extends TestBase { |
| |
| private final boolean includeWorldGreeter; |
| private final TestParameters parameters; |
| |
| private DataResourceConsumerForTesting dataResourceConsumer; |
| private static final String LINE_COMMENT = "# This is a comment."; |
| private static final String POSTFIX_COMMENT = "# POSTFIX_COMMENT"; |
| |
| @Parameters(name = "{1}, include WorldGreeter: {0}") |
| public static List<Object[]> data() { |
| return buildParameters( |
| BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build()); |
| } |
| |
| public ServiceLoaderTest(boolean includeWorldGreeter, TestParameters parameters) { |
| this.includeWorldGreeter = includeWorldGreeter; |
| this.parameters = parameters; |
| } |
| |
| @Test |
| public void test() throws Exception { |
| String expectedOutput = includeWorldGreeter ? "Hello world!" : "Hello"; |
| |
| List<String> serviceImplementations = Lists.newArrayList(); |
| |
| // Add a comment to the service implementations file. |
| serviceImplementations.add(LINE_COMMENT); |
| |
| // Postfix a valid line with a comment. |
| serviceImplementations.add(HelloGreeter.class.getTypeName() + POSTFIX_COMMENT); |
| |
| if (includeWorldGreeter) { |
| serviceImplementations.add(WorldGreeter.class.getTypeName()); |
| } |
| |
| R8TestRunResult result = |
| testForR8(parameters.getBackend()) |
| .addInnerClasses(ServiceLoaderTest.class) |
| .addKeepMainRule(TestClass.class) |
| .addDataEntryResources( |
| DataEntryResource.fromBytes( |
| StringUtils.lines(serviceImplementations).getBytes(), |
| "META-INF/services/" + Greeter.class.getTypeName(), |
| Origin.unknown())) |
| .addOptionsModification( |
| options -> { |
| dataResourceConsumer = |
| new DataResourceConsumerForTesting(options.dataResourceConsumer); |
| options.dataResourceConsumer = dataResourceConsumer; |
| options.enableInliningOfInvokesWithNullableReceivers = false; |
| }) |
| .enableGraphInspector() |
| .enableMemberValuePropagationAnnotations() |
| .setMinApi(parameters.getApiLevel()) |
| .run(parameters.getRuntime(), TestClass.class) |
| .assertSuccessWithOutput(expectedOutput); |
| |
| CodeInspector inspector = result.inspector(); |
| |
| ClassSubject greeterSubject = inspector.clazz(Greeter.class); |
| assertEquals(includeWorldGreeter, greeterSubject.isPresent()); |
| |
| ClassSubject helloGreeterSubject = inspector.clazz(HelloGreeter.class); |
| assertThat(helloGreeterSubject, isPresent()); |
| |
| ClassSubject worldGreeterSubject = inspector.clazz(WorldGreeter.class); |
| assertEquals(includeWorldGreeter, worldGreeterSubject.isPresent()); |
| |
| String serviceFileName = |
| includeWorldGreeter ? greeterSubject.getFinalName() : helloGreeterSubject.getFinalName(); |
| List<String> lines = |
| dataResourceConsumer.get(AppServices.SERVICE_DIRECTORY_NAME + serviceFileName); |
| assertEquals(includeWorldGreeter ? 2 : 1, lines.size()); |
| // Comments in application service files are not carried over to the output. |
| lines.forEach(line -> assertFalse(line.contains(LINE_COMMENT))); |
| lines.forEach(line -> assertFalse(line.contains(POSTFIX_COMMENT))); |
| if (includeWorldGreeter) { |
| assertTrue( |
| helloGreeterSubject.getFinalName().compareTo(worldGreeterSubject.getFinalName()) < 0); |
| } |
| assertEquals(helloGreeterSubject.getFinalName(), lines.get(0)); |
| if (includeWorldGreeter) { |
| assertEquals(worldGreeterSubject.getFinalName(), lines.get(1)); |
| } |
| |
| verifyGraphInformation(result.graphInspector()); |
| } |
| |
| private void verifyGraphInformation(GraphInspector graphInspector) throws Exception { |
| assertEquals(1, graphInspector.getRoots().size()); |
| QueryNode keepMain = graphInspector.rule(Origin.unknown(), 1, 1).assertRoot(); |
| |
| MethodReference mainMethod = |
| methodFromMethod(TestClass.class.getDeclaredMethod("main", String[].class)); |
| graphInspector.method(mainMethod).assertKeptBy(keepMain); |
| |
| ClassReference helloGreeterClass = classFromClass(HelloGreeter.class); |
| MethodReference helloGreeterInitMethod = methodFromMethod(HelloGreeter.class.getConstructor()); |
| MethodReference helloGreeterGreetingMethod = |
| methodFromMethod(HelloGreeter.class.getDeclaredMethod("greeting")); |
| |
| graphInspector.clazz(helloGreeterClass).assertKeptBy(graphInspector.method(mainMethod)); |
| graphInspector.clazz(helloGreeterClass).assertReflectedFrom(mainMethod); |
| graphInspector.method(helloGreeterInitMethod).assertReflectedFrom(mainMethod); |
| |
| // TODO(b/121313747): greeting() is called from main(), so this should be strengthened to |
| // `assertInvokedFrom(mainMethod)`. |
| graphInspector.method(helloGreeterGreetingMethod).assertPresent(); |
| |
| if (includeWorldGreeter) { |
| ClassReference worldGreeterClass = classFromClass(WorldGreeter.class); |
| MethodReference worldGreeterInitMethod = |
| methodFromMethod(WorldGreeter.class.getConstructor()); |
| MethodReference worldGreeterGreetingMethod = |
| methodFromMethod(WorldGreeter.class.getDeclaredMethod("greeting")); |
| |
| graphInspector.clazz(worldGreeterClass).assertKeptBy(graphInspector.method(mainMethod)); |
| graphInspector.clazz(worldGreeterClass).assertReflectedFrom(mainMethod); |
| graphInspector.method(worldGreeterInitMethod).assertReflectedFrom(mainMethod); |
| |
| // TODO(b/121313747): greeting() is called from main(), so this should be strengthened to |
| // `assertInvokedFrom(mainMethod)`. |
| graphInspector.method(worldGreeterGreetingMethod).assertPresent(); |
| } |
| } |
| |
| @Test |
| public void testResourceElimination() throws Exception { |
| String expectedOutput = "Hello world!"; |
| |
| List<String> serviceImplementations = Lists.newArrayList(HelloGreeter.class.getTypeName()); |
| if (includeWorldGreeter) { |
| serviceImplementations.add(WorldGreeter.class.getTypeName()); |
| } |
| |
| CodeInspector inspector = |
| testForR8(parameters.getBackend()) |
| .addInnerClasses(ServiceLoaderTest.class) |
| .addKeepMainRule(OtherTestClass.class) |
| .addDataEntryResources( |
| DataEntryResource.fromBytes( |
| StringUtils.lines(serviceImplementations).getBytes(), |
| "META-INF/services/" + Greeter.class.getTypeName(), |
| Origin.unknown())) |
| .addOptionsModification( |
| options -> { |
| dataResourceConsumer = |
| new DataResourceConsumerForTesting(options.dataResourceConsumer); |
| options.dataResourceConsumer = dataResourceConsumer; |
| }) |
| .setMinApi(parameters.getRuntime()) |
| .run(parameters.getRuntime(), OtherTestClass.class) |
| .assertSuccessWithOutput(expectedOutput) |
| .inspector(); |
| |
| ClassSubject greeterSubject = inspector.clazz(Greeter.class); |
| assertThat(greeterSubject, not(isPresent())); |
| |
| ClassSubject helloGreeterSubject = inspector.clazz(HelloGreeter.class); |
| assertThat(helloGreeterSubject, not(isPresent())); |
| |
| ClassSubject worldGreeterSubject = inspector.clazz(WorldGreeter.class); |
| assertThat(worldGreeterSubject, not(isPresent())); |
| |
| assertTrue(dataResourceConsumer.isEmpty()); |
| } |
| |
| static class TestClass { |
| |
| public static void main(String[] args) { |
| for (Greeter greeter : ServiceLoader.load(Greeter.class)) { |
| System.out.print(greeter.greeting()); |
| } |
| } |
| } |
| |
| static class OtherTestClass { |
| |
| public static void main(String[] args) { |
| System.out.print("Hello world!"); |
| } |
| } |
| |
| public interface Greeter { |
| |
| String greeting(); |
| } |
| |
| public static class HelloGreeter implements Greeter { |
| |
| @NeverPropagateValue |
| @Override |
| public String greeting() { |
| return "Hello"; |
| } |
| } |
| |
| public static class WorldGreeter implements Greeter { |
| |
| @Override |
| public String greeting() { |
| return " world!"; |
| } |
| } |
| } |