-
Notifications
You must be signed in to change notification settings - Fork 292
Add telemetry collection for MSTest usage analytics #7570
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 7 commits
06ad3e1
b609215
3e6f8b5
6265e4f
f1c1fa5
cbf9140
2a26a81
6259d0e
07a82f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,14 +20,18 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; | |
| internal sealed class MSTestDiscoverer : ITestDiscoverer | ||
| { | ||
| private readonly ITestSourceHandler _testSourceHandler; | ||
| private readonly Func<string, IDictionary<string, object>, Task>? _telemetrySender; | ||
|
|
||
| public MSTestDiscoverer() | ||
| : this(new TestSourceHandler()) | ||
| { | ||
| } | ||
|
|
||
| internal /* for testing purposes */ MSTestDiscoverer(ITestSourceHandler testSourceHandler) | ||
| => _testSourceHandler = testSourceHandler; | ||
| internal /* for testing purposes */ MSTestDiscoverer(ITestSourceHandler testSourceHandler, Func<string, IDictionary<string, object>, Task>? telemetrySender = null) | ||
| { | ||
| _testSourceHandler = testSourceHandler; | ||
| _telemetrySender = telemetrySender; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Discovers the tests available from the provided source. Not supported for .xap source. | ||
|
|
@@ -47,9 +51,23 @@ internal void DiscoverTests(IEnumerable<string> sources, IDiscoveryContext disco | |
| Ensure.NotNull(logger); | ||
| Ensure.NotNull(discoverySink); | ||
|
|
||
| if (MSTestDiscovererHelpers.InitializeDiscovery(sources, discoveryContext, logger, configuration, _testSourceHandler)) | ||
| // Initialize telemetry collection if not already set (e.g. first call in the session) | ||
| if (!MSTestTelemetryDataCollector.IsTelemetryOptedOut()) | ||
| { | ||
| _ = MSTestTelemetryDataCollector.EnsureInitialized(); | ||
| } | ||
|
|
||
| try | ||
| { | ||
| if (MSTestDiscovererHelpers.InitializeDiscovery(sources, discoveryContext, logger, configuration, _testSourceHandler)) | ||
| { | ||
| new UnitTestDiscoverer(_testSourceHandler).DiscoverTests(sources, logger, discoverySink, discoveryContext); | ||
| } | ||
| } | ||
| finally | ||
| { | ||
| new UnitTestDiscoverer(_testSourceHandler).DiscoverTests(sources, logger, discoverySink, discoveryContext); | ||
| // Use Task.Run to avoid capturing any SynchronizationContext that could cause deadlocks | ||
|
||
| Task.Run(() => MSTestTelemetryDataCollector.SendTelemetryAndResetAsync(_telemetrySender)).GetAwaiter().GetResult(); | ||
|
||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; | |
| internal sealed class MSTestExecutor : ITestExecutor | ||
| { | ||
| private readonly CancellationToken _cancellationToken; | ||
| private readonly Func<string, IDictionary<string, object>, Task>? _telemetrySender; | ||
|
|
||
| /// <summary> | ||
| /// Token for canceling the test run. | ||
|
|
@@ -35,10 +36,11 @@ public MSTestExecutor() | |
| _cancellationToken = CancellationToken.None; | ||
| } | ||
|
|
||
| internal MSTestExecutor(CancellationToken cancellationToken) | ||
| internal MSTestExecutor(CancellationToken cancellationToken, Func<string, IDictionary<string, object>, Task>? telemetrySender = null) | ||
| { | ||
| TestExecutionManager = new TestExecutionManager(); | ||
| _cancellationToken = cancellationToken; | ||
| _telemetrySender = telemetrySender; | ||
| } | ||
|
|
||
| /// <summary> | ||
|
|
@@ -105,12 +107,25 @@ internal async Task RunTestsAsync(IEnumerable<TestCase>? tests, IRunContext? run | |
| Ensure.NotNull(frameworkHandle); | ||
| Ensure.NotNullOrEmpty(tests); | ||
|
|
||
| // Initialize telemetry collection if not already set | ||
| if (!MSTestTelemetryDataCollector.IsTelemetryOptedOut()) | ||
| { | ||
| _ = MSTestTelemetryDataCollector.EnsureInitialized(); | ||
| } | ||
|
|
||
|
Comment on lines
+116
to
+123
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [AI generated] The |
||
| if (!MSTestDiscovererHelpers.InitializeDiscovery(from test in tests select test.Source, runContext, frameworkHandle, configuration, new TestSourceHandler())) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| await RunTestsFromRightContextAsync(frameworkHandle, async testRunToken => await TestExecutionManager.RunTestsAsync(tests, runContext, frameworkHandle, testRunToken).ConfigureAwait(false)).ConfigureAwait(false); | ||
| try | ||
| { | ||
| await RunTestsFromRightContextAsync(frameworkHandle, async testRunToken => await TestExecutionManager.RunTestsAsync(tests, runContext, frameworkHandle, testRunToken).ConfigureAwait(false)).ConfigureAwait(false); | ||
| } | ||
| finally | ||
| { | ||
| await SendTelemetryAsync().ConfigureAwait(false); | ||
| } | ||
| } | ||
|
|
||
| internal async Task RunTestsAsync(IEnumerable<string>? sources, IRunContext? runContext, IFrameworkHandle? frameworkHandle, IConfiguration? configuration) | ||
|
|
@@ -123,14 +138,27 @@ internal async Task RunTestsAsync(IEnumerable<string>? sources, IRunContext? run | |
| Ensure.NotNull(frameworkHandle); | ||
| Ensure.NotNullOrEmpty(sources); | ||
|
|
||
| // Initialize telemetry collection if not already set | ||
| if (!MSTestTelemetryDataCollector.IsTelemetryOptedOut()) | ||
| { | ||
| _ = MSTestTelemetryDataCollector.EnsureInitialized(); | ||
| } | ||
|
|
||
| TestSourceHandler testSourceHandler = new(); | ||
| if (!MSTestDiscovererHelpers.InitializeDiscovery(sources, runContext, frameworkHandle, configuration, testSourceHandler)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| sources = testSourceHandler.GetTestSources(sources); | ||
| await RunTestsFromRightContextAsync(frameworkHandle, async testRunToken => await TestExecutionManager.RunTestsAsync(sources, runContext, frameworkHandle, testSourceHandler, testRunToken).ConfigureAwait(false)).ConfigureAwait(false); | ||
| try | ||
| { | ||
| await RunTestsFromRightContextAsync(frameworkHandle, async testRunToken => await TestExecutionManager.RunTestsAsync(sources, runContext, frameworkHandle, testSourceHandler, testRunToken).ConfigureAwait(false)).ConfigureAwait(false); | ||
| } | ||
| finally | ||
| { | ||
| await SendTelemetryAsync().ConfigureAwait(false); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
|
|
@@ -139,6 +167,9 @@ internal async Task RunTestsAsync(IEnumerable<string>? sources, IRunContext? run | |
| public void Cancel() | ||
| => _testRunCancellationToken?.Cancel(); | ||
|
|
||
| private async Task SendTelemetryAsync() | ||
| => await MSTestTelemetryDataCollector.SendTelemetryAndResetAsync(_telemetrySender).ConfigureAwait(false); | ||
|
|
||
| private async Task RunTestsFromRightContextAsync(IFrameworkHandle frameworkHandle, Func<TestRunCancellationToken, Task> runTestsAction) | ||
| { | ||
| ApartmentState? requestedApartmentState = MSTestSettings.RunConfigurationSettings.ExecutionApartmentState; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[AI generated] Good questions. The parameterless constructor is still called — it's the public entry point used by the VSTest framework to instantiate the discoverer via reflection. The
internal /* for testing purposes */constructor withtelemetrySenderis used byMSTestBridgedTestFramework(MTP path) which passes a real telemetry sender. Both constructors are needed. The "for testing purposes" comment could be misleading now since it's also used in production (MTP path) — happy to update the comment if the team prefers.