コード例 #1
0
        public async Task JsonServerHandlesDeserializationErrors()
        {
            // fake transport and serializer
            var server           = new StubServer();
            var client           = new StubClient(server);
            var provider         = new StubMessageTypeProvider();
            var serverSerializer = new BrokenSerializer();
            var clientSerializer = new Serializer();
            var executor         = new StubExecutor();

            using (var js = new JsonServer(server, provider, serverSerializer, executor))
                using (var jc = new JsonClient(client, provider, clientSerializer))
                {
                    js.Start();

                    var tcs = new TaskCompletionSource <bool>();
                    js.UnhandledException += (s, e) => tcs.TrySetException(e.Exception);

                    // TODO: can we have something better than a timeout here?
                    await Assert_TimedOut(jc.ConnectAsync(), timeout : Task.Delay(200));

                    // the server should have got an unhandled exception
                    Assert.ThrowsAsync <NotImplementedException>(async() => await Assert_NotTimedOut(tcs.Task));
                }
        }
コード例 #2
0
        protected async Task CallGenericMessagesCore(JsonServer js, JsonClient jc, ICredentials credentials = null)
        {
            js.Start();
            await jc.ConnectAsync();

            var intMsg = new GenericRequest <int> {
                Value = 1
            };
            var intResult = await jc.Call(intMsg);

            Assert.AreEqual(2, intResult.Result);

            var dtMsg = new GenericRequest <DateTime> {
                Value = new DateTime(2018, 12, 18)
            };
            var dtResult = await jc.Call(dtMsg);

            Assert.AreEqual(new DateTime(2019, 12, 18), dtResult.Result);

            var strMsg = new GenericRequest <string> {
                Value = "World"
            };
            var strResult = await jc.Call(strMsg);

            Assert.AreEqual("Hello World!", strResult.Result);

            var boolMsg = new GenericRequest <bool> {
                Value = true
            };

            Assert.ThrowsAsync <MethodNotFoundException>(async() => await jc.Call(boolMsg));

            // make sure all incoming messages are processed
            Assert.AreEqual(0, jc.PendingMessages.Count);
        }
コード例 #3
0
        protected async Task CallUnregisteredServiceCore(JsonServer js, JsonClient jc, ICredentials credentials = null)
        {
            // event handlers
            var connected    = 0;
            var disconnected = 0;

            js.ClientConnected    += (s, e) => connected++;
            js.ClientDisconnected += (s, e) => disconnected++;
            js.UnhandledException += (s, e) => Assert.Fail($"Unhandled server exception: {e.Exception}. Connected: {connected}, disconnected: {disconnected}.");
            jc.UnhandledException += (s, e) => Assert.Fail($"Unhandled client exception: {e.Exception}. Connected: {connected}, disconnected: {disconnected}.");

            // start json server and connect the client
            js.Start();
            await jc.ConnectAsync(credentials);

            // call UnregisteredService
            var msg = new UnregisteredService();
            var ex  = Assert.ThrowsAsync <MethodNotFoundException>(async() =>
                                                                   await Assert_NotTimedOut(jc.Call(msg), "jc.Call(UnregisteredService)"));

            Assert.AreEqual(MethodNotFoundException.ErrorCode, ex.Code);

            // make sure all incoming messages are processed
            Assert.AreEqual(0, jc.PendingMessages.Count);
        }
