private void RunReplication(ReplicatorConfiguration config, int expectedErrCode, C4ErrorDomain expectedErrDomain)
        {
            Misc.SafeSwap(ref _repl, new Replicator(config));
            _waitAssert = new WaitAssert();
            var token = _repl.AddChangeListener((sender, args) =>
            {
                _waitAssert.RunConditionalAssert(() =>
                {
                    VerifyChange(args, expectedErrCode, expectedErrDomain);
                    if (config.Continuous && args.Status.Activity == ReplicatorActivityLevel.Idle &&
                        args.Status.Progress.Completed == args.Status.Progress.Total)
                    {
                        ((Replicator)sender).Stop();
                    }

                    return(args.Status.Activity == ReplicatorActivityLevel.Stopped);
                });
            });

            _repl.Start();
            try {
                _waitAssert.WaitForResult(TimeSpan.FromSeconds(10));
            } catch {
                _repl.Stop();
                throw;
            } finally {
                _repl.RemoveChangeListener(token);
            }
        }
        private void WithActiveReplicatorAndURLEndpointListeners(bool isCloseNotDelete)
        {
            WaitAssert waitIdleAssert1    = new WaitAssert();
            WaitAssert waitStoppedAssert1 = new WaitAssert();

            _listener = CreateListener();
            var _listener2 = CreateNewListener();

            _listener.Config.Database.ActiveStoppables.Count.Should().Be(2);
            _listener2.Config.Database.ActiveStoppables.Count.Should().Be(2);

            using (var doc1 = new MutableDocument("doc1"))
                using (var doc2 = new MutableDocument("doc2")) {
                    doc1.SetString("name", "Sam");
                    Db.Save(doc1);
                    doc2.SetString("name", "Mary");
                    OtherDb.Save(doc2);
                }

            var target  = new DatabaseEndpoint(Db);
            var config1 = CreateConfig(target, ReplicatorType.PushAndPull, true, sourceDb: OtherDb);
            var repl1   = new Replicator(config1);

            repl1.AddChangeListener((sender, args) => {
                waitIdleAssert1.RunConditionalAssert(() => {
                    return(args.Status.Activity == ReplicatorActivityLevel.Idle);
                });

                waitStoppedAssert1.RunConditionalAssert(() => {
                    return(args.Status.Activity == ReplicatorActivityLevel.Stopped);
                });
            });

            repl1.Start();

            waitIdleAssert1.WaitForResult(TimeSpan.FromSeconds(10));
            OtherDb.ActiveStoppables.Count.Should().Be(3);

            if (isCloseNotDelete)
            {
                OtherDb.Close();
            }
            else
            {
                OtherDb.Delete();
            }

            OtherDb.ActiveStoppables.Count.Should().Be(0);
            OtherDb.IsClosedLocked.Should().Be(true);

            waitStoppedAssert1.WaitForResult(TimeSpan.FromSeconds(30));
        }
        public void TestStopContinuousReplicator()
        {
            var config = CreateConfig(true, false, true);

            using (var r = new Replicator(config)) {
                var stopWhen = new[]
                {
                    ReplicatorActivityLevel.Connecting, ReplicatorActivityLevel.Busy,
                    ReplicatorActivityLevel.Idle, ReplicatorActivityLevel.Idle
                };

                foreach (var when in stopWhen)
                {
                    var stopped    = 0;
                    var waitAssert = new WaitAssert();
                    var token      = r.AddChangeListener((sender, args) =>
                    {
                        waitAssert.RunConditionalAssert(() =>
                        {
                            VerifyChange(args, 0, 0);

                            // On Windows, at least, sometimes the connection is so fast that Connecting never gets called
                            if ((args.Status.Activity == when ||
                                 (when == ReplicatorActivityLevel.Connecting && args.Status.Activity > when)) &&
                                Interlocked.Exchange(ref stopped, 1) == 0)
                            {
                                WriteLine("***** Stop Replicator *****");
                                ((Replicator)sender).Stop();
                            }

                            if (args.Status.Activity == ReplicatorActivityLevel.Stopped)
                            {
                                WriteLine("Stopped!");
                            }

                            return(args.Status.Activity == ReplicatorActivityLevel.Stopped);
                        });
                    });

                    WriteLine("***** Start Replicator *****");
                    r.Start();
                    try {
                        waitAssert.WaitForResult(TimeSpan.FromSeconds(5));
                    } finally {
                        r.RemoveChangeListener(token);
                    }

                    Task.Delay(100).Wait();
                }
            }
        }
        private void DocumentChanged(object sender, DocumentChangedEventArgs args)
        {
            if (_docCallbackShouldThrow)
            {
                _wa.RunAssert(() => throw new InvalidOperationException("Unexpected doc change notification"));
            }
            else
            {
                WriteLine($"Received {args.DocumentID}");
                _wa.RunConditionalAssert(() =>
                {
                    lock (_expectedDocumentChanges) {
                        _expectedDocumentChanges.Should()
                        .Contain(args.DocumentID, "because otherwise a rogue notification came");
                        _expectedDocumentChanges.Remove(args.DocumentID);

                        WriteLine(
                            $"Expecting {_expectedDocumentChanges.Count} more changes ({JsonConvert.SerializeObject(_expectedDocumentChanges)})");
                        return(_expectedDocumentChanges.Count == 0);
                    }
                });
            }
        }
        public void TestStatus()
        {
            ulong maxConnectionCount = 0UL;
            ulong maxActiveCount     = 0UL;

            //init and start a listener
            _listener = CreateListener(false);

            //listener is started at this point
            _listener.Status.ConnectionCount.Should().Be(0, "Listener's connection count should be 0 because no client connection has been established.");
            _listener.Status.ActiveConnectionCount.Should().Be(0, "Listener's active connection count should be 0 because no client connection has been established.");

            using (var doc1 = new MutableDocument())
                using (var doc2 = new MutableDocument()) {
                    doc1.SetString("name", "Sam");
                    Db.Save(doc1);
                    doc2.SetString("name", "Mary");
                    OtherDb.Save(doc2);
                }

            var targetEndpoint = _listener.LocalEndpoint();
            var config         = new ReplicatorConfiguration(Db, targetEndpoint);

            using (var repl = new Replicator(config)) {
                var waitAssert = new WaitAssert();
                var token      = repl.AddChangeListener((sender, args) =>
                {
                    WriteLine($"Yeehaw {_listener.Status.ConnectionCount} / {_listener.Status.ActiveConnectionCount}");

                    maxConnectionCount = Math.Max(maxConnectionCount, _listener.Status.ConnectionCount);
                    maxActiveCount     = Math.Max(maxActiveCount, _listener.Status.ActiveConnectionCount);

                    waitAssert.RunConditionalAssert(() =>
                    {
                        return(args.Status.Activity == ReplicatorActivityLevel.Stopped);
                    });
                });

                repl.Start();
                while (repl.Status.Activity != ReplicatorActivityLevel.Busy)
                {
                    Thread.Sleep(100);
                }

                // For some reason running on mac throws off the timing enough so that the active connection count
                // of 1 is never seen.  So record the value right after it becomes busy.
                maxConnectionCount = Math.Max(maxConnectionCount, _listener.Status.ConnectionCount);
                maxActiveCount     = Math.Max(maxActiveCount, _listener.Status.ActiveConnectionCount);

                try {
                    waitAssert.WaitForResult(TimeSpan.FromSeconds(100));
                } finally {
                    repl.RemoveChangeListener(token);
                }
            }

            maxConnectionCount.Should().Be(1);
            maxActiveCount.Should().Be(1);

            //stop the listener
            _listener.Stop();
            _listener.Status.ConnectionCount.Should().Be(0, "Listener's connection count should be 0 because the connection is stopped.");
            _listener.Status.ActiveConnectionCount.Should().Be(0, "Listener's active connection count should be 0 because the connection is stopped.");
        }