Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/aspnetcore.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKevin Pilch <kevinpi@microsoft.com>2019-08-21 00:00:29 +0300
committerGitHub <noreply@github.com>2019-08-21 00:00:29 +0300
commit733218c6520edf03971f7befef4e39b6d4076a9f (patch)
treea9b0f7b6896ae7d0dfee49360dc816dbbf91d67a /src
parent55b271227dd024b9941cb1af6c4d5f62ea5f2682 (diff)
parentbe2a71855bb62c51846e20e624990da4783792ab (diff)
Merge pull request #13226 from aspnet/rynowak/interop-reliability
Improve reliability of reliability tests
Diffstat (limited to 'src')
-rw-r--r--src/Components/Server/src/Circuits/CircuitHost.cs4
-rw-r--r--src/Components/test/E2ETest/ServerExecutionTests/ComponentHubInvalidEventTest.cs138
-rw-r--r--src/Components/test/E2ETest/ServerExecutionTests/ComponentHubReliabilityTest.cs228
-rw-r--r--src/Components/test/E2ETest/ServerExecutionTests/IgnitorTest.cs131
-rw-r--r--src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs280
-rw-r--r--src/Components/test/E2ETest/ServerExecutionTests/RemoteRendererBufferLimitTest.cs77
-rw-r--r--src/Components/test/testassets/BasicTestApp/ServerReliability/ReliabilityComponent.razor6
-rw-r--r--src/Components/test/testassets/Ignitor/BlazorClient.cs224
-rw-r--r--src/Components/test/testassets/Ignitor/CapturedJSInteropCall.cs19
-rw-r--r--src/Components/test/testassets/Ignitor/CapturedRenderBatch.cs17
-rw-r--r--src/Components/test/testassets/Ignitor/Operations.cs18
-rw-r--r--src/Components/test/testassets/Ignitor/Program.cs12
12 files changed, 577 insertions, 577 deletions
diff --git a/src/Components/Server/src/Circuits/CircuitHost.cs b/src/Components/Server/src/Circuits/CircuitHost.cs
index d1e851f090..0d7c7336dd 100644
--- a/src/Components/Server/src/Circuits/CircuitHost.cs
+++ b/src/Components/Server/src/Circuits/CircuitHost.cs
@@ -649,12 +649,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
{
_intializationStarted = LoggerMessage.Define(
LogLevel.Debug,
- EventIds.InitializationFailed,
+ EventIds.InitializationStarted,
"Circuit initialization started.");
_intializationSucceded = LoggerMessage.Define(
LogLevel.Debug,
- EventIds.InitializationFailed,
+ EventIds.InitializationSucceeded,
"Circuit initialization succeeded.");
_intializationFailed = LoggerMessage.Define(
diff --git a/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubInvalidEventTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubInvalidEventTest.cs
new file mode 100644
index 0000000000..8621107ec4
--- /dev/null
+++ b/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubInvalidEventTest.cs
@@ -0,0 +1,138 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Components.E2ETest;
+using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
+using Microsoft.AspNetCore.Components.RenderTree;
+using Microsoft.AspNetCore.Components.Web;
+using Microsoft.AspNetCore.SignalR.Client;
+using Microsoft.Extensions.Logging;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.AspNetCore.Components.E2ETests.ServerExecutionTests
+{
+ public class ComponentHubInvalidEventTest : IgnitorTest<AspNetSiteServerFixture>
+ {
+ public ComponentHubInvalidEventTest(AspNetSiteServerFixture serverFixture, ITestOutputHelper output)
+ : base(serverFixture, output)
+ {
+ }
+
+ protected override void InitializeFixture(AspNetSiteServerFixture serverFixture)
+ {
+ serverFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
+ }
+
+ protected async override Task InitializeAsync()
+ {
+ var rootUri = ServerFixture.RootUri;
+ Assert.True(await Client.ConnectAsync(new Uri(rootUri, "/subdir")), "Couldn't connect to the app");
+ Assert.Single(Batches);
+
+ await Client.SelectAsync("test-selector-select", "BasicTestApp.CounterComponent");
+ Assert.Equal(2, Batches.Count);
+ }
+
+ [Fact]
+ public async Task DispatchingAnInvalidEventArgument_DoesNotProduceWarnings()
+ {
+ // Arrange
+ var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
+ $"detailed exceptions in 'CircuitOptions.DetailedErrors'. Bad input data.";
+
+ var eventDescriptor = Serialize(new WebEventDescriptor()
+ {
+ BrowserRendererId = 0,
+ EventHandlerId = 3,
+ EventArgsType = "mouse",
+ });
+
+ // Act
+ await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
+ "DispatchBrowserEvent",
+ eventDescriptor,
+ "{sadfadsf]"));
+
+ // Assert
+ var actualError = Assert.Single(Errors);
+ Assert.Equal(expectedError, actualError);
+ Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
+ Assert.Contains(Logs, l => (l.LogLevel, l.Exception?.Message) == (LogLevel.Debug, "There was an error parsing the event arguments. EventId: '3'."));
+ }
+
+ [Fact]
+ public async Task DispatchingAnInvalidEvent_DoesNotTriggerWarnings()
+ {
+ // Arrange
+ var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
+ $"detailed exceptions in 'CircuitOptions.DetailedErrors'. Failed to dispatch event.";
+
+ var eventDescriptor = Serialize(new WebEventDescriptor()
+ {
+ BrowserRendererId = 0,
+ EventHandlerId = 1990,
+ EventArgsType = "mouse",
+ });
+
+ var eventArgs = new MouseEventArgs
+ {
+ Type = "click",
+ Detail = 1,
+ ScreenX = 47,
+ ScreenY = 258,
+ ClientX = 47,
+ ClientY = 155,
+ };
+
+ // Act
+ await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
+ "DispatchBrowserEvent",
+ eventDescriptor,
+ Serialize(eventArgs)));
+
+ // Assert
+ var actualError = Assert.Single(Errors);
+ Assert.Equal(expectedError, actualError);
+ Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
+ Assert.Contains(Logs, l => (l.LogLevel, l.Message, l.Exception?.Message) ==
+ (LogLevel.Debug,
+ "There was an error dispatching the event '1990' to the application.",
+ "There is no event handler associated with this event. EventId: '1990'. (Parameter 'eventHandlerId')"));
+ }
+
+ [Fact]
+ public async Task DispatchingAnInvalidRenderAcknowledgement_DoesNotTriggerWarnings()
+ {
+ // Arrange
+ var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
+ $"detailed exceptions in 'CircuitOptions.DetailedErrors'. Failed to complete render batch '1846'.";
+
+
+ Client.ConfirmRenderBatch = false;
+ await Client.ClickAsync("counter");
+
+ // Act
+ await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
+ "OnRenderCompleted",
+ 1846,
+ null));
+
+ // Assert
+ var actualError = Assert.Single(Errors);
+ Assert.Equal(expectedError, actualError);
+ Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
+
+ var entry = Assert.Single(Logs, l => l.EventId.Name == "OnRenderCompletedFailed");
+ Assert.Equal(LogLevel.Debug, entry.LogLevel);
+ Assert.Matches("Failed to complete render batch '1846' in circuit host '.*'\\.", entry.Message);
+ Assert.Equal("Received an acknowledgement for batch with id '1846' when the last batch produced was '4'.", entry.Exception.Message);
+ }
+
+ private string Serialize<T>(T browserEventDescriptor) =>
+ JsonSerializer.Serialize(browserEventDescriptor, TestJsonSerializerOptionsProvider.Options);
+ }
+}
diff --git a/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubReliabilityTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubReliabilityTest.cs
index a1401a4aca..8d770e64de 100644
--- a/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubReliabilityTest.cs
+++ b/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubReliabilityTest.cs
@@ -2,9 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@@ -13,58 +11,22 @@ using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.SignalR.Client;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Testing;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
- public class ComponentHubReliabilityTest : IClassFixture<AspNetSiteServerFixture>, IDisposable
+ public class ComponentHubReliabilityTest : IgnitorTest<AspNetSiteServerFixture>
{
- private static readonly TimeSpan DefaultLatencyTimeout = Debugger.IsAttached ? TimeSpan.MaxValue : TimeSpan.FromSeconds(10);
- private readonly AspNetSiteServerFixture _serverFixture;
-
public ComponentHubReliabilityTest(AspNetSiteServerFixture serverFixture, ITestOutputHelper output)
+ : base(serverFixture, output)
{
- _serverFixture = serverFixture;
- Output = output;
-
- serverFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
- CreateDefaultConfiguration();
}
- public BlazorClient Client { get; set; }
- public ITestOutputHelper Output { get; set; }
- private IList<Batch> Batches { get; set; } = new List<Batch>();
- private IList<string> Errors { get; set; } = new List<string>();
- private ConcurrentQueue<LogMessage> Logs { get; set; } = new ConcurrentQueue<LogMessage>();
-
- public TestSink TestSink { get; set; }
-
- private void CreateDefaultConfiguration()
+ protected override void InitializeFixture(AspNetSiteServerFixture serverFixture)
{
- Client = new BlazorClient() { DefaultLatencyTimeout = DefaultLatencyTimeout };
- Client.RenderBatchReceived += (id, data) => Batches.Add(new Batch(id, data));
- Client.OnCircuitError += (error) => Errors.Add(error);
- Client.LoggerProvider = new XunitLoggerProvider(Output);
- Client.FormatError = (error) =>
- {
- var logs = string.Join(Environment.NewLine, Logs);
- return new Exception(error + Environment.NewLine + logs);
- };
-
- _ = _serverFixture.RootUri; // this is needed for the side-effects of getting the URI.
- TestSink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
- TestSink.MessageLogged += LogMessages;
- }
-
- private void LogMessages(WriteContext context)
- {
- var log = new LogMessage(context.LogLevel, context.EventId, context.Message, context.Exception);
- Logs.Enqueue(log);
- Output.WriteLine(log.ToString());
+ serverFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
}
[Fact]
@@ -72,10 +34,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
// Arrange
var expectedError = "The circuit host '.*?' has already been initialized.";
- var rootUri = _serverFixture.RootUri;
+ var rootUri = ServerFixture.RootUri;
var baseUri = new Uri(rootUri, "/subdir");
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
Assert.Single(Batches);
+
var descriptors = await Client.GetPrerenderDescriptors(baseUri);
// Act
@@ -96,7 +59,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
// Arrange
var expectedError = "The uris provided are invalid.";
- var rootUri = _serverFixture.RootUri;
+ var rootUri = ServerFixture.RootUri;
var uri = new Uri(rootUri, "/subdir");
Assert.True(await Client.ConnectAsync(uri, connectAutomatically: false), "Couldn't connect to the app");
var descriptors = await Client.GetPrerenderDescriptors(uri);
@@ -117,7 +80,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
// Arrange
var expectedError = "The circuit failed to initialize.";
- var rootUri = _serverFixture.RootUri;
+ var rootUri = ServerFixture.RootUri;
var uri = new Uri(rootUri, "/subdir");
Assert.True(await Client.ConnectAsync(uri, connectAutomatically: false), "Couldn't connect to the app");
var descriptors = await Client.GetPrerenderDescriptors(uri);
@@ -138,7 +101,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
// Arrange
var expectedError = "Circuit not initialized.";
- var rootUri = _serverFixture.RootUri;
+ var rootUri = ServerFixture.RootUri;
var baseUri = new Uri(rootUri, "/subdir");
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
Assert.Empty(Batches);
@@ -164,7 +127,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
// Arrange
var expectedError = "Circuit not initialized.";
- var rootUri = _serverFixture.RootUri;
+ var rootUri = ServerFixture.RootUri;
var baseUri = new Uri(rootUri, "/subdir");
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
Assert.Empty(Batches);
@@ -188,7 +151,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
// Arrange
var expectedError = "Circuit not initialized.";
- var rootUri = _serverFixture.RootUri;
+ var rootUri = ServerFixture.RootUri;
var baseUri = new Uri(rootUri, "/subdir");
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
Assert.Empty(Batches);
@@ -206,125 +169,12 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Assert.Contains(Logs, l => (l.LogLevel, l.Message) == (LogLevel.Debug, "Call to 'DispatchBrowserEvent' received before the circuit host initialization"));
}
- private async Task GoToTestComponent(IList<Batch> batches)
- {
- var rootUri = _serverFixture.RootUri;
- Assert.True(await Client.ConnectAsync(new Uri(rootUri, "/subdir")), "Couldn't connect to the app");
- Assert.Single(batches);
-
- await Client.SelectAsync("test-selector-select", "BasicTestApp.CounterComponent");
- Assert.Equal(2, batches.Count);
- }
-
- [Fact]
- public async Task DispatchingAnInvalidEventArgument_DoesNotProduceWarnings()
- {
- // Arrange
- var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
- $"detailed exceptions in 'CircuitOptions.DetailedErrors'. Bad input data.";
-
- var eventDescriptor = Serialize(new WebEventDescriptor()
- {
- BrowserRendererId = 0,
- EventHandlerId = 3,
- EventArgsType = "mouse",
- });
-
- await GoToTestComponent(Batches);
- Assert.Equal(2, Batches.Count);
-
- // Act
- await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
- "DispatchBrowserEvent",
- eventDescriptor,
- "{sadfadsf]"));
-
- // Assert
- var actualError = Assert.Single(Errors);
- Assert.Equal(expectedError, actualError);
- Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
- Assert.Contains(Logs, l => (l.LogLevel, l.Exception?.Message) == (LogLevel.Debug, "There was an error parsing the event arguments. EventId: '3'."));
- }
-
- [Fact]
- public async Task DispatchingAnInvalidEvent_DoesNotTriggerWarnings()
- {
- // Arrange
- var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
- $"detailed exceptions in 'CircuitOptions.DetailedErrors'. Failed to dispatch event.";
-
- var eventDescriptor = Serialize(new WebEventDescriptor()
- {
- BrowserRendererId = 0,
- EventHandlerId = 1990,
- EventArgsType = "mouse",
- });
-
- var eventArgs = new MouseEventArgs
- {
- Type = "click",
- Detail = 1,
- ScreenX = 47,
- ScreenY = 258,
- ClientX = 47,
- ClientY = 155,
- };
-
- await GoToTestComponent(Batches);
- Assert.Equal(2, Batches.Count);
-
- // Act
- await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
- "DispatchBrowserEvent",
- eventDescriptor,
- Serialize(eventArgs)));
-
- // Assert
- var actualError = Assert.Single(Errors);
- Assert.Equal(expectedError, actualError);
- Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
- Assert.Contains(Logs, l => (l.LogLevel, l.Message, l.Exception?.Message) ==
- (LogLevel.Debug,
- "There was an error dispatching the event '1990' to the application.",
- "There is no event handler associated with this event. EventId: '1990'. (Parameter 'eventHandlerId')"));
- }
-
- [Fact]
- public async Task DispatchingAnInvalidRenderAcknowledgement_DoesNotTriggerWarnings()
- {
- // Arrange
- var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
- $"detailed exceptions in 'CircuitOptions.DetailedErrors'. Failed to complete render batch '1846'.";
-
- await GoToTestComponent(Batches);
- Assert.Equal(2, Batches.Count);
-
- Client.ConfirmRenderBatch = false;
- await Client.ClickAsync("counter");
-
- // Act
- await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
- "OnRenderCompleted",
- 1846,
- null));
-
- // Assert
- var actualError = Assert.Single(Errors);
- Assert.Equal(expectedError, actualError);
- Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
-
- var entry = Assert.Single(Logs, l => l.EventId.Name == "OnRenderCompletedFailed");
- Assert.Equal(LogLevel.Debug, entry.LogLevel);
- Assert.Matches("Failed to complete render batch '1846' in circuit host '.*'\\.", entry.Message);
- Assert.Equal("Received an acknowledgement for batch with id '1846' when the last batch produced was '4'.", entry.Exception.Message);
- }
-
[Fact]
public async Task CannotInvokeOnRenderCompletedBeforeInitialization()
{
// Arrange
var expectedError = "Circuit not initialized.";
- var rootUri = _serverFixture.RootUri;
+ var rootUri = ServerFixture.RootUri;
var baseUri = new Uri(rootUri, "/subdir");
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
Assert.Empty(Batches);
@@ -347,7 +197,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
// Arrange
var expectedError = "Circuit not initialized.";
- var rootUri = _serverFixture.RootUri;
+ var rootUri = ServerFixture.RootUri;
var baseUri = new Uri(rootUri, "/subdir");
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
Assert.Empty(Batches);
@@ -373,7 +223,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"For more details turn on detailed exceptions in 'CircuitOptions.DetailedErrors'. " +
"Location change to 'http://example.com' failed.";
- var rootUri = _serverFixture.RootUri;
+ var rootUri = ServerFixture.RootUri;
var baseUri = new Uri(rootUri, "/subdir");
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
Assert.Single(Batches);
@@ -402,7 +252,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"For more details turn on detailed exceptions in 'CircuitOptions.DetailedErrors'. " +
"Location change failed.";
- var rootUri = _serverFixture.RootUri;
+ var rootUri = ServerFixture.RootUri;
var baseUri = new Uri(rootUri, "/subdir");
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
Assert.Single(Batches);
@@ -421,7 +271,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
var entry = Assert.Single(Logs, l => l.EventId.Name == "LocationChangeFailed");
Assert.Equal(LogLevel.Error, entry.LogLevel);
- Assert.Matches($"Location change to '{new Uri(_serverFixture.RootUri, "/test")}' in circuit '.*' failed\\.", entry.Message);
+ Assert.Matches($"Location change to '{new Uri(ServerFixture.RootUri, "/test")}' in circuit '.*' failed\\.", entry.Message);
}
[Theory]
@@ -436,7 +286,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
// Arrange
var expectedError = "Unhandled exception in circuit .*";
- var rootUri = _serverFixture.RootUri;
+ var rootUri = ServerFixture.RootUri;
var baseUri = new Uri(rootUri, "/subdir");
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
Assert.Single(Batches);
@@ -467,7 +317,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
// Arrange
var expectedError = "Unhandled exception in circuit .*";
- var rootUri = _serverFixture.RootUri;
+ var rootUri = ServerFixture.RootUri;
var baseUri = new Uri(rootUri, "/subdir");
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
Assert.Single(Batches);
@@ -493,47 +343,5 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Logs,
e => LogLevel.Error == e.LogLevel && Regex.IsMatch(e.Message, expectedError));
}
-
- public void Dispose()
- {
- TestSink.MessageLogged -= LogMessages;
- }
-
- private string Serialize<T>(T browserEventDescriptor) =>
- JsonSerializer.Serialize(browserEventDescriptor, TestJsonSerializerOptionsProvider.Options);
-
- [DebuggerDisplay("{LogLevel.ToString(),nq} - {Message ?? \"null\",nq} - {Exception?.Message,nq}")]
- private class LogMessage
- {
- public LogMessage(LogLevel logLevel, EventId eventId, string message, Exception exception)
- {
- LogLevel = logLevel;
- EventId = eventId;
- Message = message;
- Exception = exception;
- }
-
- public LogLevel LogLevel { get; set; }
- public EventId EventId { get; set; }
- public string Message { get; set; }
- public Exception Exception { get; set; }
-
- public override string ToString()
- {
- return $"{LogLevel}: {EventId} {Message}{(Exception != null ? Environment.NewLine : "")}{Exception}";
- }
- }
-
- private class Batch
- {
- public Batch(int id, byte[] data)
- {
- Id = id;
- Data = data;
- }
-
- public int Id { get; }
- public byte[] Data { get; }
- }
}
}
diff --git a/src/Components/test/E2ETest/ServerExecutionTests/IgnitorTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/IgnitorTest.cs
new file mode 100644
index 0000000000..ed9cd36c68
--- /dev/null
+++ b/src/Components/test/E2ETest/ServerExecutionTests/IgnitorTest.cs
@@ -0,0 +1,131 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Ignitor;
+using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Testing;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.AspNetCore.Components
+{
+ // Base class for Ignitor-based tests.
+ public abstract class IgnitorTest<TFixture> : IClassFixture<TFixture>, IAsyncLifetime
+ where TFixture : ServerFixture
+ {
+ private static readonly TimeSpan DefaultTimeout = Debugger.IsAttached ? TimeSpan.MaxValue : TimeSpan.FromSeconds(30);
+
+ protected IgnitorTest(TFixture serverFixture, ITestOutputHelper output)
+ {
+ ServerFixture = serverFixture;
+ Output = output;
+ }
+
+ protected BlazorClient Client { get; private set; }
+
+ protected ConcurrentQueue<LogMessage> Logs { get; } = new ConcurrentQueue<LogMessage>();
+
+ protected ITestOutputHelper Output { get; }
+
+ protected TFixture ServerFixture { get; }
+
+ protected TimeSpan Timeout { get; set; } = DefaultTimeout;
+
+ private TestSink TestSink { get; set; }
+
+ protected IReadOnlyCollection<CapturedRenderBatch> Batches => Client?.Operations?.Batches;
+
+ protected IReadOnlyCollection<string> DotNetCompletions => Client?.Operations?.DotNetCompletions;
+
+ protected IReadOnlyCollection<string> Errors => Client?.Operations?.Errors;
+
+ protected IReadOnlyCollection<CapturedJSInteropCall> JSInteropCalls => Client?.Operations?.JSInteropCalls;
+
+ // Called to initialize the fixture as part of InitializeAsync.
+ protected virtual void InitializeFixture(TFixture serverFixture)
+ {
+ }
+
+ async Task IAsyncLifetime.InitializeAsync()
+ {
+ Client = new BlazorClient()
+ {
+ CaptureOperations = true,
+ DefaultOperationTimeout = Timeout,
+ };
+ Client.LoggerProvider = new XunitLoggerProvider(Output);
+ Client.FormatError = (error) =>
+ {
+ var logs = string.Join(Environment.NewLine, Logs);
+ return new Exception(error + Environment.NewLine + logs);
+ };
+
+ InitializeFixture(ServerFixture);
+ _ = ServerFixture.RootUri; // This is needed for the side-effects of starting the server.
+
+ if (ServerFixture is WebHostServerFixture hostFixture)
+ {
+ TestSink = hostFixture.Host.Services.GetRequiredService<TestSink>();
+ TestSink.MessageLogged += TestSink_MessageLogged;
+ }
+
+ await InitializeAsync();
+ }
+
+ async Task IAsyncLifetime.DisposeAsync()
+ {
+ if (TestSink != null)
+ {
+ TestSink.MessageLogged -= TestSink_MessageLogged;
+ }
+
+ await DisposeAsync();
+ }
+
+ protected virtual Task InitializeAsync()
+ {
+ return Task.CompletedTask;
+ }
+
+ protected virtual Task DisposeAsync()
+ {
+ return Task.CompletedTask;
+ }
+
+ private void TestSink_MessageLogged(WriteContext context)
+ {
+ var log = new LogMessage(context.LogLevel, context.EventId, context.Message, context.Exception);
+ Logs.Enqueue(log);
+ Output.WriteLine(log.ToString());
+ }
+
+ [DebuggerDisplay("{LogLevel.ToString(),nq} - {Message ?? \"null\",nq} - {Exception?.Message,nq}")]
+ protected sealed class LogMessage
+ {
+ public LogMessage(LogLevel logLevel, EventId eventId, string message, Exception exception)
+ {
+ LogLevel = logLevel;
+ EventId = eventId;
+ Message = message;
+ Exception = exception;
+ }
+
+ public LogLevel LogLevel { get; set; }
+ public EventId EventId { get; set; }
+ public string Message { get; set; }
+ public Exception Exception { get; set; }
+
+ public override string ToString()
+ {
+ return $"{LogLevel}: {EventId} {Message}{(Exception != null ? Environment.NewLine : "")}{Exception}";
+ }
+ }
+ }
+}
diff --git a/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs b/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs
index ff81e5e337..a848669b78 100644
--- a/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs
+++ b/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
@@ -21,55 +20,26 @@ using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
- [Flaky("https://github.com/aspnet/AspNetCore/issues/13086", FlakyOn.All)]
- public class InteropReliabilityTests : IClassFixture<AspNetSiteServerFixture>, IDisposable
+ public class InteropReliabilityTests : IgnitorTest<AspNetSiteServerFixture>
{
- private static readonly TimeSpan DefaultLatencyTimeout = TimeSpan.FromSeconds(30);
- private readonly AspNetSiteServerFixture _serverFixture;
-
public InteropReliabilityTests(AspNetSiteServerFixture serverFixture, ITestOutputHelper output)
+ : base(serverFixture, output)
{
- _serverFixture = serverFixture;
- Output = output;
-
- serverFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
- CreateDefaultConfiguration();
}
- public BlazorClient Client { get; set; }
- public ITestOutputHelper Output { get; set; }
- private IList<Batch> Batches { get; set; } = new List<Batch>();
- private List<DotNetCompletion> DotNetCompletions = new List<DotNetCompletion>();
- private List<JSInteropCall> JSInteropCalls = new List<JSInteropCall>();
- private IList<string> Errors { get; set; } = new List<string>();
- private ConcurrentQueue<LogMessage> Logs { get; set; } = new ConcurrentQueue<LogMessage>();
-
- public TestSink TestSink { get; set; }
-
- private void CreateDefaultConfiguration()
+ protected override void InitializeFixture(AspNetSiteServerFixture serverFixture)
{
- Client = new BlazorClient() { DefaultLatencyTimeout = DefaultLatencyTimeout };
- Client.RenderBatchReceived += (id, data) => Batches.Add(new Batch(id, data));
- Client.DotNetInteropCompletion += (method) => DotNetCompletions.Add(new DotNetCompletion(method));
- Client.JSInterop += (asyncHandle, identifier, argsJson) => JSInteropCalls.Add(new JSInteropCall(asyncHandle, identifier, argsJson));
- Client.OnCircuitError += (error) => Errors.Add(error);
- Client.LoggerProvider = new XunitLoggerProvider(Output);
- Client.FormatError = (error) =>
- {
- var logs = string.Join(Environment.NewLine, Logs);
- return new Exception(error + Environment.NewLine + logs);
- };
-
- _ = _serverFixture.RootUri; // this is needed for the side-effects of getting the URI.
- TestSink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
- TestSink.MessageLogged += LogMessages;
+ serverFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
}
- private void LogMessages(WriteContext context)
+ protected async override Task InitializeAsync()
{
- var log = new LogMessage(context.LogLevel, context.Message, context.Exception);
- Logs.Enqueue(log);
- Output.WriteLine(log.ToString());
+ var rootUri = ServerFixture.RootUri;
+ Assert.True(await Client.ConnectAsync(new Uri(rootUri, "/subdir")), "Couldn't connect to the app");
+ Assert.Single(Batches);
+
+ await Client.SelectAsync("test-selector-select", "BasicTestApp.ReliabilityComponent");
+ Assert.Equal(2, Batches.Count);
}
[Fact]
@@ -79,7 +49,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
var expectedError = "[\"1\"," +
"false," +
"\"There was an exception invoking \\u0027WriteAllText\\u0027 on assembly \\u0027System.IO.FileSystem\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
- await GoToTestComponent(Batches);
// Act
await Client.InvokeDotNetMethod(
@@ -90,7 +59,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
JsonSerializer.Serialize(new[] { ".\\log.txt", "log" }));
// Assert
- Assert.Single(DotNetCompletions, c => c.Message == expectedError);
+ Assert.Single(DotNetCompletions, c => c == expectedError);
await ValidateClientKeepsWorking(Client, Batches);
}
@@ -102,8 +71,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"false," +
"\"There was an exception invoking \\u0027MadeUpMethod\\u0027 on assembly \\u0027BasicTestApp\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
- await GoToTestComponent(Batches);
-
// Act
await Client.InvokeDotNetMethod(
"1",
@@ -113,7 +80,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
JsonSerializer.Serialize(new[] { ".\\log.txt", "log" }));
// Assert
- Assert.Single(DotNetCompletions, c => c.Message == expectedError);
+ Assert.Single(DotNetCompletions, c => c == expectedError);
await ValidateClientKeepsWorking(Client, Batches);
}
@@ -125,18 +92,16 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"false," +
"\"There was an exception invoking \\u0027NotifyLocationChanged\\u0027 on assembly \\u0027Microsoft.AspNetCore.Components.Server\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
- await GoToTestComponent(Batches);
-
// Act
await Client.InvokeDotNetMethod(
"1",
"Microsoft.AspNetCore.Components.Server",
"NotifyLocationChanged",
null,
- JsonSerializer.Serialize(new[] { _serverFixture.RootUri }));
+ JsonSerializer.Serialize(new[] { ServerFixture.RootUri }));
// Assert
- Assert.Single(DotNetCompletions, c => c.Message == expectedError);
+ Assert.Single(DotNetCompletions, c => c == expectedError);
await ValidateClientKeepsWorking(Client, Batches);
}
@@ -148,18 +113,16 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"false," +
"\"There was an exception invoking \\u0027NotifyLocationChanged\\u0027 on assembly \\u0027\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
- await GoToTestComponent(Batches);
-
// Act
await Client.InvokeDotNetMethod(
"1",
"",
"NotifyLocationChanged",
null,
- JsonSerializer.Serialize(new object[] { _serverFixture.RootUri + "counter", false }));
+ JsonSerializer.Serialize(new object[] { ServerFixture.RootUri + "counter", false }));
// Assert
- Assert.Single(DotNetCompletions, c => c.Message == expectedError);
+ Assert.Single(DotNetCompletions, c => c == expectedError);
await ValidateClientKeepsWorking(Client, Batches);
}
@@ -171,18 +134,16 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"false," +
"\"There was an exception invoking \\u0027\\u0027 on assembly \\u0027Microsoft.AspNetCore.Components.Server\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
- await GoToTestComponent(Batches);
-
// Act
await Client.InvokeDotNetMethod(
"1",
"Microsoft.AspNetCore.Components.Server",
"",
null,
- JsonSerializer.Serialize(new object[] { _serverFixture.RootUri + "counter", false }));
+ JsonSerializer.Serialize(new object[] { ServerFixture.RootUri + "counter", false }));
// Assert
- Assert.Single(DotNetCompletions, c => c.Message == expectedError);
+ Assert.Single(DotNetCompletions, c => c == expectedError);
await ValidateClientKeepsWorking(Client, Batches);
}
@@ -196,8 +157,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"false," +
"\"There was an exception invoking \\u0027Reverse\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
- await GoToTestComponent(Batches);
-
// Act
await Client.InvokeDotNetMethod(
"1",
@@ -206,7 +165,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
null,
JsonSerializer.Serialize(Array.Empty<object>()));
- Assert.Single(DotNetCompletions, c => c.Message == expectedDotNetObjectRef);
+ Assert.Single(DotNetCompletions, c => c == expectedDotNetObjectRef);
await Client.InvokeDotNetMethod(
"1",
@@ -216,7 +175,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
JsonSerializer.Serialize(Array.Empty<object>()));
// Assert
- Assert.Single(DotNetCompletions, c => c.Message == "[\"1\",true,\"tnatropmI\"]");
+ Assert.Single(DotNetCompletions, c => c == "[\"1\",true,\"tnatropmI\"]");
await Client.InvokeDotNetMethod(
"1",
@@ -225,7 +184,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
3, // non existing ref
JsonSerializer.Serialize(Array.Empty<object>()));
- Assert.Single(DotNetCompletions, c => c.Message == expectedError);
+ Assert.Single(DotNetCompletions, c => c == expectedError);
await ValidateClientKeepsWorking(Client, Batches);
}
@@ -238,8 +197,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"false," +
"\"There was an exception invoking \\u0027ReceiveTrivial\\u0027 on assembly \\u0027BasicTestApp\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
- await GoToTestComponent(Batches);
-
await Client.InvokeDotNetMethod(
"1",
"BasicTestApp",
@@ -247,7 +204,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
null,
JsonSerializer.Serialize(Array.Empty<object>()));
- Assert.Single(DotNetCompletions, c => c.Message == expectedImportantDotNetObjectRef);
+ Assert.Single(DotNetCompletions, c => c == expectedImportantDotNetObjectRef);
// Act
await Client.InvokeDotNetMethod(
@@ -258,19 +215,16 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
JsonSerializer.Serialize(new object[] { new { __dotNetObject = 1 } }));
// Assert
- Assert.Single(DotNetCompletions, c => c.Message == expectedError);
+ Assert.Single(DotNetCompletions, c => c == expectedError);
await ValidateClientKeepsWorking(Client, Batches);
}
[Fact]
- [Flaky("https://github.com/aspnet/AspNetCore/issues/13086", FlakyOn.AzP.Windows)]
public async Task ContinuesWorkingAfterInvalidAsyncReturnCallback()
{
// Arrange
var expectedError = "An exception occurred executing JS interop: The JSON value could not be converted to System.Int32. Path: $ | LineNumber: 0 | BytePositionInLine: 3.. See InnerException for more details.";
- await GoToTestComponent(Batches);
-
// Act
await Client.ClickAsync("triggerjsinterop-malformed");
@@ -278,11 +232,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Assert.NotEqual(default, call);
var id = call.AsyncHandle;
- await Client.HubConnection.InvokeAsync(
- "EndInvokeJSFromDotNet",
- id,
- true,
- $"[{id}, true, \"{{\"]");
+ await Client.ExpectRenderBatch(async () =>
+ {
+ await Client.HubConnection.InvokeAsync(
+ "EndInvokeJSFromDotNet",
+ id,
+ true,
+ $"[{id}, true, \"{{\"]");
+ });
var text = Assert.Single(
Client.FindElementById("errormessage-malformed").Children.OfType<TextNode>(),
@@ -295,10 +252,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
public async Task JSInteropCompletionSuccess()
{
// Arrange
- await GoToTestComponent(Batches);
- var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
- var logEvents = new List<(LogLevel logLevel, string)>();
- sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name));
// Act
await Client.ClickAsync("triggerjsinterop-success");
@@ -307,27 +260,27 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Assert.NotEqual(default, call);
var id = call.AsyncHandle;
- await Client.HubConnection.InvokeAsync(
- "EndInvokeJSFromDotNet",
- id++,
- true,
- $"[{id}, true, null]");
+ await Client.ExpectRenderBatch(async () =>
+ {
+ await Client.HubConnection.InvokeAsync(
+ "EndInvokeJSFromDotNet",
+ id,
+ true,
+ $"[{id}, true, null]");
+ });
Assert.Single(
Client.FindElementById("errormessage-success").Children.OfType<TextNode>(),
e => "" == e.TextContent);
- Assert.Contains((LogLevel.Debug, "EndInvokeJSSucceeded"), logEvents);
+ var entry = Assert.Single(Logs, l => l.EventId.Name == "EndInvokeJSSucceeded");
+ Assert.Equal(LogLevel.Debug, entry.LogLevel);
}
[Fact]
public async Task JSInteropThrowsInUserCode()
{
// Arrange
- await GoToTestComponent(Batches);
- var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
- var logEvents = new List<(LogLevel logLevel, string)>();
- sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name));
// Act
await Client.ClickAsync("triggerjsinterop-failure");
@@ -349,9 +302,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Client.FindElementById("errormessage-failure").Children.OfType<TextNode>(),
e => "There was an error invoking sendFailureCallbackReturn" == e.TextContent);
- Assert.Contains((LogLevel.Debug, "EndInvokeJSFailed"), logEvents);
+ var entry = Assert.Single(Logs, l => l.EventId.Name == "EndInvokeJSFailed");
+ Assert.Equal(LogLevel.Debug, entry.LogLevel);
- Assert.DoesNotContain(logEvents, m => m.logLevel > LogLevel.Information);
+ Assert.DoesNotContain(Logs, m => m.LogLevel > LogLevel.Information);
await ValidateClientKeepsWorking(Client, Batches);
}
@@ -360,10 +314,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
public async Task MalformedJSInteropCallbackDisposesCircuit()
{
// Arrange
- await GoToTestComponent(Batches);
- var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
- var logEvents = new List<(LogLevel logLevel, string)>();
- sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name));
// Act
await Client.ClickAsync("triggerjsinterop-malformed");
@@ -386,7 +336,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Client.FindElementById("errormessage-malformed").Children.OfType<TextNode>(),
e => "" == e.TextContent);
- Assert.Contains((LogLevel.Debug, "EndInvokeDispatchException"), logEvents);
+ var entry = Assert.Single(Logs, l => l.EventId.Name == "EndInvokeDispatchException");
+ Assert.Equal(LogLevel.Debug, entry.LogLevel);
await Client.ExpectCircuitErrorAndDisconnect(async () =>
{
@@ -402,8 +353,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"false," +
"\"There was an exception invoking \\u0027NotifyLocationChanged\\u0027 on assembly \\u0027Microsoft.AspNetCore.Components.Server\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
- await GoToTestComponent(Batches);
-
// Act
await Client.InvokeDotNetMethod(
"1",
@@ -413,7 +362,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"[ \"invalidPayload\"}");
// Assert
- Assert.Single(DotNetCompletions, c => c.Message == expectedError);
+ Assert.Single(DotNetCompletions, c => c == expectedError);
await ValidateClientKeepsWorking(Client, Batches);
}
@@ -425,8 +374,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"false," +
"\"There was an exception invoking \\u0027ReceiveTrivial\\u0027 on assembly \\u0027BasicTestApp\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
- await GoToTestComponent(Batches);
-
// Act
await Client.InvokeDotNetMethod(
"1",
@@ -436,7 +383,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"[ { \"data\": {\"}} ]");
// Assert
- Assert.Single(DotNetCompletions, c => c.Message == expectedError);
+ Assert.Single(DotNetCompletions, c => c == expectedError);
await ValidateClientKeepsWorking(Client, Batches);
}
@@ -444,10 +391,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
public async Task DispatchingEventsWithInvalidPayloadsShutsDownCircuitGracefully()
{
// Arrange
- await GoToTestComponent(Batches);
- var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
- var logEvents = new List<(LogLevel logLevel, string)>();
- sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name));
// Act
await Client.ExpectCircuitError(async () =>
@@ -458,9 +401,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
null);
});
- Assert.Contains(
- (LogLevel.Debug, "DispatchEventFailedToParseEventData"),
- logEvents);
+ var entry = Assert.Single(Logs, l => l.EventId.Name == "DispatchEventFailedToParseEventData");
+ Assert.Equal(LogLevel.Debug, entry.LogLevel);
// Taking any other action will fail because the circuit is disposed.
await Client.ExpectCircuitErrorAndDisconnect(async () =>
@@ -473,10 +415,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
public async Task DispatchingEventsWithInvalidEventDescriptor()
{
// Arrange
- await GoToTestComponent(Batches);
- var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
- var logEvents = new List<(LogLevel logLevel, string)>();
- sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name));
// Act
await Client.ExpectCircuitError(async () =>
@@ -487,9 +425,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"{}");
});
- Assert.Contains(
- (LogLevel.Debug, "DispatchEventFailedToParseEventData"),
- logEvents);
+ var entry = Assert.Single(Logs, l => l.EventId.Name == "DispatchEventFailedToParseEventData");
+ Assert.Equal(LogLevel.Debug, entry.LogLevel);
// Taking any other action will fail because the circuit is disposed.
await Client.ExpectCircuitErrorAndDisconnect(async () =>
@@ -502,10 +439,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
public async Task DispatchingEventsWithInvalidEventArgs()
{
// Arrange
- await GoToTestComponent(Batches);
- var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
- var logEvents = new List<(LogLevel logLevel, string eventIdName, Exception exception)>();
- sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name, wc.Exception));
// Act
var browserDescriptor = new WebEventDescriptor()
@@ -524,9 +457,9 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
});
Assert.Contains(
- logEvents,
- e => e.eventIdName == "DispatchEventFailedToParseEventData" && e.logLevel == LogLevel.Debug &&
- e.exception.Message == "There was an error parsing the event arguments. EventId: '6'.");
+ Logs,
+ e => e.EventId.Name == "DispatchEventFailedToParseEventData" && e.LogLevel == LogLevel.Debug &&
+ e.Exception.Message == "There was an error parsing the event arguments. EventId: '6'.");
// Taking any other action will fail because the circuit is disposed.
await Client.ExpectCircuitErrorAndDisconnect(async () =>
@@ -539,10 +472,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
public async Task DispatchingEventsWithInvalidEventHandlerId()
{
// Arrange
- await GoToTestComponent(Batches);
- var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
- var logEvents = new List<(LogLevel logLevel, string eventIdName, Exception exception)>();
- sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name, wc.Exception));
// Act
var mouseEventArgs = new MouseEventArgs()
@@ -566,9 +495,9 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
});
Assert.Contains(
- logEvents,
- e => e.eventIdName == "DispatchEventFailedToDispatchEvent" && e.logLevel == LogLevel.Debug &&
- e.exception is ArgumentException ae && ae.Message.Contains("There is no event handler associated with this event. EventId: '1'."));
+ Logs,
+ e => e.EventId.Name == "DispatchEventFailedToDispatchEvent" && e.LogLevel == LogLevel.Debug &&
+ e.Exception is ArgumentException ae && ae.Message.Contains("There is no event handler associated with this event. EventId: '1'."));
// Taking any other action will fail because the circuit is disposed.
await Client.ExpectCircuitErrorAndDisconnect(async () =>
@@ -581,21 +510,18 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
public async Task EventHandlerThrowsSyncExceptionTerminatesTheCircuit()
{
// Arrange
- await GoToTestComponent(Batches);
- var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
- var logEvents = new List<(LogLevel logLevel, string eventIdName, Exception exception)>();
- sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name, wc.Exception));
// Act
- await Client.ClickAsync("event-handler-throw-sync", expectRenderBatch: false);
-
- await Task.Delay(1000);
+ await Client.ExpectCircuitError(async () =>
+ {
+ await Client.ClickAsync("event-handler-throw-sync", expectRenderBatch: false);
+ });
Assert.Contains(
- logEvents,
- e => LogLevel.Error == e.logLevel &&
- "CircuitUnhandledException" == e.eventIdName &&
- "Handler threw an exception" == e.exception.Message);
+ Logs,
+ e => LogLevel.Error == e.LogLevel &&
+ "CircuitUnhandledException" == e.EventId.Name &&
+ "Handler threw an exception" == e.Exception.Message);
// Now if you try to click again, you will get *forcibly* disconnected for trying to talk to
// a circuit that's gone.
@@ -605,7 +531,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
});
}
- private Task ValidateClientKeepsWorking(BlazorClient Client, IList<Batch> batches) =>
+ private Task ValidateClientKeepsWorking(BlazorClient Client, IReadOnlyCollection<CapturedRenderBatch> batches) =>
ValidateClientKeepsWorking(Client, () => batches.Count);
private async Task ValidateClientKeepsWorking(BlazorClient Client, Func<int> countAccessor)
@@ -615,75 +541,5 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Assert.Equal(currentBatches + 1, countAccessor());
}
-
- private async Task GoToTestComponent(IList<Batch> batches)
- {
- var rootUri = _serverFixture.RootUri;
- Assert.True(await Client.ConnectAsync(new Uri(rootUri, "/subdir")), "Couldn't connect to the app");
- Assert.Single(batches);
-
- await Client.SelectAsync("test-selector-select", "BasicTestApp.ReliabilityComponent");
- Assert.Equal(2, batches.Count);
- }
-
- public void Dispose()
- {
- TestSink.MessageLogged -= LogMessages;
- }
-
- private class LogMessage
- {
- public LogMessage(LogLevel logLevel, string message, Exception exception)
- {
- LogLevel = logLevel;
- Message = message;
- Exception = exception;
- }
-
- public LogLevel LogLevel { get; set; }
- public string Message { get; set; }
- public Exception Exception { get; set; }
-
- public override string ToString()
- {
- return $"{LogLevel}: {Message}{(Exception != null ? Environment.NewLine : "")}{Exception}";
- }
- }
-
- private class Batch
- {
- public Batch(int id, byte[] data)
- {
- Id = id;
- Data = data;
- }
-
- public int Id { get; }
- public byte[] Data { get; }
- }
-
- private class DotNetCompletion
- {
- public DotNetCompletion(string message)
- {
- Message = message;
- }
-
- public string Message { get; }
- }
-
- private class JSInteropCall
- {
- public JSInteropCall(int asyncHandle, string identifier, string argsJson)
- {
- AsyncHandle = asyncHandle;
- Identifier = identifier;
- ArgsJson = argsJson;
- }
-
- public int AsyncHandle { get; }
- public string Identifier { get; }
- public string ArgsJson { get; }
- }
}
}
diff --git a/src/Components/test/E2ETest/ServerExecutionTests/RemoteRendererBufferLimitTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/RemoteRendererBufferLimitTest.cs
index 360adc4b95..d77d2e6572 100644
--- a/src/Components/test/E2ETest/ServerExecutionTests/RemoteRendererBufferLimitTest.cs
+++ b/src/Components/test/E2ETest/ServerExecutionTests/RemoteRendererBufferLimitTest.cs
@@ -2,55 +2,33 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Ignitor;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Testing;
using Xunit;
+using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{
- public class RemoteRendererBufferLimitTest : IClassFixture<AspNetSiteServerFixture>, IDisposable
+ public class RemoteRendererBufferLimitTest : IgnitorTest<AspNetSiteServerFixture>
{
- private static readonly TimeSpan DefaultLatencyTimeout = Debugger.IsAttached ? TimeSpan.FromSeconds(60) : TimeSpan.FromMilliseconds(500);
-
- private AspNetSiteServerFixture _serverFixture;
+ public RemoteRendererBufferLimitTest(AspNetSiteServerFixture serverFixture, ITestOutputHelper output)
+ : base(serverFixture, output)
+ {
+ }
- public RemoteRendererBufferLimitTest(AspNetSiteServerFixture serverFixture)
+ protected override void InitializeFixture(AspNetSiteServerFixture serverFixture)
{
serverFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
- _serverFixture = serverFixture;
-
- // Needed here for side-effects
- _ = _serverFixture.RootUri;
-
- Client = new BlazorClient() { DefaultLatencyTimeout = DefaultLatencyTimeout };
- Client.RenderBatchReceived += (id, data) => Batches.Add(new Batch(id, data));
-
- Sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
- Sink.MessageLogged += LogMessages;
}
- public BlazorClient Client { get; set; }
-
- private IList<Batch> Batches { get; set; } = new List<Batch>();
-
- // We use a stack so that we can search the logs in reverse order
- private ConcurrentStack<LogMessage> Logs { get; set; } = new ConcurrentStack<LogMessage>();
-
- public TestSink Sink { get; private set; }
-
[Fact]
public async Task DispatchedEventsWillKeepBeingProcessed_ButUpdatedWillBeDelayedUntilARenderIsAcknowledged()
{
// Arrange
- var baseUri = new Uri(_serverFixture.RootUri, "/subdir");
+ var baseUri = new Uri(ServerFixture.RootUri, "/subdir");
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
Assert.Single(Batches);
@@ -75,48 +53,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Client.ConfirmRenderBatch = true;
// This will resume the render batches.
- await Client.ExpectRenderBatch(() => Client.ConfirmBatch(Batches[^1].Id));
+ await Client.ExpectRenderBatch(() => Client.ConfirmBatch(Batches.Last().Id));
// Assert
Assert.Equal("12", ((TextNode)Client.FindElementById("the-count").Children.Single()).TextContent);
Assert.Equal(fullCount + 1, Batches.Count);
}
-
- private void LogMessages(WriteContext context) => Logs.Push(new LogMessage(context.LogLevel, context.Message, context.Exception));
-
- [DebuggerDisplay("{Message,nq}")]
- private class LogMessage
- {
- public LogMessage(LogLevel logLevel, string message, Exception exception)
- {
- LogLevel = logLevel;
- Message = message;
- Exception = exception;
- }
-
- public LogLevel LogLevel { get; set; }
- public string Message { get; set; }
- public Exception Exception { get; set; }
- }
-
- private class Batch
- {
- public Batch(int id, byte[] data)
- {
- Id = id;
- Data = data;
- }
-
- public int Id { get; }
- public byte[] Data { get; }
- }
-
- public void Dispose()
- {
- if (Sink != null)
- {
- Sink.MessageLogged -= LogMessages;
- }
- }
}
}
diff --git a/src/Components/test/testassets/BasicTestApp/ServerReliability/ReliabilityComponent.razor b/src/Components/test/testassets/BasicTestApp/ServerReliability/ReliabilityComponent.razor
index 8557fa95c0..13256fb7bc 100644
--- a/src/Components/test/testassets/BasicTestApp/ServerReliability/ReliabilityComponent.razor
+++ b/src/Components/test/testassets/BasicTestApp/ServerReliability/ReliabilityComponent.razor
@@ -77,7 +77,7 @@
{
int currentCount = 0;
string errorMalformed = "";
- string errorSuccess = "";
+ string errorSuccess = "(this will be cleared)";
string errorFailure = "";
bool showConstructorThrow;
bool showAttachThrow;
@@ -116,6 +116,10 @@
var result = await JSRuntime.InvokeAsync<object>(
"sendSuccessCallbackReturn",
Array.Empty<object>());
+
+ // Make sure we trigger a render when the interop call completes.
+ // The test uses a render to synchronize.
+ errorSuccess = "";
}
catch (Exception e)
{
diff --git a/src/Components/test/testassets/Ignitor/BlazorClient.cs b/src/Components/test/testassets/Ignitor/BlazorClient.cs
index 4985af2207..3f96604574 100644
--- a/src/Components/test/testassets/Ignitor/BlazorClient.cs
+++ b/src/Components/test/testassets/Ignitor/BlazorClient.cs
@@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Diagnostics;
-using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
@@ -33,8 +31,20 @@ namespace Ignitor
});
}
- public TimeSpan? DefaultLatencyTimeout { get; set; } = TimeSpan.FromMilliseconds(500);
- public TimeSpan? DefaultConnectTimeout { get; set; } = TimeSpan.FromSeconds(10);
+ public TimeSpan? DefaultConnectionTimeout { get; set; } = TimeSpan.FromSeconds(10);
+ public TimeSpan? DefaultOperationTimeout { get; set; } = TimeSpan.FromMilliseconds(500);
+
+ /// <summary>
+ /// Gets or sets a value that determines whether the client will capture data such
+ /// as render batches, interop calls, and errors for later inspection.
+ /// </summary>
+ public bool CaptureOperations { get; set; }
+
+ /// <summary>
+ /// Gets the collections of operation results that are captured when <see cref="CaptureOperations"/>
+ /// is true.
+ /// </summary>
+ public Operations Operations { get; private set; }
public Func<string, Exception> FormatError { get; set; }
@@ -44,23 +54,23 @@ namespace Ignitor
private TaskCompletionSource<object> TaskCompletionSource { get; }
- private CancellableOperation NextBatchReceived { get; set; }
+ private CancellableOperation<CapturedRenderBatch> NextBatchReceived { get; set; }
- private CancellableOperation NextErrorReceived { get; set; }
+ private CancellableOperation<string> NextErrorReceived { get; set; }
- private CancellableOperation NextDisconnect { get; set; }
+ private CancellableOperation<Exception> NextDisconnect { get; set; }
- private CancellableOperation NextJSInteropReceived { get; set; }
+ private CancellableOperation<CapturedJSInteropCall> NextJSInteropReceived { get; set; }
- private CancellableOperation NextDotNetInteropCompletionReceived { get; set; }
+ private CancellableOperation<string> NextDotNetInteropCompletionReceived { get; set; }
public ILoggerProvider LoggerProvider { get; set; }
public bool ConfirmRenderBatch { get; set; } = true;
- public event Action<int, string, string> JSInterop;
+ public event Action<CapturedJSInteropCall> JSInterop;
- public event Action<int, byte[]> RenderBatchReceived;
+ public event Action<CapturedRenderBatch> RenderBatchReceived;
public event Action<string> DotNetInteropCompletion;
@@ -70,66 +80,66 @@ namespace Ignitor
public ElementHive Hive { get; set; } = new ElementHive();
- public bool ImplicitWait => DefaultLatencyTimeout != null;
+ public bool ImplicitWait => DefaultOperationTimeout != null;
public HubConnection HubConnection { get; set; }
- public Task PrepareForNextBatch(TimeSpan? timeout)
+ public Task<CapturedRenderBatch> PrepareForNextBatch(TimeSpan? timeout)
{
if (NextBatchReceived?.Completion != null)
{
throw new InvalidOperationException("Invalid state previous task not completed");
}
- NextBatchReceived = new CancellableOperation(timeout);
+ NextBatchReceived = new CancellableOperation<CapturedRenderBatch>(timeout);
return NextBatchReceived.Completion.Task;
}
- public Task PrepareForNextJSInterop(TimeSpan? timeout)
+ public Task<CapturedJSInteropCall> PrepareForNextJSInterop(TimeSpan? timeout)
{
if (NextJSInteropReceived?.Completion != null)
{
throw new InvalidOperationException("Invalid state previous task not completed");
}
- NextJSInteropReceived = new CancellableOperation(timeout);
+ NextJSInteropReceived = new CancellableOperation<CapturedJSInteropCall>(timeout);
return NextJSInteropReceived.Completion.Task;
}
- public Task PrepareForNextDotNetInterop(TimeSpan? timeout)
+ public Task<string> PrepareForNextDotNetInterop(TimeSpan? timeout)
{
if (NextDotNetInteropCompletionReceived?.Completion != null)
{
throw new InvalidOperationException("Invalid state previous task not completed");
}
- NextDotNetInteropCompletionReceived = new CancellableOperation(timeout);
+ NextDotNetInteropCompletionReceived = new CancellableOperation<string>(timeout);
return NextDotNetInteropCompletionReceived.Completion.Task;
}
- public Task PrepareForNextCircuitError(TimeSpan? timeout)
+ public Task<string> PrepareForNextCircuitError(TimeSpan? timeout)
{
if (NextErrorReceived?.Completion != null)
{
throw new InvalidOperationException("Invalid state previous task not completed");
}
- NextErrorReceived = new CancellableOperation(timeout);
+ NextErrorReceived = new CancellableOperation<string>(timeout);
return NextErrorReceived.Completion.Task;
}
- public Task PrepareForNextDisconnect(TimeSpan? timeout)
+ public Task<Exception> PrepareForNextDisconnect(TimeSpan? timeout)
{
if (NextDisconnect?.Completion != null)
{
throw new InvalidOperationException("Invalid state previous task not completed");
}
- NextDisconnect = new CancellableOperation(timeout);
+ NextDisconnect = new CancellableOperation<Exception>(timeout);
return NextDisconnect.Completion.Task;
}
@@ -160,115 +170,162 @@ namespace Ignitor
return ExpectRenderBatch(() => elementNode.SelectAsync(HubConnection, value));
}
- public async Task ExpectRenderBatch(Func<Task> action, TimeSpan? timeout = null)
+ public async Task<CapturedRenderBatch> ExpectRenderBatch(Func<Task> action, TimeSpan? timeout = null)
{
var task = WaitForRenderBatch(timeout);
await action();
- await task;
+ return await task;
}
- public async Task ExpectJSInterop(Func<Task> action, TimeSpan? timeout = null)
+ public async Task<CapturedJSInteropCall> ExpectJSInterop(Func<Task> action, TimeSpan? timeout = null)
{
var task = WaitForJSInterop(timeout);
await action();
- await task;
+ return await task;
}
- public async Task ExpectDotNetInterop(Func<Task> action, TimeSpan? timeout = null)
+ public async Task<string> ExpectDotNetInterop(Func<Task> action, TimeSpan? timeout = null)
{
var task = WaitForDotNetInterop(timeout);
await action();
- await task;
+ return await task;
}
- public async Task ExpectCircuitError(Func<Task> action, TimeSpan? timeout = null)
+ public async Task<string> ExpectCircuitError(Func<Task> action, TimeSpan? timeout = null)
{
var task = WaitForCircuitError(timeout);
await action();
- await task;
+ return await task;
+ }
+
+ public async Task<Exception> ExpectDisconnect(Func<Task> action, TimeSpan? timeout = null)
+ {
+ var task = WaitForDisconnect(timeout);
+ await action();
+ return await task;
}
- public async Task ExpectCircuitErrorAndDisconnect(Func<Task> action, TimeSpan? timeout = null)
+ public async Task<(string error, Exception exception)> ExpectCircuitErrorAndDisconnect(Func<Task> action, TimeSpan? timeout = null)
{
+ string error = null;
+
// NOTE: timeout is used for each operation individually.
- await ExpectDisconnect(async () =>
+ var exception = await ExpectDisconnect(async () =>
{
- await ExpectCircuitError(action, timeout);
+ error = await ExpectCircuitError(action, timeout);
}, timeout);
- }
- public async Task ExpectDisconnect(Func<Task> action, TimeSpan? timeout = null)
- {
- var task = WaitForDisconnect(timeout);
- await action();
- await task;
+ return (error, exception);
}
- private Task WaitForRenderBatch(TimeSpan? timeout = null)
+ private async Task<CapturedRenderBatch> WaitForRenderBatch(TimeSpan? timeout = null)
{
if (ImplicitWait)
{
- if (DefaultLatencyTimeout == null && timeout == null)
+ if (DefaultOperationTimeout == null && timeout == null)
{
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
}
- return PrepareForNextBatch(timeout ?? DefaultLatencyTimeout);
+ try
+ {
+ return await PrepareForNextBatch(timeout ?? DefaultOperationTimeout);
+ }
+ catch (OperationCanceledException)
+ {
+ throw FormatError("Timed out while waiting for batch.");
+ }
}
- return Task.CompletedTask;
+ return null;
}
- private async Task WaitForJSInterop(TimeSpan? timeout = null)
+ private async Task<CapturedJSInteropCall> WaitForJSInterop(TimeSpan? timeout = null)
{
if (ImplicitWait)
{
- if (DefaultLatencyTimeout == null && timeout == null)
+ if (DefaultOperationTimeout == null && timeout == null)
{
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
}
- await PrepareForNextJSInterop(timeout ?? DefaultLatencyTimeout);
+ try
+ {
+ return await PrepareForNextJSInterop(timeout ?? DefaultOperationTimeout);
+ }
+ catch (OperationCanceledException)
+ {
+ throw FormatError("Timed out while waiting for JS Interop.");
+ }
}
+
+ return null;
}
- private async Task WaitForDotNetInterop(TimeSpan? timeout = null)
+ private async Task<string> WaitForDotNetInterop(TimeSpan? timeout = null)
{
if (ImplicitWait)
{
- if (DefaultLatencyTimeout == null && timeout == null)
+ if (DefaultOperationTimeout == null && timeout == null)
{
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
}
- await PrepareForNextDotNetInterop(timeout ?? DefaultLatencyTimeout);
+ try
+ {
+ return await PrepareForNextDotNetInterop(timeout ?? DefaultOperationTimeout);
+ }
+ catch (OperationCanceledException)
+ {
+ throw FormatError("Timed out while waiting for .NET interop.");
+ }
}
+
+ return null;
}
- private async Task WaitForCircuitError(TimeSpan? timeout = null)
+ private async Task<string> WaitForCircuitError(TimeSpan? timeout = null)
{
if (ImplicitWait)
{
- if (DefaultLatencyTimeout == null && timeout == null)
+ if (DefaultOperationTimeout == null && timeout == null)
{
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
}
- await PrepareForNextCircuitError(timeout ?? DefaultLatencyTimeout);
+ try
+ {
+ return await PrepareForNextCircuitError(timeout ?? DefaultOperationTimeout);
+ }
+ catch (OperationCanceledException)
+ {
+ throw FormatError("Timed out while waiting for circuit error.");
+ }
}
+
+ return null;
}
- private async Task WaitForDisconnect(TimeSpan? timeout = null)
+ private async Task<Exception> WaitForDisconnect(TimeSpan? timeout = null)
{
if (ImplicitWait)
{
- if (DefaultLatencyTimeout == null && timeout == null)
+ if (DefaultOperationTimeout == null && timeout == null)
{
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
}
+
+ try
+ {
+ return await PrepareForNextDisconnect(timeout ?? DefaultOperationTimeout);
+ }
+ catch (OperationCanceledException)
+ {
+ throw FormatError("Timed out while waiting for disconnect.");
+ }
}
- await PrepareForNextDisconnect(timeout ?? DefaultLatencyTimeout);
+ return null;
}
public async Task<bool> ConnectAsync(Uri uri, bool connectAutomatically = true)
@@ -294,6 +351,11 @@ namespace Ignitor
HubConnection.On<string>("JS.Error", OnError);
HubConnection.Closed += OnClosedAsync;
+ if (CaptureOperations)
+ {
+ Operations = new Operations();
+ }
+
if (!connectAutomatically)
{
return true;
@@ -302,35 +364,41 @@ namespace Ignitor
var descriptors = await GetPrerenderDescriptors(uri);
await ExpectRenderBatch(
async () => CircuitId = await HubConnection.InvokeAsync<string>("StartCircuit", uri, uri, descriptors),
- DefaultConnectTimeout);
+ DefaultConnectionTimeout);
return CircuitId != null;
}
- private void OnEndInvokeDotNet(string completion)
+ private void OnEndInvokeDotNet(string message)
{
- DotNetInteropCompletion?.Invoke(completion);
+ Operations?.DotNetCompletions.Enqueue(message);
+ DotNetInteropCompletion?.Invoke(message);
NextDotNetInteropCompletionReceived?.Completion?.TrySetResult(null);
}
private void OnBeginInvokeJS(int asyncHandle, string identifier, string argsJson)
{
- JSInterop?.Invoke(asyncHandle, identifier, argsJson);
+ var call = new CapturedJSInteropCall(asyncHandle, identifier, argsJson);
+ Operations?.JSInteropCalls.Enqueue(call);
+ JSInterop?.Invoke(call);
NextJSInteropReceived?.Completion?.TrySetResult(null);
}
- private void OnRenderBatch(int batchId, byte[] batchData)
+ private void OnRenderBatch(int id, byte[] data)
{
- RenderBatchReceived?.Invoke(batchId, batchData);
+ var capturedBatch = new CapturedRenderBatch(id, data);
+
+ Operations?.Batches.Enqueue(capturedBatch);
+ RenderBatchReceived?.Invoke(capturedBatch);
- var batch = RenderBatchReader.Read(batchData);
+ var batch = RenderBatchReader.Read(data);
Hive.Update(batch);
if (ConfirmRenderBatch)
{
- _ = ConfirmBatch(batchId);
+ _ = ConfirmBatch(id);
}
NextBatchReceived?.Completion?.TrySetResult(null);
@@ -343,8 +411,12 @@ namespace Ignitor
private void OnError(string error)
{
+ Operations?.Errors.Enqueue(error);
OnCircuitError?.Invoke(error);
+ // If we get an error, forcibly terminate anything else we're waiting for. These
+ // tests should only encounter errors in specific situations, and this ensures that
+ // we fail with a good message.
var exception = FormatError?.Invoke(error) ?? new Exception(error);
NextBatchReceived?.Completion?.TrySetException(exception);
NextDotNetInteropCompletionReceived?.Completion.TrySetException(exception);
@@ -415,7 +487,7 @@ namespace Ignitor
return element;
}
- private class CancellableOperation
+ private class CancellableOperation<TResult>
{
public CancellableOperation(TimeSpan? timeout)
{
@@ -423,24 +495,32 @@ namespace Ignitor
Initialize();
}
+ public TimeSpan? Timeout { get; }
+
+ public TaskCompletionSource<TResult> Completion { get; set; }
+
+ public CancellationTokenSource Cancellation { get; set; }
+
+ public CancellationTokenRegistration CancellationRegistration { get; set; }
+
private void Initialize()
{
- Completion = new TaskCompletionSource<object>(TaskContinuationOptions.RunContinuationsAsynchronously);
+ Completion = new TaskCompletionSource<TResult>(TaskContinuationOptions.RunContinuationsAsynchronously);
Completion.Task.ContinueWith(
(task, state) =>
{
- var operation = (CancellableOperation)state;
+ var operation = (CancellableOperation<TResult>)state;
operation.Dispose();
},
this,
TaskContinuationOptions.ExecuteSynchronously); // We need to execute synchronously to clean-up before anything else continues
- if (Timeout != null)
+ if (Timeout != null && Timeout != System.Threading.Timeout.InfiniteTimeSpan && Timeout != TimeSpan.MaxValue)
{
Cancellation = new CancellationTokenSource(Timeout.Value);
CancellationRegistration = Cancellation.Token.Register(
(self) =>
{
- var operation = (CancellableOperation)self;
+ var operation = (CancellableOperation<TResult>)self;
operation.Completion.TrySetCanceled(operation.Cancellation.Token);
operation.Cancellation.Dispose();
operation.CancellationRegistration.Dispose();
@@ -455,14 +535,6 @@ namespace Ignitor
Cancellation.Dispose();
CancellationRegistration.Dispose();
}
-
- public TimeSpan? Timeout { get; }
-
- public TaskCompletionSource<object> Completion { get; set; }
-
- public CancellationTokenSource Cancellation { get; set; }
-
- public CancellationTokenRegistration CancellationRegistration { get; set; }
}
private string[] ReadMarkers(string content)
diff --git a/src/Components/test/testassets/Ignitor/CapturedJSInteropCall.cs b/src/Components/test/testassets/Ignitor/CapturedJSInteropCall.cs
new file mode 100644
index 0000000000..4af491a58a
--- /dev/null
+++ b/src/Components/test/testassets/Ignitor/CapturedJSInteropCall.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Ignitor
+{
+ public class CapturedJSInteropCall
+ {
+ public CapturedJSInteropCall(int asyncHandle, string identifier, string argsJson)
+ {
+ AsyncHandle = asyncHandle;
+ Identifier = identifier;
+ ArgsJson = argsJson;
+ }
+
+ public int AsyncHandle { get; }
+ public string Identifier { get; }
+ public string ArgsJson { get; }
+ }
+}
diff --git a/src/Components/test/testassets/Ignitor/CapturedRenderBatch.cs b/src/Components/test/testassets/Ignitor/CapturedRenderBatch.cs
new file mode 100644
index 0000000000..df2de87e9f
--- /dev/null
+++ b/src/Components/test/testassets/Ignitor/CapturedRenderBatch.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Ignitor
+{
+ public class CapturedRenderBatch
+ {
+ public CapturedRenderBatch(int id, byte[] data)
+ {
+ Id = id;
+ Data = data;
+ }
+
+ public int Id { get; }
+ public byte[] Data { get; }
+ }
+}
diff --git a/src/Components/test/testassets/Ignitor/Operations.cs b/src/Components/test/testassets/Ignitor/Operations.cs
new file mode 100644
index 0000000000..4dcf8798b5
--- /dev/null
+++ b/src/Components/test/testassets/Ignitor/Operations.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Concurrent;
+
+namespace Ignitor
+{
+ public sealed class Operations
+ {
+ public ConcurrentQueue<CapturedRenderBatch> Batches { get; } = new ConcurrentQueue<CapturedRenderBatch>();
+
+ public ConcurrentQueue<string> DotNetCompletions { get; } = new ConcurrentQueue<string>();
+
+ public ConcurrentQueue<string> Errors { get; } = new ConcurrentQueue<string>();
+
+ public ConcurrentQueue<CapturedJSInteropCall> JSInteropCalls { get; } = new ConcurrentQueue<CapturedJSInteropCall>();
+ }
+}
diff --git a/src/Components/test/testassets/Ignitor/Program.cs b/src/Components/test/testassets/Ignitor/Program.cs
index 8f5785e7d3..43486d490e 100644
--- a/src/Components/test/testassets/Ignitor/Program.cs
+++ b/src/Components/test/testassets/Ignitor/Program.cs
@@ -2,10 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Net.Http;
-using System.Runtime.InteropServices;
-using System.Text.Json;
-using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;
@@ -38,9 +34,9 @@ namespace Ignitor
var done = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
// Click the counter button 1000 times
- client.RenderBatchReceived += (int batchId, byte[] data) =>
+ client.RenderBatchReceived += (batch) =>
{
- if (batchId < 1000)
+ if (batch.Id < 1000)
{
var _ = client.ClickAsync("thecounter");
}
@@ -56,8 +52,8 @@ namespace Ignitor
return 0;
}
- private static void OnJSInterop(int callId, string identifier, string argsJson) =>
- Console.WriteLine("JS Invoke: " + identifier + " (" + argsJson + ")");
+ private static void OnJSInterop(CapturedJSInteropCall call) =>
+ Console.WriteLine("JS Invoke: " + call.Identifier + " (" + call.ArgsJson + ")");
public Program()
{