Skip to content

Commit

Permalink
Parent test result support for data driven tests (#417)
Browse files Browse the repository at this point in the history
* Parent test result support for data driven tests

* smoke tests fix

* review comments

* review comments and UTs

* UTs

* review comments
  • Loading branch information
abhishkk authored Jun 5, 2018
1 parent 78676d7 commit e13ca08
Show file tree
Hide file tree
Showing 11 changed files with 416 additions and 36 deletions.
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())
{
return UTF.UnitTestOutcome.Unknown;
}

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

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;
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();
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);
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
14 changes: 11 additions & 3 deletions test/E2ETests/Automation.CLI/CLITestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,20 @@ public void ValidatePassedTests(params string[] passedTests)
/// </remarks>
public void ValidateFailedTests(string source, params string[] failedTests)
{
// Make sure only expected number of tests failed and not more.
Assert.AreEqual(failedTests.Length, this.runEventsHandler.FailedTests.Count);

this.ValidateFailedTestsCount(failedTests.Length);
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);
this.ValidateFailedTestsContain(
TestAssembly,
"CustomTestMethod2 (A)",
"CustomTestMethod2 (A)",
Expand Down
Loading

5 comments on commit e13ca08

@Exoow
Copy link
Contributor

@Exoow Exoow commented on e13ca08 Jun 8, 2018

Choose a reason for hiding this comment

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

Is there any way to disable this parent reporting? It's immensely annoying to see tests with no logging in them in VSTS. And it skews test result counts.

@abhishkk
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Exoow Parent result allows to support hierarchical result viewing both is vsts and trx. We are planning to support hierarchical test results view in vsts. Trx already shows hierarchical results for data driven.
Parent result reporting helps in allowing hierarchical view of results.

What do you mean by no logging in tests?

@Exoow
Copy link
Contributor

@Exoow Exoow commented on e13ca08 Jun 8, 2018

Choose a reason for hiding this comment

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

See the screenshot below. It is simply listed alongside its datarows, but doesn't show any info (which is obvious). However, it's not a test that actually has results by itself, so it's completely useless (to me) in this and related views.

image

@abhishkk
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Exoow The vsts results view experience is going to change in coming few weeks after which all the data driven results will be shown as hierarchical. All data driven results will be under the parent result.

@Exoow
Copy link
Contributor

@Exoow Exoow commented on e13ca08 Jun 8, 2018

Choose a reason for hiding this comment

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

Okay, let's hope for the best. :)

Please sign in to comment.