/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.maven.plugins.surefire.report;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
import org.apache.maven.plugins.surefire.report.ReportTestCase.FlakyError;
import org.apache.maven.plugins.surefire.report.ReportTestCase.FlakyFailure;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

/**
 * @author Kristian Rosenvold
 */
@SuppressWarnings({"checkstyle:magicnumber", "checkstyle:linelength"})
class TestSuiteXmlParserTest {
    private static final String[] LINE_PATTERNS = {"at org.apache.Test.", "at org.apache.Test$"};

    private final Collection<String> loggedErrors = new ArrayList<>();

    private ConsoleLogger consoleLogger;

    @BeforeEach
    void instantiateLogger() {
        consoleLogger = new ConsoleLogger() {
            @Override
            public boolean isDebugEnabled() {
                return true;
            }

            @Override
            public void debug(String message) {}

            @Override
            public boolean isInfoEnabled() {
                return true;
            }

            @Override
            public void info(String message) {}

            @Override
            public boolean isWarnEnabled() {
                return true;
            }

            @Override
            public void warning(String message) {
                loggedErrors.add(message);
            }

            @Override
            public boolean isErrorEnabled() {
                return true;
            }

            @Override
            public void error(String message) {
                loggedErrors.add(message);
            }

            @Override
            public void error(String message, Throwable t) {
                loggedErrors.add(message);
            }

            @Override
            public void error(Throwable t) {
                loggedErrors.add(t.getLocalizedMessage());
            }
        };
    }

    @AfterEach
    void verifyErrorFreeLogger() {
        assertThat(loggedErrors, is(empty()));
    }

