diff --git a/Implab.Format.Test/Implab.Format.Test.csproj b/Implab.Format.Test/Implab.Format.Test.csproj
deleted file mode 100644
--- a/Implab.Format.Test/Implab.Format.Test.csproj
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
- Debug
- AnyCPU
- 8.0.30703
- 2.0
- {4D364996-7ECD-4193-8F90-F223FFEA49DA}
- Library
- Implab.Format.Test
- Implab.Format.Test
- v4.5
- 0.2
-
-
- true
- full
- false
- bin\Debug
- DEBUG;
- prompt
- 4
- false
-
-
- full
- true
- bin\Release
- prompt
- 4
- false
-
-
-
- ..\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll
- True
-
-
-
-
-
-
-
-
-
-
- {F550F1F8-8746-4AD0-9614-855F4C4B7F05}
- Implab
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Implab.Format.Test/packages.config b/Implab.Format.Test/packages.config
deleted file mode 100644
--- a/Implab.Format.Test/packages.config
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/Implab.Format.Test/JsonTests.cs b/Implab.Test/JsonTests.cs
rename from Implab.Format.Test/JsonTests.cs
rename to Implab.Test/JsonTests.cs
--- a/Implab.Format.Test/JsonTests.cs
+++ b/Implab.Test/JsonTests.cs
@@ -1,17 +1,16 @@
-using NUnit.Framework;
+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.Format.Test {
- [TestFixture]
- public class JsonTests {
-
- [Test]
+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 []{}:")) {
@@ -42,18 +41,18 @@ namespace Implab.Format.Test {
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);
+ 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.IsFalse(scanner.ReadToken(out value, out tokenType));
+ Assert.False(scanner.ReadToken(out value, out tokenType));
}
}
- [Test]
+ [Fact]
public void TestScannerBadTokens() {
var bad = new[] {
" 1",
@@ -71,7 +70,7 @@ namespace Implab.Format.Test {
"-.123"
};
- foreach (var json in bad) {
+ foreach (var json in bad) {
using (var scanner = JsonStringScanner.Create(json)) {
try {
string value;
@@ -81,92 +80,72 @@ namespace Implab.Format.Test {
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);
+ Assert.True(false, $"Token '{json}' shouldn't pass");
} 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]]");
+ [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]]");
}
- [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 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 xmlReader = new JsonXmlReader(JsonReader.ParseString(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", NodesPrefix = "json" })) {
- while (xmlReader.Read())
- Console.WriteLine($"{new string(' ', xmlReader.Depth * 2)}{xmlReader}");
- }
+ 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);
- }
+ 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();
}
}
}
diff --git a/Implab.Test/MockPollComponent.cs b/Implab.Test/MockPollComponent.cs
--- a/Implab.Test/MockPollComponent.cs
+++ b/Implab.Test/MockPollComponent.cs
@@ -21,11 +21,13 @@ namespace Implab.Test {
}
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);
}
diff --git a/Implab.Test/RunnableComponentTests.cs b/Implab.Test/RunnableComponentTests.cs
--- a/Implab.Test/RunnableComponentTests.cs
+++ b/Implab.Test/RunnableComponentTests.cs
@@ -8,7 +8,7 @@ namespace Implab.Test {
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 @@ namespace Implab.Test {
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(() => m.Start());
+
+ // Initialize
+ m.Initialize();
+ await m.Completion;
+
+ // Start
+ m.Start();
+ Assert.Equal(ExecutionState.Starting, m.State);
+
+ // Check invalid transitions
+ Assert.Throws(() => 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(() => m.Start());
+
+ // Stop
+ m.Stop();
+
+ // Check invalid transitions
+ Assert.Throws(() => m.Start());
+ Assert.Throws(() => m.Stop());
+
+ // Stopped
+ stopped.Start();
+ await m.Completion;
+ Assert.Equal(ExecutionState.Stopped, m.State);
+
+ // Check invalid transitions
+ Assert.Throws(() => m.Start());
+ Assert.Throws(() => 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);
}
diff --git a/Implab/Components/IInitializable.cs b/Implab/Components/IInitializable.cs
--- a/Implab/Components/IInitializable.cs
+++ b/Implab/Components/IInitializable.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading;
namespace Implab.Components {
///
@@ -23,6 +24,7 @@ namespace Implab.Components {
///
///
void Initialize();
+ void Initialize(CancellationToken ct);
}
}
diff --git a/Implab/Components/IRunnable.cs b/Implab/Components/IRunnable.cs
--- a/Implab/Components/IRunnable.cs
+++ b/Implab/Components/IRunnable.cs
@@ -19,6 +19,7 @@ namespace Implab.Components {
/// This operation is cancellable and it's expected to move to
/// the failed state or just ignore the cancellation request,
///
+ void Start();
void Start(CancellationToken ct);
///
@@ -31,8 +32,9 @@ namespace Implab.Components {
/// will be requested to cancel. The stop operatin will be
/// performed only if the component in the running state.
///
+ void Stop();
void Stop(CancellationToken ct);
-
+
///
/// Current state of the componenet, dynamically reflects the current state.
///
diff --git a/Implab/Components/PollingComponent.cs b/Implab/Components/PollingComponent.cs
--- a/Implab/Components/PollingComponent.cs
+++ b/Implab/Components/PollingComponent.cs
@@ -49,7 +49,8 @@ namespace Implab.Components {
m_cancellation.Cancel();
try {
// await for pending poll
- await m_poll;
+ if (m_poll != null)
+ await m_poll;
} catch (OperationCanceledException) {
// OK
}
diff --git a/Implab/Components/RunnableComponent.cs b/Implab/Components/RunnableComponent.cs
--- a/Implab/Components/RunnableComponent.cs
+++ b/Implab/Components/RunnableComponent.cs
@@ -171,9 +171,13 @@ namespace Implab.Components {
}
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 @@ namespace Implab.Components {
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 @@ namespace Implab.Components {
}
+ public void Stop() {
+ Stop(CancellationToken.None);
+ }
+
public void Stop(CancellationToken ct) {
var cookie = new object();
if (MoveStop(cookie))
@@ -230,7 +242,12 @@ namespace Implab.Components {
async Task StopAsync(CancellationToken ct) {
m_current.Cancel();
- await Completion;
+
+ try {
+ await Completion;
+ } catch(OperationCanceledException) {
+ // OK
+ }
ct.ThrowIfCancellationRequested();
@@ -302,12 +319,13 @@ namespace Implab.Components {
}
}
- 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;
}
}
diff --git a/Implab/Implab.csproj b/Implab/Implab.csproj
--- a/Implab/Implab.csproj
+++ b/Implab/Implab.csproj
@@ -8,8 +8,8 @@
and SharedLock, Trace helpers on top of System.Diagnostics, ObjectPool etc.
2012-2018 Sergey Smirnov
- 3.0.6
- https://opensource.org/licenses/BSD-2-Clause
+ 3.0.8
+ https://hg.implab.org/pub/ImplabNet/file/tip/Implab/license.txt
https://implab.org
https://hg.implab.org/pub/ImplabNet/
mercurial