Skip to content

Commit bf6fca3

Browse files
committed
improving Update/RenameAndRestart tests
1 parent 7ee8577 commit bf6fca3

File tree

3 files changed

+110
-72
lines changed

3 files changed

+110
-72
lines changed

test/WebJobs.Script.Tests.Integration/EndToEndTestFixture.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public abstract class EndToEndTestFixture : IDisposable
2828
private readonly ScriptSettingsManager _settingsManager;
2929
private readonly ManualResetEventSlim _hostStartedEvent = new ManualResetEventSlim();
3030

31-
protected EndToEndTestFixture(string rootPath, string testId, ProxyClientExecutor proxyClient = null)
31+
protected EndToEndTestFixture(string rootPath, string testId, ProxyClientExecutor proxyClient = null, bool startHost = true)
3232
{
3333
_settingsManager = ScriptSettingsManager.Instance;
3434
FixtureId = testId;
@@ -71,9 +71,12 @@ protected EndToEndTestFixture(string rootPath, string testId, ProxyClientExecuto
7171
// Note: This has to be done after the call to Initialize or all file logging will be disabled.
7272
Host.ScriptConfig.HostConfig.Tracing.ConsoleLevel = TraceLevel.Off;
7373

74-
Host.HostStarted += (s, e) => _hostStartedEvent.Set();
75-
Host.Start();
76-
_hostStartedEvent.Wait(TimeSpan.FromSeconds(30));
74+
if (startHost)
75+
{
76+
Host.HostStarted += (s, e) => _hostStartedEvent.Set();
77+
Host.Start();
78+
_hostStartedEvent.Wait(TimeSpan.FromSeconds(30));
79+
}
7780
}
7881

7982
public Mock<IScriptHostEnvironment> ScriptHostEnvironmentMock { get; }

test/WebJobs.Script.Tests.Integration/Host/ScriptHostManagerTests.cs

Lines changed: 96 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -41,104 +41,121 @@ public ScriptHostManagerTests()
4141
// Update a script file (the function.json) to force the ScriptHost to re-index and pick up new changes.
4242
// Test with timers:
4343
[Fact]
44-
public async Task UpdateFileAndRestart()
44+
public void UpdateFileAndRestart()
4545
{
46-
CancellationTokenSource cts = new CancellationTokenSource();
47-
var fixture = new NodeEndToEndTests.TestFixture();
48-
var blob1 = UpdateOutputName("testblob", "first", fixture);
49-
50-
await fixture.Host.StopAsync();
46+
var fixture = new NodeEndToEndTests.TestFixture(false);
5147
var config = fixture.Host.ScriptConfig;
5248

53-
ExceptionDispatchInfo exception = null;
54-
ExceptionDispatchInfo updateException = null;
49+
config.OnConfigurationApplied = c =>
50+
{
51+
c.Functions = new Collection<string> { "TimerTrigger" };
52+
};
53+
54+
var blob1 = UpdateOutputName("testblob", "first", fixture);
55+
5556
using (var eventManager = new ScriptEventManager())
5657
using (var manager = new ScriptHostManager(config, eventManager))
5758
{
59+
string GetErrorTraces()
60+
{
61+
var messages = fixture.TraceWriter.GetTraces()
62+
.Where(t => t.Level == TraceLevel.Error)
63+
.Select(t => t.Message);
64+
65+
return string.Join(Environment.NewLine, messages);
66+
}
67+
68+
List<Exception> exceptions = new List<Exception>();
69+
5870
// Background task to run while the main thread is pumping events at RunAndBlock().
59-
Thread t = new Thread(_ =>
71+
Thread backgroundThread = new Thread(_ =>
6072
{
6173
try
6274
{
6375
// don't start until the manager is running
64-
TestHelpers.Await(() => manager.State == ScriptHostState.Running).Wait();
76+
TestHelpers.Await(() => manager.State == ScriptHostState.Running,
77+
userMessageCallback: GetErrorTraces).GetAwaiter().GetResult();
6578

6679
// Wait for initial execution.
6780
TestHelpers.Await(() =>
68-
{
69-
bool exists = blob1.Exists();
70-
return exists;
71-
}, timeout: 10 * 1000).Wait();
81+
{
82+
bool exists = blob1.Exists();
83+
return exists;
84+
}, timeout: 10 * 1000, userMessageCallback: GetErrorTraces).GetAwaiter().GetResult();
7285

7386
// This changes the bindings so that we now write to blob2
7487
var blob2 = UpdateOutputName("first", "testblob", fixture);
7588

7689
// wait for newly executed
7790
TestHelpers.Await(() =>
78-
{
79-
bool exists = blob2.Exists();
80-
return exists;
81-
}, timeout: 30 * 1000).Wait();
91+
{
92+
bool exists = blob2.Exists();
93+
return exists;
94+
}, timeout: 30 * 1000, userMessageCallback: GetErrorTraces).GetAwaiter().GetResult();
95+
96+
// The TimerTrigger can fire before the host is fully started. To be more
97+
// reliably clean up the test, wait until it is running before calling Stop.
98+
TestHelpers.Await(() => manager.State == ScriptHostState.Running,
99+
userMessageCallback: GetErrorTraces).GetAwaiter().GetResult();
82100
}
83101
catch (Exception ex)
84102
{
85-
exception = ExceptionDispatchInfo.Capture(ex);
103+
exceptions.Add(ex);
86104
}
87105
finally
88106
{
89107
try
90108
{
91-
UpdateOutputName("first", "testblob", fixture);
109+
// Calling Stop (rather than using a token) lets us wait until all listeners have stopped.
110+
manager.Stop();
92111
}
93112
catch (Exception ex)
94113
{
95-
updateException = ExceptionDispatchInfo.Capture(ex);
114+
exceptions.Add(ex);
96115
}
97116
}
98-
99-
cts.Cancel();
100117
});
101-
t.Start();
102-
103-
manager.RunAndBlock(cts.Token);
104118

105-
t.Join();
119+
try
120+
{
121+
backgroundThread.Start();
122+
manager.RunAndBlock();
123+
Assert.True(backgroundThread.Join(60000), "The background task did not complete in 60 seconds.");
106124

107-
Assert.True(exception == null, exception?.SourceException?.ToString());
108-
Assert.True(updateException == null, "Failed to update output name: " + updateException?.SourceException?.ToString());
125+
string exceptionString = string.Join(Environment.NewLine, exceptions.Select(p => p.ToString()));
126+
Assert.True(exceptions.Count() == 0, exceptionString);
127+
}
128+
finally
129+
{
130+
// make sure to put the original names back
131+
UpdateOutputName("first", "testblob", fixture);
132+
}
109133
}
110134
}
111135

112136
[Fact]
113-
public async Task RenameFunctionAndRestart()
137+
public void RenameFunctionAndRestart()
114138
{
115139
var oldDirectory = Path.Combine(Directory.GetCurrentDirectory(), "TestScripts/Node/TimerTrigger");
116140
var newDirectory = Path.Combine(Directory.GetCurrentDirectory(), "TestScripts/Node/MovedTrigger");
117141

118-
CancellationTokenSource cts = new CancellationTokenSource();
142+
var fixture = new NodeEndToEndTests.TestFixture(false);
143+
var config = fixture.Host.ScriptConfig;
119144

120-
// Don't wait forever
121-
bool timeout = false;
122-
Task ignore = Task.Delay(TimeSpan.FromMinutes(3)).ContinueWith((t) =>
145+
config.OnConfigurationApplied = c =>
123146
{
124-
timeout = true;
125-
cts.Cancel();
126-
});
127-
128-
var fixture = new NodeEndToEndTests.TestFixture();
129-
await fixture.Host.StopAsync();
130-
var config = fixture.Host.ScriptConfig;
131-
List<string> pollMessages = new List<string>();
147+
c.Functions = new Collection<string> { "TimerTrigger", "MovedTrigger" };
148+
};
132149

133150
var blob = fixture.TestOutputContainer.GetBlockBlobReference("testblob");
134-
135-
ExceptionDispatchInfo exception = null;
136-
ExceptionDispatchInfo moveException = null;
137151
var mockEnvironment = new Mock<IScriptHostEnvironment>();
152+
138153
using (var eventManager = new ScriptEventManager())
139154
using (var manager = new ScriptHostManager(config, eventManager, mockEnvironment.Object))
140155
using (var resetEvent = new ManualResetEventSlim())
141156
{
157+
List<Exception> exceptions = new List<Exception>();
158+
142159
mockEnvironment.Setup(e => e.RestartHost())
143160
.Callback(() =>
144161
{
@@ -147,21 +164,21 @@ public async Task RenameFunctionAndRestart()
147164
});
148165

149166
// Background task to run while the main thread is pumping events at RunAndBlock().
150-
Thread t = new Thread(_ =>
167+
Thread backgroundThread = new Thread(_ =>
151168
{
152169
try
153170
{
154171
// don't start until the manager is running
155172
TestHelpers.Await(() => manager.State == ScriptHostState.Running,
156-
userMessageCallback: () => "Host did not start in time.").Wait();
173+
userMessageCallback: () => "Host did not start in time.").GetAwaiter().GetResult();
157174

158175
// Wait for initial execution.
159176
TestHelpers.Await(() =>
160177
{
161178
bool exists = blob.Exists();
162-
pollMessages.Add($"TimerTrigger: [{DateTime.UtcNow.ToString("HH:mm:ss.fff")}] '{blob.Uri}' exists: {exists}");
163179
return exists;
164-
}, timeout: 10 * 1000, userMessageCallback: () => $"Blob '{blob.Uri}' was not created by 'TimerTrigger' in time.").Wait();
180+
}, timeout: 10 * 1000,
181+
userMessageCallback: () => $"Blob '{blob.Uri}' was not created by 'TimerTrigger' in time.").GetAwaiter().GetResult();
165182

166183
// find __dirname from blob
167184
string text;
@@ -184,9 +201,9 @@ public async Task RenameFunctionAndRestart()
184201
TestHelpers.Await(() =>
185202
{
186203
bool exists = blob.Exists();
187-
pollMessages.Add($"MovedTrigger: [{DateTime.UtcNow.ToString("HH:mm:ss.fff")}] '{blob.Uri}' exists: {exists}");
188204
return exists;
189-
}, timeout: 30 * 1000, userMessageCallback: () => $"Blob '{blob.Uri}' was not created by 'MovedTrigger' in time.").Wait();
205+
}, timeout: 30 * 1000,
206+
userMessageCallback: () => $"Blob '{blob.Uri}' was not created by 'MovedTrigger' in time.").GetAwaiter().GetResult();
190207