    @Test
    void testParse() throws Exception {
        TestSuiteXmlParser testSuiteXmlParser = new TestSuiteXmlParser(consoleLogger);
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
                + "<testsuite failures=\"4\" time=\"0.00005\" errors=\"0\" skipped=\"0\" tests=\"4\" name=\"wellFormedXmlFailures.TestSurefire3\">\n"
                + "  <properties>\n"
                + "    <property name=\"java.runtime.name\" value=\"Java(TM) SE Runtime Environment\"/>\n"
                + "    <property name=\"sun.cpu.isalist\" value=\"amd64\"/>\n"
                + "  </properties>\n"
                + "  <testcase time=\"5E-3\" classname=\"wellFormedXmlFailures.TestSurefire3\" name=\"testLower\">\n"
                + "    <failure message=\"&lt;\" type=\"junit.framework.AssertionFailedError\"><![CDATA[junit.framework.AssertionFailedError: <\n"
                + "\tat junit.framework.Assert.fail(Assert.java:47)\n"
                + "\tat wellFormedXmlFailures.TestSurefire3.testLower(TestSurefire3.java:30)\n"
                + "]]></failure>\n"
                + "  </testcase>\n"
                + "  <testcase time=\"0\" classname=\"wellFormedXmlFailures.TestSurefire3\" name=\"testU0000\">\n"
                + "    <failure message=\"&amp;0#;\" type=\"junit.framework.AssertionFailedError\">junit.framework.AssertionFailedError:  \n"
                + "\tat junit.framework.Assert.fail(Assert.java:47)\n"
                + "\tat wellFormedXmlFailures.TestSurefire3.testU0000(TestSurefire3.java:40)\n"
                + "</failure>\n"
                + "  </testcase>\n"
                + "  <testcase time=\"0\" classname=\"wellFormedXmlFailures.TestSurefire3\" name=\"testGreater\">\n"
                + "    <failure message=\"&gt;\" type=\"junit.framework.AssertionFailedError\">junit.framework.AssertionFailedError: >\n"
                + "\tat junit.framework.Assert.fail(Assert.java:47)\n"
                + "\tat wellFormedXmlFailures.TestSurefire3.testGreater(TestSurefire3.java:35)\n"
                + "</failure>\n"
                + "  </testcase>\n"
                + "  <testcase time=\"0\" classname=\"wellFormedXmlFailures.TestSurefire3\" name=\"testQuote\">\n"
                + "    <failure message=\"&quot;\" type=\"junit.framework.AssertionFailedError\">junit.framework.AssertionFailedError: \"\n"
                + "\tat junit.framework.Assert.fail(Assert.java:47)\n"
                + "\tat wellFormedXmlFailures.TestSurefire3.testQuote(TestSurefire3.java:25)\n"
                + "</failure>\n"
                + "  </testcase>\n"
                + "</testsuite>";
        InputStream byteArrayIs = new ByteArrayInputStream(xml.getBytes());
        List<ReportTestSuite> parse = testSuiteXmlParser.parse(new InputStreamReader(byteArrayIs, UTF_8));
        assertThat(parse.size(), is(1));
        ReportTestSuite report = parse.get(0);
        assertThat(report.getFullClassName(), is("wellFormedXmlFailures.TestSurefire3"));
        assertThat(report.getName(), is("TestSurefire3"));
        assertThat(report.getPackageName(), is("wellFormedXmlFailures"));
        assertThat(report.getNumberOfTests(), is(4));
        assertThat(report.getNumberOfSkipped(), is(0));
        assertThat(report.getNumberOfErrors(), is(0));
        assertThat(report.getNumberOfFailures(), is(4));
        assertThat(report.getNumberOfFlakes(), is(0));
        assertThat(report.getTimeElapsed(), is(0.00005f));
        assertThat(report.getTestCases().size(), is(4));

        List<ReportTestCase> tests = report.getTestCases();
        assertThat(tests.get(0).getFullClassName(), is("wellFormedXmlFailures.TestSurefire3"));
        assertThat(tests.get(0).getName(), is("testLower"));
        assertThat(
                tests.get(0).getFailureDetail(),
                is("junit.framework.AssertionFailedError: <\n"
                        + "\tat junit.framework.Assert.fail(Assert.java:47)\n"
                        + "\tat wellFormedXmlFailures.TestSurefire3.testLower(TestSurefire3.java:30)\n"));
        assertThat(tests.get(0).getClassName(), is("TestSurefire3"));
        assertThat(tests.get(0).getTime(), is(0.005f));
        assertThat(tests.get(0).getFailureErrorLine(), is("30"));
        assertThat(tests.get(0).getFailureMessage(), is("<"));
        assertThat(tests.get(0).getFullName(), is("wellFormedXmlFailures.TestSurefire3.testLower"));
        assertThat(tests.get(0).getFailureType(), is("junit.framework.AssertionFailedError"));
        assertThat(tests.get(0).hasError(), is(false));

        assertThat(tests.get(1).getFullClassName(), is("wellFormedXmlFailures.TestSurefire3"));
        assertThat(tests.get(1).getName(), is("testU0000"));
        assertThat(
                tests.get(1).getFailureDetail(),
                is("junit.framework.AssertionFailedError:  \n" + "\tat junit.framework.Assert.fail(Assert.java:47)\n"
                        + "\tat wellFormedXmlFailures.TestSurefire3.testU0000(TestSurefire3.java:40)\n"));
        assertThat(tests.get(1).getClassName(), is("TestSurefire3"));
        assertThat(tests.get(1).getTime(), is(0f));
        assertThat(tests.get(1).getFailureErrorLine(), is("40"));
        assertThat(tests.get(1).getFailureMessage(), is("&0#;"));
        assertThat(tests.get(1).getFullName(), is("wellFormedXmlFailures.TestSurefire3.testU0000"));
        assertThat(tests.get(1).getFailureType(), is("junit.framework.AssertionFailedError"));
        assertThat(tests.get(1).hasError(), is(false));

        assertThat(tests.get(2).getFullClassName(), is("wellFormedXmlFailures.TestSurefire3"));
        assertThat(tests.get(2).getName(), is("testGreater"));
        assertThat(
                tests.get(2).getFailureDetail(),
                is("junit.framework.AssertionFailedError: >\n" + "\tat junit.framework.Assert.fail(Assert.java:47)\n"
                        + "\tat wellFormedXmlFailures.TestSurefire3.testGreater(TestSurefire3.java:35)\n"));
        assertThat(tests.get(2).getClassName(), is("TestSurefire3"));
        assertThat(tests.get(2).getTime(), is(0f));
        assertThat(tests.get(2).getFailureErrorLine(), is("35"));
        assertThat(tests.get(2).getFailureMessage(), is(">"));
        assertThat(tests.get(2).getFullName(), is("wellFormedXmlFailures.TestSurefire3.testGreater"));
        assertThat(tests.get(2).getFailureType(), is("junit.framework.AssertionFailedError"));
        assertThat(tests.get(2).hasError(), is(false));

        assertThat(tests.get(3).getFullClassName(), is("wellFormedXmlFailures.TestSurefire3"));
        assertThat(tests.get(3).getName(), is("testQuote"));
        assertThat(
                tests.get(3).getFailureDetail(),
                is("junit.framework.AssertionFailedError: \"\n" + "\tat junit.framework.Assert.fail(Assert.java:47)\n"
                        + "\tat wellFormedXmlFailures.TestSurefire3.testQuote(TestSurefire3.java:25)\n"));
        assertThat(tests.get(3).getClassName(), is("TestSurefire3"));
        assertThat(tests.get(3).getTime(), is(0f));
        assertThat(tests.get(3).getFailureErrorLine(), is("25"));
        assertThat(tests.get(3).getFailureMessage(), is("\""));
        assertThat(tests.get(3).getFullName(), is("wellFormedXmlFailures.TestSurefire3.testQuote"));
        assertThat(tests.get(3).getFailureType(), is("junit.framework.AssertionFailedError"));
        assertThat(tests.get(3).hasError(), is(false));
    }

    @Test
    void testParser() throws Exception {
        TestSuiteXmlParser parser = new TestSuiteXmlParser(consoleLogger);

        Collection<ReportTestSuite> oldResult = parser.parse(
                "src/test/resources/fixture/testsuitexmlparser/TEST-org.apache.maven.surefire.test.FailingTest.xml");

        assertNotNull(oldResult);

        assertEquals(1, oldResult.size());
        ReportTestSuite next = oldResult.iterator().next();
        assertEquals(2, next.getNumberOfTests());
    }

