diff --git a/src/Microsoft.TestPlatform.Utilities/StringUtilities.cs b/src/Microsoft.TestPlatform.Utilities/StringUtilities.cs new file mode 100644 index 0000000000..f71f809976 --- /dev/null +++ b/src/Microsoft.TestPlatform.Utilities/StringUtilities.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.Utilities +{ + using System.Collections.Generic; + using System.Text; + + public static class StringExtensions + { + public static IEnumerable Tokenize(this string input, char separator, char escape) + { + if (string.IsNullOrEmpty(input)) yield break; + + var buffer = new StringBuilder(); + var escaping = false; + foreach (var c in input) + { + if (escaping) + { + buffer.Append(c); + escaping = false; + } + else if (c == escape) + { + escaping = true; + } + else if (c == separator) + { + yield return buffer.Flush(); + } + else + { + buffer.Append(c); + } + } + if (buffer.Length > 0 || input[input.Length - 1] == separator) yield return buffer.Flush(); + } + + private static string Flush(this StringBuilder stringBuilder) + { + var result = stringBuilder.ToString(); + stringBuilder.Clear(); + return result; + } + } +} diff --git a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs index 67b6667f06..97780cad67 100644 --- a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs @@ -12,13 +12,13 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities; using Microsoft.VisualStudio.TestPlatform.CommandLineUtilities; using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.Utilities; - using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; internal class RunSpecificTestsArgumentProcessor : IArgumentProcessor @@ -83,6 +83,9 @@ internal class RunSpecificTestsArgumentProcessorCapabilities : BaseArgumentProce internal class RunSpecificTestsArgumentExecutor : IArgumentExecutor { + public const char SplitDelimiter = ','; + public const char EscapeDelimiter = '\\'; + #region Fields /// @@ -171,7 +174,10 @@ public void Initialize(string argument) { if (!string.IsNullOrWhiteSpace(argument)) { - this.selectedTestNames = new Collection(argument.Split(new[] { CommandLineResources.SearchStringDelimiter }, StringSplitOptions.RemoveEmptyEntries)); + this.selectedTestNames = new Collection( + argument.Tokenize(SplitDelimiter, EscapeDelimiter) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(s => s.Trim()).ToList()); } if (this.selectedTestNames == null || this.selectedTestNames.Count <= 0) @@ -194,7 +200,7 @@ public ArgumentProcessorResult Execute() Contract.Assert(this.testRequestManager != null); Contract.Assert(!string.IsNullOrWhiteSpace(this.runSettingsManager.ActiveRunSettings.SettingsXml)); - if (this.commandLineOptions.Sources.Count() <= 0) + if (!this.commandLineOptions.Sources.Any()) { throw new CommandLineException(string.Format(CultureInfo.CurrentUICulture, CommandLineResources.MissingTestSourceFile)); } diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/StringUtilitiesTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/StringUtilitiesTests.cs new file mode 100644 index 0000000000..c285f3a155 --- /dev/null +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/StringUtilitiesTests.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Linq; + +namespace Microsoft.TestPlatform.Utilities.UnitTests +{ + using Castle.Core.Internal; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class StringUtilitiesTests + { + [TestMethod] + public void SplitShouldReturnWhenStringisNullOrEmpty() + { + var argsList = string.Empty.Tokenize(SplitChar, EscapeChar); + + Assert.IsTrue(argsList.IsNullOrEmpty()); + } + + [TestMethod] + public void SplitShouldReturnWhenStringDoesntContainSplitChar() + { + var data = "foobar"; + var argsList = data.Tokenize(SplitChar, EscapeChar); + var enumerable = argsList as string[] ?? argsList.ToArray(); + + Assert.IsTrue(enumerable.Length == 1); + Assert.IsTrue(enumerable.First().Equals(data)); + } + + [TestMethod] + public void SplitShouldSplitWhenStringContainsSplitChar() + { + var data = "foo,bar"; + var argsList = data.Tokenize(SplitChar, EscapeChar); + var enumerable = argsList as string[] ?? argsList.ToArray(); + + Assert.IsTrue(enumerable.Length == 2); + } + + [TestMethod] + public void SplitShouldSplitWhenStringWithSplitCharStartEnd() + { + var data = ",foo,bar,"; + var argsList = data.Tokenize(SplitChar, EscapeChar); + var enumerable = argsList as string[] ?? argsList.ToArray(); + + Assert.IsTrue(enumerable.Length == 4); + } + + [TestMethod] + public void SplitShouldEscapeSplitCharWhenEscapedCharPresent() + { + var data = "foo\\,bar"; + var argsList = data.Tokenize(SplitChar, EscapeChar); + var enumerable = argsList as string[] ?? argsList.ToArray(); + + Assert.IsTrue(enumerable.Length == 1); + Assert.IsTrue(enumerable.First().Equals("foo,bar")); + } + + [TestMethod] + public void SplitShouldEscapeSplitCharWhenEscapedNonEscapedCharPresent() + { + var data = "foo\\,,bar"; + var argsList = data.Tokenize(SplitChar, EscapeChar); + var enumerable = argsList as string[] ?? argsList.ToArray(); + Assert.IsTrue(enumerable.Length == 2); + Assert.IsTrue(enumerable.First().Equals("foo,")); + } + + [TestMethod] + public void SplitShouldSplitWhenOnlySplitCharPresent() + { + var data = ","; + var argsList = data.Tokenize(SplitChar, EscapeChar); + var enumerable = argsList as string[] ?? argsList.ToArray(); + + Assert.IsTrue(enumerable.Length == 2); + } + + [TestMethod] + public void SplitShouldNotSplitWhenNoSplitCharPresent() + { + var data = "foo\\bar"; + var argsList = data.Tokenize(SplitChar, EscapeChar); + var enumerable = argsList as string[] ?? argsList.ToArray(); + + Assert.IsTrue(enumerable.Length == 1); + } + + private const char SplitChar = ','; + private const char EscapeChar = '\\'; + } +} diff --git a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs index b2c2f55b87..f1c4a992b4 100644 --- a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs @@ -101,12 +101,69 @@ public void CapabilitiesShouldReturnAppropriateProperties() #region RunSpecificTestsArgumentExecutorTests + [TestMethod] + public void InitializeShouldThrowIfArgumentIsNull() + { + CommandLineOptions.Instance.Reset(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, new TestPlatform(), TestLoggerManager.Instance, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask); + var executor = GetExecutor(testRequestManager); + + Assert.ThrowsException(() => { executor.Initialize(null); }); + } + + [TestMethod] + public void InitializeShouldThrowIfArgumentIsEmpty() + { + CommandLineOptions.Instance.Reset(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, new TestPlatform(), TestLoggerManager.Instance, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask); + var executor = GetExecutor(testRequestManager); + + Assert.ThrowsException(() => { executor.Initialize(String.Empty); }); + } + + [TestMethod] + public void InitializeShouldThrowIfArgumentIsWhiteSpace() + { + CommandLineOptions.Instance.Reset(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, new TestPlatform(), TestLoggerManager.Instance, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask); + var executor = GetExecutor(testRequestManager); + + Assert.ThrowsException(() => { executor.Initialize(" "); }); + } + + [TestMethod] + public void InitializeShouldThrowIfArgumentsAreEmpty() + { + CommandLineOptions.Instance.Reset(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, new TestPlatform(), TestLoggerManager.Instance, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask); + var executor = GetExecutor(testRequestManager); + + Assert.ThrowsException(() => { executor.Initialize(" , "); }); + } + + [TestMethod] + public void ExecutorShouldSplitTestsSeparatedByComma() + { + CommandLineOptions.Instance.Reset(); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, new TestPlatform(), TestLoggerManager.Instance, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask); + var executor = GetExecutor(testRequestManager); + + Assert.ThrowsException(() => executor.Execute()); + } + [TestMethod] public void ExecutorExecuteForNoSourcesShouldThrowCommandLineException() { CommandLineOptions.Instance.Reset(); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, new TestPlatform(), TestLoggerManager.Instance, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask); var executor = GetExecutor(testRequestManager); + Assert.ThrowsException(() => executor.Execute()); } @@ -260,7 +317,6 @@ public void ExecutorExecuteShouldCatchInvalidOperationExceptionThrownDuringExecu public void ExecutorExecuteShouldForValidSourcesAndNoTestsDiscoveredShouldLogWarningAndReturnSuccess() { var mockTestPlatform = new Mock(); - var mockTestRunRequest = new Mock(); var mockDiscoveryRequest = new Mock(); this.ResetAndAddSourceToCommandLineOptions(); @@ -285,7 +341,6 @@ public void ExecutorExecuteShouldForValidSourcesAndNoTestsDiscoveredShouldLogWar public void ExecutorExecuteShouldForValidSourcesAndNoTestsDiscoveredShouldLogAppropriateWarningIfTestAdapterPathIsNotSetAndReturnSuccess() { var mockTestPlatform = new Mock(); - var mockTestRunRequest = new Mock(); var mockDiscoveryRequest = new Mock(); this.ResetAndAddSourceToCommandLineOptions(); @@ -328,6 +383,113 @@ public void ExecutorExecuteShouldForValidSourcesAndValidSelectedTestsRunsTestsAn Assert.AreEqual(ArgumentProcessorResult.Success, argumentProcessorResult); } + [TestMethod] + public void ExecutorShouldRunTestsWhenTestsAreCommaSeparated() + { + var mockTestPlatform = new Mock(); + var mockTestRunRequest = new Mock(); + var mockDiscoveryRequest = new Mock(); + + ResetAndAddSourceToCommandLineOptions(); + + List list = new List(); + list.Add(new TestCase("Test1", new Uri("http://FooTestUri1"), "Source1")); + list.Add(new TestCase("Test2", new Uri("http://FooTestUri1"), "Source1")); + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(list)); + + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny())).Returns(mockTestRunRequest.Object); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask); + var executor = GetExecutor(testRequestManager); + + executor.Initialize("Test1, Test2"); + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + + mockOutput.Verify(o => o.WriteLine(It.IsAny(), OutputLevel.Warning), Times.Never); + Assert.AreEqual(ArgumentProcessorResult.Success, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorShouldRunTestsWhenTestsAreFiltered() + { + var mockTestPlatform = new Mock(); + var mockTestRunRequest = new Mock(); + var mockDiscoveryRequest = new Mock(); + + ResetAndAddSourceToCommandLineOptions(); + + List list = new List(); + list.Add(new TestCase("Test1", new Uri("http://FooTestUri1"), "Source1")); + list.Add(new TestCase("Test2", new Uri("http://FooTestUri1"), "Source1")); + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(list)); + + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny())).Returns(mockTestRunRequest.Object); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask); + var executor = GetExecutor(testRequestManager); + + executor.Initialize("Test1"); + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + + mockOutput.Verify(o => o.WriteLine(It.IsAny(), OutputLevel.Warning), Times.Never); + Assert.AreEqual(ArgumentProcessorResult.Success, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorShouldWarnWhenTestsAreNotAvailable() + { + var mockTestPlatform = new Mock(); + var mockTestRunRequest = new Mock(); + var mockDiscoveryRequest = new Mock(); + + ResetAndAddSourceToCommandLineOptions(); + + List list = new List(); + list.Add(new TestCase("Test2", new Uri("http://FooTestUri1"), "Source1")); + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(list)); + + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny())).Returns(mockTestRunRequest.Object); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask); + var executor = GetExecutor(testRequestManager); + + executor.Initialize("Test1, Test2"); + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + + mockOutput.Verify(o => o.WriteLine("A total of 1 tests were discovered but some tests do not match the specified selection criteria(Test1). Use right value(s) and try again.", OutputLevel.Warning), Times.Once); + Assert.AreEqual(ArgumentProcessorResult.Success, argumentProcessorResult); + } + + [TestMethod] + public void ExecutorShouldRunTestsWhenTestsAreCommaSeparatedWithEscape() + { + var mockTestPlatform = new Mock(); + var mockTestRunRequest = new Mock(); + var mockDiscoveryRequest = new Mock(); + + ResetAndAddSourceToCommandLineOptions(); + + List list = new List(); + list.Add(new TestCase("Test1(a,b)", new Uri("http://FooTestUri1"), "Source1")); + list.Add(new TestCase("Test2(c,d)", new Uri("http://FooTestUri1"), "Source1")); + mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(list)); + + mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny())).Returns(mockTestRunRequest.Object); + mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); + + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestLoggerManager.Instance, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask); + var executor = GetExecutor(testRequestManager); + + executor.Initialize("Test1(a\\,b), Test2(c\\,d)"); + ArgumentProcessorResult argumentProcessorResult = executor.Execute(); + + mockOutput.Verify(o => o.WriteLine(It.IsAny(), OutputLevel.Warning), Times.Never); + Assert.AreEqual(ArgumentProcessorResult.Success, argumentProcessorResult); + } + #endregion private void ResetAndAddSourceToCommandLineOptions()