Skip to content

Commit

Permalink
Remove regex tests blocking on async calls (dotnet#100982)
Browse files Browse the repository at this point in the history
It looks like these were written this way due to the limitation that spans can't be locals in an async method, even if the span doesn't live past an await. Address this for now by separating the code out into a local function.
  • Loading branch information
stephentoub authored and matouskozak committed Apr 30, 2024
1 parent 006be09 commit 0c0e6fb
Showing 1 changed file with 108 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;

namespace System.Text.RegularExpressions.Tests
Expand Down Expand Up @@ -41,68 +42,81 @@ public void EnumerateMatches_Ctor_Invalid()

[Theory]
[MemberData(nameof(RegexHelpers.AvailableEngines_MemberData), MemberType = typeof(RegexHelpers))]
public void EnumerateMatches_Lookahead(RegexEngine engine)
public async Task EnumerateMatches_Lookahead(RegexEngine engine)
{
if (RegexHelpers.IsNonBacktracking(engine))
{
// lookaheads not supported
return;
}

const string Pattern = @"\b(?!un)\w+\b";
const string Input = "unite one unethical ethics use untie ultimate";
Test("unite one unethical ethics use untie ultimate",
await RegexHelpers.GetRegexAsync(engine, @"\b(?!un)\w+\b", RegexOptions.IgnoreCase),
["one", "ethics", "use", "ultimate"]);

Regex r = RegexHelpers.GetRegexAsync(engine, Pattern, RegexOptions.IgnoreCase).GetAwaiter().GetResult();
int count = 0;
string[] expectedMatches = ["one", "ethics", "use", "ultimate"];
ReadOnlySpan<char> span = Input.AsSpan();
foreach (ValueMatch match in r.EnumerateMatches(span))
static void Test(string input, Regex r, string[] expectedMatches)
{
Assert.Equal(expectedMatches[count++], span.Slice(match.Index, match.Length).ToString());
ReadOnlySpan<char> span = input.AsSpan();

int count = 0;
foreach (ValueMatch match in r.EnumerateMatches(span))
{
Assert.Equal(expectedMatches[count++], span.Slice(match.Index, match.Length).ToString());
}

Assert.Equal(expectedMatches.Length, count);
}
Assert.Equal(4, count);
}

[Theory]
[MemberData(nameof(RegexHelpers.AvailableEngines_MemberData), MemberType = typeof(RegexHelpers))]
public void EnumerateMatches_Lookbehind(RegexEngine engine)
public async Task EnumerateMatches_Lookbehind(RegexEngine engine)
{
if (RegexHelpers.IsNonBacktracking(engine))
{
// lookbehinds not supported
return;
}

const string Pattern = @"(?<=\b20)\d{2}\b";
const string Input = "2010 1999 1861 2140 2009";
Test("2010 1999 1861 2140 2009",
await RegexHelpers.GetRegexAsync(engine, @"(?<=\b20)\d{2}\b", RegexOptions.IgnoreCase),
["10", "09"]);

Regex r = RegexHelpers.GetRegexAsync(engine, Pattern, RegexOptions.IgnoreCase).GetAwaiter().GetResult();
int count = 0;
string[] expectedMatches = ["10", "09"];
ReadOnlySpan<char> span = Input.AsSpan();
foreach (ValueMatch match in r.EnumerateMatches(span))
static void Test(string input, Regex r, string[] expectedMatches)
{
Assert.Equal(expectedMatches[count++], span.Slice(match.Index, match.Length).ToString());
ReadOnlySpan<char> span = input.AsSpan();

int count = 0;
foreach (ValueMatch match in r.EnumerateMatches(span))
{
Assert.Equal(expectedMatches[count++], span.Slice(match.Index, match.Length).ToString());
}

Assert.Equal(expectedMatches.Length, count);
}
Assert.Equal(2, count);
}

[Theory]
[MemberData(nameof(RegexHelpers.AvailableEngines_MemberData), MemberType = typeof(RegexHelpers))]
public void EnumerateMatches_CheckIndex(RegexEngine engine)
public async Task EnumerateMatches_CheckIndex(RegexEngine engine)
{
const string Pattern = @"e{2}\w\b";
const string Input = "needing a reed";

Regex r = RegexHelpers.GetRegexAsync(engine, Pattern).GetAwaiter().GetResult();
int count = 0;
string[] expectedMatches = ["eed"];
int[] expectedIndex = [11];
ReadOnlySpan<char> span = Input.AsSpan();
foreach (ValueMatch match in r.EnumerateMatches(span))
Test("needing a reed",
await RegexHelpers.GetRegexAsync(engine, @"e{2}\w\b"),
["eed"],
[11]);

static void Test(string input, Regex r, string[] expectedMatches, int[] expectedIndex)
{
Assert.Equal(expectedMatches[count], span.Slice(match.Index, match.Length).ToString());
Assert.Equal(expectedIndex[count++], match.Index);
ReadOnlySpan<char> span = input.AsSpan();

int count = 0;
foreach (ValueMatch match in r.EnumerateMatches(span))
{
Assert.Equal(expectedMatches[count], span.Slice(match.Index, match.Length).ToString());
Assert.Equal(expectedIndex[count], match.Index);

count++;
}
}
}
}
Expand All @@ -111,91 +125,102 @@ public partial class RegexMultipleMatchTests
{
[Theory]
[MemberData(nameof(Matches_TestData))]
public void EnumerateMatches(RegexEngine engine, string pattern, string input, RegexOptions options, CaptureData[] expected)
public async Task EnumerateMatches(RegexEngine engine, string pattern, string input, RegexOptions options, CaptureData[] expected)
{
Regex regexAdvanced = RegexHelpers.GetRegexAsync(engine, pattern, options).GetAwaiter().GetResult();
int count = 0;
ReadOnlySpan<char> span = input.AsSpan();
foreach (ValueMatch match in regexAdvanced.EnumerateMatches(span))
Test(input, expected, await RegexHelpers.GetRegexAsync(engine, pattern, options));

static void Test(string input, CaptureData[] expected, Regex regexAdvanced)
{
Assert.Equal(expected[count].Index, match.Index);
Assert.Equal(expected[count].Length, match.Length);
Assert.Equal(expected[count].Value, span.Slice(match.Index, match.Length).ToString());
count++;
int count = 0;
ReadOnlySpan<char> span = input.AsSpan();
foreach (ValueMatch match in regexAdvanced.EnumerateMatches(span))
{
Assert.Equal(expected[count].Index, match.Index);
Assert.Equal(expected[count].Length, match.Length);
Assert.Equal(expected[count].Value, span.Slice(match.Index, match.Length).ToString());
count++;
}

Assert.Equal(expected.Length, count);
}
Assert.Equal(expected.Length, count);
}
}

public partial class RegexMatchTests
{
[Theory]
[MemberData(nameof(Match_Count_TestData))]
public void EnumerateMatches_Count(RegexEngine engine, string pattern, string input, int expectedCount)
public async Task EnumerateMatches_Count(RegexEngine engine, string pattern, string input, int expectedCount)
{
Regex r = RegexHelpers.GetRegexAsync(engine, pattern).GetAwaiter().GetResult();
int count = 0;
foreach (ValueMatch _ in r.EnumerateMatches(input))
Test(input, expectedCount, await RegexHelpers.GetRegexAsync(engine, pattern));

static void Test(string input, int expectedCount, Regex r)
{
count++;
int count = 0;
foreach (ValueMatch _ in r.EnumerateMatches(input))
{
count++;
}

Assert.Equal(expectedCount, count);
}
Assert.Equal(expectedCount, count);
}
}

public partial class RegexCountTests
{
[Theory]
[MemberData(nameof(Count_ReturnsExpectedCount_TestData))]
public void EnumerateMatches_ReturnsExpectedCount(RegexEngine engine, string pattern, string input, int startat, RegexOptions options, int expectedCount)
public async Task EnumerateMatches_ReturnsExpectedCount(RegexEngine engine, string pattern, string input, int startat, RegexOptions options, int expectedCount)
{
Regex r = RegexHelpers.GetRegexAsync(engine, pattern, options).GetAwaiter().GetResult();

int count;

count = 0;
foreach (ValueMatch _ in r.EnumerateMatches(input, startat))
{
count++;
}
Assert.Equal(expectedCount, count);

bool isDefaultStartAt = startat == ((options & RegexOptions.RightToLeft) != 0 ? input.Length : 0);
if (!isDefaultStartAt)
{
return;
}
Test(engine, pattern, input, startat, options, expectedCount, await RegexHelpers.GetRegexAsync(engine, pattern, options));

if (options == RegexOptions.None && engine == RegexEngine.Interpreter)
static void Test(RegexEngine engine, string pattern, string input, int startat, RegexOptions options, int expectedCount, Regex r)
{
count = 0;
foreach (ValueMatch _ in Regex.EnumerateMatches(input, pattern))
int count = 0;
foreach (ValueMatch _ in r.EnumerateMatches(input, startat))
{
count++;
}
Assert.Equal(expectedCount, count);
}

switch (engine)
{
case RegexEngine.Interpreter:
case RegexEngine.Compiled:
case RegexEngine.NonBacktracking:
RegexOptions engineOptions = RegexHelpers.OptionsFromEngine(engine);
count = 0;
foreach (ValueMatch _ in Regex.EnumerateMatches(input, pattern, options | engineOptions))
{
count++;
}
Assert.Equal(expectedCount, count);
bool isDefaultStartAt = startat == ((options & RegexOptions.RightToLeft) != 0 ? input.Length : 0);
if (!isDefaultStartAt)
{
return;
}

if (options == RegexOptions.None && engine == RegexEngine.Interpreter)
{
count = 0;
foreach (ValueMatch _ in Regex.EnumerateMatches(input, pattern, options | engineOptions, Regex.InfiniteMatchTimeout))
foreach (ValueMatch _ in Regex.EnumerateMatches(input, pattern))
{
count++;
}
Assert.Equal(expectedCount, count);
break;
}

switch (engine)
{
case RegexEngine.Interpreter:
case RegexEngine.Compiled:
case RegexEngine.NonBacktracking:
RegexOptions engineOptions = RegexHelpers.OptionsFromEngine(engine);
count = 0;
foreach (ValueMatch _ in Regex.EnumerateMatches(input, pattern, options | engineOptions))
{
count++;
}
Assert.Equal(expectedCount, count);

count = 0;
foreach (ValueMatch _ in Regex.EnumerateMatches(input, pattern, options | engineOptions, Regex.InfiniteMatchTimeout))
{
count++;
}
Assert.Equal(expectedCount, count);
break;
}
}
}
}
Expand Down

0 comments on commit 0c0e6fb

Please sign in to comment.