    @Test
    void successfulSurefireTestReport() throws Exception {
        TestSuiteXmlParser parser = new TestSuiteXmlParser(consoleLogger);
        File surefireReport = new File("src/test/resources/junit-pathWithÜmlaut/TEST-umlautTest.BasicTest.xml");
        assumeTrue(surefireReport.isFile());
        Collection<ReportTestSuite> suites = parser.parse(surefireReport.getCanonicalPath());
        assertNotNull(suites);
        assertEquals(1, suites.size());
        ReportTestSuite suite = suites.iterator().next();
        assertThat(suite.getNumberOfTests(), is(1));
        assertEquals(1, suite.getNumberOfTests());
        assertEquals(0, suite.getNumberOfFlakes());
        assertEquals(0, suite.getNumberOfFailures());
        assertEquals(0, suite.getNumberOfErrors());
        assertEquals(0, suite.getNumberOfSkipped());
        assertThat(suite.getTimeElapsed(), is(0.002f));
        assertThat(suite.getFullClassName(), is("umlautTest.BasicTest"));
        assertThat(suite.getPackageName(), is("umlautTest"));
        assertThat(suite.getName(), is("BasicTest"));
        ReportTestCase test = suite.getTestCases().iterator().next();
        assertTrue(test.isSuccessful());
        assertNull(test.getFailureDetail());
        assertNull(test.getFailureErrorLine());
        assertNull(test.getFailureType());
        assertThat(test.getTime(), is(0.002f));
        assertThat(test.getFullClassName(), is("umlautTest.BasicTest"));
        assertThat(test.getClassName(), is("BasicTest"));
        assertThat(test.getName(), is("testSetUp"));
        assertThat(test.getFullName(), is("umlautTest.BasicTest.testSetUp"));
    }

    @Test
    void testParserHitsFailsafeSummary() throws Exception {
        TestSuiteXmlParser parser = new TestSuiteXmlParser(consoleLogger);

        parser.parse("src/test/resources/fixture/testsuitexmlparser/failsafe-summary.xml");

        assertFalse(parser.isValid());

        parser.parse(
                "src/test/resources/fixture/testsuitexmlparser/TEST-org.apache.maven.surefire.test.FailingTest.xml");

        assertTrue(parser.isValid());
    }

    @Test
    void lastIndexOfPatternOfOrdinalTest() {
        final StringBuilder stackTrace = new StringBuilder("\tat org.apache.Test.util(Test.java:60)\n"
                + "\tat org.apache.Test.test(Test.java:30)\n" + "\tat com.sun.Impl.xyz(Impl.java:258)\n");

        int[] result = TestSuiteXmlParser.lastIndexOf(stackTrace, LINE_PATTERNS);
        assertThat(result[0], is(40));
        assertThat(result[1], is(0));
        String errorLine = TestSuiteXmlParser.parseErrorLine(stackTrace, "org.apache.Test");
        assertThat(errorLine, is("30"));
    }

    @Test
    void lastIndexOfPatternOfOrdinalTestWithCause() {
        final StringBuilder stackTrace = new StringBuilder(
                "\tat org.apache.Test.util(Test.java:60)\n" + "\tat org.apache.Test.test(Test.java:30)\n"
                        + "\tat com.sun.Impl.xyz(Impl.java:258)\n"
                        + "\tat Caused by: java.lang.IndexOutOfBoundsException\n"
                        + "\tat org.apache.Test.util(Test.java:70)\n");

        int[] result = TestSuiteXmlParser.lastIndexOf(stackTrace, LINE_PATTERNS);
        assertThat(result[0], is(40));
        assertThat(result[1], is(0));
        String errorLine = TestSuiteXmlParser.parseErrorLine(stackTrace, "org.apache.Test");
        assertThat(errorLine, is("30"));
    }

    @Test
    void lastIndexOfPatternOfEnclosedTest() {
        final StringBuilder source = new StringBuilder("\tat org.apache.Test.util(Test.java:60)\n"
                + "\tat org.apache.Test$Nested.test(Test.java:30)\n"
                + "\tat com.sun.Impl.xyz(Impl.java:258)\n");

        int[] result = TestSuiteXmlParser.lastIndexOf(source, LINE_PATTERNS);
        assertThat(result[0], is(40));
        assertThat(result[1], is(1));
        String errorLine = TestSuiteXmlParser.parseErrorLine(source, "org.apache.Test$Nested");
        assertThat(errorLine, is("30"));
    }

    @Test
    void lastIndexOfPatternOfEnclosedTestWithCause() {
        final StringBuilder source = new StringBuilder(
                "\tat org.apache.Test.util(Test.java:60)\n" + "\tat org.apache.Test$Nested.test(Test.java:30)\n"
                        + "\tat com.sun.Impl.xyz(Impl.java:258)\n"
                        + "\tat Caused by: java.lang.IndexOutOfBoundsException\n"
                        + "\tat org.apache.Test$Nested.util(Test.java:70)\n");

        int[] result = TestSuiteXmlParser.lastIndexOf(source, LINE_PATTERNS);
        assertThat(result[0], is(40));
        assertThat(result[1], is(1));
        String errorLine = TestSuiteXmlParser.parseErrorLine(source, "org.apache.Test$Nested");
        assertThat(errorLine, is("30"));
    }

