| // 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.tracereferences; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import com.android.tools.r8.CompilationFailedException; |
| import com.android.tools.r8.DiagnosticsChecker; |
| import com.android.tools.r8.DiagnosticsHandler; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestParametersCollection; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.references.Reference; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.DescriptorUtils; |
| import com.android.tools.r8.utils.StringDiagnostic; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.android.tools.r8.utils.ZipUtils.ZipBuilder; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import java.nio.file.Path; |
| import java.util.List; |
| 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 TraceReferencesDiagnosticTest extends TestBase { |
| @Parameters(name = "{0}") |
| public static TestParametersCollection data() { |
| return getTestParameters().withNoneRuntime().build(); |
| } |
| |
| public TraceReferencesDiagnosticTest(TestParameters parameters) { |
| parameters.assertNoneRuntime(); |
| } |
| |
| @Test |
| public void traceReferencesDiagnosticClassesFieldsAndMethods() throws Throwable { |
| Path dir = temp.newFolder().toPath(); |
| Path targetJar = |
| ZipBuilder.builder(dir.resolve("target.jar")) |
| .addBytes( |
| DescriptorUtils.getPathFromJavaType(Target.class), |
| transformer(Target.class) |
| .removeFields( |
| (access, name, descriptor, signature, value) -> |
| name.equals("missingField1")) |
| .removeFields( |
| (access, name, descriptor, signature, value) -> |
| name.equals("missingField2")) |
| .removeMethods( |
| (access, name, descriptor, signature, exceptions) -> |
| name.equals("missingMethod")) |
| .transform()) |
| .build(); |
| Path sourceJar = |
| ZipBuilder.builder(dir.resolve("source.jar")) |
| .addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| ToolHelper.getClassFileForTestClass(Source.class)) |
| .build(); |
| |
| String prefix = " Lcom/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest$"; |
| List<String> snippets = |
| ImmutableList.of( |
| "Tracereferences found 3 classe(s), 2 field(s) and 4 method(s) without definition", |
| StringUtils.lines( |
| "Classe(s) without definition:", |
| prefix + "Target1;", |
| prefix + "Target2;", |
| prefix + "Target3;"), |
| StringUtils.lines( |
| "Field(s) without definition:", |
| prefix + "Target;missingField1:I", |
| prefix + "Target;missingField2:I"), |
| StringUtils.lines( |
| "Method(s) without definition:", |
| prefix + "Target1;<init>()V", |
| prefix + "Target2;<init>()V", |
| prefix + "Target3;<init>()V", |
| prefix + "Target;missingMethod()V")); |
| try { |
| DiagnosticsChecker.checkErrorDiagnostics( |
| checker -> { |
| DiagnosticsChecker.checkContains(snippets, checker.errors); |
| try { |
| assertEquals(1, checker.errors.size()); |
| assertTrue(checker.errors.get(0) instanceof MissingDefinitionsDiagnostic); |
| MissingDefinitionsDiagnostic diagnostic = |
| (MissingDefinitionsDiagnostic) checker.errors.get(0); |
| assertEquals( |
| diagnostic.getMissingClasses(), |
| ImmutableSet.of( |
| Reference.classFromClass(Target1.class), |
| Reference.classFromClass(Target2.class), |
| Reference.classFromClass(Target3.class))); |
| assertEquals( |
| diagnostic.getMissingFields(), |
| ImmutableSet.of( |
| Reference.fieldFromField(Target.class.getField("missingField1")), |
| Reference.fieldFromField(Target.class.getField("missingField2")))); |
| assertEquals( |
| diagnostic.getMissingMethods(), |
| ImmutableSet.of( |
| Reference.methodFromMethod(Target1.class.getDeclaredConstructor()), |
| Reference.methodFromMethod(Target2.class.getDeclaredConstructor()), |
| Reference.methodFromMethod(Target3.class.getDeclaredConstructor()), |
| Reference.methodFromMethod(Target.class.getMethod("missingMethod")))); |
| } catch (ReflectiveOperationException e) { |
| fail("Unexpected exception"); |
| } |
| }, |
| handler -> |
| TraceReferences.run( |
| TraceReferencesCommand.builder(handler) |
| .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) |
| .addSourceFiles(sourceJar) |
| .addTargetFiles(targetJar) |
| .setConsumer(TraceReferencesConsumer.emptyConsumer()) |
| .build())); |
| fail("Unexpected success"); |
| } catch (CompilationFailedException e) { |
| // Expected. |
| } |
| } |
| |
| @Test |
| public void traceReferencesDiagnosticFieldsAndMethods() throws Throwable { |
| Path dir = temp.newFolder().toPath(); |
| Path targetJar = |
| ZipBuilder.builder(dir.resolve("target.jar")) |
| .addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| ToolHelper.getClassFileForTestClass(Target1.class), |
| ToolHelper.getClassFileForTestClass(Target2.class), |
| ToolHelper.getClassFileForTestClass(Target3.class)) |
| .addBytes( |
| DescriptorUtils.getPathFromJavaType(Target.class), |
| transformer(Target.class) |
| .removeFields( |
| (access, name, descriptor, signature, value) -> |
| name.equals("missingField1")) |
| .removeFields( |
| (access, name, descriptor, signature, value) -> |
| name.equals("missingField2")) |
| .removeMethods( |
| (access, name, descriptor, signature, exceptions) -> |
| name.equals("missingMethod")) |
| .transform()) |
| .build(); |
| Path sourceJar = |
| ZipBuilder.builder(dir.resolve("source.jar")) |
| .addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| ToolHelper.getClassFileForTestClass(Source.class)) |
| .build(); |
| |
| String prefix = " Lcom/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest$"; |
| List<String> snippets = |
| ImmutableList.of( |
| "Tracereferences found 2 field(s) and 1 method(s) without definition", |
| StringUtils.lines( |
| "Field(s) without definition:", |
| prefix + "Target;missingField1:I", |
| prefix + "Target;missingField2:I"), |
| StringUtils.lines("Method(s) without definition:", prefix + "Target;missingMethod()V")); |
| try { |
| DiagnosticsChecker.checkErrorDiagnostics( |
| checker -> { |
| DiagnosticsChecker.checkContains(snippets, checker.errors); |
| DiagnosticsChecker.checkNotContains( |
| ImmutableList.of("Classe(s) without definition:"), checker.errors); |
| try { |
| assertEquals(1, checker.errors.size()); |
| assertTrue(checker.errors.get(0) instanceof MissingDefinitionsDiagnostic); |
| MissingDefinitionsDiagnostic diagnostic = |
| (MissingDefinitionsDiagnostic) checker.errors.get(0); |
| assertEquals(diagnostic.getMissingClasses(), ImmutableSet.of()); |
| assertEquals( |
| diagnostic.getMissingFields(), |
| ImmutableSet.of( |
| Reference.fieldFromField(Target.class.getField("missingField1")), |
| Reference.fieldFromField(Target.class.getField("missingField2")))); |
| assertEquals( |
| diagnostic.getMissingMethods(), |
| ImmutableSet.of( |
| Reference.methodFromMethod(Target.class.getMethod("missingMethod")))); |
| } catch (ReflectiveOperationException e) { |
| fail("Unexpected exception"); |
| } |
| }, |
| handler -> |
| TraceReferences.run( |
| TraceReferencesCommand.builder(handler) |
| .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) |
| .addSourceFiles(sourceJar) |
| .addTargetFiles(targetJar) |
| .setConsumer(TraceReferencesConsumer.emptyConsumer()) |
| .build())); |
| fail("Unexpected success"); |
| } catch (CompilationFailedException e) { |
| // Expected. |
| } |
| } |
| |
| @Test |
| public void traceReferencesDiagnosticMethods() throws Throwable { |
| Path dir = temp.newFolder().toPath(); |
| Path targetJar = |
| ZipBuilder.builder(dir.resolve("target.jar")) |
| .addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| ToolHelper.getClassFileForTestClass(Target1.class), |
| ToolHelper.getClassFileForTestClass(Target2.class), |
| ToolHelper.getClassFileForTestClass(Target3.class)) |
| .addBytes( |
| DescriptorUtils.getPathFromJavaType(Target.class), |
| transformer(Target.class) |
| .removeMethods( |
| (access, name, descriptor, signature, exceptions) -> |
| name.equals("missingMethod")) |
| .transform()) |
| .build(); |
| Path sourceJar = |
| ZipBuilder.builder(dir.resolve("source.jar")) |
| .addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| ToolHelper.getClassFileForTestClass(Source.class)) |
| .build(); |
| |
| String prefix = " Lcom/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest$"; |
| List<String> snippets = |
| ImmutableList.of( |
| "Tracereferences found 1 method(s) without definition", |
| StringUtils.lines("Method(s) without definition:", prefix + "Target;missingMethod()V")); |
| try { |
| DiagnosticsChecker.checkErrorDiagnostics( |
| checker -> { |
| DiagnosticsChecker.checkContains(snippets, checker.errors); |
| try { |
| assertEquals(1, checker.errors.size()); |
| assertTrue(checker.errors.get(0) instanceof MissingDefinitionsDiagnostic); |
| MissingDefinitionsDiagnostic diagnostic = |
| (MissingDefinitionsDiagnostic) checker.errors.get(0); |
| assertEquals(diagnostic.getMissingClasses(), ImmutableSet.of()); |
| assertEquals(diagnostic.getMissingFields(), ImmutableSet.of()); |
| assertEquals( |
| diagnostic.getMissingMethods(), |
| ImmutableSet.of( |
| Reference.methodFromMethod(Target.class.getMethod("missingMethod")))); |
| } catch (ReflectiveOperationException e) { |
| fail("Unexpected exception"); |
| } |
| }, |
| handler -> |
| TraceReferences.run( |
| TraceReferencesCommand.builder(handler) |
| .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) |
| .addSourceFiles(sourceJar) |
| .addTargetFiles(targetJar) |
| .setConsumer(TraceReferencesConsumer.emptyConsumer()) |
| .build())); |
| fail("Unexpected success"); |
| } catch (CompilationFailedException e) { |
| // Expected. |
| } |
| } |
| |
| static class FailingConsumer implements TraceReferencesConsumer { |
| private final String where; |
| |
| FailingConsumer(String where) { |
| this.where = where; |
| } |
| |
| @Override |
| public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) { |
| if (where.equals("acceptType")) { |
| handler.error(new StringDiagnostic("Error in " + where)); |
| } |
| } |
| |
| @Override |
| public void acceptField(TracedField tracedField, DiagnosticsHandler handler) { |
| if (where.equals("acceptField")) { |
| handler.error(new StringDiagnostic("Error in " + where)); |
| } |
| } |
| |
| @Override |
| public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) { |
| if (where.equals("acceptMethod")) { |
| handler.error(new StringDiagnostic("Error in " + where)); |
| } |
| } |
| |
| @Override |
| public void finished(DiagnosticsHandler handler) { |
| if (where.equals("finished")) { |
| handler.error(new StringDiagnostic("Error in " + where)); |
| } |
| } |
| } |
| |
| @Test |
| public void traceReferencesConsumerError() throws Throwable { |
| Path dir = temp.newFolder().toPath(); |
| Path targetJar = |
| ZipBuilder.builder(dir.resolve("target.jar")) |
| .addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| ToolHelper.getClassFileForTestClass(Target.class), |
| ToolHelper.getClassFileForTestClass(Target1.class), |
| ToolHelper.getClassFileForTestClass(Target2.class), |
| ToolHelper.getClassFileForTestClass(Target3.class)) |
| .build(); |
| Path sourceJar = |
| ZipBuilder.builder(dir.resolve("source.jar")) |
| .addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| ToolHelper.getClassFileForTestClass(Source.class)) |
| .build(); |
| |
| for (String where : new String[] {"acceptType", "acceptField", "acceptMethod", "finished"}) { |
| try { |
| DiagnosticsChecker.checkErrorsContains( |
| "Error in " + where, |
| handler -> |
| TraceReferences.run( |
| TraceReferencesCommand.builder(handler) |
| .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) |
| .addSourceFiles(sourceJar) |
| .addTargetFiles(targetJar) |
| .setConsumer(new FailingConsumer(where)) |
| .build())); |
| fail("Unexpected success"); |
| } catch (CompilationFailedException e) { |
| // Expected. |
| } |
| } |
| } |
| |
| static class Target1 {} |
| |
| static class Target2 {} |
| |
| static class Target3 {} |
| |
| static class Target { |
| public static int missingField1; |
| public static int missingField2; |
| |
| public static void missingMethod() {} |
| } |
| |
| static class Source { |
| public static void source() { |
| new Target1(); |
| new Target2(); |
| new Target3(); |
| |
| Target.missingField1 = 1; |
| Target.missingField2 = 2; |
| Target.missingMethod(); |
| } |
| } |
| } |