Skip to content

Commit d640937

Browse files
authored
[Templates] Diagnostics improvements and certificate fixes (#21493)
* Move template specific helpers out of shared and into templates * More debug info, fix casing issues * Keep test skipped * Undo lock changes * Standarize retry logic * Tweak certificate validation code
1 parent 88dbfaa commit d640937

File tree

8 files changed

+42
-32
lines changed

8 files changed

+42
-32
lines changed

src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplates.Tests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<!-- Shared testing infrastructure for running E2E tests using selenium -->
44
<Import Project="$(SharedSourceRoot)E2ETesting\E2ETesting.props" />
@@ -28,6 +28,7 @@
2828
<ItemGroup>
2929
<EmbeddedResource Include="template-baselines.json" />
3030
<Compile Include="$(SharedSourceRoot)Process\*.cs" LinkBase="shared\Process" />
31+
<Compile Include="..\Shared\**" LinkBase="Helpers" />
3132
</ItemGroup>
3233

3334
<ItemGroup>

src/Shared/E2ETesting/AspNetProcess.cs renamed to src/ProjectTemplates/Shared/AspNetProcess.cs

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Linq;
99
using System.Net;
1010
using System.Net.Http;
11+
using System.Net.Security;
1112
using System.Threading.Tasks;
1213
using AngleSharp.Dom.Html;
1314
using AngleSharp.Parser.Html;
@@ -33,6 +34,7 @@ public class AspNetProcess : IDisposable
3334

3435
private string _certificatePath;
3536
private string _certificatePassword = Guid.NewGuid().ToString();
37+
private string _certificateThumbprint;
3638

3739
internal readonly Uri ListeningUri;
3840
internal ProcessEx Process { get; }
@@ -46,22 +48,21 @@ public AspNetProcess(
4648
bool hasListeningUri = true,
4749
ILogger logger = null)
4850
{
51+
_certificatePath = Path.Combine(workingDirectory, $"{Guid.NewGuid()}.pfx");
52+
EnsureDevelopmentCertificates();
53+
4954
_output = output;
5055
_httpClient = new HttpClient(new HttpClientHandler()
5156
{
5257
AllowAutoRedirect = true,
5358
UseCookies = true,
5459
CookieContainer = new CookieContainer(),
55-
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,
60+
ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => (certificate.Subject != "CN=localhost" && errors == SslPolicyErrors.None) || certificate?.Thumbprint == _certificateThumbprint,
5661
})
5762
{
5863
Timeout = TimeSpan.FromMinutes(2)
5964
};
6065

61-
_certificatePath = Path.Combine(workingDirectory, $"{Guid.NewGuid()}.pfx");
62-
63-
EnsureDevelopmentCertificates();
64-
6566
output.WriteLine("Running ASP.NET application...");
6667

6768
var arguments = published ? $"exec {dllPath}" : "run";
@@ -70,8 +71,8 @@ public AspNetProcess(
7071

7172
var finalEnvironmentVariables = new Dictionary<string, string>(environmentVariables)
7273
{
73-
["ASPNETCORE_KESTREL__CERTIFICATES__DEFAULT__PATH"] = _certificatePath,
74-
["ASPNETCORE_KESTREL__CERTIFICATES__DEFAULT__PASSWORD"] = _certificatePassword
74+
["ASPNETCORE_Kestrel__Certificates__Default__Path"] = _certificatePath,
75+
["ASPNETCORE_Kestrel__Certificates__Default__Password"] = _certificatePassword
7576
};
7677

7778
Process = ProcessEx.Run(output, workingDirectory, DotNetMuxer.MuxerPathOrDefault(), arguments, envVars: finalEnvironmentVariables);
@@ -81,8 +82,8 @@ public AspNetProcess(
8182
if (hasListeningUri)
8283
{
8384
logger?.LogInformation("AspNetProcess - Getting listening uri");
84-
ListeningUri = GetListeningUri(output) ?? throw new InvalidOperationException("Couldn't find the listening URL.");
85-
logger?.LogInformation($"AspNetProcess - Got {ListeningUri.ToString()}");
85+
ListeningUri = ResolveListeningUrl(output);
86+
logger?.LogInformation($"AspNetProcess - Got {ListeningUri}");
8687
}
8788
}
8889

@@ -91,6 +92,7 @@ internal void EnsureDevelopmentCertificates()
9192
var now = DateTimeOffset.Now;
9293
var manager = CertificateManager.Instance;
9394
var certificate = manager.CreateAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1));
95+
_certificateThumbprint = certificate.Thumbprint;
9496
manager.ExportCertificate(certificate, path: _certificatePath, includePrivateKey: true, _certificatePassword);
9597
}
9698

@@ -134,13 +136,13 @@ public async Task AssertPagesOk(IEnumerable<Page> pages)
134136

135137
public async Task ContainsLinks(Page page)
136138
{
137-
var response = await RequestWithRetries(client =>
139+
var response = await RetryHelper.RetryRequest(async () =>
138140
{
139141
var request = new HttpRequestMessage(
140142
HttpMethod.Get,
141143
new Uri(ListeningUri, page.Url));
142-
return client.SendAsync(request);
143-
}, _httpClient);
144+
return await _httpClient.SendAsync(request);
145+
}, logger: NullLogger.Instance);
144146