    @Test
    void shouldParserEverythingInOrdinalTest() throws Exception {
        TestSuiteXmlParser parser = new TestSuiteXmlParser(consoleLogger);
        List<ReportTestSuite> tests =
                parser.parse("src/test/resources/fixture/testsuitexmlparser/TEST-surefire.MyTest.xml");
        assertTrue(parser.isValid());
        assertThat(tests.size(), is(1));
        assertThat(tests.get(0).getFullClassName(), is("surefire.MyTest"));
        assertThat(tests.get(0).getNumberOfErrors(), is(1));
        assertThat(tests.get(0).getNumberOfFlakes(), is(0));
        assertThat(tests.get(0).getNumberOfSkipped(), is(0));
        assertThat(tests.get(0).getNumberOfFailures(), is(0));
        assertThat(tests.get(0).getPackageName(), is("surefire"));
        assertThat(tests.get(0).getNumberOfTests(), is(1));
        assertThat(tests.get(0).getTestCases().size(), is(1));
        assertFalse(tests.get(0).getTestCases().get(0).isSuccessful());
        assertThat(tests.get(0).getTestCases().get(0).getFailureErrorLine(), is("13"));
        assertThat(tests.get(0).getTestCases().get(0).getFailureType(), is("java.lang.RuntimeException"));
        assertThat(tests.get(0).getTestCases().get(0).getFullClassName(), is("surefire.MyTest"));
        assertThat(tests.get(0).getTestCases().get(0).getClassName(), is("MyTest"));
        assertThat(tests.get(0).getTestCases().get(0).getName(), is("test"));
        assertThat(tests.get(0).getTestCases().get(0).getFullName(), is("surefire.MyTest.test"));
        assertThat(tests.get(0).getTestCases().get(0).getTime(), is(0.1f));
        assertThat(tests.get(0).getTestCases().get(0).getFailureMessage(), is("this is different message"));

        assertThat(
                tests.get(0).getTestCases().get(0).getFailureDetail(),
                is("java.lang.RuntimeException: java.lang.IndexOutOfBoundsException\n"
                        + "\tat surefire.MyTest.rethrownDelegate(MyTest.java:24)\n"
                        + "\tat surefire.MyTest.newRethrownDelegate(MyTest.java:17)\n"
                        + "\tat surefire.MyTest.test(MyTest.java:13)\n"
                        + "\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n"
                        + "\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)\n"
                        + "\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n"
                        + "\tat java.lang.reflect.Method.invoke(Method.java:606)\n"
                        + "\tat org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)\n"
                        + "\tat org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)\n"
                        + "\tat org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)\n"
                        + "\tat org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)\n"
                        + "\tat org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)\n"
                        + "\tat org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)\n"
                        + "\tat org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)\n"
                        + "\tat org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)\n"
                        + "\tat org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)\n"
                        + "\tat org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)\n"
                        + "\tat org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)\n"
                        + "\tat org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)\n"
                        + "\tat org.junit.runners.ParentRunner.run(ParentRunner.java:363)\n"
                        + "\tat org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:272)\n"
                        + "\tat org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:167)\n"
                        + "\tat org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:147)\n"
                        + "\tat org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:130)\n"
                        + "\tat org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:211)\n"
                        + "\tat org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:163)\n"
                        + "\tat org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:105)\n"
                        + "\tCaused by: java.lang.IndexOutOfBoundsException\n"
                        + "\tat surefire.MyTest.failure(MyTest.java:33)\n"
                        + "\tat surefire.MyTest.access$100(MyTest.java:9)\n"
                        + "\tat surefire.MyTest$Nested.run(MyTest.java:38)\n"
                        + "\tat surefire.MyTest.delegate(MyTest.java:29)\n"
                        + "\tat surefire.MyTest.rethrownDelegate(MyTest.java:22)"));
        assertThat(tests.get(0).getTestCases().get(0).hasError(), is(true));
    }

    @Test
    void shouldParserEverythingInEnclosedTest() throws Exception {
        TestSuiteXmlParser parser = new TestSuiteXmlParser(consoleLogger);
        List<ReportTestSuite> tests =
                parser.parse("src/test/resources/fixture/testsuitexmlparser/TEST-surefire.MyTest-enclosed.xml");
        assertTrue(parser.isValid());
        assertThat(tests.size(), is(1));
        assertThat(tests.get(0).getFullClassName(), is("surefire.MyTest$A"));
        assertThat(tests.get(0).getNumberOfErrors(), is(1));
        assertThat(tests.get(0).getNumberOfFlakes(), is(0));
        assertThat(tests.get(0).getNumberOfSkipped(), is(0));
        assertThat(tests.get(0).getNumberOfFailures(), is(0));
        assertThat(tests.get(0).getPackageName(), is("surefire"));
        assertThat(tests.get(0).getNumberOfTests(), is(1));
        assertThat(tests.get(0).getTestCases().size(), is(1));
        assertFalse(tests.get(0).getTestCases().get(0).isSuccessful());
        assertThat(tests.get(0).getTestCases().get(0).getFailureErrorLine(), is("45"));
        assertThat(tests.get(0).getTestCases().get(0).getFailureType(), is("java.lang.RuntimeException"));
        assertThat(tests.get(0).getTestCases().get(0).getFullClassName(), is("surefire.MyTest$A"));
        assertThat(tests.get(0).getTestCases().get(0).getClassName(), is("MyTest$A"));
        assertThat(tests.get(0).getTestCases().get(0).getName(), is("t"));
        assertThat(tests.get(0).getTestCases().get(0).getFullName(), is("surefire.MyTest$A.t"));
        assertThat(tests.get(0).getTestCases().get(0).getTime(), is(0f));

        assertThat(tests.get(0).getTestCases().get(0).getFailureMessage(), is("java.lang.IndexOutOfBoundsException"));

        assertThat(
                tests.get(0).getTestCases().get(0).getFailureDetail(),
                is("java.lang.RuntimeException: java.lang.IndexOutOfBoundsException\n"
                        + "\tat surefire.MyTest.rethrownDelegate(MyTest.java:24)\n"
                        + "\tat surefire.MyTest.newRethrownDelegate(MyTest.java:17)\n"
                        + "\tat surefire.MyTest.access$200(MyTest.java:9)\n"
                        + "\tat surefire.MyTest$A.t(MyTest.java:45)\n"
                        + "\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n"
                        + "\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)\n"
                        + "\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n"
                        + "\tat java.lang.reflect.Method.invoke(Method.java:606)\n"
                        + "\tat org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)\n"
                        + "\tat org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)\n"
                        + "\tat org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)\n"
                        + "\tat org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)\n"
                        + "\tat org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)\n"
                        + "\tat org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)\n"
                        + "\tat org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)\n"
                        + "\tat org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)\n"
                        + "\tat org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)\n"
                        + "\tat org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)\n"
                        + "\tat org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)\n"
                        + "\tat org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)\n"
                        + "\tat org.junit.runners.ParentRunner.run(ParentRunner.java:363)\n"
                        + "\tat org.junit.runners.Suite.runChild(Suite.java:128)\n"
                        + "\tat org.junit.runners.Suite.runChild(Suite.java:27)\n"
                        + "\tat org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)\n"
                        + "\tat org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)\n"
                        + "\tat org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)\n"
                        + "\tat org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)\n"
                        + "\tat org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)\n"
                        + "\tat org.junit.runners.ParentRunner.run(ParentRunner.java:363)\n"
                        + "\tat org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:272)\n"
                        + "\tat org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:167)\n"
                        + "\tat org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:147)\n"
                        + "\tat org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:130)\n"
                        + "\tat org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:211)\n"
                        + "\tat org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:163)\n"
                        + "\tat org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:105)\n"
                        + "\tCaused by: java.lang.IndexOutOfBoundsException\n"
                        + "\tat surefire.MyTest.failure(MyTest.java:33)\n"
                        + "\tat surefire.MyTest.access$100(MyTest.java:9)\n"
                        + "\tat surefire.MyTest$Nested.run(MyTest.java:38)\n"
                        + "\tat surefire.MyTest.delegate(MyTest.java:29)\n"
                        + "\tat surefire.MyTest.rethrownDelegate(MyTest.java:22)\n"));
        assertThat(tests.get(0).getTestCases().get(0).hasError(), is(true));
    }

