Skip to content

Commit 89f1eaa

Browse files
authored
More integration test work (#10293)
This PR: * Re-enables logging to test output for integration tests * Increases the log level of all logging during integration tests * Actually deploys Razor to the experimental hive so we're testing Razor main on VS main * Previously, without that change, we were testing using the integration test source from main, against whatever bits were inserted in VS main. This seemed odd to me, but if anyone thinks that is a desired test scenario let me know. It does explain a couple of failures I remember seeing ages ago, which was the reason I had backed out the logging in the first place * Output VS version info to the pipeline logs
2 parents 2e4b22b + c5c07d6 commit 89f1eaa

File tree

7 files changed

+76
-34
lines changed

7 files changed

+76
-34
lines changed

eng/pipelines/test-integration-job.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ steps:
3737
-build
3838
-pack
3939
-publish
40+
-deploy
4041
/p:BuildDependencyVsix=false
4142
name: Build
4243
displayName: Build

eng/scripts/CreateVSHive.ps1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,9 @@ for($i=0; $i -le 3; $i++)
3030
if($success -eq $false){
3131
throw "Failed to create hive"
3232
}
33+
34+
Write-Host "-- VS Info --"
35+
$vsDir = Split-Path -Parent $devenvExePath
36+
$isolationIni = Join-Path $vsDir 'devenv.isolation.ini'
37+
Get-Content $isolationIni | Write-Host
38+
Write-Host "-- /VS Info --"

src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectWorkspaceStateGenerator.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public void Dispose()
5959
// Release before dispose to ensure we don't throw exceptions from the background thread trying to release
6060
// while we're disposing. Multiple releases are fine, and if we release and it lets something passed the lock
6161
// our cancellation token check will mean its a no-op.
62+
_logger.LogTrace($"Releasing the semaphore in Dispose");
6263
_semaphore.Release();
6364
_semaphore.Dispose();
6465

@@ -121,14 +122,19 @@ private async Task UpdateWorkspaceStateAsync(Project? workspaceProject, IProject
121122
// So if we now do multiple requests to resolve TagHelpers simultaneously it results in only a
122123
// single one executing at a time so that we don't have N number of requests in flight with these
123124
// 10mb payloads waiting to be processed.
125+
_logger.LogTrace($"In UpdateWorkspaceStateAsync, waiting for the semaphore, for '{projectSnapshot.Key}'");
124126
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
125127
}
126128
catch (Exception)
127129
{
130+
_logger.LogTrace($"Exception waiting for the semaphore '{projectSnapshot.Key}'");
131+
128132
// Object disposed or task cancelled exceptions should be swallowed/no-op'd
129133
return;
130134
}
131135

136+
_logger.LogTrace($"Got the semaphore '{projectSnapshot.Key}'");
137+
132138
try
133139
{
134140
OnStartingBackgroundWork();
@@ -140,54 +146,71 @@ private async Task UpdateWorkspaceStateAsync(Project? workspaceProject, IProject
140146

141147
var workspaceState = await GetProjectWorkspaceStateAsync(workspaceProject, projectSnapshot, cancellationToken);
142148

143-
if (workspaceState is null || cancellationToken.IsCancellationRequested)
149+
if (workspaceState is null)
150+
{
151+
_logger.LogTrace($"Couldn't get any state for '{projectSnapshot.Key}'");
152+
return;
153+
}
154+
else if (cancellationToken.IsCancellationRequested)
144155
{
156+
_logger.LogTrace($"Got a cancellation request during discovery for '{projectSnapshot.Key}'");
145157
return;
146158
}
147159

160+
_logger.LogTrace($"Updating project info with {workspaceState.TagHelpers.Length} tag helpers for '{projectSnapshot.Key}'");
161+
148162
await _projectManager
149163
.UpdateAsync(
150164
static (updater, state) =>
151165
{
152-
var (projectKey, workspaceState, cancellationToken) = state;
166+
var (projectKey, workspaceState, logger, cancellationToken) = state;
153167

154168
if (cancellationToken.IsCancellationRequested)
155169
{
156170
return;
157171
}
158172

173+
logger.LogTrace($"Really updating project info with {workspaceState.TagHelpers.Length} tag helpers for '{projectKey}'");
159174
updater.ProjectWorkspaceStateChanged(projectKey, workspaceState);
160175
},
161-
state: (projectSnapshot.Key, workspaceState, cancellationToken),
176+
state: (projectSnapshot.Key, workspaceState, _logger, cancellationToken),
162177
cancellationToken)
163178
.ConfigureAwait(false);
164179
}
165180
catch (OperationCanceledException)
166181
{
182+
_logger.LogTrace($"Got an OperationCancelledException, for '{projectSnapshot.Key}'");
167183
// Abort work if we get a task canceled exception
168184
return;
169185
}
170186
catch (Exception ex)
171187
{
188+
_logger.LogTrace($"Got an exception, for '{projectSnapshot.Key}'");
172189
_logger.LogError(ex);
173190
}
174191
finally
175192
{
176193
try
177194
{
195+
_logger.LogTrace($"Felt cute, might release a semaphore later, for '{projectSnapshot.Key}'");
196+
178197
// Prevent ObjectDisposedException if we've disposed before we got here. The dispose method will release
179198
// anyway, so we're all good.
180199
if (!_disposeTokenSource.IsCancellationRequested)
181200
{
201+
_logger.LogTrace($"Releasing the semaphore, for '{projectSnapshot.Key}'");
182202
_semaphore.Release();
183203
}
204+
205+
_logger.LogTrace($"If you didn't see a log message about releasing a semaphore, we have a problem. (for '{projectSnapshot.Key}')");
184206
}
185207
catch
186208
{
187209
// Swallow exceptions that happen from releasing the semaphore.
188210
}
189211
}
190212

213+
_logger.LogTrace($"All finished for '{projectSnapshot.Key}'");
191214
OnBackgroundWorkCompleted();
192215
}
193216

@@ -207,6 +230,8 @@ await _projectManager
207230
return ProjectWorkspaceState.Default;
208231
}
209232

