diff --git a/its/plugin/projects/nosetests_project/pytest.xml b/its/plugin/projects/nosetests_project/pytest.xml new file mode 100644 index 0000000000..7779e003c1 --- /dev/null +++ b/its/plugin/projects/nosetests_project/pytest.xml @@ -0,0 +1,23 @@ + + + + + def test_failing(): + > assert False + E assert False + + tests/test_prod.py:5: AssertionError + + + + def test_crashing(): + > raise RuntimeError() + E RuntimeError + + tests/test_prod.py:8: RuntimeError + + + + skipped + + \ No newline at end of file diff --git a/its/plugin/src/test/java/com/sonar/python/it/plugin/TestReportTest.java b/its/plugin/src/test/java/com/sonar/python/it/plugin/TestReportTest.java index af3f7a5546..e2cb7c6476 100644 --- a/its/plugin/src/test/java/com/sonar/python/it/plugin/TestReportTest.java +++ b/its/plugin/src/test/java/com/sonar/python/it/plugin/TestReportTest.java @@ -69,6 +69,19 @@ public void import_report() throws Exception { .build()); } + @Test + public void import_pytest_report() { + orchestrator.executeBuild(createBuild("pytest.xml")); + assertProjectMeasures(PROJECT, new ImmutableMap.Builder() + .put("tests", 3) + .put("test_failures", 2) + .put("test_errors", 0) + .put("skipped_tests", 1) + .put("test_success_density", 33) + .put("test_execution_time", 1) + .build()); + } + @Test public void simple_mode() throws Exception { orchestrator.executeBuild(createBuild("nosetests.xml").setProperty("sonar.python.xunit.skipDetails", "true")); diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/PythonXUnitSensor.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/PythonXUnitSensor.java index e2aaef5fa8..e0c6528789 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/PythonXUnitSensor.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/PythonXUnitSensor.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.CheckForNull; import javax.xml.stream.XMLStreamException; import org.apache.commons.lang.StringUtils; import org.sonar.api.batch.fs.FileSystem; @@ -78,26 +79,16 @@ private static void simpleMode(final SensorContext context, List reports) parser.parse(report); } - int testsCount = 0; - int testsSkipped = 0; - int testsErrors = 0; - int testsFailures = 0; - long testsTime = 0; - for (TestSuite report : parserHandler.getParsedReports()) { - testsCount += report.getTests() - report.getSkipped(); - testsSkipped += report.getSkipped(); - testsErrors += report.getErrors(); - testsFailures += report.getFailures(); - testsTime += report.getTime(); - } + TestResult total = new TestResult(); + parserHandler.getParsedReports().forEach(testSuite -> testSuite.getTestCases().forEach(total::addTestCase)); - if (testsCount > 0) { + if (total.getTests() > 0) { InputComponent module = context.module(); - saveMeasure(context, module, CoreMetrics.TESTS, testsCount); - saveMeasure(context, module, CoreMetrics.SKIPPED_TESTS, testsSkipped); - saveMeasure(context, module, CoreMetrics.TEST_ERRORS, testsErrors); - saveMeasure(context, module, CoreMetrics.TEST_FAILURES, testsFailures); - saveMeasure(context, module, CoreMetrics.TEST_EXECUTION_TIME, testsTime); + saveMeasure(context, module, CoreMetrics.TESTS, total.getExecutedTests()); + saveMeasure(context, module, CoreMetrics.SKIPPED_TESTS, total.getSkipped()); + saveMeasure(context, module, CoreMetrics.TEST_ERRORS, total.getErrors()); + saveMeasure(context, module, CoreMetrics.TEST_FAILURES, total.getFailures()); + saveMeasure(context, module, CoreMetrics.TEST_EXECUTION_TIME, total.getTime()); } } @@ -114,24 +105,35 @@ private void detailedMode(final SensorContext context, List reports) throw } private void processReportDetailed(SensorContext context, Collection parsedReports) { - Collection locatedResources = lookupResources(parsedReports); - for (TestSuite fileReport : locatedResources) { - InputFile inputFile = fileReport.getInputFile(); + Map locatedResources = lookupResources(parsedReports); + for (Map.Entry entry : locatedResources.entrySet()) { + InputFile inputFile = entry.getKey(); + TestResult fileTestResult = entry.getValue(); + LOG.debug("Saving test execution measures for '{}'", inputFile.toString()); + + saveMeasure(context, inputFile, CoreMetrics.SKIPPED_TESTS, fileTestResult.getSkipped()); + saveMeasure(context, inputFile, CoreMetrics.TESTS, fileTestResult.getExecutedTests()); + saveMeasure(context, inputFile, CoreMetrics.TEST_ERRORS, fileTestResult.getErrors()); + saveMeasure(context, inputFile, CoreMetrics.TEST_FAILURES, fileTestResult.getFailures()); + saveMeasure(context, inputFile, CoreMetrics.TEST_EXECUTION_TIME, fileTestResult.getTime()); + } + } - if (LOG.isDebugEnabled()) { - LOG.debug("Saving test execution measures for '{}' under resource '{}'", fileReport.getKey(), inputFile.relativePath()); - } + @CheckForNull + private InputFile findResource(TestCase testCase, String fileKey) { + InputFile unitTestFile = null; - saveMeasure(context, inputFile, CoreMetrics.SKIPPED_TESTS, fileReport.getSkipped()); - saveMeasure(context, inputFile, CoreMetrics.TESTS, fileReport.getTests() - fileReport.getSkipped()); - saveMeasure(context, inputFile, CoreMetrics.TEST_ERRORS, fileReport.getErrors()); - saveMeasure(context, inputFile, CoreMetrics.TEST_FAILURES, fileReport.getFailures()); - saveMeasure(context, inputFile, CoreMetrics.TEST_EXECUTION_TIME, fileReport.getTime()); + if (testCase.getFile() != null) { + unitTestFile = getSonarTestFile(new File(testCase.getFile())); } - } - private InputFile findResource(String fileKey) { - return findResourceUsingNoseTestsStrategy(fileKey); + if (unitTestFile == null) { + String testClassname = testCase.getTestClassname(); + String key = testClassname != null ? testClassname : fileKey; + return findResourceUsingNoseTestsStrategy(key); + } + + return unitTestFile; } private InputFile findResourceUsingNoseTestsStrategy(String fileKey) { @@ -151,32 +153,27 @@ private InputFile findResourceUsingNoseTestsStrategy(String fileKey) { return unitTestFile; } - private Collection lookupResources(Collection testReports) { - Map locatedReports = new HashMap<>(); - - for (TestSuite report : testReports) { - String fileKey = report.getKey(); - - LOG.debug("Trying to find a SonarQube resource for '{}'", fileKey); - InputFile inputFile = findResource(fileKey); - if (inputFile != null) { - LOG.debug("The resource was found '{}'", inputFile); - - TestSuite summaryReport = locatedReports.get(inputFile.absolutePath()); - if (summaryReport != null) { - summaryReport.addMeasures(report); + private Map lookupResources(Collection testReports) { + Map testResultsByFile = new HashMap<>(); + + for (TestSuite testSuite : testReports) { + testSuite.getTestCases().forEach(testCase -> { + String testClassname = testCase.getTestClassname(); + LOG.debug("Trying to find a SonarQube resource for test case '{}'", testClassname); + InputFile inputFile = findResource(testCase, testSuite.getKey()); + if (inputFile != null) { + LOG.debug("The resource was found '{}'", inputFile); + testResultsByFile.computeIfAbsent(inputFile, k -> new TestResult()).addTestCase(testCase); } else { - report.setInputFile(inputFile); - locatedReports.put(inputFile.absolutePath(), report); + LOG.warn("The resource for '{}' is not found, drilling down to the details of this test won't be possible", testClassname); } - } else { - LOG.warn("The resource for '{}' is not found, drilling down to the details of this test won't be possible", fileKey); - } + }); } - return locatedReports.values(); + return testResultsByFile; } + @CheckForNull private InputFile getSonarTestFile(File file) { LOG.debug("Using the key '{}' to lookup the resource in SonarQube", file.getPath()); return fileSystem.inputFile(fileSystem.predicates().is(file)); diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestCase.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestCase.java index 85810e9c8c..92711a16a3 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestCase.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestCase.java @@ -19,6 +19,8 @@ */ package org.sonar.plugins.python.xunit; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import org.apache.commons.lang.StringEscapeUtils; /** @@ -27,20 +29,22 @@ */ public class TestCase { - private static final String STATUS_OK = "ok"; - private static final String STATUS_ERROR = "error"; - private static final String STATUS_FAILURE = "failure"; - private static final String STATUS_SKIPPED = "skipped"; + public static final String STATUS_OK = "ok"; + public static final String STATUS_ERROR = "error"; + public static final String STATUS_FAILURE = "failure"; + public static final String STATUS_SKIPPED = "skipped"; - private String name; - private String status = STATUS_OK; - private String stackTrace; - private String errorMessage; - private int time = 0; + private final String name; + private final String status; + private final String stackTrace; + private final String errorMessage; + private final int time; + private final String file; + private final String testClassname; /** * Constructs a testcase instance out of following parameters - * + * * @param name * The name of this testcase * @param time @@ -51,13 +55,19 @@ public class TestCase { * The stack trace occurred while executing of this testcase; pass "" if the testcase passed/skipped. * @param msg * The error message associated with this testcase of the execution was erroneous; pass "" if not. + * @param file + * The optional file to which this test case applies. + * @param testClassname + * The classname of the test. */ - public TestCase(String name, int time, String status, String stack, String msg) { + public TestCase(String name, int time, String status, String stack, String msg, @Nullable String file, @Nullable String testClassname) { this.name = name; this.time = time; this.stackTrace = stack; this.errorMessage = msg; this.status = status; + this.file = file; + this.testClassname = testClassname; } /** @@ -85,6 +95,16 @@ public int getTime() { return time; } + @CheckForNull + public String getFile() { + return file; + } + + @CheckForNull + public String getTestClassname() { + return testClassname; + } + /** * Returns execution details as sonar-conform XML */ diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestResult.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestResult.java new file mode 100644 index 0000000000..42e7e81f65 --- /dev/null +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestResult.java @@ -0,0 +1,66 @@ +/* + * SonarQube Python Plugin + * Copyright (C) 2011-2019 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.xunit; + +public class TestResult { + + private int errors = 0; + private int skipped = 0; + private int tests = 0; + private int time = 0; + private int failures = 0; + + public int getErrors() { + return errors; + } + + public int getSkipped() { + return skipped; + } + + public int getTests() { + return tests; + } + + public int getExecutedTests() { + return tests - skipped; + } + + public int getTime() { + return time; + } + + public int getFailures() { + return failures; + } + + public void addTestCase(TestCase tc) { + if (tc.isSkipped()) { + skipped++; + } else if (tc.isFailure()) { + failures++; + } else if (tc.isError()) { + errors++; + } + tests++; + time += tc.getTime(); + } + +} diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestSuite.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestSuite.java index 7470873baf..35f822d3f8 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestSuite.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestSuite.java @@ -19,8 +19,6 @@ */ package org.sonar.plugins.python.xunit; -import org.sonar.api.batch.fs.InputFile; - import java.util.ArrayList; import java.util.List; @@ -30,12 +28,6 @@ public class TestSuite { private String key; - private InputFile inputFile = null; - private int errors = 0; - private int skipped = 0; - private int tests = 0; - private int time = 0; - private int failures = 0; private List testCases; /** @@ -53,79 +45,10 @@ public String getKey() { return key; } - public int getErrors() { - return errors; - } - - public int getSkipped() { - return skipped; - } - - public int getTests() { - return tests; - } - - public int getTime() { - return time; - } - - public int getFailures() { - return failures; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - TestSuite that = (TestSuite) o; - return key.equals(that.key); - } - - @Override - public int hashCode() { - return key.hashCode(); - } - - /** - * Adds the given test case to this testsuite maintaining the internal statistics - * - * @param tc - * the test case to add - */ public void addTestCase(TestCase tc) { - if (tc.isSkipped()) { - skipped++; - } else if (tc.isFailure()) { - failures++; - } else if (tc.isError()) { - errors++; - } - tests++; - time += tc.getTime(); testCases.add(tc); } - /** - * Adds the measures contained by the given test suite to this test suite - * - * @param ts - * the test suite to add the measures from - */ - public TestSuite addMeasures(TestSuite ts) { - for (TestCase tc : ts.getTestCases()) { - addTestCase(tc); - } - return this; - } - - /** - * Returns the testcases contained by this test suite - */ public List getTestCases() { return testCases; } @@ -143,11 +66,4 @@ public String getDetails() { return details.toString(); } - public void setInputFile(InputFile inputFile) { - this.inputFile = inputFile; - } - - public InputFile getInputFile() { - return inputFile; - } } diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestSuiteParser.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestSuiteParser.java index 78fe2bc02e..5d8cf453a9 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestSuiteParser.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/xunit/TestSuiteParser.java @@ -19,6 +19,8 @@ */ package org.sonar.plugins.python.xunit; +import java.util.ArrayList; +import java.util.List; import org.codehaus.staxmate.in.ElementFilter; import org.codehaus.staxmate.in.SMHierarchicCursor; import org.codehaus.staxmate.in.SMInputCursor; @@ -27,29 +29,23 @@ import javax.xml.stream.XMLStreamException; import java.text.ParseException; import java.util.Collection; -import java.util.HashMap; import java.util.Locale; -import java.util.Map; public class TestSuiteParser implements XmlStreamHandler { - private Map testSuites = new HashMap<>(); + private List testSuites = new ArrayList<>(); @Override public void stream(SMHierarchicCursor rootCursor) throws XMLStreamException { SMInputCursor testSuiteCursor = rootCursor.constructDescendantCursor(new ElementFilter("testsuite")); while (testSuiteCursor.getNext() != null) { String testSuiteClassName = testSuiteCursor.getAttrValue("name"); - + TestSuite testSuite = new TestSuite(testSuiteClassName); + testSuites.add(testSuite); SMInputCursor testCaseCursor = testSuiteCursor.childElementCursor("testcase"); + while (testCaseCursor.getNext() != null) { - String testClassName = getClassname(testCaseCursor, testSuiteClassName); - TestSuite report = testSuites.get(testClassName); - if (report == null) { - report = new TestSuite(testClassName); - testSuites.put(testClassName, report); - } - report.addTestCase(parseTestCaseTag(testCaseCursor)); + testSuite.addTestCase(parseTestCaseTag(testCaseCursor)); } } } @@ -58,40 +54,35 @@ public void stream(SMHierarchicCursor rootCursor) throws XMLStreamException { * Returns successfully parsed reports as a collection of TestSuite objects. */ public Collection getParsedReports() { - return testSuites.values(); - } - - private static String getClassname(SMInputCursor testCaseCursor, String defaultClassname) throws XMLStreamException { - String testClassName = testCaseCursor.getAttrValue("classname"); - return testClassName == null ? defaultClassname : testClassName; + return testSuites; } private static TestCase parseTestCaseTag(SMInputCursor testCaseCursor) throws XMLStreamException { - // TODO: get a decent grammar for the junit format and check the - // logic inside this method against it. - String name = parseTestCaseName(testCaseCursor); Double time = parseTime(testCaseCursor); - String status = "ok"; + String status = TestCase.STATUS_OK; String stack = ""; String msg = ""; + String file = testCaseCursor.getAttrValue("file"); + String testClassName = testCaseCursor.getAttrValue("classname"); + SMInputCursor childCursor = testCaseCursor.childElementCursor(); if (childCursor.getNext() != null) { String elementName = childCursor.getLocalName(); - if ("skipped".equals(elementName)) { - status = "skipped"; - } else if ("failure".equals(elementName)) { - status = "failure"; + if (TestCase.STATUS_SKIPPED.equals(elementName)) { + status = TestCase.STATUS_SKIPPED; + } else if (TestCase.STATUS_FAILURE.equals(elementName)) { + status = TestCase.STATUS_FAILURE; msg = childCursor.getAttrValue("message"); stack = childCursor.collectDescendantText(); - } else if ("error".equals(elementName)) { - status = "error"; + } else if (TestCase.STATUS_ERROR.equals(elementName)) { + status = TestCase.STATUS_ERROR; msg = childCursor.getAttrValue("message"); stack = childCursor.collectDescendantText(); } } - return new TestCase(name, time.intValue(), status, stack, msg); + return new TestCase(name, time.intValue(), status, stack, msg, file, testClassName); } private static double parseTime(SMInputCursor testCaseCursor) throws XMLStreamException { diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/PythonXUnitSensorTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/PythonXUnitSensorTest.java index 75ef2118c0..6c8dbaa30d 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/PythonXUnitSensorTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/PythonXUnitSensorTest.java @@ -35,6 +35,9 @@ public class PythonXUnitSensorTest { + private static final String FILE_SAMPLE1 = "test_sample1.py"; + private static final String FILE_SAMPLE2 = "tests/dir/test_sample2.py"; + private File baseDir = new File("src/test/resources/org/sonar/plugins/python"); Settings settings; PythonXUnitSensor sensor; @@ -50,8 +53,8 @@ public void setUp() { @Test public void shouldSaveCorrectMeasures() { - DefaultInputFile testFile1 = TestInputFileBuilder.create("", "test_sample1.py").build(); - DefaultInputFile testFile2 = TestInputFileBuilder.create("", "tests/dir/test_sample2.py").build(); + DefaultInputFile testFile1 = TestInputFileBuilder.create("", FILE_SAMPLE1).build(); + DefaultInputFile testFile2 = TestInputFileBuilder.create("", FILE_SAMPLE2).build(); fs.add(testFile1); fs.add(testFile2); sensor.execute(context); @@ -85,7 +88,7 @@ public void shouldSaveCorrectMeasuresSimpleMode() { @Test public void shouldReportNothingWhenNoReportFound() { - DefaultInputFile testFile1 = TestInputFileBuilder.create("", "test_sample1.py").build(); + DefaultInputFile testFile1 = TestInputFileBuilder.create("", FILE_SAMPLE1).build(); fs.add(testFile1); settings.setProperty(PythonXUnitSensor.REPORT_PATH_KEY, "notexistingpath"); @@ -103,6 +106,65 @@ public void shouldThrowWhenGivenInvalidTime() { sensor.execute(context); } + @Test + public void shouldSaveCorrectMeasuresWithPyTestFormat() { + DefaultInputFile testFile1 = TestInputFileBuilder.create("", FILE_SAMPLE1).build(); + DefaultInputFile testFile2 = TestInputFileBuilder.create("", FILE_SAMPLE2).build(); + fs.add(testFile1); + fs.add(testFile2); + settings.setProperty(PythonXUnitSensor.REPORT_PATH_KEY, "xunit-reports/pytest-xunit-result.xml"); + sensor.execute(context); + + assertThat(measure(testFile1, CoreMetrics.TESTS)).isEqualTo(2); + assertThat(measure(testFile2, CoreMetrics.TESTS)).isEqualTo(8); + + assertThat(measure(testFile1, CoreMetrics.SKIPPED_TESTS)).isEqualTo(0); + assertThat(measure(testFile2, CoreMetrics.SKIPPED_TESTS)).isEqualTo(2); + + assertThat(measure(testFile1, CoreMetrics.TEST_ERRORS)).isEqualTo(0); + assertThat(measure(testFile2, CoreMetrics.TEST_ERRORS)).isEqualTo(0); + + assertThat(measure(testFile1, CoreMetrics.TEST_FAILURES)).isEqualTo(1); + assertThat(measure(testFile2, CoreMetrics.TEST_FAILURES)).isEqualTo(1); + } + + @Test + public void testNoTestReport() { + DefaultInputFile testFile2 = TestInputFileBuilder.create("", FILE_SAMPLE2).build(); + fs.add(testFile2); + settings.setProperty(PythonXUnitSensor.REPORT_PATH_KEY, "xunit-reports/empty-xunit-result.xml"); + settings.setProperty(PythonXUnitSensor.SKIP_DETAILS, true); + sensor.execute(context); + + assertThat(context.measure(context.module().key(), CoreMetrics.TESTS)).isNull(); + } + + @Test + public void fallbackToTestsuiteName() { + DefaultInputFile testFile1 = TestInputFileBuilder.create("", FILE_SAMPLE1).build(); + fs.add(testFile1); + settings.setProperty(PythonXUnitSensor.REPORT_PATH_KEY, "xunit-reports/no-classname-xunit-result.xml"); + sensor.execute(context); + + assertThat(measure(testFile1, CoreMetrics.TESTS)).isEqualTo(2); + assertThat(measure(testFile1, CoreMetrics.SKIPPED_TESTS)).isEqualTo(0); + assertThat(measure(testFile1, CoreMetrics.TEST_ERRORS)).isEqualTo(0); + assertThat(measure(testFile1, CoreMetrics.TEST_FAILURES)).isEqualTo(1); + } + + @Test + public void malformedReport() { + DefaultInputFile testFile1 = TestInputFileBuilder.create("", FILE_SAMPLE1).build(); + fs.add(testFile1); + settings.setProperty(PythonXUnitSensor.REPORT_PATH_KEY, "xunit-reports/malformed-xunit-result.xml"); + sensor.execute(context); + + assertThat(measure(testFile1, CoreMetrics.TESTS)).isEqualTo(2); + assertThat(measure(testFile1, CoreMetrics.SKIPPED_TESTS)).isEqualTo(0); + assertThat(measure(testFile1, CoreMetrics.TEST_ERRORS)).isEqualTo(0); + assertThat(measure(testFile1, CoreMetrics.TEST_FAILURES)).isEqualTo(0); + } + private Integer moduleMeasure(Metric metric) { return measure(context.module(), metric); } diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/TestCaseTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/TestCaseTest.java index 217dc51ba6..ea155b4867 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/TestCaseTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/TestCaseTest.java @@ -32,11 +32,11 @@ public void rendersRightDetails() { Map ioMap = new HashMap(); ioMap.put("", - new TestCase("name", 1, "ok", "", "")); + new TestCase("name", 1, "ok", "", "", null, null)); ioMap.put("", - new TestCase("name", 1, "error", "stack", "errmsg")); + new TestCase("name", 1, "error", "stack", "errmsg", null, null)); ioMap.put("", - new TestCase("name", 1, "failure", "stack", "errmsg")); + new TestCase("name", 1, "failure", "stack", "errmsg","file", "testClassname")); for(Map.Entry entry: ioMap.entrySet()) { assertEquals(entry.getKey(), entry.getValue().getDetails()); diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/TestResultTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/TestResultTest.java new file mode 100644 index 0000000000..8d9449cab6 --- /dev/null +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/TestResultTest.java @@ -0,0 +1,113 @@ +/* + * SonarQube Python Plugin + * Copyright (C) 2011-2019 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.xunit; + +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TestResultTest { + + TestResult testResult; + + @Before + public void setUp() { + testResult = new TestResult(); + } + + @Test + public void newBornSuiteShouldHaveVirginStatistics() { + assertThat(testResult.getTests()).isEqualTo(0); + assertThat(testResult.getExecutedTests()).isEqualTo(0); + assertThat(testResult.getErrors()).isEqualTo(0); + assertThat(testResult.getFailures()).isEqualTo(0); + assertThat(testResult.getSkipped()).isEqualTo(0); + assertThat(testResult.getTime()).isEqualTo(0); + } + + @Test + public void addingTestCaseShouldIncrementStatistics() { + int testBefore = testResult.getTests(); + int timeBefore = testResult.getTime(); + + final int EXEC_TIME = 10; + testResult.addTestCase(createTestCase(EXEC_TIME, "status")); + + assertThat(testResult.getTests()).isEqualTo(testBefore + 1); + assertThat(testResult.getTime()).isEqualTo(timeBefore + EXEC_TIME); + } + + @Test + public void executedTestsValue() { + testResult.addTestCase(createTestCase(1, "ok")); + testResult.addTestCase(createTestCase(2, "skipped")); + testResult.addTestCase(createTestCase(3, "ok")); + testResult.addTestCase(createTestCase(4, "error")); + testResult.addTestCase(createTestCase(5, "skipped")); + + assertThat(testResult.getTests()).isEqualTo(5); + assertThat(testResult.getExecutedTests()).isEqualTo(3); + assertThat(testResult.getErrors()).isEqualTo(1); + assertThat(testResult.getFailures()).isEqualTo(0); + assertThat(testResult.getSkipped()).isEqualTo(2); + assertThat(testResult.getTime()).isEqualTo(15); + } + + private static TestCase createTestCase(int time, String status) { + return new TestCase("name", time, status, "stack", "msg", "file", "testClassname"); + } + + @Test + public void addingAnErroneousTestCaseShouldIncrementErrorStatistic() { + int errorsBefore = testResult.getErrors(); + TestCase error = mock(TestCase.class); + when(error.isError()).thenReturn(true); + + testResult.addTestCase(error); + + assertThat(testResult.getErrors()).isEqualTo(errorsBefore + 1); + } + + @Test + public void addingAFailedestCaseShouldIncrementFailedStatistic() { + int failedBefore = testResult.getFailures(); + TestCase failedTC = mock(TestCase.class); + when(failedTC.isFailure()).thenReturn(true); + + testResult.addTestCase(failedTC); + + assertThat(testResult.getFailures()).isEqualTo(failedBefore + 1); + } + + @Test + public void addingASkippedTestCaseShouldIncrementSkippedStatistic() { + int skippedBefore = testResult.getSkipped(); + TestCase skippedTC = mock(TestCase.class); + when(skippedTC.isSkipped()).thenReturn(true); + + testResult.addTestCase(skippedTC); + + assertThat(testResult.getSkipped()).isEqualTo(skippedBefore + 1); + } + +} diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/TestSuiteTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/TestSuiteTest.java index 5bd84ee9b2..f47109bd85 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/TestSuiteTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/xunit/TestSuiteTest.java @@ -19,120 +19,23 @@ */ package org.sonar.plugins.python.xunit; -import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class TestSuiteTest { - TestSuite suite; - TestSuite equalSuite; - TestSuite otherSuite; - - @Before - public void setUp() { - suite = new TestSuite("key"); - equalSuite = new TestSuite("key"); - otherSuite = new TestSuite("otherkey"); - } - - @Test - public void suiteDoesntEqualsNull() { - assertThat(suite).isNotEqualTo(null); - } - - @Test - public void suiteDoesntEqualsMiscObject() { - assertThat(suite).isNotEqualTo("string"); - } - - @Test - public void suiteEqualityIsReflexive() { - assertThat(suite).isEqualTo(suite); - assertThat(otherSuite).isEqualTo(otherSuite); - assertThat(equalSuite).isEqualTo(equalSuite); - } - - @Test - public void suiteEqualityWorksAsExpected() { - assertThat(suite).isEqualTo(equalSuite); - assertThat(suite).isNotEqualTo(otherSuite); - } - - @Test - public void suiteHashWorksAsExpected() { - assertThat(suite.hashCode()).isEqualTo(equalSuite.hashCode()); - assertThat(suite.hashCode()).isNotEqualTo(otherSuite.hashCode()); - } @Test - public void newBornSuiteShouldHaveVirginStatistics() { - assertThat(suite.getTests()).isEqualTo(0); - assertThat(suite.getErrors()).isEqualTo(0); - assertThat(suite.getFailures()).isEqualTo(0); - assertThat(suite.getSkipped()).isEqualTo(0); - assertThat(suite.getTime()).isEqualTo(0); + public void test() { + TestSuite suite = new TestSuite("key"); + assertThat(suite.getKey()).isEqualTo("key"); + assertThat(suite.getTestCases()).isEmpty(); assertThat(suite.getDetails()).isEqualTo(""); - } - - @Test - public void addingTestCaseShouldIncrementStatistics() { - int testBefore = suite.getTests(); - int timeBefore = suite.getTime(); - final int EXEC_TIME = 10; - suite.addTestCase(new TestCase("name", EXEC_TIME, "status", "stack", "msg")); - - assertThat(suite.getTests()).isEqualTo(testBefore + 1); - assertThat(suite.getTime()).isEqualTo(timeBefore + EXEC_TIME); - } - - @Test - public void addingAnErroneousTestCaseShouldIncrementErrorStatistic() { - int errorsBefore = suite.getErrors(); - TestCase error = mock(TestCase.class); - when(error.isError()).thenReturn(true); - - suite.addTestCase(error); - - assertThat(suite.getErrors()).isEqualTo(errorsBefore + 1); - } - - @Test - public void addingAFailedestCaseShouldIncrementFailedStatistic() { - int failedBefore = suite.getFailures(); - TestCase failedTC = mock(TestCase.class); - when(failedTC.isFailure()).thenReturn(true); - - suite.addTestCase(failedTC); - - assertThat(suite.getFailures()).isEqualTo(failedBefore + 1); - } - - @Test - public void addingASkippedTestCaseShouldIncrementSkippedStatistic() { - int skippedBefore = suite.getSkipped(); - TestCase skippedTC = mock(TestCase.class); - when(skippedTC.isSkipped()).thenReturn(true); - - suite.addTestCase(skippedTC); - - assertThat(suite.getSkipped()).isEqualTo(skippedBefore + 1); + TestCase testCase = new TestCase("name", 1, "status", "stack", "msg", "file", "testClassname"); + suite.addTestCase(testCase); + assertThat(suite.getTestCases()).containsExactly(testCase); + assertThat(suite.getDetails()).isEqualTo(""); } - @Test - public void addingAnotherTestSuiteShouldMaintainStatistics() { - TestCase tc = mock(TestCase.class); - when(tc.isSkipped()).thenReturn(true); - TestSuite ts1 = new TestSuite("1"); - TestSuite ts2 = new TestSuite("2"); - ts1.addTestCase(tc); - ts2.addTestCase(tc); - - TestSuite summedUp = ts1.addMeasures(ts2); - - assertThat(summedUp.getSkipped()).isEqualTo(2); - } } diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/xunit-reports/empty-xunit-result.xml b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/xunit-reports/empty-xunit-result.xml new file mode 100644 index 0000000000..7421e40aef --- /dev/null +++ b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/xunit-reports/empty-xunit-result.xml @@ -0,0 +1,3 @@ + + + diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/xunit-reports/malformed-xunit-result.xml b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/xunit-reports/malformed-xunit-result.xml new file mode 100644 index 0000000000..3443a2e6af --- /dev/null +++ b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/xunit-reports/malformed-xunit-result.xml @@ -0,0 +1,8 @@ + + + + + Failure message 1 more info + + + diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/xunit-reports/no-classname-xunit-result.xml b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/xunit-reports/no-classname-xunit-result.xml new file mode 100644 index 0000000000..e6736b6f03 --- /dev/null +++ b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/xunit-reports/no-classname-xunit-result.xml @@ -0,0 +1,9 @@ + + + + + + Failure message 1 more info + + + diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/xunit-reports/pytest-xunit-result.xml b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/xunit-reports/pytest-xunit-result.xml new file mode 100644 index 0000000000..a348062de6 --- /dev/null +++ b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/xunit-reports/pytest-xunit-result.xml @@ -0,0 +1,28 @@ + + + + + + Failure message 1 more info + + + + + + + + + + + more info + + + + + + + Failure message 2 more info + + + \ No newline at end of file