Stop throwing from `TryGetArgumentByNameOrIndex` when using the `System.Text.Json` formatter

This also adjusts how we test with ETW events turned on so that issues like this will be caught rather than swallowed.

Fixes #1038
This commit is contained in:
Andrew Arnott 2024-05-06 18:59:52 -06:00
Родитель f6a3b480bc
Коммит c5fd7b7437
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: F33A420C60ED9C6F
6 изменённых файлов: 62 добавлений и 14 удалений

Просмотреть файл

@ -17,13 +17,23 @@ steps:
condition: and(succeeded(), ${{ parameters.RunTests }})
- task: DotNetCoreCLI@2
displayName: 🧪 dotnet test -f net472 (+EventSource)
displayName: 🧪 dotnet test -f net472 (+EventSource throw)
inputs:
command: test
arguments: --no-build -c $(BuildConfiguration) -f net472 --filter "TestCategory!=FailsInCloudTest$(FailsOnMonoFilter)" -v n /p:CollectCoverage=true /bl:"$(Build.ArtifactStagingDirectory)/build_logs/test_net472_etw.binlog" --diag "$(Build.ArtifactStagingDirectory)/test_logs/net472_etw.txt"
testRunTitle: streamjsonrpc.tests-etw (net472, $(Agent.JobName))
env:
StreamJsonRpc_TestWithEventSource: 1
StreamJsonRpc_TestWithEventSource: 1 # allow exceptions from EventSource to propagate
condition: and(succeeded(), ne(variables['OptProf'], 'true'), eq(variables['Agent.OS'], 'Windows_NT'))
- task: DotNetCoreCLI@2
displayName: 🧪 dotnet test -f net472 (+EventSource production)
inputs:
command: test
arguments: --no-build -c $(BuildConfiguration) -f net472 --filter "TestCategory!=FailsInCloudTest$(FailsOnMonoFilter)" -v n /p:CollectCoverage=true /bl:"$(Build.ArtifactStagingDirectory)/build_logs/test_net472_etw.binlog" --diag "$(Build.ArtifactStagingDirectory)/test_logs/net472_etw.txt"
testRunTitle: streamjsonrpc.tests-etw (net472, $(Agent.JobName))
env:
StreamJsonRpc_TestWithEventSource: 2 # swallow exceptions from EventSource, as is done in production
condition: and(succeeded(), ne(variables['OptProf'], 'true'), eq(variables['Agent.OS'], 'Windows_NT'))
- ${{ if parameters.IsOptProf }}:

Просмотреть файл

