Skip to content

[Helix] Try out the new dotnet test blame functionality #23862

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 32 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ede369a
Create HangingTest.cs
HaoK Jul 10, 2020
9f6e089
Update HangingTest.cs
HaoK Jul 10, 2020
20ef2e2
Update HangingTest.cs
HaoK Jul 10, 2020
d3f7077
Switch to test instead of vstest to prepare for new blame flag
HaoK Jul 27, 2020
4065596
Try new test timeout flag for --blame
HaoK Jul 28, 2020
771f946
Update TestRunner.cs
HaoK Jul 28, 2020
31755f9
Upload all dmp files under TestResults
HaoK Jul 29, 2020
3c7c75e
Add rest of hang tests from helix-prototype
HaoK Jul 29, 2020
bdb0e9f
Remove collector
HaoK Jul 29, 2020
b6f09cb
Try renaming projects
HaoK Jul 29, 2020
f4f5f28
Rename tests for clarity
HaoK Jul 29, 2020
775ffb2
Fix dump uploads to only use file name
HaoK Jul 29, 2020
d7a3ecf
Use ordinal comparison when detecting event handler attributes. Fixes…
SteveSandersonMS Jul 31, 2020
3f6af28
Pass serialization exceptions to Hub disconnect (#24408)
BrennanConroy Jul 31, 2020
ec22d3e
Update dependencies from https://github.com/dotnet/roslyn build 20200…
dotnet-maestro[bot] Jul 31, 2020
60c94a4
Wait for component to mount before checking for output (#24451)
captainsafia Jul 31, 2020
0f37658
Remove debian 8 from helix runs (#24454)
HaoK Jul 31, 2020
02ba8fd
Avoid doing unncecessary work when generating component declaration f…
pranavkm Jul 31, 2020
ad0d7d2
Update dependencies from https://github.com/dotnet/arcade build 20200…
dotnet-maestro[bot] Jul 31, 2020
870f27c
Remove Blazor internal profiling infrastructure (#24468)
SteveSandersonMS Jul 31, 2020
5832d66
Update documentation on darc (#24487)
captainsafia Jul 31, 2020
71290fb
Update Blazor WebAssembly in-product survey link for .NET 5. (#24508)
danroth27 Aug 2, 2020
06075c4
Remove unused HTML files (#24488)
dougbu Aug 2, 2020
ab2565f
Increase main Helix job timeout (#24493)
dougbu Aug 3, 2020
2a7702b
Dipping toes into linker friendliness (#24458)
davidfowl Aug 3, 2020
0ef7591
[HTTPS] Fix the CertificateManagerEventSource event ids (#24519)
0xced Aug 3, 2020
9b0afda
Run full helix matrix for crash dumps
HaoK Aug 3, 2020
d9e0cf4
Try only uploading to root
HaoK Aug 3, 2020
3d85085
Add trx logger and upload trx files
HaoK Aug 7, 2020
e0c1593
Merge branch 'master' into helix/dmp
HaoK Aug 7, 2020
e2939c7
Remove collector for now
HaoK Aug 7, 2020
b9bf023
Update TestRunner.cs
HaoK Aug 17, 2020
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
6 changes: 5 additions & 1 deletion .azure/pipelines/helix-matrix.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# We only want to run full helix matrix on master
pr: none
pr:
autoCancel: true
branches:
include:
- '*'
trigger: none
schedules:
- cron: "0 */12 * * *"
Expand Down
22 changes: 16 additions & 6 deletions eng/helix/content/RunTests/TestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public async Task<int> RunTestsAsync()
{
// Timeout test run 5 minutes before the Helix job would timeout
var cts = new CancellationTokenSource(Options.Timeout.Subtract(TimeSpan.FromMinutes(5)));
var commonTestArgs = $"test {Options.Target} --logger:xunit --logger:\"console;verbosity=normal\" --blame \"CollectHangDump;TestTimeout=5m\"";
var commonTestArgs = $"test {Options.Target} --logger:xunit --logger:\"console;verbosity=normal\" --blame \"CollectHangDump;TestTimeout=5m\"";
if (Options.Quarantined)
{
Console.WriteLine("Running quarantined tests.");
Expand Down Expand Up @@ -322,9 +322,7 @@ public void UploadResults()
// Combine the directory name + log name for the copied log file name to avoid overwriting duplicate test names in different test projects
var logName = $"{Path.GetFileName(Path.GetDirectoryName(file))}_{Path.GetFileName(file)}";
Console.WriteLine($"Copying: {file} to {Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, logName)}");
// Need to copy to HELIX_WORKITEM_UPLOAD_ROOT and HELIX_WORKITEM_UPLOAD_ROOT/../ in order for Azure Devops attachments to link properly and for Helix to store the logs
File.Copy(file, Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, logName));
File.Copy(file, Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, "..", logName));
}
}
else
Expand All @@ -338,15 +336,27 @@ public void UploadResults()
{
var fileName = Path.GetFileName(file);
Console.WriteLine($"Copying: {file} to {Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, fileName)}");
// Need to copy to HELIX_WORKITEM_UPLOAD_ROOT and HELIX_WORKITEM_UPLOAD_ROOT/../ in order for Azure Devops attachments to link properly and for Helix to store the logs
File.Copy(file, Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, fileName));
File.Copy(file, Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, "..", fileName));
}
}
else
{
Console.WriteLine("No dmps found in TestResults");
}
}
Console.WriteLine($"Copying TestResults/**/*.trx to {HELIX_WORKITEM_UPLOAD_ROOT}/");
if (Directory.Exists("TestResults"))
{
foreach (var file in Directory.EnumerateFiles("TestResults", "*.trx", SearchOption.AllDirectories))
{
var fileName = Path.GetFileName(file);
Console.WriteLine($"Copying: {file} to {Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, fileName)}");
File.Copy(file, Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, fileName));
}
}
else
{
Console.WriteLine("No trx found in TestResults");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ public void AddAttribute(int sequence, string name)
throw new InvalidOperationException($"Valueless attributes may only be added immediately after frames of type {RenderTreeFrameType.Element}");
}


_entries.AppendAttribute(sequence, name, BoxedTrue);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Components/Web.JS/dist/Release/blazor.server.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Components/Web.JS/dist/Release/blazor.webassembly.js

Large diffs are not rendered by default.

158 changes: 158 additions & 0 deletions src/Framework/test/HangingTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace XUnitTestProject3
{
public class UnitTest1
{
static UnitTest1()
{
AppDomain.CurrentDomain.FirstChanceException += (sender, e) =>
{
if (e.Exception is TestTimeoutException)
{
// Block forever, this allows the test host to collect a dump with the test still on the stack
new ManualResetEventSlim().Wait();
}
};
}

[Fact]
public async Task AsyncTimeoutTest()
{
var tcs = new TaskCompletionSource<object>();

await tcs.Task.OrTimeout();
}
}


static class TaskExtensions
{
private const int DefaultTimeout = 5 * 1000;

public static Task OrTimeout(this ValueTask task, int milliseconds = DefaultTimeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null)
{
return OrTimeout(task, new TimeSpan(0, 0, 0, 0, milliseconds), memberName, filePath, lineNumber);
}

public static Task OrTimeout(this ValueTask task, TimeSpan timeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null)
{
return task.AsTask().TimeoutAfter(timeout, filePath, lineNumber ?? 0);
}

public static Task OrTimeout(this Task task, int milliseconds = DefaultTimeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null)
{
return OrTimeout(task, new TimeSpan(0, 0, 0, 0, milliseconds), memberName, filePath, lineNumber);
}

public static Task OrTimeout(this Task task, TimeSpan timeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null)
{
return task.TimeoutAfter(timeout, filePath, lineNumber ?? 0);
}

public static Task<T> OrTimeout<T>(this ValueTask<T> task, int milliseconds = DefaultTimeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null) =>
OrTimeout(task, new TimeSpan(0, 0, 0, 0, milliseconds), memberName, filePath, lineNumber);

public static Task<T> OrTimeout<T>(this ValueTask<T> task, TimeSpan timeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null) =>
task.AsTask().OrTimeout(timeout, memberName, filePath, lineNumber);

public static Task<T> OrTimeout<T>(this Task<T> task, int milliseconds = DefaultTimeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null)
{
return OrTimeout(task, new TimeSpan(0, 0, 0, 0, milliseconds), memberName, filePath, lineNumber);
}

public static Task<T> OrTimeout<T>(this Task<T> task, TimeSpan timeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null)
{
return task.TimeoutAfter(timeout, filePath, lineNumber ?? 0);
}

public static async Task OrThrowIfOtherFails(this Task task, Task otherTask)
{
var completed = await Task.WhenAny(task, otherTask);
if (completed == otherTask && otherTask.IsFaulted)
{
// Manifest the exception
otherTask.GetAwaiter().GetResult();
throw new Exception("Unreachable code");
}
else
{
// Await the task we were asked to await. Either it's finished, or the otherTask finished successfully, and it's not our job to check that
await task;
}
}

public static async Task<T> OrThrowIfOtherFails<T>(this Task<T> task, Task otherTask)
{
await OrThrowIfOtherFails((Task)task, otherTask);

// If we get here, 'task' is finished and succeeded.
return task.GetAwaiter().GetResult();
}

public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout,
[CallerFilePath] string filePath = null,
[CallerLineNumber] int lineNumber = default)
{
// Don't create a timer if the task is already completed
// or the debugger is attached
if (task.IsCompleted || Debugger.IsAttached)
{
return await task;
}

var cts = new CancellationTokenSource();
if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token)))
{
cts.Cancel();
return await task;
}
else
{
throw new TestTimeoutException(CreateMessage(timeout, filePath, lineNumber));
}
}

public static async Task TimeoutAfter(this Task task, TimeSpan timeout,
[CallerFilePath] string filePath = null,
[CallerLineNumber] int lineNumber = default)
{
// Don't create a timer if the task is already completed
// or the debugger is attached
if (task.IsCompleted || Debugger.IsAttached)
{
await task;
return;
}

var cts = new CancellationTokenSource();
if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token)))
{
cts.Cancel();
await task;
}
else
{
throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber));
}
}

