diff --git a/README.md b/README.md
index 0f8dc9ce..54084721 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ The Jenkins Coverage Plug-in collects reports of code coverage or mutation cover
- [JaCoCo](https://www.jacoco.org/jacoco): Code Coverage
- [Cobertura](https://cobertura.github.io/cobertura/): Code Coverage
- [OpenCover](https://github.com/OpenCover/opencover): Code Coverage
+- [VectorCAST](https://www.vector.com/int/en/products/products-a-z/software/vectorcast): Code Coverage including MC/DC, Function, Function Call coverages
- [PIT](https://pitest.org/): Mutation Coverage
- [JUnit](https://ant.apache.org/manual/Tasks/junitreport.html): Test Results
- [NUnit](https://nunit.org/): Test Results
@@ -107,6 +108,7 @@ The Coverage Plug-in supports the following report formats:
- [JaCoCo](https://www.jacoco.org/jacoco): Code Coverage
- [Cobertura](https://cobertura.github.io/cobertura/): Code Coverage
- [OpenCover](https://github.com/OpenCover/opencover): Code Coverage
+- [VectorCAST](https://www.vector.com/int/en/products/products-a-z/software/vectorcast) Code Coverage including MC/DC, Function, Function Call coverages
- [PIT](https://pitest.org/): Mutation Coverage
- [JUnit](https://ant.apache.org/manual/Tasks/junitreport.html): Test Results
- [NUnit](https://nunit.org/): Test Results
diff --git a/plugin/pom.xml b/plugin/pom.xml
index ffe68dfc..ccb587fd 100644
--- a/plugin/pom.xml
+++ b/plugin/pom.xml
@@ -25,7 +25,7 @@
jenkinsci/coverage-plugin
- 0.45.0
+ 0.46.0
1.17.2
diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java
index 3508ab14..1e2b1ac7 100644
--- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java
+++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageSeriesBuilder.java
@@ -20,6 +20,9 @@ public class CoverageSeriesBuilder extends SeriesBuilder {
static final String BRANCH_COVERAGE = "branch";
static final String MUTATION_COVERAGE = "mutation";
static final String TEST_STRENGTH = "test-strength";
+ static final String MCDC_PAIR_COVERAGE = "mcdc-pair";
+ static final String FUNCTION_CALL_COVERAGE = "function-call";
+ static final String METHOD_COVERAGE = "method";
@Override
protected Map computeSeries(final CoverageStatistics statistics) {
@@ -29,6 +32,10 @@ protected Map computeSeries(final CoverageStatistics statistics)
add(statistics, Metric.BRANCH, BRANCH_COVERAGE, series);
add(statistics, Metric.MUTATION, MUTATION_COVERAGE, series);
add(statistics, Metric.TEST_STRENGTH, TEST_STRENGTH, series);
+ add(statistics, Metric.MCDC_PAIR, MCDC_PAIR_COVERAGE, series);
+ add(statistics, Metric.FUNCTION_CALL, FUNCTION_CALL_COVERAGE, series);
+ add(statistics, Metric.METHOD, METHOD_COVERAGE, series);
+
return series;
}
diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageTrendChart.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageTrendChart.java
index 7e3da39f..ab9ab534 100644
--- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageTrendChart.java
+++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/charts/CoverageTrendChart.java
@@ -23,6 +23,26 @@
* @see JacksonFacade
*/
public class CoverageTrendChart {
+ /* Line Mode used to indicate whether is should be a filled line chart or line chart */
+ private static FilledMode lineMode;
+
+ /**
+ * Sets the line mode for the trend chart.
+ *
+ * @param dataSet
+ *
+ */
+ private void setLineMode(final LinesDataSet dataSet) {
+ // If the dataset contains MCDC or Function Call Coverage
+ if (dataSet.containsSeries(CoverageSeriesBuilder.MCDC_PAIR_COVERAGE)
+ || dataSet.containsSeries(CoverageSeriesBuilder.FUNCTION_CALL_COVERAGE)) {
+ lineMode = FilledMode.LINES;
+ }
+ else {
+ lineMode = FilledMode.FILLED;
+ }
+ }
+
/**
* Creates the chart for the specified results.
*
@@ -38,11 +58,13 @@ public LinesChartModel create(final Iterable> re
final ChartModelConfiguration configuration) {
CoverageSeriesBuilder builder = new CoverageSeriesBuilder();
LinesDataSet dataSet = builder.createDataSet(configuration, results);
+
+ setLineMode(dataSet);
LinesChartModel model = new LinesChartModel(dataSet);
if (dataSet.isNotEmpty()) {
LineSeries lineSeries = new LineSeries(Messages.Metric_LINE(),
- JenkinsPalette.GREEN.normal(), StackedMode.SEPARATE_LINES, FilledMode.FILLED,
+ JenkinsPalette.GREEN.normal(), StackedMode.SEPARATE_LINES, lineMode,
dataSet.getSeries(CoverageSeriesBuilder.LINE_COVERAGE));
model.addSeries(lineSeries);
model.useContinuousRangeAxis();
@@ -55,6 +77,13 @@ public LinesChartModel create(final Iterable> re
JenkinsPalette.GREEN.dark());
addSeries(dataSet, model, Messages.Metric_TEST_STRENGTH(), CoverageSeriesBuilder.TEST_STRENGTH,
JenkinsPalette.GREEN.light());
+
+ addSeries(dataSet, model, Messages.Metric_MCDC_PAIR(), CoverageSeriesBuilder.MCDC_PAIR_COVERAGE,
+ JenkinsPalette.RED.light());
+ addSeries(dataSet, model, Messages.Metric_METHOD(), CoverageSeriesBuilder.METHOD_COVERAGE,
+ JenkinsPalette.RED.normal());
+ addSeries(dataSet, model, Messages.Metric_FUNCTION_CALL(), CoverageSeriesBuilder.FUNCTION_CALL_COVERAGE,
+ JenkinsPalette.RED.dark());
}
return model;
}
@@ -63,7 +92,7 @@ private static void addSeries(final LinesDataSet dataSet, final LinesChartModel
final String name, final String seriesId, final String color) {
if (dataSet.containsSeries(seriesId)) {
LineSeries branchSeries = new LineSeries(name,
- color, StackedMode.SEPARATE_LINES, FilledMode.FILLED,
+ color, StackedMode.SEPARATE_LINES, lineMode,
dataSet.getSeries(seriesId));
model.addSeries(branchSeries);
diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/model/ElementFormatter.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/model/ElementFormatter.java
index 2ef45235..dceb0923 100644
--- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/model/ElementFormatter.java
+++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/model/ElementFormatter.java
@@ -399,6 +399,10 @@ public String getDisplayName(final Metric metric) {
return Messages.Metric_LOC();
case TESTS:
return Messages.Metric_TESTS();
+ case MCDC_PAIR:
+ return Messages.Metric_MCDC_PAIR();
+ case FUNCTION_CALL:
+ return Messages.Metric_FUNCTION_CALL();
default:
throw new NoSuchElementException("No display name found for metric " + metric);
}
@@ -472,6 +476,10 @@ public String getLabel(final Metric metric) {
return Messages.Metric_Short_LOC();
case TESTS:
return Messages.Metric_Short_TESTS();
+ case MCDC_PAIR:
+ return Messages.Metric_Short_MCDC_PAIR();
+ case FUNCTION_CALL:
+ return Messages.Metric_Short_FUNCTION_CALL();
default:
throw new NoSuchElementException("No label found for metric " + metric);
}
@@ -527,6 +535,8 @@ public ListBoxModel getMetricItems() {
add(options, Metric.COMPLEXITY_MAXIMUM);
add(options, Metric.LOC);
add(options, Metric.TESTS);
+ add(options, Metric.MCDC_PAIR);
+ add(options, Metric.FUNCTION_CALL);
return options;
}
diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/source/CoverageSourcePrinter.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/source/CoverageSourcePrinter.java
index b7362ce4..15609339 100644
--- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/source/CoverageSourcePrinter.java
+++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/source/CoverageSourcePrinter.java
@@ -17,13 +17,13 @@
*/
class CoverageSourcePrinter implements Serializable {
private static final long serialVersionUID = -6044649044983631852L;
- private static final Sanitizer SANITIZER = new Sanitizer();
+ protected static final Sanitizer SANITIZER = new Sanitizer();
static final String UNDEFINED = "noCover";
static final String NO_COVERAGE = "coverNone";
static final String FULL_COVERAGE = "coverFull";
static final String PARTIAL_COVERAGE = "coverPart";
- private static final String NBSP = " ";
+ protected static final String NBSP = " ";
private final String path;
private final int[] linesToPaint;
@@ -54,7 +54,7 @@ public String renderLine(final int line, final String sourceCode) {
.render();
}
- private String cleanupCode(final String content) {
+ protected String cleanupCode(final String content) {
return content.replace("\n", StringUtils.EMPTY)
.replace("\r", StringUtils.EMPTY)
.replace(" ", NBSP)
@@ -133,4 +133,8 @@ int getCounter(final int line, final int... counters) {
}
return 0;
}
-}
+
+ public String getColumnHeader() {
+ return StringUtils.EMPTY;
+ }
+}
\ No newline at end of file
diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/source/SourceCodePainter.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/source/SourceCodePainter.java
index 6ea8a556..c73bbfc3 100644
--- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/source/SourceCodePainter.java
+++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/source/SourceCodePainter.java
@@ -91,6 +91,10 @@ public void processSourceCodePainting(final Node rootNode, final List
private CoverageSourcePrinter createFileModel(final Node rootNode, final FileNode fileNode) {
if (rootNode.getValue(Metric.MUTATION).isPresent()) {
return new MutationSourcePrinter(fileNode);
+ }
+ else if (rootNode.getValue(Metric.MCDC_PAIR).isPresent()
+ || rootNode.getValue(Metric.FUNCTION_CALL).isPresent()) {
+ return new VectorCastSourcePrinter(fileNode);
}
else {
return new CoverageSourcePrinter(fileNode);
@@ -206,6 +210,9 @@ private int paint(final CoverageSourcePrinter paint, final String relativePathId
Path fullSourcePath = paintedFilesFolder.resolve(sanitizedFileName);
try (BufferedWriter output = Files.newBufferedWriter(fullSourcePath)) {
List lines = Files.readAllLines(Paths.get(resolvedPath.getRemote()), charset);
+
+ // added a header to display what is being shown in each column
+ output.write(paint.getColumnHeader());
for (int line = 0; line < lines.size(); line++) {
output.write(paint.renderLine(line + 1, lines.get(line)));
}
diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/source/VectorCastSourcePrinter.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/source/VectorCastSourcePrinter.java
new file mode 100644
index 00000000..08d49dbd
--- /dev/null
+++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/source/VectorCastSourcePrinter.java
@@ -0,0 +1,470 @@
+package io.jenkins.plugins.coverage.metrics.source;
+
+import org.apache.commons.lang3.StringUtils;
+
+import edu.hm.hafner.coverage.FileNode;
+
+import static j2html.TagCreator.*;
+
+/**
+ * Provides all required information for a {@link FileNode} so that its source code can be rendered together with the
+ * line and mutation coverage in HTML.
+ */
+@SuppressWarnings("PMD.GodClass")
+public class VectorCastSourcePrinter extends CoverageSourcePrinter {
+ private static final long serialVersionUID = 7204367145168517936L;
+
+ private final int[] mcdcPairCoveredPerLine;
+ private final int[] mcdcPairMissedPerLine;
+
+ private final int[] functionCallCoveredPerLine;
+ private final int[] functionCallMissedPerLine;
+
+ VectorCastSourcePrinter(final FileNode file) {
+ super(file);
+ mcdcPairCoveredPerLine = file.getMcdcPairCoveredCounters();
+ mcdcPairMissedPerLine = file.getMcdcPairMissedCounters();
+
+ functionCallCoveredPerLine = file.getFunctionCallCoveredCounters();
+ functionCallMissedPerLine = file.getFunctionCallMissedCounters();
+ }
+
+ /**
+ * Gets the tr HTML tag for this source line. Used for case where both MCDC and FCC are present.
+ *
+ * @param line
+ * line number for the summary data
+ *
+ * @param sourceCode
+ * line of source code
+ *
+ * @param isPainted
+ * indicator of if the line should be painted
+ *
+ * @param third
+ * third column string.
+ *
+ * @param fouth
+ * fouth column string.
+ *
+ * @return string for the html row
+ *
+ */
+ private String getTr(final int line, final String sourceCode, final boolean isPainted, final String third, final String fouth) {
+ var trData = tr()
+ .withClass(isPainted ? getColorClass(line) : UNDEFINED)
+ .condAttr(isPainted, "data-html-tooltip", isPainted ? getTooltip(line) : StringUtils.EMPTY);
+
+ trData.with(
+ td().withClass("line").with(a().withName(String.valueOf(line)).withText(String.valueOf(line))),
+ td().withClass("hits").with(isPainted ? text(getSummaryColumn(line)) : text(StringUtils.EMPTY))
+ );
+
+ if (!third.equals(StringUtils.EMPTY)) {
+ trData.with(td().withClass("hits").with(isPainted ? text(third) : text(StringUtils.EMPTY)));
+ }
+ if (!fouth.equals(StringUtils.EMPTY)) {
+ trData.with(td().withClass("hits").with(isPainted ? text(fouth) : text(StringUtils.EMPTY)));
+ }
+
+ trData.with(td().withClass("code").with(rawHtml(SANITIZER.render(cleanupCode(sourceCode)))));
+
+ return trData.render();
+ }
+
+ /**
+ * Gets the tr HTML tag for this source line. Used for case where neither MCDC or FCC are present.
+ *
+ * @param line
+ * line number for the summary data
+ *
+ * @param sourceCode
+ * line of source code
+ *
+ * @param isPainted
+ * indicator of if the line should be painted
+ *
+ * @return string for the html row
+ *
+ */
+ private String getTr(final int line, final String sourceCode, final boolean isPainted) {
+ return getTr(line, sourceCode, isPainted, StringUtils.EMPTY, StringUtils.EMPTY);
+ }
+
+ /**
+ * Gets the tr HTML tag for this source line. Used for MCDC or FCC but not both.
+ *
+ * @param line
+ * line number for the summary data
+ *
+ * @param sourceCode
+ * line of source code
+ *
+ * @param isPainted
+ * indicator of if the line should be painted
+ *
+ * @param third
+ * third column string.
+ *
+ * @return string for the html row
+ *
+ */
+ private String getTr(final int line, final String sourceCode, final boolean isPainted, final String third) {
+ return getTr(line, sourceCode, isPainted, third, StringUtils.EMPTY);
+ }
+
+ /**
+ * Main call to render the source line in HTML table format.
+ *
+ * @param line
+ * line number for the summary data
+ *
+ * @param sourceCode
+ * line of source code
+ *
+ * @return string of the source code line in HTML format
+ *
+ */
+ @Override
+ public String renderLine(final int line, final String sourceCode) {
+ var isPainted = isPainted(line);
+ var hasMcdc = hasAnyMcdcPairCoverage();
+ var hasFc = hasAnyFunctionCallCoverage();
+
+ String trString;
+
+ // If this file only has Line, St/Br, and FunctionCall
+ if (!hasMcdc && hasFc) {
+ trString = getTr(line, sourceCode, isPainted, getFunctionCallSummaryColumn(line));
+ }
+ // If this file only has Line, St/Br, and MCDC
+ else if (hasMcdc && !hasFc) {
+ trString = getTr(line, sourceCode, isPainted, getMcdcPairSummaryColumn(line));
+ }
+ // If this file only has Line, St/Br, FunctionCall and MCDC
+ else if (hasMcdc && hasFc) {
+ trString = getTr(line, sourceCode, isPainted, getFunctionCallSummaryColumn(line), getMcdcPairSummaryColumn(line));
+ }
+ // If this file only has Line and St/Br
+ else {
+ trString = getTr(line, sourceCode, isPainted);
+ }
+ return trString;
+ }
+
+ /**
+ * Gets the column header given MCDC or FCC but not both.
+ *
+ * @param third
+ * third column string.
+ *
+ * @return string for the column header
+ *
+ */
+ private String getColumnHeader(final String third) {
+ return getColumnHeader(third, NBSP);
+ }
+
+ /**
+ * Gets the column header.
+ *
+ * @param third
+ * third column string.
+ *
+ * @param fourth
+ * fourth column string.
+ *
+ * @return string for the column header
+ *
+ */
+ private String getColumnHeader(final String third, final String fourth) {
+ return tr().withClass(UNDEFINED).with(
+ td().withClass("line").with(text("Line")),
+ td().withClass("line").with(text("St/Br")),
+ td().withClass("line").with(text(third)),
+ td().withClass("line").with(text(fourth)),
+ td().withClass("line").with(text(NBSP))
+ ).render();
+ }
+
+ /**
+ * Gets the source code column header.
+ *
+ * @return string of the column header
+ *
+ */
+ @Override
+ public String getColumnHeader() {
+ var hasMcdc = hasAnyMcdcPairCoverage();
+ var hasFc = hasAnyFunctionCallCoverage();
+ String trString;
+
+ // If this file only has Line, St/Br, and FunctionCall
+ if (!hasMcdc && hasFc) {
+ trString = getColumnHeader("FCall");
+ }
+ // If this file only has Line, St/Br, and MCDC
+ else if (hasMcdc && !hasFc) {
+ trString = getColumnHeader("MC/DC");
+ }
+ // If this file only has Line, St/Br, FunctionCall and MCDC
+ else if (hasMcdc && hasFc) {
+ trString = getColumnHeader("FCall", "MC/DC");
+ }
+ // If this file only has Line and St/Br
+ else {
+ // this is the original metrics so maybe don't print header?
+ trString = tr().withClass(UNDEFINED).with(
+ td().withClass("line").with(text("Line ")),
+ td().withClass("line").with(text("St/Br")),
+ td().withClass("line").with(text(NBSP))
+ ).render();
+ }
+
+ return trString;
+ }
+
+ /**
+ * Gets the color class depending on coverage types.
+ *
+ * @param line
+ * line number for the summary data
+ *
+ * @return string of the color
+ *
+ */
+ @Override
+ public String getColorClass(final int line) {
+ if (getCovered(line) == 0 && getMcdcPairCovered(line) == 0 && getFunctionCallCovered(line) == 0) {
+ return NO_COVERAGE;
+ }
+ else if (getMissed(line) == 0 && getMcdcPairMissed(line) == 0 && getFunctionCallMissed(line) == 0) {
+ return FULL_COVERAGE;
+ }
+ else {
+ return PARTIAL_COVERAGE;
+ }
+ }
+
+ /**
+ * Constructs the tooltip depending on the coverage.
+ *
+ * @param line
+ * line number for the summary data
+ *
+ * @return the MCDC Pair tooltip
+ *
+ */
+ public String getMcdcPairTooltip(final int line) {
+ var mcdcPairCovered = getMcdcPairCovered(line);
+ var mcdcPairMissed = getMcdcPairMissed(line);
+
+ return getTooltip(mcdcPairCovered, mcdcPairMissed, false, "MC/DC pairs");
+ }
+
+ /**
+ * Constructs the tooltip depending on the coverage.
+ *
+ * @param line
+ * line number for the summary data
+ *
+ * @return the function call tooltip
+ *
+ */
+
+ public String getfunctionCallTooltip(final int line) {
+ var functionCallCovered = getFunctionCallCovered(line);
+ var functionCallMissed = getFunctionCallMissed(line);
+
+ return getTooltip(functionCallCovered, functionCallMissed, true, "Function calls");
+ }
+
+ /**
+ * Constructs the tooltip depending on the coverage.
+ *
+ * @param covered
+ * count of the covered coverage
+ *
+ * @param missed
+ * count of the missed coverage
+ *
+ * @param checkAny
+ * boolean to indicate if code should check for any covered bits
+ *
+ * @param description
+ * Description of the coverage - MCDC/FCC
+ *
+ * @return
+ * string of the tooltip
+ */
+ public String getTooltip(final int covered, final int missed, final boolean checkAny, final String description) {
+ String tooltip = "";
+
+ if (covered + missed > 1) {
+ if (missed == 0) {
+ tooltip = String.format("All %s covered: %d/%d", description, covered, covered + missed);
+ }
+ else {
+ tooltip = String.format("%s partially covered: %d/%d", description, covered, covered + missed);
+ }
+ }
+ else if (checkAny && covered == 1) {
+ tooltip = String.format("%s covered", description);
+ }
+
+ return tooltip;
+ }
+
+ /**
+ * Constructs the tooltip depending on the coverage.
+ *
+ * @param line
+ * line number for the summary data
+ *
+ * @return
+ * string of the tooltip
+ */
+ @Override
+ public String getTooltip(final int line) {
+ String toolTipString = "";
+
+ String lineBranchToolTipString = super.getTooltip(line);
+ String mcdcPairToolTipString = getMcdcPairTooltip(line);
+ String functionCallToolTipString = getfunctionCallTooltip(line);
+
+ if (lineBranchToolTipString.length() > 0) {
+ toolTipString += lineBranchToolTipString;
+ }
+ if (mcdcPairToolTipString.length() > 0) {
+ if (toolTipString.length() > 0) {
+ toolTipString += " | ";
+ }
+ toolTipString += mcdcPairToolTipString;
+ }
+ if (functionCallToolTipString.length() > 0) {
+ if (toolTipString.length() > 0) {
+ toolTipString += " | ";
+ }
+ toolTipString += functionCallToolTipString;
+ }
+
+ return toolTipString;
+ }
+
+ /**
+ * Returns true if there is any MCDC Pair coverage.
+ *
+ * @return
+ * true if there is any MCDC Pair coverage
+ */
+ public boolean hasAnyMcdcPairCoverage() {
+ boolean hasMcDc = false;
+ for (int i = 0; i < mcdcPairCoveredPerLine.length && !hasMcDc; i++) {
+ if ((mcdcPairCoveredPerLine[i] + mcdcPairMissedPerLine[i]) > 0) {
+ hasMcDc = true;
+ }
+ }
+
+ return hasMcDc;
+ }
+
+ /**
+ * Returns true if there is any Function Call coverage.
+ *
+ * @return
+ * true if there is any Function Call coverage
+ */
+
+ public boolean hasAnyFunctionCallCoverage() {
+ boolean hasFc = false;
+ for (int i = 0; i < functionCallMissedPerLine.length && !hasFc; i++) {
+ if ((functionCallCoveredPerLine[i] + functionCallMissedPerLine[i]) > 0) {
+ hasFc = true;
+ }
+ }
+
+ return hasFc;
+ }
+
+ /**
+ * Returns the count of covered MCDC Pairs on a line.
+ *
+ * @param line
+ * line number for the summary data
+ * @return
+ * the number of covered MCDC Pairs on a line
+ */
+ public int getMcdcPairCovered(final int line) {
+ return getCounter(line, mcdcPairCoveredPerLine);
+ }
+
+ /**
+ * Returns the count of missed MCDC Pairs on a line.
+ *
+ * @param line
+ * line number for the summary data
+ * @return
+ * the number of missed MCDC Pairs on a line
+ */
+ public int getMcdcPairMissed(final int line) {
+ return getCounter(line, mcdcPairMissedPerLine);
+ }
+
+ /**
+ * Returns the count of covered Function Calls on a line.
+ *
+ * @param line
+ * line number for the summary data
+ * @return
+ * the number of covered Function Calls on a line
+ */
+ public int getFunctionCallCovered(final int line) {
+ return getCounter(line, functionCallCoveredPerLine);
+ }
+
+ /**
+ * Returns the count of missed Function Calls on a line.
+ *
+ * @param line
+ * line number for the summary data
+ * @return
+ * the number of missed Function Calls on a line
+ */
+ public int getFunctionCallMissed(final int line) {
+ return getCounter(line, functionCallMissedPerLine);
+ }
+
+ /**
+ * Creates the MCDC Pairs summary column.
+ *
+ * @param line
+ * line number for the summary data
+ * @return
+ * string of the MCDC Pairs summary column
+ */
+ public String getMcdcPairSummaryColumn(final int line) {
+ var covered = getMcdcPairCovered(line);
+ var missed = getMcdcPairMissed(line);
+ if (covered + missed > 1) {
+ return String.format("%d/%d", covered, covered + missed);
+ }
+ return String.valueOf(covered);
+ }
+
+ /**
+ * Creates the function call summary column.
+ *
+ * @param line
+ * line number for the summary data
+ * @return
+ * string of the function call summary column
+ */
+ public String getFunctionCallSummaryColumn(final int line) {
+ var covered = getFunctionCallCovered(line);
+ var missed = getFunctionCallMissed(line);
+ if (covered + missed > 1) {
+ return String.format("%d/%d", covered, covered + missed);
+ }
+ return String.valueOf(covered);
+ }
+}
diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java
index 1f6cfe61..85075862 100644
--- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java
+++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java
@@ -571,7 +571,8 @@ public double getTrend(final Baseline baseline, final Metric metric) {
NavigableSet getMetricsForSummary() {
return new TreeSet<>(
Set.of(Metric.LINE, Metric.LOC, Metric.BRANCH, Metric.COMPLEXITY_DENSITY,
- Metric.MUTATION, Metric.TEST_STRENGTH, Metric.TESTS));
+ Metric.MUTATION, Metric.TEST_STRENGTH, Metric.TESTS,
+ Metric.MCDC_PAIR, Metric.FUNCTION_CALL));
}
/**
diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageChecksPublisher.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageChecksPublisher.java
index d0b3f84d..1728cb0d 100644
--- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageChecksPublisher.java
+++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageChecksPublisher.java
@@ -76,6 +76,14 @@ class CoverageChecksPublisher {
this.annotationScope = annotationScope;
}
+ private ChecksFormatter getFormatter() {
+ if (rootNode.getValue(Metric.FUNCTION_CALL).isPresent()
+ || rootNode.getValue(Metric.MCDC_PAIR).isPresent()) {
+ return new VectorCastFormatter();
+ }
+ return new ChecksFormatter();
+ }
+
/**
* Publishes the coverage report as Checks to SCM platforms.
*
@@ -83,8 +91,7 @@ class CoverageChecksPublisher {
* The task listener
*/
void publishCoverageReport(final TaskListener listener) {
- var publisher = ChecksPublisherFactory.fromRun(action.getOwner(), listener);
- publisher.publish(extractChecksDetails());
+ ChecksPublisherFactory.fromRun(action.getOwner(), listener).publish(extractChecksDetails());
}
@VisibleForTesting
@@ -106,7 +113,7 @@ ChecksDetails extractChecksDetails() {
}
private String getChecksTitle() {
- return getMetricsForTitle().stream()
+ return getFormatter().getTitleMetrics().stream()
.map(this::format)
.flatMap(Optional::stream)
.collect(Collectors.joining(", "));
@@ -137,11 +144,6 @@ private String getDeltaDetails(final Baseline baseline, final Metric metric) {
return StringUtils.EMPTY;
}
- private NavigableSet getMetricsForTitle() {
- return new TreeSet<>(
- Set.of(Metric.LINE, Metric.BRANCH, Metric.MUTATION));
- }
-
private String getSummary() {
return getAnnotationSummary()
+ getOverallCoverageSummary()
@@ -369,7 +371,7 @@ private String createDeltaBaselinesOverview() {
description.append(getBulletListItem(1,
formatText(TextFormat.BOLD,
getUrlText(action.getTitle(baseline), getBaseUrl() + baseline.getUrl()))));
- for (Value value : action.getValues(baseline)) {
+ for (Value value : getValues(baseline)) {
String display = FORMATTER.formatDetailedValueWithMetric(value);
if (action.hasDelta(baseline, value.getMetric())) {
display += String.format(" - Delta: %s", action.formatDelta(baseline, value.getMetric()));
@@ -382,11 +384,17 @@ private String createDeltaBaselinesOverview() {
return description.toString();
}
+ private List getValues(final Baseline baseline) {
+ return action.getAllValues(baseline).stream()
+ .filter(value -> getFormatter().getOverviewMetrics().contains(value.getMetric()))
+ .collect(Collectors.toList());
+ }
+
private String createProjectOverview() {
StringBuilder description = new StringBuilder(getSectionHeader(TITLE_HEADER_LEVEL, "Project Overview"));
description.append("No changes detected, that affect the code coverage.\n");
- for (Value value : action.getValues(Baseline.PROJECT)) {
+ for (Value value : getValues(Baseline.PROJECT)) {
description.append(getBulletListItem(1, FORMATTER.formatDetailedValueWithMetric(value)));
}
@@ -421,6 +429,7 @@ private String getProjectMetricsSummary() {
var builder = new StringBuilder(getSectionHeader(TITLE_HEADER_LEVEL, "Project coverage details"));
builder.append(COLUMN);
builder.append(COLUMN);
+
builder.append(getMetricStream()
.map(FORMATTER::getDisplayName)
.collect(asColumn()));
@@ -458,7 +467,9 @@ private String getFormatDelta(final Baseline baseline, final Metric metric) {
}
private Stream getMetricStream() {
- return Metric.getCoverageMetrics().stream().skip(1);
+ return Metric.getCoverageMetrics().stream()
+ .skip(1)
+ .filter(m -> rootNode.getValue(m).isPresent());
}
private Collector asColumn() {
@@ -530,4 +541,31 @@ private enum TextFormat {
BOLD,
CURSIVE
}
+
+ /**
+ * Determines the metrics that should be shown in the title, overview, and tables.
+ * Metrics without a valid value in the coverage tree will be skipped.
+ */
+ private static class ChecksFormatter {
+ NavigableSet getTitleMetrics() {
+ return new TreeSet<>(
+ Set.of(Metric.LINE, Metric.BRANCH, Metric.MUTATION));
+ }
+
+ NavigableSet getOverviewMetrics() {
+ return new TreeSet<>(
+ Set.of(Metric.LINE, Metric.LOC, Metric.BRANCH, Metric.COMPLEXITY_DENSITY,
+ Metric.MUTATION, Metric.TEST_STRENGTH, Metric.TESTS,
+ Metric.MCDC_PAIR, Metric.FUNCTION_CALL));
+ }
+ }
+
+ private static class VectorCastFormatter extends ChecksFormatter {
+ @Override
+ NavigableSet getOverviewMetrics() {
+ var valueMetrics = super.getOverviewMetrics();
+ valueMetrics.add(Metric.METHOD);
+ return valueMetrics;
+ }
+ }
}
diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageTableModel.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageTableModel.java
index 2f4f4fe4..cb9f5e05 100644
--- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageTableModel.java
+++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageTableModel.java
@@ -35,6 +35,7 @@
import io.jenkins.plugins.datatables.TableConfiguration;
import io.jenkins.plugins.datatables.TableConfiguration.SelectStyle;
import io.jenkins.plugins.datatables.TableModel;
+import org.apache.commons.lang3.StringUtils;
import static j2html.TagCreator.*;
@@ -118,6 +119,13 @@ public List getColumns() {
Messages.Column_DeltaLineCoverage("Δ"), columns);
configureValueColumn("branchCoverage", Metric.BRANCH, Messages.Column_BranchCoverage(),
Messages.Column_DeltaBranchCoverage("Δ"), columns);
+
+ /* VectorCAST metrics */
+ configureValueColumn("mcdcPairCoverage", Metric.MCDC_PAIR, Messages.Column_MCDCPairs(),
+ "", columns);
+ configureValueColumn("functionCallCoverage", Metric.FUNCTION_CALL, Messages.Column_FunctionCall(),
+ "", columns);
+
configureValueColumn("mutationCoverage", Metric.MUTATION, Messages.Column_MutationCoverage(),
Messages.Column_DeltaMutationCoverage("Δ"), columns);
configureValueColumn("testStrength", Metric.TEST_STRENGTH, Messages.Column_TestStrength(),
@@ -174,13 +182,15 @@ private void configureValueColumn(final String key, final Metric metric, final S
.withResponsivePriority(1)
.build();
columns.add(lineCoverage);
- TableColumn lineCoverageDelta = new ColumnBuilder().withHeaderLabel(deltaHeaderLabel)
- .withDataPropertyKey(key + "Delta")
- .withDetailedCell()
- .withType(ColumnType.NUMBER)
- .withResponsivePriority(2)
- .build();
- columns.add(lineCoverageDelta);
+ if (StringUtils.isNotEmpty(deltaHeaderLabel)) {
+ TableColumn lineCoverageDelta = new ColumnBuilder().withHeaderLabel(deltaHeaderLabel)
+ .withDataPropertyKey(key + "Delta")
+ .withDetailedCell()
+ .withType(ColumnType.NUMBER)
+ .withResponsivePriority(2)
+ .build();
+ columns.add(lineCoverageDelta);
+ }
}
}
@@ -251,6 +261,18 @@ public DetailedCell> getBranchCoverage() {
return createColoredCoverageColumn(getCoverageOfNode(Metric.BRANCH));
}
+ public DetailedCell> getMethodCoverage() {
+ return createColoredCoverageColumn(getCoverageOfNode(Metric.METHOD));
+ }
+
+ public DetailedCell> getMcdcPairCoverage() {
+ return createColoredCoverageColumn(getCoverageOfNode(Metric.MCDC_PAIR));
+ }
+
+ public DetailedCell> getFunctionCallCoverage() {
+ return createColoredCoverageColumn(getCoverageOfNode(Metric.FUNCTION_CALL));
+ }
+
public DetailedCell> getMutationCoverage() {
return createColoredCoverageColumn(getCoverageOfNode(Metric.MUTATION));
}
diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageTool.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageTool.java
index 90db7346..49fe9019 100644
--- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageTool.java
+++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageTool.java
@@ -214,6 +214,9 @@ public enum Parser {
COBERTURA(Messages._Parser_Cobertura(), ParserType.COVERAGE,
"**/cobertura.xml",
"symbol-footsteps-outline plugin-ionicons-api"),
+ VECTORCAST(Messages._Parser_VectorCAST(), ParserType.COVERAGE,
+ "xml_data/cobertura/coverage_results*.xml",
+ "symbol-footsteps-outline plugin-ionicons-api"),
JACOCO(Messages._Parser_JaCoCo(), ParserType.COVERAGE,
"**/jacoco.xml",
"symbol-footsteps-outline plugin-ionicons-api"),
diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel.java
index d7e7eb3a..95f26f69 100644
--- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel.java
+++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageViewModel.java
@@ -78,7 +78,8 @@ public class CoverageViewModel extends DefaultAsyncTableContentProvider implemen
private static final ElementFormatter FORMATTER = new ElementFormatter();
private static final Set TREE_METRICS = Set.of(
- Metric.LINE, Metric.BRANCH, Metric.MUTATION, Metric.TEST_STRENGTH, Metric.COMPLEXITY, Metric.TESTS);
+ Metric.LINE, Metric.BRANCH, Metric.MUTATION, Metric.TEST_STRENGTH, Metric.COMPLEXITY, Metric.TESTS,
+ Metric.MCDC_PAIR, Metric.FUNCTION_CALL);
private static final String UNDEFINED = "-";
private final Run, ?> owner;
private final String displayName;
@@ -283,6 +284,15 @@ private Metric getCoverageMetricFromText(final String text) {
if (text.contains("complexity")) {
return Metric.COMPLEXITY;
}
+ if (text.contains("mcdc-pair")) {
+ return Metric.MCDC_PAIR;
+ }
+ if (text.contains("function-call")) {
+ return Metric.FUNCTION_CALL;
+ }
+ if (text.contains("method")) {
+ return Metric.METHOD;
+ }
throw new IllegalArgumentException("Unknown coverage metric: " + text);
}
diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/model/Messages.properties b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/model/Messages.properties
index 2c854201..3d6d9c2d 100644
--- a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/model/Messages.properties
+++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/model/Messages.properties
@@ -14,6 +14,8 @@ Metric.COMPLEXITY_DENSITY=Complexity Density
Metric.COMPLEXITY_MAXIMUM=Maximum Cyclomatic Complexity
Metric.LOC=Lines of Code
Metric.TESTS=Test Cases
+Metric.MCDC_PAIR=MC/DC Pair Coverage
+Metric.FUNCTION_CALL=Function Call Coverage
Metric.Short.CONTAINER=Container
Metric.Short.MODULE=Module
@@ -31,6 +33,8 @@ Metric.Short.COMPLEXITY_DENSITY=Complexity Density
Metric.Short.COMPLEXITY_MAXIMUM=Max. Complexity
Metric.Short.LOC=LOC
Metric.Short.TESTS=Tests
+Metric.Short.MCDC_PAIR=MC/DC Pair
+Metric.Short.FUNCTION_CALL=Function Call
Metric.MUTATION.Killed=Killed
Metric.MUTATION.Survived=Survived
diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageMetricColumn/help-metric.html b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageMetricColumn/help-metric.html
index 1179cd75..d2230d7d 100644
--- a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageMetricColumn/help-metric.html
+++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageMetricColumn/help-metric.html
@@ -22,6 +22,10 @@
Branch coverage or decision coverage (given as percentage)
MUTATION
Mutation coverage (given as percentage)
+ MCDC_PAIR
+ MC/DC Pair coverage (given as percentage)
+ FUNCTION_CALL
+ Function call coverage (given as percentage)
TEST_STRENGTH
Test Strength (given as percentage)
COMPLEXITY
diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGate/help-metric.html b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGate/help-metric.html
index 29ba28b1..e268c815 100644
--- a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGate/help-metric.html
+++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGate/help-metric.html
@@ -22,6 +22,10 @@
Branch coverage or decision coverage (given as percentage)
MUTATION
Mutation coverage (given as percentage)
+ MCDC_PAIR
+ MC/DC Pair coverage (given as percentage)
+ FUNCTION_CALL
+ Function call coverage(given as percentage)
TEST_STRENGTH
Test Strength (given as percentage)
COMPLEXITY
diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/Messages.properties b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/Messages.properties
index 3d4c32aa..1ceffbbd 100644
--- a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/Messages.properties
+++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/Messages.properties
@@ -7,6 +7,7 @@ Parser.PIT=PIT Mutation Testing Reports
Parser.Junit=JUnit Test Results
Parser.Nunit=NUnit Test Results
Parser.Xunit=XUnit Test Results
+Parser.VectorCAST=VectorCAST Coverage Results
Coverage.Not.Available=n/a
Coverage.Link.Name=Coverage Report
@@ -35,6 +36,9 @@ Column.Tests=Tests
Column.Complexity=Complexity
Column.MaxComplexity=Max. Complexity
Column.ComplexityDensity=Complexity / LOC
+Column.MCDCPairs=MC/DC Pairs
+Column.DeltaMCDCPairsCoverage=MCDCPairs {0}
+Column.FunctionCall=Function Call
MessagesViewModel.Title=Code Coverage
diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/AbstractCoverageTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/AbstractCoverageTest.java
index 6f135811..a27d691c 100644
--- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/AbstractCoverageTest.java
+++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/AbstractCoverageTest.java
@@ -17,6 +17,7 @@
import edu.hm.hafner.coverage.Node;
import edu.hm.hafner.coverage.Value;
import edu.hm.hafner.coverage.parser.JacocoParser;
+import edu.hm.hafner.coverage.parser.VectorCastParser;
import edu.hm.hafner.util.FilteredLog;
import edu.hm.hafner.util.ResourceTest;
import edu.hm.hafner.util.SecureXmlParserFactory.ParsingException;
@@ -56,6 +57,10 @@ protected Node readJacocoResult(final String fileName) {
return readResult(fileName, new JacocoParser());
}
+ protected Node readVectorCastResult(final String fileName) {
+ return readResult(fileName, new VectorCastParser(CoverageParser.ProcessingMode.FAIL_FAST));
+ }
+
/**
* Reads and parses a JaCoCo coverage report.
*
diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageChecksPublisherTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageChecksPublisherTest.java
index 159b8743..9839a4e3 100644
--- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageChecksPublisherTest.java
+++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageChecksPublisherTest.java
@@ -33,11 +33,11 @@
@DefaultLocale("en")
class CoverageChecksPublisherTest extends AbstractCoverageTest {
- private static final String JENKINS_BASE_URL = "http://127.0.0.1:8080";
- private static final String BUILD_LINK = "job/pipeline-coding-style/job/5";
- private static final String COVERAGE_ID = "coverage";
- private static final String REPORT_NAME = "Name";
- private static final int ANNOTATIONS_COUNT_FOR_MODIFIED = 3;
+ protected static final String JENKINS_BASE_URL = "http://127.0.0.1:8080";
+ protected static final String BUILD_LINK = "job/pipeline-coding-style/job/5";
+ protected static final String COVERAGE_ID = "coverage";
+ protected static final String REPORT_NAME = "Name";
+ protected static final int ANNOTATIONS_COUNT_FOR_MODIFIED = 3;
@Test
void shouldShowQualityGateDetails() {
@@ -117,7 +117,7 @@ private void assertMutationAnnotations(final ChecksOutput output, final int expe
}
}
- private void assertThatTitleIs(final CoverageChecksPublisher publisher, final String expectedTitle) {
+ protected void assertThatTitleIs(final CoverageChecksPublisher publisher, final String expectedTitle) {
var checkDetails = publisher.extractChecksDetails();
assertThat(checkDetails.getOutput()).isPresent().get().satisfies(output -> {
assertThat(output.getTitle()).isPresent().contains(expectedTitle);
@@ -141,6 +141,97 @@ void shouldCreateChecksReportJaCoCo(final ChecksAnnotationScope scope, final int
assertThatDetailsAreCorrect(checkDetails, expectedAnnotations);
}
+ @Test
+ void shouldShowVectorCastQualityGateDetails() {
+ var result = readVectorCastResult("vectorcast-statement-mcdc-fcc.xml");
+
+ var publisher = new CoverageChecksPublisher(createActionWithoutDelta(result,
+ CoverageQualityGateEvaluatorTest.createQualityGateResult()), result, REPORT_NAME,
+ ChecksAnnotationScope.SKIP, createJenkins());
+
+ var checkDetails = publisher.extractChecksDetails();
+
+ var expectedQualityGateSummary = toString("vectorcast-coverage-publisher-quality-gate.checks-expected-result");
+ assertThat(checkDetails.getOutput()).isPresent().get().satisfies(output -> {
+ assertThat(output.getSummary()).isPresent()
+ .get()
+ .asString()
+ .containsIgnoringWhitespaces(expectedQualityGateSummary);
+ });
+
+ var expectedOverview = toString("vectorcast-coverage-publisher-quality-gate-overview.checks-expected-result");
+ assertThat(checkDetails.getOutput()).isPresent().get().satisfies(output -> {
+ assertThat(output.getSummary()).isPresent()
+ .get()
+ .asString()
+ .containsIgnoringWhitespaces(expectedOverview);
+ });
+ }
+
+ @Test
+ void shouldShowProjectBaselineForVectorCast() {
+ var result = readVectorCastResult("vectorcast-statement-mcdc-fcc.xml");
+
+ var publisher = new CoverageChecksPublisher(createActionWithoutDelta(result), result, REPORT_NAME,
+ ChecksAnnotationScope.SKIP, createJenkins());
+
+ assertThatTitleIs(publisher, "Line Coverage: 79.93%, Branch Coverage: 66.18%");
+ }
+
+ @ParameterizedTest(name = "should create checks (scope = {0}, expected annotations = {1})")
+ @CsvSource({"SKIP, 0", "ALL_LINES, 6", "MODIFIED_LINES, 0"})
+ void shouldCreateChecksReportStatementBranch(final ChecksAnnotationScope scope, final int expectedAnnotations) {
+ shouldCreateChecksReport(scope, expectedAnnotations,
+ "vectorcast-statement-branch.xml",
+ "vectorcast-coverage-publisher-s+b-details.checks-expected-result",
+ "vectorcast-coverage-publisher-s+b-overview.checks-expected-result");
+ }
+
+ @ParameterizedTest(name = "should create checks (scope = {0}, expected annotations = {1})")
+ @CsvSource({"SKIP, 0", "ALL_LINES, 8", "MODIFIED_LINES, 0"})
+ void shouldCreateChecksReportStatementMcdc(final ChecksAnnotationScope scope, final int expectedAnnotations) {
+ shouldCreateChecksReport(scope, expectedAnnotations,
+ "vectorcast-statement-mcdc.xml",
+ "vectorcast-coverage-publisher-s+mcdc-details.checks-expected-result",
+ "vectorcast-coverage-publisher-s+mcdc-overview.checks-expected-result");
+ }
+
+ @ParameterizedTest(name = "should create checks (scope = {0}, expected annotations = {1})")
+ @CsvSource({"SKIP, 0", "ALL_LINES, 59", "MODIFIED_LINES, 0"})
+ void shouldCreateChecksReportStatementMcdcFunctionCall(final ChecksAnnotationScope scope, final int expectedAnnotations) {
+ shouldCreateChecksReport(scope, expectedAnnotations,
+ "vectorcast-statement-mcdc-fcc.xml",
+ "vectorcast-coverage-publisher-s+mcdc+fcc-details.checks-expected-result",
+ "vectorcast-coverage-publisher-s+mcdc+fcc-overview.checks-expected-result");
+ }
+
+ void shouldCreateChecksReport(final ChecksAnnotationScope scope, final int expectedAnnotations,
+ final String inFile, final String checkDetailsFile, final String checkOverviewFile) {
+ var result = readVectorCastResult(inFile);
+
+ var publisher = new CoverageChecksPublisher(createCoverageBuildAction(result), result, REPORT_NAME, scope, createJenkins());
+
+ var checkDetails = publisher.extractChecksDetails();
+
+ assertThat(checkDetails.getName()).isPresent().contains(REPORT_NAME);
+ assertThat(checkDetails.getStatus()).isEqualTo(ChecksStatus.COMPLETED);
+ assertThat(checkDetails.getConclusion()).isEqualTo(ChecksConclusion.SUCCESS);
+ assertThat(checkDetails.getDetailsURL()).isPresent()
+ .contains("http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage");
+ assertThatDetailsAreCorrect(checkDetails, expectedAnnotations, checkDetailsFile, checkOverviewFile);
+ }
+
+ private void assertThatDetailsAreCorrect(final ChecksDetails checkDetails, final int expectedAnnotations,
+ final String checkDetailsFile, final String checkOverviewFile) {
+ assertThat(checkDetails.getOutput()).isPresent().get().satisfies(output -> {
+ assertThat(output.getTitle()).isPresent().contains("Line Coverage: 50.00% (+50.00%)");
+ var expectedDetails = toString(checkDetailsFile);
+ assertThat(output.getText()).isPresent().get().asString().isEqualToNormalizingWhitespace(expectedDetails);
+ assertChecksAnnotations(output, expectedAnnotations);
+ assertSummary(output, checkOverviewFile);
+ });
+ }
+
private void assertThatDetailsAreCorrect(final ChecksDetails checkDetails, final int expectedAnnotations) {
assertThat(checkDetails.getOutput()).isPresent().get().satisfies(output -> {
assertThat(output.getTitle()).isPresent().contains("Line Coverage: 50.00% (+50.00%)");
@@ -151,13 +242,13 @@ private void assertThatDetailsAreCorrect(final ChecksDetails checkDetails, final
});
}
- private void assertSummary(final ChecksOutput checksOutput, final String fileName) {
+ protected void assertSummary(final ChecksOutput checksOutput, final String fileName) {
assertThat(checksOutput.getSummary()).isPresent()
.get()
.asString().isEqualToNormalizingWhitespace(toString(fileName));
}
- private void assertChecksAnnotations(final ChecksOutput checksOutput, final int expectedAnnotations) {
+ protected void assertChecksAnnotations(final ChecksOutput checksOutput, final int expectedAnnotations) {
if (expectedAnnotations == ANNOTATIONS_COUNT_FOR_MODIFIED) {
assertThat(checksOutput.getChecksAnnotations()).hasSize(expectedAnnotations).satisfiesExactly(
annotation -> {
@@ -182,14 +273,14 @@ private void assertChecksAnnotations(final ChecksOutput checksOutput, final int
}
}
- private JenkinsFacade createJenkins() {
+ protected JenkinsFacade createJenkins() {
JenkinsFacade jenkinsFacade = mock(JenkinsFacade.class);
when(jenkinsFacade.getAbsoluteUrl(BUILD_LINK, COVERAGE_ID)).thenReturn(
JENKINS_BASE_URL + "/" + BUILD_LINK + "/" + COVERAGE_ID);
return jenkinsFacade;
}
- private CoverageBuildAction createCoverageBuildAction(final Node result) {
+ protected CoverageBuildAction createCoverageBuildAction(final Node result) {
var testCoverage = new CoverageBuilder(Metric.LINE)
.withCovered(1)
.withMissed(1)
@@ -220,7 +311,7 @@ private CoverageBuildAction createCoverageBuildAction(final Node result) {
new TreeMap<>(Map.of(Metric.LINE, Fraction.ONE_HALF)), List.of(testCoverage), false);
}
- private CoverageBuildAction createActionWithoutDelta(final Node result) {
+ protected CoverageBuildAction createActionWithoutDelta(final Node result) {
return createActionWithoutDelta(result, new QualityGateResult());
}
diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoveragePluginITest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoveragePluginITest.java
index fbb9ce9a..185fb2fb 100644
--- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoveragePluginITest.java
+++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoveragePluginITest.java
@@ -33,13 +33,24 @@
import static org.assertj.core.api.Assertions.*;
/**
- * Integration test for different JaCoCo, Cobertura, and PIT files.
+ * Integration tests for different parsers.
*/
class CoveragePluginITest extends AbstractCoverageITest {
private static final String COBERTURA_HIGHER_COVERAGE_FILE = "cobertura-higher-coverage.xml";
private static final int COBERTURA_COVERED_LINES = 8;
private static final int COBERTURA_MISSED_LINES = 0;
private static final String NO_FILES_FOUND_ERROR_MESSAGE = "[-ERROR-] No files found for pattern '**/*xml'. Configuration error?";
+ private static final String VECTORCAST_HIGHER_COVERAGE_FILE = "vectorcast-statement-mcdc-fcc.xml";
+ private static final int VECTORCAST_COVERED_LINES = 235;
+ private static final int VECTORCAST_MISSED_LINES = 59;
+ private static final int VECTORCAST_COVERED_BRANCH = 180;
+ private static final int VECTORCAST_MISSED_BRANCH = 92;
+ private static final int VECTORCAST_COVERED_MCDC_PAIR = 24;
+ private static final int VECTORCAST_MISSED_MCDC_PAIR = 35;
+ private static final int VECTORCAST_COVERED_FUNCTION_CALL = 62;
+ private static final int VECTORCAST_MISSED_FUNCTION_CALL = 17;
+ private static final int VECTORCAST_COVERED_METHOD = 21;
+ private static final int VECTORCAST_MISSED_METHOD = 9;
@Test
void shouldFailWithoutParserInFreestyleJob() {
@@ -167,7 +178,8 @@ private void verifyJaCoCoAction(final CoverageBuildAction coverageResult) {
.build());
var tableModel = coverageResult.getTarget().getTableModel(CoverageViewModel.ABSOLUTE_COVERAGE_TABLE_ID);
assertThat(tableModel)
- .extracting(TableModel::getColumns).asList()
+ .extracting(TableModel::getColumns)
+ .asInstanceOf(LIST)
.extracting("headerLabel")
.containsExactly("Hash",
"Modified",
@@ -486,7 +498,8 @@ private void verifyOnePitResult(final ParameterizedJob, ?> project) {
var tableModel = coverageResult.getTarget().getTableModel(CoverageViewModel.ABSOLUTE_COVERAGE_TABLE_ID);
assertThat(tableModel)
- .extracting(TableModel::getColumns).asList()
+ .extracting(TableModel::getColumns)
+ .asInstanceOf(LIST)
.extracting("headerLabel")
.containsExactly("Hash",
"Modified",
@@ -566,4 +579,383 @@ void shouldRecordResultsWithDifferentId() {
// TODO: verify that two different trend charts are returned!
}
+
+ @Test @Issue("785")
+ void shouldIgnoreErrors() {
+ WorkflowJob job = createPipeline();
+ copyFileToWorkspace(job, "cobertura-duplicate-methods.xml", "cobertura.xml");
+ job.setDefinition(new CpsFlowDefinition(
+ "node {\n"
+ + " recordCoverage tools: [[parser: 'COBERTURA']]\n"
+ + " }\n", true));
+
+ Run, ?> failure = buildWithResult(job, Result.FAILURE);
+
+ assertThat(getConsoleLog(failure))
+ .contains("java.lang.IllegalArgumentException: There is already the same child [METHOD] Enumerate()");
+
+ job.setDefinition(new CpsFlowDefinition(
+ "node {\n"
+ + " recordCoverage tools: [[parser: 'COBERTURA']], ignoreParsingErrors: true\n"
+ + " }\n", true));
+
+ Run, ?> success = buildWithResult(job, Result.SUCCESS);
+
+ assertThat(getConsoleLog(success))
+ .doesNotContain("java.lang.IllegalArgumentException");
+ }
+
+ @Test
+ void shouldIgnoreEmptyListOfFiles() {
+ WorkflowJob job = createPipeline();
+ job.setDefinition(new CpsFlowDefinition(
+ "node {\n"
+ + " recordCoverage tools: [[parser: 'JACOCO']]\n"
+ + " }\n", true));
+
+ Run, ?> run = buildWithResult(job, Result.SUCCESS);
+
+ assertThat(getConsoleLog(run))
+ .contains("Using default pattern '**/jacoco.xml' since user defined pattern is not set",
+ "[-ERROR-] No files found for pattern '**/jacoco.xml'. Configuration error?")
+ .containsPattern("Searching for all files in '.*' that match the pattern '\\*\\*/jacoco.xml'")
+ .doesNotContain("Expanding pattern");
+ }
+
+ @Test
+ void shouldParseFileWithJaCoCo() {
+ WorkflowJob job = createPipeline();
+ copyFilesToWorkspace(job, "jacoco.xml");
+ job.setDefinition(new CpsFlowDefinition(
+ "node {\n"
+ + " recordCoverage tools: [[parser: 'JACOCO']]\n"
+ + " }\n", true));
+
+ Run, ?> run = buildWithResult(job, Result.SUCCESS);
+
+ assertThat(getConsoleLog(run))
+ .contains("Using default pattern '**/jacoco.xml' since user defined pattern is not set",
+ "-> found 1 file",
+ "MODULE: 100.00% (1/1)",
+ "PACKAGE: 100.00% (1/1)",
+ "FILE: 70.00% (7/10)",
+ "CLASS: 83.33% (15/18)",
+ "METHOD: 95.10% (97/102)",
+ "INSTRUCTION: 93.33% (1260/1350)",
+ "LINE: 91.02% (294/323)",
+ "BRANCH: 93.97% (109/116)",
+ "COMPLEXITY: 160")
+ .containsPattern("Searching for all files in '.*' that match the pattern '\\*\\*/jacoco.xml'")
+ .containsPattern("Successfully parsed file .*/jacoco.xml")
+ .doesNotContain("Expanding pattern");
+ }
+
+ private void assertContentOfFirstVectorCastRow(final CoverageRow r) {
+ assertThatCell(r.getFileName())
+ .contains("title=\"CurrentRelease/database/src/database.c\"");
+ assertThat(r.getPackageName()).isEqualTo("CurrentRelease.database.src");
+ assertThat(r.getTests()).isEqualTo(0);
+ assertThat(r.getComplexity()).isEqualTo(5);
+ assertThat(r.getLoc()).isEqualTo(17);
+ assertThat(r.getMaxComplexity()).isEqualTo(2);
+ assertThatCell(r.getLineCoverage())
+ .contains("title=\"Covered: 17 - Missed: 0\">100.00%");
+ assertThatCell(r.getLineCoverageDelta()).contains("n/a");
+ assertThatCell(r.getBranchCoverage())
+ .contains("title=\"Covered: 9 - Missed: 2\">81.82%");
+ assertThatCell(r.getBranchCoverageDelta()).contains("n/a");
+ assertThatCell(r.getMethodCoverage())
+ .contains("title=\"Covered: 3 - Missed: 0\">100.00%");
+ assertThatCell(r.getMcdcPairCoverage())
+ .contains("title=\"Covered: 1 - Missed: 1\">50.00%");
+ assertThatCell(r.getFunctionCallCoverage())
+ .contains("title=\"Covered: 4 - Missed: 0\">100.00%");
+ assertThatCell(r.getDensity()).contains("0.29");
+ }
+
+ @Test
+ void shouldRecordOneVectorCastResultInFreestyleJob() {
+ FreeStyleProject project = createFreestyleJob(Parser.VECTORCAST, VECTORCAST_HIGHER_COVERAGE_FILE);
+
+ verifyOneVectorCastResult(project);
+ }
+
+ @Test
+ void shouldRecordOneVectorCastResultInPipeline() {
+ WorkflowJob job = createPipeline(Parser.VECTORCAST, VECTORCAST_HIGHER_COVERAGE_FILE);
+
+ verifyOneVectorCastResult(job);
+ }
+
+ @Test
+ void shouldRecordOneVectorCastResultInDeclarativePipeline() {
+ WorkflowJob job = createDeclarativePipeline(Parser.VECTORCAST, VECTORCAST_HIGHER_COVERAGE_FILE);
+
+ verifyOneVectorCastResult(job);
+ }
+
+ private void verifyOneVectorCastResult(final ParameterizedJob, ?> project) {
+ Run, ?> build = buildSuccessfully(project);
+
+ verifyVectorCastAction(build.getAction(CoverageBuildAction.class));
+ }
+
+ private void verifyVectorCastAction(final CoverageBuildAction coverageResult) {
+ assertThat(coverageResult.getAllValues(Baseline.PROJECT)).extracting(Value::getMetric)
+ .containsExactly(Metric.MODULE,
+ Metric.PACKAGE,
+ Metric.FILE,
+ Metric.CLASS,
+ Metric.METHOD,
+ Metric.LINE,
+ Metric.BRANCH,
+ Metric.MCDC_PAIR,
+ Metric.FUNCTION_CALL,
+ Metric.COMPLEXITY,
+ Metric.COMPLEXITY_MAXIMUM,
+ Metric.COMPLEXITY_DENSITY,
+ Metric.LOC);
+ assertThat(coverageResult.getAllValues(Baseline.PROJECT)).contains(
+ new CoverageBuilder()
+ .withMetric(Metric.LINE)
+ .withCovered(VECTORCAST_COVERED_LINES)
+ .withMissed(VECTORCAST_MISSED_LINES)
+ .build());
+ assertThat(coverageResult.getAllValues(Baseline.PROJECT)).contains(
+ new CoverageBuilder()
+ .withMetric(Metric.BRANCH)
+ .withCovered(VECTORCAST_COVERED_BRANCH)
+ .withMissed(VECTORCAST_MISSED_BRANCH)
+ .build());
+ assertThat(coverageResult.getAllValues(Baseline.PROJECT)).contains(
+ new CoverageBuilder()
+ .withMetric(Metric.FUNCTION_CALL)
+ .withCovered(VECTORCAST_COVERED_FUNCTION_CALL)
+ .withMissed(VECTORCAST_MISSED_FUNCTION_CALL)
+ .build());
+ assertThat(coverageResult.getAllValues(Baseline.PROJECT)).contains(
+ new CoverageBuilder()
+ .withMetric(Metric.MCDC_PAIR)
+ .withCovered(VECTORCAST_COVERED_MCDC_PAIR)
+ .withMissed(VECTORCAST_MISSED_MCDC_PAIR)
+ .build());
+ assertThat(coverageResult.getAllValues(Baseline.PROJECT)).contains(
+ new CoverageBuilder()
+ .withMetric(Metric.METHOD)
+ .withCovered(VECTORCAST_COVERED_METHOD)
+ .withMissed(VECTORCAST_MISSED_METHOD)
+ .build());
+
+ var tableModel = coverageResult.getTarget().getTableModel(CoverageViewModel.ABSOLUTE_COVERAGE_TABLE_ID);
+ assertThat(tableModel)
+ .extracting(TableModel::getColumns)
+ .asInstanceOf(LIST)
+ .extracting("headerLabel")
+ .containsExactly("Hash",
+ "Modified",
+ "File",
+ "Package",
+ "Line",
+ "Line Δ",
+ "Branch",
+ "Branch Δ",
+ "MC/DC Pairs",
+ "Function Call",
+ "LOC",
+ "Complexity",
+ "Max. Complexity",
+ "Complexity / LOC");
+ assertThat(coverageResult.getAllValues(Baseline.PROJECT))
+ .contains(createLineCoverageBuilder()
+ .withCovered(VECTORCAST_COVERED_LINES)
+ .withMissed(VECTORCAST_MISSED_LINES)
+ .build());
+ assertThat(tableModel.getRows())
+ .hasSize(8)
+ .first()
+ .isInstanceOfSatisfying(CoverageRow.class, this::assertContentOfFirstVectorCastRow);
+ }
+
+ @Test
+ void shouldRecordVectorCastAndJacocoResultsInFreestyleJob() {
+ FreeStyleProject project = createFreeStyleProjectWithWorkspaceFiles(JACOCO_ANALYSIS_MODEL_FILE,
+ VECTORCAST_HIGHER_COVERAGE_FILE);
+
+ CoverageRecorder recorder = new CoverageRecorder();
+
+ var vectorcast = new CoverageTool();
+ vectorcast.setParser(Parser.VECTORCAST);
+ vectorcast.setPattern(VECTORCAST_HIGHER_COVERAGE_FILE);
+
+ var jacoco = new CoverageTool();
+ jacoco.setParser(Parser.JACOCO);
+ jacoco.setPattern(JACOCO_ANALYSIS_MODEL_FILE);
+
+ recorder.setTools(List.of(jacoco, vectorcast));
+ project.getPublishersList().add(recorder);
+
+ verifyForOneVectorCastAndOneJacoco(project);
+ }
+
+ @Test
+ void shouldRecordVectorCastAndJacocoResultsInPipeline() {
+ WorkflowJob job = createPipelineWithWorkspaceFiles(JACOCO_ANALYSIS_MODEL_FILE, VECTORCAST_HIGHER_COVERAGE_FILE);
+
+ setPipelineScript(job,
+ "recordCoverage tools: ["
+ + "[parser: 'VECTORCAST', pattern: '" + VECTORCAST_HIGHER_COVERAGE_FILE + "'],"
+ + "[parser: 'JACOCO', pattern: '" + JACOCO_ANALYSIS_MODEL_FILE + "']"
+ + "]");
+
+ verifyForOneVectorCastAndOneJacoco(job);
+ }
+
+ @Test
+ void shouldRecordVectorCastAndJacocoResultsInDeclarativePipeline() {
+ WorkflowJob job = createPipelineWithWorkspaceFiles(JACOCO_ANALYSIS_MODEL_FILE, VECTORCAST_HIGHER_COVERAGE_FILE);
+
+ job.setDefinition(new CpsFlowDefinition("pipeline {\n"
+ + " agent any\n"
+ + " stages {\n"
+ + " stage('Test') {\n"
+ + " steps {\n"
+ + " recordCoverage(tools: [\n"
+ + " [parser: 'VECTORCAST', pattern: '" + VECTORCAST_HIGHER_COVERAGE_FILE + "'],\n"
+ + " [parser: 'JACOCO', pattern: '" + JACOCO_ANALYSIS_MODEL_FILE + "']\n"
+ + " ])\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + "}", true));
+
+ verifyForOneVectorCastAndOneJacoco(job);
+ }
+
+ private void verifyForOneVectorCastAndOneJacoco(final ParameterizedJob, ?> project) {
+ Run, ?> build = buildSuccessfully(project);
+
+ CoverageBuildAction coverageResult = build.getAction(CoverageBuildAction.class);
+ assertThat(coverageResult.getAllValues(Baseline.PROJECT))
+ .contains(createLineCoverageBuilder()
+ .withCovered(JACOCO_ANALYSIS_MODEL_COVERED + VECTORCAST_COVERED_LINES)
+ .withMissed(JACOCO_ANALYSIS_MODEL_MISSED + VECTORCAST_MISSED_LINES)
+ .build());
+
+ assertThat(getConsoleLog(build)).contains(
+ "[Coverage] Recording coverage results",
+ "[Coverage] Creating parser for VectorCAST Coverage Results",
+ "that match the pattern 'vectorcast-statement-mcdc-fcc.xml'",
+ "Successfully processed file 'vectorcast-statement-mcdc-fcc.xml'",
+ "[Coverage] Creating parser for JaCoCo Coverage Reports",
+ "that match the pattern 'jacoco-analysis-model.xml'",
+ "Successfully processed file 'jacoco-analysis-model.xml'");
+ var log = coverageResult.getLog();
+ assertThat(log.getInfoMessages()).contains("Recording coverage results",
+ "Creating parser for VectorCAST Coverage Results",
+ "Successfully processed file 'vectorcast-statement-mcdc-fcc.xml'",
+ "Creating parser for JaCoCo Coverage Reports",
+ "Successfully processed file 'jacoco-analysis-model.xml'",
+ "Resolving source code files...",
+ "-> finished resolving of absolute paths (found: 0, not found: 315)",
+ "Obtaining result action of reference build",
+ "Reference build recorder is not configured",
+ "-> Found no reference build",
+ "No quality gates have been set - skipping",
+ "Executing source code painting...",
+ "Painting 315 source files on agent",
+ "-> finished painting (0 files have been painted, 315 files failed)",
+ "Copying painted sources from agent to build folder",
+ "-> extracting...",
+ "-> done",
+ "Finished coverage processing - adding the action to the build...");
+ assertThat(log.getErrorMessages()).contains(
+ "Errors while resolving source files on agent:",
+ "- Source file 'edu/hm/hafner/analysis/parser/PerlCriticParser.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/parser/StyleCopParser.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/registry/RoboCopyDescriptor.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/parser/fxcop/FxCopRuleSet.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/registry/SpotBugsDescriptor.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/parser/AcuCobolParser.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/registry/FlexSdkDescriptor.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/registry/BrakemanDescriptor.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/registry/PyDocStyleDescriptor.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/parser/AjcParser.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/ReaderFactory.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/parser/DiabCParser.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/registry/OELintAdvDescriptor.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/registry/GnuFortranDescriptor.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/parser/Armcc5CompilerParser.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/registry/DoxygenDescriptor.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/registry/ProtoLintDescriptor.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/registry/GoLintDescriptor.java' not found",
+ "- Source file 'edu/hm/hafner/analysis/ModuleResolver.java' not found",
+ " ... skipped logging of 295 additional errors ...");
+ }
+
+ @Test
+ void shouldRecordVerctorCastResultsWithDifferentId() {
+ WorkflowJob job = createPipelineWithWorkspaceFiles(JACOCO_ANALYSIS_MODEL_FILE, VECTORCAST_HIGHER_COVERAGE_FILE);
+
+ setPipelineScript(job,
+ "recordCoverage "
+ + "tools: [[parser: 'VECTORCAST', pattern: '" + VECTORCAST_HIGHER_COVERAGE_FILE + "']],"
+ + "id: 'vectorcast', name: 'VectorCast Results'\n"
+ + "recordCoverage "
+ + "tools: ["
+ + "[parser: 'JACOCO', pattern: '" + JACOCO_ANALYSIS_MODEL_FILE + "']],"
+ + "id: 'jacoco', name: 'JaCoCo Results'\n");
+
+ Run, ?> build = buildSuccessfully(job);
+
+ List coverageResult = build.getActions(CoverageBuildAction.class);
+ assertThat(coverageResult).hasSize(2);
+
+ assertThat(coverageResult).element(0).satisfies(
+ a -> {
+ assertThat(a.getUrlName()).isEqualTo("vectorcast");
+ assertThat(a.getDisplayName()).isEqualTo("VectorCast Results");
+ verifyVectorCastAction(a);
+ }
+ );
+ assertThat(coverageResult).element(1).satisfies(
+ a -> {
+ assertThat(a.getUrlName()).isEqualTo("jacoco");
+ assertThat(a.getDisplayName()).isEqualTo("JaCoCo Results");
+ verifyJaCoCoAction(a);
+ });
+
+ // TODO: verify that two different trend charts are returned!
+ }
+
+ @Test
+ void shouldParseFileWithVectorCast() {
+ WorkflowJob job = createPipeline();
+ copyFileToWorkspace(job, "vectorcast-statement-mcdc-fcc.xml", "xml_data/cobertura/coverage_results_test.xml");
+ job.setDefinition(new CpsFlowDefinition(
+ "node {\n"
+ + " recordCoverage tools: [[parser: 'VECTORCAST']]\n"
+ + " }\n", true));
+
+ Run, ?> run = buildWithResult(job, Result.SUCCESS);
+
+ assertThat(getConsoleLog(run))
+ .contains("Using default pattern 'xml_data/cobertura/coverage_results*.xml' since user defined pattern is not set",
+ "-> found 1 file",
+ "MODULE: 100.00% (1/1)",
+ "PACKAGE: 100.00% (5/5)",
+ "FILE: 75.00% (6/8)",
+ "CLASS: 75.00% (6/8)",
+ "METHOD: 70.00% (21/30)",
+ "LINE: 79.93% (235/294)",
+ "BRANCH: 66.18% (180/272)",
+ "MCDC_PAIR: 40.68% (24/59)",
+ "FUNCTION_CALL: 78.48% (62/79)",
+ "COMPLEXITY: 100",
+ "COMPLEXITY_MAXIMUM: 26",
+ "COMPLEXITY_DENSITY: 100/294",
+ "LOC: 294")
+ .containsPattern("Searching for all files in '.*' that match the pattern 'xml_data/cobertura/coverage_results\\*.xml'")
+ .containsPattern("Successfully parsed file '.*/xml_data/cobertura/coverage_results_test.xml")
+ .doesNotContain("Expanding pattern");
+ }
}
diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorderITest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorderITest.java
deleted file mode 100644
index 85f1e519..00000000
--- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorderITest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package io.jenkins.plugins.coverage.metrics.steps;
-
-import org.junit.jupiter.api.Test;
-import org.junitpioneer.jupiter.Issue;
-
-import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
-import org.jenkinsci.plugins.workflow.job.WorkflowJob;
-import hudson.model.Result;
-import hudson.model.Run;
-
-import io.jenkins.plugins.util.IntegrationTestWithJenkinsPerSuite;
-
-import static org.assertj.core.api.Assertions.*;
-
-/**
- * Tests the class {@link CoverageRecorder}.
- *
- * @author Ullrich Hafner
- */
-class CoverageRecorderITest extends IntegrationTestWithJenkinsPerSuite {
- @Test @Issue("785")
- void shouldIgnoreErrors() {
- WorkflowJob job = createPipeline();
- copyFileToWorkspace(job, "cobertura-duplicate-methods.xml", "cobertura.xml");
- job.setDefinition(new CpsFlowDefinition(
- "node {\n"
- + " recordCoverage tools: [[parser: 'COBERTURA']]\n"
- + " }\n", true));
-
- Run, ?> failure = buildWithResult(job, Result.FAILURE);
-
- assertThat(getConsoleLog(failure))
- .contains("java.lang.IllegalArgumentException: There is already the same child [METHOD] Enumerate()");
-
- job.setDefinition(new CpsFlowDefinition(
- "node {\n"
- + " recordCoverage tools: [[parser: 'COBERTURA']], ignoreParsingErrors: true\n"
- + " }\n", true));
-
- Run, ?> success = buildWithResult(job, Result.SUCCESS);
-
- assertThat(getConsoleLog(success))
- .doesNotContain("java.lang.IllegalArgumentException");
- }
-
- @Test
- void shouldIgnoreEmptyListOfFiles() {
- WorkflowJob job = createPipeline();
- job.setDefinition(new CpsFlowDefinition(
- "node {\n"
- + " recordCoverage tools: [[parser: 'JACOCO']]\n"
- + " }\n", true));
-
- Run, ?> run = buildWithResult(job, Result.SUCCESS);
-
- assertThat(getConsoleLog(run))
- .contains("Using default pattern '**/jacoco.xml' since user defined pattern is not set",
- "[-ERROR-] No files found for pattern '**/jacoco.xml'. Configuration error?")
- .containsPattern("Searching for all files in '.*' that match the pattern '\\*\\*/jacoco.xml'")
- .doesNotContain("Expanding pattern");
- }
-
- @Test
- void shouldParseFileWithJaCoCo() {
- WorkflowJob job = createPipeline();
- copyFilesToWorkspace(job, "jacoco.xml");
- job.setDefinition(new CpsFlowDefinition(
- "node {\n"
- + " recordCoverage tools: [[parser: 'JACOCO']]\n"
- + " }\n", true));
-
- Run, ?> run = buildWithResult(job, Result.SUCCESS);
-
- assertThat(getConsoleLog(run))
- .contains("Using default pattern '**/jacoco.xml' since user defined pattern is not set",
- "-> found 1 file",
- "MODULE: 100.00% (1/1)",
- "PACKAGE: 100.00% (1/1)",
- "FILE: 70.00% (7/10)",
- "CLASS: 83.33% (15/18)",
- "METHOD: 95.10% (97/102)",
- "INSTRUCTION: 93.33% (1260/1350)",
- "LINE: 91.02% (294/323)",
- "BRANCH: 93.97% (109/116)",
- "COMPLEXITY: 160")
- .containsPattern("Searching for all files in '.*' that match the pattern '\\*\\*/jacoco.xml'")
- .containsPattern("Successfully parsed file .*/jacoco.xml")
- .doesNotContain("Expanding pattern");
- }
-}
diff --git a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-quality-gate-overview.checks-expected-result b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-quality-gate-overview.checks-expected-result
new file mode 100644
index 00000000..1b33ab3c
--- /dev/null
+++ b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-quality-gate-overview.checks-expected-result
@@ -0,0 +1,26 @@
+#### Project Overview
+
+No changes detected, that affect the code coverage.
+* Method Coverage: 70.00% (21/30)
+* Line Coverage: 79.93% (235/294)
+* Branch Coverage: 66.18% (180/272)
+* MC/DC Pair Coverage: 40.68% (24/59)
+* Function Call Coverage: 78.48% (62/79)
+* Complexity Density: 0.34%
+* Lines of Code: 294
+
+#### Quality Gates Summary
+
+Overall result: Failed
+- Overall project - File Coverage: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00)
+- Overall project - Line Coverage: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00)
+- Modified code lines - File Coverage: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00)
+- Modified code lines - Line Coverage: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00)
+- Modified files - File Coverage: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00)
+- Modified files - Line Coverage: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00)
+- Overall project (difference to reference job) - File Coverage: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00)
+- Overall project (difference to reference job) - Line Coverage: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00)
+- Modified code lines (difference to modified files) - File Coverage: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00)
+- Modified code lines (difference to modified files) - Line Coverage: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00)
+- Modified files (difference to reference job) - File Coverage: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00)
+- Modified files (difference to reference job) - Line Coverage: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00)
diff --git a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-quality-gate.checks-expected-result b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-quality-gate.checks-expected-result
new file mode 100644
index 00000000..a765f994
--- /dev/null
+++ b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-quality-gate.checks-expected-result
@@ -0,0 +1,15 @@
+#### Quality Gates Summary
+
+Overall result: Failed
+- Overall project - File Coverage: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00)
+- Overall project - Line Coverage: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00)
+- Modified code lines - File Coverage: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00)
+- Modified code lines - Line Coverage: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00)
+- Modified files - File Coverage: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00)
+- Modified files - Line Coverage: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00)
+- Overall project (difference to reference job) - File Coverage: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00)
+- Overall project (difference to reference job) - Line Coverage: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00)
+- Modified code lines (difference to modified files) - File Coverage: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00)
+- Modified code lines (difference to modified files) - Line Coverage: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00)
+- Modified files (difference to reference job) - File Coverage: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00)
+- Modified files (difference to reference job) - Line Coverage: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00)
diff --git a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+b-details.checks-expected-result b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+b-details.checks-expected-result
new file mode 100644
index 00000000..bf61d508
--- /dev/null
+++ b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+b-details.checks-expected-result
@@ -0,0 +1,11 @@
+#### Project coverage details
+
+||Module Coverage|Package Coverage|File Coverage|Class Coverage|Line Coverage|Branch Coverage
+|:---:|:---:|:---:|:---:|:---:|:---:|:---:
+:feet: **Overall project**|100.00% (1/1)|100.00% (5/5)|100.00% (3/3)|100.00% (3/3)|65.00% (52/80)|67.65% (23/34)
+:chart_with_upwards_trend: **Overall project (difference to reference job)**|+0.00%|-50.00% :arrow_down:|n/a|n/a|+50.00% :arrow_up:|n/a
+:feet: **Modified files**|n/a|n/a|n/a|n/a|50.00% (1/2)|n/a
+:chart_with_upwards_trend: **Modified files (difference to reference job)**|n/a|n/a|n/a|n/a|+50.00% :arrow_up:|n/a
+:feet: **Modified code lines**|n/a|n/a|n/a|n/a|50.00% (1/2)|n/a
+:chart_with_upwards_trend: **Modified code lines (difference to modified files)**|n/a|n/a|n/a|n/a|+50.00% :arrow_up:|n/a
+:feet: **Indirect changes**|n/a|n/a|n/a|n/a|50.00% (1/2)|n/a
diff --git a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+b-overview.checks-expected-result b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+b-overview.checks-expected-result
new file mode 100644
index 00000000..78d6ad2b
--- /dev/null
+++ b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+b-overview.checks-expected-result
@@ -0,0 +1,11 @@
+#### Project Overview
+
+No changes detected, that affect the code coverage.
+* Line Coverage: 65.00% (52/80)
+* Branch Coverage: 67.65% (23/34)
+* Complexity Density: 0.31%
+* Lines of Code: 80
+
+#### Quality Gates Summary
+
+No active quality gates.
\ No newline at end of file
diff --git a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+mcdc+fcc-details.checks-expected-result b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+mcdc+fcc-details.checks-expected-result
new file mode 100644
index 00000000..bf9f6189
--- /dev/null
+++ b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+mcdc+fcc-details.checks-expected-result
@@ -0,0 +1,11 @@
+#### Project coverage details
+
+||Module Coverage|Package Coverage|File Coverage|Class Coverage|Method Coverage|Line Coverage|Branch Coverage|MC/DC Pair Coverage|Function Call Coverage
+|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:
+:feet: **Overall project**|100.00% (1/1)|100.00% (10/10)|75.00% (6/8)|75.00% (6/8)|70.00% (21/30)|79.93% (235/294)|66.18% (180/272)|40.68% (24/59)|78.48% (62/79)
+:chart_with_upwards_trend: **Overall project (difference to reference job)**|+0.00%|-50.00% :arrow_down:|n/a|n/a|n/a|+50.00% :arrow_up:|n/a|n/a|n/a
+:feet: **Modified files**|n/a|n/a|n/a|n/a|n/a|50.00% (1/2)|n/a|n/a|n/a
+:chart_with_upwards_trend: **Modified files (difference to reference job)**|n/a|n/a|n/a|n/a|n/a|+50.00% :arrow_up:|n/a|n/a|n/a
+:feet: **Modified code lines**|n/a|n/a|n/a|n/a|n/a|50.00% (1/2)|n/a|n/a|n/a
+:chart_with_upwards_trend: **Modified code lines (difference to modified files)**|n/a|n/a|n/a|n/a|n/a|+50.00% :arrow_up:|n/a|n/a|n/a
+:feet: **Indirect changes**|n/a|n/a|n/a|n/a|n/a|50.00% (1/2)|n/a|n/a|n/a
diff --git a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+mcdc+fcc-overview.checks-expected-result b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+mcdc+fcc-overview.checks-expected-result
new file mode 100644
index 00000000..cb1e02f5
--- /dev/null
+++ b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+mcdc+fcc-overview.checks-expected-result
@@ -0,0 +1,14 @@
+#### Project Overview
+
+No changes detected, that affect the code coverage.
+* Method Coverage: 70.00% (21/30)
+* Line Coverage: 79.93% (235/294)
+* Branch Coverage: 66.18% (180/272)
+* MC/DC Pair Coverage: 40.68% (24/59)
+* Function Call Coverage: 78.48% (62/79)
+* Complexity Density: 0.34%
+* Lines of Code: 294
+
+#### Quality Gates Summary
+
+No active quality gates.
diff --git a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+mcdc-details.checks-expected-result b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+mcdc-details.checks-expected-result
new file mode 100644
index 00000000..5e130de0
--- /dev/null
+++ b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+mcdc-details.checks-expected-result
@@ -0,0 +1,11 @@
+#### Project coverage details
+
+||Module Coverage|Package Coverage|File Coverage|Class Coverage|Line Coverage|Branch Coverage|MC/DC Pair Coverage
+|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:
+:feet: **Overall project**|100.00% (1/1)|80.00% (4/5)|66.67% (2/3)|66.67% (2/3)|65.22% (45/69)|71.43% (20/28)|66.67% (8/12)
+:chart_with_upwards_trend: **Overall project (difference to reference job)**|+0.00%|-50.00% :arrow_down:|n/a|n/a|+50.00% :arrow_up:|n/a|n/a
+:feet: **Modified files**|n/a|n/a|n/a|n/a|50.00% (1/2)|n/a|n/a
+:chart_with_upwards_trend: **Modified files (difference to reference job)**|n/a|n/a|n/a|n/a|+50.00% :arrow_up:|n/a|n/a
+:feet: **Modified code lines**|n/a|n/a|n/a|n/a|50.00% (1/2)|n/a|n/a
+:chart_with_upwards_trend: **Modified code lines (difference to modified files)**|n/a|n/a|n/a|n/a|+50.00% :arrow_up:|n/a|n/a
+:feet: **Indirect changes**|n/a|n/a|n/a|n/a|50.00% (1/2)|n/a|n/a
diff --git a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+mcdc-overview.checks-expected-result b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+mcdc-overview.checks-expected-result
new file mode 100644
index 00000000..5538923d
--- /dev/null
+++ b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-s+mcdc-overview.checks-expected-result
@@ -0,0 +1,12 @@
+#### Project Overview
+
+No changes detected, that affect the code coverage.
+* Line Coverage: 65.22% (45/69)
+* Branch Coverage: 71.43% (20/28)
+* MC/DC Pair Coverage: 66.67% (8/12)
+* Complexity Density: 0.36%
+* Lines of Code: 69
+
+#### Quality Gates Summary
+
+No active quality gates.
diff --git a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-summary.checks-expected-result b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-summary.checks-expected-result
new file mode 100644
index 00000000..14c58b12
--- /dev/null
+++ b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-coverage-publisher-summary.checks-expected-result
@@ -0,0 +1,23 @@
+#### Summary for modified lines
+
+- 3 lines have been modified
+- 2 lines are not covered
+- 1 line is covered only partially
+
+#### Overview by baseline
+
+* **[Overall project (difference to reference job)](http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage#overview)**
+ * Line Coverage: 91.02% (294/323) - Delta: +50.00%
+ * Branch Coverage: 93.97% (109/116)
+ * Complexity Density: 0.50%
+ * Lines of Code: 323
+* **[Modified files (difference to reference job)](http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage#modifiedFilesCoverage)**
+ * Line Coverage: 50.00% (1/2) - Delta: +50.00%
+* **[Modified code lines (difference to modified files)](http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage#modifiedLinesCoverage)**
+ * Line Coverage: 50.00% (1/2) - Delta: +50.00%
+* **[Indirect changes](http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage#indirectCoverage)**
+ * Line Coverage: 50.00% (1/2)
+
+#### Quality Gates Summary
+
+No active quality gates.
diff --git a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-duplicate-methods-statement-mcdc-fcc.xml b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-duplicate-methods-statement-mcdc-fcc.xml
new file mode 100644
index 00000000..aa22d856
--- /dev/null
+++ b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-duplicate-methods-statement-mcdc-fcc.xml
@@ -0,0 +1,786 @@
+
+
+
+
+ ./
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-statement-branch.xml b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-statement-branch.xml
new file mode 100644
index 00000000..4a589690
--- /dev/null
+++ b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-statement-branch.xml
@@ -0,0 +1,215 @@
+
+
+
+ ./
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-statement-mcdc-fcc.xml b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-statement-mcdc-fcc.xml
new file mode 100644
index 00000000..c1ab4334
--- /dev/null
+++ b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-statement-mcdc-fcc.xml
@@ -0,0 +1,785 @@
+
+
+
+
+ ./
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-statement-mcdc.xml b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-statement-mcdc.xml
new file mode 100644
index 00000000..725e4a65
--- /dev/null
+++ b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/vectorcast-statement-mcdc.xml
@@ -0,0 +1,141 @@
+
+
+
+
+ ./
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui-tests/src/main/java/io/jenkins/plugins/coverage/publisher/threshold/GlobalThreshold.java b/ui-tests/src/main/java/io/jenkins/plugins/coverage/publisher/threshold/GlobalThreshold.java
index bdf6100b..7375c894 100644
--- a/ui-tests/src/main/java/io/jenkins/plugins/coverage/publisher/threshold/GlobalThreshold.java
+++ b/ui-tests/src/main/java/io/jenkins/plugins/coverage/publisher/threshold/GlobalThreshold.java
@@ -52,7 +52,6 @@ public enum GlobalThresholdTarget {
FILE("File"),
CLASS("Class"),
METHOD("Method"),
- FUNCTION("Function"),
INSTRUCTION("Instruction"),
LINE("Line"),
CONDITIONAL("Conditional");