@ -20,7 +20,7 @@ internal sealed class JsonRpcEventSource : EventSource
/// <summary>
/// The singleton instance of this event source.
/// </summary>
internal static readonly JsonRpcEventSource Instance = new JsonRpcEventSource();
internal static readonly JsonRpcEventSource Instance = new();
/// <summary>
/// The event ID for the <see cref="SendingNotification"/>.
@ -98,9 +98,9 @@ internal sealed class JsonRpcEventSource : EventSource
private const int MessageHandlerReceivedEvent = 33;
/// <summary>
/// A flag indicating whether to force tracing to be on.
/// Gets the testing mode that ETW tracing is running under.
/// </summary>
private static readonly bool AlwaysOn = Environment.GetEnvironmentVariable("StreamJsonRpc_TestWithEventSource") == "1";
private static readonly SharedUtilities.EventSourceTestMode EventSourceTestingActive = SharedUtilities.GetEventSourceTestMode();
/// <summary>
/// Initializes a new instance of the <see cref="JsonRpcEventSource"/> class.
@ -113,7 +113,7 @@ internal sealed class JsonRpcEventSource : EventSource
}
/// <inheritdoc cref="EventSource.IsEnabled(EventLevel, EventKeywords)"/>
public new bool IsEnabled(EventLevel level, EventKeywords keywords) => AlwaysOn || base.IsEnabled(level, keywords);
public new bool IsEnabled(EventLevel level, EventKeywords keywords) => EventSourceTestingActive != SharedUtilities.EventSourceTestMode.None || base.IsEnabled(level, keywords);
/// <summary>
/// Signals the transmission of a notification.
@ -301,7 +301,7 @@ internal sealed class JsonRpcEventSource : EventSource
formatted = true;
}
}
catch
catch when (EventSourceTestingActive != SharedUtilities.EventSourceTestMode.DoNotSwallowExceptions)
{
// Swallow exceptions when deserializing args for ETW logging. It's never worth causing a functional failure.
}
@ -339,7 +339,7 @@ internal sealed class JsonRpcEventSource : EventSource
formatted = true;
}
}
catch
catch when (EventSourceTestingActive != SharedUtilities.EventSourceTestMode.DoNotSwallowExceptions)
{
// Swallow exceptions when deserializing args for ETW logging. It's never worth causing a functional failure.
}
@ -368,9 +368,9 @@ internal sealed class JsonRpcEventSource : EventSource
builder.Append("null");
break;
case string s:
builder.Append("\"");
builder.Append('"');
builder.Append(s);
builder.Append("\"");
builder.Append('"');
break;
default:
builder.Append(value);

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace StreamJsonRpc;
/// <summary>
/// Utilities that are source-shared between the library and its tests.
/// </summary>
internal static class SharedUtilities
{
/// <summary>
/// The various modes that can be used to test the <see cref="JsonRpcEventSource"/> class.
/// </summary>
internal enum EventSourceTestMode
{
/// <summary>
/// ETW events are not forced on.
/// </summary>
None,
/// <summary>
/// ETW events are forced on and exceptions are swallowed as they would be in production.
/// </summary>
EmulateProduction,
/// <summary>
/// ETW events are forced on and exceptions are not swallowed, allowing tests to detect errors in ETW logging.
/// </summary>
DoNotSwallowExceptions,
}
internal static EventSourceTestMode GetEventSourceTestMode() => Environment.GetEnvironmentVariable("StreamJsonRpc_TestWithEventSource") switch
{
"1" => EventSourceTestMode.EmulateProduction,
"2" => EventSourceTestMode.DoNotSwallowExceptions,
_ => EventSourceTestMode.None,
};
}

Просмотреть файл

@ -516,7 +516,7 @@ public class SystemTextJsonFormatter : FormatterBase, IJsonRpcMessageFormatter,
}
break;
case JsonValueKind.Array:
case JsonValueKind.Array when position >= 0:
int elementIndex = 0;
foreach (JsonElement arrayElement in this.JsonArguments.Value.EnumerateArray())
{
@ -528,8 +528,6 @@ public class SystemTextJsonFormatter : FormatterBase, IJsonRpcMessageFormatter,
}
break;
default:
throw new JsonException("Unexpected value kind for arguments: " + (this.JsonArguments?.ValueKind.ToString() ?? "null"));
}
try

Просмотреть файл

@ -372,9 +372,10 @@ public class JsonRpcMessagePackLengthTests : JsonRpcTests
/// verbose ETW tracing would fail to deserialize arguments with the primitive formatter that deserialize just fine for the actual method dispatch.</para>
/// <para>This test is effective because custom msgpack extensions cause the <see cref="PrimitiveObjectFormatter"/> to throw an exception when deserializing.</para>
/// </remarks>
[Theory, PairwiseData]
[SkippableTheory, PairwiseData]
public async Task VerboseLoggingDoesNotFailWhenArgsDoNotDeserializePrimitively(bool namedArguments)
{
Skip.IfNot(SharedUtilities.GetEventSourceTestMode() == SharedUtilities.EventSourceTestMode.EmulateProduction, $"This test specifically verifies behavior when the EventSource should swallow exceptions. Current mode: {SharedUtilities.GetEventSourceTestMode()}.");
var server = new MessagePackServer();
this.serverRpc.AllowModificationWhileListening = true;
this.serverRpc.AddLocalRpcTarget(server);

Просмотреть файл

@ -8,6 +8,7 @@
<ItemGroup>
<Compile Include="..\..\src\StreamJsonRpc\DisposableAction.cs" Link="DisposableAction.cs" />
<Compile Include="..\..\src\StreamJsonRpc\SharedUtilities.cs" Link="SharedUtilities.cs" />
<Compile Update="DisposableProxyJsonTests.cs" DependentUpon="DisposableProxyTests.cs" />
<Compile Update="DisposableProxyMessagePackTests.cs" DependentUpon="DisposableProxyTests.cs" />
<Compile Update="DisposableProxySystemTextJsonTests.cs" DependentUpon="DisposableProxyTests.cs" />