diff --git a/src/libraries/System.Text.RegularExpressions/System.Text.RegularExpressions.sln b/src/libraries/System.Text.RegularExpressions/System.Text.RegularExpressions.sln index 2bde91c3efe702..188f97b70acf5a 100644 --- a/src/libraries/System.Text.RegularExpressions/System.Text.RegularExpressions.sln +++ b/src/libraries/System.Text.RegularExpressions/System.Text.RegularExpressions.sln @@ -11,9 +11,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.RegularExpressi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.RegularExpressions", "src\System.Text.RegularExpressions.csproj", "{0409C086-D7CC-43F8-9762-C94FB1E47F5B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.RegularExpressions.Generators.Tests", "tests\System.Text.RegularExpressions.Generators.Tests\System.Text.RegularExpressions.Generators.Tests.csproj", "{32ABFCDA-10FD-4A98-A429-145C28021EBE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.RegularExpressions.Tests", "tests\FunctionalTests\System.Text.RegularExpressions.Tests.csproj", "{8EE1A7C4-3630-4900-8976-9B3ADAFF10DC}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.RegularExpressions.Tests", "tests\System.Text.RegularExpressions.Tests.csproj", "{8EE1A7C4-3630-4900-8976-9B3ADAFF10DC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.RegularExpressions.UnitTests", "tests\UnitTests\System.Text.RegularExpressions.UnitTests.csproj", "{A86931EC-34DC-40A8-BD8C-F5E13BDBA903}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{2ACCCAAB-F0CE-4839-82BD-F174861DEA78}" EndProject @@ -53,22 +53,22 @@ Global {0409C086-D7CC-43F8-9762-C94FB1E47F5B}.Debug|Any CPU.Build.0 = Debug|Any CPU {0409C086-D7CC-43F8-9762-C94FB1E47F5B}.Release|Any CPU.ActiveCfg = Release|Any CPU {0409C086-D7CC-43F8-9762-C94FB1E47F5B}.Release|Any CPU.Build.0 = Release|Any CPU - {32ABFCDA-10FD-4A98-A429-145C28021EBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {32ABFCDA-10FD-4A98-A429-145C28021EBE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {32ABFCDA-10FD-4A98-A429-145C28021EBE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {32ABFCDA-10FD-4A98-A429-145C28021EBE}.Release|Any CPU.Build.0 = Release|Any CPU {8EE1A7C4-3630-4900-8976-9B3ADAFF10DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8EE1A7C4-3630-4900-8976-9B3ADAFF10DC}.Debug|Any CPU.Build.0 = Debug|Any CPU {8EE1A7C4-3630-4900-8976-9B3ADAFF10DC}.Release|Any CPU.ActiveCfg = Release|Any CPU {8EE1A7C4-3630-4900-8976-9B3ADAFF10DC}.Release|Any CPU.Build.0 = Release|Any CPU + {A86931EC-34DC-40A8-BD8C-F5E13BDBA903}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A86931EC-34DC-40A8-BD8C-F5E13BDBA903}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A86931EC-34DC-40A8-BD8C-F5E13BDBA903}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A86931EC-34DC-40A8-BD8C-F5E13BDBA903}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {63551298-BFD4-43FC-8465-AC454228B83C} = {2ACCCAAB-F0CE-4839-82BD-F174861DEA78} - {32ABFCDA-10FD-4A98-A429-145C28021EBE} = {2ACCCAAB-F0CE-4839-82BD-F174861DEA78} {8EE1A7C4-3630-4900-8976-9B3ADAFF10DC} = {2ACCCAAB-F0CE-4839-82BD-F174861DEA78} + {A86931EC-34DC-40A8-BD8C-F5E13BDBA903} = {2ACCCAAB-F0CE-4839-82BD-F174861DEA78} {08F0E5F4-BBD5-45CC-BB12-BA37A83AD7B6} = {0D20E771-24BD-4F9E-BBD0-60156E8C44FC} {77CDA838-6489-4816-8847-DE2C7F5E1DCE} = {0D20E771-24BD-4F9E-BBD0-60156E8C44FC} {3699C8E2-C354-4AED-81DC-ECBAC3EFEB4B} = {0D20E771-24BD-4F9E-BBD0-60156E8C44FC} diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexFindOptimizations.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexFindOptimizations.cs index 135202e770bc1f..e8d727582de52d 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexFindOptimizations.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexFindOptimizations.cs @@ -127,25 +127,17 @@ public RegexFindOptimizations(RegexTree tree, CultureInfo culture) // The set contains one and only one character, meaning every match starts // with the same literal value (potentially case-insensitive). Search for that. FixedDistanceLiteral = (chars[0], 0); - FindMode = (_rightToLeft, set.CaseInsensitive) switch - { - (false, false) => FindNextStartingPositionMode.FixedLiteral_LeftToRight_CaseSensitive, - (false, true) => FindNextStartingPositionMode.FixedLiteral_LeftToRight_CaseInsensitive, - (true, false) => FindNextStartingPositionMode.LeadingLiteral_RightToLeft_CaseSensitive, - (true, true) => FindNextStartingPositionMode.LeadingLiteral_RightToLeft_CaseInsensitive, - }; + FindMode = set.CaseInsensitive ? + FindNextStartingPositionMode.LeadingLiteral_RightToLeft_CaseInsensitive : + FindNextStartingPositionMode.LeadingLiteral_RightToLeft_CaseSensitive; } else { // The set may match multiple characters. Search for that. FixedDistanceSets = new() { (chars, set.CharClass, 0, set.CaseInsensitive) }; - FindMode = (_rightToLeft, set.CaseInsensitive) switch - { - (false, false) => FindNextStartingPositionMode.LeadingSet_LeftToRight_CaseSensitive, - (false, true) => FindNextStartingPositionMode.LeadingSet_LeftToRight_CaseInsensitive, - (true, false) => FindNextStartingPositionMode.LeadingSet_RightToLeft_CaseSensitive, - (true, true) => FindNextStartingPositionMode.LeadingSet_RightToLeft_CaseInsensitive, - }; + FindMode = set.CaseInsensitive ? + FindNextStartingPositionMode.LeadingSet_RightToLeft_CaseInsensitive : + FindNextStartingPositionMode.LeadingSet_RightToLeft_CaseSensitive; _asciiLookups = new uint[1][]; } } diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexTreeAnalyzer.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexTreeAnalyzer.cs index 7845cf455b9745..990456b3aff89c 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexTreeAnalyzer.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexTreeAnalyzer.cs @@ -13,17 +13,17 @@ internal static class RegexTreeAnalyzer public static AnalysisResults Analyze(RegexCode code) { var results = new AnalysisResults(code); - results._complete = TryAnalyze(code.Tree.Root, results, isAtomicBySelfOrParent: true); + results._complete = TryAnalyze(code.Tree.Root, results, isAtomicByAncestor: true); return results; - static bool TryAnalyze(RegexNode node, AnalysisResults results, bool isAtomicBySelfOrParent) + static bool TryAnalyze(RegexNode node, AnalysisResults results, bool isAtomicByAncestor) { if (!StackHelper.TryEnsureSufficientExecutionStack()) { return false; } - if (isAtomicBySelfOrParent) + if (isAtomicByAncestor) { // We've been told by our parent that we should be considered atomic, so add ourselves // to the atomic collection. @@ -45,6 +45,7 @@ static bool TryAnalyze(RegexNode node, AnalysisResults results, bool isAtomicByS } // Update state for certain node types. + bool isAtomicBySelf = false; switch (node.Kind) { // Some node types add atomicity around what they wrap. Set isAtomicBySelfOrParent to true for such nodes @@ -52,7 +53,7 @@ static bool TryAnalyze(RegexNode node, AnalysisResults results, bool isAtomicByS case RegexNodeKind.Atomic: case RegexNodeKind.NegativeLookaround: case RegexNodeKind.PositiveLookaround: - isAtomicBySelfOrParent = true; + isAtomicBySelf = true; break; // Track any nodes that are themselves captures. @@ -70,7 +71,7 @@ static bool TryAnalyze(RegexNode node, AnalysisResults results, bool isAtomicByS // Determine whether the child should be treated as atomic (whether anything // can backtrack into it), which is influenced by whether this node (the child's // parent) is considered atomic by itself or by its parent. - bool treatChildAsAtomic = isAtomicBySelfOrParent && node.Kind switch + bool treatChildAsAtomic = (isAtomicByAncestor | isAtomicBySelf) && node.Kind switch { // If the parent is atomic, so is the child. That's the whole purpose // of the Atomic node, and lookarounds are also implicitly atomic. @@ -104,14 +105,16 @@ static bool TryAnalyze(RegexNode node, AnalysisResults results, bool isAtomicByS } // If the child contains captures, so too does this parent. - if (results.MayContainCapture(child)) + if (results._containsCapture.Contains(child)) { results._containsCapture.Add(node); } // If the child might require backtracking into it, so too might the parent, - // unless the parent is itself considered atomic. - if (!isAtomicBySelfOrParent && results.MayBacktrack(child)) + // unless the parent is itself considered atomic. Here we don't consider parental + // atomicity, as we need to surface upwards to the parent whether any backtracking + // will be visible from this node to it. + if (!isAtomicBySelf && (results._mayBacktrack?.Contains(child) == true)) { (results._mayBacktrack ??= new()).Add(node); } @@ -149,7 +152,7 @@ internal sealed class AnalysisResults /// Gets the code that was analyzed. public RegexCode Code { get; } - /// Gets whether a node is considered atomic based on itself or its ancestry. + /// Gets whether a node is considered atomic based on its ancestry. public bool IsAtomicByAncestor(RegexNode node) => _isAtomicByAncestor.Contains(node); /// Gets whether a node directly or indirectly contains captures. diff --git a/src/libraries/System.Text.RegularExpressions/tests/AttRegexTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/AttRegexTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/AttRegexTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/AttRegexTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/CaptureCollectionTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/CaptureCollectionTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/CaptureCollectionTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/CaptureCollectionTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/CaptureCollectionTests2.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/CaptureCollectionTests2.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/CaptureCollectionTests2.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/CaptureCollectionTests2.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/CustomDerivedRegexScenarioTest.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/CustomDerivedRegexScenarioTest.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/CustomDerivedRegexScenarioTest.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/CustomDerivedRegexScenarioTest.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/GroupCollectionReadOnlyDictionaryTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/GroupCollectionReadOnlyDictionaryTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/GroupCollectionReadOnlyDictionaryTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/GroupCollectionReadOnlyDictionaryTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/GroupCollectionTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/GroupCollectionTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/GroupCollectionTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/GroupCollectionTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/GroupCollectionTests2.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/GroupCollectionTests2.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/GroupCollectionTests2.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/GroupCollectionTests2.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/MatchCollectionTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/MatchCollectionTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/MatchCollectionTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/MatchCollectionTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/MatchCollectionTests2.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/MatchCollectionTests2.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/MatchCollectionTests2.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/MatchCollectionTests2.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/MonoRegexTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/MonoRegexTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/MonoRegexTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/MonoRegexTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/PrecompiledRegexScenarioTest.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/PrecompiledRegexScenarioTest.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/PrecompiledRegexScenarioTest.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/PrecompiledRegexScenarioTest.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.Cache.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Cache.Tests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/Regex.Cache.Tests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Cache.Tests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.CompileToAssembly.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.CompileToAssembly.Tests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/Regex.CompileToAssembly.Tests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.CompileToAssembly.Tests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.Count.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Count.Tests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/Regex.Count.Tests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Count.Tests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.Ctor.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Ctor.Tests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/Regex.Ctor.Tests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Ctor.Tests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.EscapeUnescape.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.EscapeUnescape.Tests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/Regex.EscapeUnescape.Tests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.EscapeUnescape.Tests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.GetGroupNames.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.GetGroupNames.Tests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/Regex.GetGroupNames.Tests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.GetGroupNames.Tests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.Groups.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Groups.Tests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/Regex.Groups.Tests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Groups.Tests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.KnownPattern.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.KnownPattern.Tests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/Regex.KnownPattern.Tests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.KnownPattern.Tests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.Match.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/Regex.Match.Tests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.MultipleMatches.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.MultipleMatches.Tests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/Regex.MultipleMatches.Tests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.MultipleMatches.Tests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.Replace.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Replace.Tests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/Regex.Replace.Tests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Replace.Tests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.Split.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Split.Tests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/Regex.Split.Tests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Split.Tests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.Tests.Common.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Tests.Common.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/Regex.Tests.Common.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Tests.Common.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.UnicodeChar.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.UnicodeChar.Tests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/Regex.UnicodeChar.Tests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.UnicodeChar.Tests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexAssert.netcoreapp.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexAssert.netcoreapp.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexAssert.netcoreapp.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexAssert.netcoreapp.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexAssert.netfx.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexAssert.netfx.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexAssert.netfx.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexAssert.netfx.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexCharacterSetTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexCharacterSetTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexCharacterSetTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexCharacterSetTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexCompilationInfoTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexCompilationInfoTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexCompilationInfoTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexCompilationInfoTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexCultureTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexCultureTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexCultureTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexCultureTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexExperiment.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexExperiment.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexExperiment.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexExperiment.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexGeneratorAttributeTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorAttributeTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexGeneratorAttributeTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorAttributeTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexGeneratorHelper.netcoreapp.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs similarity index 75% rename from src/libraries/System.Text.RegularExpressions/tests/RegexGeneratorHelper.netcoreapp.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs index f4904dc9c73247..c2d30702339e46 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/RegexGeneratorHelper.netcoreapp.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs @@ -24,11 +24,12 @@ namespace System.Text.RegularExpressions.Tests public static class RegexGeneratorHelper { private static readonly CSharpParseOptions s_previewParseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview); - private static readonly MetadataReference[] s_refs = CreateReferences(); private static readonly EmitOptions s_emitOptions = new EmitOptions(debugInformationFormat: DebugInformationFormat.Embedded); private static readonly CSharpGeneratorDriver s_generatorDriver = CSharpGeneratorDriver.Create(new[] { new RegexGenerator().AsSourceGenerator() }, parseOptions: s_previewParseOptions); private static Compilation? s_compilation; + internal static MetadataReference[] References { get; } = CreateReferences(); + private static MetadataReference[] CreateReferences() { if (PlatformDetection.IsBrowser) @@ -49,6 +50,61 @@ private static MetadataReference[] CreateReferences() }; } + internal static byte[] CreateAssemblyImage(string source, string assemblyName) + { + CSharpCompilation compilation = CSharpCompilation.Create( + assemblyName, + new[] { CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview)) }, + References, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + var ms = new MemoryStream(); + if (compilation.Emit(ms).Success) + { + return ms.ToArray(); + } + + throw new InvalidOperationException(); + } + + internal static async Task> RunGenerator( + string code, bool compile = false, LanguageVersion langVersion = LanguageVersion.Preview, MetadataReference[]? additionalRefs = null, bool allowUnsafe = false, CancellationToken cancellationToken = default) + { + var proj = new AdhocWorkspace() + .AddSolution(SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create())) + .AddProject("RegexGeneratorTest", "RegexGeneratorTest.dll", "C#") + .WithMetadataReferences(additionalRefs is not null ? References.Concat(additionalRefs) : References) + .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: allowUnsafe) + .WithNullableContextOptions(NullableContextOptions.Enable)) + .WithParseOptions(new CSharpParseOptions(langVersion)) + .AddDocument("RegexGenerator.g.cs", SourceText.From(code, Encoding.UTF8)).Project; + + Assert.True(proj.Solution.Workspace.TryApplyChanges(proj.Solution)); + + Compilation? comp = await proj!.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false); + Debug.Assert(comp is not null); + + var generator = new RegexGenerator(); + CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { generator.AsSourceGenerator() }, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(langVersion)); + GeneratorDriver gd = cgd.RunGenerators(comp!, cancellationToken); + GeneratorDriverRunResult generatorResults = gd.GetRunResult(); + if (!compile) + { + return generatorResults.Diagnostics; + } + + comp = comp.AddSyntaxTrees(generatorResults.GeneratedTrees.ToArray()); + EmitResult results = comp.Emit(Stream.Null, cancellationToken: cancellationToken); + if (!results.Success || results.Diagnostics.Length != 0 || generatorResults.Diagnostics.Length != 0) + { + throw new ArgumentException( + string.Join(Environment.NewLine, results.Diagnostics.Concat(generatorResults.Diagnostics)) + Environment.NewLine + + string.Join(Environment.NewLine, generatorResults.GeneratedTrees.Select(t => t.ToString()))); + } + + return generatorResults.Diagnostics.Concat(results.Diagnostics).Where(d => d.Severity != DiagnosticSeverity.Hidden).ToArray(); + } + internal static async Task SourceGenRegexAsync( string pattern, RegexOptions? options = null, TimeSpan? matchTimeout = null, CancellationToken cancellationToken = default) { @@ -109,7 +165,7 @@ internal static async Task SourceGenRegexAsync( var proj = new AdhocWorkspace() .AddSolution(SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create())) .AddProject("Test", "test.dll", "C#") - .WithMetadataReferences(s_refs) + .WithMetadataReferences(References) .WithCompilationOptions( new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary, diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexGeneratorHelper.netfx.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netfx.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexGeneratorHelper.netfx.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netfx.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/System.Text.RegularExpressions.Generators.Tests/RegexGeneratorParserTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs similarity index 76% rename from src/libraries/System.Text.RegularExpressions/tests/System.Text.RegularExpressions.Generators.Tests/RegexGeneratorParserTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs index d78f6801341be6..c84f2455c8c689 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/System.Text.RegularExpressions.Generators.Tests/RegexGeneratorParserTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs @@ -3,19 +3,12 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Emit; -using Microsoft.CodeAnalysis.Text; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; using System.Threading.Tasks; using Xunit; -namespace System.Text.RegularExpressions.Generator.Tests +namespace System.Text.RegularExpressions.Tests { // Tests don't actually use reflection emit, but they do generate assembly via Roslyn in-memory at run time and expect it to be JIT'd. // The tests also use typeof(object).Assembly.Location, which returns an empty string on wasm. @@ -25,7 +18,7 @@ public class RegexGeneratorParserTests [Fact] public async Task Diagnostic_MultipleAttributes() { - IReadOnlyList diagnostics = await RunGenerator(@" + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class C { @@ -62,7 +55,7 @@ public async Task Diagnostic_MalformedCtor(string attribute) { // Validate the generator doesn't crash with an incomplete attribute - IReadOnlyList diagnostics = await RunGenerator($@" + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator($@" using System.Text.RegularExpressions; partial class C {{ @@ -82,7 +75,7 @@ partial class C [InlineData("\"ab[]\"")] public async Task Diagnostic_InvalidRegexPattern(string pattern) { - IReadOnlyList diagnostics = await RunGenerator($@" + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator($@" using System.Text.RegularExpressions; partial class C {{ @@ -98,7 +91,7 @@ partial class C [InlineData(0x800)] public async Task Diagnostic_InvalidRegexOptions(int options) { - IReadOnlyList diagnostics = await RunGenerator(@$" + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@$" using System.Text.RegularExpressions; partial class C {{ @@ -115,7 +108,7 @@ partial class C [InlineData(0)] public async Task Diagnostic_InvalidRegexTimeout(int matchTimeout) { - IReadOnlyList diagnostics = await RunGenerator(@$" + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@$" using System.Text.RegularExpressions; partial class C {{ @@ -130,7 +123,7 @@ partial class C [Fact] public async Task Diagnostic_MethodMustReturnRegex() { - IReadOnlyList diagnostics = await RunGenerator(@" + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class C { @@ -145,7 +138,7 @@ partial class C [Fact] public async Task Diagnostic_MethodMustNotBeGeneric() { - IReadOnlyList diagnostics = await RunGenerator(@" + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class C { @@ -160,7 +153,7 @@ partial class C [Fact] public async Task Diagnostic_MethodMustBeParameterless() { - IReadOnlyList diagnostics = await RunGenerator(@" + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class C { @@ -175,7 +168,7 @@ partial class C [Fact] public async Task Diagnostic_MethodMustBePartial() { - IReadOnlyList diagnostics = await RunGenerator(@" + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class C { @@ -191,7 +184,7 @@ partial class C [Fact] public async Task Diagnostic_InvalidLangVersion() { - IReadOnlyList diagnostics = await RunGenerator(@" + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class C { @@ -206,7 +199,7 @@ partial class C [Fact] public async Task Diagnostic_RightToLeft_LimitedSupport() { - IReadOnlyList diagnostics = await RunGenerator(@" + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class C { @@ -221,7 +214,7 @@ partial class C [Fact] public async Task Diagnostic_NonBacktracking_LimitedSupport() { - IReadOnlyList diagnostics = await RunGenerator(@" + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class C { @@ -236,7 +229,7 @@ partial class C [Fact] public async Task Diagnostic_PositiveLookbehind_LimitedSupport() { - IReadOnlyList diagnostics = await RunGenerator(@" + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class C { @@ -251,7 +244,7 @@ partial class C [Fact] public async Task Diagnostic_NegativeLookbehind_LimitedSupport() { - IReadOnlyList diagnostics = await RunGenerator(@" + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class C { @@ -266,7 +259,7 @@ partial class C [Fact] public async Task Valid_ClassWithoutNamespace() { - Assert.Empty(await RunGenerator(@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class C { @@ -282,7 +275,7 @@ partial class C [InlineData("RegexOptions.IgnoreCase | RegexOptions.CultureInvariant")] public async Task Valid_PatternOptions(string options) { - Assert.Empty(await RunGenerator($@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator($@" using System.Text.RegularExpressions; partial class C {{ @@ -298,7 +291,7 @@ partial class C [InlineData("1_000")] public async Task Valid_PatternOptionsTimeout(string timeout) { - Assert.Empty(await RunGenerator($@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator($@" using System.Text.RegularExpressions; partial class C {{ @@ -311,7 +304,7 @@ partial class C [Fact] public async Task Valid_NamedArguments() { - Assert.Empty(await RunGenerator($@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator($@" using System.Text.RegularExpressions; partial class C {{ @@ -324,7 +317,7 @@ partial class C [Fact] public async Task Valid_ReorderedNamedArguments() { - Assert.Empty(await RunGenerator($@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator($@" using System.Text.RegularExpressions; partial class C {{ @@ -342,7 +335,7 @@ partial class C [InlineData(true)] public async Task Valid_ClassWithNamespace(bool allowUnsafe) { - Assert.Empty(await RunGenerator(@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; namespace A { @@ -358,7 +351,7 @@ partial class C [Fact] public async Task Valid_ClassWithFileScopedNamespace() { - Assert.Empty(await RunGenerator(@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; namespace A; partial class C @@ -372,7 +365,7 @@ partial class C [Fact] public async Task Valid_ClassWithNestedNamespaces() { - Assert.Empty(await RunGenerator(@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; namespace A { @@ -391,7 +384,7 @@ partial class C [Fact] public async Task Valid_NestedClassWithoutNamespace() { - Assert.Empty(await RunGenerator(@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class B { @@ -407,7 +400,7 @@ partial class C [Fact] public async Task Valid_NestedClassWithNamespace() { - Assert.Empty(await RunGenerator(@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; namespace A { @@ -426,7 +419,7 @@ partial class C [Fact] public async Task Valid_NestedClassWithFileScopedNamespace() { - Assert.Empty(await RunGenerator(@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; namespace A; partial class B @@ -443,7 +436,7 @@ partial class C [Fact] public async Task Valid_NestedClassesWithNamespace() { - Assert.Empty(await RunGenerator(@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; namespace A { @@ -471,7 +464,7 @@ private partial class F [Fact] public async Task Valid_NullableRegex() { - Assert.Empty(await RunGenerator(@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" #nullable enable using System.Text.RegularExpressions; partial class C @@ -485,7 +478,7 @@ partial class C [Fact] public async Task Valid_ClassWithGenericConstraints() { - Assert.Empty(await RunGenerator(@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" using D; using System.Text.RegularExpressions; namespace A @@ -539,7 +532,7 @@ public static IEnumerable Valid_Modifiers_MemberData() [MemberData(nameof(Valid_Modifiers_MemberData))] public async Task Valid_Modifiers(string type, string typeModifier, bool instance, string methodVisibility) { - Assert.Empty(await RunGenerator(@$" + Assert.Empty(await RegexGeneratorHelper.RunGenerator(@$" using System.Text.RegularExpressions; {typeModifier} partial {type} C {{ @@ -552,7 +545,7 @@ public async Task Valid_Modifiers(string type, string typeModifier, bool instanc [Fact] public async Task Valid_MultiplRegexMethodsPerClass() { - Assert.Empty(await RunGenerator(@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class C1 { @@ -579,7 +572,7 @@ partial class C2 [Fact] public async Task Valid_NestedVaryingTypes() { - Assert.Empty(await RunGenerator(@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; public partial class A { @@ -604,7 +597,7 @@ public partial struct E [Fact] public async Task MultipleTypeDefinitions_DoesntBreakGeneration() { - byte[] referencedAssembly = CreateAssemblyImage(@" + byte[] referencedAssembly = RegexGeneratorHelper.CreateAssemblyImage(@" namespace System.Text.RegularExpressions; [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] @@ -613,7 +606,7 @@ internal sealed class RegexGeneratorAttribute : Attribute public RegexGeneratorAttribute(string pattern){} }", "TestAssembly"); - Assert.Empty(await RunGenerator(@" + Assert.Empty(await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class C { @@ -621,80 +614,5 @@ partial class C private static partial Regex Valid(); }", compile: true, additionalRefs: new[] { MetadataReference.CreateFromImage(referencedAssembly) })); } - - private async Task> RunGenerator( - string code, bool compile = false, LanguageVersion langVersion = LanguageVersion.Preview, MetadataReference[]? additionalRefs = null, bool allowUnsafe = false, CancellationToken cancellationToken = default) - { - var proj = new AdhocWorkspace() - .AddSolution(SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create())) - .AddProject("RegexGeneratorTest", "RegexGeneratorTest.dll", "C#") - .WithMetadataReferences(additionalRefs is not null ? s_refs.Concat(additionalRefs) : s_refs) - .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: allowUnsafe) - .WithNullableContextOptions(NullableContextOptions.Enable)) - .WithParseOptions(new CSharpParseOptions(langVersion)) - .AddDocument("RegexGenerator.g.cs", SourceText.From(code, Encoding.UTF8)).Project; - - Assert.True(proj.Solution.Workspace.TryApplyChanges(proj.Solution)); - - Compilation? comp = await proj!.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false); - Debug.Assert(comp is not null); - - var generator = new RegexGenerator(); - CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { generator.AsSourceGenerator() }, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(langVersion)); - GeneratorDriver gd = cgd.RunGenerators(comp!, cancellationToken); - GeneratorDriverRunResult generatorResults = gd.GetRunResult(); - if (!compile) - { - return generatorResults.Diagnostics; - } - - comp = comp.AddSyntaxTrees(generatorResults.GeneratedTrees.ToArray()); - EmitResult results = comp.Emit(Stream.Null, cancellationToken: cancellationToken); - if (!results.Success || results.Diagnostics.Length != 0 || generatorResults.Diagnostics.Length != 0) - { - throw new ArgumentException( - string.Join(Environment.NewLine, results.Diagnostics.Concat(generatorResults.Diagnostics)) + Environment.NewLine + - string.Join(Environment.NewLine, generatorResults.GeneratedTrees.Select(t => t.ToString()))); - } - - return generatorResults.Diagnostics.Concat(results.Diagnostics).Where(d => d.Severity != DiagnosticSeverity.Hidden).ToArray(); - } - - private static byte[] CreateAssemblyImage(string source, string assemblyName) - { - CSharpCompilation compilation = CSharpCompilation.Create( - assemblyName, - new[] { CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview)) }, - s_refs.ToArray(), - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - - var ms = new MemoryStream(); - if (compilation.Emit(ms).Success) - { - return ms.ToArray(); - } - - throw new InvalidOperationException(); - } - - private static readonly MetadataReference[] s_refs = CreateReferences(); - - private static MetadataReference[] CreateReferences() - { - if (PlatformDetection.IsBrowser) - { - // These tests that use Roslyn don't work well on browser wasm today - return new MetadataReference[0]; - } - - string corelibPath = typeof(object).Assembly.Location; - return new[] - { - MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(corelibPath)!, "System.Runtime.dll")), - MetadataReference.CreateFromFile(typeof(Unsafe).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Regex).Assembly.Location), - }; - } } } diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexGroupNameTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGroupNameTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexGroupNameTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGroupNameTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexMatchTimeoutExceptionTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexMatchTimeoutExceptionTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexMatchTimeoutExceptionTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexMatchTimeoutExceptionTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexParserTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexParserTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexParserTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexParserTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexParserTests.netcoreapp.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexParserTests.netcoreapp.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexParserTests.netcoreapp.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexParserTests.netcoreapp.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexParserTests.netfx.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexParserTests.netfx.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexParserTests.netfx.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexParserTests.netfx.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexReductionTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexReductionTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexReductionTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexReductionTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/RegexRunnerTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexRunnerTests.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/RegexRunnerTests.cs rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexRunnerTests.cs diff --git a/src/libraries/System.Text.RegularExpressions/tests/System.Text.RegularExpressions.Tests.csproj b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/System.Text.RegularExpressions.Tests.csproj similarity index 86% rename from src/libraries/System.Text.RegularExpressions/tests/System.Text.RegularExpressions.Tests.csproj rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/System.Text.RegularExpressions.Tests.csproj index 951257ab4b5b6c..38b247a35c2633 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/System.Text.RegularExpressions.Tests.csproj +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/System.Text.RegularExpressions.Tests.csproj @@ -7,6 +7,7 @@ $(NetCoreAppCurrent);net48 true true + true @@ -34,7 +35,7 @@ - + @@ -53,13 +54,13 @@ + - - + diff --git a/src/libraries/System.Text.RegularExpressions/tests/THIRD-PARTY-NOTICES.TXT b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/THIRD-PARTY-NOTICES.TXT similarity index 100% rename from src/libraries/System.Text.RegularExpressions/tests/THIRD-PARTY-NOTICES.TXT rename to src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/THIRD-PARTY-NOTICES.TXT diff --git a/src/libraries/System.Text.RegularExpressions/tests/System.Text.RegularExpressions.Generators.Tests/System.Text.RegularExpressions.Generators.Tests.csproj b/src/libraries/System.Text.RegularExpressions/tests/System.Text.RegularExpressions.Generators.Tests/System.Text.RegularExpressions.Generators.Tests.csproj deleted file mode 100644 index f80ffadc2c994b..00000000000000 --- a/src/libraries/System.Text.RegularExpressions/tests/System.Text.RegularExpressions.Generators.Tests/System.Text.RegularExpressions.Generators.Tests.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - $(NetCoreAppCurrent) - true - true - enable - - $(NoWarn);xUnit2008 - true - - - - - - - - - - - - - diff --git a/src/libraries/System.Text.RegularExpressions/tests/System.Text.RegularExpressions.Generators.Tests/System.Text.RegularExpressions.Generators.Tests.sln b/src/libraries/System.Text.RegularExpressions/tests/System.Text.RegularExpressions.Generators.Tests/System.Text.RegularExpressions.Generators.Tests.sln deleted file mode 100644 index 91fab691d5ff0d..00000000000000 --- a/src/libraries/System.Text.RegularExpressions/tests/System.Text.RegularExpressions.Generators.Tests/System.Text.RegularExpressions.Generators.Tests.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31709.452 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.RegularExpressions.Generators.Tests", "System.Text.RegularExpressions.Generators.Tests.csproj", "{66CC1D41-5724-4E9A-A427-51B592D49453}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.RegularExpressions.Generator", "..\..\gen\System.Text.RegularExpressions.Generator.csproj", "{3047EBB3-BF7B-4019-81D5-88E86D9F89EA}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {66CC1D41-5724-4E9A-A427-51B592D49453}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {66CC1D41-5724-4E9A-A427-51B592D49453}.Debug|Any CPU.Build.0 = Debug|Any CPU - {66CC1D41-5724-4E9A-A427-51B592D49453}.Release|Any CPU.ActiveCfg = Release|Any CPU - {66CC1D41-5724-4E9A-A427-51B592D49453}.Release|Any CPU.Build.0 = Release|Any CPU - {3047EBB3-BF7B-4019-81D5-88E86D9F89EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3047EBB3-BF7B-4019-81D5-88E86D9F89EA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3047EBB3-BF7B-4019-81D5-88E86D9F89EA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3047EBB3-BF7B-4019-81D5-88E86D9F89EA}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {CAB7B6EA-E3DE-4731-9CFB-22F32D326700} - EndGlobalSection -EndGlobal diff --git a/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexFindOptimizationsTests.cs b/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexFindOptimizationsTests.cs new file mode 100644 index 00000000000000..f36bac05061f76 --- /dev/null +++ b/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexFindOptimizationsTests.cs @@ -0,0 +1,137 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using Xunit; + +namespace System.Text.RegularExpressions.Tests +{ + public class RegexFindOptimizationsTests + { + [Theory] + + [InlineData(@"^", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_Beginning)] + [InlineData(@"^hello", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_Beginning)] + [InlineData(@"^hello$", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_Beginning)] + [InlineData(@"^hi|^hello", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_Beginning)] + + [InlineData(@"\G", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_Start)] + [InlineData(@"\Ghello", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_Start)] + [InlineData(@"\Ghello$", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_Start)] + [InlineData(@"\Ghi|\Ghello", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_Start)] + + [InlineData(@"\Z", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_EndZ)] + [InlineData(@"\Zhello", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_EndZ)] + [InlineData(@"\Zhello$", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_EndZ)] + [InlineData(@"\Zhi|\Zhello", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_EndZ)] + + [InlineData(@"\z", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_End)] + [InlineData(@"\zhello", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_End)] + [InlineData(@"\zhello$", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_End)] + [InlineData(@"\zhi|\zhello", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingAnchor_LeftToRight_End)] + + [InlineData(@"^", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_Beginning)] + [InlineData(@"hello^", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_Beginning)] + [InlineData(@"$hello^", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_Beginning)] + [InlineData(@"hi^|hello^", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_Beginning)] + + [InlineData(@"\G", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_Start)] + [InlineData(@"hello\G", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_Start)] + [InlineData(@"$hello\G", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_Start)] + [InlineData(@"hi\G|hello\G", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_Start)] + + [InlineData(@"\Z", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_EndZ)] + [InlineData(@"hello\Z", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_EndZ)] + [InlineData(@"$hello\Z", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_EndZ)] + [InlineData(@"hi\Z|hello\Z", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_EndZ)] + + [InlineData(@"\z", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_End)] + [InlineData(@"hello\z", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_End)] + [InlineData(@"$hello\z", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_End)] + [InlineData(@"hi\z|hello\z", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingAnchor_RightToLeft_End)] + public void LeadingAnchor_LeftToRight(string pattern, RegexOptions options, int expectedMode) + { + Assert.Equal((FindNextStartingPositionMode)expectedMode, ComputeOptimizations(pattern, options).FindMode); + } + + [Theory] + [InlineData(@"abc\z", RegexOptions.None, (int)FindNextStartingPositionMode.TrailingAnchor_FixedLength_LeftToRight_End, 3, (int)RegexNodeKind.End)] + [InlineData(@"abc\Z", RegexOptions.None, (int)FindNextStartingPositionMode.TrailingAnchor_FixedLength_LeftToRight_EndZ, 3, (int)RegexNodeKind.EndZ)] + [InlineData(@"abc$", RegexOptions.None, (int)FindNextStartingPositionMode.TrailingAnchor_FixedLength_LeftToRight_EndZ, 3, (int)RegexNodeKind.EndZ)] + [InlineData(@"a{4,10}$", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingPrefix_LeftToRight_CaseSensitive, 10, (int)RegexNodeKind.EndZ)] + [InlineData(@"(abc|defg){1,2}\z", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingSet_LeftToRight_CaseSensitive, 8, (int)RegexNodeKind.End)] + public void TrailingAnchor(string pattern, RegexOptions options, int expectedMode, int expectedLength, int trailingAnchor) + { + RegexFindOptimizations opts = ComputeOptimizations(pattern, options); + Assert.Equal((FindNextStartingPositionMode)expectedMode, opts.FindMode); + Assert.Equal(expectedLength, opts.MaxPossibleLength); + Assert.Equal((RegexNodeKind)trailingAnchor, opts.TrailingAnchor); + } + + [Theory] + [InlineData(@"ab", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingPrefix_LeftToRight_CaseSensitive, "ab")] + [InlineData(@"ab", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingPrefix_RightToLeft_CaseSensitive, "ab")] + [InlineData(@"(a)(bc)", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingPrefix_LeftToRight_CaseSensitive, "abc")] + [InlineData(@"(a)(bc)", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingPrefix_RightToLeft_CaseSensitive, "bc")] + [InlineData(@"a{10}", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingPrefix_LeftToRight_CaseSensitive, "aaaaaaaaaa")] + [InlineData(@"a{10}", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingPrefix_RightToLeft_CaseSensitive, "aaaaaaaaaa")] + [InlineData(@"(?>a{10,20})", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingPrefix_LeftToRight_CaseSensitive, "aaaaaaaaaa")] + [InlineData(@"(?>a{10,20})", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingPrefix_RightToLeft_CaseSensitive, "aaaaaaaaaa")] + [InlineData(@"a{3,5}?", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingPrefix_LeftToRight_CaseSensitive, "aaa")] + [InlineData(@"a{3,5}?", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingPrefix_RightToLeft_CaseSensitive, "aaa")] + [InlineData(@"ab{5}", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingPrefix_LeftToRight_CaseSensitive, "abbbbb")] + [InlineData(@"ab{5}", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingPrefix_RightToLeft_CaseSensitive, "abbbbb")] + [InlineData(@"ab\w", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingPrefix_LeftToRight_CaseSensitive, "ab")] + [InlineData(@"\wab", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingPrefix_RightToLeft_CaseSensitive, "ab")] + [InlineData(@"(ab){3}", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingPrefix_LeftToRight_CaseSensitive, "ababab")] + [InlineData(@"(ab){3}", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingPrefix_RightToLeft_CaseSensitive, "ab")] + [InlineData(@"(ab){2,4}(de){4,}", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingPrefix_LeftToRight_CaseSensitive, "abab")] + [InlineData(@"(ab){2,4}(de){4,}", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingPrefix_RightToLeft_CaseSensitive, "de")] + [InlineData(@"ab|(abc)|(abcd)", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingPrefix_LeftToRight_CaseSensitive, "ab")] + [InlineData(@"ab|(abc)|(abcd)", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingPrefix_RightToLeft_CaseSensitive, "ab")] + [InlineData(@"ab(?=cd)", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingPrefix_LeftToRight_CaseSensitive, "ab")] + [InlineData(@"ab(?=cd)", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingPrefix_RightToLeft_CaseSensitive, "ab")] + public void LeadingPrefix(string pattern, RegexOptions options, int expectedMode, string expectedPrefix) + { + RegexFindOptimizations opts = ComputeOptimizations(pattern, options); + Assert.Equal((FindNextStartingPositionMode)expectedMode, opts.FindMode); + Assert.Equal(expectedPrefix, opts.LeadingCaseSensitivePrefix); + } + + [Theory] + [InlineData(@"[ab]", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingSet_LeftToRight_CaseSensitive, "ab")] + [InlineData(@"[Aa]", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingSet_LeftToRight_CaseSensitive, "Aa")] + [InlineData(@"a", RegexOptions.IgnoreCase, (int)FindNextStartingPositionMode.LeadingSet_LeftToRight_CaseSensitive, "Aa")] + [InlineData(@"ab|cd|ef|gh", RegexOptions.None, (int)FindNextStartingPositionMode.LeadingSet_LeftToRight_CaseSensitive, "aceg")] + [InlineData(@"[ab]", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingSet_RightToLeft_CaseSensitive, "ab")] + [InlineData(@"[Aa]", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingSet_RightToLeft_CaseSensitive, "Aa")] + [InlineData(@"a", RegexOptions.IgnoreCase | RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingSet_RightToLeft_CaseSensitive, "Aa")] + [InlineData(@"ab|cd|ef|gh", RegexOptions.RightToLeft, (int)FindNextStartingPositionMode.LeadingSet_RightToLeft_CaseSensitive, "bdfh")] + public void LeadingSet(string pattern, RegexOptions options, int expectedMode, string expectedChars) + { + RegexFindOptimizations opts = ComputeOptimizations(pattern, options); + Assert.Equal((FindNextStartingPositionMode)expectedMode, opts.FindMode); + Assert.Equal(1, opts.FixedDistanceSets.Count); + Assert.Equal(0, opts.FixedDistanceSets[0].Distance); + Assert.Equal(expectedChars, new string(opts.FixedDistanceSets[0].Chars)); + } + + [Theory] + [InlineData(@"\d*a", RegexOptions.None, (int)FindNextStartingPositionMode.LiteralAfterLoop_LeftToRight_CaseSensitive, null, 'a')] + [InlineData(@"\d*abc", RegexOptions.None, (int)FindNextStartingPositionMode.LiteralAfterLoop_LeftToRight_CaseSensitive, "abc", 0)] + public void LiteralAfterLoop(string pattern, RegexOptions options, int expectedMode, string? expectedString, char expectedChar) + { + RegexFindOptimizations opts = ComputeOptimizations(pattern, options); + Assert.Equal((FindNextStartingPositionMode)expectedMode, opts.FindMode); + Assert.NotNull(opts.LiteralAfterLoop); + Assert.Equal(expectedString, opts.LiteralAfterLoop.Value.Literal.String); + Assert.Equal(expectedChar, opts.LiteralAfterLoop.Value.Literal.Char); + } + + private static RegexFindOptimizations ComputeOptimizations(string pattern, RegexOptions options) + { + RegexCode code = RegexWriter.Write(RegexParser.Parse(pattern, options, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); + return new RegexFindOptimizations(code.Tree, CultureInfo.InvariantCulture); + } + } +} diff --git a/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexTreeAnalyzerTests.cs b/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexTreeAnalyzerTests.cs new file mode 100644 index 00000000000000..ce6f323d0c1fb2 --- /dev/null +++ b/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexTreeAnalyzerTests.cs @@ -0,0 +1,107 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using Xunit; +using Xunit.Sdk; + +namespace System.Text.RegularExpressions.Tests +{ + public class RegexTreeAnalyzerTests + { + [Fact] + public void SimpleString() + { + (RegexCode code, AnalysisResults analysis) = Analyze("abc"); + + RegexNode rootCapture = AssertNode(analysis, code.Tree.Root, RegexNodeKind.Capture, atomicByAncestor: true, backtracks: false, captures: true); + RegexNode abc = AssertNode(analysis, rootCapture.Child(0), RegexNodeKind.Multi, atomicByAncestor: true, backtracks: false, captures: false); + } + + [Fact] + public void AlternationWithCaptures() + { + (RegexCode code, AnalysisResults analysis) = Analyze("abc|d(e)f|(ghi)"); + + RegexNode rootCapture = AssertNode(analysis, code.Tree.Root, RegexNodeKind.Capture, atomicByAncestor: true, backtracks: false, captures: true); + RegexNode implicitAtomic = AssertNode(analysis, rootCapture.Child(0), RegexNodeKind.Atomic, atomicByAncestor: true, backtracks: false, captures: true); + RegexNode alternation = AssertNode(analysis, implicitAtomic.Child(0), RegexNodeKind.Alternate, atomicByAncestor: true, backtracks: false, captures: true); + + RegexNode abc = AssertNode(analysis, alternation.Child(0), RegexNodeKind.Multi, atomicByAncestor: true, backtracks: false, captures: false); + RegexNode def = AssertNode(analysis, alternation.Child(1), RegexNodeKind.Concatenate, atomicByAncestor: true, backtracks: false, captures: true); + RegexNode ghiCapture = AssertNode(analysis, alternation.Child(2), RegexNodeKind.Capture, atomicByAncestor: true, backtracks: false, captures: true); + + RegexNode d = AssertNode(analysis, def.Child(0), RegexNodeKind.One, atomicByAncestor: false, backtracks: false, captures: false); + RegexNode eCapture = AssertNode(analysis, def.Child(1), RegexNodeKind.Capture, atomicByAncestor: false, backtracks: false, captures: true); + RegexNode f = AssertNode(analysis, def.Child(2), RegexNodeKind.One, atomicByAncestor: true, backtracks: false, captures: false); + + RegexNode e = AssertNode(analysis, eCapture.Child(0), RegexNodeKind.One, atomicByAncestor: false, backtracks: false, captures: false); + + RegexNode ghi = AssertNode(analysis, ghiCapture.Child(0), RegexNodeKind.Multi, atomicByAncestor: true, backtracks: false, captures: false); + } + + [Fact] + public void LoopsReducedWithAutoAtomic() + { + (RegexCode code, AnalysisResults analysis) = Analyze("a*(b*)c*"); + + RegexNode rootCapture = AssertNode(analysis, code.Tree.Root, RegexNodeKind.Capture, atomicByAncestor: true, backtracks: false, captures: true); + RegexNode concat = AssertNode(analysis, rootCapture.Child(0), RegexNodeKind.Concatenate, atomicByAncestor: true, backtracks: false, captures: true); + + RegexNode aStar = AssertNode(analysis, concat.Child(0), RegexNodeKind.Oneloopatomic, atomicByAncestor: false, backtracks: false, captures: false); + RegexNode implicitBumpalong = AssertNode(analysis, concat.Child(1), RegexNodeKind.UpdateBumpalong, atomicByAncestor: false, backtracks: false, captures: false); + RegexNode bStarCapture = AssertNode(analysis, concat.Child(2), RegexNodeKind.Capture, atomicByAncestor: false, backtracks: false, captures: true); + RegexNode cStar = AssertNode(analysis, concat.Child(3), RegexNodeKind.Oneloopatomic, atomicByAncestor: true, backtracks: false, captures: false); + + RegexNode bStar = AssertNode(analysis, bStarCapture.Child(0), RegexNodeKind.Oneloopatomic, atomicByAncestor: false, backtracks: false, captures: false); + } + + [Fact] + public void AtomicGroupAroundBacktracking() + { + (RegexCode code, AnalysisResults analysis) = Analyze("[ab]*(?>[bc]*[cd])[ef]"); + + RegexNode rootCapture = AssertNode(analysis, code.Tree.Root, RegexNodeKind.Capture, atomicByAncestor: true, backtracks: true, captures: true); + RegexNode rootConcat = AssertNode(analysis, rootCapture.Child(0), RegexNodeKind.Concatenate, atomicByAncestor: true, backtracks: true, captures: false); + + RegexNode abStar = AssertNode(analysis, rootConcat.Child(0), RegexNodeKind.Setloop, atomicByAncestor: false, backtracks: true, captures: false); + RegexNode implicitBumpalong = AssertNode(analysis, rootConcat.Child(1), RegexNodeKind.UpdateBumpalong, atomicByAncestor: false, backtracks: false, captures: false); + RegexNode atomic = AssertNode(analysis, rootConcat.Child(2), RegexNodeKind.Atomic, atomicByAncestor: false, backtracks: false, captures: false); + RegexNode ef = AssertNode(analysis, rootConcat.Child(3), RegexNodeKind.Set, atomicByAncestor: true, backtracks: false, captures: false); + + // TODO: fix backtracking for concat + RegexNode atomicConcat = AssertNode(analysis, atomic.Child(0), RegexNodeKind.Concatenate, atomicByAncestor: true, backtracks: true, captures: false); + + RegexNode bcStar = AssertNode(analysis, atomicConcat.Child(0), RegexNodeKind.Setloop, atomicByAncestor: false, backtracks: true, captures: false); + RegexNode cd = AssertNode(analysis, atomicConcat.Child(1), RegexNodeKind.Set, atomicByAncestor: true, backtracks: false, captures: false); + } + + private static (RegexCode Code, AnalysisResults Analysis) Analyze(string pattern) + { + RegexCode code = RegexWriter.Write(RegexParser.Parse(pattern, RegexOptions.None, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); + return (code, RegexTreeAnalyzer.Analyze(code)); + } + + private static RegexNode AssertNode(AnalysisResults analysis, RegexNode node, RegexNodeKind kind, bool atomicByAncestor, bool backtracks, bool captures) + { + Assert.Equal(kind, node.Kind); + + if (atomicByAncestor != analysis.IsAtomicByAncestor(node)) + { + throw new XunitException($"Expected atomicByParent == {atomicByAncestor} for {node.Kind}, got {!atomicByAncestor}"); + } + + if (backtracks != analysis.MayBacktrack(node)) + { + throw new XunitException($"Expected backtracks == {backtracks} for {node.Kind}, got {!backtracks}"); + } + + if (captures != analysis.MayContainCapture(node)) + { + throw new XunitException($"Expected captures == {captures} for {node.Kind}, got {!captures}"); + } + + return node; + } + } +} diff --git a/src/libraries/System.Text.RegularExpressions/tests/UnitTests/Stubs.cs b/src/libraries/System.Text.RegularExpressions/tests/UnitTests/Stubs.cs new file mode 100644 index 00000000000000..70594d4e08bb46 --- /dev/null +++ b/src/libraries/System.Text.RegularExpressions/tests/UnitTests/Stubs.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace System.Text.RegularExpressions +{ + internal sealed class RegexReplacement + { + public RegexReplacement(string rep, RegexNode concat, Hashtable caps) { } + + private const int Specials = 4; + public const int LeftPortion = -1; + public const int RightPortion = -2; + public const int LastGroup = -3; + public const int WholeString = -4; + } +} diff --git a/src/libraries/System.Text.RegularExpressions/tests/UnitTests/System.Text.RegularExpressions.UnitTests.csproj b/src/libraries/System.Text.RegularExpressions/tests/UnitTests/System.Text.RegularExpressions.UnitTests.csproj new file mode 100644 index 00000000000000..a9a79b20404d55 --- /dev/null +++ b/src/libraries/System.Text.RegularExpressions/tests/UnitTests/System.Text.RegularExpressions.UnitTests.csproj @@ -0,0 +1,44 @@ + + + + + $(NoWarn);xUnit2008;SYSLIB0036 + ..\..\src\Resources\Strings.resx + $(NetCoreAppCurrent) + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +