diff --git a/src/InEngine.Commands.Test/InEngine.Commands.Test.csproj b/src/InEngine.Commands.Test/InEngine.Commands.Test.csproj new file mode 100644 index 0000000..e48d527 --- /dev/null +++ b/src/InEngine.Commands.Test/InEngine.Commands.Test.csproj @@ -0,0 +1,25 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + + + + + diff --git a/src/InEngine.Commands.Test/Sample/MinimalTest.cs b/src/InEngine.Commands.Test/Sample/MinimalTest.cs new file mode 100644 index 0000000..2f5f712 --- /dev/null +++ b/src/InEngine.Commands.Test/Sample/MinimalTest.cs @@ -0,0 +1,22 @@ +using InEngine.Commands.Sample; +using InEngine.Core.IO; +using InEngineTesting; +using Moq; + +namespace InEngine.Commands.Test.Sample; + +public class MinimalTest : TestBase +{ + [Test] + public async Task ShouldSucceed() + { + const string expected = "This is an example of a minimal command."; + var mockWrite = new Mock(); + Subject.Write = mockWrite.Object; + + await Subject.RunAsync(); + + mockWrite.Verify(x => x.Line(expected), Times.Once()); + Assert.Pass(); + } +} \ No newline at end of file diff --git a/src/InEngine.Commands.Test/Sample/SayHelloTest.cs b/src/InEngine.Commands.Test/Sample/SayHelloTest.cs new file mode 100644 index 0000000..faa9fd7 --- /dev/null +++ b/src/InEngine.Commands.Test/Sample/SayHelloTest.cs @@ -0,0 +1,22 @@ +using InEngine.Commands.Sample; +using InEngine.Core.IO; +using InEngineTesting; +using Moq; + +namespace InEngine.Commands.Test.Sample; + +public class SayHelloTest : TestBase +{ + [Test] + public async Task ShouldSayHelloTest() + { + const string expected = "hello"; + var mockWrite = new Mock(); + Subject.Write = mockWrite.Object; + + await Subject.RunAsync(); + + mockWrite.Verify(x => x.Line(expected), Times.Once()); + Assert.Pass(); + } +} \ No newline at end of file diff --git a/src/InEngine.Commands.Test/Usings.cs b/src/InEngine.Commands.Test/Usings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/src/InEngine.Commands.Test/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/src/InEngine.Commands/CommandsPlugin.cs b/src/InEngine.Commands/CommandsPlugin.cs index 9714d09..c69a472 100644 --- a/src/InEngine.Commands/CommandsPlugin.cs +++ b/src/InEngine.Commands/CommandsPlugin.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using CommandLine; using InEngine.Core; using InEngine.Core.Commands; using InEngine.Core.Scheduling; @@ -8,12 +7,6 @@ namespace InEngine.Commands; public class CommandsPlugin : AbstractPlugin { - [VerbOption("fail", HelpText = "Always fail. Useful for end-to-end testing.")] - public AlwaysFail AlwaysFail { get; set; } - - [VerbOption("succeed", HelpText = "A null operation command. Literally does nothing.")] - public AlwaysSucceed Null { get; set; } - public override void Schedule(ISchedule schedule) { schedule.Command(new Echo { VerbatimText = "Core Echo command." }) diff --git a/src/InEngine.Commands/InEngine.Commands.csproj b/src/InEngine.Commands/InEngine.Commands.csproj index e2e5e00..52f67ca 100644 --- a/src/InEngine.Commands/InEngine.Commands.csproj +++ b/src/InEngine.Commands/InEngine.Commands.csproj @@ -4,7 +4,7 @@ 5.0.0 - 4.0.0 + 5.0.0 5.0.0 Ethan Hann Plugin-based queuing and scheduling command server. diff --git a/src/InEngine.Commands/Sample/ShowProgress.cs b/src/InEngine.Commands/Sample/ShowProgress.cs index e9bd69b..01c331f 100644 --- a/src/InEngine.Commands/Sample/ShowProgress.cs +++ b/src/InEngine.Commands/Sample/ShowProgress.cs @@ -3,16 +3,11 @@ namespace InEngine.Commands.Sample; -/* - * The AbstractCommand class adds functionality, including a logger and a - * progress bar. - */ +/// +/// The AbstractCommand class adds functionality, including a logger and a progress bar. +/// public class ShowProgress : AbstractCommand { - /* - * Note that the override keyword is necessary in the Run method - * signature as the base class method is virtual. - */ public override async Task RunAsync() { // Define the ticks (aka steps) for the command... diff --git a/src/InEngine.Core.Test/Commands/AlwaysFailTest.cs b/src/InEngine.Core.Test/Commands/AlwaysFailTest.cs new file mode 100644 index 0000000..a5a92db --- /dev/null +++ b/src/InEngine.Core.Test/Commands/AlwaysFailTest.cs @@ -0,0 +1,20 @@ +using InEngine.Core.Commands; +using InEngine.Core.Exceptions; +using InEngineTesting; + +namespace InEngine.Core.Test.Commands; + +public class AlwaysFailTest : TestBase +{ + [Test] + public void ShouldFailWithException() + { + Assert.ThrowsAsync(async () => await Subject.RunAsync()); + } + + [Test] + public void ShouldFailWithExceptionWhenRunWithLifeCycleMethods() + { + Assert.ThrowsAsync(async () => await Subject.RunWithLifeCycleAsync()); + } +} \ No newline at end of file diff --git a/src/InEngine.Core.Test/Commands/AlwaysSucceedTest.cs b/src/InEngine.Core.Test/Commands/AlwaysSucceedTest.cs new file mode 100644 index 0000000..2bd7b3c --- /dev/null +++ b/src/InEngine.Core.Test/Commands/AlwaysSucceedTest.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using InEngine.Core.Commands; +using InEngine.Core.IO; +using InEngineTesting; +using Moq; + +namespace InEngine.Core.Test.Commands; + +public class AlwaysSucceedTest : TestBase +{ + [Test] + public async Task ShouldSucceed() + { + const string expected = "This command always succeeds."; + var mockWrite = new Mock(); + Subject.Write = mockWrite.Object; + + await Subject.RunAsync(); + + mockWrite.Verify(x => x.Info(expected), Times.Once()); + Assert.Pass(); + } +} \ No newline at end of file diff --git a/src/InEngine.Core.Test/Commands/ChainTest.cs b/src/InEngine.Core.Test/Commands/ChainTest.cs index 12eb97f..8318b85 100644 --- a/src/InEngine.Core.Test/Commands/ChainTest.cs +++ b/src/InEngine.Core.Test/Commands/ChainTest.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; -using InEngine.Commands; using InEngine.Core.Commands; using InEngine.Core.Exceptions; +using System.Threading.Tasks; +using InEngineTesting; using Moq; -using NUnit.Framework; namespace InEngine.Core.Test.Commands; @@ -16,7 +16,7 @@ public void Setup() } [Test] - public void ShouldRunChainOfCommands() + public async Task ShouldRunChainOfCommands() { var mockCommand1 = new Mock(); var mockCommand2 = new Mock(); @@ -27,7 +27,7 @@ public void ShouldRunChainOfCommands() }; Subject.Commands = commands; - Subject.RunAsync(); + await Subject.RunAsync(); mockCommand1.Verify(x => x.RunAsync(), Times.Once()); mockCommand2.Verify(x => x.RunAsync(), Times.Once()); @@ -47,26 +47,26 @@ public void ShouldRunChainOfCommandsAndFail() }; Subject.Commands = commands; - Assert.That(Subject.RunAsync, Throws.TypeOf()); + Assert.ThrowsAsync(async () => await Subject.RunAsync()); mockCommand1.Verify(x => x.RunAsync(), Times.Once()); mockCommand2.Verify(x => x.RunAsync(), Times.Never()); } [Test] - public void ShouldRunChainOfDifferentCommands() + public async Task ShouldRunChainOfDifferentCommands() { Subject.Commands = new List { new AlwaysSucceed(), - new Echo() { VerbatimText = "Hello, world!" }, + new Echo { VerbatimText = "Hello, world!" }, }; - Subject.RunAsync(); + await Subject.RunAsync(); } [Test] - public void ShouldRunChainOfDifferentCommandsAsAbstractCommand() + public async Task ShouldRunChainOfDifferentCommandsAsAbstractCommand() { Subject.Commands = new AbstractCommand[] { @@ -74,6 +74,6 @@ public void ShouldRunChainOfDifferentCommandsAsAbstractCommand() new Echo(verbatimText: "Hello, world!"), }; - Subject.RunAsync(); + await Subject.RunAsync(); } } \ No newline at end of file diff --git a/src/InEngine.Core.Test/InEngine.Core.Test.csproj b/src/InEngine.Core.Test/InEngine.Core.Test.csproj index d3323bc..1d2136b 100644 --- a/src/InEngine.Core.Test/InEngine.Core.Test.csproj +++ b/src/InEngine.Core.Test/InEngine.Core.Test.csproj @@ -6,7 +6,7 @@ - 4.0.0 + 5.0.0 5.0.0 Ethan Hann Plugin-based queuing and scheduling command server. @@ -15,14 +15,15 @@ - - - + + + + diff --git a/src/InEngine.Core.Test/Queuing/Commands/ConsumeTest.cs b/src/InEngine.Core.Test/Queuing/Commands/ConsumeTest.cs index 981da6c..d87c557 100644 --- a/src/InEngine.Core.Test/Queuing/Commands/ConsumeTest.cs +++ b/src/InEngine.Core.Test/Queuing/Commands/ConsumeTest.cs @@ -1,11 +1,12 @@ using InEngine.Commands; using InEngine.Core.Queuing.Commands; using Moq; -using NUnit.Framework; using Quartz; namespace InEngine.Core.Test.Queuing.Commands; +using InEngineTesting; + [TestFixture] public class ConsumeTest : TestBase { diff --git a/src/InEngine.Core.Test/Queuing/Commands/PublishTest.cs b/src/InEngine.Core.Test/Queuing/Commands/PublishTest.cs index 7a98e84..fe5228e 100644 --- a/src/InEngine.Core.Test/Queuing/Commands/PublishTest.cs +++ b/src/InEngine.Core.Test/Queuing/Commands/PublishTest.cs @@ -3,8 +3,8 @@ using InEngine.Commands; using InEngine.Core.Commands; using InEngine.Core.Exceptions; +using InEngineTesting; using InEngine.Core.Queuing.Commands; -using NUnit.Framework; namespace InEngine.Core.Test.Queuing.Commands; diff --git a/src/InEngine.Core.Test/Queuing/Enqueue.cs b/src/InEngine.Core.Test/Queuing/Enqueue.cs index 0a94f06..0b93205 100644 --- a/src/InEngine.Core.Test/Queuing/Enqueue.cs +++ b/src/InEngine.Core.Test/Queuing/Enqueue.cs @@ -4,7 +4,6 @@ using InEngine.Core.Commands; using InEngine.Core.Queuing; using Moq; -using NUnit.Framework; using Serialize.Linq.Extensions; namespace InEngine.Core.Test.Queuing; diff --git a/src/InEngine.Core.Test/Queuing/QueueAdapterTest.cs b/src/InEngine.Core.Test/Queuing/QueueAdapterTest.cs index b295500..423d07e 100644 --- a/src/InEngine.Core.Test/Queuing/QueueAdapterTest.cs +++ b/src/InEngine.Core.Test/Queuing/QueueAdapterTest.cs @@ -3,8 +3,8 @@ using InEngine.Commands; using InEngine.Core.Commands; using InEngine.Core.Queuing; +using InEngineTesting; using Moq; -using NUnit.Framework; using Serialize.Linq.Extensions; namespace InEngine.Core.Test.Queuing; diff --git a/src/InEngine.Core.Test/Scheduling/ScheduleTest.cs b/src/InEngine.Core.Test/Scheduling/ScheduleTest.cs index a950e0f..91f2919 100644 --- a/src/InEngine.Core.Test/Scheduling/ScheduleTest.cs +++ b/src/InEngine.Core.Test/Scheduling/ScheduleTest.cs @@ -1,7 +1,7 @@ using System; -using InEngine.Commands; +using InEngine.Core.Commands; using InEngine.Core.Scheduling; -using NUnit.Framework; +using InEngineTesting; namespace InEngine.Core.Test.Scheduling; diff --git a/src/InEngine.Core.Test/Usings.cs b/src/InEngine.Core.Test/Usings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/src/InEngine.Core.Test/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/src/InEngine.Core/AbstractCommand.cs b/src/InEngine.Core/AbstractCommand.cs index 7a93485..1ee59bb 100644 --- a/src/InEngine.Core/AbstractCommand.cs +++ b/src/InEngine.Core/AbstractCommand.cs @@ -9,6 +9,7 @@ namespace InEngine.Core; +using System.Threading; using Microsoft.Extensions.Logging; public abstract class AbstractCommand : IJob, IConsoleWrite, IHasCommandLifeCycle, IHasMailSettings @@ -16,7 +17,7 @@ public abstract class AbstractCommand : IJob, IConsoleWrite, IHasCommandLifeCycl protected readonly ILogger Log; public CommandLifeCycle CommandLifeCycle { get; set; } = new CommandLifeCycle(); - public Write Write { get; set; } = new Write(); + public IConsoleWrite Write { get; set; } = new Write(); public ProgressBar ProgressBar { get; internal set; } public string Name { get; set; } public string SchedulerGroup { get; set; } @@ -43,9 +44,13 @@ public virtual void Run() { } - public virtual async Task RunAsync() => await Task.Run(Run).ConfigureAwait(false); - - public virtual async Task RunWithLifeCycle() + public virtual async Task RunAsync() + { + Run(); + await Task.CompletedTask; + } + + public virtual async Task RunWithLifeCycleAsync() { try { @@ -54,9 +59,16 @@ public virtual async Task RunWithLifeCycle() await RunAsync(); else { - var task = Task.Run(RunAsync); - if (!task.Wait(TimeSpan.FromSeconds(SecondsBeforeTimeout))) - throw new Exception($"Scheduled command timed out after {SecondsBeforeTimeout} second(s)."); + var timeoutSignal = new CancellationTokenSource(TimeSpan.FromSeconds(SecondsBeforeTimeout)); + try + { + await RunAsync().WaitAsync(timeoutSignal.Token).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw new CommandFailedException( + $"Scheduled command timed out after {SecondsBeforeTimeout} second(s)."); + } } CommandLifeCycle.FirePostActions(this); @@ -97,23 +109,43 @@ public virtual async Task Execute(IJobExecutionContext context) }); } - await RunWithLifeCycle(); + await RunWithLifeCycleAsync(); } #endregion - #region Console output + #region Console Output public IConsoleWrite Info(object val) => Write.Info(val); public IConsoleWrite Warning(object val) => Write.Warning(val); public IConsoleWrite Error(object val) => Write.Error(val); public IConsoleWrite Line(object val) => Write.Line(val); - public IConsoleWrite ColoredLine(object val, ConsoleColor consoleColor) => Write.ColoredLine(val, consoleColor); + public IConsoleWrite LineWithColor(object val, ConsoleColor consoleColor) => Write.LineWithColor(val, consoleColor); public IConsoleWrite InfoText(object val) => Write.InfoText(val); public IConsoleWrite WarningText(object val) => Write.WarningText(val); public IConsoleWrite ErrorText(object val) => Write.ErrorText(val); public IConsoleWrite Text(object val) => Write.Text(val); - public IConsoleWrite ColoredText(object val, ConsoleColor consoleColor) => Write.ColoredText(val, consoleColor); + + public IConsoleWrite TextWithColor(object val, ConsoleColor consoleColor, bool writeLine) => + Write.TextWithColor(val, consoleColor, writeLine); + + public async Task NewlineAsync(int count = 1) => await Write.NewlineAsync(count); + public async Task InfoAsync(object val) => await Write.InfoAsync(val); + public async Task WarningAsync(object val) => await Write.WarningAsync(val); + public async Task ErrorAsync(object val) => await Write.ErrorAsync(val); + public async Task LineAsync(object val) => await Write.LineAsync(val); + + public async Task LineWithColorAsync(object val, ConsoleColor consoleColor) => + await Write.LineWithColorAsync(val, consoleColor); + + public async Task InfoTextAsync(object val) => await Write.InfoTextAsync(val); + public async Task WarningTextAsync(object val) => await Write.WarningTextAsync(val); + public async Task ErrorTextAsync(object val) => await Write.ErrorTextAsync(val); + public async Task TextAsync(object val) => await Write.TextAsync(val); + + public async Task TextWithColorAsync(object val, ConsoleColor consoleColor, bool writeLine = false) => + await Write.TextWithColorAsync(val, consoleColor, writeLine); + public IConsoleWrite Newline(int count = 1) => Write.Newline(count); public string FlushBuffer() => Write.FlushBuffer(); public void ToFile(string path, string text, bool shouldAppend = false) => Write.ToFile(path, text, shouldAppend); diff --git a/src/InEngine.Commands/AlwaysFail.cs b/src/InEngine.Core/Commands/AlwaysFail.cs similarity index 59% rename from src/InEngine.Commands/AlwaysFail.cs rename to src/InEngine.Core/Commands/AlwaysFail.cs index b052d7f..00ac345 100644 --- a/src/InEngine.Commands/AlwaysFail.cs +++ b/src/InEngine.Core/Commands/AlwaysFail.cs @@ -1,15 +1,18 @@ using System; -using InEngine.Core; +using System.Threading.Tasks; using InEngine.Core.Exceptions; -namespace InEngine.Commands; +namespace InEngine.Core.Commands; /// -/// Dummy command for testing and sample code. +/// Dummy command for testing. /// public class AlwaysFail : AbstractCommand { - public override void Run() => throw new CommandFailedException("This command always fails."); + public override async Task RunAsync() + { + throw new CommandFailedException("This command always fails."); + } public override void Failed(Exception exception) { diff --git a/src/InEngine.Commands/AlwaysSucceed.cs b/src/InEngine.Core/Commands/AlwaysSucceed.cs similarity index 59% rename from src/InEngine.Commands/AlwaysSucceed.cs rename to src/InEngine.Core/Commands/AlwaysSucceed.cs index a288983..0d9971f 100644 --- a/src/InEngine.Commands/AlwaysSucceed.cs +++ b/src/InEngine.Core/Commands/AlwaysSucceed.cs @@ -1,9 +1,7 @@ -using InEngine.Core; - -namespace InEngine.Commands; +namespace InEngine.Core.Commands; /// -/// Dummy command for testing and sample code. +/// Dummy command for testing. /// public class AlwaysSucceed : AbstractCommand { diff --git a/src/InEngine.Core/Commands/CommandsPlugin.cs b/src/InEngine.Core/Commands/CommandsPlugin.cs index 21d77e4..764ee7e 100644 --- a/src/InEngine.Core/Commands/CommandsPlugin.cs +++ b/src/InEngine.Core/Commands/CommandsPlugin.cs @@ -9,4 +9,13 @@ public class CommandPlugin : AbstractPlugin [VerbOption("exec", HelpText = "Execute an external program.")] public Exec Exec { get; set; } + + [VerbOption("sleep", HelpText = "Sleep (in seconds)")] + public Sleep Sleep { get; set; } + + [VerbOption("fail", HelpText = "Always fail. Useful for end-to-end testing.")] + public AlwaysFail AlwaysFail { get; set; } + + [VerbOption("succeed", HelpText = "A null operation command. Literally does nothing.")] + public AlwaysSucceed AlwaysSucceed { get; set; } } \ No newline at end of file diff --git a/src/InEngine.Core/Commands/Echo.cs b/src/InEngine.Core/Commands/Echo.cs index 8f644ed..e80c73d 100644 --- a/src/InEngine.Core/Commands/Echo.cs +++ b/src/InEngine.Core/Commands/Echo.cs @@ -1,4 +1,6 @@ -using CommandLine; +using System; +using System.Threading.Tasks; +using CommandLine; namespace InEngine.Core.Commands; @@ -16,5 +18,5 @@ public Echo() [Option("text", HelpText = "The text to echo.")] public string VerbatimText { get; init; } - public override void Run() => Line(VerbatimText); + public override async Task RunAsync() => await LineAsync(VerbatimText); } \ No newline at end of file diff --git a/src/InEngine.Core/Commands/Sleep.cs b/src/InEngine.Core/Commands/Sleep.cs index 49334ba..8845e0e 100644 --- a/src/InEngine.Core/Commands/Sleep.cs +++ b/src/InEngine.Core/Commands/Sleep.cs @@ -3,14 +3,17 @@ namespace InEngine.Core.Commands; +using CommandLine; + public class Sleep : AbstractCommand { - public int MillisecondsTimeout { get; set; } + [Option("duration", HelpText = "The number of seconds to sleep.")] + public int DurationInSeconds { get; set; } = 3; - public override void Run() + public override async Task RunAsync() { - Warning("Going to sleep..."); - Thread.Sleep(MillisecondsTimeout); - Info("Done sleeping!"); + await WarningAsync("Going to sleep..."); + Thread.Sleep(DurationInSeconds * 1000); + await InfoAsync("Done sleeping!"); } } \ No newline at end of file diff --git a/src/InEngine.Core/IO/IConsoleWrite.cs b/src/InEngine.Core/IO/IConsoleWrite.cs new file mode 100644 index 0000000..80f2369 --- /dev/null +++ b/src/InEngine.Core/IO/IConsoleWrite.cs @@ -0,0 +1,45 @@ +using System; + +namespace InEngine.Core.IO; + +using System.Threading.Tasks; + +public interface IConsoleWrite +{ + #region Sync Methods + + IConsoleWrite Newline(int count = 1); + IConsoleWrite Info(object val); + IConsoleWrite Warning(object val); + IConsoleWrite Error(object val); + IConsoleWrite Line(object val); + IConsoleWrite LineWithColor(object val, ConsoleColor consoleColor); + + IConsoleWrite InfoText(object val); + IConsoleWrite WarningText(object val); + IConsoleWrite ErrorText(object val); + IConsoleWrite Text(object val); + IConsoleWrite TextWithColor(object val, ConsoleColor consoleColor, bool writeLine = false); + + #endregion + + #region Async Methods + + Task NewlineAsync(int count = 1); + Task InfoAsync(object val); + Task WarningAsync(object val); + Task ErrorAsync(object val); + Task LineAsync(object val); + Task LineWithColorAsync(object val, ConsoleColor consoleColor); + + Task InfoTextAsync(object val); + Task WarningTextAsync(object val); + Task ErrorTextAsync(object val); + Task TextAsync(object val); + Task TextWithColorAsync(object val, ConsoleColor consoleColor, bool writeLine = false); + + #endregion + + string FlushBuffer(); + void ToFile(string path, string text, bool shouldAppend = false); +} \ No newline at end of file diff --git a/src/InEngine.Core/IO/IWrite.cs b/src/InEngine.Core/IO/IWrite.cs deleted file mode 100644 index 8155666..0000000 --- a/src/InEngine.Core/IO/IWrite.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace InEngine.Core.IO; - -public interface IConsoleWrite -{ - IConsoleWrite Newline(int count = 1); - IConsoleWrite Info(object val); - IConsoleWrite Warning(object val); - IConsoleWrite Error(object val); - IConsoleWrite Line(object val); - IConsoleWrite ColoredLine(object val, ConsoleColor consoleColor); - - IConsoleWrite InfoText(object val); - IConsoleWrite WarningText(object val); - IConsoleWrite ErrorText(object val); - IConsoleWrite Text(object val); - IConsoleWrite ColoredText(object val, ConsoleColor consoleColor); - - string FlushBuffer(); - void ToFile(string path, string text, bool shouldAppend = false); -} \ No newline at end of file diff --git a/src/InEngine.Core/IO/Write.cs b/src/InEngine.Core/IO/Write.cs index 5ddaea4..8622c9e 100644 --- a/src/InEngine.Core/IO/Write.cs +++ b/src/InEngine.Core/IO/Write.cs @@ -2,29 +2,29 @@ using System.Collections.Generic; using System.IO; using System.Threading; +using System.Threading.Tasks; namespace InEngine.Core.IO; public class Write : IConsoleWrite { - static readonly Mutex consoleOutputLock = new Mutex(); - static readonly Mutex fileOutputLock = new Mutex(); + private static readonly Mutex ConsoleOutputLock = new(); + public static readonly Mutex FileOutputLock = new(); public ConsoleColor InfoColor { get; set; } = ConsoleColor.Green; public ConsoleColor WarningColor { get; set; } = ConsoleColor.Yellow; public ConsoleColor ErrorColor { get; set; } = ConsoleColor.Red; public ConsoleColor LineColor { get; set; } = ConsoleColor.White; - public List Buffer { get; set; } = new List(); + public List Buffer { get; set; } = new(); public bool IsBufferEnabled { get; set; } public Write() : this(true) { } - public Write(bool isBufferEnabled) - { - IsBufferEnabled = isBufferEnabled; - } + public Write(bool isBufferEnabled) => IsBufferEnabled = isBufferEnabled; + + #region Sync Methods public IConsoleWrite Newline(int count = 1) { @@ -33,77 +33,90 @@ public IConsoleWrite Newline(int count = 1) return this; } - public IConsoleWrite Info(object val) - { - return ColoredLine(val, InfoColor); - } + public IConsoleWrite Info(object val) => LineWithColor(val, InfoColor); + public IConsoleWrite Error(object val) => LineWithColor(val, ErrorColor); + public IConsoleWrite Warning(object val) => LineWithColor(val, WarningColor); + public IConsoleWrite Line(object val) => LineWithColor(val, LineColor); - public IConsoleWrite Error(object val) + public IConsoleWrite LineWithColor(object val, ConsoleColor consoleColor) { - return ColoredLine(val, ErrorColor); + TextWithColor(val, consoleColor, true); + return this; } - public IConsoleWrite Warning(object val) - { - return ColoredLine(val, WarningColor); - } + public IConsoleWrite InfoText(object val) => TextWithColor(val, InfoColor); + public IConsoleWrite ErrorText(object val) => TextWithColor(val, ErrorColor); + public IConsoleWrite WarningText(object val) => TextWithColor(val, WarningColor); + public IConsoleWrite Text(object val) => TextWithColor(val, LineColor); - public IConsoleWrite Line(object val) + public IConsoleWrite TextWithColor(object val, ConsoleColor consoleColor, bool writeLine = false) { - return ColoredLine(val, LineColor); - } + var text = BeginWriting(val, consoleColor); - public IConsoleWrite ColoredLine(object val, ConsoleColor consoleColor) - { - WriteColoredLineOrText(val, consoleColor, true); + if (writeLine) + Console.WriteLine(val); + else + Console.Write(val); + + EndWriting(text, writeLine); return this; } - public IConsoleWrite InfoText(object val) - { - return ColoredText(val, InfoColor); - } + #endregion - public IConsoleWrite ErrorText(object val) - { - return ColoredText(val, ErrorColor); - } + #region Async Methods - public IConsoleWrite WarningText(object val) + public async Task NewlineAsync(int count = 1) { - return ColoredText(val, WarningColor); + for (var i = 0; i < count; i++) + await Console.Out.WriteLineAsync(); } - public IConsoleWrite Text(object val) + public async Task InfoAsync(object val) => await LineWithColorAsync(val, InfoColor); + public async Task ErrorAsync(object val) => await LineWithColorAsync(val, ErrorColor); + public async Task WarningAsync(object val) => await LineWithColorAsync(val, WarningColor); + public async Task LineAsync(object val) => await LineWithColorAsync(val, LineColor); + + public async Task LineWithColorAsync(object val, ConsoleColor consoleColor) => + await TextWithColorAsync(val, consoleColor, true); + + public async Task InfoTextAsync(object val) => await TextWithColorAsync(val, InfoColor); + public async Task ErrorTextAsync(object val) => await TextWithColorAsync(val, ErrorColor); + public async Task WarningTextAsync(object val) => await TextWithColorAsync(val, WarningColor); + public async Task TextAsync(object val) => await TextWithColorAsync(val, LineColor); + + public async Task TextWithColorAsync(object val, ConsoleColor consoleColor, bool writeLine = false) { - return ColoredText(val, LineColor); + var text = BeginWriting(val, consoleColor); + + if (writeLine) + await Console.Out.WriteLineAsync(text); + else + await Console.Out.WriteAsync(text); + + EndWriting(text, writeLine); } - public IConsoleWrite ColoredText(object val, ConsoleColor consoleColor) + #endregion + + protected string BeginWriting(object val, ConsoleColor consoleColor) { - WriteColoredLineOrText(val, consoleColor, false); - return this; + ConsoleOutputLock.WaitOne(); + Console.ForegroundColor = consoleColor; + return val?.ToString() ?? string.Empty; } - void WriteColoredLineOrText(object val, ConsoleColor consoleColor, bool writeLine) + protected void EndWriting(string text, bool writeLine) { - if (val == null) - val = String.Empty; - consoleOutputLock.WaitOne(); - Console.ForegroundColor = consoleColor; - if (writeLine) - Console.WriteLine(val); - else - Console.Write(val); Console.ResetColor(); if (IsBufferEnabled) { - Buffer.Add(val.ToString()); + Buffer.Add(text); if (writeLine) Buffer.Add(Environment.NewLine); } - consoleOutputLock.ReleaseMutex(); + ConsoleOutputLock.ReleaseMutex(); } public string FlushBuffer() @@ -115,12 +128,12 @@ public string FlushBuffer() public void ToFile(string path, string text, bool shouldAppend = false) { - fileOutputLock.WaitOne(); + FileOutputLock.WaitOne(); if (shouldAppend) File.AppendAllText(path, text); else File.WriteAllText(path, text); - fileOutputLock.ReleaseMutex(); + FileOutputLock.ReleaseMutex(); } } \ No newline at end of file diff --git a/src/InEngine.Core/InEngine.Core.csproj b/src/InEngine.Core/InEngine.Core.csproj index 8d5bc71..afdd327 100644 --- a/src/InEngine.Core/InEngine.Core.csproj +++ b/src/InEngine.Core/InEngine.Core.csproj @@ -3,7 +3,7 @@ net6.0 - 4.0.1 + 5.0.0 5.0.0 Ethan Hann Plugin-based queuing and scheduling command server. @@ -27,7 +27,7 @@ - + diff --git a/src/InEngine.Core/Queuing/Clients/FileClient.cs b/src/InEngine.Core/Queuing/Clients/FileClient.cs index 1d9032d..06f7696 100644 --- a/src/InEngine.Core/Queuing/Clients/FileClient.cs +++ b/src/InEngine.Core/Queuing/Clients/FileClient.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using InEngine.Core.Exceptions; using InEngine.Core.IO; using InEngine.Core.Queuing.Message; @@ -55,7 +56,7 @@ private void PublishToQueue(CommandEnvelope commandEnvelope, string queuePath) ); } - public void Consume(CancellationToken cancellationToken) + public async Task Consume(CancellationToken cancellationToken) { try { @@ -63,8 +64,7 @@ public void Consume(CancellationToken cancellationToken) { try { - if (Consume() == null) - Thread.Sleep(5000); + await Consume(); } catch (Exception exception) { @@ -84,7 +84,7 @@ public void Consume(CancellationToken cancellationToken) } } - public ICommandEnvelope Consume() + public async Task Consume() { FileInfo fileInfo; var inProgressFilePath = string.Empty; @@ -107,7 +107,7 @@ public ICommandEnvelope Consume() ConsumeLock.ReleaseMutex(); - var commandEnvelope = File.ReadAllText(inProgressFilePath).DeserializeFromJson(); + var commandEnvelope = (await File.ReadAllTextAsync(inProgressFilePath)).DeserializeFromJson(); var command = commandEnvelope.GetCommandInstanceAndIncrementRetry(() => { File.Move(inProgressFilePath, Path.Combine(FailedQueuePath, fileInfo.Name)); @@ -116,7 +116,7 @@ public ICommandEnvelope Consume() try { command.WriteSummaryToConsole(); - command.RunWithLifeCycle().RunSynchronously(); + await command.RunWithLifeCycleAsync(); } catch (Exception exception) { diff --git a/src/InEngine.Core/Queuing/Clients/RabbitMQClient.cs b/src/InEngine.Core/Queuing/Clients/RabbitMQClient.cs index fd36aec..cb8c58c 100644 --- a/src/InEngine.Core/Queuing/Clients/RabbitMQClient.cs +++ b/src/InEngine.Core/Queuing/Clients/RabbitMQClient.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using System.Threading; +using System.Threading.Tasks; using InEngine.Core.Exceptions; using InEngine.Core.IO; using InEngine.Core.Queuing.Message; @@ -94,13 +95,18 @@ public void Recover() { } - public void Consume(CancellationToken cancellationToken) + public async Task Consume(CancellationToken cancellationToken) { InitChannel(); var consumer = new EventingBasicConsumer(Channel); - consumer.Received += (model, result) => + consumer.Received += async (model, result) => { var eventingConsumer = (EventingBasicConsumer)model; + if (eventingConsumer == null) + { + Log.LogWarning("EventingBasicConsumer is null while attempting to consume messages"); + return; + } var serializedMessage = Encoding.UTF8.GetString(result.Body); var commandEnvelope = serializedMessage.DeserializeFromJson(); @@ -115,7 +121,7 @@ public void Consume(CancellationToken cancellationToken) try { command.WriteSummaryToConsole(); - command.RunWithLifeCycle().RunSynchronously(); + await command.RunWithLifeCycleAsync(); } catch (Exception exception) { @@ -133,9 +139,11 @@ public void Consume(CancellationToken cancellationToken) eventingConsumer.Model.BasicAck(result.DeliveryTag, false); }; Channel.BasicConsume(queue: PendingQueueName, autoAck: false, consumer: consumer); + + await Task.Yield(); } - public ICommandEnvelope Consume() + public async Task Consume() { InitChannel(); var result = Channel.BasicGet(PendingQueueName, false); @@ -155,7 +163,7 @@ public ICommandEnvelope Consume() try { command.WriteSummaryToConsole(); - command.RunWithLifeCycle().RunSynchronously(); + await command.RunWithLifeCycleAsync(); } catch (Exception exception) { diff --git a/src/InEngine.Core/Queuing/Clients/RedisClient.cs b/src/InEngine.Core/Queuing/Clients/RedisClient.cs index f65069c..c40b55e 100644 --- a/src/InEngine.Core/Queuing/Clients/RedisClient.cs +++ b/src/InEngine.Core/Queuing/Clients/RedisClient.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using InEngine.Core.Exceptions; using InEngine.Core.IO; @@ -29,7 +30,7 @@ public class RedisClient : IQueueClient public string FailedQueueName => QueueBaseName + $":{QueueName}:{QueueNames.Failed}"; - public static Lazy lazyConnection = new Lazy(() => + public static readonly Lazy LazyConnection = new(() => { var redisConfig = ConfigurationOptions.Parse($"{ClientSettings.Host}:{ClientSettings.Port}"); redisConfig.Password = string.IsNullOrWhiteSpace(ClientSettings.Password) ? null : ClientSettings.Password; @@ -37,9 +38,8 @@ public class RedisClient : IQueueClient return ConnectionMultiplexer.Connect(redisConfig); }); - public static ConnectionMultiplexer Connection => lazyConnection.Value; + public static ConnectionMultiplexer Connection => LazyConnection.Value; - public ConnectionMultiplexer _connectionMultiplexer; private bool isDisposed; public IDatabase Redis => Connection.GetDatabase(ClientSettings.Database); @@ -82,17 +82,16 @@ public void Recover() PublishToChannel(); } - public void Consume(CancellationToken cancellationToken) + public async Task Consume(CancellationToken cancellationToken) { try { InitChannel(); - Connection.GetSubscriber().Subscribe(RedisChannel, - delegate - { - Task.Factory.StartNew(Consume, cancellationToken, TaskCreationOptions.LongRunning, - TaskScheduler.Default); - }); + var channelMessageQueue = await Connection.GetSubscriber().SubscribeAsync(RedisChannel); + channelMessageQueue.OnMessage(async _ => + { + await Consume(); + }); } catch (OperationCanceledException exception) { @@ -104,12 +103,11 @@ public void Consume(CancellationToken cancellationToken) } } - public ICommandEnvelope Consume() + public async Task Consume() { var rawRedisMessageValue = Redis.ListRightPopLeftPush(PendingQueueName, InProgressQueueName); var serializedMessage = rawRedisMessageValue.ToString(); - if (serializedMessage == null) - return null; + var commandEnvelope = serializedMessage.DeserializeFromJson(); if (commandEnvelope == null) throw new CommandFailedException("Could not deserialize the command."); @@ -122,7 +120,7 @@ public ICommandEnvelope Consume() try { command.WriteSummaryToConsole(); - command.RunWithLifeCycle().RunSynchronously(); + await command.RunWithLifeCycleAsync(); } catch (Exception exception) { @@ -189,11 +187,10 @@ public void Dispose() private void Dispose(bool disposing) { - if (isDisposed) + if (isDisposed) return; - if (!disposing) + if (!disposing) return; - _connectionMultiplexer?.Dispose(); isDisposed = true; } } \ No newline at end of file diff --git a/src/InEngine.Core/Queuing/Clients/SyncClient.cs b/src/InEngine.Core/Queuing/Clients/SyncClient.cs index c0758f6..340d076 100644 --- a/src/InEngine.Core/Queuing/Clients/SyncClient.cs +++ b/src/InEngine.Core/Queuing/Clients/SyncClient.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading; +using System.Threading.Tasks; using InEngine.Core.IO; using InEngine.Core.Queuing.Message; @@ -43,12 +44,12 @@ public void Recover() { } - public void Consume(CancellationToken cancellationToken) + public async Task Consume(CancellationToken cancellationToken) { throw new NotImplementedException(); } - public ICommandEnvelope Consume() + public async Task Consume() { throw new NotImplementedException(); } diff --git a/src/InEngine.Core/Queuing/Dequeue.cs b/src/InEngine.Core/Queuing/Dequeue.cs index 8e4939b..a72bbd9 100644 --- a/src/InEngine.Core/Queuing/Dequeue.cs +++ b/src/InEngine.Core/Queuing/Dequeue.cs @@ -25,29 +25,26 @@ public Dequeue() public async Task StartAsync() { - var allTasks = new List(); Log.LogDebug("Start dequeue tasks for primary queue..."); - allTasks.AddRange(MakeTasks(true, QueueSettings.PrimaryQueueConsumers)); + await AddConsumers(false, QueueSettings.PrimaryQueueConsumers); Log.LogDebug("Start dequeue tasks for secondary queue..."); - allTasks.AddRange(MakeTasks(false, QueueSettings.SecondaryQueueConsumers)); - await Task.WhenAll(allTasks); + await AddConsumers(true, QueueSettings.SecondaryQueueConsumers); // Recover from restart, if necessary. QueueAdapter.Make(false, QueueSettings, MailSettings).Recover(); QueueAdapter.Make(true, QueueSettings, MailSettings).Recover(); } - IList MakeTasks(bool useSecondaryQueue = false, int numberOfTasks = 0) + private async Task AddConsumers(bool useSecondaryQueue = false, int numberOfTasks = 0) { - return Enumerable.Range(0, numberOfTasks).Select((i) => { + for (var i = 0; i < numberOfTasks; i++) + { Log.LogDebug("Registering Dequeuer {I}", i); - return Task.Factory.StartNew(() => { - var queue = QueueAdapter.Make(useSecondaryQueue, QueueSettings, MailSettings); - queue.Id = i; - queueAdapters.Add(queue); - queue.Consume(CancellationTokenSource.Token); - }, TaskCreationOptions.LongRunning); - }).ToList(); + var queue = QueueAdapter.Make(useSecondaryQueue, QueueSettings, MailSettings); + queue.Id = i; + queueAdapters.Add(queue); + await queue.Consume(CancellationTokenSource.Token); + } } public void Dispose() diff --git a/src/InEngine.Core/Queuing/IQueueClient.cs b/src/InEngine.Core/Queuing/IQueueClient.cs index 99cd813..68b5035 100644 --- a/src/InEngine.Core/Queuing/IQueueClient.cs +++ b/src/InEngine.Core/Queuing/IQueueClient.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading; +using System.Threading.Tasks; using InEngine.Core.Queuing.Message; using InEngine.Core.IO; using Microsoft.Extensions.Logging; @@ -16,8 +17,8 @@ public interface IQueueClient : IHasMailSettings, IDisposable string QueueName { get; set; } bool UseCompression { get; set; } void Publish(AbstractCommand command); - void Consume(CancellationToken cancellationToken); - ICommandEnvelope Consume(); + Task Consume(CancellationToken cancellationToken); + Task Consume(); void Recover(); Dictionary GetQueueLengths(); bool ClearPendingQueue(); diff --git a/src/InEngine.Core/Queuing/Message/CommandEnvelope.cs b/src/InEngine.Core/Queuing/Message/CommandEnvelope.cs index 52e0309..70b26e9 100644 --- a/src/InEngine.Core/Queuing/Message/CommandEnvelope.cs +++ b/src/InEngine.Core/Queuing/Message/CommandEnvelope.cs @@ -24,7 +24,7 @@ public AbstractCommand GetCommandInstanceAndIncrementRetry(Action actionOnFail = } catch (Exception exception) { - actionOnFail.Invoke(); + actionOnFail?.Invoke(); throw new CommandNotExtractableFromEnvelopeException(CommandClassName, exception); } } diff --git a/src/InEngine.Core/Queuing/QueueAdapter.cs b/src/InEngine.Core/Queuing/QueueAdapter.cs index 2fb0bc8..15d8b27 100644 --- a/src/InEngine.Core/Queuing/QueueAdapter.cs +++ b/src/InEngine.Core/Queuing/QueueAdapter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading; +using System.Threading.Tasks; using InEngine.Core.IO; using InEngine.Core.Queuing.Clients; using InEngine.Core.Queuing.Message; @@ -89,8 +90,8 @@ public static QueueAdapter Make(bool useSecondaryQueue, QueueSettings queueSetti } public void Publish(AbstractCommand command) => QueueClient.Publish(command); - public void Consume(CancellationToken cancellationToken) => QueueClient.Consume(cancellationToken); - public ICommandEnvelope Consume() => QueueClient.Consume(); + public async Task Consume(CancellationToken cancellationToken) => await QueueClient.Consume(cancellationToken); + public async Task Consume() => await QueueClient.Consume(); public void Recover() => QueueClient.Recover(); public bool ClearPendingQueue() => QueueClient.ClearPendingQueue(); public bool ClearInProgressQueue() => QueueClient.ClearInProgressQueue(); diff --git a/src/InEngine.Core/ServerHost.cs b/src/InEngine.Core/ServerHost.cs index 15e87df..bddc7e6 100644 --- a/src/InEngine.Core/ServerHost.cs +++ b/src/InEngine.Core/ServerHost.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using InEngine.Core.IO; using InEngine.Core.Queuing; using InEngine.Core.Scheduling; @@ -13,7 +14,7 @@ public class ServerHost : IDisposable, IHasMailSettings, IHasQueueSettings public QueueSettings QueueSettings { get; set; } private bool isDisposed; - public void Start() + public async Task StartAsync() { SuperScheduler = new SuperScheduler(); SuperScheduler.Initialize(MailSettings); @@ -24,11 +25,9 @@ public void Start() }; SuperScheduler.Start(); - StartDequeueAsync(); + await Dequeue.StartAsync(); } - public async void StartDequeueAsync() => await Dequeue.StartAsync(); - public void Dispose() { Dispose(true); diff --git a/src/InEngine.Net.sln b/src/InEngine.Net.sln index 0872e7a..7be887f 100644 --- a/src/InEngine.Net.sln +++ b/src/InEngine.Net.sln @@ -16,6 +16,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InEngine.Commands", "InEngi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InEngine.Core.Test", "InEngine.Core.Test\InEngine.Core.Test.csproj", "{030204D0-6469-4A46-827E-122B0215F47E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InEngine.Commands.Test", "InEngine.Commands.Test\InEngine.Commands.Test.csproj", "{44C2AE5C-F3EB-4559-B712-534485AB18C1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InEngineTesting", "InEngineTesting\InEngineTesting.csproj", "{08FFCD33-8926-4E04-AC3B-91B6DFB71E61}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -38,6 +42,14 @@ Global {030204D0-6469-4A46-827E-122B0215F47E}.Debug|Any CPU.Build.0 = Debug|Any CPU {030204D0-6469-4A46-827E-122B0215F47E}.Release|Any CPU.ActiveCfg = Release|Any CPU {030204D0-6469-4A46-827E-122B0215F47E}.Release|Any CPU.Build.0 = Release|Any CPU + {44C2AE5C-F3EB-4559-B712-534485AB18C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44C2AE5C-F3EB-4559-B712-534485AB18C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44C2AE5C-F3EB-4559-B712-534485AB18C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44C2AE5C-F3EB-4559-B712-534485AB18C1}.Release|Any CPU.Build.0 = Release|Any CPU + {08FFCD33-8926-4E04-AC3B-91B6DFB71E61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08FFCD33-8926-4E04-AC3B-91B6DFB71E61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08FFCD33-8926-4E04-AC3B-91B6DFB71E61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08FFCD33-8926-4E04-AC3B-91B6DFB71E61}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/InEngine/ArgumentInterpreter.cs b/src/InEngine/ArgumentInterpreter.cs index edae81f..c7432c1 100644 --- a/src/InEngine/ArgumentInterpreter.cs +++ b/src/InEngine/ArgumentInterpreter.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using CommandLine; using InEngine.Core; using InEngine.Core.Exceptions; using InEngine.Core.IO; using InEngine.Core.Queuing; -using System.Resources; using Microsoft.Extensions.Logging; namespace InEngine; @@ -19,11 +19,10 @@ public class ArgumentInterpreter public ArgumentInterpreter() { - var resourceManager = new ResourceManager("InEngine.resources", typeof(ArgumentInterpreter).Assembly); - CliLogo = resourceManager.GetString("cliLogo"); + CliLogo = resources.cliLogo; } - public void Interpret(string[] args) + public async Task Interpret(string[] args) { var pluginAssemblies = PluginAssembly.Load(); var parser = new Parser(with => { @@ -44,7 +43,7 @@ public void Interpret(string[] args) { Write.Info(CliLogo); Write.Line("Starting...").Newline(); - Program.RunServer(); + await Program.RunServerAsync(); ExitWithSuccess(); } diff --git a/src/InEngine/InEngine.csproj b/src/InEngine/InEngine.csproj index 5ec28af..36d7ae1 100644 --- a/src/InEngine/InEngine.csproj +++ b/src/InEngine/InEngine.csproj @@ -6,7 +6,7 @@ - 4.0.0 + 5.0.0 5.0.0 Ethan Hann Plugin-based queuing and scheduling command server. diff --git a/src/InEngine/Program.cs b/src/InEngine/Program.cs index 806e8e2..63bd65b 100644 --- a/src/InEngine/Program.cs +++ b/src/InEngine/Program.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Threading.Tasks; using InEngine.Core; namespace InEngine; @@ -8,17 +9,17 @@ public static class Program { public static ServerHost ServerHost { get; set; } - private static void Main(string[] args) + private static async Task Main(string[] args) { /* * Set current working directory as services use the system directory by default. * Also, allow running the CLI from a different directory than the application root. */ Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); - new ArgumentInterpreter().Interpret(args); + await new ArgumentInterpreter().Interpret(args); } - public static void RunServer() + public static async Task RunServerAsync() { var settings = InEngineSettings.Make(); ServerHost = new ServerHost @@ -27,8 +28,8 @@ public static void RunServer() QueueSettings = settings.Queue, }; - ServerHost.Start(); - Console.WriteLine("Press any key to exit..."); + await ServerHost.StartAsync(); + Console.WriteLine(resources.foregroundServerInputPrompt); Console.ReadLine(); ServerHost.Dispose(); } diff --git a/src/InEngine/resources.Designer.cs b/src/InEngine/resources.Designer.cs index 0160ea5..3b1c984 100644 --- a/src/InEngine/resources.Designer.cs +++ b/src/InEngine/resources.Designer.cs @@ -50,5 +50,11 @@ internal static string cliLogo { return ResourceManager.GetString("cliLogo", resourceCulture); } } + + internal static string foregroundServerInputPrompt { + get { + return ResourceManager.GetString("foregroundServerInputPrompt", resourceCulture); + } + } } } diff --git a/src/InEngine/resources.resx b/src/InEngine/resources.resx index 10bffaa..8b5d142 100644 --- a/src/InEngine/resources.resx +++ b/src/InEngine/resources.resx @@ -26,4 +26,7 @@ |___|_| |_|_____|_| |_|\__, |_|_| |_|\___(_|_| \_|_____| |_| |___/ + + Press any key to exit... + \ No newline at end of file diff --git a/src/InEngineTesting/InEngineTesting.csproj b/src/InEngineTesting/InEngineTesting.csproj new file mode 100644 index 0000000..07b23f6 --- /dev/null +++ b/src/InEngineTesting/InEngineTesting.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/src/InEngine.Core.Test/TestBaseT.cs b/src/InEngineTesting/TestBaseT.cs similarity index 87% rename from src/InEngine.Core.Test/TestBaseT.cs rename to src/InEngineTesting/TestBaseT.cs index 2c85f62..9f88d07 100644 --- a/src/InEngine.Core.Test/TestBaseT.cs +++ b/src/InEngineTesting/TestBaseT.cs @@ -1,6 +1,6 @@ using NUnit.Framework; -namespace InEngine.Core.Test; +namespace InEngineTesting; public abstract class TestBase where TSubject : new() {