コード例 #4
0
        protected async Task CallDisconnectAndReconnectCore(JsonServer js, JsonClient jc, ICredentials credentials = null)
        {
            js.ProductName    = "My awesome server";
            js.ProductVersion = "1.2.3.4";
            js.Start();

            // connect, call, disconnect
            await Assert_NotTimedOut(jc.ConnectAsync(credentials), "connect");

            var result = await Assert_NotTimedOut(jc.Call(new VersionRequest()), "jc.Call(VersionRequest)");

            Assert.NotNull(result);
            Assert.AreEqual(js.ProductName, result.ProductName);
            Assert.AreEqual(js.ProductVersion, result.ProductVersion);
            await Assert_NotTimedOut(jc.DisconnectAsync(), "disconnect");

            // reconnect, call, disconnect
            await Assert_NotTimedOut(jc.ConnectAsync(credentials), "reconnect");

            result = await Assert_NotTimedOut(jc.Call(new VersionRequest()), "jc.Call(VersionRequest) after reconnect");

            Assert.NotNull(result);
            Assert.AreEqual(js.ProductName, result.ProductName);
            Assert.AreEqual(js.ProductVersion, result.ProductVersion);
            await Assert_NotTimedOut(jc.DisconnectAsync(), "disconnect completely");

            Assert.AreEqual(0, jc.PendingMessages.Count);
        }
コード例 #5
0
        private async Task CallBuiltinVersionServiceCore(JsonServer js, JsonClient jc, ICredentials credentials = null)
        {
            // event handlers
            var connected    = 0;
            var disconnected = 0;

            js.ClientConnected    += (s, e) => connected++;
            js.ClientDisconnected += (s, e) => disconnected++;
            js.UnhandledException += (s, e) => Assert.Fail($"Unhandled server exception: {e.Exception}. Connected: {connected}, disconnected: {disconnected}.");
            jc.UnhandledException += (s, e) => Assert.Fail($"Unhandled client exception: {e.Exception}. Connected: {connected}, disconnected: {disconnected}.");

            // start json server and connect the client
            js.Start();

            Assert.IsNull(jc.SessionId);
            var sessionId = await jc.ConnectAsync(credentials);

            Assert.IsNotNull(jc.SessionId);
            Assert.AreEqual(sessionId, jc.SessionId);

            // call Version
            var msg    = new VersionRequest();
            var result = await Assert_NotTimedOut(jc.Call(msg), "jc.Call(VersionRequest)");

            Assert.NotNull(result);
            Assert.AreEqual(nameof(JsonServices), result.ProductName);
            Assert.NotNull(result.ProductVersion);
            Assert.NotNull(result.EngineVersion);

            // make sure all incoming messages are processed
            Assert.AreEqual(0, jc.PendingMessages.Count);
        }
コード例 #6
0
        protected virtual async Task CallDelayServiceCore(JsonServer js, JsonClient jc, ICredentials credentials = null)
        {
            js.Start();
            await jc.ConnectAsync(credentials);

            await Assert_NotTimedOut(jc.Call(new DelayRequest {
                Milliseconds = 10
            }), "jc.Call(Delay 10)");
            await Assert_TimedOut(jc.Call(new DelayRequest {
                Milliseconds = 200
            }), "jc.Call(Delay 200)", Task.Delay(10));

            // make sure that await completes before the server is disposed (affects NetMQ server)
            await Task.Delay(300);
        }
コード例 #7
0
        protected async Task CallDelayServiceAndAbortConnectionCore(JsonServer js, JsonClient jc, ICredentials credentials = null)
        {
            js.Start();
            await jc.ConnectAsync(credentials);

            var call = jc.Call(new DelayRequest {
                Milliseconds = 1000
            });
            await Task.Delay(100);             // make sure that the call was actually sent

            await jc.Client.DisconnectAsync(); // should fire Disconnected event

            Assert.ThrowsAsync <ClientDisconnectedException>(async() =>
                                                             await Assert_NotTimedOut(call, "jc.Call(Delay 1000)", Task.Delay(2000)));
        }
コード例 #8
0
        public async Task CallServiceBeforeConnectingShouldFail()
        {
            // fake transport and serializer
            var server     = new StubServer();
            var client     = new StubClient(server);
            var serializer = new Serializer();
            var executor   = new StubExecutor();
            var provider   = new StubMessageTypeProvider();

            var js = new JsonServer(server, provider, serializer, executor);
            var jc = new JsonClient(client, provider, serializer);

            js.Start();

            Assert.ThrowsAsync <AuthRequiredException>(async() =>
                                                       await Assert_NotTimedOut(jc.Call(new GetVersion()), "jc.Call(GetVersion) before Connect"));

            await Assert_NotTimedOut(jc.ConnectAsync(), "jc.ConnectAsync()");
            await Assert_NotTimedOut(jc.Call(new GetVersion()), "jc.Call(GetVersion) after connect");
        }