191208
using (var stream = new MemoryStream())
192209
{
@@ -195,36 +212,46 @@ public async Task RenameFunctionAndRestart()
195212
}
196213

197214
Assert.Contains("MovedTrigger", text);
215+
216+
// The TimerTrigger can fire before the host is fully started. To be more
217+
// reliably clean up the test, wait until it is running before calling Stop.
218+
TestHelpers.Await(() => manager.State == ScriptHostState.Running).GetAwaiter().GetResult();
198219
}
199220
catch (Exception ex)
200221
{
201-
exception = ExceptionDispatchInfo.Capture(ex);
222+
exceptions.Add(ex);
202223
}
203224
finally
204225
{
205226
try
206227
{
207-
Directory.Move(newDirectory, oldDirectory);
228+
manager.Stop();
208229
}
209230
catch (Exception ex)
210231
{
211-
moveException = ExceptionDispatchInfo.Capture(ex);
232+
exceptions.Add(ex);
212233
}
213234
}
214-
215-
cts.Cancel();
216235
});
217236

218-
t.Start();
219-
220-
manager.RunAndBlock(cts.Token);
221-
222-
Assert.True(t.Join(TimeSpan.FromSeconds(10)), "Thread join timed out.");
237+
try
238+
{
239+
backgroundThread.Start();
240+
manager.RunAndBlock();
241+
Assert.True(backgroundThread.Join(60000), "The background task did not complete in 60 seconds.");
223242

224-
Assert.False(timeout, "The test timed out and was canceled.");
225-
string pollString = string.Join(Environment.NewLine, pollMessages);
226-
Assert.True(exception == null, pollString + Environment.NewLine + exception?.SourceException?.ToString());
227-
Assert.True(moveException == null, "Failed to move function back to original directory: " + moveException?.SourceException?.ToString());
243+
string exceptionString = string.Join(Environment.NewLine, exceptions.Select(p => p.ToString()));
244+
Assert.True(exceptions.Count() == 0, exceptionString);
245+
}
246+
finally
247+
{
248+
// Move the directory back after the host has stopped to prevent
249+
// unnecessary host restarts
250+
if (Directory.Exists(newDirectory))
251+
{
252+
Directory.Move(newDirectory, oldDirectory);
253+
}
254+
}
228255
}
229256
}
230257

@@ -694,13 +721,15 @@ private static CloudBlockBlob UpdateOutputName(string prev, string hint, EndToEn
694721
{
695722
string name = hint;
696723

724+
// As soon as we touch the file, the trigger may reload, so delete any existing blob first.
725+
var blob = fixture.TestOutputContainer.GetBlockBlobReference(name);
726+
blob.DeleteIfExists();
727+
697728
string manifestPath = Path.Combine(Environment.CurrentDirectory, @"TestScripts\Node\TimerTrigger\function.json");
698729
string content = File.ReadAllText(manifestPath);
699730
content = content.Replace(prev, name);
700731
File.WriteAllText(manifestPath, content);
701732

702-
var blob = fixture.TestOutputContainer.GetBlockBlobReference(name);
703-
blob.DeleteIfExists();
704733
return blob;
705734
}
706735

test/WebJobs.Script.Tests.Integration/NodeEndToEndTests.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1297,7 +1297,13 @@ public async Task HttpTrigger_Scenarios_Buffer()
12971297

12981298
public class TestFixture : EndToEndTestFixture
12991299
{
1300-
public TestFixture() : base(@"TestScripts\Node", "node")
1300+
public TestFixture()
1301+
: base(@"TestScripts\Node", "node")
1302+
{
1303+
}
1304+
1305+
internal TestFixture(bool startHost)
1306+
: base(@"TestScripts\Node", "node", startHost: startHost)
13011307
{
13021308
}
13031309
}

0 commit comments

Comments
 (0)