From d01770c1b8e96ac4d89e81fc3da6392f609fe0bb Mon Sep 17 00:00:00 2001 From: stephentoub Date: Mon, 25 Apr 2016 11:12:08 -0400 Subject: [PATCH] Add more String.Concat tests Tests covering a few more special cases of String.Concat, including checking for a string.Empty optimization and a mini-stress test that tries to trigger the fallback defensive-copy case in String.Concat. --- .../tests/System/StringTests.cs | 96 +++++++++++++++++-- 1 file changed, 89 insertions(+), 7 deletions(-) diff --git a/src/System.Runtime/tests/System/StringTests.cs b/src/System.Runtime/tests/System/StringTests.cs index 1546c47271b0..511084a71d1e 100644 --- a/src/System.Runtime/tests/System/StringTests.cs +++ b/src/System.Runtime/tests/System/StringTests.cs @@ -7,6 +7,8 @@ using System.Collections.Generic; using System.Globalization; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; using Xunit; namespace System.Tests @@ -65,12 +67,12 @@ public static unsafe void Ctor_CharPtr_Int_Int_Invalid() Assert.Throws("startIndex", () => { fixed (char* value = valueArray) { new string(value, -1, 8); } // Start index < 0 - }); + }); Assert.Throws("length", () => { fixed (char* value = valueArray) { new string(value, 0, -1); } // Length < 0 - }); + }); Assert.Throws("ptr", () => new string((char*)null, 0, 1)); // null ptr with non-zero length } @@ -168,56 +170,136 @@ public static void Length(string s, int expected) public static IEnumerable Concat_Strings_TestData() { + yield return new object[] { new string[0], "" }; + yield return new object[] { new string[] { "1" }, "1" }; yield return new object[] { new string[] { null }, "" }; + yield return new object[] { new string[] { "" }, "" }; yield return new object[] { new string[] { "1", "2" }, "12" }; yield return new object[] { new string[] { null, "1" }, "1" }; + yield return new object[] { new string[] { "", "1" }, "1" }; yield return new object[] { new string[] { "1", null }, "1" }; + yield return new object[] { new string[] { "1", "" }, "1" }; yield return new object[] { new string[] { null, null }, "" }; + yield return new object[] { new string[] { "", "" }, "" }; yield return new object[] { new string[] { "1", "2", "3" }, "123" }; yield return new object[] { new string[] { null, "1", "2" }, "12" }; + yield return new object[] { new string[] { "", "1", "2" }, "12" }; yield return new object[] { new string[] { "1", null, "2" }, "12" }; + yield return new object[] { new string[] { "1", "", "2" }, "12" }; yield return new object[] { new string[] { "1", "2", null }, "12" }; + yield return new object[] { new string[] { "1", "2", "" }, "12" }; + yield return new object[] { new string[] { null, "2", null }, "2" }; + yield return new object[] { new string[] { "", "2", "" }, "2" }; yield return new object[] { new string[] { null, null, null }, "" }; + yield return new object[] { new string[] { "", "", "" }, "" }; yield return new object[] { new string[] { "1", "2", "3", "4" }, "1234" }; yield return new object[] { new string[] { null, "1", "2", "3" }, "123" }; + yield return new object[] { new string[] { "", "1", "2", "3" }, "123" }; yield return new object[] { new string[] { "1", null, "2", "3" }, "123" }; + yield return new object[] { new string[] { "1", "", "2", "3" }, "123" }; yield return new object[] { new string[] { "1", "2", null, "3" }, "123" }; + yield return new object[] { new string[] { "1", "2", "", "3" }, "123" }; yield return new object[] { new string[] { "1", "2", "3", null }, "123" }; + yield return new object[] { new string[] { "1", "2", "3", "" }, "123" }; yield return new object[] { new string[] { "1", null, null, null }, "1" }; + yield return new object[] { new string[] { "1", "", "", "" }, "1" }; yield return new object[] { new string[] { null, "1", null, "2" }, "12" }; + yield return new object[] { new string[] { "", "1", "", "2" }, "12" }; yield return new object[] { new string[] { null, null, null, null }, "" }; + yield return new object[] { new string[] { "", "", "", "" }, "" }; yield return new object[] { new string[] { "1", "2", "3", "4", "5" }, "12345" }; yield return new object[] { new string[] { null, "1", "2", "3", "4" }, "1234" }; + yield return new object[] { new string[] { "", "1", "2", "3", "4" }, "1234" }; yield return new object[] { new string[] { "1", null, "2", "3", "4" }, "1234" }; + yield return new object[] { new string[] { "1", "", "2", "3", "4" }, "1234" }; yield return new object[] { new string[] { "1", "2", null, "3", "4" }, "1234" }; + yield return new object[] { new string[] { "1", "2", "", "3", "4" }, "1234" }; yield return new object[] { new string[] { "1", "2", "3", null, "4" }, "1234" }; + yield return new object[] { new string[] { "1", "2", "3", "", "4" }, "1234" }; yield return new object[] { new string[] { "1", "2", "3", "4", null }, "1234" }; + yield return new object[] { new string[] { "1", "2", "3", "4", "" }, "1234" }; + yield return new object[] { new string[] { "1", null, "3", null, "5" }, "135" }; + yield return new object[] { new string[] { "1", "", "3", "", "5" }, "135" }; yield return new object[] { new string[] { null, null, null, null, null }, "" }; + yield return new object[] { new string[] { "", "", "", "", "" }, "" }; + + yield return new object[] { new string[] { "abcd", "efgh", "ijkl", "mnop", "qrst", "uvwx", "yz" }, "abcdefghijklmnopqrstuvwxyz" }; } [Theory] [MemberData(nameof(Concat_Strings_TestData))] public static void Concat_String(string[] values, string expected) { + Action validate = result => + { + Assert.Equal(expected, result); + // if (result.Length == 0) Assert.Same(string.Empty, result); + }; + if (values.Length == 2) { - Assert.Equal(expected, string.Concat(values[0], values[1])); + validate(string.Concat(values[0], values[1])); } else if (values.Length == 3) { - Assert.Equal(expected, string.Concat(values[0], values[1], values[2])); + validate(string.Concat(values[0], values[1], values[2])); } else if (values.Length == 4) { - Assert.Equal(expected, string.Concat(values[0], values[1], values[2], values[3])); + validate(string.Concat(values[0], values[1], values[2], values[3])); + } + + validate(string.Concat(values)); + validate(string.Concat((IEnumerable)values)); + } + + [Fact] + [OuterLoop] // mini-stress test that likely runs for several seconds + public static void Concat_String_ConcurrencySafe() + { + var inputs = new string[2] { "abc", "def" }; + var cts = new CancellationTokenSource(); + using (var b = new Barrier(2)) + { + // String.Concat(string[]) has a slow path that handles the case where the + // input array is mutated concurrently. Queue two tasks, one that repeatedly + // does concats and the other that mutates the array concurrently. This isn't + // guaranteed to trigger the special case, but it typically does. + Task.WaitAll( + Task.Run(() => + { + b.SignalAndWait(); + while (!cts.IsCancellationRequested) + { + string result = string.Concat(inputs); + Assert.True(result == "abcdef" || result == "abc" || result == "def" || result == "", $"result == {result}"); + } + }), + Task.Run(() => + { + b.SignalAndWait(); + try + { + for (int iter = 0; iter < 100000000; iter++) + { + Volatile.Write(ref inputs[0], null); + Volatile.Write(ref inputs[1], null); + Volatile.Write(ref inputs[0], "abc"); + Volatile.Write(ref inputs[1], "def"); + } + } + + finally + { + cts.Cancel(); + } + })); } - Assert.Equal(expected, string.Concat(values)); - Assert.Equal(expected, string.Concat((IEnumerable)values)); } public static IEnumerable Concat_Objects_TestData()