    @Test
    void shouldParserEverythingInEnclosedTrimStackTraceTest() throws Exception {
        TestSuiteXmlParser parser = new TestSuiteXmlParser(consoleLogger);
        List<ReportTestSuite> tests = parser.parse(
                "src/test/resources/fixture/testsuitexmlparser/TEST-surefire.MyTest-enclosed-trimStackTrace.xml");
        assertTrue(parser.isValid());
        assertThat(tests.size(), is(1));
        assertThat(tests.get(0).getFullClassName(), is("surefire.MyTest$A"));
        assertThat(tests.get(0).getNumberOfErrors(), is(1));
        assertThat(tests.get(0).getNumberOfFlakes(), is(0));
        assertThat(tests.get(0).getNumberOfSkipped(), is(0));
        assertThat(tests.get(0).getNumberOfFailures(), is(0));
        assertThat(tests.get(0).getPackageName(), is("surefire"));
        assertThat(tests.get(0).getNumberOfTests(), is(1));
        assertThat(tests.get(0).getTestCases().size(), is(1));
        assertFalse(tests.get(0).getTestCases().get(0).isSuccessful());
        assertThat(tests.get(0).getTestCases().get(0).getFailureErrorLine(), is("45"));
        assertThat(tests.get(0).getTestCases().get(0).getFailureType(), is("java.lang.RuntimeException"));
        assertThat(tests.get(0).getTestCases().get(0).getFullClassName(), is("surefire.MyTest$A"));
        assertThat(tests.get(0).getTestCases().get(0).getClassName(), is("MyTest$A"));
        assertThat(tests.get(0).getTestCases().get(0).getName(), is("t"));
        assertThat(tests.get(0).getTestCases().get(0).getFullName(), is("surefire.MyTest$A.t"));
        assertThat(tests.get(0).getTestCases().get(0).getTime(), is(0f));

        assertThat(tests.get(0).getTestCases().get(0).getFailureMessage(), is("java.lang.IndexOutOfBoundsException"));

        assertThat(
                tests.get(0).getTestCases().get(0).getFailureDetail(),
                is("java.lang.RuntimeException: java.lang.IndexOutOfBoundsException\n"
                        + "\tat surefire.MyTest.failure(MyTest.java:33)\n"
                        + "\tat surefire.MyTest.access$100(MyTest.java:9)\n"
                        + "\tat surefire.MyTest$Nested.run(MyTest.java:38)\n"
                        + "\tat surefire.MyTest.delegate(MyTest.java:29)\n"
                        + "\tat surefire.MyTest.rethrownDelegate(MyTest.java:22)\n"
                        + "\tat surefire.MyTest.newRethrownDelegate(MyTest.java:17)\n"
                        + "\tat surefire.MyTest.access$200(MyTest.java:9)\n"
                        + "\tat surefire.MyTest$A.t(MyTest.java:45)\n"));
        assertThat(tests.get(0).getTestCases().get(0).hasError(), is(true));
    }

