Merge pull request #13226 from aspnet/rynowak/interop-reliability
Improve reliability of reliability tests
This commit is contained in:
commit
733218c652
|
|
@ -649,12 +649,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
{
|
{
|
||||||
_intializationStarted = LoggerMessage.Define(
|
_intializationStarted = LoggerMessage.Define(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.InitializationFailed,
|
EventIds.InitializationStarted,
|
||||||
"Circuit initialization started.");
|
"Circuit initialization started.");
|
||||||
|
|
||||||
_intializationSucceded = LoggerMessage.Define(
|
_intializationSucceded = LoggerMessage.Define(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.InitializationFailed,
|
EventIds.InitializationSucceeded,
|
||||||
"Circuit initialization succeeded.");
|
"Circuit initialization succeeded.");
|
||||||
|
|
||||||
_intializationFailed = LoggerMessage.Define(
|
_intializationFailed = LoggerMessage.Define(
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,9 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -13,58 +11,22 @@ using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||||
using Microsoft.AspNetCore.Components.RenderTree;
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Testing;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
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)
|
public ComponentHubReliabilityTest(AspNetSiteServerFixture serverFixture, ITestOutputHelper output)
|
||||||
|
: base(serverFixture, output)
|
||||||
{
|
{
|
||||||
_serverFixture = serverFixture;
|
}
|
||||||
Output = output;
|
|
||||||
|
|
||||||
|
protected override void InitializeFixture(AspNetSiteServerFixture serverFixture)
|
||||||
|
{
|
||||||
serverFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
|
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()
|
|
||||||
{
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -72,10 +34,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedError = "The circuit host '.*?' has already been initialized.";
|
var expectedError = "The circuit host '.*?' has already been initialized.";
|
||||||
var rootUri = _serverFixture.RootUri;
|
var rootUri = ServerFixture.RootUri;
|
||||||
var baseUri = new Uri(rootUri, "/subdir");
|
var baseUri = new Uri(rootUri, "/subdir");
|
||||||
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
|
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
|
||||||
Assert.Single(Batches);
|
Assert.Single(Batches);
|
||||||
|
|
||||||
var descriptors = await Client.GetPrerenderDescriptors(baseUri);
|
var descriptors = await Client.GetPrerenderDescriptors(baseUri);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -96,7 +59,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedError = "The uris provided are invalid.";
|
var expectedError = "The uris provided are invalid.";
|
||||||
var rootUri = _serverFixture.RootUri;
|
var rootUri = ServerFixture.RootUri;
|
||||||
var uri = new Uri(rootUri, "/subdir");
|
var uri = new Uri(rootUri, "/subdir");
|
||||||
Assert.True(await Client.ConnectAsync(uri, connectAutomatically: false), "Couldn't connect to the app");
|
Assert.True(await Client.ConnectAsync(uri, connectAutomatically: false), "Couldn't connect to the app");
|
||||||
var descriptors = await Client.GetPrerenderDescriptors(uri);
|
var descriptors = await Client.GetPrerenderDescriptors(uri);
|
||||||
|
|
@ -117,7 +80,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedError = "The circuit failed to initialize.";
|
var expectedError = "The circuit failed to initialize.";
|
||||||
var rootUri = _serverFixture.RootUri;
|
var rootUri = ServerFixture.RootUri;
|
||||||
var uri = new Uri(rootUri, "/subdir");
|
var uri = new Uri(rootUri, "/subdir");
|
||||||
Assert.True(await Client.ConnectAsync(uri, connectAutomatically: false), "Couldn't connect to the app");
|
Assert.True(await Client.ConnectAsync(uri, connectAutomatically: false), "Couldn't connect to the app");
|
||||||
var descriptors = await Client.GetPrerenderDescriptors(uri);
|
var descriptors = await Client.GetPrerenderDescriptors(uri);
|
||||||
|
|
@ -138,7 +101,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedError = "Circuit not initialized.";
|
var expectedError = "Circuit not initialized.";
|
||||||
var rootUri = _serverFixture.RootUri;
|
var rootUri = ServerFixture.RootUri;
|
||||||
var baseUri = new Uri(rootUri, "/subdir");
|
var baseUri = new Uri(rootUri, "/subdir");
|
||||||
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
|
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
|
||||||
Assert.Empty(Batches);
|
Assert.Empty(Batches);
|
||||||
|
|
@ -164,7 +127,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedError = "Circuit not initialized.";
|
var expectedError = "Circuit not initialized.";
|
||||||
var rootUri = _serverFixture.RootUri;
|
var rootUri = ServerFixture.RootUri;
|
||||||
var baseUri = new Uri(rootUri, "/subdir");
|
var baseUri = new Uri(rootUri, "/subdir");
|
||||||
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
|
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
|
||||||
Assert.Empty(Batches);
|
Assert.Empty(Batches);
|
||||||
|
|
@ -188,7 +151,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedError = "Circuit not initialized.";
|
var expectedError = "Circuit not initialized.";
|
||||||
var rootUri = _serverFixture.RootUri;
|
var rootUri = ServerFixture.RootUri;
|
||||||
var baseUri = new Uri(rootUri, "/subdir");
|
var baseUri = new Uri(rootUri, "/subdir");
|
||||||
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
|
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
|
||||||
Assert.Empty(Batches);
|
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"));
|
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]
|
[Fact]
|
||||||
public async Task CannotInvokeOnRenderCompletedBeforeInitialization()
|
public async Task CannotInvokeOnRenderCompletedBeforeInitialization()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedError = "Circuit not initialized.";
|
var expectedError = "Circuit not initialized.";
|
||||||
var rootUri = _serverFixture.RootUri;
|
var rootUri = ServerFixture.RootUri;
|
||||||
var baseUri = new Uri(rootUri, "/subdir");
|
var baseUri = new Uri(rootUri, "/subdir");
|
||||||
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
|
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
|
||||||
Assert.Empty(Batches);
|
Assert.Empty(Batches);
|
||||||
|
|
@ -347,7 +197,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedError = "Circuit not initialized.";
|
var expectedError = "Circuit not initialized.";
|
||||||
var rootUri = _serverFixture.RootUri;
|
var rootUri = ServerFixture.RootUri;
|
||||||
var baseUri = new Uri(rootUri, "/subdir");
|
var baseUri = new Uri(rootUri, "/subdir");
|
||||||
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
|
Assert.True(await Client.ConnectAsync(baseUri, connectAutomatically: false));
|
||||||
Assert.Empty(Batches);
|
Assert.Empty(Batches);
|
||||||
|
|
@ -373,7 +223,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
"For more details turn on detailed exceptions in 'CircuitOptions.DetailedErrors'. " +
|
"For more details turn on detailed exceptions in 'CircuitOptions.DetailedErrors'. " +
|
||||||
"Location change to 'http://example.com' failed.";
|
"Location change to 'http://example.com' failed.";
|
||||||
|
|
||||||
var rootUri = _serverFixture.RootUri;
|
var rootUri = ServerFixture.RootUri;
|
||||||
var baseUri = new Uri(rootUri, "/subdir");
|
var baseUri = new Uri(rootUri, "/subdir");
|
||||||
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
|
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
|
||||||
Assert.Single(Batches);
|
Assert.Single(Batches);
|
||||||
|
|
@ -402,7 +252,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
"For more details turn on detailed exceptions in 'CircuitOptions.DetailedErrors'. " +
|
"For more details turn on detailed exceptions in 'CircuitOptions.DetailedErrors'. " +
|
||||||
"Location change failed.";
|
"Location change failed.";
|
||||||
|
|
||||||
var rootUri = _serverFixture.RootUri;
|
var rootUri = ServerFixture.RootUri;
|
||||||
var baseUri = new Uri(rootUri, "/subdir");
|
var baseUri = new Uri(rootUri, "/subdir");
|
||||||
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
|
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
|
||||||
Assert.Single(Batches);
|
Assert.Single(Batches);
|
||||||
|
|
@ -421,7 +271,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
|
|
||||||
var entry = Assert.Single(Logs, l => l.EventId.Name == "LocationChangeFailed");
|
var entry = Assert.Single(Logs, l => l.EventId.Name == "LocationChangeFailed");
|
||||||
Assert.Equal(LogLevel.Error, entry.LogLevel);
|
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]
|
[Theory]
|
||||||
|
|
@ -436,7 +286,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedError = "Unhandled exception in circuit .*";
|
var expectedError = "Unhandled exception in circuit .*";
|
||||||
var rootUri = _serverFixture.RootUri;
|
var rootUri = ServerFixture.RootUri;
|
||||||
var baseUri = new Uri(rootUri, "/subdir");
|
var baseUri = new Uri(rootUri, "/subdir");
|
||||||
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
|
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
|
||||||
Assert.Single(Batches);
|
Assert.Single(Batches);
|
||||||
|
|
@ -467,7 +317,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedError = "Unhandled exception in circuit .*";
|
var expectedError = "Unhandled exception in circuit .*";
|
||||||
var rootUri = _serverFixture.RootUri;
|
var rootUri = ServerFixture.RootUri;
|
||||||
var baseUri = new Uri(rootUri, "/subdir");
|
var baseUri = new Uri(rootUri, "/subdir");
|
||||||
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
|
Assert.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
|
||||||
Assert.Single(Batches);
|
Assert.Single(Batches);
|
||||||
|
|
@ -493,47 +343,5 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
Logs,
|
Logs,
|
||||||
e => LogLevel.Error == e.LogLevel && Regex.IsMatch(e.Message, expectedError));
|
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; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
@ -21,55 +20,26 @@ using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
[Flaky("https://github.com/aspnet/AspNetCore/issues/13086", FlakyOn.All)]
|
public class InteropReliabilityTests : IgnitorTest<AspNetSiteServerFixture>
|
||||||
public class InteropReliabilityTests : IClassFixture<AspNetSiteServerFixture>, IDisposable
|
|
||||||
{
|
{
|
||||||
private static readonly TimeSpan DefaultLatencyTimeout = TimeSpan.FromSeconds(30);
|
|
||||||
private readonly AspNetSiteServerFixture _serverFixture;
|
|
||||||
|
|
||||||
public InteropReliabilityTests(AspNetSiteServerFixture serverFixture, ITestOutputHelper output)
|
public InteropReliabilityTests(AspNetSiteServerFixture serverFixture, ITestOutputHelper output)
|
||||||
|
: base(serverFixture, output)
|
||||||
{
|
{
|
||||||
_serverFixture = serverFixture;
|
}
|
||||||
Output = output;
|
|
||||||
|
|
||||||
|
protected override void InitializeFixture(AspNetSiteServerFixture serverFixture)
|
||||||
|
{
|
||||||
serverFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
|
serverFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
|
||||||
CreateDefaultConfiguration();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlazorClient Client { get; set; }
|
protected async override Task InitializeAsync()
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
Client = new BlazorClient() { DefaultLatencyTimeout = DefaultLatencyTimeout };
|
var rootUri = ServerFixture.RootUri;
|
||||||
Client.RenderBatchReceived += (id, data) => Batches.Add(new Batch(id, data));
|
Assert.True(await Client.ConnectAsync(new Uri(rootUri, "/subdir")), "Couldn't connect to the app");
|
||||||
Client.DotNetInteropCompletion += (method) => DotNetCompletions.Add(new DotNetCompletion(method));
|
Assert.Single(Batches);
|
||||||
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.
|
await Client.SelectAsync("test-selector-select", "BasicTestApp.ReliabilityComponent");
|
||||||
TestSink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
|
Assert.Equal(2, Batches.Count);
|
||||||
TestSink.MessageLogged += LogMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LogMessages(WriteContext context)
|
|
||||||
{
|
|
||||||
var log = new LogMessage(context.LogLevel, context.Message, context.Exception);
|
|
||||||
Logs.Enqueue(log);
|
|
||||||
Output.WriteLine(log.ToString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -79,7 +49,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
var expectedError = "[\"1\"," +
|
var expectedError = "[\"1\"," +
|
||||||
"false," +
|
"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\"]";
|
"\"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
|
// Act
|
||||||
await Client.InvokeDotNetMethod(
|
await Client.InvokeDotNetMethod(
|
||||||
|
|
@ -90,7 +59,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
JsonSerializer.Serialize(new[] { ".\\log.txt", "log" }));
|
JsonSerializer.Serialize(new[] { ".\\log.txt", "log" }));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Single(DotNetCompletions, c => c.Message == expectedError);
|
Assert.Single(DotNetCompletions, c => c == expectedError);
|
||||||
await ValidateClientKeepsWorking(Client, Batches);
|
await ValidateClientKeepsWorking(Client, Batches);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,8 +71,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
"false," +
|
"false," +
|
||||||
"\"There was an exception invoking \\u0027MadeUpMethod\\u0027 on assembly \\u0027BasicTestApp\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
|
"\"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
|
// Act
|
||||||
await Client.InvokeDotNetMethod(
|
await Client.InvokeDotNetMethod(
|
||||||
"1",
|
"1",
|
||||||
|
|
@ -113,7 +80,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
JsonSerializer.Serialize(new[] { ".\\log.txt", "log" }));
|
JsonSerializer.Serialize(new[] { ".\\log.txt", "log" }));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Single(DotNetCompletions, c => c.Message == expectedError);
|
Assert.Single(DotNetCompletions, c => c == expectedError);
|
||||||
await ValidateClientKeepsWorking(Client, Batches);
|
await ValidateClientKeepsWorking(Client, Batches);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,18 +92,16 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
"false," +
|
"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\"]";
|
"\"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
|
// Act
|
||||||
await Client.InvokeDotNetMethod(
|
await Client.InvokeDotNetMethod(
|
||||||
"1",
|
"1",
|
||||||
"Microsoft.AspNetCore.Components.Server",
|
"Microsoft.AspNetCore.Components.Server",
|
||||||
"NotifyLocationChanged",
|
"NotifyLocationChanged",
|
||||||
null,
|
null,
|
||||||
JsonSerializer.Serialize(new[] { _serverFixture.RootUri }));
|
JsonSerializer.Serialize(new[] { ServerFixture.RootUri }));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Single(DotNetCompletions, c => c.Message == expectedError);
|
Assert.Single(DotNetCompletions, c => c == expectedError);
|
||||||
await ValidateClientKeepsWorking(Client, Batches);
|
await ValidateClientKeepsWorking(Client, Batches);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,18 +113,16 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
"false," +
|
"false," +
|
||||||
"\"There was an exception invoking \\u0027NotifyLocationChanged\\u0027 on assembly \\u0027\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
|
"\"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
|
// Act
|
||||||
await Client.InvokeDotNetMethod(
|
await Client.InvokeDotNetMethod(
|
||||||
"1",
|
"1",
|
||||||
"",
|
"",
|
||||||
"NotifyLocationChanged",
|
"NotifyLocationChanged",
|
||||||
null,
|
null,
|
||||||
JsonSerializer.Serialize(new object[] { _serverFixture.RootUri + "counter", false }));
|
JsonSerializer.Serialize(new object[] { ServerFixture.RootUri + "counter", false }));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Single(DotNetCompletions, c => c.Message == expectedError);
|
Assert.Single(DotNetCompletions, c => c == expectedError);
|
||||||
await ValidateClientKeepsWorking(Client, Batches);
|
await ValidateClientKeepsWorking(Client, Batches);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,18 +134,16 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
"false," +
|
"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\"]";
|
"\"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
|
// Act
|
||||||
await Client.InvokeDotNetMethod(
|
await Client.InvokeDotNetMethod(
|
||||||
"1",
|
"1",
|
||||||
"Microsoft.AspNetCore.Components.Server",
|
"Microsoft.AspNetCore.Components.Server",
|
||||||
"",
|
"",
|
||||||
null,
|
null,
|
||||||
JsonSerializer.Serialize(new object[] { _serverFixture.RootUri + "counter", false }));
|
JsonSerializer.Serialize(new object[] { ServerFixture.RootUri + "counter", false }));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Single(DotNetCompletions, c => c.Message == expectedError);
|
Assert.Single(DotNetCompletions, c => c == expectedError);
|
||||||
|
|
||||||
await ValidateClientKeepsWorking(Client, Batches);
|
await ValidateClientKeepsWorking(Client, Batches);
|
||||||
}
|
}
|
||||||
|
|
@ -196,8 +157,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
"false," +
|
"false," +
|
||||||
"\"There was an exception invoking \\u0027Reverse\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
|
"\"There was an exception invoking \\u0027Reverse\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
|
||||||
|
|
||||||
await GoToTestComponent(Batches);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await Client.InvokeDotNetMethod(
|
await Client.InvokeDotNetMethod(
|
||||||
"1",
|
"1",
|
||||||
|
|
@ -206,7 +165,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
null,
|
null,
|
||||||
JsonSerializer.Serialize(Array.Empty<object>()));
|
JsonSerializer.Serialize(Array.Empty<object>()));
|
||||||
|
|
||||||
Assert.Single(DotNetCompletions, c => c.Message == expectedDotNetObjectRef);
|
Assert.Single(DotNetCompletions, c => c == expectedDotNetObjectRef);
|
||||||
|
|
||||||
await Client.InvokeDotNetMethod(
|
await Client.InvokeDotNetMethod(
|
||||||
"1",
|
"1",
|
||||||
|
|
@ -216,7 +175,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
JsonSerializer.Serialize(Array.Empty<object>()));
|
JsonSerializer.Serialize(Array.Empty<object>()));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Single(DotNetCompletions, c => c.Message == "[\"1\",true,\"tnatropmI\"]");
|
Assert.Single(DotNetCompletions, c => c == "[\"1\",true,\"tnatropmI\"]");
|
||||||
|
|
||||||
await Client.InvokeDotNetMethod(
|
await Client.InvokeDotNetMethod(
|
||||||
"1",
|
"1",
|
||||||
|
|
@ -225,7 +184,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
3, // non existing ref
|
3, // non existing ref
|
||||||
JsonSerializer.Serialize(Array.Empty<object>()));
|
JsonSerializer.Serialize(Array.Empty<object>()));
|
||||||
|
|
||||||
Assert.Single(DotNetCompletions, c => c.Message == expectedError);
|
Assert.Single(DotNetCompletions, c => c == expectedError);
|
||||||
await ValidateClientKeepsWorking(Client, Batches);
|
await ValidateClientKeepsWorking(Client, Batches);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,8 +197,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
"false," +
|
"false," +
|
||||||
"\"There was an exception invoking \\u0027ReceiveTrivial\\u0027 on assembly \\u0027BasicTestApp\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
|
"\"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(
|
await Client.InvokeDotNetMethod(
|
||||||
"1",
|
"1",
|
||||||
"BasicTestApp",
|
"BasicTestApp",
|
||||||
|
|
@ -247,7 +204,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
null,
|
null,
|
||||||
JsonSerializer.Serialize(Array.Empty<object>()));
|
JsonSerializer.Serialize(Array.Empty<object>()));
|
||||||
|
|
||||||
Assert.Single(DotNetCompletions, c => c.Message == expectedImportantDotNetObjectRef);
|
Assert.Single(DotNetCompletions, c => c == expectedImportantDotNetObjectRef);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await Client.InvokeDotNetMethod(
|
await Client.InvokeDotNetMethod(
|
||||||
|
|
@ -258,19 +215,16 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
JsonSerializer.Serialize(new object[] { new { __dotNetObject = 1 } }));
|
JsonSerializer.Serialize(new object[] { new { __dotNetObject = 1 } }));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Single(DotNetCompletions, c => c.Message == expectedError);
|
Assert.Single(DotNetCompletions, c => c == expectedError);
|
||||||
await ValidateClientKeepsWorking(Client, Batches);
|
await ValidateClientKeepsWorking(Client, Batches);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Flaky("https://github.com/aspnet/AspNetCore/issues/13086", FlakyOn.AzP.Windows)]
|
|
||||||
public async Task ContinuesWorkingAfterInvalidAsyncReturnCallback()
|
public async Task ContinuesWorkingAfterInvalidAsyncReturnCallback()
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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.";
|
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
|
// Act
|
||||||
await Client.ClickAsync("triggerjsinterop-malformed");
|
await Client.ClickAsync("triggerjsinterop-malformed");
|
||||||
|
|
||||||
|
|
@ -278,11 +232,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
Assert.NotEqual(default, call);
|
Assert.NotEqual(default, call);
|
||||||
|
|
||||||
var id = call.AsyncHandle;
|
var id = call.AsyncHandle;
|
||||||
await Client.HubConnection.InvokeAsync(
|
await Client.ExpectRenderBatch(async () =>
|
||||||
"EndInvokeJSFromDotNet",
|
{
|
||||||
id,
|
await Client.HubConnection.InvokeAsync(
|
||||||
true,
|
"EndInvokeJSFromDotNet",
|
||||||
$"[{id}, true, \"{{\"]");
|
id,
|
||||||
|
true,
|
||||||
|
$"[{id}, true, \"{{\"]");
|
||||||
|
});
|
||||||
|
|
||||||
var text = Assert.Single(
|
var text = Assert.Single(
|
||||||
Client.FindElementById("errormessage-malformed").Children.OfType<TextNode>(),
|
Client.FindElementById("errormessage-malformed").Children.OfType<TextNode>(),
|
||||||
|
|
@ -295,10 +252,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
public async Task JSInteropCompletionSuccess()
|
public async Task JSInteropCompletionSuccess()
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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
|
// Act
|
||||||
await Client.ClickAsync("triggerjsinterop-success");
|
await Client.ClickAsync("triggerjsinterop-success");
|
||||||
|
|
@ -307,27 +260,27 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
Assert.NotEqual(default, call);
|
Assert.NotEqual(default, call);
|
||||||
|
|
||||||
var id = call.AsyncHandle;
|
var id = call.AsyncHandle;
|
||||||
await Client.HubConnection.InvokeAsync(
|
await Client.ExpectRenderBatch(async () =>
|
||||||
"EndInvokeJSFromDotNet",
|
{
|
||||||
id++,
|
await Client.HubConnection.InvokeAsync(
|
||||||
true,
|
"EndInvokeJSFromDotNet",
|
||||||
$"[{id}, true, null]");
|
id,
|
||||||
|
true,
|
||||||
|
$"[{id}, true, null]");
|
||||||
|
});
|
||||||
|
|
||||||
Assert.Single(
|
Assert.Single(
|
||||||
Client.FindElementById("errormessage-success").Children.OfType<TextNode>(),
|
Client.FindElementById("errormessage-success").Children.OfType<TextNode>(),
|
||||||
e => "" == e.TextContent);
|
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]
|
[Fact]
|
||||||
public async Task JSInteropThrowsInUserCode()
|
public async Task JSInteropThrowsInUserCode()
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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
|
// Act
|
||||||
await Client.ClickAsync("triggerjsinterop-failure");
|
await Client.ClickAsync("triggerjsinterop-failure");
|
||||||
|
|
@ -349,9 +302,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
Client.FindElementById("errormessage-failure").Children.OfType<TextNode>(),
|
Client.FindElementById("errormessage-failure").Children.OfType<TextNode>(),
|
||||||
e => "There was an error invoking sendFailureCallbackReturn" == e.TextContent);
|
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);
|
await ValidateClientKeepsWorking(Client, Batches);
|
||||||
}
|
}
|
||||||
|
|
@ -360,10 +314,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
public async Task MalformedJSInteropCallbackDisposesCircuit()
|
public async Task MalformedJSInteropCallbackDisposesCircuit()
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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
|
// Act
|
||||||
await Client.ClickAsync("triggerjsinterop-malformed");
|
await Client.ClickAsync("triggerjsinterop-malformed");
|
||||||
|
|
@ -386,7 +336,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
Client.FindElementById("errormessage-malformed").Children.OfType<TextNode>(),
|
Client.FindElementById("errormessage-malformed").Children.OfType<TextNode>(),
|
||||||
e => "" == e.TextContent);
|
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 () =>
|
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
||||||
{
|
{
|
||||||
|
|
@ -402,8 +353,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
"false," +
|
"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\"]";
|
"\"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
|
// Act
|
||||||
await Client.InvokeDotNetMethod(
|
await Client.InvokeDotNetMethod(
|
||||||
"1",
|
"1",
|
||||||
|
|
@ -413,7 +362,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
"[ \"invalidPayload\"}");
|
"[ \"invalidPayload\"}");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Single(DotNetCompletions, c => c.Message == expectedError);
|
Assert.Single(DotNetCompletions, c => c == expectedError);
|
||||||
await ValidateClientKeepsWorking(Client, Batches);
|
await ValidateClientKeepsWorking(Client, Batches);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -425,8 +374,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
"false," +
|
"false," +
|
||||||
"\"There was an exception invoking \\u0027ReceiveTrivial\\u0027 on assembly \\u0027BasicTestApp\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.DetailedErrors\\u0027\"]";
|
"\"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
|
// Act
|
||||||
await Client.InvokeDotNetMethod(
|
await Client.InvokeDotNetMethod(
|
||||||
"1",
|
"1",
|
||||||
|
|
@ -436,7 +383,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
"[ { \"data\": {\"}} ]");
|
"[ { \"data\": {\"}} ]");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Single(DotNetCompletions, c => c.Message == expectedError);
|
Assert.Single(DotNetCompletions, c => c == expectedError);
|
||||||
await ValidateClientKeepsWorking(Client, Batches);
|
await ValidateClientKeepsWorking(Client, Batches);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -444,10 +391,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
public async Task DispatchingEventsWithInvalidPayloadsShutsDownCircuitGracefully()
|
public async Task DispatchingEventsWithInvalidPayloadsShutsDownCircuitGracefully()
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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
|
// Act
|
||||||
await Client.ExpectCircuitError(async () =>
|
await Client.ExpectCircuitError(async () =>
|
||||||
|
|
@ -458,9 +401,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
null);
|
null);
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Contains(
|
var entry = Assert.Single(Logs, l => l.EventId.Name == "DispatchEventFailedToParseEventData");
|
||||||
(LogLevel.Debug, "DispatchEventFailedToParseEventData"),
|
Assert.Equal(LogLevel.Debug, entry.LogLevel);
|
||||||
logEvents);
|
|
||||||
|
|
||||||
// Taking any other action will fail because the circuit is disposed.
|
// Taking any other action will fail because the circuit is disposed.
|
||||||
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
||||||
|
|
@ -473,10 +415,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
public async Task DispatchingEventsWithInvalidEventDescriptor()
|
public async Task DispatchingEventsWithInvalidEventDescriptor()
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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
|
// Act
|
||||||
await Client.ExpectCircuitError(async () =>
|
await Client.ExpectCircuitError(async () =>
|
||||||
|
|
@ -487,9 +425,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
"{}");
|
"{}");
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Contains(
|
var entry = Assert.Single(Logs, l => l.EventId.Name == "DispatchEventFailedToParseEventData");
|
||||||
(LogLevel.Debug, "DispatchEventFailedToParseEventData"),
|
Assert.Equal(LogLevel.Debug, entry.LogLevel);
|
||||||
logEvents);
|
|
||||||
|
|
||||||
// Taking any other action will fail because the circuit is disposed.
|
// Taking any other action will fail because the circuit is disposed.
|
||||||
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
||||||
|
|
@ -502,10 +439,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
public async Task DispatchingEventsWithInvalidEventArgs()
|
public async Task DispatchingEventsWithInvalidEventArgs()
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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
|
// Act
|
||||||
var browserDescriptor = new WebEventDescriptor()
|
var browserDescriptor = new WebEventDescriptor()
|
||||||
|
|
@ -524,9 +457,9 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
logEvents,
|
Logs,
|
||||||
e => e.eventIdName == "DispatchEventFailedToParseEventData" && e.logLevel == LogLevel.Debug &&
|
e => e.EventId.Name == "DispatchEventFailedToParseEventData" && e.LogLevel == LogLevel.Debug &&
|
||||||
e.exception.Message == "There was an error parsing the event arguments. EventId: '6'.");
|
e.Exception.Message == "There was an error parsing the event arguments. EventId: '6'.");
|
||||||
|
|
||||||
// Taking any other action will fail because the circuit is disposed.
|
// Taking any other action will fail because the circuit is disposed.
|
||||||
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
||||||
|
|
@ -539,10 +472,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
public async Task DispatchingEventsWithInvalidEventHandlerId()
|
public async Task DispatchingEventsWithInvalidEventHandlerId()
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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
|
// Act
|
||||||
var mouseEventArgs = new MouseEventArgs()
|
var mouseEventArgs = new MouseEventArgs()
|
||||||
|
|
@ -566,9 +495,9 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
logEvents,
|
Logs,
|
||||||
e => e.eventIdName == "DispatchEventFailedToDispatchEvent" && e.logLevel == LogLevel.Debug &&
|
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'."));
|
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.
|
// Taking any other action will fail because the circuit is disposed.
|
||||||
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
||||||
|
|
@ -581,21 +510,18 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
public async Task EventHandlerThrowsSyncExceptionTerminatesTheCircuit()
|
public async Task EventHandlerThrowsSyncExceptionTerminatesTheCircuit()
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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
|
// Act
|
||||||
await Client.ClickAsync("event-handler-throw-sync", expectRenderBatch: false);
|
await Client.ExpectCircuitError(async () =>
|
||||||
|
{
|
||||||
await Task.Delay(1000);
|
await Client.ClickAsync("event-handler-throw-sync", expectRenderBatch: false);
|
||||||
|
});
|
||||||
|
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
logEvents,
|
Logs,
|
||||||
e => LogLevel.Error == e.logLevel &&
|
e => LogLevel.Error == e.LogLevel &&
|
||||||
"CircuitUnhandledException" == e.eventIdName &&
|
"CircuitUnhandledException" == e.EventId.Name &&
|
||||||
"Handler threw an exception" == e.exception.Message);
|
"Handler threw an exception" == e.Exception.Message);
|
||||||
|
|
||||||
// Now if you try to click again, you will get *forcibly* disconnected for trying to talk to
|
// Now if you try to click again, you will get *forcibly* disconnected for trying to talk to
|
||||||
// a circuit that's gone.
|
// 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);
|
ValidateClientKeepsWorking(Client, () => batches.Count);
|
||||||
|
|
||||||
private async Task ValidateClientKeepsWorking(BlazorClient Client, Func<int> countAccessor)
|
private async Task ValidateClientKeepsWorking(BlazorClient Client, Func<int> countAccessor)
|
||||||
|
|
@ -615,75 +541,5 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
|
|
||||||
Assert.Equal(currentBatches + 1, countAccessor());
|
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; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,55 +2,33 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ignitor;
|
using Ignitor;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Testing;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
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);
|
public RemoteRendererBufferLimitTest(AspNetSiteServerFixture serverFixture, ITestOutputHelper output)
|
||||||
|
: base(serverFixture, output)
|
||||||
private AspNetSiteServerFixture _serverFixture;
|
|
||||||
|
|
||||||
public RemoteRendererBufferLimitTest(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; }
|
protected override void InitializeFixture(AspNetSiteServerFixture serverFixture)
|
||||||
|
{
|
||||||
private IList<Batch> Batches { get; set; } = new List<Batch>();
|
serverFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
|
||||||
|
}
|
||||||
// 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]
|
[Fact]
|
||||||
public async Task DispatchedEventsWillKeepBeingProcessed_ButUpdatedWillBeDelayedUntilARenderIsAcknowledged()
|
public async Task DispatchedEventsWillKeepBeingProcessed_ButUpdatedWillBeDelayedUntilARenderIsAcknowledged()
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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.True(await Client.ConnectAsync(baseUri), "Couldn't connect to the app");
|
||||||
Assert.Single(Batches);
|
Assert.Single(Batches);
|
||||||
|
|
||||||
|
|
@ -75,48 +53,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
Client.ConfirmRenderBatch = true;
|
Client.ConfirmRenderBatch = true;
|
||||||
|
|
||||||
// This will resume the render batches.
|
// This will resume the render batches.
|
||||||
await Client.ExpectRenderBatch(() => Client.ConfirmBatch(Batches[^1].Id));
|
await Client.ExpectRenderBatch(() => Client.ConfirmBatch(Batches.Last().Id));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("12", ((TextNode)Client.FindElementById("the-count").Children.Single()).TextContent);
|
Assert.Equal("12", ((TextNode)Client.FindElementById("the-count").Children.Single()).TextContent);
|
||||||
Assert.Equal(fullCount + 1, Batches.Count);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
{
|
{
|
||||||
int currentCount = 0;
|
int currentCount = 0;
|
||||||
string errorMalformed = "";
|
string errorMalformed = "";
|
||||||
string errorSuccess = "";
|
string errorSuccess = "(this will be cleared)";
|
||||||
string errorFailure = "";
|
string errorFailure = "";
|
||||||
bool showConstructorThrow;
|
bool showConstructorThrow;
|
||||||
bool showAttachThrow;
|
bool showAttachThrow;
|
||||||
|
|
@ -116,6 +116,10 @@
|
||||||
var result = await JSRuntime.InvokeAsync<object>(
|
var result = await JSRuntime.InvokeAsync<object>(
|
||||||
"sendSuccessCallbackReturn",
|
"sendSuccessCallbackReturn",
|
||||||
Array.Empty<object>());
|
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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
@ -33,8 +31,20 @@ namespace Ignitor
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSpan? DefaultLatencyTimeout { get; set; } = TimeSpan.FromMilliseconds(500);
|
public TimeSpan? DefaultConnectionTimeout { get; set; } = TimeSpan.FromSeconds(10);
|
||||||
public TimeSpan? DefaultConnectTimeout { 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; }
|
public Func<string, Exception> FormatError { get; set; }
|
||||||
|
|
||||||
|
|
@ -44,23 +54,23 @@ namespace Ignitor
|
||||||
|
|
||||||
private TaskCompletionSource<object> TaskCompletionSource { get; }
|
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 ILoggerProvider LoggerProvider { get; set; }
|
||||||
|
|
||||||
public bool ConfirmRenderBatch { get; set; } = true;
|
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;
|
public event Action<string> DotNetInteropCompletion;
|
||||||
|
|
||||||
|
|
@ -70,66 +80,66 @@ namespace Ignitor
|
||||||
|
|
||||||
public ElementHive Hive { get; set; } = new ElementHive();
|
public ElementHive Hive { get; set; } = new ElementHive();
|
||||||
|
|
||||||
public bool ImplicitWait => DefaultLatencyTimeout != null;
|
public bool ImplicitWait => DefaultOperationTimeout != null;
|
||||||
|
|
||||||
public HubConnection HubConnection { get; set; }
|
public HubConnection HubConnection { get; set; }
|
||||||
|
|
||||||
public Task PrepareForNextBatch(TimeSpan? timeout)
|
public Task<CapturedRenderBatch> PrepareForNextBatch(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
if (NextBatchReceived?.Completion != null)
|
if (NextBatchReceived?.Completion != null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid state previous task not completed");
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
NextBatchReceived = new CancellableOperation(timeout);
|
NextBatchReceived = new CancellableOperation<CapturedRenderBatch>(timeout);
|
||||||
|
|
||||||
return NextBatchReceived.Completion.Task;
|
return NextBatchReceived.Completion.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task PrepareForNextJSInterop(TimeSpan? timeout)
|
public Task<CapturedJSInteropCall> PrepareForNextJSInterop(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
if (NextJSInteropReceived?.Completion != null)
|
if (NextJSInteropReceived?.Completion != null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid state previous task not completed");
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
NextJSInteropReceived = new CancellableOperation(timeout);
|
NextJSInteropReceived = new CancellableOperation<CapturedJSInteropCall>(timeout);
|
||||||
|
|
||||||
return NextJSInteropReceived.Completion.Task;
|
return NextJSInteropReceived.Completion.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task PrepareForNextDotNetInterop(TimeSpan? timeout)
|
public Task<string> PrepareForNextDotNetInterop(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
if (NextDotNetInteropCompletionReceived?.Completion != null)
|
if (NextDotNetInteropCompletionReceived?.Completion != null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid state previous task not completed");
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
NextDotNetInteropCompletionReceived = new CancellableOperation(timeout);
|
NextDotNetInteropCompletionReceived = new CancellableOperation<string>(timeout);
|
||||||
|
|
||||||
return NextDotNetInteropCompletionReceived.Completion.Task;
|
return NextDotNetInteropCompletionReceived.Completion.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task PrepareForNextCircuitError(TimeSpan? timeout)
|
public Task<string> PrepareForNextCircuitError(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
if (NextErrorReceived?.Completion != null)
|
if (NextErrorReceived?.Completion != null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid state previous task not completed");
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
NextErrorReceived = new CancellableOperation(timeout);
|
NextErrorReceived = new CancellableOperation<string>(timeout);
|
||||||
|
|
||||||
return NextErrorReceived.Completion.Task;
|
return NextErrorReceived.Completion.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task PrepareForNextDisconnect(TimeSpan? timeout)
|
public Task<Exception> PrepareForNextDisconnect(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
if (NextDisconnect?.Completion != null)
|
if (NextDisconnect?.Completion != null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid state previous task not completed");
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
NextDisconnect = new CancellableOperation(timeout);
|
NextDisconnect = new CancellableOperation<Exception>(timeout);
|
||||||
|
|
||||||
return NextDisconnect.Completion.Task;
|
return NextDisconnect.Completion.Task;
|
||||||
}
|
}
|
||||||
|
|
@ -160,115 +170,162 @@ namespace Ignitor
|
||||||
return ExpectRenderBatch(() => elementNode.SelectAsync(HubConnection, value));
|
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);
|
var task = WaitForRenderBatch(timeout);
|
||||||
await action();
|
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);
|
var task = WaitForJSInterop(timeout);
|
||||||
await action();
|
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);
|
var task = WaitForDotNetInterop(timeout);
|
||||||
await action();
|
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);
|
var task = WaitForCircuitError(timeout);
|
||||||
await action();
|
await action();
|
||||||
await task;
|
return await task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExpectCircuitErrorAndDisconnect(Func<Task> action, TimeSpan? timeout = null)
|
public async Task<Exception> ExpectDisconnect(Func<Task> action, TimeSpan? timeout = null)
|
||||||
{
|
|
||||||
// NOTE: timeout is used for each operation individually.
|
|
||||||
await ExpectDisconnect(async () =>
|
|
||||||
{
|
|
||||||
await ExpectCircuitError(action, timeout);
|
|
||||||
}, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ExpectDisconnect(Func<Task> action, TimeSpan? timeout = null)
|
|
||||||
{
|
{
|
||||||
var task = WaitForDisconnect(timeout);
|
var task = WaitForDisconnect(timeout);
|
||||||
await action();
|
await action();
|
||||||
await task;
|
return await task;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task WaitForRenderBatch(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.
|
||||||
|
var exception = await ExpectDisconnect(async () =>
|
||||||
|
{
|
||||||
|
error = await ExpectCircuitError(action, timeout);
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
return (error, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<CapturedRenderBatch> WaitForRenderBatch(TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
if (ImplicitWait)
|
if (ImplicitWait)
|
||||||
{
|
{
|
||||||
if (DefaultLatencyTimeout == null && timeout == null)
|
if (DefaultOperationTimeout == null && timeout == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
|
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 (ImplicitWait)
|
||||||
{
|
{
|
||||||
if (DefaultLatencyTimeout == null && timeout == null)
|
if (DefaultOperationTimeout == null && timeout == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
|
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 (ImplicitWait)
|
||||||
{
|
{
|
||||||
if (DefaultLatencyTimeout == null && timeout == null)
|
if (DefaultOperationTimeout == null && timeout == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
|
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 (ImplicitWait)
|
||||||
{
|
{
|
||||||
if (DefaultLatencyTimeout == null && timeout == null)
|
if (DefaultOperationTimeout == null && timeout == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
|
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await PrepareForNextCircuitError(timeout ?? DefaultLatencyTimeout);
|
try
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task WaitForDisconnect(TimeSpan? timeout = null)
|
|
||||||
{
|
|
||||||
if (ImplicitWait)
|
|
||||||
{
|
|
||||||
if (DefaultLatencyTimeout == null && timeout == null)
|
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
|
return await PrepareForNextCircuitError(timeout ?? DefaultOperationTimeout);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
throw FormatError("Timed out while waiting for circuit error.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await PrepareForNextDisconnect(timeout ?? DefaultLatencyTimeout);
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Exception> WaitForDisconnect(TimeSpan? timeout = null)
|
||||||
|
{
|
||||||
|
if (ImplicitWait)
|
||||||
|
{
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> ConnectAsync(Uri uri, bool connectAutomatically = true)
|
public async Task<bool> ConnectAsync(Uri uri, bool connectAutomatically = true)
|
||||||
|
|
@ -294,6 +351,11 @@ namespace Ignitor
|
||||||
HubConnection.On<string>("JS.Error", OnError);
|
HubConnection.On<string>("JS.Error", OnError);
|
||||||
HubConnection.Closed += OnClosedAsync;
|
HubConnection.Closed += OnClosedAsync;
|
||||||
|
|
||||||
|
if (CaptureOperations)
|
||||||
|
{
|
||||||
|
Operations = new Operations();
|
||||||
|
}
|
||||||
|
|
||||||
if (!connectAutomatically)
|
if (!connectAutomatically)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -302,35 +364,41 @@ namespace Ignitor
|
||||||
var descriptors = await GetPrerenderDescriptors(uri);
|
var descriptors = await GetPrerenderDescriptors(uri);
|
||||||
await ExpectRenderBatch(
|
await ExpectRenderBatch(
|
||||||
async () => CircuitId = await HubConnection.InvokeAsync<string>("StartCircuit", uri, uri, descriptors),
|
async () => CircuitId = await HubConnection.InvokeAsync<string>("StartCircuit", uri, uri, descriptors),
|
||||||
DefaultConnectTimeout);
|
DefaultConnectionTimeout);
|
||||||
return CircuitId != null;
|
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);
|
NextDotNetInteropCompletionReceived?.Completion?.TrySetResult(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBeginInvokeJS(int asyncHandle, string identifier, string argsJson)
|
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);
|
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);
|
||||||
|
|
||||||
var batch = RenderBatchReader.Read(batchData);
|
Operations?.Batches.Enqueue(capturedBatch);
|
||||||
|
RenderBatchReceived?.Invoke(capturedBatch);
|
||||||
|
|
||||||
|
var batch = RenderBatchReader.Read(data);
|
||||||
|
|
||||||
Hive.Update(batch);
|
Hive.Update(batch);
|
||||||
|
|
||||||
if (ConfirmRenderBatch)
|
if (ConfirmRenderBatch)
|
||||||
{
|
{
|
||||||
_ = ConfirmBatch(batchId);
|
_ = ConfirmBatch(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
NextBatchReceived?.Completion?.TrySetResult(null);
|
NextBatchReceived?.Completion?.TrySetResult(null);
|
||||||
|
|
@ -343,8 +411,12 @@ namespace Ignitor
|
||||||
|
|
||||||
private void OnError(string error)
|
private void OnError(string error)
|
||||||
{
|
{
|
||||||
|
Operations?.Errors.Enqueue(error);
|
||||||
OnCircuitError?.Invoke(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);
|
var exception = FormatError?.Invoke(error) ?? new Exception(error);
|
||||||
NextBatchReceived?.Completion?.TrySetException(exception);
|
NextBatchReceived?.Completion?.TrySetException(exception);
|
||||||
NextDotNetInteropCompletionReceived?.Completion.TrySetException(exception);
|
NextDotNetInteropCompletionReceived?.Completion.TrySetException(exception);
|
||||||
|
|
@ -415,7 +487,7 @@ namespace Ignitor
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CancellableOperation
|
private class CancellableOperation<TResult>
|
||||||
{
|
{
|
||||||
public CancellableOperation(TimeSpan? timeout)
|
public CancellableOperation(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
|
|
@ -423,24 +495,32 @@ namespace Ignitor
|
||||||
Initialize();
|
Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TimeSpan? Timeout { get; }
|
||||||
|
|
||||||
|
public TaskCompletionSource<TResult> Completion { get; set; }
|
||||||
|
|
||||||
|
public CancellationTokenSource Cancellation { get; set; }
|
||||||
|
|
||||||
|
public CancellationTokenRegistration CancellationRegistration { get; set; }
|
||||||
|
|
||||||
private void Initialize()
|
private void Initialize()
|
||||||
{
|
{
|
||||||
Completion = new TaskCompletionSource<object>(TaskContinuationOptions.RunContinuationsAsynchronously);
|
Completion = new TaskCompletionSource<TResult>(TaskContinuationOptions.RunContinuationsAsynchronously);
|
||||||
Completion.Task.ContinueWith(
|
Completion.Task.ContinueWith(
|
||||||
(task, state) =>
|
(task, state) =>
|
||||||
{
|
{
|
||||||
var operation = (CancellableOperation)state;
|
var operation = (CancellableOperation<TResult>)state;
|
||||||
operation.Dispose();
|
operation.Dispose();
|
||||||
},
|
},
|
||||||
this,
|
this,
|
||||||
TaskContinuationOptions.ExecuteSynchronously); // We need to execute synchronously to clean-up before anything else continues
|
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);
|
Cancellation = new CancellationTokenSource(Timeout.Value);
|
||||||
CancellationRegistration = Cancellation.Token.Register(
|
CancellationRegistration = Cancellation.Token.Register(
|
||||||
(self) =>
|
(self) =>
|
||||||
{
|
{
|
||||||
var operation = (CancellableOperation)self;
|
var operation = (CancellableOperation<TResult>)self;
|
||||||
operation.Completion.TrySetCanceled(operation.Cancellation.Token);
|
operation.Completion.TrySetCanceled(operation.Cancellation.Token);
|
||||||
operation.Cancellation.Dispose();
|
operation.Cancellation.Dispose();
|
||||||
operation.CancellationRegistration.Dispose();
|
operation.CancellationRegistration.Dispose();
|
||||||
|
|
@ -455,14 +535,6 @@ namespace Ignitor
|
||||||
Cancellation.Dispose();
|
Cancellation.Dispose();
|
||||||
CancellationRegistration.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)
|
private string[] ReadMarkers(string content)
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
|
|
@ -38,9 +34,9 @@ namespace Ignitor
|
||||||
var done = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var done = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
// Click the counter button 1000 times
|
// 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");
|
var _ = client.ClickAsync("thecounter");
|
||||||
}
|
}
|
||||||
|
|
@ -56,8 +52,8 @@ namespace Ignitor
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnJSInterop(int callId, string identifier, string argsJson) =>
|
private static void OnJSInterop(CapturedJSInteropCall call) =>
|
||||||
Console.WriteLine("JS Invoke: " + identifier + " (" + argsJson + ")");
|
Console.WriteLine("JS Invoke: " + call.Identifier + " (" + call.ArgsJson + ")");
|
||||||
|
|
||||||
public Program()
|
public Program()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue