Back to Repositories

Validating R2 Resource Usage Detection in Butterknife

This test suite validates the proper usage of R2 resources in the Butterknife library, focusing on detecting invalid R2 usage patterns in Android applications. The tests ensure that R2 references are only used within annotations and verify suppression handling.

Test Coverage Overview

The test suite provides comprehensive coverage of R2 usage scenarios in Android applications.

Key functionality tested includes:
  • Clean R2-free code validation
  • Valid R2 usage within annotations
  • Invalid R2 usage outside annotations
  • R2 usage with @SuppressWarnings
Edge cases cover different resource types (string, id, color, etc.) and various package reference patterns.

Implementation Analysis

The testing approach utilizes Android Lint’s testing infrastructure to validate R2 usage patterns.

The implementation employs mock Java files to simulate different usage scenarios, with TestFile objects representing R2 resource definitions and test cases. The tests leverage Lint’s builder pattern for configuring test cases and asserting expected outcomes.

Technical Details

Testing tools and configuration:
  • Android Lint Test Infrastructure
  • JUnit 4 Framework
  • TestLintTask for lint checking
  • TestFiles utility for Java source creation
  • Custom annotation definitions (@BindTest)

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices in Android development.

Notable practices include:
  • Isolated test cases with clear purpose
  • Comprehensive mock file setup
  • Explicit error count verification
  • Clean code organization with separate test scenarios
  • Proper test naming conventions

jakewharton/butterknife

butterknife-lint/src/test/java/butterknife/lint/InvalidR2UsageDetectorTest.java

            
package butterknife.lint;

import com.android.tools.lint.checks.infrastructure.TestFile;
import org.junit.Test;

import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;

public final class InvalidR2UsageDetectorTest {
  private static final TestFile BIND_TEST = java(""
      + "package sample.r2;\n"
      + "\n"
      + "import java.lang.annotation.ElementType;\n"
      + "import java.lang.annotation.Retention;\n"
      + "import java.lang.annotation.RetentionPolicy;\n"
      + "import java.lang.annotation.Target;\n"
      + "\n"
      + "@Retention(RetentionPolicy.SOURCE) @Target({ ElementType.FIELD, ElementType.METHOD })\n"
      + "public @interface BindTest {\n"
      + "  int value();\n"
      + "}\n");

  private static final TestFile R2 = java(""
      + "package sample.r2;\n"
      + "\n"
      + "public final class R2 {\n"
      + "  public static final class array {\n"
      + "    public static final int res = 0x7f040001;\n"
      + "  }\n"
      + "\n"
      + "  public static final class attr {\n"
      + "    public static final int res = 0x7f040002;\n"
      + "  }\n"
      + "\n"
      + "  public static final class bool {\n"
      + "    public static final int res = 0x7f040003;\n"
      + "  }\n"
      + "\n"
      + "  public static final class color {\n"
      + "    public static final int res = 0x7f040004;\n"
      + "  }\n"
      + "\n"
      + "  public static final class dimen {\n"
      + "    public static final int res = 0x7f040005;\n"
      + "  }\n"
      + "\n"
      + "  public static final class drawable {\n"
      + "    public static final int res = 0x7f040006;\n"
      + "  }\n"
      + "\n"
      + "  public static final class id {\n"
      + "    public static final int res = 0x7f040007;\n"
      + "  }\n"
      + "\n"
      + "  public static final class integer {\n"
      + "    public static final int res = 0x7f040008;\n"
      + "  }\n"
      + "\n"
      + "  public static final class string {\n"
      + "    public static final int res = 0x7f040009;\n"
      + "  }\n"
      + "}");

  @Test public void noR2Usage() {
    lint() //
        .files(R2, //
            java("" //
                + "package sample;\n" //
                + "class NoR2Usage {}\n")) //
        .issues(InvalidR2UsageDetector.ISSUE) //
        .run() //
        .expectClean();
  }

  @Test public void usesR2InAnnotations() {
    lint() //
        .files(R2, BIND_TEST, //
            java(""
                + "package sample.r2;\n"
                + "\n"
                + "public class R2UsageInAnnotations {\n"
                + "\n"
                + "  @BindTest(sample.r2.R2.string.res) String test;\n"
                + "\n"
                + "  @BindTest(R2.id.res) public void foo() {}\n"
                + "}\n") //
        ) //
        .issues(InvalidR2UsageDetector.ISSUE) //
        .run() //
        .expectClean();
  }

  @Test public void usesR2OutsideAnnotations() {
    lint() //
        .files(R2, //
            java(""
                + "package sample.r2;\n"
                + "\n"
                + "public class R2UsageOutsideAnnotations {\n"
                + "\n"
                + "  int array = sample.r2.R2.array.res;\n"
                + "\n"
                + "  public void foo(int color) {}\n"
                + "\n"
                + "  public void bar() {\n"
                + "    foo(R2.color.res);\n"
                + "  }\n"
                + "}\n" //
            )) //
        .issues(InvalidR2UsageDetector.ISSUE) //
        .run() //
        .expectErrorCount(2) //
        .expectWarningCount(0);
  }

  @Test public void usesR2WithSuppression() {
    lint() //
        .files(R2, java(""
            + "package sample.r2;\n"
            + "\n"
            + "public class R2UsageWithSuppression {\n"
            + "\n"
            + "  @SuppressWarnings(\"InvalidR2Usage\")\n"
            + "  int bool = sample.r2.R2.bool.res;\n"
            + "\n"
            + "  public void foo(int attr) {}\n"
            + "\n"
            + "  @SuppressWarnings(\"InvalidR2Usage\")\n"
            + "  public void bar() {\n"
            + "    foo(R2.attr.res);\n"
            + "  }\n"
            + "}\n")) //
        .issues(InvalidR2UsageDetector.ISSUE) //
        .run() //
        .expectClean();
  }
}