// Copyright 2012-2026 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Mirrors Go checkFor from server/test_test.go. using System.Diagnostics; using ZB.MOM.NatsNet.Server; namespace ZB.MOM.NatsNet.Server.IntegrationTests.Helpers; /// /// Retry/polling helpers for integration tests. /// Mirrors Go checkFor from server/test_test.go. /// internal static class CheckHelper { /// /// Polls repeatedly until it returns null (success) /// or the timeout expires, in which case the last exception is thrown. /// Mirrors Go checkFor(t, timeout, interval, func() error). /// public static void CheckFor(TimeSpan timeout, TimeSpan interval, Func check) { var sw = Stopwatch.StartNew(); Exception? last = null; while (sw.Elapsed < timeout) { last = check(); if (last == null) return; Thread.Sleep(interval); } // One final attempt after the sleep boundary. last = check(); if (last == null) return; throw new TimeoutException( $"CheckFor timed out after {timeout}: {last.Message}", last); } /// /// Async version of . Uses Task.Delay instead of /// Thread.Sleep to avoid blocking the thread pool. /// public static async Task CheckForAsync( TimeSpan timeout, TimeSpan interval, Func> check, CancellationToken cancellationToken = default) { var sw = Stopwatch.StartNew(); Exception? last = null; while (sw.Elapsed < timeout) { last = await check().ConfigureAwait(false); if (last == null) return; await Task.Delay(interval, cancellationToken).ConfigureAwait(false); } // One final attempt. last = await check().ConfigureAwait(false); if (last == null) return; throw new TimeoutException( $"CheckForAsync timed out after {timeout}: {last.Message}", last); } /// /// Waits until all servers in have formed a cluster /// (each server sees at least servers.Length - 1 routes). /// Uses a 10-second timeout with 100 ms poll interval. /// Mirrors Go checkClusterFormed. /// public static void CheckClusterFormed(params NatsServer[] servers) { var expected = servers.Length - 1; CheckFor(TimeSpan.FromSeconds(10), TimeSpan.FromMilliseconds(100), () => { foreach (var s in servers) { var routes = s.NumRoutes(); if (routes < expected) return new Exception( $"Server {s.Options.ServerName} has {routes} routes, expected {expected}."); } return null; }); } /// /// Waits until the given server has at least /// leaf node connections. /// Uses a 10-second timeout with 100 ms poll interval. /// Mirrors Go checkLeafNodeConnectedCount. /// public static void CheckLeafNodeConnectedCount(NatsServer server, int expected) { CheckFor(TimeSpan.FromSeconds(10), TimeSpan.FromMilliseconds(100), () => { var count = server.NumLeafNodes(); if (count < expected) return new Exception( $"Server {server.Options.ServerName} has {count} leaf nodes, expected {expected}."); return null; }); } }