Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit fd239bc

Browse files
authored
Add more functional and perf tests for string.ToLower/Upper (#28748)
* Add more functional tests for string.ToLower/Upper{Invariant} * Add more string.ToLower/Upper perf tests
1 parent f759243 commit fd239bc

File tree

2 files changed

+161
-40
lines changed

2 files changed

+161
-40
lines changed

src/System.Runtime/tests/Performance/Perf.String.cs

Lines changed: 91 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Globalization;
6+
using System.Linq;
67
using System.Text;
78
using System.Collections.Generic;
89
using Xunit;
@@ -232,28 +233,104 @@ public void Substring_int_int(int size)
232233
testString.Substring(startIndex, length);
233234
}
234235

235-
[Benchmark]
236-
[MemberData(nameof(TestStringSizes))]
237-
public void ToLower(int size)
236+
[Flags]
237+
public enum ChangeCaseOptions
238238
{
239-
PerfUtils utils = new PerfUtils();
240-
string testString = utils.CreateString(size);
239+
None = 0,
240+
UniqueString = 0x1,
241+
242+
MiddleTurkishI = 0x2,
243+
MiddleDifferentCase = 0x4,
244+
AllDifferentCase = 0x8,
245+
}
246+
247+
public static IEnumerable<object[]> ChangeCaseMemberData() =>
248+
from size in new[] { 1, 10, 500 }
249+
from culture in new[] { "en-US" }
250+
from options in new[]
251+
{
252+
ChangeCaseOptions.None,
253+
ChangeCaseOptions.MiddleTurkishI,
254+
ChangeCaseOptions.MiddleDifferentCase,
255+
ChangeCaseOptions.AllDifferentCase,
256+
257+
ChangeCaseOptions.UniqueString,
258+
ChangeCaseOptions.UniqueString | ChangeCaseOptions.MiddleTurkishI,
259+
ChangeCaseOptions.UniqueString | ChangeCaseOptions.MiddleDifferentCase,
260+
ChangeCaseOptions.UniqueString | ChangeCaseOptions.AllDifferentCase,
261+
}
262+
select new object[] { size, options, culture };
263+
264+
private static string CreateChangeCaseString(int size, ChangeCaseOptions options, bool upper)
265+
{
266+
Span<char> chars = new char[size];
267+
268+
bool differentCase = (options & ChangeCaseOptions.AllDifferentCase) != 0;
269+
chars.Fill(upper != differentCase ? 'S' : 's');
270+
271+
if ((options & ChangeCaseOptions.MiddleTurkishI) != 0)
272+
{
273+
chars[chars.Length / 2] = '\u0131';
274+
}
275+
else if ((options & ChangeCaseOptions.MiddleDifferentCase) != 0)
276+
{
277+
char c = chars[chars.Length / 2];
278+
chars[chars.Length / 2] = char.IsUpper(c) ? char.ToLower(c) : char.ToUpper(c);
279+
}
280+
281+
return chars.ToString();
282+
}
283+
284+
[Benchmark, MeasureGCAllocations]
285+
[MemberData(nameof(ChangeCaseMemberData))]
286+
public void ToLower(int size, ChangeCaseOptions options, string cultureName)
287+
{
288+
const int Iters = 10_000;
289+
var strings = new string[Iters];
290+
var culture = new CultureInfo(cultureName); // Benchmark doesn't support CultureInfo as argument
291+
string target = CreateChangeCaseString(size, options, upper: false);
292+
241293
foreach (var iteration in Benchmark.Iterations)
294+
{
295+
for (int i = 0; i < strings.Length; i++)
296+
{
297+
strings[i] = (options & ChangeCaseOptions.UniqueString) != 0 ? string.Copy(target) : target;
298+
}
299+
242300
using (iteration.StartMeasurement())
243-
for (int i = 0; i < 10000; i++)
244-
testString.ToLower();
301+
{
302+
for (int i = 0; i < Iters; i++)
303+
{
304+
strings[i].ToLower(culture);
305+
}
306+
}
307+
}
245308
}
246309

