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

feat: improved query syntax #90

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,22 @@ and define the following in your `csproj`:
```
These attributes will ask for the runtime test engine to replace the ones defined by the `Uno.UI.RuntimeTests.Engine` package.

## Test runner (UnitTestsControl) filtering syntax
- Search terms are separated by space. Multiple consecutive spaces are treated same as one.
- Multiple search terms are chained with AND logic.
- Search terms are case insensitive.
- `-` can be used before any term for exclusion, effectively inverting the results.
- Special tags can be used to match certain part of the test: // syntax: tag:term
- `class` or `c` matches the class name
- `method` or `m` matches the method name
- `displayname` or `d` matches the display name in [DataRow]
- Search term without a prefixing tag will match either of method name or class name.

Comment on lines +164 to +174
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
## Test runner (UnitTestsControl) filtering syntax
- Search terms are separated by space. Multiple consecutive spaces are treated same as one.
- Multiple search terms are chained with AND logic.
- Search terms are case insensitive.
- `-` can be used before any term for exclusion, effectively inverting the results.
- Special tags can be used to match certain part of the test: // syntax: tag:term
- `class` or `c` matches the class name
- `method` or `m` matches the method name
- `displayname` or `d` matches the display name in [DataRow]
- Search term without a prefixing tag will match either of method name or class name.
## Test runner (UnitTestsControl) filtering syntax
- Search terms are separated by space. Multiple consecutive spaces are treated the same as one.
- Multiple search terms are chained with AND logic.
- Search terms are case insensitive.
- `-` can be used before any term for exclusion, effectively inverting the results.
- Special tags can be used to match certain parts of the test: // syntax: tag:term
- `class` or `c` matches the class name
- `method` or `m` matches the method name
- `displayname` or `d` matches the display name in [DataRow]
- Search term without a prefixing tag will match either method name or class name.

Examples:
- `listview`
- `listview measure`
- `listview measure -recycle`
- `c:listview m:measure -m:recycle`

## Running the tests automatically during CI
_TBD_
86 changes: 86 additions & 0 deletions src/TestApp/shared/EngineFeatureTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

#if HAS_UNO_WINUI || WINDOWS_WINUI
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
#else
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#endif

namespace Uno.UI.RuntimeTests.Engine
{
/// <summary>
/// Contains tests relevant to the RTT engine features.
/// </summary>
[TestClass]
public class MetaTests
{
[TestMethod]
[RunsOnUIThread]
public async Task When_Test_ContentHelper()
{
var SUT = new TextBlock() { Text = "Hello" };
UnitTestsUIContentHelper.Content = SUT;

await UnitTestsUIContentHelper.WaitForIdle();
await UnitTestsUIContentHelper.WaitForLoaded(SUT);
}

[TestMethod]
[DataRow("hello", DisplayName = "hello test")]
[DataRow("goodbye", DisplayName = "goodbye test")]
public void When_DisplayName(string text)
{
}

[TestMethod]
[DataRow("at index 0")]
[DataRow("at index 1")]
[DataRow("at index 2")]
public void When_DataRows(string arg)
{
}

[TestMethod]
[DataRow("at index 0", "asd")]
[DataRow("at index 1", "asd")]
[DataRow("at index 2", "zxc")]
public void When_DataRows2(string arg, string arg2)
{
}

[DataTestMethod]
[DynamicData(nameof(GetDynamicData), DynamicDataSourceType.Method)]
public void When_DynamicData(string arg, string arg2)
{
}

[TestMethod]
[InjectedPointer(Windows.Devices.Input.PointerDeviceType.Touch)]
[InjectedPointer(Windows.Devices.Input.PointerDeviceType.Pen)]
public void When_InjectedPointers()
{
}

[TestMethod]
[DataRow("at index 0")]
[DataRow("at index 1")]
[InjectedPointer(Windows.Devices.Input.PointerDeviceType.Touch)]
[InjectedPointer(Windows.Devices.Input.PointerDeviceType.Pen)]
public void When_InjectedPointers_DataRows(string arg)
{
}

public static IEnumerable<object[]> GetDynamicData()
{
yield return new object[] { "at index 0", "asd" };
yield return new object[] { "at index 1", "asd" };
yield return new object[] { "at index 2", "zxc" };
}
}
}
21 changes: 21 additions & 0 deletions src/TestApp/shared/Extensions/AssertExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Uno.UI.RuntimeTests.Engine.Extensions;

internal static class AssertExtensions
{
public static void AreEqual<T>(this Assert assert, T expected, T actual, IEqualityComparer<T> comparer)
{
if (!comparer.Equals(expected, actual))
{
Assert.Fail(string.Join("\n",
"AreEqual failed.",
$"Expected: {expected}",
$"Actual: {actual}"
));
}
}
}
21 changes: 3 additions & 18 deletions src/TestApp/shared/SanityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

namespace Uno.UI.RuntimeTests.Engine
{
/// <summary>
/// Contains sanity/smoke tests used to assert basic scenarios.
/// </summary>
[TestClass]
public class SanityTests
{
Expand All @@ -28,24 +31,6 @@ public async Task Is_Still_Sane()
await Task.Delay(2000);
}

[TestMethod]
[RunsOnUIThread]
public async Task When_Test_ContentHelper()
{
var SUT = new TextBlock() { Text = "Hello" };
UnitTestsUIContentHelper.Content = SUT;

await UnitTestsUIContentHelper.WaitForIdle();
await UnitTestsUIContentHelper.WaitForLoaded(SUT);
}

[TestMethod]
[DataRow("hello", DisplayName = "hello test")]
[DataRow("goodbye", DisplayName = "goodbye test")]
public void Is_Sane_With_Cases(string text)
{
}

#if DEBUG
[TestMethod]
public async Task No_Longer_Sane() // expected to fail
Expand Down
123 changes: 123 additions & 0 deletions src/TestApp/shared/SearchQueryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Uno.UI.RuntimeTests.Engine.Extensions;
using static Uno.UI.RuntimeTests.UnitTestsControl;

namespace Uno.UI.RuntimeTests.Engine;

[TestClass]
public partial class SearchQueryTests
{
[TestMethod]
[DataRow("asd")] // simple
[DataRow("^asd")] // match start
[DataRow("asd$")] // match end
[DataRow("^asd$")] // full match
public void When_SearchPredicatePart_Parse(string input)
{
var actual = SearchPredicatePart.Parse(input);
var expected = input switch
{
"asd" => new SearchPredicatePart(input, "asd"),
"^asd" => new SearchPredicatePart(input, "asd", MatchStart: true),
"asd$" => new SearchPredicatePart(input, "asd", MatchEnd: true),
"^asd$" => new SearchPredicatePart(input, "asd", MatchStart: true, MatchEnd: true),

_ => throw new ArgumentOutOfRangeException(input),
};

Assert.AreEqual(expected, actual);
}

[TestMethod]
[DataRow("asd")] // simple
[DataRow("^asd,qwe$,^zxc$")] // multi parts
[DataRow("-asd")] // exclusion
[DataRow("tag:asd")] // tagged
public void When_SearchPredicate_ParseFragment(string input)
{
var actual = SearchPredicate.ParseFragment(input);
var expected = input switch
{
"asd" => new SearchPredicate("asd", "asd", Parts: new SearchPredicatePart("asd", "asd")),
"^asd,qwe$,^zxc$" => new SearchPredicate("^asd,qwe$,^zxc$", "^asd,qwe$,^zxc$", Parts: new[]{
new SearchPredicatePart("^asd", "asd", MatchStart: true),
new SearchPredicatePart("qwe$", "qwe", MatchEnd: true),
new SearchPredicatePart("^zxc$", "zxc", MatchStart: true, MatchEnd: true),
}),
"-asd" => new SearchPredicate("-asd", "asd", Exclusion: true, Parts: new[]{
new SearchPredicatePart("asd", "asd"),
}),
"tag:asd" => new SearchPredicate("tag:asd", "asd", Tag: "tag", Parts: new[]{
new SearchPredicatePart("asd", "asd"),
}),

_ => throw new ArgumentOutOfRangeException(input),
};

Assert.That.AreEqual(expected, actual, SearchPredicate.DefaultComparer);
}

[TestMethod]
[DataRow("asd")] // simple
[DataRow("asd qwe")] // multi fragments
[DataRow("class:asd method:qwe display_name:zxc -123")] // tags
[DataRow("c:asd m:qwe d:zxc -123")] // aliased tags
[DataRow("d:\"^asd \\\", asd$\"")] // quoted with escape
[DataRow("at:asd\"asd\"asd,^asd,asd$")] // inner literal quote
[DataRow("asd @0")] // custom prefix
[DataRow("p:\"index 0\",\"index 1\" -p:\"asd\"")] // multiple quotes

public void When_SearchPredicate_Parse(string input)
{
var actual = SearchPredicate.ParseQuery(input);
var predicates = input switch
{
"asd" => new SearchPredicate[] { new("asd", "asd", Parts: new SearchPredicatePart[] { new("asd", "asd"), }), },
"asd qwe" => new SearchPredicate[] {
new("asd", "asd", Parts: new SearchPredicatePart[] { new("asd", "asd"), }),
new("qwe", "qwe", Parts: new SearchPredicatePart[] { new("qwe", "qwe"), }),
},
"class:asd method:qwe display_name:zxc -123" => new SearchPredicate[] {
new("class:asd", "asd", Tag: "class", Parts: new SearchPredicatePart[] { new("asd", "asd"), }),
new("method:qwe", "qwe", Tag: "method", Parts: new SearchPredicatePart[] { new("qwe", "qwe"), }),
new("display_name:zxc", "zxc", Tag: "display_name", Parts: new SearchPredicatePart[] { new("zxc", "zxc"), }),
new("-123", "123", Exclusion: true, Parts: new SearchPredicatePart[] { new("123", "123"), }),
},
"c:asd m:qwe d:zxc -123" => new SearchPredicate[] {
new("c:asd", "asd", Tag: "class", Parts: new SearchPredicatePart[] { new("asd", "asd"), }),
new("m:qwe", "qwe", Tag: "method", Parts: new SearchPredicatePart[] { new("qwe", "qwe"), }),
new("d:zxc", "zxc", Tag: "display_name", Parts: new SearchPredicatePart[] { new("zxc", "zxc"), }),
new("-123", "123", Exclusion: true, Parts: new SearchPredicatePart[] { new("123", "123"), }),
},
"d:\"^asd \\\", asd$\"" => new SearchPredicate[] {
new("d:\"^asd \\\", asd$\"", "\"^asd \\\", asd$\"", Tag: "display_name", Parts: new SearchPredicatePart[] {
new("\"^asd \\\", asd$\"", "asd \", asd", MatchStart: true, MatchEnd: true),
}),
},
"at:asd\"asd\"asd,^asd,asd$" => new SearchPredicate[] {
new("at:asd\"asd\"asd,^asd,asd$", "asd\"asd\"asd,^asd,asd$", Tag: "at", Parts: new SearchPredicatePart[] {
new("asd\"asd\"asd", "asd\"asd\"asd"),
new ("^asd", "asd", MatchStart: true),
new ("asd$", "asd", MatchEnd: true),
}),
},
"asd @0" => new SearchPredicate[] {
new("asd", "asd", Parts: new SearchPredicatePart[] { new ("asd", "asd"), }),
new("@0", "0", Tag: "at", Parts: new SearchPredicatePart[] { new ("0", "0"), }),
},
"p:\"index 0\",\"index 1\" -p:\"asd\"" => new SearchPredicate[] {
new("p:\"index 0\",\"index 1\"", "\"index 0\",\"index 1\"", Tag: "params", Parts: new SearchPredicatePart[] { new("\"index 0\"", "index 0"), new("\"index 1\"", "index 1") }),
new("-p:\"asd\"", "\"asd\"", Exclusion: true, Tag: "params", Parts: new SearchPredicatePart[] { new("\"asd\"", "asd") })
},

_ => throw new ArgumentOutOfRangeException(input),
};
var expected = new SearchPredicateCollection(predicates!);

Assert.That.AreEqual(expected, actual, SearchPredicate.DefaultQueryComparer);
}
}
2 changes: 2 additions & 0 deletions src/TestApp/shared/Uno.UI.RuntimeTests.Engine.Shared.shproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<Import Project="Uno.UI.RuntimeTests.Engine.Shared.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
<ItemGroup>
<_Globbed_Compile Remove="Extensions\AssertExtensions.cs" />
<_Globbed_Compile Remove="MetaAttributes.cs" />
<_Globbed_Compile Remove="SearchQueryTests.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
<Compile Include="$(MSBuildThisFileDirectory)App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Extensions\AssertExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)MetaAttributes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)EngineFeatureTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SearchQueryTests.cs" />
</ItemGroup>
<ItemGroup>
<Page Include="$(MSBuildThisFileDirectory)MainPage.xaml">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Uno.UI.RuntimeTests.Extensions;

internal static partial class StringExtensions
{
/// <summary>
/// Like <see cref="string.Split(char[])"/>, but allows exception to be made with a Regex pattern.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Like <see cref="string.Split(char[])"/>, but allows exception to be made with a Regex pattern.
/// Like <see cref="string.Split(char[])"/>, but allows exceptions to be made with a Regex pattern.

/// </summary>
/// <param name="input"></param>
/// <param name="separator"></param>
/// <param name="ignoredPattern">segments matched by the regex will not be splited.</param>
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// <param name="ignoredPattern">segments matched by the regex will not be splited.</param>
/// <param name="ignoredPattern">segments matched by the regex will not be split.</param>

/// <param name="skipEmptyEntries"></param>
/// <returns></returns>
public static string[] SplitWithIgnore(this string input, char separator, string ignoredPattern, bool skipEmptyEntries)
{
var ignores = Regex.Matches(input, ignoredPattern);

var shards = new List<string>();
for (int i = 0; i < input.Length; i++)
{
var nextSpaceDelimiter = input.IndexOf(separator, i);

// find the next space, if inside a quote
while (nextSpaceDelimiter != -1 && ignores.FirstOrDefault(x => InRange(x, nextSpaceDelimiter)) is { } enclosingIgnore)
{
nextSpaceDelimiter = enclosingIgnore.Index + enclosingIgnore.Length is { } afterIgnore && afterIgnore < input.Length
? input.IndexOf(separator, afterIgnore)
: -1;
}

if (nextSpaceDelimiter != -1)
{
shards.Add(input.Substring(i, nextSpaceDelimiter - i));
i = nextSpaceDelimiter;

// skip multiple continuous spaces
while (skipEmptyEntries && i + 1 < input.Length && input[i + 1] == separator) i++;
}
else
{
shards.Add(input.Substring(i));
break;
}
}

return shards.ToArray();

bool InRange(Match x, int index) => x.Index <= index && index < (x.Index + x.Length);
}
}
Loading