private static string CreateMessage(TimeSpan timeout, string filePath, int lineNumber)
=> string.IsNullOrEmpty(filePath)
? $"The operation timed out after reaching the limit of {timeout.TotalMilliseconds}ms."
: $"The operation at {filePath}:{lineNumber} timed out after reaching the limit of {timeout.TotalMilliseconds}ms.";
}

public class TestTimeoutException : TimeoutException
{
public TestTimeoutException(string message) : base(message)
{

}
}
}
58 changes: 58 additions & 0 deletions src/Identity/src/Common/AssemblyResolution.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#if NET472

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Microsoft.DotNet
{
internal static class AssemblyResolution
{
internal static TaskLoggingHelper Log;

public static void Initialize()
{
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
}

private static Assembly AssemblyResolve(object sender, ResolveEventArgs args)
{
var name = new AssemblyName(args.Name);

if (!name.Name.Equals("System.Collections.Immutable", StringComparison.OrdinalIgnoreCase))
{
return null;
}

var fullPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "System.Collections.Immutable.dll");

Assembly sci;
try
{
sci = Assembly.LoadFile(fullPath);
}
catch (Exception e)
{
Log?.LogWarning($"AssemblyResolve: exception while loading '{fullPath}': {e.Message}");
return null;
}

if (name.Version <= sci.GetName().Version)
{
Log?.LogMessage(MessageImportance.Low, $"AssemblyResolve: loaded '{fullPath}' to {AppDomain.CurrentDomain.FriendlyName}");
return sci;
}

return null;
}
}
}

