Mercurial > pub > ImplabNet
changeset 262:f1696cdc3d7a v3 v3.0.8
Added IInitializable.Initialize() overload
Added IRunnable.Start(), IRunnable.Start() overloads
Fixed cancellation of the current operation when Stop() is called
More tests
author | cin |
---|---|
date | Mon, 16 Apr 2018 02:12:39 +0300 |
parents | 05a87f575512 |
children | 711572866e0c |
files | Implab.Format.Test/Implab.Format.Test.csproj Implab.Format.Test/JsonTests.cs Implab.Format.Test/packages.config Implab.Test/JsonTests.cs Implab.Test/MockPollComponent.cs Implab.Test/RunnableComponentTests.cs Implab/Components/IInitializable.cs Implab/Components/IRunnable.cs Implab/Components/PollingComponent.cs Implab/Components/RunnableComponent.cs Implab/Implab.csproj |
diffstat | 11 files changed, 422 insertions(+), 245 deletions(-) [+] |
line wrap: on
line diff
--- a/Implab.Format.Test/Implab.Format.Test.csproj Fri Apr 13 19:15:11 2018 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <ProductVersion>8.0.30703</ProductVersion> - <SchemaVersion>2.0</SchemaVersion> - <ProjectGuid>{4D364996-7ECD-4193-8F90-F223FFEA49DA}</ProjectGuid> - <OutputType>Library</OutputType> - <RootNamespace>Implab.Format.Test</RootNamespace> - <AssemblyName>Implab.Format.Test</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> - <ReleaseVersion>0.2</ReleaseVersion> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>full</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\Debug</OutputPath> - <DefineConstants>DEBUG;</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>full</DebugType> - <Optimize>true</Optimize> - <OutputPath>bin\Release</OutputPath> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> - </PropertyGroup> - <ItemGroup> - <Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> - <HintPath>..\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="System" /> - <Reference Include="System.Xml" /> - </ItemGroup> - <ItemGroup> - <Compile Include="JsonTests.cs" /> - </ItemGroup> - <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> - <ItemGroup> - <ProjectReference Include="..\Implab\Implab.csproj"> - <Project>{F550F1F8-8746-4AD0-9614-855F4C4B7F05}</Project> - <Name>Implab</Name> - </ProjectReference> - </ItemGroup> - <ItemGroup> - <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> - </ItemGroup> - <ItemGroup> - <None Include="packages.config" /> - </ItemGroup> -</Project> \ No newline at end of file
--- a/Implab.Format.Test/JsonTests.cs Fri Apr 13 19:15:11 2018 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,173 +0,0 @@ -using NUnit.Framework; -using System; -using Implab.Automaton; -using Implab.Xml; -using System.Xml; -using Implab.Formats; -using Implab.Formats.Json; -using System.IO; - -namespace Implab.Format.Test { - [TestFixture] - public class JsonTests { - - [Test] - public void TestScannerValidTokens() { - using (var scanner = JsonStringScanner.Create(@"9123, -123, 0, 0.1, -0.2, -0.1e3, 1.3E-3, ""some \t\n\u0020 text"", literal []{}:")) { - - Tuple<JsonTokenType, object>[] expexted = { - new Tuple<JsonTokenType,object>(JsonTokenType.Number, "9123"), - new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), - new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-123"), - new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), - new Tuple<JsonTokenType,object>(JsonTokenType.Number, "0"), - new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), - new Tuple<JsonTokenType,object>(JsonTokenType.Number, "0.1"), - new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), - new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-0.2"), - new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), - new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-0.1e3"), - new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), - new Tuple<JsonTokenType,object>(JsonTokenType.Number, "1.3E-3"), - new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), - new Tuple<JsonTokenType,object>(JsonTokenType.String, "some \t\n text"), - new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), - new Tuple<JsonTokenType,object>(JsonTokenType.Literal, "literal"), - new Tuple<JsonTokenType,object>(JsonTokenType.BeginArray, null), - new Tuple<JsonTokenType,object>(JsonTokenType.EndArray, null), - new Tuple<JsonTokenType,object>(JsonTokenType.BeginObject, null), - new Tuple<JsonTokenType,object>(JsonTokenType.EndObject, null), - new Tuple<JsonTokenType,object>(JsonTokenType.NameSeparator, null) - }; - - string value; - JsonTokenType tokenType; - for (var i = 0; i < expexted.Length; i++) { - - Assert.IsTrue(scanner.ReadToken(out value, out tokenType)); - Assert.AreEqual(expexted[i].Item1, tokenType); - Assert.AreEqual(expexted[i].Item2, value); - } - - Assert.IsFalse(scanner.ReadToken(out value, out tokenType)); - } - } - - [Test] - public void TestScannerBadTokens() { - var bad = new[] { - " 1", - " literal", - " \"", - "\"unclosed string", - "1.bad", - "001", // should be read as three numbers - "--10", - "+10", - "1.0.0", - "1e1.0", - "l1teral0", - ".123", - "-.123" - }; - - foreach (var json in bad) { - using (var scanner = JsonStringScanner.Create(json)) { - try { - string value; - JsonTokenType token; - scanner.ReadToken(out value, out token); - if (!Object.Equals(value, json)) { - Console.WriteLine("'{0}' is read as {1}", json, value is String ? String.Format("'{0}'", value) : value); - continue; - } - Assert.Fail("Token '{0}' shouldn't pass", json); - } catch (ParserException e) { - Console.WriteLine(e.Message); - } - } - } - } - - [Test] - public void JsonXmlReaderSimpleTest() { - var json = "\"some text\""; - //Console.WriteLine($"JSON: {json}"); - //Console.WriteLine("XML"); - /*using (var xmlReader = new JsonXmlReader(new JSONParser(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", RootName = "string", NodesPrefix = "json" })) { - Assert.AreEqual(xmlReader.ReadState, System.Xml.ReadState.Initial); - - AssertRead(xmlReader, XmlNodeType.XmlDeclaration); - AssertRead(xmlReader, XmlNodeType.Element); - AssertRead(xmlReader, XmlNodeType.Text); - AssertRead(xmlReader, XmlNodeType.EndElement); - Assert.IsFalse(xmlReader.Read()); - }*/ - - //DumpJsonParse("\"text value\""); - //DumpJsonParse("null"); - //DumpJsonParse("true"); - //DumpJsonParse("{}"); - //DumpJsonParse("[]"); - DumpJsonParse("{\"one\":1, \"two\":2}"); - DumpJsonParse("[1,\"\",2,3]"); - DumpJsonParse("[{\"info\": [7,8,9]}]"); - DumpJsonFlatParse("[1,2,\"\",[3,4],{\"info\": [5,6]},{\"num\": [7,8,null]}, null,[null]]"); - } - - [Test] - public void JsonBenchmark() { - var t = Environment.TickCount; - using (var reader = new JsonXmlReader(JsonReader.Create("e:\\citylots.json"), new JsonXmlReaderOptions { NamespaceUri = "XmlReaderSimpleTest", RootName = "data" })) { - while (reader.Read()) { - } - } - Console.WriteLine($"JsonXmlReader: {Environment.TickCount - t} ms"); - - t = Environment.TickCount; - using(var reader = JsonReader.Create("e:\\citylots.json")) { - while(reader.Read()) { - } - } - - Console.WriteLine($"JsonReader: {Environment.TickCount - t} ms"); - - t = Environment.TickCount; - using (var reader = XmlReader.Create("file:///e:\\citylots.xml")) { - while (reader.Read()) { - } - } - - Console.WriteLine($"XmlReader: {Environment.TickCount - t} ms"); - } - - void AssertRead(XmlReader reader, XmlNodeType expected) { - Assert.IsTrue(reader.Read()); - Console.WriteLine($"{new string(' ', reader.Depth * 2)}{reader}"); - Assert.AreEqual(expected, reader.NodeType); - } - - void DumpJsonParse(string json) { - Console.WriteLine($"JSON: {json}"); - Console.WriteLine("XML"); - using (var xmlReader = new JsonXmlReader(JsonReader.ParseString(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", NodesPrefix = "json" })) { - while (xmlReader.Read()) - Console.WriteLine($"{new string(' ', xmlReader.Depth * 2)}{xmlReader}"); - } - } - - void DumpJsonFlatParse(string json) { - Console.WriteLine($"JSON: {json}"); - Console.WriteLine("XML"); - using (var xmlWriter = XmlWriter.Create(Console.Out, new XmlWriterSettings { - Indent = true, - CloseOutput = false, - ConformanceLevel = ConformanceLevel.Document - })) - using (var xmlReader = new JsonXmlReader(JsonReader.ParseString(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", NodesPrefix = "", FlattenArrays = true })) { - xmlWriter.WriteNode(xmlReader, false); - } - } - } -} -
--- a/Implab.Format.Test/packages.config Fri Apr 13 19:15:11 2018 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<packages> - <package id="NUnit" version="3.8.1" targetFramework="net45" /> -</packages> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Test/JsonTests.cs Mon Apr 16 02:12:39 2018 +0300 @@ -0,0 +1,152 @@ +using Xunit; +using System; +using Implab.Automaton; +using Implab.Xml; +using System.Xml; +using Implab.Formats; +using Implab.Formats.Json; +using System.IO; + +namespace Implab.Test { + public class JsonTests { + + [Fact] + public void TestScannerValidTokens() { + using (var scanner = JsonStringScanner.Create(@"9123, -123, 0, 0.1, -0.2, -0.1e3, 1.3E-3, ""some \t\n\u0020 text"", literal []{}:")) { + + Tuple<JsonTokenType, object>[] expexted = { + new Tuple<JsonTokenType,object>(JsonTokenType.Number, "9123"), + new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), + new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-123"), + new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), + new Tuple<JsonTokenType,object>(JsonTokenType.Number, "0"), + new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), + new Tuple<JsonTokenType,object>(JsonTokenType.Number, "0.1"), + new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), + new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-0.2"), + new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), + new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-0.1e3"), + new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), + new Tuple<JsonTokenType,object>(JsonTokenType.Number, "1.3E-3"), + new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), + new Tuple<JsonTokenType,object>(JsonTokenType.String, "some \t\n text"), + new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), + new Tuple<JsonTokenType,object>(JsonTokenType.Literal, "literal"), + new Tuple<JsonTokenType,object>(JsonTokenType.BeginArray, null), + new Tuple<JsonTokenType,object>(JsonTokenType.EndArray, null), + new Tuple<JsonTokenType,object>(JsonTokenType.BeginObject, null), + new Tuple<JsonTokenType,object>(JsonTokenType.EndObject, null), + new Tuple<JsonTokenType,object>(JsonTokenType.NameSeparator, null) + }; + + string value; + JsonTokenType tokenType; + for (var i = 0; i < expexted.Length; i++) { + + Assert.True(scanner.ReadToken(out value, out tokenType)); + Assert.Equal(expexted[i].Item1, tokenType); + Assert.Equal(expexted[i].Item2, value); + } + + Assert.False(scanner.ReadToken(out value, out tokenType)); + } + } + + [Fact] + public void TestScannerBadTokens() { + var bad = new[] { + " 1", + " literal", + " \"", + "\"unclosed string", + "1.bad", + "001", // should be read as three numbers + "--10", + "+10", + "1.0.0", + "1e1.0", + "l1teral0", + ".123", + "-.123" + }; + + foreach (var json in bad) { + using (var scanner = JsonStringScanner.Create(json)) { + try { + string value; + JsonTokenType token; + scanner.ReadToken(out value, out token); + if (!Object.Equals(value, json)) { + Console.WriteLine("'{0}' is read as {1}", json, value is String ? String.Format("'{0}'", value) : value); + continue; + } + Assert.True(false, $"Token '{json}' shouldn't pass"); + } catch (ParserException e) { + Console.WriteLine(e.Message); + } + } + } + } + + [Fact] + public void JsonXmlReaderSimpleTest() { + var json = "\"some text\""; + //Console.WriteLine($"JSON: {json}"); + //Console.WriteLine("XML"); + /*using (var xmlReader = new JsonXmlReader(new JSONParser(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", RootName = "string", NodesPrefix = "json" })) { + Assert.AreEqual(xmlReader.ReadState, System.Xml.ReadState.Initial); + + AssertRead(xmlReader, XmlNodeType.XmlDeclaration); + AssertRead(xmlReader, XmlNodeType.Element); + AssertRead(xmlReader, XmlNodeType.Text); + AssertRead(xmlReader, XmlNodeType.EndElement); + Assert.IsFalse(xmlReader.Read()); + }*/ + + DumpJsonParse("\"text value\""); + DumpJsonParse("null"); + DumpJsonParse("true"); + DumpJsonParse("{}"); + DumpJsonParse("[]"); + DumpJsonParse("{\"one\":1, \"two\":2}"); + DumpJsonParse("[1,\"\",2,3]"); + DumpJsonParse("[{\"info\": [7,8,9]}]"); + DumpJsonFlatParse("[1,2,\"\",[3,4],{\"info\": [5,6]},{\"num\": [7,8,null]}, null,[null]]"); + } + + void AssertRead(XmlReader reader, XmlNodeType expected) { + Assert.True(reader.Read()); + Console.WriteLine($"{new string(' ', reader.Depth * 2)}{reader}"); + Assert.Equal(expected, reader.NodeType); + } + + void DumpJsonParse(string json) { + Console.WriteLine($"JSON: {json}"); + Console.WriteLine("XML"); + using (var xmlWriter = XmlWriter.Create(Console.Out, new XmlWriterSettings { + Indent = true, + CloseOutput = false, + ConformanceLevel = ConformanceLevel.Document + })) + using (var xmlReader = new JsonXmlReader(JsonReader.ParseString(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", NodesPrefix = "json" })) { + xmlWriter.WriteNode(xmlReader, false); + } + Console.WriteLine(); + } + + void DumpJsonFlatParse(string json) { + Console.WriteLine($"JSON: {json}"); + Console.WriteLine("XML"); + using (var xmlWriter = XmlWriter.Create(Console.Out, new XmlWriterSettings { + Indent = true, + CloseOutput = false, + ConformanceLevel = ConformanceLevel.Document + })) + using (var xmlReader = new JsonXmlReader(JsonReader.ParseString(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", NodesPrefix = "", FlattenArrays = true })) { + xmlWriter.WriteNode(xmlReader, false); + } + Console.WriteLine(); + } + } +} +
--- a/Implab.Test/MockPollComponent.cs Fri Apr 13 19:15:11 2018 +0300 +++ b/Implab.Test/MockPollComponent.cs Mon Apr 16 02:12:39 2018 +0300 @@ -21,11 +21,13 @@ } protected async override Task StopInternalAsync(CancellationToken ct) { + await base.StopInternalAsync(ct); if (StopWorker != null) await StopWorker.Invoke(ct); } protected async override Task StartInternalAsync(CancellationToken ct) { + await base.StartInternalAsync(ct); if (StartWorker != null) await StartWorker.Invoke(ct); }
--- a/Implab.Test/RunnableComponentTests.cs Fri Apr 13 19:15:11 2018 +0300 +++ b/Implab.Test/RunnableComponentTests.cs Mon Apr 16 02:12:39 2018 +0300 @@ -8,7 +8,7 @@ public class RunnableComponentTests { [Fact] - public async Task Test1() { + public async Task SimpleStartStop() { using (var m = new MockPollComponent(true)) { m.StartWorker = async (ct) => await Task.Yield(); @@ -16,12 +16,246 @@ Assert.Equal(ExecutionState.Ready, m.State); Assert.NotNull(m.Completion); - - m.Start(CancellationToken.None); + + m.Start(); await m.Completion; Assert.Equal(ExecutionState.Running, m.State); + m.Stop(); + await m.Completion; + Assert.Equal(ExecutionState.Stopped, m.State); + } + } + + [Fact] + public async Task SyncStart() { + using (var m = new MockPollComponent(true)) { + m.Start(); + Assert.Equal(ExecutionState.Running, m.State); + await m.Completion; + } + } + + [Fact] + public async Task AsyncStarting() { + using (var m = new MockPollComponent(true)) { + var signal = Safe.CreateTask(); + + m.StartWorker = async (ct) => await signal; + m.Start(); + + Assert.Equal(ExecutionState.Starting, m.State); + Assert.False(m.Completion.IsCompleted); + + signal.Start(); + + await m.Completion; + + Assert.Equal(ExecutionState.Running, m.State); + } + } + + [Fact] + public async Task FailWhileStarting() { + using (var m = new MockPollComponent(true)) { + const string failMessage = "Fail me"; + var signal = new Task(() => { + throw new Exception(failMessage); + }); + + m.StartWorker = async (ct) => await signal; + m.Start(); + + Assert.Equal(ExecutionState.Starting, m.State); + Assert.False(m.Completion.IsCompleted); + + signal.Start(); + try { + await m.Completion; + Assert.True(false); + } catch (Exception e) { + Assert.Equal(failMessage, e.Message); + } + + Assert.Equal(ExecutionState.Failed, m.State); + } + } + + [Fact] + public async Task SyncStop() { + using (var m = new MockPollComponent(true)) { + m.Start(); + Assert.Equal(ExecutionState.Running, m.State); + m.Stop(); + Assert.Equal(ExecutionState.Stopped, m.State); + await m.Completion; + } + } + + [Fact] + public async Task AsyncStopping() { + using (var m = new MockPollComponent(true)) { + var signal = Safe.CreateTask(); + + m.StopWorker = async (ct) => await signal; + + // Start + m.Start(); + Assert.Equal(ExecutionState.Running, m.State); + + // Stop + m.Stop(); + Assert.Equal(ExecutionState.Stopping, m.State); + Assert.False(m.Completion.IsCompleted); + signal.Start(); + + await m.Completion; + + Assert.Equal(ExecutionState.Stopped, m.State); + } + } + + [Fact] + public async Task FailWhileStopping() { + using (var m = new MockPollComponent(true)) { + const string failMessage = "Fail me"; + var signal = new Task(() => { + throw new Exception(failMessage); + }); + + m.StopWorker = async (ct) => await signal; + + // Start + m.Start(); + Assert.Equal(ExecutionState.Running, m.State); + + // Stop + m.Stop(); + Assert.Equal(ExecutionState.Stopping, m.State); + Assert.False(m.Completion.IsCompleted); + + signal.Start(); + try { + await m.Completion; + Assert.True(false); + } catch (Exception e) { + Assert.Equal(failMessage, e.Message); + } + + Assert.Equal(ExecutionState.Failed, m.State); + } + } + + [Fact] + public async Task ThrowOnInvalidTrasition() { + using (var m = new MockPollComponent(false)) { + var started = Safe.CreateTask(); + var stopped = Safe.CreateTask(); + + m.StartWorker = async (ct) => await started; + m.StopWorker = async (ct) => await stopped; + + Assert.Throws<InvalidOperationException>(() => m.Start()); + + // Initialize + m.Initialize(); + await m.Completion; + + // Start + m.Start(); + Assert.Equal(ExecutionState.Starting, m.State); + + // Check invalid transitions + Assert.Throws<InvalidOperationException>(() => m.Start()); + + // Component can be stopped before started + // m.Stop(CancellationToken.None); + + // Running + started.Start(); + await m.Completion; + Assert.Equal(ExecutionState.Running, m.State); + + + Assert.Throws<InvalidOperationException>(() => m.Start()); + + // Stop + m.Stop(); + + // Check invalid transitions + Assert.Throws<InvalidOperationException>(() => m.Start()); + Assert.Throws<InvalidOperationException>(() => m.Stop()); + + // Stopped + stopped.Start(); + await m.Completion; + Assert.Equal(ExecutionState.Stopped, m.State); + + // Check invalid transitions + Assert.Throws<InvalidOperationException>(() => m.Start()); + Assert.Throws<InvalidOperationException>(() => m.Stop()); + } + } + + [Fact] + public async Task CancelStart() { + using (var m = new MockPollComponent(true)) { + m.StartWorker = (ct) => Safe.CreateTask(ct); + + m.Start(); + var start = m.Completion; + + Assert.Equal(ExecutionState.Starting, m.State); + m.Stop(); + await m.Completion; + Assert.Equal(ExecutionState.Stopped, m.State); + Assert.True(start.IsCompleted); + Assert.True(start.IsCanceled); + } + } + + [Fact] + public async Task AwaitWorker() { + using (var m = new MockPollComponent(true)) { + var worker = Safe.CreateTask(); + + m.PollWorker = (ct) => worker; + + m.Start(CancellationToken.None); + await m.Completion; + + Assert.Equal(ExecutionState.Running, m.State); + m.Stop(CancellationToken.None); + Assert.Equal(ExecutionState.Stopping, m.State); + worker.Start(); + await m.Completion; + Assert.Equal(ExecutionState.Stopped, m.State); + } + } + + [Fact] + public async Task CancelWorker() { + using (var m = new MockPollComponent(true)) { + var worker = Safe.CreateTask(); + + var started = Safe.CreateTask(); + + m.PollWorker = async (ct) => { + started.Start(); + await worker; + ct.ThrowIfCancellationRequested(); + }; + + m.Start(CancellationToken.None); + await m.Completion; + await started; // await for the poll worker to start + + Assert.Equal(ExecutionState.Running, m.State); + + m.Stop(CancellationToken.None); + Assert.Equal(ExecutionState.Stopping, m.State); + worker.Start(); await m.Completion; Assert.Equal(ExecutionState.Stopped, m.State); }
--- a/Implab/Components/IInitializable.cs Fri Apr 13 19:15:11 2018 +0300 +++ b/Implab/Components/IInitializable.cs Mon Apr 16 02:12:39 2018 +0300 @@ -1,4 +1,5 @@ using System; +using System.Threading; namespace Implab.Components { /// <summary> @@ -23,6 +24,7 @@ /// </para> /// </remarks> void Initialize(); + void Initialize(CancellationToken ct); } }
--- a/Implab/Components/IRunnable.cs Fri Apr 13 19:15:11 2018 +0300 +++ b/Implab/Components/IRunnable.cs Mon Apr 16 02:12:39 2018 +0300 @@ -19,6 +19,7 @@ /// This operation is cancellable and it's expected to move to /// the failed state or just ignore the cancellation request, /// </remarks> + void Start(); void Start(CancellationToken ct); /// <summary> @@ -31,8 +32,9 @@ /// will be requested to cancel. The stop operatin will be /// performed only if the component in the running state. /// </remarks> + void Stop(); void Stop(CancellationToken ct); - + /// <summary> /// Current state of the componenet, dynamically reflects the current state. /// </summary>
--- a/Implab/Components/PollingComponent.cs Fri Apr 13 19:15:11 2018 +0300 +++ b/Implab/Components/PollingComponent.cs Mon Apr 16 02:12:39 2018 +0300 @@ -49,7 +49,8 @@ m_cancellation.Cancel(); try { // await for pending poll - await m_poll; + if (m_poll != null) + await m_poll; } catch (OperationCanceledException) { // OK }
--- a/Implab/Components/RunnableComponent.cs Fri Apr 13 19:15:11 2018 +0300 +++ b/Implab/Components/RunnableComponent.cs Mon Apr 16 02:12:39 2018 +0300 @@ -171,9 +171,13 @@ } public void Initialize() { + Initialize(CancellationToken.None); + } + + public void Initialize(CancellationToken ct) { var cookie = new object(); if (MoveInitialize(cookie)) - Safe.NoWait(ScheduleTask(InitializeInternalAsync, CancellationToken.None, cookie)); + Safe.NoWait(ScheduleTask(InitializeInternalAsync, ct, cookie)); else throw new InvalidOperationException(); } @@ -191,6 +195,10 @@ return Task.CompletedTask; } + public void Start() { + Start(CancellationToken.None); + } + public void Start(CancellationToken ct) { var cookie = new object(); if (MoveStart(cookie)) @@ -220,6 +228,10 @@ } + public void Stop() { + Stop(CancellationToken.None); + } + public void Stop(CancellationToken ct) { var cookie = new object(); if (MoveStop(cookie)) @@ -230,7 +242,12 @@ async Task StopAsync(CancellationToken ct) { m_current.Cancel(); - await Completion; + + try { + await Completion; + } catch(OperationCanceledException) { + // OK + } ct.ThrowIfCancellationRequested(); @@ -302,12 +319,13 @@ } } - void MoveFailed(Exception err, object cookie) { + bool MoveFailed(Exception err, object cookie) { lock (m_lock) { if (m_cookie != cookie) - return; + return false; LastError = err; State = ExecutionState.Failed; + return true; } }
--- a/Implab/Implab.csproj Fri Apr 13 19:15:11 2018 +0300 +++ b/Implab/Implab.csproj Mon Apr 16 02:12:39 2018 +0300 @@ -8,8 +8,8 @@ and SharedLock, Trace helpers on top of System.Diagnostics, ObjectPool etc. </Description> <Copyright>2012-2018 Sergey Smirnov</Copyright> - <Version>3.0.6</Version> - <PackageLicenseUrl>https://opensource.org/licenses/BSD-2-Clause</PackageLicenseUrl> + <Version>3.0.8</Version> + <PackageLicenseUrl>https://hg.implab.org/pub/ImplabNet/file/tip/Implab/license.txt</PackageLicenseUrl> <PackageProjectUrl>https://implab.org</PackageProjectUrl> <RepositoryUrl>https://hg.implab.org/pub/ImplabNet/</RepositoryUrl> <RepositoryType>mercurial</RepositoryType>