コード例 #9
0
        public void JsonServerHandlesMessageTypeProvidersErrors()
        {
            // fake transport and serializer
            var server           = new StubServer();
            var client           = new StubClient(server);
            var provider         = new BrokenMessageTypeProvider();
            var serverSerializer = new Serializer();
            var clientSerializer = new Serializer();
            var executor         = new StubExecutor();

            using (var js = new JsonServer(server, provider, serverSerializer, executor))
                using (var jc = new JsonClient(client, provider, clientSerializer))
                {
                    js.Start();

                    var ex = Assert.ThrowsAsync <InvalidRequestException>(() => Assert_NotTimedOut(jc.ConnectAsync(), timeout: Task.Delay(200)));

                    // note: JsonServicesException.MessageId is lost
                    // when an exception is translated to Error and back again
                    Assert.IsNull(ex.MessageId);
                }
        }
コード例 #10
0
        protected async Task CallGetVersionServiceCore(JsonServer js, JsonClient jc, ICredentials credentials = null)
        {
            // event handlers
            var connected    = 0;
            var disconnected = 0;

            js.ClientConnected    += (s, e) => connected++;
            js.ClientDisconnected += (s, e) => disconnected++;
            js.UnhandledException += (s, e) => Assert.Fail($"Unhandled server exception: {e.Exception}. Connected: {connected}, disconnected: {disconnected}.");
            jc.UnhandledException += (s, e) => Assert.Fail($"Unhandled client exception: {e.Exception}. Connected: {connected}, disconnected: {disconnected}.");

            // start json server and connect the client
            js.Start();

            Assert.IsNull(jc.SessionId);
            var sessionId = await jc.ConnectAsync(credentials);

            Assert.IsNotNull(jc.SessionId);
            Assert.AreEqual(sessionId, jc.SessionId);

            // call GetVersion
            var msg    = new GetVersion();
            var result = await Assert_NotTimedOut(jc.Call(msg), "jc.Call(msg)");

            Assert.NotNull(result);
            Assert.AreEqual("0.01-alpha", result.Version);

            // call GetVersion
            msg = new GetVersion {
                IsInternal = true
            };
            result = await Assert_NotTimedOut(jc.Call(msg), "jc.Call(msg...IsInternal)");

            Assert.NotNull(result);
            Assert.AreEqual("Version 0.01-alpha, build 12345, by yallie", result.Version);

            // make sure all incoming messages are processed
            Assert.AreEqual(0, jc.PendingMessages.Count);
        }
コード例 #11
0
        protected async Task CallCalculateServiceCore(JsonServer js, JsonClient jc, ICredentials credentials = null)
        {
            // unhandled exception handlers
            var connected    = 0;
            var disconnected = 0;

            js.ClientConnected    += (s, e) => connected++;
            js.ClientDisconnected += (s, e) => disconnected++;
            js.UnhandledException += (s, e) => Assert.Fail($"Unhandled server exception: {e.Exception}. Connected: {connected}, disconnected: {disconnected}.");
            jc.UnhandledException += (s, e) => Assert.Fail($"Unhandled client exception: {e.Exception}. Connected: {connected}, disconnected: {disconnected}.");

            // start json server and connect the client
            js.Start();
            await jc.ConnectAsync(credentials);

            // normal call
            var msg = new Calculate
            {
                FirstOperand  = 353,
                SecondOperand = 181,
                Operation     = "+",
            };

            var result = await Assert_NotTimedOut(jc.Call(msg), "353 + 181");

            Assert.NotNull(result);
            Assert.AreEqual(534, result.Result);

            msg.SecondOperand = 333;
            result            = await Assert_NotTimedOut(jc.Call(msg), "353 + 333");

            Assert.NotNull(result);
            Assert.AreEqual(686, result.Result);

            msg.Operation = "-";
            result        = await Assert_NotTimedOut(jc.Call(msg), "353 - 333");

            Assert.NotNull(result);
            Assert.AreEqual(20, result.Result);

            // call with error
            msg.Operation = "#";
            var ex = Assert.ThrowsAsync <InternalErrorException>(async() =>
                                                                 await Assert_NotTimedOut(jc.Call(msg), "353 # 333"));

            // internal server error
            Assert.AreEqual(-32603, ex.Code);
            Assert.AreEqual("Internal server error: Bad operation: #", ex.Message);

            // call with another error
            msg.Operation     = "%";
            msg.SecondOperand = 0;
            ex = Assert.ThrowsAsync <InternalErrorException>(async() =>
                                                             await Assert_NotTimedOut(jc.Call(msg), "353 % 0"));

            // internal server error
            Assert.AreEqual(-32603, ex.Code);
            Assert.AreEqual("Internal server error: Attempted to divide by zero.", ex.Message);

            // normal call again
            msg.Operation = "*";
            result        = await Assert_NotTimedOut(jc.Call(msg), "353 * 0");

            Assert.NotNull(result);
            Assert.AreEqual(0, result.Result);

            msg.Operation     = "+";
            msg.SecondOperand = 181;
            result            = await Assert_NotTimedOut(jc.Call(msg), "353 + 181 again");

            Assert.NotNull(result);
            Assert.AreEqual(534, result.Result);

            // make sure all incoming messages are processed
            Assert.AreEqual(0, jc.PendingMessages.Count);
        }
コード例 #12
0
ファイル: Program.cs プロジェクト: Snerble/ProjectCRepo
        /// <summary>
        /// Repeatable part of the program setup. Note that the threads and database must first be disposed or null.
        /// </summary>
        static void Setup()
        {
            // Dispose existing database connections
            Utils.DisposeDatabases();

            #region Apply AppSettings
            dynamic appSettings = Config["appSettings"];

            // Create log files in release mode only
            if (!DEBUG)
            {
                Log.Config("Creating log file");
                string log = appSettings.logDir;
                // Create the directory if it doesn't exist
                try
                {
                    if (!Directory.Exists(log))
                    {
                        Directory.CreateDirectory(log);
                    }
                }
                catch (Exception e)
                {
                    Log.Fatal($"{e.GetType().Name}: {e.Message}", e, false);
                    Terminate(14001);
                }
                log += "/latest.log";
                // Delete the file if it already exists
                if (File.Exists(log))
                {
                    File.Delete(log);
                }
                Log.OutputStreams.Add(File.CreateText(log));
            }
            #endregion

            Log.Config("Creating database connection...");
            Database = Utils.GetDatabase();

            try
            {
                // Compile razor .cshtml templates
                Templates.CompileAll();
            }
            catch (Exception e)
            {
                // Print any error and terminate
                Log.Fatal("Template compilation failed:");
                Log.Fatal($"{e.GetType().Name}: {e.Message}", e);
                Terminate(1);
            }

            #region Setup Threads
            dynamic performance = Config["performance"];

            for (int i = 0; i < (int)performance.apiThreads; i++)
            {
                var server = new JsonServer(JSONQueue);
                Servers.Add(server);
                server.Start();
            }
            for (int i = 0; i < (int)performance.htmlThreads; i++)
            {
                var server = new HTMLServer(listener.Queue);
                Servers.Add(server);
                server.Start();
            }
            for (int i = 0; i < (int)performance.resourceThreads; i++)
            {
                var server = new ResourceServer(ResourceQueue);
                Servers.Add(server);
                server.Start();
            }
            #endregion
        }
