Enable netfx testing. (#588)
* Enable netfx testing. * Skip impacted tests and re-enable netfx testing. * Remove redundant comment. * Remove unnecessary project configuration. * Simplify test server copying.
This commit is contained in:
parent
c0440760ac
commit
aa75f6f37e
32 changed files with 1940 additions and 53 deletions
|
|
@ -60,7 +60,7 @@
|
|||
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="$(System9Version)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="$(System9Version)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="9.5.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageVersion Include="Moq" Version="4.20.72" />
|
||||
<PackageVersion Include="OpenTelemetry" Version="1.12.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Exporter.InMemory" Version="1.12.0" />
|
||||
|
|
@ -75,8 +75,8 @@
|
|||
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageVersion Include="Serilog" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Linq.AsyncEnumerable" Version="$(System10Version)" />
|
||||
<PackageVersion Include="xunit.v3" Version="2.0.2" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.0" />
|
||||
<PackageVersion Include="xunit.v3" Version="2.0.3" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.1" />
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageVersion Include="JsonSchema.Net" Version="7.3.4" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
1395
src/Common/CancellableStreamReader/CancellableStreamReader.cs
Normal file
1395
src/Common/CancellableStreamReader/CancellableStreamReader.cs
Normal file
File diff suppressed because it is too large
Load diff
15
src/Common/CancellableStreamReader/TextReaderExtensions.cs
Normal file
15
src/Common/CancellableStreamReader/TextReaderExtensions.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
namespace System.IO;
|
||||
|
||||
internal static class TextReaderExtensions
|
||||
{
|
||||
public static ValueTask<string> ReadLineAsync(this TextReader reader, CancellationToken cancellationToken)
|
||||
{
|
||||
if (reader is CancellableStreamReader cancellableReader)
|
||||
{
|
||||
return cancellableReader.ReadLineAsync(cancellationToken)!;
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new ValueTask<string>(reader.ReadLineAsync());
|
||||
}
|
||||
}
|
||||
317
src/Common/CancellableStreamReader/ValueStringBuilder.cs
Normal file
317
src/Common/CancellableStreamReader/ValueStringBuilder.cs
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace System.Text
|
||||
{
|
||||
internal ref partial struct ValueStringBuilder
|
||||
{
|
||||
private char[]? _arrayToReturnToPool;
|
||||
private Span<char> _chars;
|
||||
private int _pos;
|
||||
|
||||
public ValueStringBuilder(Span<char> initialBuffer)
|
||||
{
|
||||
_arrayToReturnToPool = null;
|
||||
_chars = initialBuffer;
|
||||
_pos = 0;
|
||||
}
|
||||
|
||||
public ValueStringBuilder(int initialCapacity)
|
||||
{
|
||||
_arrayToReturnToPool = ArrayPool<char>.Shared.Rent(initialCapacity);
|
||||
_chars = _arrayToReturnToPool;
|
||||
_pos = 0;
|
||||
}
|
||||
|
||||
public int Length
|
||||
{
|
||||
get => _pos;
|
||||
set
|
||||
{
|
||||
Debug.Assert(value >= 0);
|
||||
Debug.Assert(value <= _chars.Length);
|
||||
_pos = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Capacity => _chars.Length;
|
||||
|
||||
public void EnsureCapacity(int capacity)
|
||||
{
|
||||
// This is not expected to be called this with negative capacity
|
||||
Debug.Assert(capacity >= 0);
|
||||
|
||||
// If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception.
|
||||
if ((uint)capacity > (uint)_chars.Length)
|
||||
Grow(capacity - _pos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a pinnable reference to the builder.
|
||||
/// Does not ensure there is a null char after <see cref="Length"/>
|
||||
/// This overload is pattern matched in the C# 7.3+ compiler so you can omit
|
||||
/// the explicit method call, and write eg "fixed (char* c = builder)"
|
||||
/// </summary>
|
||||
public ref char GetPinnableReference()
|
||||
{
|
||||
return ref MemoryMarshal.GetReference(_chars);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a pinnable reference to the builder.
|
||||
/// </summary>
|
||||
/// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
|
||||
public ref char GetPinnableReference(bool terminate)
|
||||
{
|
||||
if (terminate)
|
||||
{
|
||||
EnsureCapacity(Length + 1);
|
||||
_chars[Length] = '\0';
|
||||
}
|
||||
return ref MemoryMarshal.GetReference(_chars);
|
||||
}
|
||||
|
||||
public ref char this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(index < _pos);
|
||||
return ref _chars[index];
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string s = _chars.Slice(0, _pos).ToString();
|
||||
Dispose();
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>Returns the underlying storage of the builder.</summary>
|
||||
public Span<char> RawChars => _chars;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a span around the contents of the builder.
|
||||
/// </summary>
|
||||
/// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
|
||||
public ReadOnlySpan<char> AsSpan(bool terminate)
|
||||
{
|
||||
if (terminate)
|
||||
{
|
||||
EnsureCapacity(Length + 1);
|
||||
_chars[Length] = '\0';
|
||||
}
|
||||
return _chars.Slice(0, _pos);
|
||||
}
|
||||
|
||||
public ReadOnlySpan<char> AsSpan() => _chars.Slice(0, _pos);
|
||||
public ReadOnlySpan<char> AsSpan(int start) => _chars.Slice(start, _pos - start);
|
||||
public ReadOnlySpan<char> AsSpan(int start, int length) => _chars.Slice(start, length);
|
||||
|
||||
public bool TryCopyTo(Span<char> destination, out int charsWritten)
|
||||
{
|
||||
if (_chars.Slice(0, _pos).TryCopyTo(destination))
|
||||
{
|
||||
charsWritten = _pos;
|
||||
Dispose();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
charsWritten = 0;
|
||||
Dispose();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(int index, char value, int count)
|
||||
{
|
||||
if (_pos > _chars.Length - count)
|
||||
{
|
||||
Grow(count);
|
||||
}
|
||||
|
||||
int remaining = _pos - index;
|
||||
_chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
|
||||
_chars.Slice(index, count).Fill(value);
|
||||
_pos += count;
|
||||
}
|
||||
|
||||
public void Insert(int index, string? s)
|
||||
{
|
||||
if (s == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int count = s.Length;
|
||||
|
||||
if (_pos > (_chars.Length - count))
|
||||
{
|
||||
Grow(count);
|
||||
}
|
||||
|
||||
int remaining = _pos - index;
|
||||
_chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
|
||||
s
|
||||
#if !NET
|
||||
.AsSpan()
|
||||
#endif
|
||||
.CopyTo(_chars.Slice(index));
|
||||
_pos += count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Append(char c)
|
||||
{
|
||||
int pos = _pos;
|
||||
Span<char> chars = _chars;
|
||||
if ((uint)pos < (uint)chars.Length)
|
||||
{
|
||||
chars[pos] = c;
|
||||
_pos = pos + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
GrowAndAppend(c);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Append(string? s)
|
||||
{
|
||||
if (s == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int pos = _pos;
|
||||
if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
|
||||
{
|
||||
_chars[pos] = s[0];
|
||||
_pos = pos + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendSlow(s);
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendSlow(string s)
|
||||
{
|
||||
int pos = _pos;
|
||||
if (pos > _chars.Length - s.Length)
|
||||
{
|
||||
Grow(s.Length);
|
||||
}
|
||||
|
||||
s
|
||||
#if !NET
|
||||
.AsSpan()
|
||||
#endif
|
||||
.CopyTo(_chars.Slice(pos));
|
||||
_pos += s.Length;
|
||||
}
|
||||
|
||||
public void Append(char c, int count)
|
||||
{
|
||||
if (_pos > _chars.Length - count)
|
||||
{
|
||||
Grow(count);
|
||||
}
|
||||
|
||||
Span<char> dst = _chars.Slice(_pos, count);
|
||||
for (int i = 0; i < dst.Length; i++)
|
||||
{
|
||||
dst[i] = c;
|
||||
}
|
||||
_pos += count;
|
||||
}
|
||||
|
||||
public void Append(scoped ReadOnlySpan<char> value)
|
||||
{
|
||||
int pos = _pos;
|
||||
if (pos > _chars.Length - value.Length)
|
||||
{
|
||||
Grow(value.Length);
|
||||
}
|
||||
|
||||
value.CopyTo(_chars.Slice(_pos));
|
||||
_pos += value.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<char> AppendSpan(int length)
|
||||
{
|
||||
int origPos = _pos;
|
||||
if (origPos > _chars.Length - length)
|
||||
{
|
||||
Grow(length);
|
||||
}
|
||||
|
||||
_pos = origPos + length;
|
||||
return _chars.Slice(origPos, length);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void GrowAndAppend(char c)
|
||||
{
|
||||
Grow(1);
|
||||
Append(c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resize the internal buffer either by doubling current buffer size or
|
||||
/// by adding <paramref name="additionalCapacityBeyondPos"/> to
|
||||
/// <see cref="_pos"/> whichever is greater.
|
||||
/// </summary>
|
||||
/// <param name="additionalCapacityBeyondPos">
|
||||
/// Number of chars requested beyond current position.
|
||||
/// </param>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void Grow(int additionalCapacityBeyondPos)
|
||||
{
|
||||
Debug.Assert(additionalCapacityBeyondPos > 0);
|
||||
Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed.");
|
||||
|
||||
const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength
|
||||
|
||||
// Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try
|
||||
// to double the size if possible, bounding the doubling to not go beyond the max array length.
|
||||
int newCapacity = (int)Math.Max(
|
||||
(uint)(_pos + additionalCapacityBeyondPos),
|
||||
Math.Min((uint)_chars.Length * 2, ArrayMaxLength));
|
||||
|
||||
// Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative.
|
||||
// This could also go negative if the actual required length wraps around.
|
||||
char[] poolArray = ArrayPool<char>.Shared.Rent(newCapacity);
|
||||
|
||||
_chars.Slice(0, _pos).CopyTo(poolArray);
|
||||
|
||||
char[]? toReturn = _arrayToReturnToPool;
|
||||
_chars = _arrayToReturnToPool = poolArray;
|
||||
if (toReturn != null)
|
||||
{
|
||||
ArrayPool<char>.Shared.Return(toReturn);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Dispose()
|
||||
{
|
||||
char[]? toReturn = _arrayToReturnToPool;
|
||||
this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
|
||||
if (toReturn != null)
|
||||
{
|
||||
ArrayPool<char>.Shared.Return(toReturn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using ModelContextProtocol;
|
||||
using System.Buffers;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace System.IO;
|
||||
|
||||
|
|
@ -33,4 +34,31 @@ internal static class StreamExtensions
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ValueTask<int> ReadAsync(this Stream stream, Memory<byte> buffer, CancellationToken cancellationToken)
|
||||
{
|
||||
Throw.IfNull(stream);
|
||||
if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> segment))
|
||||
{
|
||||
return new ValueTask<int>(stream.ReadAsync(segment.Array, segment.Offset, segment.Count, cancellationToken));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ReadAsyncCore(stream, buffer, cancellationToken);
|
||||
static async ValueTask<int> ReadAsyncCore(Stream stream, Memory<byte> buffer, CancellationToken cancellationToken)
|
||||
{
|
||||
byte[] array = ArrayPool<byte>.Shared.Rent(buffer.Length);
|
||||
try
|
||||
{
|
||||
int bytesRead = await stream.ReadAsync(array, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
array.AsSpan(0, bytesRead).CopyTo(buffer.Span);
|
||||
return bytesRead;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(array);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
namespace System.IO;
|
||||
|
||||
internal static class TextReaderExtensions
|
||||
{
|
||||
public static Task<string> ReadLineAsync(this TextReader reader, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return reader.ReadLineAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,15 @@ internal static class Throw
|
|||
}
|
||||
}
|
||||
|
||||
public static void IfNegative(int arg, [CallerArgumentExpression(nameof(arg))] string? parameterName = null)
|
||||
{
|
||||
if (arg < 0)
|
||||
{
|
||||
Throw(parameterName);
|
||||
static void Throw(string? parameterName) => throw new ArgumentOutOfRangeException(parameterName, "must not be negative.");
|
||||
}
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
private static void ThrowArgumentNullOrWhiteSpaceException(string? parameterName)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ internal sealed class StdioClientSessionTransport : StreamClientSessionTransport
|
|||
private int _cleanedUp = 0;
|
||||
|
||||
public StdioClientSessionTransport(StdioClientTransportOptions options, Process process, string endpointName, Queue<string> stderrRollingLog, ILoggerFactory? loggerFactory)
|
||||
: base(process.StandardInput, process.StandardOutput, endpointName, loggerFactory)
|
||||
: base(process.StandardInput.BaseStream, process.StandardOutput.BaseStream, encoding: null, endpointName, loggerFactory)
|
||||
{
|
||||
_process = process;
|
||||
_options = options;
|
||||
|
|
|
|||
|
|
@ -69,8 +69,6 @@ public sealed partial class StdioClientTransport : IClientTransport
|
|||
{
|
||||
LogTransportConnecting(logger, endpointName);
|
||||
|
||||
UTF8Encoding noBomUTF8 = new(encoderShouldEmitUTF8Identifier: false);
|
||||
|
||||
ProcessStartInfo startInfo = new()
|
||||
{
|
||||
FileName = command,
|
||||
|
|
@ -80,10 +78,10 @@ public sealed partial class StdioClientTransport : IClientTransport
|
|||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WorkingDirectory = _options.WorkingDirectory ?? Environment.CurrentDirectory,
|
||||
StandardOutputEncoding = noBomUTF8,
|
||||
StandardErrorEncoding = noBomUTF8,
|
||||
StandardOutputEncoding = StreamClientSessionTransport.NoBomUtf8Encoding,
|
||||
StandardErrorEncoding = StreamClientSessionTransport.NoBomUtf8Encoding,
|
||||
#if NET
|
||||
StandardInputEncoding = noBomUTF8,
|
||||
StandardInputEncoding = StreamClientSessionTransport.NoBomUtf8Encoding,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
@ -164,7 +162,7 @@ public sealed partial class StdioClientTransport : IClientTransport
|
|||
Encoding originalInputEncoding = Console.InputEncoding;
|
||||
try
|
||||
{
|
||||
Console.InputEncoding = noBomUTF8;
|
||||
Console.InputEncoding = StreamClientSessionTransport.NoBomUtf8Encoding;
|
||||
processStarted = process.Start();
|
||||
}
|
||||
finally
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Client;
|
||||
|
|
@ -7,6 +8,8 @@ namespace ModelContextProtocol.Client;
|
|||
/// <summary>Provides the client side of a stream-based session transport.</summary>
|
||||
internal class StreamClientSessionTransport : TransportBase
|
||||
{
|
||||
internal static UTF8Encoding NoBomUtf8Encoding { get; } = new(encoderShouldEmitUTF8Identifier: false);
|
||||
|
||||
private readonly TextReader _serverOutput;
|
||||
private readonly TextWriter _serverInput;
|
||||
private readonly SemaphoreSlim _sendLock = new(1, 1);
|
||||
|
|
@ -54,6 +57,43 @@ internal class StreamClientSessionTransport : TransportBase
|
|||
readTask.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StreamClientSessionTransport"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serverInput">
|
||||
/// The server's input stream. Messages written to this stream will be sent to the server.
|
||||
/// </param>
|
||||
/// <param name="serverOutput">
|
||||
/// The server's output stream. Messages read from this stream will be received from the server.
|
||||
/// </param>
|
||||
/// <param name="encoding">
|
||||
/// The encoding used for reading and writing messages from the input and output streams. Defaults to UTF-8 without BOM if null.
|
||||
/// </param>
|
||||
/// <param name="endpointName">
|
||||
/// A name that identifies this transport endpoint in logs.
|
||||
/// </param>
|
||||
/// <param name="loggerFactory">
|
||||
/// Optional factory for creating loggers. If null, a NullLogger will be used.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// This constructor starts a background task to read messages from the server output stream.
|
||||
/// The transport will be marked as connected once initialized.
|
||||
/// </remarks>
|
||||
public StreamClientSessionTransport(Stream serverInput, Stream serverOutput, Encoding? encoding, string endpointName, ILoggerFactory? loggerFactory)
|
||||
: this(
|
||||
new StreamWriter(serverInput, encoding ?? NoBomUtf8Encoding),
|
||||
#if NET
|
||||
new StreamReader(serverOutput, encoding ?? NoBomUtf8Encoding),
|
||||
#else
|
||||
new CancellableStreamReader(serverOutput, encoding ?? NoBomUtf8Encoding),
|
||||
#endif
|
||||
endpointName,
|
||||
loggerFactory)
|
||||
{
|
||||
Throw.IfNull(serverInput);
|
||||
Throw.IfNull(serverOutput);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -47,8 +47,9 @@ public sealed class StreamClientTransport : IClientTransport
|
|||
public Task<ITransport> ConnectAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult<ITransport>(new StreamClientSessionTransport(
|
||||
new StreamWriter(_serverInput),
|
||||
new StreamReader(_serverOutput),
|
||||
_serverInput,
|
||||
_serverOutput,
|
||||
encoding: null,
|
||||
"Client (stream)",
|
||||
_loggerFactory));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<PackageId>ModelContextProtocol.Core</PackageId>
|
||||
<Description>Core .NET SDK for the Model Context Protocol (MCP)</Description>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard2.0'">
|
||||
|
|
@ -20,6 +21,7 @@
|
|||
<!-- Dependencies only needed by netstandard2.0 -->
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<Compile Include="..\Common\Polyfills\**\*.cs" />
|
||||
<Compile Include="..\Common\CancellableStreamReader\**\*.cs" />
|
||||
<PackageReference Include="Microsoft.Bcl.Memory" />
|
||||
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public class McpServerPrimitiveCollection<T> : ICollection<T>, IReadOnlyCollecti
|
|||
/// </summary>
|
||||
public McpServerPrimitiveCollection(IEqualityComparer<string>? keyComparer = null)
|
||||
{
|
||||
_primitives = new(keyComparer);
|
||||
_primitives = new(keyComparer ?? EqualityComparer<string>.Default);
|
||||
}
|
||||
|
||||
/// <summary>Occurs when the collection is changed.</summary>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,11 @@ public class StreamServerTransport : TransportBase
|
|||
|
||||
_logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance;
|
||||
|
||||
#if NET
|
||||
_inputReader = new StreamReader(inputStream, Encoding.UTF8);
|
||||
#else
|
||||
_inputReader = new CancellableStreamReader(inputStream, Encoding.UTF8);
|
||||
#endif
|
||||
_outputStream = outputStream;
|
||||
|
||||
SetConnected();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
namespace ModelContextProtocol.Tests.Utils;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Utils;
|
||||
|
||||
public class MockHttpHandler : HttpMessageHandler
|
||||
{
|
||||
|
|
|
|||
15
tests/Common/Utils/ProcessExtensions.cs
Normal file
15
tests/Common/Utils/ProcessExtensions.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
namespace System.Diagnostics;
|
||||
|
||||
public static class ProcessExtensions
|
||||
{
|
||||
public static async Task WaitForExitAsync(this Process process, TimeSpan timeout)
|
||||
{
|
||||
#if NET
|
||||
using var shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken);
|
||||
shutdownCts.CancelAfter(timeout);
|
||||
await process.WaitForExitAsync(shutdownCts.Token);
|
||||
#else
|
||||
process.WaitForExit(milliseconds: (int)timeout.TotalMilliseconds);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="Serilog" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -25,14 +25,14 @@ public class ClientIntegrationTestFixture
|
|||
|
||||
TestServerTransportOptions = new()
|
||||
{
|
||||
Command = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "TestServer.exe" : "dotnet",
|
||||
Command = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "TestServer.exe" : PlatformDetection.IsMonoRuntime ? "mono" : "dotnet",
|
||||
Name = "TestServer",
|
||||
};
|
||||
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// Change to Arguments to "mcp-server-everything" if you want to run the server locally after creating a symlink
|
||||
TestServerTransportOptions.Arguments = ["TestServer.dll"];
|
||||
TestServerTransportOptions.Arguments = [PlatformDetection.IsMonoRuntime ? "TestServer.exe" : "TestServer.dll"];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -606,7 +606,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
|
||||
McpClientTool progressTool = tools.First(t => t.Name == "sends_progress_notifications");
|
||||
|
||||
TaskCompletionSource tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
TaskCompletionSource<bool> tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
int remainingNotifications = 10;
|
||||
|
||||
ConcurrentQueue<ProgressNotificationParams> notifications = new();
|
||||
|
|
@ -618,7 +618,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
notifications.Enqueue(pn);
|
||||
if (Interlocked.Decrement(ref remainingNotifications) == 0)
|
||||
{
|
||||
tcs.SetResult();
|
||||
tcs.SetResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ public class EverythingSseServerFixture : IAsyncDisposable
|
|||
|
||||
using var stopProcess = Process.Start(stopInfo)
|
||||
?? throw new InvalidOperationException($"Could not stop process for {stopInfo.FileName} with '{stopInfo.Arguments}'.");
|
||||
await stopProcess.WaitForExitAsync();
|
||||
await stopProcess.WaitForExitAsync(TimeSpan.FromSeconds(10));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -60,6 +60,7 @@ public class EverythingSseServerFixture : IAsyncDisposable
|
|||
|
||||
private static bool CheckIsDockerAvailable()
|
||||
{
|
||||
#if NET
|
||||
try
|
||||
{
|
||||
ProcessStartInfo processStartInfo = new()
|
||||
|
|
@ -78,5 +79,9 @@ public class EverythingSseServerFixture : IAsyncDisposable
|
|||
{
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
// Do not run docker tests using .NET framework.
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue