diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IDataAttachment.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IDataAttachment.cs index 735dc0048c..cae4bcd6c8 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IDataAttachment.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IDataAttachment.cs @@ -6,7 +6,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel /// /// Interface used to define a data attachment. /// - public interface IDataAttachment + internal interface IDataAttachment { /// /// Gets the description for the attachment. diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/ITestAggregation.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/ITestAggregation.cs new file mode 100644 index 0000000000..8a5b372067 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/ITestAggregation.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Collections.Generic; + + internal interface ITestAggregation : ITestElement + { + Dictionary TestLinks { get; } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/ITestElement.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/ITestElement.cs new file mode 100644 index 0000000000..92d7db22f4 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/ITestElement.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + internal interface ITestElement + { + TestId Id { get; } + string Name { get; set; } + string Owner { get; set; } + string Storage { get; set; } + string Adapter { get; } + int Priority { get; set; } + bool IsRunnable { get; } + TestExecId ExecutionId { get; set; } + TestExecId ParentExecutionId { get; set; } + TestListCategoryId CategoryId { get; set; } + TestCategoryItemCollection TestCategories { get; } + TestType TestType { get; } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/ITestResult.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/ITestResult.cs new file mode 100644 index 0000000000..6835dc5fc1 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/ITestResult.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + + using System; + + internal interface ITestResult + { + TestResultId Id { get; } + string ResultType { get; set; } + string StdOut { get; set; } + string StdErr { get; set; } + string DebugTrace { get; set; } + string TestResultsDirectory { get; } + string RelativeTestResultsDirectory { get; } + string ErrorMessage { get; set; } + string ErrorStackTrace { get; set; } + string ComputerName { get; } + string[] TextMessages { get; set; } + int DataRowInfo { get; set; } + DateTime StartTime { get; set; } + DateTime EndTime { get; set; } + TimeSpan Duration { get; set; } + TestOutcome Outcome { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/ITestResultAggregation.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/ITestResultAggregation.cs new file mode 100644 index 0000000000..7ee85e3e4c --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/ITestResultAggregation.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System.Collections.Generic; + + internal interface ITestResultAggregation : ITestResult + { + List InnerResults { get; } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStore.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStore.cs index bfaf94d069..cb020b977d 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStore.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStore.cs @@ -9,7 +9,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel /// Implementing this interface indicates a custom persistence logic is provided. /// The attribute based persistence is ignored in such a case. /// - public interface IXmlTestStore + internal interface IXmlTestStore { /// /// Saves the class under the XmlElement. diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStoreCustom.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStoreCustom.cs index ca5bcec137..7ff25b9fc0 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStoreCustom.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/IXmlTestStoreCustom.cs @@ -8,7 +8,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.XML /// /// Implementing this interface allows you to customize XmlStore persistence. /// - public interface IXmlTestStoreCustom + internal interface IXmlTestStoreCustom { /// /// Gets the name of the tag to use to persist this object. diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/XmlTestStoreParameters.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/XmlTestStoreParameters.cs index 11e8f37567..aa086638b4 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/XmlTestStoreParameters.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Interfaces/XmlTestStoreParameters.cs @@ -15,7 +15,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel /// saved when 'MyClass.SaveDetails' parameter is set to 'true'. /// [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] - public sealed class XmlTestStoreParameters : Dictionary + internal sealed class XmlTestStoreParameters : Dictionary { private XmlTestStoreParameters() { diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/OrderedTestElement.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/OrderedTestElement.cs new file mode 100644 index 0000000000..907b14bc6d --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/OrderedTestElement.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// Ordered test element. + /// + internal class OrderedTestElement : TestElementAggregation, IXmlTestStoreCustom + { + public OrderedTestElement(Guid id, string name, string adapter) : base(id, name, adapter) { } + + string IXmlTestStoreCustom.ElementName + { + get { return Constants.OrderedTestElementName; } + } + + string IXmlTestStoreCustom.NamespaceUri + { + get { return null; } + } + + /// + /// Gets the test type. + /// + public override TestType TestType + { + get { return Constants.OrderedTestType; } + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestCategoryItems.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestCategoryItems.cs index 9ebc88feaf..14ffe14426 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestCategoryItems.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestCategoryItems.cs @@ -14,7 +14,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel /// /// Stores a string which categorizes the Test /// - public sealed class TestCategoryItem : IXmlTestStore + internal sealed class TestCategoryItem : IXmlTestStore { #region Fields [StoreXmlSimpleField(Location = "@TestCategory", DefaultValue = "")] @@ -123,7 +123,7 @@ public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameter /// /// A collection of strings which categorize the test. /// - public sealed class TestCategoryItemCollection : EqtBaseCollection + internal sealed class TestCategoryItemCollection : EqtBaseCollection { #region Constructors /// diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestElement.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestElement.cs new file mode 100644 index 0000000000..9562822962 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestElement.cs @@ -0,0 +1,252 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Diagnostics; + using System.Globalization; + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + using TrxLoggerResources = Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger.Resources.TrxResource; + + /// + /// Test element. + /// + internal abstract class TestElement : ITestElement, IXmlTestStore + { + /// + /// Default priority for a test method that does not specify a priority + /// + protected const int DefaultPriority = int.MaxValue; + + protected TestId id; + protected string name; + protected string owner; + protected string storage; + protected string adapter; + protected int priority; + protected bool isRunnable; + protected TestExecId executionId; + protected TestExecId parentExecutionId; + protected TestCategoryItemCollection testCategories; + protected TestListCategoryId catId; + + public TestElement(Guid id, string name, string adapter) + { + Debug.Assert(!string.IsNullOrEmpty(name), "name is null"); + Debug.Assert(!string.IsNullOrEmpty(adapter), "adapter is null"); + + this.Initialize(); + + this.id = new TestId(id); + this.name = name; + this.adapter = adapter; + } + + + /// + /// Gets the id. + /// + public TestId Id + { + get { return this.id; } + } + + /// + /// Gets or sets the name. + /// + public string Name + { + get { return this.name; } + + set + { + EqtAssert.ParameterNotNull(value, "Name"); + this.name = value; + } + } + + /// + /// Gets or sets the owner. + /// + public string Owner + { + get { return this.owner; } + + set + { + EqtAssert.ParameterNotNull(value, "Owner"); + this.owner = value; + } + } + + /// + /// Gets or sets the priority. + /// + public int Priority + { + get { return this.priority; } + set { this.priority = value; } + } + + /// + /// Gets or sets the storage. + /// + public string Storage + { + get { return this.storage; } + + set + { + EqtAssert.StringNotNullOrEmpty(value, "Storage"); + this.storage = value.ToLowerInvariant(); + } + } + + /// + /// Gets or sets the execution id. + /// + public TestExecId ExecutionId + { + get { return this.executionId; } + set { this.executionId = value; } + } + + /// + /// Gets or sets the parent execution id. + /// + public TestExecId ParentExecutionId + { + get { return this.parentExecutionId; } + set { this.parentExecutionId = value; } + } + + /// + /// Gets the isRunnable value. + /// + public bool IsRunnable + { + get { return this.isRunnable; } + } + + /// + /// Gets or sets the category id. + /// + /// + /// Instead of setting to null use TestListCategoryId.Uncategorized + /// + public TestListCategoryId CategoryId + { + get { return this.catId; } + + set + { + EqtAssert.ParameterNotNull(value, "CategoryId"); + this.catId = value; + } + } + + /// + /// Gets or sets the test categories. + /// + public TestCategoryItemCollection TestCategories + { + get { return this.testCategories; } + + set + { + EqtAssert.ParameterNotNull(value, "value"); + this.testCategories = value; + } + } + + /// + /// Gets the adapter name. + /// + public string Adapter + { + get { return adapter; } + } + + /// + /// Gets the test type. + /// + public abstract TestType TestType { get; } + + /// + /// Override for ToString. + /// + /// String representation of test element. + public override string ToString() + { + return string.Format( + CultureInfo.InvariantCulture, + "'{0}' {1}", + this.name != null ? this.name : TrxLoggerResources.Common_NullInMessages, + this.id != null ? this.id.ToString() : TrxLoggerResources.Common_NullInMessages); + } + + /// + /// Override for Equals. + /// + /// + /// The object to compare. + /// + /// + /// The . + /// + public override bool Equals(object other) + { + TestElement otherTest = other as TestElement; + return (otherTest == null) ? + false : + this.id.Equals(otherTest.id); + } + + /// + /// Override for GetHashCode + /// + /// + /// The . + /// + public override int GetHashCode() + { + return this.id.GetHashCode(); + } + + public virtual void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence h = new XmlPersistence(); + + h.SaveSimpleField(element, "@name", this.name, null); + h.SaveSimpleField(element, "@storage", this.storage, string.Empty); + h.SaveSimpleField(element, "@priority", this.priority, DefaultPriority); + h.SaveSimpleField(element, "Owners/Owner/@name", this.owner, string.Empty); + h.SaveObject(this.testCategories, element, "TestCategory", parameters); + + if (this.executionId != null) + h.SaveGuid(element, "Execution/@id", this.executionId.Id); + if (this.parentExecutionId != null) + h.SaveGuid(element, "Execution/@parentId", this.parentExecutionId.Id); + + XmlTestStoreParameters testIdParameters = XmlTestStoreParameters.GetParameters(); + testIdParameters[TestId.IdLocationKey] = "@id"; + h.SaveObject(this.id, element, testIdParameters); + } + + private void Initialize() + { + this.id = TestId.Empty; + this.name = string.Empty; + this.owner = string.Empty; + this.priority = DefaultPriority; + this.storage = string.Empty; + this.executionId = TestExecId.Empty; + this.parentExecutionId = TestExecId.Empty; + this.testCategories = new TestCategoryItemCollection(); + this.isRunnable = true; + this.catId = TestListCategoryId.Uncategorized; + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestElementAggregation.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestElementAggregation.cs new file mode 100644 index 0000000000..d814acaee6 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestElementAggregation.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Collections.Generic; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// Test aggregation element. + /// + internal abstract class TestElementAggregation : TestElement, ITestAggregation + { + protected Dictionary testLinks = new Dictionary(); + + public TestElementAggregation(Guid id, string name, string adapter) : base(id, name, adapter) { } + + /// + /// Test links. + /// + public Dictionary TestLinks + { + get { return testLinks; } + } + + public override void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + { + base.Save(element, parameters); + + XmlPersistence h = new XmlPersistence(); + if (testLinks.Count > 0) + { + h.SaveIEnumerable(testLinks.Values, element, "TestLinks", ".", "TestLink", parameters); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestEntry.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestEntry.cs index 7b6b8d791d..294f50ad58 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestEntry.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestEntry.cs @@ -3,9 +3,10 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel { + using System; + using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; - using Microsoft.TestPlatform.Extensions.TrxLogger.XML; /// @@ -16,8 +17,10 @@ internal sealed class TestEntry : IXmlTestStore #region Fields private TestId testId; - private TestExecId execId; + private Guid executionId; + private Guid parentExecutionId; private TestListCategoryId categoryId; + private List testEntries; #endregion @@ -46,17 +49,41 @@ public TestEntry(TestId testId, TestListCategoryId catId) /// /// Gets or sets the exec id. /// - public TestExecId ExecId + public Guid ExecutionId { - get + get { return this.executionId; } + + set { - return this.execId; + Debug.Assert(value != null, "ExecId is null"); + this.executionId = value; } + } + + /// + /// Gets or sets the parent exec id. + /// + public Guid ParentExecutionId + { + get { return this.parentExecutionId; } set { Debug.Assert(value != null, "ExecId is null"); - this.execId = value; + this.parentExecutionId = value; + } + } + + public List TestEntries + { + get + { + if (this.testEntries == null) + { + this.testEntries = new List(); + } + + return this.testEntries; } } @@ -83,10 +110,10 @@ public override bool Equals(object obj) return false; } - Debug.Assert(this.execId != null, "this.execId is null"); - Debug.Assert(e.execId != null, "e.execId is null"); + Debug.Assert(this.executionId != null, "this.executionId is null"); + Debug.Assert(e.executionId != null, "e.executionId is null"); - if (!this.execId.Equals(e.execId)) + if (!this.executionId.Equals(e.executionId)) { return false; } @@ -104,7 +131,7 @@ public override bool Equals(object obj) /// public override int GetHashCode() { - return this.execId.GetHashCode(); + return this.executionId.GetHashCode(); } #endregion @@ -126,8 +153,12 @@ public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameter helper.SaveSingleFields(element, this, parameters); helper.SaveObject(this.testId, element, null); - helper.SaveGuid(element, "@executionId", this.execId.Id); + helper.SaveGuid(element, "@executionId", this.executionId); + if (parentExecutionId != null) + helper.SaveGuid(element, "@parentExecutionId", this.parentExecutionId); helper.SaveGuid(element, "@testListId", this.categoryId.Id); + if (this.TestEntries.Count > 0) + helper.SaveIEnumerable(TestEntries, element, "TestEntries", ".", "TestEntry", parameters); } #endregion diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestExecId.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestExecId.cs index a960834e99..dcf48aa29b 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestExecId.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestExecId.cs @@ -10,7 +10,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel /// Class identifying test execution id. /// Execution ID is assigned to test at run creation time and is guaranteed to be unique within that run. /// - public sealed class TestExecId + internal sealed class TestExecId { private static TestExecId emptyId = new TestExecId(Guid.Empty); diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestId.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestId.cs index a871790b2f..6bfada65f3 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestId.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestId.cs @@ -15,7 +15,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel /// /// Class that uniquely identifies a test. /// - public sealed class TestId : IEquatable, IComparable, IComparable, IXmlTestStore + internal sealed class TestId : IEquatable, IComparable, IComparable, IXmlTestStore { #region Constants diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestLink.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestLink.cs new file mode 100644 index 0000000000..b770e30d20 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestLink.cs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Diagnostics; + using System.Globalization; + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// Test link. + /// + internal sealed class TestLink : IXmlTestStore + { + private Guid id; + private string name = string.Empty; + private string storage = string.Empty; + + public TestLink(Guid id, string name, string storage) + { + if (id == Guid.Empty) + { + throw new ArgumentException("ID cant be empty"); + } + + EqtAssert.StringNotNullOrEmpty(name, "name"); + EqtAssert.ParameterNotNull(storage, "storage"); + + this.id = id; + this.name = name; + this.storage = storage; + } + + /// + /// Gets the id. + /// + public Guid Id + { + get { return this.id; } + } + + /// + /// Gets the name. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the storage. + /// + public string Storage + { + get { return this.storage; } + } + + /// + /// Whether this Link is equal to other Link. Compares by Id. + /// + public override bool Equals(object other) + { + TestLink link = other as TestLink; + return (link == null) ? + false : + this.id.Equals(link.id); + } + + /// + /// Whether this Link is exactly the same as other Link. Compares all fields. + /// + public bool IsSame(TestLink other) + { + if (other == null) + return false; + + return this.id.Equals(other.id) && + this.name.Equals(other.name) && + this.storage.Equals(other.storage); + } + + /// + /// Override for GetHashCode. + /// + /// + public override int GetHashCode() + { + return this.id.GetHashCode(); + } + + /// + /// Override for ToString. + /// + /// + public override string ToString() + { + return string.Format( + CultureInfo.InvariantCulture, + "Link to '{0}' {1} '{2}'.", + this.name != null ? this.name : "(null)", + this.id.ToString("B"), + this.storage != null ? this.storage : "(null)"); + } + + public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence h = new XmlPersistence(); + h.SaveGuid(element, "@id", this.Id); + h.SaveSimpleField(element, "@name", this.name, null); + h.SaveSimpleField(element, "@storage", this.storage, string.Empty); + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategory.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategory.cs index df9eaf7406..22c88e0021 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategory.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategory.cs @@ -13,7 +13,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel /// /// The test list category. /// - public class TestListCategory : IXmlTestStore + internal class TestListCategory : IXmlTestStore { #region Fields diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategoryId.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategoryId.cs index cfad2cc563..d50fd06cf8 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategoryId.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestListCategoryId.cs @@ -9,7 +9,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel /// /// Class to categorize the tests. /// - public sealed class TestListCategoryId + internal sealed class TestListCategoryId { private static TestListCategoryId emptyId = new TestListCategoryId(Guid.Empty); diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestOutcome.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestOutcome.cs index b302628dc2..97b336b316 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestOutcome.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestOutcome.cs @@ -13,7 +13,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel /// NOTE: the order is important and is used for computing outcome for aggregations. /// More important outcomes come first. See TestOutcomeHelper.GetAggregationOutcome. /// - public enum TestOutcome + internal enum TestOutcome { /// /// There was a system error while we were trying to execute a test. diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs index e34dbabc8d..517fecafb8 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs @@ -4,21 +4,27 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel { using System; + using System.Collections; + using System.Collections.Generic; using System.Diagnostics; + using System.Globalization; + using System.IO; + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using TrxLoggerResources = Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger.Resources.TrxResource; /// /// Class to uniquely identify test results /// - public sealed class TestResultId : IXmlTestStore + internal sealed class TestResultId : IXmlTestStore { #region Fields - private Guid runId; - - // id of test within run - private TestExecId executionId; - private TestId testId; + private Guid runId; + private Guid executionId; + private Guid parentExecutionId; + private Guid testId; #endregion @@ -39,10 +45,11 @@ public sealed class TestResultId : IXmlTestStore /// /// The test id. /// - public TestResultId(Guid runId, TestExecId executionId, TestId testId) + public TestResultId(Guid runId, Guid executionId, Guid parentExecutionId, Guid testId) { this.runId = runId; this.executionId = executionId; + this.parentExecutionId = parentExecutionId; this.testId = testId; } @@ -53,11 +60,27 @@ public TestResultId(Guid runId, TestExecId executionId, TestId testId) /// /// Gets the execution id. /// - public TestExecId ExecutionId + public Guid ExecutionId { get { return this.executionId; } } + /// + /// Gets the parent execution id. + /// + public Guid ParentExecutionId + { + get { return this.parentExecutionId; } + } + + /// + /// Gets the test id. + /// + public Guid TestId + { + get { return this.testId; } + } + #endregion #region Overrides @@ -101,7 +124,7 @@ public override int GetHashCode() /// public override string ToString() { - return this.executionId.Id.ToString("B"); + return this.executionId.ToString("B"); } #endregion @@ -121,11 +144,11 @@ public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameter XmlPersistence helper = new XmlPersistence(); if (this.executionId != null) - { - helper.SaveGuid(element, "@executionId", this.executionId.Id); - } + helper.SaveGuid(element, "@executionId", this.executionId); + if (this.parentExecutionId != null) + helper.SaveGuid(element, "@parentExecutionId", this.parentExecutionId); - helper.SaveObject(this.testId, element, null); + helper.SaveGuid(element, "@testId", this.testId); } #endregion @@ -190,4 +213,416 @@ public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameter #endregion } + + /// + /// Class for test result. + /// + internal class TestResult : ITestResult, IXmlTestStore + { + #region Fields + + private TestResultId id; + private string resultName; + private string computerInfo; + private string stdOut; + private string stdErr; + private string debugTrace; + private string resultType; + private int dataRowInfo; + private TimeSpan duration; + private DateTime startTime; + private DateTime endTime; + private TestType testType; + private TestOutcome outcome; + private TestRun testRun; + private TestResultErrorInfo errorInfo; + private TestListCategoryId categoryId; + private ArrayList textMessages; + + /// + /// Directory containing the test result files, relative to the root test results directory + /// + private string relativeTestResultsDirectory; + + /// + /// Paths to test result files, relative to the test results folder, sorted in increasing order + /// + private SortedList resultFiles = new SortedList(StringComparer.OrdinalIgnoreCase); + + /// + /// Information provided by data collectors for the test case + /// + private List collectorDataEntries = new List(); + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// + /// The computer name. + /// + /// + /// The run id. + /// + /// + /// The test. + /// + /// + /// The outcome. + /// + public TestResult( + Guid runId, + Guid testId, + Guid executionId, + Guid parentExecutionId, + string resultName, + string computerName, + TestOutcome outcome, + TestType testType, + TestListCategoryId testCategoryId) + { + Debug.Assert(computerName != null, "computername is null"); + Debug.Assert(!Guid.Empty.Equals(executionId), "ExecutionId is empty"); + Debug.Assert(!Guid.Empty.Equals(testId), "TestId is empty"); + + this.Initialize(); + + this.id = new TestResultId(runId, executionId, parentExecutionId, testId); + this.resultName = resultName; + this.testType = testType; + this.computerInfo = computerName; + this.outcome = outcome; + this.categoryId = testCategoryId; + this.relativeTestResultsDirectory = TestRunDirectories.GetRelativeTestResultsDirectory(executionId); + } + + #endregion + + #region properties + + /// + /// Gets or sets the end time. + /// + public DateTime EndTime + { + get { return this.endTime; } + set { this.endTime = value; } + } + + /// + /// Gets or sets the start time. + /// + public DateTime StartTime + { + get { return this.startTime; } + set { this.startTime = value; } + } + + /// + /// Gets or sets the duration. + /// + public TimeSpan Duration + { + get { return this.duration; } + + set + { + // On some hardware the Stopwatch.Elapsed can return a negative number. This tends + // to happen when the duration of the test is very short and it is hardware dependent + // (seems to happen most on virtual machines or machines with AMD processors). To prevent + // reporting a negative duration, use TimeSpan.Zero when the elapsed time is less than zero. + EqtTrace.WarningIf(value < TimeSpan.Zero, "TestResult.Duration: The duration is being set to {0}. Since the duration is negative the duration will be updated to zero.", value); + this.duration = value > TimeSpan.Zero ? value : TimeSpan.Zero; + } + } + + /// + /// Gets the computer name. + /// + public string ComputerName + { + get { return this.computerInfo; } + } + + /// + /// Gets or sets the outcome. + /// + public TestOutcome Outcome + { + get { return this.outcome; } + set { this.outcome = value; } + } + + + /// + /// Gets or sets the id. + /// + public TestResultId Id + { + get { return this.id; } + internal set { this.id = value; } + } + + /// + /// Gets or sets the error message. + /// + public string ErrorMessage + { + get { return (this.errorInfo == null) ? string.Empty : this.errorInfo.Message; } + set { this.errorInfo = new TestResultErrorInfo(value); } + } + + /// + /// Gets or sets the error stack trace. + /// + public string ErrorStackTrace + { + get { return (this.errorInfo == null) ? string.Empty : this.errorInfo.StackTrace; } + + set + { + Debug.Assert(this.errorInfo != null, "errorInfo is null"); + this.errorInfo.StackTrace = value; + } + } + + /// + /// Gets the text messages. + /// + /// + /// Additional information messages from TestTextResultMessage, e.g. generated by TestOutcome.WriteLine. + /// Avoid using this property in the following way: for (int i=0; i<prop.Length; i++) { ... prop[i] ...} + /// + public string[] TextMessages + { + get { return (string[])this.textMessages.ToArray(typeof(string)); } + + set + { + if (value != null) + this.textMessages = new ArrayList(value); + else + this.textMessages.Clear(); + } + } + + /// + /// Gets or sets the standard out. + /// + public string StdOut + { + get { return this.stdOut ?? string.Empty; } + set { this.stdOut = value; } + } + + /// + /// Gets or sets the standard err. + /// + public string StdErr + { + get { return this.stdErr ?? string.Empty; } + set { this.stdErr = value; } + } + + /// + /// Gets or sets the debug trace. + /// + public string DebugTrace + { + get { return this.debugTrace ?? string.Empty; } + set { this.debugTrace = value; } + } + + /// + /// Gets the path to the test results directory + /// + public string TestResultsDirectory + { + get + { + if (this.testRun == null) + { + Debug.Fail("'m_testRun' is null"); + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, TrxLoggerResources.Common_MissingRunInResult)); + } + + return this.testRun.GetResultFilesDirectory(this); + } + } + + /// + /// Gets the directory containing the test result files, relative to the root results directory + /// + public string RelativeTestResultsDirectory + { + get { return this.relativeTestResultsDirectory; } + } + + /// + /// Gets or sets the data row info. + /// + public int DataRowInfo + { + get { return this.dataRowInfo; } + set { this.dataRowInfo = value; } + } + + /// + /// Gets or sets the result type. + /// + public string ResultType + { + get { return this.resultType; } + set { this.resultType = value; } + } + + #endregion + + #region Overrides + public override bool Equals(object obj) + { + TestResult trm = obj as TestResult; + if (trm == null) + { + return false; + } + Debug.Assert(this.id != null, "id is null"); + Debug.Assert(trm.id != null, "test result message id is null"); + return this.id.Equals(trm.id); + } + + public override int GetHashCode() + { + Debug.Assert(this.id != null, "id is null"); + return this.id.GetHashCode(); + } + + #endregion + + /// + /// Helper function to add a text message info to the test result + /// + /// Message to be added + public void AddTextMessage(string text) + { + EqtAssert.ParameterNotNull(text, "text"); + this.textMessages.Add(text); + } + + /// + /// Sets the test run the test was executed in + /// + /// The test run the test was executed in + internal virtual void SetTestRun(TestRun testRun) + { + Debug.Assert(testRun != null, "'testRun' is null"); + this.testRun = testRun; + } + + /// + /// Adds result files to the collection + /// + /// Paths to the result files + internal void AddResultFiles(IEnumerable resultFileList) + { + Debug.Assert(resultFileList != null, "'resultFileList' is null"); + + string testResultsDirectory = this.TestResultsDirectory; + foreach (string resultFile in resultFileList) + { + Debug.Assert(!string.IsNullOrEmpty(resultFile), "'resultFile' is null or empty"); + Debug.Assert(resultFile.Trim() == resultFile, "'resultFile' has whitespace at the ends"); + + this.resultFiles[FileHelper.MakePathRelative(resultFile, testResultsDirectory)] = null; + } + } + + /// + /// Adds collector data entries to the collection + /// + /// The collector data entry to add + internal void AddCollectorDataEntries(IEnumerable collectorDataEntryList) + { + Debug.Assert(collectorDataEntryList != null, "'collectorDataEntryList' is null"); + + string testResultsDirectory = this.TestResultsDirectory; + foreach (CollectorDataEntry collectorDataEntry in collectorDataEntryList) + { + Debug.Assert(collectorDataEntry != null, "'collectorDataEntry' is null"); + Debug.Assert(!this.collectorDataEntries.Contains(collectorDataEntry), "The collector data entry already exists in the collection"); +#if DEBUG + // Verify that any URI data attachments in the entry have relative paths + foreach (IDataAttachment attachment in collectorDataEntry.Attachments) + { + UriDataAttachment uriDataAttachment = attachment as UriDataAttachment; + if (uriDataAttachment != null) + { + Debug.Assert(uriDataAttachment.Uri.IsAbsoluteUri, "'collectorDataEntry' contains a URI data attachment with a relative URI"); + } + } +#endif + + this.collectorDataEntries.Add(collectorDataEntry.Clone(testResultsDirectory, false)); + } + } + + + #region IXmlTestStore Members + + /// + /// Saves the class under the XmlElement.. + /// + /// + /// The parent xml. + /// + /// + /// The parameter + /// + public virtual void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + { + XmlPersistence helper = new XmlPersistence(); + + helper.SaveObject(this.id, element, ".", parameters); + helper.SaveSimpleField(element, "@testName", this.resultName, string.Empty); + helper.SaveSimpleField(element, "@computerName", this.computerInfo, string.Empty); + helper.SaveSimpleField(element, "@duration", this.duration, default(TimeSpan)); + helper.SaveSimpleField(element, "@startTime", this.startTime, default(DateTime)); + helper.SaveSimpleField(element, "@endTime", this.endTime, default(DateTime)); + helper.SaveGuid(element, "@testType", this.testType.Id); + + if (this.stdOut != null) + this.stdOut = this.stdOut.Trim(); + + if (this.stdErr != null) + this.stdErr = this.stdErr.Trim(); + + helper.SaveSimpleField(element, "@outcome", this.outcome, default(TestOutcome)); + helper.SaveSimpleField(element, "Output/StdOut", this.stdOut, string.Empty); + helper.SaveSimpleField(element, "Output/StdErr", this.stdErr, string.Empty); + helper.SaveSimpleField(element, "Output/DebugTrace", this.debugTrace, string.Empty); + helper.SaveObject(this.errorInfo, element, "Output/ErrorInfo", parameters); + helper.SaveGuid(element, "@testListId", this.categoryId.Id); + helper.SaveIEnumerable(this.textMessages, element, "Output/TextMessages", ".", "Message", parameters); + helper.SaveSimpleField(element, "@relativeResultsDirectory", this.relativeTestResultsDirectory, null); + helper.SaveIEnumerable(this.resultFiles.Keys, element, "ResultFiles", "@path", "ResultFile", parameters); + helper.SaveIEnumerable(this.collectorDataEntries, element, "CollectorDataEntries", ".", "Collector", parameters); + + if (this.dataRowInfo >= 0) + helper.SaveSimpleField(element, "@dataRowInfo", this.dataRowInfo, -1); + + if (!string.IsNullOrEmpty(this.resultType)) + helper.SaveSimpleField(element, "@resultType", this.resultType, string.Empty); + } + + #endregion + + private void Initialize() + { + this.textMessages = new ArrayList(); + this.dataRowInfo = -1; + } + } } diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResultAggregation.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResultAggregation.cs new file mode 100644 index 0000000000..1561fa287e --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResultAggregation.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel +{ + using System; + using System.Collections.Generic; + using Microsoft.TestPlatform.Extensions.TrxLogger.XML; + + /// + /// Test result aggregation. + /// + internal class TestResultAggregation : TestResult, ITestResultAggregation + { + protected List innerResults; + + public TestResultAggregation( + Guid runId, + Guid testId, + Guid executionId, + Guid parentExecutionId, + string resultName, + string computerName, + TestOutcome outcome, + TestType testType, + TestListCategoryId testCategoryId) : base(runId, testId, executionId, parentExecutionId, resultName, computerName, outcome, testType, testCategoryId) { } + + /// + /// Gets the inner results. + /// + public List InnerResults + { + get + { + if (innerResults == null) + { + innerResults = new List(); + } + return innerResults; + } + } + + public override void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + { + base.Save(element, parameters); + + XmlPersistence helper = new XmlPersistence(); + if (this.InnerResults.Count > 0) + helper.SaveIEnumerable(this.InnerResults, element, "InnerResults", ".", null, parameters); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRun.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRun.cs index d2d2500e7b..a40949ef3f 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRun.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestRun.cs @@ -17,7 +17,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel /// /// Class having information about a test run. /// - public sealed class TestRun + internal sealed class TestRun { #region Fields @@ -159,7 +159,7 @@ internal Guid Id /// /// Result directory. /// - internal string GetResultFilesDirectory(UnitTestResult result) + internal string GetResultFilesDirectory(TestResult result) { EqtAssert.ParameterNotNull(result, "result"); return Path.Combine(this.GetResultsDirectory(), result.RelativeTestResultsDirectory); diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestType.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestType.cs index a7d6611009..2ca37de652 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestType.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestType.cs @@ -10,7 +10,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel /// /// Class identifying test type. /// - public sealed class TestType : IXmlTestStore + internal sealed class TestType : IXmlTestStore { [StoreXmlSimpleField(".")] private Guid typeId; diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestElement.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestElement.cs index 6e6c3105eb..b50a9ef1e1 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestElement.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestElement.cs @@ -5,106 +5,33 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel { using System; using System.Diagnostics; - using System.Globalization; - using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; using Microsoft.TestPlatform.Extensions.TrxLogger.XML; - using TrxLoggerResources = Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger.Resources.TrxResource; - /// - /// Class for all tests + /// Unit test element. /// - internal class UnitTestElement : IXmlTestStore, IXmlTestStoreCustom + internal class UnitTestElement : TestElement, IXmlTestStoreCustom { - #region Constants - /// - /// Default priority for a test method that does not specify a priority - /// - internal const int DefaultPriority = int.MaxValue; - - /// - /// Timeout value indicating a not-set timeout - /// - internal const int NotSetTimeout = 0; - - private static readonly Guid TestTypeGuid = new Guid("13CDC9D9-DDB5-4fa4-A97D-D965CCFC6D4B"); - private static readonly TestType TestTypeInstance = new TestType(TestTypeGuid); - - #endregion - - #region Fields - - private TestId id; - - private string name; - - private string owner; - - private int priority; - - private TestCategoryItemCollection testCategories; - - private TestExecId executionId; - - private string storage; - private string codeBase; - - // partial or fully qualified name of the adapter used to execute the test - private string executorUriOfAdapter; - private TestMethod testMethod; - private bool isRunnable; - - private TestListCategoryId catId; - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// - /// The id. - /// - /// - /// The name. - /// - /// - /// The adapter type name. - /// - /// - /// The test method. - /// public UnitTestElement( Guid id, string name, - string executorUriOfAdapter, - TestMethod testMethod) + string adapter, + TestMethod testMethod) : base(id, name, adapter) { - Debug.Assert(!string.IsNullOrEmpty(name), "name is null"); - Debug.Assert(!string.IsNullOrEmpty(executorUriOfAdapter), "executorUriOfAdapter is null"); + Debug.Assert(!string.IsNullOrEmpty(adapter), "adapter is null"); Debug.Assert(testMethod != null, "testMethod is null"); + Debug.Assert(testMethod != null && testMethod.ClassName != null, "className is null"); - this.Initialize(); - - this.id = new TestId(id); - this.name = name; - this.executorUriOfAdapter = executorUriOfAdapter; this.testMethod = testMethod; - Debug.Assert(this.testMethod.ClassName != null, "className is null"); } - #endregion - - #region IXmlTestStoreCustom - string IXmlTestStoreCustom.ElementName { - get { return "UnitTest"; } + get { return Constants.UnitTestElementName; } } string IXmlTestStoreCustom.NamespaceUri @@ -112,207 +39,28 @@ string IXmlTestStoreCustom.NamespaceUri get { return null; } } - #endregion - - /// - /// Gets or sets the category id. - /// - /// - /// Instead of setting to null use TestListCategoryId.Uncategorized - /// - public TestListCategoryId CategoryId - { - get - { - return this.catId; - } - - set - { - EqtAssert.ParameterNotNull(value, "CategoryId"); - this.catId = value; - } - } - - /// - /// Gets the id. - /// - public TestId Id - { - get { return this.id; } - } - - /// - /// Gets or sets the execution id. - /// - public TestExecId ExecutionId - { - get { return this.executionId; } - set { this.executionId = value; } - } - - /// - /// Gets or sets the name. - /// - public string Name - { - get - { - return this.name; - } - - set - { - EqtAssert.ParameterNotNull(value, "Name"); - - this.name = value; - } - } - - /// - /// Gets or sets the storage. - /// - public string Storage - { - get - { - return this.storage; - } - - set - { - EqtAssert.StringNotNullOrEmpty(value, "Storage"); - this.storage = value.ToLowerInvariant(); - } - } - - /// - /// Gets or sets the priority. - /// - public int Priority - { - get - { - return this.priority; - } - - set - { - this.priority = value; - } - } - - /// - /// Gets or sets the owner. - /// - public string Owner - { - get - { - return this.owner; - } - - set - { - EqtAssert.ParameterNotNull(value, "Owner"); - this.owner = value; - } - } - /// /// Gets the test type. /// - public TestType TestType + public override TestType TestType { - get { return TestTypeInstance; } + get { return Constants.UnitTestType; } } /// - /// Gets or sets the test categories. + /// Gets or sets the storage. /// - public TestCategoryItemCollection TestCategories + public string CodeBase { - get - { - return this.testCategories; - } + get { return this.codeBase; } set { - EqtAssert.ParameterNotNull(value, "value"); - this.testCategories = value; - } - } - - /// - /// The assign code base. - /// - /// - /// The code base. - /// - public void AssignCodeBase(string cb) - { - EqtAssert.StringNotNullOrEmpty(cb, "codeBase"); - this.codeBase = cb; - } - - public bool IsRunnable - { - get { return this.isRunnable; } - } - - #region Overrides - - /// - /// Override for Tostring. - /// - /// - /// The . - /// - public override string ToString() - { - return string.Format( - CultureInfo.InvariantCulture, - "'{0}' {1}", - this.name != null ? this.name : TrxLoggerResources.Common_NullInMessages, - this.id != null ? this.id.ToString() : TrxLoggerResources.Common_NullInMessages); - } - - /// - /// Override for Equals. - /// - /// - /// The object to compare. - /// - /// - /// The . - /// - public override bool Equals(object other) - { - UnitTestElement otherTest = other as UnitTestElement; - if (otherTest == null) - { - return false; + EqtAssert.StringNotNullOrEmpty(value, "CodeBase"); + this.codeBase = value; } - - return this.id.Equals(otherTest.id); } - /// - /// Override for GetHashCode - /// - /// - /// The . - /// - public override int GetHashCode() - { - return this.id.GetHashCode(); - } - - #endregion - - #region IXmlTestStore Members - /// /// Saves the class under the XmlElement.. /// @@ -322,47 +70,14 @@ public override int GetHashCode() /// /// The parameter /// - public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) + public override void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) { + base.Save(element, parameters); XmlPersistence h = new XmlPersistence(); - h.SaveSimpleField(element, "@name", this.name, null); - h.SaveSimpleField(element, "@storage", this.storage, string.Empty); - h.SaveSimpleField(element, "@priority", this.priority, DefaultPriority); - h.SaveSimpleField(element, "Owners/Owner/@name", this.owner, string.Empty); - h.SaveObject(this.testCategories, element, "TestCategory", parameters); - - // Save the test ID. We exclude "test" from the default locations used by TestId, since this is already a test - // element. Ideally, we would let TestId save the IDs to the default locations, but the previous behavior of - // TestElement was to store the test ID at @testId, and since we can't change this, TestId supports custom - // locations for the IDs. See TestId.GetLocations for more info. - XmlTestStoreParameters testIdParameters = XmlTestStoreParameters.GetParameters(); - testIdParameters[TestId.IdLocationKey] = "@id"; - h.SaveObject(this.id, element, testIdParameters); - - if (this.executionId != null) - { - h.SaveGuid(element, "Execution/@id", this.executionId.Id); - } - h.SaveSimpleField(element, "TestMethod/@codeBase", this.codeBase, string.Empty); - h.SaveSimpleField(element, "TestMethod/@executorUriOfAdapter", this.executorUriOfAdapter, string.Empty); + h.SaveSimpleField(element, "TestMethod/@adapterTypeName", this.adapter, string.Empty); h.SaveObject(this.testMethod, element, "TestMethod", parameters); } - - #endregion //IXmlTestStore - - private void Initialize() - { - this.id = TestId.Empty; - this.name = string.Empty; - this.owner = string.Empty; - this.priority = DefaultPriority; - this.executionId = TestExecId.Empty; - this.testCategories = new TestCategoryItemCollection(); - this.storage = string.Empty; - this.isRunnable = true; - this.catId = TestListCategoryId.Uncategorized; - } } } diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestResult.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestResult.cs index 4c7027370d..66c815765e 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestResult.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UnitTestResult.cs @@ -4,455 +4,21 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel { using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - - using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; - using Microsoft.TestPlatform.Extensions.TrxLogger.XML; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - - using TrxLoggerResources = Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger.Resources.TrxResource; /// /// Class for unit test result. /// - internal class UnitTestResult: IXmlTestStore + internal class UnitTestResult: TestResultAggregation { - #region Fields - // id of test within run - private TestResultId id; - - // name of test within run - private string testName; - - private string computerInfo; - - private TimeSpan duration; - - private DateTime startTime; - - private DateTime endTime; - - // type of test (Guid) - private TestType testType; - - /// - /// The outcome of the test result - /// - private TestOutcome outcome; - - /// - /// The test run in which the test was executed - /// - private TestRun testRun; - - private string stdOut; - - private string stdErr; - - private string debugTrace; - - private TestResultErrorInfo errorInfo; - - private TestListCategoryId categoryId; - - private ArrayList textMessages; - - /// - /// Directory containing the test result files, relative to the root test results directory - /// - private string relativeTestResultsDirectory; - - /// - /// Paths to test result files, relative to the test results folder, sorted in increasing order - /// - private SortedList resultFiles = new SortedList(StringComparer.OrdinalIgnoreCase); - - /// - /// Information provided by data collectors for the test case - /// - private List collectorDataEntries = new List(); - - #endregion - - #region Constructor - - /// - /// Initializes a new instance of the class. - /// - /// - /// The computer name. - /// - /// - /// The run id. - /// - /// - /// The test. - /// - /// - /// The outcome. - /// - public UnitTestResult(string computerName, Guid runId, UnitTestElement test, TestOutcome outcome) - { - Debug.Assert(computerName != null, "computername is null"); - Debug.Assert(test != null, "test is null"); - Debug.Assert(!Guid.Empty.Equals(test.ExecutionId.Id), "ExecutionId is empty"); - Debug.Assert(!Guid.Empty.Equals(test.Id.Id), "Id is empty"); - - this.Initialize(); - - this.id = new TestResultId(runId, test.ExecutionId, test.Id); - this.testName = test.Name; - this.testType = test.TestType; - this.computerInfo = computerName; - - this.outcome = outcome; - this.categoryId = test.CategoryId; - this.relativeTestResultsDirectory = TestRunDirectories.GetRelativeTestResultsDirectory(test.ExecutionId.Id); - } - - #endregion - - #region properties - - /// - /// Gets or sets the end time. - /// - public DateTime EndTime - { - get { return this.endTime; } - set { this.endTime = value; } - } - - /// - /// Gets or sets the start time. - /// - public DateTime StartTime - { - get { return this.startTime; } - set { this.startTime = value; } - } - - /// - /// Gets or sets the duration. - /// - public TimeSpan Duration - { - get - { - return this.duration; - } - - set - { - // On some hardware the Stopwatch.Elapsed can return a negative number. This tends - // to happen when the duration of the test is very short and it is hardware dependent - // (seems to happen most on virtual machines or machines with AMD processors). To prevent - // reporting a negative duration, use TimeSpan.Zero when the elapsed time is less than zero. - EqtTrace.WarningIf(value < TimeSpan.Zero, "TestResult.Duration: The duration is being set to {0}. Since the duration is negative the duration will be updated to zero.", value); - this.duration = value > TimeSpan.Zero ? value : TimeSpan.Zero; - } - } - - /// - /// Gets the computer name. - /// - public string ComputerName - { - get { return this.computerInfo; } - } - - /// - /// Gets or sets the outcome. - /// - public TestOutcome Outcome - { - get { return this.outcome; } - set { this.outcome = value; } - } - - - /// - /// Gets or sets the id. - /// - public TestResultId Id - { - get { return this.id; } - internal set { this.id = value; } - } - - /// - /// Gets or sets the error message. - /// - public string ErrorMessage - { - get - { - if (this.errorInfo == null) - { - return string.Empty; - } - - return this.errorInfo.Message; - } - - set - { - this.errorInfo = new TestResultErrorInfo(value); - } - } - - /// - /// Gets or sets the error stack trace. - /// - public string ErrorStackTrace - { - get - { - if (this.errorInfo == null) - { - return string.Empty; - } - - return this.errorInfo.StackTrace; - } - - set - { - Debug.Assert(this.errorInfo != null, "errorInfo is null"); - this.errorInfo.StackTrace = value; - } - } - - /// - /// Gets the text messages. - /// - /// - /// Additional information messages from TestTextResultMessage, e.g. generated by TestOutcome.WriteLine. - /// Avoid using this property in the following way: for (int i=0; i<prop.Length; i++) { ... prop[i] ...} - /// - public string[] TextMessages - { - get - { - return (string[])this.textMessages.ToArray(typeof(string)); - } - - internal set - { - if (value != null) - { - this.textMessages = new ArrayList(value); - } - else - { - this.textMessages.Clear(); - } - } - } - - /// - /// Gets or sets the standard out. - /// - public string StdOut - { - get { return this.stdOut ?? string.Empty; } - set { this.stdOut = value; } - } - - /// - /// Gets or sets the standard err. - /// - public string StdErr - { - get { return this.stdErr ?? string.Empty; } - set { this.stdErr = value; } - } - - /// - /// Gets or sets the debug trace. - /// - public string DebugTrace - { - get { return this.debugTrace ?? string.Empty; } - set { this.debugTrace = value; } - } - - /// - /// Gets the path to the test results directory - /// - public string TestResultsDirectory - { - get - { - if (this.testRun == null) - { - Debug.Fail("'m_testRun' is null"); - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, TrxLoggerResources.Common_MissingRunInResult)); - } - - return this.testRun.GetResultFilesDirectory(this); - } - } - - /// - /// Gets the directory containing the test result files, relative to the root results directory - /// - internal string RelativeTestResultsDirectory - { - get - { - return this.relativeTestResultsDirectory; - } - } - - #endregion - - #region Overrides - public override bool Equals(object obj) - { - UnitTestResult trm = obj as UnitTestResult; - if (trm == null) - { - return false; - } - Debug.Assert(this.id != null, "id is null"); - Debug.Assert(trm.id != null, "test result message id is null"); - return this.id.Equals(trm.id); - } - - public override int GetHashCode() - { - Debug.Assert(this.id != null, "id is null"); - return this.id.GetHashCode(); - } - - #endregion - - /// - /// Helper function to add a text message info to the test result - /// - /// Message to be added - public void AddTextMessage(string text) - { - EqtAssert.ParameterNotNull(text, "text"); - this.textMessages.Add(text); - } - - /// - /// Sets the test run the test was executed in - /// - /// The test run the test was executed in - internal virtual void SetTestRun(TestRun testRun) - { - Debug.Assert(testRun != null, "'testRun' is null"); - this.testRun = testRun; - } - - /// - /// Adds result files to the collection - /// - /// Paths to the result files - internal void AddResultFiles(IEnumerable resultFileList) - { - Debug.Assert(resultFileList != null, "'resultFileList' is null"); - - string testResultsDirectory = this.TestResultsDirectory; - foreach (string resultFile in resultFileList) - { - Debug.Assert(!string.IsNullOrEmpty(resultFile), "'resultFile' is null or empty"); - Debug.Assert(resultFile.Trim() == resultFile, "'resultFile' has whitespace at the ends"); - Debug.Assert(Path.IsPathRooted(resultFile), "'resultFile' is a relative path"); - - this.resultFiles[FileHelper.MakePathRelative(resultFile, testResultsDirectory)] = null; - } - } - - /// - /// Adds collector data entries to the collection - /// - /// The collector data entry to add - internal void AddCollectorDataEntries(IEnumerable collectorDataEntryList) - { - Debug.Assert(collectorDataEntryList != null, "'collectorDataEntryList' is null"); - - string testResultsDirectory = this.TestResultsDirectory; - foreach (CollectorDataEntry collectorDataEntry in collectorDataEntryList) - { - Debug.Assert(collectorDataEntry != null, "'collectorDataEntry' is null"); - Debug.Assert(!this.collectorDataEntries.Contains(collectorDataEntry), "The collector data entry already exists in the collection"); -#if DEBUG - // Verify that any URI data attachments in the entry have relative paths - foreach (IDataAttachment attachment in collectorDataEntry.Attachments) - { - UriDataAttachment uriDataAttachment = attachment as UriDataAttachment; - if (uriDataAttachment != null) - { - Debug.Assert(uriDataAttachment.Uri.IsAbsoluteUri, "'collectorDataEntry' contains a URI data attachment with a relative URI"); - } - } -#endif - - this.collectorDataEntries.Add(collectorDataEntry.Clone(testResultsDirectory, false)); - } - } - - - #region IXmlTestStore Members - - /// - /// Saves the class under the XmlElement.. - /// - /// - /// The parent xml. - /// - /// - /// The parameter - /// - public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters) - { - XmlPersistence helper = new XmlPersistence(); - - helper.SaveObject(this.id, element, ".", parameters); - helper.SaveSimpleField(element, "@testName", this.testName, string.Empty); - helper.SaveSimpleField(element, "@computerName", this.computerInfo, string.Empty); - helper.SaveSimpleField(element, "@duration", this.duration, default(TimeSpan)); - helper.SaveSimpleField(element, "@startTime", this.startTime, default(DateTime)); - helper.SaveSimpleField(element, "@endTime", this.endTime, default(DateTime)); - helper.SaveGuid(element, "@testType", this.testType.Id); - - if (this.stdOut != null) - { - this.stdOut = this.stdOut.Trim(); - } - - if (this.stdErr != null) - { - this.stdErr = this.stdErr.Trim(); - } - - helper.SaveSimpleField(element, "@outcome", this.outcome, default(TestOutcome)); - helper.SaveSimpleField(element, "Output/StdOut", this.stdOut, string.Empty); - helper.SaveSimpleField(element, "Output/StdErr", this.stdErr, string.Empty); - helper.SaveSimpleField(element, "Output/DebugTrace", this.debugTrace, string.Empty); - helper.SaveObject(this.errorInfo, element, "Output/ErrorInfo", parameters); - - helper.SaveGuid(element, "@testListId", this.categoryId.Id); - - helper.SaveIEnumerable(this.textMessages, element, "Output/TextMessages", ".", "Message", parameters); - helper.SaveSimpleField(element, "@relativeResultsDirectory", this.relativeTestResultsDirectory, null); - helper.SaveIEnumerable(this.resultFiles.Keys, element, "ResultFiles", "@path", "ResultFile", parameters); - helper.SaveIEnumerable(this.collectorDataEntries, element, "CollectorDataEntries", ".", "Collector", parameters); - } - - #endregion - - private void Initialize() - { - this.textMessages = new ArrayList(); - } + public UnitTestResult( + Guid runId, + Guid testId, + Guid executionId, + Guid parentExecutionId, + string resultName, + string computerName, + TestOutcome outcome, + TestType testType, + TestListCategoryId testCategoryId) : base(runId, testId, executionId, parentExecutionId, resultName, computerName, outcome, testType, testCategoryId) { } } } diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs index efe86e3862..918a0839a2 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs @@ -15,7 +15,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel /// Class that provides a basic implementation of IUriAttachment, which can be used by plugin /// writers to send any resource accessible by a URI as an attachment. /// - public class UriDataAttachment : IDataAttachment, IXmlTestStore + internal class UriDataAttachment : IDataAttachment, IXmlTestStore { #region Private fields diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs index 0470951b77..7c96abb56f 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs @@ -4,6 +4,7 @@ namespace Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger { using System; + using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -11,50 +12,24 @@ namespace Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger using System.IO; using System.Text; using System.Xml; - using Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; using Microsoft.TestPlatform.Extensions.TrxLogger.XML; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.Utilities; - using ObjectModel.Logging; - + using TrxLoggerConstants = Microsoft.TestPlatform.Extensions.TrxLogger.Utility.Constants; using TrxLoggerObjectModel = Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; using TrxLoggerResources = Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger.Resources.TrxResource; /// /// Logger for Generating TRX /// - [FriendlyName(TrxLogger.FriendlyName)] - [ExtensionUri(TrxLogger.ExtensionUri)] + [FriendlyName(TrxLoggerConstants.FriendlyName)] + [ExtensionUri(TrxLoggerConstants.ExtensionUri)] internal class TrxLogger : ITestLoggerWithParameters { - #region Constants - - /// - /// Uri used to uniquely identify the TRX logger. - /// - public const string ExtensionUri = "logger://Microsoft/TestPlatform/TrxLogger/v1"; - - /// - /// Alternate user friendly string to uniquely identify the console logger. - /// - public const string FriendlyName = "Trx"; - - /// - /// Prefix of the data collector - /// - public const string DataCollectorUriPrefix = "dataCollector://"; - - /// - /// Log file parameter key - /// - public const string LogFileNameKey = "LogFileName"; - - #endregion - #region Fields /// @@ -63,9 +38,13 @@ internal class TrxLogger : ITestLoggerWithParameters private string trxFilePath; private TrxLoggerObjectModel.TestRun testRun; - private List results; - private List testElements; - private List entries; + private ConcurrentDictionary results; + private ConcurrentDictionary testElements; + private ConcurrentDictionary entries; + + // Caching results and inner test entries for constant time lookup for inner parents. + private ConcurrentDictionary innerResults; + private ConcurrentDictionary innerTestEntries; /// /// Specifies the run level "out" messages @@ -206,7 +185,7 @@ internal TrxLoggerObjectModel.TestOutcome TestResultOutcome /// /// Event args /// - internal void TestMessageHandler(object sender, TestRunMessageEventArgs e) + public void TestMessageHandler(object sender, TestRunMessageEventArgs e) { ValidateArg.NotNull(sender, "sender"); ValidateArg.NotNull(e, "e"); @@ -241,41 +220,43 @@ internal void TestMessageHandler(object sender, TestRunMessageEventArgs e) /// /// The eventArgs. /// - internal void TestResultHandler(object sender, ObjectModel.Logging.TestResultEventArgs e) + public void TestResultHandler(object sender, ObjectModel.Logging.TestResultEventArgs e) { + // Create test run if (this.testRun == null) - { - Guid runId = Guid.NewGuid(); - - this.testRun = new TestRun(runId); + CreateTestRun(); - // We cannot rely on the StartTime for the first test result - // In case of parallel, first test result is the fastest test and not the one which started first. - // Setting Started to DateTime.Now in Intialize will make sure we include the startup cost, which was being ignored earlier. - // This is in parity with the way we set this.testRun.Finished - this.testRun.Started = this.testRunStartTime; - - // Save default test settings - string runDeploymentRoot = FileHelper.ReplaceInvalidFileNameChars(this.testRun.Name); - TestRunConfiguration testrunConfig = new TestRunConfiguration("default"); + // Convert skipped test to a log entry as that is the behaviour of mstest. + if (e.Result.Outcome == ObjectModel.TestOutcome.Skipped) + this.HandleSkippedTest(e.Result); - testrunConfig.RunDeploymentRootDirectory = runDeploymentRoot; + var testType = Converter.GetTestType(e.Result); + var executionId = Converter.GetExecutionId(e.Result); - this.testRun.RunConfiguration = testrunConfig; - } + // Setting parent properties like parent result, parent test element, parent execution id. + var parentExecutionId = Converter.GetParentExecutionId(e.Result); + var parentTestResult = GetTestResult(parentExecutionId); + var parentTestElement = (parentTestResult != null) ? GetTestElement(parentTestResult.Id.TestId) : null; - // Convert skipped test to a log entry as that is the behaviour of mstest. - if (e.Result.Outcome == ObjectModel.TestOutcome.Skipped) + // Switch to flat test results in case any parent related information is missing. + if (parentTestResult == null || parentTestElement == null || parentExecutionId == Guid.Empty) { - this.HandleSkippedTest(e.Result); + parentTestResult = null; + parentTestElement = null; + parentExecutionId = Guid.Empty; } - // Create MSTest test element from rocksteady test case - UnitTestElement testElement = Converter.ToUnitTestElement(e.Result); + // Create trx test element from rocksteady test case + var testElement = GetOrCreateTestElement(executionId, parentExecutionId, testType, parentTestElement, e.Result); + + // Update test links. Test Links are updated in case of Ordered test. + UpdateTestLinks(testElement, parentTestElement); - // Conver the rocksteady result to MSTest result - TrxLoggerObjectModel.TestOutcome testOutcome = Converter.ToOutcome(e.Result.Outcome); - TrxLoggerObjectModel.UnitTestResult testResult = Converter.ToUnitTestResult(e.Result, testElement, testOutcome, this.testRun, this.testResultsDirPath); + // Convert the rocksteady result to trx test result + var testResult = CreateTestResult(executionId, parentExecutionId, testType, testElement, parentTestElement, parentTestResult, e.Result); + + // Update test entries + UpdateTestEntries(executionId, parentExecutionId, testElement, parentTestElement); // Set various counts (passtests, failed tests, total tests) this.totalTests++; @@ -288,19 +269,6 @@ internal void TestResultHandler(object sender, ObjectModel.Logging.TestResultEve { this.passTests++; } - - // Add results to in-memory lists that are saved to the xml at completion. - this.results.Add(testResult); - - if (!this.testElements.Contains(testElement)) - { - this.testElements.Add(testElement); - } - - // create a test entry - TestEntry te = new TestEntry(testElement.Id, TestListCategory.UncategorizedResults.Id); - te.ExecId = testElement.ExecutionId; - this.entries.Add(te); } /// @@ -312,7 +280,7 @@ internal void TestResultHandler(object sender, ObjectModel.Logging.TestResultEve /// /// Test run complete events arguments. /// - internal void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e) + public void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e) { if (this.testRun != null) { @@ -328,13 +296,13 @@ internal void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e) helper.SaveObject(this.testRun.RunConfiguration, rootElement, "TestSettings", parameters); // Save test results - helper.SaveIEnumerable(this.results, rootElement, "Results", ".", null, parameters); + helper.SaveIEnumerable(this.results.Values, rootElement, "Results", ".", null, parameters); // Save test definitions - helper.SaveIEnumerable(this.testElements, rootElement, "TestDefinitions", ".", null, parameters); + helper.SaveIEnumerable(this.testElements.Values, rootElement, "TestDefinitions", ".", null, parameters); // Save test entries - helper.SaveIEnumerable(this.entries, rootElement, "TestEntries", ".", "TestEntry", parameters); + helper.SaveIEnumerable(this.entries.Values, rootElement, "TestEntries", ".", "TestEntry", parameters); // Save default categories List categories = new List(); @@ -426,9 +394,11 @@ internal virtual void PopulateTrxFile(string trxFileName, XmlElement rootElement // Initializes trx logger cache. private void InitializeInternal() { - this.results = new List(); - this.testElements = new List(); - this.entries = new List(); + this.results = new ConcurrentDictionary(); + this.innerResults = new ConcurrentDictionary(); + this.testElements = new ConcurrentDictionary(); + this.entries = new ConcurrentDictionary(); + this.innerTestEntries = new ConcurrentDictionary(); this.runLevelErrorsAndWarnings = new List(); this.testRun = null; this.totalTests = 0; @@ -465,7 +435,7 @@ private void DeriveTrxFilePath() { if (this.parametersDictionary != null) { - var isLogFileNameParameterExists = this.parametersDictionary.TryGetValue(TrxLogger.LogFileNameKey, out string logFileNameValue); + var isLogFileNameParameterExists = this.parametersDictionary.TryGetValue(TrxLoggerConstants.LogFileNameKey, out string logFileNameValue); if (isLogFileNameParameterExists && !string.IsNullOrWhiteSpace(logFileNameValue)) { this.trxFilePath = Path.Combine(this.testResultsDirPath, logFileNameValue); @@ -490,6 +460,214 @@ private void SetDefaultTrxFilePath() this.trxFilePath = FileHelper.GetNextIterationFileName(this.testResultsDirPath, defaultTrxFileName, false); } + /// + /// Creates test run. + /// + private void CreateTestRun() + { + // Skip run creation if already exists. + if (testRun != null) + return; + + Guid runId = Guid.NewGuid(); + this.testRun = new TestRun(runId); + + // We cannot rely on the StartTime for the first test result + // In case of parallel, first test result is the fastest test and not the one which started first. + // Setting Started to DateTime.Now in Intialize will make sure we include the startup cost, which was being ignored earlier. + // This is in parity with the way we set this.testRun.Finished + this.testRun.Started = this.testRunStartTime; + + // Save default test settings + string runDeploymentRoot = FileHelper.ReplaceInvalidFileNameChars(this.testRun.Name); + TestRunConfiguration testrunConfig = new TestRunConfiguration("default"); + testrunConfig.RunDeploymentRootDirectory = runDeploymentRoot; + this.testRun.RunConfiguration = testrunConfig; + } + + /// + /// Gets test result from stored test results. + /// + /// + /// Test result + private ITestResult GetTestResult(Guid executionId) + { + ITestResult testResult = null; + + if (executionId != Guid.Empty) + { + this.results.TryGetValue(executionId, out testResult); + + if (testResult == null) + this.innerResults.TryGetValue(executionId, out testResult); + } + + return testResult; + } + + /// + /// Gets test element from stored test elements. + /// + /// + /// + private ITestElement GetTestElement(Guid testId) + { + this.testElements.TryGetValue(testId, out var testElement); + return testElement; + } + + /// + /// Gets or creates test element. + /// + /// + /// + /// + /// + /// + /// Trx test element + private ITestElement GetOrCreateTestElement(Guid executionId, Guid parentExecutionId, TestType testType, ITestElement parentTestElement, ObjectModel.TestResult rockSteadyTestResult) + { + ITestElement testElement = parentTestElement; + + // For scenarios like data driven tests, test element is same as parent test element. + if (parentTestElement != null && !parentTestElement.TestType.Equals(TrxLoggerConstants.OrderedTestType)) + { + return testElement; + } + + Guid testId = Converter.GetTestId(rockSteadyTestResult.TestCase); + + // Scenario for inner test case when parent test element is not present. + var testName = rockSteadyTestResult.TestCase.DisplayName; + if (parentTestElement == null && !string.IsNullOrEmpty(rockSteadyTestResult.DisplayName)) + { + testId = Guid.NewGuid(); + testName = rockSteadyTestResult.DisplayName; + } + + // Get test element + testElement = GetTestElement(testId); + + // Create test element + if (testElement == null) + { + testElement = Converter.ToTestElement(testId, executionId, parentExecutionId, testName, testType, rockSteadyTestResult.TestCase); + testElements.TryAdd(testId, testElement); + } + + return testElement; + } + + /// + /// Update test links + /// + /// + /// + private void UpdateTestLinks(ITestElement testElement, ITestElement parentTestElement) + { + if (parentTestElement != null && + parentTestElement.TestType.Equals(TrxLoggerConstants.OrderedTestType) && + !(parentTestElement as OrderedTestElement).TestLinks.ContainsKey(testElement.Id.Id)) + { + (parentTestElement as OrderedTestElement).TestLinks.Add(testElement.Id.Id, new TestLink(testElement.Id.Id, testElement.Name, testElement.Storage)); + } + } + + /// + /// Creates test result + /// + /// + /// + /// + /// + /// + /// + /// + /// Trx test result + private ITestResult CreateTestResult(Guid executionId, Guid parentExecutionId, TestType testType, + ITestElement testElement, ITestElement parentTestElement, ITestResult parentTestResult, ObjectModel.TestResult rocksteadyTestResult) + { + // Create test result + TrxLoggerObjectModel.TestOutcome testOutcome = Converter.ToOutcome(rocksteadyTestResult.Outcome); + var testResult = Converter.ToTestResult(testElement.Id.Id, executionId, parentExecutionId, testElement.Name, + this.testResultsDirPath, testType, testElement.CategoryId, testOutcome, this.testRun, rocksteadyTestResult); + + // Normal result scenario + if (parentTestResult == null) + { + this.results.TryAdd(executionId, testResult); + return testResult; + } + + // Ordered test inner result scenario + if (parentTestElement != null && parentTestElement.TestType.Equals(TrxLoggerConstants.OrderedTestType)) + { + (parentTestResult as TestResultAggregation).InnerResults.Add(testResult); + this.innerResults.TryAdd(executionId, testResult); + return testResult; + } + + // Data driven inner result scenario + if (parentTestElement != null && parentTestElement.TestType.Equals(TrxLoggerConstants.UnitTestType)) + { + (parentTestResult as TestResultAggregation).InnerResults.Add(testResult); + testResult.DataRowInfo = (parentTestResult as TestResultAggregation).InnerResults.Count; + testResult.ResultType = TrxLoggerConstants.InnerDataDrivenResultType; + parentTestResult.ResultType = TrxLoggerConstants.ParentDataDrivenResultType; + return testResult; + } + + return testResult; + } + + /// + /// Update test entries + /// + /// + /// + /// + /// + private void UpdateTestEntries(Guid executionId, Guid parentExecutionId, ITestElement testElement, ITestElement parentTestElement) + { + TestEntry te = new TestEntry(testElement.Id, TestListCategory.UncategorizedResults.Id); + te.ExecutionId = executionId; + + if (parentTestElement == null) + { + this.entries.TryAdd(executionId, te); + } + else if (parentTestElement.TestType.Equals(TrxLoggerConstants.OrderedTestType)) + { + te.ParentExecutionId = parentExecutionId; + + var parentTestEntry = GetTestEntry(parentExecutionId); + if (parentTestEntry != null) + parentTestEntry.TestEntries.Add(te); + + this.innerTestEntries.TryAdd(executionId, te); + } + } + + /// + /// Gets test entry from stored test entries. + /// + /// + /// Test entry + private TestEntry GetTestEntry(Guid executionId) + { + TestEntry testEntry = null; + + if (executionId != Guid.Empty) + { + this.entries.TryGetValue(executionId, out testEntry); + + if (testEntry == null) + this.innerTestEntries.TryGetValue(executionId, out testEntry); + } + + return testEntry; + } + #endregion } } \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Collection.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Collection.cs index 2cd1eeaf92..6d50491458 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Collection.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Collection.cs @@ -15,7 +15,7 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.Utility /// Base class for Eqt Collections. /// Fast collection, default implementations (Add/Remove/etc) do not allow null items and ignore duplicates. /// - public class EqtBaseCollection : ICollection, IXmlTestStore + internal class EqtBaseCollection : ICollection, IXmlTestStore { #region private classes /// diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Constants.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Constants.cs new file mode 100644 index 0000000000..420069e373 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Constants.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.Utility +{ + using System; + using Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + internal static class Constants + { + /// + /// Uri used to uniquely identify the TRX logger. + /// + public const string ExtensionUri = "logger://Microsoft/TestPlatform/TrxLogger/v1"; + + /// + /// Alternate user friendly string to uniquely identify the console logger. + /// + public const string FriendlyName = "Trx"; + + /// + /// Prefix of the data collector + /// + public const string DataCollectorUriPrefix = "dataCollector://"; + + /// + /// Log file parameter key + /// + public const string LogFileNameKey = "LogFileName"; + + /// + /// Ordered test element name + /// + public const string OrderedTestElementName = "OrderedTest"; + + /// + /// Unit test element name + /// + public const string UnitTestElementName = "UnitTest"; + + /// + /// Property Id storing the ExecutionId. + /// + public const string ExecutionIdPropertyIdentifier = "ExecutionId"; + + /// + /// Property Id storing the ParentExecutionId. + /// + public const string ParentExecutionIdPropertyIdentifier = "ParentExecId"; + + /// + /// Property If storing the TestType. + /// + public const string TestTypePropertyIdentifier = "TestType"; + + /// + /// Parent data driven result type. + /// + public const string ParentDataDrivenResultType = "DataDrivenTest"; + + /// + /// Inner data driven result type. + /// + public const string InnerDataDrivenResultType = "DataDrivenDataRow"; + + /// + /// Property Id storing the TMITestId. + /// + public const string TmiTestIdPropertyIdentifier = "MSTestDiscoverer.TmiTestId"; + + /// + /// Ordered test type guid + /// + public static readonly Guid OrderedTestTypeGuid = new Guid("ec4800e8-40e5-4ab3-8510-b8bf29b1904d"); + + /// + /// Ordered test type + /// + public static readonly TestType OrderedTestType = new TestType(OrderedTestTypeGuid); + + /// + /// Unit test type guid + /// + public static readonly Guid UnitTestTypeGuid = new Guid("13CDC9D9-DDB5-4fa4-A97D-D965CCFC6D4B"); + + /// + /// Unit test type + /// + public static readonly TestType UnitTestType = new TestType(UnitTestTypeGuid); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty ExecutionIdProperty = TestProperty.Register("ExecutionId", ExecutionIdPropertyIdentifier, typeof(Guid), TestPropertyAttributes.Hidden, typeof(ObjectModel.TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty ParentExecIdProperty = TestProperty.Register("ParentExecId", ParentExecutionIdPropertyIdentifier, typeof(Guid), TestPropertyAttributes.Hidden, typeof(ObjectModel.TestResult)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly TestProperty TestTypeProperty = TestProperty.Register("TestType", TestTypePropertyIdentifier, typeof(Guid), TestPropertyAttributes.Hidden, typeof(ObjectModel.TestResult)); + + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs index 137421ed19..ef531fedf5 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs @@ -11,11 +11,9 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.Utility using System.IO; using System.Linq; using System.Text; - using Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; using ObjectModel = Microsoft.VisualStudio.TestPlatform.ObjectModel; - using TrxLoggerResources = Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger.Resources.TrxResource; using TrxObjectModel = Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; @@ -25,97 +23,92 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.Utility internal class Converter { /// - /// Property Id storing the TMITestId. + /// Converts platform test case to trx test element. /// - private const string TmiTestIdPropertyIdentifier = "MSTestDiscoverer.TmiTestId"; - - /// - /// Converts the parameter rockSteady test case to test element - /// - /// - /// The rockSteady Test Result. - /// - /// - /// The . - /// - internal static TrxObjectModel.UnitTestElement ToUnitTestElement(ObjectModel.TestResult rockSteadyTestResult) + /// + /// + /// + /// + /// + /// + /// Trx test element + public static ITestElement ToTestElement( + Guid testId, + Guid executionId, + Guid parentExecutionId, + String testName, + TestType testType, + ObjectModel.TestCase rockSteadyTestCase) { - return GetQToolsTestElementFromTestCase(rockSteadyTestResult); + var testElement = CreateTestElement(testId, testName, rockSteadyTestCase.FullyQualifiedName, rockSteadyTestCase.ExecutorUri.ToString(), rockSteadyTestCase.Source, testType); + + testElement.Storage = rockSteadyTestCase.Source; + testElement.Priority = GetPriority(rockSteadyTestCase); + testElement.Owner = GetOwner(rockSteadyTestCase); + testElement.ExecutionId = new TestExecId(executionId); + testElement.ParentExecutionId = new TestExecId(parentExecutionId); + + var testCategories = GetCustomPropertyValueFromTestCase(rockSteadyTestCase, "MSTestDiscoverer.TestCategory"); + foreach (string testCategory in testCategories) + { + testElement.TestCategories.Add(testCategory); + } + + return testElement; } /// - /// Returns QToolsCommon.TestElement from rockSteady TestCase. + /// Converts the rockSteady result to unit test result /// - /// - /// The rockSteady Test Result. - /// - /// - /// The . - /// - internal static TrxObjectModel.UnitTestElement GetQToolsTestElementFromTestCase(ObjectModel.TestResult rockSteadyTestResult) + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// Trx test result object + public static ITestResult ToTestResult( + Guid testId, + Guid executionId, + Guid parentExecutionId, + string testName, + string trxFileDirectory, + TestType testType, + TestListCategoryId testCategoryId, + TrxObjectModel.TestOutcome testOutcome, + TestRun testRun, + ObjectModel.TestResult rockSteadyTestResult) { - ObjectModel.TestCase rockSteadyTestCase = rockSteadyTestResult.TestCase; - - // Fix for bug# 868033 - // Use TMI Test id when available. This is needed to ensure that test id in trx files is same as specified in - // .vsmdi files. - // (This is required for test explorer: It removes all test nodes where test id is not in expected test id when merging - // trx files from different batches). - Guid testId = GetTmiTestId(rockSteadyTestCase); - -#if NET451 - if (Guid.Empty.Equals(testId)) - { - testId = rockSteadyTestCase.Id; - } -#else - testId = Guid.NewGuid(); -#endif + var resultName = !string.IsNullOrEmpty(rockSteadyTestResult.DisplayName) ? rockSteadyTestResult.DisplayName : testName; + var testResult = CreateTestResult(testRun.Id, testId, executionId, parentExecutionId, resultName, Environment.MachineName, testOutcome, testType, testCategoryId); - string testDisplayName = rockSteadyTestCase.DisplayName; + if (rockSteadyTestResult.ErrorMessage != null) + testResult.ErrorMessage = rockSteadyTestResult.ErrorMessage; - // If it is an inner test case name - if (!string.IsNullOrEmpty(rockSteadyTestResult.DisplayName)) - { - testId = Guid.NewGuid(); // Changing of guid is done so that VS can load trx otherwise it fails with duplicate id error. - testDisplayName = rockSteadyTestResult.DisplayName; - } + if (rockSteadyTestResult.ErrorStackTrace != null) + testResult.ErrorStackTrace = rockSteadyTestResult.ErrorStackTrace; - TrxObjectModel.TestMethod testMethod = GetTestMethod(testDisplayName, rockSteadyTestCase); + if (rockSteadyTestResult.EndTime != null) + testResult.EndTime = rockSteadyTestResult.EndTime.UtcDateTime; - // convert the rocksteady tests to TestElement. - TrxObjectModel.UnitTestElement testElement = new TrxObjectModel.UnitTestElement(testId, testDisplayName, rockSteadyTestCase.ExecutorUri.ToString(), testMethod); - testElement.ExecutionId = new TrxObjectModel.TestExecId(Guid.NewGuid()); - testElement.AssignCodeBase(rockSteadyTestCase.Source); - testElement.Storage = rockSteadyTestCase.Source; + if (rockSteadyTestResult.StartTime != null) + testResult.StartTime = rockSteadyTestResult.StartTime.UtcDateTime; - if (rockSteadyTestCase.Traits != null) - { - ObjectModel.Trait priorityTrait = rockSteadyTestCase.Traits.FirstOrDefault(t => t.Name.Equals("Priority")); - if (priorityTrait != null) - { - int priorityValue; - if (Int32.TryParse(priorityTrait.Value, out priorityValue)) - { - testElement.Priority = priorityValue; - } - } + if (rockSteadyTestResult.Duration != null) + testResult.Duration = rockSteadyTestResult.Duration; - ObjectModel.Trait ownerTrait = rockSteadyTestCase.Traits.FirstOrDefault(t => t.Name.Equals("Owner")); - if (ownerTrait != null) - { - testElement.Owner = ownerTrait.Value; - } - } + // Clear exsting messages and store rocksteady result messages. + testResult.TextMessages = null; + UpdateResultMessages(testResult, rockSteadyTestResult); - // reading TestCategories from the testcase - var testCategories = GetCustomPropertyValueFromTestCase(rockSteadyTestCase, "MSTestDiscoverer.TestCategory"); - foreach (string testCategory in testCategories) - { - testElement.TestCategories.Add(testCategory); - } + // Save result attachments to target location. + UpdateTestResultAttachments(rockSteadyTestResult, testResult, testRun, trxFileDirectory, true); - return testElement; + return testResult; } /// @@ -128,7 +121,7 @@ internal static TrxObjectModel.UnitTestElement GetQToolsTestElementFromTestCase( /// The . /// [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] - internal static TrxObjectModel.TestOutcome ToOutcome(ObjectModel.TestOutcome rockSteadyOutcome) + public static TrxObjectModel.TestOutcome ToOutcome(ObjectModel.TestOutcome rockSteadyOutcome) { TrxObjectModel.TestOutcome outcome = TrxObjectModel.TestOutcome.Failed; @@ -153,35 +146,7 @@ internal static TrxObjectModel.TestOutcome ToOutcome(ObjectModel.TestOutcome roc return outcome; } - /// - /// Converts the rockSteady result to unit test result - /// - /// rock steady test result - /// testElement of that test - /// Test outcome - /// test run object - /// TRX file directory - /// TestResult object - internal static TrxObjectModel.UnitTestResult ToUnitTestResult( - ObjectModel.TestResult rockSteadyTestResult, - TrxObjectModel.UnitTestElement testElement, - TrxObjectModel.TestOutcome testOutcome, - TrxObjectModel.TestRun testRun, - string trxFileDirectory) - { - TrxObjectModel.UnitTestResult qtoolsResult = GetQToolsTestResultFromTestResult(rockSteadyTestResult, testElement, testOutcome, testRun); - - // Clear exsting messages and store rocksteady result messages. - qtoolsResult.TextMessages = null; - UpdateResultMessages(qtoolsResult, rockSteadyTestResult); - - // Save result attachments to target location. - UpdateTestResultAttachments(rockSteadyTestResult, qtoolsResult, testRun, trxFileDirectory, true); - - return qtoolsResult; - } - - internal static List ToCollectionEntries(IEnumerable attachmentSets, TestRun testRun, string trxFileDirectory) + public static List ToCollectionEntries(IEnumerable attachmentSets, TestRun testRun, string trxFileDirectory) { List collectorEntries = new List(); if (attachmentSets == null) @@ -191,7 +156,7 @@ internal static List ToCollectionEntries(IEnumerable ToCollectionEntries(IEnumerable ToResultFiles(IEnumerable attachmentSets, TestRun testRun, string trxFileDirectory, List errorMessages) + public static IList ToResultFiles(IEnumerable attachmentSets, TestRun testRun, string trxFileDirectory, List errorMessages) { List resultFiles = new List(); if (attachmentSets == null) @@ -211,7 +176,7 @@ internal static IList ToResultFiles(IEnumerable ToResultFiles(IEnumerable - /// Returns the QToolsCommon.TestResult object created from rockSteady TestResult. - /// - /// rock steady test result - /// testElement of that test - /// Test outcome - /// test run object - /// TestResult object - private static TrxObjectModel.UnitTestResult GetQToolsTestResultFromTestResult( - ObjectModel.TestResult rockSteadyTestResult, - TrxObjectModel.UnitTestElement testElement, - TrxObjectModel.TestOutcome testOutcome, - TrxObjectModel.TestRun testRun) - { - UnitTestResult testResult = new UnitTestResult(Environment.MachineName, testRun.Id, testElement, testOutcome); - if (rockSteadyTestResult.ErrorMessage != null) - { - testResult.ErrorMessage = rockSteadyTestResult.ErrorMessage; - } - - if (rockSteadyTestResult.ErrorStackTrace != null) - { - testResult.ErrorStackTrace = rockSteadyTestResult.ErrorStackTrace; - } - - // set start and end times - if (rockSteadyTestResult.EndTime != null) - { - testResult.EndTime = rockSteadyTestResult.EndTime.UtcDateTime; - } - if (rockSteadyTestResult.StartTime != null) - { - testResult.StartTime = rockSteadyTestResult.StartTime.UtcDateTime; - } - - if (rockSteadyTestResult.Duration != null) - { - testResult.Duration = rockSteadyTestResult.Duration; - } - - return testResult; - } - /// /// Copies the result messages to unitTestResult /// /// TRX TestResult /// rock steady test result - private static void UpdateResultMessages(TrxObjectModel.UnitTestResult unitTestResult, ObjectModel.TestResult testResult) + private static void UpdateResultMessages(TrxObjectModel.TestResult unitTestResult, ObjectModel.TestResult testResult) { StringBuilder debugTrace = new StringBuilder(); StringBuilder stdErr = new StringBuilder(); @@ -322,7 +244,7 @@ private static void UpdateResultMessages(TrxObjectModel.UnitTestResult unitTestR /// TestCase object extracted from the TestResult /// Property Name from the list of properties in TestCase /// list of properties - internal static List GetCustomPropertyValueFromTestCase(ObjectModel.TestCase testCase, string categoryID) + public static List GetCustomPropertyValueFromTestCase(ObjectModel.TestCase testCase, string categoryID) { var customProperty = testCase.Properties.FirstOrDefault(t => t.Id.Equals(categoryID)); @@ -343,52 +265,90 @@ internal static List GetCustomPropertyValueFromTestCase(ObjectModel.Test } /// - /// Return TMI Test id when available for TestPlatform TestCase. + /// Gets test id. + /// Return TMI Test id when available for TestPlatform test case. /// - /// - /// The rock Steady Test Case. - /// - /// - /// The . - /// - private static Guid GetTmiTestId(ObjectModel.TestCase rockSteadyTestCase) + /// + /// Test id + public static Guid GetTestId(ObjectModel.TestCase rockSteadyTestCase) { - Guid tmiTestId = Guid.Empty; - ObjectModel.TestProperty tmiTestIdProperty = rockSteadyTestCase.Properties.FirstOrDefault(property => property.Id.Equals(TmiTestIdPropertyIdentifier)); + Guid testId = Guid.Empty; + + // Setting test id to tmi test id. + ObjectModel.TestProperty tmiTestIdProperty = rockSteadyTestCase.Properties.FirstOrDefault( + property => property.Id.Equals(Constants.TmiTestIdPropertyIdentifier)); + if (null != tmiTestIdProperty) - { - tmiTestId = rockSteadyTestCase.GetPropertyValue(tmiTestIdProperty, Guid.Empty); - } - return tmiTestId; + testId = rockSteadyTestCase.GetPropertyValue(tmiTestIdProperty, Guid.Empty); + + // If tmi test id not present, picking from platform test id. + if (Guid.Empty.Equals(testId)) + testId = rockSteadyTestCase.Id; + + return testId; } /// - /// Returns TestMethod for given testCase name and its class name. + /// Gets parent execution id of test result. /// - /// test case display name - /// rockSteady Test Case - /// The - private static TrxObjectModel.TestMethod GetTestMethod(string testDisplayName, ObjectModel.TestCase rockSteadyTestCase) + /// + /// Parent execution id. + public static Guid GetParentExecutionId(ObjectModel.TestResult testResult) { - string className = "DefaultClassName"; - string testCaseName = rockSteadyTestCase.FullyQualifiedName; - if (testCaseName.Contains(".")) - { - className = testCaseName.Substring(0, testCaseName.LastIndexOf('.')); - } - else if (testCaseName.Contains("::")) - { - // if this is a C++ test case then we would have a "::" instaed of a '.' - className = testCaseName.Substring(0, testCaseName.LastIndexOf("::")); + TestProperty parentExecutionIdProperty = testResult.Properties.FirstOrDefault( + property => property.Id.Equals(Constants.ParentExecutionIdPropertyIdentifier)); - // rename for a consistent behaviour for all tests. - className = className.Replace("::", "."); - } + return parentExecutionIdProperty == null ? + Guid.Empty : + testResult.GetPropertyValue(parentExecutionIdProperty, Guid.Empty); + } + + /// + /// Gets execution Id of test result. Creates new id if not present in test result properties. + /// + /// + /// Execution id. + public static Guid GetExecutionId(ObjectModel.TestResult testResult) + { + TestProperty executionIdProperty = testResult.Properties.FirstOrDefault( + property => property.Id.Equals(Constants.ExecutionIdPropertyIdentifier)); + + var executionId = Guid.Empty; + if (executionIdProperty != null) + executionId = testResult.GetPropertyValue(executionIdProperty, Guid.Empty); - return new TrxObjectModel.TestMethod(testDisplayName, className); + return executionId.Equals(Guid.Empty) ? Guid.NewGuid() : executionId; } - private static void UpdateTestResultAttachments(ObjectModel.TestResult rockSteadyTestResult, TrxObjectModel.UnitTestResult testResult, TestRun testRun, string trxFileDirectory, bool addAttachments) + /// + /// Gets test type of test result. + /// Currently trx supports ordered test and unit test. All tests except ordered test are modified as unit test type. + /// + /// + /// Test type + public static TestType GetTestType(ObjectModel.TestResult testResult) + { + var testTypeGuid = Constants.UnitTestTypeGuid; + + // Get test type from property. default to unit test type. + TestProperty testTypeProperty = testResult.Properties.FirstOrDefault(property => property.Id.Equals(Constants.TestTypePropertyIdentifier)); + testTypeGuid = (testTypeProperty == null) ? testTypeGuid : testResult.GetPropertyValue(testTypeProperty, testTypeGuid); + + // Currently trx supports ordered test and unit test. All tests except ordered test are modified as unit test type. + return (testTypeGuid.Equals(Constants.OrderedTestTypeGuid)) ? + Constants.OrderedTestType : + Constants.UnitTestType; + } + + /// + /// Updates test result attachments. + /// + /// + /// + /// + /// + /// + private static void UpdateTestResultAttachments(ObjectModel.TestResult rockSteadyTestResult, TrxObjectModel.TestResult testResult, TestRun testRun, string trxFileDirectory, bool addAttachments) { if (rockSteadyTestResult.Attachments == null || rockSteadyTestResult.Attachments.Count == 0) { @@ -409,14 +369,14 @@ private static void UpdateTestResultAttachments(ObjectModel.TestResult rockStead try { // If the attachement is from data collector - if (attachmentSet.Uri.AbsoluteUri.StartsWith(TrxLogger.DataCollectorUriPrefix, StringComparison.OrdinalIgnoreCase)) + if (attachmentSet.Uri.AbsoluteUri.StartsWith(Constants.DataCollectorUriPrefix, StringComparison.OrdinalIgnoreCase)) { - CollectorDataEntry collectorEntry = ToCollectorEntry(attachmentSet, testResult.Id.ExecutionId.Id, testRun, trxFileDirectory); + CollectorDataEntry collectorEntry = ToCollectorEntry(attachmentSet, testResult.Id.ExecutionId, testRun, trxFileDirectory); collectorEntries.Add(collectorEntry); } else { - IList testResultFiles = ToResultFiles(attachmentSet, testResult.Id.ExecutionId.Id, testRun, trxFileDirectory); + IList testResultFiles = ToResultFiles(attachmentSet, testResult.Id.ExecutionId, testRun, trxFileDirectory); resultFiles.AddRange(testResultFiles); } } @@ -489,7 +449,7 @@ private static CollectorDataEntry ToCollectorEntry(ObjectModel.AttachmentSet att // (Trx viewer automatically adds In\ to the collected file. string fileName = Path.Combine(Environment.MachineName, Path.GetFileName(sourceFile)); Uri sourceFileUri = new Uri(fileName, UriKind.Relative); - UriDataAttachment dataAttachment = new UriDataAttachment(uriDataAttachment.Description, sourceFileUri); + TrxObjectModel.UriDataAttachment dataAttachment = new TrxObjectModel.UriDataAttachment(uriDataAttachment.Description, sourceFileUri); uriDataAttachments.Add(dataAttachment); } @@ -561,5 +521,140 @@ private static void CopyFile(string sourceFile, string targetFile) throw; } } + + /// + /// Gets priority of test. + /// + /// + /// Priority + private static int GetPriority(ObjectModel.TestCase rockSteadyTestCase) + { + int priority = int.MaxValue; + + ObjectModel.Trait priorityTrait = rockSteadyTestCase.Traits?.FirstOrDefault(t => t.Name.Equals("Priority")); + if (priorityTrait != null && Int32.TryParse(priorityTrait.Value, out int priorityValue)) + priority = priorityValue; + + return priority; + } + + /// + /// Gets owner of test. + /// + /// + /// Owner + private static string GetOwner(ObjectModel.TestCase rockSteadyTestCase) + { + string owner = null; + + ObjectModel.Trait ownerTrait = rockSteadyTestCase.Traits?.FirstOrDefault(t => t.Name.Equals("Owner")); + if (ownerTrait != null) + owner = ownerTrait.Value; + + return owner ?? string.Empty; + } + + /// + /// Gets TestMethod for given testCase name and its class name. + /// + /// test case display name + /// rockSteady Test Case + /// The + private static TestMethod GetTestMethod(string testDisplayName, string testCaseName, string source) + { + string className = "DefaultClassName"; + if (testCaseName.Contains(".")) + { + className = testCaseName.Substring(0, testCaseName.LastIndexOf('.')); + } + else if (testCaseName.Contains("::")) + { + // if this is a C++ test case then we would have a "::" instaed of a '.' + className = testCaseName.Substring(0, testCaseName.LastIndexOf("::")); + + // rename for a consistent behaviour for all tests. + className = className.Replace("::", "."); + } + else + { + // Setting class name as source name if FQDn doesnt have . or :: [E.g. ordered test, web test] + try + { + string testCaseSource = Path.GetFileNameWithoutExtension(source); + if (!String.IsNullOrEmpty(testCaseSource)) + { + className = testCaseSource; + } + } + catch (ArgumentException) + { + // If source is not valid file path, then className will continue to point default value. + } + } + + return new TestMethod(testDisplayName, className); + } + + /// + /// Create test element. + /// Currently trx supports only UnitTest and OrderedTest. All tests except OrderedTest all converted to UnitTest. + /// + /// + /// + /// + /// + /// + /// + /// Trx test element + private static TestElement CreateTestElement(Guid testId, string name, string fullyQualifiedName, string adapter, string source, TestType testType) + { + TestElement testElement = null; + + if (testType.Equals(Constants.OrderedTestType)) + { + testElement = new OrderedTestElement(testId, name, adapter); + } + else + { + var codeBase = source; + var testMethod = GetTestMethod(name, fullyQualifiedName, source); + + testElement = new UnitTestElement(testId, name, adapter, testMethod); + (testElement as UnitTestElement).CodeBase = codeBase; + } + + return testElement; + } + + /// + /// Create test result. + /// Currently trx supports only UnitTest and OrderedTest. All tests except OrderedTest all converted to unit test result. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// Trx test result + private static TrxObjectModel.TestResult CreateTestResult( + Guid runId, + Guid testId, + Guid executionId, + Guid parentExecutionId, + string resultName, + string computerName, + TrxObjectModel.TestOutcome outcome, + TestType testType, + TestListCategoryId testCategoryId) + { + return testType.Equals(Constants.OrderedTestType) ? + new TestResultAggregation(runId, testId, executionId, parentExecutionId, resultName, Environment.MachineName, outcome, testType, testCategoryId) : + new UnitTestResult(runId, testId, executionId, parentExecutionId, resultName, Environment.MachineName, outcome, testType, testCategoryId); + } + } } diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs index ea33391d12..293e874643 100644 --- a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs @@ -10,22 +10,17 @@ namespace Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests using System.IO; using System.Linq; using System.Xml; - + using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; using Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger; using Microsoft.VisualStudio.TestTools.UnitTesting; - using Moq; - - using Utility; - using VisualStudio.TestPlatform.ObjectModel; using VisualStudio.TestPlatform.ObjectModel.Client; using VisualStudio.TestPlatform.ObjectModel.Logging; - using ObjectModel = Microsoft.VisualStudio.TestPlatform.ObjectModel; + using TrxLoggerConstants = Microsoft.TestPlatform.Extensions.TrxLogger.Utility.Constants; using TrxLoggerObjectModel = Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; using TrxLoggerResources = Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger.Resources.TrxResource; - using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; [TestClass] public class TrxLoggerTests @@ -44,7 +39,7 @@ public void Initialize() this.testableTrxLogger = new TestableTrxLogger(); this.parameters = new Dictionary(2); this.parameters[DefaultLoggerParameterNames.TestRunDirectory] = TrxLoggerTests.DefaultTestRunDirectory; - this.parameters[TrxLogger.LogFileNameKey] = TrxLoggerTests.DefaultLogFileNameParameterValue; + this.parameters[TrxLoggerConstants.LogFileNameKey] = TrxLoggerTests.DefaultLogFileNameParameterValue; this.testableTrxLogger.Initialize(this.events.Object, this.parameters); } @@ -293,6 +288,222 @@ public void TestResultHandlerShouldCreateOneUnitTestElementForEachTestCase() Assert.AreEqual(this.testableTrxLogger.UnitTestElementCount, 2, "TestResultHandler is not creating test result entry for each test case"); } + [TestMethod] + public void TestResultHandlerShouldAddFlatResultsIfParentTestResultIsNotPresent() + { + ObjectModel.TestCase testCase1 = CreateTestCase("TestCase1"); + + Guid parentExecutionId = Guid.NewGuid(); + + ObjectModel.TestResult result1 = new ObjectModel.TestResult(testCase1); + result1.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result1.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + ObjectModel.TestResult result2 = new ObjectModel.TestResult(testCase1); + result2.Outcome = ObjectModel.TestOutcome.Failed; + result2.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result2.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + Mock resultEventArg1 = new Mock(result1); + Mock resultEventArg2 = new Mock(result2); + + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg2.Object); + + Assert.AreEqual(this.testableTrxLogger.TestResultCount, 2, "TestResultHandler is not creating flat results when parent result is not present."); + } + + [TestMethod] + public void TestResultHandlerShouldAddHierarchicalResultsIfParentTestResultIsPresent() + { + ObjectModel.TestCase testCase1 = CreateTestCase("TestCase1"); + + Guid parentExecutionId = Guid.NewGuid(); + + ObjectModel.TestResult result1 = new ObjectModel.TestResult(testCase1); + result1.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, parentExecutionId); + + ObjectModel.TestResult result2 = new ObjectModel.TestResult(testCase1); + result2.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result2.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + ObjectModel.TestResult result3 = new ObjectModel.TestResult(testCase1); + result3.Outcome = ObjectModel.TestOutcome.Failed; + result3.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result3.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + Mock resultEventArg1 = new Mock(result1); + Mock resultEventArg2 = new Mock(result2); + Mock resultEventArg3 = new Mock(result3); + + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg2.Object); + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg3.Object); + + Assert.AreEqual(this.testableTrxLogger.TestResultCount, 1, "TestResultHandler is not creating hierarchical results when parent result is present."); + Assert.AreEqual(this.testableTrxLogger.TotalTestCount, 3, "TestResultHandler is not adding all inner results in parent test result."); + } + + [TestMethod] + public void TestResultHandlerShouldAddSingleTestElementForDataDrivenTests() + { + ObjectModel.TestCase testCase1 = CreateTestCase("TestCase1"); + + Guid parentExecutionId = Guid.NewGuid(); + + ObjectModel.TestResult result1 = new ObjectModel.TestResult(testCase1); + result1.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, parentExecutionId); + + ObjectModel.TestResult result2 = new ObjectModel.TestResult(testCase1); + result2.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result2.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + ObjectModel.TestResult result3 = new ObjectModel.TestResult(testCase1); + result3.Outcome = ObjectModel.TestOutcome.Failed; + result3.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result3.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + Mock resultEventArg1 = new Mock(result1); + Mock resultEventArg2 = new Mock(result2); + Mock resultEventArg3 = new Mock(result3); + + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg2.Object); + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg3.Object); + + Assert.AreEqual(this.testableTrxLogger.UnitTestElementCount, 1, "TestResultHandler is adding multiple test elements for data driven tests."); + } + + [TestMethod] + public void TestResultHandlerShouldAddSingleTestEntryForDataDrivenTests() + { + ObjectModel.TestCase testCase1 = CreateTestCase("TestCase1"); + + Guid parentExecutionId = Guid.NewGuid(); + + ObjectModel.TestResult result1 = new ObjectModel.TestResult(testCase1); + result1.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, parentExecutionId); + + ObjectModel.TestResult result2 = new ObjectModel.TestResult(testCase1); + result2.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result2.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + ObjectModel.TestResult result3 = new ObjectModel.TestResult(testCase1); + result3.Outcome = ObjectModel.TestOutcome.Failed; + result3.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result3.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + Mock resultEventArg1 = new Mock(result1); + Mock resultEventArg2 = new Mock(result2); + Mock resultEventArg3 = new Mock(result3); + + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg2.Object); + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg3.Object); + + Assert.AreEqual(this.testableTrxLogger.TestEntryCount, 1, "TestResultHandler is adding multiple test entries for data driven tests."); + } + + [TestMethod] + public void TestResultHandlerShouldAddHierarchicalResultsForOrderedTest() + { + ObjectModel.TestCase testCase1 = CreateTestCase("TestCase1"); + ObjectModel.TestCase testCase2 = CreateTestCase("TestCase2"); + ObjectModel.TestCase testCase3 = CreateTestCase("TestCase3"); + + Guid parentExecutionId = Guid.NewGuid(); + + ObjectModel.TestResult result1 = new ObjectModel.TestResult(testCase1); + result1.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, parentExecutionId); + result1.SetPropertyValue(TrxLoggerConstants.TestTypeProperty, TrxLoggerConstants.OrderedTestTypeGuid); + + ObjectModel.TestResult result2 = new ObjectModel.TestResult(testCase2); + result2.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result2.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + ObjectModel.TestResult result3 = new ObjectModel.TestResult(testCase3); + result3.Outcome = ObjectModel.TestOutcome.Failed; + result3.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result3.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + Mock resultEventArg1 = new Mock(result1); + Mock resultEventArg2 = new Mock(result2); + Mock resultEventArg3 = new Mock(result3); + + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg2.Object); + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg3.Object); + + Assert.AreEqual(this.testableTrxLogger.TestResultCount, 1, "TestResultHandler is not creating hierarchical results for ordered test."); + Assert.AreEqual(this.testableTrxLogger.TotalTestCount, 3, "TestResultHandler is not adding all inner results in ordered test."); + } + + [TestMethod] + public void TestResultHandlerShouldAddMultipleTestElementsForOrderedTest() + { + ObjectModel.TestCase testCase1 = CreateTestCase("TestCase1"); + ObjectModel.TestCase testCase2 = CreateTestCase("TestCase2"); + ObjectModel.TestCase testCase3 = CreateTestCase("TestCase3"); + + Guid parentExecutionId = Guid.NewGuid(); + + ObjectModel.TestResult result1 = new ObjectModel.TestResult(testCase1); + result1.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, parentExecutionId); + result1.SetPropertyValue(TrxLoggerConstants.TestTypeProperty, TrxLoggerConstants.OrderedTestTypeGuid); + + ObjectModel.TestResult result2 = new ObjectModel.TestResult(testCase2); + result2.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result2.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + ObjectModel.TestResult result3 = new ObjectModel.TestResult(testCase3); + result3.Outcome = ObjectModel.TestOutcome.Failed; + result3.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result3.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + Mock resultEventArg1 = new Mock(result1); + Mock resultEventArg2 = new Mock(result2); + Mock resultEventArg3 = new Mock(result3); + + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg2.Object); + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg3.Object); + + Assert.AreEqual(this.testableTrxLogger.UnitTestElementCount, 3, "TestResultHandler is not adding multiple test elements for ordered test."); + } + + [TestMethod] + public void TestResultHandlerShouldAddSingleTestEntryForOrderedTest() + { + ObjectModel.TestCase testCase1 = CreateTestCase("TestCase1"); + ObjectModel.TestCase testCase2 = CreateTestCase("TestCase2"); + ObjectModel.TestCase testCase3 = CreateTestCase("TestCase3"); + + Guid parentExecutionId = Guid.NewGuid(); + + ObjectModel.TestResult result1 = new ObjectModel.TestResult(testCase1); + result1.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, parentExecutionId); + result1.SetPropertyValue(TrxLoggerConstants.TestTypeProperty, TrxLoggerConstants.OrderedTestTypeGuid); + + ObjectModel.TestResult result2 = new ObjectModel.TestResult(testCase2); + result2.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result2.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + ObjectModel.TestResult result3 = new ObjectModel.TestResult(testCase3); + result3.Outcome = ObjectModel.TestOutcome.Failed; + result3.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + result3.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + Mock resultEventArg1 = new Mock(result1); + Mock resultEventArg2 = new Mock(result2); + Mock resultEventArg3 = new Mock(result3); + + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg1.Object); + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg2.Object); + this.testableTrxLogger.TestResultHandler(new object(), resultEventArg3.Object); + + Assert.AreEqual(this.testableTrxLogger.TestEntryCount, 1, "TestResultHandler is adding multiple test entries for ordered test."); + } + [TestMethod] public void OutcomeOfRunWillBeFailIfAnyTestsFails() { @@ -365,7 +576,7 @@ public void OutcomeOfRunWillBeCompletedIfNoTestsFails() public void TheDefaultTrxFileNameShouldNotHaveWhiteSpace() { // To create default trx file, log file parameter should be null. - this.parameters[TrxLogger.LogFileNameKey] = null; + this.parameters[TrxLoggerConstants.LogFileNameKey] = null; this.testableTrxLogger.Initialize(this.events.Object, this.parameters); this.MakeTestRunComplete(); @@ -378,7 +589,7 @@ public void TheDefaultTrxFileNameShouldNotHaveWhiteSpace() public void DefaultTrxFileShouldCreateIfLogFileNameParameterNotPassed() { // To create default trx file, If LogFileName parameter not passed - this.parameters.Remove(TrxLogger.LogFileNameKey); + this.parameters.Remove(TrxLoggerConstants.LogFileNameKey); this.testableTrxLogger.Initialize(this.events.Object, this.parameters); this.MakeTestRunComplete(); @@ -418,7 +629,7 @@ public void GetCustomPropertyValueFromTestCaseShouldReadCategoyrAttributesFromTe /// Unit test for assigning or populating test categories read to the unit test element. /// [TestMethod] - public void GetQToolsTestElementFromTestCaseShouldAssignTestCategoryOfUnitTestElement() + public void ToTestElementShouldAssignTestCategoryOfUnitTestElement() { ObjectModel.TestCase testCase = CreateTestCase("TestCase1"); ObjectModel.TestResult result = new ObjectModel.TestResult(testCase); @@ -426,7 +637,7 @@ public void GetQToolsTestElementFromTestCaseShouldAssignTestCategoryOfUnitTestEl testCase.SetPropertyValue(testProperty, new[] { "AsmLevel", "ClassLevel", "MethodLevel" }); - TrxLoggerObjectModel.UnitTestElement unitTestElement = Converter.GetQToolsTestElementFromTestCase(result); + var unitTestElement = Converter.ToTestElement(testCase.Id, Guid.Empty, Guid.Empty, testCase.DisplayName, TrxLoggerConstants.UnitTestType, testCase); object[] expected = new[] { "MethodLevel", "ClassLevel", "AsmLevel" }; @@ -437,12 +648,12 @@ public void GetQToolsTestElementFromTestCaseShouldAssignTestCategoryOfUnitTestEl /// Unit test for regression when there's no test categories. /// [TestMethod] - public void GetQToolsTestElementFromTestCaseShouldNotFailWhenThereIsNoTestCategoreis() + public void ToTestElementShouldNotFailWhenThereIsNoTestCategoreis() { ObjectModel.TestCase testCase = CreateTestCase("TestCase1"); ObjectModel.TestResult result = new ObjectModel.TestResult(testCase); - TrxLoggerObjectModel.UnitTestElement unitTestElement = Converter.GetQToolsTestElementFromTestCase(result); + var unitTestElement = Converter.ToTestElement(testCase.Id, Guid.Empty, Guid.Empty, testCase.DisplayName, TrxLoggerConstants.UnitTestType, testCase); object[] expected = Enumerable.Empty().ToArray();