All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.plugins.python.coverage.CoberturaParser Maven / Gradle / Ivy

The newest version!
/*
 * SonarQube Python Plugin
 * Copyright (C) 2011-2024 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.plugins.python.coverage;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.xml.stream.XMLStreamException;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.codehaus.staxmate.in.SMInputCursor;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.coverage.NewCoverage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.plugins.python.EmptyReportException;
import org.sonar.plugins.python.parser.StaxParser;

public class CoberturaParser {

  private static final Logger LOG = LoggerFactory.getLogger(CoberturaParser.class);

  private final Set errors = new LinkedHashSet<>();
  private int unresolvedFilenameCount;

  public void parseReport(File xmlFile, SensorContext context, final Map coverageData) throws XMLStreamException {
    LOG.info("Parsing report '{}'", xmlFile);
    unresolvedFilenameCount = 0;

    StaxParser parser = new StaxParser(rootCursor -> {
      File defaultBaseDirectory = context.fileSystem().baseDir();
      List baseDirectories = Collections.singletonList(defaultBaseDirectory);
      try {
        rootCursor.advance();
      } catch (com.ctc.wstx.exc.WstxEOFException eofExc) {
        LOG.debug("Unexpected end of file is encountered", eofExc);
        throw new EmptyReportException();
      }
      SMInputCursor cursor = rootCursor.childElementCursor();
      while (cursor.getNext() != null) {
        if ("sources".equals(cursor.getLocalName())) {
          baseDirectories = extractBaseDirectories(cursor, defaultBaseDirectory);
        } else if ("packages".equals(cursor.getLocalName())) {
          collectFileMeasures(cursor.descendantElementCursor("class"), context, coverageData, baseDirectories);
        }
      }
    });
    parser.parse(xmlFile);
    if (unresolvedFilenameCount > 1) {
      String message = String.format("Cannot resolve %d file paths, ignoring coverage measures for those files", unresolvedFilenameCount);
      LOG.error(message);
      errors.add(message);
    }
  }

  private List extractBaseDirectories(SMInputCursor sources, File defaultBaseDirectory) throws XMLStreamException {
    List baseDirectories = new ArrayList<>();
    SMInputCursor source = sources.childElementCursor("source");
    while (source.getNext() != null) {
      String path = FilenameUtils.normalize(source.collectDescendantText());
      if (!StringUtils.isBlank(path)) {
        File baseDirectory = new File(path);
        if (baseDirectory.isDirectory()) {
          baseDirectories.add(baseDirectory);
        } else {
          String formattedMessage = String.format("Invalid directory path in 'source' element: %s", path);
          LOG.warn(formattedMessage);
          errors.add(formattedMessage);
        }
      }
    }
    if (baseDirectories.isEmpty()) {
      return Collections.singletonList(defaultBaseDirectory);
    }
    return baseDirectories;
  }

  private void collectFileMeasures(SMInputCursor classCursor, SensorContext context, Map coverageData, List baseDirectories)
    throws XMLStreamException {
    while (classCursor.getNext() != null) {
      String filename = FilenameUtils.normalize(classCursor.getAttrValue("filename"));
      InputFile inputFile = resolve(context, baseDirectories, filename);
      if (inputFile != null) {
        NewCoverage coverage = coverageData.computeIfAbsent(inputFile, f -> context.newCoverage().onFile(f));
        collectFileData(classCursor, coverage);
      } else {
        classCursor.advance();
      }
    }
  }

  @Nullable
  private InputFile resolve(SensorContext context, List baseDirectories, String filename) {
    String absolutePath;
    File file = new File(filename);
    if (file.isAbsolute()) {
      if (!file.exists()) {
        logUnresolvedFile("Cannot resolve the file path '%s' of the coverage report, the file does not exist in all 'source'.", filename);
      }
      absolutePath = file.getAbsolutePath();
    } else {
      List fileList = baseDirectories.stream()
        .map(base -> new File(base, filename))
        .filter(File::exists)
        .toList();
      if (fileList.isEmpty()) {
        logUnresolvedFile("Cannot resolve the file path '%s' of the coverage report, the file does not exist in all 'source'.", filename);
        return null;
      }
      if (fileList.size() > 1) {
        logUnresolvedFile("Cannot resolve the file path '%s' of the coverage report, ambiguity, the file exists in several 'source'.", filename);
        return null;
      }
      absolutePath = fileList.get(0).getAbsolutePath();
    }
    return context.fileSystem().inputFile(context.fileSystem().predicates().hasAbsolutePath(absolutePath));
  }

  private void logUnresolvedFile(String message, String filename) {
    unresolvedFilenameCount++;
    if (unresolvedFilenameCount == 1) {
      String formattedMessage = String.format(message, filename);
      LOG.error(formattedMessage);
      errors.add(formattedMessage);
    }
  }

  private static void collectFileData(SMInputCursor classCursor, NewCoverage coverage) throws XMLStreamException {
    SMInputCursor line = classCursor.childElementCursor("lines").advance().childElementCursor("line");
    while (line.getNext() != null) {
      int lineId = Integer.parseInt(line.getAttrValue("number"));
      coverage.lineHits(lineId, Integer.parseInt(line.getAttrValue("hits")));

      String isBranch = line.getAttrValue("branch");
      String text = line.getAttrValue("condition-coverage");
      if (StringUtils.equals(isBranch, "true") && StringUtils.isNotBlank(text)) {
        String[] conditions = StringUtils.split(StringUtils.substringBetween(text, "(", ")"), "/");
        coverage.conditions(lineId, Integer.parseInt(conditions[1]), Integer.parseInt(conditions[0]));
      }
    }
  }

  public Set errors() {
    return errors;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy