mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-31 09:02:41 +01:00
Adds a Discord reconnection test
This commit is contained in:
@@ -25,7 +25,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the next available <see cref="Message"/> or <see langword="null"/> if the <see cref="IProvider"/> needed to reconnect.</returns>
|
||||
/// <remarks>Note that private messages will come in the form of <see cref="Channel"/>s not returned in <see cref="MapChannels(IEnumerable{Api.Models.ChatChannel}, CancellationToken)"/></remarks>
|
||||
/// <remarks>Note that private messages will come in the form of <see cref="Channel"/>s not returned in <see cref="MapChannels(IEnumerable{Api.Models.ChatChannel}, CancellationToken)"/>. Do not <see cref="IDisposable.Dispose"/> the <see cref="IProvider"/> on continuations run from the returned <see cref="Task"/>.</remarks>
|
||||
Task<Message> NextMessage(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -20,6 +20,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// </summary>
|
||||
readonly Queue<Message> messageQueue;
|
||||
|
||||
/// <summary>
|
||||
/// Used for synchronizing access to <see cref="reconnectCts"/> and <see cref="reconnectTask"/>.
|
||||
/// </summary>
|
||||
readonly object reconnectTaskLock;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="TaskCompletionSource{TResult}"/> that completes while <see cref="messageQueue"/> isn't empty
|
||||
/// </summary>
|
||||
@@ -47,6 +52,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
messageQueue = new Queue<Message>();
|
||||
nextMessage = new TaskCompletionSource<object>();
|
||||
|
||||
reconnectTaskLock = new object();
|
||||
|
||||
SetReconnectInterval(reconnectInterval).GetAwaiter().GetResult();
|
||||
logger.LogTrace("Created.");
|
||||
}
|
||||
@@ -106,35 +113,37 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// Stops and awaits the <see cref="reconnectTask"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
async Task StopReconnectionTimer()
|
||||
Task StopReconnectionTimer()
|
||||
{
|
||||
reconnectCts?.Cancel();
|
||||
reconnectCts?.Dispose();
|
||||
if (reconnectTask != null)
|
||||
{
|
||||
await reconnectTask.ConfigureAwait(false);
|
||||
reconnectTask = null;
|
||||
}
|
||||
lock (reconnectTaskLock)
|
||||
if (reconnectCts != null)
|
||||
{
|
||||
reconnectCts.Cancel();
|
||||
reconnectCts.Dispose();
|
||||
reconnectCts = null;
|
||||
Task reconnectTask = this.reconnectTask;
|
||||
this.reconnectTask = null;
|
||||
return reconnectTask;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SetReconnectInterval(uint reconnectInterval)
|
||||
public Task SetReconnectInterval(uint reconnectInterval)
|
||||
{
|
||||
if (reconnectInterval == 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(reconnectInterval), reconnectInterval, "Reconnect interval cannot be zero!");
|
||||
|
||||
await StopReconnectionTimer().ConfigureAwait(false);
|
||||
reconnectCts = new CancellationTokenSource();
|
||||
try
|
||||
Task stopOldTimerTask;
|
||||
lock (reconnectTaskLock)
|
||||
{
|
||||
stopOldTimerTask = StopReconnectionTimer();
|
||||
reconnectCts = new CancellationTokenSource();
|
||||
reconnectTask = ReconnectionLoop(reconnectInterval, reconnectCts.Token);
|
||||
}
|
||||
catch
|
||||
{
|
||||
reconnectCts.Dispose();
|
||||
reconnectCts = null;
|
||||
throw;
|
||||
}
|
||||
|
||||
return stopOldTimerTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Tgstation.Server.Host.Tests")]
|
||||
[assembly: InternalsVisibleTo("Tgstation.Server.Tests")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
@@ -11,16 +13,67 @@ using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Client;
|
||||
using Tgstation.Server.Host;
|
||||
using Tgstation.Server.Host.Components.Chat.Providers;
|
||||
|
||||
namespace Tgstation.Server.Tests
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("SkipWhenLiveUnitTesting")]
|
||||
public sealed class IntegrationTest
|
||||
{
|
||||
readonly IServerClientFactory clientFactory = new ServerClientFactory(new ProductHeaderValue(Assembly.GetExecutingAssembly().GetName().Name, Assembly.GetExecutingAssembly().GetName().Version.ToString()));
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestAutomaticDiscordReconnection()
|
||||
{
|
||||
var discordToken = Environment.GetEnvironmentVariable("TGS4_TEST_DISCORD_TOKEN");
|
||||
if (String.IsNullOrWhiteSpace(discordToken))
|
||||
Assert.Inconclusive("The TGS4_TEST_DISCORD_TOKEN environment variable must be set to run this test!");
|
||||
|
||||
using (var discordProvider = new DiscordProvider(Mock.Of<ILogger<DiscordProvider>>(), discordToken, 1))
|
||||
{
|
||||
var connectResult = await discordProvider.Connect(default).ConfigureAwait(false);
|
||||
Assert.IsTrue(connectResult, "Failed to connect to discord!");
|
||||
Assert.IsTrue(discordProvider.Connected, "Discord provider is not connected!");
|
||||
|
||||
// Forcefully close the connection under the provider's nose
|
||||
// This will be detected in real life scenarios
|
||||
DiscordSocketClient socketClient = typeof(DiscordProvider)
|
||||
.GetField("client", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
?.GetValue(discordProvider)
|
||||
as DiscordSocketClient;
|
||||
Assert.IsNotNull(socketClient, "Reflection unable to read discord socket client!");
|
||||
|
||||
await socketClient.LogoutAsync().ConfigureAwait(false);
|
||||
|
||||
Assert.IsFalse(discordProvider.Connected, "Discord provider is still connected!");
|
||||
|
||||
try
|
||||
{
|
||||
using (CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(70)))
|
||||
{
|
||||
do
|
||||
{
|
||||
var message = await discordProvider.NextMessage(cts.Token).ConfigureAwait(false);
|
||||
if (message == null)
|
||||
break;
|
||||
}
|
||||
while (true);
|
||||
|
||||
// Prevents a deadlock coming from having the NextMessage continuation call Dispose
|
||||
await Task.Yield();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Assert.Fail("Failed to reconnect within the time period!");
|
||||
}
|
||||
|
||||
Assert.IsTrue(discordProvider.Connected, "Discord provider not connected!");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("SkipWhenLiveUnitTesting")]
|
||||
public async Task TestUpdate()
|
||||
{
|
||||
var updatePathRoot = Path.GetTempFileName();
|
||||
@@ -95,7 +148,6 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("SkipWhenLiveUnitTesting")]
|
||||
public async Task TestStandardOperation()
|
||||
{
|
||||
var server = new TestingServer(clientFactory, null);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
|
||||
<PackageReference Include="Moq" Version="4.13.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user