145147
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
146148
var parser = new HtmlParser();
@@ -173,7 +175,7 @@ public async Task ContainsLinks(Page page)
173175
Assert.True(string.Equals(anchor.Href, expectedLink), $"Expected next link to be {expectedLink} but it was {anchor.Href}.");
174176
var result = await RetryHelper.RetryRequest(async () =>
175177
{
176-
return await RequestWithRetries(client => client.GetAsync(anchor.Href), _httpClient);
178+
return await _httpClient.GetAsync(anchor.Href);
177179
}, logger: NullLogger.Instance);
178180

179181
Assert.True(IsSuccessStatusCode(result), $"{anchor.Href} is a broken link!");
@@ -203,7 +205,7 @@ private async Task<T> RequestWithRetries<T>(Func<HttpClient, Task<T>> requester,
203205
throw new InvalidOperationException("Max retries reached.");
204206
}
205207

206-
private Uri GetListeningUri(ITestOutputHelper output)
208+
private Uri ResolveListeningUrl(ITestOutputHelper output)
207209
{
208210
// Wait until the app is accepting HTTP requests
209211
output.WriteLine("Waiting until ASP.NET application is accepting connections...");
@@ -232,21 +234,27 @@ private Uri GetListeningUri(ITestOutputHelper output)
232234

233235
private string GetListeningMessage()
234236
{
237+
var buffer = new List<string>();
235238
try
236239
{
237-
return Process
238-
// This will timeout at most after 5 minutes.
239-
.OutputLinesAsEnumerable
240-
.Where(line => line != null)
241-
// This used to do StartsWith, but this is less strict and can prevent issues (very rare) where
242-
// console logging interleaves with other console output in a bad way. For example:
243-
// dbugNow listening on: http://127.0.0.1:12857
244-
.FirstOrDefault(line => line.Trim().Contains(ListeningMessagePrefix, StringComparison.Ordinal));
240+
foreach (var line in Process.OutputLinesAsEnumerable)
241+
{
242+
if (line != null)
243+
{
244+
buffer.Add(line);
245+
if (line.Trim().Contains(ListeningMessagePrefix, StringComparison.Ordinal))
246+
{
247+
return line;
248+
}
249+
}
250+
}
245251
}
246252
catch (OperationCanceledException)
247253
{
248-
return null;
249254
}
255+
256+
throw new InvalidOperationException(@$"Couldn't find listening url:
257+
{string.Join(Environment.NewLine, buffer)}");
250258
}
251259

252260
private bool IsSuccessStatusCode(HttpResponseMessage response)
@@ -260,14 +268,13 @@ public Task AssertOk(string requestUrl)
260268
public Task AssertNotFound(string requestUrl)
261269
=> AssertStatusCode(requestUrl, HttpStatusCode.NotFound);
262270

263-
internal Task<HttpResponseMessage> SendRequest(string path)
264-
{
265-
return RequestWithRetries(client => client.GetAsync(new Uri(ListeningUri, path)), _httpClient);
266-
}
271+
internal Task<HttpResponseMessage> SendRequest(string path) =>
272+
RetryHelper.RetryRequest(() => _httpClient.GetAsync(new Uri(ListeningUri, path)), logger: NullLogger.Instance);
267273

268274
public async Task AssertStatusCode(string requestUrl, HttpStatusCode statusCode, string acceptContentType = null)
269275
{
270-
var response = await RequestWithRetries(client => {
276+
var response = await RetryHelper.RetryRequest(() =>
277+
{
271278
var request = new HttpRequestMessage(
272279
HttpMethod.Get,
273280
new Uri(ListeningUri, requestUrl));
@@ -277,8 +284,9 @@ public async Task AssertStatusCode(string requestUrl, HttpStatusCode statusCode,
277284
request.Headers.Add("Accept", acceptContentType);
278285
}
279286

280-
return client.SendAsync(request);
281-
}, _httpClient);
287+
return _httpClient.SendAsync(request);
288+
}, logger: NullLogger.Instance);
289+
282290
Assert.True(statusCode == response.StatusCode, $"Expected {requestUrl} to have status '{statusCode}' but it was '{response.StatusCode}'.");
283291
}
284292

src/ProjectTemplates/test/ProjectTemplates.Tests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<!-- Shared testing infrastructure for running E2E tests using selenium -->
44
<Import Project="$(SharedSourceRoot)E2ETesting\E2ETesting.props" />
@@ -28,6 +28,7 @@
2828
<EmbeddedResource Include="template-baselines.json" />
2929
<Compile Include="$(SharedSourceRoot)Process\*.cs" LinkBase="shared\Process" />
3030
<Compile Include="$(SharedSourceRoot)test\SkipOnHelixAttribute.cs" />
31+
<Compile Include="..\Shared\**" LinkBase="Helpers" />
3132
</ItemGroup>
3233

3334
<ItemGroup>

0 commit comments

Comments
 (0)