Skip to content

Commit

Permalink
feat: improved query syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiaoy312 committed Apr 14, 2023
1 parent cbf16c3 commit 8ce91be
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 40 deletions.
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.

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

## Running the tests automatically during CI
_TBD_
41 changes: 41 additions & 0 deletions src/TestApp/shared/EngineFeatureTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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)
{
}
}
}
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)MetaAttributes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)EngineFeatureTests.cs" />
</ItemGroup>
<ItemGroup>
<Page Include="$(MSBuildThisFileDirectory)MainPage.xaml">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class UnitTestEngineConfig

public static UnitTestEngineConfig Default { get; } = new UnitTestEngineConfig();

public string[]? Filters { get; set; }
public string? Query { get; set; }

public int Attempts { get; set; } = DefaultRepeatCount;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#if !UNO_RUNTIMETESTS_DISABLE_UI

#nullable enable

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using static System.StringComparison;

namespace Uno.UI.RuntimeTests;

partial class UnitTestsControl
{
private static IEnumerable<MethodInfo> FilterTests(UnitTestClassInfo testClassInfo, string? query)
{
var tests = testClassInfo.Tests?.AsEnumerable() ?? Array.Empty<MethodInfo>();
foreach (var filter in SearchPredicate.ParseQuery(query))
{
// chain filters with AND logic
tests = tests.Where(x =>
filter.Exclusion ^ // use xor to flip the result based on Exclusion
filter.Tag?.ToLowerInvariant() switch
{
"class" => MatchClassName(x, filter.Text),
"displayname" => MatchDisplayName(x, filter.Text),
"method" => MatchMethodName(x, filter.Text),

_ => MatchClassName(x, filter.Text) || MatchMethodName(x, filter.Text),
}
);
}

bool MatchClassName(MethodInfo x, string value) => x.DeclaringType?.Name.Contains(value, InvariantCultureIgnoreCase) ?? false;
bool MatchMethodName(MethodInfo x, string value) => x.Name.Contains(value, InvariantCultureIgnoreCase);
bool MatchDisplayName(MethodInfo x, string value) =>
// fixme: since we are returning MethodInfo for match, there is no way to specify
// which of the [DataRow] or which row within [DynamicData] without refactoring.
// fixme: support [DynamicData]
x.GetCustomAttributes<DataRowAttribute>().Any(y => y.DisplayName.Contains(value, InvariantCultureIgnoreCase));

return tests;
}

public record SearchPredicate(string Raw, string Text, bool Exclusion, string? Tag = null)
{
private static readonly IReadOnlyDictionary<string, string> TagAliases =
new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase)
{
["c"] = "class",
["m"] = "method",
["d"] = "displayname",
};

public static SearchPredicate[] ParseQuery(string? query)
{
if (string.IsNullOrWhiteSpace(query)) return Array.Empty<SearchPredicate>();

return query!.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Select(Parse)
.OfType<SearchPredicate>() // trim null
.Where(x => x.Text.Length > 0) // ignore empty tag query eg: "c:"
.ToArray();
}

public static SearchPredicate? Parse(string criteria)
{
if (string.IsNullOrWhiteSpace(criteria)) return null;

var raw = criteria.Trim();
var text = raw;
if (text.StartsWith('-') is var exclusion && exclusion)
{
text = text.Substring(1);
}
var tag = default(string?);
if (text.Split(':', 2) is { Length: 2 } tagParts)
{
tag = TagAliases.TryGetValue(tagParts[0], out var alias) ? alias : tagParts[0];
text = tagParts[1];
}

return new(raw, text, exclusion, tag);
}
}
}

#endif
26 changes: 5 additions & 21 deletions src/Uno.UI.RuntimeTests.Engine.Library/UI/UnitTestsControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ public sealed partial class UnitTestsControl : UserControl
#endif
#pragma warning restore CS0109

private const StringComparison StrComp = StringComparison.InvariantCultureIgnoreCase;
private Task? _runner;
private CancellationTokenSource? _cts = new CancellationTokenSource();
#if DEBUG
Expand Down Expand Up @@ -499,7 +498,7 @@ private void EnableConfigPersistence()
consoleOutput.IsChecked = config.IsConsoleOutputEnabled;
runIgnored.IsChecked = config.IsRunningIgnored;
retry.IsChecked = config.Attempts > 1;
testFilter.Text = string.Join(";", config.Filters ?? Array.Empty<string>());
testFilter.Text = config.Query ?? string.Empty;
}
}
catch (Exception)
Expand Down Expand Up @@ -534,15 +533,11 @@ private UnitTestEngineConfig BuildConfig()
var isConsoleOutput = consoleOutput.IsChecked ?? false;
var isRunningIgnored = runIgnored.IsChecked ?? false;
var attempts = (retry.IsChecked ?? true) ? UnitTestEngineConfig.DefaultRepeatCount : 1;
var filter = testFilter.Text.Trim();
if (string.IsNullOrEmpty(filter))
{
filter = null;
}
var query = testFilter.Text.Trim() is { Length: >0 } text ? text: null;

return new UnitTestEngineConfig
{
Filters = filter?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>(),
Query = query,
IsConsoleOutputEnabled = isConsoleOutput,
IsRunningIgnored = isRunningIgnored,
Attempts = attempts,
Expand Down Expand Up @@ -675,17 +670,6 @@ await _dispatcher.RunAsync(() =>
await GenerateTestResults();
}

private static IEnumerable<MethodInfo> FilterTests(UnitTestClassInfo testClassInfo, string[]? filters)
{
var testClassNameContainsFilters = filters?.Any(f => testClassInfo.Type?.FullName?.Contains(f, StrComp) ?? false) ?? false;
return testClassInfo.Tests?.
Where(t => ((!filters?.Any()) ?? true)
|| testClassNameContainsFilters
|| (filters?.Any(f => t.DeclaringType?.FullName?.Contains(f, StrComp) ?? false) ?? false)
|| (filters?.Any(f => t.Name.Contains(f, StrComp)) ?? false))
?? Array.Empty<MethodInfo>();
}

private async Task ExecuteTestsForInstance(
CancellationToken ct,
object instance,
Expand All @@ -696,7 +680,7 @@ private async Task ExecuteTestsForInstance(
? ConsoleOutputRecorder.Start()
: default;

var tests = UnitTestsControl.FilterTests(testClassInfo, config.Filters)
var tests = FilterTests(testClassInfo, config.Query)
.Select(method => new UnitTestMethodInfo(instance, method))
.ToArray();

Expand All @@ -705,7 +689,7 @@ private async Task ExecuteTestsForInstance(
return;
}

ReportTestClass(testClassInfo.Type.GetTypeInfo());
ReportTestClass(testClassInfo.Type!.GetTypeInfo());
_ = ReportMessage($"Running {tests.Length} test methods");

foreach (var test in tests)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<Compile Include="$(MSBuildThisFileDirectory)UI\UnitTestEngineConfig.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\UnitTestMethodInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\UnitTestsControl.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\UnitTestsControl.Filtering.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\UnitTestsControl.CustomConsoleOutput.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\UnitTestsUIContentHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\UnitTestDispatcherCompat.cs" />
Expand Down

0 comments on commit 8ce91be

Please sign in to comment.