From a931ad98e75b23b28fac578a518f51594f16a4fa Mon Sep 17 00:00:00 2001 From: Shiva Shankar Date: Tue, 9 Jul 2019 19:38:31 +0530 Subject: [PATCH] Implemented the cancellation of discovery request (#2076) * Implemented the cancellation of discovery request * Added an end to end tests to cover the discovery cancellation scenario * Fixed the E2E test to check only for the discovered tests * Increased the timeout for one of the tests --- TestPlatform.sln | 19 ++- .../DesignMode/DesignModeClient.cs | 6 + .../RequestHelper/ITestRequestManager.cs | 5 + .../Messages/MessageType.cs | 5 + .../Discovery/DiscovererEnumerator.cs | 124 +++++++++++------- .../Discovery/DiscoveryManager.cs | 20 +-- .../Resources/Resources.Designer.cs | 9 ++ .../Resources/Resources.resx | 3 + .../Resources/xlf/Resources.cs.xlf | 5 + .../Resources/xlf/Resources.de.xlf | 5 + .../Resources/xlf/Resources.es.xlf | 5 + .../Resources/xlf/Resources.fr.xlf | 5 + .../Resources/xlf/Resources.it.xlf | 5 + .../Resources/xlf/Resources.ja.xlf | 5 + .../Resources/xlf/Resources.ko.xlf | 5 + .../Resources/xlf/Resources.pl.xlf | 5 + .../Resources/xlf/Resources.pt-BR.xlf | 5 + .../Resources/xlf/Resources.ru.xlf | 5 + .../Resources/xlf/Resources.tr.xlf | 5 + .../Resources/xlf/Resources.xlf | 5 + .../Resources/xlf/Resources.zh-Hans.xlf | 5 + .../Resources/xlf/Resources.zh-Hant.xlf | 5 + .../ITranslationLayerRequestSender.cs | 5 + .../VsTestConsoleRequestSender.cs | 6 + .../VsTestConsoleWrapper.cs | 3 +- .../TestPlatformHelpers/TestRequestManager.cs | 86 +++++++----- .../DifferentTestFrameworkSimpleTests.cs | 13 +- .../TranslationLayerTests/DiscoverTests.cs | 46 ++++++- .../Discovery/DiscovererEnumeratorTests.cs | 66 ++++++++-- .../EventHandlers/TestRequestHandlerTests.cs | 2 +- .../DiscoveryTestProject.csproj | 31 +++++ .../LongDiscoveryTestClass.cs | 26 ++++ 32 files changed, 420 insertions(+), 125 deletions(-) create mode 100644 test/TestAssets/DiscoveryTestProject/DiscoveryTestProject.csproj create mode 100644 test/TestAssets/DiscoveryTestProject/LongDiscoveryTestClass.cs diff --git a/TestPlatform.sln b/TestPlatform.sln index 3849fef928..4db14778e0 100644 --- a/TestPlatform.sln +++ b/TestPlatform.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.16 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29025.244 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED0C35EB-7F31-4841-A24F-8EB708FFA959}" EndProject @@ -170,6 +170,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsMigrator", "src\Set EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsMigrator.UnitTests", "test\SettingsMigrator.UnitTests\SettingsMigrator.UnitTests.csproj", "{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscoveryTestProject", "test\TestAssets\DiscoveryTestProject\DiscoveryTestProject.csproj", "{D16ACC60-52F8-4912-8870-5733A9F6852D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -852,6 +854,18 @@ Global {E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Release|x64.Build.0 = Release|Any CPU {E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Release|x86.ActiveCfg = Release|Any CPU {E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Release|x86.Build.0 = Release|Any CPU + {D16ACC60-52F8-4912-8870-5733A9F6852D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D16ACC60-52F8-4912-8870-5733A9F6852D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D16ACC60-52F8-4912-8870-5733A9F6852D}.Debug|x64.ActiveCfg = Debug|Any CPU + {D16ACC60-52F8-4912-8870-5733A9F6852D}.Debug|x64.Build.0 = Debug|Any CPU + {D16ACC60-52F8-4912-8870-5733A9F6852D}.Debug|x86.ActiveCfg = Debug|Any CPU + {D16ACC60-52F8-4912-8870-5733A9F6852D}.Debug|x86.Build.0 = Debug|Any CPU + {D16ACC60-52F8-4912-8870-5733A9F6852D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D16ACC60-52F8-4912-8870-5733A9F6852D}.Release|Any CPU.Build.0 = Release|Any CPU + {D16ACC60-52F8-4912-8870-5733A9F6852D}.Release|x64.ActiveCfg = Release|Any CPU + {D16ACC60-52F8-4912-8870-5733A9F6852D}.Release|x64.Build.0 = Release|Any CPU + {D16ACC60-52F8-4912-8870-5733A9F6852D}.Release|x86.ActiveCfg = Release|Any CPU + {D16ACC60-52F8-4912-8870-5733A9F6852D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -925,6 +939,7 @@ Global {A7E2261B-B2E6-4CBF-983F-E3A3FF8E52E3} = {D9A30E32-D466-4EC5-B4F2-62E17562279B} {69F5FF81-5615-4F06-B83C-FCF979BB84CA} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} {E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} + {D16ACC60-52F8-4912-8870-5733A9F6852D} = {8DA7CBD9-F17E-41B6-90C4-CFF55848A25A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0541B30C-FF51-4E28-B172-83F5F3934BCD} diff --git a/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs b/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs index 2c80c6ef3e..41e6b135ae 100644 --- a/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs +++ b/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs @@ -196,6 +196,12 @@ private void ProcessRequests(ITestRequestManager testRequestManager) break; } + case MessageType.CancelDiscovery: + { + testRequestManager.CancelDiscovery(); + break; + } + case MessageType.CancelTestRun: { testRequestManager.CancelTestRun(); diff --git a/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs b/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs index db1e20024a..d23975afed 100644 --- a/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs +++ b/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs @@ -53,5 +53,10 @@ public interface ITestRequestManager : IDisposable /// Abort the current TestRun /// void AbortTestRun(); + + /// + /// Cancels the current discovery request + /// + void CancelDiscovery(); } } diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs index 099a15d6ad..b7eb93ded8 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs @@ -63,6 +63,11 @@ public static class MessageType /// public const string DiscoveryComplete = "TestDiscovery.Completed"; + /// + /// Cancel Test Discovery + /// + public const string CancelDiscovery = "TestDiscovery.Cancel"; + /// /// The session start. /// diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscovererEnumerator.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscovererEnumerator.cs index 4ab2a1aa17..48666d0c3d 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscovererEnumerator.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Discovery/DiscovererEnumerator.cs @@ -10,7 +10,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery using System.IO; using System.Linq; using System.Reflection; - + using System.Threading; using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities; using Microsoft.VisualStudio.TestPlatform.Common.Filtering; @@ -33,44 +33,59 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery /// internal class DiscovererEnumerator { - private DiscoveryResultCache discoveryResultCache; - private ITestPlatformEventSource testPlatformEventSource; - private IRequestData requestData; - private IAssemblyProperties assemblyProperties; + private readonly DiscoveryResultCache discoveryResultCache; + private readonly ITestPlatformEventSource testPlatformEventSource; + private readonly IRequestData requestData; + private readonly IAssemblyProperties assemblyProperties; + private readonly CancellationToken cancellationToken; /// /// Initializes a new instance of the class. /// /// The request data for providing discovery services and data. /// The discovery result cache. - /// Metric Collector - public DiscovererEnumerator(IRequestData requestData, DiscoveryResultCache discoveryResultCache) : this(requestData, discoveryResultCache, TestPlatformEventSource.Instance) + public DiscovererEnumerator(IRequestData requestData, DiscoveryResultCache discoveryResultCache, CancellationToken token) + : this(requestData, discoveryResultCache, TestPlatformEventSource.Instance, token) { } - internal DiscovererEnumerator(IRequestData requestData, + /// + /// Initializes a new instance of the class. + /// + /// The request data for providing discovery services and data. + /// The discovery result cache. + /// Telemetry events receiver + /// Cancellation Token to abort discovery + public DiscovererEnumerator(IRequestData requestData, DiscoveryResultCache discoveryResultCache, - ITestPlatformEventSource testPlatformEventSource) + ITestPlatformEventSource testPlatformEventSource, + CancellationToken token) + : this(requestData, discoveryResultCache, testPlatformEventSource, new AssemblyProperties(), token) { - this.discoveryResultCache = discoveryResultCache; - this.testPlatformEventSource = testPlatformEventSource; - this.requestData = requestData; - this.assemblyProperties = new AssemblyProperties(); } - // Added this to make class testable, needed a PEHeader mocked Instance - internal DiscovererEnumerator(IRequestData requestData, + /// + /// Initializes a new instance of the class. + /// + /// The request data for providing discovery services and data. + /// The discovery result cache. + /// Telemetry events receiver + /// Information on the assemblies being discovered + /// Cancellation Token to abort discovery + public DiscovererEnumerator(IRequestData requestData, DiscoveryResultCache discoveryResultCache, ITestPlatformEventSource testPlatformEventSource, - IAssemblyProperties assemblyProperties) + IAssemblyProperties assemblyProperties, + CancellationToken token) { + // Added this to make class testable, needed a PEHeader mocked Instance this.discoveryResultCache = discoveryResultCache; this.testPlatformEventSource = testPlatformEventSource; this.requestData = requestData; this.assemblyProperties = assemblyProperties; + this.cancellationToken = token; } - /// /// Discovers tests from the sources. /// @@ -78,14 +93,20 @@ internal DiscovererEnumerator(IRequestData requestData, /// The settings. /// The test case filter. /// The logger. - internal void LoadTests(IDictionary> testExtensionSourceMap, IRunSettings settings, string testCaseFilter, IMessageLogger logger) + public void LoadTests(IDictionary> testExtensionSourceMap, IRunSettings settings, string testCaseFilter, IMessageLogger logger) { this.testPlatformEventSource.DiscoveryStart(); - foreach (var kvp in testExtensionSourceMap) + try { - this.LoadTestsFromAnExtension(kvp.Key, kvp.Value, settings, testCaseFilter, logger); + foreach (var kvp in testExtensionSourceMap) + { + this.LoadTestsFromAnExtension(kvp.Key, kvp.Value, settings, testCaseFilter, logger); + } + } + finally + { + this.testPlatformEventSource.DiscoveryStop(this.discoveryResultCache.TotalDiscoveredTests); } - this.testPlatformEventSource.DiscoveryStop(this.discoveryResultCache.TotalDiscoveredTests); } /// @@ -105,7 +126,7 @@ private void LoadTestsFromAnExtension(string extensionAssembly, IEnumerable> GetDiscoverers( string extensionAssembly, @@ -436,7 +473,7 @@ private static IEnumerable public class DiscoveryManager : IDiscoveryManager { - private TestSessionMessageLogger sessionMessageLogger; - private ITestPlatformEventSource testPlatformEventSource; - private IRequestData requestData; + private readonly TestSessionMessageLogger sessionMessageLogger; + private readonly ITestPlatformEventSource testPlatformEventSource; + private readonly IRequestData requestData; private ITestDiscoveryEventsHandler2 testDiscoveryEventsHandler; private DiscoveryCriteria discoveryCriteria; + private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); /// /// Initializes a new instance of the class. @@ -109,7 +110,7 @@ public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEve // If there are sources to discover if (verifiedExtensionSourceMap.Any()) { - new DiscovererEnumerator(this.requestData, discoveryResultCache).LoadTests( + new DiscovererEnumerator(this.requestData, discoveryResultCache, cancellationTokenSource.Token).LoadTests( verifiedExtensionSourceMap, RunSettingsUtilities.CreateAndInitializeRunSettings(discoveryCriteria.RunSettings), discoveryCriteria.TestCaseFilter, @@ -138,9 +139,10 @@ public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEve // Collecting Total Tests Discovered this.requestData.MetricsCollection.Add(TelemetryDataConstants.TotalTestsDiscovered, totalDiscoveredTestCount); - - var discoveryCompleteEventsArgs = new DiscoveryCompleteEventArgs(totalDiscoveredTestCount, false); - discoveryCompleteEventsArgs.Metrics = this.requestData.MetricsCollection.Metrics; + var discoveryCompleteEventsArgs = new DiscoveryCompleteEventArgs(totalDiscoveredTestCount, false) + { + Metrics = this.requestData.MetricsCollection.Metrics + }; eventHandler.HandleDiscoveryComplete(discoveryCompleteEventsArgs, lastChunk); } @@ -161,7 +163,7 @@ public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEve /// public void Abort() { - // do nothing for now. + this.cancellationTokenSource.Cancel(); } private void OnReportTestCases(IEnumerable testCases) diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.Designer.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.Designer.cs index ee5729186a..29ba479681 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.Designer.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.Designer.cs @@ -250,6 +250,15 @@ internal static string StringSeperator { } } + /// + /// Looks up a localized string similar to Discovery of tests cancelled.. + /// + internal static string TestDiscoveryCancelled { + get { + return ResourceManager.GetString("TestDiscoveryCancelled", resourceCulture); + } + } + /// /// Looks up a localized string similar to Logging TestHost Diagnostics in file: {0}. /// diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.resx b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.resx index f23a139ea2..8e51734589 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.resx +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.resx @@ -195,4 +195,7 @@ No test matches the given testcase filter `{0}` in {1} + + Discovery of tests cancelled. + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.cs.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.cs.xlf index 4b13ac29a5..e9ebcc811b 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.cs.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.cs.xlf @@ -202,6 +202,11 @@ Žádný test neodpovídá danému filtru testovacích případů {0} v: {1} + + Discovery of tests cancelled. + Discovery of tests cancelled. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.de.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.de.xlf index e00692696f..d0a5f20427 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.de.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.de.xlf @@ -202,6 +202,11 @@ Kein Test entspricht dem angegebenen Testfallfilter "{0}" in "{1}". + + Discovery of tests cancelled. + Discovery of tests cancelled. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.es.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.es.xlf index e7aea95913..37c9a42691 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.es.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.es.xlf @@ -202,6 +202,11 @@ Ninguna prueba coincide con el filtro de casos de prueba proporcionado "{0}" en {1} + + Discovery of tests cancelled. + Discovery of tests cancelled. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.fr.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.fr.xlf index b1a935654b..d4b7d1a32f 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.fr.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.fr.xlf @@ -202,6 +202,11 @@ Aucun test ne correspond au filtre testcase donné `{0}` dans {1} + + Discovery of tests cancelled. + Discovery of tests cancelled. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.it.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.it.xlf index 572f77c4f4..67756aea99 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.it.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.it.xlf @@ -202,6 +202,11 @@ Nessun test corrisponde al filtro di test case specificato `{0}` in {1} + + Discovery of tests cancelled. + Discovery of tests cancelled. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ja.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ja.xlf index 63baf9e928..43be67f220 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ja.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ja.xlf @@ -202,6 +202,11 @@ 指定のテストケース フィルター `{0}` に一致するテストは {1} にありません + + Discovery of tests cancelled. + Discovery of tests cancelled. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ko.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ko.xlf index e478788cae..bafed6bd6a 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ko.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ko.xlf @@ -202,6 +202,11 @@ {1}에 지정된 테스트 사례 필터 `{0}`과(와) 일치하는 테스트가 없습니다. + + Discovery of tests cancelled. + Discovery of tests cancelled. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pl.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pl.xlf index 0665f698e4..815d170a08 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pl.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pl.xlf @@ -202,6 +202,11 @@ Żaden test nie jest zgodny z podanym filtrem przypadku testowego „{0}” w elemencie {1} + + Discovery of tests cancelled. + Discovery of tests cancelled. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pt-BR.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pt-BR.xlf index f7e0c55e08..3bfd15d201 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pt-BR.xlf @@ -202,6 +202,11 @@ Nenhum teste corresponde ao filtro de testcase `{0}` determinado no {1} + + Discovery of tests cancelled. + Discovery of tests cancelled. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ru.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ru.xlf index 56c800fdeb..68ba5c1ef9 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ru.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ru.xlf @@ -202,6 +202,11 @@ Нет тестов, соответствующих указанному фильтру тестовых случаев "{0}" в {1} + + Discovery of tests cancelled. + Discovery of tests cancelled. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.tr.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.tr.xlf index 910fcb85b7..702cad8308 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.tr.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.tr.xlf @@ -202,6 +202,11 @@ {1} içinde sunulan test çalışması filtresi `{0}` ile eşleşen test yok + + Discovery of tests cancelled. + Discovery of tests cancelled. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.xlf index ae9f1b44ef..087be26de8 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.xlf @@ -113,6 +113,11 @@ No test is available for testcase filter `{0}` in {1} + + Discovery of tests cancelled. + Discovery of tests cancelled. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hans.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hans.xlf index 8d1084dec9..b6f0fa3bb5 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hans.xlf @@ -202,6 +202,11 @@ 没有测试匹配 {1} 中的给定用例测试筛选器“{0}” + + Discovery of tests cancelled. + Discovery of tests cancelled. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hant.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hant.xlf index c6bf1aef3a..5298bdc38b 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hant.xlf @@ -202,6 +202,11 @@ 沒有任何測試符合 {1} 中的指定 testcase 篩選 `{0}` + + Discovery of tests cancelled. + Discovery of tests cancelled. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs index a49522c7d7..bf8beeea6d 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs @@ -105,5 +105,10 @@ internal interface ITranslationLayerRequestSender : IDisposable /// On process exit unblocks communication waiting calls /// void OnProcessExited(); + + /// + /// Cancels the discovery of tests + /// + void CancelDiscovery(); } } diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs index ec2314770f..1f4222fea3 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs @@ -275,6 +275,12 @@ public void AbortTestRun() this.communicationManager.SendMessage(MessageType.AbortTestRun); } + /// + public void CancelDiscovery() + { + this.communicationManager.SendMessage(MessageType.CancelDiscovery); + } + /// public void OnProcessExited() { diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs index 99250c2b07..f340ef50e3 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs @@ -166,8 +166,7 @@ public void DiscoverTests(IEnumerable sources, string discoverySettings, /// public void CancelDiscovery() { - // TODO: Cancel Discovery - // this.requestSender.CancelDiscovery(); + this.requestSender.CancelDiscovery(); } /// diff --git a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs index 109479fa63..3fcea2bf9b 100644 --- a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs +++ b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs @@ -36,21 +36,17 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers /// internal class TestRequestManager : ITestRequestManager { - private ITestPlatform testPlatform; - + private readonly ITestPlatform testPlatform; private CommandLineOptions commandLineOptions; - - private ITestPlatformEventSource testPlatformEventSource; - + private readonly ITestPlatformEventSource testPlatformEventSource; private TestRunResultAggregator testRunResultAggregator; - private static ITestRequestManager testRequestManagerInstance; - private InferHelper inferHelper; - private const int runRequestTimeout = 5000; - private bool telemetryOptedIn; + private readonly object syncObject = new object(); + private readonly Task metricsPublisher; + private bool isDisposed; /// /// Maintains the current active execution request @@ -58,11 +54,11 @@ internal class TestRequestManager : ITestRequestManager /// private ITestRunRequest currentTestRunRequest; - private object syncobject = new object(); - - private Task metricsPublisher; - - private bool isDisposed; + /// + /// Maintains the current active discovery request + /// Assumption : There can only be one active discovery request. + /// + private IDiscoveryRequest currentDiscoveryRequest; #region Constructor @@ -161,36 +157,45 @@ public void DiscoverTests(DiscoveryRequestPayload discoveryPayload, ITestDiscove } // create discovery request - var criteria = new DiscoveryCriteria(discoveryPayload.Sources, batchSize, this.commandLineOptions.TestStatsEventTimeout, runsettings); - criteria.TestCaseFilter = this.commandLineOptions.TestCaseFilterValue; + var criteria = new DiscoveryCriteria(discoveryPayload.Sources, batchSize, this.commandLineOptions.TestStatsEventTimeout, runsettings) + { + TestCaseFilter = this.commandLineOptions.TestCaseFilterValue + }; - try + // Make sure to run the run request inside a lock as the below section is not thread-safe + // There can be only one discovery or execution request at a given point in time + lock (this.syncObject) { - using (IDiscoveryRequest discoveryRequest = this.testPlatform.CreateDiscoveryRequest(requestData, criteria, discoveryPayload.TestPlatformOptions)) + try { - try - { - discoveryEventsRegistrar?.RegisterDiscoveryEvents(discoveryRequest); + EqtTrace.Info("TestRequestManager.DiscoverTests: Synchronization context taken"); - this.testPlatformEventSource.DiscoveryRequestStart(); + this.currentDiscoveryRequest = this.testPlatform.CreateDiscoveryRequest(requestData, criteria, discoveryPayload.TestPlatformOptions); + discoveryEventsRegistrar?.RegisterDiscoveryEvents(this.currentDiscoveryRequest); - discoveryRequest.DiscoverAsync(); - discoveryRequest.WaitForCompletion(); - } + // Notify start of discovery start + this.testPlatformEventSource.DiscoveryRequestStart(); - finally + // Start the discovery of tests and wait for completion + this.currentDiscoveryRequest.DiscoverAsync(); + this.currentDiscoveryRequest.WaitForCompletion(); + } + finally + { + if (this.currentDiscoveryRequest != null) { - discoveryEventsRegistrar?.UnregisterDiscoveryEvents(discoveryRequest); + // Dispose the discovery request and unregister for events + discoveryEventsRegistrar?.UnregisterDiscoveryEvents(currentDiscoveryRequest); + this.currentDiscoveryRequest.Dispose(); + this.currentDiscoveryRequest = null; } - } - } - finally - { - EqtTrace.Info("TestRequestManager.DiscoverTests: Discovery tests completed."); - this.testPlatformEventSource.DiscoveryRequestStop(); - // Posts the Discovery Complete event. - this.metricsPublisher.Result.PublishMetrics(TelemetryDataConstants.TestDiscoveryCompleteEvent, requestData.MetricsCollection.Metrics); + EqtTrace.Info("TestRequestManager.DiscoverTests: Discovery tests completed."); + this.testPlatformEventSource.DiscoveryRequestStop(); + + // Posts the Discovery Complete event. + this.metricsPublisher.Result.PublishMetrics(TelemetryDataConstants.TestDiscoveryCompleteEvent, requestData.MetricsCollection.Metrics); + } } } @@ -312,6 +317,15 @@ public void CancelTestRun() this.currentTestRunRequest?.CancelAsync(); } + /// + /// Cancel the test discovery. + /// + public void CancelDiscovery() + { + EqtTrace.Info("TestRequestManager.CancelTestDiscovery: Sending cancel request."); + this.currentDiscoveryRequest?.Abort(); + } + /// /// Aborts the test run. /// @@ -544,7 +558,7 @@ private void RunTests(IRequestData requestData, TestRunCriteria testRunCriteria, // TranslationLayer can process faster as it directly gets the raw unserialized messages whereas // below logic needs to deserialize and do some cleanup // While this section is cleaning up, TranslationLayer can trigger run causing multiple threads to run the below section at the same time - lock (syncobject) + lock (this.syncObject) { try { diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs b/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs index a58e5eec03..197c3eea23 100644 --- a/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs +++ b/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs @@ -122,7 +122,7 @@ public void RunTestsWithXunitAdapter(RunnerInfo runnerInfo) [NetFullTargetFrameworkDataSource] public void RunTestsWithChutzpahAdapter(RunnerInfo runnerInfo) { - AcceptanceTestBase.SetTestEnvironment(this.testEnvironment, runnerInfo); + SetTestEnvironment(this.testEnvironment, runnerInfo); this.Setup(); var sources = new List @@ -147,16 +147,5 @@ public void RunTestsWithChutzpahAdapter(RunnerInfo runnerInfo) Assert.AreEqual(1, this.runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed)); Assert.AreEqual(1, testCase.FirstOrDefault().TestCase.LineNumber); } - - private IList GetTestAssemblies() - { - var testAssemblies = new List - { - this.GetAssetFullPath("SimpleTestProject.dll"), - this.GetAssetFullPath("SimpleTestProject2.dll") - }; - - return testAssemblies; - } } } \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/DiscoverTests.cs b/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/DiscoverTests.cs index ec852586ee..a274adf8bc 100644 --- a/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/DiscoverTests.cs +++ b/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/DiscoverTests.cs @@ -3,15 +3,17 @@ namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; using Microsoft.TestPlatform.TestUtilities; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; - using System; - using System.Collections.Generic; - using System.Linq; - using VisualStudio.TestPlatform.ObjectModel.Logging; + using Moq; [TestClass] public class DiscoverTests : AcceptanceTestBase @@ -169,6 +171,42 @@ public void DiscoverTestsUsingSourceNavigation(RunnerInfo runnerInfo) } } + [TestMethod] + [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] + public void CancelTestDiscovery(RunnerInfo runnerInfo) + { + // Setup + var testAssemblies = new List + { + this.GetAssetFullPath("DiscoveryTestProject.dll"), + this.GetAssetFullPath("SimpleTestProject.dll"), + this.GetAssetFullPath("SimpleTestProject2.dll") + }; + + SetTestEnvironment(this.testEnvironment, runnerInfo); + this.Setup(); + + var discoveredTests = new List(); + var discoveryEvents = new Mock(); + discoveryEvents.Setup((events) => events.HandleDiscoveredTests(It.IsAny>())).Callback + ((IEnumerable testcases) => { discoveredTests.AddRange(testcases); }); + + // Act + var discoveryTask = Task.Run(() => + { + this.vstestConsoleWrapper.DiscoverTests(testAssemblies, this.GetDefaultRunSettings(), discoveryEvents.Object); + }); + + Task.Delay(2000).Wait(); + vstestConsoleWrapper.CancelDiscovery(); + discoveryTask.Wait(); + + // Assert. + int discoveredSources = discoveredTests.Select((testcase) => testcase.Source).Distinct().Count(); + Assert.AreNotEqual(testAssemblies.Count, discoveredSources, "All test assemblies discovered"); + } + private IList GetTestAssemblies() { var testAssemblies = new List diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscovererEnumeratorTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscovererEnumeratorTests.cs index d9ae243a93..cb56326c87 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscovererEnumeratorTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscovererEnumeratorTests.cs @@ -9,7 +9,7 @@ namespace TestPlatform.CrossPlatEngine.UnitTests.Discovery using System.Globalization; using System.Linq; using System.Reflection; - + using System.Threading; using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; @@ -29,14 +29,15 @@ namespace TestPlatform.CrossPlatEngine.UnitTests.Discovery [TestClass] public class DiscovererEnumeratorTests { - private DiscovererEnumerator discovererEnumerator; - private Mock mockTestPlatformEventSource; - private DiscoveryResultCache discoveryResultCache; - private Mock mockRequestData; - private Mock mockMetricsCollection; - private Mock mockAssemblyProperties; - private Mock runSettingsMock; - private Mock messageLoggerMock; + private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + private readonly DiscovererEnumerator discovererEnumerator; + private readonly Mock mockTestPlatformEventSource; + private readonly DiscoveryResultCache discoveryResultCache; + private readonly Mock mockRequestData; + private readonly Mock mockMetricsCollection; + private readonly Mock mockAssemblyProperties; + private readonly Mock runSettingsMock; + private readonly Mock messageLoggerMock; public DiscovererEnumeratorTests() { @@ -46,7 +47,7 @@ public DiscovererEnumeratorTests() this.mockMetricsCollection = new Mock(); this.mockAssemblyProperties = new Mock(); this.mockRequestData.Setup(rd => rd.MetricsCollection).Returns(this.mockMetricsCollection.Object); - this.discovererEnumerator = new DiscovererEnumerator(this.mockRequestData.Object, this.discoveryResultCache, this.mockTestPlatformEventSource.Object, this.mockAssemblyProperties.Object); + this.discovererEnumerator = new DiscovererEnumerator(this.mockRequestData.Object, this.discoveryResultCache, this.mockTestPlatformEventSource.Object, this.mockAssemblyProperties.Object, this.cancellationTokenSource.Token); this.runSettingsMock = new Mock(); this.messageLoggerMock = new Mock(); TestPluginCacheTests.SetupMockExtensions( new string[] { typeof(DiscovererEnumeratorTests).GetTypeInfo().Assembly.Location }, @@ -381,13 +382,50 @@ public void LoadTestsShouldCollectMetrics() mockMetricsCollector.Verify(rd => rd.Add(TelemetryDataConstants.TimeTakenToLoadAdaptersInSec, It.IsAny()), Times.Once); } + [TestMethod] + public void LoadTestsShouldNotCallIntoDiscoverersWhenCancelled() + { + // Setup + string[] extensions = new string[] { typeof(DiscovererEnumeratorTests).GetTypeInfo().Assembly.Location }; + TestPluginCacheTests.SetupMockExtensions(extensions, () => { }); + + var dllsources = new List + { + typeof(DiscoveryResultCacheTests).GetTypeInfo().Assembly.Location, + typeof(DiscoveryResultCacheTests).GetTypeInfo().Assembly.Location + }; + var jsonsources = new List + { + "test1.json", + "test2.json" + }; + var sources = new List(dllsources); + sources.AddRange(jsonsources); + + var extensionSourceMap = new Dictionary> + { + { "_none_", sources } + }; + + // Act + this.cancellationTokenSource.Cancel(); + var runSettings = this.runSettingsMock.Object; + string testCaseFilter = "TestFilter"; + this.discovererEnumerator.LoadTests(extensionSourceMap, runSettings, testCaseFilter, this.messageLoggerMock.Object); + + // Validate + Assert.IsFalse(ManagedDllTestDiscoverer.IsManagedDiscoverTestCalled); + Assert.IsFalse(JsonTestDiscoverer.IsDiscoverTestCalled); + this.messageLoggerMock.Verify(logger => logger.SendMessage(TestMessageLevel.Warning, "Discovery of tests cancelled."), Times.Once); + } + [TestMethod] public void LoadTestsShouldCallIntoTheAdapterWithTheRightTestCaseSink() { - this.InvokeLoadTestWithMockSetup(); + this.InvokeLoadTestWithMockSetup(); - Assert.IsTrue(ManagedDllTestDiscoverer.IsManagedDiscoverTestCalled); - Assert.AreEqual(2, this.discoveryResultCache.Tests.Count); + Assert.IsTrue(ManagedDllTestDiscoverer.IsManagedDiscoverTestCalled); + Assert.AreEqual(2, this.discoveryResultCache.Tests.Count); } [TestMethod] @@ -483,7 +521,7 @@ public void LoadTestsShouldLogWarningMessageOnNoTestsInAssemblies() var expectedMessage = $"No test is available in {sourcesString}. Make sure that test discoverer & executors are registered and platform & framework version settings are appropriate and try again."; - this.messageLoggerMock.Verify( l => l.SendMessage(TestMessageLevel.Warning, expectedMessage)); + this.messageLoggerMock.Verify(l => l.SendMessage(TestMessageLevel.Warning, expectedMessage)); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/TestRequestHandlerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/TestRequestHandlerTests.cs index d728a07d4e..912aa8ee75 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/TestRequestHandlerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/TestRequestHandlerTests.cs @@ -131,7 +131,7 @@ public void ProcessRequestsShouldProcessMessagesUntilSessionCompleted() var task = this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); this.SendSessionEnd(); - Assert.IsTrue(task.Wait(20)); + Assert.IsTrue(task.Wait(2000)); } #region Version Check Protocol diff --git a/test/TestAssets/DiscoveryTestProject/DiscoveryTestProject.csproj b/test/TestAssets/DiscoveryTestProject/DiscoveryTestProject.csproj new file mode 100644 index 0000000000..36b0a05122 --- /dev/null +++ b/test/TestAssets/DiscoveryTestProject/DiscoveryTestProject.csproj @@ -0,0 +1,31 @@ + + + + + + + + + DiscoveryTestProject + netcoreapp1.0;netcoreapp1.1;netcoreapp2.0;net451 + x64 + + + + $(MSTestFrameworkVersion) + + + $(MSTestAdapterVersion) + + + $(NETTestSdkPreviousVersion) + + + + + + + + + + diff --git a/test/TestAssets/DiscoveryTestProject/LongDiscoveryTestClass.cs b/test/TestAssets/DiscoveryTestProject/LongDiscoveryTestClass.cs new file mode 100644 index 0000000000..2d5c65feeb --- /dev/null +++ b/test/TestAssets/DiscoveryTestProject/LongDiscoveryTestClass.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace DiscoveryTestProject3 +{ + [TestClass] + public class LongDiscoveryTestClass + { + [MyTestMethod] + public void CustomTestMethod() + { + + } + } + + internal class MyTestMethodAttribute : TestMethodAttribute + { + public MyTestMethodAttribute() + { + Thread.Sleep(10000); + } + } +}