Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/Adapter/MSTest.CoreAdapter/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ internal static class Constants

internal static readonly TestProperty DoNotParallelizeProperty = TestProperty.Register("MSTestDiscoverer.DoNotParallelize", DoNotParallelizeLabel, typeof(bool), TestPropertyAttributes.Hidden, typeof(TestCase));

internal static readonly TestProperty ExecutionIdProperty = TestProperty.Register("ExecutionId", ExecutionIdLabel, typeof(Guid), TestPropertyAttributes.Hidden, typeof(TestResult));

internal static readonly TestProperty ParentExecIdProperty = TestProperty.Register("ParentExecId", ParentExecIdLabel, typeof(Guid), TestPropertyAttributes.Hidden, typeof(TestResult));

internal static readonly TestProperty InnerResultsCountProperty = TestProperty.Register("InnerResultsCount", InnerResultsCountLabel, typeof(int), TestPropertyAttributes.Hidden, typeof(TestResult));

#endregion

#region Private Constants
Expand All @@ -59,6 +65,9 @@ internal static class Constants
private const string PriorityLabel = "Priority";
private const string DeploymentItemsLabel = "DeploymentItems";
private const string DoNotParallelizeLabel = "DoNotParallelize";
private const string ExecutionIdLabel = "ExecutionId";
private const string ParentExecIdLabel = "ParentExecId";
private const string InnerResultsCountLabel = "InnerResultsCount";

#endregion
}
Expand Down
102 changes: 80 additions & 22 deletions src/Adapter/MSTest.CoreAdapter/Execution/TestMethodRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,12 +215,23 @@ internal UnitTestResult[] RunTestMethod()
Debug.Assert(this.testMethodInfo.TestMethod != null, "Test method should not be null.");

List<UTF.TestResult> results = new List<UTF.TestResult>();
var isDataDriven = false;

// Parent result. Added in properties bag only when results are greater than 1.
var parentResultWatch = new Stopwatch();
parentResultWatch.Start();
var parentResult = new UTF.TestResult
{
Outcome = UTF.UnitTestOutcome.InProgress,
ExecutionId = Guid.NewGuid()
};

