diff --git a/GoogleTestAdapter/Core.Tests/Core.Tests.csproj b/GoogleTestAdapter/Core.Tests/Core.Tests.csproj
index 229460a58..5057fc1f8 100644
--- a/GoogleTestAdapter/Core.Tests/Core.Tests.csproj
+++ b/GoogleTestAdapter/Core.Tests/Core.Tests.csproj
@@ -92,6 +92,7 @@
+
diff --git a/GoogleTestAdapter/Core.Tests/TestCases/TestCaseFactoryTests.cs b/GoogleTestAdapter/Core.Tests/TestCases/TestCaseFactoryTests.cs
new file mode 100644
index 000000000..74b00b8a1
--- /dev/null
+++ b/GoogleTestAdapter/Core.Tests/TestCases/TestCaseFactoryTests.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using FluentAssertions;
+using GoogleTestAdapter.Model;
+using GoogleTestAdapter.Tests.Common;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using static GoogleTestAdapter.Tests.Common.TestMetadata.TestCategories;
+
+namespace GoogleTestAdapter.TestCases
+{
+
+ [TestClass]
+ public class TestCaseFactoryTests : TestsBase
+ {
+
+ [TestMethod]
+ [TestCategory(Unit)]
+ public void CreateTestCases_DiscoveryTimeoutIsExceeded_DiscoveryIsCanceledAndCancelationIsLogged()
+ {
+ MockOptions.Setup(o => o.TestDiscoveryTimeoutInSeconds).Returns(1);
+ MockOptions.Setup(o => o.ParseSymbolInformation).Returns(false);
+
+ var reportedTestCases = new List();
+ var stopWatch = Stopwatch.StartNew();
+ var factory = new TestCaseFactory(TestResources.TenSecondsWaiter, MockLogger.Object, TestEnvironment.Options, null);
+ var returnedTestCases = factory.CreateTestCases(testCase => reportedTestCases.Add(testCase));
+ stopWatch.Stop();
+
+ reportedTestCases.Should().BeEmpty();
+ returnedTestCases.Should().BeEmpty();
+ stopWatch.Elapsed.Should().BeGreaterOrEqualTo(TimeSpan.FromSeconds(1));
+ stopWatch.Elapsed.Should().BeLessThan(TimeSpan.FromSeconds(2));
+ MockLogger.Verify(o => o.LogError(It.Is(s => s.Contains(TestResources.TenSecondsWaiter))), Times.Once);
+ MockLogger.Verify(o => o.DebugError(It.Is(s => s.Contains(Path.GetFileName(TestResources.TenSecondsWaiter)))), Times.Once);
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/GoogleTestAdapter/Core/Settings/IGoogleTestAdapterSettings.cs b/GoogleTestAdapter/Core/Settings/IGoogleTestAdapterSettings.cs
index 04e5823ce..c1976f467 100644
--- a/GoogleTestAdapter/Core/Settings/IGoogleTestAdapterSettings.cs
+++ b/GoogleTestAdapter/Core/Settings/IGoogleTestAdapterSettings.cs
@@ -27,6 +27,7 @@ public interface IGoogleTestAdapterSettings
bool? ShuffleTests { get; set; }
int? ShuffleTestsSeed { get; set; }
string TestDiscoveryRegex { get; set; }
+ int? TestDiscoveryTimeoutInSeconds { get; set; }
string WorkingDir { get; set; }
string PathExtension { get; set; }
string BatchForTestSetup { get; set; }
@@ -58,6 +59,7 @@ public static void GetUnsetValuesFrom(this IGoogleTestAdapterSettings self, IGoo
self.ShuffleTests = self.ShuffleTests ?? other.ShuffleTests;
self.ShuffleTestsSeed = self.ShuffleTestsSeed ?? other.ShuffleTestsSeed;
self.TestDiscoveryRegex = self.TestDiscoveryRegex ?? other.TestDiscoveryRegex;
+ self.TestDiscoveryTimeoutInSeconds = self.TestDiscoveryTimeoutInSeconds ?? other.TestDiscoveryTimeoutInSeconds;
self.WorkingDir = self.WorkingDir ?? other.WorkingDir;
self.PathExtension = self.PathExtension ?? other.PathExtension;
self.BatchForTestSetup = self.BatchForTestSetup ?? other.BatchForTestSetup;
diff --git a/GoogleTestAdapter/Core/Settings/RunSettings.cs b/GoogleTestAdapter/Core/Settings/RunSettings.cs
index 92506a903..682003e79 100644
--- a/GoogleTestAdapter/Core/Settings/RunSettings.cs
+++ b/GoogleTestAdapter/Core/Settings/RunSettings.cs
@@ -24,6 +24,9 @@ public RunSettings(string projectRegex)
public virtual string TestDiscoveryRegex { get; set; }
public bool ShouldSerializeTestDiscoveryRegex() { return TestDiscoveryRegex != null; }
+ public virtual int? TestDiscoveryTimeoutInSeconds { get; set; }
+ public bool ShouldSerializeTestDiscoveryTimeoutInSeconds() { return TestDiscoveryTimeoutInSeconds != null; }
+
public virtual string WorkingDir { get; set; }
public bool ShouldSerializeWorkingDir() { return WorkingDir != null; }
diff --git a/GoogleTestAdapter/Core/Settings/SettingsWrapper.cs b/GoogleTestAdapter/Core/Settings/SettingsWrapper.cs
index f26e1563f..0d46e5262 100644
--- a/GoogleTestAdapter/Core/Settings/SettingsWrapper.cs
+++ b/GoogleTestAdapter/Core/Settings/SettingsWrapper.cs
@@ -259,6 +259,23 @@ public static string ReplacePlaceholders(string userParameters, string executabl
public virtual string TestDiscoveryRegex => _currentSettings.TestDiscoveryRegex ?? OptionTestDiscoveryRegexDefaultValue;
+ public const string OptionTestDiscoveryTimeoutInSeconds = "Test discovery timeout in s";
+ public const int OptionTestDiscoveryTimeoutInSecondsDefaultValue = 30;
+ public const string OptionTestDiscoveryTimeoutInSecondsDescription =
+ "Number of seconds after which test discovery will be assumed to have failed. 0: Infinite timeout";
+
+ public virtual int TestDiscoveryTimeoutInSeconds {
+ get
+ {
+ int timeout = _currentSettings.TestDiscoveryTimeoutInSeconds ?? OptionTestDiscoveryTimeoutInSecondsDefaultValue;
+ if (timeout < 0)
+ timeout = OptionTestDiscoveryTimeoutInSecondsDefaultValue;
+
+ return timeout == 0 ? int.MaxValue : timeout;
+ }
+ }
+
+
public const string OptionWorkingDir = "Working directory";
public const string OptionWorkingDirDefaultValue = ExecutableDirPlaceholder;
public const string OptionWorkingDirDescription =
diff --git a/GoogleTestAdapter/Core/TestCases/TestCaseFactory.cs b/GoogleTestAdapter/Core/TestCases/TestCaseFactory.cs
index 90d19296f..3396e110f 100644
--- a/GoogleTestAdapter/Core/TestCases/TestCaseFactory.cs
+++ b/GoogleTestAdapter/Core/TestCases/TestCaseFactory.cs
@@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
+using System.Threading.Tasks;
using GoogleTestAdapter.Common;
using GoogleTestAdapter.DiaResolver;
using GoogleTestAdapter.Helpers;
@@ -13,7 +14,7 @@
namespace GoogleTestAdapter.TestCases
{
- internal class TestCaseFactory
+ public class TestCaseFactory
{
private readonly ILogger _logger;
private readonly SettingsWrapper _settings;
@@ -103,13 +104,34 @@ private IList NewCreateTestcases(Action reportTestCase, List
try
{
- var executor = new ProcessExecutor(null, _logger);
- int processExitCode = executor.ExecuteCommandBlocking(
- _executable,
- GoogleTestConstants.ListTestsOption.Trim(),
- "",
- _settings.GetPathExtension(_executable),
- lineAction);
+ int processExitCode = ProcessExecutor.ExecutionFailed;
+ ProcessExecutor executor = null;
+ var listAndParseTestsTask = new Task(() =>
+ {
+ executor = new ProcessExecutor(null, _logger);
+ processExitCode = executor.ExecuteCommandBlocking(
+ _executable,
+ GoogleTestConstants.ListTestsOption.Trim(),
+ "",
+ _settings.GetPathExtension(_executable),
+ lineAction);
+ }, TaskCreationOptions.AttachedToParent);
+ listAndParseTestsTask.Start();
+
+ if (!listAndParseTestsTask.Wait(TimeSpan.FromSeconds(_settings.TestDiscoveryTimeoutInSeconds)))
+ {
+ executor?.Cancel();
+
+ string dir = Path.GetDirectoryName(_executable);
+ string file = Path.GetFileName(_executable);
+ string cdToWorkingDir = $@"cd ""{dir}""";
+ string listTestsCommand = $"{file} {GoogleTestConstants.ListTestsOption.Trim()}";
+
+ _logger.LogError($"Test discovery was cancelled after {_settings.TestDiscoveryTimeoutInSeconds}s for executable {_executable}");
+ _logger.DebugError($"Test whether the following commands can be executed sucessfully on the command line (make sure all required binaries are on the PATH):{Environment.NewLine}{cdToWorkingDir}{Environment.NewLine}{listTestsCommand}");
+
+ return new List();
+ }
if (!CheckProcessExitCode(processExitCode, standardOutput))
return new List();
diff --git a/GoogleTestAdapter/Resources/AllTestSettings.gta.runsettings b/GoogleTestAdapter/Resources/AllTestSettings.gta.runsettings
index b9ef65ee1..c959108e2 100644
--- a/GoogleTestAdapter/Resources/AllTestSettings.gta.runsettings
+++ b/GoogleTestAdapter/Resources/AllTestSettings.gta.runsettings
@@ -19,6 +19,7 @@
false
0
.*Tests.*.exe
+ 30
${SolutionDir}
C:\fooDir;C:\barDir
diff --git a/GoogleTestAdapter/Tests.Common/Resources/TestData/misc/TenSecondsWaiter.exe b/GoogleTestAdapter/Tests.Common/Resources/TestData/misc/TenSecondsWaiter.exe
new file mode 100644
index 000000000..3a68765a4
Binary files /dev/null and b/GoogleTestAdapter/Tests.Common/Resources/TestData/misc/TenSecondsWaiter.exe differ
diff --git a/GoogleTestAdapter/Tests.Common/TestResources.cs b/GoogleTestAdapter/Tests.Common/TestResources.cs
index d0fae3e56..5195a827b 100644
--- a/GoogleTestAdapter/Tests.Common/TestResources.cs
+++ b/GoogleTestAdapter/Tests.Common/TestResources.cs
@@ -21,6 +21,8 @@ public static class TestResources
public const string Results0Batch = @"Tests\Returns0.bat";
public const string Results1Batch = @"Tests\Returns1.bat";
+ public const string TenSecondsWaiter = TestdataDir + @"misc\TenSecondsWaiter.exe";
+
public const int NrOfSampleTests = 88;
public const string SampleTests = SampleTestsSolutionDir + @"Debug\Tests_gta.exe";
public const string SampleTestsRelease = SampleTestsSolutionDir + @"Release\Tests_gta.exe";
diff --git a/GoogleTestAdapter/Tests.Common/Tests.Common.csproj b/GoogleTestAdapter/Tests.Common/Tests.Common.csproj
index 12eb92956..28bdab306 100644
--- a/GoogleTestAdapter/Tests.Common/Tests.Common.csproj
+++ b/GoogleTestAdapter/Tests.Common/Tests.Common.csproj
@@ -123,6 +123,9 @@
+
+ PreserveNewest
+
PreserveNewest
diff --git a/GoogleTestAdapter/Tests.Common/TestsBase.cs b/GoogleTestAdapter/Tests.Common/TestsBase.cs
index 00940625d..cbb6ef271 100644
--- a/GoogleTestAdapter/Tests.Common/TestsBase.cs
+++ b/GoogleTestAdapter/Tests.Common/TestsBase.cs
@@ -43,6 +43,8 @@ public static void SetupOptions(Mock mockOptions)
mockOptions.Setup(o => o.CheckCorrectUsage(It.IsAny())).Callback(() => { });
mockOptions.Setup(o => o.Clone()).Returns(mockOptions.Object);
+ mockOptions.Setup(o => o.TestDiscoveryTimeoutInSeconds)
+ .Returns(SettingsWrapper.OptionTestDiscoveryTimeoutInSecondsDefaultValue);
mockOptions.Setup(o => o.TraitsRegexesBefore).Returns(new List());
mockOptions.Setup(o => o.TraitsRegexesAfter).Returns(new List());
mockOptions.Setup(o => o.TestNameSeparator).Returns(SettingsWrapper.OptionTestNameSeparatorDefaultValue);
diff --git a/GoogleTestAdapter/VsPackage/GoogleTestExtensionOptionsPage.cs b/GoogleTestAdapter/VsPackage/GoogleTestExtensionOptionsPage.cs
index 62ae64273..c874ba4c4 100644
--- a/GoogleTestAdapter/VsPackage/GoogleTestExtensionOptionsPage.cs
+++ b/GoogleTestAdapter/VsPackage/GoogleTestExtensionOptionsPage.cs
@@ -159,6 +159,7 @@ private RunSettings GetRunSettingsFromOptionPages()
{
PrintTestOutput = _generalOptions.PrintTestOutput,
TestDiscoveryRegex = _generalOptions.TestDiscoveryRegex,
+ TestDiscoveryTimeoutInSeconds = _generalOptions.TestDiscoveryTimeoutInSeconds,
WorkingDir = _generalOptions.WorkingDir,
PathExtension = _generalOptions.PathExtension,
TraitsRegexesBefore = _generalOptions.TraitsRegexesBefore,
diff --git a/GoogleTestAdapter/VsPackage/OptionsPages/GeneralOptionsDialogPage.cs b/GoogleTestAdapter/VsPackage/OptionsPages/GeneralOptionsDialogPage.cs
index 5804ac3fb..70f2dc2ef 100644
--- a/GoogleTestAdapter/VsPackage/OptionsPages/GeneralOptionsDialogPage.cs
+++ b/GoogleTestAdapter/VsPackage/OptionsPages/GeneralOptionsDialogPage.cs
@@ -20,6 +20,16 @@ public string TestDiscoveryRegex
}
private string _testDiscoveryRegex = SettingsWrapper.OptionTestDiscoveryRegexDefaultValue;
+ [Category(SettingsWrapper.CategoryTestExecutionName)]
+ [DisplayName(SettingsWrapper.OptionTestDiscoveryTimeoutInSeconds)]
+ [Description(SettingsWrapper.OptionTestDiscoveryTimeoutInSecondsDescription)]
+ public int TestDiscoveryTimeoutInSeconds
+ {
+ get { return _testDiscoveryTimeoutInSeconds; }
+ set { SetAndNotify(ref _testDiscoveryTimeoutInSeconds, value); }
+ }
+ private int _testDiscoveryTimeoutInSeconds = SettingsWrapper.OptionTestDiscoveryTimeoutInSecondsDefaultValue;
+
[Category(SettingsWrapper.CategoryTestExecutionName)]
[DisplayName(SettingsWrapper.OptionWorkingDir)]
[Description(SettingsWrapper.OptionWorkingDirDescription)]