/// <summary> /// Converts the log entries stored within the provided test result into equivalent /// Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResultMessages /// </summary> /// <param name="result">The Boost.Test.Result.TestResult whose LogEntries are to be converted.</param> /// <returns>An enumeration of TestResultMessage equivalent to the Boost log entries stored within the provided TestResult.</returns> private static IEnumerable <TestResultMessage> GetTestMessages(BoostTestAdapter.Boost.Results.TestResult result) { foreach (LogEntry entry in result.LogEntries) { string category = null; if ( (entry is LogEntryInfo) || (entry is LogEntryMessage) || (entry is LogEntryStandardOutputMessage) ) { category = TestResultMessage.StandardOutCategory; } else if ( (entry is LogEntryWarning) || (entry is LogEntryError) || (entry is LogEntryFatalError) || (entry is LogEntryMemoryLeak) || (entry is LogEntryException) || (entry is LogEntryStandardErrorMessage) ) { category = TestResultMessage.StandardErrorCategory; } else { // Skip unknown message types continue; } yield return(new TestResultMessage(category, GetTestResultMessageText(result.Unit, entry))); } }
/// <summary> /// Asserts BoostTestResult against the expected details /// </summary> /// <param name="testResult">The BoostTestResult to test</param> /// <param name="parentTestResult">The expected parent BoostTestResult of testResult</param> /// <param name="name">The expected TestCase display name</param> /// <param name="result">The expected TestCase execution result</param> /// <param name="assertionsPassed">The expected number of passed assertions (e.g. BOOST_CHECKS)</param> /// <param name="assertionsFailed">The expected number of failed assertions (e.g. BOOST_CHECKS, BOOST_REQUIRE, BOOST_FAIL etc.)</param> /// <param name="expectedFailures">The expected number of expected test failures</param> private void AssertReportDetails( BoostTestResult testResult, BoostTestResult parentTestResult, string name, TestResultType result, uint assertionsPassed, uint assertionsFailed, uint expectedFailures ) { Assert.That(testResult.Unit.Name, Is.EqualTo(name)); if (parentTestResult == null) { Assert.That(testResult.Unit.Parent, Is.Null); } else { Assert.That(parentTestResult.Unit, Is.EqualTo(testResult.Unit.Parent)); } Assert.That(testResult.Result, Is.EqualTo(result)); Assert.That(testResult.AssertionsPassed, Is.EqualTo(assertionsPassed)); Assert.That(testResult.AssertionsFailed, Is.EqualTo(assertionsFailed)); Assert.That(testResult.ExpectedFailures, Is.EqualTo(expectedFailures)); }
/// <summary> /// Enumerates all log entries which are deemed to be an error (i.e. Warning, Error, Fatal Error and Exception). /// </summary> /// <param name="result">The TestResult which hosts the log entries.</param> /// <returns>An enumeration of error flagging log entries.</returns> private static IEnumerable <LogEntry> GetErrors(BoostTestAdapter.Boost.Results.TestResult result) { IEnumerable <LogEntry> errors = result.LogEntries.Where((e) => (e is LogEntryWarning) || (e is LogEntryError) || (e is LogEntryFatalError) || (e is LogEntryException) ); // Only provide a single memory leak error if the test succeeded successfully (i.e. all asserts passed) return(errors.Any() ? errors : result.LogEntries.Where((e) => (e is LogEntryMemoryLeak)).Take(1)); }
/// <summary> /// Converts a Boost Test Result into an equivalent Visual Studio Test result /// </summary> /// <param name="test">The test case under consideration</param> /// <param name="result">The Boost test result for the test case under consideration</param> /// <param name="start">The test starting time</param> /// <param name="end">The test ending time</param> /// <returns>A Visual Studio test result equivalent to the Boost Test result</returns> private static VSTestResult GenerateResult(VSTestCase test, BoostTestResult result, DateTimeOffset start, DateTimeOffset end) { Code.Require(test, "test"); Code.Require(result, "result"); // Convert the Boost.Test.Result data structure into an equivalent Visual Studio model VSTestResult vsResult = result.AsVSTestResult(test); vsResult.StartTime = start; vsResult.EndTime = end; return(vsResult); }
/// <summary> /// Compresses a message so that it is suitable for the UI. /// </summary> /// <param name="result">The erroneous LogEntry whose message is to be displayed.</param> /// <returns>A compressed message suitable for UI.</returns> private static string GetErrorMessage(BoostTestAdapter.Boost.Results.TestResult result) { StringBuilder sb = new StringBuilder(); foreach (LogEntry error in GetErrors(result)) { sb.Append(error.Detail).Append(Environment.NewLine); } // Remove redundant NewLine at the end sb.Remove((sb.Length - Environment.NewLine.Length), Environment.NewLine.Length); return(sb.ToString()); }
/// <summary> /// Converts a Boost.Test.Result.TestResult model into an equivalent /// Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult model. /// </summary> /// <param name="result">The Boost.Test.Result.TestResult model to convert.</param> /// <param name="test">The Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase model which is related to the result.</param> /// <returns>The Boost.Test.Result.TestResult model converted into its Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult counterpart.</returns> public static VSTestResult AsVSTestResult(this BoostTestAdapter.Boost.Results.TestResult result, VSTestCase test) { Utility.Code.Require(result, "result"); Utility.Code.Require(test, "test"); VSTestResult vsResult = new VSTestResult(test); vsResult.ComputerName = Environment.MachineName; vsResult.Outcome = GetTestOutcome(result.Result); // Boost.Test.Result.TestResult.Duration is in microseconds // 1 millisecond = 10,000 ticks // => 1 microsecond = 10 ticks // Reference: https://msdn.microsoft.com/en-us/library/zz841zbz(v=vs.110).aspx long ticks = (long)Math.Min((result.Duration * 10), long.MaxValue); // Clamp tick count to 1 in case Boost duration is listed as 0 vsResult.Duration = new TimeSpan(Math.Max(ticks, 1)); if (result.LogEntries.Count > 0) { foreach (TestResultMessage message in GetTestMessages(result)) { vsResult.Messages.Add(message); } // Test using the TestOutcome type since elements from the // Boost Result type may be collapsed into a particular value if (vsResult.Outcome == VSTestOutcome.Failed) { LogEntry error = GetLastError(result); if (error != null) { vsResult.ErrorMessage = GetErrorMessage(result); if (error.Source != null) { //String format for a hyper linkable Stack Trace //Reference: NUnit3 Test Adapter. vsResult.ErrorStackTrace = string.Format(CultureInfo.InvariantCulture, "at {0}() in {1}:line {2}", vsResult.TestCase.DisplayName, ConvertSlashes(error.Source.File), error.Source.LineNumber); } } } } return(vsResult); }
/// <summary> /// Generates TestResults based on Boost Test result output. /// </summary> /// <param name="testRun">The tests which have been executed in the prior test run.</param> /// <param name="start">The test execution start time.</param> /// <param name="end">The test execution end time.</param> /// <param name="settings">boost test adapter settings</param> /// <returns>A Visual Studio TestResult related to the executed test.</returns> private static IEnumerable <VSTestResult> GenerateTestResults(TestRun testRun, DateTimeOffset start, DateTimeOffset end, BoostTestAdapterSettings settings) { TestResultCollection results = new TestResultCollection(); try { results.Parse(testRun.Arguments, settings); } catch (XmlException) { string text = ((File.Exists(testRun.Arguments.ReportFile)) ? File.ReadAllText(testRun.Arguments.ReportFile) : string.Empty); if (text.Trim().StartsWith(TestNotFound, StringComparison.Ordinal)) { return(testRun.Tests.Select(GenerateNotFoundResult)); } else { // Represent result parsing exception as a test fatal error if (string.IsNullOrEmpty(text)) { text = "Boost Test result file was not found or is empty."; } return(testRun.Tests.Select(test => { Boost.Results.TestResult exception = new Boost.Results.TestResult(results); exception.Unit = Boost.Test.TestUnit.FromFullyQualifiedName(test.FullyQualifiedName); // NOTE Divide by 10 to compensate for duration calculation described in VSTestResult.AsVSTestResult(this Boost.Results.TestResult, VSTestCase) exception.Duration = ((ulong)(end - start).Ticks) / 10; exception.Result = TestResultType.Failed; exception.LogEntries.Add(new Boost.Results.LogEntryTypes.LogEntryFatalError(text)); return GenerateResult(test, exception, start, end); })); } } return(testRun.Tests. Select(test => { // Locate the test result associated to the current test Boost.Results.TestResult result = results[test.FullyQualifiedName]; return (result == null) ? null : GenerateResult(test, result, start, end); }). Where(result => (result != null))); }
/// <summary> /// Generates a BoostTestResult instance from the contained configuration. /// </summary> /// <returns>A BoostTestResult instance based on the pre-set configuration</returns> public BoostTestResult Build() { BoostTestResult result = new BoostTestResult(null); result.Result = this.ResultType; result.Unit = this.Unit; result.Duration = this.TimeDuration; foreach (LogEntry entry in this.Logs) { result.LogEntries.Add(entry); } return(result); }
/// <summary> /// Generates TestResults based on Boost Test result output. /// </summary> /// <param name="testRun">The tests which have been executed in the prior test run.</param> /// <param name="start">The test execution start time.</param> /// <param name="end">The test execution end time.</param> /// <param name="settings">boost test adapter settings</param> /// <returns>A Visual Studio Test result related to the executed test.</returns> private static IEnumerable <VSTestResult> GenerateTestResults(TestRun testRun, DateTimeOffset start, DateTimeOffset end, BoostTestAdapterSettings settings) { IDictionary <string, BoostTestResult> results; try { results = BoostTestResultParser.Parse(testRun.Arguments, settings); } catch (FileNotFoundException ex) { Logger.Exception(ex, "Boost.Test result file [{0}] was not found", ex.FileName); // Represent result parsing exception as a test fatal error return(testRun.Tests.Select(test => GenerateTestFailure(test, "Boost.Test result file was not found.", start, end))); } catch (XmlException ex) { Logger.Exception(ex, "Failed to parse Boost.Test result file as XML"); string text = File.ReadAllText(testRun.Arguments.ReportFile); if (text.Trim().StartsWith(TestNotFound, StringComparison.Ordinal)) { Logger.Warn("Boost.Test could not run test batch [{0}] since a test could not be found", testRun.Tests); return(testRun.Tests.Select(GenerateNotFoundResult)); } else { if (string.IsNullOrEmpty(text)) { Logger.Warn("Boost.Test report file was empty"); } return(testRun.Tests.Select(test => GenerateTestFailure(test, text, start, end))); } } return(testRun.Tests. Select(test => { // Locate the test result associated to the current test BoostTestResult result = null; return (results.TryGetValue(test.FullyQualifiedName, out result)) ? GenerateResult(test, result, start, end) : null; }). Where(result => (result != null))); }
/// <summary> /// Generates a generic test failure /// </summary> /// <param name="test">The test which is to be considered as failed (regardless of its outcome)</param> /// <param name="error">The error detail string</param> /// <param name="start">The test execution start time</param> /// <param name="end">The test execution end time</param> /// <returns>A failing VSTestResult with the provided error information</returns> private static VSTestResult GenerateTestFailure(VSTestCase test, string error, DateTimeOffset start, DateTimeOffset end) { // Represent the test failure with a dummy result to make use of GenerateResult var exception = new BoostTestResult(); exception.Unit = Boost.Test.TestUnit.FromFullyQualifiedName(test.FullyQualifiedName); // NOTE Divide by 10 to compensate for duration calculation described in VSTestResult.AsVSTestResult(this Boost.Results.TestResult, VSTestCase) exception.Duration = ((ulong)(end - start).Ticks) / 10; exception.Result = TestResultType.Failed; exception.LogEntries.Add(new Boost.Results.LogEntryTypes.LogEntryFatalError() { Detail = error }); return(GenerateResult(test, exception, start, end)); }
/// <summary> /// Converts a Boost.Test.Result.TestResult model into an equivalent /// Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult model. /// </summary> /// <param name="result">The Boost.Test.Result.TestResult model to convert.</param> /// <param name="test">The Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase model which is related to the result.</param> /// <returns>The Boost.Test.Result.TestResult model converted into its Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult counterpart.</returns> public static VSTestResult AsVSTestResult(this BoostTestAdapter.Boost.Results.TestResult result, VSTestCase test) { Utility.Code.Require(result, "result"); Utility.Code.Require(test, "test"); VSTestResult vsResult = new VSTestResult(test); vsResult.ComputerName = Environment.MachineName; vsResult.Outcome = GetTestOutcome(result.Result); // Boost.Test.Result.TestResult.Duration is in microseconds // 1 millisecond = 10,000 ticks // => 1 microsecond = 10 ticks // Reference: https://msdn.microsoft.com/en-us/library/zz841zbz(v=vs.110).aspx long ticks = (long)Math.Min((result.Duration * 10), long.MaxValue); // Clamp tick count to 1 in case Boost duration is listed as 0 vsResult.Duration = new TimeSpan(Math.Max(ticks, 1)); if (result.LogEntries.Count > 0) { foreach (TestResultMessage message in GetTestMessages(result)) { vsResult.Messages.Add(message); } // Test using the TestOutcome type since elements from the // Boost Result type may be collapsed into a particular value if (vsResult.Outcome == VSTestOutcome.Failed) { LogEntry error = GetLastError(result); if (error != null) { vsResult.ErrorMessage = GetErrorMessage(result); vsResult.ErrorStackTrace = ((error.Source == null) ? null : error.Source.ToString()); } } } return(vsResult); }
/// <summary> /// Aggregates the two results as one result structure if compatible. /// </summary> /// <param name="lhs">The left-hand side result to aggregate</param> /// <param name="rhs">The right-hand side result to aggregate</param> /// <returns>A TestResult instance consisting of both results as one or lhs in case of incompatibilities</returns> private static TestResult Aggregate(TestResult lhs, TestResult rhs) { // If lhs and rhs are incompatible, return the first non-null argument if ((lhs == null) || (rhs == null) || (lhs.Collection != rhs.Collection) || (lhs.Unit.FullyQualifiedName != rhs.Unit.FullyQualifiedName)) { return ((lhs != null) ? lhs : rhs); } TestResult rvalue = new TestResult(lhs.Collection); rvalue.Unit = lhs.Unit; // Select the worst of the result types int result = Math.Max((int) lhs.Result, (int) rhs.Result); rvalue.Result = (TestResultType) result; // Sum up totals rvalue.AssertionsPassed = lhs.AssertionsPassed + rhs.AssertionsPassed; rvalue.AssertionsFailed = lhs.AssertionsFailed + rhs.AssertionsFailed; rvalue.ExpectedFailures = lhs.ExpectedFailures + rhs.ExpectedFailures; return rvalue; }
/// <summary> /// Asserts general log details contained within a BoostTestResult /// </summary> /// <param name="testResult">The BoostTestResult to test</param> /// <param name="duration">The expected test case execution duration</param> private void AssertLogDetails(BoostTestResult testResult, uint duration) { AssertLogDetails(testResult, duration, new List<LogEntry>()); }
/// <summary> /// Parses a TestCase log node. /// </summary> /// <param name="node">The TestCase Xml node to parse.</param> /// <param name="path">The QualifiedNameBuilder which hosts the current fully qualified path.</param> /// <param name="collection">The TestResultCollection which will host the result.</param> private static void ParseTestCaseLog(XmlNode node, QualifiedNameBuilder path, TestResultCollection collection) { // Temporarily push TestCase on TestSuite name builder to acquire the fully qualified name of the TestCase path.Push(node.Attributes[Xml.Name].Value); // Acquire result record of this TestCase TestResult result = collection[path.ToString()]; if (result == null) { result = new TestResult(collection); collection[path.ToString()] = result; } // Reset path to original value path.Pop(); XmlNode testingTime = node.SelectSingleNode(Xml.TestingTime); if (testingTime != null) { // Boost test testing time is listed in microseconds result.Duration = ulong.Parse(testingTime.InnerText, CultureInfo.InvariantCulture); } ParseTestCaseLogEntries(node.ChildNodes, result); }
/// <summary> /// Generates TestResults based on Boost Test result output. /// </summary> /// <param name="testRun">The tests which have been executed in the prior test run.</param> /// <param name="start">The test execution start time.</param> /// <param name="end">The test execution end time.</param> /// <param name="settings">boost test adapter settings</param> /// <returns>A Visual Studio TestResult related to the executed test.</returns> private static IEnumerable<VSTestResult> GenerateTestResults(TestRun testRun, DateTimeOffset start, DateTimeOffset end, BoostTestAdapterSettings settings) { TestResultCollection results = new TestResultCollection(); try { results.Parse(testRun.Arguments, settings); } catch (XmlException) { string text = ((File.Exists(testRun.Arguments.ReportFile)) ? File.ReadAllText(testRun.Arguments.ReportFile) : string.Empty); if (text.Trim().StartsWith(TestNotFound, StringComparison.Ordinal)) { return testRun.Tests.Select(GenerateNotFoundResult); } else { // Represent result parsing exception as a test fatal error if (string.IsNullOrEmpty(text)) { text = "Boost Test result file was not found or is empty."; } return testRun.Tests.Select(test => { Boost.Results.TestResult exception = new Boost.Results.TestResult(results); exception.Unit = Boost.Test.TestUnit.FromFullyQualifiedName(test.FullyQualifiedName); // NOTE Divide by 10 to compensate for duration calculation described in VSTestResult.AsVSTestResult(this Boost.Results.TestResult, VSTestCase) exception.Duration = ((ulong)(end - start).Ticks) / 10; exception.Result = TestResultType.Failed; exception.LogEntries.Add(new Boost.Results.LogEntryTypes.LogEntryFatalError(text)); return GenerateResult(test, exception, start, end); }); } } return testRun.Tests. Select(test => { // Locate the test result associated to the current test Boost.Results.TestResult result = results[test.FullyQualifiedName]; return (result == null) ? null : GenerateResult(test, result, start, end); }). Where(result => (result != null)); }
/// <summary> /// Parses Log Entries from the collection of log nodes. /// </summary> /// <param name="nodes">The collection of Xml nodes which are valid LogEntry nodes.</param> /// <param name="result">The TestResult which will host the parsed LogEntries.</param> private static void ParseTestCaseLogEntries(XmlNodeList nodes, TestResult result) { foreach (XmlNode child in nodes) { if (child.NodeType == XmlNodeType.Element) { LogEntry entry = null; switch (child.Name) { case Xml.Info: entry = new LogEntryInfo(child.InnerText); break; case Xml.Message: entry = new LogEntryMessage(child.InnerText); break; case Xml.Warning: entry = new LogEntryWarning(child.InnerText); break; case Xml.Error: entry = new LogEntryError(child.InnerText); break; case Xml.FatalError: entry = new LogEntryFatalError(child.InnerText); break; case Xml.Exception: entry = ParseTestCaseLogException(child); break; } if (entry != null) { entry.Source = ParseSourceInfo(child); result.LogEntries.Add(entry); } } } }
/// <summary> /// Parses a general test result information from the provided node. /// </summary> /// <param name="node">The XPathNavigator pointing to a TestUnit node.</param> /// <param name="unit">The test unit for which the test results are related to.</param> /// <param name="collection">The TestResultCollection which will host the result.</param> private static TestResult ParseTestResult(XPathNavigator node, TestUnit unit, TestResultCollection collection) { TestResult result = new TestResult(collection); result.Unit = unit; result.Result = ParseResultType(node.GetAttribute(Xml.Result, string.Empty)); result.AssertionsPassed = uint.Parse(node.GetAttribute(Xml.AssertionsPassed, string.Empty), CultureInfo.InvariantCulture); result.AssertionsFailed = uint.Parse(node.GetAttribute(Xml.AssertionsFailed, string.Empty), CultureInfo.InvariantCulture); result.ExpectedFailures = uint.Parse(node.GetAttribute(Xml.ExpectedFailures, string.Empty), CultureInfo.InvariantCulture); return result; }
/// <summary> /// Given a TestResult returns the last error type log entry. /// </summary> /// <param name="result">The TestResult which hosts the necessary log entries</param> /// <returns>The last error type log entry or null if none are available.</returns> private static LogEntry GetLastError(BoostTestAdapter.Boost.Results.TestResult result) { // Select the last error issued within a Boost Test report return(GetErrors(result).LastOrDefault()); }
/// <summary> /// Asserts general log details contained within a BoostTestResult /// </summary> /// <param name="testResult">The BoostTestResult to test</param> /// <param name="duration">The expected test case execution duration</param> /// <param name="entries">The expected list of log entries generated from test case execution</param> private void AssertLogDetails(BoostTestResult testResult, uint duration, IList<LogEntry> entries) { Assert.That(testResult.LogEntries.Count, Is.EqualTo(expected: entries.Count)); Assert.That(testResult.Duration, Is.EqualTo(duration)); foreach (LogEntry entry in entries) { LogEntry found = testResult.LogEntries.FirstOrDefault( e => { // serge: In BoostXmlLog.Parse(TestResultCollection collection) method // Xml document is recreated. There are insignificant whitespaces // are appearing during transformation. So let's truncate them. var entryDetail = Regex.Replace(entry.Detail, @"\r|\n\s+", string.Empty); var eDetail = Regex.Replace(e.Detail, @"\r|\n\s+", string.Empty); return (e.ToString() == entry.ToString()) && (eDetail == entryDetail); }); Assert.That(found, Is.Not.Null); AssertSourceInfoDetails(found.Source, entry.Source); } var entriesMemLeaks = entries.Where((e) => e is LogEntryMemoryLeak).GetEnumerator(); var testResultMemleaks = testResult.LogEntries.Where((e) => e is LogEntryMemoryLeak).GetEnumerator(); while (testResultMemleaks.MoveNext() && entriesMemLeaks.MoveNext()) { AssertMemoryLeakDetails((LogEntryMemoryLeak)testResultMemleaks.Current, (LogEntryMemoryLeak)entriesMemLeaks.Current); } }
/// <summary> /// Asserts BoostTestResult against the expected details /// </summary> /// <param name="testResult">The BoostTestResult to test</param> /// <param name="parentTestResult">The expected parent BoostTestResult of testResult</param> /// <param name="name">The expected TestCase display name</param> /// <param name="result">The expected TestCase execution result</param> /// <param name="assertionsPassed">The expected number of passed assertions (e.g. BOOST_CHECKS)</param> /// <param name="assertionsFailed">The expected number of failed assertions (e.g. BOOST_CHECKS, BOOST_REQUIRE, BOOST_FAIL etc.)</param> /// <param name="expectedFailures">The expected number of expected test failures</param> /// <param name="testCasesPassed">The expected number of passed child TestCases</param> /// <param name="testCasesFailed">The expected number of failed child TestCases</param> /// <param name="testCasesSkipped">The expected number of skipped child TestCases</param> /// <param name="testCasesAborted">The expected number of aborted child TestCases</param> private void AssertReportDetails( BoostTestResult testResult, BoostTestResult parentTestResult, string name, TestResultType result, uint assertionsPassed, uint assertionsFailed, uint expectedFailures, uint testCasesPassed, uint testCasesFailed, uint testCasesSkipped, uint testCasesAborted ) { AssertReportDetails(testResult, parentTestResult, name, result, assertionsPassed, assertionsFailed, expectedFailures); Assert.That(testResult.TestCasesPassed, Is.EqualTo(testCasesPassed)); Assert.That(testResult.TestCasesFailed, Is.EqualTo(testCasesFailed)); Assert.That(testResult.TestCasesSkipped, Is.EqualTo(testCasesSkipped)); Assert.That(testResult.TestCasesAborted, Is.EqualTo(testCasesAborted)); }
/// <summary> /// Generates a BoostTestResult instance from the contained configuration. /// </summary> /// <returns>A BoostTestResult instance based on the pre-set configuration</returns> public BoostTestResult Build() { BoostTestResult result = new BoostTestResult(null); result.Result = this.ResultType; result.Unit = this.Unit; result.Duration = this.TimeDuration; foreach (LogEntry entry in this.Logs) { result.LogEntries.Add(entry); } return result; }
/// <summary> /// Asserts general log details contained within a BoostTestResult /// </summary> /// <param name="testResult">The BoostTestResult to test</param> /// <param name="duration">The expected test case execution duration</param> /// <param name="entries">The expected list of log entries generated from test case execution</param> private void AssertLogDetails(BoostTestResult testResult, uint duration, IList<LogEntry> entries) { Assert.That(testResult.LogEntries.Count, Is.EqualTo(expected: entries.Count)); Assert.That(testResult.Duration, Is.EqualTo(duration)); foreach (LogEntry entry in entries) { LogEntry found = testResult.LogEntries.FirstOrDefault( e => { var entryDetail = Regex.Replace(entry.Detail, @"\r|\n", string.Empty); var eDetail = Regex.Replace(e.Detail, @"\r|\n", string.Empty); return (e.ToString() == entry.ToString()) && (eDetail == entryDetail); }); Assert.That(found, Is.Not.Null); AssertSourceInfoDetails(found.Source, entry.Source); } var entriesMemLeaks = entries.Where((e) => e is LogEntryMemoryLeak).GetEnumerator(); var testResultMemleaks = testResult.LogEntries.Where((e) => e is LogEntryMemoryLeak).GetEnumerator(); while (testResultMemleaks.MoveNext() && entriesMemLeaks.MoveNext()) { AssertMemoryLeakDetails((LogEntryMemoryLeak)testResultMemleaks.Current, (LogEntryMemoryLeak)entriesMemLeaks.Current); } }