247-
[Benchmark]
248-
[MemberData(nameof(TestStringSizes))]
249-
public void ToUpper(int size)
310+
[Benchmark, MeasureGCAllocations]
311+
[MemberData(nameof(ChangeCaseMemberData))]
312+
public void ToUpper(int size, ChangeCaseOptions options, string cultureName)
250313
{
251-
PerfUtils utils = new PerfUtils();
252-
string testString = utils.CreateString(size);
314+
const int Iters = 10_000;
315+
var strings = new string[Iters];
316+
var culture = new CultureInfo(cultureName); // Benchmark doesn't support CultureInfo as argument
317+
string target = CreateChangeCaseString(size, options, upper: true);
318+
253319
foreach (var iteration in Benchmark.Iterations)
320+
{
321+
for (int i = 0; i < strings.Length; i++)
322+
{
323+
strings[i] = (options & ChangeCaseOptions.UniqueString) != 0 ? string.Copy(target) : target;
324+
}
325+
254326
using (iteration.StartMeasurement())
255-
for (int i = 0; i < 10000; i++)
256-
testString.ToUpper();
327+
{
328+
for (int i = 0; i < Iters; i++)
329+
{
330+
strings[i].ToUpper(culture);
331+
}
332+
}
333+
}
257334
}
258335

259336
[Benchmark]

src/System.Runtime/tests/System/StringTests.cs

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2608,8 +2608,10 @@ public static void ToCharArray_Invalid()
26082608
}
26092609

26102610
[Theory]
2611-
[InlineData("HELLO", "hello")]
26122611
[InlineData("hello", "hello")]
2612+
[InlineData("HELLO", "hello")]
2613+
[InlineData("hElLo", "hello")]
2614+
[InlineData("HeLlO", "hello")]
26132615
[InlineData("", "")]
26142616
public static void ToLower(string s, string expected)
26152617
{
@@ -2622,17 +2624,28 @@ public static void ToLower(string s, string expected)
26222624

26232625
private static IEnumerable<object[]> ToLower_Culture_TestData()
26242626
{
2625-
yield return new object[] { "H\u0049 World", "h\u0131 world", new CultureInfo("tr-TR") };
2626-
yield return new object[] { "H\u0130 World", "h\u0069 world", new CultureInfo("tr-TR") };
2627-
yield return new object[] { "H\u0131 World", "h\u0131 world", new CultureInfo("tr-TR") };
2627+
var tuples = new[]
2628+
{
2629+
Tuple.Create('\u0049', '\u0131', new CultureInfo("tr-TR")),
2630+
Tuple.Create('\u0130', '\u0069', new CultureInfo("tr-TR")),
2631+
Tuple.Create('\u0131', '\u0131', new CultureInfo("tr-TR")),
26282632

2629-
yield return new object[] { "H\u0049 World", "h\u0069 world", new CultureInfo("en-US") };
2630-
yield return new object[] { "H\u0130 World", "h\u0069 world", new CultureInfo("en-US") };
2631-
yield return new object[] { "H\u0131 World", "h\u0131 world", new CultureInfo("en-US") };
2633+
Tuple.Create('\u0049', '\u0069', new CultureInfo("en-US")),
2634+
Tuple.Create('\u0130', '\u0069', new CultureInfo("en-US")),
2635+
Tuple.Create('\u0131', '\u0131', new CultureInfo("en-US")),
2636+
2637+
Tuple.Create('\u0049', '\u0069', CultureInfo.InvariantCulture),
2638+
Tuple.Create('\u0130', '\u0130', CultureInfo.InvariantCulture),
2639+
Tuple.Create('\u0131', '\u0131', CultureInfo.InvariantCulture),
2640+
};
26322641

2633-
yield return new object[] { "H\u0049 World", "h\u0069 world", CultureInfo.InvariantCulture };
2634-
yield return new object[] { "H\u0130 World", "h\u0130 world", CultureInfo.InvariantCulture };
2635-
yield return new object[] { "H\u0131 World", "h\u0131 world", CultureInfo.InvariantCulture };
2642+
foreach (Tuple<char, char, CultureInfo> tuple in tuples)
2643+
{
2644+
yield return new object[] { $"{tuple.Item1}Hello World", $"{tuple.Item2}hello world", tuple.Item3 };
2645+
yield return new object[] { $"HeLlO{tuple.Item1} WORLD", $"hello{tuple.Item2} world", tuple.Item3 };
2646+
yield return new object[] { $"hello world{tuple.Item1}", $"hello world{tuple.Item2}", tuple.Item3 };
2647+
yield return new object[] { new string(tuple.Item1, 100), new string(tuple.Item2, 100), tuple.Item3 };
2648+
}
26362649
}
26372650

26382651
[Fact]
@@ -2648,19 +2661,21 @@ public static void Test_ToLower_Culture()
26482661
}).Dispose();
26492662
}
26502663

