diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestElement.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestElement.cs
index c6040f3a90..8ab1e1c55a 100644
--- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestElement.cs
+++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestElement.cs
@@ -34,6 +34,7 @@ internal abstract class TestElement : ITestElement, IXmlTestStore
protected TestExecId _parentExecutionId;
protected TestCategoryItemCollection _testCategories;
protected WorkItemCollection _workItems;
+ protected TestPropertyItemCollection _testProperties;
protected TestListCategoryId _catId;
public TestElement(Guid id, string name, string adapter)
@@ -178,6 +179,16 @@ public WorkItemCollection WorkItems
}
}
+ public TestPropertyItemCollection TestProperties
+ {
+ get { return _testProperties; }
+ set
+ {
+ EqtAssert.ParameterNotNull(value, "TestProperties");
+ _testProperties = value;
+ }
+ }
+
///
/// Gets the adapter name.
///
@@ -238,6 +249,7 @@ public virtual void Save(System.Xml.XmlElement element, XmlTestStoreParameters p
h.SaveSimpleField(element, "@priority", _priority, DefaultPriority);
h.SaveSimpleField(element, "Owners/Owner/@name", _owner, string.Empty);
h.SaveObject(_testCategories, element, "TestCategory", parameters);
+ h.SaveObject(_testProperties, element, "Properties", parameters);
if (_executionId != null)
h.SaveGuid(element, "Execution/@id", _executionId.Id);
@@ -262,6 +274,7 @@ private void Initialize()
_parentExecutionId = TestExecId.Empty;
_testCategories = new TestCategoryItemCollection();
_workItems = new WorkItemCollection();
+ _testProperties = new TestPropertyItemCollection();
_isRunnable = true;
_catId = TestListCategoryId.Uncategorized;
}
diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestPropertyItems.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestPropertyItems.cs
new file mode 100644
index 0000000000..e854e09f9d
--- /dev/null
+++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestPropertyItems.cs
@@ -0,0 +1,244 @@
+// 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.Text;
+
+using Utility;
+
+using XML;
+
+#region TestPropertyItem
+///
+/// Stores a string which categorizes the Test
+///
+internal sealed class TestPropertyItem : IXmlTestStore
+{
+ #region Fields
+ [StoreXmlSimpleField(Location = "Key", DefaultValue = "")]
+ private readonly string _key = string.Empty;
+
+ [StoreXmlSimpleField(Location = "Value", DefaultValue = "")]
+ private readonly string _value = string.Empty;
+
+ #endregion
+
+ #region Constructors
+ ///
+ /// Create a new item with the key/value set
+ ///
+ /// The key.
+ /// The value.
+ public TestPropertyItem(string key, string value)
+ {
+ // Treat null as empty.
+ if (string.IsNullOrEmpty(key))
+ throw new ArgumentNullException(nameof(key));
+
+ if (value == null)
+ {
+ value = String.Empty;
+ }
+
+ _key = key;
+ _value = value;
+ }
+
+ #endregion
+
+ #region Properties/Methods
+ ///
+ /// Gets the Key for this TestProperty
+ ///
+ public string Key
+ {
+ get
+ {
+ return _key;
+ }
+ }
+
+ ///
+ /// Gets the Value for this TestProperty
+ ///
+ public string Value
+ {
+ get
+ {
+ return _value;
+ }
+ }
+
+ #endregion
+
+ #region Methods - overrides
+ ///
+ /// Compare the values of the items
+ ///
+ /// Value being compared to.
+ /// True if the values are the same and false otherwise.
+ public override bool Equals(object other)
+ {
+ TestPropertyItem otherItem = other as TestPropertyItem;
+ if (otherItem == null)
+ {
+ return false;
+ }
+ return String.Equals(_key, otherItem._key, StringComparison.OrdinalIgnoreCase) && String.Equals(_value, otherItem._value, StringComparison.Ordinal);
+ }
+
+ ///
+ /// Convert the property name to a hashcode
+ ///
+ /// Hashcode of the category.
+ public override int GetHashCode()
+ {
+ return _key.ToUpperInvariant().GetHashCode() ^ _value.GetHashCode();
+ }
+
+ ///
+ /// Convert the property name to a string
+ ///
+ /// The property.
+ public override string ToString()
+ {
+ return _key + " = " + _value;
+ }
+ #endregion
+
+ #region IXmlTestStore Members
+
+ ///
+ /// Saves the class under the XmlElement.
+ ///
+ /// XmlElement element
+ /// XmlTestStoreParameters parameters
+ public void Save(System.Xml.XmlElement element, XmlTestStoreParameters parameters)
+ {
+ new XmlPersistence().SaveSingleFields(element, this, parameters);
+ }
+
+ #endregion
+}
+#endregion
+
+#region TestPropertyItemCollection
+///
+/// A collection of strings which categorize the test.
+///
+internal sealed class TestPropertyItemCollection : EqtBaseCollection
+{
+ #region Constructors
+ ///
+ /// Creates an empty TestPropertyItemCollection.
+ ///
+ public TestPropertyItemCollection()
+ {
+ _childElementName = "Property";
+ }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Adds the property.
+ ///
+ /// Key to be added.
+ /// Value to be added.
+ public void Add(string key, string value)
+ {
+ Add(new TestPropertyItem(key, value));
+ }
+
+ ///
+ /// Adds the property.
+ ///
+ /// Property to be added.
+ public override void Add(TestPropertyItem item)
+ {
+ EqtAssert.ParameterNotNull(item, nameof(item));
+
+ // Don't add empty items.
+ if (!String.IsNullOrEmpty(item.Key))
+ {
+ base.Add(item);
+ }
+ }
+
+ ///
+ /// Convert the TestPropertyItemCollection to a string.
+ /// each item is surrounded by a comma (,)
+ ///
+ ///
+ public override string ToString()
+ {
+ var returnString = new StringBuilder();
+ if (Count > 0)
+ {
+ returnString.Append(',');
+ foreach (TestPropertyItem item in this)
+ {
+ returnString.Append(item.ToString());
+ returnString.Append(',');
+ }
+ }
+
+ return returnString.ToString();
+ }
+
+ ///
+ /// Compare the collection items
+ ///
+ /// other collection
+ /// true if the collections contain the same items
+ public override bool Equals(object obj)
+ {
+ TestPropertyItemCollection other = obj as TestPropertyItemCollection;
+ bool result = false;
+
+ if (other == null)
+ {
+ // Other object is not a TestPropertyItemCollection.
+ result = false;
+ }
+ else if (Object.ReferenceEquals(this, other))
+ {
+ // The other object is the same object as this one.
+ result = true;
+ }
+ else if (Count != other.Count)
+ {
+ // The count of categories in the other object does not
+ // match this one, so they are not equal.
+ result = false;
+ }
+ else
+ {
+ // Check each item and return on the first mismatch.
+ foreach (TestPropertyItem item in this)
+ {
+ if (!other.Contains(item))
+ {
+ result = false;
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Return the hash code of this collection
+ ///
+ /// The hashcode.
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+ #endregion
+}
+#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 7ad22686e5..4569e3efb0 100644
--- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Collection.cs
+++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Collection.cs
@@ -61,7 +61,7 @@ public void Dispose()
#region Fields
protected Hashtable _container;
- private string _childElementName;
+ protected string _childElementName;
#endregion
#region Constructors
diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs
index e26b44f53a..02ee8708e2 100644
--- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs
+++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs
@@ -75,6 +75,11 @@ public ITestElement ToTestElement(
testElement.WorkItems.Add(workItem);
}
+ foreach (var trait in rockSteadyTestCase.Traits)
+ {
+ testElement.TestProperties.Add(trait.Name, trait.Value);
+ }
+
return testElement;
}
diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs
index 85b027ab70..b1a14bbaca 100644
--- a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs
+++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs
@@ -804,6 +804,53 @@ public void TestRunInformationShouldContainUtcDateTime()
ValidateDateTimeInTrx(_testableTrxLogger.TrxFile);
}
+ [TestMethod]
+ public void TraitsForTestCaseShouldBeOutputtedInTrx()
+ {
+ ObjectModel.TestCase testCase = CreateTestCase("TestCaseWithTraits");
+ testCase.Traits.Add("Trait1", "Value1");
+ testCase.Traits.Add("Trait2", "Value2");
+
+ var result = new ObjectModel.TestResult(testCase);
+ var resultEventArg1 = new Mock(result);
+
+ _testableTrxLogger.TestResultHandler(new object(), resultEventArg1.Object);
+
+ var testRunCompleteEventArgs = TrxLoggerTests.CreateTestRunCompleteEventArgs();
+ _testableTrxLogger.TestRunCompleteHandler(new object(), testRunCompleteEventArgs);
+
+ Assert.IsTrue(File.Exists(_testableTrxLogger.TrxFile), string.Format("TRX file: {0}, should have got created.", _testableTrxLogger.TrxFile));
+
+ // Validate TRX output - we expect something like this:
+ /*
+
+
+ Trait1
+ Value1
+
+
+ Trait2
+ Value2
+
+
+ */
+
+ using FileStream file = File.OpenRead(_testableTrxLogger.TrxFile);
+ using XmlReader reader = XmlReader.Create(file);
+ XDocument document = XDocument.Load(reader);
+ XNamespace ns = document.Root.GetDefaultNamespace();
+ var testDefinitionsElement = document.Descendants(ns.GetName("TestDefinitions")).FirstOrDefault();
+ var unitTestElement = testDefinitionsElement.Element(ns.GetName("UnitTest"));
+
+ Assert.IsNotNull(unitTestElement, "Unable to find UnitTest element in TRX");
+
+ var propertiesElement = unitTestElement.Element(ns.GetName("Properties"));
+ Assert.IsNotNull(propertiesElement, "Unable to find Properties element for UnitTest element in TRX");
+
+ var propertyElements = propertiesElement.Elements(ns.GetName("Property"));
+ Assert.AreEqual(2, propertyElements.Count(), "We should have two properties logged in TRX");
+ }
+
private void ValidateDateTimeInTrx(string trxFileName)
{
using FileStream file = File.OpenRead(trxFileName);
diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs
index 1e8864df3a..328673ee0c 100644
--- a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs
+++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs
@@ -114,6 +114,24 @@ public void ToTestElementShouldAssignWorkItemOfUnitTestElement()
CollectionAssert.AreEquivalent(expected, unitTestElement.WorkItems.ToArray());
}
+ [TestMethod]
+ public void ToTestElementShouldShouldAssignTraitsOfUnitTestElement()
+ {
+ var testCase = CreateTestCase("TestCaseWithTraits");
+ var result = new TestPlatformObjectModel.TestResult(testCase);
+
+ testCase.Traits.Add("Trait1", "Value1");
+ testCase.Traits.Add("Trait2", "Value2");
+
+ var unitTestElement = _converter.ToTestElement(testCase.Id, Guid.Empty, Guid.Empty, testCase.DisplayName, TrxLoggerConstants.UnitTestType, testCase);
+
+ // They only way to check for TestProperties is to cast the ITestElement object to UnitTestElement
+ Assert.IsInstanceOfType(unitTestElement, typeof(UnitTestElement));
+
+ var expected = new[] { new TestPropertyItem("Trait1", "Value1"), new TestPropertyItem("Trait2", "Value2") };
+ CollectionAssert.AreEquivalent(expected, ((UnitTestElement)unitTestElement).TestProperties.ToArray());
+ }
+
///
/// Unit test for regression when there's no test categories.
///