Skip to content

Commit

Permalink
Fixed test result duration (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
icnocop authored May 14, 2022
1 parent 437ab26 commit b6e77d4
Show file tree
Hide file tree
Showing 14 changed files with 192 additions and 215 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
runs-on: windows-latest

env:
VERSION: 1.2.1
VERSION: 1.2.2

strategy:
matrix:
Expand Down
1 change: 1 addition & 0 deletions AzurePipelines.TestLogger.sln
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{B8315F74-E
build.cake = build.cake
build.ps1 = build.ps1
build.ruleset = build.ruleset
.github\workflows\build.yml = .github\workflows\build.yml
Directory.Build.props = Directory.Build.props
LICENSE = LICENSE
README.md = README.md
Expand Down
4 changes: 4 additions & 0 deletions ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 1.2.2

- [Fix] Fixed test result duration. (#15, @icnocop).

# 1.2.1

- [Fix] Added Semver dependency to NuGet package. (#12, @icnocop).
Expand Down
97 changes: 45 additions & 52 deletions src/AzurePipelines.TestLogger/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,27 +59,20 @@ public IApiClient WithDefaultCredentials()
return this;
}

public async Task<string> MarkTestCasesCompleted(int testRunId, IEnumerable<TestResultParent> testCases, DateTime completedDate, CancellationToken cancellationToken)
{
string requestBody = GetTestCasesAsCompleted(testCases, completedDate);

return await SendAsync(new HttpMethod("PATCH"), $"/{testRunId}/results", requestBody, cancellationToken).ConfigureAwait(false);
}

public async Task<int> AddTestRun(TestRun testRun, CancellationToken cancellationToken)
{
string requestBody = new Dictionary<string, object>
{
{ "name", testRun.Name },
{ "build", new Dictionary<string, object> { { "id", testRun.BuildId } } },
{ "startedDate", testRun.StartedDate.ToString(_dateFormatString) },
{ "isAutomated", true }
string requestBody = new Dictionary<string, object>
{
{ "name", testRun.Name },
{ "build", new Dictionary<string, object> { { "id", testRun.BuildId } } },
{ "startedDate", testRun.StartedDate.ToString(_dateFormatString) },
{ "isAutomated", true }
}.ToJson();

string responseString = await SendAsync(HttpMethod.Post, null, requestBody, cancellationToken).ConfigureAwait(false);
using (StringReader reader = new StringReader(responseString))
{
JsonObject response = JsonDeserializer.Deserialize(reader) as JsonObject;
JsonObject response = JsonDeserializer.Deserialize(reader) as JsonObject;
return response.ValueAsInt("id");
}
}
Expand Down Expand Up @@ -108,13 +101,13 @@ public async Task<int[]> AddTestCases(int testRunId, string[] testCaseNames, Dat
{
Dictionary<string, object> properties = new Dictionary<string, object>
{
{ "testCaseTitle", x },
{ "automatedTestName", x },
{ "resultGroupType", "generic" },
{ "outcome", "Passed" }, // Start with a passed outcome initially
{ "state", "InProgress" },
{ "startedDate", startedDate.ToString(_dateFormatString) },
{ "automatedTestType", "UnitTest" },
{ "testCaseTitle", x },
{ "automatedTestName", x },
{ "resultGroupType", "generic" },
{ "outcome", "Passed" }, // Start with a passed outcome initially
{ "state", "InProgress" },
{ "startedDate", startedDate.ToString(_dateFormatString) },
{ "automatedTestType", "UnitTest" },
{ "automatedTestTypeId", "13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" } // This is used in the sample response and also appears in web searches
};
if (!string.IsNullOrEmpty(source))
Expand Down Expand Up @@ -145,18 +138,17 @@ public async Task<int[]> AddTestCases(int testRunId, string[] testCaseNames, Dat
}
}

public async Task MarkTestRunCompleted(int testRunId, DateTime startedDate, DateTime completedDate, CancellationToken cancellationToken)
public async Task MarkTestRunCompleted(int testRunId, bool aborted, DateTime completedDate, CancellationToken cancellationToken)
{
// Mark the overall test run as completed
string requestBody = $@"{{
""state"": ""Completed"",
""startedDate"": ""{startedDate.ToString(_dateFormatString)}"",
""state"": ""{(aborted ? "Aborted" : "Completed")}"",
""completedDate"": ""{completedDate.ToString(_dateFormatString)}""
}}";

await SendAsync(new HttpMethod("PATCH"), $"/{testRunId}", requestBody, cancellationToken).ConfigureAwait(false);
}

}

protected Dictionary<string, object> GetTestResultProperties(ITestResult testResult)
{
// https://docs.microsoft.com/en-us/rest/api/azure/devops/test/results/list?view=azure-devops-rest-6.0#testcaseresult
Expand Down Expand Up @@ -184,47 +176,48 @@ protected Dictionary<string, object> GetTestResultProperties(ITestResult testRes
}

Dictionary<string, object> properties = new Dictionary<string, object>
{
{
{ "outcome", testOutcome },
{ "computerName", testResult.ComputerName },
{ "runBy", new Dictionary<string, object> { { "displayName", BuildRequestedFor } } }
};

AddAdditionalTestResultProperties(testResult, properties);

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

string errorStackTrace = testResult.ErrorStackTrace;
if (!string.IsNullOrEmpty(errorStackTrace))
{
properties.Add("stackTrace", errorStackTrace);
}

string errorMessage = testResult.ErrorMessage;

if (!string.IsNullOrEmpty(errorMessage))
{
properties.Add("errorMessage", errorMessage);
}
}
else
{
// Handle output type skip, NotFound and None
if (testResult.Outcome == TestOutcome.Passed || testResult.Outcome == TestOutcome.Failed)
{
properties.Add("startedDate", testResult.StartTime.ToString(_dateFormatString));
properties.Add("completedDate", testResult.EndTime.ToString(_dateFormatString));

long duration = Convert.ToInt64(testResult.Duration.TotalMilliseconds);
properties.Add("durationInMs", duration.ToString(CultureInfo.InvariantCulture));

string errorStackTrace = testResult.ErrorStackTrace;
if (!string.IsNullOrEmpty(errorStackTrace))
{
properties.Add("stackTrace", errorStackTrace);
}

string errorMessage = testResult.ErrorMessage;

if (!string.IsNullOrEmpty(errorMessage))
{
properties.Add("errorMessage", errorMessage);
}
}
else
{
// Handle output type skip, NotFound and None
}

return properties;
}

internal abstract string GetTestCasesAsCompleted(IEnumerable<TestResultParent> testCases, DateTime completedDate);

internal abstract string GetTestResults(
Dictionary<string, TestResultParent> testCaseTestResults,
IEnumerable<IGrouping<string, ITestResult>> testResultsByParent,
DateTime completedDate);

DateTime completedDate);

internal virtual void AddAdditionalTestResultProperties(ITestResult testResult, Dictionary<string, object> properties)
{
}
Expand Down
13 changes: 0 additions & 13 deletions src/AzurePipelines.TestLogger/ApiClientV3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,6 @@ public ApiClientV3(string collectionUri, string teamProject, string apiVersionSt
{
}

internal override string GetTestCasesAsCompleted(IEnumerable<TestResultParent> testCases, DateTime completedDate)
{
// https://docs.microsoft.com/en-us/azure/devops/integrate/previous-apis/test/results?view=tfs-2015#add-test-results-to-a-test-run
return "[ " + string.Join(", ", testCases.Select(x =>
$@"{{
""TestResult"": {{ ""Id"": {x.Id} }},
""testCase"": {{ ""id"": {x.Id} }},
""state"": ""Completed"",
""startedDate"": ""{x.StartedDate.ToString(_dateFormatString)}"",
""completedDate"": ""{completedDate.ToString(_dateFormatString)}""
}}")) + " ]";
}

internal override string GetTestResults(
Dictionary<string, TestResultParent> testCaseTestResults,
IEnumerable<IGrouping<string, ITestResult>> testResultsByParent,
Expand Down
12 changes: 0 additions & 12 deletions src/AzurePipelines.TestLogger/ApiClientV5.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,6 @@ public ApiClientV5(string collectionUri, string teamProject, string apiVersionSt
{
}

internal override string GetTestCasesAsCompleted(IEnumerable<TestResultParent> testCases, DateTime completedDate)
{
// https://docs.microsoft.com/en-us/rest/api/azure/devops/test/results/add?view=azure-devops-rest-5.0
return "[ " + string.Join(", ", testCases.Select(x =>
$@"{{
""id"": {x.Id},
""state"": ""Completed"",
""startedDate"": ""{x.StartedDate.ToString(_dateFormatString)}"",
""completedDate"": ""{completedDate.ToString(_dateFormatString)}""
}}")) + " ]";
}

internal override string GetTestResults(
Dictionary<string, TestResultParent> testCaseTestResults,
IEnumerable<IGrouping<string, ITestResult>> testResultsByParent,
Expand Down
6 changes: 5 additions & 1 deletion src/AzurePipelines.TestLogger/AzurePipelinesTestLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@ public void Initialize(TestLoggerEvents events, Dictionary<string, string> param

// Register for the events
events.TestRunMessage += TestMessageHandler;

// when a single test has finished
events.TestResult += TestResultHandler;

// when the entire test run is finished
events.TestRunComplete += TestRunCompleteHandler;
}

Expand All @@ -137,6 +141,6 @@ private void TestResultHandler(object sender, TestResultEventArgs e) =>
_queue.Enqueue(new VstpTestResult(e.Result));

private void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e) =>
_queue.Flush(new VstpTestRunComplete(e.AttachmentSets));
_queue.Flush(new VstpTestRunComplete(e.IsAborted || e.IsCanceled, e.AttachmentSets));
}
}
6 changes: 2 additions & 4 deletions src/AzurePipelines.TestLogger/IApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,14 @@ internal interface IApiClient

IApiClient WithDefaultCredentials();

Task<string> MarkTestCasesCompleted(int testRunId, IEnumerable<TestResultParent> testCases, DateTime completedDate, CancellationToken cancellationToken);

Task<int> AddTestRun(TestRun testRun, CancellationToken cancellationToken);

Task UpdateTestResults(int testRunId, Dictionary<string, TestResultParent> parents, IEnumerable<IGrouping<string, ITestResult>> testResultsByParent, CancellationToken cancellationToken);

Task UpdateTestResults(int runId, VstpTestRunComplete testRunComplete, CancellationToken cancellationToken);
Task UpdateTestResults(int testRunId, VstpTestRunComplete testRunComplete, CancellationToken cancellationToken);

Task<int[]> AddTestCases(int testRunId, string[] testCaseNames, DateTime startedDate, string source, CancellationToken cancellationToken);

Task MarkTestRunCompleted(int testRunId, DateTime startedDate, DateTime completedDate, CancellationToken cancellationToken);
Task MarkTestRunCompleted(int testRunId, bool aborted, DateTime completedDate, CancellationToken cancellationToken);
}
}
2 changes: 2 additions & 0 deletions src/AzurePipelines.TestLogger/ITestResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ internal interface ITestResult
string FullyQualifiedName { get; }
string DisplayName { get; }
TestOutcome Outcome { get; }
DateTimeOffset StartTime { get; }
DateTimeOffset EndTime { get; }
TimeSpan Duration { get; }
string ErrorStackTrace { get; }
string ErrorMessage { get; }
Expand Down
Loading

0 comments on commit b6e77d4

Please sign in to comment.