if (this.testMethodInfo.TestMethodOptions.Executor != null)
{
UTF.DataSourceAttribute[] dataSourceAttribute = this.testMethodInfo.GetAttributes<UTF.DataSourceAttribute>(false);
if (dataSourceAttribute != null && dataSourceAttribute.Length == 1)
{
isDataDriven = true;
Stopwatch watch = new Stopwatch();
watch.Start();

Expand Down Expand Up @@ -296,6 +307,7 @@ internal UnitTestResult[] RunTestMethod()

if (testDataSources != null && testDataSources.Length > 0)
{
isDataDriven = true;
foreach (var testDataSource in testDataSources)
{
foreach (var data in testDataSource.GetData(this.testMethodInfo.MethodInfo))
Expand Down Expand Up @@ -345,35 +357,81 @@ internal UnitTestResult[] RunTestMethod()
this.testMethodInfo.TestMethodName);
}

if (results != null && results.Count > 0)
{
// aggregate for data driven tests
UTF.UnitTestOutcome aggregateOutcome = UTF.UnitTestOutcome.Passed;
parentResultWatch.Stop();
parentResult.Duration = parentResultWatch.Elapsed;

foreach (var result in results)
{
if (result.Outcome != UTF.UnitTestOutcome.Passed)
{
if (aggregateOutcome != UTF.UnitTestOutcome.Failed)
{
if (result.Outcome == UTF.UnitTestOutcome.Failed
|| aggregateOutcome != UTF.UnitTestOutcome.Timeout)
{
aggregateOutcome = result.Outcome;
}
}
}
}
// Get aggregate outcome.
var aggregateOutcome = this.GetAggregateOutcome(results);
this.testContext.SetOutcome(aggregateOutcome);

this.testContext.SetOutcome(aggregateOutcome);
// Set a result in case no result is present.
if (!results.Any())
{
results.Add(new UTF.TestResult() { Outcome = aggregateOutcome, TestFailureException = new TestFailedException(UnitTestOutcome.Error, Resource.UTA_NoTestResult) });
}
else

// In case of data driven, set parent info in results.
if (isDataDriven)
{
this.testContext.SetOutcome(UTF.UnitTestOutcome.Unknown);
results.Add(new UTF.TestResult() { Outcome = UTF.UnitTestOutcome.Unknown, TestFailureException = new TestFailedException(UnitTestOutcome.Error, Resource.UTA_NoTestResult) });
parentResult.Outcome = aggregateOutcome;
results = this.UpdateResultsWithParentInfo(results, parentResult);
}

return results.ToArray().ToUnitTestResults();
}

/// <summary>
/// Gets aggregate outcome.
/// </summary>
/// <param name="results">Results.</param>
/// <returns>Aggregate outcome.</returns>
private UTF.UnitTestOutcome GetAggregateOutcome(List<UTF.TestResult> results)
{
// In case results are not present, set outcome as unknown.
if (!results.Any())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: results.Count==0 seems more readable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of list, using Count or Any doesn't matter. But in case of Enumerable any is preferred over count as count iterates through entire list. Thus as a general practice, for both list and enumerable I prefer any.

{
return UTF.UnitTestOutcome.Unknown;
}

// Get aggregate outcome.
var aggregateOutcome = results[0].Outcome;
foreach (var result in results)
{
UnitTestOutcomeExtensions.GetMoreImportantOutcome(aggregateOutcome, result.Outcome);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to assign result of GetMoreImportantOutcome() back to aggregateOutcome?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. UTs caught this and were failing. Corrected.

}

return aggregateOutcome;
}

/// <summary>
/// Updates given resutls with parent info if results are greater than 1.
/// Add parent results as first result in updated result.
/// </summary>
/// <param name="results">Results.</param>
/// <param name="parentResult">Parent results.</param>
/// <returns>Updated results which contains parent result as first result. All other results contains parent result info.</returns>
private List<UTF.TestResult> UpdateResultsWithParentInfo(List<UTF.TestResult> results, UTF.TestResult parentResult)
{
// Return results in case there are no results.
if (!results.Any())
{
return results;
}

// UpdatedResults contain parent result at first position and remaining results has parent info updated.
var updatedResults = new List<UTF.TestResult>();
updatedResults.Add(parentResult);

foreach (var result in results)
{
result.ExecutionId = Guid.NewGuid();
result.ParentExecId = parentResult.ExecutionId;
parentResult.InnerResultsCount++;

updatedResults.Add(result);
}

return updatedResults;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public static UnitTestResult[] ToUnitTestResults(this UTF.TestResult[] testResul
unitTestResult.DisplayName = testResults[i].DisplayName;
unitTestResult.DatarowIndex = testResults[i].DatarowIndex;
unitTestResult.ResultFiles = testResults[i].ResultFiles;
unitTestResult.ExecutionId = testResults[i].ExecutionId;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add/modify some existing tests to cover this as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

unitTestResult.ParentExecId = testResults[i].ParentExecId;
unitTestResult.InnerResultsCount = testResults[i].InnerResultsCount;
unitTestResults[i] = unitTestResult;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ public static UnitTestOutcome ToUnitTestOutcome(this UTF.UnitTestOutcome framewo
/// <returns> Outcome which has higher importance.</returns>
internal static UTF.UnitTestOutcome GetMoreImportantOutcome(this UTF.UnitTestOutcome outcome1, UTF.UnitTestOutcome outcome2)
{
return outcome1 < outcome2 ? outcome1 : outcome2;
var unitTestOutcome1 = outcome1.ToUnitTestOutcome();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch 👍

var unitTestOutcome2 = outcome2.ToUnitTestOutcome();
return unitTestOutcome1 < unitTestOutcome2 ? outcome1 : outcome2;
}
}
}
19 changes: 19 additions & 0 deletions src/Adapter/MSTest.CoreAdapter/ObjectModel/UnitTestResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,21 @@ internal UnitTestResult(UnitTestOutcome outcome, string errorMessage)
/// </summary>
public string ErrorStackTrace { get; internal set; }

/// <summary>
/// Gets the execution id of the result
/// </summary>
public Guid ExecutionId { get; internal set; }

/// <summary>
/// Gets the parent execution id of the result
/// </summary>
public Guid ParentExecId { get; internal set; }

/// <summary>
/// Gets the inner results count of the result
/// </summary>
public int InnerResultsCount { get; internal set; }

/// <summary>
/// Gets the duration of the result
/// </summary>
Expand Down Expand Up @@ -149,6 +164,10 @@ internal TestResult ToTestResult(TestCase testCase, DateTimeOffset startTime, Da
EndTime = endTime
};

testResult.SetPropertyValue<Guid>(Constants.ExecutionIdProperty, this.ExecutionId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar for this. Add/modify existing tests to cover this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

testResult.SetPropertyValue<Guid>(Constants.ParentExecIdProperty, this.ParentExecId);
testResult.SetPropertyValue<int>(Constants.InnerResultsCountProperty, this.InnerResultsCount);

if (!string.IsNullOrEmpty(this.StandardOut))
{
TestResultMessage message = new TestResultMessage(TestResultMessage.StandardOutCategory, this.StandardOut);
Expand Down
15 changes: 15 additions & 0 deletions src/TestFramework/MSTest.Core/Attributes/VSTestAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,21 @@ public TestResult()
/// </summary>
public string TestContextMessages { get; set; }

/// <summary>
/// Gets or sets the execution id of the result.
/// </summary>
public Guid ExecutionId { get; set; }

/// <summary>
/// Gets or sets the parent execution id of the result.
/// </summary>
public Guid ParentExecId { get; set; }

/// <summary>
/// Gets or sets the inner results count of the result.
/// </summary>
public int InnerResultsCount { get; set; }

/// <summary>
/// Gets or sets the duration of test execution.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions test/E2ETests/Automation.CLI/CLITestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,16 @@ public void ValidateFailedTests(string source, params string[] failedTests)
this.ValidateFailedTestsContain(source, failedTests);
}

/// <summary>
/// Validates the count of failed tests.
/// </summary>
/// <param name="expectedFailedTestsCount">Expected failed tests count.</param>
public void ValidateFailedTestsCount(int expectedFailedTestsCount)
{
// Make sure only expected number of tests failed and not more.
Assert.AreEqual(expectedFailedTestsCount, this.runEventsHandler.FailedTests.Count);
}

/// <summary>
/// Validates if the test results have the specified set of skipped tests.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ public void ExecuteCustomTestExtensibilityWithTestDataTests()
"CustomTestMethod2 (B)",
"CustomTestMethod2 (B)",
"CustomTestMethod2 (B)");
this.ValidateFailedTests(

// Parent results should fail and thus failed count should be 7.
this.ValidateFailedTestsCount(7);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we add this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ValidateFailedTests method checks for number of tests. Number of params passed to the methods are considered as numbers of tests. In our case, we dont want to pass parent results as param but still want to consider it for results length check. Thus checking the count of tests separately.

this.ValidateFailedTestsContain(
TestAssembly,
"CustomTestMethod2 (A)",
"CustomTestMethod2 (A)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,9 +429,11 @@ public void RunTestMethodForMultipleResultsReturnMultipleResults()
var testMethodRunner = new TestMethodRunner(testMethodInfo, this.testMethod, this.testContextImplementation, false);
Copy link
Member

@jayaranigarg jayaranigarg May 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a bunch of data-driven tests in this class. Please make sure they are validating correct things even though they are not failing. Ideally they should have failed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Various data driven tests were failing. We have fixed them in the PR.


var results = testMethodRunner.Execute();
Assert.AreEqual(2, results.Length);
Assert.AreEqual(AdapterTestOutcome.Passed, results[0].Outcome);
Assert.AreEqual(AdapterTestOutcome.Failed, results[1].Outcome);
Assert.AreEqual(3, results.Length);

// results[0] is parent result.
Assert.AreEqual(AdapterTestOutcome.Passed, results[1].Outcome);
Assert.AreEqual(AdapterTestOutcome.Failed, results[2].Outcome);
}

[TestMethodV1]
Expand Down Expand Up @@ -543,9 +545,11 @@ public void RunTestMethodShouldSetDataRowIndexForDataDrivenTestsWhenDataIsProvid
var results = testMethodRunner.RunTestMethod();

// check for datarowIndex
Assert.AreEqual(results[0].DatarowIndex, 0);
Assert.AreEqual(results[1].DatarowIndex, 1);
Assert.AreEqual(results[2].DatarowIndex, 2);
// 1st is parent result.
Assert.AreEqual(results[0].DatarowIndex, -1);
Assert.AreEqual(results[1].DatarowIndex, 0);
Assert.AreEqual(results[2].DatarowIndex, 1);
Assert.AreEqual(results[3].DatarowIndex, 2);
}

[TestMethodV1]
Expand All @@ -570,9 +574,11 @@ public void RunTestMethodShoudlRunOnlyDataSourceTestsWhenBothDataSourceAndDataRo
var results = testMethodRunner.RunTestMethod();

// check for datarowIndex as only DataSource Tests are Run
Assert.AreEqual(results[0].DatarowIndex, 0);
Assert.AreEqual(results[1].DatarowIndex, 1);
Assert.AreEqual(results[2].DatarowIndex, 2);
// 1st is parent result.
Assert.AreEqual(results[0].DatarowIndex, -1);
Assert.AreEqual(results[1].DatarowIndex, 0);
Assert.AreEqual(results[2].DatarowIndex, 1);
Assert.AreEqual(results[3].DatarowIndex, 2);
}

[TestMethodV1]
Expand Down Expand Up @@ -640,8 +646,80 @@ public void RunTestMethodShouldSetResultFilesIfPresentForDataDrivenTests()
this.testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(this.methodInfo, It.IsAny<Type>(), It.IsAny<bool>())).Returns(attribs);

var results = testMethodRunner.RunTestMethod();
CollectionAssert.Contains(results[0].ResultFiles.ToList(), "C:\\temp.txt");
CollectionAssert.Contains(results[1].ResultFiles.ToList(), "C:\\temp.txt");
CollectionAssert.Contains(results[2].ResultFiles.ToList(), "C:\\temp.txt");
}