コード例 #13
0
        protected async Task TestSubscriptionsAndUnsubscriptionsCore(JsonServer js, JsonClient jc, JsonClient sc, ICredentials credentials = null)
        {
            // unhandled exception handlers
            var connected    = 0;
            var disconnected = 0;

            js.ClientConnected    += (s, e) => connected++;
            js.ClientDisconnected += (s, e) => disconnected++;
            js.UnhandledException += (s, e) => Assert.Fail($"Unhandled server exception: {e.Exception}. Connected: {connected}, disconnected: {disconnected}.");
            jc.UnhandledException += (s, e) => Assert.Fail($"Unhandled client exception in jc (first client): {e.Exception}. Connected: {connected}, disconnected: {disconnected}.");
            sc.UnhandledException += (s, e) => Assert.Fail($"Unhandled client exception in sc (second client): {e.Exception}. Connected: {connected}, disconnected: {disconnected}.");

            // start json server and connect both clients
            js.Start();
            await Assert_NotTimedOut(jc.ConnectAsync(credentials), "jc.ConnectAsync()");
            await Assert_NotTimedOut(sc.ConnectAsync(credentials), "sc.ConnectAsync()");

            // subscribe to jc events
            var jcounter     = 0;
            var jcancel      = default(bool?);
            var jtcs         = new TaskCompletionSource <bool>();
            var junsubscribe = await Assert_NotTimedOut(jc.Subscribe <CancelEventArgs>(
                                                            EventBroadcaster.BeforeShutdownEventName, (s, e) =>
            {
                jcounter++;
                jcancel = e.Cancel;
                jtcs.TrySetResult(true);
            }), "jc.Subscribe<CancelEventArgs>(...)");

            // subscribe to sc events
            var scounter     = 0;
            var spropName    = default(string);
            var stcs         = new TaskCompletionSource <bool>();
            var sunsubscribe = await Assert_NotTimedOut(sc.Subscribe <MyCoolEventArgs>(
                                                            EventBroadcaster.AfterStartupEventName, (s, e) =>
            {
                scounter++;
                spropName = e.PropertyName;
                stcs.TrySetResult(true);
            }), "sc.Subscribe<MyCoolEventArgs>(...)");

            // one-way call EventBroadcaster.AfterStartup
            jc.Notify(new EventBroadcaster
            {
                EventName = EventBroadcaster.AfterStartupEventName,
            });

            // sc is subscribed to AfterStartup event, jc is not
            await Assert_NotTimedOut(stcs.Task, "stcs.Task");

            Assert.AreEqual(1, scounter);
            Assert.AreEqual(0, jcounter);
            Assert.AreEqual(nameof(EventBroadcaster), spropName);

            // call EventBroadcaster.BeforeShutdown
            await Assert_NotTimedOut(jc.Call(new EventBroadcaster
            {
                EventName = EventBroadcaster.BeforeShutdownEventName,
            }), "jc.Call(new EventBroadcaster(...BeforeShutdown))");

            // js is subscribed to BeforeShutdown event, sc is not
            await Assert_NotTimedOut(jtcs.Task, "jtcs.Task");

            Assert.AreEqual(1, scounter);
            Assert.AreEqual(1, jcounter);
            Assert.IsTrue(jcancel);

            // restart both task completion sources
            jtcs = new TaskCompletionSource <bool>();
            stcs = new TaskCompletionSource <bool>();

            // call EventBroadcaster.BeforeShutdown
            await Assert_NotTimedOut(jc.Call(new EventBroadcaster
            {
                EventName = EventBroadcaster.BeforeShutdownEventName,
            }), "jc.Call(new EventBroadcaster(...BeforeShutdown)) #2");

            // js is subscribed to BeforeShutdown event, sc is not
            await Assert_NotTimedOut(jtcs.Task, "jtcs.Task #2");

            Assert.AreEqual(1, scounter);
            Assert.AreEqual(2, jcounter);
            Assert.IsTrue(jcancel);

            // unsubscribe sc from AfterStartup event
            await Assert_NotTimedOut(sunsubscribe(), "sunsubscribe()");

            // call EventBroadcaster.AfterStartup
            await Assert_NotTimedOut(jc.Call(new EventBroadcaster
            {
                EventName = EventBroadcaster.AfterStartupEventName,
            }), "jc.Call(new EventBroadcaster(...AfterStartup)) #2");

            // make sure that event is not handled anymore
            await Assert_TimedOut(stcs.Task, "stcs.Task #2", Task.Delay(200));

            Assert.AreEqual(1, scounter);

            // unsubscribe jc from BeforeShutdown event
            await Assert_NotTimedOut(junsubscribe(), "junsubscribe()");

            jtcs     = new TaskCompletionSource <bool>();
            scounter = 0;
            jcounter = 0;

            // call EventBroadcaster.BeforeShutdown
            await Assert_NotTimedOut(jc.Call(new EventBroadcaster
            {
                EventName = EventBroadcaster.BeforeShutdownEventName,
            }), "jc.Call(new EventBroadcaster(...BeforeShutdown)) #3");

            // nobody is subscribed to BeforeShutdown event
            await Assert_TimedOut(jtcs.Task, "jtcs.Task #3", Task.Delay(200));

            Assert.AreEqual(0, scounter);
            Assert.AreEqual(0, jcounter);

            // make sure all incoming messages are processed
            Assert.AreEqual(0, jc.PendingMessages.Count);
            Assert.AreEqual(0, sc.PendingMessages.Count);
        }
