1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using Xunit;
6 using System.Collections.Generic;
7 using System.Runtime.InteropServices;
8 using System.Runtime.CompilerServices;
9 
10 using static System.Buffers.Binary.BinaryPrimitives;
11 
12 namespace System
13 {
14     public static class TestHelpers
15     {
16         public static void Validate<T>(this Span<T> span, params T[] expected) where T : struct, IEquatable<T>
17         {
span.SequenceEqualSystem.TestHelpers.IEquatable18             Assert.True(span.SequenceEqual(expected));
19         }
20 
ValidateReferenceType(this Span<T> span, params T[] expected)21         public static void ValidateReferenceType<T>(this Span<T> span, params T[] expected)
22         {
23             Assert.Equal(span.Length, expected.Length);
24             for (int i = 0; i < expected.Length; i++)
25             {
26                 T actual = span[i];
27                 Assert.Same(expected[i], actual);
28             }
29 
30             T ignore;
31             AssertThrows<IndexOutOfRangeException, T>(span, (_span) => ignore = _span[expected.Length]);
32         }
33 
ValidateNonNullEmpty(this Span<T> span)34         public static unsafe void ValidateNonNullEmpty<T>(this Span<T> span)
35         {
36             Assert.True(span.IsEmpty);
37 
38             // Validate that empty Span is not normalized to null
39             Assert.True(Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)) != null);
40         }
41 
AssertThrowsAction(Span<T> span)42         public delegate void AssertThrowsAction<T>(Span<T> span);
43 
44         // Cannot use standard Assert.Throws() when testing Span - Span and closures don't get along.
45         public static void AssertThrows<E, T>(Span<T> span, AssertThrowsAction<T> action) where E : Exception
46         {
47             try
48             {
49                 action(span);
50                 Assert.False(true, "Expected exception: " + typeof(E).GetType());
51             }
52             catch (E)
53             {
54             }
55             catch (Exception wrongException)
56             {
57                 Assert.False(true, "Wrong exception thrown: Expected " + typeof(E).GetType() + ": Actual: " + wrongException.GetType());
58             }
59         }
60 
61         //
62         // The innocent looking construct:
63         //
64         //    Assert.Throws<E>( () => new Span() );
65         //
66         // generates a hidden box of the Span as the return value of the lambda. This makes the IL illegal and unloadable on
67         // runtimes that enforce the actual Span rules (never mind that we expect never to reach the box instruction...)
68         //
69         // The workaround is to code it like this:
70         //
71         //    Assert.Throws<E>( () => new Span().DontBox() );
72         //
73         // which turns the lambda return type back to "void" and eliminates the troublesome box instruction.
74         //
DontBox(this Span<T> span)75         public static void DontBox<T>(this Span<T> span)
76         {
77             // This space intentionally left blank.
78         }
79 
80         public static void Validate<T>(this ReadOnlySpan<T> span, params T[] expected) where T : struct, IEquatable<T>
81         {
span.SequenceEqualSystem.TestHelpers.IEquatable82             Assert.True(span.SequenceEqual(expected));
83         }
84 
ValidateReferenceType(this ReadOnlySpan<T> span, params T[] expected)85         public static void ValidateReferenceType<T>(this ReadOnlySpan<T> span, params T[] expected)
86         {
87             Assert.Equal(span.Length, expected.Length);
88             for (int i = 0; i < expected.Length; i++)
89             {
90                 T actual = span[i];
91                 Assert.Same(expected[i], actual);
92             }
93 
94             T ignore;
95             AssertThrows<IndexOutOfRangeException, T>(span, (_span) => ignore = _span[expected.Length]);
96         }
97 
ValidateNonNullEmpty(this ReadOnlySpan<T> span)98         public static unsafe void ValidateNonNullEmpty<T>(this ReadOnlySpan<T> span)
99         {
100             Assert.True(span.IsEmpty);
101 
102             // Validate that empty Span is not normalized to null
103             Assert.True(Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)) != null);
104         }
105 
AssertThrowsActionReadOnly(ReadOnlySpan<T> span)106         public delegate void AssertThrowsActionReadOnly<T>(ReadOnlySpan<T> span);
107 
108         // Cannot use standard Assert.Throws() when testing Span - Span and closures don't get along.
109         public static void AssertThrows<E, T>(ReadOnlySpan<T> span, AssertThrowsActionReadOnly<T> action) where E : Exception
110         {
111             try
112             {
113                 action(span);
114                 Assert.False(true, "Expected exception: " + typeof(E).GetType());
115             }
116             catch (E)
117             {
118             }
119             catch (Exception wrongException)
120             {
121                 Assert.False(true, "Wrong exception thrown: Expected " + typeof(E).GetType() + ": Actual: " + wrongException.GetType());
122             }
123         }
124 
125         //
126         // The innocent looking construct:
127         //
128         //    Assert.Throws<E>( () => new Span() );
129         //
130         // generates a hidden box of the Span as the return value of the lambda. This makes the IL illegal and unloadable on
131         // runtimes that enforce the actual Span rules (never mind that we expect never to reach the box instruction...)
132         //
133         // The workaround is to code it like this:
134         //
135         //    Assert.Throws<E>( () => new Span().DontBox() );
136         //
137         // which turns the lambda return type back to "void" and eliminates the troublesome box instruction.
138         //
DontBox(this ReadOnlySpan<T> span)139         public static void DontBox<T>(this ReadOnlySpan<T> span)
140         {
141             // This space intentionally left blank.
142         }
143 
144         public static void Validate<T>(this Memory<T> memory, params T[] expected) where T : struct, IEquatable<T>
145         {
memory.Span.SequenceEqualSystem.TestHelpers.IEquatable146             Assert.True(memory.Span.SequenceEqual(expected));
147         }
148 
ValidateReferenceType(this Memory<T> memory, params T[] expected)149         public static void ValidateReferenceType<T>(this Memory<T> memory, params T[] expected)
150         {
151             T[] bufferArray = memory.ToArray();
152             Assert.Equal(memory.Length, expected.Length);
153             for (int i = 0; i < expected.Length; i++)
154             {
155                 T actual = bufferArray[i];
156                 Assert.Same(expected[i], actual);
157             }
158         }
159 
160         public static void Validate<T>(this ReadOnlyMemory<T> memory, params T[] expected) where T : struct, IEquatable<T>
161         {
memory.Span.SequenceEqualSystem.TestHelpers.IEquatable162             Assert.True(memory.Span.SequenceEqual(expected));
163         }
164 
ValidateReferenceType(this ReadOnlyMemory<T> memory, params T[] expected)165         public static void ValidateReferenceType<T>(this ReadOnlyMemory<T> memory, params T[] expected)
166         {
167             T[] bufferArray = memory.ToArray();
168             Assert.Equal(memory.Length, expected.Length);
169             for (int i = 0; i < expected.Length; i++)
170             {
171                 T actual = bufferArray[i];
172                 Assert.Same(expected[i], actual);
173             }
174         }
175 
176         public static void Validate<T>(Span<byte> span, T value) where T : struct
177         {
178             T read = ReadMachineEndian<T>(span);
Assert.EqualSystem.TestHelpers.__anon1179             Assert.Equal(value, read);
span.ClearSystem.TestHelpers.__anon1180             span.Clear();
181         }
182 
183         public static TestStructExplicit s_testExplicitStruct = new TestStructExplicit
184         {
185             S0 = short.MaxValue,
186             I0 = int.MaxValue,
187             L0 = long.MaxValue,
188             US0 = ushort.MaxValue,
189             UI0 = uint.MaxValue,
190             UL0 = ulong.MaxValue,
191             S1 = short.MinValue,
192             I1 = int.MinValue,
193             L1 = long.MinValue,
194             US1 = ushort.MinValue,
195             UI1 = uint.MinValue,
196             UL1 = ulong.MinValue
197         };
198 
GetSpanBE()199         public static Span<byte> GetSpanBE()
200         {
201             Span<byte> spanBE = new byte[Unsafe.SizeOf<TestStructExplicit>()];
202 
203             WriteInt16BigEndian(spanBE, s_testExplicitStruct.S0);
204             WriteInt32BigEndian(spanBE.Slice(2), s_testExplicitStruct.I0);
205             WriteInt64BigEndian(spanBE.Slice(6), s_testExplicitStruct.L0);
206             WriteUInt16BigEndian(spanBE.Slice(14), s_testExplicitStruct.US0);
207             WriteUInt32BigEndian(spanBE.Slice(16), s_testExplicitStruct.UI0);
208             WriteUInt64BigEndian(spanBE.Slice(20), s_testExplicitStruct.UL0);
209             WriteInt16BigEndian(spanBE.Slice(28), s_testExplicitStruct.S1);
210             WriteInt32BigEndian(spanBE.Slice(30), s_testExplicitStruct.I1);
211             WriteInt64BigEndian(spanBE.Slice(34), s_testExplicitStruct.L1);
212             WriteUInt16BigEndian(spanBE.Slice(42), s_testExplicitStruct.US1);
213             WriteUInt32BigEndian(spanBE.Slice(44), s_testExplicitStruct.UI1);
214             WriteUInt64BigEndian(spanBE.Slice(48), s_testExplicitStruct.UL1);
215 
216             Assert.Equal(56, spanBE.Length);
217             return spanBE;
218         }
219 
GetSpanLE()220         public static Span<byte> GetSpanLE()
221         {
222             Span<byte> spanLE = new byte[Unsafe.SizeOf<TestStructExplicit>()];
223 
224             WriteInt16LittleEndian(spanLE, s_testExplicitStruct.S0);
225             WriteInt32LittleEndian(spanLE.Slice(2), s_testExplicitStruct.I0);
226             WriteInt64LittleEndian(spanLE.Slice(6), s_testExplicitStruct.L0);
227             WriteUInt16LittleEndian(spanLE.Slice(14), s_testExplicitStruct.US0);
228             WriteUInt32LittleEndian(spanLE.Slice(16), s_testExplicitStruct.UI0);
229             WriteUInt64LittleEndian(spanLE.Slice(20), s_testExplicitStruct.UL0);
230             WriteInt16LittleEndian(spanLE.Slice(28), s_testExplicitStruct.S1);
231             WriteInt32LittleEndian(spanLE.Slice(30), s_testExplicitStruct.I1);
232             WriteInt64LittleEndian(spanLE.Slice(34), s_testExplicitStruct.L1);
233             WriteUInt16LittleEndian(spanLE.Slice(42), s_testExplicitStruct.US1);
234             WriteUInt32LittleEndian(spanLE.Slice(44), s_testExplicitStruct.UI1);
235             WriteUInt64LittleEndian(spanLE.Slice(48), s_testExplicitStruct.UL1);
236 
237             Assert.Equal(56, spanLE.Length);
238             return spanLE;
239         }
240 
241         [StructLayout(LayoutKind.Explicit)]
242         public struct TestStructExplicit
243         {
244             [FieldOffset(0)]
245             public short S0;
246             [FieldOffset(2)]
247             public int I0;
248             [FieldOffset(6)]
249             public long L0;
250             [FieldOffset(14)]
251             public ushort US0;
252             [FieldOffset(16)]
253             public uint UI0;
254             [FieldOffset(20)]
255             public ulong UL0;
256             [FieldOffset(28)]
257             public short S1;
258             [FieldOffset(30)]
259             public int I1;
260             [FieldOffset(34)]
261             public long L1;
262             [FieldOffset(42)]
263             public ushort US1;
264             [FieldOffset(44)]
265             public uint UI1;
266             [FieldOffset(48)]
267             public ulong UL1;
268         }
269 
270         [StructLayout(LayoutKind.Sequential)]
271         public sealed class TestClass
272         {
273             private double _d;
274             public char C0;
275             public char C1;
276             public char C2;
277             public char C3;
278             public char C4;
279         }
280 
281         [StructLayout(LayoutKind.Sequential)]
282         public struct TestValueTypeWithReference
283         {
284             public int I;
285             public string S;
286         }
287 
288         public enum TestEnum
289         {
290             e0,
291             e1,
292             e2,
293             e3,
294             e4,
295         }
296 
297         [MethodImpl(MethodImplOptions.NoInlining)]
DoNotIgnore(T value, int consumed)298         public static void DoNotIgnore<T>(T value, int consumed)
299         {
300         }
301 
302         //
303         // { text, start, length } triplets. A "-1" in start or length means "test the overload that doesn't have that parameter."
304         //
305         public static IEnumerable<object[]> StringSliceTestData
306         {
307             get
308             {
309                 foreach (string text in new string[] { string.Empty, "012" })
310                 {
311                     yield return new object[] { text, -1, -1 };
312                     for (int start = 0; start <= text.Length; start++)
313                     {
314                         yield return new object[] { text, start, -1 };
315 
316                         for (int length = 0; length <= text.Length - start; length++)
317                         {
318                             yield return new object[] { text, start, length };
319                         }
320                     }
321                 }
322             }
323         }
324 
325         public static IEnumerable<object[]> StringSlice2ArgTestOutOfRangeData
326         {
327             get
328             {
329                 foreach (string text in new string[] { string.Empty, "012" })
330                 {
331                     yield return new object[] { text, -1 };
332                     yield return new object[] { text, int.MinValue };
333 
334                     yield return new object[] { text, text.Length + 1 };
335                     yield return new object[] { text, int.MaxValue };
336                 }
337             }
338         }
339 
340         public static IEnumerable<object[]> StringSlice3ArgTestOutOfRangeData
341         {
342             get
343             {
344                 foreach (string text in new string[] { string.Empty, "012" })
345                 {
346                     yield return new object[] { text, -1, 0 };
347                     yield return new object[] { text, int.MinValue, 0 };
348 
349                     yield return new object[] { text, text.Length + 1, 0 };
350                     yield return new object[] { text, int.MaxValue, 0 };
351 
352                     yield return new object[] { text, 0, -1 };
353                     yield return new object[] { text, 0, int.MinValue };
354 
355                     yield return new object[] { text, 0, text.Length + 1 };
356                     yield return new object[] { text, 0, int.MaxValue };
357 
358                     yield return new object[] { text, 1, text.Length };
359                     yield return new object[] { text, 1, int.MaxValue };
360 
361                     yield return new object[] { text, text.Length - 1, 2 };
362                     yield return new object[] { text, text.Length - 1, int.MaxValue };
363 
364                     yield return new object[] { text, text.Length, 1 };
365                     yield return new object[] { text, text.Length, int.MaxValue };
366                 }
367             }
368         }
369     }
370 }
371 
372