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>