SonarQube 测试自定义 Java 规则无法显示语义信息

Kev*_*rom 4 java sonarqube

我正在学习 SonarQube 的 API,试图扩展 java 插件规则。我成功地遵循了本教程

现在我想构建一个简单的分析来检查toString() 单元测试中是否使用了某个方法。

public class TS_SensitiveEqualityCheck extends BaseTreeVisitor implements JavaFileScanner {

  private final Deque<Boolean> methodContainsToStringInAssert = new ArrayDeque<Boolean>();
  private final Deque<Boolean> inUnitTest = new ArrayDeque<Boolean>();

  private JavaFileScannerContext context;

  @Override
  public void scanFile(final JavaFileScannerContext context) {
    this.context = context;
    scan(context.getTree());
  }

  @Override
  public void visitMethod(MethodTree methodTree) {
    if (ModifiersUtils.hasModifier(methodTree.modifiers(), Modifier.ABSTRACT)) {
      return;
    }
    boolean isUnitTest = isUnitTest(methodTree);
    inUnitTest.push(isUnitTest);
    System.out.println("For method " + methodTree.simpleName() 
    + " found [isUnitTest | isViolation] :  " 
    + String.valueOf(isUnitTest));
    methodContainsToStringInAssert.push(false);
    super.visitMethod(methodTree);
    inUnitTest.pop();
    Boolean isViolation = methodContainsToStringInAssert.pop();
    System.out.println("For method " + methodTree.simpleName() 
            + " found [isUnitTest | isViolation] :  " 
            + String.valueOf(isUnitTest) + " " 
            + String.valueOf(isViolation) );
    if (isUnitTest && isViolation) { 
      context.reportIssue(this, methodTree.simpleName(), "This test method uses unsafe equality checking!");
    }
  }

  @Override
  public void visitMethodInvocation(MethodInvocationTree mit) {
    if (!inUnitTest()) {
      return;
    }
    Symbol mis = mit.symbol();
    System.out.println(mis.name()); // null when encountering an assertion.
    if (mis.name() != null && mis.name().equals("toString")) {
        setTrue(methodContainsToStringInAssert);
    }
    super.visitMethodInvocation(mit);
  }

  private boolean inUnitTest() {
    return !inUnitTest.isEmpty() && inUnitTest.peek();
  }

  private static void setTrue(Deque<Boolean> collection) {
    if (collection != null && !collection.peek()) {
      collection.pop();
      collection.push(true);
    }
  }

  private static boolean isUnitTest(MethodTree methodTree) {
    JavaSymbol.MethodJavaSymbol symbol = (JavaSymbol.MethodJavaSymbol) methodTree.symbol();
    while (symbol != null) {
      if (symbol.metadata().isAnnotatedWith("org.junit.Test")) {
        return true;
      }
      symbol = symbol.overriddenSymbol();
    }
    Symbol.TypeSymbol enclosingClass = methodTree.symbol().enclosingClass();
    return (enclosingClass != null 
            // && enclosingClass.type().isSubtypeOf("junit.framework.TestCase")  // errors!!! does not get the package name of the class!!!
            && methodTree.simpleName().name().startsWith("test"));
  }
Run Code Online (Sandbox Code Playgroud)

}

对于给定的测试文件 SonarQube 找不到任何断言方法调用!只有方法B.is()给出结果,即mit.symbol().name != null。谁能解释为什么会出错?这是用作测试的文件:

import junit.framework.TestCase;
    import javax.annotation.Nullable;

public class AssertionsInTestsCheckTestJunit3 extends TestCase { public void testCompliant() { B b = new B(); b.is(); org.junit.Assert.assertTrue(b.is()); } public void testNoncompliant() { // Noncompliant org.junit.Assert.assertTrue(this.toString().equals("")); } public void testNoncompliant2() { // Noncompliant org.junit.Assert.assertEquals(this.toString(), ""); } public void testNoncompliant3() { // Noncompliant org.junit.Fail.fail(this.toString()); doWork(); } @Nullable public Test notAtest() { compliant1(); } } public class B { public boolean is() { return true; } } </pre></code>

Run Code Online (Sandbox Code Playgroud)

请注意,这段代码做什么并不重要!

Mic*_*eam 5

Java 分析器需要源文件中使用的库的字节码来完成语义模型。没有它,大部分可以检索的语义信息都将丢失。

在您的测试文件中,您使用的是junit。但是,缺少与 junit 相关的二进制文件,因为您很可能没有向检查验证程序提供库。这部分还没有在教程中描述。

默认情况下,不会向检查验证器提供任何外部库,因为本教程在当前状态下不需要外部库。解释如何使用外部源的部分尚未编写,但一旦完成,它应该可以通过以下链接获得:如何测试需要外部二进制文件的源

现在,为了解决您的问题并总结本教程中将要介绍的内容,您必须执行以下操作:

为了在分析文件时使用足够的字节码,您必须向检查验证程序提供junit 二进制文件。有多种方法可以做到这一点,但最简单的方法可能是通过将 jar 放置在项目中的专用位置来提供它:target/test-jars. 默认情况下,检查验证程序将在此文件夹中查找。

您可以通过更改项目根目录下的 pom.xml 文件自动添加任何所需的库:

  1. 在插件根目录的 pom.xml 文件中,您应该看到一个注释部分:pom.xml#L108(来自教程中使用的模板)
  2. 取消注释这部分代码。请注意,对于该示例,它已经包含 apache commons-collections 的二进制文件。
 <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>2.10</version>
    <executions>
      <execution>
        <id>copy</id>
        <phase>test-compile</phase>
        <goals>
          <goal>copy</goal>
        </goals>
        <configuration>
          <artifactItems>
            <artifactItem>
              <groupId>org.apache.commons</groupId>
              <artifactId>commons-collections4</artifactId>
              <version>4.0</version>
              <type>jar</type>
            </artifactItem>
          </artifactItems>
          <outputDirectory>${project.build.directory}/test-jars</outputDirectory>
        </configuration>
      </execution>
    </executions>
  </plugin>
Run Code Online (Sandbox Code Playgroud)
  1. 替换<artifactItem>以使用 junit,或者简单地添加一个新工件:
  <artifactItem>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <type>jar</type>
  </artifactItem>
Run Code Online (Sandbox Code Playgroud)
  1. 使用mvn clean install -DskipTests. 具有所需版本的junit jar 将由maven 下载,并放置在您的target/test-jars文件夹中。

  2. 重新运行您的测试。应该发现问题。