コード例 #14
0
        protected async Task TestFilteredSubscriptionsAndUnsubscriptionsCore(JsonServer js, JsonClient jc, JsonClient sc, ICredentials credentials = null)
        {
            // unhandled exception handlers
            var connected    = 0;
            var disconnected = 0;

            js.ClientConnected    += (s, e) => connected++;
            js.ClientDisconnected += (s, e) => disconnected++;
            js.UnhandledException += (s, e) => Assert.Fail($"Unhandled server exception: {e.Exception}. Connected: {connected}, disconnected: {disconnected}.");
            jc.UnhandledException += (s, e) => Assert.Fail($"Unhandled client exception in jc (first client): {e.Exception}. Connected: {connected}, disconnected: {disconnected}.");
            sc.UnhandledException += (s, e) => Assert.Fail($"Unhandled client exception in sc (second client): {e.Exception}. Connected: {connected}, disconnected: {disconnected}.");

            // start json server and connect both clients
            js.Start();
            await Assert_NotTimedOut(jc.ConnectAsync(credentials), "jc.ConnectAsync()");
            await Assert_NotTimedOut(sc.ConnectAsync(credentials), "sc.ConnectAsync()");

            // subscribe to jc events
            var jcounter     = 0;
            var jeventArgs   = default(FilteredEventArgs);
            var jtcs         = new TaskCompletionSource <bool>();
            var junsubscribe = await Assert_NotTimedOut(jc.Subscribe <FilteredEventArgs>(
                                                            EventBroadcaster.FilteredEventName, (s, e) =>
            {
                jcounter++;
                jeventArgs = e;
                jtcs.TrySetResult(true);
            },
                                                            new Dictionary <string, string>
            {
                { nameof(FilteredEventArgs.StringProperty), "Twain" }
            }), "jc.Subscribe<FilteredEventArgs>(...)");

            // subscribe to sc events
            var scounter     = 0;
            var seventArgs   = default(FilteredEventArgs);
            var stcs         = new TaskCompletionSource <bool>();
            var sunsubscribe = await Assert_NotTimedOut(sc.Subscribe <FilteredEventArgs>(
                                                            EventBroadcaster.FilteredEventName, (s, e) =>
            {
                scounter++;
                seventArgs = e;
                stcs.TrySetResult(true);
            },
                                                            new Dictionary <string, string>
            {
                { nameof(FilteredEventArgs.StringProperty), "Mark" }
            }), "sc.Subscribe<FilteredEventArgs>(...)");

            // call EventBroadcaster.FilteredEvent
            await Assert_NotTimedOut(jc.Call(new EventBroadcaster
            {
                EventName      = EventBroadcaster.FilteredEventName,
                StringArgument = "Mark Hamill",
            }), "jc.Call(new EventBroadcaster...FilteredEvent...Mark Hamill))");

            // sc is subscribed to Mark filtered event, jc is not
            await Assert_NotTimedOut(stcs.Task, "stcs.Task");

            Assert.AreEqual(1, scounter);
            Assert.AreEqual(0, jcounter);
            Assert.AreEqual("Mark Hamill", seventArgs.StringProperty);

            // restart both task completion sources
            jtcs = new TaskCompletionSource <bool>();
            stcs = new TaskCompletionSource <bool>();

            // call EventBroadcaster.FilteredEvent
            await Assert_NotTimedOut(jc.Call(new EventBroadcaster
            {
                EventName      = EventBroadcaster.FilteredEventName,
                StringArgument = "Mark Twain",
            }), "jc.Call(new EventBroadcaster(...FilteredEvent...Mark Twain))");

            // js and sc are both subscribed to this filtered event
            await Assert_NotTimedOut(jtcs.Task, "jtcs.Task");
            await Assert_NotTimedOut(stcs.Task, "stcs.Task");

            Assert.AreEqual(2, scounter);
            Assert.AreEqual(1, jcounter);
            Assert.AreEqual("Mark Twain", jeventArgs.StringProperty);
            Assert.AreEqual("Mark Twain", seventArgs.StringProperty);

            // restart both task completion sources
            jtcs = new TaskCompletionSource <bool>();
            stcs = new TaskCompletionSource <bool>();

            // call EventBroadcaster.FilteredEvent
            await Assert_NotTimedOut(sc.Call(new EventBroadcaster
            {
                EventName      = EventBroadcaster.FilteredEventName,
                StringArgument = "TWAIN driver"
            }), "sc.Call(new EventBroadcaster(...FilteredEvent...TWAIN driver))");

            // jc is subscribed to TWAIN filtered event, sc is not
            await Assert_NotTimedOut(jtcs.Task, "jtcs.Task #2");

            Assert.AreEqual(2, scounter);
            Assert.AreEqual(2, jcounter);
            Assert.AreEqual("TWAIN driver", jeventArgs.StringProperty);

            // unsubscribe sc from the filtered event
            await Assert_NotTimedOut(sunsubscribe(), "sunsubscribe()");

            // one-way call EventBroadcaster.FilteredEvent
            jc.Notify(new EventBroadcaster
            {
                EventName      = EventBroadcaster.FilteredEventName,
                StringArgument = "Mark Knopfler"
            });

            // make sure that event is not handled anymore
            await Assert_TimedOut(stcs.Task, "stcs.Task #2", Task.Delay(200));

            Assert.AreEqual(2, scounter);
            Assert.AreEqual(2, jcounter);

            // unsubscribe jc from the filtered event
            await Assert_NotTimedOut(junsubscribe(), "junsubscribe()");

            jtcs     = new TaskCompletionSource <bool>();
            scounter = 0;
            jcounter = 0;

            // call EventBroadcaster.FilteredEvent
            await Assert_NotTimedOut(sc.Call(new EventBroadcaster
            {
                EventName      = EventBroadcaster.FilteredEventName,
                StringArgument = "Twain, Mark",
            }), "sc.Call(new EventBroadcaster(...FilteredEvent...Twain, Mark))");

            // nobody is subscribed to BeforeShutdown event
            await Assert_TimedOut(jtcs.Task, "jtcs.Task #3", Task.Delay(200));

            Assert.AreEqual(0, scounter);
            Assert.AreEqual(0, jcounter);

            // make sure all incoming messages are processed
            Assert.AreEqual(0, jc.PendingMessages.Count);
            Assert.AreEqual(0, sc.PendingMessages.Count);
        }