blob: 5976b7ccf1cb374a23e845b911cb4940842192d7 [file] [log] [blame]
// Copyright (c) 2020, 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.rewrite.serviceloaders;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.Box;
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.google.common.io.ByteStreams;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
// Test where a service implementation class is missing.
@RunWith(Parameterized.class)
public class MissingServiceImplementationClassTest extends TestBase {
private final TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public MissingServiceImplementationClassTest(TestParameters parameters) {
this.parameters = parameters;
}
@Test
public void testR8() throws Exception {
Box<DataResourceConsumerForTesting> dataResourceConsumer = new Box<>();
R8TestCompileResult compileResult =
testForR8(parameters.getBackend())
.addProgramClasses(TestClass.class, Service.class)
.addKeepMainRule(TestClass.class)
.addKeepClassAndMembersRulesWithAllowObfuscation(Service.class)
.addDataEntryResources(
DataEntryResource.fromBytes(
StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
AppServices.SERVICE_DIRECTORY_NAME + Service.class.getTypeName(),
Origin.unknown()))
.addOptionsModification(
options -> {
dataResourceConsumer.set(
new DataResourceConsumerForTesting(options.dataResourceConsumer));
options.dataResourceConsumer = dataResourceConsumer.get();
})
.setMinApi(parameters.getApiLevel())
.compile();
CodeInspector inspector = compileResult.inspector();
ClassSubject serviceClassSubject = inspector.clazz(Service.class);
assertThat(serviceClassSubject, isPresent());
// Verify that ServiceImpl was not removed from META-INF/services/[...]Service.
inspectResource(
dataResourceConsumer.get().get("META-INF/services/" + serviceClassSubject.getFinalName()));
// Execution fails since META-INF/services/[...]Service is referring to a missing class.
compileResult
.run(parameters.getRuntime(), TestClass.class)
.assertFailureWithErrorThatThrows(ServiceConfigurationError.class);
// Verify that it is still possible to inject a new implementation of Service after the
// compilation.
testForR8(parameters.getBackend())
.addProgramClasses(ServiceImpl.class)
.addClasspathClasses(Service.class)
.addKeepAllClassesRule()
.addApplyMapping(compileResult.getProguardMap())
.setMinApi(parameters.getApiLevel())
.compile()
.addRunClasspathFiles(
transformServiceDeclarationInProgram(
compileResult.writeToZip(),
AppServices.SERVICE_DIRECTORY_NAME + serviceClassSubject.getFinalName(),
ServiceImpl.class.getTypeName()))
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("Hello world!");
}
private void inspectResource(List<String> contents) {
assertNotNull(contents);
assertEquals(1, contents.size());
assertEquals(ServiceImpl.class.getTypeName(), contents.get(0));
}
private Path transformServiceDeclarationInProgram(Path program, String fileName, String contents)
throws Exception {
Path newProgram = temp.newFolder().toPath().resolve("program.jar");
try (ZipOutputStream outputStream = new ZipOutputStream(Files.newOutputStream(newProgram))) {
try (ZipInputStream inputStream = new ZipInputStream(Files.newInputStream(program))) {
ZipEntry next = inputStream.getNextEntry();
while (next != null) {
outputStream.putNextEntry(new ZipEntry(next.getName()));
if (next.getName().equals(fileName)) {
outputStream.write(contents.getBytes());
} else {
outputStream.write(ByteStreams.toByteArray(inputStream));
}
outputStream.closeEntry();
next = inputStream.getNextEntry();
}
}
}
return newProgram;
}
static class TestClass {
public static void main(String[] args) {
for (Object object : ServiceLoader.load(getServiceClass())) {
if (object instanceof Service) {
Service service = (Service) object;
service.greet();
}
}
}
static Class<?> getServiceClass() {
return System.currentTimeMillis() >= 0 ? Service.class : Object.class;
}
}
public interface Service {
void greet();
}
public static class ServiceImpl implements Service {
@NeverInline
public void greet() {
System.out.println("Hello world!");
}
}
}