2651-
private static void ToLower_Culture(string actual, string expected, CultureInfo culture)
2664+
private static void ToLower_Culture(string input, string expected, CultureInfo culture)
26522665
{
26532666
CultureInfo.CurrentCulture = culture;
2654-
Assert.True(actual.ToLower().Equals(expected, StringComparison.Ordinal));
2667+
Assert.True(input.ToLower().Equals(expected, StringComparison.Ordinal), $"Input: {input}, Expected: {expected}, Actual: {input.ToLower()}");
26552668

2656-
Span<char> destination = new char[actual.Length];
2657-
Assert.Equal(actual.Length, actual.AsSpan().ToLower(destination, culture));
2669+
Span<char> destination = new char[input.Length];
2670+
Assert.Equal(input.Length, input.AsSpan().ToLower(destination, culture));
26582671
Assert.Equal(expected, destination.ToString());
26592672
}
26602673

26612674
[Theory]
2662-
[InlineData("HELLO", "hello")]
26632675
[InlineData("hello", "hello")]
2676+
[InlineData("HELLO", "hello")]
2677+
[InlineData("hElLo", "hello")]
2678+
[InlineData("HeLlO", "hello")]
26642679
[InlineData("", "")]
26652680
public static void ToLowerInvariant(string s, string expected)
26662681
{
@@ -2684,6 +2699,8 @@ public static void ToString(string s)
26842699
[Theory]
26852700
[InlineData("hello", "HELLO")]
26862701
[InlineData("HELLO", "HELLO")]
2702+
[InlineData("hElLo", "HELLO")]
2703+
[InlineData("HeLlO", "HELLO")]
26872704
[InlineData("", "")]
26882705
public static void ToUpper(string s, string expected)
26892706
{
@@ -2694,17 +2711,34 @@ public static void ToUpper(string s, string expected)
26942711
Assert.Equal(expected, destination.ToString());
26952712
}
26962713

2714+
private static IEnumerable<object[]> ToUpper_TurkishI_MemberData(
2715+
params KeyValuePair<char, char>[] mappings)
2716+
{
2717+
foreach (KeyValuePair<char, char> mapping in mappings)
2718+
{
2719+
yield return new[] { $"{mapping.Key}", $"{mapping.Value}" };
2720+
yield return new[] { $"{mapping.Key}a TeSt", $"{mapping.Value}A TEST" };
2721+
yield return new[] { $"a T{mapping.Key}est", $"A T{mapping.Value}EST" };
2722+
yield return new[] { $"A test{mapping.Key}", $"A TEST{mapping.Value}" };
2723+
yield return new[] { new string(mapping.Key, 100), new string(mapping.Value, 100) };
2724+
}
2725+
}
2726+
2727+
public static IEnumerable<object[]> ToUpper_TurkishI_TurkishCulture_MemberData() =>
2728+
ToUpper_TurkishI_MemberData(
2729+
new KeyValuePair<char, char>('\u0069', '\u0130'),
2730+
new KeyValuePair<char, char>('\u0130', '\u0130'),
2731+
new KeyValuePair<char, char>('\u0131', '\u0049'));
2732+
26972733
[Theory]
2698-
[InlineData("H\u0069 World", "H\u0130 WORLD")]
2699-
[InlineData("H\u0130 World", "H\u0130 WORLD")]
2700-
[InlineData("H\u0131 World", "H\u0049 WORLD")]
2734+
[MemberData(nameof(ToUpper_TurkishI_TurkishCulture_MemberData))]
27012735
public static void ToUpper_TurkishI_TurkishCulture(string s, string expected)
27022736
{
27032737
RemoteInvoke((str, expectedString) =>
27042738
{
27052739
CultureInfo.CurrentCulture = new CultureInfo("tr-TR");
27062740

2707-
Assert.True(str.ToUpper().Equals(expectedString, StringComparison.Ordinal));
2741+
Assert.True(str.ToUpper().Equals(expectedString, StringComparison.Ordinal), "Actual: " + str.ToUpper());
27082742

27092743
Span<char> destination = new char[str.Length];
27102744
Assert.Equal(str.Length, str.AsSpan().ToUpper(destination, CultureInfo.CurrentCulture));
@@ -2714,17 +2748,21 @@ public static void ToUpper_TurkishI_TurkishCulture(string s, string expected)
27142748
}, s.ToString(), expected.ToString()).Dispose();
27152749
}
27162750

2751+
public static IEnumerable<object[]> ToUpper_TurkishI_EnglishUSCulture_MemberData() =>
2752+
ToUpper_TurkishI_MemberData(
2753+
new KeyValuePair<char, char>('\u0069', '\u0049'),
2754+
new KeyValuePair<char, char>('\u0130', '\u0130'),
2755+
new KeyValuePair<char, char>('\u0131', '\u0049'));
2756+
27172757
[Theory]
2718-
[InlineData("H\u0069 World", "H\u0049 WORLD")]
2719-
[InlineData("H\u0130 World", "H\u0130 WORLD")]
2720-
[InlineData("H\u0131 World", "H\u0049 WORLD")]
2758+
[MemberData(nameof(ToUpper_TurkishI_EnglishUSCulture_MemberData))]
27212759
public static void ToUpper_TurkishI_EnglishUSCulture(string s, string expected)
27222760
{
27232761
RemoteInvoke((str, expectedString) =>
27242762
{
27252763
CultureInfo.CurrentCulture = new CultureInfo("en-US");
27262764

2727-
Assert.True(str.ToUpper().Equals(expectedString, StringComparison.Ordinal));
2765+
Assert.True(str.ToUpper().Equals(expectedString, StringComparison.Ordinal), "Actual: " + str.ToUpper());
27282766

27292767
Span<char> destination = new char[str.Length];
27302768
Assert.Equal(str.Length, str.AsSpan().ToUpper(destination, CultureInfo.CurrentCulture));
@@ -2734,10 +2772,14 @@ public static void ToUpper_TurkishI_EnglishUSCulture(string s, string expected)
27342772
}, s.ToString(), expected.ToString()).Dispose();
27352773
}
27362774

2775+
public static IEnumerable<object[]> ToUpper_TurkishI_InvariantCulture_MemberData() =>
2776+
ToUpper_TurkishI_MemberData(
2777+
new KeyValuePair<char, char>('\u0069', '\u0049'),
2778+
new KeyValuePair<char, char>('\u0130', '\u0130'),
2779+
new KeyValuePair<char, char>('\u0131', '\u0131'));
2780+
27372781
[Theory]
2738-
[InlineData("H\u0069 World", "H\u0049 WORLD")]
2739-
[InlineData("H\u0130 World", "H\u0130 WORLD")]
2740-
[InlineData("H\u0131 World", "H\u0131 WORLD")]
2782+
[MemberData(nameof(ToUpper_TurkishI_InvariantCulture_MemberData))]
27412783
public static void ToUpper_TurkishI_InvariantCulture(string s, string expected)
27422784
{
27432785
RemoteInvoke((str, expectedString) =>
@@ -2757,6 +2799,8 @@ public static void ToUpper_TurkishI_InvariantCulture(string s, string expected)
27572799
[Theory]
27582800
[InlineData("hello", "HELLO")]
27592801
[InlineData("HELLO", "HELLO")]
2802+
[InlineData("hElLo", "HELLO")]
2803+
[InlineData("HeLlO", "HELLO")]
27602804
[InlineData("", "")]
27612805
public static void ToUpperInvariant(string s, string expected)
27622806
{

0 commit comments

Comments
 (0)