@@ -107,6 +107,54 @@ public async Task DelayAsync(TimeSpan? delay = null, TimeSpan? playbackDelay = n
107107 }
108108 }
109109
110+ /// <summary>
111+ /// A number of our tests have built in delays while we wait an expected
112+ /// amount of time for a service operation to complete and this method
113+ /// allows us to wait (unless we're playing back recordings, which can
114+ /// complete immediately).
115+ /// <para>This method allows us to return early if the <paramref name="predicate"/> condition evaluates to true.
116+ /// It evaluates the predicate after every <paramref name="delayPerIteration"/> or <paramref name="playbackDelayPerIteration"/> time span.
117+ /// It returns when the condition evaluates to <c>true</c>, or if the condition stays false after <paramref name="maxIterations"/> checks.</para>
118+ /// </summary>
119+ /// <param name="predicate">Condition that will result in early end of delay when it evaluates to <c>true</c>.</param>
120+ /// <param name="delayPerIteration">The time to wait per iteration. Defaults to 1s.</param>
121+ /// <param name="playbackDelayPerIteration">
122+ /// An optional time wait if we're playing back a recorded test. This
123+ /// is useful for allowing client side events to get processed.
124+ /// </param>
125+ /// <param name="maxIterations">Maximum number of iterations of the wait-and-check cycle.</param>
126+ /// <param name="cancellationToken">Optional <see cref="CancellationToken"/> to check.</param>
127+ /// <returns>A task that will (optionally) delay.</returns>
128+ /// <exception cref="OperationCanceledException">The <paramref name="cancellationToken"/> was signaled.</exception>
129+ public async Task ConditionallyDelayAsync ( Func < bool > predicate , TimeSpan ? delayPerIteration = null , TimeSpan ? playbackDelayPerIteration = null , uint maxIterations = 1 , CancellationToken cancellationToken = default )
130+ {
131+ TimeSpan waitPeriod = TimeSpan . Zero ;
132+
133+ for ( int i = 0 ; i < maxIterations ; i ++ )
134+ {
135+ if ( predicate ( ) )
136+ {
137+ TestContext . WriteLine ( $ "{ nameof ( ConditionallyDelayAsync ) } : Condition evaluated to true in { waitPeriod . TotalSeconds } seconds.") ;
138+ return ;
139+ }
140+
141+ cancellationToken . ThrowIfCancellationRequested ( ) ;
142+
143+ if ( Mode != RecordedTestMode . Playback )
144+ {
145+ waitPeriod += delayPerIteration ?? TimeSpan . FromSeconds ( 1 ) ;
146+ await Task . Delay ( delayPerIteration ?? TimeSpan . FromSeconds ( 1 ) ) ;
147+ }
148+ else if ( playbackDelayPerIteration != null )
149+ {
150+ waitPeriod += playbackDelayPerIteration . Value ;
151+ await Task . Delay ( playbackDelayPerIteration . Value ) ;
152+ }
153+ }
154+
155+ TestContext . WriteLine ( $ "{ nameof ( ConditionallyDelayAsync ) } : Condition did not evaluate to true in { waitPeriod . TotalSeconds } seconds.") ;
156+ }
157+
110158 /// <summary>
111159 /// Assert that we can catch the desired exception. NUnit's default
112160 /// forces everything to be sync.
0 commit comments