[TestMethodV1]
public void RunTestMethodShouldReturnParentResultForDataDrivenTests()
{
var testMethodInfo = new TestableTestmethodInfo(this.methodInfo, this.testClassInfo, this.testMethodOptions, () => new UTF.TestResult());
var testMethodRunner = new TestMethodRunner(testMethodInfo, this.testMethod, this.testContextImplementation, false);

UTF.DataSourceAttribute dataSourceAttribute = new UTF.DataSourceAttribute("DummyConnectionString", "DummyTableName");

var attribs = new Attribute[] { dataSourceAttribute };

// Setup mocks
this.testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(this.methodInfo, It.IsAny<Type>(), It.IsAny<bool>())).Returns(attribs);
this.testablePlatformServiceProvider.MockTestDataSource.Setup(tds => tds.GetData(testMethodInfo, this.testContextImplementation)).Returns(new object[] { 1, 2, 3 });

var results = testMethodRunner.RunTestMethod();

// check for parent result
Assert.AreEqual(4, results.Length);
Assert.AreEqual(results[0].ExecutionId, results[1].ParentExecId);
}

[TestMethodV1]
public void RunTestMethodShouldNotReturnParentResultForNonDataDrivenTests()
{
var testMethodInfo = new TestableTestmethodInfo(this.methodInfo, this.testClassInfo, this.testMethodOptions, () => new UTF.TestResult());
var testMethodRunner = new TestMethodRunner(testMethodInfo, this.testMethod, this.testContextImplementation, false);

UTF.DataSourceAttribute dataSourceAttribute = new UTF.DataSourceAttribute("DummyConnectionString", "DummyTableName");

var attribs = new Attribute[] { dataSourceAttribute };

// Setup mocks
this.testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(this.methodInfo, It.IsAny<Type>(), It.IsAny<bool>())).Returns(attribs);
this.testablePlatformServiceProvider.MockTestDataSource.Setup(tds => tds.GetData(testMethodInfo, this.testContextImplementation)).Returns(new object[] { 1 });

