diff --git a/Directory.Build.props b/Directory.Build.props index e30d562..c62a972 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ $(MSBuildThisFileDirectory) - 6.0.0 + 7.0.0 diff --git a/src/JUnit.Xml.TestLogger/JunitXmlSerializer.cs b/src/JUnit.Xml.TestLogger/JunitXmlSerializer.cs index 892c12f..835049d 100644 --- a/src/JUnit.Xml.TestLogger/JunitXmlSerializer.cs +++ b/src/JUnit.Xml.TestLogger/JunitXmlSerializer.cs @@ -298,12 +298,26 @@ private XElement CreateTestSuiteElement(List results, TestRunCon // Adding required properties, system-out, and system-err elements in the correct // positions as required by the xsd. In system-out collapse consecutive newlines to a // single newline. - var element = new XElement( + var suiteElement = new XElement( "testsuite", new XElement("properties"), - testCaseElements, - new XElement("system-out", new XCData(stdOut.ToString())), - new XElement("system-err", new XCData(stdErr.ToString()))); + testCaseElements); + + // Add system-out and system-err elements only if they have content + var systemOutContent = this.InputSanitizer.Sanitize(stdOut.ToString()); + var systemErrContent = this.InputSanitizer.Sanitize(stdErr.ToString()); + + if (!string.IsNullOrWhiteSpace(systemOutContent)) + { + suiteElement.Add(new XElement("system-out", systemOutContent)); + } + + if (!string.IsNullOrWhiteSpace(systemErrContent)) + { + suiteElement.Add(new XElement("system-err", systemErrContent)); + } + + var element = suiteElement; element.SetAttributeValue("name", Path.GetFileName(results.First().AssemblyPath)); @@ -410,12 +424,12 @@ private XElement CreateTestCaseElement(TestResultInfo result) if (!string.IsNullOrWhiteSpace(stdOut.ToString())) { - testcaseElement.Add(new XElement("system-out", new XCData(stdOut.ToString()))); + testcaseElement.Add(new XElement("system-out", this.InputSanitizer.Sanitize(stdOut.ToString()))); } if (!string.IsNullOrWhiteSpace(stdErr.ToString())) { - testcaseElement.Add(new XElement("system-err", new XCData(stdErr.ToString()))); + testcaseElement.Add(new XElement("system-err", this.InputSanitizer.Sanitize(stdErr.ToString()))); } testcaseElement.Add(CreatePropertiesElement(result)); diff --git a/src/NUnit.Xml.TestLogger/NUnitXmlSerializer.cs b/src/NUnit.Xml.TestLogger/NUnitXmlSerializer.cs index 5b0ec77..fe4e499 100644 --- a/src/NUnit.Xml.TestLogger/NUnitXmlSerializer.cs +++ b/src/NUnit.Xml.TestLogger/NUnitXmlSerializer.cs @@ -242,6 +242,7 @@ private static TestSuite CreateFixture(IGrouping results private static XElement CreateTestCaseElement(TestResultInfo result) { + var sanitizer = new InputSanitizerXml(); var element = new XElement( "test-case", new XAttribute("name", result.DisplayName), @@ -267,7 +268,7 @@ private static XElement CreateTestCaseElement(TestResultInfo result) if (!string.IsNullOrWhiteSpace(stdOut.ToString())) { - element.Add(new XElement("output", new XCData(stdOut.ToString()))); + element.Add(new XElement("output", sanitizer.Sanitize(stdOut.ToString()))); } if (result.Outcome == TestOutcome.Failed) diff --git a/src/Xunit.Xml.TestLogger/XunitXmlSerializer.cs b/src/Xunit.Xml.TestLogger/XunitXmlSerializer.cs index ad0aadf..5a85036 100644 --- a/src/Xunit.Xml.TestLogger/XunitXmlSerializer.cs +++ b/src/Xunit.Xml.TestLogger/XunitXmlSerializer.cs @@ -248,6 +248,7 @@ private static bool IsError(TestResultInfo result) private static XElement CreateTestElement(TestResultInfo result) { + var sanitizer = new InputSanitizerXml(); var element = new XElement( "test", new XAttribute("name", result.DisplayName), @@ -266,7 +267,7 @@ private static XElement CreateTestElement(TestResultInfo result) else if (m.Category == "skipReason") { // Using the self-defined category skipReason for now - element.Add(new XElement("reason", new XCData(m.Text))); + element.Add(new XElement("reason", sanitizer.Sanitize(m.Text))); } } diff --git a/test/JUnit.Xml.TestLogger.AcceptanceTests/JUnitTestLoggerStoreConsoleOutputOptionsAcceptanceTests.cs b/test/JUnit.Xml.TestLogger.AcceptanceTests/JUnitTestLoggerStoreConsoleOutputOptionsAcceptanceTests.cs index ceabb89..c29c420 100644 --- a/test/JUnit.Xml.TestLogger.AcceptanceTests/JUnitTestLoggerStoreConsoleOutputOptionsAcceptanceTests.cs +++ b/test/JUnit.Xml.TestLogger.AcceptanceTests/JUnitTestLoggerStoreConsoleOutputOptionsAcceptanceTests.cs @@ -128,7 +128,7 @@ public void StoreConsoleOutput_False_DoesNotContainConsoleOut() Assert.DoesNotContain("{998AC9EC-7429-42CD-AD55-72037E7AF3D8}", passedTestCaseStdOutNode.Value); var testSuiteStdOutNode = resultsXml.XPathSelectElement("/testsuites/testsuite/system-out"); - Assert.IsTrue(testSuiteStdOutNode.Value.Equals(string.Empty)); + Assert.IsNull(testSuiteStdOutNode); TestCaseShouldHaveAttachmentInStandardOut(resultsXml, "PassTest11"); } @@ -149,7 +149,7 @@ public void StoreConsoleOutput_False_DoesNotContainConsoleErr() Assert.IsNull(failedTestCaseStdErrNode); var testSuiteStdErrNode = resultsXml.XPathSelectElement("/testsuites/testsuite/system-err"); - Assert.IsTrue(testSuiteStdErrNode.Value.Equals(string.Empty)); + Assert.IsNull(testSuiteStdErrNode); } [TestMethod] @@ -214,7 +214,7 @@ public void StoreConsoleOutput_TestCase_ContainsConsoleOutOnlyForTestCase() Assert.Contains("{998AC9EC-7429-42CD-AD55-72037E7AF3D8}", passedTestCaseStdOutNode.Value); var testSuiteStdOutNode = resultsXml.XPathSelectElement("/testsuites/testsuite/system-out"); - Assert.IsTrue(testSuiteStdOutNode.Value.Equals(string.Empty)); + Assert.IsNull(testSuiteStdOutNode); TestCaseShouldHaveAttachmentInStandardOut(resultsXml, "PassTest11"); } @@ -236,7 +236,7 @@ public void StoreConsoleOutput_TestCase_ContainsConsoleErrOnlyForTestCase() Assert.Contains("{33F5FD22-6F40-499D-98E4-481D87FAEAA1}", failedTestCaseStdErrNode.Value); var testSuiteStdErrNode = resultsXml.XPathSelectElement("/testsuites/testsuite/system-err"); - Assert.IsTrue(testSuiteStdErrNode.Value.Equals(string.Empty)); + Assert.IsNull(testSuiteStdErrNode); } private static void TestCaseShouldHaveAttachmentInStandardOut(XDocument resultsXml, string testcaseName) diff --git a/test/JUnit.Xml.TestLogger.UnitTests/JUnitXmlTestSerializerTests.cs b/test/JUnit.Xml.TestLogger.UnitTests/JUnitXmlTestSerializerTests.cs index e500adb..c2076b7 100644 --- a/test/JUnit.Xml.TestLogger.UnitTests/JUnitXmlTestSerializerTests.cs +++ b/test/JUnit.Xml.TestLogger.UnitTests/JUnitXmlTestSerializerTests.cs @@ -65,7 +65,7 @@ public void CreateTestSuiteShouldGroupTestSuitesByName() } [TestMethod] - public void TestCaseSystemOutShouldUseCDATA() + public void TestCaseSystemOutShouldBeSanitized() { var serializer = new JunitXmlSerializer(); var result = CreateTestResultInfo( @@ -79,7 +79,7 @@ public void TestCaseSystemOutShouldUseCDATA() } [TestMethod] - public void TestCaseSystemErrShouldUseCDATA() + public void TestCaseSystemErrShouldBeSanitized() { var serializer = new JunitXmlSerializer(); var result = CreateTestResultInfo( @@ -93,7 +93,7 @@ public void TestCaseSystemErrShouldUseCDATA() } [TestMethod] - public void TestSuiteSystemOutShouldUseCDATA() + public void TestSuiteSystemOutShouldBeSanitized() { var serializer = new JunitXmlSerializer(); var results = new List { CreateTestResultInfo() }; @@ -112,13 +112,12 @@ public void TestSuiteSystemOutShouldUseCDATA() var systemOutElement = doc.XPathSelectElement("//testsuite/system-out"); Assert.IsNotNull(systemOutElement); - Assert.IsTrue(systemOutElement.FirstNode is XCData); - var cdata = (XCData)systemOutElement.FirstNode; - StringAssert.Contains(cdata.Value, "Framework info with & characters"); + Assert.IsTrue(systemOutElement.FirstNode is System.Xml.Linq.XText); + StringAssert.Contains(systemOutElement.Value, "Framework info with & characters"); } [TestMethod] - public void TestSuiteSystemErrShouldUseCDATA() + public void TestSuiteSystemErrShouldBeSanitized() { var serializer = new JunitXmlSerializer(); var results = new List { CreateTestResultInfo() }; @@ -137,9 +136,92 @@ public void TestSuiteSystemErrShouldUseCDATA() var systemErrElement = doc.XPathSelectElement("//testsuite/system-err"); Assert.IsNotNull(systemErrElement); - Assert.IsTrue(systemErrElement.FirstNode is XCData); - var cdata = (XCData)systemErrElement.FirstNode; - StringAssert.Contains(cdata.Value, "Error - Error message with & characters"); + Assert.IsTrue(systemErrElement.FirstNode is System.Xml.Linq.XText); + StringAssert.Contains(systemErrElement.Value, "Error - Error message with & characters"); + } + + [TestMethod] + public void TestSuiteShouldNotIncludeEmptySystemOutElement() + { + var serializer = new JunitXmlSerializer(); + var results = new List { CreateTestResultInfo() }; + var messages = new List(); // No messages + + var xml = serializer.Serialize( + CreateTestLoggerConfiguration(), + CreateTestRunConfiguration(), + results, + messages); + + var doc = XDocument.Parse(xml); + var systemOutElement = doc.XPathSelectElement("//testsuite/system-out"); + + Assert.IsNull(systemOutElement, "Empty system-out element should not be included in test suite"); + } + + [TestMethod] + public void TestSuiteShouldNotIncludeEmptySystemErrElement() + { + var serializer = new JunitXmlSerializer(); + var results = new List { CreateTestResultInfo() }; + var messages = new List(); // No messages + + var xml = serializer.Serialize( + CreateTestLoggerConfiguration(), + CreateTestRunConfiguration(), + results, + messages); + + var doc = XDocument.Parse(xml); + var systemErrElement = doc.XPathSelectElement("//testsuite/system-err"); + + Assert.IsNull(systemErrElement, "Empty system-err element should not be included in test suite"); + } + + [TestMethod] + public void TestSuiteShouldIncludeSystemOutElementWhenContentExists() + { + var serializer = new JunitXmlSerializer(); + var results = new List { CreateTestResultInfo() }; + var messages = new List + { + new TestMessageInfo(TestMessageLevel.Informational, "Framework info message") + }; + + var xml = serializer.Serialize( + CreateTestLoggerConfiguration(), + CreateTestRunConfiguration(), + results, + messages); + + var doc = XDocument.Parse(xml); + var systemOutElement = doc.XPathSelectElement("//testsuite/system-out"); + + Assert.IsNotNull(systemOutElement, "Non-empty system-out element should be included in test suite"); + Assert.IsTrue(systemOutElement.Value.Contains("Framework info message")); + } + + [TestMethod] + public void TestSuiteShouldIncludeSystemErrElementWhenContentExists() + { + var serializer = new JunitXmlSerializer(); + var results = new List { CreateTestResultInfo() }; + var messages = new List + { + new TestMessageInfo(TestMessageLevel.Error, "Error message") + }; + + var xml = serializer.Serialize( + CreateTestLoggerConfiguration(), + CreateTestRunConfiguration(), + results, + messages); + + var doc = XDocument.Parse(xml); + var systemErrElement = doc.XPathSelectElement("//testsuite/system-err"); + + Assert.IsNotNull(systemErrElement, "Non-empty system-err element should be included in test suite"); + Assert.IsTrue(systemErrElement.Value.Contains("Error - Error message")); } private static LoggerConfiguration CreateTestLoggerConfiguration() @@ -193,9 +275,9 @@ private static string SerializeAndExtractElementContent(JunitXmlSerializer seria var targetElement = testCaseElement.Element(elementName); Assert.IsNotNull(targetElement, $"Element '{elementName}' not found in testcase"); - Assert.IsTrue(targetElement.FirstNode is XCData, $"Element '{elementName}' does not contain CDATA"); + Assert.IsTrue(targetElement.FirstNode is System.Xml.Linq.XText, $"Element '{elementName}' should contain text node"); - return ((XCData)targetElement.FirstNode).Value; + return targetElement.Value; } private static TestSuite CreateTestSuite(string name) diff --git a/test/TestLogger.Fixtures/DotnetTestFixture.cs b/test/TestLogger.Fixtures/DotnetTestFixture.cs index 4d288e5..3d5bb83 100644 --- a/test/TestLogger.Fixtures/DotnetTestFixture.cs +++ b/test/TestLogger.Fixtures/DotnetTestFixture.cs @@ -53,9 +53,8 @@ public string Execute(string assemblyName, string loggerArgs, bool collectCovera } }; cleanProcess.Start(); - var cleanOutput = cleanProcess.StandardOutput.ReadToEnd(); + cleanProcess.StandardOutput.ReadToEnd(); cleanProcess.WaitForExit(); - Console.WriteLine("\n\n## Clean output:\n" + cleanOutput); } if (!isMTP) diff --git a/test/TestLogger.UnitTests/InputSanitizerXmlTests.cs b/test/TestLogger.UnitTests/InputSanitizerXmlTests.cs index e168aa4..3f02973 100644 --- a/test/TestLogger.UnitTests/InputSanitizerXmlTests.cs +++ b/test/TestLogger.UnitTests/InputSanitizerXmlTests.cs @@ -24,6 +24,7 @@ public void ReplaceInvalidXmlCharShouldIgnoreEmptyOrNullInput(string input) [DataRow("aa\0\vbb", @"aa\u0000\u000bbb")] [DataRow("aa\uFFFE", @"aa\ufffe")] [DataRow("aa\u001F", @"aa\u001f")] // 0x1F from original JUnit logger bug report. + [DataRow("Hello\x1B[4mWorld", @"Hello\u001b[4mWorld")] // ANSI escape character \x1B public void ReplaceInvalidXmlCharShouldReplaceInvalidXmlCharWithUnicode(string input, string output) { var sut = new InputSanitizerXml(); diff --git a/test/Xunit.Xml.TestLogger.AcceptanceTests/TestResultsXmlTests.cs b/test/Xunit.Xml.TestLogger.AcceptanceTests/TestResultsXmlTests.cs index b84b89a..7fed747 100644 --- a/test/Xunit.Xml.TestLogger.AcceptanceTests/TestResultsXmlTests.cs +++ b/test/Xunit.Xml.TestLogger.AcceptanceTests/TestResultsXmlTests.cs @@ -422,9 +422,9 @@ public void SkippedTestElementShouldContainSkippingReason(string resultFileName) Assert.Equal(1, reasonNodes.Count); var reasonNode = reasonNodes[0].FirstChild; - Assert.IsType(reasonNode); + Assert.IsType(reasonNode); - XmlCDataSection reasonData = (XmlCDataSection)reasonNode; + XmlText reasonData = (XmlText)reasonNode; string expectedReason = "Skipped"; Assert.Equal(expectedReason, reasonData.Value);