#endif
109 changes: 109 additions & 0 deletions src/Identity/src/Common/AssemblyResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;

namespace Microsoft.DotNet.Build.Common.Desktop
{
/// <summary>
/// Used to enable app-local assembly unification.
/// </summary>
internal static class AssemblyResolver
{
static AssemblyResolver()
{
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}

/// <summary>
/// Call to enable the assembly resolver for the current AppDomain.
/// </summary>
public static void Enable()
{
// intentionally empty. This is just meant to ensure the static constructor
// has run.
}

private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
// apply any existing policy
AssemblyName referenceName = new AssemblyName(AppDomain.CurrentDomain.ApplyPolicy(args.Name));

string fileName = referenceName.Name + ".dll";
string assemblyPath = null;
string probingPath = null;
Assembly assm = null;

// look next to requesting assembly
assemblyPath = args.RequestingAssembly?.Location;
if (!String.IsNullOrEmpty(assemblyPath))
{
probingPath = Path.Combine(Path.GetDirectoryName(assemblyPath), fileName);
Debug.WriteLine($"Considering {probingPath} based on RequestingAssembly");
if (Probe(probingPath, referenceName.Version, out assm))
{
return assm;
}
}

// look next to the executing assembly
assemblyPath = Assembly.GetExecutingAssembly().Location;
if (!String.IsNullOrEmpty(assemblyPath))
{
probingPath = Path.Combine(Path.GetDirectoryName(assemblyPath), fileName);

Debug.WriteLine($"Considering {probingPath} based on ExecutingAssembly");
if (Probe(probingPath, referenceName.Version, out assm))
{
return assm;
}
}

// look in AppDomain base directory
probingPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
Debug.WriteLine($"Considering {probingPath} based on BaseDirectory");
if (Probe(probingPath, referenceName.Version, out assm))
{
return assm;
}

// look in current directory
Debug.WriteLine($"Considering {fileName}");
if (Probe(fileName, referenceName.Version, out assm))
{
return assm;
}

return null;
}

/// <summary>
/// Considers a path to load for satisfying an assembly ref and loads it
/// if the file exists and version is sufficient.
/// </summary>
/// <param name="filePath">Path to consider for load</param>
/// <param name="minimumVersion">Minimum version to consider</param>
/// <param name="assembly">loaded assembly</param>
/// <returns>true if assembly was loaded</returns>
private static bool Probe(string filePath, Version minimumVersion, out Assembly assembly)
{
if (File.Exists(filePath))
{
AssemblyName name = AssemblyName.GetAssemblyName(filePath);

if (name.Version >= minimumVersion)
{
assembly = Assembly.Load(name);
return true;
}
}

assembly = null;
return false;
}
}
}
Loading