var results = testMethodRunner.RunTestMethod();

// Parent result should not exist.
Assert.AreEqual(1, results.Length);
Assert.AreEqual(Guid.Empty, results[0].ParentExecId);
}

[TestMethodV1]
public void RunTestMethodShouldSetParentResultOutcomeProperly()
{
var testMethodAttributeMock = new Mock<UTF.TestMethodAttribute>();
testMethodAttributeMock.Setup(_ => _.Execute(It.IsAny<UTF.ITestMethod>())).Returns(new[]
{
new UTF.TestResult { Outcome = UTF.UnitTestOutcome.Passed },
new UTF.TestResult { Outcome = UTF.UnitTestOutcome.Failed }
});

var localTestMethodOptions = new TestMethodOptions
{
Timeout = 200,
Executor = testMethodAttributeMock.Object,
TestContext = this.testContextImplementation,
ExpectedException = null
};

var testMethodInfo = new TestableTestmethodInfo(this.methodInfo, this.testClassInfo, localTestMethodOptions, null);
var testMethodRunner = new TestMethodRunner(testMethodInfo, this.testMethod, this.testContextImplementation, false);

var results = testMethodRunner.Execute();
Assert.AreEqual(3, results.Length);

// Parent result should show proper aggregate outcome.
Assert.AreEqual(AdapterTestOutcome.Failed, results[0].Outcome);
Assert.AreEqual(AdapterTestOutcome.Passed, results[1].Outcome);
Assert.AreEqual(AdapterTestOutcome.Failed, results[2].Outcome);
}

#region Test data
Expand Down