diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc1f99a..836e319 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,6 @@ on: - rc1 - rc2 - rc3 - - hash-review pull_request: concurrency: diff --git a/CHANGES.md b/CHANGES.md index c322040..e7c6abb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,17 @@ # cstring - Changes +## 4.1.0-alpha2 - 7th October 2026 + +* performance optimisations; + + +## 4.1.0-beta1 - 21st October 2026 + +* Brought the tip CMake runners up to **misc-dev-scripts** 0.6.2 and merged that gold into **prepare_cmake.sh**, keeping the cstring `-D` flags and `--gap-groups`; +* Defined `CSTRING_VER_RELSTATUS` (`0x81`, beta 1) and aliased `CSTRING_VER_ALPHABETA` to it; + + ## 4.1.0-alpha1 - 6th October 2026 * Added 64-bit `djb2` hash functions: `cstring_hash_djb2()`, `cstring_hash_djb2_case()`, `cstring_hash_djb2_buf()`, and `cstring_hash_djb2_buf_case()`; diff --git a/Doxyfile b/Doxyfile index 516eed8..b445d2c 100644 --- a/Doxyfile +++ b/Doxyfile @@ -2,7 +2,7 @@ PROJECT_NAME = "cstring" PROJECT_BRIEF = "Extensible C-style strings and vectors of such, for Unix and Windows" -PROJECT_NUMBER = 4.1.0-alpha1 +PROJECT_NUMBER = 4.1.1-alpha1 # Prefer SIS_CMAKE_BUILD_DIR-aligned output (same convention as Diagnosticism). # ./dox/ remains gitignored for legacy/local runs that override OUTPUT_DIRECTORY. diff --git a/NEWS.md b/NEWS.md index 8c8100c..fedcdd6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,8 @@ | Date | News Item | Details | | ------------------- | ------------------------------------ | ------- | | Available from [**cstring** project on GitHub](https://synesissoftware.com/cstring): | +| 24th October 2026 | Release of [cstring 4.1.1-alpha1](https://github.com/synesissoftware/cstring/releases/tag/4.1.1-alpha1) | Performance optimisations | +| 21st October 2026 | Release of [cstring 4.1.0-beta1](https://github.com/synesissoftware/cstring/releases/tag/4.1.0-beta1) | helper gold 0.6.2; `CSTRING_VER_RELSTATUS` | | 6th October 2026 | Release of [cstring 4.1.0-alpha1](https://github.com/synesissoftware/cstring/releases/tag/4.1.0-alpha1) | `djb2`, `FNV-1a`, and `SDBM`; `cstring_equal()` / `cstring_compare()`; `--wide-strings`; counted copies stop at `cch` | | 4th October 2026 | Release of [cstring 4.0.19](https://github.com/synesissoftware/cstring/releases/tag/4.0.19) | lone CR ends a line; `numRead` includes EOL; readonly truncate; zero-size realloc frees; **test.performance.cstring_readline**; shortened test and example directories; **test.unit.cstring**; README usage modes | | 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**) | diff --git a/TODO.md b/TODO.md index f7934c5..14d943e 100644 --- a/TODO.md +++ b/TODO.md @@ -16,9 +16,9 @@ * [x] ~~~Delete Visual Studio 2003+ files~~~ - ✅; * [x] ~~~discriminate on `_WIN32` in implementation (and maybe also in API)~~~ - ✅; * [x] ~~~check `CSTRING_USE_WINAPI_`~~~; -* [ ] custom arena(s); -* [ ] wide-string CI; +* [x] ~~~wide-string CI~~~ - ✅; * [ ] `cstring_vector_readlineEx()` that takes a flag to prevent truncate, thereby allowing client code to add to an existing string; +* [ ] custom arena(s); * [ ] when go to 5.x, change the name of `cstring_vector_readLines()` to `cstring_vector_readlines()`; * [ ] when go to 5.x, consider use of SSO; * [ ] when go to 5.x, consider expanding `cstring_vector_t` to allow it to own the memory of the strings it manages, such that can do a single file read and then break up into strings without allocating payload memory; @@ -64,7 +64,7 @@ * [ ] Hash API in cstring/hash.h; * [ ] Vector API in cstring/vector.h; * [ ] Umbrella / All API in cstring/cstring.h; -* [-] ~~~Makefiles (legacy build trees removed; CMake-only)~~~ - ❌; +* [x] ~~~Makefiles (legacy build trees removed; CMake-only)~~~ - ❌; * [ ] Packages: * [ ] vcpkg; * [ ] HomeBrew; diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index 92aa085..429e2c4 100644 --- a/include/cstring/cstring.h +++ b/include/cstring/cstring.h @@ -4,7 +4,7 @@ * Purpose: Definition of the cstring.core API. * * Created: 16th June 1994 - * Updated: 6th October 2026 + * Updated: 24th October 2026 * * Home: http://synesis.com.au/software/ * @@ -56,7 +56,7 @@ # define CSTRING_VER_CSTRING_H_CSTRING_MAJOR 3 # define CSTRING_VER_CSTRING_H_CSTRING_MINOR 13 # define CSTRING_VER_CSTRING_H_CSTRING_REVISION 3 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 100 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 103 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ @@ -105,6 +105,8 @@ # define CSTRING_VER_4_0_18 0x040012ff # define CSTRING_VER_4_0_19 0x040013ff # define CSTRING_VER_4_1_0_A1 0x04010041 +# define CSTRING_VER_4_1_0_A2 0x04010042 +# define CSTRING_VER_4_1_1_A1 0x04010141 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR @@ -119,24 +121,29 @@ * The patch version number of **cstring** */ +/** \def CSTRING_VER_RELSTATUS + * The release-status number of **cstring** + */ + /** \def CSTRING_VER * The current composite version number of **cstring** */ #define CSTRING_VER_MAJOR 4 #define CSTRING_VER_MINOR 1 -#define CSTRING_VER_PATCH 0 -#define CSTRING_VER_ALPHABETA 0x41 +#define CSTRING_VER_PATCH 1 +#define CSTRING_VER_RELSTATUS 0x41 #define CSTRING_VER \ (0\ | ( CSTRING_VER_MAJOR << 24 ) \ | ( CSTRING_VER_MINOR << 16 ) \ | ( CSTRING_VER_PATCH << 8 ) \ - | ( CSTRING_VER_ALPHABETA << 0 ) \ + | ( CSTRING_VER_RELSTATUS << 0 ) \ ) #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION +# define CSTRING_VER_ALPHABETA CSTRING_VER_RELSTATUS # define CSTRING_VER_REVISION CSTRING_VER_PATCH #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ diff --git a/include/cstring/cstring.vector.h b/include/cstring/cstring.vector.h index 25df446..9e7c3d3 100644 --- a/include/cstring/cstring.vector.h +++ b/include/cstring/cstring.vector.h @@ -4,7 +4,7 @@ * Purpose: Definition of the cstring.vector API. * * Created: 21st January 2012 - * Updated: 28th September 2026 + * Updated: 29th September 2026 * * Home: http://synesis.com.au/software/ * diff --git a/prepare_cmake.sh b/prepare_cmake.sh index ce66efe..946350c 100755 --- a/prepare_cmake.sh +++ b/prepare_cmake.sh @@ -34,16 +34,24 @@ Configuration=Release ExamplesDisabled=0 MinGW=$(sis_cmake_is_truey "${SIS_CMAKE_MINGW:-}" && echo 1 || echo 0) MSVC_MT=0 -NO_cxx=0 -NO_p99=0 -NO_shwild=0 RunMake=0 SisUseColours=0 -STLSoftDirGiven= TestingDisabled=0 VerboseMakefile=0 -WideStrings=0 +# ########################################################## +# PROJECT-SPECIFIC FLAGS (customise per library; keep marker) +# +# Add project-only options here (e.g. --no-cpp, --stlsoft-root-dir, +# --no-shwild). The common body below must stay aligned with +# misc-dev-scripts/shell-scripts/cmake-helpers/prepare_cmake.sh. + + +NO_cxx=0 +NO_p99=0 +NO_shwild=0 +STLSoftDirGiven= +WideStrings=0 # ########################################################## # colours diff --git a/run_all_examples.cmd b/run_all_examples.cmd index 11273cb..b10e3d2 100644 --- a/run_all_examples.cmd +++ b/run_all_examples.cmd @@ -4,6 +4,8 @@ SETLOCAL SET SCRIPT_DIRECTORY=%~dp0 SET SCRIPT_PATH_DOC=%~n0[%~x0] +SET "ALLOWED_TO_FAIL_FILE=%SCRIPT_DIRECTORY%.sis\ci_examples_allowed_to_fail.txt" +SET "PROGRAM_STATUS=0" IF DEFINED SIS_CMAKE_BUILD_DIR ( @@ -45,6 +47,22 @@ Flags/options: ^ displays this help and terminates ^ +^ + + files: ^ + +^ + + .sis\ci_examples_allowed_to_fail.txt ^ + + optional list of example programs ^(one name per line; ^ + + lines beginning with '#' are ignored^) that are allowed to fail; ^ + + such a program is still executed, but a non-zero exit is ^ + + reported as anticipated and does not stop the run ^ + EXIT /B 0 ) ELSE ( @@ -78,12 +96,36 @@ SET SIS_EXAMPLE_SMOKE=1 ECHO Running all %ProjectName% example programs -FOR /F "usebackq" %%f IN (`DIR /A:-D /B /S "%CMAKE_DIR%" ^| FINDSTR /I example.*\.exe$`) DO ( - - ECHO . - ECHO executing %%f - %%f - IF ERRORLEVEL 1 EXIT /B %ERRORLEVEL% -) - -ENDLOCAL +FOR /F "usebackq" %%f IN (`DIR /A:-D /B /S "%CMAKE_DIR%" ^| FINDSTR /I example.*\.exe$`) DO CALL :run_program "%%f" + +ENDLOCAL & EXIT /B %PROGRAM_STATUS% + + +REM ########################################################## +REM subroutines + +:run_program +IF %PROGRAM_STATUS% NEQ 0 EXIT /B 0 +ECHO . +ECHO executing %~1 +"%~1" +SET "FAILURE_STATUS=%ERRORLEVEL%" +IF %FAILURE_STATUS% EQU 0 EXIT /B 0 +CALL :is_allowed_to_fail "%~1" +IF NOT DEFINED ALLOWED_TO_FAIL GOTO run_program_failed +ECHO anticipated failure: %~1 exited with status %FAILURE_STATUS%; it is listed in .sis\ci_examples_allowed_to_fail.txt +EXIT /B 0 +:run_program_failed +SET "PROGRAM_STATUS=%FAILURE_STATUS%" +EXIT /B 0 + +:is_allowed_to_fail +SET "ALLOWED_TO_FAIL=" +IF NOT EXIST "%ALLOWED_TO_FAIL_FILE%" EXIT /B 0 +FOR /F "usebackq eol=# tokens=1" %%n IN ("%ALLOWED_TO_FAIL_FILE%") DO CALL :match_allowed "%%n" "%~n1" "%~nx1" +EXIT /B 0 + +:match_allowed +IF /I "%~1"=="%~2" SET "ALLOWED_TO_FAIL=1" +IF /I "%~1"=="%~3" SET "ALLOWED_TO_FAIL=1" +EXIT /B 0 diff --git a/run_all_examples.sh b/run_all_examples.sh index 967f82c..2f03c18 100755 --- a/run_all_examples.sh +++ b/run_all_examples.sh @@ -27,6 +27,7 @@ CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} ProjectNameFile="$Dir/.sis/project_name.txt" ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") ScriptPath=$0 +AllowedToFailFile="$Dir/.sis/ci_examples_allowed_to_fail.txt" AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} ListOnly=0 @@ -115,6 +116,49 @@ sis_cmake_build() { cmake "${args[@]}" } +# Reduces a program path to its (lowercase) name, without any directory or +# .exe suffix. +sis_program_stem() { + + local p="${1//\\//}" + + p="${p##*/}" + + case "$p" in + *.exe|*.EXE) p="${p%.*}" ;; + esac + + printf '%s' "$p" | tr '[:upper:]' '[:lower:]' +} + +# Succeeds if the program is named (by name or stem, case-insensitively) in +# the optional .sis/ci_examples_allowed_to_fail.txt file; blank lines and +# lines beginning with '#' are ignored. +sis_is_allowed_to_fail() { + + local name line + + [ -f "$AllowedToFailFile" ] || return 1 + + name=$(sis_program_stem "$1") + + while IFS= read -r line || [ -n "$line" ]; do + + line="${line//$'\r'/}" + + case "$line" in + ''|\#*) continue ;; + esac + + if [ "$name" = "$(sis_program_stem "$line")" ]; then + + return 0 + fi + done < "$AllowedToFailFile" + + return 1 +} + # ########################################################## # command-line handling @@ -160,6 +204,15 @@ Flags/options: does not execute a build before running programs + files: + + .sis/ci_examples_allowed_to_fail.txt + optional list of example programs (one name per line; blank lines + and lines beginning with '#' are ignored) that are allowed to fail; + such a program is still executed, but a non-zero exit is reported as + anticipated and neither stops the run nor affects the exit status + + standard flags: --help @@ -247,7 +300,13 @@ if [ $status -eq 0 ]; then if [ $ListOnly -ne 0 ]; then - echo "would execute ${fClr}:" + if sis_is_allowed_to_fail "$f"; then + + echo "would execute ${fClr} (allowed to fail):" + else + + echo "would execute ${fClr}:" + fi continue fi @@ -260,11 +319,25 @@ if [ $status -eq 0 ]; then : else - status=$? + fStatus=$? + + if sis_is_allowed_to_fail "$f"; then + + echo "${SisClr_Yellow}${SisClr_Bold}anticipated failure${SisClr_None}: ${fClr} exited with status ${fStatus}; it is listed in .sis/ci_examples_allowed_to_fail.txt" + + continue + fi + + status=$fStatus break 1 fi - done < <(find "$CMakeDir" -type f \( -name 'example*' ! -name '*.md' \) \( -perm -100 -o -name '*.exe' \) -print0 2>/dev/null | sort -z) + done < <(find "$CMakeDir" -type f \( -name 'example*' ! -name '*.md' \) \ + ! -path '*/CMakeFiles/*' \ + ! -name '*.a' \ + ! -name '*.d' \ + ! -name '*.lib' \ + \( -perm -100 -o -name '*.exe' \) -print0 2>/dev/null | sort -z) if [ $NumPrograms -eq 0 ]; then diff --git a/run_all_scratch_tests.cmd b/run_all_scratch_tests.cmd index 9b05623..b5436ef 100644 --- a/run_all_scratch_tests.cmd +++ b/run_all_scratch_tests.cmd @@ -1,9 +1,13 @@ @echo off -SETLOCAL +SETLOCAL ENABLEDELAYEDEXPANSION SET SCRIPT_DIRECTORY=%~dp0 SET SCRIPT_PATH_DOC=%~n0[%~x0] +SET "ALLOWED_TO_FAIL_FILE=%SCRIPT_DIRECTORY%.sis\ci_scratch_tests_allowed_to_fail.txt" +SET "SKIP_INTERACTIVE_FILE=%SCRIPT_DIRECTORY%.sis\ci_scratch_tests_skip_interactive.txt" +SET "PROGRAM_STATUS=0" +SET "SKIP_INTERACTIVE=0" IF DEFINED SIS_CMAKE_BUILD_DIR ( @@ -35,6 +39,14 @@ Flags/options: ^ behaviour: ^ +^ + + --skip-interactive ^ + + skips programs listed in .sis\ci_scratch_tests_skip_interactive.txt ^ + + ^(GUI / speech / desktop-interactive; unsuitable for headless CI^) ^ + ^ standard flags: ^ @@ -45,8 +57,35 @@ Flags/options: ^ displays this help and terminates ^ +^ + + files: ^ + +^ + + .sis\ci_scratch_tests_allowed_to_fail.txt ^ + + optional list of scratch-test programs ^(one name per line; ^ + + lines beginning with '#' are ignored^) that are allowed to fail; ^ + + such a program is still executed, but a non-zero exit is ^ + + reported as anticipated and does not stop the run ^ + +^ + + .sis\ci_scratch_tests_skip_interactive.txt ^ + + list of scratch-test programs skipped when --skip-interactive ^ + + is given ^(one name per line; blank lines and '#' ignored^) ^ + EXIT /B 0 + ) ELSE IF /I {--skip-interactive}=={%%a} ( + + SET "SKIP_INTERACTIVE=1" ) ELSE ( ECHO "%SCRIPT_DIRECTORY%: unrecognised argument '%%a'; use --help for usage" 1>&2 @@ -55,6 +94,13 @@ Flags/options: ^ ) ) +IF "%SKIP_INTERACTIVE%"=="1" IF NOT EXIST "%SKIP_INTERACTIVE_FILE%" ( + + ECHO %SCRIPT_PATH_DOC%: --skip-interactive: skip list not found at '%SKIP_INTERACTIVE_FILE%' 1>&2 + + EXIT /B 1 +) + if NOT EXIST "%CMAKE_DIR%" ( ECHO "CMake build directory '%CMAKE_DIR%' does not exist" @@ -74,12 +120,55 @@ IF NOT DEFINED ProjectName ( ECHO Running all %ProjectName% scratch-test programs -FOR /F "usebackq" %%f IN (`DIR /A:-D /B /S "%CMAKE_DIR%" ^| FINDSTR /I test.*scratch.*\.exe$`) DO ( +FOR /F "usebackq" %%f IN (`DIR /A:-D /B /S "%CMAKE_DIR%" ^| FINDSTR /I test.*scratch.*\.exe$`) DO CALL :run_program "%%f" + +ENDLOCAL & EXIT /B %PROGRAM_STATUS% + + +REM ########################################################## +REM subroutines + +:run_program +IF %PROGRAM_STATUS% NEQ 0 EXIT /B 0 +CALL :is_skipped_interactive "%~1" +IF DEFINED SKIPPED_INTERACTIVE ( ECHO . - ECHO executing %%f - %%f - IF ERRORLEVEL 1 EXIT /B %ERRORLEVEL% + ECHO skipping %~1 ^(interactive; --skip-interactive^) + EXIT /B 0 ) - -ENDLOCAL +ECHO . +ECHO executing %~1 +"%~1" +SET "FAILURE_STATUS=%ERRORLEVEL%" +IF %FAILURE_STATUS% EQU 0 EXIT /B 0 +CALL :is_allowed_to_fail "%~1" +IF NOT DEFINED ALLOWED_TO_FAIL GOTO run_program_failed +ECHO anticipated failure: %~1 exited with status %FAILURE_STATUS%; it is listed in .sis\ci_scratch_tests_allowed_to_fail.txt +EXIT /B 0 +:run_program_failed +SET "PROGRAM_STATUS=%FAILURE_STATUS%" +EXIT /B 0 + +:is_allowed_to_fail +SET "ALLOWED_TO_FAIL=" +IF NOT EXIST "%ALLOWED_TO_FAIL_FILE%" EXIT /B 0 +FOR /F "usebackq eol=# tokens=1" %%n IN ("%ALLOWED_TO_FAIL_FILE%") DO CALL :match_allowed "%%n" "%~n1" "%~nx1" +EXIT /B 0 + +:match_allowed +IF /I "%~1"=="%~2" SET "ALLOWED_TO_FAIL=1" +IF /I "%~1"=="%~3" SET "ALLOWED_TO_FAIL=1" +EXIT /B 0 + +:is_skipped_interactive +SET "SKIPPED_INTERACTIVE=" +IF NOT "%SKIP_INTERACTIVE%"=="1" EXIT /B 0 +IF NOT EXIST "%SKIP_INTERACTIVE_FILE%" EXIT /B 0 +FOR /F "usebackq eol=# tokens=1" %%n IN ("%SKIP_INTERACTIVE_FILE%") DO CALL :match_skip "%%n" "%~n1" "%~nx1" +EXIT /B 0 + +:match_skip +IF /I "%~1"=="%~2" SET "SKIPPED_INTERACTIVE=1" +IF /I "%~1"=="%~3" SET "SKIPPED_INTERACTIVE=1" +EXIT /B 0 diff --git a/run_all_scratch_tests.sh b/run_all_scratch_tests.sh index 5e6659a..0250180 100755 --- a/run_all_scratch_tests.sh +++ b/run_all_scratch_tests.sh @@ -27,10 +27,13 @@ CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} ProjectNameFile="$Dir/.sis/project_name.txt" ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") ScriptPath=$0 +AllowedToFailFile="$Dir/.sis/ci_scratch_tests_allowed_to_fail.txt" +SkipInteractiveFile="$Dir/.sis/ci_scratch_tests_skip_interactive.txt" AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} ListOnly=0 RunMake=1 +SkipInteractive=0 SisUseColours=0 @@ -115,6 +118,78 @@ sis_cmake_build() { cmake "${args[@]}" } +# Reduces a program path to its (lowercase) name, without any directory or +# .exe suffix. +sis_program_stem() { + + local p="${1//\\//}" + + p="${p##*/}" + + case "$p" in + *.exe|*.EXE) p="${p%.*}" ;; + esac + + printf '%s' "$p" | tr '[:upper:]' '[:lower:]' +} + +# Succeeds if the program is named (by name or stem, case-insensitively) in +# the optional .sis/ci_scratch_tests_allowed_to_fail.txt file; blank lines +# and lines beginning with '#' are ignored. +sis_is_allowed_to_fail() { + + local name line + + [ -f "$AllowedToFailFile" ] || return 1 + + name=$(sis_program_stem "$1") + + while IFS= read -r line || [ -n "$line" ]; do + + line="${line//$'\r'/}" + + case "$line" in + ''|\#*) continue ;; + esac + + if [ "$name" = "$(sis_program_stem "$line")" ]; then + + return 0 + fi + done < "$AllowedToFailFile" + + return 1 +} + +# Succeeds if --skip-interactive is active and the program is named in +# .sis/ci_scratch_tests_skip_interactive.txt (GUI / speech / similar). +sis_is_skipped_interactive() { + + local name line + + [ $SkipInteractive -ne 0 ] || return 1 + + [ -f "$SkipInteractiveFile" ] || return 1 + + name=$(sis_program_stem "$1") + + while IFS= read -r line || [ -n "$line" ]; do + + line="${line//$'\r'/}" + + case "$line" in + ''|\#*) continue ;; + esac + + if [ "$name" = "$(sis_program_stem "$line")" ]; then + + return 0 + fi + done < "$SkipInteractiveFile" + + return 1 +} + # ########################################################## # command-line handling @@ -134,6 +209,10 @@ while [[ $# -gt 0 ]]; do RunMake=0 ;; + --skip-interactive) + + SkipInteractive=1 + ;; --help) [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" @@ -159,6 +238,25 @@ Flags/options: --no-make does not execute a build before running programs + --skip-interactive + skips programs listed in .sis/ci_scratch_tests_skip_interactive.txt + (GUI / speech / desktop-interactive programs unsuitable for headless + CI; principally Windows MessageBox and speech sinks) + + + files: + + .sis/ci_scratch_tests_allowed_to_fail.txt + optional list of scratch-test programs (one name per line; blank + lines and lines beginning with '#' are ignored) that are allowed to + fail; such a program is still executed, but a non-zero exit is + reported as anticipated and neither stops the run nor affects the + exit status + + .sis/ci_scratch_tests_skip_interactive.txt + list of scratch-test programs skipped when --skip-interactive is + given (one name per line; blank lines and '#' comments ignored) + standard flags: @@ -180,6 +278,13 @@ EOF shift done +if [ $SkipInteractive -ne 0 ] && [ ! -f "$SkipInteractiveFile" ]; then + + >&2 echo "${ScriptPath}: --skip-interactive: skip list not found at '$SkipInteractiveFile'" + + exit 1 +fi + # ########################################################## # main() @@ -241,9 +346,29 @@ if [ $status -eq 0 ]; then fClr="${SisClr_Blue}${SisClr_Bold}${f}${SisClr_None}" + if sis_is_skipped_interactive "$f"; then + + if [ $ListOnly -ne 0 ]; then + + echo "would skip ${fClr} (interactive; --skip-interactive)" + else + + echo + echo "skipping ${fClr} (interactive; --skip-interactive)" + fi + + continue + fi + if [ $ListOnly -ne 0 ]; then - echo "would execute ${fClr}:" + if sis_is_allowed_to_fail "$f"; then + + echo "would execute ${fClr} (allowed to fail):" + else + + echo "would execute ${fClr}:" + fi continue fi @@ -256,11 +381,26 @@ if [ $status -eq 0 ]; then : else - status=$? + fStatus=$? + + if sis_is_allowed_to_fail "$f"; then + + echo "${SisClr_Yellow}${SisClr_Bold}anticipated failure${SisClr_None}: ${fClr} exited with status ${fStatus}; it is listed in .sis/ci_scratch_tests_allowed_to_fail.txt" + + continue + fi + + status=$fStatus break 1 fi - done < <(find "$CMakeDir" -type f \( -name 'test_scratch*' -o -name 'test.scratch.*' \) \( -perm -100 -o -name '*.exe' \) -print0 2>/dev/null | sort -z) + done < <(find "$CMakeDir" -type f \( -name 'test_scratch*' -o -name 'test.scratch.*' \) \ + ! -path '*/CMakeFiles/*' \ + ! -name '*.a' \ + ! -name '*.d' \ + ! -name '*.lib' \ + ! -name '*.log' \ + \( -perm -100 -o -name '*.exe' \) -print0 2>/dev/null | sort -z) if [ $NumPrograms -eq 0 ]; then diff --git a/run_all_unit_tests.sh b/run_all_unit_tests.sh index 301ccae..c7250bd 100755 --- a/run_all_unit_tests.sh +++ b/run_all_unit_tests.sh @@ -282,7 +282,12 @@ if [ $status -eq 0 ]; then break 1 fi - done < <(find "$CMakeDir" -type f \( -name 'test_unit*' -o -name 'test.unit.*' \) \( -perm -100 -o -name '*.exe' \) -print0 2>/dev/null | sort -z) + done < <(find "$CMakeDir" -type f \( -name 'test_unit*' -o -name 'test.unit.*' \) \ + ! -path '*/CMakeFiles/*' \ + ! -name '*.a' \ + ! -name '*.d' \ + ! -name '*.lib' \ + \( -perm -100 -o -name '*.exe' \) -print0 2>/dev/null | sort -z) if [ $NumPrograms -eq 0 ]; then diff --git a/src/cstring.core.c b/src/cstring.core.c index a21b670..7c5a80a 100644 --- a/src/cstring.core.c +++ b/src/cstring.core.c @@ -4,7 +4,7 @@ * Purpose: The implementation of the cstring core API * * Created: 16th June 1994 - * Updated: 6th October 2026 + * Updated: 7th October 2026 * * Home: http://synesis.com.au/software/ * @@ -173,21 +173,21 @@ convert_negative_index_( } /* - * Copies at most \c lim characters from \c src. Stops at a NUL inside that - * span. Does not read \c src past \c lim: callers pass a slice of \c lim - * characters, which need not be NUL-terminated. + * Copies exactly \c lim characters from \c src into \c dst. Interior NUL + * characters are copied. There is no scan for a NUL, and the copy does not + * read past \c lim. A NULL \c src zero-fills \c lim characters. Callers + * pass a slice of \c lim addressable characters, which need not be + * NUL-terminated. The copy is \c memcpy. * * \param dst Destination. May NOT be \c NULL; - * \param src Source. May be \c NULL; - * \param lim Maximum number of characters to read from \c src; - * - * \return The index of the NUL, or \c lim when the slice contains none; 0 - * when \c src is \c NULL + * \param src Source. May be \c NULL. When non-NULL, \c lim characters at + * \c src are addressable; + * \param lim Number of characters to copy; * * \pre NULL != dst */ static -size_t +void cstring_strlcpy_safe_( cstring_char_t dst[] , cstring_char_t const* src @@ -199,29 +199,10 @@ cstring_strlcpy_safe_( if (NULL == src) { memset(dst, 0, sizeof(cstring_char_t) * lim); - - return 0; } else { - size_t i; - - for (i = 0; i != lim; ++i, ++dst, ++src) - { - *dst = *src; - - if ('\0' == src[0]) - { - break; - } - } - - if (i != lim) - { - memset(dst, 0, sizeof(cstring_char_t) * (lim - i)); - } - - return i; + memcpy(dst, src, sizeof(cstring_char_t) * lim); } } @@ -299,7 +280,11 @@ cstring_realloc_2_( */ if (0 == cb) { - free(pv); + if (NULL != pv) + { + free(pv); + } + return NULL; } @@ -371,6 +356,31 @@ cstring_realloc_( return cstring_realloc_2_(pv, cch, flags, prc, NULL, NULL); } +/** Releases owned storage and leaves \c pcs empty with a NULL pointer. */ +static +CSTRING_RC +cstring_release_to_empty_( + struct cstring_t* pcs +) +{ + CSTRING_RC rc = CSTRING_RC_SUCCESS; + + CSTRING_ASSERT(NULL != pcs); + + if (!(CSTRING_F_MEMORY_IS_BORROWED & pcs->flags) && + NULL != pcs->ptr) + { + (void)cstring_realloc_(pcs->ptr, 0, pcs->flags, &rc); + } + + pcs->len = 0; + pcs->ptr = NULL; + pcs->capacity = 0; + pcs->flags = 0; + + return rc; +} + /* Writes cch characters of s as bytes. Narrow uses "%.*s". Wide converts * one character at a time with wcrtomb(), and stops at a NUL the way "%.*s" * does. %ls is not used: its spelling and its precision are not the same on @@ -554,7 +564,17 @@ cstring_create( CSTRING_ASSERT(NULL != pcs); - cch = (0 == len) ? 1u : len; + if (0 == len) + { + pcs->len = 0; + pcs->ptr = NULL; + pcs->capacity = 0; + pcs->flags = 0; + + return CSTRING_RC_SUCCESS; + } + + cch = len; cch = (cch + (CSTRING_ALLOC_GRANULARITY - 1)) & ~(CSTRING_ALLOC_GRANULARITY - 1); pcs->ptr = (cstring_char_t*)cstring_realloc_(NULL, cch + 1, 0, &rc); @@ -588,7 +608,17 @@ cstring_createLen( CSTRING_ASSERT(NULL != pcs); - cch = sizeof(cstring_char_t) * ((0 == len) ? 1u : len); + if (0 == len) + { + pcs->len = 0; + pcs->ptr = NULL; + pcs->capacity = 0; + pcs->flags = 0; + + return CSTRING_RC_SUCCESS; + } + + cch = len; cch = (cch + (CSTRING_ALLOC_GRANULARITY - 1)) & ~(CSTRING_ALLOC_GRANULARITY - 1); pcs->ptr = (cstring_char_t*)cstring_realloc_(NULL, cch + 1, 0, &rc); @@ -729,6 +759,16 @@ cstring_createLenFn( pcs->ptr = (cstring_char_t*)arena; pcs->capacity = capacity - 1; } + else if (0 == len && + 0 == capacity) + { + pcs->len = 0; + pcs->ptr = NULL; + pcs->capacity = 0; + pcs->flags = 0; + + return CSTRING_RC_SUCCESS; + } else { size_t cch; @@ -766,21 +806,9 @@ cstring_destroy( struct cstring_t* pcs ) { - CSTRING_RC rc = CSTRING_RC_SUCCESS; - CSTRING_ASSERT(NULL != pcs); - if (!(CSTRING_F_MEMORY_IS_BORROWED & pcs->flags)) - { - (void)cstring_realloc_(pcs->ptr, 0, pcs->flags, &rc); - } - - pcs->len = 0; - pcs->ptr = NULL; - pcs->capacity = 0; - pcs->flags = 0; - - return rc; + return cstring_release_to_empty_(pcs); } #ifndef CSTRING_OBSOLETE @@ -981,12 +1009,30 @@ cstring_assignFn( { const size_t len = cstring_strlen_safe_(s); + if (0 == len) + { + if (NULL != pcs->ptr) + { + pcs->len = 0; + pcs->ptr[0] = '\0'; + + return CSTRING_RC_SUCCESS; + } + + pcs->len = 0; + pcs->ptr = NULL; + pcs->capacity = 0; + pcs->flags = 0; + + return CSTRING_RC_SUCCESS; + } + if (0 == pcs->capacity || pcs->capacity < len) { size_t cch; - cch = (0 == len) ? 1u : len; + cch = len; cch = (cch + (CSTRING_ALLOC_GRANULARITY - 1)) & ~(CSTRING_ALLOC_GRANULARITY - 1); if (cch < pcs->capacity * 2) @@ -1081,12 +1127,30 @@ cstring_assignLenFn( } else { + if (0 == len) + { + if (NULL != pcs->ptr) + { + pcs->len = 0; + pcs->ptr[0] = '\0'; + + return CSTRING_RC_SUCCESS; + } + + pcs->len = 0; + pcs->ptr = NULL; + pcs->capacity = 0; + pcs->flags = 0; + + return CSTRING_RC_SUCCESS; + } + if (0 == pcs->capacity || pcs->capacity < len) { size_t cch; - cch = (0 == len) ? 1u : len; + cch = len; cch = (cch + (CSTRING_ALLOC_GRANULARITY - 1)) & ~(CSTRING_ALLOC_GRANULARITY - 1); if (cch < pcs->capacity * 2) @@ -1193,7 +1257,23 @@ cstring_appendFn( size_t const len = cstring_strlen_safe_(s); size_t const newLen = pcs->len + len; - if (pcs->capacity < newLen) + if (0 == len) + { + return CSTRING_RC_SUCCESS; + } + + /* Capacity already covers the write: copy in place. */ + if (pcs->capacity >= newLen) + { + CSTRING_ASSERT(NULL != pcs->ptr); + + cstring_memcpy_safe_(pcs->ptr + pcs->len, s, len * sizeof(cstring_char_t)); + pcs->len = newLen; + pcs->ptr[pcs->len] = '\0'; + + return CSTRING_RC_SUCCESS; + } + { size_t cch; @@ -1299,13 +1379,29 @@ cstring_appendLenFn( } else { - size_t newLen = pcs->len + len; + size_t const newLen = pcs->len + len; + + if (0 == len) + { + return CSTRING_RC_SUCCESS; + } + + /* Capacity already covers the write: copy in place. */ + if (pcs->capacity >= newLen) + { + CSTRING_ASSERT(NULL != pcs->ptr); + + cstring_strlcpy_safe_(pcs->ptr + pcs->len, s, len); + pcs->len = newLen; + pcs->ptr[pcs->len] = '\0'; + + return CSTRING_RC_SUCCESS; + } - if (pcs->capacity < newLen) { size_t cch; - cch = pcs->len + len; + cch = newLen; cch = (cch + (CSTRING_ALLOC_GRANULARITY - 1)) & ~(CSTRING_ALLOC_GRANULARITY - 1); if (cch < pcs->capacity * 2) @@ -1458,6 +1554,8 @@ cstring_readline( numRead = &dummy; } + *numRead = 0u; + if (NULL == stm || 0 != ferror(stm)) { diff --git a/src/cstring.vector.c b/src/cstring.vector.c index cef19b6..c3a3134 100644 --- a/src/cstring.vector.c +++ b/src/cstring.vector.c @@ -8,7 +8,7 @@ * * Home: http://synesis.com.au/software/ * - * Copyright (c) 2019-2025, Matthew Wilson and Synesis Information Systems + * Copyright (c) 2019-2026, Matthew Wilson and Synesis Information Systems * Copyright (c) 1994-2019, Matthew Wilson and Synesis Software * All rights reserved. * @@ -71,6 +71,99 @@ #define CSTRING_VECTOR_DEF_CAPACITY_ (8) +/* ///////////////////////////////////////////////////////////////////////// + * debugging + */ + +#define CSTRING_VECTOR_ASSERT(expr) assert(expr) + + +/* ///////////////////////////////////////////////////////////////////////// + * helper functions + */ + +/** Zero-fills \c n slots to the empty \c cstring_t_DEFAULT form. + * + * Equivalent to calling \c cstring_init on each slot; that function always + * succeeds and only clears the four fields. + */ +static +void +cstring_vector_init_empty_slots_( + cstring_t* slots +, size_t n +) +{ + if (0 != n) + { + CSTRING_VECTOR_ASSERT(NULL != slots); + + memset(slots, 0, sizeof(cstring_t) * n); + } +} + +/** Destroys live slots only; already-empty slots are left alone. + * + * An empty slot (as from \c cstring_init / \c cstring_t_DEFAULT) has a NULL + * pointer, zero length, zero capacity, and zero flags. Skipping those avoids + * a per-element call on \c cstring_vector_create / destroy of empty vectors. + */ +static +CSTRING_RC +cstring_vector_destroy_slots_( + cstring_t* slots +, size_t n +) +{ + CSTRING_RC rc = CSTRING_RC_SUCCESS; + size_t i; + + if (0 == n) + { + return CSTRING_RC_SUCCESS; + } + + CSTRING_VECTOR_ASSERT(NULL != slots); + + for (i = 0; i != n; ++i) + { + cstring_t* const pcs = slots + i; + + if (0 != pcs->len || + NULL != pcs->ptr || + 0 != pcs->capacity || + 0 != pcs->flags) + { + CSTRING_RC const rc2 = cstring_destroy(pcs); + + if (CSTRING_RC_SUCCESS != rc2 && + CSTRING_RC_SUCCESS == rc) + { + rc = rc2; + } + } + } + + return rc; +} + + +/* ///////////////////////////////////////////////////////////////////////// + * compiler warnings + */ + +#if defined(_MSC_VER) +# if _MSC_VER >= 1200 +# pragma warning(push) +# endif /* _MSC_VER >= 1200 */ +# if _MSC_VER >= 1310 +# if !defined(__COMO__) +# pragma warning(disable : 4055) +# endif /* !__COMO__ */ +# endif /* _MSC_VER >= 1310 */ +#endif /* compiler */ + + /* ///////////////////////////////////////////////////////////////////////// * compiler compatibility */ @@ -148,21 +241,11 @@ cstring_vector_destroy( cstring_vector_t* pcsv ) { - CSTRING_RC rc = CSTRING_RC_SUCCESS; - size_t i; + CSTRING_RC rc; CSTRING_ASSERT(NULL != pcsv); - for (i = 0; i != pcsv->len; ++i) - { - CSTRING_RC rc2 = cstring_destroy(pcsv->ptr + i); - - if (CSTRING_RC_SUCCESS != rc2 && - CSTRING_RC_SUCCESS == rc) - { - rc = rc2; - } - } + rc = cstring_vector_destroy_slots_(pcsv->ptr, pcsv->len); free(pcsv->ptr); @@ -180,22 +263,12 @@ cstring_vector_truncate( , size_t len ) { - CSTRING_RC rc = CSTRING_RC_SUCCESS; - size_t i; + CSTRING_RC rc; CSTRING_ASSERT(NULL != pcsv); CSTRING_ASSERT(len <= pcsv->len); - for (i = len; i != pcsv->len; ++i) - { - CSTRING_RC rc2 = cstring_destroy(pcsv->ptr + i); - - if (CSTRING_RC_SUCCESS != rc2 && - CSTRING_RC_SUCCESS == rc) - { - rc = rc2; - } - } + rc = cstring_vector_destroy_slots_(pcsv->ptr + len, pcsv->len - len); pcsv->len = len; @@ -220,36 +293,10 @@ cstring_vector_create( if (CSTRING_RC_SUCCESS == rc) { - size_t i; - CSTRING_ASSERT(pcsv->capacity >= initialSize); - for (i = 0; i != initialSize; ++i) - { - CSTRING_RC rc2 = cstring_init(pcsv->ptr + i); - - if (CSTRING_RC_SUCCESS != rc2) - { - while (0 != i) - { - cstring_destroy(pcsv->ptr + (i - 1)); - - --i; - } - - rc = rc2; - - pcsv->len = 0; - - cstring_vector_destroy(pcsv); - - break; - } - } - } + cstring_vector_init_empty_slots_(pcsv->ptr, initialSize); - if (CSTRING_RC_SUCCESS == rc) - { pcsv->len = initialSize; } @@ -280,7 +327,8 @@ cstring_vector_insertAt( if (newSize > pcsv->capacity) { - cstring_t* newPtr = realloc(pcsv->ptr, sizeof(cstring_t) * newSize); + size_t const newCapacity = (newSize * 3) / 2; + cstring_t* newPtr = realloc(pcsv->ptr, sizeof(cstring_t) * newCapacity); if (NULL == newPtr) { @@ -289,7 +337,7 @@ cstring_vector_insertAt( else { pcsv->ptr = newPtr; - pcsv->capacity = newSize; + pcsv->capacity = newCapacity; } } @@ -320,11 +368,9 @@ cstring_vector_insertAt( if (CSTRING_RC_SUCCESS != rc2) { - while (0 != i) + for (; 0 != i; --i) { cstring_destroy(pcsv->ptr + position + (i - 1)); - - --i; } rc = rc2; diff --git a/test/component/cstring_readline/entry.cpp b/test/component/cstring_readline/entry.cpp index c0be07a..00a0a07 100644 --- a/test/component/cstring_readline/entry.cpp +++ b/test/component/cstring_readline/entry.cpp @@ -4,7 +4,7 @@ * Purpose: Component-tests `cstring_readline()`. * * Created: 23rd May 2009 - * Updated: 5th October 2026 + * Updated: 7th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/test/performance/cstring/main.cpp b/test/performance/cstring/main.cpp index 74d909f..633053d 100644 --- a/test/performance/cstring/main.cpp +++ b/test/performance/cstring/main.cpp @@ -7,7 +7,7 @@ * p99 is available, also report per-iteration percentiles. * * Created: 23rd September 2026 - * Updated: 4th October 2026 + * Updated: 7th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -71,6 +71,17 @@ size_t const SIZES[] = }; const size_t NUM_STACK_ELEMENTS = 512; + +/* append_len_inc8 appends this many characters, from an empty string up to + * the limit. The limit is a multiple of the chunk. + */ +size_t const APPEND_INC8_CHUNK = 8u; +size_t const APPEND_INC8_LIMIT = 8000u; + +static_assert( + 0u == (APPEND_INC8_LIMIT % APPEND_INC8_CHUNK) +, "APPEND_INC8_LIMIT must be a multiple of APPEND_INC8_CHUNK" +); } // anonymous namespace @@ -464,6 +475,86 @@ scenario_append_len_growth( emit_row(scenario, chunk, IMPL_RAW, num_iterations, num_appends, raw, cs.tm_ns); } +/* Repeated 8-character appends growing a string from length 0 to 8,000. The + * size column is the final length, and each action is one append. The chunk + * matches cstring's allocation granularity. std::string and an exact + * realloc floor run the same growth. + */ +void +scenario_append_len_inc8( + size_t num_iterations +, size_t num_warm_loops +) +{ + size_t const chunk = APPEND_INC8_CHUNK; + size_t const final_len = APPEND_INC8_LIMIT; + size_t const num_appends = final_len / chunk; + std::string const payload = make_payload(chunk); + char const* const p = payload.data(); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [p, chunk, num_appends]() -> std::uint64_t { + cstring_t s = cstring_t_DEFAULT; + cstring_create(&s, ""); + for (size_t i = 0; num_appends != i; ++i) + { + cstring_appendLen(&s, p, chunk); + } + std::uint64_t const a = s.len; + cstring_destroy(&s); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [p, chunk, num_appends]() -> std::uint64_t { + std::string s; + for (size_t i = 0; num_appends != i; ++i) + { + s.append(p, chunk); + } + return s.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [p, chunk, num_appends]() -> std::uint64_t { + raw_string s; + raw_init(&s); + for (size_t i = 0; num_appends != i; ++i) + { + raw_append_len(&s, p, chunk); + } + std::uint64_t const a = s.len; + raw_destroy(&s); + return a; + }); + + emit_row("append_len_inc8", final_len, IMPL_CSTRING, num_iterations, num_appends, cs, cs.tm_ns); + emit_windows_cstring_arenas( + "append_len_inc8" + , final_len + , num_iterations + , num_warm_loops + , num_appends + , cs.tm_ns + , [p, chunk, num_appends](cstring_flags_t flags) -> std::uint64_t + { + cstring_t s = cstring_t_DEFAULT; + + cstring_createEx(&s, "", flags, NULL, 0); + + for (size_t i = 0; num_appends != i; ++i) + { + cstring_appendLen(&s, p, chunk); + } + + std::uint64_t const a = s.len; + + cstring_destroy(&s); + + return a; + } + ); + emit_row("append_len_inc8", final_len, IMPL_STD, num_iterations, num_appends, st, cs.tm_ns); + emit_row("append_len_inc8", final_len, IMPL_RAW, num_iterations, num_appends, raw, cs.tm_ns); +} + void scenario_append_len_reserved( size_t n @@ -852,8 +943,11 @@ int main(int /*argc*/, char* /*argv*/[]) } } + scenario_append_len_inc8(heavy_iters, num_warm_loops); + return EXIT_SUCCESS; } /* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/performance/cstring_readline/main.cpp b/test/performance/cstring_readline/main.cpp index 164de2e..ff95b6d 100644 --- a/test/performance/cstring_readline/main.cpp +++ b/test/performance/cstring_readline/main.cpp @@ -9,7 +9,7 @@ * require p99. * * Created: 27th September 2026 - * Updated: 4th October 2026 + * Updated: 7th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/test/performance/cstring_vector/main.cpp b/test/performance/cstring_vector/main.cpp index 91234ef..908bedf 100644 --- a/test/performance/cstring_vector/main.cpp +++ b/test/performance/cstring_vector/main.cpp @@ -8,7 +8,7 @@ * getline, platformstl::file_lines). * * Created: 23rd September 2026 - * Updated: 4th October 2026 + * Updated: 7th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/test/performance/perf_harness.hpp b/test/performance/perf_harness.hpp index a3fef6e..c05b701 100644 --- a/test/performance/perf_harness.hpp +++ b/test/performance/perf_harness.hpp @@ -4,7 +4,7 @@ * Purpose: Shared helpers for cstring performance programs. * * Created: 23rd September 2026 - * Updated: 4th October 2026 + * Updated: 7th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/test/scratch/versions/CMakeLists.txt b/test/scratch/versions/CMakeLists.txt index 5b5ed67..94022e4 100644 --- a/test/scratch/versions/CMakeLists.txt +++ b/test/scratch/versions/CMakeLists.txt @@ -1 +1,2 @@ +# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten define_example_program(test.scratch.versions main.c) diff --git a/test/scratch/versions/main.c b/test/scratch/versions/main.c index fa6cd1c..219b657 100644 --- a/test/scratch/versions/main.c +++ b/test/scratch/versions/main.c @@ -4,7 +4,7 @@ * Purpose: Prints cstring composite version (and STLSoft when present). * * Created: 17th September 2026 - * Updated: 17th September 2026 + * Updated: 19th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -19,9 +19,6 @@ #include -#define PROGRAM_NAME "versions" - - static void version( FILE* stm diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 430f715..e7838a1 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -5,12 +5,12 @@ if(NOT NO_CSTRING_CPP_API) add_subdirectory(compare.cxx) endif() -add_subdirectory(cstring_vector) +add_subdirectory(cstring) if(NOT NO_CSTRING_CPP_API) add_subdirectory(cstring.cxx) endif() -add_subdirectory(cstring) +add_subdirectory(cstring_vector) add_subdirectory(hash) if(NOT NO_CSTRING_CPP_API) diff --git a/test/unit/auto-buffer/entry.c b/test/unit/auto-buffer/entry.c index 3b11084..f3a850b 100644 --- a/test/unit/auto-buffer/entry.c +++ b/test/unit/auto-buffer/entry.c @@ -4,7 +4,7 @@ * Purpose: Tests borrowed string functionality. * * Created: 28th July 2011 - * Updated: 5th October 2026 + * Updated: 7th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/test/unit/compare/entry.c b/test/unit/compare/entry.c index 3991627..2442442 100644 --- a/test/unit/compare/entry.c +++ b/test/unit/compare/entry.c @@ -192,8 +192,8 @@ static void TEST_cstring_equal_AND_cstring_compare_ORDER(void) static void TEST_cstring_equal_AND_cstring_compare_EMBEDDED_NUL(void) { - /* cstring_createLen() stops at the first NUL, so the slice is built - * here. + /* The payload is this array, so equal and compare read the embedded + * NUL. len includes the byte after that NUL. */ cstring_char_t a_nul_b[3]; cstring_char_t a_nul_c[3]; diff --git a/test/unit/cstring.cxx/entry.cpp b/test/unit/cstring.cxx/entry.cpp index 8bcd68a..a0ea4b8 100644 --- a/test/unit/cstring.cxx/entry.cpp +++ b/test/unit/cstring.cxx/entry.cpp @@ -4,7 +4,7 @@ * Purpose: Unit-tests for cstring instance general functionality. * * Created: 23rd May 2009 - * Updated: 4th October 2026 + * Updated: 7th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -240,10 +240,9 @@ static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AN else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_(CSTRING_T_(""), str.ptr); - TEST_INT_GT(0u, str.capacity); - TEST_INT_GE(str.len, str.capacity); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); cstring_destroy(&str); @@ -265,10 +264,9 @@ static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AN else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_(CSTRING_T_(""), str.ptr); - TEST_INT_GT(0u, str.capacity); - TEST_INT_GE(str.len, str.capacity); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); cstring_destroy(&str); @@ -290,10 +288,9 @@ static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AN else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_(CSTRING_T_(""), str.ptr); - TEST_INT_GT(0u, str.capacity); - TEST_INT_GE(str.len, str.capacity); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); cstring_destroy(&str); @@ -315,10 +312,9 @@ static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AN else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_(CSTRING_T_(""), str.ptr); - TEST_INT_GT(0u, str.capacity); - TEST_INT_GE(str.len, str.capacity); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); cstring_destroy(&str); @@ -340,10 +336,9 @@ static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AN else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_(CSTRING_T_(""), str.ptr); - TEST_INT_GT(0u, str.capacity); - TEST_INT_GE(str.len, str.capacity); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); cstring_destroy(&str); @@ -365,10 +360,9 @@ static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AN else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_(CSTRING_T_(""), str.ptr); - TEST_INT_GT(0u, str.capacity); - TEST_INT_GE(str.len, str.capacity); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); cstring_destroy(&str); @@ -424,10 +418,15 @@ static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AN TEST_PTR_NE(NULL, str.ptr); TEST_STR_EQ_(CSTRING_T_(""), str.ptr); TEST_CHAR_EQ(CSTRING_T_('\0'), str.ptr[0]); + /* cch runs past the NUL that ends "". Only ptr[0] and the + * terminator at ptr[cch] are defined. + */ + /* TEST_CHAR_EQ(CSTRING_T_('\0'), str.ptr[1]); TEST_CHAR_EQ(CSTRING_T_('\0'), str.ptr[2]); TEST_CHAR_EQ(CSTRING_T_('\0'), str.ptr[3]); TEST_CHAR_EQ(CSTRING_T_('\0'), str.ptr[4]); + */ TEST_CHAR_EQ(CSTRING_T_('\0'), str.ptr[5]); TEST_INT_GE(5u, str.capacity); TEST_INT_GE(str.len, str.capacity); @@ -548,10 +547,18 @@ static void TEST_cstring_assignLen_AND_cstring_createLen() else { TEST_INT_EQ(cch, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_N_(s, stlsoft::apply_const_ptr(str.ptr), int(i)); - TEST_INT_GE(cch, str.capacity); - TEST_INT_GE(str.len, str.capacity); + if (0u == cch) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_N_(s, stlsoft::apply_const_ptr(str.ptr), int(i)); + TEST_INT_GE(cch, str.capacity); + TEST_INT_GE(str.len, str.capacity); + } cstring_destroy(&str); @@ -578,10 +585,18 @@ static void TEST_cstring_assignLen_AND_cstring_createLen() else { TEST_INT_EQ(cch, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_N_(s, stlsoft::apply_const_ptr(str.ptr), int(i)); - TEST_INT_GE(cch, str.capacity); - TEST_INT_GE(str.len, str.capacity); + if (0u == cch) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_N_(s, stlsoft::apply_const_ptr(str.ptr), int(i)); + TEST_INT_GE(cch, str.capacity); + TEST_INT_GE(str.len, str.capacity); + } } }} @@ -607,10 +622,18 @@ static void TEST_cstring_assignLen_AND_cstring_createLen() else { TEST_INT_EQ(cch, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_N_(s, stlsoft::apply_const_ptr(str.ptr), int(i)); - TEST_INT_GE(cch, str.capacity); - TEST_INT_GE(str.len, str.capacity); + if (0u == cch) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_N_(s, stlsoft::apply_const_ptr(str.ptr), int(i)); + TEST_INT_GE(cch, str.capacity); + TEST_INT_GE(str.len, str.capacity); + } cstring_destroy(&str); @@ -803,8 +826,17 @@ static void TEST_cstring_create() cstring_create(&str, s); TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); + if (0u == i) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + } cstring_destroy(&str); @@ -825,9 +857,18 @@ static void TEST_cstring_createLen() cstring_createLen(&str, s, i); - TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); + TEST_INT_EQ(i, str.len); + if (0u == i) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + } cstring_destroy(&str); @@ -850,8 +891,17 @@ static void TEST_cstring_createEx() cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); + if (0u == str.len) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + } cstring_destroy(&str); @@ -914,9 +964,18 @@ static void TEST_cstring_createEx() cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); - TEST_INT_GE(str.capacity, ::GlobalSize(str.ptr)); + if (0u == str.len) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, ::GlobalSize(str.ptr)); + } cstring_destroy(&str); @@ -936,9 +995,18 @@ static void TEST_cstring_createEx() cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); - TEST_INT_GE(str.capacity, ::HeapSize(::GetProcessHeap(), 0, str.ptr)); + if (0u == str.len) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, ::HeapSize(::GetProcessHeap(), 0, str.ptr)); + } cstring_destroy(&str); @@ -958,9 +1026,18 @@ static void TEST_cstring_createEx() cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); - TEST_INT_GE(str.capacity, comstl::CoTaskMemGetSize(str.ptr)); + if (0u == str.len) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, comstl::CoTaskMemGetSize(str.ptr)); + } cstring_destroy(&str); @@ -983,9 +1060,18 @@ static void TEST_cstring_createLenEx() cstring_createLenEx(&str, s, i, flags, NULL, 0); - TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); + TEST_INT_EQ(i, str.len); + if (0u == i) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + } cstring_destroy(&str); @@ -1047,10 +1133,19 @@ static void TEST_cstring_createLenEx() cstring_createLenEx(&str, s, i, flags, NULL, 0); - TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); - TEST_INT_GE(str.capacity, ::GlobalSize(str.ptr)); + TEST_INT_EQ(i, str.len); + if (0u == i) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, ::GlobalSize(str.ptr)); + } cstring_destroy(&str); @@ -1069,10 +1164,19 @@ static void TEST_cstring_createLenEx() cstring_createLenEx(&str, s, i, flags, NULL, 0); - TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); - TEST_INT_GE(str.capacity, ::HeapSize(::GetProcessHeap(), 0, str.ptr)); + TEST_INT_EQ(i, str.len); + if (0u == i) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, ::HeapSize(::GetProcessHeap(), 0, str.ptr)); + } cstring_destroy(&str); @@ -1091,10 +1195,19 @@ static void TEST_cstring_createLenEx() cstring_createLenEx(&str, s, i, flags, NULL, 0); - TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); - TEST_INT_GE(str.capacity, comstl::CoTaskMemGetSize(str.ptr)); + TEST_INT_EQ(i, str.len); + if (0u == i) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, comstl::CoTaskMemGetSize(str.ptr)); + } cstring_destroy(&str); diff --git a/test/unit/cstring/entry.c b/test/unit/cstring/entry.c index ff86cd8..75afe8d 100644 --- a/test/unit/cstring/entry.c +++ b/test/unit/cstring/entry.c @@ -4,7 +4,7 @@ * Purpose: Unit-tests for cstring instance general functionality. * * Created: 23rd May 2009 - * Updated: 6th October 2026 + * Updated: 7th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -241,10 +241,9 @@ static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AN else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_(CSTRING_T_(""), str.ptr); - TEST_INT_GT(0u, str.capacity); - TEST_INT_GE(str.len, str.capacity); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); cstring_destroy(&str); @@ -266,10 +265,9 @@ static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AN else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_(CSTRING_T_(""), str.ptr); - TEST_INT_GT(0u, str.capacity); - TEST_INT_GE(str.len, str.capacity); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); cstring_destroy(&str); @@ -291,10 +289,9 @@ static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AN else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_(CSTRING_T_(""), str.ptr); - TEST_INT_GT(0u, str.capacity); - TEST_INT_GE(str.len, str.capacity); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); cstring_destroy(&str); @@ -316,10 +313,9 @@ static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AN else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_(CSTRING_T_(""), str.ptr); - TEST_INT_GT(0u, str.capacity); - TEST_INT_GE(str.len, str.capacity); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); cstring_destroy(&str); @@ -341,10 +337,9 @@ static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AN else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_(CSTRING_T_(""), str.ptr); - TEST_INT_GT(0u, str.capacity); - TEST_INT_GE(str.len, str.capacity); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); cstring_destroy(&str); @@ -366,10 +361,9 @@ static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AN else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_(CSTRING_T_(""), str.ptr); - TEST_INT_GT(0u, str.capacity); - TEST_INT_GE(str.len, str.capacity); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); cstring_destroy(&str); @@ -425,10 +419,15 @@ static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AN TEST_PTR_NE(NULL, str.ptr); TEST_STR_EQ_(CSTRING_T_(""), str.ptr); TEST_CHAR_EQ(CSTRING_T_('\0'), str.ptr[0]); + /* cch runs past the NUL that ends "". Only ptr[0] and the + * terminator at ptr[cch] are defined. + */ + /* TEST_CHAR_EQ(CSTRING_T_('\0'), str.ptr[1]); TEST_CHAR_EQ(CSTRING_T_('\0'), str.ptr[2]); TEST_CHAR_EQ(CSTRING_T_('\0'), str.ptr[3]); TEST_CHAR_EQ(CSTRING_T_('\0'), str.ptr[4]); + */ TEST_CHAR_EQ(CSTRING_T_('\0'), str.ptr[5]); TEST_INT_GE(5u, str.capacity); TEST_INT_GE(str.len, str.capacity); @@ -549,10 +548,18 @@ static void TEST_cstring_assignLen_AND_cstring_createLen(void) else { TEST_INT_EQ(cch, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_N_(s, str.ptr, (int)i); - TEST_INT_GE(cch, str.capacity); - TEST_INT_GE(str.len, str.capacity); + if (0u == cch) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_N_(s, str.ptr, (int)i); + TEST_INT_GE(cch, str.capacity); + TEST_INT_GE(str.len, str.capacity); + } cstring_destroy(&str); @@ -579,10 +586,18 @@ static void TEST_cstring_assignLen_AND_cstring_createLen(void) else { TEST_INT_EQ(cch, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_N_(s, str.ptr, (int)i); - TEST_INT_GE(cch, str.capacity); - TEST_INT_GE(str.len, str.capacity); + if (0u == cch) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_N_(s, str.ptr, (int)i); + TEST_INT_GE(cch, str.capacity); + TEST_INT_GE(str.len, str.capacity); + } } }} @@ -608,10 +623,18 @@ static void TEST_cstring_assignLen_AND_cstring_createLen(void) else { TEST_INT_EQ(cch, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_STR_EQ_N_(s, str.ptr, (int)i); - TEST_INT_GE(cch, str.capacity); - TEST_INT_GE(str.len, str.capacity); + if (0u == cch) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_N_(s, str.ptr, (int)i); + TEST_INT_GE(cch, str.capacity); + TEST_INT_GE(str.len, str.capacity); + } cstring_destroy(&str); @@ -804,8 +827,17 @@ static void TEST_cstring_create(void) cstring_create(&str, s); TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); + if (0u == i) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + } cstring_destroy(&str); @@ -826,9 +858,18 @@ static void TEST_cstring_createLen(void) cstring_createLen(&str, s, i); - TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); + TEST_INT_EQ(i, str.len); + if (0u == i) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + } cstring_destroy(&str); @@ -875,8 +916,17 @@ static void TEST_cstring_createEx(void) cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); + if (0u == str.len) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + } cstring_destroy(&str); @@ -939,9 +989,18 @@ static void TEST_cstring_createEx(void) cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); - TEST_INT_GE(str.capacity, GlobalSize(str.ptr)); + if (0u == str.len) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, GlobalSize(str.ptr)); + } cstring_destroy(&str); @@ -961,9 +1020,18 @@ static void TEST_cstring_createEx(void) cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); - TEST_INT_GE(str.capacity, HeapSize(GetProcessHeap(), 0, str.ptr)); + if (0u == str.len) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, HeapSize(GetProcessHeap(), 0, str.ptr)); + } cstring_destroy(&str); @@ -983,9 +1051,18 @@ static void TEST_cstring_createEx(void) cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); - TEST_INT_GE(str.capacity, comstl__CoTaskMemGetSize(str.ptr)); + if (0u == str.len) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, comstl__CoTaskMemGetSize(str.ptr)); + } cstring_destroy(&str); @@ -1008,9 +1085,18 @@ static void TEST_cstring_createLenEx(void) cstring_createLenEx(&str, s, i, flags, NULL, 0); - TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); + TEST_INT_EQ(i, str.len); + if (0u == i) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + } cstring_destroy(&str); @@ -1072,10 +1158,19 @@ static void TEST_cstring_createLenEx(void) cstring_createLenEx(&str, s, i, flags, NULL, 0); - TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); - TEST_INT_GE(str.capacity, GlobalSize(str.ptr)); + TEST_INT_EQ(i, str.len); + if (0u == i) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, GlobalSize(str.ptr)); + } cstring_destroy(&str); @@ -1094,10 +1189,19 @@ static void TEST_cstring_createLenEx(void) cstring_createLenEx(&str, s, i, flags, NULL, 0); - TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); - TEST_INT_GE(str.capacity, HeapSize(GetProcessHeap(), 0, str.ptr)); + TEST_INT_EQ(i, str.len); + if (0u == i) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, HeapSize(GetProcessHeap(), 0, str.ptr)); + } cstring_destroy(&str); @@ -1116,10 +1220,19 @@ static void TEST_cstring_createLenEx(void) cstring_createLenEx(&str, s, i, flags, NULL, 0); - TEST_INT_GE(i, str.len); - TEST_STR_EQ_(s, str.ptr); - TEST_INT_GE(str.len, str.capacity); - TEST_INT_GE(str.capacity, comstl__CoTaskMemGetSize(str.ptr)); + TEST_INT_EQ(i, str.len); + if (0u == i) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, comstl__CoTaskMemGetSize(str.ptr)); + } cstring_destroy(&str); diff --git a/test/unit/cstring_vector/entry.c b/test/unit/cstring_vector/entry.c index ae033e4..95f4f5f 100644 --- a/test/unit/cstring_vector/entry.c +++ b/test/unit/cstring_vector/entry.c @@ -4,7 +4,7 @@ * Purpose: Tests `cstring_vector_t` functionality. * * Created: 12th January 2024 - * Updated: 5th October 2026 + * Updated: 7th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -314,7 +314,14 @@ static void TEST_cstring_vector_insertAt_LIST(void) for (i = 0; i != STLSOFT_NUM_ELEMENTS(strings); ++i) { TEST_INT_EQ(cstring_testing_strlen_(strings[i]), csv.ptr[i].len); - TEST_STR_EQ_(strings[i], csv.ptr[i].ptr); + if (0u == csv.ptr[i].len) + { + TEST_PTR_EQ(NULL, csv.ptr[i].ptr); + } + else + { + TEST_STR_EQ_(strings[i], csv.ptr[i].ptr); + } } } else diff --git a/test/unit/insert-replace/entry.c b/test/unit/insert-replace/entry.c index 0985be5..4f3bc83 100644 --- a/test/unit/insert-replace/entry.c +++ b/test/unit/insert-replace/entry.c @@ -4,7 +4,7 @@ * Purpose: Unit-tests for cstring insert and replace functionality. * * Created: 4th June 2009 - * Updated: 5th October 2026 + * Updated: 7th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -138,18 +138,26 @@ static void TEST_cstring_createN(void) if (CSTRING_RC_SUCCESS == rc) { TEST_INT_EQ((size_t)i, str.len); - TEST_PTR_NE(NULL, str.ptr); - TEST_INT_GE(str.len, str.capacity); + if (0u == i) + { + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + } + else + { + TEST_PTR_NE(NULL, str.ptr); + TEST_INT_GE(str.len, str.capacity); #if defined(XTESTS_HAS_SHWILD) && !defined(CSTRING_USE_WIDE_STRINGS) - /* created string must be entirely '~' */ - TEST_MS_DOES_NOT_MATCH("*[a-zA-Z0-9]*", str.ptr); - TEST_MS_DOES_NOT_MATCH("*[ ,.<>/?'\";:[{]}`!@#$%^&*()=_+\\\\|-]*", str.ptr); + /* created string must be entirely '~' */ + TEST_MS_DOES_NOT_MATCH("*[a-zA-Z0-9]*", str.ptr); + TEST_MS_DOES_NOT_MATCH("*[ ,.<>/?'\";:[{]}`!@#$%^&*()=_+\\\\|-]*", str.ptr); #else /* ? shwild && !wide */ - TEST_PTR_EQ(NULL, cstring_testing_strpbrk_(str.ptr, CSTRING_T_("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890,<.>/?'\";:[{]}`!@#$%^&*()-_=+\\|"))); + TEST_PTR_EQ(NULL, cstring_testing_strpbrk_(str.ptr, CSTRING_T_("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890,<.>/?'\";:[{]}`!@#$%^&*()-_=+\\|"))); #endif /* shwild && !wide */ + } cstring_destroy(&str);