3

I have an application with the following structure:

my-application
  +- pom.xml
  +- app
  |   +- scripts
  |   |   +- app.js
  |   |   +- **/*.js
  |   +- 3rd-party-libs
  +- build
  +- node_modules
  +- test

I've create the pom.xml only to run the SonarQube analysis. Otherwise, all the tasks are run by Grunt (tests are run with Karma).

The content of the pom.xml is the following:

<properties>
    <sonar.language>js</sonar.language>
    <sonar.sourceEncoding>UTF-8</sonar.sourceEncoding>
    <sonar.javascript.coveragePlugin>lcov</sonar.javascript.coveragePlugin>
    <sonar.javascript.lcov.reportPath>build/karma/coverage/lcov.info</sonar.javascript.lcov.reportPath>
    <sonar.exclusions>app/3rd-party-libs/**,node_modules/**</sonar.exclusions>
    <sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
</properties>

<build>
    <sourceDirectory>app/scripts</sourceDirectory>
    <testSourceDirectory>test</testSourceDirectory>
</build>

When I run grunt test, it creates a build/karma/coverage/lcov.info that contains the following information:

TN:
SF:./app/scripts/app.js
FN:16,(anonymous_1)
FN:26,(anonymous_2)
FNF:2
...

After the SonarQube analysis, the dashboard shows a 0% code coverage.

I suspected that the path in the SF: was the source of the error. Thus, I've changed the sonar.javascript.lcov.reportPath property to use another lcov.info to test different values: app.js, ./app.js, app/scripts/app.js, ./app/scripts/app.js, but none worked, keeping the coverage to 0%.

What I am missing?

Just in case, I have the following configuration in my karma.conf.js:

coverageReporter: {
  reporters: [
    {
      type: 'lcov',
      dir: 'build/karma/coverage',
      subdir: '.'
    }
  ]
},

ps: Sonar version is 3.7.2, but I also tried on a 4.3, with the same results...


Edit: I've updated my configuration to use Sonar-runner directly, I'm using the latest version of Sonar (5.0.1) and JS plugin (2.3). I've also modified manually the lcov.info to have a "good" format (at least one format that matches the Sonar repo example):

SF:./app/scripts/app.js
DA:2,1
DA:20,1
DA:29,1
DA:34,1
end_of_record
SF:./app/scripts/services/exampleService.js
DA:1,1
DA:11,1
DA:12,0
end_of_record

The sonar-project.properties looks like:

sonar.projectKey=xxx
sonar.projectName=xxx
sonar.projectVersion=xxx
sonar.sourceEncoding=UTF-8

sonar.sources=app/scripts
sonar.tests=test
sonar.exclusions=app/3rd-party-libs/**,node_modules/**
sonar.dynamicAnalysis=reuseReports
sonar.language=js
sonar.projectBaseDir=.
sonar.javascript.coveragePlugin=lcov
sonar.javascript.lcov.reportPath=build/karma/coverage/lcov.info

And still, 0% of coverage :(

Romain Linsolas
  • 79,475
  • 49
  • 202
  • 273
  • Salut Romain ;-) I've a simple project with JS coverage that works here: https://github.com/bellingard/multi-language-test. (BTW, no need to use Maven for that, SonarQube Runner only is enough) If you look carefully, you'll see that the LCOV report on my project does not have exactly the same format, this is probably where the problem is. If you try to manually update your LCOV file to have the same format, can you confirm that it works? – Fabrice - SonarSource Team Feb 25 '15 at 13:39
  • Bonjour @Fabrice-SonarQubeTeam :) I've updated my question to add my new tests, with Sonar runner and a "manual" `lcov.info` file... – Romain Linsolas Feb 25 '15 at 14:14
  • I've made another simple test: I've cloned your repo (https://github.com/bellingard/multi-language-test) and run a simple analysis (`mvn clean install` and then `sonar-runner`). I get 46% of coverage on Java code, but 0 on JS code (in `src/main/javascript` or `src/main/javascript/com/company`). Am I missing something in my configuration? Some tools? – Romain Linsolas Feb 25 '15 at 14:19

1 Answers1

6

I was clueless, so I decided to modif the JavaScript plugin to add more logs. And I finally found the error, which is a vicious problem of... case sensitivity!

Let me explain. Let's consider the saveMeasureFromLCOVFile method of the CoverageSensor.java:

  protected void saveMeasureFromLCOVFile(SensorContext context) {
    String providedPath = settings.getString(JavaScriptPlugin.LCOV_REPORT_PATH);
    File lcovFile = getIOFile(fileSystem.baseDir(), providedPath);
    ...
    LOG.info("Analysing {}", lcovFile);

    LCOVParser parser = new LCOVParser(fileSystem.baseDir());
    Map<String, CoverageMeasuresBuilder> coveredFiles = parser.parseFile(lcovFile);

    for (InputFile inputFile : fileSystem.inputFiles(mainFilePredicate)) {
      try {
        CoverageMeasuresBuilder fileCoverage = coveredFiles.get(inputFile.file().getAbsolutePath());
        org.sonar.api.resources.File resource = org.sonar.api.resources.File.create(inputFile.relativePath());

        if (fileCoverage != null) {
          for (Measure measure : fileCoverage.createMeasures()) {
            context.saveMeasure(resource, measure);
          }
        } else {
          // colour all lines as not executed
          LOG.debug("Default value of zero will be saved for file: {}", resource.getPath());
          LOG.debug("Because: either was not present in LCOV report either was not able to retrieve associated SonarQube resource");
          saveZeroValueForResource(resource, context);
        }
      } catch (Exception e) {
        LOG.error("Problem while calculating coverage for " + inputFile.absolutePath(), e);
      }
   }
  }

First, it reads the lcov.info file given to know for which files we have coverage data (retrieved by parsing the file, done with LCOVParser class). After that, it takes the same file from the coveredFiles map to do the matching between metrics and code. If the file is not found (else part of the if (fileCoverage != null) {), then the code coverage is forced to 0.

That's what happened on my project.

So why is it happening? Simply because in my environment, inputFile is equals to d:\dev\my-application\app\scripts\app.js and in coveredFiles map, I have D:\dev\my-application\app\scripts\app.js. Note the difference of the case in the drive letter (d: against D:). As the map.get(...) is case sensitive, fileCoverage is null and then no coverage is calculated.

Now, I have to investigate on how I can force the path to have correct case...


After more investigation, I found a modification in the plugin code that works (at least for me, I didn't get into all the possible impacts). In LCOVParser, the filePath = CoverageSensor.getIOFile(moduleBaseDir, filePath).getCanonicalPath(); could be modified to filePath = CoverageSensor.getIOFile(moduleBaseDir, filePath).getAbsolutePath();, since the first one returns a path like D:\... while the second will return d:\....

In fact, I'm not even what is the preferred case to use on Windows. The following code:

public static void main(String[] args) throws IOException {
    System.out.println("PATH 1 : " + new File(".").getAbsolutePath());
    System.out.println("PATH 2 : " + new File(".").getCanonicalPath());
}

will return:

PATH 1 : D:\dev\preclosing\preclosing-eme\.
PATH 2 : D:\dev\preclosing\preclosing-eme

Anyway, I'm stuck for the moment, and I'm not even sure how to solve my issue without waiting for a JS plugin fix (since my "official" Sonar is a little bit old for the moment and only support JS plugin up to v2.1).

Romain Linsolas
  • 79,475
  • 49
  • 202
  • 273
  • 1
    Hi Romain, indeed comparing paths is really tricky. Here the use of getCanonicalPath() is wrong since it may transform the path (change case and/or resolve symlinks) in a way that prevent to match it later. I have pushed a fix in a branch so please test: https://github.com/SonarCommunity/sonar-javascript/tree/fix_canonical_path_issue – Julien H. - SonarSource Team Feb 26 '15 at 09:56
  • Hello @JulienHENRY. I confirm that the fix is working, I get the coverage. Now I have to understand how to change the `lcov.info` format, as many lines of this report are ignored by the plugin (the ones that start with `FNF`, `FNDA`, `BRF`, `BRH`, etc.) – Romain Linsolas Mar 02 '15 at 08:16
  • Glad to hear that the filesystem part is fixed by my change. Concerning syntax of lcov report this is not my domain so I will ask someone else to have a look. – Julien H. - SonarSource Team Mar 03 '15 at 08:22
  • Thanks @JulienHENRY. I've created a dedicated question for the LCOV format: http://stackoverflow.com/questions/28806209/how-to-change-the-format-of-the-lcov-report-executed-by-karma – Romain Linsolas Mar 03 '15 at 09:55