    @Test
    void shouldParserEverythingInNestedClassTest() throws Exception {
        TestSuiteXmlParser parser = new TestSuiteXmlParser(consoleLogger);
        List<ReportTestSuite> tests =
                parser.parse("src/test/resources/fixture/testsuitexmlparser/TEST-surefire.MyTest-nestedClass.xml");
        assertTrue(parser.isValid());
        assertThat(tests.size(), is(1));
        assertThat(tests.get(0).getFullClassName(), is("surefire.MyTest"));
        assertThat(tests.get(0).getNumberOfErrors(), is(1));
        assertThat(tests.get(0).getNumberOfFlakes(), is(0));
        assertThat(tests.get(0).getNumberOfSkipped(), is(0));
        assertThat(tests.get(0).getNumberOfFailures(), is(0));
        assertThat(tests.get(0).getPackageName(), is("surefire"));
        assertThat(tests.get(0).getNumberOfTests(), is(1));
        assertThat(tests.get(0).getTestCases().size(), is(1));
        assertFalse(tests.get(0).getTestCases().get(0).isSuccessful());
        assertThat(tests.get(0).getTestCases().get(0).getFailureErrorLine(), is("13"));
        assertThat(tests.get(0).getTestCases().get(0).getFailureType(), is("java.lang.RuntimeException"));
        assertThat(tests.get(0).getTestCases().get(0).getFullClassName(), is("surefire.MyTest"));
        assertThat(tests.get(0).getTestCases().get(0).getClassName(), is("MyTest"));
        assertThat(tests.get(0).getTestCases().get(0).getName(), is("test"));
        assertThat(tests.get(0).getTestCases().get(0).getFullName(), is("surefire.MyTest.test"));
        assertThat(tests.get(0).getTestCases().get(0).getTime(), is(0f));

        assertThat(tests.get(0).getTestCases().get(0).getFailureMessage(), is("java.lang.IndexOutOfBoundsException"));

        assertThat(
                tests.get(0).getTestCases().get(0).getFailureDetail(),
                is("java.lang.RuntimeException: java.lang.IndexOutOfBoundsException\n"
                        + "\tat surefire.MyTest.rethrownDelegate(MyTest.java:24)\n"
                        + "\tat surefire.MyTest.newRethrownDelegate(MyTest.java:17)\n"
                        + "\tat surefire.MyTest.test(MyTest.java:13)\n"
                        + "\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n"
                        + "\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)\n"
                        + "\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n"
                        + "\tat java.lang.reflect.Method.invoke(Method.java:606)\n"
                        + "\tat org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)\n"
                        + "\tat org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)\n"
                        + "\tat org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)\n"
                        + "\tat org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)\n"
                        + "\tat org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)\n"
                        + "\tat org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)\n"
                        + "\tat org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)\n"
                        + "\tat org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)\n"
                        + "\tat org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)\n"
                        + "\tat org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)\n"
                        + "\tat org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)\n"
                        + "\tat org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)\n"
                        + "\tat org.junit.runners.ParentRunner.run(ParentRunner.java:363)\n"
                        + "\tat org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:272)\n"
                        + "\tat org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:167)\n"
                        + "\tat org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:147)\n"
                        + "\tat org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:130)\n"
                        + "\tat org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:211)\n"
                        + "\tat org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:163)\n"
                        + "\tat org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:105)\n"
                        + "\tCaused by: java.lang.IndexOutOfBoundsException\n"
                        + "\tat surefire.MyTest.failure(MyTest.java:33)\n"
                        + "\tat surefire.MyTest.access$100(MyTest.java:9)\n"
                        + "\tat surefire.MyTest$Nested.run(MyTest.java:38)\n"
                        + "\tat surefire.MyTest.delegate(MyTest.java:29)\n"
                        + "\tat surefire.MyTest.rethrownDelegate(MyTest.java:22)"));
        assertThat(tests.get(0).getTestCases().get(0).hasError(), is(true));
    }

    @Test
    void shouldParserEverythingInNestedClassTrimStackTraceTest() throws Exception {
        TestSuiteXmlParser parser = new TestSuiteXmlParser(consoleLogger);
        List<ReportTestSuite> tests = parser.parse(
                "src/test/resources/fixture/testsuitexmlparser/TEST-surefire.MyTest-nestedClass-trimStackTrace.xml");
        assertTrue(parser.isValid());
        assertThat(tests.size(), is(1));
        assertThat(tests.get(0).getFullClassName(), is("surefire.MyTest"));
        assertThat(tests.get(0).getNumberOfErrors(), is(1));
        assertThat(tests.get(0).getNumberOfFlakes(), is(0));
        assertThat(tests.get(0).getNumberOfSkipped(), is(0));
        assertThat(tests.get(0).getNumberOfFailures(), is(0));
        assertThat(tests.get(0).getPackageName(), is("surefire"));
        assertThat(tests.get(0).getNumberOfTests(), is(1));
        assertThat(tests.get(0).getTestCases().size(), is(1));
        assertFalse(tests.get(0).getTestCases().get(0).isSuccessful());
        assertThat(tests.get(0).getTestCases().get(0).getFailureErrorLine(), is("13"));
        assertThat(tests.get(0).getTestCases().get(0).getFailureType(), is("java.lang.RuntimeException"));
        assertThat(tests.get(0).getTestCases().get(0).getFullClassName(), is("surefire.MyTest"));
        assertThat(tests.get(0).getTestCases().get(0).getClassName(), is("MyTest"));
        assertThat(tests.get(0).getTestCases().get(0).getName(), is("test"));
        assertThat(tests.get(0).getTestCases().get(0).getFullName(), is("surefire.MyTest.test"));
        assertThat(tests.get(0).getTestCases().get(0).getTime(), is(0f));

        assertThat(tests.get(0).getTestCases().get(0).getFailureMessage(), is("java.lang.IndexOutOfBoundsException"));

        assertThat(
                tests.get(0).getTestCases().get(0).getFailureDetail(),
                is("java.lang.RuntimeException: java.lang.IndexOutOfBoundsException\n"
                        + "\tat surefire.MyTest.rethrownDelegate(MyTest.java:24)\n"
                        + "\tat surefire.MyTest.newRethrownDelegate(MyTest.java:17)\n"
                        + "\tat surefire.MyTest.test(MyTest.java:13)\n"
                        + "\tCaused by: java.lang.IndexOutOfBoundsException\n"
                        + "\tat surefire.MyTest.failure(MyTest.java:33)\n"
                        + "\tat surefire.MyTest.access$100(MyTest.java:9)\n"
                        + "\tat surefire.MyTest$Nested.run(MyTest.java:38)\n"
                        + "\tat surefire.MyTest.delegate(MyTest.java:29)\n"
                        + "\tat surefire.MyTest.rethrownDelegate(MyTest.java:22)"));
        assertThat(tests.get(0).getTestCases().get(0).hasError(), is(true));
    }

