Skip to content

Commit

Permalink
Fixed test run hang if an error occurs when trying to send test resul…
Browse files Browse the repository at this point in the history
…ts to the server

Pretty print requests and responses for easier troubleshooting
Added integration tests
disable warning NETSDK1138: The target framework 'netcoreapp2.1' is out of support and will not receive security updates in the future.
  • Loading branch information
icnocop committed Oct 16, 2023
1 parent b6e77d4 commit ae1935e
Show file tree
Hide file tree
Showing 19 changed files with 600 additions and 94 deletions.
30 changes: 13 additions & 17 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ jobs:
echo SEM_VERSION=${{ env.SEM_VERSION }}
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4.1.0

- name: Setup .NET Core 2.1
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 2.1

Expand All @@ -63,23 +63,19 @@ jobs:
copy ".\src\AzurePipelines.TestLogger\bin\${{ matrix.Configuration }}\netstandard1.5\*.dll" .\src\AzurePipelines.TestLogger\contentFiles\any\any
- name: Pack
run: dotnet pack .\src\AzurePipelines.TestLogger\AzurePipelines.TestLogger.csproj --configuration ${{ matrix.Configuration }} -p:NuspecProperties="Version=${{ env.SEM_VERSION }}" --no-restore --no-build --output:.\build -p:NuspecFile=AzurePipelines.TestLogger.nuspec
run: dotnet pack .\src\AzurePipelines.TestLogger\AzurePipelines.TestLogger.csproj --configuration ${{ matrix.Configuration }} -p:NuspecProperties="Version=${{ env.SEM_VERSION }}" --no-restore --no-build --output:.\build -p:NuspecFile=..\AzurePipelines.TestLogger\AzurePipelines.TestLogger.nuspec

- name: Test
run: dotnet test --no-build --no-restore --verbosity normal --configuration ${{ matrix.Configuration }}

- name: Create zip
run: |
mkdir AzurePipelines.TestLogger.${{ env.SEM_VERSION }}
copy ".\src\AzurePipelines.TestLogger\bin\${{ matrix.Configuration }}\netstandard1.5\*.*" .\AzurePipelines.TestLogger.${{ env.SEM_VERSION }}
copy "~\.nuget\packages\semver\2.0.6\lib\netstandard1.1\*.*" .\AzurePipelines.TestLogger.${{ env.SEM_VERSION }}
copy ".\LICENSE" .\AzurePipelines.TestLogger.${{ env.SEM_VERSION }}
copy ".\README.md" .\AzurePipelines.TestLogger.${{ env.SEM_VERSION }}
copy ".\ReleaseNotes.md" .\AzurePipelines.TestLogger.${{ env.SEM_VERSION }}
tar -cf AzurePipelines.TestLogger.${{ env.SEM_VERSION }}.zip .\AzurePipelines.TestLogger.${{ env.SEM_VERSION }}
- name: Create Zip
uses: vimtor/[email protected]
with:
files: LICENSE README.md ReleaseNotes.md .\src\AzurePipelines.TestLogger\bin\${{ matrix.Configuration }}\netstandard1.5
dest: AzurePipelines.TestLogger.${{ env.SEM_VERSION }}.zip

- name: Upload Build Artifact
uses: actions/upload-artifact@v2.3.1
uses: actions/upload-artifact@v3.1.3
with:
name: AzurePipelines.TestLogger ${{ env.SEM_VERSION }} ${{ matrix.Configuration }}
path: AzurePipelines.TestLogger.${{ env.SEM_VERSION }}.zip
Expand All @@ -95,15 +91,15 @@ jobs:
fail_on_unmatched_files: true
files: |
AzurePipelines.TestLogger.${{ env.SEM_VERSION }}.zip
.\src\AzurePipelines.TestLogger\build\AzurePipelines.TestLogger.${{ env.SEM_VERSION }}.nupkg
.\build\AzurePipelines.TestLogger.${{ env.SEM_VERSION }}.nupkg
- name: Upload NuGet Package
uses: actions/upload-artifact@v2.3.1
uses: actions/upload-artifact@v3.1.3
with:
name: AzurePipelines.TestLogger ${{ env.SEM_VERSION }} ${{ matrix.Configuration }}.nupkg
path: .\src\AzurePipelines.TestLogger\build\AzurePipelines.TestLogger.${{ env.SEM_VERSION }}.nupkg
path: .\build\AzurePipelines.TestLogger.${{ env.SEM_VERSION }}.nupkg
if-no-files-found: error

