Skip to content

Commit

Permalink
Sets test states and outcomes (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
daveaglick committed Dec 14, 2018
1 parent 1415dc7 commit 78b2fed
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 19 deletions.
4 changes: 4 additions & 0 deletions ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.4.2

- [Feature] Sets test states and outcomes

# 0.4.1

- [Fix] Fixed a bug with source parsing and file paths
Expand Down
55 changes: 43 additions & 12 deletions src/AzurePipelines.TestLogger/LoggerQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ internal class LoggerQueue
private readonly string _jobName;

// Internal for testing
internal Dictionary<string, int> ParentIds { get; } = new Dictionary<string, int>();
internal Dictionary<string, TestResultParent> Parents { get; } = new Dictionary<string, TestResultParent>();
internal int RunId { get; set; }
internal string Source { get; set; }
internal string TestRunEndpoint { get; set; }

Expand All @@ -51,11 +52,14 @@ public void Flush()
// Any active consumer will circle back around and batch post the remaining queue
_consumeTask.Wait(TimeSpan.FromSeconds(60));

// Update the run and parents to a completed state
SendTestsCompleted(_consumeTaskCancellationSource.Token).Wait(TimeSpan.FromSeconds(60));

// Cancel any active HTTP requests if still hasn't finished flushing
_consumeTaskCancellationSource.Cancel();
if (!_consumeTask.Wait(TimeSpan.FromSeconds(10)))
{
throw new TimeoutException("cancellation didn't happen quickly");
throw new TimeoutException("Cancellation didn't happen quickly");
}
}
catch (Exception ex)
Expand Down Expand Up @@ -93,8 +97,8 @@ private async Task SendResultsAsync(ITestResult[] testResults, CancellationToken
if (TestRunEndpoint == null)
{
Source = GetSource(testResults);
int runId = await CreateTestRun(cancellationToken);
TestRunEndpoint = $"/{runId}/results";
RunId = await CreateTestRun(cancellationToken);
TestRunEndpoint = $"/{RunId}/results";
}

// Group results by their parent
Expand Down Expand Up @@ -156,7 +160,7 @@ internal IEnumerable<IGrouping<string, ITestResult>> GroupTestResultsByParent(IT
}
// At this point, name should always have at least one '.' to represent the Class.Method
// We need to start at the opening method paren if there is one
// We need to start at the opening method if there is one
int startIndex = name.IndexOf('(');
if(startIndex < 0)
{
Expand All @@ -171,7 +175,7 @@ internal async Task CreateParents(IEnumerable<IGrouping<string, ITestResult>> te
// Find the parents that don't exist
string[] parentsToAdd = testResultsByParent
.Select(x => x.Key)
.Where(x => !ParentIds.ContainsKey(x))
.Where(x => !Parents.ContainsKey(x))
.ToArray();

// Batch an add operation and record the new parent IDs
Expand All @@ -183,8 +187,10 @@ internal async Task CreateParents(IEnumerable<IGrouping<string, ITestResult>> te
{
{ "testCaseTitle", x },
{ "automatedTestName", x },
{ "outcome", "Passed" }, // Start with a passed outcome initially
{ "state", "InProgress" },
{ "automatedTestType", "UnitTest" },
{ "automatedTestTypeId", "13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" } // This is used in the sample response and also appears in web searches
{ "automatedTestTypeId", "13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" } // This is used in the sample response and also appears in web searches
};
if (!string.IsNullOrEmpty(Source))
{
Expand All @@ -203,7 +209,8 @@ internal async Task CreateParents(IEnumerable<IGrouping<string, ITestResult>> te
}
for (int c = 0; c < parents.Length; c++)
{
ParentIds.Add(parentsToAdd[c], ((JsonObject)parents[c]).ValueAsInt("id"));
int id = ((JsonObject)parents[c]).ValueAsInt("id");
Parents.Add(parentsToAdd[c], new TestResultParent(id));
}
}
}
Expand All @@ -213,13 +220,20 @@ private async Task SendTestResults(IEnumerable<IGrouping<string, ITestResult>> t
{
string request = "[ " + string.Join(", ", testResultsByParent.Select(x =>
{
int parentId = ParentIds[x.Key];
TestResultParent parent = Parents[x.Key];
string subResults = "[ " + string.Join(", ", x.Select(GetTestResultJson)) + " ]";
return $"{{ \"id\": { parentId }, \"subResults\": { subResults } }}";
string failedOutcome = x.Any(t => t.Outcome == TestOutcome.Failed) ? "\"outcome\": \"Failed\"," : null;
parent.Duration += Convert.ToInt64(x.Sum(t => t.Duration.TotalMilliseconds));
return $@"{{
""id"": { parent.Id },
""durationInMs"": { parent.Duration },
{ failedOutcome },
""subResults"": { subResults }
}}";
})) + " ]";
await _apiClient.SendAsync(new HttpMethod("PATCH"), TestRunEndpoint, "5.0-preview.5", request, cancellationToken);
}

private string GetTestResultJson(ITestResult testResult)
{
Dictionary<string, object> properties = new Dictionary<string, object>
Expand All @@ -230,7 +244,7 @@ private string GetTestResultJson(ITestResult testResult)

if (testResult.Outcome == TestOutcome.Passed || testResult.Outcome == TestOutcome.Failed)
{
int duration = Convert.ToInt32(testResult.Duration.TotalMilliseconds);
long duration = Convert.ToInt64(testResult.Duration.TotalMilliseconds);
properties.Add("durationInMs", duration.ToString(CultureInfo.InvariantCulture));

string errorStackTrace = testResult.ErrorStackTrace;
Expand Down Expand Up @@ -266,5 +280,22 @@ private string GetTestResultJson(ITestResult testResult)

return properties.ToJson();
}

private async Task SendTestsCompleted(CancellationToken cancellationToken)
{
// Mark all parents as completed
string parentRequest = "[ " + string.Join(", ", Parents.Values.Select(x =>
$@"{{
""id"": { x.Id },
""state"": ""Completed""
}}")) + " ]";
await _apiClient.SendAsync(new HttpMethod("PATCH"), TestRunEndpoint, "5.0-preview.5", parentRequest, cancellationToken);

// Mark the overall test run as completed
string testRunRequest = $@"{{
""state"": ""Completed""
}}";
await _apiClient.SendAsync(new HttpMethod("PATCH"), $"/{RunId}", "5.0-preview.5", testRunRequest, cancellationToken);
}
}
}
14 changes: 14 additions & 0 deletions src/AzurePipelines.TestLogger/TestResultParent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace AzurePipelines.TestLogger
{
internal class TestResultParent
{
public int Id { get; }

public long Duration { get; set; }

public TestResultParent(int id)
{
Id = id;
}
}
}
14 changes: 7 additions & 7 deletions tests/AzurePipelines.TestLogger.Tests/LoggerQueueTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public void CreateParents()
Source = "Fizz.Buzz",
TestRunEndpoint = "/ep"
};
loggerQueue.ParentIds.Add("FitzFixture", 123);
loggerQueue.Parents.Add("FitzFixture", new TestResultParent(123));
ITestResult[] testResults = new[]
{
new TestTestResult
Expand Down Expand Up @@ -209,25 +209,25 @@ public void CreateParents()
{
""testCaseTitle"": ""FooFixture"",
""automatedTestName"": ""FooFixture"",
""outcome"": ""Passed"",
""state"": ""InProgress"",
""automatedTestType"": ""UnitTest"",
""automatedTestTypeId"": ""13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b"",
""automatedTestStorage"": ""Fizz.Buzz""
},
{
""testCaseTitle"": ""FutzFixture.NestedFixture"",
""automatedTestName"": ""FutzFixture.NestedFixture"",
""outcome"": ""Passed"",
""state"": ""InProgress"",
""automatedTestType"": ""UnitTest"",
""automatedTestTypeId"": ""13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b"",
""automatedTestStorage"": ""Fizz.Buzz""
}
]")
});
loggerQueue.ParentIds.ShouldBe(new[]
{
KeyValuePair.Create("FitzFixture", 123),
KeyValuePair.Create("FooFixture", 100),
KeyValuePair.Create("FutzFixture.NestedFixture", 101)
}, true);
loggerQueue.Parents.Keys.ShouldBe(new[] { "FitzFixture", "FooFixture", "FutzFixture.NestedFixture" }, true);
loggerQueue.Parents.Values.Select(x => x.Id).ShouldBe(new[] { 123, 100, 101 }, true);
}
}
}

0 comments on commit 78b2fed

Please sign in to comment.