Skip to content
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

Support escaping , in Test filter #1374

Merged
merged 13 commits into from
Jan 17, 2018
47 changes: 47 additions & 0 deletions src/Microsoft.TestPlatform.Utilities/StringUtilities.cs
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already vstest/src/Microsoft.TestPlatform.CoreUtilities/Utilities/StringExtensions.cs
Can we move this there ?

{
public static IEnumerable<string> 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -83,6 +83,9 @@ internal class RunSpecificTestsArgumentProcessorCapabilities : BaseArgumentProce

internal class RunSpecificTestsArgumentExecutor : IArgumentExecutor
{
public const char SplitDelimiter = ',';
public const char EscapeDelimiter = '\\';

#region Fields

/// <summary>
Expand Down Expand Up @@ -171,7 +174,10 @@ public void Initialize(string argument)
{
if (!string.IsNullOrWhiteSpace(argument))
{
this.selectedTestNames = new Collection<string>(argument.Split(new[] { CommandLineResources.SearchStringDelimiter }, StringSplitOptions.RemoveEmptyEntries));
this.selectedTestNames = new Collection<string>(
argument.Tokenize(SplitDelimiter, EscapeDelimiter)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(s => s.Trim()).ToList());
}

if (this.selectedTestNames == null || this.selectedTestNames.Count <= 0)
Expand All @@ -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));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use SequenceEqual instead or have another assert? just length check isn't good.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in the cases where we required.

}

private const char SplitChar = ',';
private const char EscapeChar = '\\';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<CommandLineException>(() => { 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<CommandLineException>(() => { 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<CommandLineException>(() => { 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<CommandLineException>(() => { 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<CommandLineException>(() => 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<CommandLineException>(() => executor.Execute());
}

Expand Down Expand Up @@ -260,7 +317,6 @@ public void ExecutorExecuteShouldCatchInvalidOperationExceptionThrownDuringExecu
public void ExecutorExecuteShouldForValidSourcesAndNoTestsDiscoveredShouldLogWarningAndReturnSuccess()
{
var mockTestPlatform = new Mock<ITestPlatform>();
var mockTestRunRequest = new Mock<ITestRunRequest>();
var mockDiscoveryRequest = new Mock<IDiscoveryRequest>();

this.ResetAndAddSourceToCommandLineOptions();
Expand All @@ -285,7 +341,6 @@ public void ExecutorExecuteShouldForValidSourcesAndNoTestsDiscoveredShouldLogWar
public void ExecutorExecuteShouldForValidSourcesAndNoTestsDiscoveredShouldLogAppropriateWarningIfTestAdapterPathIsNotSetAndReturnSuccess()
{
var mockTestPlatform = new Mock<ITestPlatform>();
var mockTestRunRequest = new Mock<ITestRunRequest>();
var mockDiscoveryRequest = new Mock<IDiscoveryRequest>();

this.ResetAndAddSourceToCommandLineOptions();
Expand Down Expand Up @@ -328,6 +383,113 @@ public void ExecutorExecuteShouldForValidSourcesAndValidSelectedTestsRunsTestsAn
Assert.AreEqual(ArgumentProcessorResult.Success, argumentProcessorResult);
}

[TestMethod]
public void ExecutorShouldRunTestsWhenTestsAreCommaSeparated()
{
var mockTestPlatform = new Mock<ITestPlatform>();
var mockTestRunRequest = new Mock<ITestRunRequest>();
var mockDiscoveryRequest = new Mock<IDiscoveryRequest>();

ResetAndAddSourceToCommandLineOptions();

List<TestCase> list = new List<TestCase>();
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<IRequestData>(), It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object);
mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<IRequestData>(), It.IsAny<DiscoveryCriteria>())).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<string>(), OutputLevel.Warning), Times.Never);
Assert.AreEqual(ArgumentProcessorResult.Success, argumentProcessorResult);
}

[TestMethod]
public void ExecutorShouldRunTestsWhenTestsAreFiltered()
{
var mockTestPlatform = new Mock<ITestPlatform>();
var mockTestRunRequest = new Mock<ITestRunRequest>();
var mockDiscoveryRequest = new Mock<IDiscoveryRequest>();

ResetAndAddSourceToCommandLineOptions();

List<TestCase> list = new List<TestCase>();
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<IRequestData>(), It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object);
mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<IRequestData>(), It.IsAny<DiscoveryCriteria>())).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<string>(), OutputLevel.Warning), Times.Never);
Assert.AreEqual(ArgumentProcessorResult.Success, argumentProcessorResult);
}

[TestMethod]
public void ExecutorShouldWarnWhenTestsAreNotAvailable()
{
var mockTestPlatform = new Mock<ITestPlatform>();
var mockTestRunRequest = new Mock<ITestRunRequest>();
var mockDiscoveryRequest = new Mock<IDiscoveryRequest>();

ResetAndAddSourceToCommandLineOptions();

List<TestCase> list = new List<TestCase>();
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<IRequestData>(), It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object);
mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<IRequestData>(), It.IsAny<DiscoveryCriteria>())).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<ITestPlatform>();
var mockTestRunRequest = new Mock<ITestRunRequest>();
var mockDiscoveryRequest = new Mock<IDiscoveryRequest>();

ResetAndAddSourceToCommandLineOptions();

List<TestCase> list = new List<TestCase>();
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<IRequestData>(), It.IsAny<TestRunCriteria>())).Returns(mockTestRunRequest.Object);
mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny<IRequestData>(), It.IsAny<DiscoveryCriteria>())).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<string>(), OutputLevel.Warning), Times.Never);
Assert.AreEqual(ArgumentProcessorResult.Success, argumentProcessorResult);
}

#endregion

private void ResetAndAddSourceToCommandLineOptions()
Expand Down