diff --git a/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/FilterOperator.cs b/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/FilterOperator.cs index 9743f49f87..d518d7548f 100644 --- a/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/FilterOperator.cs +++ b/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/FilterOperator.cs @@ -19,9 +19,4 @@ internal enum FilterOperator /// Combine the following expressions with a logical OR. /// Or, - - /// - /// Filter the following expression by the given property. - /// - Equals, } diff --git a/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/OperatorKind.cs b/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/OperatorKind.cs index 051583489c..b9c7da2819 100644 --- a/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/OperatorKind.cs +++ b/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/OperatorKind.cs @@ -41,6 +41,11 @@ internal enum OperatorKind /// FilterEquals, + /// + /// Filter not equals operator. + /// + FilterNotEquals, + /// /// Operator used for combining multiple filters with a logical OR. /// diff --git a/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/TreeNodeFilter.cs b/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/TreeNodeFilter.cs index 9bec02318e..9a81e683df 100644 --- a/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/TreeNodeFilter.cs +++ b/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/TreeNodeFilter.cs @@ -38,6 +38,7 @@ internal TreeNodeFilter(string filter) /// FILTER_EXPR = /// '(' FILTER_EXPR ')' /// | TOKEN '=' TOKEN + /// | TOKEN '!=' TOKEN /// | FILTER_EXPR OP FILTER_EXPR /// | TOKEN /// OP = '&' | '|' @@ -210,6 +211,13 @@ private static List ParseFilter(string filter) isPropAllowed = false; break; + case "!=": + operatorStack.Push(OperatorKind.FilterNotEquals); + + isOperatorAllowed = false; + isPropAllowed = false; + break; + default: expressionStack.Push(new ValueExpression(token)); @@ -311,7 +319,6 @@ private static void ProcessStackOperator(OperatorKind op, Stack FilterOperator.And, OperatorKind.Or => FilterOperator.Or, - OperatorKind.FilterEquals => FilterOperator.Equals, _ => throw ApplicationStateGuard.Unreachable(), }; @@ -319,6 +326,7 @@ private static void ProcessStackOperator(OperatorKind op, Stack TokenizeFilter(string filter) break; + case '!': + if (i + 1 < filter.Length && filter[i + 1] == '=') + { + if (lastStringTokenBuilder.Length > 0) + { + yield return lastStringTokenBuilder.ToString(); + lastStringTokenBuilder.Clear(); + } + + yield return "!="; + i++; + } + else + { + goto default; + } + + break; + default: lastStringTokenBuilder.Append(Regex.Escape(filter[i].ToString())); break; diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Requests/TreeNodeFilterTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Requests/TreeNodeFilterTests.cs index f07a899ae9..f587f02457 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Requests/TreeNodeFilterTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Requests/TreeNodeFilterTests.cs @@ -105,6 +105,42 @@ public void Parameters_PropertyCheck() Assert.IsFalse(filter.MatchesFilter("/ProjectB.UnitTests", new PropertyBag())); } + [TestMethod] + public void Parameters_NegatedPropertyCheck() + { + TreeNodeFilter filter = new("/*.UnitTests[Tag!=Fast]"); + Assert.IsFalse(filter.MatchesFilter("/ProjectB.UnitTests", new PropertyBag(new KeyValuePairStringProperty("Tag", "Fast")))); + Assert.IsTrue(filter.MatchesFilter("/ProjectB.UnitTests", new PropertyBag(new KeyValuePairStringProperty("Tag", "Slow")))); + Assert.IsTrue(filter.MatchesFilter("/ProjectB.UnitTests", new PropertyBag())); + } + + [TestMethod] + public void Parameters_NegatedPropertyCheckWithMatchAllFilter() + { + TreeNodeFilter filter = new("/**[Tag!=Fast]"); + Assert.IsFalse(filter.MatchesFilter("/ProjectB.UnitTests", new PropertyBag(new KeyValuePairStringProperty("Tag", "Fast")))); + Assert.IsTrue(filter.MatchesFilter("/ProjectB.UnitTests", new PropertyBag(new KeyValuePairStringProperty("Tag", "Slow")))); + Assert.IsTrue(filter.MatchesFilter("/ProjectB.UnitTests", new PropertyBag())); + } + + [TestMethod] + public void Parameters_NegatedPropertyCheckCombinedWithAnd() + { + TreeNodeFilter filter = new("/*.UnitTests[(Tag!=Fast)&(Tag!=Slow)]"); + Assert.IsFalse(filter.MatchesFilter("/ProjectB.UnitTests", new PropertyBag(new KeyValuePairStringProperty("Tag", "Fast")))); + Assert.IsFalse(filter.MatchesFilter("/ProjectB.UnitTests", new PropertyBag(new KeyValuePairStringProperty("Tag", "Slow")))); + Assert.IsTrue(filter.MatchesFilter("/ProjectB.UnitTests", new PropertyBag())); + } + + [TestMethod] + public void Parameters_NegatedPropertyCheckCombinedWithOr() + { + TreeNodeFilter filter = new("/*.UnitTests[(Tag!=Fast)|(Tag!=Slow)]"); + Assert.IsTrue(filter.MatchesFilter("/ProjectB.UnitTests", new PropertyBag(new KeyValuePairStringProperty("Tag", "Fast")))); + Assert.IsTrue(filter.MatchesFilter("/ProjectB.UnitTests", new PropertyBag(new KeyValuePairStringProperty("Tag", "Slow")))); + Assert.IsTrue(filter.MatchesFilter("/ProjectB.UnitTests", new PropertyBag())); + } + [TestMethod] public void Parameters_DisallowAtStart() => Assert.ThrowsException(() => _ = new TreeNodeFilter("/[Tag=Fast]"));