Fixes in URI implementation to handle some special cases, added some tests too.

Fix flaky test.
Renamed tests to have consistent naming.
This commit is contained in:
Martín Lucas Golini
2026-05-03 02:22:37 -03:00
parent ecf7c20262
commit 7150633c1a
20 changed files with 235 additions and 14 deletions
@@ -0,0 +1,252 @@
#include "utest.h"
#include <eepp/core/string.hpp>
#include <eepp/system/filesystem.hpp>
#include <eepp/system/sys.hpp>
using namespace std::literals;
using namespace EE;
using namespace EE::System;
UTEST( String, countLines ) {
EXPECT_EQ( static_cast<size_t>( 0 ), String::countLines( "" ) );
EXPECT_EQ( static_cast<size_t>( 1 ), String::countLines( "A" ) );
EXPECT_EQ( static_cast<size_t>( 2 ), String::countLines( "A\n" ) );
EXPECT_EQ( static_cast<size_t>( 2 ), String::countLines( "A\nB" ) );
EXPECT_EQ( static_cast<size_t>( 3 ), String::countLines( "A\nB\n" ) );
EXPECT_EQ( static_cast<size_t>( 2 ), String::countLines( "\n" ) );
EXPECT_EQ( static_cast<size_t>( 3 ), String::countLines( "\n\n" ) );
}
UTEST( FileSystem, fileCountLines ) {
std::string path = Sys::getTempPath() + "eepp_test_count_lines.txt";
FileSystem::fileWrite( path, "A\nB\nC" );
bool isBinary = false;
EXPECT_EQ( static_cast<size_t>( 3 ), FileSystem::fileCountLines( path, &isBinary ) );
EXPECT_FALSE( isBinary );
FileSystem::fileWrite( path, "A\nB\nC\n" );
EXPECT_EQ( static_cast<size_t>( 4 ), FileSystem::fileCountLines( path, &isBinary ) );
EXPECT_FALSE( isBinary );
// Empty file
FileSystem::fileWrite( path, "" );
EXPECT_EQ( static_cast<size_t>( 0 ), FileSystem::fileCountLines( path, &isBinary ) );
EXPECT_FALSE( isBinary );
// Binary test
std::string binaryData = "A\n";
binaryData += '\0';
binaryData += "B\n";
FileSystem::fileWrite( path, (const Uint8*)binaryData.data(), (Uint32)binaryData.size() );
EXPECT_EQ( static_cast<size_t>( 0 ), FileSystem::fileCountLines( path, &isBinary ) );
EXPECT_TRUE( isBinary );
FileSystem::fileRemove( path );
}
UTEST( String, isAscii ) {
// Empty string
EXPECT_TRUE( String::isAscii( String::View( U"" ) ) );
// Simple short ASCII string
String strAscii( "Hello World" );
EXPECT_TRUE( strAscii.isAscii() );
// String with non-ASCII at the end
String strNonAsciiEnd( "Hello world\u0080" );
EXPECT_FALSE( strNonAsciiEnd.isAscii() );
// String with non-ASCII at the beginning
String strNonAsciiBegin( "\u0080Hello world" );
EXPECT_FALSE( strNonAsciiBegin.isAscii() );
// String with non-ASCII in the middle
String strNonAsciiMid( "Hello \u0080 world" );
EXPECT_FALSE( strNonAsciiMid.isAscii() );
// Test boundary around 127
String str127;
str127 += (String::StringBaseType)127;
EXPECT_TRUE( str127.isAscii() );
String str128;
str128 += (String::StringBaseType)128;
EXPECT_FALSE( str128.isAscii() );
// Test SIMD chunk boundaries (assumed 8 elements for AVX2, 4 for NEON)
// We'll test lengths around 4, 8, 16, 32 to cover various chunk alignments
// 1. Exact chunks + 0 remainder
{
// 32 chars (4x8 AVX2, 8x4 NEON)
String longAscii( "01234567890123456789012345678901" );
EXPECT_TRUE( longAscii.isAscii() );
// 32 chars with invalid at last position 31
String longNonAscii = longAscii;
longNonAscii[31] = 129;
EXPECT_FALSE( longNonAscii.isAscii() );
// 32 chars with invalid at first position 0
longNonAscii = longAscii;
longNonAscii[0] = 129;
EXPECT_FALSE( longNonAscii.isAscii() );
}
// 2. Exact chunks + remainder
{
// 33 chars (one element remainder)
String longAscii( "01234567890123456789012345678901A" );
EXPECT_TRUE( longAscii.isAscii() );
// invalid at remainder
String longNonAscii = longAscii;
longNonAscii[32] = 130;
EXPECT_FALSE( longNonAscii.isAscii() );
}
// 3. Just below chunk size (7 chars)
{
String shortAscii( "0123456" );
EXPECT_TRUE( shortAscii.isAscii() );
String shortNonAscii = shortAscii;
shortNonAscii[6] = 131;
EXPECT_FALSE( shortNonAscii.isAscii() );
}
// Large string verification
{
String largeAscii;
for ( int i = 0; i < 1024; ++i )
largeAscii += "A";
EXPECT_TRUE( largeAscii.isAscii() );
String largeNonAscii = largeAscii;
largeNonAscii[512] = 200; // fail in the middle
EXPECT_FALSE( largeNonAscii.isAscii() );
}
}
UTEST( String, isLatin1 ) {
// Empty string
EXPECT_TRUE( String::isLatin1( String::View( U"" ) ) );
// ASCII is also Latin1
String strAscii( "Hello World" );
EXPECT_TRUE( strAscii.isLatin1() );
// Latin1 characters (128-255)
String strLatin1;
strLatin1 += (String::StringBaseType)0xFF; // 255
EXPECT_TRUE( strLatin1.isLatin1() );
// Non-Latin1 (>255)
String strNonLatin1;
strNonLatin1 += (String::StringBaseType)0x100; // 256
EXPECT_FALSE( strNonLatin1.isLatin1() );
// Boundary Check
String str255;
str255 += (String::StringBaseType)255;
EXPECT_TRUE( str255.isLatin1() );
// Complex string with Latin1 chars
String complexLatin1 = String::fromUtf8( "Héllø Wørld"sv ); // Assuming these are in Latin1 range
// Note: 'ø' is 0xF8 (248), 'é' is 0xE9 (233). Both in Latin1.
EXPECT_TRUE( complexLatin1.isLatin1() );
// Verify SIMD paths for isLatin1 (uses same template logic but limit=255)
{
// 32 chars of 255
String longLatin1( 32, (String::StringBaseType)255 );
EXPECT_TRUE( longLatin1.isLatin1() );
}
}
UTEST( String, isAsciiHighBit ) {
// Test comparison safety (unsigned vs signed issue)
// 0x80000000 is a very large number, definitely not ASCII.
// If signed comparison was used, it might be interpreted as negative and thus < 127.
String strHigh;
strHigh += (String::StringBaseType)0x80000000;
EXPECT_FALSE( strHigh.isAscii() );
String strHigh2;
strHigh2 += (String::StringBaseType)0xFFFFFFFF;
EXPECT_FALSE( strHigh2.isAscii() );
// Mixed with ASCII
String strMixed = "Hello";
strMixed += (String::StringBaseType)0x80000000;
EXPECT_FALSE( strMixed.isAscii() );
}
UTEST( String, isAsciiPatterns ) {
// Alternating
String alt;
for (int i = 0; i < 100; i++) {
alt += (i % 2 == 0) ? 'a' : (char)128;
}
EXPECT_FALSE( alt.isAscii() );
// Block of invalid in middle of valid
String block(100, 'a');
for(int i=40; i<60; i++) block[i] = 200;
EXPECT_FALSE( block.isAscii() );
}
UTEST( String, isLatin1HighBit ) {
String strHigh;
strHigh += (String::StringBaseType)0x80000000;
EXPECT_FALSE( strHigh.isLatin1() );
}
UTEST( String, stripAnsiCodes ) {
// 1. Basic color codes
std::string redBold = "\x1B[1;31mHello\x1B[0m";
String::stripAnsiCodes( redBold );
EXPECT_STREQ( "Hello", redBold.c_str() );
// 2. Cursor movement (CSI)
std::string clearScreen = "\x1B[2JMove";
String::stripAnsiCodes( clearScreen );
EXPECT_STREQ( "Move", clearScreen.c_str() );
// 3. No codes
std::string plain = "Just text";
String::stripAnsiCodes( plain );
EXPECT_STREQ( "Just text", plain.c_str() );
// 4. Multiple mixed codes
std::string complex = "A\x1B[32mB\x1B[33mC\x1B[0m";
String::stripAnsiCodes( complex );
EXPECT_STREQ( "ABC", complex.c_str() );
// 5. Code at end
std::string endCode = "End\x1B[K";
String::stripAnsiCodes( endCode );
EXPECT_STREQ( "End", endCode.c_str() );
// 6. Code at start
std::string startCode = "\x1B[HStart";
String::stripAnsiCodes( startCode );
EXPECT_STREQ( "Start", startCode.c_str() );
// 7. Long string (trigger SIMD paths)
std::string longStr;
std::string expected;
for ( int i = 0; i < 1000; i++ ) {
longStr += "a\x1B[31mb";
expected += "ab";
}
String::stripAnsiCodes( longStr );
EXPECT_STREQ( expected.c_str(), longStr.c_str() );
// 8. Adjacent codes
std::string adjacent = "Double\x1B[1m\x1B[31mColor";
String::stripAnsiCodes( adjacent );
EXPECT_STREQ( "DoubleColor", adjacent.c_str() );
}