diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 4f9d42b..4ea7055 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -26,4 +26,4 @@ jobs: - name: Build run: dotnet build --no-restore - name: Test - run: dotnet test --no-build --verbosity normal + run: dotnet test --no-build --verbosity normal --filter FullyQualifiedName!~SpanExtensions.Tests.Fuzzing diff --git a/SpanExtensions.sln b/SpanExtensions.sln index f44a04a..3b36ef6 100644 --- a/SpanExtensions.sln +++ b/SpanExtensions.sln @@ -5,6 +5,10 @@ VisualStudioVersion = 17.5.33627.172 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpanExtensions", "src\SpanExtensions.csproj", "{75DE5AFD-663E-415D-9B95-6BC513BD4A07}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "tests\unit-tests\UnitTests.csproj", "{B48A0293-A7FF-4E39-8F8D-57B6F824D70F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FuzzingTests", "tests\fuzzing\FuzzingTests.csproj", "{63CA0F05-0019-4ED3-AD94-45A5CE4D338F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +19,14 @@ Global {75DE5AFD-663E-415D-9B95-6BC513BD4A07}.Debug|Any CPU.Build.0 = Debug|Any CPU {75DE5AFD-663E-415D-9B95-6BC513BD4A07}.Release|Any CPU.ActiveCfg = Release|Any CPU {75DE5AFD-663E-415D-9B95-6BC513BD4A07}.Release|Any CPU.Build.0 = Release|Any CPU + {B48A0293-A7FF-4E39-8F8D-57B6F824D70F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B48A0293-A7FF-4E39-8F8D-57B6F824D70F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B48A0293-A7FF-4E39-8F8D-57B6F824D70F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B48A0293-A7FF-4E39-8F8D-57B6F824D70F}.Release|Any CPU.Build.0 = Release|Any CPU + {63CA0F05-0019-4ED3-AD94-45A5CE4D338F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63CA0F05-0019-4ED3-AD94-45A5CE4D338F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63CA0F05-0019-4ED3-AD94-45A5CE4D338F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63CA0F05-0019-4ED3-AD94-45A5CE4D338F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Enumerators/Split/SpanSplitWithCountEnumerator.cs b/src/Enumerators/Split/SpanSplitWithCountEnumerator.cs index e65a913..203ba5a 100644 --- a/src/Enumerators/Split/SpanSplitWithCountEnumerator.cs +++ b/src/Enumerators/Split/SpanSplitWithCountEnumerator.cs @@ -6,12 +6,12 @@ namespace SpanExtensions.Enumerators /// Supports iteration over a by splitting it a a specified delimiter of type with an upper limit of splits performed. /// /// The type of elements in the enumerated . - public ref struct SpanSplitWithCountEnumerator where T : IEquatable + public ref struct SpanSplitWithCountEnumerator where T : IEquatable { ReadOnlySpan Span; readonly T Delimiter; readonly int Count; - readonly CountExceedingBehaviour CountExceedingBehaviour; + readonly CountExceedingBehaviour CountExceedingBehaviour; int currentCount; bool enumerationDone; readonly int CountMinusOne; @@ -68,18 +68,18 @@ public bool MoveNext() switch(CountExceedingBehaviour) { case CountExceedingBehaviour.CutLastElements: - break; + break; case CountExceedingBehaviour.AppendLastElements: - if(currentCount == CountMinusOne) + if(currentCount == CountMinusOne) { - ReadOnlySpan lower = span[..index]; - ReadOnlySpan upper = span[(index + 1)..]; - Span temp = new T[lower.Length + upper.Length]; - lower.CopyTo(temp[..index]); + ReadOnlySpan lower = span[..index]; + ReadOnlySpan upper = span[(index + 1)..]; + Span temp = new T[lower.Length + upper.Length]; + lower.CopyTo(temp[..index]); upper.CopyTo(temp[index..]); - Current = temp; + Current = temp; currentCount++; - return true; + return true; } break; default: diff --git a/src/Extensions/ReadOnlySpan/ReadOnlySpanExtensions.Linq.cs b/src/Extensions/ReadOnlySpan/ReadOnlySpanExtensions.Linq.cs index e250794..f3247ed 100644 --- a/src/Extensions/ReadOnlySpan/ReadOnlySpanExtensions.Linq.cs +++ b/src/Extensions/ReadOnlySpan/ReadOnlySpanExtensions.Linq.cs @@ -315,9 +315,9 @@ public static ReadOnlySpan Take(this ReadOnlySpan source, int count) public static ReadOnlySpan SkipWhile(this ReadOnlySpan source, Predicate condition) { int count = 0; - while (count < source.Length) + while(count < source.Length) { - T t = source[count]; + T t = source[count]; if(!condition(t)) { return source.Skip(count); diff --git a/src/Extensions/Span/SpanExtensions.Split.cs b/src/Extensions/Span/SpanExtensions.Split.cs index 6a2f427..31df39a 100644 --- a/src/Extensions/Span/SpanExtensions.Split.cs +++ b/src/Extensions/Span/SpanExtensions.Split.cs @@ -56,7 +56,7 @@ public static SpanSplitStringSplitOptionsEnumerator Split(this Span source /// A bitwise combination of the enumeration values that specifies whether to trim results and include empty results. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitStringSplitOptionsWithCountEnumerator Split(this Span source, char delimiter, StringSplitOptions options, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public static SpanSplitStringSplitOptionsWithCountEnumerator Split(this Span source, char delimiter, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) { return new SpanSplitStringSplitOptionsWithCountEnumerator(source, delimiter, count, options, countExceedingBehaviour); } @@ -108,7 +108,7 @@ public static SpanSplitAnyStringSplitOptionsEnumerator SplitAny(this Span /// The maximum number of sub-ReadOnlySpans to split into. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitAnyStringSplitOptionsWithCountEnumerator SplitAny(this Span source, ReadOnlySpan delimiters, StringSplitOptions options, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public static SpanSplitAnyStringSplitOptionsWithCountEnumerator SplitAny(this Span source, ReadOnlySpan delimiters, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) { return new SpanSplitAnyStringSplitOptionsWithCountEnumerator(source, delimiters, count, options, countExceedingBehaviour); } @@ -160,7 +160,7 @@ public static SpanSplitSequenceStringSplitOptionsEnumerator Split(this SpanA bitwise combination of the enumeration values that specifies whether to trim results and include empty results. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitSequenceStringSplitOptionsWithCountEnumerator Split(this Span source, ReadOnlySpan delimiter, StringSplitOptions options, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public static SpanSplitSequenceStringSplitOptionsWithCountEnumerator Split(this Span source, ReadOnlySpan delimiter, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) { return new SpanSplitSequenceStringSplitOptionsWithCountEnumerator(source, delimiter, count, options, countExceedingBehaviour); } diff --git a/src/InvalidCountExceedingBehaviourException.cs b/src/InvalidCountExceedingBehaviourException.cs index 75eed7e..20fa89f 100644 --- a/src/InvalidCountExceedingBehaviourException.cs +++ b/src/InvalidCountExceedingBehaviourException.cs @@ -13,9 +13,9 @@ public class InvalidCountExceedingBehaviourException : Exception /// /// The invalid . public InvalidCountExceedingBehaviourException(CountExceedingBehaviour countExceedingBehaviour) : - base($"CountExceedingBehaviour with ID '{(int) countExceedingBehaviour} is not defined. CountExceedingBehaviour only defines {GetCountExceedingBehaviourNamesListed()}.") + base($"CountExceedingBehaviour with ID '{(int)countExceedingBehaviour} is not defined. CountExceedingBehaviour only defines {GetCountExceedingBehaviourNamesListed()}.") { - + } static string GetCountExceedingBehaviourNamesListed() @@ -24,7 +24,7 @@ static string GetCountExceedingBehaviourNamesListed() #if NET5_0_OR_GREATER countExceedingBehaviourNames = Enum.GetNames(); #else - countExceedingBehaviourNames = (string[]) Enum.GetNames(typeof(CountExceedingBehaviour)); + countExceedingBehaviourNames = (string[])Enum.GetNames(typeof(CountExceedingBehaviour)); #endif switch(countExceedingBehaviourNames.Length) { @@ -33,7 +33,7 @@ static string GetCountExceedingBehaviourNamesListed() case 1: return countExceedingBehaviourNames[0]; default: - string first = countExceedingBehaviourNames[0]; + string first = countExceedingBehaviourNames[0]; string end = string.Join(',', countExceedingBehaviourNames, 1, countExceedingBehaviourNames.Length - 1); return $"{first} and {end}"; } diff --git a/src/SpanExtensions.csproj b/src/SpanExtensions.csproj index 15337b4..951e1e2 100644 --- a/src/SpanExtensions.csproj +++ b/src/SpanExtensions.csproj @@ -2,7 +2,7 @@ net8.0;net7.0;net6.0;net5.0;netstandard2.1 - disable + disable enable True AnyCPU diff --git a/tests/fuzzing/FuzzingTests.csproj b/tests/fuzzing/FuzzingTests.csproj new file mode 100644 index 0000000..f810771 --- /dev/null +++ b/tests/fuzzing/FuzzingTests.csproj @@ -0,0 +1,32 @@ + + + + net8.0;net7.0;net6.0;net5.0 + latest + enable + enable + + false + true + + SpanExtensions.Tests.Fuzzing + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/tests/fuzzing/GlobalUsings.cs b/tests/fuzzing/GlobalUsings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/tests/fuzzing/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/tests/fuzzing/TestHelper.cs b/tests/fuzzing/TestHelper.cs new file mode 100644 index 0000000..76ee164 --- /dev/null +++ b/tests/fuzzing/TestHelper.cs @@ -0,0 +1,812 @@ +using SpanExtensions; +using System.Collections; +using System.Diagnostics; +using System.Globalization; +using System.Text; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static class TestHelper + { + public static readonly Random random = new(); + + /// + /// Generates a sequence of a specified number of random integers that are in the specified range. + /// + /// The number of integers to generate. + /// The inclusive lower bound of the random numbers returned. + /// The exclusive upper bound of the random number returned. must be greater than or equal to . + /// A sequence of random integers. + public static IEnumerable GenerateRandomIntegers(int count, int minValue, int maxValue) + { + for(int i = 0; i < count; i++) + { + yield return random.Next(minValue, maxValue); + } + } + + /// + /// Generates a random string of a specified length. The string is generated from an alphabet of lowercase letters, numbers, and white spaces. + /// + /// The length of the generated string. + /// A random string. + public static string GenerateRandomString(int length) + { + const string alphabet = "abcdefghijklmnopqrstuvwxyz0123456789 "; + + StringBuilder builder = new(length); + for(int i = 0; i < length; i++) + { + int alphabetIndex = random.Next(alphabet.Length); + builder.Append(alphabet[alphabetIndex]); + } + + return builder.ToString(); + } + + /// + /// Get a random element from the specified sequence, or a specified default value if the source is empty. + /// + /// The type of the sequence array. + /// The target sequence. + /// The value to return if is empty. + /// A random element from , or if is empty. + public static T RandomElementOrDefault(this ReadOnlySpan source, T @default = default) where T : struct + { + return !source.IsEmpty ? source[random.Next(source.Length)] : @default; + } + + /// + /// Get a random element from the specified array, or a specified default value if the array is empty. + /// + /// The type of the target array. + /// The target array. + /// The value to return if is empty. + /// A random element from , or if the array is empty. + public static T RandomElementOrDefault(this T[] array, T @default = default) where T : struct + { + return RandomElementOrDefault(array.AsReadOnlySpan(), @default); + } + + /// + /// Get a random element from the specified string, or a specified default character if the string is empty. + /// + /// The target string. + /// The value to return if is empty. + /// A random element from , or if is empty. + public static char RandomElementOrDefault(this string @string, char @default = default) + { + return RandomElementOrDefault(@string.AsSpan(), @default); + } + + /// + /// Get a random element subsequence of the specified length from the specified sequence, + /// or a specified default sequence if the source is empty or shorter than the specified length. + /// + /// The type of the target sequence. + /// The target sequence. + /// The length of the subsequence. + /// The value to return if is empty or shorter than . + /// A random subsequence of length , or if is empty or shorted than that length. + /// If is negative. + /// If the length of did not match . + public static T[] RandomSubsequenceOrDefault(this ReadOnlySpan source, int length, T[]? @default = null) where T : struct + { + if(length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length), "Can't be negative."); + } + if(@default != null && @default.Length != length) + { + throw new ArgumentException("The length must match.", nameof(@default)); + } + + if(source.Length < length) + { + return @default ?? new T[length]; + } + + int startIndex = random.Next(source.Length - length); + return source[startIndex..(startIndex + length)].ToArray(); + } + + /// + /// Get a random element subsequence of the specified length from the specified array, + /// or a specified default sequence if the source is empty or shorter than the specified length. + /// + /// The type of the target array. + /// The target array. + /// The length of the subsequence. + /// The value to return if is empty or shorter than . + /// A random subsequence of length , or if the array is empty or shorted than that length. + /// If is negative. + /// If the length of did not match . + public static T[] RandomSubsequenceOrDefault(this T[] array, int length, T[]? @default = null) where T : struct + { + return RandomSubsequenceOrDefault(array.AsReadOnlySpan(), length, @default); + } + + /// + /// Get a random element subsequence of the specified length from the specified string, + /// or a specified default sequence if the source is empty or shorter than the specified length. + /// + /// The target string. + /// The length of the string. + /// The value to return if is empty or shorter than . + /// A random subsequence of length , or if is empty or shorted than that length. + /// If is negative. + /// If the length of did not match . + public static char[] RandomSubsequenceOrDefault(this string @string, int length, char[]? @default = null) + { + return RandomSubsequenceOrDefault(@string.AsSpan(), length, @default); + } + + /// + /// Replaces a random element in the array with the specified replacement. + /// + /// The type of the target array. + /// The target array. + /// The element to replace with. + /// A copy of with the element at a random position replaced with . + public static T[] ReplaceRandomElement(this T[] source, T replacement) + { + T[] copy = new T[source.Length]; + source.CopyTo(copy, 0); + + if(source.Length != 0) + { + copy[random.Next(source.Length)] = replacement; + } + + return copy; + } + + /// + /// Replaces the element in the specified position with the specified replacement. + /// + /// The type of the target array. + /// The target array. + /// The position of the element to replace with. + /// The element to replace with. + /// A copy of with the element at replaced with . + public static T[] ReplaceAt(this T[] source, int position, T replacement) + { + T[] copy = new T[source.Length]; + source.CopyTo(copy, 0); + copy[position] = replacement; + return copy; + } + + /// + /// An alternative to that multiplies the current element by a multiplier every time. + /// + /// The first element. + /// The maximum the last element can be. + /// The multiplier. + /// If isn't positive, or if or is negative. + public sealed class MultiplierRange(int start, int max, int multiplier) : IEnumerable + { + public IEnumerator GetEnumerator() + { + if(start <= 0) + { + throw new ArgumentOutOfRangeException(nameof(start), "Value must be positive."); + } + if(max < 0) + { + throw new ArgumentOutOfRangeException(nameof(max), "Value can't be negative."); + } + if(multiplier < 0) + { + throw new ArgumentOutOfRangeException(nameof(multiplier), "Value can't be negative."); + } + + static IEnumerator Iterate(int start, int max, int multiplier) + { + for(int i = start; i <= max; i *= multiplier) + { + yield return i; + } + } + + return Iterate(start, max, multiplier); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + /// + /// Iterates over two sequence is order. + /// + /// The type of the elements. + /// The first sequence to iterate. + /// THe second sequence ot iterate. + /// An that iterates over the two sequences in order. + public static IEnumerable And(this IEnumerable first, IEnumerable second) + { + foreach(T item in first) + { + yield return item; + } + foreach(T item in second) + { + yield return item; + } + } + + /// + /// Generates a message to be displayed when an assertion fails containing the necessary information to reproduce the failure. + /// + /// The element type of the source span. + /// The elements inside the source span. + /// The expected results. + /// The actual results. + /// The method that failed an assertion. + /// The argument names and values of the method that failed an asserion. + /// The message to be displayed when assertion fails. + public static string GenerateAssertionMessage(IEnumerable source, IEnumerable> expected, IEnumerable> actual, string method, params (string argName, object? argValue)[] args) + { + static void AppendKeyValues(StringBuilder builder, string key, params (string subkey, object? value)[] keyValues) + { + static IEnumerable Enumerate(IEnumerable enumerable) + { + List list = []; + + IEnumerator enumerator = enumerable.GetEnumerator(); + while(enumerator.MoveNext()) + { + list.Add(enumerator.Current); + } + + return list; + } + + static string ToString(object? obj) + { + return obj switch + { + null => "null", + IEnumerable enumerable => '[' + string.Join(", ", Enumerate(enumerable).Select(x => ToString(x))) + ']', + _ => obj.ToString() + } ?? "null"; + } + + builder.Append(key).AppendLine(":"); + foreach((string subkey, object? value) in keyValues) + { + builder.Append('\t').Append(subkey).Append(": ").AppendLine(ToString(value)); + } + } + + static void AppendNestedEnumerable(StringBuilder builder, string key, IEnumerable> nestedEnumerable) + { + builder.Append(key).AppendLine(": ["); + bool emptyCollection = true; + foreach(IEnumerable enumerable in nestedEnumerable) + { + emptyCollection = false; + builder.Append("\t[").AppendJoin(", ", enumerable).AppendLine("],"); + } + builder.Length -= Environment.NewLine.Length + (!emptyCollection ? 1 : 0); // remove the last endline and comma + builder.AppendLine().AppendLine("]"); + } + + StringBuilder builder = new(); + + builder.AppendLine(); + builder.Append("Runtime Version: ").AppendLine(Environment.Version.ToString()); + builder.Append("Source: [").AppendJoin(", ", source).AppendLine("]"); + builder.Append("Method: ").AppendLine(method); + AppendKeyValues(builder, "Arguments", args); + AppendNestedEnumerable(builder, "Expected", expected); + AppendNestedEnumerable(builder, "Actual", actual); + + return builder.ToString(); + } + + /// + /// Tests whether the specified nested collections are equal. + /// + /// The element type in the compared collections. + /// The first collection to compare. + /// The second collection to compare. + /// if both collections are equal; otherwise . + public static bool SequencesEqual(IEnumerable> first, IEnumerable> second) where T : IEquatable + { + IEnumerator> firstEnumerator = first.GetEnumerator(); + IEnumerator> secondEnumerator = second.GetEnumerator(); + + while(firstEnumerator.MoveNext()) + { + if(!secondEnumerator.MoveNext()) // first is longer + { + return false; + } + + IEnumerator firstSubEnumerator = firstEnumerator.Current.GetEnumerator(); + IEnumerator secondSubEnumerator = secondEnumerator.Current.GetEnumerator(); + + while(firstSubEnumerator.MoveNext()) + { + if(!secondSubEnumerator.MoveNext()) // first is larger + { + return false; + } + + if(!firstSubEnumerator.Current.Equals(secondSubEnumerator.Current)) + { + return false; + } + } + + if(secondSubEnumerator.MoveNext()) // second is larger + { + return false; + } + } + + if(secondEnumerator.MoveNext()) // second is larger + { + return false; + } + + return true; + } + + /// + /// Tests whether calling the specified method on the specified source with the specified arguments results in the expected result. + /// + /// The element type of the source span. + /// The expected results. + /// The actual results. + /// The elements inside the source span. + /// The method that was called. + /// The argument names and values of the method that was called. + public static void AssertMethodResults(IEnumerable> expected, IEnumerable> actual, IEnumerable source, string method, params (string argName, object? argValue)[] args) where T : IEquatable + { + if(!SequencesEqual(expected, actual)) + { + Assert.Fail(GenerateAssertionMessage(source, expected, actual, method, args)); + } + } + + /// + /// Creates a new span over the target array. + /// + /// The array type. + /// The target array. + /// A over . + public static ReadOnlySpan AsReadOnlySpan(this T[] source) + { + return source.AsSpan(); + } + +#if !NET8_0_OR_GREATER + /// Counts the number of times the specified occurs in the . + /// The element type of the span. + /// The span to search. + /// The value for which to search. + /// The number of times was found in the . + public static int Count(this ReadOnlySpan span, T value) where T : IEquatable + { + int count = 0; + foreach(T item in span) + { + if(item.Equals(value)) + { + count++; + } + } + return count; + } + + /// Counts the number of times the specified occurs in the . + /// The element type of the span. + /// The span to search. + /// The value for which to search. + /// The number of times was found in the . + public static int Count(this Span span, T value) where T : IEquatable + { + return Count((ReadOnlySpan)span, value); + } +#endif + + /// Counts the number of times the specified occurs in the . + /// The element type of the array. + /// The array to search. + /// The value for which to search. + /// The number of times was found in the . + public static int Count(this T[] array, T value) where T : IEquatable + { + return array.AsSpan().Count(value); + } + + /// Counts the number of times the specified occurs in the . + /// The string to search. + /// The value for which to search. + /// The number of times was found in the . + public static int Count(this string @string, char value) + { + return @string.AsSpan().Count(value); + } + +#if !NET8_0_OR_GREATER + /// Counts the number of times any of the specified occur in the . + /// The element type of the span. + /// The span to search. + /// The values for which to search. + /// The number of times any of the was found in the . + public static int Count(this ReadOnlySpan span, ReadOnlySpan values) where T : IEquatable + { + int count = 0; + foreach(T item in span) + { + if(values.Contains(item)) + { + count++; + } + } + return count; + } + + /// Counts the number of times any of the specified occur in the . + /// The element type of the span. + /// The span to search. + /// The values for which to search. + /// The number of times any of the was found in the . + public static int Count(this Span span, ReadOnlySpan values) where T : IEquatable + { + return Count((ReadOnlySpan)span, values); + } +#endif + + /// Counts the number of times any of the specified occur in the . + /// The element type of the array. + /// The array to search. + /// The values for which to search. + /// The number of times any of the was found in the . + public static int Count(this T[] array, ReadOnlySpan values) where T : IEquatable + { + return array.AsSpan().Count(values); + } + + /// Counts the number of times any of the specified occur in the . + /// The string to search. + /// The values for which to search. + /// The number of times any of the was found in the . + public static int Count(this string @string, ReadOnlySpan values) + { + return @string.AsSpan().Count(values); + } + + /// + /// Counts the (non-overlaping) occurrences of a subsequence in the target sequence. + /// + /// The element type in the sequence. + /// The target sequence. + /// The subsequence to count. + /// The number of occurrences of in . + public static int CountSubsequence(this ReadOnlySpan sequence, ReadOnlySpan subsequence) where T : IEquatable + { + if(sequence.IsEmpty || subsequence.IsEmpty) + { + return 0; + } + + int count = 0, position; + while((position = sequence.IndexOf(subsequence)) != -1) + { + count++; + sequence = sequence[(position + subsequence.Length)..]; + } + return count; + } + + /// + /// Counts the (non-overlaping) occurrences of a subsequence in the target array. + /// + /// The element type in the array. + /// The target array. + /// The subsequence to count. + /// The number of occurrences of in . + public static int CountSubsequence(this T[] array, ReadOnlySpan subsequence) where T : IEquatable + { + return CountSubsequence(array.AsSpan(), subsequence); + } + + /// + /// Counts the (non-overlaping) occurrences of a subsequence in the target string. + /// + /// The target string. + /// The subsequence to count. + /// The number of occurrences of in . + public static int CountSubsequence(this string @string, ReadOnlySpan subsequence) + { + return CountSubsequence(@string.AsSpan(), subsequence); + } + + /// + /// Returns an indicating that the specified value wasn't handled. + /// + /// The value that wasn't handled. + public static Exception UnhandledCaseException(CountExceedingBehaviour countExceedingBehaviour) + { + return new +#if NET7_0_OR_GREATER + UnreachableException +#else + NotImplementedException +#endif + ($"Unhandled {nameof(CountExceedingBehaviour)} enum value: {countExceedingBehaviour}."); + } + + /// + /// Take up to a specified count of elements from an array. + /// Unlike [..], this doesn't throw an exception when is greater than the length of . + /// + /// The type of the array. + /// The array to cut. + /// The number of elements to take. + /// The cut array. + public static T[] UpTo(this T[] source, int count) + { + return source.Length <= count ? source : source[..count]; + } + + /// + /// Splits a sequence into a maximum number of subsequences based on a specified delimiter. + /// + /// The element type of the sequence. + /// The sequence to be split. + /// A that delimits the subsequences in . + /// The maximum number of splits. If zero, split on every occurence of . + /// Specifies how the elements after count splits should be handled. + /// A sequence of split subsequences. + /// If is negative. + public static IEnumerable> Split(IEnumerable source, T delimiter, int count = int.MaxValue, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) where T : IEquatable + { +#if NET8_0_OR_GREATER + ArgumentOutOfRangeException.ThrowIfNegative(count); +#else + if(count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } +#endif + + if(count != 0) + { + List segment = []; + + foreach(T element in source) + { + if(count == 1 && countExceedingBehaviour == CountExceedingBehaviour.CutRemainingElements && element.Equals(delimiter)) + { + break; + } + + if(count == 1 || !element.Equals(delimiter)) + { + segment.Add(element); + } + else + { + yield return segment; + segment = []; + count--; + } + } + + yield return segment; + } + } + + /// + /// Splits a sequence into a maximum number of subsequences based on specified delimiters. + /// + /// The element type of the sequence. + /// The sequence to be split. + /// A with the instances of that delimit the subsequences in . + /// The maximum number of splits. If zero, split on every occurence of . + /// Specifies how the elements after count splits should be handled. + /// A sequence of split subsequences. + /// If is negative. + public static IEnumerable> SplitAny(IEnumerable source, IEnumerable delimiters, int count = int.MaxValue, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) where T : IEquatable + { +#if NET8_0_OR_GREATER + ArgumentOutOfRangeException.ThrowIfNegative(count); +#else + if(count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } +#endif + + if(count != 0) + { + + List segment = []; + foreach(T element in source) + { + if(count == 1 && countExceedingBehaviour == CountExceedingBehaviour.CutRemainingElements && delimiters.Any(delimiter => element.Equals(delimiter))) + { + break; + } + + if(count == 1 || delimiters.All(delimiter => !element.Equals(delimiter))) + { + segment.Add(element); + } + else + { + yield return segment; + segment = []; + count--; + } + } + + yield return segment; + } + } + + /// + /// Splits a sequence into a maximum number of subsequences based on specified delimiter subsequence. + /// + /// The sequence to be split. + /// A that delimits the subsequences in . + /// The maximum number of splits. If zero, split on every occurence of . + /// Specifies how the elements after count splits should be handled. + /// A sequence of split subsequences. + /// If is negative. + public static IEnumerable> Split(IEnumerable source, IEnumerable delimiter, int count = int.MaxValue, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) where T : IEquatable + { +#if NET8_0_OR_GREATER + ArgumentOutOfRangeException.ThrowIfNegative(count); +#else + if(count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } +#endif + + if(count == 0) + { + return []; + } + + int delimiterLength = delimiter.Count(); + if(delimiterLength == 0) + { + return [source]; + } + else if(delimiterLength == 1) + { + return Split(source, delimiter.First(), count, countExceedingBehaviour); + } + + string _source = '{' + string.Join("},{", source) + '}'; + string _delimiter = '{' + string.Join("},{", delimiter) + '}'; + + string[] _splits = countExceedingBehaviour switch + { + CountExceedingBehaviour.AppendRemainingElements => _source.Split(_delimiter, count, StringSplitOptions.None), + CountExceedingBehaviour.CutRemainingElements => _source.Split(_delimiter, StringSplitOptions.None).UpTo(count), + _ => throw UnhandledCaseException(countExceedingBehaviour) + }; + + IEnumerable> splits = _splits.Select(s => s.Trim(',').Split(',').Where(x => x.Length != 0).Select(x => x[1..^1]).Where(x => x.Length != 0)); + + return source switch + { + IEnumerable => (IEnumerable>)splits.Select(s => s.Select(x => int.Parse(x, CultureInfo.InvariantCulture))), + IEnumerable => (IEnumerable>)splits.Select(s => s.Select(x => x[0])), + _ => throw new NotImplementedException($"Type {typeof(T)} was not implemented.") + }; + } + + /// + /// Splits a string into a maximum number of substrings based on a specified delimiting string and, optionally, options. + /// + /// The type of the separator. The only supported types: , , []. + /// The string to be split. + /// A char/string/cahr[] that delimits the substrings in this instance. + /// The maximum number of elements expected in the array. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// Specifies how the elements after count splits should be handled. + /// An array that contains at most substrings from this instance that are delimited by . + /// + public static string[] Split(this string source, T separator, int count = int.MaxValue, StringSplitOptions options = StringSplitOptions.None, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) + { +#if NET8_0_OR_GREATER + ArgumentOutOfRangeException.ThrowIfNegative(count); +#else + if(count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } +#endif + + if(count == 0) + { + return []; + } + + // When count is 1 and RemoveEmptyEntries option is set, it's a special case where splits shouldn't be recursively removed. + // Since string.Split doesn't have the CutRemainingElements option, we have to manually handle this. + static string[] FirstSubstring(string source, TSame separator, StringSplitOptions options = StringSplitOptions.None) + { + string first = separator switch + { + char charSeparator => source.Split(charSeparator)[0], + string stringSeparator => source.Split(stringSeparator)[0], + char[] charSeparators => source.Split(charSeparators)[0], + _ => throw new NotSupportedException($"Invalid separator type: {typeof(T)}") + }; + +#if NET5_0_OR_GREATER + if(options.HasFlag(StringSplitOptions.TrimEntries)) + { + first = first.Trim(); + } +#endif + + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries) && string.IsNullOrEmpty(first)) + { + return []; + } + + return [first]; + } + + return separator switch + { + char charSeparator => countExceedingBehaviour switch + { + CountExceedingBehaviour.AppendRemainingElements => source.Split(charSeparator, count, options), + CountExceedingBehaviour.CutRemainingElements => count == 1 ? FirstSubstring(source, charSeparator, options) : source.Split(charSeparator, options).UpTo(count), + _ => throw UnhandledCaseException(countExceedingBehaviour) + }, + string stringSeparator => countExceedingBehaviour switch + { + CountExceedingBehaviour.AppendRemainingElements => source.Split(stringSeparator, count, options), + CountExceedingBehaviour.CutRemainingElements => count == 1 ? FirstSubstring(source, stringSeparator, options) : source.Split(stringSeparator, options).UpTo(count), + _ => throw UnhandledCaseException(countExceedingBehaviour) + }, + char[] charSeparators => countExceedingBehaviour switch + { + CountExceedingBehaviour.AppendRemainingElements => source.Split(charSeparators, count, options), + CountExceedingBehaviour.CutRemainingElements => count == 1 ? FirstSubstring(source, charSeparators, options) : source.Split(charSeparators, options).UpTo(count), + _ => throw UnhandledCaseException(countExceedingBehaviour) + }, + _ => throw new NotSupportedException($"Invalid separator type: {typeof(T)}") + }; + } + + /// + /// Get an array with all permutations of the enum flags. + /// + /// All permutations of the enum flags. + public static StringSplitOptions[] GetAllStringSplitOptions() + { +#if NET5_0_OR_GREATER + // ensure that no new option was added in an update + Debug.Assert(Enumerable.SequenceEqual( + (StringSplitOptions[])Enum.GetValues(typeof(StringSplitOptions)), + [StringSplitOptions.None, StringSplitOptions.RemoveEmptyEntries, StringSplitOptions.TrimEntries] + )); + + return [ + StringSplitOptions.None, + StringSplitOptions.RemoveEmptyEntries, + StringSplitOptions.TrimEntries, + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries + ]; +#else + return [ + StringSplitOptions.None, + StringSplitOptions.RemoveEmptyEntries + ]; +#endif + } + } +} diff --git a/tests/fuzzing/Tests/ReadOnlySpan/Linq/Sum.cs b/tests/fuzzing/Tests/ReadOnlySpan/Linq/Sum.cs new file mode 100644 index 0000000..f4074fc --- /dev/null +++ b/tests/fuzzing/Tests/ReadOnlySpan/Linq/Sum.cs @@ -0,0 +1,12 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class ReadOnlySpanLinqTests + { + public sealed class Sum + { + // todo + } + } +} diff --git a/tests/fuzzing/Tests/ReadOnlySpan/Split/ReadOnlySpanSplitTests.cs b/tests/fuzzing/Tests/ReadOnlySpan/Split/ReadOnlySpanSplitTests.cs new file mode 100644 index 0000000..5018d3e --- /dev/null +++ b/tests/fuzzing/Tests/ReadOnlySpan/Split/ReadOnlySpanSplitTests.cs @@ -0,0 +1,10 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class ReadOnlySpanSplitTests + { + static readonly IEnumerable stringSplitOptions = GetAllStringSplitOptions(); + static readonly CountExceedingBehaviour[] countExceedingBehaviours = (CountExceedingBehaviour[])Enum.GetValues(typeof(CountExceedingBehaviour)); + } +} diff --git a/tests/fuzzing/Tests/ReadOnlySpan/Split/Split.cs b/tests/fuzzing/Tests/ReadOnlySpan/Split/Split.cs new file mode 100644 index 0000000..acc4f3b --- /dev/null +++ b/tests/fuzzing/Tests/ReadOnlySpan/Split/Split.cs @@ -0,0 +1,115 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class ReadOnlySpanSplitTests + { + public static class Split + { + public static TheoryData SplitData(int iterations) + { + const int minValue = 0; + const int maxValue = 100; + + TheoryData data = new(); + + foreach(int length in new MultiplierRange(1, 1000, 10).And([0])) + { + data.Add(iterations, length, minValue, maxValue); + } + + return data; + } + + public sealed class SplitWithoutParameters + { + public static readonly TheoryData _SplitData = SplitData(250000); + + [Theory] + [MemberData(nameof(_SplitData))] + public void Fuzz(int iterations, int length, int minValue, int maxValue) + { + static void AssertOptions(T[] array, T delimiter) where T : IEquatable + { + AssertMethodResults( + expected: Split(array, delimiter), + actual: array.AsReadOnlySpan().Split(delimiter).ToSystemEnumerable(), + source: array, + method: nameof(ReadOnlySpanExtensions.Split), + args: ("delimiter", delimiter) + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + int[] integerArray = GenerateRandomIntegers(length, minValue, maxValue).ToArray(); + int integerDelimiter = integerArray.RandomElementOrDefault(0); + AssertOptions(integerArray, integerDelimiter); + AssertOptions(integerArray, maxValue); + + char[] charArray = GenerateRandomString(length).ToCharArray(); + char charDelimiter = charArray.RandomElementOrDefault('0'); + const char charMissingDelimiter = 'ა'; + AssertOptions(charArray, charDelimiter); + AssertOptions(charArray, charMissingDelimiter); + } + } + } + + public sealed class SplitWithCount + { + public static readonly TheoryData _SplitData = SplitData(25000); + + [Theory] + [MemberData(nameof(_SplitData))] + public void Fuzz(int iterations, int length, int minValue, int maxValue) + { + static void AssertOptions(T[] array, T delimiter, int count, CountExceedingBehaviour countExceedingBehaviour) where T : IEquatable + { + AssertMethodResults( + expected: Split(array, delimiter, count, countExceedingBehaviour), + actual: array.AsReadOnlySpan().Split(delimiter, count, countExceedingBehaviour).ToSystemEnumerable(), + source: array, + method: nameof(ReadOnlySpanExtensions.Split), + args: [("delimiter", delimiter), ("count", count), ("countExceedingBehaviour", countExceedingBehaviour)] + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + int[] integerArray = GenerateRandomIntegers(length, minValue, maxValue).ToArray(); + int integerDelimiter = integerArray.RandomElementOrDefault(0); + int countDelimiters = integerArray.Count(integerDelimiter); + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(integerArray, integerDelimiter, 0, countExceedingBehaviour); + AssertOptions(integerArray, integerDelimiter, 1, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(integerArray, integerDelimiter, countDelimiters - 1, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(integerArray, integerDelimiter, countDelimiters, countExceedingBehaviour); + AssertOptions(integerArray, integerDelimiter, countDelimiters + 2, countExceedingBehaviour); + AssertOptions(integerArray, maxValue, 0, countExceedingBehaviour); + AssertOptions(integerArray, maxValue, 1, countExceedingBehaviour); + AssertOptions(integerArray, maxValue, 2, countExceedingBehaviour); + } + + char[] charArray = GenerateRandomString(length).ToCharArray(); + char charDelimiter = charArray.RandomElementOrDefault('0'); + const char charMissingDelimiter = 'ა'; + countDelimiters = charArray.Count(charDelimiter); + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(charArray, charDelimiter, 0, countExceedingBehaviour); + AssertOptions(charArray, charDelimiter, 1, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(charArray, charDelimiter, countDelimiters - 1, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(charArray, charDelimiter, countDelimiters, countExceedingBehaviour); + AssertOptions(charArray, charDelimiter, countDelimiters + 2, countExceedingBehaviour); + AssertOptions(charArray, charMissingDelimiter, 0, countExceedingBehaviour); + AssertOptions(charArray, charMissingDelimiter, 1, countExceedingBehaviour); + AssertOptions(charArray, charMissingDelimiter, 2, countExceedingBehaviour); + } + } + } + } + } + } +} diff --git a/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitAny.cs b/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitAny.cs new file mode 100644 index 0000000..ac845af --- /dev/null +++ b/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitAny.cs @@ -0,0 +1,133 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class ReadOnlySpanSplitTests + { + public static class SplitAny + { + public static TheoryData SplitAnyData(int iterations) + { + const int minValue = 0; + const int maxValue = 100; + + TheoryData data = new(); + + foreach(int length in new MultiplierRange(1, 1000, 10).And([0])) + { + foreach(int delimitersLength in ((IEnumerable)[0, 1, 5, 25, 50]).Where(x => x <= length * 3)) + { + foreach(float delimitersOccurencePart in GetParts(delimitersLength)) + { + data.Add(iterations, length, minValue, maxValue, delimitersLength, delimitersOccurencePart); + } + } + } + + return data; + + static IEnumerable GetParts(int delimitersLength) + { + return delimitersLength switch + { + 0 => [0f], + 1 => [0f, 1f], + _ => [0f, 0.5f, 1f] + }; + } + } + + public sealed class SplitAnyWithoutParameters + { + public static readonly TheoryData _SplitAnyData = SplitAnyData(11000); + + [Theory] + [MemberData(nameof(_SplitAnyData))] + public void Fuzz(int iterations, int length, int minValue, int maxValue, int delimitersLength, float delimitersOccurencePart) + { + static void AssertOptions(T[] array, T[] delimiters) where T : IEquatable + { + AssertMethodResults( + expected: SplitAny(array, delimiters), + actual: array.AsReadOnlySpan().SplitAny(delimiters).ToSystemEnumerable(), + source: array, + method: nameof(ReadOnlySpanExtensions.SplitAny), + args: ("delimiters", delimiters) + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + int[] integerArray = GenerateRandomIntegers(length, minValue, maxValue).ToArray(); + int[] integerDelimiters = Enumerable.Range(0, delimitersLength).Select(i => + i < delimitersLength * delimitersOccurencePart ? integerArray.RandomElementOrDefault() + : maxValue + i + ).ToArray(); + AssertOptions(integerArray, integerDelimiters); + + char[] charArray = GenerateRandomString(length).ToCharArray(); + char[] charDelimiters = Enumerable.Range(0, delimitersLength).Select(i => + i < delimitersLength * delimitersOccurencePart ? charArray.RandomElementOrDefault() + : (char)('ა' + i) + ).ToArray(); + AssertOptions(charArray, charDelimiters); + } + } + } + + public sealed class SplitAnyWithCount + { + public static readonly TheoryData _SplitAnyData = SplitAnyData(2000); + + [Theory] + [MemberData(nameof(_SplitAnyData))] + public void Fuzz(int iterations, int length, int minValue, int maxValue, int delimitersLength, float delimitersOccurencePart) + { + static void AssertOptions(T[] array, T[] delimiters, int count, CountExceedingBehaviour countExceedingBehaviour) where T : IEquatable + { + AssertMethodResults( + expected: SplitAny(array, delimiters, count, countExceedingBehaviour), + actual: array.AsReadOnlySpan().SplitAny(delimiters, count, countExceedingBehaviour).ToSystemEnumerable(), + source: array, + method: nameof(ReadOnlySpanExtensions.SplitAny), + args: [("delimiters", delimiters), ("count", count), ("countExceedingBehaviour", countExceedingBehaviour)] + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + int[] integerArray = GenerateRandomIntegers(length, minValue, maxValue).ToArray(); + int[] integerDelimiters = Enumerable.Range(0, delimitersLength).Select(i => + i < delimitersLength * delimitersOccurencePart ? integerArray.RandomElementOrDefault() + : maxValue + i + ).ToArray(); + int countDelimiters = integerArray.Count(integerDelimiters); + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(integerArray, integerDelimiters, 0, countExceedingBehaviour); + AssertOptions(integerArray, integerDelimiters, 1, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(integerArray, integerDelimiters, countDelimiters, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(integerArray, integerDelimiters, countDelimiters, countExceedingBehaviour); + AssertOptions(integerArray, integerDelimiters, countDelimiters + 2, countExceedingBehaviour); + } + + char[] charArray = GenerateRandomString(length).ToCharArray(); + char[] charDelimiters = Enumerable.Range(0, delimitersLength).Select(i => + i < delimitersLength * delimitersOccurencePart ? charArray.RandomElementOrDefault() + : (char)('ა' + i) + ).ToArray(); + countDelimiters = charArray.Count(charDelimiters); + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(charArray, charDelimiters, 0, countExceedingBehaviour); + AssertOptions(charArray, charDelimiters, 0, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(charArray, charDelimiters, countDelimiters - 1, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(charArray, charDelimiters, countDelimiters, countExceedingBehaviour); + AssertOptions(charArray, charDelimiters, countDelimiters + 2, countExceedingBehaviour); + } + } + } + } + } + } +} diff --git a/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitAnyString.cs b/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitAnyString.cs new file mode 100644 index 0000000..8f77ae7 --- /dev/null +++ b/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitAnyString.cs @@ -0,0 +1,114 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class ReadOnlySpanSplitTests + { + public static class SplitAnyString + { + public static TheoryData SplitAnyData(int iterations) + { + TheoryData data = new(); + + foreach(int length in new MultiplierRange(1, 1000, 10).And([0])) + { + foreach(int delimitersLength in ((IEnumerable)[0, 1, 5, 25, 50]).Where(x => x <= length * 3)) + { + foreach(float delimitersOccurencePart in GetParts(delimitersLength)) + { + data.Add(iterations, length, delimitersLength, delimitersOccurencePart); + } + } + } + + return data; + + static IEnumerable GetParts(int delimitersLength) + { + return delimitersLength switch + { + 0 => [0f], + 1 => [0f, 1f], + _ => [0f, 0.5f, 1f] + }; + } + } + + public sealed class SplitAnyWithoutParameters + { + public static readonly TheoryData _SplitAnyData = SplitAnyData(15000); + + [Theory] + [MemberData(nameof(_SplitAnyData))] + public void Fuzz(int iterations, int length, int delimitersLength, float delimitersOccurencePart) + { + static void AssertOptions(string @string, char[] delimiters, StringSplitOptions options) + { + AssertMethodResults( + expected: @string.Split(delimiters, options), + actual: @string.AsSpan().SplitAny(delimiters, options).ToSystemEnumerable(), + source: @string, + method: nameof(ReadOnlySpanExtensions.SplitAny), + args: [("delimiters", delimiters), ("options", options)] + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + string @string = GenerateRandomString(length); + char[] charDelimiters = Enumerable.Range(0, delimitersLength).Select(i => + i < delimitersLength * delimitersOccurencePart ? @string.RandomElementOrDefault() + : (char)('ა' + i) + ).ToArray(); + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertOptions(@string, charDelimiters, options); + } + } + } + } + + public sealed class SplitAnyWithCount + { + public static readonly TheoryData _SplitAnyData = SplitAnyData(2000); + + [Theory] + [MemberData(nameof(_SplitAnyData))] + public void Fuzz(int iterations, int length, int delimitersLength, float delimitersOccurencePart) + { + static void AssertOptions(string @string, char[] delimiters, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour) + { + AssertMethodResults( + expected: @string.Split(delimiters, count, options, countExceedingBehaviour), + actual: @string.AsSpan().SplitAny(delimiters, count, options, countExceedingBehaviour).ToSystemEnumerable(), + source: @string, + method: nameof(ReadOnlySpanExtensions.SplitAny), + args: [("delimiters", delimiters), ("count", count), ("options", options), ("countExceedingBehaviour", countExceedingBehaviour)] + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + string @string = GenerateRandomString(length); + char[] charDelimiters = Enumerable.Range(0, delimitersLength).Select(i => + i < delimitersLength * delimitersOccurencePart ? @string.RandomElementOrDefault() + : (char)('ა' + i) + ).ToArray(); + int countDelimiters = @string.AsSpan().Count(charDelimiters); + foreach(StringSplitOptions options in stringSplitOptions) + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(@string, charDelimiters, 0, options, countExceedingBehaviour); + AssertOptions(@string, charDelimiters, 1, options, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(@string, charDelimiters, countDelimiters - 1, options, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(@string, charDelimiters, countDelimiters, options, countExceedingBehaviour); + AssertOptions(@string, charDelimiters, countDelimiters + 2, options, countExceedingBehaviour); + } + } + } + } + } + } + } +} diff --git a/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitSequence.cs b/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitSequence.cs new file mode 100644 index 0000000..1bd9338 --- /dev/null +++ b/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitSequence.cs @@ -0,0 +1,124 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class ReadOnlySpanSplitTests + { + public static class SplitSequence + { + public static TheoryData SplitWithDelimiterSequenceData(int iterations) + { + const int minValue = 0; + const int maxValue = 100; + + TheoryData data = new(); + + foreach(int length in new MultiplierRange(1, 1000, 10).And([0])) + { + foreach(int delimiterLength in new MultiplierRange(3, length * 10, 10).And([0, 1])) + { + data.Add(iterations, length, minValue, maxValue, delimiterLength); + } + } + + return data; + } + + public sealed class SplitWithDelimiterSequence + { + public static readonly TheoryData _SplitWithDelimiterSequenceData = SplitWithDelimiterSequenceData(20000); + + [Theory] + [MemberData(nameof(_SplitWithDelimiterSequenceData))] + public void Fuzz(int iterations, int length, int minValue, int maxValue, int delimiterLength) + { + static void AssertOptions(T[] array, T[] delimiter) where T : IEquatable + { + AssertMethodResults( + expected: Split(array, delimiter), + actual: array.AsReadOnlySpan().Split(delimiter).ToSystemEnumerable(), + source: array, + method: nameof(ReadOnlySpanExtensions.Split), + args: ("delimiter", delimiter) + ); + } + + int[] randomIntDelimiterArray = GenerateRandomIntegers(delimiterLength, minValue, maxValue).ToArray(); + char[] randomcharDelimiterArray = GenerateRandomString(delimiterLength).ToCharArray(); + for(int iteration = 0; iteration < iterations; iteration++) + { + int[] integerArray = GenerateRandomIntegers(length, minValue, maxValue).ToArray(); + int[] integerSequenceDelimiter = integerArray.RandomSubsequenceOrDefault(delimiterLength, randomIntDelimiterArray); + int[] integerSequenceMissingDelimiter = integerSequenceDelimiter.ReplaceRandomElement(maxValue); + AssertOptions(integerArray, integerSequenceDelimiter); + AssertOptions(integerArray, integerSequenceMissingDelimiter); + + char[] charArray = GenerateRandomString(length).ToCharArray(); + char[] charSequenceDelimiter = charArray.RandomSubsequenceOrDefault(delimiterLength, randomcharDelimiterArray); + char[] charSequenceMissingDelimiter = charSequenceDelimiter.ReplaceRandomElement('ა'); + AssertOptions(charArray, charSequenceDelimiter); + AssertOptions(charArray, charSequenceMissingDelimiter); + } + } + } + + public sealed class SplitWithDelimiterSequenceAndCount + { + public static readonly TheoryData _SplitWithDelimiterSequenceData = SplitWithDelimiterSequenceData(5000); + + [Theory] + [MemberData(nameof(_SplitWithDelimiterSequenceData))] + public void Fuzz(int iterations, int length, int minValue, int maxValue, int delimiterLength) + { + static void AssertOptions(T[] array, T[] delimiter, int count, CountExceedingBehaviour countExceedingBehaviour) where T : IEquatable + { + AssertMethodResults( + expected: Split(array, delimiter, count, countExceedingBehaviour), + actual: array.AsReadOnlySpan().Split(delimiter, count, countExceedingBehaviour).ToSystemEnumerable(), + source: array, + method: nameof(ReadOnlySpanExtensions.Split), + args: [("delimiter", delimiter), ("count", count), ("countExceedingBehaviour", countExceedingBehaviour)] + ); + } + + int[] randomIntDelimiterArray = GenerateRandomIntegers(delimiterLength, minValue, maxValue).ToArray(); + char[] randomcharDelimiterArray = GenerateRandomString(delimiterLength).ToCharArray(); + for(int iteration = 0; iteration < iterations; iteration++) + { + int[] integerArray = GenerateRandomIntegers(length, minValue, maxValue).ToArray(); + int[] integerSequenceDelimiter = integerArray.RandomSubsequenceOrDefault(delimiterLength, randomIntDelimiterArray); + int[] integerSequenceMissingDelimiter = integerSequenceDelimiter.ReplaceRandomElement(maxValue); + int countDelimiters = integerArray.CountSubsequence(integerSequenceDelimiter); + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(integerArray, integerSequenceDelimiter, 0, countExceedingBehaviour); + AssertOptions(integerArray, integerSequenceDelimiter, 1, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(integerArray, integerSequenceDelimiter, countDelimiters - 1, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(integerArray, integerSequenceDelimiter, countDelimiters, countExceedingBehaviour); + AssertOptions(integerArray, integerSequenceDelimiter, countDelimiters + 2, countExceedingBehaviour); + AssertOptions(integerArray, integerSequenceMissingDelimiter, 0, countExceedingBehaviour); + AssertOptions(integerArray, integerSequenceMissingDelimiter, 1, countExceedingBehaviour); + AssertOptions(integerArray, integerSequenceMissingDelimiter, 2, countExceedingBehaviour); + } + + char[] charArray = GenerateRandomString(length).ToCharArray(); + char[] charSequenceDelimiter = charArray.RandomSubsequenceOrDefault(delimiterLength, randomcharDelimiterArray); + char[] charSequenceMissingDelimiter = charSequenceDelimiter.ReplaceRandomElement('ა'); + countDelimiters = charArray.CountSubsequence(charSequenceDelimiter); + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(charArray, charSequenceDelimiter, 0, countExceedingBehaviour); + AssertOptions(charArray, charSequenceDelimiter, 1, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(charArray, charSequenceDelimiter, countDelimiters - 1, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(charArray, charSequenceDelimiter, countDelimiters, countExceedingBehaviour); + AssertOptions(charArray, charSequenceDelimiter, countDelimiters + 2, countExceedingBehaviour); + AssertOptions(charArray, charSequenceMissingDelimiter, 0, countExceedingBehaviour); + AssertOptions(charArray, charSequenceMissingDelimiter, 1, countExceedingBehaviour); + AssertOptions(charArray, charSequenceMissingDelimiter, 2, countExceedingBehaviour); + } + } + } + } + } + } +} diff --git a/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitString.cs b/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitString.cs new file mode 100644 index 0000000..32b1830 --- /dev/null +++ b/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitString.cs @@ -0,0 +1,97 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class ReadOnlySpanSplitTests + { + public static class SplitString + { + public static TheoryData SplitData(int iterations) + { + TheoryData data = new(); + + foreach(int length in new MultiplierRange(1, 1000, 10).And([0])) + { + data.Add(iterations, length); + } + + return data; + } + + public sealed class SplitWithoutParameters + { + public static readonly TheoryData _SplitData = SplitData(125000); + + [Theory] + [MemberData(nameof(_SplitData))] + public void Fuzz(int iterations, int length) + { + static void AssertOptions(string @string, char delimiter, StringSplitOptions options) + { + AssertMethodResults( + expected: @string.Split(delimiter, options), + actual: @string.AsSpan().Split(delimiter, options).ToSystemEnumerable(), + source: @string, + method: nameof(ReadOnlySpanExtensions.Split), + args: [("delimiter", delimiter), ("options", options)] + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + string @string = GenerateRandomString(length); + char charDelimiter = @string.RandomElementOrDefault('0'); + const char charMissingDelimiter = 'ა'; + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertOptions(@string, charDelimiter, options); + AssertOptions(@string, charMissingDelimiter, options); + } + } + } + } + + public sealed class SplitWithCount + { + public static readonly TheoryData _SplitData = SplitData(20000); + + [Theory] + [MemberData(nameof(_SplitData))] + public void Fuzz(int iterations, int length) + { + static void AssertOptions(string @string, char delimiter, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour) + { + AssertMethodResults( + expected: @string.Split(delimiter, count, options, countExceedingBehaviour), + actual: @string.AsSpan().Split(delimiter, count, options, countExceedingBehaviour).ToSystemEnumerable(), + source: @string, + method: nameof(ReadOnlySpanExtensions.Split), + args: [("delimiter", delimiter), ("count", count), ("options", options), ("countExceedingBehaviour", countExceedingBehaviour)] + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + string @string = GenerateRandomString(length); + char charDelimiter = @string.RandomElementOrDefault('0'); + int countDelimiters = @string.Count(charDelimiter); + foreach(StringSplitOptions options in stringSplitOptions) + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(@string, charDelimiter, 0, options, countExceedingBehaviour); + AssertOptions(@string, charDelimiter, 1, options, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(@string, charDelimiter, countDelimiters - 1, options, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(@string, charDelimiter, countDelimiters, options, countExceedingBehaviour); + AssertOptions(@string, charDelimiter, countDelimiters + 2, options, countExceedingBehaviour); + AssertOptions(@string, 'ა', 0, options, countExceedingBehaviour); + AssertOptions(@string, 'ა', 1, options, countExceedingBehaviour); + AssertOptions(@string, 'ა', 2, options, countExceedingBehaviour); + } + } + } + } + } + } + } +} diff --git a/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitStringSequence.cs b/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitStringSequence.cs new file mode 100644 index 0000000..792a1ee --- /dev/null +++ b/tests/fuzzing/Tests/ReadOnlySpan/Split/SplitStringSequence.cs @@ -0,0 +1,103 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class ReadOnlySpanSplitTests + { + public static class SplitStringSequence + { + public static TheoryData SplitWithDelimiterSequenceData(int iterations) + { + TheoryData data = new(); + + foreach(int length in new MultiplierRange(1, 1000, 10).And([0])) + { + foreach(int delimiterLength in new MultiplierRange(3, length * 10, 10).And([0, 1])) + { + data.Add(iterations, length, delimiterLength); + } + } + + return data; + } + + public sealed class SplitWithDelimiterSequence + { + public static readonly TheoryData _SplitWithDelimiterSequenceData = SplitWithDelimiterSequenceData(30000); + + [Theory] + [MemberData(nameof(_SplitWithDelimiterSequenceData))] + public void Fuzz(int iterations, int length, int delimiterLength) + { + static void AssertOptions(string @string, char[] delimiter, StringSplitOptions options) + { + AssertMethodResults( + expected: @string.Split(new string(delimiter), options), + actual: @string.AsSpan().Split(delimiter, options).ToSystemEnumerable(), + source: @string, + method: nameof(ReadOnlySpanExtensions.Split), + args: [("delimiter", delimiter), ("options", options)] + ); + } + + char[] randomcharDelimiterArray = GenerateRandomString(delimiterLength).ToCharArray(); + for(int iteration = 0; iteration < iterations; iteration++) + { + string @string = GenerateRandomString(length); + char[] charSequenceDelimiter = @string.RandomSubsequenceOrDefault(delimiterLength, randomcharDelimiterArray); + char[] charSequenceMissingDelimiter = charSequenceDelimiter.ReplaceRandomElement('ა'); + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertOptions(@string, charSequenceDelimiter, options); + AssertOptions(@string, charSequenceMissingDelimiter, options); + } + } + } + } + + public sealed class SplitWithDelimiterSequenceAndCount + { + public static readonly TheoryData _SplitWithDelimiterSequenceData = SplitWithDelimiterSequenceData(10000); + + [Theory] + [MemberData(nameof(_SplitWithDelimiterSequenceData))] + public void Fuzz(int iterations, int length, int delimiterLength) + { + static void AssertOptions(string @string, char[] delimiter, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour) + { + AssertMethodResults( + expected: @string.Split(new string(delimiter), count, options, countExceedingBehaviour), + actual: @string.AsSpan().Split(delimiter, count, options, countExceedingBehaviour).ToSystemEnumerable(), + source: @string, + method: nameof(ReadOnlySpanExtensions.Split), + args: [("delimiter", delimiter), ("count", count), ("options", options), ("countExceedingBehaviour", countExceedingBehaviour)] + ); + } + + char[] randomcharDelimiterArray = GenerateRandomString(delimiterLength).ToCharArray(); + for(int iteration = 0; iteration < iterations; iteration++) + { + string @string = GenerateRandomString(length); + char[] charSequenceDelimiter = @string.RandomSubsequenceOrDefault(delimiterLength, randomcharDelimiterArray); + char[] charSequenceMissingDelimiter = charSequenceDelimiter.ReplaceRandomElement('ა'); + int countDelimiters = @string.CountSubsequence(charSequenceDelimiter); + foreach(StringSplitOptions options in stringSplitOptions) + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(@string, charSequenceDelimiter, 0, options, countExceedingBehaviour); + AssertOptions(@string, charSequenceDelimiter, 1, options, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(@string, charSequenceDelimiter, countDelimiters - 1, options, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(@string, charSequenceDelimiter, countDelimiters, options, countExceedingBehaviour); + AssertOptions(@string, charSequenceDelimiter, countDelimiters + 2, options, countExceedingBehaviour); + AssertOptions(@string, charSequenceMissingDelimiter, 0, options, countExceedingBehaviour); + AssertOptions(@string, charSequenceMissingDelimiter, 1, options, countExceedingBehaviour); + AssertOptions(@string, charSequenceMissingDelimiter, 2, options, countExceedingBehaviour); + } + } + } + } + } + } + } +} diff --git a/tests/fuzzing/Tests/ReadOnlySpan/String/Remove.cs b/tests/fuzzing/Tests/ReadOnlySpan/String/Remove.cs new file mode 100644 index 0000000..457d549 --- /dev/null +++ b/tests/fuzzing/Tests/ReadOnlySpan/String/Remove.cs @@ -0,0 +1,12 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class ReadOnlySpanStringTests + { + public sealed class Remove + { + // todo + } + } +} diff --git a/tests/fuzzing/Tests/Span/Linq/Sum.cs b/tests/fuzzing/Tests/Span/Linq/Sum.cs new file mode 100644 index 0000000..4c78d6d --- /dev/null +++ b/tests/fuzzing/Tests/Span/Linq/Sum.cs @@ -0,0 +1,12 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class SpanLinqTests + { + public sealed class Sum + { + // todo + } + } +} diff --git a/tests/fuzzing/Tests/Span/Split/SpanSplitTests.cs b/tests/fuzzing/Tests/Span/Split/SpanSplitTests.cs new file mode 100644 index 0000000..7b87d94 --- /dev/null +++ b/tests/fuzzing/Tests/Span/Split/SpanSplitTests.cs @@ -0,0 +1,10 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class SpanSplitTests + { + static readonly IEnumerable stringSplitOptions = GetAllStringSplitOptions(); + static readonly CountExceedingBehaviour[] countExceedingBehaviours = (CountExceedingBehaviour[])Enum.GetValues(typeof(CountExceedingBehaviour)); + } +} diff --git a/tests/fuzzing/Tests/Span/Split/Split.cs b/tests/fuzzing/Tests/Span/Split/Split.cs new file mode 100644 index 0000000..05af533 --- /dev/null +++ b/tests/fuzzing/Tests/Span/Split/Split.cs @@ -0,0 +1,115 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class SpanSplitTests + { + public static class Split + { + public static TheoryData SplitData(int iterations) + { + const int minValue = 0; + const int maxValue = 100; + + TheoryData data = new(); + + foreach(int length in new MultiplierRange(1, 1000, 10).And([0])) + { + data.Add(iterations, length, minValue, maxValue); + } + + return data; + } + + public sealed class SplitWithoutParameters + { + public static readonly TheoryData _SplitData = SplitData(250000); + + [Theory] + [MemberData(nameof(_SplitData))] + public void Fuzz(int iterations, int length, int minValue, int maxValue) + { + static void AssertOptions(T[] array, T delimiter) where T : IEquatable + { + AssertMethodResults( + expected: Split(array, delimiter), + actual: array.AsSpan().Split(delimiter).ToSystemEnumerable(), + source: array, + method: nameof(SpanExtensions.Split), + args: ("delimiter", delimiter) + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + int[] integerArray = GenerateRandomIntegers(length, minValue, maxValue).ToArray(); + int integerDelimiter = integerArray.RandomElementOrDefault(0); + AssertOptions(integerArray, integerDelimiter); + AssertOptions(integerArray, maxValue); + + char[] charArray = GenerateRandomString(length).ToCharArray(); + char charDelimiter = charArray.RandomElementOrDefault('0'); + const char charMissingDelimiter = 'ა'; + AssertOptions(charArray, charDelimiter); + AssertOptions(charArray, charMissingDelimiter); + } + } + } + + public sealed class SplitWithCount + { + public static readonly TheoryData _SplitData = SplitData(25000); + + [Theory] + [MemberData(nameof(_SplitData))] + public void Fuzz(int iterations, int length, int minValue, int maxValue) + { + static void AssertOptions(T[] array, T delimiter, int count, CountExceedingBehaviour countExceedingBehaviour) where T : IEquatable + { + AssertMethodResults( + expected: Split(array, delimiter, count, countExceedingBehaviour), + actual: array.AsSpan().Split(delimiter, count, countExceedingBehaviour).ToSystemEnumerable(), + source: array, + method: nameof(SpanExtensions.Split), + args: [("delimiter", delimiter), ("count", count), ("countExceedingBehaviour", countExceedingBehaviour)] + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + int[] integerArray = GenerateRandomIntegers(length, minValue, maxValue).ToArray(); + int integerDelimiter = integerArray.RandomElementOrDefault(0); + int countDelimiters = integerArray.Count(integerDelimiter); + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(integerArray, integerDelimiter, 0, countExceedingBehaviour); + AssertOptions(integerArray, integerDelimiter, 1, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(integerArray, integerDelimiter, countDelimiters - 1, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(integerArray, integerDelimiter, countDelimiters, countExceedingBehaviour); + AssertOptions(integerArray, integerDelimiter, countDelimiters + 2, countExceedingBehaviour); + AssertOptions(integerArray, maxValue, 0, countExceedingBehaviour); + AssertOptions(integerArray, maxValue, 1, countExceedingBehaviour); + AssertOptions(integerArray, maxValue, 2, countExceedingBehaviour); + } + + char[] charArray = GenerateRandomString(length).ToCharArray(); + char charDelimiter = charArray.RandomElementOrDefault('0'); + const char charMissingDelimiter = 'ა'; + countDelimiters = charArray.Count(charDelimiter); + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(charArray, charDelimiter, 0, countExceedingBehaviour); + AssertOptions(charArray, charDelimiter, 1, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(charArray, charDelimiter, countDelimiters - 1, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(charArray, charDelimiter, countDelimiters, countExceedingBehaviour); + AssertOptions(charArray, charDelimiter, countDelimiters + 2, countExceedingBehaviour); + AssertOptions(charArray, charMissingDelimiter, 0, countExceedingBehaviour); + AssertOptions(charArray, charMissingDelimiter, 1, countExceedingBehaviour); + AssertOptions(charArray, charMissingDelimiter, 2, countExceedingBehaviour); + } + } + } + } + } + } +} diff --git a/tests/fuzzing/Tests/Span/Split/SplitAny.cs b/tests/fuzzing/Tests/Span/Split/SplitAny.cs new file mode 100644 index 0000000..8dde1d0 --- /dev/null +++ b/tests/fuzzing/Tests/Span/Split/SplitAny.cs @@ -0,0 +1,133 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class SpanSplitTests + { + public static class SplitAny + { + public static TheoryData SplitAnyData(int iterations) + { + const int minValue = 0; + const int maxValue = 100; + + TheoryData data = new(); + + foreach(int length in new MultiplierRange(1, 1000, 10).And([0])) + { + foreach(int delimitersLength in ((IEnumerable)[0, 1, 5, 25, 50]).Where(x => x <= length * 3)) + { + foreach(float delimitersOccurencePart in GetParts(delimitersLength)) + { + data.Add(iterations, length, minValue, maxValue, delimitersLength, delimitersOccurencePart); + } + } + } + + return data; + + static IEnumerable GetParts(int delimitersLength) + { + return delimitersLength switch + { + 0 => [0f], + 1 => [0f, 1f], + _ => [0f, 0.5f, 1f] + }; + } + } + + public sealed class SplitAnyWithoutParameters + { + public static readonly TheoryData _SplitAnyData = SplitAnyData(11000); + + [Theory] + [MemberData(nameof(_SplitAnyData))] + public void Fuzz(int iterations, int length, int minValue, int maxValue, int delimitersLength, float delimitersOccurencePart) + { + static void AssertOptions(T[] array, T[] delimiters) where T : IEquatable + { + AssertMethodResults( + expected: SplitAny(array, delimiters), + actual: array.AsSpan().SplitAny(delimiters).ToSystemEnumerable(), + source: array, + method: nameof(SpanExtensions.SplitAny), + args: ("delimiters", delimiters) + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + int[] integerArray = GenerateRandomIntegers(length, minValue, maxValue).ToArray(); + int[] integerDelimiters = Enumerable.Range(0, delimitersLength).Select(i => + i < delimitersLength * delimitersOccurencePart ? integerArray.RandomElementOrDefault() + : maxValue + i + ).ToArray(); + AssertOptions(integerArray, integerDelimiters); + + char[] charArray = GenerateRandomString(length).ToCharArray(); + char[] charDelimiters = Enumerable.Range(0, delimitersLength).Select(i => + i < delimitersLength * delimitersOccurencePart ? charArray.RandomElementOrDefault() + : (char)('ა' + i) + ).ToArray(); + AssertOptions(charArray, charDelimiters); + } + } + } + + public sealed class SplitAnyWithCount + { + public static readonly TheoryData _SplitAnyData = SplitAnyData(2000); + + [Theory] + [MemberData(nameof(_SplitAnyData))] + public void Fuzz(int iterations, int length, int minValue, int maxValue, int delimitersLength, float delimitersOccurencePart) + { + static void AssertOptions(T[] array, T[] delimiters, int count, CountExceedingBehaviour countExceedingBehaviour) where T : IEquatable + { + AssertMethodResults( + expected: SplitAny(array, delimiters, count, countExceedingBehaviour), + actual: array.AsSpan().SplitAny(delimiters, count, countExceedingBehaviour).ToSystemEnumerable(), + source: array, + method: nameof(SpanExtensions.SplitAny), + args: [("delimiters", delimiters), ("count", count), ("countExceedingBehaviour", countExceedingBehaviour)] + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + int[] integerArray = GenerateRandomIntegers(length, minValue, maxValue).ToArray(); + int[] integerDelimiters = Enumerable.Range(0, delimitersLength).Select(i => + i < delimitersLength * delimitersOccurencePart ? integerArray.RandomElementOrDefault() + : maxValue + i + ).ToArray(); + int countDelimiters = integerArray.Count(integerDelimiters); + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(integerArray, integerDelimiters, 0, countExceedingBehaviour); + AssertOptions(integerArray, integerDelimiters, 1, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(integerArray, integerDelimiters, countDelimiters, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(integerArray, integerDelimiters, countDelimiters, countExceedingBehaviour); + AssertOptions(integerArray, integerDelimiters, countDelimiters + 2, countExceedingBehaviour); + } + + char[] charArray = GenerateRandomString(length).ToCharArray(); + char[] charDelimiters = Enumerable.Range(0, delimitersLength).Select(i => + i < delimitersLength * delimitersOccurencePart ? charArray.RandomElementOrDefault() + : (char)('ა' + i) + ).ToArray(); + countDelimiters = charArray.Count(charDelimiters); + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(charArray, charDelimiters, 0, countExceedingBehaviour); + AssertOptions(charArray, charDelimiters, 0, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(charArray, charDelimiters, countDelimiters - 1, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(charArray, charDelimiters, countDelimiters, countExceedingBehaviour); + AssertOptions(charArray, charDelimiters, countDelimiters + 2, countExceedingBehaviour); + } + } + } + } + } + } +} diff --git a/tests/fuzzing/Tests/Span/Split/SplitAnyString.cs b/tests/fuzzing/Tests/Span/Split/SplitAnyString.cs new file mode 100644 index 0000000..4b37ea1 --- /dev/null +++ b/tests/fuzzing/Tests/Span/Split/SplitAnyString.cs @@ -0,0 +1,114 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class SpanSplitTests + { + public static class SplitAnyString + { + public static TheoryData SplitAnyData(int iterations) + { + TheoryData data = new(); + + foreach(int length in new MultiplierRange(1, 1000, 10).And([0])) + { + foreach(int delimitersLength in ((IEnumerable)[0, 1, 5, 25, 50]).Where(x => x <= length * 3)) + { + foreach(float delimitersOccurencePart in GetParts(delimitersLength)) + { + data.Add(iterations, length, delimitersLength, delimitersOccurencePart); + } + } + } + + return data; + + static IEnumerable GetParts(int delimitersLength) + { + return delimitersLength switch + { + 0 => [0f], + 1 => [0f, 1f], + _ => [0f, 0.5f, 1f] + }; + } + } + + public sealed class SplitAnyWithoutParameters + { + public static readonly TheoryData _SplitAnyData = SplitAnyData(15000); + + [Theory] + [MemberData(nameof(_SplitAnyData))] + public void Fuzz(int iterations, int length, int delimitersLength, float delimitersOccurencePart) + { + static void AssertOptions(string @string, char[] delimiters, StringSplitOptions options) + { + AssertMethodResults( + expected: @string.Split(delimiters, options), + actual: @string.ToCharArray().AsSpan().SplitAny(delimiters, options).ToSystemEnumerable(), + source: @string, + method: nameof(SpanExtensions.SplitAny), + args: [("delimiters", delimiters), ("options", options)] + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + string @string = GenerateRandomString(length); + char[] charDelimiters = Enumerable.Range(0, delimitersLength).Select(i => + i < delimitersLength * delimitersOccurencePart ? @string.RandomElementOrDefault() + : (char)('ა' + i) + ).ToArray(); + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertOptions(@string, charDelimiters, options); + } + } + } + } + + public sealed class SplitAnyWithCount + { + public static readonly TheoryData _SplitAnyData = SplitAnyData(2000); + + [Theory] + [MemberData(nameof(_SplitAnyData))] + public void Fuzz(int iterations, int length, int delimitersLength, float delimitersOccurencePart) + { + static void AssertOptions(string @string, char[] delimiters, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour) + { + AssertMethodResults( + expected: @string.Split(delimiters, count, options, countExceedingBehaviour), + actual: @string.ToCharArray().AsSpan().SplitAny(delimiters, count, options, countExceedingBehaviour).ToSystemEnumerable(), + source: @string, + method: nameof(SpanExtensions.SplitAny), + args: [("delimiters", delimiters), ("count", count), ("options", options), ("countExceedingBehaviour", countExceedingBehaviour)] + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + string @string = GenerateRandomString(length); + char[] charDelimiters = Enumerable.Range(0, delimitersLength).Select(i => + i < delimitersLength * delimitersOccurencePart ? @string.RandomElementOrDefault() + : (char)('ა' + i) + ).ToArray(); + int countDelimiters = @string.AsSpan().Count(charDelimiters); + foreach(StringSplitOptions options in stringSplitOptions) + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(@string, charDelimiters, 0, options, countExceedingBehaviour); + AssertOptions(@string, charDelimiters, 1, options, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(@string, charDelimiters, countDelimiters - 1, options, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(@string, charDelimiters, countDelimiters, options, countExceedingBehaviour); + AssertOptions(@string, charDelimiters, countDelimiters + 2, options, countExceedingBehaviour); + } + } + } + } + } + } + } +} diff --git a/tests/fuzzing/Tests/Span/Split/SplitSequence.cs b/tests/fuzzing/Tests/Span/Split/SplitSequence.cs new file mode 100644 index 0000000..6cc3291 --- /dev/null +++ b/tests/fuzzing/Tests/Span/Split/SplitSequence.cs @@ -0,0 +1,124 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class SpanSplitTests + { + public static class SplitSequence + { + public static TheoryData SplitWithDelimiterSequenceData(int iterations) + { + const int minValue = 0; + const int maxValue = 100; + + TheoryData data = new(); + + foreach(int length in new MultiplierRange(1, 1000, 10).And([0])) + { + foreach(int delimiterLength in new MultiplierRange(3, length * 10, 10).And([0, 1])) + { + data.Add(iterations, length, minValue, maxValue, delimiterLength); + } + } + + return data; + } + + public sealed class SplitWithDelimiterSequence + { + public static readonly TheoryData _SplitWithDelimiterSequenceData = SplitWithDelimiterSequenceData(20000); + + [Theory] + [MemberData(nameof(_SplitWithDelimiterSequenceData))] + public void Fuzz(int iterations, int length, int minValue, int maxValue, int delimiterLength) + { + static void AssertOptions(T[] array, T[] delimiter) where T : IEquatable + { + AssertMethodResults( + expected: Split(array, delimiter), + actual: array.AsSpan().Split(delimiter).ToSystemEnumerable(), + source: array, + method: nameof(SpanExtensions.Split), + args: ("delimiter", delimiter) + ); + } + + int[] randomIntDelimiterArray = GenerateRandomIntegers(delimiterLength, minValue, maxValue).ToArray(); + char[] randomcharDelimiterArray = GenerateRandomString(delimiterLength).ToCharArray(); + for(int iteration = 0; iteration < iterations; iteration++) + { + int[] integerArray = GenerateRandomIntegers(length, minValue, maxValue).ToArray(); + int[] integerSequenceDelimiter = integerArray.RandomSubsequenceOrDefault(delimiterLength, randomIntDelimiterArray); + int[] integerSequenceMissingDelimiter = integerSequenceDelimiter.ReplaceRandomElement(maxValue); + AssertOptions(integerArray, integerSequenceDelimiter); + AssertOptions(integerArray, integerSequenceMissingDelimiter); + + char[] charArray = GenerateRandomString(length).ToCharArray(); + char[] charSequenceDelimiter = charArray.RandomSubsequenceOrDefault(delimiterLength, randomcharDelimiterArray); + char[] charSequenceMissingDelimiter = charSequenceDelimiter.ReplaceRandomElement('ა'); + AssertOptions(charArray, charSequenceDelimiter); + AssertOptions(charArray, charSequenceMissingDelimiter); + } + } + } + + public sealed class SplitWithDelimiterSequenceAndCount + { + public static readonly TheoryData _SplitWithDelimiterSequenceData = SplitWithDelimiterSequenceData(5000); + + [Theory] + [MemberData(nameof(_SplitWithDelimiterSequenceData))] + public void Fuzz(int iterations, int length, int minValue, int maxValue, int delimiterLength) + { + static void AssertOptions(T[] array, T[] delimiter, int count, CountExceedingBehaviour countExceedingBehaviour) where T : IEquatable + { + AssertMethodResults( + expected: Split(array, delimiter, count, countExceedingBehaviour), + actual: array.AsSpan().Split(delimiter, count, countExceedingBehaviour).ToSystemEnumerable(), + source: array, + method: nameof(SpanExtensions.Split), + args: [("delimiter", delimiter), ("count", count), ("countExceedingBehaviour", countExceedingBehaviour)] + ); + } + + int[] randomIntDelimiterArray = GenerateRandomIntegers(delimiterLength, minValue, maxValue).ToArray(); + char[] randomcharDelimiterArray = GenerateRandomString(delimiterLength).ToCharArray(); + for(int iteration = 0; iteration < iterations; iteration++) + { + int[] integerArray = GenerateRandomIntegers(length, minValue, maxValue).ToArray(); + int[] integerSequenceDelimiter = integerArray.RandomSubsequenceOrDefault(delimiterLength, randomIntDelimiterArray); + int[] integerSequenceMissingDelimiter = integerSequenceDelimiter.ReplaceRandomElement(maxValue); + int countDelimiters = integerArray.CountSubsequence(integerSequenceDelimiter); + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(integerArray, integerSequenceDelimiter, 0, countExceedingBehaviour); + AssertOptions(integerArray, integerSequenceDelimiter, 1, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(integerArray, integerSequenceDelimiter, countDelimiters - 1, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(integerArray, integerSequenceDelimiter, countDelimiters, countExceedingBehaviour); + AssertOptions(integerArray, integerSequenceDelimiter, countDelimiters + 2, countExceedingBehaviour); + AssertOptions(integerArray, integerSequenceMissingDelimiter, 0, countExceedingBehaviour); + AssertOptions(integerArray, integerSequenceMissingDelimiter, 1, countExceedingBehaviour); + AssertOptions(integerArray, integerSequenceMissingDelimiter, 2, countExceedingBehaviour); + } + + char[] charArray = GenerateRandomString(length).ToCharArray(); + char[] charSequenceDelimiter = charArray.RandomSubsequenceOrDefault(delimiterLength, randomcharDelimiterArray); + char[] charSequenceMissingDelimiter = charSequenceDelimiter.ReplaceRandomElement('ა'); + countDelimiters = charArray.CountSubsequence(charSequenceDelimiter); + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(charArray, charSequenceDelimiter, 0, countExceedingBehaviour); + AssertOptions(charArray, charSequenceDelimiter, 1, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(charArray, charSequenceDelimiter, countDelimiters - 1, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(charArray, charSequenceDelimiter, countDelimiters, countExceedingBehaviour); + AssertOptions(charArray, charSequenceDelimiter, countDelimiters + 2, countExceedingBehaviour); + AssertOptions(charArray, charSequenceMissingDelimiter, 0, countExceedingBehaviour); + AssertOptions(charArray, charSequenceMissingDelimiter, 1, countExceedingBehaviour); + AssertOptions(charArray, charSequenceMissingDelimiter, 2, countExceedingBehaviour); + } + } + } + } + } + } +} diff --git a/tests/fuzzing/Tests/Span/Split/SplitString.cs b/tests/fuzzing/Tests/Span/Split/SplitString.cs new file mode 100644 index 0000000..849275e --- /dev/null +++ b/tests/fuzzing/Tests/Span/Split/SplitString.cs @@ -0,0 +1,97 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class SpanSplitTests + { + public static class SplitString + { + public static TheoryData SplitData(int iterations) + { + TheoryData data = new(); + + foreach(int length in new MultiplierRange(1, 1000, 10).And([0])) + { + data.Add(iterations, length); + } + + return data; + } + + public sealed class SplitWithoutParameters + { + public static readonly TheoryData _SplitData = SplitData(125000); + + [Theory] + [MemberData(nameof(_SplitData))] + public void Fuzz(int iterations, int length) + { + static void AssertOptions(string @string, char delimiter, StringSplitOptions options) + { + AssertMethodResults( + expected: @string.Split(delimiter, options), + actual: @string.ToCharArray().AsSpan().Split(delimiter, options).ToSystemEnumerable(), + source: @string, + method: nameof(SpanExtensions.Split), + args: [("delimiter", delimiter), ("options", options)] + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + string @string = GenerateRandomString(length); + char charDelimiter = @string.RandomElementOrDefault('0'); + const char charMissingDelimiter = 'ა'; + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertOptions(@string, charDelimiter, options); + AssertOptions(@string, charMissingDelimiter, options); + } + } + } + } + + public sealed class SplitWithCount + { + public static readonly TheoryData _SplitData = SplitData(20000); + + [Theory] + [MemberData(nameof(_SplitData))] + public void Fuzz(int iterations, int length) + { + static void AssertOptions(string @string, char delimiter, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour) + { + AssertMethodResults( + expected: @string.Split(delimiter, count, options, countExceedingBehaviour), + actual: @string.ToCharArray().AsSpan().Split(delimiter, count, options, countExceedingBehaviour).ToSystemEnumerable(), + source: @string, + method: nameof(SpanExtensions.Split), + args: [("delimiter", delimiter), ("count", count), ("options", options), ("countExceedingBehaviour", countExceedingBehaviour)] + ); + } + + for(int iteration = 0; iteration < iterations; iteration++) + { + string @string = GenerateRandomString(length); + char charDelimiter = @string.RandomElementOrDefault('0'); + int countDelimiters = @string.Count(charDelimiter); + foreach(StringSplitOptions options in stringSplitOptions) + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(@string, charDelimiter, 0, options, countExceedingBehaviour); + AssertOptions(@string, charDelimiter, 1, options, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(@string, charDelimiter, countDelimiters - 1, options, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(@string, charDelimiter, countDelimiters, options, countExceedingBehaviour); + AssertOptions(@string, charDelimiter, countDelimiters + 2, options, countExceedingBehaviour); + AssertOptions(@string, 'ა', 0, options, countExceedingBehaviour); + AssertOptions(@string, 'ა', 1, options, countExceedingBehaviour); + AssertOptions(@string, 'ა', 2, options, countExceedingBehaviour); + } + } + } + } + } + } + } +} diff --git a/tests/fuzzing/Tests/Span/Split/SplitStringSequence.cs b/tests/fuzzing/Tests/Span/Split/SplitStringSequence.cs new file mode 100644 index 0000000..3764e6c --- /dev/null +++ b/tests/fuzzing/Tests/Span/Split/SplitStringSequence.cs @@ -0,0 +1,103 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class SpanSplitTests + { + public static class SplitStringSequence + { + public static TheoryData SplitWithDelimiterSequenceData(int iterations) + { + TheoryData data = new(); + + foreach(int length in new MultiplierRange(1, 1000, 10).And([0])) + { + foreach(int delimiterLength in new MultiplierRange(3, length * 10, 10).And([0, 1])) + { + data.Add(iterations, length, delimiterLength); + } + } + + return data; + } + + public sealed class SplitWithDelimiterSequence + { + public static readonly TheoryData _SplitWithDelimiterSequenceData = SplitWithDelimiterSequenceData(30000); + + [Theory] + [MemberData(nameof(_SplitWithDelimiterSequenceData))] + public void Fuzz(int iterations, int length, int delimiterLength) + { + static void AssertOptions(string @string, char[] delimiter, StringSplitOptions options) + { + AssertMethodResults( + expected: @string.Split(new string(delimiter), options), + actual: @string.ToCharArray().AsSpan().Split(delimiter, options).ToSystemEnumerable(), + source: @string, + method: nameof(SpanExtensions.Split), + args: [("delimiter", delimiter), ("options", options)] + ); + } + + char[] randomcharDelimiterArray = GenerateRandomString(delimiterLength).ToCharArray(); + for(int iteration = 0; iteration < iterations; iteration++) + { + string @string = GenerateRandomString(length); + char[] charSequenceDelimiter = @string.RandomSubsequenceOrDefault(delimiterLength, randomcharDelimiterArray); + char[] charSequenceMissingDelimiter = charSequenceDelimiter.ReplaceRandomElement('ა'); + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertOptions(@string, charSequenceDelimiter, options); + AssertOptions(@string, charSequenceMissingDelimiter, options); + } + } + } + } + + public sealed class SplitWithDelimiterSequenceAndCount + { + public static readonly TheoryData _SplitWithDelimiterSequenceData = SplitWithDelimiterSequenceData(10000); + + [Theory] + [MemberData(nameof(_SplitWithDelimiterSequenceData))] + public void Fuzz(int iterations, int length, int delimiterLength) + { + static void AssertOptions(string @string, char[] delimiter, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour) + { + AssertMethodResults( + expected: @string.Split(new string(delimiter), count, options, countExceedingBehaviour), + actual: @string.ToCharArray().AsSpan().Split(delimiter, count, options, countExceedingBehaviour).ToSystemEnumerable(), + source: @string, + method: nameof(SpanExtensions.Split), + args: [("delimiter", delimiter), ("count", count), ("options", options), ("countExceedingBehaviour", countExceedingBehaviour)] + ); + } + + char[] randomcharDelimiterArray = GenerateRandomString(delimiterLength).ToCharArray(); + for(int iteration = 0; iteration < iterations; iteration++) + { + string @string = GenerateRandomString(length); + char[] charSequenceDelimiter = @string.RandomSubsequenceOrDefault(delimiterLength, randomcharDelimiterArray); + char[] charSequenceMissingDelimiter = charSequenceDelimiter.ReplaceRandomElement('ა'); + int countDelimiters = @string.CountSubsequence(charSequenceDelimiter); + foreach(StringSplitOptions options in stringSplitOptions) + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertOptions(@string, charSequenceDelimiter, 0, options, countExceedingBehaviour); + AssertOptions(@string, charSequenceDelimiter, 1, options, countExceedingBehaviour); + if(countDelimiters - 1 > 1) AssertOptions(@string, charSequenceDelimiter, countDelimiters - 1, options, countExceedingBehaviour); + if(countDelimiters > 1) AssertOptions(@string, charSequenceDelimiter, countDelimiters, options, countExceedingBehaviour); + AssertOptions(@string, charSequenceDelimiter, countDelimiters + 2, options, countExceedingBehaviour); + AssertOptions(@string, charSequenceMissingDelimiter, 0, options, countExceedingBehaviour); + AssertOptions(@string, charSequenceMissingDelimiter, 1, options, countExceedingBehaviour); + AssertOptions(@string, charSequenceMissingDelimiter, 2, options, countExceedingBehaviour); + } + } + } + } + } + } + } +} diff --git a/tests/fuzzing/Tests/Span/String/Remove.cs b/tests/fuzzing/Tests/Span/String/Remove.cs new file mode 100644 index 0000000..0403c06 --- /dev/null +++ b/tests/fuzzing/Tests/Span/String/Remove.cs @@ -0,0 +1,12 @@ +using static SpanExtensions.Tests.Fuzzing.TestHelper; + +namespace SpanExtensions.Tests.Fuzzing +{ + public static partial class SpanStringTests + { + public sealed class Remove + { + // todo + } + } +} diff --git a/tests/fuzzing/ToSystemEnumerableExtensions.cs b/tests/fuzzing/ToSystemEnumerableExtensions.cs new file mode 100644 index 0000000..ba3ab80 --- /dev/null +++ b/tests/fuzzing/ToSystemEnumerableExtensions.cs @@ -0,0 +1,155 @@ +using SpanExtensions.Enumerators; + +namespace SpanExtensions.Tests.Fuzzing +{ + /// + /// Extension methods to convert enumerators into . + /// This obviously defeats the purpose of using spans. This is for testing only. + /// + public static class ToSystemEnumerableExtensions + { + public static IEnumerable> ToSystemEnumerable(this SpanSplitEnumerator spanEnumerator) where T : IEquatable + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitWithCountEnumerator spanEnumerator) where T : IEquatable + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitStringSplitOptionsEnumerator spanEnumerator) + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitStringSplitOptionsWithCountEnumerator spanEnumerator) + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitAnyEnumerator spanEnumerator) where T : IEquatable + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitAnyWithCountEnumerator spanEnumerator) where T : IEquatable + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitAnyStringSplitOptionsEnumerator spanEnumerator) + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitAnyStringSplitOptionsWithCountEnumerator spanEnumerator) + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitSequenceEnumerator spanEnumerator) where T : IEquatable + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitSequenceWithCountEnumerator spanEnumerator) where T : IEquatable + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitSequenceStringSplitOptionsEnumerator spanEnumerator) + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitSequenceStringSplitOptionsWithCountEnumerator spanEnumerator) + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + } +} diff --git a/tests/unit-tests/GlobalUsings.cs b/tests/unit-tests/GlobalUsings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/tests/unit-tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/tests/unit-tests/TestHelper.cs b/tests/unit-tests/TestHelper.cs new file mode 100644 index 0000000..4b60d99 --- /dev/null +++ b/tests/unit-tests/TestHelper.cs @@ -0,0 +1,58 @@ +using System.Diagnostics; + +namespace SpanExtensions.Tests.UnitTests +{ + public static class TestHelper + { + /// + /// Verifies that two jagged sequences are equivalent, using a default comparer. + /// + /// + /// This is a wrapper for that improves tests messages on failure. + /// + /// The type of the objects to be compared. + /// The expected value. + /// The value to be compared against. + /// Thrown when the objects are not equal. + public static void AssertEqual(IEnumerable> expected, IEnumerable> actual) + { + if(expected is not T[][]) + { + expected = expected.Select(x => x.ToArray()).ToArray(); + } + if(actual is not T[][]) + { + actual = actual.Select(x => x.ToArray()).ToArray(); + } + + Assert.Equal(expected, actual); + } + + /// + /// Get an array with all permutations of the enum flags. + /// + /// All permutations of the enum flags. + public static StringSplitOptions[] GetAllStringSplitOptions() + { +#if NET5_0_OR_GREATER + // ensure that no new option was added in an update + Debug.Assert(Enumerable.SequenceEqual( + (StringSplitOptions[])Enum.GetValues(typeof(StringSplitOptions)), + [StringSplitOptions.None, StringSplitOptions.RemoveEmptyEntries, StringSplitOptions.TrimEntries] + )); + + return [ + StringSplitOptions.None, + StringSplitOptions.RemoveEmptyEntries, + StringSplitOptions.TrimEntries, + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries + ]; +#else + return [ + StringSplitOptions.None, + StringSplitOptions.RemoveEmptyEntries + ]; +#endif + } + } +} diff --git a/tests/unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs b/tests/unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs new file mode 100644 index 0000000..4703fc4 --- /dev/null +++ b/tests/unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs @@ -0,0 +1,12 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class ReadOnlySpanLinqTests + { + public sealed class Sum + { + // todo + } + } +} diff --git a/tests/unit-tests/Tests/ReadOnlySpan/Split/ReadOnlySpanSplitTests.cs b/tests/unit-tests/Tests/ReadOnlySpan/Split/ReadOnlySpanSplitTests.cs new file mode 100644 index 0000000..a7d6be5 --- /dev/null +++ b/tests/unit-tests/Tests/ReadOnlySpan/Split/ReadOnlySpanSplitTests.cs @@ -0,0 +1,10 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class ReadOnlySpanSplitTests + { + static readonly IEnumerable stringSplitOptions = GetAllStringSplitOptions(); + static readonly CountExceedingBehaviour[] countExceedingBehaviours = (CountExceedingBehaviour[])Enum.GetValues(typeof(CountExceedingBehaviour)); + } +} diff --git a/tests/unit-tests/Tests/ReadOnlySpan/Split/Split.cs b/tests/unit-tests/Tests/ReadOnlySpan/Split/Split.cs new file mode 100644 index 0000000..5ef27b3 --- /dev/null +++ b/tests/unit-tests/Tests/ReadOnlySpan/Split/Split.cs @@ -0,0 +1,174 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class ReadOnlySpanSplitTests + { + public sealed class Split + { + [Fact] + public void EnumerationReturnsReadOnlySpans() + { +#pragma warning disable CS0183 // 'is' expression's given expression is always of the provided type + foreach(var span in "aba".AsSpan().Split('b')) + { + Assert.True(span is ReadOnlySpan); + } + + foreach(var span in "aba".AsSpan().Split('b', 10)) + { + Assert.True(span is ReadOnlySpan); + } +#pragma warning restore CS0183 // 'is' expression's given expression is always of the provided type + } + + [Fact] + public void EmptySourceResultInEmptySpan() + { + AssertEqual( + [[]], + "".AsSpan().Split('a').ToSystemEnumerable() + ); + } + + [Fact] + public void NoDelimiterOccurenceResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().Split('c').ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualZeroResultsInNothing() + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertEqual( + [], + "abba".AsSpan().Split('a', 0, countExceedingBehaviour).ToSystemEnumerable() + ); + AssertEqual( + [], + "abba".AsSpan().Split('c', 0, countExceedingBehaviour).ToSystemEnumerable() + ); + } + } + + [Fact] + public void CountEqualOneResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().Split('a', 1).ToSystemEnumerable() + ); + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().Split('c', 1).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersResultInEmptySpan() + { + AssertEqual( + [['a'], [], ['a']], + "abba".AsSpan().Split('b').ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheStartResultInEmptySpan() + { + AssertEqual( + [[], ['a', 'a']], + "baa".AsSpan().Split('b').ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndResultInEmptySpan() + { + AssertEqual( + [['a', 'a'], []], + "aab".AsSpan().Split('b').ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInSpanWithEverythingAfterAndIncludingLastDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'a', 'a']], + "aabaa".AsSpan().Split('b', 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'b', 'a', 'a']], + "aabaabaa".AsSpan().Split('b', 2).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithCountEqualDelimiterCountResultsInSpanWithDelimiter() + { + AssertEqual( + [['a', 'a', 'b']], + "aab".AsSpan().Split('b', 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'b']], + "aabab".AsSpan().Split('b', 2).ToSystemEnumerable() + ); + } + + [Fact] + public void DefaultCountExceedingBehaviourOptionIsAppendRemainingElements() + { + AssertEqual( + "aab".AsSpan().Split('b', 1, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aab".AsSpan().Split('b', 1).ToSystemEnumerable() + ); + AssertEqual( + "aabab".AsSpan().Split('b', 2, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabab".AsSpan().Split('b', 2).ToSystemEnumerable() + ); + AssertEqual( + "aabaa".AsSpan().Split('b', 1, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabaa".AsSpan().Split('b', 1).ToSystemEnumerable() + ); + AssertEqual( + "aabaabaa".AsSpan().Split('b', 2, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabaabaa".AsSpan().Split('b', 2).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInEverythingAfterAndIncludingLastDelimiterBeingCut() + { + AssertEqual( + [['a', 'a']], + "aabaa".AsSpan().Split('b', 1, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aabaabaa".AsSpan().Split('b', 2, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + } + + [Fact] + public void NegativeCountThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => "aabb".AsSpan().Split('b', -1)); + Assert.Throws(() => "aabb".AsSpan().Split('c', -1)); + } + + [Fact] + public void UndefinedCountExceedingBehaviourOptionThrowsArgumentException() + { + Assert.Throws(() => "aabb".AsSpan().Split('b', 1, (CountExceedingBehaviour)255)); + Assert.Throws(() => "aabb".AsSpan().Split('c', 1, (CountExceedingBehaviour)255)); + } + } + } +} diff --git a/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitAny.cs b/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitAny.cs new file mode 100644 index 0000000..9c96ea6 --- /dev/null +++ b/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitAny.cs @@ -0,0 +1,219 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class ReadOnlySpanSplitTests + { + public sealed class SplitAny + { + [Fact] + public void EnumerationReturnsReadOnlySpans() + { +#pragma warning disable CS0183 // 'is' expression's given expression is always of the provided type + foreach(var span in "abaca".AsSpan().SplitAny(['b', 'c'])) + { + Assert.True(span is ReadOnlySpan); + } + + foreach(var span in "abaca".AsSpan().SplitAny(['b', 'c'], 10)) + { + Assert.True(span is ReadOnlySpan); + } +#pragma warning restore CS0183 // 'is' expression's given expression is always of the provided type + } + + [Fact] + public void EmptySourceResultInEmptySpan() + { + AssertEqual( + [[]], + "".AsSpan().SplitAny(['a', 'b']).ToSystemEnumerable() + ); + } + + [Fact] + public void NoDelimiterOccurenceResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().SplitAny(['c', 'd']).ToSystemEnumerable() + ); + } + + [Fact] + public void EmptyDelimiterSpanResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().SplitAny([]).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualZeroResultsInNothing() + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertEqual( + [], + "abba".AsSpan().SplitAny(['a', 'b'], 0, countExceedingBehaviour).ToSystemEnumerable() + ); + AssertEqual( + [], + "abba".AsSpan().SplitAny(['c', 'd'], 0, countExceedingBehaviour).ToSystemEnumerable() + ); + } + } + + [Fact] + public void CountEqualOneResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().SplitAny(['a', 'b'], 1).ToSystemEnumerable() + ); + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().SplitAny(['c', 'd'], 1).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersResultInEmptySpan() + { + AssertEqual( + [['a'], [], ['a']], + "abba".AsSpan().SplitAny(['b', 'c']).ToSystemEnumerable() + ); + AssertEqual( + [['a'], [], ['a']], + "abca".AsSpan().SplitAny(['b', 'c']).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheStartResultInEmptySpan() + { + AssertEqual( + [[], ['a', 'a']], + "baa".AsSpan().SplitAny(['b', 'c']).ToSystemEnumerable() + ); + AssertEqual( + [[], ['a', 'a']], + "caa".AsSpan().SplitAny(['b', 'c']).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndResultInEmptySpan() + { + AssertEqual( + [['a', 'a'], []], + "aab".AsSpan().SplitAny(['b', 'c']).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], []], + "aac".AsSpan().SplitAny(['b', 'c']).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInSpanWithEverythingAfterAndIncludingLastDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'a', 'a']], + "aabaa".AsSpan().SplitAny(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a', 'c', 'a', 'a']], + "aacaa".AsSpan().SplitAny(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'c', 'a', 'a']], + "aabaacaa".AsSpan().SplitAny(['b', 'c'], 2).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'b', 'a', 'a']], + "aacaabaa".AsSpan().SplitAny(['b', 'c'], 2).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithCountEqualDelimiterCountResultsInSpanWithDelimiter() + { + AssertEqual( + [['a', 'a', 'b']], + "aab".AsSpan().SplitAny(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a', 'c']], + "aac".AsSpan().SplitAny(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'c']], + "aabac".AsSpan().SplitAny(['b', 'c'], 2).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'b']], + "aacab".AsSpan().SplitAny(['b', 'c'], 2).ToSystemEnumerable() + ); + } + + [Fact] + public void DefaultCountExceedingBehaviourOptionIsAppendRemainingElements() + { + AssertEqual( + "aab".AsSpan().SplitAny(['b', 'c'], 1, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aab".AsSpan().SplitAny(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + "aabac".AsSpan().SplitAny(['b', 'c'], 2, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabac".AsSpan().SplitAny(['b', 'c'], 2).ToSystemEnumerable() + ); + AssertEqual( + "aacaa".AsSpan().SplitAny(['b', 'c'], 1, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aacaa".AsSpan().SplitAny(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + "aacaabaa".AsSpan().SplitAny(['b', 'c'], 2, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aacaabaa".AsSpan().SplitAny(['b', 'c'], 2).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInEverythingAfterAndIncludingLastDelimiterBeingCut() + { + AssertEqual( + [['a', 'a']], + "aabaa".AsSpan().SplitAny(['b', 'c'], 1, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "aacaa".AsSpan().SplitAny(['b', 'c'], 1, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aabaacaa".AsSpan().SplitAny(['b', 'c'], 2, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aacaabaa".AsSpan().SplitAny(['b', 'c'], 2, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + } + + [Fact] + public void NegativeCountThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => "aabc".AsSpan().SplitAny(['b', 'c'], -1)); + Assert.Throws(() => "aabc".AsSpan().SplitAny(['d', 'e'], -1)); + } + + [Fact] + public void UndefinedCountExceedingBehaviourOptionThrowsArgumentException() + { + Assert.Throws(() => "aabb".AsSpan().SplitAny(['d', 'e'], 1, (CountExceedingBehaviour)255)); + Assert.Throws(() => "aabb".AsSpan().SplitAny(['d', 'e'], 1, (CountExceedingBehaviour)255)); + } + } + } +} diff --git a/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitAnyString.cs b/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitAnyString.cs new file mode 100644 index 0000000..e551a07 --- /dev/null +++ b/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitAnyString.cs @@ -0,0 +1,442 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class ReadOnlySpanSplitTests + { + public sealed class SplitAnyString + { + [Fact] + public void EnumerationReturnsReadOnlySpans() + { +#pragma warning disable CS0183 // 'is' expression's given expression is always of the provided type + foreach(var span in "abaca".AsSpan().SplitAny(['b', 'c'], StringSplitOptions.None)) + { + Assert.True(span is ReadOnlySpan); + } + + foreach(var span in "abaca".AsSpan().SplitAny(['b', 'c'], 10, StringSplitOptions.None)) + { + Assert.True(span is ReadOnlySpan); + } +#pragma warning restore CS0183 // 'is' expression's given expression is always of the provided type + } + + [Fact] + public void EmptySourceResultInEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(!options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [[]], + "".AsSpan().SplitAny(['a', 'b'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void NoDelimiterOccurenceResultsInNoChange() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().SplitAny(['c', 'd'], options).ToSystemEnumerable() + ); + } + } + + [Fact] + public void WhiteSpaceCharactersAssumedWhenDelimitersSpanIsEmpty() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertEqual( + [['a'], ['b'], ['c'], ['d']], + "a b c d".AsSpan().SplitAny([], StringSplitOptions.None).ToSystemEnumerable() + ); + } + } + + [Fact] + public void CountEqualZeroResultsInNothing() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertEqual( + [], + "abba".AsSpan().SplitAny(['a', 'b'], 0, options, countExceedingBehaviour).ToSystemEnumerable() + ); + AssertEqual( + [], + "abba".AsSpan().SplitAny(['c', 'd'], 0, options, countExceedingBehaviour).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void CountEqualOneResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().SplitAny(['a', 'b'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().SplitAny(['c', 'd'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersResultInEmptySpan() + { + AssertEqual( + [['a'], [], ['a']], + "abba".AsSpan().SplitAny(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a'], [], ['a']], + "abca".AsSpan().SplitAny(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a'], ['a']], + "abba".AsSpan().SplitAny(['b', 'c'], options).ToSystemEnumerable() + ); + AssertEqual( + [['a'], ['a']], + "abca".AsSpan().SplitAny(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void DelimiterAtStartEndResultInEmptySpan() + { + AssertEqual( + [[], ['a', 'a']], + "baa".AsSpan().SplitAny(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [[], ['a', 'a']], + "caa".AsSpan().SplitAny(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheStartWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "baa".AsSpan().SplitAny(['b', 'c'], options).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "caa".AsSpan().SplitAny(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void DelimiterAtTheEndResultInEmptySpan() + { + AssertEqual( + [['a', 'a'], []], + "aab".AsSpan().SplitAny(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], []], + "aac".AsSpan().SplitAny(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "aab".AsSpan().SplitAny(['b', 'c'], options).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "aac".AsSpan().SplitAny(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void CountEqualDelimiterCountResultsInSpanWithEverythingAfterAndIncludingLastDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'a', 'a']], + "aabaa".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a', 'c', 'a', 'a']], + "aacaa".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'c', 'a', 'a']], + "aabaacaa".AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'b', 'a', 'a']], + "aacaabaa".AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithCountEqualDelimiterCountResultsInSpanWithDelimiter() + { + AssertEqual( + [['a', 'a', 'b']], + "aab".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a', 'c']], + "aac".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'c']], + "aabac".AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'b']], + "aacab".AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DefaultCountExceedingBehaviourOptionIsAppendRemainingElements() + { + AssertEqual( + "aabaa".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabaa".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aabaabaa".AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabaabaa".AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aab".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aab".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aabab".AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabab".AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInEverythingAfterAndIncludingLastDelimiterBeingCut() + { + AssertEqual( + [['a', 'a']], + "aabaa".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "aacaa".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aabaacaa".AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aacaabaa".AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersAtTheEndWithCountEqualDelimiterCountWithRemoveEmptyEntriesOptionResultInNoSpanWithDelimiter() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "aabb".AsSpan().SplitAny(['b', 'c'], 2, options).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "aabc".AsSpan().SplitAny(['b', 'c'], 2, options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void TrimEntriesOptionTrimsLastSpan() + { + AssertEqual( + [['a'], [], ['a']], + " a b b a ".AsSpan().SplitAny(['b', 'c'], StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a'], [], ['a']], + " a b c a ".AsSpan().SplitAny(['b', 'c'], StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void WhiteSpaceSpanWithTrimEntriesAndRemoveEmptyEntriesOptionsReturnsNothing() + { + AssertEqual( + [], + " \t".AsSpan().SplitAny(['_', '!'], StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersAtTheEndWithRemoveEmptyEntriesOptionResultInNoEmptySpans() + { + AssertEqual( + [['a', 'a']], + "aabb".AsSpan().SplitAny(['b', 'c'], StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "aabc".AsSpan().SplitAny(['b', 'c'], StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesOptionResultsInNothingIfSourceEmpty() + { + AssertEqual( + [], + "".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesOptionDoesNotRecursivelyRemoveEmptySpansAtTheStart() + { + AssertEqual( + [['b', 'a', 'a']], + "baa".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + AssertEqual( + [['b', 'c', 'a', 'a']], + "bcaa".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountGreaterThanOneWithRemoveEmptyEntriesOptionRecursivelyRemovesEmptySpansAtTheStart() + { + AssertEqual( + [['a', 'a']], + "baa".AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "bcaa".AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesAndTrimEntriesOptionsResultsInNothingIfSourceWhiteSpace() + { + AssertEqual( + [], + " \t".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesAndTrimEntriesOptionsDoesNotRecursivelyRemoveWhiteSpaceSpansAtTheStart() + { + AssertEqual( + [['b', '\t', 'a', 'a']], + " b\taa\n".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + AssertEqual( + [['b', '\t', 'c', '\n', 'a', 'a']], + " b\tc\naa\r".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountGreaterThanOneWithRemoveEmptyEntriesAndTrimEntriesOptionsRecursivelyRemovesWhiteSpaceSpansAtTheStart() + { + AssertEqual( + [['a', 'a']], + " b\taa\n".AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + " b\tc\naa\r".AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void EmptyDelimiterSpanResultsSameAsCountEqualOne() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertEqual( + "".AsSpan().SplitAny([], 1, options).ToSystemEnumerable(), + "".AsSpan().SplitAny([], options).ToSystemEnumerable() + ); + AssertEqual( + " ".AsSpan().SplitAny([], 1, options).ToSystemEnumerable(), + " ".AsSpan().SplitAny([], options).ToSystemEnumerable() + ); + AssertEqual( + " aabb ".AsSpan().SplitAny([], 1, options).ToSystemEnumerable(), + " aabb ".AsSpan().SplitAny([], options).ToSystemEnumerable() + ); + } + } + + [Fact] + public void NegativeCountThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => "aabc".AsSpan().SplitAny(['b', 'c'], -1, StringSplitOptions.None)); + Assert.Throws(() => "aabc".AsSpan().SplitAny(['d', 'e'], -1, StringSplitOptions.None)); + } + + [Fact] + public void UndefinedCountExceedingBehaviourOptionThrowsArgumentException() + { + Assert.Throws(() => "aabc".AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None, (CountExceedingBehaviour)255)); + Assert.Throws(() => "aabc".AsSpan().SplitAny(['d', 'e'], 1, StringSplitOptions.None, (CountExceedingBehaviour)255)); + } + + [Fact] + public void UndefinedStringSplitOptionsThrowsArgumentException() + { + Assert.Throws(() => "aabc".AsSpan().SplitAny(['b', 'c'], (StringSplitOptions)255)); + Assert.Throws(() => "aabc".AsSpan().SplitAny(['d', 'e'], (StringSplitOptions)255)); + } + } + } +} diff --git a/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitSequence.cs b/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitSequence.cs new file mode 100644 index 0000000..fa6e3f7 --- /dev/null +++ b/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitSequence.cs @@ -0,0 +1,183 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class ReadOnlySpanSplitTests + { + public sealed class SplitSequence + { + [Fact] + public void EnumerationReturnsReadOnlySpans() + { +#pragma warning disable CS0183 // 'is' expression's given expression is always of the provided type + foreach(var span in "abca".AsSpan().Split(['b', 'c'])) + { + Assert.True(span is ReadOnlySpan); + } + + foreach(var span in "abca".AsSpan().Split(['b', 'c'], 10)) + { + Assert.True(span is ReadOnlySpan); + } +#pragma warning restore CS0183 // 'is' expression's given expression is always of the provided type + } + + [Fact] + public void EmptySourceResultInEmptySpan() + { + AssertEqual( + [[]], + "".AsSpan().Split(['a', 'b']).ToSystemEnumerable() + ); + } + + [Fact] + public void NoDelimiterOccurenceResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().Split(['b', 'c']).ToSystemEnumerable() + ); + } + + [Fact] + public void EmptyDelimiterSpanResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().Split([]).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualZeroResultsInNothing() + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertEqual( + [], + "abba".AsSpan().Split(['a', 'b'], 0, countExceedingBehaviour).ToSystemEnumerable() + ); + AssertEqual( + [], + "abba".AsSpan().Split(['b', 'c'], 0, countExceedingBehaviour).ToSystemEnumerable() + ); + } + } + + [Fact] + public void CountEqualOneResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().Split(['a', 'b'], 1).ToSystemEnumerable() + ); + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().Split(['b', 'c'], 1).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersResultInEmptySpan() + { + AssertEqual( + [['a'], [], ['a']], + "abcbca".AsSpan().Split(['b', 'c']).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheStartResultInEmptySpan() + { + AssertEqual( + [[], ['a', 'a']], + "bcaa".AsSpan().Split(['b', 'c']).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndResultInEmptySpan() + { + AssertEqual( + [['a', 'a'], []], + "aabc".AsSpan().Split(['b', 'c']).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInSpanWithEverythingAfterAndIncludingLastDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'c', 'a', 'a']], + "aabcaa".AsSpan().Split(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'b', 'c', 'a', 'a']], + "aabcaabcaa".AsSpan().Split(['b', 'c'], 2).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithCountEqualDelimiterCountResultsInSpanWithDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'c']], + "aabc".AsSpan().Split(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'b', 'c']], + "aabcabc".AsSpan().Split(['b', 'c'], 2).ToSystemEnumerable() + ); + } + + [Fact] + public void DefaultCountExceedingBehaviourOptionIsAppendRemainingElements() + { + AssertEqual( + "aabc".AsSpan().Split(['b', 'c'], 1, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabc".AsSpan().Split(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + "aabcabc".AsSpan().Split(['b', 'c'], 2, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabcabc".AsSpan().Split(['b', 'c'], 2).ToSystemEnumerable() + ); + AssertEqual( + "aabcaa".AsSpan().Split(['b', 'c'], 1, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabcaa".AsSpan().Split(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + "aabcaabcaa".AsSpan().Split(['b', 'c'], 2, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabcaabcaa".AsSpan().Split(['b', 'c'], 2).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInEverythingAfterAndIncludingLastDelimiterBeingCut() + { + AssertEqual( + [['a', 'a']], + "aabcaa".AsSpan().Split(['b', 'c'], 1, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aabcaabcaa".AsSpan().Split(['b', 'c'], 2, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + } + + [Fact] + public void NegativeCountThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => "aabcbc".AsSpan().Split(['b', 'c'], -1)); + Assert.Throws(() => "aabcbc".AsSpan().Split(['c', 'd'], -1)); + } + + [Fact] + public void UndefinedCountExceedingBehaviourOptionThrowsArgumentException() + { + Assert.Throws(() => "aabcbc".AsSpan().Split(['b', 'c'], 1, (CountExceedingBehaviour)255)); + Assert.Throws(() => "aabcbc".AsSpan().Split(['c', 'd'], 1, (CountExceedingBehaviour)255)); + } + } + } +} diff --git a/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitString.cs b/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitString.cs new file mode 100644 index 0000000..e981c29 --- /dev/null +++ b/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitString.cs @@ -0,0 +1,350 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class ReadOnlySpanSplitTests + { + public sealed class SplitString + { + [Fact] + public void EnumerationReturnsReadOnlySpans() + { +#pragma warning disable CS0183 // 'is' expression's given expression is always of the provided type + foreach(var span in "aba".AsSpan().Split('b', StringSplitOptions.None)) + { + Assert.True(span is ReadOnlySpan); + } + + foreach(var span in "aba".AsSpan().Split('b', 10, StringSplitOptions.None)) + { + Assert.True(span is ReadOnlySpan); + } +#pragma warning restore CS0183 // 'is' expression's given expression is always of the provided type + } + + [Fact] + public void EmptySourceResultInEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(!options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [[]], + "".AsSpan().Split('a', options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void NoDelimiterOccurenceResultsInNoChange() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().Split('c', options).ToSystemEnumerable() + ); + } + } + + [Fact] + public void CountEqualZeroResultsInNothing() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertEqual( + [], + "abba".AsSpan().Split('a', 0, options, countExceedingBehaviour).ToSystemEnumerable() + ); + AssertEqual( + [], + "abba".AsSpan().Split('c', 0, options, countExceedingBehaviour).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void CountEqualOneResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().Split('a', 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().Split('c', 1, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersResultInEmptySpan() + { + AssertEqual( + [['a'], [], ['a']], + "abba".AsSpan().Split('b', StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a'], ['a']], + "abba".AsSpan().Split('b', options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void DelimiterAtTheStartResultInEmptySpan() + { + AssertEqual( + [[], ['a', 'a']], + "baa".AsSpan().Split('b', StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheStartWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "baa".AsSpan().Split('b', options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void DelimiterAtTheEndResultInEmptySpan() + { + AssertEqual( + [['a', 'a'], []], + "aab".AsSpan().Split('b', StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "aab".AsSpan().Split('b', options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void CountEqualDelimiterCountResultsInSpanWithEverythingAfterAndIncludingLastDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'a', 'a']], + "aabaa".AsSpan().Split('b', 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'b', 'a', 'a']], + "aabaabaa".AsSpan().Split('b', 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithCountEqualDelimiterCountResultsInSpanWithDelimiter() + { + AssertEqual( + [['a', 'a', 'b']], + "aab".AsSpan().Split('b', 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'b']], + "aabab".AsSpan().Split('b', 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DefaultCountExceedingBehaviourOptionIsAppendRemainingElements() + { + AssertEqual( + "aabaa".AsSpan().Split('b', 1, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabaa".AsSpan().Split('b', 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aabaabaa".AsSpan().Split('b', 2, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabaabaa".AsSpan().Split('b', 2, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aab".AsSpan().Split('b', 1, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aab".AsSpan().Split('b', 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aabab".AsSpan().Split('b', 2, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabab".AsSpan().Split('b', 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInEverythingAfterAndIncludingLastDelimiterBeingCut() + { + AssertEqual( + [['a', 'a']], + "aabaa".AsSpan().Split('b', 1, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aabaabaa".AsSpan().Split('b', 2, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersAtTheEndWithCountEqualDelimiterCountWithRemoveEmptyEntriesOptionResultInNoSpanWithDelimiter() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "aabb".AsSpan().Split('b', 2, options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void TrimEntriesOptionTrimsEverySpan() + { + AssertEqual( + [['a'], ['a']], + " a\tb\na\r".AsSpan().Split('b', StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void WhiteSpaceSpanWithTrimEntriesAndRemoveEmptyEntriesOptionsReturnsNothing() + { + AssertEqual( + [], + " \t".AsSpan().Split('_', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersAtTheEndWithRemoveEmptyEntriesOptionResultInNoEmptySpans() + { + AssertEqual( + [['a', 'a']], + "aabb".AsSpan().Split('b', StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesOptionResultsInNothingIfSourceEmpty() + { + AssertEqual( + [], + "".AsSpan().Split('b', 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesOptionDoesNotRecursivelyRemoveEmptySpansAtTheStart() + { + AssertEqual( + [['b', 'a', 'a']], + "baa".AsSpan().Split('b', 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + AssertEqual( + [['b', 'b', 'a', 'a']], + "bbaa".AsSpan().Split('b', 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountGreaterThanOneWithRemoveEmptyEntriesOptionRecursivelyRemovesEmptySpansAtTheStart() + { + AssertEqual( + [['a', 'a']], + "baa".AsSpan().Split('b', 2, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "bbaa".AsSpan().Split('b', 2, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesAndTrimEntriesOptionsResultsInNothingIfSourceWhiteSpace() + { + AssertEqual( + [], + " \t".AsSpan().Split('b', 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesAndTrimEntriesOptionsDoesNotRecursivelyRemoveWhiteSpaceSpansAtTheStart() + { + AssertEqual( + [['b', '\t', 'a', 'a']], + " b\taa\n".AsSpan().Split('b', 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + AssertEqual( + [['b', '\t', 'b', '\n', 'a', 'a']], + " b\tb\naa\r".AsSpan().Split('b', 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountGreaterThanOneWithRemoveEmptyEntriesAndTrimEntriesOptionsRecursivelyRemovesWhiteSpaceSpansAtTheStart() + { + AssertEqual( + [['a', 'a']], + " b\taa\n".AsSpan().Split('b', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + " b\tb\naa\r".AsSpan().Split('b', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void NegativeCountThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => "aabb".AsSpan().Split('b', -1, StringSplitOptions.None)); + Assert.Throws(() => "aabb".AsSpan().Split('c', -1, StringSplitOptions.None)); + } + + [Fact] + public void UndefinedCountExceedingBehaviourOptionThrowsArgumentException() + { + Assert.Throws(() => "aabb".AsSpan().Split('b', 1, StringSplitOptions.None, (CountExceedingBehaviour)255)); + Assert.Throws(() => "aabb".AsSpan().Split('c', 1, StringSplitOptions.None, (CountExceedingBehaviour)255)); + } + + [Fact] + public void UndefinedStringSplitOptionsThrowsArgumentException() + { + Assert.Throws(() => "aabb".AsSpan().Split('b', (StringSplitOptions)255)); + Assert.Throws(() => "aabb".AsSpan().Split('c', (StringSplitOptions)255)); + } + } + } +} diff --git a/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitStringSequence.cs b/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitStringSequence.cs new file mode 100644 index 0000000..9c19bf1 --- /dev/null +++ b/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitStringSequence.cs @@ -0,0 +1,359 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class ReadOnlySpanSplitTests + { + public sealed class SplitStringSequence + { + [Fact] + public void EnumerationReturnsReadOnlySpans() + { +#pragma warning disable CS0183 // 'is' expression's given expression is always of the provided type + foreach(var span in "aba".AsSpan().Split(['b', 'c'], StringSplitOptions.None)) + { + Assert.True(span is ReadOnlySpan); + } + + foreach(var span in "aba".AsSpan().Split(['b', 'c'], 10, StringSplitOptions.None)) + { + Assert.True(span is ReadOnlySpan); + } +#pragma warning restore CS0183 // 'is' expression's given expression is always of the provided type + } + + [Fact] + public void EmptySourceResultInEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(!options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [[]], + "".AsSpan().Split(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void NoDelimiterOccurenceResultsInNoChange() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().Split(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + + [Fact] + public void EmptyDelimiterSpanResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().Split([], StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualZeroResultsInNothing() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertEqual( + [], + "abba".AsSpan().Split(['a', 'b'], 0, options, countExceedingBehaviour).ToSystemEnumerable() + ); + AssertEqual( + [], + "abba".AsSpan().Split(['b', 'c'], 0, options, countExceedingBehaviour).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void CountEqualOneResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().Split(['a', 'b'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + ["abba".ToCharArray()], + "abba".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersResultInEmptySpan() + { + AssertEqual( + [['a'], [], ['a']], + "abcbca".AsSpan().Split(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a'], ['a']], + "abcbca".AsSpan().Split(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void DelimiterAtTheStartResultInEmptySpan() + { + AssertEqual( + [[], ['a', 'a']], + "bcaa".AsSpan().Split(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheStartWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "bcaa".AsSpan().Split(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void DelimiterAtTheEndResultInEmptySpan() + { + AssertEqual( + [['a', 'a'], []], + "aabc".AsSpan().Split(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "aabc".AsSpan().Split(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void CountEqualDelimiterCountResultsInSpanWithEverythingAfterAndIncludingLastDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'c', 'a', 'a']], + "aabcaa".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'b', 'c', 'a', 'a']], + "aabcaabcaa".AsSpan().Split(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithCountEqualDelimiterCountResultsInSpanWithDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'c']], + "aabc".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'b', 'c']], + "aabcabc".AsSpan().Split(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DefaultCountExceedingBehaviourOptionIsAppendRemainingElements() + { + AssertEqual( + "aabcaa".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabcaa".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aabcaabcaa".AsSpan().Split(['b', 'c'], 2, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabcaabcaa".AsSpan().Split(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aabc".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabc".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aabcabc".AsSpan().Split(['b', 'c'], 2, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabcabc".AsSpan().Split(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInEverythingAfterAndIncludingLastDelimiterBeingCut() + { + AssertEqual( + [['a', 'a']], + "aabcaa".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aabcaabcaa".AsSpan().Split(['b', 'c'], 2, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersAtTheEndWithCountEqualDelimiterCountWithRemoveEmptyEntriesOptionResultInNoSpanWithDelimiter() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "aabcbc".AsSpan().Split(['b', 'c'], 2, options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void TrimEntriesOptionTrimsEverySpan() + { + AssertEqual( + [['a'], ['a']], + " a\tbc\na\r".AsSpan().Split(['b', 'c'], StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void WhiteSpaceSpanWithTrimEntriesAndRemoveEmptyEntriesOptionsReturnsNothing() + { + AssertEqual( + [], + " \t".AsSpan().Split(['b', 'c'], StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersAtTheEndWithRemoveEmptyEntriesOptionResultInNoEmptySpans() + { + AssertEqual( + [['a', 'a']], + "aabcbc".AsSpan().Split(['b', 'c'], StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesOptionResultsInNothingIfSourceEmpty() + { + AssertEqual( + [], + "".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesOptionDoesNotRecursivelyRemoveEmptySpansAtTheStart() + { + AssertEqual( + [['b', 'c', 'a', 'a']], + "bcaa".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + AssertEqual( + [['b', 'c', 'b', 'c', 'a', 'a']], + "bcbcaa".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountGreaterThanOneWithRemoveEmptyEntriesOptionRecursivelyRemovesEmptySpansAtTheStart() + { + AssertEqual( + [['a', 'a']], + "bcaa".AsSpan().Split(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "bcbcaa".AsSpan().Split(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesAndTrimEntriesOptionsResultsInNothingIfSourceWhiteSpace() + { + AssertEqual( + [], + " \t".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesAndTrimEntriesOptionsDoesNotRecursivelyRemoveWhiteSpaceSpansAtTheStart() + { + AssertEqual( + [['b', 'c', '\t', 'a', 'a']], + " bc\taa\n".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + AssertEqual( + [['b', 'c', '\t', 'b', 'c', '\n', 'a', 'a']], + " bc\tbc\naa\r".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountGreaterThanOneWithRemoveEmptyEntriesAndTrimEntriesOptionsRecursivelyRemovesWhiteSpaceSpansAtTheStart() + { + AssertEqual( + [['a', 'a']], + " bc\taa\n".AsSpan().Split(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + " bc\tbc\naa\r".AsSpan().Split(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void NegativeCountThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => "aabc".AsSpan().Split(['b', 'c'], -1, StringSplitOptions.None)); + Assert.Throws(() => "aabb".AsSpan().Split(['b', 'c'], -1, StringSplitOptions.None)); + } + + [Fact] + public void UndefinedCountExceedingBehaviourOptionThrowsArgumentException() + { + Assert.Throws(() => "aabc".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None, (CountExceedingBehaviour)255)); + Assert.Throws(() => "aabb".AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None, (CountExceedingBehaviour)255)); + } + + [Fact] + public void UndefinedStringSplitOptionsThrowsArgumentException() + { + Assert.Throws(() => "aabc".AsSpan().Split(['b', 'c'], (StringSplitOptions)255)); + Assert.Throws(() => "aabb".AsSpan().Split(['b', 'c'], (StringSplitOptions)255)); + } + } + } +} diff --git a/tests/unit-tests/Tests/ReadOnlySpan/String/Remove.cs b/tests/unit-tests/Tests/ReadOnlySpan/String/Remove.cs new file mode 100644 index 0000000..3c48307 --- /dev/null +++ b/tests/unit-tests/Tests/ReadOnlySpan/String/Remove.cs @@ -0,0 +1,12 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class ReadOnlySpanStringTests + { + public sealed class Remove + { + // todo + } + } +} diff --git a/tests/unit-tests/Tests/Span/Linq/Sum.cs b/tests/unit-tests/Tests/Span/Linq/Sum.cs new file mode 100644 index 0000000..a3dca48 --- /dev/null +++ b/tests/unit-tests/Tests/Span/Linq/Sum.cs @@ -0,0 +1,12 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class SpanLinqTests + { + public sealed class Sum + { + // todo + } + } +} diff --git a/tests/unit-tests/Tests/Span/Split/SpanSplitTests.cs b/tests/unit-tests/Tests/Span/Split/SpanSplitTests.cs new file mode 100644 index 0000000..62908d6 --- /dev/null +++ b/tests/unit-tests/Tests/Span/Split/SpanSplitTests.cs @@ -0,0 +1,10 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class SpanSplitTests + { + static readonly IEnumerable stringSplitOptions = GetAllStringSplitOptions(); + static readonly CountExceedingBehaviour[] countExceedingBehaviours = (CountExceedingBehaviour[])Enum.GetValues(typeof(CountExceedingBehaviour)); + } +} diff --git a/tests/unit-tests/Tests/Span/Split/Split.cs b/tests/unit-tests/Tests/Span/Split/Split.cs new file mode 100644 index 0000000..766ab20 --- /dev/null +++ b/tests/unit-tests/Tests/Span/Split/Split.cs @@ -0,0 +1,174 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class SpanSplitTests + { + public sealed class Split + { + [Fact] + public void EnumerationReturnsSpans() + { +#pragma warning disable CS0183 // 'is' expression's given expression is always of the provided type + foreach(var span in "aba".ToCharArray().AsSpan().Split('b')) + { + Assert.True(span is Span); + } + + foreach(var span in "aba".ToCharArray().AsSpan().Split('b', 10)) + { + Assert.True(span is Span); + } +#pragma warning restore CS0183 // 'is' expression's given expression is always of the provided type + } + + [Fact] + public void EmptySourceResultInEmptySpan() + { + AssertEqual( + [[]], + "".ToCharArray().AsSpan().Split('a').ToSystemEnumerable() + ); + } + + [Fact] + public void NoDelimiterOccurenceResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().Split('c').ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualZeroResultsInNothing() + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertEqual( + [], + "abba".ToCharArray().AsSpan().Split('a', 0, countExceedingBehaviour).ToSystemEnumerable() + ); + AssertEqual( + [], + "abba".ToCharArray().AsSpan().Split('c', 0, countExceedingBehaviour).ToSystemEnumerable() + ); + } + } + + [Fact] + public void CountEqualOneResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().Split('a', 1).ToSystemEnumerable() + ); + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().Split('c', 1).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersResultInEmptySpan() + { + AssertEqual( + [['a'], [], ['a']], + "abba".ToCharArray().AsSpan().Split('b').ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheStartResultInEmptySpan() + { + AssertEqual( + [[], ['a', 'a']], + "baa".ToCharArray().AsSpan().Split('b').ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndResultInEmptySpan() + { + AssertEqual( + [['a', 'a'], []], + "aab".ToCharArray().AsSpan().Split('b').ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInSpanWithEverythingAfterAndIncludingLastDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'a', 'a']], + "aabaa".ToCharArray().AsSpan().Split('b', 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'b', 'a', 'a']], + "aabaabaa".ToCharArray().AsSpan().Split('b', 2).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithCountEqualDelimiterCountResultsInSpanWithDelimiter() + { + AssertEqual( + [['a', 'a', 'b']], + "aab".ToCharArray().AsSpan().Split('b', 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'b']], + "aabab".ToCharArray().AsSpan().Split('b', 2).ToSystemEnumerable() + ); + } + + [Fact] + public void DefaultCountExceedingBehaviourOptionIsAppendRemainingElements() + { + AssertEqual( + "aab".ToCharArray().AsSpan().Split('b', 1, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aab".ToCharArray().AsSpan().Split('b', 1).ToSystemEnumerable() + ); + AssertEqual( + "aabab".ToCharArray().AsSpan().Split('b', 2, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabab".ToCharArray().AsSpan().Split('b', 2).ToSystemEnumerable() + ); + AssertEqual( + "aabaa".ToCharArray().AsSpan().Split('b', 1, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabaa".ToCharArray().AsSpan().Split('b', 1).ToSystemEnumerable() + ); + AssertEqual( + "aabaabaa".ToCharArray().AsSpan().Split('b', 2, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabaabaa".ToCharArray().AsSpan().Split('b', 2).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInEverythingAfterAndIncludingLastDelimiterBeingCut() + { + AssertEqual( + [['a', 'a']], + "aabaa".ToCharArray().AsSpan().Split('b', 1, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aabaabaa".ToCharArray().AsSpan().Split('b', 2, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + } + + [Fact] + public void NegativeCountThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => "aabb".ToCharArray().AsSpan().Split('b', -1)); + Assert.Throws(() => "aabb".ToCharArray().AsSpan().Split('c', -1)); + } + + [Fact] + public void UndefinedCountExceedingBehaviourOptionThrowsArgumentException() + { + Assert.Throws(() => "aabb".ToCharArray().AsSpan().Split('b', 1, (CountExceedingBehaviour)255)); + Assert.Throws(() => "aabb".ToCharArray().AsSpan().Split('c', 1, (CountExceedingBehaviour)255)); + } + } + } +} diff --git a/tests/unit-tests/Tests/Span/Split/SplitAny.cs b/tests/unit-tests/Tests/Span/Split/SplitAny.cs new file mode 100644 index 0000000..341c11e --- /dev/null +++ b/tests/unit-tests/Tests/Span/Split/SplitAny.cs @@ -0,0 +1,219 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class SpanSplitTests + { + public sealed class SplitAny + { + [Fact] + public void EnumerationReturnsSpans() + { +#pragma warning disable CS0183 // 'is' expression's given expression is always of the provided type + foreach(var span in "abaca".ToCharArray().AsSpan().SplitAny(['b', 'c'])) + { + Assert.True(span is Span); + } + + foreach(var span in "abaca".ToCharArray().AsSpan().SplitAny(['b', 'c'], 10)) + { + Assert.True(span is Span); + } +#pragma warning restore CS0183 // 'is' expression's given expression is always of the provided type + } + + [Fact] + public void EmptySourceResultInEmptySpan() + { + AssertEqual( + [[]], + "".ToCharArray().AsSpan().SplitAny(['a', 'b']).ToSystemEnumerable() + ); + } + + [Fact] + public void NoDelimiterOccurenceResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().SplitAny(['c', 'd']).ToSystemEnumerable() + ); + } + + [Fact] + public void EmptyDelimiterSpanResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().SplitAny([]).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualZeroResultsInNothing() + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertEqual( + [], + "abba".ToCharArray().AsSpan().SplitAny(['a', 'b'], 0, countExceedingBehaviour).ToSystemEnumerable() + ); + AssertEqual( + [], + "abba".ToCharArray().AsSpan().SplitAny(['c', 'd'], 0, countExceedingBehaviour).ToSystemEnumerable() + ); + } + } + + [Fact] + public void CountEqualOneResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().SplitAny(['a', 'b'], 1).ToSystemEnumerable() + ); + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().SplitAny(['c', 'd'], 1).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersResultInEmptySpan() + { + AssertEqual( + [['a'], [], ['a']], + "abba".ToCharArray().AsSpan().SplitAny(['b', 'c']).ToSystemEnumerable() + ); + AssertEqual( + [['a'], [], ['a']], + "abca".ToCharArray().AsSpan().SplitAny(['b', 'c']).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheStartResultInEmptySpan() + { + AssertEqual( + [[], ['a', 'a']], + "baa".ToCharArray().AsSpan().SplitAny(['b', 'c']).ToSystemEnumerable() + ); + AssertEqual( + [[], ['a', 'a']], + "caa".ToCharArray().AsSpan().SplitAny(['b', 'c']).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndResultInEmptySpan() + { + AssertEqual( + [['a', 'a'], []], + "aab".ToCharArray().AsSpan().SplitAny(['b', 'c']).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], []], + "aac".ToCharArray().AsSpan().SplitAny(['b', 'c']).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInSpanWithEverythingAfterAndIncludingLastDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'a', 'a']], + "aabaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a', 'c', 'a', 'a']], + "aacaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'c', 'a', 'a']], + "aabaacaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'b', 'a', 'a']], + "aacaabaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithCountEqualDelimiterCountResultsInSpanWithDelimiter() + { + AssertEqual( + [['a', 'a', 'b']], + "aab".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a', 'c']], + "aac".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'c']], + "aabac".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'b']], + "aacab".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2).ToSystemEnumerable() + ); + } + + [Fact] + public void DefaultCountExceedingBehaviourOptionIsAppendRemainingElements() + { + AssertEqual( + "aab".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aab".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + "aabac".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabac".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2).ToSystemEnumerable() + ); + AssertEqual( + "aacaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aacaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + "aacaabaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aacaabaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInEverythingAfterAndIncludingLastDelimiterBeingCut() + { + AssertEqual( + [['a', 'a']], + "aabaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "aacaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aabaacaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aacaabaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + } + + [Fact] + public void NegativeCountThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => "aabc".ToCharArray().AsSpan().SplitAny(['b', 'c'], -1)); + Assert.Throws(() => "aabc".ToCharArray().AsSpan().SplitAny(['d', 'e'], -1)); + } + + [Fact] + public void UndefinedCountExceedingBehaviourOptionThrowsArgumentException() + { + Assert.Throws(() => "aabb".ToCharArray().AsSpan().SplitAny(['d', 'e'], 1, (CountExceedingBehaviour)255)); + Assert.Throws(() => "aabb".ToCharArray().AsSpan().SplitAny(['d', 'e'], 1, (CountExceedingBehaviour)255)); + } + } + } +} diff --git a/tests/unit-tests/Tests/Span/Split/SplitAnyString.cs b/tests/unit-tests/Tests/Span/Split/SplitAnyString.cs new file mode 100644 index 0000000..892c3be --- /dev/null +++ b/tests/unit-tests/Tests/Span/Split/SplitAnyString.cs @@ -0,0 +1,442 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class SpanSplitTests + { + public sealed class SplitAnyString + { + [Fact] + public void EnumerationReturnsSpans() + { +#pragma warning disable CS0183 // 'is' expression's given expression is always of the provided type + foreach(var span in "abaca".ToCharArray().AsSpan().SplitAny(['b', 'c'], StringSplitOptions.None)) + { + Assert.True(span is Span); + } + + foreach(var span in "abaca".ToCharArray().AsSpan().SplitAny(['b', 'c'], 10, StringSplitOptions.None)) + { + Assert.True(span is Span); + } +#pragma warning restore CS0183 // 'is' expression's given expression is always of the provided type + } + + [Fact] + public void EmptySourceResultInEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(!options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [[]], + "".ToCharArray().AsSpan().SplitAny(['a', 'b'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void NoDelimiterOccurenceResultsInNoChange() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().SplitAny(['c', 'd'], options).ToSystemEnumerable() + ); + } + } + + [Fact] + public void WhiteSpaceCharactersAssumedWhenDelimitersSpanIsEmpty() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertEqual( + [['a'], ['b'], ['c'], ['d']], + "a b c d".ToCharArray().AsSpan().SplitAny([], StringSplitOptions.None).ToSystemEnumerable() + ); + } + } + + [Fact] + public void CountEqualZeroResultsInNothing() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertEqual( + [], + "abba".ToCharArray().AsSpan().SplitAny(['a', 'b'], 0, options, countExceedingBehaviour).ToSystemEnumerable() + ); + AssertEqual( + [], + "abba".ToCharArray().AsSpan().SplitAny(['c', 'd'], 0, options, countExceedingBehaviour).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void CountEqualOneResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().SplitAny(['a', 'b'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().SplitAny(['c', 'd'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersResultInEmptySpan() + { + AssertEqual( + [['a'], [], ['a']], + "abba".ToCharArray().AsSpan().SplitAny(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a'], [], ['a']], + "abca".ToCharArray().AsSpan().SplitAny(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a'], ['a']], + "abba".ToCharArray().AsSpan().SplitAny(['b', 'c'], options).ToSystemEnumerable() + ); + AssertEqual( + [['a'], ['a']], + "abca".ToCharArray().AsSpan().SplitAny(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void DelimiterAtStartEndResultInEmptySpan() + { + AssertEqual( + [[], ['a', 'a']], + "baa".ToCharArray().AsSpan().SplitAny(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [[], ['a', 'a']], + "caa".ToCharArray().AsSpan().SplitAny(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheStartWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "baa".ToCharArray().AsSpan().SplitAny(['b', 'c'], options).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "caa".ToCharArray().AsSpan().SplitAny(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void DelimiterAtTheEndResultInEmptySpan() + { + AssertEqual( + [['a', 'a'], []], + "aab".ToCharArray().AsSpan().SplitAny(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], []], + "aac".ToCharArray().AsSpan().SplitAny(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "aab".ToCharArray().AsSpan().SplitAny(['b', 'c'], options).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "aac".ToCharArray().AsSpan().SplitAny(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void CountEqualDelimiterCountResultsInSpanWithEverythingAfterAndIncludingLastDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'a', 'a']], + "aabaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a', 'c', 'a', 'a']], + "aacaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'c', 'a', 'a']], + "aabaacaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'b', 'a', 'a']], + "aacaabaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithCountEqualDelimiterCountResultsInSpanWithDelimiter() + { + AssertEqual( + [['a', 'a', 'b']], + "aab".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a', 'c']], + "aac".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'c']], + "aabac".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'b']], + "aacab".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DefaultCountExceedingBehaviourOptionIsAppendRemainingElements() + { + AssertEqual( + "aabaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aabaabaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabaabaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aab".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aab".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aabab".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabab".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInEverythingAfterAndIncludingLastDelimiterBeingCut() + { + AssertEqual( + [['a', 'a']], + "aabaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "aacaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aabaacaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aacaabaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersAtTheEndWithCountEqualDelimiterCountWithRemoveEmptyEntriesOptionResultInNoSpanWithDelimiter() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "aabb".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, options).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "aabc".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void TrimEntriesOptionTrimsLastSpan() + { + AssertEqual( + [['a'], [], ['a']], + " a b b a ".ToCharArray().AsSpan().SplitAny(['b', 'c'], StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a'], [], ['a']], + " a b c a ".ToCharArray().AsSpan().SplitAny(['b', 'c'], StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void WhiteSpaceSpanWithTrimEntriesAndRemoveEmptyEntriesOptionsReturnsNothing() + { + AssertEqual( + [], + " \t".ToCharArray().AsSpan().SplitAny(['_', '!'], StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersAtTheEndWithRemoveEmptyEntriesOptionResultInNoEmptySpans() + { + AssertEqual( + [['a', 'a']], + "aabb".ToCharArray().AsSpan().SplitAny(['b', 'c'], StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "aabc".ToCharArray().AsSpan().SplitAny(['b', 'c'], StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesOptionResultsInNothingIfSourceEmpty() + { + AssertEqual( + [], + "".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesOptionDoesNotRecursivelyRemoveEmptySpansAtTheStart() + { + AssertEqual( + [['b', 'a', 'a']], + "baa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + AssertEqual( + [['b', 'c', 'a', 'a']], + "bcaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountGreaterThanOneWithRemoveEmptyEntriesOptionRecursivelyRemovesEmptySpansAtTheStart() + { + AssertEqual( + [['a', 'a']], + "baa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "bcaa".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesAndTrimEntriesOptionsResultsInNothingIfSourceWhiteSpace() + { + AssertEqual( + [], + " \t".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesAndTrimEntriesOptionsDoesNotRecursivelyRemoveWhiteSpaceSpansAtTheStart() + { + AssertEqual( + [['b', '\t', 'a', 'a']], + " b\taa\n".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + AssertEqual( + [['b', '\t', 'c', '\n', 'a', 'a']], + " b\tc\naa\r".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountGreaterThanOneWithRemoveEmptyEntriesAndTrimEntriesOptionsRecursivelyRemovesWhiteSpaceSpansAtTheStart() + { + AssertEqual( + [['a', 'a']], + " b\taa\n".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + " b\tc\naa\r".ToCharArray().AsSpan().SplitAny(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void EmptyDelimiterSpanResultsSameAsCountEqualOne() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertEqual( + "".ToCharArray().AsSpan().SplitAny([], 1, options).ToSystemEnumerable(), + "".ToCharArray().AsSpan().SplitAny([], options).ToSystemEnumerable() + ); + AssertEqual( + " ".ToCharArray().AsSpan().SplitAny([], 1, options).ToSystemEnumerable(), + " ".ToCharArray().AsSpan().SplitAny([], options).ToSystemEnumerable() + ); + AssertEqual( + " aabb ".ToCharArray().AsSpan().SplitAny([], 1, options).ToSystemEnumerable(), + " aabb ".ToCharArray().AsSpan().SplitAny([], options).ToSystemEnumerable() + ); + } + } + + [Fact] + public void NegativeCountThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => "aabc".ToCharArray().AsSpan().SplitAny(['b', 'c'], -1, StringSplitOptions.None)); + Assert.Throws(() => "aabc".ToCharArray().AsSpan().SplitAny(['d', 'e'], -1, StringSplitOptions.None)); + } + + [Fact] + public void UndefinedCountExceedingBehaviourOptionThrowsArgumentException() + { + Assert.Throws(() => "aabc".ToCharArray().AsSpan().SplitAny(['b', 'c'], 1, StringSplitOptions.None, (CountExceedingBehaviour)255)); + Assert.Throws(() => "aabc".ToCharArray().AsSpan().SplitAny(['d', 'e'], 1, StringSplitOptions.None, (CountExceedingBehaviour)255)); + } + + [Fact] + public void UndefinedStringSplitOptionsThrowsArgumentException() + { + Assert.Throws(() => "aabc".ToCharArray().AsSpan().SplitAny(['b', 'c'], (StringSplitOptions)255)); + Assert.Throws(() => "aabc".ToCharArray().AsSpan().SplitAny(['d', 'e'], (StringSplitOptions)255)); + } + } + } +} diff --git a/tests/unit-tests/Tests/Span/Split/SplitSequence.cs b/tests/unit-tests/Tests/Span/Split/SplitSequence.cs new file mode 100644 index 0000000..e7383b9 --- /dev/null +++ b/tests/unit-tests/Tests/Span/Split/SplitSequence.cs @@ -0,0 +1,183 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class SpanSplitTests + { + public sealed class SplitSequence + { + [Fact] + public void EnumerationReturnsSpans() + { +#pragma warning disable CS0183 // 'is' expression's given expression is always of the provided type + foreach(var span in "abca".ToCharArray().AsSpan().Split(['b', 'c'])) + { + Assert.True(span is Span); + } + + foreach(var span in "abca".ToCharArray().AsSpan().Split(['b', 'c'], 10)) + { + Assert.True(span is Span); + } +#pragma warning restore CS0183 // 'is' expression's given expression is always of the provided type + } + + [Fact] + public void EmptySourceResultInEmptySpan() + { + AssertEqual( + [[]], + "".ToCharArray().AsSpan().Split(['a', 'b']).ToSystemEnumerable() + ); + } + + [Fact] + public void NoDelimiterOccurenceResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().Split(['b', 'c']).ToSystemEnumerable() + ); + } + + [Fact] + public void EmptyDelimiterSpanResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().Split([]).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualZeroResultsInNothing() + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertEqual( + [], + "abba".ToCharArray().AsSpan().Split(['a', 'b'], 0, countExceedingBehaviour).ToSystemEnumerable() + ); + AssertEqual( + [], + "abba".ToCharArray().AsSpan().Split(['b', 'c'], 0, countExceedingBehaviour).ToSystemEnumerable() + ); + } + } + + [Fact] + public void CountEqualOneResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().Split(['a', 'b'], 1).ToSystemEnumerable() + ); + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().Split(['b', 'c'], 1).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersResultInEmptySpan() + { + AssertEqual( + [['a'], [], ['a']], + "abcbca".ToCharArray().AsSpan().Split(['b', 'c']).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheStartResultInEmptySpan() + { + AssertEqual( + [[], ['a', 'a']], + "bcaa".ToCharArray().AsSpan().Split(['b', 'c']).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndResultInEmptySpan() + { + AssertEqual( + [['a', 'a'], []], + "aabc".ToCharArray().AsSpan().Split(['b', 'c']).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInSpanWithEverythingAfterAndIncludingLastDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'c', 'a', 'a']], + "aabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'b', 'c', 'a', 'a']], + "aabcaabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 2).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithCountEqualDelimiterCountResultsInSpanWithDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'c']], + "aabc".ToCharArray().AsSpan().Split(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'b', 'c']], + "aabcabc".ToCharArray().AsSpan().Split(['b', 'c'], 2).ToSystemEnumerable() + ); + } + + [Fact] + public void DefaultCountExceedingBehaviourOptionIsAppendRemainingElements() + { + AssertEqual( + "aabc".ToCharArray().AsSpan().Split(['b', 'c'], 1, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabc".ToCharArray().AsSpan().Split(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + "aabcabc".ToCharArray().AsSpan().Split(['b', 'c'], 2, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabcabc".ToCharArray().AsSpan().Split(['b', 'c'], 2).ToSystemEnumerable() + ); + AssertEqual( + "aabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 1, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 1).ToSystemEnumerable() + ); + AssertEqual( + "aabcaabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 2, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabcaabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 2).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInEverythingAfterAndIncludingLastDelimiterBeingCut() + { + AssertEqual( + [['a', 'a']], + "aabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 1, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aabcaabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 2, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + } + + [Fact] + public void NegativeCountThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => "aabcbc".ToCharArray().AsSpan().Split(['b', 'c'], -1)); + Assert.Throws(() => "aabcbc".ToCharArray().AsSpan().Split(['c', 'd'], -1)); + } + + [Fact] + public void UndefinedCountExceedingBehaviourOptionThrowsArgumentException() + { + Assert.Throws(() => "aabcbc".ToCharArray().AsSpan().Split(['b', 'c'], 1, (CountExceedingBehaviour)255)); + Assert.Throws(() => "aabcbc".ToCharArray().AsSpan().Split(['c', 'd'], 1, (CountExceedingBehaviour)255)); + } + } + } +} diff --git a/tests/unit-tests/Tests/Span/Split/SplitString.cs b/tests/unit-tests/Tests/Span/Split/SplitString.cs new file mode 100644 index 0000000..6569130 --- /dev/null +++ b/tests/unit-tests/Tests/Span/Split/SplitString.cs @@ -0,0 +1,350 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class SpanSplitTests + { + public sealed class SplitString + { + [Fact] + public void EnumerationReturnsSpans() + { +#pragma warning disable CS0183 // 'is' expression's given expression is always of the provided type + foreach(var span in "aba".ToCharArray().AsSpan().Split('b', StringSplitOptions.None)) + { + Assert.True(span is Span); + } + + foreach(var span in "aba".ToCharArray().AsSpan().Split('b', 10, StringSplitOptions.None)) + { + Assert.True(span is Span); + } +#pragma warning restore CS0183 // 'is' expression's given expression is always of the provided type + } + + [Fact] + public void EmptySourceResultInEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(!options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [[]], + "".ToCharArray().AsSpan().Split('a', options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void NoDelimiterOccurenceResultsInNoChange() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().Split('c', options).ToSystemEnumerable() + ); + } + } + + [Fact] + public void CountEqualZeroResultsInNothing() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertEqual( + [], + "abba".ToCharArray().AsSpan().Split('a', 0, options, countExceedingBehaviour).ToSystemEnumerable() + ); + AssertEqual( + [], + "abba".ToCharArray().AsSpan().Split('c', 0, options, countExceedingBehaviour).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void CountEqualOneResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().Split('a', 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().Split('c', 1, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersResultInEmptySpan() + { + AssertEqual( + [['a'], [], ['a']], + "abba".ToCharArray().AsSpan().Split('b', StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a'], ['a']], + "abba".ToCharArray().AsSpan().Split('b', options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void DelimiterAtTheStartResultInEmptySpan() + { + AssertEqual( + [[], ['a', 'a']], + "baa".ToCharArray().AsSpan().Split('b', StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheStartWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "baa".ToCharArray().AsSpan().Split('b', options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void DelimiterAtTheEndResultInEmptySpan() + { + AssertEqual( + [['a', 'a'], []], + "aab".ToCharArray().AsSpan().Split('b', StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "aab".ToCharArray().AsSpan().Split('b', options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void CountEqualDelimiterCountResultsInSpanWithEverythingAfterAndIncludingLastDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'a', 'a']], + "aabaa".ToCharArray().AsSpan().Split('b', 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'b', 'a', 'a']], + "aabaabaa".ToCharArray().AsSpan().Split('b', 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithCountEqualDelimiterCountResultsInSpanWithDelimiter() + { + AssertEqual( + [['a', 'a', 'b']], + "aab".ToCharArray().AsSpan().Split('b', 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'b']], + "aabab".ToCharArray().AsSpan().Split('b', 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DefaultCountExceedingBehaviourOptionIsAppendRemainingElements() + { + AssertEqual( + "aabaa".ToCharArray().AsSpan().Split('b', 1, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabaa".ToCharArray().AsSpan().Split('b', 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aabaabaa".ToCharArray().AsSpan().Split('b', 2, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabaabaa".ToCharArray().AsSpan().Split('b', 2, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aab".ToCharArray().AsSpan().Split('b', 1, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aab".ToCharArray().AsSpan().Split('b', 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aabab".ToCharArray().AsSpan().Split('b', 2, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabab".ToCharArray().AsSpan().Split('b', 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInEverythingAfterAndIncludingLastDelimiterBeingCut() + { + AssertEqual( + [['a', 'a']], + "aabaa".ToCharArray().AsSpan().Split('b', 1, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aabaabaa".ToCharArray().AsSpan().Split('b', 2, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersAtTheEndWithCountEqualDelimiterCountWithRemoveEmptyEntriesOptionResultInNoSpanWithDelimiter() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "aabb".ToCharArray().AsSpan().Split('b', 2, options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void TrimEntriesOptionTrimsEverySpan() + { + AssertEqual( + [['a'], ['a']], + " a\tb\na\r".ToCharArray().AsSpan().Split('b', StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void WhiteSpaceSpanWithTrimEntriesAndRemoveEmptyEntriesOptionsReturnsNothing() + { + AssertEqual( + [], + " \t".ToCharArray().AsSpan().Split('_', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersAtTheEndWithRemoveEmptyEntriesOptionResultInNoEmptySpans() + { + AssertEqual( + [['a', 'a']], + "aabb".ToCharArray().AsSpan().Split('b', StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesOptionResultsInNothingIfSourceEmpty() + { + AssertEqual( + [], + "".ToCharArray().AsSpan().Split('b', 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesOptionDoesNotRecursivelyRemoveEmptySpansAtTheStart() + { + AssertEqual( + [['b', 'a', 'a']], + "baa".ToCharArray().AsSpan().Split('b', 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + AssertEqual( + [['b', 'b', 'a', 'a']], + "bbaa".ToCharArray().AsSpan().Split('b', 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountGreaterThanOneWithRemoveEmptyEntriesOptionRecursivelyRemovesEmptySpansAtTheStart() + { + AssertEqual( + [['a', 'a']], + "baa".ToCharArray().AsSpan().Split('b', 2, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "bbaa".ToCharArray().AsSpan().Split('b', 2, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesAndTrimEntriesOptionsResultsInNothingIfSourceWhiteSpace() + { + AssertEqual( + [], + " \t".ToCharArray().AsSpan().Split('b', 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesAndTrimEntriesOptionsDoesNotRecursivelyRemoveWhiteSpaceSpansAtTheStart() + { + AssertEqual( + [['b', '\t', 'a', 'a']], + " b\taa\n".ToCharArray().AsSpan().Split('b', 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + AssertEqual( + [['b', '\t', 'b', '\n', 'a', 'a']], + " b\tb\naa\r".ToCharArray().AsSpan().Split('b', 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountGreaterThanOneWithRemoveEmptyEntriesAndTrimEntriesOptionsRecursivelyRemovesWhiteSpaceSpansAtTheStart() + { + AssertEqual( + [['a', 'a']], + " b\taa\n".ToCharArray().AsSpan().Split('b', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + " b\tb\naa\r".ToCharArray().AsSpan().Split('b', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void NegativeCountThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => "aabb".ToCharArray().AsSpan().Split('b', -1, StringSplitOptions.None)); + Assert.Throws(() => "aabb".ToCharArray().AsSpan().Split('c', -1, StringSplitOptions.None)); + } + + [Fact] + public void UndefinedCountExceedingBehaviourOptionThrowsArgumentException() + { + Assert.Throws(() => "aabb".ToCharArray().AsSpan().Split('b', 1, StringSplitOptions.None, (CountExceedingBehaviour)255)); + Assert.Throws(() => "aabb".ToCharArray().AsSpan().Split('c', 1, StringSplitOptions.None, (CountExceedingBehaviour)255)); + } + + [Fact] + public void UndefinedStringSplitOptionsThrowsArgumentException() + { + Assert.Throws(() => "aabb".ToCharArray().AsSpan().Split('b', (StringSplitOptions)255)); + Assert.Throws(() => "aabb".ToCharArray().AsSpan().Split('c', (StringSplitOptions)255)); + } + } + } +} diff --git a/tests/unit-tests/Tests/Span/Split/SplitStringSequence.cs b/tests/unit-tests/Tests/Span/Split/SplitStringSequence.cs new file mode 100644 index 0000000..4ba1080 --- /dev/null +++ b/tests/unit-tests/Tests/Span/Split/SplitStringSequence.cs @@ -0,0 +1,359 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class SpanSplitTests + { + public sealed class SplitStringSequence + { + [Fact] + public void EnumerationReturnsSpans() + { +#pragma warning disable CS0183 // 'is' expression's given expression is always of the provided type + foreach(var span in "aba".ToCharArray().AsSpan().Split(['b', 'c'], StringSplitOptions.None)) + { + Assert.True(span is Span); + } + + foreach(var span in "aba".ToCharArray().AsSpan().Split(['b', 'c'], 10, StringSplitOptions.None)) + { + Assert.True(span is Span); + } +#pragma warning restore CS0183 // 'is' expression's given expression is always of the provided type + } + + [Fact] + public void EmptySourceResultInEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(!options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [[]], + "".ToCharArray().AsSpan().Split(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void NoDelimiterOccurenceResultsInNoChange() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().Split(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + + [Fact] + public void EmptyDelimiterSpanResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().Split([], StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualZeroResultsInNothing() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + foreach(CountExceedingBehaviour countExceedingBehaviour in countExceedingBehaviours) + { + AssertEqual( + [], + "abba".ToCharArray().AsSpan().Split(['a', 'b'], 0, options, countExceedingBehaviour).ToSystemEnumerable() + ); + AssertEqual( + [], + "abba".ToCharArray().AsSpan().Split(['b', 'c'], 0, options, countExceedingBehaviour).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void CountEqualOneResultsInNoChange() + { + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().Split(['a', 'b'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + ["abba".ToCharArray()], + "abba".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersResultInEmptySpan() + { + AssertEqual( + [['a'], [], ['a']], + "abcbca".ToCharArray().AsSpan().Split(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a'], ['a']], + "abcbca".ToCharArray().AsSpan().Split(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void DelimiterAtTheStartResultInEmptySpan() + { + AssertEqual( + [[], ['a', 'a']], + "bcaa".ToCharArray().AsSpan().Split(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheStartWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "bcaa".ToCharArray().AsSpan().Split(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void DelimiterAtTheEndResultInEmptySpan() + { + AssertEqual( + [['a', 'a'], []], + "aabc".ToCharArray().AsSpan().Split(['b', 'c'], StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithRemoveEmptyEntriesOptionResultInNoEmptySpan() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "aabc".ToCharArray().AsSpan().Split(['b', 'c'], options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void CountEqualDelimiterCountResultsInSpanWithEverythingAfterAndIncludingLastDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'c', 'a', 'a']], + "aabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a', 'b', 'c', 'a', 'a']], + "aabcaabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DelimiterAtTheEndWithCountEqualDelimiterCountResultsInSpanWithDelimiter() + { + AssertEqual( + [['a', 'a', 'b', 'c']], + "aabc".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'b', 'c']], + "aabcabc".ToCharArray().AsSpan().Split(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void DefaultCountExceedingBehaviourOptionIsAppendRemainingElements() + { + AssertEqual( + "aabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aabcaabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 2, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabcaabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aabc".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabc".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None).ToSystemEnumerable() + ); + AssertEqual( + "aabcabc".ToCharArray().AsSpan().Split(['b', 'c'], 2, StringSplitOptions.None, CountExceedingBehaviour.AppendRemainingElements).ToSystemEnumerable(), + "aabcabc".ToCharArray().AsSpan().Split(['b', 'c'], 2, StringSplitOptions.None).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualDelimiterCountResultsInEverythingAfterAndIncludingLastDelimiterBeingCut() + { + AssertEqual( + [['a', 'a']], + "aabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a'], ['a', 'a']], + "aabcaabcaa".ToCharArray().AsSpan().Split(['b', 'c'], 2, StringSplitOptions.None, CountExceedingBehaviour.CutRemainingElements).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersAtTheEndWithCountEqualDelimiterCountWithRemoveEmptyEntriesOptionResultInNoSpanWithDelimiter() + { + foreach(StringSplitOptions options in stringSplitOptions) + { + if(options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) + { + AssertEqual( + [['a', 'a']], + "aabcbc".ToCharArray().AsSpan().Split(['b', 'c'], 2, options).ToSystemEnumerable() + ); + } + } + } + + [Fact] + public void TrimEntriesOptionTrimsEverySpan() + { + AssertEqual( + [['a'], ['a']], + " a\tbc\na\r".ToCharArray().AsSpan().Split(['b', 'c'], StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void WhiteSpaceSpanWithTrimEntriesAndRemoveEmptyEntriesOptionsReturnsNothing() + { + AssertEqual( + [], + " \t".ToCharArray().AsSpan().Split(['b', 'c'], StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void ConsecutiveDelimitersAtTheEndWithRemoveEmptyEntriesOptionResultInNoEmptySpans() + { + AssertEqual( + [['a', 'a']], + "aabcbc".ToCharArray().AsSpan().Split(['b', 'c'], StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesOptionResultsInNothingIfSourceEmpty() + { + AssertEqual( + [], + "".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesOptionDoesNotRecursivelyRemoveEmptySpansAtTheStart() + { + AssertEqual( + [['b', 'c', 'a', 'a']], + "bcaa".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + AssertEqual( + [['b', 'c', 'b', 'c', 'a', 'a']], + "bcbcaa".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountGreaterThanOneWithRemoveEmptyEntriesOptionRecursivelyRemovesEmptySpansAtTheStart() + { + AssertEqual( + [['a', 'a']], + "bcaa".ToCharArray().AsSpan().Split(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + "bcbcaa".ToCharArray().AsSpan().Split(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesAndTrimEntriesOptionsResultsInNothingIfSourceWhiteSpace() + { + AssertEqual( + [], + " \t".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountEqualOneWithRemoveEmptyEntriesAndTrimEntriesOptionsDoesNotRecursivelyRemoveWhiteSpaceSpansAtTheStart() + { + AssertEqual( + [['b', 'c', '\t', 'a', 'a']], + " bc\taa\n".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + AssertEqual( + [['b', 'c', '\t', 'b', 'c', '\n', 'a', 'a']], + " bc\tbc\naa\r".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void CountGreaterThanOneWithRemoveEmptyEntriesAndTrimEntriesOptionsRecursivelyRemovesWhiteSpaceSpansAtTheStart() + { + AssertEqual( + [['a', 'a']], + " bc\taa\n".ToCharArray().AsSpan().Split(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + AssertEqual( + [['a', 'a']], + " bc\tbc\naa\r".ToCharArray().AsSpan().Split(['b', 'c'], 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToSystemEnumerable() + ); + } + + [Fact] + public void NegativeCountThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => "aabc".ToCharArray().AsSpan().Split(['b', 'c'], -1, StringSplitOptions.None)); + Assert.Throws(() => "aabb".ToCharArray().AsSpan().Split(['b', 'c'], -1, StringSplitOptions.None)); + } + + [Fact] + public void UndefinedCountExceedingBehaviourOptionThrowsArgumentException() + { + Assert.Throws(() => "aabc".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None, (CountExceedingBehaviour)255)); + Assert.Throws(() => "aabb".ToCharArray().AsSpan().Split(['b', 'c'], 1, StringSplitOptions.None, (CountExceedingBehaviour)255)); + } + + [Fact] + public void UndefinedStringSplitOptionsThrowsArgumentException() + { + Assert.Throws(() => "aabc".ToCharArray().AsSpan().Split(['b', 'c'], (StringSplitOptions)255)); + Assert.Throws(() => "aabb".ToCharArray().AsSpan().Split(['b', 'c'], (StringSplitOptions)255)); + } + } + } +} diff --git a/tests/unit-tests/Tests/Span/String/Remove.cs b/tests/unit-tests/Tests/Span/String/Remove.cs new file mode 100644 index 0000000..cb40594 --- /dev/null +++ b/tests/unit-tests/Tests/Span/String/Remove.cs @@ -0,0 +1,12 @@ +using static SpanExtensions.Tests.UnitTests.TestHelper; + +namespace SpanExtensions.Tests.UnitTests +{ + public static partial class SpanStringTests + { + public sealed class Remove + { + // todo + } + } +} diff --git a/tests/unit-tests/ToSystemEnumerableExtensions.cs b/tests/unit-tests/ToSystemEnumerableExtensions.cs new file mode 100644 index 0000000..034ba2a --- /dev/null +++ b/tests/unit-tests/ToSystemEnumerableExtensions.cs @@ -0,0 +1,155 @@ +using SpanExtensions.Enumerators; + +namespace SpanExtensions.Tests.UnitTests +{ + /// + /// Extension methods to convert enumerators into . + /// This obviously defeats the purpose of using spans. This is for testing only. + /// + public static class ToSystemEnumerableExtensions + { + public static IEnumerable> ToSystemEnumerable(this SpanSplitEnumerator spanEnumerator) where T : IEquatable + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitWithCountEnumerator spanEnumerator) where T : IEquatable + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitStringSplitOptionsEnumerator spanEnumerator) + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitStringSplitOptionsWithCountEnumerator spanEnumerator) + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitAnyEnumerator spanEnumerator) where T : IEquatable + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitAnyWithCountEnumerator spanEnumerator) where T : IEquatable + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitAnyStringSplitOptionsEnumerator spanEnumerator) + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitAnyStringSplitOptionsWithCountEnumerator spanEnumerator) + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitSequenceEnumerator spanEnumerator) where T : IEquatable + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitSequenceWithCountEnumerator spanEnumerator) where T : IEquatable + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitSequenceStringSplitOptionsEnumerator spanEnumerator) + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + + public static IEnumerable> ToSystemEnumerable(this SpanSplitSequenceStringSplitOptionsWithCountEnumerator spanEnumerator) + { + List list = []; + + foreach(ReadOnlySpan element in spanEnumerator) + { + list.Add(element.ToArray()); + } + + return list; + } + } +} diff --git a/tests/unit-tests/UnitTests.csproj b/tests/unit-tests/UnitTests.csproj new file mode 100644 index 0000000..24d283f --- /dev/null +++ b/tests/unit-tests/UnitTests.csproj @@ -0,0 +1,32 @@ + + + + net8.0;net7.0;net6.0;net5.0 + latest + enable + enable + + false + true + + SpanExtensions.Tests.UnitTests + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + +