blob: 323943eedf9c7e468213ccb165dcdbe44a50b292 [file] [log] [blame]
using J2N.Text;
using Lucene.Net.Attributes;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Assert = Lucene.Net.TestFramework.Assert;
namespace Lucene.Net.Util
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
[TestFixture]
public class TestCharsRef : LuceneTestCase
{
[Test]
public virtual void TestUTF16InUTF8Order()
{
int numStrings = AtLeast(1000);
BytesRef[] utf8 = new BytesRef[numStrings];
CharsRef[] utf16 = new CharsRef[numStrings];
for (int i = 0; i < numStrings; i++)
{
string s = TestUtil.RandomUnicodeString(Random);
utf8[i] = new BytesRef(s);
utf16[i] = new CharsRef(s);
}
Array.Sort(utf8);
#pragma warning disable 612, 618
Array.Sort(utf16, CharsRef.UTF16SortedAsUTF8Comparer);
#pragma warning restore 612, 618
for (int i = 0; i < numStrings; i++)
{
Assert.AreEqual(utf8[i].Utf8ToString(), utf16[i].ToString());
}
}
[Test]
public virtual void TestAppend()
{
CharsRef @ref = new CharsRef();
StringBuilder builder = new StringBuilder();
int numStrings = AtLeast(10);
for (int i = 0; i < numStrings; i++)
{
char[] charArray = TestUtil.RandomRealisticUnicodeString(Random, 1, 100).ToCharArray();
int offset = Random.Next(charArray.Length);
int length = charArray.Length - offset;
builder.Append(charArray, offset, length);
@ref.Append(charArray, offset, length);
}
Assert.AreEqual(builder.ToString(), @ref.ToString());
}
[Test]
public virtual void TestCopy()
{
int numIters = AtLeast(10);
for (int i = 0; i < numIters; i++)
{
CharsRef @ref = new CharsRef();
char[] charArray = TestUtil.RandomRealisticUnicodeString(Random, 1, 100).ToCharArray();
int offset = Random.Next(charArray.Length);
int length = charArray.Length - offset;
string str = new string(charArray, offset, length);
@ref.CopyChars(charArray, offset, length);
Assert.AreEqual(str, @ref.ToString());
}
}
// LUCENE-3590, AIOOBE if you append to a charsref with offset != 0
[Test]
public virtual void TestAppendChars()
{
char[] chars = new char[] { 'a', 'b', 'c', 'd' };
CharsRef c = new CharsRef(chars, 1, 3); // bcd
c.Append(new char[] { 'e' }, 0, 1);
Assert.AreEqual("bcde", c.ToString());
}
// LUCENE-3590, AIOOBE if you copy to a charsref with offset != 0
[Test]
public virtual void TestCopyChars()
{
char[] chars = new char[] { 'a', 'b', 'c', 'd' };
CharsRef c = new CharsRef(chars, 1, 3); // bcd
char[] otherchars = new char[] { 'b', 'c', 'd', 'e' };
c.CopyChars(otherchars, 0, 4);
Assert.AreEqual("bcde", c.ToString());
}
// LUCENE-3590, AIOOBE if you copy to a charsref with offset != 0
[Test]
public virtual void TestCopyCharsRef()
{
char[] chars = new char[] { 'a', 'b', 'c', 'd' };
CharsRef c = new CharsRef(chars, 1, 3); // bcd
char[] otherchars = new char[] { 'b', 'c', 'd', 'e' };
c.CopyChars(new CharsRef(otherchars, 0, 4));
Assert.AreEqual("bcde", c.ToString());
}
// LUCENENET NOTE: Removed the CharAt(int) method from the
// ICharSequence interface and replaced with this[int]
//// LUCENE-3590: fix charsequence to fully obey interface
//[Test]
//public virtual void TestCharSequenceCharAt()
//{
// CharsRef c = new CharsRef("abc");
// Assert.AreEqual('b', c.CharAt(1));
// try
// {
// c.CharAt(-1);
// Assert.Fail();
// }
// catch (Exception expected) when (expected.IsIndexOutOfBoundsException())
// {
// // expected exception
// }
// try
// {
// c.CharAt(3);
// Assert.Fail();
// }
// catch (Exception expected) when (expected.IsIndexOutOfBoundsException())
// {
// // expected exception
// }
//}
// LUCENE-3590: fix charsequence to fully obey interface
[Test, LuceneNetSpecific]
public virtual void TestCharSequenceIndexer()
{
CharsRef c = new CharsRef("abc");
Assert.AreEqual('b', c[1]);
try
{
var _ = c[-1];
Assert.Fail();
}
catch (Exception expected) when (expected.IsIndexOutOfBoundsException())
{
// expected exception
}
try
{
var _ = c[3];
Assert.Fail();
}
catch (Exception expected) when (expected.IsIndexOutOfBoundsException())
{
// expected exception
}
}
// LUCENE-3590: fix off-by-one in subsequence, and fully obey interface
// LUCENE-4671: fix subSequence
[Test]
public virtual void TestCharSequenceSubSequence()
{
ICharSequence[] sequences = {
new CharsRef("abc"),
new CharsRef("0abc".ToCharArray(), 1, 3),
new CharsRef("abc0".ToCharArray(), 0, 3),
new CharsRef("0abc0".ToCharArray(), 1, 3)
};
foreach (ICharSequence c in sequences)
{
DoTestSequence(c);
}
}
private void DoTestSequence(ICharSequence c)
{
// slice
Assert.AreEqual("a", c.Subsequence(0, 1 - 0).ToString()); // LUCENENET: Corrected 2nd parameter
// mid subsequence
Assert.AreEqual("b", c.Subsequence(1, 2 - 1).ToString()); // LUCENENET: Corrected 2nd parameter
// end subsequence
Assert.AreEqual("bc", c.Subsequence(1, 3 - 1).ToString()); // LUCENENET: Corrected 2nd parameter
// empty subsequence
Assert.AreEqual("", c.Subsequence(0, 0 - 0).ToString()); // LUCENENET: Corrected 2nd parameter
try
{
c.Subsequence(-1, 1 - -1); // LUCENENET: Corrected 2nd parameter
Assert.Fail();
}
catch (Exception expected) when (expected.IsIndexOutOfBoundsException())
{
// expected exception
}
try
{
c.Subsequence(0, -1 - 0); // LUCENENET: Corrected 2nd parameter
Assert.Fail();
}
catch (Exception expected) when (expected.IsIndexOutOfBoundsException())
{
// expected exception
}
try
{
c.Subsequence(0, 4 - 0); // LUCENENET: Corrected 2nd parameter
Assert.Fail();
}
catch (Exception expected) when (expected.IsIndexOutOfBoundsException())
{
// expected exception
}
try
{
c.Subsequence(2, 1 - 2); // LUCENENET: Corrected 2nd parameter
Assert.Fail();
}
catch (Exception expected) when (expected.IsIndexOutOfBoundsException())
{
// expected exception
}
}
#if FEATURE_SERIALIZABLE
[Test, LuceneNetSpecific]
public void TestSerialization()
{
var chars = "The quick brown fox jumped over the lazy dog.".ToCharArray();
var charsRef = new CharsRef(chars, 8, 10);
Assert.AreEqual(10, charsRef.Length);
Assert.AreSame(chars, charsRef.Chars);
Assert.AreEqual(chars, charsRef.Chars);
Assert.AreEqual(8, charsRef.Offset);
var clone = Clone(charsRef);
Assert.AreEqual(10, clone.Length);
Assert.AreNotSame(chars, clone.Chars);
Assert.AreEqual(chars, clone.Chars);
Assert.AreEqual(8, clone.Offset);
}
#endif
#region Test Data
// Reuse existing string test data but add offsets
public static IEnumerable<object[]> StringSliceTestData2
{
get
{
foreach (var data in TestHelpers.StringSliceTestData)
{
// data = [text, start, length]
var text = (string)data[0];
var start = (int)data[1];
var length = (int)data[2];
// Offset = 0 case
yield return new object[] { text, start, length, 0 };
// Offset = 1 case, if feasible
if (text.Length > 0)
yield return new object[] { text, start, length, 1 };
}
}
}
public static IEnumerable<object[]> StringSlice1ArgTestOutOfRangeData2
{
get
{
foreach (var data in TestHelpers.StringSlice2ArgTestOutOfRangeData)
{
// [text, start]
yield return new object[] { (string)data[0], (int)data[1], 0 };
yield return new object[] { (string)data[0], (int)data[1], 1 };
}
}
}
public static IEnumerable<object[]> StringSlice2ArgTestOutOfRangeData2
{
get
{
foreach (var data in TestHelpers.StringSlice3ArgTestOutOfRangeData)
{
// [text, start, length]
yield return new object[] { (string)data[0], (int)data[1], (int)data[2], 0 };
yield return new object[] { (string)data[0], (int)data[1], (int)data[2], 1 };
}
}
}
#endregion
#region AsSpan
[Test]
[LuceneNetSpecific]
public static void Test_AsSpan_Nullary()
{
var buffer = "Hello".ToCharArray();
var cr = new CharsRef(buffer) { Offset = 0, Length = buffer.Length };
ReadOnlySpan<char> span = cr.AsSpan();
char[] expected = cr.Chars.AsSpan(cr.Offset, cr.Length).ToArray();
span.Validate(expected);
}
[Test]
[LuceneNetSpecific]
public static void Test_AsSpan_Empty()
{
var cr = new CharsRef(new char[0]) { Offset = 0, Length = 0 };
ReadOnlySpan<char> span = cr.AsSpan();
span.ValidateNonNullEmpty();
}
[TestCaseSource(nameof(StringSliceTestData2))]
[LuceneNetSpecific]
public static unsafe void Test_AsSpan_StartAndLength(string text, int start, int length, int offset)
{
var chars = text.ToCharArray();
var buffer = new char[chars.Length + offset];
Array.Copy(chars, 0, buffer, offset, chars.Length);
var cr = new CharsRef(buffer) { Offset = offset, Length = chars.Length };
if (start == -1)
{
Validate(cr, 0, cr.Length, cr.AsSpan());
Validate(cr, 0, cr.Length, cr.AsSpan(0));
Validate(cr, 0, cr.Length, cr.AsSpan(0..^0));
}
else if (length == -1)
{
Validate(cr, start, cr.Length - start, cr.AsSpan(start));
Validate(cr, start, cr.Length - start, cr.AsSpan(start..));
}
else
{
Validate(cr, start, length, cr.AsSpan(start, length));
Validate(cr, start, length, cr.AsSpan(start..(start + length)));
}
static unsafe void Validate(CharsRef text, int start, int length, ReadOnlySpan<char> span)
{
Assert.AreEqual(length, span.Length);
fixed (char* pText = &MemoryMarshal.GetReference(text.Chars.AsSpan()))
{
char* expected = pText + text.Offset + start;
void* actual = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
Assert.AreEqual((IntPtr)expected, (IntPtr)actual);
}
}
}
[TestCaseSource(nameof(StringSlice1ArgTestOutOfRangeData2))]
[LuceneNetSpecific]
public static void Test_AsSpan_1Arg_OutOfRange(string text, int start, int offset)
{
var chars = text.ToCharArray();
var buffer = new char[chars.Length + offset];
Array.Copy(chars, 0, buffer, offset, chars.Length);
var cr = new CharsRef(buffer) { Offset = offset, Length = chars.Length };
Assert.Throws<ArgumentOutOfRangeException>(() => cr.AsSpan(start));
}
[TestCaseSource(nameof(StringSlice2ArgTestOutOfRangeData2))]
[LuceneNetSpecific]
public static void Test_AsSpan_2Arg_OutOfRange(string text, int start, int length, int offset)
{
var chars = text.ToCharArray();
var buffer = new char[chars.Length + offset];
Array.Copy(chars, 0, buffer, offset, chars.Length);
var cr = new CharsRef(buffer) { Offset = offset, Length = chars.Length };
Assert.Throws<ArgumentOutOfRangeException>(() => cr.AsSpan(start, length));
}
[TestCase(5, 0, 0)] // Offset = 0
[TestCase(5, 2, 0)]
[TestCase(5, 5, 0)]
[TestCase(5, 0, 1)] // Offset = 1
[TestCase(5, 2, 1)]
[TestCase(5, 5, 1)]
[LuceneNetSpecific]
public static void Test_AsSpan_Index_FromEnd(int length, int startIndex, int offset)
{
var chars = new char[length + offset];
for (int i = 0; i < length; i++) chars[i + offset] = (char)(i + 1);
var cr = new CharsRef(chars) { Offset = offset, Length = length };
// From-end Index
if (startIndex <= length)
{
ReadOnlySpan<char> spanFromEnd = cr.AsSpan(^(length - startIndex));
Assert.AreEqual(length - startIndex, spanFromEnd.Length);
if (spanFromEnd.Length > 0) Assert.AreEqual((char)(startIndex + 1), spanFromEnd[0]);
}
}
[TestCase(5, -1, 0)]
[TestCase(5, 6, 0)]
[TestCase(5, -1, 1)]
[TestCase(5, 6, 1)]
[LuceneNetSpecific]
public static void Test_AsSpan_Index_FromEnd_OutOfRange(int length, int startIndex, int offset)
{
var chars = new char[length + offset];
var cr = new CharsRef(chars) { Offset = offset, Length = length };
Assert.Throws<ArgumentOutOfRangeException>(() => cr.AsSpan(^(length + 1))); // from-end invalid
}
[TestCase(5, 0, 3)]
[TestCase(5, 1, 2)]
[TestCase(5, 0, 5)]
[LuceneNetSpecific]
public static void Test_AsSpan_Range(int length, int start, int subLength)
{
var chars = new char[length + 1];
for (int i = 0; i < length; i++) chars[i + 1] = (char)(i + 10);
var cr = new CharsRef(chars) { Offset = 1, Length = length };
// Range overload using .. syntax
ReadOnlySpan<char> span = cr.AsSpan(start..(start + subLength));
Assert.AreEqual(subLength, span.Length);
if (subLength > 0) Assert.AreEqual((char)(10 + start), span[0]);
}
[TestCase(5, 0, 3, 0)] // length, start, subLength, offset
[TestCase(5, 1, 2, 0)]
[TestCase(5, 0, 5, 0)]
[TestCase(5, 0, 3, 1)] // Offset = 1
[TestCase(5, 1, 2, 1)]
[TestCase(5, 0, 5, 1)]
[LuceneNetSpecific]
public static void Test_AsSpan_Range_WithOffset(int length, int start, int subLength, int offset)
{
var chars = new char[length + offset];
for (int i = 0; i < length; i++) chars[i + offset] = (char)(i + 10);
var cr = new CharsRef(chars) { Offset = offset, Length = length };
ReadOnlySpan<char> span = cr.AsSpan(start..(start + subLength));
Assert.AreEqual(subLength, span.Length);
if (subLength > 0) Assert.AreEqual((char)(10 + start), span[0]);
}
[TestCase(5, 3, 3)]
[TestCase(5, -1, 2)]
[TestCase(5, 4, 2)]
[LuceneNetSpecific]
public static void Test_AsSpan_Range_OutOfRange(int length, int start, int subLength)
{
var chars = new char[length];
var cr = new CharsRef(chars) { Offset = 0, Length = length };
Assert.Throws<ArgumentOutOfRangeException>(() => cr.AsSpan(start..(start + subLength)));
}
[TestCase(5, 0, 5, 1)] // offset + start + length > bytes.Length -> second check
[TestCase(5, 2, 3, 1)] // offset causes spill over backing array
[TestCase(5, 0, 5, 2)] // offset + length > bytes.Length
[LuceneNetSpecific]
public static void Test_AsSpan_Range_OutOfRange_OffsetCheck(int length, int start, int subLength, int offset)
{
var chars = new char[length]; // intentionally too short to trigger the second check
var cr = new CharsRef(chars) { Offset = offset, Length = length };
Assert.Throws<ArgumentOutOfRangeException>(() => cr.AsSpan(start, subLength));
Assert.Throws<ArgumentOutOfRangeException>(() => cr.AsSpan(start..(start + subLength)));
}
#endregion
#region AsMemory
[TestCase(0, 0)]
[TestCase(3, 0)]
[TestCase(3, 1)]
[TestCase(3, 2)]
[TestCase(3, 3)]
[TestCase(10, 0)]
[TestCase(10, 3)]
[TestCase(10, 10)]
[LuceneNetSpecific]
public static void Test_AsMemory_WithStart(int length, int start)
{
Span<char> preload = stackalloc char[length];
preload.Fill('\0');
var cr = new CharsRef(preload) { Offset = 0, Length = length };
var m = cr.AsMemory(start);
Assert.AreEqual(length - start, m.Length);
if (start != length)
{
cr.Chars[cr.Offset + start] = (char)42;
Assert.AreEqual((char)42, m.Span[0]);
}
}
[TestCase(0, -1)]
[TestCase(0, 1)]
[TestCase(5, 6)]
[LuceneNetSpecific]
public static void Test_AsMemory_WithStart_OutOfRange(int length, int start)
{
var chars = new char[length];
var cr = new CharsRef(chars) { Offset = 0, Length = length };
Assert.Throws<ArgumentOutOfRangeException>(() => cr.AsMemory(start));
}
[TestCase(10, 3, 2, 0)]
[TestCase(10, 3, 2, 2)] // offset
[LuceneNetSpecific]
public static void Test_AsMemory_WithStartAndLength(int length, int start, int subLength, int offset)
{
Span<char> preload = stackalloc char[length + offset];
preload.Fill('\0');
var cr = new CharsRef(preload) { Offset = offset, Length = length };
var m = cr.AsMemory(start, subLength);
Assert.AreEqual(subLength, m.Length);
if (subLength != 0)
{
cr.Chars[offset + start] = (char)42;
Assert.AreEqual((char)42, m.Span[0]);
}
}
[TestCase(0, -1, 0)]
[TestCase(0, 1, 0)]
[TestCase(0, 0, -1)]
[TestCase(0, 0, 1)]
[TestCase(5, 6, 0)]
[TestCase(5, 3, 3)]
[LuceneNetSpecific]
public static void Test_AsMemory_WithStartAndLength_OutOfRange(int length, int start, int subLength)
{
var chars = new char[length];
var cr = new CharsRef(chars) { Offset = 0, Length = length };
Assert.Throws<ArgumentOutOfRangeException>(() => cr.AsMemory(start, subLength));
}
[TestCase(5, 0, 0)]
[TestCase(5, 2, 0)]
[TestCase(5, 5, 0)]
[TestCase(5, 0, 1)]
[TestCase(5, 2, 1)]
[TestCase(5, 5, 1)]
[LuceneNetSpecific]
public static void Test_AsMemory_Index_FromEnd(int length, int startIndex, int offset)
{
var chars = new char[length + offset];
for (int i = 0; i < length; i++) chars[i + offset] = (char)(i + 1);
var cr = new CharsRef(chars) { Offset = offset, Length = length };
// From-end Index
ReadOnlyMemory<char> memFromEnd = cr.AsMemory(^(length - startIndex));
Assert.AreEqual(length - startIndex, memFromEnd.Length);
if (memFromEnd.Length > 0) Assert.AreEqual((char)(startIndex + 1), memFromEnd.Span[0]);
}
[TestCase(5, -1, 0)]
[TestCase(5, 6, 0)]
[TestCase(5, -1, 1)]
[TestCase(5, 6, 1)]
[LuceneNetSpecific]
public static void Test_AsMemory_Index_OutOfRange(int length, int startIndex, int offset)
{
var chars = new char[length + offset];
var cr = new CharsRef(chars) { Offset = offset, Length = length };
Assert.Throws<ArgumentOutOfRangeException>(() => cr.AsMemory(^(length + 1))); // from-end invalid
}
[TestCase(5, 0, 5)]
[TestCase(5, 1, 3)]
[TestCase(5, 2, 2)]
[TestCase(5, 0, 0)]
[LuceneNetSpecific]
public static void Test_AsMemory_Range(int length, int start, int subLength)
{
var chars = new char[length + 1];
for (int i = 0; i < length; i++) chars[i + 1] = (char)(i + 100);
var cr = new CharsRef(chars) { Offset = 1, Length = length };
// Correct: use Range syntax
ReadOnlyMemory<char> m = cr.AsMemory(start..(start + subLength));
Assert.AreEqual(subLength, m.Length);
if (subLength > 0)
{
Assert.AreEqual((char)(100 + start), m.Span[0]);
}
// Also test start.. for completeness
ReadOnlyMemory<char> m2 = cr.AsMemory(start..);
Assert.AreEqual(length - start, m2.Length);
}
[TestCase(5, 0, 3, 0)]
[TestCase(5, 1, 2, 0)]
[TestCase(5, 0, 5, 0)]
[TestCase(5, 0, 3, 1)]
[TestCase(5, 1, 2, 1)]
[TestCase(5, 0, 5, 1)]
[LuceneNetSpecific]
public static void Test_AsMemory_Range_WithOffset(int length, int start, int subLength, int offset)
{
var chars = new char[length + offset];
for (int i = 0; i < length; i++) chars[i + offset] = (char)(i + 10);
var cr = new CharsRef(chars) { Offset = offset, Length = length };
ReadOnlyMemory<char> mem = cr.AsMemory(start..(start + subLength));
Assert.AreEqual(subLength, mem.Length);
if (subLength > 0) Assert.AreEqual((char)(10 + start), mem.Span[0]);
}
[TestCase(5, 0, 6)]
[TestCase(5, 4, 2)]
[TestCase(5, 3, -1)]
[LuceneNetSpecific]
public static void Test_AsMemory_Range_OutOfRange(int length, int start, int subLength)
{
var cr = new CharsRef(new char[length]) { Offset = 0, Length = length };
Assert.Throws<ArgumentOutOfRangeException>(() => cr.AsMemory(start..(start + subLength)));
}
[TestCase(5, 0, 5, 1)] // offset + start + length > chars.Length -> second check
[TestCase(5, 2, 3, 1)] // offset causes spill over backing array
[TestCase(5, 0, 5, 2)] // offset + length > chars.Length
[LuceneNetSpecific]
public static void Test_AsMemory_Range_OutOfRange_OffsetCheck(int length, int start, int subLength, int offset)
{
var chars = new char[length]; // intentionally too short to trigger the second check
var cr = new CharsRef(chars) { Offset = offset, Length = length };
Assert.Throws<ArgumentOutOfRangeException>(() => cr.AsMemory(start, subLength));
Assert.Throws<ArgumentOutOfRangeException>(() => cr.AsMemory(start..(start + subLength)));
}
#endregion
#region Append
[Test]
[LuceneNetSpecific]
public static void Test_Append_WithinCapacity()
{
var cr = new CharsRef(new char[10]) { Length = 0 };
cr.Append(new char[] { (char)1, (char)2, (char)3 });
cr.Append(new char[] { (char)4, (char)5 });
Assert.AreEqual(5, cr.Length);
CollectionAssert.AreEqual(new char[] { (char)1, (char)2, (char)3, (char)4, (char)5 }, cr.AsSpan().ToArray());
}
[Test]
[LuceneNetSpecific]
public static void Test_Append_ExceedsCapacity()
{
var cr = new CharsRef(new char[2]) { Length = 0 };
cr.Append(new char[] { (char)1, (char)2, (char)3 });
Assert.AreEqual(3, cr.Length);
CollectionAssert.AreEqual(new char[] { (char)1, (char)2, (char)3 }, cr.AsSpan().ToArray());
}
#endregion
#region Constructors
[Test]
[LuceneNetSpecific]
public static void Test_FromCharSpan()
{
var span = "abc".AsSpan();
var br = new CharsRef(span);
Assert.AreEqual(3, br.Length);
Assert.AreEqual("abc", br.AsSpan().ToString());
}
#endregion
#region CopyChars
[Test]
[LuceneNetSpecific]
public static void Test_CopyChars()
{
var br = new CharsRef(new char[10]) { Offset = 0, Length = 0 };
br.CopyChars("hello".AsSpan());
Assert.AreEqual("hello", br.AsSpan().ToString());
}
#endregion
}
}