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