- name: Publish NuGet Package
if: ${{ matrix.Configuration == 'Release' && github.event_name != 'pull_request' }}
run: dotnet nuget push .\src\AzurePipelines.TestLogger\build\AzurePipelines.TestLogger.${{ env.SEM_VERSION }}.nupkg --api-key ${{ secrets.NUGET_KEY }} --source https://api.nuget.org/v3/index.json
run: dotnet nuget push .\build\AzurePipelines.TestLogger.${{ env.SEM_VERSION }}.nupkg --api-key ${{ secrets.NUGET_KEY }} --source https://api.nuget.org/v3/index.json
19 changes: 17 additions & 2 deletions AzurePipelines.TestLogger.sln
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31112.23
# Visual Studio Version 17
VisualStudioVersion = 17.7.34031.279
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C95ECC05-F3E8-49F4-B7C5-A29CD7EACFC1}"
EndProject
Expand All @@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{B8315F74-E
stylecop.json = stylecop.json
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleUnitTestProject", "tests\SampleUnitTestProject\SampleUnitTestProject.csproj", "{AFB35FB3-F22D-436A-84F7-A5DEFD879D3D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -60,13 +62,26 @@ Global
{8C42EBD4-FF36-44B6-A70E-7D83CB0626F8}.Release|x64.Build.0 = Release|Any CPU
{8C42EBD4-FF36-44B6-A70E-7D83CB0626F8}.Release|x86.ActiveCfg = Release|Any CPU
{8C42EBD4-FF36-44B6-A70E-7D83CB0626F8}.Release|x86.Build.0 = Release|Any CPU
{AFB35FB3-F22D-436A-84F7-A5DEFD879D3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AFB35FB3-F22D-436A-84F7-A5DEFD879D3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AFB35FB3-F22D-436A-84F7-A5DEFD879D3D}.Debug|x64.ActiveCfg = Debug|Any CPU
{AFB35FB3-F22D-436A-84F7-A5DEFD879D3D}.Debug|x64.Build.0 = Debug|Any CPU
{AFB35FB3-F22D-436A-84F7-A5DEFD879D3D}.Debug|x86.ActiveCfg = Debug|Any CPU
{AFB35FB3-F22D-436A-84F7-A5DEFD879D3D}.Debug|x86.Build.0 = Debug|Any CPU
{AFB35FB3-F22D-436A-84F7-A5DEFD879D3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AFB35FB3-F22D-436A-84F7-A5DEFD879D3D}.Release|Any CPU.Build.0 = Release|Any CPU
{AFB35FB3-F22D-436A-84F7-A5DEFD879D3D}.Release|x64.ActiveCfg = Release|Any CPU
{AFB35FB3-F22D-436A-84F7-A5DEFD879D3D}.Release|x64.Build.0 = Release|Any CPU
{AFB35FB3-F22D-436A-84F7-A5DEFD879D3D}.Release|x86.ActiveCfg = Release|Any CPU
{AFB35FB3-F22D-436A-84F7-A5DEFD879D3D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{77CA5040-B4A0-4D0B-ADDD-09853A385007} = {C95ECC05-F3E8-49F4-B7C5-A29CD7EACFC1}
{8C42EBD4-FF36-44B6-A70E-7D83CB0626F8} = {FA92AD98-1291-4A90-A3AA-ED81A9BBC86E}
{AFB35FB3-F22D-436A-84F7-A5DEFD879D3D} = {FA92AD98-1291-4A90-A3AA-ED81A9BBC86E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A7517899-6171-4E6B-BDD7-DBE01B34E83A}
Expand Down
Empty file added root
Empty file.
2 changes: 1 addition & 1 deletion src/AzurePipelines.TestLogger/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ internal virtual async Task<string> SendAsync(HttpMethod method, string endpoint

if (Verbose)
{
Console.WriteLine($"Request:\n{method} {requestUri}\n{body}\n\nResponse:\n{response.StatusCode}\n{responseBody}");
Console.WriteLine($"Request:\n{method} {requestUri}\n{body.Indented()}\n\nResponse:\n{response.StatusCode}\n{responseBody.Indented()}\n");
}

try
Expand Down
1 change: 0 additions & 1 deletion src/AzurePipelines.TestLogger/ApiClientV5.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using AzurePipelines.TestLogger.Json;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
Expand Down
52 changes: 26 additions & 26 deletions src/AzurePipelines.TestLogger/AzurePipelines.TestLogger.csproj
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.5</TargetFramework>
<Version>1.0.0</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
</PropertyGroup>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.5</TargetFramework>
<Version>1.0.0</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<NoPackageAnalysis>true</NoPackageAnalysis>
</PropertyGroup>

<ItemGroup>
<Compile Remove="contentFiles\**" />
<EmbeddedResource Remove="contentFiles\**" />
<None Remove="contentFiles\**" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" Version="15.0.0" />
<PackageReference Include="Semver" Version="2.0.6" />
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>AzurePipelines.TestLogger.Tests</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>DynamicProxyGenAssembly2</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" Version="15.0.0" />
<PackageReference Include="Semver" Version="2.0.6" GeneratePathProperty="true" />
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>AzurePipelines.TestLogger.Tests</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>DynamicProxyGenAssembly2</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<Target Name="CopyFileFromNuGetPackage" AfterTargets="Build">
<Copy SourceFiles="$(PkgSemver)\lib\netstandard1.1\Semver.dll" DestinationFolder="$(OutDir)" />
</Target>

</Project>
28 changes: 24 additions & 4 deletions src/AzurePipelines.TestLogger/AzurePipelinesTestLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public class AzurePipelinesTestLogger : ITestLoggerWithParameters

public AzurePipelinesTestLogger()
{
// For debugging purposes
// System.Diagnostics.Debugger.Launch();
_environmentVariableProvider = new EnvironmentVariableProvider();
_apiClientFactory = new ApiClientFactory();
}
Expand Down Expand Up @@ -137,10 +139,28 @@ private void TestMessageHandler(object sender, TestRunMessageEventArgs e)
// Add code to handle message
}

private void TestResultHandler(object sender, TestResultEventArgs e) =>
_queue.Enqueue(new VstpTestResult(e.Result));
private void TestResultHandler(object sender, TestResultEventArgs e)
{
try
{
_queue.Enqueue(new VstpTestResult(e.Result));
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}

private void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e) =>
_queue.Flush(new VstpTestRunComplete(e.IsAborted || e.IsCanceled, e.AttachmentSets));
private void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e)
{
try
{
_queue.Flush(new VstpTestRunComplete(e.IsAborted || e.IsCanceled, e.AttachmentSets));
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
}
41 changes: 41 additions & 0 deletions src/AzurePipelines.TestLogger/Json/JsonExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,46 @@ private static string JsonEscape(string value)

return sb.ToString();
}

/// <summary>
/// Indent JSON string.
/// </summary>
/// <param name="input">The JSON string.</param>
/// <returns>The indented JSON string.</returns>
public static string Indented(this string input)
{
int level = 0;
StringBuilder result = new StringBuilder();

for (int i = 0; i < input.Length; i++)
{
char c = input[i];

if (c == '{' || c == '[')
{
result.Append(c);
result.AppendLine();
result.Append(new string(' ', ++level * 2));
}
else if (c == '}' || c == ']')
{
result.AppendLine();
result.Append(new string(' ', --level * 2));
result.Append(c);
}
else if (c == ',')
{
result.Append(c);
result.AppendLine();
result.Append(new string(' ', level * 2));
}
else
{
result.Append(c);
}
}

return result.ToString();
}
}
}
77 changes: 35 additions & 42 deletions src/AzurePipelines.TestLogger/LoggerQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,72 +43,65 @@ public void Flush(VstpTestRunComplete testRunComplete)
// Cancel any idle consumers and let them return
_queue.Cancel();

