Separate ASP.NET Core tests into a distinct project and make the main test project netfx compatible. (#254)

* Separate ASP.NET Core tests into a distinct project and make the main test project netfx compatible.

* Fix merge conflicts.

* Update tests/ModelContextProtocol.Tests/ModelContextProtocol.Tests.csproj

Co-authored-by: Stephen Halter <halter73@gmail.com>

* Revert "Fix merge conflicts."

This reverts commit 852169b96786bad2144795540fa164e20b4b197e.

* Fix merge conflicts

---------

Co-authored-by: Stephen Halter <halter73@gmail.com>
This commit is contained in:
Eirik Tsarpalis 2025-04-09 19:16:53 +01:00 • committed by GitHub
commit 39ab6cde0d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 324 additions and 63 deletions

View file

@ -10,4 +10,28 @@ internal static class ProcessExtensions
_ = entireProcessTree;
process.Kill();
}
public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default)
{
if (process.HasExited)
{
return;
}
var tcs = new TaskCompletionSource<bool>();
void ProcessExitedHandler(object? sender, EventArgs e) => tcs.TrySetResult(true);
try
{
process.EnableRaisingEvents = true;
process.Exited += ProcessExitedHandler;
using var _ = cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken));
await tcs.Task.ConfigureAwait(false);
}
finally
{
process.Exited -= ProcessExitedHandler;
}
}
}

View file

@ -4,21 +4,7 @@ internal static class TextReaderExtensions
{
public static Task<string> ReadLineAsync(this TextReader reader, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<string>(cancellationToken);
}
cancellationToken.ThrowIfCancellationRequested();
return reader.ReadLineAsync();
}
public static Task<string> ReadToEndAsync(this TextReader reader, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<string>(cancellationToken);
}
return reader.ReadToEndAsync();
}
}

View file

@ -5,33 +5,6 @@ namespace System.IO;
internal static class TextWriterExtensions
{
public static async Task WriteLineAsync(this TextWriter writer, ReadOnlyMemory<char> value, CancellationToken cancellationToken)
{
Throw.IfNull(writer);
if (value.IsEmpty)
{
return;
}
cancellationToken.ThrowIfCancellationRequested();
if (MemoryMarshal.TryGetString(value, out string str, out int start, out int length) &&
start == 0 && length == str.Length)
{
await writer.WriteLineAsync(str).ConfigureAwait(false);
}
else if (MemoryMarshal.TryGetArray(value, out ArraySegment<char> array) &&
array.Array is not null && array.Offset == 0 && array.Count == array.Array.Length)
{
await writer.WriteLineAsync(array.Array).ConfigureAwait(false);
}
else
{
await writer.WriteLineAsync(value.ToArray()).ConfigureAwait(false);
}
}
public static async Task FlushAsync(this TextWriter writer, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

View file

@ -9,13 +9,18 @@ internal static class TaskExtensions
return WaitAsync(task, Timeout.InfiniteTimeSpan, cancellationToken);
}
public static async Task<T> WaitAsync<T>(this Task<T> task, CancellationToken cancellationToken)
public static Task<T> WaitAsync<T>(this Task<T> task, CancellationToken cancellationToken)
{
await WaitAsync(task, Timeout.InfiniteTimeSpan, cancellationToken);
return WaitAsync(task, Timeout.InfiniteTimeSpan, cancellationToken);
}
public static async Task<T> WaitAsync<T>(this Task<T> task, TimeSpan timeout, CancellationToken cancellationToken = default)
{
await WaitAsync((Task)task, timeout, cancellationToken).ConfigureAwait(false);
return task.Result;
}
public static async Task WaitAsync(this Task task, TimeSpan timeout, CancellationToken cancellationToken)
public static async Task WaitAsync(this Task task, TimeSpan timeout, CancellationToken cancellationToken = default)
{
Throw.IfNull(task);

41
src/Common/Throw.cs Normal file
View file

@ -0,0 +1,41 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace ModelContextProtocol.Utils;
/// <summary>Provides helper methods for throwing exceptions.</summary>
internal static class Throw
{
// NOTE: Most of these should be replaced with extension statics for the relevant extension
// type as downlevel polyfills once the C# 14 extension everything feature is available.
public static void IfNull([NotNull] object? arg, [CallerArgumentExpression(nameof(arg))] string? parameterName = null)
{
if (arg is null)
{
ThrowArgumentNullException(parameterName);
}
}
public static void IfNullOrWhiteSpace([NotNull] string? arg, [CallerArgumentExpression(nameof(arg))] string? parameterName = null)
{
if (arg is null || arg.AsSpan().IsWhiteSpace())
{
ThrowArgumentNullOrWhiteSpaceException(parameterName);
}
}
[DoesNotReturn]
private static void ThrowArgumentNullOrWhiteSpaceException(string? parameterName)
{
if (parameterName is null)
{
ThrowArgumentNullException(parameterName);
}
throw new ArgumentException("Value cannot be empty or composed entirely of whitespace.", parameterName);
}
[DoesNotReturn]
private static void ThrowArgumentNullException(string? parameterName) => throw new ArgumentNullException(parameterName);
}

View file

@ -14,6 +14,10 @@
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Common\Throw.cs" Link="Utils\Throw.cs" />
</ItemGroup>
<!-- Dependencies only needed by netstandard2.0 -->
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<Compile Include="..\Common\Polyfills\**\*.cs" />