@@ -105,6 +105,15 @@ private Mock<IHost> CreateMockHost(SemaphoreSlim disposedSemaphore = null)
105105 public async Task StartAsync_Succeeds ( )
106106 {
107107 var hostBuilder = new Mock < IScriptHostBuilder > ( ) ;
108+ _host . Setup ( h => h . StartAsync ( It . IsAny < CancellationToken > ( ) ) )
109+ . Callback ( ( ) =>
110+ {
111+ if ( Activity . Current != null )
112+ {
113+ throw new InvalidOperationException ( "Activity flowed into host start." ) ;
114+ }
115+ } )
116+ . Returns ( Task . CompletedTask ) ;
108117 hostBuilder . Setup ( b => b . BuildHost ( It . IsAny < bool > ( ) , It . IsAny < bool > ( ) ) ) . Returns ( _host . Object ) ;
109118
110119 _webHostLoggerProvider = new TestLoggerProvider ( ) ;
@@ -124,6 +133,8 @@ public async Task StartAsync_Succeeds()
124133 _hostBuiltChangeTokenSource ,
125134 _hostBuiltChangeTokenSourceResolverOptions ) ;
126135
136+ using Activity activity = new ( "TestActivity_ShouldNotFlowIntoHostStart" ) ;
137+ activity . Start ( ) ;
127138 await _hostService . StartAsync ( CancellationToken . None ) ;
128139
129140 // add general post startup validations here
@@ -190,12 +201,9 @@ public async Task HostRestart_Specialization_Succeeds()
190201 await _hostService . StartAsync ( CancellationToken . None ) ;
191202 Assert . True ( AreRequiredMetricsGenerated ( metricsLogger ) ) ;
192203
193- Thread restartHostThread = new Thread ( new ThreadStart ( RestartHost ) ) ;
194- Thread specializeHostThread = new Thread ( new ThreadStart ( SpecializeHost ) ) ;
195- restartHostThread . Start ( ) ;
196- specializeHostThread . Start ( ) ;
197- restartHostThread . Join ( ) ;
198- specializeHostThread . Join ( ) ;
204+ Task restartHost = _hostService . RestartHostAsync ( "test" , default ) ;
205+ Task specializeHost = SpecializeHostAsync ( ) ;
206+ await Task . WhenAll ( restartHost , specializeHost ) . TestWaitAsync ( TimeSpan . FromSeconds ( 60 ) ) ; // this is a slow test for some reason.
199207
200208 var logMessages = _webHostLoggerProvider . GetAllLogMessages ( ) . Where ( m => m . FormattedMessage . Contains ( "Restarting host." ) ) ;
201209 Assert . Equal ( 2 , logMessages . Count ( ) ) ;
@@ -204,9 +212,8 @@ public async Task HostRestart_Specialization_Succeeds()
204212 [ Fact ]
205213 public async Task HostRestart_DuringInitializationWithError_Recovers ( )
206214 {
207- SemaphoreSlim semaphore = new SemaphoreSlim ( 1 , 1 ) ;
208- await semaphore . WaitAsync ( ) ;
209- bool waitingInHostBuilder = false ;
215+ TaskCompletionSource startBlock = new ( ) ;
216+ TaskCompletionSource initialStart = new ( ) ;
210217 var metricsLogger = new TestMetricsLogger ( ) ;
211218
212219 // Have the first host start, but pause. We'll issue a restart, then let
@@ -216,8 +223,8 @@ public async Task HostRestart_DuringInitializationWithError_Recovers()
216223 . Setup ( h => h . StartAsync ( It . IsAny < CancellationToken > ( ) ) )
217224 . Returns ( async ( ) =>
218225 {
219- waitingInHostBuilder = true ;
220- await semaphore . WaitAsync ( ) ;
226+ initialStart . SetResult ( ) ;
227+ await startBlock . Task ;
221228 throw new InvalidOperationException ( "Something happened at startup!" ) ;
222229 } ) ;
223230
@@ -226,17 +233,14 @@ public async Task HostRestart_DuringInitializationWithError_Recovers()
226233 . Setup ( h => h . StartAsync ( It . IsAny < CancellationToken > ( ) ) )
227234 . Returns ( ( ) => Task . CompletedTask ) ;
228235
229- var hostBuilder = new Mock < IScriptHostBuilder > ( ) ;
230- hostBuilder . SetupSequence ( b => b . BuildHost ( It . IsAny < bool > ( ) , It . IsAny < bool > ( ) ) )
231- . Returns ( hostA . Object )
232- . Returns ( hostB . Object ) ;
236+ OrderedScriptHostBuilder hostBuilder = new ( hostA . Object , hostB . Object ) ;
233237
234238 _webHostLoggerProvider = new TestLoggerProvider ( ) ;
235239 _loggerFactory = new LoggerFactory ( ) ;
236240 _loggerFactory . AddProvider ( _webHostLoggerProvider ) ;
237241
238242 _hostService = new WebJobsScriptHostService (
239- _monitor , hostBuilder . Object , _loggerFactory ,
243+ _monitor , hostBuilder , _loggerFactory ,
240244 _mockScriptWebHostEnvironment . Object , _mockEnvironment . Object ,
241245 _hostPerformanceManager , _healthMonitorOptions ,
242246 metricsLogger , new Mock < IApplicationLifetime > ( ) . Object ,
@@ -245,20 +249,17 @@ public async Task HostRestart_DuringInitializationWithError_Recovers()
245249 TestLoggerProvider hostALogger = hostA . Object . GetTestLoggerProvider ( ) ;
246250 TestLoggerProvider hostBLogger = hostB . Object . GetTestLoggerProvider ( ) ;
247251
248- Task initialStart = _hostService . StartAsync ( CancellationToken . None ) ;
249-
250- Thread restartHostThread = new Thread ( new ThreadStart ( RestartHost ) ) ;
251- restartHostThread . Start ( ) ;
252- await TestHelpers . Await ( ( ) => waitingInHostBuilder ) ;
252+ Task firstStart = _hostService . StartAsync ( CancellationToken . None ) ;
253+ await initialStart . Task . TestWaitAsync ( ) ;
254+ Task restartTask = _hostService . RestartHostAsync ( "test" , default ) ;
253255
254256 // Now let the first host continue. It will throw, which should be correctly handled.
255- semaphore . Release ( ) ;
257+ startBlock . SetResult ( ) ;
256258
257- await TestHelpers . Await ( ( ) => _hostService . State == ScriptHostState . Running ) ;
258-
259- restartHostThread . Join ( ) ;
259+ await restartTask ;
260+ Assert . Equal ( ScriptHostState . Running , _hostService . State ) ;
260261
261- await initialStart ;
262+ await firstStart ;
262263 Assert . True ( AreRequiredMetricsGenerated ( metricsLogger ) ) ;
263264
264265 // Make sure the error was logged to the correct logger
@@ -280,61 +281,48 @@ public async Task HostRestart_DuringInitializationWithError_Recovers()
280281 [ Fact ]
281282 public async Task HostRestart_DuringInitialization_Cancels ( )
282283 {
283- SemaphoreSlim semaphore = new SemaphoreSlim ( 1 , 1 ) ;
284- await semaphore . WaitAsync ( ) ;
285- bool waitingInHostBuilder = false ;
284+ TaskCompletionSource startBlock = new ( ) ;
285+ TaskCompletionSource initialStart = new ( ) ;
286286 var metricsLogger = new TestMetricsLogger ( ) ;
287287
288288 // Have the first host start, but pause. We'll issue a restart, then
289- // let this first host continue and see the cancelation token has fired.
289+ // let this first host continue and see the cancellation token has fired.
290290 var hostA = CreateMockHost ( ) ;
291291 hostA
292292 . Setup ( h => h . StartAsync ( It . IsAny < CancellationToken > ( ) ) )
293293 . Returns < CancellationToken > ( async ct =>
294294 {
295- waitingInHostBuilder = true ;
296- await semaphore . WaitAsync ( ) ;
297- ct . ThrowIfCancellationRequested ( ) ;
295+ initialStart . SetResult ( ) ;
296+ await startBlock . Task . WaitAsync ( ct ) ;
298297 } ) ;
299298
300299 var hostB = CreateMockHost ( ) ;
301300 hostB
302301 . Setup ( h => h . StartAsync ( It . IsAny < CancellationToken > ( ) ) )
303302 . Returns ( ( ) => Task . CompletedTask ) ;
304303
305- var hostBuilder = new Mock < IScriptHostBuilder > ( ) ;
306- hostBuilder . SetupSequence ( b => b . BuildHost ( It . IsAny < bool > ( ) , It . IsAny < bool > ( ) ) )
307- . Returns ( hostA . Object )
308- . Returns ( hostB . Object ) ;
304+ OrderedScriptHostBuilder hostBuilder = new ( hostA . Object , hostB . Object ) ;
309305
310306 _webHostLoggerProvider = new TestLoggerProvider ( ) ;
311307 _loggerFactory = new LoggerFactory ( ) ;
312308 _loggerFactory . AddProvider ( _webHostLoggerProvider ) ;
313309
314310 _hostService = new WebJobsScriptHostService (
315- _monitor , hostBuilder . Object , _loggerFactory ,
311+ _monitor , hostBuilder , _loggerFactory ,
316312 _mockScriptWebHostEnvironment . Object , _mockEnvironment . Object ,
317313 _hostPerformanceManager , _healthMonitorOptions ,
318314 metricsLogger , new Mock < IApplicationLifetime > ( ) . Object ,
319315 _mockConfig , new TestScriptEventManager ( ) , _hostMetrics , _functionsHostingConfigOptions , _hostBuiltChangeTokenSource , _hostBuiltChangeTokenSourceResolverOptions ) ;
320316
321317 TestLoggerProvider hostALogger = hostA . Object . GetTestLoggerProvider ( ) ;
318+ Task firstStart = _hostService . StartAsync ( default ) ;
319+ await initialStart . Task . TestWaitAsync ( ) ;
320+ Task restartTask = _hostService . RestartHostAsync ( "test" , default ) ;
322321
323- Task initialStart = _hostService . StartAsync ( CancellationToken . None ) ;
324-
325- Thread restartHostThread = new Thread ( new ThreadStart ( RestartHost ) ) ;
326- restartHostThread . Start ( ) ;
327- await TestHelpers . Await ( ( ) => waitingInHostBuilder ) ;
328- await TestHelpers . Await ( ( ) => _webHostLoggerProvider . GetLog ( ) . Contains ( "Restart requested." ) ) ;
329-
330- // Now let the first host continue. It will see that startup is canceled.
331- semaphore . Release ( ) ;
332-
333- await TestHelpers . Await ( ( ) => _hostService . State == ScriptHostState . Running ) ;
334-
335- restartHostThread . Join ( ) ;
322+ await restartTask ;
323+ Assert . Equal ( ScriptHostState . Running , _hostService . State ) ;
336324
337- await initialStart ;
325+ await firstStart ;
338326 Assert . True ( AreRequiredMetricsGenerated ( metricsLogger ) ) ;
339327
340328 // Make sure the error was logged to the correct logger
@@ -582,12 +570,7 @@ public async Task DependencyTrackingTelemetryModule_Race()
582570 await listenerTask ;
583571 }
584572
585- public void RestartHost ( )
586- {
587- _hostService . RestartHostAsync ( "test" , CancellationToken . None ) . Wait ( ) ;
588- }
589-
590- public void SpecializeHost ( )
573+ public Task SpecializeHostAsync ( )
591574 {
592575 var mockScriptWebHostEnvironment = new Mock < IScriptWebHostEnvironment > ( ) ;
593576 Mock < IConfigurationRoot > mockConfiguration = new Mock < IConfigurationRoot > ( ) ;
@@ -600,7 +583,7 @@ public void SpecializeHost()
600583 var hostNameProvider = new HostNameProvider ( mockEnvironment . Object ) ;
601584 var mockApplicationLifetime = new Mock < IApplicationLifetime > ( MockBehavior . Strict ) ;
602585 var manager = new StandbyManager ( _hostService , mockLanguageWorkerChannelManager . Object , mockConfiguration . Object , mockScriptWebHostEnvironment . Object , mockEnvironment . Object , _monitor , testLogger , hostNameProvider , mockApplicationLifetime . Object , new TestMetricsLogger ( ) ) ;
603- manager . SpecializeHostAsync ( ) . Wait ( ) ;
586+ return manager . SpecializeHostAsync ( ) ;
604587 }
605588
606589 private bool AreRequiredMetricsGenerated ( TestMetricsLogger testMetricsLogger )
@@ -646,5 +629,16 @@ public IHost BuildHost(bool skipHostStartup, bool skipHostConfigurationParsing)
646629 return null ;
647630 }
648631 }
632+
633+ // Moq SetupSequence doesn't work with execution context suppression.
634+ private class OrderedScriptHostBuilder ( params IHost [ ] hosts ) : IScriptHostBuilder
635+ {
636+ private readonly Queue < IHost > _hosts = new ( hosts ) ;
637+
638+ public IHost BuildHost ( bool skipHostStartup , bool skipHostConfigurationParsing )
639+ {
640+ return _hosts . Dequeue ( ) ;
641+ }
642+ }
649643 }
650644}
0 commit comments