try
{
// Any active consumer will circle back around and batch post the remaining queue
_consumeTask.Wait(TimeSpan.FromSeconds(60));
// 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(testRunComplete, _consumeTaskCancellationSource.Token).Wait(TimeSpan.FromSeconds(60));
// Update the run and parents to a completed state
SendTestsCompleted(testRunComplete, _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");
}
}
catch (Exception ex)
// Cancel any active HTTP requests if still hasn't finished flushing
_consumeTaskCancellationSource.Cancel();
if (!_consumeTask.Wait(TimeSpan.FromSeconds(10)))
{
Console.WriteLine(ex);
throw new TimeoutException("Cancellation didn't happen quickly");
}
}

private async Task ConsumeItemsAsync(CancellationToken cancellationToken)
{
while (true)
{
ITestResult[] nextItems = await _queue.TakeAsync().ConfigureAwait(false);

if (nextItems == null || nextItems.Length == 0)
try
{
// Queue is canceling and is empty
return;
}
ITestResult[] nextItems = await _queue.TakeAsync().ConfigureAwait(false);

if (nextItems == null || nextItems.Length == 0)
{
// Queue is canceling and is empty
return;
}

await SendResultsAsync(nextItems, cancellationToken).ConfigureAwait(false);
await SendResultsAsync(nextItems, cancellationToken).ConfigureAwait(false);

if (cancellationToken.IsCancellationRequested)
if (cancellationToken.IsCancellationRequested)
{
return;
}
}
catch (Exception ex)
{
return;
Console.WriteLine(ex);
}
}
}

private async Task SendResultsAsync(ITestResult[] testResults, CancellationToken cancellationToken)
{
try
// Create a test run if we need it
if (RunId == 0)
{
// Create a test run if we need it
if (RunId == 0)
{
Source = GetSource(testResults);
RunId = await CreateTestRun(cancellationToken).ConfigureAwait(false);
}
Source = GetSource(testResults);
RunId = await CreateTestRun(cancellationToken).ConfigureAwait(false);
}

// Group results by their parent
IEnumerable<IGrouping<string, ITestResult>> testResultsByParent = GroupTestResultsByParent(testResults);
// Group results by their parent
IEnumerable<IGrouping<string, ITestResult>> testResultsByParent = GroupTestResultsByParent(testResults);

// Create any required parent nodes
await CreateParents(testResultsByParent, cancellationToken).ConfigureAwait(false);
// Create any required parent nodes
await CreateParents(testResultsByParent, cancellationToken).ConfigureAwait(false);

// Update parents with the test results
await SendTestResults(testResultsByParent, cancellationToken).ConfigureAwait(false);
}
catch (Exception)
{
// Eat any communications exceptions
}
// Update parents with the test results
await SendTestResults(testResultsByParent, cancellationToken).ConfigureAwait(false);
}

// Internal for testing
Expand Down
Loading

0 comments on commit ae1935e

Please sign in to comment.