    @Test
    void shouldTestNotBlank() {
        assertFalse(TestSuiteXmlParser.isNotBlank(1, 2, ' ', ' ', ' ', '\n'));
        assertFalse(TestSuiteXmlParser.isNotBlank(1, 2, ' ', '\t', ' ', '\n'));
        assertFalse(TestSuiteXmlParser.isNotBlank(1, 2, ' ', ' ', '\r', '\n'));
        assertFalse(TestSuiteXmlParser.isNotBlank(1, 2, ' ', ' ', '\f', '\n'));
        assertTrue(TestSuiteXmlParser.isNotBlank(1, 2, ' ', 'a', ' ', '\n'));
        assertTrue(TestSuiteXmlParser.isNotBlank(1, 2, ' ', ' ', 'a', '\n'));
        assertTrue(TestSuiteXmlParser.isNotBlank(1, 2, ' ', 'a', 'b', '\n'));
    }

    @Test
    void shouldTestIsNumeric() {
        assertFalse(TestSuiteXmlParser.isNumeric(new StringBuilder("0?5142"), 1, 3));
        assertTrue(TestSuiteXmlParser.isNumeric(new StringBuilder("0?51M2"), 2, 4));
        assertFalse(TestSuiteXmlParser.isNumeric(new StringBuilder("0?51M2"), 2, 5));
    }

    @Test
    void shouldParseLargeFile() throws Exception {
        // Create test file
        Path tempFile = Files.createTempFile("largeReport", ".xml");

        try (BufferedWriter w = Files.newBufferedWriter(tempFile)) {
            w.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
                    + "<testsuite failures=\"1\" time=\"2.413\" errors=\"0\" skipped=\"0\" tests=\"2\" name=\"largeFile.TestSurefire3\">\n"
                    + "  <properties></properties>\n"
                    + "  <testcase time=\"0.005\" classname=\"largeFile.TestSurefire3\" name=\"testSuccess\" />\n"
                    + "  <testcase time=\"2.20\" classname=\"largeFile.TestSurefire3\" name=\"testFailure\">\n"
                    + "    <failure message=\"test failure\" type=\"junit.framework.AssertionFailedError\">junit.framework.AssertionFailedError:  \n"
                    + "\tat junit.framework.Assert.fail(Assert.java:47)\n"
                    + "\tat largeFile.TestSurefire3.testFailure(TestSurefire3.java:40)\n"
                    + "</failure>\n"
                    + "    <system-err><![CDATA[\n");

            // Adding CDATA which should be ignored during parsing and not cause memory issues
            for (int i = 0; i < 100000; i++) {
                w.write(
                        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n");
                w.flush();
            }
            w.write("]]></system-err>\n" + "  </testcase>\n" + "</testsuite>\n");
            w.flush();
        }

        System.err.println(Files.size(tempFile));

        // Parse test file
        TestSuiteXmlParser testSuiteXmlParser = new TestSuiteXmlParser(consoleLogger);

        try (InputStreamReader is = new InputStreamReader(Files.newInputStream(tempFile), UTF_8)) {
            List<ReportTestSuite> parse = testSuiteXmlParser.parse(is);

            assertThat(parse.size(), is(1));
            ReportTestSuite report = parse.get(0);
            assertThat(report.getFullClassName(), is("largeFile.TestSurefire3"));
            assertThat(report.getName(), is("TestSurefire3"));
            assertThat(report.getPackageName(), is("largeFile"));
            assertThat(report.getNumberOfTests(), is(2));
            assertThat(report.getNumberOfSkipped(), is(0));
            assertThat(report.getNumberOfErrors(), is(0));
            assertThat(report.getNumberOfFailures(), is(1));
            assertThat(report.getNumberOfFlakes(), is(0));
            assertThat(report.getTimeElapsed(), is(2.413f));
            assertThat(report.getTestCases().size(), is(2));

            List<ReportTestCase> tests = report.getTestCases();
            assertThat(tests.get(0).getFullClassName(), is("largeFile.TestSurefire3"));
            assertThat(tests.get(0).getName(), is("testSuccess"));
            assertNull(tests.get(0).getFailureDetail());
            assertThat(tests.get(0).getClassName(), is("TestSurefire3"));
            assertThat(tests.get(0).getTime(), is(0.005f));
            assertThat(tests.get(0).getFullName(), is("largeFile.TestSurefire3.testSuccess"));
            assertThat(tests.get(0).hasError(), is(false));

            assertThat(tests.get(1).getFullClassName(), is("largeFile.TestSurefire3"));
            assertThat(tests.get(1).getName(), is("testFailure"));
            assertThat(
                    tests.get(1).getFailureDetail(),
                    is("junit.framework.AssertionFailedError:  \n"
                            + "\tat junit.framework.Assert.fail(Assert.java:47)\n"
                            + "\tat largeFile.TestSurefire3.testFailure(TestSurefire3.java:40)\n"));
            assertThat(tests.get(1).getClassName(), is("TestSurefire3"));
            assertThat(tests.get(1).getTime(), is(2.20f));
            assertThat(tests.get(1).getFailureErrorLine(), is("40"));
            assertThat(tests.get(1).getFailureMessage(), is("test failure"));
            assertThat(tests.get(1).getFullName(), is("largeFile.TestSurefire3.testFailure"));
            assertThat(tests.get(1).getFailureType(), is("junit.framework.AssertionFailedError"));
            assertThat(tests.get(1).hasError(), is(false));
        }

        // Delete test file
        Files.delete(tempFile);
    }