233+
_logger.LogTrace($"Starting tag helper discovery for {projectSnapshot.FilePath}");
234+
210235
// Specifically not using BeginBlock because we want to capture cases where tag helper discovery never finishes.
211236
var telemetryId = Guid.NewGuid();
212237

src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Logging/TestOutputLogger.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ public void Log(LogLevel logLevel, string message, Exception? exception)
7474
{
7575
_provider.TestOutputHelper.WriteLine(finalMessage);
7676
}
77+
catch (InvalidOperationException iex) when (iex.Message == "There is no currently active test.")
78+
{
79+
// Ignore, something is logging a message outside of a test. Other loggers will capture it.
80+
}
7781
catch (Exception ex)
7882
{
7983
// If an exception is thrown while writing a message, throw an AggregateException that includes

src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Logging/TestOutputLoggerProvider.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66

77
namespace Microsoft.AspNetCore.Razor.Test.Common.Logging;
88

9-
internal class TestOutputLoggerProvider(ITestOutputHelper output) : ILoggerProvider
9+
internal class TestOutputLoggerProvider(ITestOutputHelper output, LogLevel logLevel = LogLevel.Trace) : ILoggerProvider
1010
{
1111
private ITestOutputHelper? _output = output;
12+
private readonly LogLevel _logLevel = logLevel;
1213

1314
public ITestOutputHelper? TestOutputHelper => _output;
1415

1516
public ILogger CreateLogger(string categoryName)
16-
=> new TestOutputLogger(this, categoryName);
17+
=> new TestOutputLogger(this, categoryName, _logLevel);
1718

1819
public void Dispose()
1920
{

src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/OutputInProcess.cs

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
using System;
55
using System.Threading;
66
using System.Threading.Tasks;
7+
using Microsoft.AspNetCore.Razor.Test.Common.Logging;
78
using Microsoft.CodeAnalysis.Razor.Logging;
89
using Microsoft.VisualStudio.Razor.IntegrationTests.Extensions;
10+
using Microsoft.VisualStudio.Razor.Settings;
911
using Microsoft.VisualStudio.Shell;
1012
using Microsoft.VisualStudio.Shell.Interop;
1113
using Microsoft.VisualStudio.TextManager.Interop;
@@ -18,33 +20,33 @@ internal partial class OutputInProcess
1820
{
1921
private const string RazorPaneName = "Razor Logger Output";
2022

21-
// private TestOutputLoggerProvider? _testLoggerProvider;
23+
private TestOutputLoggerProvider? _testLoggerProvider;
2224

23-
#pragma warning disable IDE0060 // Remove unused parameter
24-
public Task<ILogger> SetupIntegrationTestLoggerAsync(ITestOutputHelper testOutputHelper, CancellationToken cancellationToken)
25-
#pragma warning restore IDE0060 // Remove unused parameter
25+
public async Task<ILogger> SetupIntegrationTestLoggerAsync(ITestOutputHelper testOutputHelper, CancellationToken cancellationToken)
2626
{
27-
return Task.FromResult<ILogger>(NullLogger.Instance);
28-
29-
// var logger = await TestServices.Shell.GetComponentModelServiceAsync<IRazorLoggerFactory>(cancellationToken);
30-
31-
// // We can't remove logging providers, so we just keep track of ours so we can make sure it points to the right test output helper
32-
// if (_testLoggerProvider is null)
33-
// {
34-
// _testLoggerProvider = new TestOutputLoggerProvider(testOutputHelper);
35-
// logger.AddLoggerProvider(_testLoggerProvider);
36-
// }
37-
// else
38-
// {
39-
// _testLoggerProvider.SetTestOutputHelper(testOutputHelper);
40-
// }
41-
42-
// return logger.CreateLogger(GetType().Name);
27+
// Make sure we log as much as possible for integration tests
28+
var clientSettingsManager = await TestServices.Shell.GetComponentModelServiceAsync<IClientSettingsManager>(cancellationToken);
29+
clientSettingsManager.Update(clientSettingsManager.GetClientSettings().AdvancedSettings with { LogLevel = LogLevel.Trace });
30+
31+
var logger = await TestServices.Shell.GetComponentModelServiceAsync<ILoggerFactory>(cancellationToken);
32+
33+
// We can't remove logging providers, so we just keep track of ours so we can make sure it points to the right test output helper
34+
if (_testLoggerProvider is null)
35+
{
36+
_testLoggerProvider = new TestOutputLoggerProvider(testOutputHelper, LogLevel.Warning);
37+
logger.AddLoggerProvider(_testLoggerProvider);
38+
}
39+
else
40+
{
41+
_testLoggerProvider.SetTestOutputHelper(testOutputHelper);
42+
}
43+
44+
return logger.GetOrCreateLogger(GetType().Name);
4345
}
4446

4547
public void ClearIntegrationTestLogger()
4648
{
47-
// _testLoggerProvider?.SetTestOutputHelper(null);
49+
_testLoggerProvider?.SetTestOutputHelper(null);
4850
}
4951

5052
public async Task<bool> HasErrorsAsync(CancellationToken cancellationToken)

src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/LogIntegrationTestAttribute.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
using System;
55
using System.Reflection;
6+
using Microsoft.CodeAnalysis.Razor.Logging;
7+
using Microsoft.VisualStudio.ComponentModelHost;
8+
using Microsoft.VisualStudio.Shell;
69
using Xunit.Sdk;
710

811
namespace Microsoft.VisualStudio.Razor.IntegrationTests;
@@ -12,18 +15,18 @@ public class LogIntegrationTestAttribute : BeforeAfterTestAttribute
1215
{
1316
public override void Before(MethodInfo methodUnderTest)
1417
{
15-
// GetLogger(methodUnderTest.Name).LogInformation("#### Integration test start.");
18+
GetLogger(methodUnderTest.DeclaringType.Name).LogInformation($"#### Integration test start: {methodUnderTest.Name}");
1619
}
1720

1821
public override void After(MethodInfo methodUnderTest)
1922
{
20-
// GetLogger(methodUnderTest.Name).LogInformation("#### Integration test end.");
23+
GetLogger(methodUnderTest.DeclaringType.Name).LogInformation($"#### Integration test end: {methodUnderTest.Name}");
2124
}
2225

23-
// private static ILogger GetLogger(string testName)
24-
// {
25-
// var componentModel = ServiceProvider.GlobalProvider.GetService<SComponentModel, IComponentModel>();
26-
// var loggerFactory = componentModel.GetService<ILoggerFactory>();
27-
// return loggerFactory.CreateLogger(testName);
28-
// }
26+
private static ILogger GetLogger(string testName)
27+
{
28+
var componentModel = ServiceProvider.GlobalProvider.GetService<SComponentModel, IComponentModel>();
29+
var loggerFactory = componentModel.GetService<ILoggerFactory>();
30+
return loggerFactory.GetOrCreateLogger(testName);
31+
}
2932
}

0 commit comments

Comments
 (0)