mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-07-17 11:03:14 +01:00
More test fixes
This commit is contained in:
@@ -455,13 +455,20 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <inheritdoc />
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await jobManager.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
await Task.WhenAll(instances.Select(x => x.Value.Instance.StopAsync(cancellationToken))).ConfigureAwait(false);
|
||||
await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await jobManager.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
await Task.WhenAll(instances.Select(x => x.Value.Instance.StopAsync(cancellationToken))).ConfigureAwait(false);
|
||||
await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// downgrade the db if necessary
|
||||
if (downgradeVersion != null)
|
||||
await databaseContextFactory.UseContext(db => databaseSeeder.Downgrade(db, downgradeVersion, cancellationToken)).ConfigureAwait(false);
|
||||
// downgrade the db if necessary
|
||||
if (downgradeVersion != null)
|
||||
await databaseContextFactory.UseContext(db => databaseSeeder.Downgrade(db, downgradeVersion, cancellationToken)).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError("Instance manager stop exception: {0}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -38,11 +38,6 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
/// </summary>
|
||||
CompileJob CompileJob { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The current port DreamDaemon is listening on
|
||||
/// </summary>
|
||||
ushort? Port { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If the port should be rotated off when the world reboots
|
||||
/// </summary>
|
||||
|
||||
@@ -42,26 +42,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
public Models.CompileJob CompileJob => reattachInformation.Dmb.CompileJob;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ushort? Port
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckDisposed();
|
||||
if (portClosedForReboot)
|
||||
return null;
|
||||
return reattachInformation.Port;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public RebootState RebootState
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckDisposed();
|
||||
return reattachInformation.RebootState;
|
||||
}
|
||||
}
|
||||
public RebootState RebootState => reattachInformation.RebootState;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Version DMApiVersion { get; private set; }
|
||||
@@ -262,7 +243,10 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
startupTimeout,
|
||||
reattached);
|
||||
|
||||
logger.LogDebug("Created session controller. CommsKey: {0}, Port: {1}", reattachInformation.AccessIdentifier, Port);
|
||||
logger.LogDebug(
|
||||
"Created session controller. CommsKey: {0}, Port: {1}",
|
||||
reattachInformation.AccessIdentifier,
|
||||
reattachInformation.Port);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -463,7 +463,20 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, platformIdentifier.ScriptFileExtension, false, cancellationToken).ConfigureAwait(false);
|
||||
var resolvedScriptsDir = ioManager.ResolvePath(EventScriptsSubdirectory);
|
||||
|
||||
foreach (var I in files.Select(x => ioManager.GetFileName(x)).Where(x => x.StartsWith(scriptName, StringComparison.Ordinal)))
|
||||
var scriptFiles = files
|
||||
.Select(x => ioManager.GetFileName(x))
|
||||
.Where(x => x.StartsWith(scriptName, StringComparison.Ordinal))
|
||||
.ToList();
|
||||
|
||||
if (!scriptFiles.Any())
|
||||
{
|
||||
logger.LogTrace("No event scripts starting with \"{0}\" detected", scriptName);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var I in scriptFiles)
|
||||
{
|
||||
logger.LogTrace("Running event script {0}...", I);
|
||||
using (var script = processExecutor.LaunchProcess(
|
||||
ioManager.ConcatPath(resolvedScriptsDir, I),
|
||||
resolvedScriptsDir,
|
||||
@@ -481,6 +494,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
else
|
||||
logger.LogDebug("Script output:{0}{1}", Environment.NewLine, scriptOutput);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
@@ -15,17 +15,20 @@ namespace Tgstation.Server.Host.Database.Design
|
||||
/// <typeparam name="TDatabaseContext">The <see cref="DatabaseContext"/> to create <see cref="DbContextOptions"/> for.</typeparam>
|
||||
/// <param name="databaseType">The <see cref="DatabaseConfiguration.DatabaseType"/>.</param>
|
||||
/// <param name="connectionString">The <see cref="DatabaseConfiguration.ConnectionString"/>.</param>
|
||||
/// <param name="serverVersion">The <see cref="DatabaseConfiguration.ServerVersion"/>.</param>
|
||||
/// <returns>The <see cref="IOptions{TOptions}"/> for the <see cref="DatabaseConfiguration"/></returns>
|
||||
public static DbContextOptions<TDatabaseContext> CreateDatabaseContextOptions<TDatabaseContext>(
|
||||
DatabaseType databaseType,
|
||||
string connectionString)
|
||||
string connectionString,
|
||||
string serverVersion = null)
|
||||
where TDatabaseContext : DatabaseContext
|
||||
{
|
||||
var dbConfig = new DatabaseConfiguration
|
||||
{
|
||||
DesignTime = true,
|
||||
DatabaseType = databaseType,
|
||||
ConnectionString = connectionString
|
||||
ConnectionString = connectionString,
|
||||
ServerVersion = serverVersion
|
||||
};
|
||||
|
||||
var optionsFac = new DbContextOptionsBuilder<TDatabaseContext>();
|
||||
|
||||
+4
-5
@@ -1,4 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Host.Database.Migrations
|
||||
@@ -39,19 +39,18 @@ namespace Tgstation.Server.Host.Database.Migrations
|
||||
name: "LaunchSecurityLevel",
|
||||
table: "ReattachInformations");
|
||||
|
||||
// No default values b/c lol mysql
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ChatChannelsJson",
|
||||
table: "ReattachInformations",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: false,
|
||||
defaultValue: "chat_channels.tgs.json");
|
||||
nullable: false);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ChatCommandsJson",
|
||||
table: "ReattachInformations",
|
||||
type: "longtext CHARACTER SET utf8mb4",
|
||||
nullable: false,
|
||||
defaultValue: "chat_commands.tgs.json");
|
||||
nullable: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
|
||||
namespace Tgstation.Server.Host.Jobs
|
||||
{
|
||||
@@ -47,15 +48,12 @@ namespace Tgstation.Server.Host.Jobs
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
public async Task Wait(CancellationToken cancellationToken)
|
||||
public Task Wait(CancellationToken cancellationToken)
|
||||
{
|
||||
if (task == null)
|
||||
throw new InvalidOperationException("Job not started!");
|
||||
|
||||
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
|
||||
using (cancellationToken.Register(() => tcs.SetCanceled()))
|
||||
await Task.WhenAny(tcs.Task, task).ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return task.WithToken(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -76,4 +74,4 @@ namespace Tgstation.Server.Host.Jobs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,8 +298,14 @@ namespace Tgstation.Server.Host.Jobs
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
job.CancelledBy = user;
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
if (blocking)
|
||||
{
|
||||
logger.LogTrace("Waiting on cancelled job #{0}...", job.Id);
|
||||
await handler.Wait(cancellationToken).ConfigureAwait(false);
|
||||
logger.LogTrace("Done waiting on job #{0}...", job.Id);
|
||||
}
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
@@ -321,8 +327,6 @@ namespace Tgstation.Server.Host.Jobs
|
||||
{
|
||||
if (job == null)
|
||||
throw new ArgumentNullException(nameof(job));
|
||||
if (canceller == null)
|
||||
throw new ArgumentNullException(nameof(canceller));
|
||||
JobHandler handler;
|
||||
lock (synchronizationLock)
|
||||
{
|
||||
|
||||
@@ -85,8 +85,9 @@ namespace Tgstation.Server.Tests.Instance
|
||||
var dumpTask = instanceClient.DreamDaemon.CreateDump(cancellationToken);
|
||||
while (!dumpTask.IsCompleted)
|
||||
KillDD(false);
|
||||
await WaitForJob(await dumpTask, 5, true, ErrorCode.DreamDaemonOffline, cancellationToken);
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
|
||||
var job = await WaitForJob(await dumpTask, 10, true, null, cancellationToken);
|
||||
Assert.IsTrue(job.ErrorCode == ErrorCode.DreamDaemonOffline || job.ErrorCode == ErrorCode.GCoreFailure);
|
||||
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
|
||||
|
||||
var ddStatus = await instanceClient.DreamDaemon.Read(cancellationToken);
|
||||
Assert.AreEqual(WatchdogStatus.Online, ddStatus.Status.Value);
|
||||
@@ -105,7 +106,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
var startJob = await instanceClient.DreamDaemon.Start(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await WaitForJob(startJob, 10, false, null, cancellationToken);
|
||||
await WaitForJob(startJob, 20, false, null, cancellationToken);
|
||||
|
||||
daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken);
|
||||
Assert.AreEqual(WatchdogStatus.Online, daemonStatus.Status.Value);
|
||||
@@ -147,12 +148,12 @@ namespace Tgstation.Server.Tests.Instance
|
||||
blockSocket.Bind(new IPEndPoint(IPAddress.Any, 1337));
|
||||
startJob = await instanceClient.DreamDaemon.Start(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await WaitForJob(startJob, 10, true, ErrorCode.DreamDaemonPortInUse, cancellationToken);
|
||||
await WaitForJob(startJob, 20, true, ErrorCode.DreamDaemonPortInUse, cancellationToken);
|
||||
}
|
||||
|
||||
startJob = await instanceClient.DreamDaemon.Start(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await WaitForJob(startJob, 10, false, null, cancellationToken);
|
||||
await WaitForJob(startJob, 20, false, null, cancellationToken);
|
||||
|
||||
daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken);
|
||||
Assert.AreEqual(WatchdogStatus.Online, daemonStatus.Status.Value);
|
||||
@@ -178,7 +179,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
var startJob = await instanceClient.DreamDaemon.Start(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await WaitForJob(startJob, 10, false, null, cancellationToken);
|
||||
await WaitForJob(startJob, 20, false, null, cancellationToken);
|
||||
|
||||
// lock on to DD and pause it so it can't heartbeat
|
||||
var ddProcs = System.Diagnostics.Process.GetProcessesByName("DreamDaemon").ToList();
|
||||
@@ -246,7 +247,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
var startJob = await instanceClient.DreamDaemon.Start(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await WaitForJob(startJob, 10, false, null, cancellationToken);
|
||||
await WaitForJob(startJob, 20, false, null, cancellationToken);
|
||||
|
||||
daemonStatus = await DeployTestDme(DmeName, DreamDaemonSecurity.Safe, true, cancellationToken);
|
||||
|
||||
@@ -287,7 +288,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
var startJob = await instanceClient.DreamDaemon.Start(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await WaitForJob(startJob, 10, false, null, cancellationToken);
|
||||
await WaitForJob(startJob, 20, false, null, cancellationToken);
|
||||
|
||||
daemonStatus = await DeployTestDme(DmeName + "_copy", DreamDaemonSecurity.Safe, true, cancellationToken);
|
||||
|
||||
@@ -425,10 +426,10 @@ namespace Tgstation.Server.Tests.Instance
|
||||
{
|
||||
var bts = new TopicClient(new SocketParameters
|
||||
{
|
||||
SendTimeout = TimeSpan.FromSeconds(7),
|
||||
ReceiveTimeout = TimeSpan.FromSeconds(7),
|
||||
ConnectTimeout = TimeSpan.FromSeconds(7),
|
||||
DisconnectTimeout = TimeSpan.FromSeconds(7)
|
||||
SendTimeout = TimeSpan.FromSeconds(15),
|
||||
ReceiveTimeout = TimeSpan.FromSeconds(15),
|
||||
ConnectTimeout = TimeSpan.FromSeconds(15),
|
||||
DisconnectTimeout = TimeSpan.FromSeconds(15)
|
||||
});
|
||||
|
||||
try
|
||||
|
||||
@@ -136,21 +136,40 @@ namespace Tgstation.Server.Tests
|
||||
string migrationName = null;
|
||||
DbContext CreateContext()
|
||||
{
|
||||
string serverVersion = Environment.GetEnvironmentVariable($"{DatabaseConfiguration.Section}__{nameof(DatabaseConfiguration.ServerVersion)}");
|
||||
if (String.IsNullOrWhiteSpace(serverVersion))
|
||||
serverVersion = null;
|
||||
switch (databaseType)
|
||||
{
|
||||
case DatabaseType.MySql:
|
||||
case DatabaseType.MariaDB:
|
||||
migrationName = nameof(MYInitialCreate);
|
||||
return new MySqlDatabaseContext(Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<MySqlDatabaseContext>(databaseType, connectionString));
|
||||
return new MySqlDatabaseContext(
|
||||
Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<MySqlDatabaseContext>(
|
||||
databaseType,
|
||||
connectionString,
|
||||
serverVersion));
|
||||
case DatabaseType.PostgresSql:
|
||||
migrationName = nameof(PGCreate);
|
||||
return new PostgresSqlDatabaseContext(Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<PostgresSqlDatabaseContext>(databaseType, connectionString));
|
||||
return new PostgresSqlDatabaseContext(
|
||||
Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<PostgresSqlDatabaseContext>(
|
||||
databaseType,
|
||||
connectionString,
|
||||
serverVersion));
|
||||
case DatabaseType.SqlServer:
|
||||
migrationName = nameof(MSInitialCreate);
|
||||
return new SqlServerDatabaseContext(Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<SqlServerDatabaseContext>(databaseType, connectionString));
|
||||
return new SqlServerDatabaseContext(
|
||||
Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<SqlServerDatabaseContext>(
|
||||
databaseType,
|
||||
connectionString,
|
||||
serverVersion));
|
||||
case DatabaseType.Sqlite:
|
||||
migrationName = nameof(SLRebuild);
|
||||
return new SqliteDatabaseContext(Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<SqliteDatabaseContext>(databaseType, connectionString));
|
||||
return new SqliteDatabaseContext(
|
||||
Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<SqliteDatabaseContext>(
|
||||
databaseType,
|
||||
connectionString,
|
||||
serverVersion));
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -180,29 +199,7 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
|
||||
using var server = new TestingServer();
|
||||
|
||||
using var hardTimeoutCts = new CancellationTokenSource();
|
||||
|
||||
var maximumTestDuration = new TimeSpan(0, 12, 0);
|
||||
|
||||
hardTimeoutCts.CancelAfter(maximumTestDuration - new TimeSpan(0, 0, 15));
|
||||
var hardTimeoutCancellationToken = hardTimeoutCts.Token;
|
||||
hardTimeoutCancellationToken.Register(() =>
|
||||
{
|
||||
Console.WriteLine($"[{DateTimeOffset.Now}] TEST TIMEOUT HARD!");
|
||||
});
|
||||
|
||||
using var softTimeoutCts = CancellationTokenSource.CreateLinkedTokenSource(hardTimeoutCancellationToken);
|
||||
softTimeoutCts.CancelAfter(maximumTestDuration - new TimeSpan(0, 0, 45));
|
||||
var softTimeoutCancellationToken = softTimeoutCts.Token;
|
||||
bool tooLateForSoftTimeout = false;
|
||||
softTimeoutCancellationToken.Register(() =>
|
||||
{
|
||||
if (!tooLateForSoftTimeout)
|
||||
Console.WriteLine($"[{DateTimeOffset.Now}] TEST TIMEOUT SOFT!");
|
||||
});
|
||||
|
||||
using var serverCts = CancellationTokenSource.CreateLinkedTokenSource(softTimeoutCancellationToken);
|
||||
using var serverCts = new CancellationTokenSource();
|
||||
var cancellationToken = serverCts.Token;
|
||||
|
||||
TerminateAllDDs();
|
||||
@@ -258,7 +255,7 @@ namespace Tgstation.Server.Tests
|
||||
await adminClient.Administration.Restart(cancellationToken);
|
||||
}
|
||||
|
||||
await Task.WhenAny(serverTask, Task.Delay(30000, cancellationToken));
|
||||
await Task.WhenAny(serverTask, Task.Delay(TimeSpan.FromMinutes(1), cancellationToken));
|
||||
Assert.IsTrue(serverTask.IsCompleted);
|
||||
|
||||
// http bind test https://github.com/tgstation/tgstation-server/issues/1065
|
||||
@@ -278,7 +275,7 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
}
|
||||
|
||||
await Task.WhenAny(serverTask, Task.Delay(30000, cancellationToken));
|
||||
await Task.WhenAny(serverTask, Task.Delay(TimeSpan.FromMinutes(1), cancellationToken));
|
||||
Assert.IsTrue(serverTask.IsCompleted);
|
||||
|
||||
var preStartupTime = DateTimeOffset.Now;
|
||||
@@ -303,12 +300,12 @@ namespace Tgstation.Server.Tests
|
||||
.ToList();
|
||||
}
|
||||
|
||||
Assert.AreEqual(1, jobs.Count, $"Why are there multiple active jobs? \"{String.Join("\", \"", jobs.Select(x => x.Description))}\"");
|
||||
|
||||
var reattachJob = jobs.Single();
|
||||
Assert.IsTrue(reattachJob.StartedAt.Value >= preStartupTime);
|
||||
|
||||
await new JobsRequiredTest(instanceClient.Jobs).WaitForJob(reattachJob, 40, false, null, cancellationToken);
|
||||
var jrt = new JobsRequiredTest(instanceClient.Jobs);
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
Assert.IsTrue(job.StartedAt.Value >= preStartupTime);
|
||||
await jrt.WaitForJob(job, 40, false, null, cancellationToken);
|
||||
}
|
||||
|
||||
var dd = await instanceClient.DreamDaemon.Read(cancellationToken);
|
||||
Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value);
|
||||
@@ -322,7 +319,7 @@ namespace Tgstation.Server.Tests
|
||||
await adminClient.Administration.Restart(cancellationToken);
|
||||
}
|
||||
|
||||
await Task.WhenAny(serverTask, Task.Delay(30000, cancellationToken));
|
||||
await Task.WhenAny(serverTask, Task.Delay(TimeSpan.FromMinutes(1), cancellationToken));
|
||||
Assert.IsTrue(serverTask.IsCompleted);
|
||||
|
||||
preStartupTime = DateTimeOffset.Now;
|
||||
@@ -376,18 +373,21 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
finally
|
||||
{
|
||||
tooLateForSoftTimeout = true;
|
||||
serverCts.Cancel();
|
||||
|
||||
// Give the test 1 minute to cleanup
|
||||
using var hardTimeoutCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(1));
|
||||
try
|
||||
{
|
||||
await serverTask.WithToken(hardTimeoutCancellationToken).ConfigureAwait(false);
|
||||
await serverTask.WithToken(hardTimeoutCancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
|
||||
TerminateAllDDs();
|
||||
|
||||
hardTimeoutCancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
Assert.IsTrue(serverTask.IsCompleted);
|
||||
await serverTask;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
Reference in New Issue
Block a user