    @Test
    void shouldParseFlakes() throws Exception {
        // Parse test file
        TestSuiteXmlParser parser = new TestSuiteXmlParser(consoleLogger);

        List<ReportTestSuite> testSuites =
                parser.parse("src/test/resources/fixture/testsuitexmlparser/TEST-org.acme.FlakyTest.xml");
        assertTrue(parser.isValid());
        assertThat(testSuites.size(), is(1));

        assertThat(testSuites.size(), is(1));
        ReportTestSuite report = testSuites.get(0);
        assertThat(report.getFullClassName(), is("org.acme.FlakyTest"));
        assertThat(report.getName(), is("FlakyTest"));
        assertThat(report.getPackageName(), is("org.acme"));
        assertThat(report.getNumberOfTests(), is(2));
        assertThat(report.getNumberOfSkipped(), is(0));
        assertThat(report.getNumberOfErrors(), is(0));
        assertThat(report.getNumberOfFailures(), is(0));
        assertThat(report.getNumberOfFlakes(), is(4));
        assertThat(report.getTimeElapsed(), is(1.324f));
        assertThat(report.getTestCases().size(), is(2));

        List<ReportTestCase> tests = report.getTestCases();
        assertThat(tests.get(0).getFullClassName(), is("org.acme.FlakyTest"));
        assertThat(tests.get(0).getName(), is("testFlaky"));
        assertNull(tests.get(0).getFailureDetail());
        assertThat(tests.get(0).getClassName(), is("FlakyTest"));
        assertThat(tests.get(0).getTime(), is(0.034f));
        assertThat(tests.get(0).getFullName(), is("org.acme.FlakyTest.testFlaky"));
        assertThat(tests.get(0).hasError(), is(false));
        assertThat(tests.get(0).hasFlakes(), is(true));

        List<FlakyFailure> flakyFailures = tests.get(0).getFlakyFailures();
        assertThat(flakyFailures.size(), is(3));

        assertThat(flakyFailures.get(0).getMessage(), startsWith("expected: <1> but was: <0>"));
        assertThat(flakyFailures.get(0).getType(), is("org.opentest4j.AssertionFailedError"));
        assertThat(
                flakyFailures.get(0).getStackTrace(),
                containsString("at org.acme.FlakyTest.testFlaky(FlakyTest.java:18)"));

        assertThat(flakyFailures.get(1).getMessage(), startsWith("expected: <1> but was: <3>"));
        assertThat(flakyFailures.get(1).getType(), is("org.opentest4j.AssertionFailedError"));
        assertThat(
                flakyFailures.get(1).getStackTrace(),
                containsString("at org.acme.FlakyTest.testFlaky(FlakyTest.java:18)"));

        assertThat(flakyFailures.get(2).getMessage(), startsWith("expected: <1> but was: <4>"));
        assertThat(flakyFailures.get(2).getType(), is("org.opentest4j.AssertionFailedError"));
        assertThat(
                flakyFailures.get(2).getStackTrace(),
                containsString("at org.acme.FlakyTest.testFlaky(FlakyTest.java:18)"));

        List<FlakyError> flakyErrors = tests.get(0).getFlakyErrors();
        assertThat(flakyErrors.size(), is(1));
        assertThat(flakyErrors.get(0).getMessage(), startsWith("expected: <1> but was: <0>"));
        assertThat(flakyErrors.get(0).getType(), is("org.opentest4j.AssertionFailedError"));
        assertThat(
                flakyErrors.get(0).getStackTrace(),
                containsString("at org.acme.FlakyTest.testFlaky(FlakyTest.java:18)"));

        assertThat(tests.get(1).getFullClassName(), is("org.acme.FlakyTest"));
        assertThat(tests.get(1).getName(), is("testStable"));
        assertThat(tests.get(1).getClassName(), is("FlakyTest"));
        assertThat(tests.get(1).getTime(), is(0.001f));
        assertThat(tests.get(1).getFullName(), is("org.acme.FlakyTest.testStable"));
        assertThat(tests.get(1).hasError(), is(false));
        assertThat(tests.get(1).hasFlakes(), is(false));
    }
}
