Livt packages

Livt.Base

Version 1.2.1

Foundational bit, array, string, conversion, formatting, and timing helpers for Livt hardware designs.

Published files (32)

tests/StringHelperTest.lvt

namespace Livt.String.Tests

using Livt.String

/**
 * Exercises StringHelper with a shared fixture instance. Each scenario establishes the
 * inputs and state it needs.
 */
@Test
component StringHelperTest
{
	// Shared fixture instances, signals, and observation state.
	str: StringHelper

	/**
	 * Creates the shared fixture instances (StringHelper). Tests initialize the state required
	 * by their scenario.
	 */
	new()
	{
		this.str = new StringHelper()
	}

	// -----------------------------------------------------------------------
	// StartsWith
	// -----------------------------------------------------------------------

	/**
	 * Checks that StartsWith returns true for matching prefix.
	 */
	@Test
	fn StartsWithReturnsTrueForMatchingPrefix()
	{
		var data: byte[11] = "Hello World".Encode()
		var prefix: byte[5] = "Hello".Encode()
		var result: bool = this.str.StartsWith(data, 11, prefix, 5)
		assert result == true
	}

	/**
	 * Checks that StartsWith returns false for non matching prefix.
	 */
	@Test
	fn StartsWithReturnsFalseForNonMatchingPrefix()
	{
		var data: byte[11] = "Hello World".Encode()
		var prefix: byte[5] = "World".Encode()
		var result: bool = this.str.StartsWith(data, 11, prefix, 5)
		assert result == false
	}

	/**
	 * Checks that StartsWith returns true for full match.
	 */
	@Test
	fn StartsWithReturnsTrueForFullMatch()
	{
		var data: byte[5] = "Hello".Encode()
		var prefix: byte[5] = "Hello".Encode()
		var result: bool = this.str.StartsWith(data, 5, prefix, 5)
		assert result == true
	}

	/**
	 * Checks that StartsWith returns false when prefix longer than data.
	 */
	@Test
	fn StartsWithReturnsFalseWhenPrefixLongerThanData()
	{
		var data: byte[3] = "GET".Encode()
		var prefix: byte[5] = "Hello".Encode()
		var result: bool = this.str.StartsWith(data, 3, prefix, 5)
		assert result == false
	}

	/**
	 * Checks that StartsWith returns true for single byte match.
	 */
	@Test
	fn StartsWithReturnsTrueForSingleByteMatch()
	{
		var data: byte[5] = "Hello".Encode()
		var prefix: byte[1] = "H".Encode()
		var result: bool = this.str.StartsWith(data, 5, prefix, 1)
		assert result == true
	}

	// -----------------------------------------------------------------------
	// EndsWith
	// -----------------------------------------------------------------------

	/**
	 * Checks that EndsWith returns true for matching suffix.
	 */
	@Test
	fn EndsWithReturnsTrueForMatchingSuffix()
	{
		var data: byte[11] = "Hello World".Encode()
		var suffix: byte[5] = "World".Encode()
		var result: bool = this.str.EndsWith(data, 11, suffix, 5)
		assert result == true
	}

	/**
	 * Checks that EndsWith returns false for non matching suffix.
	 */
	@Test
	fn EndsWithReturnsFalseForNonMatchingSuffix()
	{
		var data: byte[11] = "Hello World".Encode()
		var suffix: byte[5] = "Hello".Encode()
		var result: bool = this.str.EndsWith(data, 11, suffix, 5)
		assert result == false
	}

	/**
	 * Checks that EndsWith returns true for full match.
	 */
	@Test
	fn EndsWithReturnsTrueForFullMatch()
	{
		var data: byte[5] = "World".Encode()
		var suffix: byte[5] = "World".Encode()
		var result: bool = this.str.EndsWith(data, 5, suffix, 5)
		assert result == true
	}

	/**
	 * Checks that EndsWith returns false when suffix longer than data.
	 */
	@Test
	fn EndsWithReturnsFalseWhenSuffixLongerThanData()
	{
		var data: byte[3] = "GET".Encode()
		var suffix: byte[5] = "World".Encode()
		var result: bool = this.str.EndsWith(data, 3, suffix, 5)
		assert result == false
	}

	// -----------------------------------------------------------------------
	// Contains
	// -----------------------------------------------------------------------

	/**
	 * Checks that Contains returns true when substring at start.
	 */
	@Test
	fn ContainsReturnsTrueWhenSubstringAtStart()
	{
		var data: byte[11] = "Hello World".Encode()
		var sub: byte[5] = "Hello".Encode()
		var result: bool = this.str.Contains(data, 11, sub, 5)
		assert result == true
	}

	/**
	 * Checks that Contains returns true when substring in middle.
	 */
	@Test
	fn ContainsReturnsTrueWhenSubstringInMiddle()
	{
		var data: byte[11] = "Hello World".Encode()
		var sub: byte[3] = "lo ".Encode()
		var result: bool = this.str.Contains(data, 11, sub, 3)
		assert result == true
	}

	/**
	 * Checks that Contains returns true when substring at end.
	 */
	@Test
	fn ContainsReturnsTrueWhenSubstringAtEnd()
	{
		var data: byte[11] = "Hello World".Encode()
		var sub: byte[5] = "World".Encode()
		var result: bool = this.str.Contains(data, 11, sub, 5)
		assert result == true
	}

	/**
	 * Checks that Contains returns false when substring absent.
	 */
	@Test
	fn ContainsReturnsFalseWhenSubstringAbsent()
	{
		var data: byte[5] = "Hello".Encode()
		var sub: byte[3] = "xyz".Encode()
		var result: bool = this.str.Contains(data, 5, sub, 3)
		assert result == false
	}

	/**
	 * Checks that Contains returns false when sub longer than data.
	 */
	@Test
	fn ContainsReturnsFalseWhenSubLongerThanData()
	{
		var data: byte[3] = "GET".Encode()
		var sub: byte[5] = "Hello".Encode()
		var result: bool = this.str.Contains(data, 3, sub, 5)
		assert result == false
	}

	// -----------------------------------------------------------------------
	// IndexOf
	// -----------------------------------------------------------------------

	/**
	 * Checks that IndexOf returns zero for prefix match.
	 */
	@Test
	fn IndexOfReturnsZeroForPrefixMatch()
	{
		var data: byte[11] = "Hello World".Encode()
		var sub: byte[5] = "Hello".Encode()
		var result: int = this.str.IndexOf(data, 11, sub, 5)
		assert result == 0
	}

	/**
	 * Checks that IndexOf returns correct index for middle match.
	 */
	@Test
	fn IndexOfReturnsCorrectIndexForMiddleMatch()
	{
		var data: byte[11] = "Hello World".Encode()
		var sub: byte[5] = "World".Encode()
		var result: int = this.str.IndexOf(data, 11, sub, 5)
		assert result == 6
	}

	/**
	 * Checks that IndexOf returns negative one when not found.
	 */
	@Test
	fn IndexOfReturnsNegativeOneWhenNotFound()
	{
		var data: byte[5] = "Hello".Encode()
		var sub: byte[3] = "xyz".Encode()
		var result: int = this.str.IndexOf(data, 5, sub, 3)
		assert result == -1
	}

	/**
	 * Checks that IndexOf returns negative one when sub longer than data.
	 */
	@Test
	fn IndexOfReturnsNegativeOneWhenSubLongerThanData()
	{
		var data: byte[3] = "GET".Encode()
		var sub: byte[5] = "Hello".Encode()
		var result: int = this.str.IndexOf(data, 3, sub, 5)
		assert result == -1
	}

	/**
	 * Checks that IndexOf returns single byte position.
	 */
	@Test
	fn IndexOfReturnsSingleBytePosition()
	{
		var data: byte[5] = "Hello".Encode()
		var sub: byte[1] = "l".Encode()
		var result: int = this.str.IndexOf(data, 5, sub, 1)
		assert result == 2
	}

	// -----------------------------------------------------------------------
	// LastIndexOf
	// -----------------------------------------------------------------------

	/**
	 * Checks that LastIndexOf returns single occurrence.
	 */
	@Test
	fn LastIndexOfReturnsSingleOccurrence()
	{
		var data: byte[11] = "Hello World".Encode()
		var sub: byte[5] = "World".Encode()
		var result: int = this.str.LastIndexOf(data, 11, sub, 5)
		assert result == 6
	}

	/**
	 * Checks that LastIndexOf returns last of multiple occurrences.
	 */
	@Test
	fn LastIndexOfReturnsLastOfMultipleOccurrences()
	{
		// "abcabc" — "bc" appears at index 1 and index 4; last is 4
		var data: byte[6] = "abcabc".Encode()
		var sub: byte[2] = "bc".Encode()
		var result: int = this.str.LastIndexOf(data, 6, sub, 2)
		assert result == 4
	}

	/**
	 * Checks that LastIndexOf returns negative one when not found.
	 */
	@Test
	fn LastIndexOfReturnsNegativeOneWhenNotFound()
	{
		var data: byte[5] = "Hello".Encode()
		var sub: byte[3] = "xyz".Encode()
		var result: int = this.str.LastIndexOf(data, 5, sub, 3)
		assert result == -1
	}

	/**
	 * Checks that LastIndexOf returns negative one when sub longer than data.
	 */
	@Test
	fn LastIndexOfReturnsNegativeOneWhenSubLongerThanData()
	{
		var data: byte[3] = "GET".Encode()
		var sub: byte[5] = "Hello".Encode()
		var result: int = this.str.LastIndexOf(data, 3, sub, 5)
		assert result == -1
	}

	/**
	 * Checks that LastIndexOf returns zero for single char at start.
	 */
	@Test
	fn LastIndexOfReturnsZeroForSingleCharAtStart()
	{
		// "Hello" — "H" appears only at index 0
		var data: byte[5] = "Hello".Encode()
		var sub: byte[1] = "H".Encode()
		var result: int = this.str.LastIndexOf(data, 5, sub, 1)
		assert result == 0
	}

	// -----------------------------------------------------------------------
	// EqualsAt
	// -----------------------------------------------------------------------

	/**
	 * Checks that EqualsAt returns true at position zero.
	 */
	@Test
	fn EqualsAtReturnsTrueAtPositionZero()
	{
		var data: byte[11] = "Hello World".Encode()
		var sub: byte[5] = "Hello".Encode()
		var result: bool = this.str.EqualsAt(data, 11, 0, sub, 5)
		assert result == true
	}

	/**
	 * Checks that EqualsAt returns true at middle position.
	 */
	@Test
	fn EqualsAtReturnsTrueAtMiddlePosition()
	{
		// "World" starts at index 6 in "Hello World"
		var data: byte[11] = "Hello World".Encode()
		var sub: byte[5] = "World".Encode()
		var result: bool = this.str.EqualsAt(data, 11, 6, sub, 5)
		assert result == true
	}

	/**
	 * Checks that EqualsAt returns false for mismatch.
	 */
	@Test
	fn EqualsAtReturnsFalseForMismatch()
	{
		var data: byte[11] = "Hello World".Encode()
		var sub: byte[5] = "World".Encode()
		var result: bool = this.str.EqualsAt(data, 11, 0, sub, 5)
		assert result == false
	}

	/**
	 * Checks that EqualsAt returns false when sub extends beyond data.
	 */
	@Test
	fn EqualsAtReturnsFalseWhenSubExtendsBeyondData()
	{
		// pos=3, subSize=5, eend=8 > dataSize=5, so the match is out of range.
		var data: byte[5] = "Hello".Encode()
		var sub: byte[5] = "World".Encode()
		var result: bool = this.str.EqualsAt(data, 5, 3, sub, 5)
		assert result == false
	}

	/**
	 * Checks that EqualsAt returns false for negative position.
	 */
	@Test
	fn EqualsAtReturnsFalseForNegativePosition()
	{
		var data: byte[5] = "Hello".Encode()
		var sub: byte[1] = "H".Encode()
		var result: bool = this.str.EqualsAt(data, 5, -1, sub, 1)
		assert result == false
	}

	// -----------------------------------------------------------------------
	// EqualsIgnoreCase
	// -----------------------------------------------------------------------

	/**
	 * Checks that EqualsIgnoreCase returns true for same case.
	 */
	@Test
	fn EqualsIgnoreCaseReturnsTrueForSameCase()
	{
		var a: byte[5] = "hello".Encode()
		var b: byte[5] = "hello".Encode()
		var result: bool = this.str.EqualsIgnoreCase(a, 5, b, 5)
		assert result == true
	}

	/**
	 * Checks that EqualsIgnoreCase returns true for mixed case.
	 */
	@Test
	fn EqualsIgnoreCaseReturnsTrueForMixedCase()
	{
		var a: byte[5] = "Hello".Encode()
		var b: byte[5] = "hello".Encode()
		var result: bool = this.str.EqualsIgnoreCase(a, 5, b, 5)
		assert result == true
	}

	/**
	 * Checks that EqualsIgnoreCase returns false for different content.
	 */
	@Test
	fn EqualsIgnoreCaseReturnsFalseForDifferentContent()
	{
		var a: byte[5] = "Hello".Encode()
		var b: byte[5] = "World".Encode()
		var result: bool = this.str.EqualsIgnoreCase(a, 5, b, 5)
		assert result == false
	}

	/**
	 * Checks that EqualsIgnoreCase returns false for different sizes.
	 */
	@Test
	fn EqualsIgnoreCaseReturnsFalseForDifferentSizes()
	{
		var a: byte[5] = "Hello".Encode()
		var b: byte[4] = "Hell".Encode()
		var result: bool = this.str.EqualsIgnoreCase(a, 5, b, 4)
		assert result == false
	}

	// -----------------------------------------------------------------------
	// NullTerminatedLength
	// -----------------------------------------------------------------------

	/**
	 * Checks that NullTerminatedLength returns max size when no null.
	 */
	@Test
	fn NullTerminatedLengthReturnsMaxSizeWhenNoNull()
	{
		var data: byte[5] = "Hello".Encode()
		var result: int = this.str.NullTerminatedLength(data, 5)
		assert result == 5
	}

	/**
	 * Checks that NullTerminatedLength returns zero for immediate null.
	 */
	@Test
	fn NullTerminatedLengthReturnsZeroForImmediateNull()
	{
		// First byte is 0x00
		var data: byte[5] = [0x00, 0x48, 0x65, 0x6C, 0x6C]
		var result: int = this.str.NullTerminatedLength(data, 5)
		assert result == 0
	}

	/**
	 * Checks that NullTerminatedLength finds null in middle.
	 */
	@Test
	fn NullTerminatedLengthFindsNullInMiddle()
	{
		// 0x48='H', 0x69='i', 0x00=null, then padding
		var data: byte[5] = [0x48, 0x69, 0x00, 0x41, 0x42]
		var result: int = this.str.NullTerminatedLength(data, 5)
		assert result == 2
	}

	/**
	 * Checks that NullTerminatedLength returns zero for zero max size.
	 */
	@Test
	fn NullTerminatedLengthReturnsZeroForZeroMaxSize()
	{
		var data: byte[5] = "Hello".Encode()
		var result: int = this.str.NullTerminatedLength(data, 0)
		assert result == 0
	}

	// -----------------------------------------------------------------------
	// ParseDecimalUInt
	// -----------------------------------------------------------------------

	/**
	 * Checks that ParseDecimalUInt parses single digit.
	 */
	@Test
	fn ParseDecimalUIntParsesSingleDigit()
	{
		// '7' = 0x37
		var data: byte[1] = [0x37]
		var result: int = this.str.ParseDecimalUInt(data, 1)
		assert result == 7
	}

	/**
	 * Checks that ParseDecimalUInt parses multiple digits.
	 */
	@Test
	fn ParseDecimalUIntParsesMultipleDigits()
	{
		// "255" = 0x32 0x35 0x35
		var data: byte[3] = [0x32, 0x35, 0x35]
		var result: int = this.str.ParseDecimalUInt(data, 3)
		assert result == 255
	}

	/**
	 * Checks that ParseDecimalUInt returns negative one for empty input.
	 */
	@Test
	fn ParseDecimalUIntReturnsNegativeOneForEmptyInput()
	{
		var data: byte[1] = [0x30]
		var result: int = this.str.ParseDecimalUInt(data, 0)
		assert result == -1
	}

	/**
	 * Checks that ParseDecimalUInt returns negative one for invalid char.
	 */
	@Test
	fn ParseDecimalUIntReturnsNegativeOneForInvalidChar()
	{
		// "4A" — 'A' is not a decimal digit
		var data: byte[2] = [0x34, 0x41]
		var result: int = this.str.ParseDecimalUInt(data, 2)
		assert result == -1
	}

	// -----------------------------------------------------------------------
	// ParseHexUInt
	// -----------------------------------------------------------------------

	/**
	 * Checks that ParseHexUInt parses lowercase hex.
	 */
	@Test
	fn ParseHexUIntParsesLowercaseHex()
	{
		// "ff" = 0x66 0x66
		var data: byte[2] = [0x66, 0x66]
		var result: int = this.str.ParseHexUInt(data, 2)
		assert result == 255
	}

	/**
	 * Checks that ParseHexUInt parses uppercase hex.
	 */
	@Test
	fn ParseHexUIntParsesUppercaseHex()
	{
		// "FF" = 0x46 0x46
		var data: byte[2] = [0x46, 0x46]
		var result: int = this.str.ParseHexUInt(data, 2)
		assert result == 255
	}

	/**
	 * Checks that ParseHexUInt parses single digit.
	 */
	@Test
	fn ParseHexUIntParsesSingleDigit()
	{
		// "a" = 0x61
		var data: byte[1] = [0x61]
		var result: int = this.str.ParseHexUInt(data, 1)
		assert result == 10
	}

	/**
	 * Checks that ParseHexUInt returns negative one for invalid char.
	 */
	@Test
	fn ParseHexUIntReturnsNegativeOneForInvalidChar()
	{
		// 'G' = 0x47 — not a valid hex digit
		var data: byte[1] = [0x47]
		var result: int = this.str.ParseHexUInt(data, 1)
		assert result == -1
	}
}