blob: 4cbaf6c819d6ec4341c6a6216bb706bdf7c718c6 [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.resource;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DataDirectoryResource;
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.DataResource;
import com.android.tools.r8.DataResourceConsumer;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.StringResource;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.BooleanUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
class Main {
private void method() {}
public static void main(String[] args) {
new Main().method();
}
}
@RunWith(Parameterized.class)
public class KeepDirectoriesTest extends ProguardCompatibilityTestBase {
private Backend backend;
private final boolean minify;
@Parameterized.Parameters(name = "Backend: {0}, Minify: {1}")
public static Collection<Object[]> data() {
return buildParameters(ToolHelper.getBackends(), BooleanUtils.values());
}
public KeepDirectoriesTest(Backend backend, boolean minify) {
this.backend = backend;
this.minify = minify;
}
// Return the original package name for this package.
private String pathForThisPackage() {
return getClass().getPackage().getName().replace('.', '/');
}
// Return the package name in the app for this package.
private String pathForThisPackage(AndroidApp app) throws Exception {
String name;
if (app.getProguardMapOutputData() != null) {
ClassNameMapper mapper =
ClassNameMapper.mapperFromString(app.getProguardMapOutputData().getString());
name = mapper.getObfuscatedToOriginalMapping().inverse.get(Main.class.getCanonicalName());
} else {
name = Main.class.getTypeName();
}
return name.substring(0, name.lastIndexOf('.')).replace('.', '/');
}
private void checkResourceNames(Set<String> resourceNames, String expectedPackageName) {
resourceNames.stream()
.filter(name -> !name.startsWith("org"))
.forEach(name -> assertTrue(name.startsWith(expectedPackageName)));
}
@Test
public void testKeepNone() throws Exception {
CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
compileWithR8("", minify, dataResourceConsumer);
assertEquals(0, dataResourceConsumer.size());
}
@Test
public void testKeepAll() throws Exception {
CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
AndroidApp output = compileWithR8("-keepdirectories", minify, dataResourceConsumer);
assertEquals(getDataResources().size(), dataResourceConsumer.size());
checkResourceNames(dataResourceConsumer.getResourceNames(), pathForThisPackage(output));
}
@Test
public void testKeepSome1() throws Exception {
CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
compileWithR8("-keepdirectories org/example/*", minify, dataResourceConsumer);
Set<String> expected =
getDataResources().stream()
.map(DataResource::getName)
.filter(name -> name.startsWith("org/example/"))
.collect(Collectors.toSet());
assertEquals(expected, dataResourceConsumer.getResourceNames());
}
private int numberOfPathSeparators(String s) {
return (int) s.chars().filter(c -> c == '/').count();
}
class PrefixMapper implements Function<String, String> {
private final String original;
private final String mapped;
PrefixMapper(String original, String mapped) {
this.original = original;
this.mapped = mapped;
}
@Override
public String apply(String s) {
if (s.startsWith(original)) {
return mapped + s.substring(original.length());
} else {
return s;
}
}
}
@Test
public void testKeepSome2() throws Exception {
CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
AndroidApp output =
compileWithR8(
"-keepdirectories org/*," + pathForThisPackage() + "/**", minify, dataResourceConsumer);
Set<String> expected1 =
getDataResources().stream()
.map(DataResource::getName)
.filter(name -> name.startsWith("org/"))
.filter(name -> numberOfPathSeparators(name) < 2)
.collect(Collectors.toSet());
PrefixMapper prefixMapper = new PrefixMapper(pathForThisPackage(), pathForThisPackage(output));
Set<String> expected2 =
getDataResources().stream()
.map(DataResource::getName)
.filter(name -> name.startsWith(pathForThisPackage() + "/"))
.map(prefixMapper)
.collect(Collectors.toSet());
assertEquals(Sets.union(expected1, expected2), dataResourceConsumer.getResourceNames());
}
private AndroidApp compileWithR8(
String proguardConfig, boolean allowMinification, DataResourceConsumer dataResourceConsumer)
throws CompilationFailedException {
String r =
"-keep"
+ (allowMinification ? ",allowobfuscation" : "")
+ " class "
+ Main.class.getCanonicalName();
R8Command command =
ToolHelper.prepareR8CommandBuilder(getAndroidApp(), emptyConsumer(backend))
.addProguardConfiguration(
ImmutableList.of(proguardConfig, "-printmapping", r), Origin.unknown())
.addLibraryFiles(runtimeJar(backend))
.build();
return ToolHelper.runR8(
command, options -> options.dataResourceConsumer = dataResourceConsumer);
}
private AndroidApp getAndroidApp() {
AndroidApp.Builder builder = AndroidApp.builder();
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(Main.class));
getDataResources().forEach(builder::addDataResource);
return builder.build();
}
protected static class CustomDataResourceConsumer implements DataResourceConsumer {
private final Set<String> resourceNames = Sets.newHashSet();
@Override
public void accept(DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
assertFalse(resourceNames.contains(directory.getName()));
resourceNames.add(directory.getName());
}
@Override
public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
throw new Unreachable();
}
@Override
public void finished(DiagnosticsHandler handler) {}
public boolean isPresent(String name) {
return resourceNames.contains(name);
}
public Set<String> getResourceNames() {
return resourceNames;
}
public int size() {
return resourceNames.size();
}
}
private List<DataResource> getDataResources() {
return ImmutableList.of(
DataDirectoryResource.fromName("org", Origin.unknown()),
DataDirectoryResource.fromName("org/example", Origin.unknown()),
DataDirectoryResource.fromName("org/example/test", Origin.unknown()),
DataDirectoryResource.fromName(pathForThisPackage(), Origin.unknown()),
DataDirectoryResource.fromName(pathForThisPackage() + "/subpackage1", Origin.unknown()),
DataDirectoryResource.fromName(pathForThisPackage() + "/subpackage2", Origin.unknown()),
DataDirectoryResource.fromName(
pathForThisPackage() + "/subpackage2/subpackage", Origin.unknown()));
}
}