Skip to content

Commit

Permalink
Add Support of Iterative Desugar Support for Testing
Browse files Browse the repository at this point in the history
- Add interative desugar support to test Idempotency.

PiperOrigin-RevId: 284286744
  • Loading branch information
Googler authored and copybara-github committed Dec 7, 2019
1 parent 0552bd7 commit 724bd31
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ public class DesugarRule implements TestRule {
* Class#getName}.
*/
String value();

/** The round during which its associated jar is being used. */
int round() default 1;
}

private static final Path ANDROID_RUNTIME_JAR_PATH =
Expand All @@ -104,13 +107,12 @@ public class DesugarRule implements TestRule {

private static final String DEFAULT_OUTPUT_ROOT_PREFIX = "desugared_dump";

private static final ClassLoader baseClassLoader = ClassLoader.getSystemClassLoader().getParent();

/** The transformation record that describes the desugaring of a jar. */
@AutoValue
abstract static class JarTransformationRecord {

private static final ClassLoader baseClassLoader =
ClassLoader.getSystemClassLoader().getParent();

/**
* The full runtime path of a pre-transformationRecord jar.
*
Expand Down Expand Up @@ -169,12 +171,14 @@ final ClassLoader getOutputClassLoader() throws MalformedURLException {

private final Object testInstance;
private final MethodHandles.Lookup testInstanceLookup;
private final int maxNumOfTransformations;
private final ImmutableList<Path> inputs;
private final ImmutableList<Path> classPathEntries;
private final ImmutableList<Path> bootClassPathEntries;
private final ImmutableList<Field> fieldForDynamicClassLoading;
private final ImmutableListMultimap<String, String> extraCustomCommandOptions;

private final List<JarTransformationRecord> jarTransformationRecords;
/** The state of the already-created directories to avoid directory re-creation. */
private final Map<String, Path> tempDirs = new HashMap<>();

Expand All @@ -191,19 +195,22 @@ public static DesugarRuleBuilder builder(Object testInstance, Lookup testInstanc

private DesugarRule(
DesugarRuleBuilder desugarRuleBuilder,
MethodHandles.Lookup testInstanceLookup,
Lookup testInstanceLookup,
int maxNumOfTransformations,
ImmutableList<Path> inputJars,
ImmutableList<Path> classPathEntries,
ImmutableList<Path> bootClassPathEntries) {
this.testInstance = desugarRuleBuilder.testInstance;
this.testInstanceLookup = testInstanceLookup;
this.maxNumOfTransformations = maxNumOfTransformations;
this.inputs = inputJars;
this.classPathEntries = classPathEntries;
this.bootClassPathEntries = bootClassPathEntries;
this.extraCustomCommandOptions =
ImmutableListMultimap.copyOf(desugarRuleBuilder.customCommandOptions);
this.fieldForDynamicClassLoading =
findAllFieldsWithAnnotation(testInstance.getClass(), LoadClass.class);
this.jarTransformationRecords = new ArrayList<>(maxNumOfTransformations);
}

@Override
Expand All @@ -212,18 +219,35 @@ public Statement apply(Statement base, Description description) {
new Statement() {
@Override
public void evaluate() throws Throwable {
JarTransformationRecord transformationRecord =
JarTransformationRecord.create(
inputs,
getRuntimeOutputPaths(inputs, temporaryFolder, tempDirs),
classPathEntries,
bootClassPathEntries,
extraCustomCommandOptions);
Desugar.main(transformationRecord.getDesugarFlags().toArray(new String[0]));

ClassLoader outputJarClassLoader = transformationRecord.getOutputClassLoader();
ImmutableList<Path> transInputs = inputs;
for (int i = 0; i < maxNumOfTransformations; i++) {
ImmutableList<Path> transOutputs =
getRuntimeOutputPaths(
transInputs,
temporaryFolder,
tempDirs,
/* defaultOutputRootPrefix= */ DEFAULT_OUTPUT_ROOT_PREFIX + "_" + i);
JarTransformationRecord transformationRecord =
JarTransformationRecord.create(
transInputs,
transOutputs,
classPathEntries,
bootClassPathEntries,
extraCustomCommandOptions);
Desugar.main(transformationRecord.getDesugarFlags().toArray(new String[0]));

jarTransformationRecords.add(transformationRecord);
transInputs = transOutputs;
}

for (Field field : fieldForDynamicClassLoading) {
String qualifiedClassName = field.getDeclaredAnnotation(LoadClass.class).value();
LoadClass loadClassAnnotation = field.getDeclaredAnnotation(LoadClass.class);
String qualifiedClassName = loadClassAnnotation.value();
int round = loadClassAnnotation.round();
ClassLoader outputJarClassLoader =
round == 0
? getInputClassLoader()
: jarTransformationRecords.get(round - 1).getOutputClassLoader();
Class<?> classLiteral = outputJarClassLoader.loadClass(qualifiedClassName);
MethodHandle fieldSetter = testInstanceLookup.unreflectSetter(field);
fieldSetter.invoke(testInstance, classLiteral);
Expand All @@ -234,12 +258,23 @@ public void evaluate() throws Throwable {
description);
}

private ClassLoader getInputClassLoader() throws MalformedURLException {
List<URL> urls = new ArrayList<>();
for (Path path : Iterables.concat(inputs, classPathEntries, bootClassPathEntries)) {
urls.add(path.toUri().toURL());
}
return URLClassLoader.newInstance(urls.toArray(new URL[0]), baseClassLoader);
}

private static ImmutableList<Path> getRuntimeOutputPaths(
ImmutableList<Path> inputs, TemporaryFolder temporaryFolder, Map<String, Path> tempDirs)
ImmutableList<Path> inputs,
TemporaryFolder temporaryFolder,
Map<String, Path> tempDirs,
String defaultOutputRootPrefix)
throws IOException {
ImmutableList.Builder<Path> outputRuntimePathsBuilder = ImmutableList.builder();
for (Path path : inputs) {
String targetDirKey = Paths.get(DEFAULT_OUTPUT_ROOT_PREFIX) + "/" + path.getParent();
String targetDirKey = Paths.get(defaultOutputRootPrefix) + "/" + path.getParent();
final Path outputDirPath;
if (tempDirs.containsKey(targetDirKey)) {
outputDirPath = tempDirs.get(targetDirKey);
Expand Down Expand Up @@ -272,6 +307,8 @@ public static class DesugarRuleBuilder {

private final Object testInstance;
private final MethodHandles.Lookup testInstanceLookup;
private final ImmutableList<Field> classLiteralFieldToBeLoaded;
private int maxNumOfTransformations;
private final List<Path> inputs = new ArrayList<>();
private final List<Path> classPathEntries = new ArrayList<>();
private final List<Path> bootClassPathEntries = new ArrayList<>();
Expand All @@ -293,15 +330,12 @@ public static class DesugarRuleBuilder {
"Expected a test instance whose class is annotated with @RunWith. %s", testClass);
}

for (Field field : findAllFieldsWithAnnotation(testClass, LoadClass.class)) {
if (Modifier.isStatic(field.getModifiers())) {
errorMessenger.addError("Expected to be non-static for field (%s)", field);
}
classLiteralFieldToBeLoaded = findAllFieldsWithAnnotation(testClass, LoadClass.class);
}

if (field.getType() != Class.class) {
errorMessenger.addError("Expected a class literal type (Class<?>) for field (%s)", field);
}
}
public DesugarRuleBuilder enableIterativeTransformation(int maxNumOfTransformations) {
this.maxNumOfTransformations = maxNumOfTransformations;
return this;
}

public DesugarRuleBuilder addRuntimeInputs(String... inputJars) {
Expand Down Expand Up @@ -350,6 +384,8 @@ private void checkJVMOptions() {

public DesugarRule build() {
checkJVMOptions();
checkClassLiteralFieldToBeLoaded();

if (bootClassPathEntries.isEmpty()
&& !customCommandOptions.containsKey("allow_empty_bootclasspath")) {
addCommandOptions("bootclasspath_entry", ANDROID_RUNTIME_JAR_PATH.toString());
Expand All @@ -366,10 +402,32 @@ public DesugarRule build() {
return new DesugarRule(
this,
testInstanceLookup,
maxNumOfTransformations,
ImmutableList.copyOf(inputs),
ImmutableList.copyOf(classPathEntries),
ImmutableList.copyOf(bootClassPathEntries));
}

private void checkClassLiteralFieldToBeLoaded() {
for (Field field : classLiteralFieldToBeLoaded) {
if (Modifier.isStatic(field.getModifiers())) {
errorMessenger.addError("Expected to be non-static for field (%s)", field);
}

if (field.getType() != Class.class) {
errorMessenger.addError("Expected a class literal type (Class<?>) for field (%s)", field);
}

LoadClass loadClassAnnotation = field.getDeclaredAnnotation(LoadClass.class);
if (loadClassAnnotation.round() < 0
|| loadClassAnnotation.round() > maxNumOfTransformations) {
errorMessenger.addError(
"Expected the round of desugar transformation within [0, %d], where 0 indicates no"
+ " transformation is used.",
maxNumOfTransformations);
}
}
}
}

/** A messenger that manages desugar configuration errors. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,23 @@ public class DesugarRuleTest {
@Rule
public final DesugarRule desugarRule =
DesugarRule.builder(this, MethodHandles.lookup())
.enableIterativeTransformation(3)
.addRuntimeInputs(
"third_party/bazel/src/test/java/com/google/devtools/build/android/desugar/libdesugar_rule_test_target.jar")
.build();

@LoadClass(
"com.google.devtools.build.android.desugar.DesugarRuleTestTarget$InterfaceSubjectToDesugar")
value =
"com.google.devtools.build.android.desugar.DesugarRuleTestTarget$InterfaceSubjectToDesugar",
round = 1)
private Class<?> interfaceSubjectToDesugarClass;

@LoadClass(
value =
"com.google.devtools.build.android.desugar.DesugarRuleTestTarget$InterfaceSubjectToDesugar",
round = 2)
private Class<?> interfaceSubjectToDesugarClassTwice;

@LoadClass(
"com.google.devtools.build.android.desugar.DesugarRuleTestTarget$InterfaceSubjectToDesugar$$CC")
private Class<?> interfaceSubjectToDesugarCompanionClass;
Expand All @@ -54,6 +63,13 @@ public void staticMethodsAreMovedFromOriginatingClass() {
() -> interfaceSubjectToDesugarClass.getDeclaredMethod("staticMethod"));
}

@Test
public void staticMethodsAreMovedFromOriginatingClass_desugarTwice() {
assertThrows(
NoSuchMethodException.class,
() -> interfaceSubjectToDesugarClassTwice.getDeclaredMethod("staticMethod"));
}

@Test
public void staticMethodsAreMovedToCompanionClass() {
assertThat(
Expand Down

0 comments on commit 724bd31

Please sign in to comment.