From 724bd31cb371df68549a45f2baf4dd81b6fa0b12 Mon Sep 17 00:00:00 2001 From: Googler Date: Fri, 6 Dec 2019 16:34:42 -0800 Subject: [PATCH] Add Support of Iterative Desugar Support for Testing - Add interative desugar support to test Idempotency. PiperOrigin-RevId: 284286744 --- .../build/android/desugar/DesugarRule.java | 108 ++++++++++++++---- .../android/desugar/DesugarRuleTest.java | 18 ++- 2 files changed, 100 insertions(+), 26 deletions(-) diff --git a/src/test/java/com/google/devtools/build/android/desugar/DesugarRule.java b/src/test/java/com/google/devtools/build/android/desugar/DesugarRule.java index 9bf35a62853f99..c299862ebfb06d 100644 --- a/src/test/java/com/google/devtools/build/android/desugar/DesugarRule.java +++ b/src/test/java/com/google/devtools/build/android/desugar/DesugarRule.java @@ -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 = @@ -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. * @@ -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 inputs; private final ImmutableList classPathEntries; private final ImmutableList bootClassPathEntries; private final ImmutableList fieldForDynamicClassLoading; private final ImmutableListMultimap extraCustomCommandOptions; + private final List jarTransformationRecords; /** The state of the already-created directories to avoid directory re-creation. */ private final Map tempDirs = new HashMap<>(); @@ -191,12 +195,14 @@ public static DesugarRuleBuilder builder(Object testInstance, Lookup testInstanc private DesugarRule( DesugarRuleBuilder desugarRuleBuilder, - MethodHandles.Lookup testInstanceLookup, + Lookup testInstanceLookup, + int maxNumOfTransformations, ImmutableList inputJars, ImmutableList classPathEntries, ImmutableList bootClassPathEntries) { this.testInstance = desugarRuleBuilder.testInstance; this.testInstanceLookup = testInstanceLookup; + this.maxNumOfTransformations = maxNumOfTransformations; this.inputs = inputJars; this.classPathEntries = classPathEntries; this.bootClassPathEntries = bootClassPathEntries; @@ -204,6 +210,7 @@ private DesugarRule( ImmutableListMultimap.copyOf(desugarRuleBuilder.customCommandOptions); this.fieldForDynamicClassLoading = findAllFieldsWithAnnotation(testInstance.getClass(), LoadClass.class); + this.jarTransformationRecords = new ArrayList<>(maxNumOfTransformations); } @Override @@ -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 transInputs = inputs; + for (int i = 0; i < maxNumOfTransformations; i++) { + ImmutableList 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); @@ -234,12 +258,23 @@ public void evaluate() throws Throwable { description); } + private ClassLoader getInputClassLoader() throws MalformedURLException { + List 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 getRuntimeOutputPaths( - ImmutableList inputs, TemporaryFolder temporaryFolder, Map tempDirs) + ImmutableList inputs, + TemporaryFolder temporaryFolder, + Map tempDirs, + String defaultOutputRootPrefix) throws IOException { ImmutableList.Builder 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); @@ -272,6 +307,8 @@ public static class DesugarRuleBuilder { private final Object testInstance; private final MethodHandles.Lookup testInstanceLookup; + private final ImmutableList classLiteralFieldToBeLoaded; + private int maxNumOfTransformations; private final List inputs = new ArrayList<>(); private final List classPathEntries = new ArrayList<>(); private final List bootClassPathEntries = new ArrayList<>(); @@ -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) { @@ -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()); @@ -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. */ diff --git a/src/test/java/com/google/devtools/build/android/desugar/DesugarRuleTest.java b/src/test/java/com/google/devtools/build/android/desugar/DesugarRuleTest.java index c244d2d0de4501..f6ad262f5885b0 100644 --- a/src/test/java/com/google/devtools/build/android/desugar/DesugarRuleTest.java +++ b/src/test/java/com/google/devtools/build/android/desugar/DesugarRuleTest.java @@ -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; @@ -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(