diff --git a/CHANGES.md b/CHANGES.md index 1b2a41c..c8cb447 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,10 @@ ## 4.0.19 - 3rd October 2026 +* Corrected `cstring_readline()` so `numRead` counts every character read from the stream for the line, including the terminator; CR and LF are not stored; +* Treated a lone CR (not followed by LF, including CR at end of stream) as a line terminator that returns `CSTRING_RC_SUCCESS` and pushes the following character back; +* Added single-line LF, CRLF, and CR cases to **test.component.cstring_readline**; **test.component.cstring_vector_readLines** splits an embedded CR into its own line; +* Passed `temp_file::CloseOnOpen` in **test.component.cstring_readline** so the creating handle is closed before `fopen`, which Windows otherwise rejects as a sharing violation; * `cstring_readline()` returns the result of its opening `cstring_truncate()`, so a readonly destination yields `CSTRING_RC_READONLY` for an empty line or immediate end of file, and the payload and stream position are left unchanged; * Added **test.component.cstring_readline** case `TEST_cstring_readline_READONLY_RETAINS_PAYLOAD`; diff --git a/NEWS.md b/NEWS.md index 02d9c20..b9c2f48 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,7 +4,7 @@ | Date | News Item | Details | | ------------------- | -------------------------------- | ------- | | Available from [**cstring** project on GitHub](https://synesissoftware.com/cstring): | -| 3rd October 2026 | Release of [cstring 4.0.19](https://github.com/synesissoftware/cstring/releases/tag/4.0.19) | `cstring_readline()` reports a failed readonly clear | +| 3rd October 2026 | Release of [cstring 4.0.19](https://github.com/synesissoftware/cstring/releases/tag/4.0.19) | lone CR ends a line; `cstring_readline()` `numRead` includes EOL; `cstring_readline()` reports failed readonly | | 29th September 2026 | Release of [cstring 4.0.18](https://github.com/synesissoftware/cstring/releases/tag/4.0.18) | `_WIN32` arena gate; **win.c**; static `CoTaskMem*` (**ole32**) | | 29th September 2026 | Release of [cstring 4.0.17](https://github.com/synesissoftware/cstring/releases/tag/4.0.17) | Perf tests; `insertAt` fix; component I/O; Windows arena rename | | 27th September 2026 | Release of [cstring 4.0.16](https://github.com/synesissoftware/cstring/releases/tag/4.0.16) | Phase 4b helpers, native `.cmd`, CI dogfood | diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index 663ba1c..7c3f9ed 100644 --- a/include/cstring/cstring.h +++ b/include/cstring/cstring.h @@ -56,7 +56,7 @@ # define CSTRING_VER_CSTRING_H_CSTRING_MAJOR 3 # define CSTRING_VER_CSTRING_H_CSTRING_MINOR 12 # define CSTRING_VER_CSTRING_H_CSTRING_REVISION 2 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 91 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 93 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ @@ -827,17 +827,21 @@ cstring_swap( /** Reads in a line of text from the given text stream + * + * A line ends at LF, at CRLF, or at a lone CR, including a CR at end of + * stream. The terminator is not stored. A lone CR pushes the next character + * back onto the stream; * * \param stm The stream from which the line will be read; * \param pcs The initialised string instance into which the line will be * stored; * \param numRead An optional pointer to a variable to receive the number of - * characters appended to the string. May be NULL; + * characters read from the stream, including any terminator. May be NULL; * * \retval CSTRING_RC_INVALIDSTREAM The stream was not valid (as tested by * ferror()); - * \retval CSTRING_RC_SUCCESS A line terminated by a carriage return was - * read in; + * \retval CSTRING_RC_SUCCESS A line terminated by CR, LF, or CRLF was read + * in; * \retval CSTRING_RC_EOF A line terminated by the end-of-file was read in; * \retval -other- Any other value returned by cstring_appendLen(); * diff --git a/run_all_component_tests.sh b/run_all_component_tests.sh index 2d432f3..c0bd27e 100755 --- a/run_all_component_tests.sh +++ b/run_all_component_tests.sh @@ -175,7 +175,7 @@ Flags/options: does not execute a build before running programs --verbosity - specifies an explicit verbosity (forwarded when supported) + specifies an explicit verbosity, forwarded to each program standard flags: @@ -275,10 +275,7 @@ if [ $status -eq 0 ]; then echo "executing ${fClr}:" fi - if "$f" --verbosity="$Verbosity" 2>/dev/null; then - - : - elif "$f"; then + if "$f" --verbosity="$Verbosity"; then : else diff --git a/run_all_examples.sh b/run_all_examples.sh index a2b652e..967f82c 100755 --- a/run_all_examples.sh +++ b/run_all_examples.sh @@ -32,7 +32,6 @@ AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} ListOnly=0 RunMake=1 SisUseColours=0 -Verbosity=${XTESTS_VERBOSITY:-${TEST_VERBOSITY:-3}} # ########################################################## @@ -135,11 +134,6 @@ while [[ $# -gt 0 ]]; do RunMake=0 ;; - --verbosity) - - shift - Verbosity=$1 - ;; --help) [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" @@ -165,9 +159,6 @@ Flags/options: --no-make does not execute a build before running programs - --verbosity - specifies an explicit verbosity (forwarded when supported) - standard flags: @@ -261,19 +252,10 @@ if [ $status -eq 0 ]; then continue fi - if [ $Verbosity -ge 3 ]; then - - echo - fi - if [ $Verbosity -ge 2 ]; then - - echo "executing ${fClr}:" - fi - - if "$f" --verbosity="$Verbosity" 2>/dev/null; then + echo + echo "executing ${fClr}:" - : - elif "$f"; then + if "$f"; then : else diff --git a/run_all_performance_tests.sh b/run_all_performance_tests.sh index 077d839..6f3c373 100755 --- a/run_all_performance_tests.sh +++ b/run_all_performance_tests.sh @@ -33,7 +33,6 @@ GapGroups=0 ListOnly=0 RunMake=1 SisUseColours=0 -Verbosity=${XTESTS_VERBOSITY:-${TEST_VERBOSITY:-3}} # ########################################################## @@ -140,11 +139,6 @@ while [[ $# -gt 0 ]]; do RunMake=0 ;; - --verbosity) - - shift - Verbosity=$1 - ;; --help) [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" @@ -174,9 +168,6 @@ Flags/options: --no-make does not execute a build before running programs - --verbosity - specifies an explicit verbosity (forwarded when supported) - standard flags: @@ -271,19 +262,10 @@ if [ $status -eq 0 ]; then continue fi - if [ $Verbosity -ge 3 ]; then - - echo - fi - if [ $Verbosity -ge 2 ]; then - - echo "executing ${fClr}:" - fi - - if "$f" --verbosity="$Verbosity" 2>/dev/null; then + echo + echo "executing ${fClr}:" - : - elif "$f"; then + if "$f"; then : else diff --git a/run_all_scratch_tests.sh b/run_all_scratch_tests.sh index 2d83450..5e6659a 100755 --- a/run_all_scratch_tests.sh +++ b/run_all_scratch_tests.sh @@ -32,7 +32,6 @@ AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} ListOnly=0 RunMake=1 SisUseColours=0 -Verbosity=${XTESTS_VERBOSITY:-${TEST_VERBOSITY:-3}} # ########################################################## @@ -135,11 +134,6 @@ while [[ $# -gt 0 ]]; do RunMake=0 ;; - --verbosity) - - shift - Verbosity=$1 - ;; --help) [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" @@ -165,9 +159,6 @@ Flags/options: --no-make does not execute a build before running programs - --verbosity - specifies an explicit verbosity (forwarded when supported) - standard flags: @@ -257,19 +248,10 @@ if [ $status -eq 0 ]; then continue fi - if [ $Verbosity -ge 3 ]; then - - echo - fi - if [ $Verbosity -ge 2 ]; then - - echo "executing ${fClr}:" - fi - - if "$f" --verbosity="$Verbosity" 2>/dev/null; then + echo + echo "executing ${fClr}:" - : - elif "$f"; then + if "$f"; then : else diff --git a/run_all_unit_tests.sh b/run_all_unit_tests.sh index eb49322..301ccae 100755 --- a/run_all_unit_tests.sh +++ b/run_all_unit_tests.sh @@ -173,7 +173,7 @@ Flags/options: accepted for compatibility; this script always runs unit tests only --verbosity - specifies an explicit verbosity (forwarded when supported) + specifies an explicit verbosity, forwarded to each program standard flags: @@ -273,10 +273,7 @@ if [ $status -eq 0 ]; then echo "executing ${fClr}:" fi - if "$f" --verbosity="$Verbosity" 2>/dev/null; then - - : - elif "$f"; then + if "$f" --verbosity="$Verbosity"; then : else diff --git a/src/cstring.core.c b/src/cstring.core.c index 7fbfa3f..bbd6245 100644 --- a/src/cstring.core.c +++ b/src/cstring.core.c @@ -62,7 +62,6 @@ #include #include #include - #ifdef CSTRING_USE_WIDE_STRINGS # include #endif /* CSTRING_USE_WIDE_STRINGS */ @@ -1400,10 +1399,19 @@ cstring_readline( if (EOF == ch) { + if ('\r' == previous) + { + cstring_truncate(pcs, pcs->len - 1u); + + return CSTRING_RC_SUCCESS; + } + return CSTRING_RC_EOF; } else { + ++*numRead; + if ('\n' == ch) { if ('\r' == previous) @@ -1415,19 +1423,29 @@ cstring_readline( } else { - cstring_char_t c1 = (char)ch; - CSTRING_RC rc = cstring_appendLen(pcs, &c1, 1u); - - if (CSTRING_RC_SUCCESS != rc) + if ('\r' == previous) { - return rc; - } + ungetc(ch, stm); + + --*numRead; - ++*numRead; + cstring_truncate(pcs, pcs->len - 1u); + + return CSTRING_RC_SUCCESS; + } + else + { + cstring_char_t c1 = (char)ch; + CSTRING_RC rc = cstring_appendLen(pcs, &c1, 1u); - previous = ch; + if (CSTRING_RC_SUCCESS != rc) + { + return rc; + } + } } + previous = ch; } } } diff --git a/test/component/test.component.cstring_readline/entry.cpp b/test/component/test.component.cstring_readline/entry.cpp index ad5d765..c1b5484 100644 --- a/test/component/test.component.cstring_readline/entry.cpp +++ b/test/component/test.component.cstring_readline/entry.cpp @@ -23,6 +23,7 @@ /* xTests header files */ #include #include +#include /* STLSoft header files */ #include @@ -46,17 +47,24 @@ namespace { - static void TEST_cstring_readline_CALLABILITY(void); - static void TEST_cstring_readline_INVALID_STREAM(void); - static void TEST_cstring_readline_SHORT_MULTILINE(void); - static void TEST_cstring_readline_EMPTY_FILE(void); - static void TEST_cstring_readline_CRLF_AND_MIXED_EOL(void); - static void TEST_cstring_readline_FINAL_EOL_VS_NO_EOL(void); - static void TEST_cstring_readline_CONSECUTIVE_EMPTY_LINES(void); - static void TEST_cstring_readline_LONG_LINES(void); - static void TEST_cstring_readline_MANY_SHORT_LINES(void); - static void TEST_cstring_readline_REUSE_AFTER_LONG_LINE(void); - static void TEST_cstring_readline_READONLY_RETAINS_PAYLOAD(void); + static void TEST_cstring_readline_CALLABILITY(); + static void TEST_cstring_readline_INVALID_STREAM(); + static void TEST_cstring_readline_EMPTY_FILE(); + static void TEST_cstring_readline_SINGLE_LINE_NO_EOL(); + static void TEST_cstring_readline_SINGLE_LINE_WITH_LF(); + static void TEST_cstring_readline_SINGLE_LINE_WITH_CRLF(); + static void TEST_cstring_readline_SINGLE_LINE_WITH_CR(); + static void TEST_cstring_readline_SINGLE_LONG_LINE_WITH_CRLF(); + static void TEST_cstring_readline_SHORT_MULTILINE(); + static void TEST_cstring_readline_CRLF_AND_MIXED_EOL(); + static void TEST_cstring_readline_FINAL_EOL_VS_NO_EOL(); + static void TEST_cstring_readline_CONSECUTIVE_EMPTY_LINES_BY_LF(); + static void TEST_cstring_readline_CONSECUTIVE_EMPTY_LINES_BY_CRLF(); + static void TEST_cstring_readline_CONSECUTIVE_EMPTY_LINES_BY_CR(); + static void TEST_cstring_readline_LONG_LINES(); + static void TEST_cstring_readline_MANY_SHORT_LINES(); + static void TEST_cstring_readline_REUSE_AFTER_LONG_LINE(); + static void TEST_cstring_readline_READONLY_RETAINS_PAYLOAD(); int setup(void*); int teardown(void*); @@ -97,11 +105,18 @@ int main(int argc, char* argv[]) { XTESTS_RUN_CASE(TEST_cstring_readline_CALLABILITY); XTESTS_RUN_CASE(TEST_cstring_readline_INVALID_STREAM); - XTESTS_RUN_CASE(TEST_cstring_readline_SHORT_MULTILINE); XTESTS_RUN_CASE(TEST_cstring_readline_EMPTY_FILE); + XTESTS_RUN_CASE(TEST_cstring_readline_SINGLE_LINE_NO_EOL); + XTESTS_RUN_CASE(TEST_cstring_readline_SINGLE_LINE_WITH_LF); + XTESTS_RUN_CASE(TEST_cstring_readline_SINGLE_LINE_WITH_CRLF); + XTESTS_RUN_CASE(TEST_cstring_readline_SINGLE_LINE_WITH_CR); + XTESTS_RUN_CASE(TEST_cstring_readline_SINGLE_LONG_LINE_WITH_CRLF); + XTESTS_RUN_CASE(TEST_cstring_readline_SHORT_MULTILINE); XTESTS_RUN_CASE(TEST_cstring_readline_CRLF_AND_MIXED_EOL); XTESTS_RUN_CASE(TEST_cstring_readline_FINAL_EOL_VS_NO_EOL); - XTESTS_RUN_CASE(TEST_cstring_readline_CONSECUTIVE_EMPTY_LINES); + XTESTS_RUN_CASE(TEST_cstring_readline_CONSECUTIVE_EMPTY_LINES_BY_LF); + XTESTS_RUN_CASE(TEST_cstring_readline_CONSECUTIVE_EMPTY_LINES_BY_CRLF); + XTESTS_RUN_CASE(TEST_cstring_readline_CONSECUTIVE_EMPTY_LINES_BY_CR); XTESTS_RUN_CASE(TEST_cstring_readline_LONG_LINES); XTESTS_RUN_CASE(TEST_cstring_readline_MANY_SHORT_LINES); XTESTS_RUN_CASE(TEST_cstring_readline_REUSE_AFTER_LONG_LINE); @@ -196,6 +211,8 @@ namespace namespace { + using ::xtests::cpp::util::temp_file; + static void TEST_cstring_readline_CALLABILITY() { @@ -215,6 +232,121 @@ static void TEST_cstring_readline_INVALID_STREAM() TEST_ENUM_EQ(CSTRING_RC_INVALIDSTREAM, cstring_readline(NULL, &cs, &n)); } +static void TEST_cstring_readline_EMPTY_FILE() +{ + /* empty file → first read is EOF with empty payload */ + + temp_file ft(temp_file::EmptyOnOpen | temp_file::DeleteOnClose | temp_file::CloseOnOpen); + FILE* const f = fopen_or_throw(ft.c_str(), "rb"); + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t numRead = 123u; + CSTRING_RC rc = cstring_readline(f, &cs, &numRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(0u, numRead); + TEST_INT_EQ(0u, cs.len); + TEST_MS_EQ("", cs); + + cstring_destroy(&cs); +} + +static void TEST_cstring_readline_SINGLE_LINE_NO_EOL() +{ + static char const input[] = "abc"; + temp_file ft(temp_file::EmptyOnOpen | temp_file::DeleteOnClose | temp_file::CloseOnOpen, input, STLSOFT_NUM_ELEMENTS(input) - 1); + FILE* const f = fopen_or_throw(ft.c_str(), "rb"); + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t numRead = 123u; + CSTRING_RC rc = cstring_readline(f, &cs, &numRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(3u, numRead); + TEST_INT_EQ(3u, cs.len); + TEST_MS_EQ("abc", cs); + + cstring_destroy(&cs); +} + +static void TEST_cstring_readline_SINGLE_LINE_WITH_LF() +{ + static char const input[] = "abc\n"; + temp_file ft(temp_file::EmptyOnOpen | temp_file::DeleteOnClose | temp_file::CloseOnOpen, input, STLSOFT_NUM_ELEMENTS(input) - 1); + FILE* const f = fopen_or_throw(ft.c_str(), "rb"); + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t numRead = 123u; + CSTRING_RC rc = cstring_readline(f, &cs, &numRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(4u, numRead); + TEST_INT_EQ(3u, cs.len); + TEST_MS_EQ("abc", cs); + + cstring_destroy(&cs); +} + +static void TEST_cstring_readline_SINGLE_LINE_WITH_CRLF() +{ + static char const input[] = "abc\r\n"; + temp_file ft(temp_file::EmptyOnOpen | temp_file::DeleteOnClose | temp_file::CloseOnOpen, input, STLSOFT_NUM_ELEMENTS(input) - 1); + FILE* const f = fopen_or_throw(ft.c_str(), "rb"); + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t numRead = 123u; + CSTRING_RC rc = cstring_readline(f, &cs, &numRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(5u, numRead); + TEST_INT_EQ(3u, cs.len); + TEST_MS_EQ("abc", cs); + + cstring_destroy(&cs); +} + +static void TEST_cstring_readline_SINGLE_LINE_WITH_CR() +{ + static char const input[] = "abc\r"; + temp_file ft(temp_file::EmptyOnOpen | temp_file::DeleteOnClose | temp_file::CloseOnOpen, input, STLSOFT_NUM_ELEMENTS(input) - 1); + FILE* const f = fopen_or_throw(ft.c_str(), "rb"); + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t numRead = 123u; + CSTRING_RC rc = cstring_readline(f, &cs, &numRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(4u, numRead); + TEST_INT_EQ(3u, cs.len); + TEST_MS_EQ("abc", cs); + + cstring_destroy(&cs); +} + +static void TEST_cstring_readline_SINGLE_LONG_LINE_WITH_CRLF() +{ + static char const input[] = "01234567890123456789012345678901234567890123456789012345678901234567890123456789\r\n"; + temp_file ft(temp_file::EmptyOnOpen | temp_file::DeleteOnClose | temp_file::CloseOnOpen, input, STLSOFT_NUM_ELEMENTS(input) - 1); + FILE* const f = fopen_or_throw(ft.c_str(), "rb"); + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t numRead = 123u; + CSTRING_RC rc = cstring_readline(f, &cs, &numRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(82u, numRead); + TEST_INT_EQ(80u, cs.len); + TEST_MS_EQ("01234567890123456789012345678901234567890123456789012345678901234567890123456789", cs); + + cstring_destroy(&cs); +} + static void TEST_cstring_readline_SHORT_MULTILINE() { { @@ -246,48 +378,48 @@ static void TEST_cstring_readline_SHORT_MULTILINE() cstring_t_DEFAULT, cstring_t_DEFAULT, }; - size_t lengths[7]; + size_t readCounts[7]; - rc = cstring_readline(f, &strings[0], &lengths[0]); + rc = cstring_readline(f, &strings[0], &readCounts[0]); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); - TEST_INT_EQ(0u, lengths[0]); + TEST_INT_EQ(1u, readCounts[0]); TEST_MS_EQ("", strings[0]); - rc = cstring_readline(f, &strings[1], &lengths[1]); + rc = cstring_readline(f, &strings[1], &readCounts[1]); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); - TEST_INT_EQ(3u, lengths[1]); + TEST_INT_EQ(4u, readCounts[1]); TEST_MS_EQ("abc", strings[1]); - rc = cstring_readline(f, &strings[2], &lengths[2]); + rc = cstring_readline(f, &strings[2], &readCounts[2]); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); - TEST_INT_EQ(6u, lengths[2]); + TEST_INT_EQ(7u, readCounts[2]); TEST_MS_EQ("abcdef", strings[2]); - rc = cstring_readline(f, &strings[3], &lengths[3]); + rc = cstring_readline(f, &strings[3], &readCounts[3]); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); - TEST_INT_EQ(12u, lengths[3]); + TEST_INT_EQ(13u, readCounts[3]); TEST_MS_EQ("abcdefghijkl", strings[3]); - rc = cstring_readline(f, &strings[4], &lengths[4]); + rc = cstring_readline(f, &strings[4], &readCounts[4]); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); - TEST_INT_EQ(26u, lengths[4]); + TEST_INT_EQ(27u, readCounts[4]); TEST_MS_EQ("abcdefghijklmnopqrstuvwxyz", strings[4]); - rc = cstring_readline(f, &strings[5], &lengths[5]); + rc = cstring_readline(f, &strings[5], &readCounts[5]); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); - TEST_INT_EQ(62u, lengths[5]); + TEST_INT_EQ(63u, readCounts[5]); TEST_MS_EQ("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", strings[5]); - rc = cstring_readline(f, &strings[6], &lengths[6]); + rc = cstring_readline(f, &strings[6], &readCounts[6]); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); - TEST_INT_EQ(3u, lengths[6]); + TEST_INT_EQ(3u, readCounts[6]); TEST_MS_EQ("xyz", strings[6]); { for (size_t i = 0; i != 7; ++i) @@ -297,27 +429,6 @@ static void TEST_cstring_readline_SHORT_MULTILINE() } } -static void TEST_cstring_readline_EMPTY_FILE() -{ - /* empty file → first read is EOF with empty payload */ - write_bytes(TEST_FILE_NAME, "", 0); - - FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); - - stlsoft::scoped_handle scoper(f, ::fclose); - - cstring_t cs = cstring_t_DEFAULT; - size_t n = 123u; - CSTRING_RC rc = cstring_readline(f, &cs, &n); - - REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); - TEST_INT_EQ(0u, n); - TEST_INT_EQ(0u, cs.len); - TEST_MS_EQ("", cs); - - cstring_destroy(&cs); -} - static void TEST_cstring_readline_CRLF_AND_MIXED_EOL() { /* CRLF, mixed EOL, and lone CR mid-line (binary I/O so CR is visible) */ @@ -338,37 +449,43 @@ static void TEST_cstring_readline_CRLF_AND_MIXED_EOL() stlsoft::scoped_handle scoper(f, ::fclose); cstring_t cs = cstring_t_DEFAULT; - size_t n; + size_t numRead; CSTRING_RC rc; - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); - /* CRLF: CR is appended then stripped; numRead still counts the CR */ - TEST_INT_EQ(4u, n); + /* CRLF: CR is appended then stripped; numRead counts the CR and the LF */ + TEST_INT_EQ(5u, numRead); TEST_INT_EQ(3u, cs.len); TEST_MS_EQ("one", cs); - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); - TEST_INT_EQ(3u, n); + TEST_INT_EQ(4u, numRead); TEST_INT_EQ(3u, cs.len); TEST_MS_EQ("two", cs); - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); - TEST_INT_EQ(6u, n); + TEST_INT_EQ(7u, numRead); TEST_INT_EQ(5u, cs.len); TEST_MS_EQ("three", cs); - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(4u, numRead); + TEST_INT_EQ(3u, cs.len); + TEST_MS_EQ("has", cs); + + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); - TEST_INT_EQ(10u, n); - TEST_INT_EQ(9u, cs.len); - TEST_MS_EQ("has\rembed", cs); + TEST_INT_EQ(7u, numRead); + TEST_INT_EQ(5u, cs.len); + TEST_MS_EQ("embed", cs); - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); - TEST_INT_EQ(3u, n); + TEST_INT_EQ(3u, numRead); TEST_INT_EQ(3u, cs.len); TEST_MS_EQ("end", cs); @@ -378,84 +495,145 @@ static void TEST_cstring_readline_CRLF_AND_MIXED_EOL() static void TEST_cstring_readline_FINAL_EOL_VS_NO_EOL() { /* final line with EOL vs without EOL */ - { - write_string_bytes(TEST_FILE_NAME, std::string("alpha\nbeta\n")); - FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); - - stlsoft::scoped_handle scoper(f, ::fclose); + { + static char const input[] = "alpha\nbeta\n"; + temp_file ft(temp_file::EmptyOnOpen | temp_file::DeleteOnClose | temp_file::CloseOnOpen, input, STLSOFT_NUM_ELEMENTS(input) - 1); + FILE* const f = fopen_or_throw(ft.c_str(), "rb"); + stlsoft::scoped_handle scoper(f, ::fclose); cstring_t cs = cstring_t_DEFAULT; - size_t n; + size_t numRead; CSTRING_RC rc; - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_MS_EQ("alpha", cs); - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_MS_EQ("beta", cs); - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); - TEST_INT_EQ(0u, n); + TEST_INT_EQ(0u, numRead); TEST_INT_EQ(0u, cs.len); cstring_destroy(&cs); } { - write_string_bytes(TEST_FILE_NAME, std::string("alpha\nbeta")); - - FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); - - stlsoft::scoped_handle scoper(f, ::fclose); + static char const input[] = "alpha\nbeta"; + temp_file ft(temp_file::EmptyOnOpen | temp_file::DeleteOnClose | temp_file::CloseOnOpen, input, STLSOFT_NUM_ELEMENTS(input) - 1); + FILE* const f = fopen_or_throw(ft.c_str(), "rb"); + stlsoft::scoped_handle scoper(f, ::fclose); cstring_t cs = cstring_t_DEFAULT; - size_t n; + size_t numRead; CSTRING_RC rc; - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_MS_EQ("alpha", cs); - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); - TEST_INT_EQ(4u, n); + TEST_INT_EQ(4u, numRead); TEST_MS_EQ("beta", cs); cstring_destroy(&cs); } } -static void TEST_cstring_readline_CONSECUTIVE_EMPTY_LINES() +static void TEST_cstring_readline_CONSECUTIVE_EMPTY_LINES_BY_LF() { /* consecutive empty lines */ - write_string_bytes(TEST_FILE_NAME, std::string("\n\n\n")); - FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + static char const input[] = "\n\n\n"; + temp_file ft(temp_file::EmptyOnOpen | temp_file::DeleteOnClose | temp_file::CloseOnOpen, input, STLSOFT_NUM_ELEMENTS(input) - 1); + FILE* const f = fopen_or_throw(ft.c_str(), "rb"); + stlsoft::scoped_handle scoper(f, ::fclose); - stlsoft::scoped_handle scoper(f, ::fclose); + cstring_t cs = cstring_t_DEFAULT; + size_t numRead; + CSTRING_RC rc; + + { for (int i = 0; i != 3; ++i) + { + rc = cstring_readline(f, &cs, &numRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(1u, numRead); + TEST_INT_EQ(0u, cs.len); + TEST_MS_EQ("", cs); + }} + + rc = cstring_readline(f, &cs, &numRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(0u, numRead); + + cstring_destroy(&cs); +} + +static void TEST_cstring_readline_CONSECUTIVE_EMPTY_LINES_BY_CRLF() +{ + /* consecutive empty lines */ + + static char const input[] = "\r\n\r\n\r\n"; + temp_file ft(temp_file::EmptyOnOpen | temp_file::DeleteOnClose | temp_file::CloseOnOpen, input, STLSOFT_NUM_ELEMENTS(input) - 1); + FILE* const f = fopen_or_throw(ft.c_str(), "rb"); + stlsoft::scoped_handle scoper(f, ::fclose); cstring_t cs = cstring_t_DEFAULT; - size_t n; + size_t numRead; CSTRING_RC rc; { for (int i = 0; i != 3; ++i) { - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); - TEST_INT_EQ(0u, n); + TEST_INT_EQ(2u, numRead); + TEST_INT_EQ(0u, cs.len); + TEST_MS_EQ("", cs); + }} + + rc = cstring_readline(f, &cs, &numRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(0u, numRead); + + cstring_destroy(&cs); +} + +static void TEST_cstring_readline_CONSECUTIVE_EMPTY_LINES_BY_CR() +{ + /* consecutive empty lines */ + + static char const input[] = "\r\r\r"; + temp_file ft(temp_file::EmptyOnOpen | temp_file::DeleteOnClose | temp_file::CloseOnOpen, input, STLSOFT_NUM_ELEMENTS(input) - 1); + FILE* const f = fopen_or_throw(ft.c_str(), "rb"); + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t numRead; + CSTRING_RC rc; + + { for (int i = 0; i != 3; ++i) + { + rc = cstring_readline(f, &cs, &numRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(1u, numRead); TEST_INT_EQ(0u, cs.len); TEST_MS_EQ("", cs); }} - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); - TEST_INT_EQ(0u, n); + TEST_INT_EQ(0u, numRead); cstring_destroy(&cs); } @@ -486,18 +664,18 @@ static void TEST_cstring_readline_LONG_LINES() stlsoft::scoped_handle scoper(f, ::fclose); cstring_t cs = cstring_t_DEFAULT; - size_t n; + size_t numRead; CSTRING_RC rc; - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); - TEST_INT_EQ(len, n); + TEST_INT_EQ(len + 1, numRead); TEST_INT_EQ(len, cs.len); REQUIRE(TEST_MS_EQ(line.c_str(), cs)); - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); - TEST_INT_EQ(len / 2u == 0 ? 1u : len / 2u, n); + TEST_INT_EQ(len / 2u == 0 ? 1u : len / 2u, numRead); cstring_destroy(&cs); }} @@ -530,22 +708,22 @@ static void TEST_cstring_readline_MANY_SHORT_LINES() stlsoft::scoped_handle scoper(f, ::fclose); cstring_t cs = cstring_t_DEFAULT; - size_t n; + size_t numRead; CSTRING_RC rc; { for (size_t i = 0; i != num_lines; ++i) { - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); - TEST_INT_EQ(expected[i].size(), n); + TEST_INT_EQ(expected[i].size() + 1, numRead); TEST_MS_EQ(expected[i].c_str(), cs); }} - rc = cstring_readline(f, &cs, &n); + rc = cstring_readline(f, &cs, &numRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); - TEST_INT_EQ(0u, n); + TEST_INT_EQ(0u, numRead); cstring_destroy(&cs); } @@ -614,7 +792,7 @@ static void TEST_cstring_readline_READONLY_RETAINS_PAYLOAD() rc = cstring_readline(f, &fresh, &n); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); - TEST_INT_EQ(0u, n); + TEST_INT_EQ(1u, n); TEST_MS_EQ("", fresh); cstring_destroy(&fresh); diff --git a/test/component/test.component.cstring_vector_readLines/entry.cpp b/test/component/test.component.cstring_vector_readLines/entry.cpp index 5e6810b..be3b18c 100644 --- a/test/component/test.component.cstring_vector_readLines/entry.cpp +++ b/test/component/test.component.cstring_vector_readLines/entry.cpp @@ -4,7 +4,7 @@ * Purpose: Component-tests `cstring_vector_readLines()`. * * Created: 27th September 2026 - * Updated: 27th September 2026 + * Updated: 3rd October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -46,16 +46,16 @@ namespace { - static void TEST_cstring_vector_readLines_CALLABILITY(void); - static void TEST_cstring_vector_readLines_EMPTY_FILE(void); - static void TEST_cstring_vector_readLines_SHORT_MULTILINE(void); - static void TEST_cstring_vector_readLines_CRLF_AND_MIXED_EOL(void); - static void TEST_cstring_vector_readLines_FINAL_EOL_AND_EMPTY_AT_EOF(void); - static void TEST_cstring_vector_readLines_CONSECUTIVE_EMPTY_LINES(void); - static void TEST_cstring_vector_readLines_APPEND_ONTO_EXISTING(void); - static void TEST_cstring_vector_readLines_LONG_LINES(void); - static void TEST_cstring_vector_readLines_MANY_SHORT_LINES(void); - static void TEST_cstring_vector_readLines_EMPTY_LINE_IN_MIDDLE(void); + static void TEST_cstring_vector_readLines_CALLABILITY(); + static void TEST_cstring_vector_readLines_EMPTY_FILE(); + static void TEST_cstring_vector_readLines_SHORT_MULTILINE(); + static void TEST_cstring_vector_readLines_CRLF_AND_MIXED_EOL(); + static void TEST_cstring_vector_readLines_FINAL_EOL_AND_EMPTY_AT_EOF(); + static void TEST_cstring_vector_readLines_CONSECUTIVE_EMPTY_LINES(); + static void TEST_cstring_vector_readLines_APPEND_ONTO_EXISTING(); + static void TEST_cstring_vector_readLines_LONG_LINES(); + static void TEST_cstring_vector_readLines_MANY_SHORT_LINES(); + static void TEST_cstring_vector_readLines_EMPTY_LINE_IN_MIDDLE(); int setup(void*); int teardown(void*); @@ -281,13 +281,14 @@ static void TEST_cstring_vector_readLines_CRLF_AND_MIXED_EOL() CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); - TEST_INT_EQ(5u, numLinesRead); - REQUIRE(TEST_INT_EQ(5u, csv.len)); + TEST_INT_EQ(6u, numLinesRead); + REQUIRE(TEST_INT_EQ(6u, csv.len)); TEST_MS_EQ("one", csv.ptr[0]); TEST_MS_EQ("two", csv.ptr[1]); TEST_MS_EQ("three", csv.ptr[2]); - TEST_MS_EQ("has\rembed", csv.ptr[3]); - TEST_MS_EQ("end", csv.ptr[4]); + TEST_MS_EQ("has", csv.ptr[3]); + TEST_MS_EQ("embed", csv.ptr[4]); + TEST_MS_EQ("end", csv.ptr[5]); cstring_vector_destroy(&csv); } diff --git a/test/component/test.component.cstring_writeline/entry.cpp b/test/component/test.component.cstring_writeline/entry.cpp index fa52b71..05017c3 100644 --- a/test/component/test.component.cstring_writeline/entry.cpp +++ b/test/component/test.component.cstring_writeline/entry.cpp @@ -4,7 +4,7 @@ * Purpose: Unit-tests of `cstring_write()` and `cstring_writeline()`. * * Created: 10th August 2020 - * Updated: 27th September 2026 + * Updated: 2nd October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -41,16 +41,16 @@ namespace { - static void TEST_cstring_writeline_CALLABILITY(void); - static void TEST_cstring_writeline_INVALID_STREAM(void); - static void TEST_cstring_writeline_MULTIPLE_LINES(void); - static void TEST_cstring_write_CONCATENATED(void); + static void TEST_cstring_writeline_CALLABILITY(); + static void TEST_cstring_writeline_INVALID_STREAM(); + static void TEST_cstring_writeline_MULTIPLE_LINES(); + static void TEST_cstring_write_CONCATENATED(); int setup(void*); int teardown(void*); - } // anonymous namespace + /* ///////////////////////////////////////////////////////////////////////// * constants & definitions */ @@ -259,10 +259,8 @@ static void TEST_cstring_write_CONCATENATED() TEST_MS_EQ(expected, out_lines[0]); } } - - - } // anonymous namespace + /* ///////////////////////////// end of file //////////////////////////// */ diff --git a/test/performance/test.performance.cstring_vector/main.cpp b/test/performance/test.performance.cstring_vector/main.cpp index f783477..5de587b 100644 --- a/test/performance/test.performance.cstring_vector/main.cpp +++ b/test/performance/test.performance.cstring_vector/main.cpp @@ -8,7 +8,7 @@ * getline, platformstl::file_lines). * * Created: 23rd September 2026 - * Updated: 23rd September 2026 + * Updated: 2nd October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -169,7 +169,6 @@ make_cstring_payload( return cs; } - } // anonymous namespace diff --git a/test/unit/test.unit.cstring.1/entry.cpp b/test/unit/test.unit.cstring.1/entry.cpp index 214a071..ac49435 100644 --- a/test/unit/test.unit.cstring.1/entry.cpp +++ b/test/unit/test.unit.cstring.1/entry.cpp @@ -4,7 +4,7 @@ * Purpose: Unit-tests for general functionality. * * Created: 23rd May 2009 - * Updated: 29th September 2026 + * Updated: 2nd October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -51,29 +51,29 @@ namespace { - static void test_1_0(void); - static void test_1_1(void); - static void test_1_2(void); - static void test_1_3(void); - static void test_1_4(void); - static void test_1_5(void); - static void test_1_6(void); - static void test_1_7(void); - static void test_1_8(void); - static void test_1_9(void); - - static void test_2_0(void); - static void test_2_1(void); - static void test_2_2(void); - static void test_2_3(void); - static void test_2_4(void); - static void test_2_5(void); - static void test_2_6(void); - static void test_2_7(void); - static void test_2_8(void); - static void test_2_9(void); - static void test_2_10(void); - static void test_2_11(void); + static void test_1_0(); + static void test_1_1(); + static void test_1_2(); + static void test_1_3(); + static void test_1_4(); + static void test_1_5(); + static void test_1_6(); + static void test_1_7(); + static void test_1_8(); + static void test_1_9(); + + static void test_2_0(); + static void test_2_1(); + static void test_2_2(); + static void test_2_3(); + static void test_2_4(); + static void test_2_5(); + static void test_2_6(); + static void test_2_7(); + static void test_2_8(); + static void test_2_9(); + static void test_2_10(); + static void test_2_11(); } // anonymous namespace