From 902ab0bc8202c12ca2f6ab28272d5d84fdffde52 Mon Sep 17 00:00:00 2001 From: synesissoftware Date: Sun, 9 Aug 2026 15:19:23 +1000 Subject: [PATCH 01/88] general boilerplate / project-support improvements (#16) --- .github/workflows/ci.yml | 8 ++--- .gitignore | 2 -- .sis/project_name.txt | 1 + AUTHORS.md | 6 ++-- CHANGES.md | 14 ++++++++ CMakeLists.txt | 5 ++- Doxyfile | 2 +- FAQ.md | 55 ++++++++++++++++++++++++++---- HOW_YOU_CAN_HELP.md | 70 +++++++++++++++++++++++++++++++++++++++ KNOWN_ISSUES.md | 7 +++- NEWS.md | 44 ++++++++++++------------ README.md | 27 +++++++++++---- TODO.md | 2 +- build_cmake.sh | 6 ++-- clean_cmake.sh | 4 ++- ctest_cmake.sh | 4 ++- generate_doxygen.sh | 4 ++- include/cstring/cstring.h | 12 ++++--- prepare_cmake.sh | 7 ++-- remove_cmake_artefacts.sh | 4 ++- run_all_examples.sh | 30 ++++++++++++++--- run_all_scratch_tests.sh | 30 ++++++++++++++--- run_all_unit_tests.sh | 30 ++++++++++++++--- 23 files changed, 296 insertions(+), 78 deletions(-) create mode 100644 .sis/project_name.txt create mode 100644 HOW_YOU_CAN_HELP.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bcfc80..fef22db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,15 +3,13 @@ name: CI on: push: branches: - - master - - dev - boilerplate + - dev + - idiomatic + - master - rc1 - rc2 - rc3 - - rc4 - - rc5 - - update pull_request: concurrency: diff --git a/.gitignore b/.gitignore index bb24aff..ca2d5c7 100644 --- a/.gitignore +++ b/.gitignore @@ -54,12 +54,10 @@ perf_test/ scripts/internal/ -/include/b64/ /include/shwild/ /include/xcontract/ /include/xcover/ /include/xtests/ -/src/b64/ /src/shwild/ /src/xcontract/ /src/xcover/ diff --git a/.sis/project_name.txt b/.sis/project_name.txt new file mode 100644 index 0000000..0fdbe59 --- /dev/null +++ b/.sis/project_name.txt @@ -0,0 +1 @@ +cstring diff --git a/AUTHORS.md b/AUTHORS.md index 631520a..39d7822 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,12 +1,12 @@ # cstring - Authors -## Major Contributors +## Major Contributors: -* Matt Wilson ([mwsis](https://github.com/mwsis)) +* **Matt Wilson** ([mwsis](https://github.com/mwsis)); -## Defect reports, fixes and suggestions (for which we are very grateful) +## Defect reports, fixes and suggestions (for which we are very grateful): * \ diff --git a/CHANGES.md b/CHANGES.md index fe3edfe..14c4111 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,20 @@ # cstring - CHANGES +## 4.0.14-alpha1 - 7th August 2026 + +* Aligned project boilerplate with **b64** / **Pantheios** (scripts, markdown, CI branches, `.sis`); +* Added **.sis/project_name.txt**; helper scripts load `ProjectName` for status echoes; +* CI `on.push.branches` set to lexicographic Pantheios set (`boilerplate`, `dev`, `idiomatic`, `master`, `rc1`–`rc3`); +* **CMakeLists.txt** — set `CMP0177` for CMake ≥ 3.31; +* **.gitignore** — removed stray **b64** include/src ignore entries; +* **README.md** — C++ badge; CI badge alt text; **Introduction**; help links; heading fixes; +* **AUTHORS.md**, **NEWS.md**, **FAQ.md**, **KNOWN_ISSUES.md** — peer layout/content alignment; +* Added **HOW_YOU_CAN_HELP.md**; +* **TODO.md** — marked `CMAKE_INSTALL_LIBDIR` complete; +* **run_all_examples.sh**, **run_all_unit_tests.sh**, **run_all_scratch_tests.sh** — coloured list/execute path output (via `tput`); + + ## 4.0.13 - 2nd August 2026 * Modular GitHub Actions CI (**ci.yml** + **ci-cell.yml**), with install-smoke and MinGW cells; diff --git a/CMakeLists.txt b/CMakeLists.txt index d5d1448..9d5f13b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ # Purpose: Top-level CMake lists file for cstring # # Created: 21st December 2023 -# Updated: 2nd August 2026 +# Updated: 6th August 2026 # # ######################################################################## # @@ -14,6 +14,9 @@ # CMake cmake_minimum_required(VERSION 3.20 FATAL_ERROR) +if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.31") + cmake_policy(SET CMP0177 NEW) +endif() # require out-of-source builds file(TO_CMAKE_PATH "${CMAKE_CURRENT_BINARY_DIR}/CMakeLists.txt" LOC_PATH) diff --git a/Doxyfile b/Doxyfile index 47aae46..bdd909d 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.0.13 +PROJECT_NUMBER = 4.0.14-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/FAQ.md b/FAQ.md index 04d0071..1f6b307 100644 --- a/FAQ.md +++ b/FAQ.md @@ -8,9 +8,11 @@ it will be used to create one. ## Table of Contents - [Q1: "How do I build cstring?"](#q1-how-do-i-build-cstring) -- [Q2: "Does cstring have its own unit-tests?"](#q2-does-cstring-have-its-own-unit-tests) -- [Q3: "How do I build without the C++ examples and tests?"](#q3-how-do-i-build-without-the-c-examples-and-tests) -- [Q4: "Where are the examples?"](#q4-where-are-the-examples) +- [Q2: "How do I install cstring?"](#q2-how-do-i-install-cstring) +- [Q3: "How do I use cstring?"](#q3-how-do-i-use-cstring) +- [Q4: "Does cstring have its own unit-tests?"](#q4-does-cstring-have-its-own-unit-tests) +- [Q5: "How do I build without the C++ examples and tests?"](#q5-how-do-i-build-without-the-c-examples-and-tests) +- [Q6: "Where are the examples?"](#q6-where-are-the-examples) # FAQs: @@ -37,7 +39,48 @@ $ ./prepare_cmake.sh -m Execute `$ ./prepare_cmake.sh --help` for the full set of options. -## Q2: "Does cstring have its own unit-tests?" +## Q2: "How do I install cstring?" + +See [INSTALL.md](./INSTALL.md) for details of how to install **cstring**. + + +## Q3: "How do I use cstring?" + +Include **cstring/cstring.h** (and **cstring/cstring.vector.h** where needed) +and link against **libcstring** (the **CMake** target is `cstring::core`). +Create and destroy instances with `cstring_create()` / `cstring_destroy()`, +and mutate with `cstring_assign()`, `cstring_append()`, and related APIs. + +A minimal sketch: + +```c +#include + +#include +#include + +int main(void) +{ + cstring_t cs; + CSTRING_RC rc = cstring_create(&cs, "Hello"); + + if (CSTRING_RC_SUCCESS != rc) + { + return EXIT_FAILURE; + } + + printf("%s\n", cs.ptr); + + cstring_destroy(&cs); + + return EXIT_SUCCESS; +} +``` + +See [INSTALL.md](./INSTALL.md) and the examples under **examples/**. + + +## Q4: "Does cstring have its own unit-tests?" Yes. Automated tests live under: @@ -50,7 +93,7 @@ and run with **run_all_unit_tests.sh** (and **CTest** where configured). Tests require **STLSoft** and **xTests** (and may optionally recognise **shwild**). -## Q3: "How do I build without the C++ examples and tests?" +## Q5: "How do I build without the C++ examples and tests?" Pass `--no-cpp` (or `-C`) to **prepare_cmake.sh**, which sets CMake `NO_CSTRING_CPP_API=ON`. That omits C++ examples and remaining C++ tests. @@ -59,7 +102,7 @@ The **C** unit-tests still require **STLSoft** and **xTests** unless you also pass `--disable-testing` / `-T`. -## Q4: "Where are the examples?" +## Q6: "Where are the examples?" Examples live under **examples/** (`c/` and `cpp/`), each with a short **README.md**. They are built when `BUILD_EXAMPLES` is on (the default); omit diff --git a/HOW_YOU_CAN_HELP.md b/HOW_YOU_CAN_HELP.md new file mode 100644 index 0000000..86bf86e --- /dev/null +++ b/HOW_YOU_CAN_HELP.md @@ -0,0 +1,70 @@ +# cstring - How You Can Help + + +## Table of Contents + +There are several ways in which you can help the **cstring** project: + +- [Defect reports](#defect-reports) +- [Feature requests](#feature-requests) +- [Submit your own extensions](#submit-your-own-extensions) +- [Sponsor development](#sponsor-development) +- [Write articles, blog-posts about your experiences](#write-articles-blog-posts-about-your-experiences) + + +## Defect reports + +We're keen to hear of any problems you have in building and, especially, in +using the library. + +Please submit an issue at: + + https://github.com/synesissoftware/cstring/issues + +Please help us to help you by submitting as much information about the +problem as you think is relevant, including: + +* the compiler you're using; +* the architecture and operating system you're targeting; +* the version of **cstring**; +* the version of **STLSoft** / **xTests** (when building tests); +* whether you're building using **CMake** or via an IDE; +* a section of the build output, including the warnings/errors involved; + + +## Feature requests + +If there are features that you think would enhance the library, please feel +free to share your ideas. + +You can either submit an issue: + + https://github.com/synesissoftware/cstring/issues + +Or provide your suggested work via a pull-request: + + https://github.com/synesissoftware/cstring/pulls + + +## Submit your own extensions + +If you have developed extensions or improvements and wish to contribute them +back to the project so others can benefit from your work, please open an +issue or pull request at: + + https://github.com/synesissoftware/cstring + + +## Sponsor development + +If you value the diligence and effort that's gone into bringing you this +production-quality software, please get in contact via +https://www.synesis.com.au/contact.html + + +## Write articles, blog-posts about your experiences + +If you've written articles or blogged about **cstring**, let us know. + + + diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index c2ef3c5..4c3f0d2 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -1,7 +1,12 @@ # cstring - Known Issues -## cstring 4 Known Issues +## cstring 4.0.x Known Issues + +* \ + + +## cstring 4.0.x Suspected Issues * \ diff --git a/NEWS.md b/NEWS.md index 1888380..db07ef1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,29 +1,27 @@ # cstring - News -| Date | News Item | -| --------------------- | ------------------------------------------------------------- | -| 2nd August 2026 | Release of [cstring 4.0.13](https://github.com/synesissoftware/cstring/releases/tag/4.0.13) (modular CI, examples, Doxygen, documentation) | -| 2nd September 2025 | Release of [cstring 4.0.12](https://github.com/synesissoftware/cstring/releases/tag/4.0.12) | -| 23rd February 2025 | Release of cstring 4.0.11 | -| 25th October 2024 | Release of cstring 4.0.10 | -| Available from [**cstring** project on GitHub](https://synesissoftware.com/cstring): | -| 28th January 2024 | Release of cstring 4.0.9 | -| 12th January 2024 | Release of cstring 4.0.8 | -| Available from **http://synesis.com.au/software/cstring**: | -| 20th February 2012 | Release of 3.6.2 | -| 24th January 2012 | Release of 3.6.1 | -| 22nd January 2012 | Release of 3.5.4 | -| 11th January 2010 | Release of 3.5.3 | -| 11th July 2009 | Release of 3.5.2 | -| 10th July 2009 | Release of 3.5.1 | -| 4th May 2008 | Release of 3.4.4 | -| 26th January 2007 | Release of 3.4.3 | -| 30th April 2006 | Release of 3.4.2 | -| 7th October 2005 | Release of 3.4.1 | -| 8th August 2005 | Release of 3.3.1 | -| 29th July 2005 | Release of 3.2.1 | +| Date | News Item | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------ | +| 7th August 2026 | Release of [cstring 4.0.14-alpha1](https://github.com/synesissoftware/cstring/releases/tag/4.0.14-alpha1) (boilerplate alignment with **b64** / **Pantheios**) | +| 2nd August 2026 | Release of [cstring 4.0.13](https://github.com/synesissoftware/cstring/releases/tag/4.0.13) (modular CI, examples, Doxygen, documentation) | +| 2nd September 2025 | Release of [cstring 4.0.12](https://github.com/synesissoftware/cstring/releases/tag/4.0.12) | +| 23rd February 2025 | Release of cstring 4.0.11 | +| 25th October 2024 | Release of cstring 4.0.10 | +| 28th January 2024 | Release of cstring 4.0.9 | +| 12th January 2024 | Release of cstring 4.0.8 | +| 20th February 2012 | Release of 3.6.2 | +| 24th January 2012 | Release of 3.6.1 | +| 22nd January 2012 | Release of 3.5.4 | +| 11th January 2010 | Release of 3.5.3 | +| 11th July 2009 | Release of 3.5.2 | +| 10th July 2009 | Release of 3.5.1 | +| 4th May 2008 | Release of 3.4.4 | +| 26th January 2007 | Release of 3.4.3 | +| 30th April 2006 | Release of 3.4.2 | +| 7th October 2005 | Release of 3.4.1 | +| 8th August 2005 | Release of 3.3.1 | +| 29th July 2005 | Release of 3.2.1 | - diff --git a/README.md b/README.md index d2b9096..b0472e0 100644 --- a/README.md +++ b/README.md @@ -4,23 +4,24 @@ ![C](https://img.shields.io/badge/C-00599C?style=flat&logo=c&logoColor=white) +![C++](https://img.shields.io/badge/C%2B%2B-00599C?style=flat&logo=c%2B%2B&logoColor=white) [![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![GitHub release](https://img.shields.io/github/v/release/synesissoftware/cstring.svg)](https://github.com/synesissoftware/cstring/releases/latest) [![Last Commit](https://img.shields.io/github/last-commit/synesissoftware/cstring)](https://github.com/synesissoftware/cstring/commits/master) -[![CMake on multiple platforms](https://github.com/synesissoftware/cstring/actions/workflows/ci.yml/badge.svg)](https://github.com/synesissoftware/cstring/actions/workflows/ci.yml) - +[![CI](https://github.com/synesissoftware/cstring/actions/workflows/ci.yml/badge.svg)](https://github.com/synesissoftware/cstring/actions/workflows/ci.yml) ## Table of Contents +- [Introduction](#introduction) - [Installation](#installation) - [Components](#components) - [Types](#types) - [String API](#string-api) - [Status and capacity](#status-and-capacity) - [Creation/destruction functions](#creationdestruction-functions) - - [Modification functions\*\*](#modification-functions) - - [File functions\*\*](#file-functions) + - [Modification functions](#modification-functions) + - [File functions](#file-functions) - [Vector API](#vector-api) - [Examples](#examples) - [Project Information](#project-information) @@ -31,6 +32,13 @@ - [License](#license) +## Introduction + +**cstring** is a small, standalone library that provides extensible C-style string instances and extensible arrays of such, for Unix and Windows. + +The **C** API has no non-standard dependencies. Optional C++ examples and remaining C++ tests may be omitted with `--no-cpp` / `NO_CSTRING_CPP_API`. Building tests requires **STLSoft** and **xTests** (and may optionally recognise **shwild**). + + ## Installation Detailed instructions - via **CMake**, via bundling - are provided in the accompanying [INSTALL.md](./INSTALL.md) file. @@ -89,7 +97,7 @@ Defined in **cstring/cstring.h**: * `cstring_createLenEx()` — as `cstring_createEx()`, from a fixed number of characters; * `cstring_destroy()` — releases resources and resets the instance; -#### Modification functions** +#### Modification functions * `cstring_assign()` — assigns a C-style string (may reallocate); * `cstring_assignLen()` — assigns a fixed character count (embedded NULs allowed); @@ -104,7 +112,7 @@ Defined in **cstring/cstring.h**: * `cstring_truncate()` — shortens the logical length (capacity unchanged); * `cstring_swap()` — swaps the contents of two instances; -#### File functions** +#### File functions * `cstring_readline()` — reads a line of text from the given text stream into the instance; * `cstring_write()` — writes the string to the given text stream; @@ -143,13 +151,18 @@ Examples live under **examples/** (`c/` and `cpp/`), each with a short **README. ### Where to get help -[GitHub Page](https://github.com/synesissoftware/cstring "GitHub Page") +* [GitHub Page](https://github.com/synesissoftware/cstring) +* [GitHub Issues](https://github.com/synesissoftware/cstring/issues) +* [FAQ.md](./FAQ.md) +* [HOW_YOU_CAN_HELP.md](./HOW_YOU_CAN_HELP.md) ### Contribution guidelines Defect reports, feature requests, and pull requests are welcome on https://github.com/synesissoftware/cstring. +See also [HOW_YOU_CAN_HELP.md](./HOW_YOU_CAN_HELP.md). + ### Dependencies diff --git a/TODO.md b/TODO.md index 516e588..f5f5309 100644 --- a/TODO.md +++ b/TODO.md @@ -29,7 +29,7 @@ * [x] ~~~Boilerplate: **INSTALL.md**, **FAQ.md**, **KNOWN_ISSUES.md**, **AUTHORS.md** layout~~~ - ✅; * [ ] CMake: * [x] ~~~custom definitions: `_BUILD_AS_UNIX` / `_BUILD_AS_WIN32`~~~ - ✅; - * [ ] `CMAKE_INSTALL_LIBDIR` (export config still references `LIB_INSTALL_DIR` in one path); + * [x] ~~~`CMAKE_INSTALL_LIBDIR` (replaces legacy `LIB_INSTALL_DIR`)~~~ - ✅; * [x] ~~~**CTest**~~~ - ✅; * [ ] build DLL on Windows; * [x] ~~~`/MT` build option for Visual C++ (`--msvc-mt` / `MSVC_USE_MT`)~~~ - ✅; diff --git a/build_cmake.sh b/build_cmake.sh index 5ffe489..f575833 100755 --- a/build_cmake.sh +++ b/build_cmake.sh @@ -6,6 +6,8 @@ Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} [[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") IgnoreRemainingFlagsAndOptions=0 Targets=() @@ -103,10 +105,10 @@ else if [ -z "$Targets" ]; then - echo "Executing build (via command \`$MakeCmd\`)" + echo "Executing build for ${ProjectName} (via command \`$MakeCmd\`)" else - echo "Executing build (via command \`$MakeCmd\`) with specific target(s) $(join_by , "${Targets[@]}")" + echo "Executing build for ${ProjectName} (via command \`$MakeCmd\`) with specific target(s) $(join_by , "${Targets[@]}")" fi $MakeCmd ${Targets[*]} diff --git a/clean_cmake.sh b/clean_cmake.sh index 0abbeed..ead9c20 100755 --- a/clean_cmake.sh +++ b/clean_cmake.sh @@ -6,6 +6,8 @@ Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} [[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") # ########################################################## @@ -69,7 +71,7 @@ else exit 1 else - echo "Cleaning build (via command \`$MakeCmd clean\`)" + echo "Cleaning ${ProjectName} build (via command \`$MakeCmd clean\`)" $MakeCmd clean status=$? diff --git a/ctest_cmake.sh b/ctest_cmake.sh index 7784d57..662facd 100755 --- a/ctest_cmake.sh +++ b/ctest_cmake.sh @@ -6,6 +6,8 @@ Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} [[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") CMakeVerbose= RunMake=1 @@ -74,7 +76,7 @@ status=0 if [ $RunMake -ne 0 ]; then - echo "Executing build (via command \`$MakeCmd\`) and then running all component and unit test programs" + echo "Executing build for ${ProjectName} (via command \`$MakeCmd\`) and then running all component and unit test programs" mkdir -p $CMakeDir || exit 1 diff --git a/generate_doxygen.sh b/generate_doxygen.sh index cbe6d58..504b661 100755 --- a/generate_doxygen.sh +++ b/generate_doxygen.sh @@ -11,6 +11,8 @@ ScriptPath=$0 Dir=$(cd "$(dirname "$ScriptPath")" && pwd) Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") # ########################################################## @@ -75,7 +77,7 @@ mkdir -p "${CMakeDir}/doxygen" echo "OUTPUT_DIRECTORY = ${CMakeDir}/doxygen" } | doxygen - -echo "API documentation written to ${CMakeDir}/doxygen/html/index.html" +echo "${ProjectName} API documentation written to ${CMakeDir}/doxygen/html/index.html" # ############################## end of file ############################# # diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index a07cd43..c89cdec 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: 2nd August 2026 + * Updated: 7th August 2026 * * Home: http://synesis.com.au/software/ * @@ -55,8 +55,8 @@ #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION # define CSTRING_VER_CSTRING_H_CSTRING_MAJOR 3 # define CSTRING_VER_CSTRING_H_CSTRING_MINOR 11 -# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 10 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 82 +# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 11 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 83 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR @@ -111,6 +111,8 @@ # define CSTRING_VER_4_0_10 0x04000aff # define CSTRING_VER_4_0_11 0x04000bff # define CSTRING_VER_4_0_12 0x04000cff +# define CSTRING_VER_4_0_13 0x04000dff +# define CSTRING_VER_4_0_14_A1 0x04000e41 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR @@ -131,8 +133,8 @@ #define CSTRING_VER_MAJOR 4 #define CSTRING_VER_MINOR 0 -#define CSTRING_VER_PATCH 13 -#define CSTRING_VER_ALPHABETA 0xFF +#define CSTRING_VER_PATCH 14 +#define CSTRING_VER_ALPHABETA 0x41 #define CSTRING_VER \ (0\ diff --git a/prepare_cmake.sh b/prepare_cmake.sh index acff6fe..7058c4e 100755 --- a/prepare_cmake.sh +++ b/prepare_cmake.sh @@ -14,6 +14,9 @@ else fi MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") + Configuration=Release ExamplesDisabled=0 MSVC_MT=0 @@ -162,7 +165,7 @@ mkdir -p $CMakeDir || exit 1 cd $CMakeDir -echo "Executing CMake (in ${CMakeDir})" +echo "Executing CMake for ${ProjectName} (in ${CMakeDir})" if [ $ExamplesDisabled -eq 0 ]; then CMakeBuildExamplesFlag="ON" ; else CMakeBuildExamplesFlag="OFF" ; fi if [ $MSVC_MT -eq 0 ]; then CMakeMsvcMtFlag="OFF" ; else CMakeMsvcMtFlag="ON" ; fi @@ -205,7 +208,7 @@ status=0 if [ $RunMake -ne 0 ]; then - echo "Executing build (via command \`$MakeCmd\`)" + echo "Executing build for ${ProjectName} (via command \`$MakeCmd\`)" $MakeCmd status=$? diff --git a/remove_cmake_artefacts.sh b/remove_cmake_artefacts.sh index 7416db9..e09e895 100755 --- a/remove_cmake_artefacts.sh +++ b/remove_cmake_artefacts.sh @@ -4,6 +4,8 @@ ScriptPath=$0 Dir=$(cd $(dirname "$ScriptPath"); pwd) Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") Directories=( CMakeFiles @@ -98,7 +100,7 @@ if [ ! -d "$CMakeDir" ]; then exit 0 else - echo "Removing all cmake artefacts in '$CMakeDir'" + echo "Removing all ${ProjectName} cmake artefacts in '$CMakeDir'" num_dirs_removed=0 num_files_removed=0 diff --git a/run_all_examples.sh b/run_all_examples.sh index 3b97b83..959144d 100755 --- a/run_all_examples.sh +++ b/run_all_examples.sh @@ -6,11 +6,31 @@ Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} [[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") ListOnly=0 RunMake=1 +# ########################################################## +# colours + +if command -v tput > /dev/null; then + + RbEnvClr_Blue=${FG_BLUE:-$(tput setaf 4)} + RbEnvClr_Red=${FG_BLUE:-$(tput setaf 1)} + RbEnvClr_Bold=${FD_BOLD:-$(tput bold)} + RbEnvClr_None=${FD_NONE:-$(tput sgr0)} +else + + RbEnvClr_Blue= + RbEnvClr_Red= + RbEnvClr_Bold= + RbEnvClr_None= +fi + + # ########################################################## # command-line handling @@ -76,7 +96,7 @@ if [ $RunMake -ne 0 ]; then if [ $ListOnly -eq 0 ]; then - echo "Executing build (via command \`$MakeCmd\`) and then running all example programs" + echo "Executing build (via command \`$MakeCmd\`) and then running all ${ProjectName} example programs" mkdir -p $CMakeDir || exit 1 @@ -99,10 +119,10 @@ if [ $status -eq 0 ]; then if [ $ListOnly -ne 0 ]; then - echo "Listing all example programs" + echo "Listing all ${ProjectName} example programs" else - echo "Running all example programs" + echo "Running all ${ProjectName} example programs" fi for f in $(find $CMakeDir -type f '(' -name 'example.c.*' -o -name 'example.cpp.*' ')' -exec test -x {} \; -print) @@ -110,13 +130,13 @@ if [ $status -eq 0 ]; then if [ $ListOnly -ne 0 ]; then - echo "would execute $f:" + echo "would execute $RbEnvClr_Blue$RbEnvClr_Bold$f$RbEnvClr_None:" continue fi echo - echo "executing $f:" + echo "executing $RbEnvClr_Blue$RbEnvClr_Bold$f$RbEnvClr_None:" $f done diff --git a/run_all_scratch_tests.sh b/run_all_scratch_tests.sh index 1df2fcc..a22a672 100755 --- a/run_all_scratch_tests.sh +++ b/run_all_scratch_tests.sh @@ -6,12 +6,32 @@ Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} [[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") ListOnly=0 RunMake=1 Verbosity=${XTESTS_VERBOSITY:-${TEST_VERBOSITY:-3}} +# ########################################################## +# colours + +if command -v tput > /dev/null; then + + RbEnvClr_Blue=${FG_BLUE:-$(tput setaf 4)} + RbEnvClr_Red=${FG_BLUE:-$(tput setaf 1)} + RbEnvClr_Bold=${FD_BOLD:-$(tput bold)} + RbEnvClr_None=${FD_NONE:-$(tput sgr0)} +else + + RbEnvClr_Blue= + RbEnvClr_Red= + RbEnvClr_Bold= + RbEnvClr_None= +fi + + # ########################################################## # command-line handling @@ -85,7 +105,7 @@ if [ $RunMake -ne 0 ]; then if [ $ListOnly -eq 0 ]; then - echo "Executing build (via command \`$MakeCmd\`) and then running all scratch (and performance) test programs" + echo "Executing build (via command \`$MakeCmd\`) and then running all ${ProjectName} scratch (and performance) test programs" mkdir -p $CMakeDir || exit 1 @@ -108,10 +128,10 @@ if [ $status -eq 0 ]; then if [ $ListOnly -ne 0 ]; then - echo "Listing all scratch (and performance) test programs" + echo "Listing all ${ProjectName} scratch (and performance) test programs" else - echo "Running all scratch (and performance) test programs" + echo "Running all ${ProjectName} scratch (and performance) test programs" fi for f in $(find $CMakeDir -type f '(' -name 'test_scratch*' -o -name 'test.scratch.*' -o -name 'test_performance*' -o -name 'test.performance.*' ')' -exec test -x {} \; -print) @@ -119,7 +139,7 @@ if [ $status -eq 0 ]; then if [ $ListOnly -ne 0 ]; then - echo "would execute $f:" + echo "would execute $RbEnvClr_Blue$RbEnvClr_Bold$f$RbEnvClr_None:" continue fi @@ -130,7 +150,7 @@ if [ $status -eq 0 ]; then fi if [ $Verbosity -ge 2 ]; then - echo "executing $f:" + echo "executing $RbEnvClr_Blue$RbEnvClr_Bold$f$RbEnvClr_None:" fi if $f; then diff --git a/run_all_unit_tests.sh b/run_all_unit_tests.sh index 12fe51a..e5fb239 100755 --- a/run_all_unit_tests.sh +++ b/run_all_unit_tests.sh @@ -6,6 +6,8 @@ Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} [[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") ListOnly=0 RunMake=1 @@ -14,6 +16,24 @@ ComponentOnly=0 Verbosity=${XTESTS_VERBOSITY:-${TEST_VERBOSITY:-3}} +# ########################################################## +# colours + +if command -v tput > /dev/null; then + + RbEnvClr_Blue=${FG_BLUE:-$(tput setaf 4)} + RbEnvClr_Red=${FG_BLUE:-$(tput setaf 1)} + RbEnvClr_Bold=${FD_BOLD:-$(tput bold)} + RbEnvClr_None=${FD_NONE:-$(tput sgr0)} +else + + RbEnvClr_Blue= + RbEnvClr_Red= + RbEnvClr_Bold= + RbEnvClr_None= +fi + + # ########################################################## # command-line handling @@ -119,7 +139,7 @@ if [ $RunMake -ne 0 ]; then if [ $ListOnly -eq 0 ]; then - echo "Executing build (via command \`$MakeCmd\`) and then running all ${TestKindDescription} programs" + echo "Executing build (via command \`$MakeCmd\`) and then running all ${ProjectName} ${TestKindDescription} programs" mkdir -p $CMakeDir || exit 1 @@ -144,10 +164,10 @@ if [ $status -eq 0 ]; then if [ $ListOnly -ne 0 ]; then - echo "Listing all ${TestKindDescription} programs" + echo "Listing all ${ProjectName} ${TestKindDescription} programs" else - echo "Running all ${TestKindDescription} programs" + echo "Running all ${ProjectName} ${TestKindDescription} programs" fi if [ $UnitOnly -ne 0 ]; then @@ -166,7 +186,7 @@ if [ $status -eq 0 ]; then if [ $ListOnly -ne 0 ]; then - echo "would execute $f:" + echo "would execute $RbEnvClr_Blue$RbEnvClr_Bold$f$RbEnvClr_None:" continue fi @@ -177,7 +197,7 @@ if [ $status -eq 0 ]; then fi if [ $Verbosity -ge 2 ]; then - echo "executing $f:" + echo "executing $RbEnvClr_Blue$RbEnvClr_Bold$f$RbEnvClr_None:" fi if $f --verbosity=$Verbosity; then From ee3e6937a1eb1d4676081fd07bf782f179a3fdaf Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Sun, 9 Aug 2026 15:19:54 +1000 Subject: [PATCH 02/88] squash-commit --- .gitattributes | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index e6605d1..5495a20 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ *.c linguist-language=C -*.cmake linguist-language=CMake +*.cmake linguist-detectable=false *.cmd linguist-detectable=false *.cpp linguist-language=C++ *.cxx linguist-language=C++ @@ -13,5 +13,5 @@ *.sln linguist-detectable=false *.vcxproj linguist-detectable=false *.vimrc linguist-detectable=false -CMakeLists.txt linguist-language=CMake +CMakeLists.txt linguist-detectable=false makefile linguist-detectable=false From 4526cb51d7c25ccf7d61fdc4c70ec5f49217e4cc Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Sun, 9 Aug 2026 15:22:17 +1000 Subject: [PATCH 03/88] 4.0.14 --- CHANGES.md | 5 +++++ NEWS.md | 11 ++++++----- include/cstring/cstring.h | 9 +++++---- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 14c4111..ed4e1d4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ # cstring - CHANGES +## 4.0.14 - 9th August 2026 + +4.0.14 + + ## 4.0.14-alpha1 - 7th August 2026 * Aligned project boilerplate with **b64** / **Pantheios** (scripts, markdown, CI branches, `.sis`); diff --git a/NEWS.md b/NEWS.md index db07ef1..febcbad 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,11 +1,12 @@ # cstring - News -| Date | News Item | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------------ | -| 7th August 2026 | Release of [cstring 4.0.14-alpha1](https://github.com/synesissoftware/cstring/releases/tag/4.0.14-alpha1) (boilerplate alignment with **b64** / **Pantheios**) | -| 2nd August 2026 | Release of [cstring 4.0.13](https://github.com/synesissoftware/cstring/releases/tag/4.0.13) (modular CI, examples, Doxygen, documentation) | -| 2nd September 2025 | Release of [cstring 4.0.12](https://github.com/synesissoftware/cstring/releases/tag/4.0.12) | +| Date | News Item | +| ------------------ | ------------------------------------------------------------------------------------------- | +| 9th August 2026 | Release of [cstring 4.0.14](https://github.com/synesissoftware/cstring/releases/tag/4.0.14) | +| 7th August 2026 | Release of [cstring 4.0.14-alpha1](https://github.com/synesissoftware/cstring/releases/tag/4.0.14-alpha1) | +| 2nd August 2026 | Release of [cstring 4.0.13](https://github.com/synesissoftware/cstring/releases/tag/4.0.13) | +| 2nd September 2025 | Release of [cstring 4.0.12](https://github.com/synesissoftware/cstring/releases/tag/4.0.12) | | 23rd February 2025 | Release of cstring 4.0.11 | | 25th October 2024 | Release of cstring 4.0.10 | | 28th January 2024 | Release of cstring 4.0.9 | diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index c89cdec..8812c68 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: 7th August 2026 + * Updated: 9th August 2026 * * Home: http://synesis.com.au/software/ * @@ -55,8 +55,8 @@ #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION # define CSTRING_VER_CSTRING_H_CSTRING_MAJOR 3 # define CSTRING_VER_CSTRING_H_CSTRING_MINOR 11 -# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 11 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 83 +# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 12 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 84 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR @@ -113,6 +113,7 @@ # define CSTRING_VER_4_0_12 0x04000cff # define CSTRING_VER_4_0_13 0x04000dff # define CSTRING_VER_4_0_14_A1 0x04000e41 +# define CSTRING_VER_4_0_14 0x04000eff #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR @@ -134,7 +135,7 @@ #define CSTRING_VER_MAJOR 4 #define CSTRING_VER_MINOR 0 #define CSTRING_VER_PATCH 14 -#define CSTRING_VER_ALPHABETA 0x41 +#define CSTRING_VER_ALPHABETA 0xFF #define CSTRING_VER \ (0\ From 52e55d8045c57d960d04ec9ebfddf5b664b7b1fb Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Sun, 9 Aug 2026 15:24:20 +1000 Subject: [PATCH 04/88] fix --- Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doxyfile b/Doxyfile index bdd909d..78af943 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.0.14-alpha1 +PROJECT_NUMBER = 4.0.14 # Prefer SIS_CMAKE_BUILD_DIR-aligned output (same convention as Diagnosticism). # ./dox/ remains gitignored for legacy/local runs that override OUTPUT_DIRECTORY. From a1cc1313dcf42079954e1be82088a31186363d1f Mon Sep 17 00:00:00 2001 From: Matt Wilson <152443343+mwsis@users.noreply.github.com> Date: Sun, 6 Sep 2026 13:04:57 +1000 Subject: [PATCH 05/88] 4.0.15 (#20) * Updated all `\param` and `\retval` Doxygen documentation comments in `cstring.h`, `cstring.vector.h`, and `cstring.core.c` to obey the 76-rule and terminate with a semicolon; * Bumped version to 4.0.15 in `cstring.h`, `Doxyfile`, `CHANGES.md`, and `NEWS.md`; * Updated `Updated:` header banners to 6th September 2026; --- CHANGES.md | 5 + Doxyfile | 2 +- NEWS.md | 1 + include/cstring/cstring.h | 357 ++++++++++++++++++------------- include/cstring/cstring.vector.h | 17 +- src/cstring.core.c | 8 +- 6 files changed, 228 insertions(+), 162 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ed4e1d4..721204b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ # cstring - CHANGES +## 4.0.15 - 6th September 2026 + +* Updated all `\param` and `\retval` Doxygen documentation comments in **cstring.h**, **cstring.vector.h**, and **cstring.core.c** to obey the 76-rule and terminate with a semicolon; + + ## 4.0.14 - 9th August 2026 4.0.14 diff --git a/Doxyfile b/Doxyfile index 78af943..0a6dc6e 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.0.14 +PROJECT_NUMBER = 4.0.15 # 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 febcbad..73010ca 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,7 @@ | Date | News Item | | ------------------ | ------------------------------------------------------------------------------------------- | +| 6th September 2026 | Release of [cstring 4.0.15](https://github.com/synesissoftware/cstring/releases/tag/4.0.15) | | 9th August 2026 | Release of [cstring 4.0.14](https://github.com/synesissoftware/cstring/releases/tag/4.0.14) | | 7th August 2026 | Release of [cstring 4.0.14-alpha1](https://github.com/synesissoftware/cstring/releases/tag/4.0.14-alpha1) | | 2nd August 2026 | Release of [cstring 4.0.13](https://github.com/synesissoftware/cstring/releases/tag/4.0.13) | diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index 8812c68..d0b2e84 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: 9th August 2026 + * Updated: 6th September 2026 * * Home: http://synesis.com.au/software/ * @@ -55,8 +55,8 @@ #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION # define CSTRING_VER_CSTRING_H_CSTRING_MAJOR 3 # define CSTRING_VER_CSTRING_H_CSTRING_MINOR 11 -# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 12 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 84 +# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 13 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 85 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR @@ -114,6 +114,7 @@ # define CSTRING_VER_4_0_13 0x04000dff # define CSTRING_VER_4_0_14_A1 0x04000e41 # define CSTRING_VER_4_0_14 0x04000eff +# define CSTRING_VER_4_0_15 0x04000fff #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR @@ -134,7 +135,7 @@ #define CSTRING_VER_MAJOR 4 #define CSTRING_VER_MINOR 0 -#define CSTRING_VER_PATCH 14 +#define CSTRING_VER_PATCH 15 #define CSTRING_VER_ALPHABETA 0xFF #define CSTRING_VER \ @@ -365,7 +366,7 @@ typedef struct cstring_t cstring_t; * given error code * \ingroup group__cstring_api * - * \param rc The error code. Must be one of the CSTRING_RC enumeration + * \param rc The error code. Must be one of the CSTRING_RC enumeration; * * \return A non-NULL nul-terminated string */ @@ -378,7 +379,7 @@ cstring_getStatusCodeString( /** \brief Returns the length of the string returned by * cstring_getStatusCodeString() for the given error code. * - * \param rc The error code. Must be one of the CSTRING_RC enumeration + * \param rc The error code. Must be one of the CSTRING_RC enumeration; * * \return The length of the string returned by * cstring_getStatusCodeString() for the the given error code, or 0 if the @@ -425,13 +426,14 @@ cstring_init( /** \brief Creates a cstring instance from a C-style string * \ingroup group__cstring_api * - * \param pcs The uninitialised cstring instance to be created - * \param s Pointer to the C-style string to copy. May be NULL + * \param pcs The uninitialised cstring instance to be created; + * \param s Pointer to the C-style string to copy. May be NULL; * * \note This must only be called on an uninitialised cstring instance * - * \retval CSTRING_RC_SUCCESS the capacity was adjusted - * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the operation + * \retval CSTRING_RC_SUCCESS the capacity was adjusted; + * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the + * operation; */ CSTRING_EXTERN_C CSTRING_RC @@ -443,15 +445,17 @@ cstring_create( /** \brief Creates a cstring instance from a fixed number of characters * \ingroup group__cstring_api * - * \param pcs The uninitialised cstring instance to be created + * \param pcs The uninitialised cstring instance to be created; * \param s Pointer to the first character in the array from which to copy. - * Must not be NULL, unless \c cch is 0 - * \param cch The number of characters to be copied into the cstring instance + * Must not be NULL, unless \c cch is 0; + * \param cch The number of characters to be copied into the cstring + * instance; * * \note This must only be called on an uninitialised cstring instance * - * \retval CSTRING_RC_SUCCESS the capacity was adjusted - * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the operation + * \retval CSTRING_RC_SUCCESS the capacity was adjusted; + * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the + * operation; */ CSTRING_EXTERN_C CSTRING_RC @@ -465,14 +469,15 @@ cstring_createLen( * of a character * \ingroup group__cstring_api * - * \param pcs The uninitialised cstring instance to be created - * \param ch The character to be repeated - * \param n The number of repetitions of character \c c + * \param pcs The uninitialised cstring instance to be created; + * \param ch The character to be repeated; + * \param n The number of repetitions of character \c c; * * \note This must only be called on an uninitialised cstring instance * - * \retval CSTRING_RC_SUCCESS the capacity was adjusted - * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the operation + * \retval CSTRING_RC_SUCCESS the capacity was adjusted; + * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the + * operation; */ CSTRING_EXTERN_C CSTRING_RC @@ -486,22 +491,27 @@ cstring_createN( /** \brief Creates a cstring instance with special characteristics from a C-style string * \ingroup group__cstring_api * - * \param pcs The uninitialised cstring instance to be created - * \param s Pointer to the C-style string to copy. May be NULL - * \param flags The flags defining the characteristics of the cstring instance - * \param arena The memory buffer for borrowed (CSTRING_F_MEMORY_IS_BORROWED), - * or a pointer to a custom memory arena. Note that custom memory arenas are - * not currently supported, and this parameter must be NULL unless the flag - * CSTRING_F_MEMORY_IS_BORROWED is specified + * \param pcs The uninitialised cstring instance to be created; + * \param s Pointer to the C-style string to copy. May be NULL; + * \param flags The flags defining the characteristics of the cstring + * instance; + * \param arena The memory buffer for borrowed + * (CSTRING_F_MEMORY_IS_BORROWED), or a pointer to a custom memory arena. + * Note that custom memory arenas are not currently supported, and this + * parameter must be NULL unless the flag CSTRING_F_MEMORY_IS_BORROWED is + * specified; * \param capacity The capacity of the buffer specified in \c arena, or - * otherwise ignored + * otherwise ignored; * * \note This must only be called on an uninitialised cstring instance * - * \retval CSTRING_RC_SUCCESS the capacity was adjusted - * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the operation - * \retval CSTRING_RC_INVALIDARENA an invalid arena specification was given in the \c flags parameter - * \retval CSTRING_RC_CUSTOMARENANOTSUPPORTED A custom arena was specified, which is not yet supported + * \retval CSTRING_RC_SUCCESS the capacity was adjusted; + * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the + * operation; + * \retval CSTRING_RC_INVALIDARENA an invalid arena specification was given + * in the \c flags parameter; + * \retval CSTRING_RC_CUSTOMARENANOTSUPPORTED A custom arena was specified, + * which is not yet supported; */ CSTRING_EXTERN_C CSTRING_RC @@ -516,24 +526,30 @@ cstring_createEx( /** \brief Creates a cstring instance with special characteristics from a fixed number of characters * \ingroup group__cstring_api * - * \param pcs The uninitialised cstring instance to be created + * \param pcs The uninitialised cstring instance to be created; * \param s Pointer to the first character in the array from which to copy. - * Must not be NULL, unless \c cch is 0 - * \param cch The number of characters to be copied into the cstring instance - * \param flags The flags defining the characteristics of the cstring instance - * \param arena The memory buffer for borrowed (CSTRING_F_MEMORY_IS_BORROWED), - * or a pointer to a custom memory arena. Note that custom memory arenas are - * not currently supported, and this parameter must be NULL unless the flag - * CSTRING_F_MEMORY_IS_BORROWED is specified + * Must not be NULL, unless \c cch is 0; + * \param cch The number of characters to be copied into the cstring + * instance; + * \param flags The flags defining the characteristics of the cstring + * instance; + * \param arena The memory buffer for borrowed + * (CSTRING_F_MEMORY_IS_BORROWED), or a pointer to a custom memory arena. + * Note that custom memory arenas are not currently supported, and this + * parameter must be NULL unless the flag CSTRING_F_MEMORY_IS_BORROWED is + * specified; * \param capacity The capacity of the buffer specified in \c arena, or - * otherwise ignored + * otherwise ignored; * * \note This must only be called on an uninitialised cstring instance * - * \retval CSTRING_RC_SUCCESS the capacity was adjusted - * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the operation - * \retval CSTRING_RC_INVALIDARENA an invalid arena specification was given in the \c flags parameter - * \retval CSTRING_RC_CUSTOMARENANOTSUPPORTED A custom arena was specified, which is not yet supported + * \retval CSTRING_RC_SUCCESS the capacity was adjusted; + * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the + * operation; + * \retval CSTRING_RC_INVALIDARENA an invalid arena specification was given + * in the \c flags parameter; + * \retval CSTRING_RC_CUSTOMARENANOTSUPPORTED A custom arena was specified, + * which is not yet supported; */ CSTRING_EXTERN_C CSTRING_RC @@ -549,12 +565,12 @@ cstring_createLenEx( /** \brief Releases resources and resets the attributes of the cstring instance * \ingroup group__cstring_api * - * \param pcs The cstring instance to be destroyed + * \param pcs The cstring instance to be destroyed; * * \note This must only be called on an initialised cstring instance, which is * then rendered in the uninitialised state. * - * \retval CSTRING_RC_SUCCESS the capacity was destroyed + * \retval CSTRING_RC_SUCCESS the capacity was destroyed; */ CSTRING_EXTERN_C CSTRING_RC @@ -566,16 +582,22 @@ cstring_destroy( /** \brief Removes the allocated buffer from the cstring instance into a caller supplied pointer * \ingroup group__cstring_api * - * \param pcs the cstring instance whose buffer is to be retrieved. May not be NULL. - * \param pRaw pointer to a buffer pointer which will receive the yielded raw memory. May not be NULL. May not be equal to pPayload. - * \param pPayload pointer to a buffer pointer which will receive the yielded payload. May not be NULL. May not be equal to pRaw. + * \param pcs the cstring instance whose buffer is to be retrieved. May not + * be NULL; + * \param pRaw pointer to a buffer pointer which will receive the yielded + * raw memory. May not be NULL. May not be equal to pPayload; + * \param pPayload pointer to a buffer pointer which will receive the + * yielded payload. May not be NULL. May not be equal to pRaw; * * \note The values of *pRaw and *pPayload may not be equal * - * \retval CSTRING_RC_SUCCESS the buffer was yielded - * \retval CSTRING_RC_READONLY the cstring instance is marked readonly (CSTRING_F_MEMORY_IS_READONLY) - * \retval CSTRING_RC_BORROWED the cstring instance is marked fixed (CSTRING_F_MEMORY_IS_BORROWED) - * \retval CSTRING_RC_CANNOTYIELDFROMSO the cstring instance uses realloc() in a Win32 dynamic library + * \retval CSTRING_RC_SUCCESS the buffer was yielded; + * \retval CSTRING_RC_READONLY the cstring instance is marked readonly + * (CSTRING_F_MEMORY_IS_READONLY); + * \retval CSTRING_RC_BORROWED the cstring instance is marked fixed + * (CSTRING_F_MEMORY_IS_BORROWED); + * \retval CSTRING_RC_CANNOTYIELDFROMSO the cstring instance uses realloc() + * in a Win32 dynamic library; * * \note When linked to cstring as a Win32 dynamic library, this function will not * work for strings allocated via realloc() @@ -602,18 +624,23 @@ cstring_yield( /** Sets the capacity of the cstring instance * \ingroup group__cstring_api * - * \param pcs The cstring instance to be adjusted - * \param capacity The capacity to which the cstring instance should be adjusted + * \param pcs The cstring instance to be adjusted; + * \param capacity The capacity to which the cstring instance should be + * adjusted; * * \note If the specified capacity would cause the string contents to be * truncated, the value is adjusted to the minimum possible without * requiring truncation. * - * \retval CSTRING_RC_SUCCESS the capacity was adjusted - * \retval CSTRING_RC_READONLY the cstring instance is marked readonly (CSTRING_F_MEMORY_IS_READONLY) - * \retval CSTRING_RC_EXCEEDFIXEDCAPACITY the cstring instance is marked fixed (CSTRING_F_MEMORY_IS_FIXED) - * \retval CSTRING_RC_EXCEEDBORROWEDCAPACITY the cstring instance is marked borrowed (CSTRING_F_MEMORY_IS_BORROWED) - * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the operation + * \retval CSTRING_RC_SUCCESS the capacity was adjusted; + * \retval CSTRING_RC_READONLY the cstring instance is marked readonly + * (CSTRING_F_MEMORY_IS_READONLY); + * \retval CSTRING_RC_EXCEEDFIXEDCAPACITY the cstring instance is marked + * fixed (CSTRING_F_MEMORY_IS_FIXED); + * \retval CSTRING_RC_EXCEEDBORROWEDCAPACITY the cstring instance is marked + * borrowed (CSTRING_F_MEMORY_IS_BORROWED); + * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the + * operation; */ CSTRING_EXTERN_C CSTRING_RC @@ -626,14 +653,19 @@ cstring_setCapacity( /** \brief Assigns a C-style string to the cstring instance * \ingroup group__cstring_api * - * \param pcs The cstring to assign to - * \param s Pointer to the C-style string to assign. May be NULL + * \param pcs The cstring to assign to; + * \param s Pointer to the C-style string to assign. May be NULL; * - * \retval CSTRING_RC_SUCCESS the requested number of bytes were assigned to the cstring - * \retval CSTRING_RC_READONLY the cstring instance is marked readonly (CSTRING_F_MEMORY_IS_READONLY) - * \retval CSTRING_RC_EXCEEDFIXEDCAPACITY the cstring instance is marked fixed (CSTRING_F_MEMORY_IS_FIXED) - * \retval CSTRING_RC_EXCEEDBORROWEDCAPACITY the cstring instance is marked borrowed (CSTRING_F_MEMORY_IS_BORROWED) - * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the operation + * \retval CSTRING_RC_SUCCESS the requested number of bytes were assigned to + * the cstring; + * \retval CSTRING_RC_READONLY the cstring instance is marked readonly + * (CSTRING_F_MEMORY_IS_READONLY); + * \retval CSTRING_RC_EXCEEDFIXEDCAPACITY the cstring instance is marked + * fixed (CSTRING_F_MEMORY_IS_FIXED); + * \retval CSTRING_RC_EXCEEDBORROWEDCAPACITY the cstring instance is marked + * borrowed (CSTRING_F_MEMORY_IS_BORROWED); + * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the + * operation; * * \note The function is safe with respect to 'self-assignment', i.e, \c s can * refer to (part of) the payload of \c pcs, and the function will still @@ -649,20 +681,25 @@ cstring_assign( /** \brief Assigns a fixed number of characters to the cstring instance * \ingroup group__cstring_api * - * \param pcs The cstring to assign to - * \param s Pointer to the first character in the array from which to assign. - * Must not be NULL, unless \c cch is 0 - * \param cch The number of characters to assign + * \param pcs The cstring to assign to; + * \param s Pointer to the first character in the array from which to + * assign. Must not be NULL, unless \c cch is 0; + * \param cch The number of characters to assign; * * \note If the source has embedded nul characters, they will be incorporated * into the cstring contents, which means that calling strlen() on the payload * pointer may give inconsistent results * - * \retval CSTRING_RC_SUCCESS the requested number of bytes were assigned to the cstring - * \retval CSTRING_RC_READONLY the cstring instance is marked readonly (CSTRING_F_MEMORY_IS_READONLY) - * \retval CSTRING_RC_EXCEEDFIXEDCAPACITY the cstring instance is marked fixed (CSTRING_F_MEMORY_IS_FIXED) - * \retval CSTRING_RC_EXCEEDBORROWEDCAPACITY the cstring instance is marked borrowed (CSTRING_F_MEMORY_IS_BORROWED) - * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the operation + * \retval CSTRING_RC_SUCCESS the requested number of bytes were assigned to + * the cstring; + * \retval CSTRING_RC_READONLY the cstring instance is marked readonly + * (CSTRING_F_MEMORY_IS_READONLY); + * \retval CSTRING_RC_EXCEEDFIXEDCAPACITY the cstring instance is marked + * fixed (CSTRING_F_MEMORY_IS_FIXED); + * \retval CSTRING_RC_EXCEEDBORROWEDCAPACITY the cstring instance is marked + * borrowed (CSTRING_F_MEMORY_IS_BORROWED); + * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the + * operation; */ CSTRING_EXTERN_C CSTRING_RC @@ -675,14 +712,19 @@ cstring_assignLen( /** \brief Copies the contents of one cstring instance to another * \ingroup group__cstring_api * - * \param pcs The cstring to assign to - * \param pcsSrc The cstring whose contents will be copied - * - * \retval CSTRING_RC_SUCCESS the requested number of bytes were copied to the cstring - * \retval CSTRING_RC_READONLY the cstring instance is marked readonly (CSTRING_F_MEMORY_IS_READONLY) - * \retval CSTRING_RC_EXCEEDFIXEDCAPACITY the cstring instance is marked fixed (CSTRING_F_MEMORY_IS_FIXED) - * \retval CSTRING_RC_EXCEEDBORROWEDCAPACITY the cstring instance is marked borrowed (CSTRING_F_MEMORY_IS_BORROWED) - * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the operation + * \param pcs The cstring to assign to; + * \param pcsSrc The cstring whose contents will be copied; + * + * \retval CSTRING_RC_SUCCESS the requested number of bytes were copied to + * the cstring; + * \retval CSTRING_RC_READONLY the cstring instance is marked readonly + * (CSTRING_F_MEMORY_IS_READONLY); + * \retval CSTRING_RC_EXCEEDFIXEDCAPACITY the cstring instance is marked + * fixed (CSTRING_F_MEMORY_IS_FIXED); + * \retval CSTRING_RC_EXCEEDBORROWEDCAPACITY the cstring instance is marked + * borrowed (CSTRING_F_MEMORY_IS_BORROWED); + * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the + * operation; */ CSTRING_EXTERN_C CSTRING_RC @@ -694,14 +736,19 @@ cstring_copy( /** \brief Appends a C-style string to the cstring instance * \ingroup group__cstring_api * - * \param pcs The cstring to append to - * \param s Pointer to the C-style string to append. May be NULL - * - * \retval CSTRING_RC_SUCCESS the requested number of bytes were appended to the cstring - * \retval CSTRING_RC_READONLY the cstring instance is marked readonly (CSTRING_F_MEMORY_IS_READONLY) - * \retval CSTRING_RC_EXCEEDFIXEDCAPACITY the cstring instance is marked fixed (CSTRING_F_MEMORY_IS_FIXED) - * \retval CSTRING_RC_EXCEEDBORROWEDCAPACITY the cstring instance is marked borrowed (CSTRING_F_MEMORY_IS_BORROWED) - * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the operation + * \param pcs The cstring to append to; + * \param s Pointer to the C-style string to append. May be NULL; + * + * \retval CSTRING_RC_SUCCESS the requested number of bytes were appended to + * the cstring; + * \retval CSTRING_RC_READONLY the cstring instance is marked readonly + * (CSTRING_F_MEMORY_IS_READONLY); + * \retval CSTRING_RC_EXCEEDFIXEDCAPACITY the cstring instance is marked + * fixed (CSTRING_F_MEMORY_IS_FIXED); + * \retval CSTRING_RC_EXCEEDBORROWEDCAPACITY the cstring instance is marked + * borrowed (CSTRING_F_MEMORY_IS_BORROWED); + * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the + * operation; */ CSTRING_EXTERN_C CSTRING_RC @@ -713,20 +760,25 @@ cstring_append( /** \brief Appends a fixed number of characters to the cstring instance * \ingroup group__cstring_api * - * \param pcs The cstring to append to - * \param s Pointer to the first character in the array from which to append. - * Must not be NULL, unless \c cch is 0 - * \param cch The number of characters to append + * \param pcs The cstring to append to; + * \param s Pointer to the first character in the array from which to + * append. Must not be NULL, unless \c cch is 0; + * \param cch The number of characters to append; * * \note If the source has embedded nul characters, they will be incorporated * into the cstring contents, which means that calling strlen() on the payload * pointer may give inconsistent results * - * \retval CSTRING_RC_SUCCESS the requested number of bytes were appended to the cstring - * \retval CSTRING_RC_READONLY the cstring instance is marked readonly (CSTRING_F_MEMORY_IS_READONLY) - * \retval CSTRING_RC_EXCEEDFIXEDCAPACITY the cstring instance is marked fixed (CSTRING_F_MEMORY_IS_FIXED) - * \retval CSTRING_RC_EXCEEDBORROWEDCAPACITY the cstring instance is marked borrowed (CSTRING_F_MEMORY_IS_BORROWED) - * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the operation + * \retval CSTRING_RC_SUCCESS the requested number of bytes were appended to + * the cstring; + * \retval CSTRING_RC_READONLY the cstring instance is marked readonly + * (CSTRING_F_MEMORY_IS_READONLY); + * \retval CSTRING_RC_EXCEEDFIXEDCAPACITY the cstring instance is marked + * fixed (CSTRING_F_MEMORY_IS_FIXED); + * \retval CSTRING_RC_EXCEEDBORROWEDCAPACITY the cstring instance is marked + * borrowed (CSTRING_F_MEMORY_IS_BORROWED); + * \retval CSTRING_RC_OUTOFMEMORY insufficient memory to carry out the + * operation; */ CSTRING_EXTERN_C CSTRING_RC @@ -739,13 +791,16 @@ cstring_appendLen( /** \brief Truncates the cstring instance * \ingroup group__cstring_api * - * \param pcs The cstring to truncate - * \param len The required length. Ignored if not less than the current length + * \param pcs The cstring to truncate; + * \param len The required length. Ignored if not less than the current + * length; * * \note The cstring instance must not be readonly * - * \retval CSTRING_RC_SUCCESS cstring instance contents were truncated (or the truncation length was out of bounds) - * \retval CSTRING_RC_READONLY the cstring instance is marked readonly (CSTRING_F_MEMORY_IS_READONLY) + * \retval CSTRING_RC_SUCCESS cstring instance contents were truncated (or + * the truncation length was out of bounds); + * \retval CSTRING_RC_READONLY the cstring instance is marked readonly + * (CSTRING_F_MEMORY_IS_READONLY); */ CSTRING_EXTERN_C CSTRING_RC @@ -757,14 +812,18 @@ cstring_truncate( /** \brief Swaps the contents of the two cstring instances * \ingroup group__cstring_api * - * \param pcs1 the cstring instance whose contents will be swapped with \c pcs2 - * \param pcs2 the cstring instance whose contents will be swapped with \c pcs1 + * \param pcs1 the cstring instance whose contents will be swapped with + * \c pcs2; + * \param pcs2 the cstring instance whose contents will be swapped with + * \c pcs1; * * \note Neither instance can be either readonly or fixed-and-borrowed * - * \retval CSTRING_RC_SUCCESS string instance contents were swapped - * \retval CSTRING_RC_READONLY one or both instances are marked readonly (CSTRING_F_MEMORY_IS_READONLY) - * \retval CSTRING_RC_BORROWED one or both instances are marked fixed (CSTRING_F_MEMORY_IS_BORROWED) + * \retval CSTRING_RC_SUCCESS string instance contents were swapped; + * \retval CSTRING_RC_READONLY one or both instances are marked readonly + * (CSTRING_F_MEMORY_IS_READONLY); + * \retval CSTRING_RC_BORROWED one or both instances are marked fixed + * (CSTRING_F_MEMORY_IS_BORROWED); */ CSTRING_EXTERN_C CSTRING_RC @@ -776,18 +835,18 @@ cstring_swap( /** Reads in a line of text from the given text stream * - * \param stm The stream from which the line will be read + * \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 + * stored; + * \param numRead An optional pointer to a variable to receive the number of + * characters appended to the string. May be NULL; * - * \retval CSTRING_RC_INVALIDSTREAM The stream was not valid (as tested - * by ferror()) + * \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_EOF A line terminted by the end-of-file was read in - * \retval -other- any other value returned by cstring_appendLen() + * read in; + * \retval CSTRING_RC_EOF A line terminted by the end-of-file was read in; + * \retval -other- any other value returned by cstring_appendLen(); * * \pre (NULL != pcs) */ @@ -801,17 +860,17 @@ cstring_readline( /** Writes a line of text to the given text stream * - * \param stm The stream to which the line will be written + * \param stm The stream to which the line will be written; * \param pcs The initialised string instance containing the string to be - * written, after which a carriage-return is appended. + * written, after which a carriage-return is appended; * \param numWritten An optional pointer to a variable to receive the number - * of characters written to the stream. May be NULL + * of characters written to the stream. May be NULL; * - * \retval CSTRING_RC_INVALIDSTREAM The stream was not valid (as tested - * by ferror()) + * \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_IOERROR Could not write to the stream + * read in; + * \retval CSTRING_RC_IOERROR Could not write to the stream; * * \pre (NULL != pcs) */ @@ -825,15 +884,15 @@ cstring_writeline( /** Writes a string to the given text stream * - * \param stm The stream to which the string will be written - * \param pcs The initialised string instance to be written + * \param stm The stream to which the string will be written; + * \param pcs The initialised string instance to be written; * \param numWritten An optional pointer to a variable to receive the number - * of characters written to the stream. May be NULL + * of characters written to the stream. May be NULL; * - * \retval CSTRING_RC_INVALIDSTREAM The stream was not valid (as tested - * by ferror()) - * \retval CSTRING_RC_SUCCESS The string was written - * \retval CSTRING_RC_IOERROR Could not write to the stream + * \retval CSTRING_RC_INVALIDSTREAM The stream was not valid (as tested by + * ferror()); + * \retval CSTRING_RC_SUCCESS The string was written; + * \retval CSTRING_RC_IOERROR Could not write to the stream; * * \pre (NULL != pcs) */ @@ -874,11 +933,11 @@ cstring_insertLen( /** Replaces a specified section of the string with another * - * \param pcs The cstring instance whose contents will be modified + * \param pcs The cstring instance whose contents will be modified; * \param index The position of the replaced section. May be negative, in - * which case it represents the position from the end of the string - * \param len The length of the replaced section - * \param s The replacing string + * which case it represents the position from the end of the string; + * \param len The length of the replaced section; + * \param s The replacing string; * * \note If the identified section to be replaced is outside the bounds of * the string, @@ -897,12 +956,12 @@ cstring_replace( /** Replaces a specified section of the string with another * - * \param pcs The cstring instance whose contents will be modified + * \param pcs The cstring instance whose contents will be modified; * \param index The position of the replaced section. May be negative, in - * which case it represents the position from the end of the string - * \param len The length of the replaced section - * \param s The replacing string - * \param cch The replacing string length + * which case it represents the position from the end of the string; + * \param len The length of the replaced section; + * \param s The replacing string; + * \param cch The replacing string length; * * \note If the identified section to be replaced is outside the bounds of * the string, @@ -922,14 +981,14 @@ cstring_replaceLen( /** Replaces all instances of one substring with another * - * \param pcs The cstring instance whose contents will be modified - * \param f The string to search for. If NULL or the empty string, - * the function returns immediately - * \param t The string to replace with. If NULL or the empty string, - * all occurences of \c f will be removed + * \param pcs The cstring instance whose contents will be modified; + * \param f The string to search for. If NULL or the empty string, the + * function returns immediately; + * \param t The string to replace with. If NULL or the empty string, all + * occurences of \c f will be removed; * \param numReplaced An optional pointer to a variable to receive the * number of replacements performed. May be NULL if caller does not - * require + * require; * * \pre (NULL != pcs) */ diff --git a/include/cstring/cstring.vector.h b/include/cstring/cstring.vector.h index 3671f7a..7a0511f 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: 2nd August 2026 + * Updated: 6th September 2026 * * Home: http://synesis.com.au/software/ * @@ -54,8 +54,8 @@ #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION # define CSTRING_VER_CSTRING_H_VECTOR_MAJOR 1 # define CSTRING_VER_CSTRING_H_VECTOR_MINOR 0 -# define CSTRING_VER_CSTRING_H_VECTOR_REVISION 2 -# define CSTRING_VER_CSTRING_H_VECTOR_EDIT 8 +# define CSTRING_VER_CSTRING_H_VECTOR_REVISION 3 +# define CSTRING_VER_CSTRING_H_VECTOR_EDIT 9 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ @@ -127,7 +127,7 @@ cstring_vector_init( * \note This must only be called on an initialised cstring-vector instance, * which is then rendered in the uninitialised state. * - * \retval CSTRING_RC_SUCCESS the capacity was destroyed + * \retval CSTRING_RC_SUCCESS the capacity was destroyed; * * \pre (NULL != pcsv) */ @@ -169,14 +169,15 @@ cstring_vector_create( /** * - * \param pcsv Pointer to the c-string vector instance - * \param position Position within the vector at which to insert the given item(s) + * \param pcsv Pointer to the c-string vector instance; + * \param position Position within the vector at which to insert the given + * item(s); * \param strings Pointer to an array of cstring_t instances with at least * as many elements as \c numStrings. May be NULL, in which case the * function will behave as if \c strings points to an array containing - * \c numStrings blank cstring_t instances. + * \c numStrings blank cstring_t instances; * \param numStrings The number of cstring_t instances to be inserted at the - * given position. + * given position; * * \pre (NULL != pcsv) * \pre (position <= pcsv->len) diff --git a/src/cstring.core.c b/src/cstring.core.c index 2e9608b..22631bf 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: 2nd August 2026 + * Updated: 6th September 2026 * * Home: http://synesis.com.au/software/ * @@ -189,9 +189,9 @@ convert_negative_index( } /* - * \param dst Destination. May NOT be \c NULL - * \param src Source. May be \c NULL - * \param lim Maximum number of elements in \c dst + * \param dst Destination. May NOT be \c NULL; + * \param src Source. May be \c NULL; + * \param lim Maximum number of elements in \c dst; * * \return The actual length of src if not \c NULL; 0 otherwise * From 5517777c2de598aab43fa3e00506e08af9203480 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 17 Sep 2026 08:23:39 +1000 Subject: [PATCH 06/88] markdown --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ed4e1d4..a0f247a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -# cstring - CHANGES +# cstring - Changes ## 4.0.14 - 9th August 2026 From 72487c50c5508464b13c1ff28f7152812a3c47e2 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 17 Sep 2026 09:34:23 +1000 Subject: [PATCH 07/88] chore(c-cpp): union files.associations from ~/dev vscode settings Give the 28 in-wave C/C++ projects (and gold **c** / **cxx** / **c_cxx** templates) a sorted **files.associations** union of all associations found under **~/dev**; --- .editorconfig | 50 +++++++++++++++++++++++++++++++++++++++++++ .vscode/settings.json | 38 +++++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b409470 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,50 @@ +root = true + +# Language indent/EOL/charset overlap with .vscode/settings.json. +# Rulers, renderWhitespace, detectIndentation, mergeEditor, and +# language-server settings cannot be expressed here — they stay in +# settings.json. + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = tab +insert_final_newline = true +trim_trailing_whitespace = true + +# [bat] +[*.{bat,cmd}] +end_of_line = crlf +indent_size = 4 +indent_style = space + +# [c] +[*.{c,h}] +indent_size = 4 +indent_style = space + +# [cmake] +[{CMakeLists.txt,*.cmake}] +indent_size = 4 +indent_style = tab + +# [json] +[*.json] +indent_size = 2 +indent_style = space + +# [markdown] +[*.md] +indent_size = 2 +indent_style = space + +# [shellscript] +[*.{bash,sh,zsh}] +indent_size = 2 +indent_style = space + +# [yaml] +[*.{yaml,yml}] +indent_size = 2 +indent_style = space diff --git a/.vscode/settings.json b/.vscode/settings.json index 4044afc..6a0da10 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,8 @@ "editor.tabSize": 4, }, "[c]": { + "editor.defaultFormatter": "ms-vscode.cpptools", + "editor.formatOnSave": true, "editor.insertSpaces": true, "editor.rulers": [ 60, 64, 68, 72, 76 ], "editor.tabSize": 4, @@ -14,6 +16,8 @@ "editor.tabSize": 4, }, "[cpp]": { + "editor.defaultFormatter": "ms-vscode.cpptools", + "editor.formatOnSave": true, "editor.insertSpaces": true, "editor.rulers": [ 60, 64, 68, 72, 76 ], "editor.tabSize": 4, @@ -49,13 +53,28 @@ "editor.insertSpaces": true, "editor.tabSize": 2, }, + "C_Cpp.autocompleteAddParentheses": true, + "C_Cpp.clang_format_fallbackStyle": "none", + "C_Cpp.clang_format_style": "file", + "C_Cpp.default.cStandard": "c17", + "C_Cpp.default.cppStandard": "c++17", + "C_Cpp.enhancedColorization": "enabled", + "C_Cpp.errorSquiggles": "enabled", + "C_Cpp.formatting": "clangFormat", + "C_Cpp.intelliSenseEngine": "default", "cmake.configureOnOpen": false, + "debug.allowBreakpointsEverywhere": true, "editor.detectIndentation": false, "editor.insertSpaces": false, "editor.renderWhitespace": "all", "editor.rulers": [ 76 ], "editor.tabSize": 2, "files.associations": { + "*.gemspec": "ruby", + "*.mdoc": "troff", + "*.roff": "troff", + "Gemfile": "ruby", + "Rakefile": "ruby", "__bit_reference": "cpp", "__bits": "cpp", "__config": "cpp", @@ -76,13 +95,17 @@ "__tuple": "cpp", "__verbose_abort": "cpp", "algorithm": "cpp", + "api.h": "c", "array": "cpp", "atomic": "cpp", + "auto_buffer.h": "c", "bit": "cpp", "bitset": "cpp", "cctype": "cpp", "charconv": "cpp", "chrono": "cpp", + "clasp.h": "c", + "cli_helpers.h": "c", "clocale": "cpp", "cmath": "cpp", "compare": "cpp", @@ -109,6 +132,7 @@ "functional": "cpp", "implicit_link.h": "c", "initializer_list": "cpp", + "invoke.h": "c", "io.h": "c", "iomanip": "cpp", "ios": "cpp", @@ -119,28 +143,39 @@ "limits": "cpp", "list": "cpp", "locale": "cpp", + "main.h": "c", "map": "cpp", "memory": "cpp", "memory_resource": "cpp", "mutex": "cpp", + "net_helpers.h": "c", "new": "cpp", "numbers": "cpp", "numeric": "cpp", "optional": "cpp", "ostream": "cpp", + "path_functions.h": "c", + "print": "cpp", "queue": "cpp", "random": "cpp", "ranges": "cpp", "ratio": "cpp", + "rc.h": "c", + "recls.h": "c", + "result_codes_.h": "c", "semaphore": "cpp", "set": "cpp", "setenv.h": "c", "shwild.h": "c", + "sleep_functions.h": "c", "span": "cpp", "sstream": "cpp", "stack": "cpp", + "stdatomic.h": "c", + "stdbool.h": "c", "stdexcept": "cpp", "stdio.h": "c", + "stock.h": "c", "stop_token": "cpp", "streambuf": "cpp", "string": "cpp", @@ -149,6 +184,7 @@ "terse-api.h": "c", "text_encoding": "cpp", "thread": "cpp", + "trace.h": "c", "tuple": "cpp", "type_traits": "cpp", "typeinfo": "cpp", @@ -159,6 +195,7 @@ "utility": "cpp", "variant": "cpp", "vector": "cpp", + "williamscrc.h": "c", "xfacet": "cpp", "xhash": "cpp", "xiosbase": "cpp", @@ -180,4 +217,3 @@ "files.trimTrailingWhitespace": true, "git.mergeEditor": false, } - From a708e89c1fe3fedaa135b31386aeb31877a19390 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 17 Sep 2026 10:13:30 +1000 Subject: [PATCH 08/88] .gitattributes --- .gitattributes | 116 ++++++++++++++++++++++++++----- .gitignore | 181 +++++++++++++++++++++---------------------------- 2 files changed, 176 insertions(+), 121 deletions(-) diff --git a/.gitattributes b/.gitattributes index 5495a20..76a2f86 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,17 +1,99 @@ -*.c linguist-language=C -*.cmake linguist-detectable=false -*.cmd linguist-detectable=false -*.cpp linguist-language=C++ -*.cxx linguist-language=C++ -*.dsp linguist-detectable=false -*.dsw linguist-detectable=false -*.h linguist-language=C -*.hpp linguist-language=C++ -*.html linguist-detectable=false -*.hxx linguist-language=C++ -*.sh linguist-detectable=false -*.sln linguist-detectable=false -*.vcxproj linguist-detectable=false -*.vimrc linguist-detectable=false -CMakeLists.txt linguist-detectable=false -makefile linguist-detectable=false +# .gitattributes — C / C++ (GitHub-hosted) +# +# Sources: GitHub Docs (line endings), git-scm gitattributes (diff=cpp), +# gitattributes/gitattributes C++.gitattributes + Common.gitattributes, +# github-linguist overrides. + +# Default: detect text, normalize to LF in the repository +* text=auto + +# --- Source --- +*.c text diff=cpp +*.c++ text diff=cpp +*.cc text diff=cpp +*.cpp text diff=cpp +*.cxx text diff=cpp +*.h text diff=cpp +*.h++ text diff=cpp +*.hh text diff=cpp +*.hpp text diff=cpp +*.hxx text diff=cpp +*.inc text +*.inl text +*.ipp text +*.tcc text +*.tpp text + +# --- Build / config --- +*.am text +*.cmake text +*.m4 text +*.mak text +*.mk text +*akefile text +CMakeLists.txt text +configure text eol=lf +configure.ac text + +# --- Scripts --- +*.bash text eol=lf +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf +*.sh text eol=lf +*.zsh text eol=lf + +# --- Docs / meta --- +*.adoc text +*.markdown text diff=markdown +*.md text diff=markdown +*.txt text +AUTHORS text +CHANGELOG text +CHANGES text +CONTRIBUTING text +COPYING text +LICENSE text +NEWS text +README text +TODO text +.gitattributes text +.gitignore text + +# --- Serialisation --- +*.json text +*.toml text +*.xml text +*.yaml text +*.yml text + +# --- Compiled / binary artefacts --- +*.a binary +*.dll binary +*.dylib binary +*.exe binary +*.gch binary +*.la binary +*.lai binary +*.lib binary +*.lo binary +*.o binary +*.obj binary +*.out binary +*.pch binary +*.slo binary +*.so binary + +# --- Archives / images (common) --- +*.gif binary +*.gz binary +*.ico binary +*.jpeg binary +*.jpg binary +*.png binary +*.tar binary +*.zip binary + +# --- GitHub Linguist --- +**/build/** linguist-generated +**/cmake-build-*/** linguist-generated diff --git a/.gitignore b/.gitignore index ca2d5c7..226c160 100644 --- a/.gitignore +++ b/.gitignore @@ -1,163 +1,127 @@ -# Synesis Information Systems .gitignore for C/C++ libraries +# Synesis Information Systems .gitignore for C / C++ libraries # directories (by name) -/.git-old/ -/.vs/ +bin/ +ideas/ +ipch/ +lib/ +scripts/internal/ +Win32/ +x64/ +.idea/ .svn/ +.vs/ -.classpath -/.idea/ -/__pycache__ -/node_modules +__pycache__/ -/_build/ +/_analyses/ /_build-ci/ +/_build/ /_internal/ -/_analyses/ - -/bin/ -/ipch/ -/lib/ -bin/ -ipch/ -lib/ /docs/ /dox/ -/dox /ideas/ -ideas/ +/include/xcontract/ +/include/xcover/ /included/ +/node_modules/ /releases/ /scratch/ -/temp/ - -/--include -/--src -/-include -/-src - -/examples/bin -/examples/c/bin -/examples/cpp/bin - -/examples/lib -/examples/c/lib -/examples/cpp/lib - -projects/util/vc10-generated/ - -perf_test/ - -scripts/internal/ - -/include/shwild/ -/include/xcontract/ -/include/xcover/ -/include/xtests/ -/src/shwild/ /src/xcontract/ /src/xcover/ -/src/xtests/ +/target/ +/temp/ + +/examples/bin/ +/examples/c/bin/ +/examples/c/lib/ +/examples/cpp/bin/ +/examples/cpp/lib/ +/examples/lib/ -Debug Multithreaded DLL/ Debug Multithreaded Dll/ -Debug Multithreaded pseudoUNIX/ +Debug Multithreaded DLL/ Debug Multithreaded pseudoUnix/ +Debug Multithreaded pseudoUNIX/ Debug Multithreaded/ Debug/ +DebugDll/ DebugDLL/ DebugDLLUNIX/ -DebugDll/ -DebugMTDLL/ +DebugMt/ DebugMTDll/ +DebugMTDLL/ DebugMTUnix/ -DebugMt/ DebugNox/ -DebugUNIX/ DebugUnix/ -Release Multithreaded DLL/ +DebugUNIX/ Release Multithreaded Dll/ -Release Multithreaded pseudoUNIX/ +Release Multithreaded DLL/ Release Multithreaded pseudoUnix/ +Release Multithreaded pseudoUNIX/ Release Multithreaded/ Release/ +ReleaseDll/ ReleaseDLL/ ReleaseDLLUNIX/ -ReleaseDll/ -ReleaseMTDLL/ +ReleaseMt/ ReleaseMTDll/ +ReleaseMTDLL/ ReleaseMTUnix/ -ReleaseMt/ ReleaseNox/ ReleaseUnix/ UDebug/ UDebugMt/ -URelease/ -UReleaseMt/ -Unicode Debug Multithreaded DLL/ Unicode Debug Multithreaded Dll/ +Unicode Debug Multithreaded DLL/ Unicode Debug Multithreaded/ Unicode Debug/ -Unicode Release Multithreaded DLL/ Unicode Release Multithreaded Dll/ +Unicode Release Multithreaded DLL/ Unicode Release Multithreaded/ Unicode Release/ - -Win32/ -x64/ +URelease/ +UReleaseMt/ # directories (by pattern) -/build/ar* -/build/bc5* -/build/como* -/build/cw* -/build/gcc3* -/build/gcc4* -/build/icl* -/build/vc5* -/build/*.zip - /include-*/ # files (by name) .DS_Store + +.classpath +.env.development.local +.env.local +.env.production.local +.env.test.local .ruby-version -.ruby-version-exclusions .svnignore .svnignore-at-root .update_deps -.env.local -.env.development.local -.env.test.local -.env.production.local - -list_changes.cmd -logging-bailout.txt -prepare.cmd -sh.exe.stackdump -tidydir.cmd -update_deps.cmd +build/CMakeCache.txt build/makefile.tmpl build/makefile.tools.xml build/shwild.tmplgen -build/CMakeCache.txt - -include/fastformat/internal/dos2unix.has.been.performed include/pantheios/internal/dos2unix.has.been.performed include/stlsoft/dos2unix.has.been.performed include/stlsoft/internal/dos2unix.has.been.performed - +list_changes.cmd +logging-bailout.txt +prepare.cmd +sh.exe.stackdump test/testpatterns.orj test/tests-plan.txt +tidydir.cmd +update_deps.cmd # files (by pattern) @@ -165,8 +129,6 @@ test/tests-plan.txt *~ *.*~ *.???_obj -*.VC.db -*.VC.opendb *.a *.app *.aps @@ -174,6 +136,7 @@ test/tests-plan.txt *.bak *.chm *.class +*.csproj*user *.d *.diff *.dll @@ -210,32 +173,42 @@ test/tests-plan.txt *.slo *.so *.suo -*.swp *.sw[ponm] +*.swp *.test *.tlog *.tmp *.unsuccessfulbuild -*.vcproj*.user -*.vcxproj*.user -*.csproj*user -*.vcproj*user -*.vcxproj*user +*.VC.db +*.VC.opendb *.xcuserstate *.zip -*.vc71.sln -*.vc8.sln -*.vc9.sln +/build/*.tmpl +/build/*.tmplgen +/build/*.tools.xml +/build/*.zip +/build/ar* +/build/bc5* +/build/como* +/build/cw* +/build/gcc3* +/build/gcc4* +/build/icl* +/build/vc5* RCa0* RCb0* -build/*.tmplgen -build/*.tmpl -build/*.tools.xml - npm-debug.log* yarn-debug.log* yarn-error.log* +*.vc71.sln +*.vc8.sln +*.vc9.sln +*.vcproj*.user +*.vcproj*user +*.vcxproj*.user +*.vcxproj*user + From e14093004c36919e412ed31dc3500e3ad1fae0da Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 17 Sep 2026 10:17:30 +1000 Subject: [PATCH 09/88] .vscode/settings.json --- .vscode/settings.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6a0da10..6aa78f6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,7 @@ "editor.defaultFormatter": "ms-vscode.cpptools", "editor.formatOnSave": true, "editor.insertSpaces": true, - "editor.rulers": [ 60, 64, 68, 72, 76 ], + "editor.rulers": [ 60, 64, 68, 72, 76, 120 ], "editor.tabSize": 4, }, "[cmake]": { @@ -19,7 +19,7 @@ "editor.defaultFormatter": "ms-vscode.cpptools", "editor.formatOnSave": true, "editor.insertSpaces": true, - "editor.rulers": [ 60, 64, 68, 72, 76 ], + "editor.rulers": [ 60, 64, 68, 72, 76, 120 ], "editor.tabSize": 4, }, "[json]": { @@ -38,6 +38,7 @@ }, "[ruby]": { "editor.insertSpaces": true, + "editor.rulers": [ 60, 76 ], "editor.tabSize": 2, }, "[shellscript]": { From 39738e25c4bd96aa46e68acc21a998b1a2279717 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 17 Sep 2026 10:18:51 +1000 Subject: [PATCH 10/88] .vimrc --- .vimrc | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.vimrc b/.vimrc index 1bf33a0..cebe6af 100644 --- a/.vimrc +++ b/.vimrc @@ -42,29 +42,29 @@ autocmd BufWritePre * %s/\s\+$//e augroup sis_c_cxx autocmd! + " [bat] + autocmd FileType bat,dosbatch setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4 colorcolumn=60,76 " [c] / [cpp] autocmd FileType c,cpp setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4 colorcolumn=60,64,68,72,76 - " [rust] - autocmd FileType rs setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4 colorcolumn=76 - " [cmake] autocmd FileType cmake setlocal noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 - " [shellscript] - autocmd FileType sh,bash,zsh setlocal expandtab tabstop=2 shiftwidth=2 softtabstop=2 colorcolumn=60,76 - - " [bat] - autocmd FileType bat,dosbatch setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4 colorcolumn=60,76 - - " [json] / [markdown] / [yaml] / [ruby] - autocmd FileType json,markdown,yaml,ruby setlocal expandtab tabstop=2 shiftwidth=2 softtabstop=2 + " [json] / [markdown] / [ruby] / [yaml] + autocmd FileType json,markdown,ruby,yaml setlocal expandtab tabstop=2 shiftwidth=2 softtabstop=2 " [python] autocmd FileType python setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4 colorcolumn=60,76 + " [rust] + autocmd FileType rs setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4 colorcolumn=76 + + " [shellscript] + autocmd FileType bash,sh,zsh setlocal expandtab tabstop=2 shiftwidth=2 softtabstop=2 colorcolumn=60,76 + " [toml] autocmd FileType toml setlocal noexpandtab tabstop=2 shiftwidth=2 softtabstop=2 + augroup END From c51736e77a49035779a66d13ca0f30199c0e0736 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 17 Sep 2026 10:21:59 +1000 Subject: [PATCH 11/88] CHANGES.md --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index a0f247a..6a9297a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,12 @@ # cstring - Changes +## Unreleased + +* Applied **misc-dev-scripts** **0.6.0** editor/Git/`.sis` drop-in templates on **boilerplate**; +* Restored historical **.gitignore** patterns as a sorted union with **misc-dev-scripts** gold section layout; + + ## 4.0.14 - 9th August 2026 4.0.14 From 93fbbfa7d79ff41324abbd56603db6b74d6f0362 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 17 Sep 2026 11:14:08 +1000 Subject: [PATCH 12/88] chore(): use computed PREFIX_VER with documented PATCH Align library version macros to c-cpp-standards. Define MAJOR, MINOR, PATCH, and ALPHABETA; compute PREFIX_VER by shifts; keep REVISION as a skip-section alias of PATCH (target at column 60); retain named historical VER_* constants only for comparison and documentation. --- include/cstring/cstring.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index 8812c68..24e2fa6 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: 9th August 2026 + * Updated: 17th September 2026 * * Home: http://synesis.com.au/software/ * @@ -145,7 +145,9 @@ | ( CSTRING_VER_ALPHABETA << 0 ) \ ) -#define CSTRING_VER_REVISION CSTRING_VER_PATCH +#ifndef CSTRING_DOCUMENTATION_SKIP_SECTION +# define CSTRING_VER_REVISION CSTRING_VER_PATCH +#endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /* ///////////////////////////////////////////////////////////////////////// From 277e74fdc050dd5f0aa88b3d47f03e9936e985a5 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 17 Sep 2026 11:59:56 +1000 Subject: [PATCH 13/88] test(): ship scratch libver version reporter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add or normalise test/scratch/libver to print the hosting PREFIX_VER as “Name: vM.m.p.ab (PREFIX_VER = 0x........)” and, when sibling public headers are included, an “efferent dependencies:” block. --- test/scratch/CMakeLists.txt | 5 +- test/scratch/libver/CMakeLists.txt | 3 +- test/scratch/libver/entry.cpp | 61 ------------------------ test/scratch/libver/main.c | 76 ++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 67 deletions(-) delete mode 100644 test/scratch/libver/entry.cpp create mode 100644 test/scratch/libver/main.c diff --git a/test/scratch/CMakeLists.txt b/test/scratch/CMakeLists.txt index ae4e779..4374fb6 100644 --- a/test/scratch/CMakeLists.txt +++ b/test/scratch/CMakeLists.txt @@ -1,4 +1 @@ -if(NOT NO_CSTRING_CPP_API) - - add_subdirectory(libver) -endif() +add_subdirectory(libver) diff --git a/test/scratch/libver/CMakeLists.txt b/test/scratch/libver/CMakeLists.txt index 7363bfb..67ac8bc 100644 --- a/test/scratch/libver/CMakeLists.txt +++ b/test/scratch/libver/CMakeLists.txt @@ -1,2 +1 @@ -# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten -define_example_program(libver entry.cpp) +define_example_program(libver main.c) diff --git a/test/scratch/libver/entry.cpp b/test/scratch/libver/entry.cpp deleted file mode 100644 index 67a02cc..0000000 --- a/test/scratch/libver/entry.cpp +++ /dev/null @@ -1,61 +0,0 @@ - -#include - -#ifdef HAS_STLSOFT -# include -#endif - -#include -#include - -#include - - -#define PROGRAM_NAME "libver" - -template< - typename T_stream -, typename T_integer -> -void -version( - T_stream& stm -, char const* libname -, T_integer libver -) -{ - stm - << libname - << " v" - << ((libver >> 24) & 0xff) - << '.' - << ((libver >> 16) & 0xff) - << '.' - << ((libver >> 8) & 0xff) - << '.' - << ((libver >> 0) & 0xff) - << std::endl - ; -} - - -int main(int /* argc */, char* /* argv */[]) -{ - { - auto const libver = CSTRING_VER; - - version(std::cout, "\tcstring", libver); - } - -#ifdef HAS_STLSOFT - - { - auto const libver = _STLSOFT_VER; - - version(std::cout, "\tSTLSoft", libver); - } -#endif - - return EXIT_SUCCESS; -} - diff --git a/test/scratch/libver/main.c b/test/scratch/libver/main.c new file mode 100644 index 0000000..567fbdd --- /dev/null +++ b/test/scratch/libver/main.c @@ -0,0 +1,76 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/scratch/libver/main.c + * + * Purpose: Prints cstring composite version (and STLSoft when present). + * + * Created: 17th September 2026 + * Updated: 17th September 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +#include + +#ifdef HAS_STLSOFT +# include +#endif + +#include +#include + + +#define PROGRAM_NAME "libver" + + +static void +version( + FILE* stm +, char const* prefix +, char const* libname +, char const* macroname +, unsigned libver +) +{ + fprintf( + stm + , "%s%s: v%u.%u.%u.%u (%s = 0x%08x)\n" + , prefix + , libname + , (libver >> 24) & 0xff + , (libver >> 16) & 0xff + , (libver >> 8) & 0xff + , (libver >> 0) & 0xff + , macroname + , libver + ); +} + + +int main(int argc, char* argv[]) +{ + ((void)&argc); + ((void)&argv); + + { + unsigned const libver = (unsigned)CSTRING_VER; + + version(stdout, "", "cstring", "CSTRING_VER", libver); + } + +#ifdef HAS_STLSOFT + + fputs("\nefferent dependencies:\n", stdout); + + { + unsigned const libver = (unsigned)_STLSOFT_VER; + + version(stdout, "\t", "STLSoft", "_STLSOFT_VER", libver); + } +#endif + + return EXIT_SUCCESS; +} + + +/* ///////////////////////////// end of file //////////////////////////// */ + From 9a77dbb90e319460abaecb48219e4fc5601fe722 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 17 Sep 2026 16:24:34 +1000 Subject: [PATCH 14/88] test(): rename scratch libver to versions Move test/scratch/libver to test/scratch/versions and retarget the executable as versions so C/C++ reporters match the Phase 3 freeze. --- test/scratch/CMakeLists.txt | 2 +- test/scratch/libver/CMakeLists.txt | 1 - test/scratch/versions/CMakeLists.txt | 1 + test/scratch/{libver => versions}/main.c | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 test/scratch/libver/CMakeLists.txt create mode 100644 test/scratch/versions/CMakeLists.txt rename test/scratch/{libver => versions}/main.c (92%) diff --git a/test/scratch/CMakeLists.txt b/test/scratch/CMakeLists.txt index 4374fb6..b18ccd2 100644 --- a/test/scratch/CMakeLists.txt +++ b/test/scratch/CMakeLists.txt @@ -1 +1 @@ -add_subdirectory(libver) +add_subdirectory(versions) diff --git a/test/scratch/libver/CMakeLists.txt b/test/scratch/libver/CMakeLists.txt deleted file mode 100644 index 67ac8bc..0000000 --- a/test/scratch/libver/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -define_example_program(libver main.c) diff --git a/test/scratch/versions/CMakeLists.txt b/test/scratch/versions/CMakeLists.txt new file mode 100644 index 0000000..8db359b --- /dev/null +++ b/test/scratch/versions/CMakeLists.txt @@ -0,0 +1 @@ +define_example_program(versions main.c) diff --git a/test/scratch/libver/main.c b/test/scratch/versions/main.c similarity index 92% rename from test/scratch/libver/main.c rename to test/scratch/versions/main.c index 567fbdd..fa6cd1c 100644 --- a/test/scratch/libver/main.c +++ b/test/scratch/versions/main.c @@ -1,5 +1,5 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: test/scratch/libver/main.c + * File: test/scratch/versions/main.c * * Purpose: Prints cstring composite version (and STLSoft when present). * @@ -19,7 +19,7 @@ #include -#define PROGRAM_NAME "libver" +#define PROGRAM_NAME "versions" static void From bce400d66a951da2dc099c700c3f19a30c2c1fea Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Fri, 18 Sep 2026 07:30:48 +1000 Subject: [PATCH 15/88] test: add canonical scratch versions reporter Add a Phase 3 gold version printer under test/scratch/versions, wire it from the scratch parent list, and use CMake target/argv0 versions; --- cmake/BuildType.cmake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmake/BuildType.cmake b/cmake/BuildType.cmake index 955d576..6b12d14 100644 --- a/cmake/BuildType.cmake +++ b/cmake/BuildType.cmake @@ -25,8 +25,10 @@ include(BuildType) if(EXISTS "${CMAKE_SOURCE_DIR}/.git") + set(default_build_type "Debug") else() + set(default_build_type "Release") endif() @@ -44,5 +46,6 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) ) endif() + # ############################## end of file ############################# # From a7fc8368a615a542581803cc8b69656b56aae8ae Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 22 Sep 2026 05:09:54 +1000 Subject: [PATCH 16/88] .gitattributes --- .gitattributes | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.gitattributes b/.gitattributes index 76a2f86..1ad0590 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,6 +3,11 @@ # Sources: GitHub Docs (line endings), git-scm gitattributes (diff=cpp), # gitattributes/gitattributes C++.gitattributes + Common.gitattributes, # github-linguist overrides. +# +# Linguist language-bar policy (Synesis): classify public C API headers as C +# and *.cmake as CMake scripts; keep helper scripts, IDE project files, and +# CMakeLists.txt config out of the advertised language mix. Prefer +# -linguist-detectable over linguist-detectable=false. # Default: detect text, normalize to LF in the repository * text=auto @@ -97,3 +102,25 @@ TODO text # --- GitHub Linguist --- **/build/** linguist-generated **/cmake-build-*/** linguist-generated + +# Classification (C-primary / C API with C++ tests; *.cmake as CMake scripts) +*.c linguist-language=C +*.cmake linguist-language=CMake +*.cpp linguist-language=C++ +*.cxx linguist-language=C++ +*.h linguist-language=C +*.hpp linguist-language=C++ +*.hxx linguist-language=C++ + +# Keep the bar on library languages (helpers / legacy IDE / docs / +# CMakeLists.txt config; leave *.cmake detectable as CMake) +*.cmd -linguist-detectable +*.dsp -linguist-detectable +*.dsw -linguist-detectable +*.html -linguist-detectable +*.sh -linguist-detectable +*.sln -linguist-detectable +*.vcxproj -linguist-detectable +*.vimrc -linguist-detectable +CMakeLists.txt -linguist-detectable +makefile -linguist-detectable From 3948256498be6a497686d250c19f9fb0a24cc9ae Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 22 Sep 2026 09:02:34 +1000 Subject: [PATCH 17/88] fix(c-cpp): quote Dir resolution in CMake helper scripts Use Dir=$(cd "$(dirname "$ScriptPath")" && pwd) so paths with spaces resolve reliably; apply the same one-line fix across in-wave helper scripts (prepare/build/clean/ctest/remove and run_all_*). --- build_cmake.sh | 2 +- clean_cmake.sh | 2 +- ctest_cmake.sh | 2 +- prepare_cmake.sh | 2 +- remove_cmake_artefacts.sh | 2 +- run_all_examples.sh | 2 +- run_all_scratch_tests.sh | 2 +- run_all_unit_tests.sh | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build_cmake.sh b/build_cmake.sh index f575833..2177c38 100755 --- a/build_cmake.sh +++ b/build_cmake.sh @@ -1,7 +1,7 @@ #! /bin/bash ScriptPath=$0 -Dir=$(cd $(dirname "$ScriptPath"); pwd) +Dir=$(cd "$(dirname "$ScriptPath")" && pwd) Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} [[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make diff --git a/clean_cmake.sh b/clean_cmake.sh index ead9c20..f8655c6 100755 --- a/clean_cmake.sh +++ b/clean_cmake.sh @@ -1,7 +1,7 @@ #! /bin/bash ScriptPath=$0 -Dir=$(cd $(dirname "$ScriptPath"); pwd) +Dir=$(cd "$(dirname "$ScriptPath")" && pwd) Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} [[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make diff --git a/ctest_cmake.sh b/ctest_cmake.sh index 662facd..6f87443 100755 --- a/ctest_cmake.sh +++ b/ctest_cmake.sh @@ -1,7 +1,7 @@ #! /bin/bash ScriptPath=$0 -Dir=$(cd $(dirname "$ScriptPath"); pwd) +Dir=$(cd "$(dirname "$ScriptPath")" && pwd) Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} [[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make diff --git a/prepare_cmake.sh b/prepare_cmake.sh index 7058c4e..9b8466f 100755 --- a/prepare_cmake.sh +++ b/prepare_cmake.sh @@ -1,7 +1,7 @@ #! /bin/bash ScriptPath=$0 -Dir=$(cd $(dirname "$ScriptPath"); pwd) +Dir=$(cd "$(dirname "$ScriptPath")" && pwd) Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} if [[ -n "$MSYSTEM" ]]; then diff --git a/remove_cmake_artefacts.sh b/remove_cmake_artefacts.sh index e09e895..796aef3 100755 --- a/remove_cmake_artefacts.sh +++ b/remove_cmake_artefacts.sh @@ -1,7 +1,7 @@ #! /bin/bash ScriptPath=$0 -Dir=$(cd $(dirname "$ScriptPath"); pwd) +Dir=$(cd "$(dirname "$ScriptPath")" && pwd) Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} ProjectNameFile="$Dir/.sis/project_name.txt" diff --git a/run_all_examples.sh b/run_all_examples.sh index 959144d..575929e 100755 --- a/run_all_examples.sh +++ b/run_all_examples.sh @@ -1,7 +1,7 @@ #! /bin/bash ScriptPath=$0 -Dir=$(cd $(dirname "$ScriptPath"); pwd) +Dir=$(cd "$(dirname "$ScriptPath")" && pwd) Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} [[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make diff --git a/run_all_scratch_tests.sh b/run_all_scratch_tests.sh index a22a672..37a68e9 100755 --- a/run_all_scratch_tests.sh +++ b/run_all_scratch_tests.sh @@ -1,7 +1,7 @@ #! /bin/bash ScriptPath=$0 -Dir=$(cd $(dirname "$ScriptPath"); pwd) +Dir=$(cd "$(dirname "$ScriptPath")" && pwd) Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} [[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make diff --git a/run_all_unit_tests.sh b/run_all_unit_tests.sh index e5fb239..cc48b02 100755 --- a/run_all_unit_tests.sh +++ b/run_all_unit_tests.sh @@ -1,7 +1,7 @@ #! /bin/bash ScriptPath=$0 -Dir=$(cd $(dirname "$ScriptPath"); pwd) +Dir=$(cd "$(dirname "$ScriptPath")" && pwd) Basename=$(basename "$ScriptPath") CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} [[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make From 6455ea408b81a4ab5306cdebd959be0bd1cfeff3 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 22 Sep 2026 10:34:12 +1000 Subject: [PATCH 18/88] chore(scratch test): **versions** => **test.scratch.version** --- test/scratch/versions/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scratch/versions/CMakeLists.txt b/test/scratch/versions/CMakeLists.txt index 8db359b..5b5ed67 100644 --- a/test/scratch/versions/CMakeLists.txt +++ b/test/scratch/versions/CMakeLists.txt @@ -1 +1 @@ -define_example_program(versions main.c) +define_example_program(test.scratch.versions main.c) From 1c9819a22e6cfabfa633c2d7bd8efda6688f2677 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 22 Sep 2026 10:58:14 +1000 Subject: [PATCH 19/88] doc(README.md): adding afferent dependencies (**errni**, **rstrip**) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b0472e0..91c57bd 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,8 @@ When supplying `'--no-shwild'` — sets `NO_SHWILD=ON` — **shwild** is not rec Projects in which **cstring** is used include: +* [**errni**](https://github.com/sistools/errni) +* [**rstrip**](https://github.com/sistools/rstrip) * [**shwild.fnmatch**](https://github.com/synesissoftware/shwild.fnmatch) From 91c2b4b2c3ad486e39bbd864e8c00e051833de02 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 22 Sep 2026 16:54:35 +1000 Subject: [PATCH 20/88] feat(cstring): colour prepare_cmake and unify cmake configure Add SisClr_* status colour (NO_COLOR, AlwaysUseColours, TTY gate), --build-shared-libs, and a single cmake invoke with CMakeGeneratorArgs so every -D is always passed; tidy prologue groups, quoting, and help text. --- prepare_cmake.sh | 156 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 108 insertions(+), 48 deletions(-) diff --git a/prepare_cmake.sh b/prepare_cmake.sh index 9b8466f..b60541a 100755 --- a/prepare_cmake.sh +++ b/prepare_cmake.sh @@ -1,8 +1,7 @@ #! /bin/bash -ScriptPath=$0 -Dir=$(cd "$(dirname "$ScriptPath")" && pwd) -Basename=$(basename "$ScriptPath") +Basename=$(basename "$0") +Dir=$(cd "$(dirname "$0")" && pwd) CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} if [[ -n "$MSYSTEM" ]]; then @@ -13,28 +12,82 @@ else DefaultMakeCmd=make fi MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} - ProjectNameFile="$Dir/.sis/project_name.txt" ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +ScriptPath=$0 +AlwaysUseColours=0 +BuildSharedLibs=0 Configuration=Release ExamplesDisabled=0 -MSVC_MT=0 MinGW="${MinGW:=0}" +MSVC_MT=0 NO_cxx=0 NO_shwild=0 RunMake=0 +SisUseColours=0 STLSoftDirGiven= TestingDisabled=0 VerboseMakefile=0 +# ########################################################## +# colours +# +# Enable when NO_COLOR is unset, tput is available, and either: +# - AlwaysUseColours is set (may set TERM if empty/dumb), or +# - stdout is a TTY and TERM is not dumb (union of collect-c's +# "TERM set + TTY" and cstring's "TTY" — empty TERM on a TTY is OK). + +SisClr_Blue= +SisClr_Bold= +SisClr_Green= +SisClr_None= +SisClr_Red= +SisClr_Yellow= + +if [ -z "${NO_COLOR:-}" ] && command -v tput >/dev/null 2>&1; then + + if [ $AlwaysUseColours -ne 0 ]; then + + if [ -z "${TERM:-}" ] || [ "$TERM" = "dumb" ]; then + + TERM=xterm-256color + fi + + SisUseColours=1 + elif [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then + + SisUseColours=1 + fi +fi + +if [ $SisUseColours -ne 0 ]; then + + SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} + SisClr_Bold=${FD_BOLD:-$(tput bold)} + SisClr_Green=${FG_GREEN:-$(tput setaf 2)} + SisClr_None=${FD_NONE:-$(tput sgr0)} + SisClr_Red=${FG_RED:-$(tput setaf 1)} + SisClr_Yellow=${FG_YELLOW:-$(tput setaf 3)} +fi + +CMakeDirClr="${SisClr_Blue}${SisClr_Bold}${CMakeDir}${SisClr_None}" +MakeCmdClr="${SisClr_Blue}${SisClr_Bold}${MakeCmd}${SisClr_None}" +ProjectNameClr="${SisClr_Blue}${SisClr_Bold}${ProjectName}${SisClr_None}" +ScriptPathClr="${SisClr_Blue}${SisClr_Bold}${ScriptPath}${SisClr_None}" + + # ########################################################## # command-line handling while [[ $# -gt 0 ]]; do case $1 in + --build-shared-libs) + + BuildSharedLibs=1 + ;; --cmake-verbose-makefile|-v) VerboseMakefile=1 @@ -82,31 +135,32 @@ while [[ $# -gt 0 ]]; do cat << EOF Creates/reinitialises the CMake build script(s) -$ScriptPath [ ... flags/options ... ] +${ScriptPath} [ ... flags/options ... ] Flags/options: behaviour: + --build-shared-libs + builds ${ProjectName} as a shared library (BUILD_SHARED_LIBS=ON) + -v --cmake-verbose-makefile - configures CMake to run verbosely (by setting CMAKE_VERBOSE_MAKEFILE - to be ON) + configures CMake to run verbosely (CMAKE_VERBOSE_MAKEFILE=ON) -d --debug-configuration - use Debug configuration (by setting CMAKE_BUILD_TYPE=Debug). Default - is to use Release + use Debug configuration (CMAKE_BUILD_TYPE=Debug). Default is Release -E --disable-examples - disables building of examples (by setting BUILD_EXAMPLES=OFF) + disables building of examples (BUILD_EXAMPLES=OFF) -T --disable-testing - disables building of tests (by setting BUILD_TESTING=OFF). Unless - testing is disabled the STLSoft and xTests libraries will be - required to be available to CMake + disables building of tests (BUILD_TESTING=OFF). Unless testing is + disabled the STLSoft and xTests libraries will be required to be + available to CMake --mingw uses explicitly the "MinGW Makefiles" generator, and defaults the @@ -123,12 +177,12 @@ Flags/options: NO_CSTRING_CPP_API). C unit-tests still require STLSoft and xTests --no-shwild - prevents recognising shwild library (CMake NO_SHWILD); xTests + prevents recognising shwild library (NO_SHWILD=ON); xTests pattern-match assertions are then unavailable -m --run-make - executes make after a successful running of CMake + executes a build via ${MakeCmd} after a successful configure -s --stlsoft-root-dir @@ -148,7 +202,7 @@ EOF ;; *) - >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + >&2 echo "${ScriptPathClr}: unrecognised argument '${SisClr_Red}${SisClr_Bold}$1${SisClr_None}'; use --help for usage" exit 1 ;; @@ -161,12 +215,14 @@ done # ########################################################## # main() -mkdir -p $CMakeDir || exit 1 +mkdir -p "$CMakeDir" || exit 1 -cd $CMakeDir +cd "$CMakeDir" -echo "Executing CMake for ${ProjectName} (in ${CMakeDir})" +echo +echo "Executing CMake for ${ProjectNameClr} (in ${CMakeDirClr})" +if [ $BuildSharedLibs -eq 0 ]; then CMakeBuildSharedLibsFlag="OFF" ; else CMakeBuildSharedLibsFlag="ON" ; fi if [ $ExamplesDisabled -eq 0 ]; then CMakeBuildExamplesFlag="ON" ; else CMakeBuildExamplesFlag="OFF" ; fi if [ $MSVC_MT -eq 0 ]; then CMakeMsvcMtFlag="OFF" ; else CMakeMsvcMtFlag="ON" ; fi if [ $NO_cxx -eq 0 ]; then CMakeNoCppApiFlag="OFF" ; else CMakeNoCppApiFlag="ON" ; fi @@ -175,40 +231,43 @@ if [ -z $STLSoftDirGiven ]; then CMakeSTLSoftVariable="" ; else CMakeSTLSoftVari if [ $TestingDisabled -eq 0 ]; then CMakeBuildTestingFlag="ON" ; else CMakeBuildTestingFlag="OFF" ; fi if [ $VerboseMakefile -eq 0 ]; then CMakeVerboseMakefileFlag="OFF" ; else CMakeVerboseMakefileFlag="ON" ; fi -if [ $MinGW -ne 0 ]; then +# NOTE: the generator is the *only* thing that may differ between the MinGW +# and the default paths; every -D option is passed in both cases, so that no +# flag can be silently ignored according to the generator selected. - cmake \ - $CMakeSTLSoftVariable \ - -DBUILD_EXAMPLES:BOOL=$CMakeBuildExamplesFlag \ - -DBUILD_TESTING:BOOL=$CMakeBuildTestingFlag \ - -DCMAKE_BUILD_TYPE=$Configuration \ - -DNO_CSTRING_CPP_API:BOOL=$CMakeNoCppApiFlag \ - -DNO_SHWILD:BOOL=$CMakeNoShwild \ - -G "MinGW Makefiles" \ - -S $Dir \ - -B $CMakeDir \ - || (cd ->/dev/null ; exit 1) -else +CMakeGeneratorArgs=() + +if [ -n "${SIS_CMAKE_GENERATOR:-}" ] && [ $MinGW -eq 0 ]; then - cmake \ - $CMakeSTLSoftVariable \ - -DBUILD_EXAMPLES:BOOL=$CMakeBuildExamplesFlag \ - -DBUILD_TESTING:BOOL=$CMakeBuildTestingFlag \ - -DCMAKE_BUILD_TYPE=$Configuration \ - -DCMAKE_VERBOSE_MAKEFILE:BOOL=$CMakeVerboseMakefileFlag \ - -DMSVC_USE_MT:BOOL=$CMakeMsvcMtFlag \ - -DNO_CSTRING_CPP_API:BOOL=$CMakeNoCppApiFlag \ - -DNO_SHWILD:BOOL=$CMakeNoShwild \ - -S $Dir \ - -B $CMakeDir \ - || (cd ->/dev/null ; exit 1) + CMakeGeneratorArgs=(-G "$SIS_CMAKE_GENERATOR") fi +if [ $MinGW -ne 0 ]; then + + CMakeGeneratorArgs=(-G "MinGW Makefiles") +fi + +cmake \ + -DBUILD_EXAMPLES:BOOL=$CMakeBuildExamplesFlag \ + -DBUILD_SHARED_LIBS:BOOL=$CMakeBuildSharedLibsFlag \ + -DBUILD_TESTING:BOOL=$CMakeBuildTestingFlag \ + -DCMAKE_BUILD_TYPE=$Configuration \ + -DCMAKE_VERBOSE_MAKEFILE:BOOL=$CMakeVerboseMakefileFlag \ + -DMSVC_USE_MT:BOOL=$CMakeMsvcMtFlag \ + -DNO_CSTRING_CPP_API:BOOL=$CMakeNoCppApiFlag \ + -DNO_SHWILD:BOOL=$CMakeNoShwild \ + $CMakeSTLSoftVariable \ + "${CMakeGeneratorArgs[@]}" \ + -B "$CMakeDir" \ + -S "$Dir" \ + || (cd ->/dev/null ; exit 1) + status=0 if [ $RunMake -ne 0 ]; then - echo "Executing build for ${ProjectName} (via command \`$MakeCmd\`)" + echo + echo "Executing build for ${ProjectNameClr} (via command \`${MakeCmdClr}\`)" $MakeCmd status=$? @@ -218,8 +277,9 @@ cd ->/dev/null if [ $VerboseMakefile -ne 0 ]; then - echo -e "contents of $CMakeDir:" - ls -al $CMakeDir + echo + echo -e "contents of ${CMakeDirClr}:" + ls -al "$CMakeDir" fi exit $status From ad2a14366d949a91629203bff05690284afe6bb4 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 22 Sep 2026 17:03:18 +1000 Subject: [PATCH 21/88] feat(cstring): force colours via env and -A in prepare_cmake Initialise AlwaysUseColours from SIS_CMAKE_ALWAYS_USE_COLOURS / SIS_ALWAYS_USE_COLOURS; let -A/--always-use-colours override NO_COLOR (pre-scan args so help and errors are coloured). --- prepare_cmake.sh | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/prepare_cmake.sh b/prepare_cmake.sh index b60541a..e113fd0 100755 --- a/prepare_cmake.sh +++ b/prepare_cmake.sh @@ -16,7 +16,7 @@ ProjectNameFile="$Dir/.sis/project_name.txt" ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") ScriptPath=$0 -AlwaysUseColours=0 +AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} BuildSharedLibs=0 Configuration=Release ExamplesDisabled=0 @@ -34,10 +34,12 @@ VerboseMakefile=0 # ########################################################## # colours # -# Enable when NO_COLOR is unset, tput is available, and either: -# - AlwaysUseColours is set (may set TERM if empty/dumb), or -# - stdout is a TTY and TERM is not dumb (union of collect-c's -# "TERM set + TTY" and cstring's "TTY" — empty TERM on a TTY is OK). +# Enable when tput is available and either: +# - AlwaysUseColours is set (overrides NO_COLOR; may set TERM if +# empty/dumb), or +# - NO_COLOR is unset, stdout is a TTY, and TERM is not dumb (union of +# collect-c's "TERM set + TTY" and cstring's "TTY" — empty TERM on a +# TTY is OK). SisClr_Blue= SisClr_Bold= @@ -46,7 +48,17 @@ SisClr_None= SisClr_Red= SisClr_Yellow= -if [ -z "${NO_COLOR:-}" ] && command -v tput >/dev/null 2>&1; then +for arg in "$@"; do + + case $arg in + --always-use-colors|--always-use-colours|-A) + + AlwaysUseColours=1 + ;; + esac +done + +if command -v tput >/dev/null 2>&1; then if [ $AlwaysUseColours -ne 0 ]; then @@ -56,7 +68,7 @@ if [ -z "${NO_COLOR:-}" ] && command -v tput >/dev/null 2>&1; then fi SisUseColours=1 - elif [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then + elif [ -z "${NO_COLOR:-}" ] && [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then SisUseColours=1 fi @@ -84,6 +96,10 @@ ScriptPathClr="${SisClr_Blue}${SisClr_Bold}${ScriptPath}${SisClr_None}" while [[ $# -gt 0 ]]; do case $1 in + --always-use-colors|--always-use-colours|-A) + + # AlwaysUseColours=1 - this is handled by the for loop above + ;; --build-shared-libs) BuildSharedLibs=1 @@ -141,6 +157,11 @@ Flags/options: behaviour: + -A + --always-use-colors + --always-use-colours + forces use of colours even when stdout is not a TTY + --build-shared-libs builds ${ProjectName} as a shared library (BUILD_SHARED_LIBS=ON) From b121a6fa77aa861da414a23ee5cd794f3d7bffef Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 22 Sep 2026 17:56:34 +1000 Subject: [PATCH 22/88] refactor(cstring): drive prepare builds with cmake --build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop MSYSTEM→MinGW and MakeCmd; honour SIS_CMAKE_MINGW via `sis_cmake_is_truey()`; add sis_cmake_build for -m; configure in-place with -S/-B and || exit 1; tighten --mingw/--msvc-mt/--run-make help. --- prepare_cmake.sh | 78 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/prepare_cmake.sh b/prepare_cmake.sh index e113fd0..d98b919 100755 --- a/prepare_cmake.sh +++ b/prepare_cmake.sh @@ -1,17 +1,29 @@ #! /bin/bash +# ########################################################## +# functions - 1 + +sis_cmake_is_truey() { + case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in + + 1|ok|on|true|yes|y) + + return 0 + ;; + *) + + return 1 + ;; + esac +} + + +# ########################################################## +# constants and variables + Basename=$(basename "$0") Dir=$(cd "$(dirname "$0")" && pwd) CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} -if [[ -n "$MSYSTEM" ]]; then - - DefaultMakeCmd=mingw32-make.exe - MinGW=1 -else - - DefaultMakeCmd=make -fi -MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} ProjectNameFile="$Dir/.sis/project_name.txt" ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") ScriptPath=$0 @@ -20,7 +32,7 @@ AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} BuildSharedLibs=0 Configuration=Release ExamplesDisabled=0 -MinGW="${MinGW:=0}" +MinGW=$(sis_cmake_is_truey "${SIS_CMAKE_MINGW:-}" && echo 1 || echo 0) MSVC_MT=0 NO_cxx=0 NO_shwild=0 @@ -85,11 +97,34 @@ if [ $SisUseColours -ne 0 ]; then fi CMakeDirClr="${SisClr_Blue}${SisClr_Bold}${CMakeDir}${SisClr_None}" -MakeCmdClr="${SisClr_Blue}${SisClr_Bold}${MakeCmd}${SisClr_None}" ProjectNameClr="${SisClr_Blue}${SisClr_Bold}${ProjectName}${SisClr_None}" ScriptPathClr="${SisClr_Blue}${SisClr_Bold}${ScriptPath}${SisClr_None}" +# ########################################################## +# functions - 2 + +sis_cmake_build() { + + local config="${SIS_CMAKE_CONFIG:-Release}" + local args=(--build "$CMakeDir") + if [ -f "$CMakeDir/CMakeCache.txt" ] && grep -q '^CMAKE_CONFIGURATION_TYPES:' "$CMakeDir/CMakeCache.txt" 2>/dev/null; then + + args+=(--config "$config") + fi + if [ "$#" -gt 0 ]; then + + local t + for t in "$@"; do + + args+=(--target "$t") + done + fi + + cmake "${args[@]}" +} + + # ########################################################## # command-line handling @@ -184,12 +219,10 @@ Flags/options: available to CMake --mingw - uses explicitly the "MinGW Makefiles" generator, and defaults the - make-command to "mingw32-make.exe" + uses explicitly the "MinGW Makefiles" generator --msvc-mt - when using Visual C++ (MSVC), the static runtime library will be - selected; the default is the dynamic runtime library + when using Visual C++ (MSVC), select the static runtime library -C --no-cpp @@ -203,7 +236,7 @@ Flags/options: -m --run-make - executes a build via ${MakeCmd} after a successful configure + executes a build via cmake --build after a successful configure -s --stlsoft-root-dir @@ -238,8 +271,6 @@ done mkdir -p "$CMakeDir" || exit 1 -cd "$CMakeDir" - echo echo "Executing CMake for ${ProjectNameClr} (in ${CMakeDirClr})" @@ -248,7 +279,7 @@ if [ $ExamplesDisabled -eq 0 ]; then CMakeBuildExamplesFlag="ON" ; else CMakeBui if [ $MSVC_MT -eq 0 ]; then CMakeMsvcMtFlag="OFF" ; else CMakeMsvcMtFlag="ON" ; fi if [ $NO_cxx -eq 0 ]; then CMakeNoCppApiFlag="OFF" ; else CMakeNoCppApiFlag="ON" ; fi if [ $NO_shwild -eq 0 ]; then CMakeNoShwild="OFF" ; else CMakeNoShwild="ON" ; fi -if [ -z $STLSoftDirGiven ]; then CMakeSTLSoftVariable="" ; else CMakeSTLSoftVariable="-DSTLSOFT=$STLSoftDirGiven/" ; fi +if [ -z "$STLSoftDirGiven" ]; then CMakeSTLSoftVariable="" ; else CMakeSTLSoftVariable="-DSTLSOFT=$STLSoftDirGiven/" ; fi if [ $TestingDisabled -eq 0 ]; then CMakeBuildTestingFlag="ON" ; else CMakeBuildTestingFlag="OFF" ; fi if [ $VerboseMakefile -eq 0 ]; then CMakeVerboseMakefileFlag="OFF" ; else CMakeVerboseMakefileFlag="ON" ; fi @@ -281,21 +312,19 @@ cmake \ "${CMakeGeneratorArgs[@]}" \ -B "$CMakeDir" \ -S "$Dir" \ - || (cd ->/dev/null ; exit 1) + || exit 1 status=0 if [ $RunMake -ne 0 ]; then echo - echo "Executing build for ${ProjectNameClr} (via command \`${MakeCmdClr}\`)" + echo "Executing build of ${ProjectNameClr} (via cmake --build)" - $MakeCmd + sis_cmake_build status=$? fi -cd ->/dev/null - if [ $VerboseMakefile -ne 0 ]; then echo @@ -307,4 +336,3 @@ exit $status # ############################## end of file ############################# # - From 12078a7f0f76101751ad61931bff562164f23cf6 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 07:27:33 +1000 Subject: [PATCH 23/88] refactor(cstring): drive build_cmake via cmake --build Replace MakeCmd/Makefile gating with sis_cmake_build; require a configured CMakeCache; add SisClr_* colour and -A to match prepare_cmake.sh. --- build_cmake.sh | 188 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 140 insertions(+), 48 deletions(-) diff --git a/build_cmake.sh b/build_cmake.sh index 2177c38..a675a9d 100755 --- a/build_cmake.sh +++ b/build_cmake.sh @@ -1,22 +1,119 @@ #! /bin/bash -ScriptPath=$0 -Dir=$(cd "$(dirname "$ScriptPath")" && pwd) -Basename=$(basename "$ScriptPath") +# ########################################################## +# functions - 1 + +sis_cmake_is_truey() { + case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in + + 1|ok|on|true|yes|y) + + return 0 + ;; + *) + + return 1 + ;; + esac +} + + +# ########################################################## +# constants and variables + +Basename=$(basename "$0") +Dir=$(cd "$(dirname "$0")" && pwd) CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} -[[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make -MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} ProjectNameFile="$Dir/.sis/project_name.txt" ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +ScriptPath=$0 +AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} IgnoreRemainingFlagsAndOptions=0 +SisUseColours=0 Targets=() # ########################################################## -# functions +# colours +# +# Enable when tput is available and either: +# - AlwaysUseColours is set (overrides NO_COLOR; may set TERM if +# empty/dumb), or +# - NO_COLOR is unset, stdout is a TTY, and TERM is not dumb (union of +# collect-c's "TERM set + TTY" and cstring's "TTY" — empty TERM on a +# TTY is OK). + +SisClr_Blue= +SisClr_Bold= +SisClr_Green= +SisClr_None= +SisClr_Red= +SisClr_Yellow= + +for arg in "$@"; do + + case $arg in + --always-use-colors|--always-use-colours|-A) + + AlwaysUseColours=1 + ;; + esac +done + +if command -v tput >/dev/null 2>&1; then + + if [ $AlwaysUseColours -ne 0 ]; then + + if [ -z "${TERM:-}" ] || [ "$TERM" = "dumb" ]; then + + TERM=xterm-256color + fi + + SisUseColours=1 + elif [ -z "${NO_COLOR:-}" ] && [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then + + SisUseColours=1 + fi +fi + +if [ $SisUseColours -ne 0 ]; then + + SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} + SisClr_Bold=${FD_BOLD:-$(tput bold)} + SisClr_Green=${FG_GREEN:-$(tput setaf 2)} + SisClr_None=${FD_NONE:-$(tput sgr0)} + SisClr_Red=${FG_RED:-$(tput setaf 1)} + SisClr_Yellow=${FG_YELLOW:-$(tput setaf 3)} +fi + +CMakeDirClr="${SisClr_Blue}${SisClr_Bold}${CMakeDir}${SisClr_None}" +ProjectNameClr="${SisClr_Blue}${SisClr_Bold}${ProjectName}${SisClr_None}" +ScriptPathClr="${SisClr_Blue}${SisClr_Bold}${ScriptPath}${SisClr_None}" + + +# ########################################################## +# functions - 2 + +sis_cmake_build() { -function join_by { local IFS="$1"; shift; echo "$*"; } + local config="${SIS_CMAKE_CONFIG:-Release}" + local args=(--build "$CMakeDir") + if [ -f "$CMakeDir/CMakeCache.txt" ] && grep -q '^CMAKE_CONFIGURATION_TYPES:' "$CMakeDir/CMakeCache.txt" 2>/dev/null; then + + args+=(--config "$config") + fi + if [ "$#" -gt 0 ]; then + + local t + for t in "$@"; do + + args+=(--target "$t") + done + fi + + cmake "${args[@]}" +} # ########################################################## @@ -26,21 +123,16 @@ while [[ $# -gt 0 ]]; do if [ $IgnoreRemainingFlagsAndOptions -ne 0 ]; then - Targets+=($1) - + Targets+=("$1") shift - continue - else - - if [ ! ${1:0:1} = '-' ]; then - - Targets+=($1) + fi - shift + if [ "${1:0:1}" != '-' ]; then - continue - fi + Targets+=("$1") + shift + continue fi case $1 in @@ -48,18 +140,33 @@ while [[ $# -gt 0 ]]; do IgnoreRemainingFlagsAndOptions=1 ;; + --always-use-colors|--always-use-colours|-A) + + # AlwaysUseColours=1 - this is handled by the for loop above + ;; --help) [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" cat << EOF Executes CMake-generated artefacts to (re)build project -$ScriptPath [ ... flags/options ... ] +${ScriptPath} [ ... flags/options ... ] Flags/options: behaviour: + (no arguments) + builds all default targets + + ... + builds one or more specific targets + + -A + --always-use-colors + --always-use-colours + forces use of colours even when stdout is not a TTY + standard flags: @@ -72,7 +179,7 @@ EOF ;; *) - >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + >&2 echo "${ScriptPathClr}: unrecognised argument '${SisClr_Red}${SisClr_Bold}$1${SisClr_None}'; use --help for usage" exit 1 ;; @@ -85,41 +192,26 @@ done # ########################################################## # main() -if [ ! -d "$CMakeDir" ]; then +if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ]; then - >&2 echo "$ScriptPath: CMake build directory '$CMakeDir' not found so nothing to do; use script 'prepare_cmake.sh' if you wish to prepare CMake artefacts" + >&2 echo "${ScriptPathClr}: CMake build directory '${CMakeDirClr}' not found or not configured; use script 'prepare_cmake.sh' first" exit 1 -else - - cd $CMakeDir - - if [ ! -f "$CMakeDir/Makefile" ]; then - - >&2 echo "$ScriptPath: CMake build directory '$CMakeDir' does not contain expected file 'Makefile', so a clean cannot be performed. It is recommended that you remove all CMake artefacts using script 'remove_cmake_artefacts.sh' followed by regeneration via 'prepare_cmake.sh'" - - cd ->/dev/null - - exit 1 - else - - if [ -z "$Targets" ]; then - - echo "Executing build for ${ProjectName} (via command \`$MakeCmd\`)" - else - - echo "Executing build for ${ProjectName} (via command \`$MakeCmd\`) with specific target(s) $(join_by , "${Targets[@]}")" - fi +fi - $MakeCmd ${Targets[*]} - status=$? +if [ ${#Targets[@]} -eq 0 ]; then - cd ->/dev/null + echo "Executing build of ${ProjectNameClr} (via cmake --build)" + sis_cmake_build + status=$? +else - exit $status - fi + echo "Executing build of ${ProjectNameClr} (via cmake --build) with specific target(s): ${Targets[*]}" + sis_cmake_build "${Targets[@]}" + status=$? fi +exit $status -# ############################## end of file ############################# # +# ############################## end of file ############################# # From e524810f9bf6a223912160fd2bcc7353cf9dd2f9 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 07:29:59 +1000 Subject: [PATCH 24/88] refactor(cstring): drive clean_cmake via cmake --build --target clean Replace MakeCmd/Makefile gating with sis_cmake_build clean; require a configured CMakeCache; add SisClr_* colour and -A to match build_cmake.sh. --- clean_cmake.sh | 160 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 127 insertions(+), 33 deletions(-) diff --git a/clean_cmake.sh b/clean_cmake.sh index f8655c6..733afbd 100755 --- a/clean_cmake.sh +++ b/clean_cmake.sh @@ -1,13 +1,117 @@ #! /bin/bash -ScriptPath=$0 -Dir=$(cd "$(dirname "$ScriptPath")" && pwd) -Basename=$(basename "$ScriptPath") +# ########################################################## +# functions - 1 + +sis_cmake_is_truey() { + case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in + + 1|ok|on|true|yes|y) + + return 0 + ;; + *) + + return 1 + ;; + esac +} + + +# ########################################################## +# constants and variables + +Basename=$(basename "$0") +Dir=$(cd "$(dirname "$0")" && pwd) CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} -[[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make -MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} ProjectNameFile="$Dir/.sis/project_name.txt" ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +ScriptPath=$0 + +AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} +SisUseColours=0 + + +# ########################################################## +# colours +# +# Enable when tput is available and either: +# - AlwaysUseColours is set (overrides NO_COLOR; may set TERM if +# empty/dumb), or +# - NO_COLOR is unset, stdout is a TTY, and TERM is not dumb (union of +# collect-c's "TERM set + TTY" and cstring's "TTY" — empty TERM on a +# TTY is OK). + +SisClr_Blue= +SisClr_Bold= +SisClr_Green= +SisClr_None= +SisClr_Red= +SisClr_Yellow= + +for arg in "$@"; do + + case $arg in + --always-use-colors|--always-use-colours|-A) + + AlwaysUseColours=1 + ;; + esac +done + +if command -v tput >/dev/null 2>&1; then + + if [ $AlwaysUseColours -ne 0 ]; then + + if [ -z "${TERM:-}" ] || [ "$TERM" = "dumb" ]; then + + TERM=xterm-256color + fi + + SisUseColours=1 + elif [ -z "${NO_COLOR:-}" ] && [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then + + SisUseColours=1 + fi +fi + +if [ $SisUseColours -ne 0 ]; then + + SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} + SisClr_Bold=${FD_BOLD:-$(tput bold)} + SisClr_Green=${FG_GREEN:-$(tput setaf 2)} + SisClr_None=${FD_NONE:-$(tput sgr0)} + SisClr_Red=${FG_RED:-$(tput setaf 1)} + SisClr_Yellow=${FG_YELLOW:-$(tput setaf 3)} +fi + +CMakeDirClr="${SisClr_Blue}${SisClr_Bold}${CMakeDir}${SisClr_None}" +ProjectNameClr="${SisClr_Blue}${SisClr_Bold}${ProjectName}${SisClr_None}" +ScriptPathClr="${SisClr_Blue}${SisClr_Bold}${ScriptPath}${SisClr_None}" + + +# ########################################################## +# functions - 2 + +sis_cmake_build() { + + local config="${SIS_CMAKE_CONFIG:-Release}" + local args=(--build "$CMakeDir") + if [ -f "$CMakeDir/CMakeCache.txt" ] && grep -q '^CMAKE_CONFIGURATION_TYPES:' "$CMakeDir/CMakeCache.txt" 2>/dev/null; then + + args+=(--config "$config") + fi + if [ "$#" -gt 0 ]; then + + local t + for t in "$@"; do + + args+=(--target "$t") + done + fi + + cmake "${args[@]}" +} # ########################################################## @@ -16,18 +120,27 @@ ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") while [[ $# -gt 0 ]]; do case $1 in + --always-use-colors|--always-use-colours|-A) + + # AlwaysUseColours=1 - this is handled by the for loop above + ;; --help) [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" cat << EOF -Executes CMake-generated artefacts to clean project +Cleans CMake-generated build artefacts via cmake --build --target clean -$ScriptPath [ ... flags/options ... ] +${ScriptPath} [ ... flags/options ... ] Flags/options: behaviour: + -A + --always-use-colors + --always-use-colours + forces use of colours even when stdout is not a TTY + standard flags: @@ -40,7 +153,7 @@ EOF ;; *) - >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + >&2 echo "${ScriptPathClr}: unrecognised argument '${SisClr_Red}${SisClr_Bold}$1${SisClr_None}'; use --help for usage" exit 1 ;; @@ -53,35 +166,16 @@ done # ########################################################## # main() -if [ ! -d "$CMakeDir" ]; then +if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ]; then - >&2 echo "$ScriptPath: CMake build directory '$CMakeDir' not found so nothing to do; use script 'prepare_cmake.sh' if you wish to prepare CMake artefacts" + >&2 echo "${ScriptPathClr}: CMake build directory '${CMakeDirClr}' not found or not configured; nothing to clean" exit 1 -else - - cd $CMakeDir - - if [ ! -f "$CMakeDir/Makefile" ]; then - - >&2 echo "$ScriptPath: CMake build directory '$CMakeDir' does not contain expected file 'Makefile', so a clean cannot be performed. It is recommended that you remove all CMake artefacts using script 'remove_cmake_artefacts.sh' followed by regeneration via 'prepare_cmake.sh'" - - cd ->/dev/null - - exit 1 - else - - echo "Cleaning ${ProjectName} build (via command \`$MakeCmd clean\`)" - - $MakeCmd clean - status=$? - - cd ->/dev/null - - exit $status - fi fi +echo "Executing clean of ${ProjectNameClr} (via cmake --build --target clean)" +sis_cmake_build clean +exit $? -# ############################## end of file ############################# # +# ############################## end of file ############################# # From a078db882ee315bb8f67b51efe80b4d228dd5c10 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 07:34:35 +1000 Subject: [PATCH 25/88] refactor(cstring): align remove_cmake_artefacts with helper dialect Add SisClr_* colour and -A; quote inventory loops; recognise Windows_NT; remove artefacts without cd into the build directory. --- remove_cmake_artefacts.sh | 144 +++++++++++++++++++++++++++----------- 1 file changed, 102 insertions(+), 42 deletions(-) diff --git a/remove_cmake_artefacts.sh b/remove_cmake_artefacts.sh index 796aef3..377d3ab 100755 --- a/remove_cmake_artefacts.sh +++ b/remove_cmake_artefacts.sh @@ -1,12 +1,16 @@ #! /bin/bash -ScriptPath=$0 -Dir=$(cd "$(dirname "$ScriptPath")" && pwd) -Basename=$(basename "$ScriptPath") +# ########################################################## +# constants and variables + +Basename=$(basename "$0") +Dir=$(cd "$(dirname "$0")" && pwd) CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} ProjectNameFile="$Dir/.sis/project_name.txt" ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +ScriptPath=$0 +AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} Directories=( CMakeFiles Testing @@ -24,14 +28,73 @@ Files=( cmake_install.cmake install_manifest.txt ) +SisUseColours=0 + + +# ########################################################## +# colours +# +# Enable when tput is available and either: +# - AlwaysUseColours is set (overrides NO_COLOR; may set TERM if +# empty/dumb), or +# - NO_COLOR is unset, stdout is a TTY, and TERM is not dumb (union of +# collect-c's "TERM set + TTY" and cstring's "TTY" — empty TERM on a +# TTY is OK). + +SisClr_Blue= +SisClr_Bold= +SisClr_Green= +SisClr_None= +SisClr_Red= +SisClr_Yellow= + +for arg in "$@"; do + + case $arg in + --always-use-colors|--always-use-colours|-A) + + AlwaysUseColours=1 + ;; + esac +done + +if command -v tput >/dev/null 2>&1; then + + if [ $AlwaysUseColours -ne 0 ]; then + + if [ -z "${TERM:-}" ] || [ "$TERM" = "dumb" ]; then + + TERM=xterm-256color + fi + + SisUseColours=1 + elif [ -z "${NO_COLOR:-}" ] && [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then + + SisUseColours=1 + fi +fi + +if [ $SisUseColours -ne 0 ]; then + + SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} + SisClr_Bold=${FD_BOLD:-$(tput bold)} + SisClr_Green=${FG_GREEN:-$(tput setaf 2)} + SisClr_None=${FD_NONE:-$(tput sgr0)} + SisClr_Red=${FG_RED:-$(tput setaf 1)} + SisClr_Yellow=${FG_YELLOW:-$(tput setaf 3)} +fi + +CMakeDirClr="${SisClr_Blue}${SisClr_Bold}${CMakeDir}${SisClr_None}" +ProjectNameClr="${SisClr_Blue}${SisClr_Bold}${ProjectName}${SisClr_None}" +ScriptPathClr="${SisClr_Blue}${SisClr_Bold}${ScriptPath}${SisClr_None}" # ########################################################## # operating environment detection -OsName="$(uname -s)" +OsName="$(uname -s 2>/dev/null || echo Unknown)" case "${OsName}" in - CYGWIN*|MINGW*|MSYS_NT*) + CYGWIN*|MINGW*|MSYS_NT*|Windows_NT) Directories+=( ARM64 @@ -43,9 +106,6 @@ case "${OsName}" in "*.sln" "*.vcxproj" ) - ;; - *) - ;; esac @@ -56,18 +116,27 @@ esac while [[ $# -gt 0 ]]; do case $1 in + --always-use-colors|--always-use-colours|-A) + + # AlwaysUseColours=1 - this is handled by the for loop above + ;; --help) [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" cat << EOF Removes all known CMake artefacts -$ScriptPath [ ... flags/options ... ] +${ScriptPath} [ ... flags/options ... ] Flags/options: behaviour: + -A + --always-use-colors + --always-use-colours + forces use of colours even when stdout is not a TTY + standard flags: @@ -80,7 +149,7 @@ EOF ;; *) - >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + >&2 echo "${ScriptPathClr}: unrecognised argument '${SisClr_Red}${SisClr_Bold}$1${SisClr_None}'; use --help for usage" exit 1 ;; @@ -95,59 +164,50 @@ done if [ ! -d "$CMakeDir" ]; then - echo "$ScriptPath: CMake build directory '$CMakeDir' not found so nothing to do; use script 'prepare_cmake.sh' if you wish to prepare CMake artefacts" + echo "${ScriptPathClr}: CMake build directory '${CMakeDirClr}' not found so nothing to do; use script 'prepare_cmake.sh' if you wish to prepare CMake artefacts" exit 0 -else - - echo "Removing all ${ProjectName} cmake artefacts in '$CMakeDir'" +fi - num_dirs_removed=0 - num_files_removed=0 +echo "Removing all ${ProjectNameClr} cmake artefacts in '${CMakeDirClr}'" - for d in ${Directories[@]} - do +num_dirs_removed=0 +num_files_removed=0 - fq_dir_path="$CMakeDir/$d" +for d in "${Directories[@]}"; do - [ -d "$fq_dir_path" ] || continue + fq_dir_path="$CMakeDir/$d" - echo "removing directory '$d'" + [ -d "$fq_dir_path" ] || continue - rm -dfr "$fq_dir_path" + echo "removing directory '$d'" - num_dirs_removed=$((num_dirs_removed+1)) - done + rm -dfr "$fq_dir_path" - cd "$CMakeDir" + num_dirs_removed=$((num_dirs_removed+1)) +done - for f in ${Files[@]} - do +for f in "${Files[@]}"; do - for fq_file_path in $f - do + for fq_file_path in "$CMakeDir"/$f; do - [ -f "$fq_file_path" ] || continue + [ -f "$fq_file_path" ] || continue - echo "removing file '$fq_file_path'" + echo "removing file '$fq_file_path'" - rm -f "$fq_file_path" + rm -f "$fq_file_path" - num_files_removed=$((num_files_removed+1)) - done + num_files_removed=$((num_files_removed+1)) done +done - cd ->/dev/null - - if [ 0 -eq $num_dirs_removed ] && [ 0 -eq $num_files_removed ]; then +if [ 0 -eq $num_dirs_removed ] && [ 0 -eq $num_files_removed ]; then - echo "nothing to do" - else + echo "nothing to do" +else - echo "removed $num_dirs_removed directories and $num_files_removed files" - fi + echo "removed $num_dirs_removed directories and $num_files_removed files" fi # ############################## end of file ############################# # - From 4f30abaa8292139c18508b6e77e683d540facbb2 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 07:42:04 +1000 Subject: [PATCH 26/88] refactor(cstring): drive ctest_cmake via cmake --build then ctest Replace MakeCmd with sis_cmake_build; require CMakeCache; fail -M when unconfigured; add --output-on-failure, SisClr_* colour, and -A. --- ctest_cmake.sh | 160 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 139 insertions(+), 21 deletions(-) diff --git a/ctest_cmake.sh b/ctest_cmake.sh index 6f87443..4b5aaa1 100755 --- a/ctest_cmake.sh +++ b/ctest_cmake.sh @@ -1,16 +1,119 @@ #! /bin/bash -ScriptPath=$0 -Dir=$(cd "$(dirname "$ScriptPath")" && pwd) -Basename=$(basename "$ScriptPath") +# ########################################################## +# functions - 1 + +sis_cmake_is_truey() { + case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in + + 1|ok|on|true|yes|y) + + return 0 + ;; + *) + + return 1 + ;; + esac +} + + +# ########################################################## +# constants and variables + +Basename=$(basename "$0") +Dir=$(cd "$(dirname "$0")" && pwd) CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} -[[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make -MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} ProjectNameFile="$Dir/.sis/project_name.txt" ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +ScriptPath=$0 -CMakeVerbose= +AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} +CTestVerbose= RunMake=1 +SisUseColours=0 + + +# ########################################################## +# colours +# +# Enable when tput is available and either: +# - AlwaysUseColours is set (overrides NO_COLOR; may set TERM if +# empty/dumb), or +# - NO_COLOR is unset, stdout is a TTY, and TERM is not dumb (union of +# collect-c's "TERM set + TTY" and cstring's "TTY" — empty TERM on a +# TTY is OK). + +SisClr_Blue= +SisClr_Bold= +SisClr_Green= +SisClr_None= +SisClr_Red= +SisClr_Yellow= + +for arg in "$@"; do + + case $arg in + --always-use-colors|--always-use-colours|-A) + + AlwaysUseColours=1 + ;; + esac +done + +if command -v tput >/dev/null 2>&1; then + + if [ $AlwaysUseColours -ne 0 ]; then + + if [ -z "${TERM:-}" ] || [ "$TERM" = "dumb" ]; then + + TERM=xterm-256color + fi + + SisUseColours=1 + elif [ -z "${NO_COLOR:-}" ] && [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then + + SisUseColours=1 + fi +fi + +if [ $SisUseColours -ne 0 ]; then + + SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} + SisClr_Bold=${FD_BOLD:-$(tput bold)} + SisClr_Green=${FG_GREEN:-$(tput setaf 2)} + SisClr_None=${FD_NONE:-$(tput sgr0)} + SisClr_Red=${FG_RED:-$(tput setaf 1)} + SisClr_Yellow=${FG_YELLOW:-$(tput setaf 3)} +fi + +CMakeDirClr="${SisClr_Blue}${SisClr_Bold}${CMakeDir}${SisClr_None}" +ProjectNameClr="${SisClr_Blue}${SisClr_Bold}${ProjectName}${SisClr_None}" +ScriptPathClr="${SisClr_Blue}${SisClr_Bold}${ScriptPath}${SisClr_None}" + + +# ########################################################## +# functions - 2 + +sis_cmake_build() { + + local config="${SIS_CMAKE_CONFIG:-Release}" + local args=(--build "$CMakeDir") + if [ -f "$CMakeDir/CMakeCache.txt" ] && grep -q '^CMAKE_CONFIGURATION_TYPES:' "$CMakeDir/CMakeCache.txt" 2>/dev/null; then + + args+=(--config "$config") + fi + if [ "$#" -gt 0 ]; then + + local t + for t in "$@"; do + + args+=(--target "$t") + done + fi + + cmake "${args[@]}" +} # ########################################################## @@ -19,13 +122,17 @@ RunMake=1 while [[ $# -gt 0 ]]; do case $1 in + --always-use-colors|--always-use-colours|-A) + + # AlwaysUseColours=1 - this is handled by the for loop above + ;; --no-make|-M) RunMake=0 ;; --verbose|-V) - CMakeVerbose=--verbose + CTestVerbose=--verbose ;; --help) @@ -33,15 +140,20 @@ while [[ $# -gt 0 ]]; do cat << EOF Runs CMake's CTest test program(s) -$ScriptPath [ ... flags/options ... ] +${ScriptPath} [ ... flags/options ... ] Flags/options: behaviour: + -A + --always-use-colors + --always-use-colours + forces use of colours even when stdout is not a TTY + -M --no-make - does not execute CMake and make before running tests + does not execute a build before running tests -V --verbose @@ -59,7 +171,7 @@ EOF ;; *) - >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + >&2 echo "${ScriptPathClr}: unrecognised argument '${SisClr_Red}${SisClr_Bold}$1${SisClr_None}'; use --help for usage" exit 1 ;; @@ -76,29 +188,36 @@ status=0 if [ $RunMake -ne 0 ]; then - echo "Executing build for ${ProjectName} (via command \`$MakeCmd\`) and then running all component and unit test programs" + echo + echo "Executing build of ${ProjectNameClr} (via cmake --build) and then running CTest" - mkdir -p $CMakeDir || exit 1 + mkdir -p "$CMakeDir" || exit 1 - cd $CMakeDir + if [ ! -f "$CMakeDir/CMakeCache.txt" ]; then - $MakeCmd - status=$? + >&2 echo "${ScriptPathClr}: '${CMakeDirClr}' is not configured; use prepare_cmake.sh first" + + exit 1 + fi - cd ->/dev/null + sis_cmake_build + status=$? else - if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ] || [ ! -d "$CMakeDir/CMakeFiles" ]; then + if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ]; then + + >&2 echo "${ScriptPathClr}: cannot run in '--no-make' mode without a previous successful configure/build" - >&2 echo "$ScriptPath: cannot run in '--no-make' mode without a previous successful build step" + exit 1 fi fi if [ $status -eq 0 ]; then - echo "Running CMake tests" + echo + echo "Running ${ProjectNameClr} CMake tests" - ctest --test-dir $CMakeDir $CMakeVerbose + ctest --test-dir "$CMakeDir" --output-on-failure $CTestVerbose status=$? fi @@ -106,4 +225,3 @@ exit $status # ############################## end of file ############################# # - From bc54b6e07a0c270a0e843ca59bd4febec8783895 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 07:49:11 +1000 Subject: [PATCH 27/88] refactor(cstring): unit/component runners use sis_cmake_build dialect Make run_all_unit_tests.sh unit-only; align both category runners with SisClr_*/-A, cmake --build, and gold basename discovery (.exe, print0). --- run_all_component_tests.sh | 294 +++++++++++++++++++++++++++++++++++++ run_all_unit_tests.sh | 231 +++++++++++++++++++---------- 2 files changed, 447 insertions(+), 78 deletions(-) create mode 100755 run_all_component_tests.sh diff --git a/run_all_component_tests.sh b/run_all_component_tests.sh new file mode 100755 index 0000000..a094046 --- /dev/null +++ b/run_all_component_tests.sh @@ -0,0 +1,294 @@ +#! /bin/bash + +# ########################################################## +# functions - 1 + +sis_cmake_is_truey() { + case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in + + 1|ok|on|true|yes|y) + + return 0 + ;; + *) + + return 1 + ;; + esac +} + + +# ########################################################## +# constants and variables + +Basename=$(basename "$0") +Dir=$(cd "$(dirname "$0")" && pwd) +CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +ScriptPath=$0 + +AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} +ListOnly=0 +RunMake=1 +SisUseColours=0 +Verbosity=${XTESTS_VERBOSITY:-${TEST_VERBOSITY:-3}} + + +# ########################################################## +# colours +# +# Enable when tput is available and either: +# - AlwaysUseColours is set (overrides NO_COLOR; may set TERM if +# empty/dumb), or +# - NO_COLOR is unset, stdout is a TTY, and TERM is not dumb (union of +# collect-c's "TERM set + TTY" and cstring's "TTY" — empty TERM on a +# TTY is OK). + +SisClr_Blue= +SisClr_Bold= +SisClr_Green= +SisClr_None= +SisClr_Red= +SisClr_Yellow= + +for arg in "$@"; do + + case $arg in + --always-use-colors|--always-use-colours|-A) + + AlwaysUseColours=1 + ;; + esac +done + +if command -v tput >/dev/null 2>&1; then + + if [ $AlwaysUseColours -ne 0 ]; then + + if [ -z "${TERM:-}" ] || [ "$TERM" = "dumb" ]; then + + TERM=xterm-256color + fi + + SisUseColours=1 + elif [ -z "${NO_COLOR:-}" ] && [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then + + SisUseColours=1 + fi +fi + +if [ $SisUseColours -ne 0 ]; then + + SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} + SisClr_Bold=${FD_BOLD:-$(tput bold)} + SisClr_Green=${FG_GREEN:-$(tput setaf 2)} + SisClr_None=${FD_NONE:-$(tput sgr0)} + SisClr_Red=${FG_RED:-$(tput setaf 1)} + SisClr_Yellow=${FG_YELLOW:-$(tput setaf 3)} +fi + +CMakeDirClr="${SisClr_Blue}${SisClr_Bold}${CMakeDir}${SisClr_None}" +ProjectNameClr="${SisClr_Blue}${SisClr_Bold}${ProjectName}${SisClr_None}" +ScriptPathClr="${SisClr_Blue}${SisClr_Bold}${ScriptPath}${SisClr_None}" + + +# ########################################################## +# functions - 2 + +sis_cmake_build() { + + local config="${SIS_CMAKE_CONFIG:-Release}" + local args=(--build "$CMakeDir") + if [ -f "$CMakeDir/CMakeCache.txt" ] && grep -q '^CMAKE_CONFIGURATION_TYPES:' "$CMakeDir/CMakeCache.txt" 2>/dev/null; then + + args+=(--config "$config") + fi + if [ "$#" -gt 0 ]; then + + local t + for t in "$@"; do + + args+=(--target "$t") + done + fi + + cmake "${args[@]}" +} + + +# ########################################################## +# command-line handling + +while [[ $# -gt 0 ]]; do + + case $1 in + --always-use-colors|--always-use-colours|-A) + + # AlwaysUseColours=1 - this is handled by the for loop above + ;; + --list-only|-l) + + ListOnly=1 + ;; + --no-make|-M) + + RunMake=0 + ;; + --verbosity) + + shift + Verbosity=$1 + ;; + --help) + + [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" + cat << EOF +Runs all (matching) component-test programs + +${ScriptPath} [ ... flags/options ... ] + +Flags/options: + + behaviour: + + -A + --always-use-colors + --always-use-colours + forces use of colours even when stdout is not a TTY + + -l + --list-only + lists the target programs but does not execute them + + -M + --no-make + does not execute a build before running programs + + --verbosity + specifies an explicit verbosity (forwarded when supported) + + + standard flags: + + --help + displays this help and terminates + +EOF + + exit 0 + ;; + *) + + >&2 echo "${ScriptPathClr}: unrecognised argument '${SisClr_Red}${SisClr_Bold}$1${SisClr_None}'; use --help for usage" + + exit 1 + ;; + esac + + shift +done + + +# ########################################################## +# main() + +status=0 + +if [ $RunMake -ne 0 ]; then + + if [ $ListOnly -eq 0 ]; then + + echo + echo "Executing build of ${ProjectNameClr} (via cmake --build) and then running all component-test programs" + + mkdir -p "$CMakeDir" || exit 1 + + if [ ! -f "$CMakeDir/CMakeCache.txt" ]; then + + >&2 echo "${ScriptPathClr}: '${CMakeDirClr}' is not configured; use prepare_cmake.sh first" + + exit 1 + fi + + sis_cmake_build + status=$? + fi +else + + if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ]; then + + >&2 echo "${ScriptPathClr}: cannot run in '--no-make' mode without a previous successful configure/build" + + exit 1 + fi +fi + +if [ $status -eq 0 ]; then + + if [ $ListOnly -ne 0 ]; then + + echo + echo "Listing all ${ProjectNameClr} component-test programs" + else + + echo + echo "Running all ${ProjectNameClr} component-test programs" + fi + + NumPrograms=0 + + while IFS= read -r -d '' f; do + + case "$f" in + *.pdb|*.ilk|*.log|*.obj|*.o) + continue + ;; + esac + + NumPrograms=$((NumPrograms + 1)) + + fClr="${SisClr_Blue}${SisClr_Bold}${f}${SisClr_None}" + + if [ $ListOnly -ne 0 ]; then + + echo "would execute ${fClr}:" + + 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 + + : + elif "$f"; then + + : + else + + status=$? + + break 1 + fi + done < <(find "$CMakeDir" -type f \( -name 'test_component*' -o -name 'test.component.*' \) \( -perm -100 -o -name '*.exe' \) -print0 2>/dev/null | sort -z) + + if [ $NumPrograms -eq 0 ]; then + + echo "${ScriptPathClr}: found no component-test programs under '${CMakeDirClr}' (none found)" + + exit 0 + fi +fi + +exit $status + + +# ############################## end of file ############################# # diff --git a/run_all_unit_tests.sh b/run_all_unit_tests.sh index cc48b02..b64930b 100755 --- a/run_all_unit_tests.sh +++ b/run_all_unit_tests.sh @@ -1,38 +1,121 @@ #! /bin/bash -ScriptPath=$0 -Dir=$(cd "$(dirname "$ScriptPath")" && pwd) -Basename=$(basename "$ScriptPath") +# ########################################################## +# functions - 1 + +sis_cmake_is_truey() { + case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in + + 1|ok|on|true|yes|y) + + return 0 + ;; + *) + + return 1 + ;; + esac +} + + +# ########################################################## +# constants and variables + +Basename=$(basename "$0") +Dir=$(cd "$(dirname "$0")" && pwd) CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} -[[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make -MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} ProjectNameFile="$Dir/.sis/project_name.txt" ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +ScriptPath=$0 +AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} ListOnly=0 RunMake=1 -UnitOnly=0 -ComponentOnly=0 +SisUseColours=0 Verbosity=${XTESTS_VERBOSITY:-${TEST_VERBOSITY:-3}} # ########################################################## # colours +# +# Enable when tput is available and either: +# - AlwaysUseColours is set (overrides NO_COLOR; may set TERM if +# empty/dumb), or +# - NO_COLOR is unset, stdout is a TTY, and TERM is not dumb (union of +# collect-c's "TERM set + TTY" and cstring's "TTY" — empty TERM on a +# TTY is OK). + +SisClr_Blue= +SisClr_Bold= +SisClr_Green= +SisClr_None= +SisClr_Red= +SisClr_Yellow= + +for arg in "$@"; do + + case $arg in + --always-use-colors|--always-use-colours|-A) + + AlwaysUseColours=1 + ;; + esac +done -if command -v tput > /dev/null; then +if command -v tput >/dev/null 2>&1; then - RbEnvClr_Blue=${FG_BLUE:-$(tput setaf 4)} - RbEnvClr_Red=${FG_BLUE:-$(tput setaf 1)} - RbEnvClr_Bold=${FD_BOLD:-$(tput bold)} - RbEnvClr_None=${FD_NONE:-$(tput sgr0)} -else + if [ $AlwaysUseColours -ne 0 ]; then - RbEnvClr_Blue= - RbEnvClr_Red= - RbEnvClr_Bold= - RbEnvClr_None= + if [ -z "${TERM:-}" ] || [ "$TERM" = "dumb" ]; then + + TERM=xterm-256color + fi + + SisUseColours=1 + elif [ -z "${NO_COLOR:-}" ] && [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then + + SisUseColours=1 + fi fi +if [ $SisUseColours -ne 0 ]; then + + SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} + SisClr_Bold=${FD_BOLD:-$(tput bold)} + SisClr_Green=${FG_GREEN:-$(tput setaf 2)} + SisClr_None=${FD_NONE:-$(tput sgr0)} + SisClr_Red=${FG_RED:-$(tput setaf 1)} + SisClr_Yellow=${FG_YELLOW:-$(tput setaf 3)} +fi + +CMakeDirClr="${SisClr_Blue}${SisClr_Bold}${CMakeDir}${SisClr_None}" +ProjectNameClr="${SisClr_Blue}${SisClr_Bold}${ProjectName}${SisClr_None}" +ScriptPathClr="${SisClr_Blue}${SisClr_Bold}${ScriptPath}${SisClr_None}" + + +# ########################################################## +# functions - 2 + +sis_cmake_build() { + + local config="${SIS_CMAKE_CONFIG:-Release}" + local args=(--build "$CMakeDir") + if [ -f "$CMakeDir/CMakeCache.txt" ] && grep -q '^CMAKE_CONFIGURATION_TYPES:' "$CMakeDir/CMakeCache.txt" 2>/dev/null; then + + args+=(--config "$config") + fi + if [ "$#" -gt 0 ]; then + + local t + for t in "$@"; do + + args+=(--target "$t") + done + fi + + cmake "${args[@]}" +} + # ########################################################## # command-line handling @@ -40,6 +123,10 @@ fi while [[ $# -gt 0 ]]; do case $1 in + --always-use-colors|--always-use-colours|-A) + + # AlwaysUseColours=1 - this is handled by the for loop above + ;; --list-only|-l) ListOnly=1 @@ -48,14 +135,6 @@ while [[ $# -gt 0 ]]; do RunMake=0 ;; - --unit-only) - - UnitOnly=1 - ;; - --component-only) - - ComponentOnly=1 - ;; --verbosity) shift @@ -65,30 +144,29 @@ while [[ $# -gt 0 ]]; do [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" cat << EOF -Runs all (matching) unit-test and/or component-test programs +Runs all (matching) unit-test programs -$ScriptPath [ ... flags/options ... ] +${ScriptPath} [ ... flags/options ... ] Flags/options: behaviour: + -A + --always-use-colors + --always-use-colours + forces use of colours even when stdout is not a TTY + -l --list-only lists the target programs but does not execute them -M --no-make - does not execute CMake and make before running tests - - --unit-only - runs only unit-test programs (test.unit.* / test_unit*) - - --component-only - runs only component-test programs (test.component.* / test_component*) + does not execute a build before running programs --verbosity - specifies an explicit verbosity for the unit-test(s) + specifies an explicit verbosity (forwarded when supported) standard flags: @@ -102,7 +180,7 @@ EOF ;; *) - >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + >&2 echo "${ScriptPathClr}: unrecognised argument '${SisClr_Red}${SisClr_Bold}$1${SisClr_None}'; use --help for usage" exit 1 ;; @@ -111,50 +189,36 @@ EOF shift done -if [ $UnitOnly -ne 0 ] && [ $ComponentOnly -ne 0 ]; then - - >&2 echo "$ScriptPath: --unit-only and --component-only are mutually exclusive" - - exit 1 -fi - # ########################################################## # main() status=0 -if [ $UnitOnly -ne 0 ]; then - - TestKindDescription='unit test' -elif [ $ComponentOnly -ne 0 ]; then - - TestKindDescription='component test' -else - - TestKindDescription='component and unit test' -fi - if [ $RunMake -ne 0 ]; then if [ $ListOnly -eq 0 ]; then - echo "Executing build (via command \`$MakeCmd\`) and then running all ${ProjectName} ${TestKindDescription} programs" + echo + echo "Executing build of ${ProjectNameClr} (via cmake --build) and then running all unit-test programs" - mkdir -p $CMakeDir || exit 1 + mkdir -p "$CMakeDir" || exit 1 - cd $CMakeDir + if [ ! -f "$CMakeDir/CMakeCache.txt" ]; then - $MakeCmd - status=$? + >&2 echo "${ScriptPathClr}: '${CMakeDirClr}' is not configured; use prepare_cmake.sh first" - cd ->/dev/null + exit 1 + fi + + sis_cmake_build + status=$? fi else - if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ] || [ ! -d "$CMakeDir/CMakeFiles" ]; then + if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ]; then - >&2 echo "$ScriptPath: cannot run in '--no-make' mode without a previous successful build step" + >&2 echo "${ScriptPathClr}: cannot run in '--no-make' mode without a previous successful configure/build" exit 1 fi @@ -164,29 +228,31 @@ if [ $status -eq 0 ]; then if [ $ListOnly -ne 0 ]; then - echo "Listing all ${ProjectName} ${TestKindDescription} programs" + echo + echo "Listing all ${ProjectNameClr} unit-test programs" else - echo "Running all ${ProjectName} ${TestKindDescription} programs" + echo + echo "Running all ${ProjectNameClr} unit-test programs" fi - if [ $UnitOnly -ne 0 ]; then + NumPrograms=0 - find_name_expr=( \( -name 'test_unit*' -o -name 'test.unit.*' \) ) - elif [ $ComponentOnly -ne 0 ]; then + while IFS= read -r -d '' f; do - find_name_expr=( \( -name 'test_component*' -o -name 'test.component.*' \) ) - else + case "$f" in + *.pdb|*.ilk|*.log|*.obj|*.o) + continue + ;; + esac - find_name_expr=( \( -name 'test_unit*' -o -name 'test.unit.*' -o -name 'test_component*' -o -name 'test.component.*' \) ) - fi + NumPrograms=$((NumPrograms + 1)) - for f in $(find "$CMakeDir" -type f "${find_name_expr[@]}" -exec test -x {} \; -print | sort) - do + fClr="${SisClr_Blue}${SisClr_Bold}${f}${SisClr_None}" if [ $ListOnly -ne 0 ]; then - echo "would execute $RbEnvClr_Blue$RbEnvClr_Bold$f$RbEnvClr_None:" + echo "would execute ${fClr}:" continue fi @@ -197,10 +263,13 @@ if [ $status -eq 0 ]; then fi if [ $Verbosity -ge 2 ]; then - echo "executing $RbEnvClr_Blue$RbEnvClr_Bold$f$RbEnvClr_None:" + echo "executing ${fClr}:" fi - if $f --verbosity=$Verbosity; then + if "$f" --verbosity="$Verbosity" 2>/dev/null; then + + : + elif "$f"; then : else @@ -209,11 +278,17 @@ if [ $status -eq 0 ]; then break 1 fi - done + done < <(find "$CMakeDir" -type f \( -name 'test_unit*' -o -name 'test.unit.*' \) \( -perm -100 -o -name '*.exe' \) -print0 2>/dev/null | sort -z) + + if [ $NumPrograms -eq 0 ]; then + + echo "${ScriptPathClr}: found no unit-test programs under '${CMakeDirClr}' (none found)" + + exit 0 + fi fi exit $status # ############################## end of file ############################# # - From aa62ebb15e4f449bbe7e05c6d14c46c789b3539d Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 07:55:48 +1000 Subject: [PATCH 28/88] fix(cstring): accept category-only flags; fix CI component runner Treat --unit-only / --component-only as no-ops on the matching category scripts; point ci-cell component tests at run_all_component_tests.sh. --- .github/workflows/ci-cell.yml | 2 +- run_all_component_tests.sh | 9 +++++++++ run_all_unit_tests.sh | 7 +++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-cell.yml b/.github/workflows/ci-cell.yml index dc1f587..4b37038 100644 --- a/.github/workflows/ci-cell.yml +++ b/.github/workflows/ci-cell.yml @@ -297,7 +297,7 @@ jobs: if [ "${{ inputs.c-compiler }}" = "mingw" ]; then export PATH="/mingw64/bin:$PATH" fi - SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_unit_tests.sh -M --component-only + SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_component_tests.sh -M --component-only test-scratch: name: Scratch tests (${{ inputs.cell-id }}) diff --git a/run_all_component_tests.sh b/run_all_component_tests.sh index a094046..2d432f3 100755 --- a/run_all_component_tests.sh +++ b/run_all_component_tests.sh @@ -127,6 +127,11 @@ while [[ $# -gt 0 ]]; do # AlwaysUseColours=1 - this is handled by the for loop above ;; + --component-only) + + # Benign: this script is already component-only (aggregate / CI may + # pass it) + ;; --list-only|-l) ListOnly=1 @@ -157,6 +162,10 @@ Flags/options: --always-use-colours forces use of colours even when stdout is not a TTY + --component-only + accepted for compatibility; this script always runs component tests + only + -l --list-only lists the target programs but does not execute them diff --git a/run_all_unit_tests.sh b/run_all_unit_tests.sh index b64930b..eb49322 100755 --- a/run_all_unit_tests.sh +++ b/run_all_unit_tests.sh @@ -135,6 +135,10 @@ while [[ $# -gt 0 ]]; do RunMake=0 ;; + --unit-only) + + # Benign: this script is already unit-only (aggregate / CI may pass it) + ;; --verbosity) shift @@ -165,6 +169,9 @@ Flags/options: --no-make does not execute a build before running programs + --unit-only + accepted for compatibility; this script always runs unit tests only + --verbosity specifies an explicit verbosity (forwarded when supported) From 9e5083c56c0abc2b019812af1b2a70897a9e6049 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 07:58:10 +1000 Subject: [PATCH 29/88] refactor(cstring): drive run_all_examples via cmake --build dialect Replace MakeCmd with sis_cmake_build; add SisClr_*/-A; discover example* executables with print0/.exe and optional --verbosity. --- run_all_examples.sh | 219 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 182 insertions(+), 37 deletions(-) diff --git a/run_all_examples.sh b/run_all_examples.sh index 575929e..9f988ec 100755 --- a/run_all_examples.sh +++ b/run_all_examples.sh @@ -1,35 +1,121 @@ #! /bin/bash -ScriptPath=$0 -Dir=$(cd "$(dirname "$ScriptPath")" && pwd) -Basename=$(basename "$ScriptPath") +# ########################################################## +# functions - 1 + +sis_cmake_is_truey() { + case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in + + 1|ok|on|true|yes|y) + + return 0 + ;; + *) + + return 1 + ;; + esac +} + + +# ########################################################## +# constants and variables + +Basename=$(basename "$0") +Dir=$(cd "$(dirname "$0")" && pwd) CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} -[[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make -MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} ProjectNameFile="$Dir/.sis/project_name.txt" ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +ScriptPath=$0 +AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} ListOnly=0 RunMake=1 +SisUseColours=0 +Verbosity=${XTESTS_VERBOSITY:-${TEST_VERBOSITY:-3}} # ########################################################## # colours +# +# Enable when tput is available and either: +# - AlwaysUseColours is set (overrides NO_COLOR; may set TERM if +# empty/dumb), or +# - NO_COLOR is unset, stdout is a TTY, and TERM is not dumb (union of +# collect-c's "TERM set + TTY" and cstring's "TTY" — empty TERM on a +# TTY is OK). + +SisClr_Blue= +SisClr_Bold= +SisClr_Green= +SisClr_None= +SisClr_Red= +SisClr_Yellow= + +for arg in "$@"; do + + case $arg in + --always-use-colors|--always-use-colours|-A) + + AlwaysUseColours=1 + ;; + esac +done -if command -v tput > /dev/null; then +if command -v tput >/dev/null 2>&1; then - RbEnvClr_Blue=${FG_BLUE:-$(tput setaf 4)} - RbEnvClr_Red=${FG_BLUE:-$(tput setaf 1)} - RbEnvClr_Bold=${FD_BOLD:-$(tput bold)} - RbEnvClr_None=${FD_NONE:-$(tput sgr0)} -else + if [ $AlwaysUseColours -ne 0 ]; then - RbEnvClr_Blue= - RbEnvClr_Red= - RbEnvClr_Bold= - RbEnvClr_None= + if [ -z "${TERM:-}" ] || [ "$TERM" = "dumb" ]; then + + TERM=xterm-256color + fi + + SisUseColours=1 + elif [ -z "${NO_COLOR:-}" ] && [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then + + SisUseColours=1 + fi fi +if [ $SisUseColours -ne 0 ]; then + + SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} + SisClr_Bold=${FD_BOLD:-$(tput bold)} + SisClr_Green=${FG_GREEN:-$(tput setaf 2)} + SisClr_None=${FD_NONE:-$(tput sgr0)} + SisClr_Red=${FG_RED:-$(tput setaf 1)} + SisClr_Yellow=${FG_YELLOW:-$(tput setaf 3)} +fi + +CMakeDirClr="${SisClr_Blue}${SisClr_Bold}${CMakeDir}${SisClr_None}" +ProjectNameClr="${SisClr_Blue}${SisClr_Bold}${ProjectName}${SisClr_None}" +ScriptPathClr="${SisClr_Blue}${SisClr_Bold}${ScriptPath}${SisClr_None}" + + +# ########################################################## +# functions - 2 + +sis_cmake_build() { + + local config="${SIS_CMAKE_CONFIG:-Release}" + local args=(--build "$CMakeDir") + if [ -f "$CMakeDir/CMakeCache.txt" ] && grep -q '^CMAKE_CONFIGURATION_TYPES:' "$CMakeDir/CMakeCache.txt" 2>/dev/null; then + + args+=(--config "$config") + fi + if [ "$#" -gt 0 ]; then + + local t + for t in "$@"; do + + args+=(--target "$t") + done + fi + + cmake "${args[@]}" +} + # ########################################################## # command-line handling @@ -37,6 +123,10 @@ fi while [[ $# -gt 0 ]]; do case $1 in + --always-use-colors|--always-use-colours|-A) + + # AlwaysUseColours=1 - this is handled by the for loop above + ;; --list-only|-l) ListOnly=1 @@ -45,25 +135,38 @@ 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" cat << EOF -Runs all example programs +Runs all (matching) example programs -$ScriptPath [ ... flags/options ... ] +${ScriptPath} [ ... flags/options ... ] Flags/options: behaviour: + -A + --always-use-colors + --always-use-colours + forces use of colours even when stdout is not a TTY + -l --list-only lists the target programs but does not execute them -M --no-make - does not execute CMake and make before running tests + does not execute a build before running programs + + --verbosity + specifies an explicit verbosity (forwarded when supported) standard flags: @@ -77,7 +180,7 @@ EOF ;; *) - >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + >&2 echo "${ScriptPathClr}: unrecognised argument '${SisClr_Red}${SisClr_Bold}$1${SisClr_None}'; use --help for usage" exit 1 ;; @@ -96,22 +199,28 @@ if [ $RunMake -ne 0 ]; then if [ $ListOnly -eq 0 ]; then - echo "Executing build (via command \`$MakeCmd\`) and then running all ${ProjectName} example programs" + echo + echo "Executing build of ${ProjectNameClr} (via cmake --build) and then running all example programs" + + mkdir -p "$CMakeDir" || exit 1 - mkdir -p $CMakeDir || exit 1 + if [ ! -f "$CMakeDir/CMakeCache.txt" ]; then - cd $CMakeDir + >&2 echo "${ScriptPathClr}: '${CMakeDirClr}' is not configured; use prepare_cmake.sh first" - $MakeCmd - status=$? + exit 1 + fi - cd ->/dev/null + sis_cmake_build + status=$? fi else - if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ] || [ ! -d "$CMakeDir/CMakeFiles" ]; then + if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ]; then - >&2 echo "$ScriptPath: cannot run in '--no-make' mode without a previous successful build step" + >&2 echo "${ScriptPathClr}: cannot run in '--no-make' mode without a previous successful configure/build" + + exit 1 fi fi @@ -119,31 +228,67 @@ if [ $status -eq 0 ]; then if [ $ListOnly -ne 0 ]; then - echo "Listing all ${ProjectName} example programs" + echo + echo "Listing all ${ProjectNameClr} example programs" else - echo "Running all ${ProjectName} example programs" + echo + echo "Running all ${ProjectNameClr} example programs" fi - for f in $(find $CMakeDir -type f '(' -name 'example.c.*' -o -name 'example.cpp.*' ')' -exec test -x {} \; -print) - do + NumPrograms=0 + + while IFS= read -r -d '' f; do + + case "$f" in + *.pdb|*.ilk|*.log|*.obj|*.o) + continue + ;; + esac + + NumPrograms=$((NumPrograms + 1)) + + fClr="${SisClr_Blue}${SisClr_Bold}${f}${SisClr_None}" if [ $ListOnly -ne 0 ]; then - echo "would execute $RbEnvClr_Blue$RbEnvClr_Bold$f$RbEnvClr_None:" + echo "would execute ${fClr}:" continue fi - echo - echo "executing $RbEnvClr_Blue$RbEnvClr_Bold$f$RbEnvClr_None:" + if [ $Verbosity -ge 3 ]; then + + echo + fi + if [ $Verbosity -ge 2 ]; then + + echo "executing ${fClr}:" + fi + + if "$f" --verbosity="$Verbosity" 2>/dev/null; then - $f - done + : + elif "$f"; then + + : + else + + status=$? + + break 1 + fi + done < <(find "$CMakeDir" -type f \( -name 'example*' ! -name '*.md' \) \( -perm -100 -o -name '*.exe' \) -print0 2>/dev/null | sort -z) + + if [ $NumPrograms -eq 0 ]; then + + echo "${ScriptPathClr}: found no example programs under '${CMakeDirClr}' (none found)" + + exit 0 + fi fi exit $status # ############################## end of file ############################# # - From 45bb3bbef5dcffaf499b2bfcc0958fc3dc937dc1 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 08:38:04 +1000 Subject: [PATCH 30/88] fix(cstring): gate cstring_vector no-arg demo on SIS_EXAMPLE_SMOKE Fail without input for humans; when SIS_EXAMPLE_SMOKE is set, use an in-process tmpfile sample. run_all_examples exports the variable for CI. --- README.md | 2 +- examples/c/example.c.cstring_vector/README.md | 6 +- examples/c/example.c.cstring_vector/main.c | 60 ++++++++++++++++++- run_all_examples.sh | 4 ++ 4 files changed, 66 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 91c57bd..080d353 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ Examples live under **examples/** (`c/` and `cpp/`), each with a short **README. | [**example.c.auto_buffer**](./examples/c/example.c.auto_buffer/) | C | Borrowed buffer that may grow to the heap | | [**example.c.cstring**](./examples/c/example.c.cstring/) | C | Core `cstring_t` create / assign / append / truncate / copy / swap | | [**example.c.cstring_create**](./examples/c/example.c.cstring_create/) | C | Minimal `cstring_create()` | -| [**example.c.cstring_vector**](./examples/c/example.c.cstring_vector/) | C | Read lines into `cstring_vector_t` and sort (requires input path or `--`) | +| [**example.c.cstring_vector**](./examples/c/example.c.cstring_vector/) | C | Read lines into `cstring_vector_t` and sort (input path or `--`; `SIS_EXAMPLE_SMOKE` enables no-arg demo) | | [**example.cpp.cstring.dynload**](./examples/cpp/example.cpp.cstring.dynload/) | C++ | Windows-only dynamic load of the cstring DLL | | [**example.cpp.HGLOBAL_on_x64**](./examples/cpp/example.cpp.HGLOBAL_on_x64/) | C++ | Windows-only `CSTRING_F_USE_WIN32_GLOBAL_MEMORY` | diff --git a/examples/c/example.c.cstring_vector/README.md b/examples/c/example.c.cstring_vector/README.md index 2e5f4bb..98d25e2 100644 --- a/examples/c/example.c.cstring_vector/README.md +++ b/examples/c/example.c.cstring_vector/README.md @@ -13,7 +13,7 @@ Built when `BUILD_EXAMPLES` is enabled (the default). ## Run -Requires an input source: +Requires an input source (path, or `--` for stdin). Intended for interactive / scripted human use: ```sh ${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/example.c.cstring_vector/example.c.cstring_vector path/to/file.txt @@ -21,10 +21,12 @@ ${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/example.c.cstring_vector/example.c.c printf 'beta\nalpha\ngamma\n' | ${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/example.c.cstring_vector/example.c.cstring_vector -- ``` +With **`SIS_EXAMPLE_SMOKE`** set, a no-argument invocation uses a small in-process `tmpfile()` sample (no child re-exec). **`run_all_examples.sh`** exports this for automated smoke runs. Without the variable, no arguments fails with a usage error. + ## Expected output -For the `printf 'beta\nalpha\ngamma\n' | … --` invocation above: +For the `printf 'beta\nalpha\ngamma\n' | … --` invocation above (and for the **`SIS_EXAMPLE_SMOKE`** no-arg demo): ``` #lines: 3 diff --git a/examples/c/example.c.cstring_vector/main.c b/examples/c/example.c.cstring_vector/main.c index d8314d0..a460381 100644 --- a/examples/c/example.c.cstring_vector/main.c +++ b/examples/c/example.c.cstring_vector/main.c @@ -5,7 +5,7 @@ * them. * * Created: ... - * Updated: 2nd August 2026 + * Updated: 23rd September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -19,6 +19,20 @@ #include #include + +/* ///////////////////////////////////////////////////////////////////////// + * helper functions + */ + +static +int is_example_smoke_detected_(void) +{ + char const* const ev = getenv("SIS_EXAMPLE_SMOKE"); + + return NULL != ev; +} + + /* ///////////////////////////////////////////////////////////////////////// * main() */ @@ -39,9 +53,44 @@ int main(int argc, char* argv[]) { case 1: - fprintf(stderr, "%s: input source not specified; use --help for usage\n", arg0); + if (!is_example_smoke_detected_()) + { + fprintf(stderr, "%s: input source not specified; use --help for usage\n", arg0); - return EXIT_FAILURE; + return EXIT_FAILURE; + } + else + { + /* No-arg smoke / demo path for run_all_examples.sh and similar. */ + + stream = tmpfile(); + + if (NULL == stream) + { + int const e = errno; + + fprintf(stderr, "%s: could not create temporary demo input: %s\n", arg0, strerror(e)); + + return EXIT_FAILURE; + } + + if (EOF == fputs("beta\nalpha\ngamma\n", stream)) + { + int const e = errno; + + fprintf(stderr, "%s: could not write temporary demo input: %s\n", arg0, strerror(e)); + + fclose(stream); + + return EXIT_FAILURE; + } + + rewind(stream); + + input_path = "(built-in demo)"; + } + + break; case 2: if (0 == strcmp("--help", argv[1])) @@ -51,6 +100,10 @@ int main(int argc, char* argv[]) , "USAGE: %s { | -- | --help }\n" , arg0 ); + fprintf( + stdout + , " With SIS_EXAMPLE_SMOKE set, no arguments runs a small built-in demo.\n" + ); return EXIT_SUCCESS; } @@ -233,5 +286,6 @@ int run_( } } + /* ///////////////////////////// end of file //////////////////////////// */ diff --git a/run_all_examples.sh b/run_all_examples.sh index 9f988ec..a2b652e 100755 --- a/run_all_examples.sh +++ b/run_all_examples.sh @@ -236,6 +236,10 @@ if [ $status -eq 0 ]; then echo "Running all ${ProjectNameClr} example programs" fi + # Examples that require human input may honour SIS_EXAMPLE_SMOKE for a + # no-arg built-in tmpfile demo (see example.c.cstring_vector). + export SIS_EXAMPLE_SMOKE=1 + NumPrograms=0 while IFS= read -r -d '' f; do From d30ab59c81c8b902540e740d64d720e343ddc49b Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 08:44:45 +1000 Subject: [PATCH 31/88] refactor(cstring): drive run_all_scratch via cmake --build dialect Replace MakeCmd with sis_cmake_build; run scratch-only (drop performance); add SisClr_*/-A; discover test_scratch* / test.scratch.* via print0/.exe. --- run_all_scratch_tests.sh | 198 +++++++++++++++++++++++++++++++-------- 1 file changed, 161 insertions(+), 37 deletions(-) diff --git a/run_all_scratch_tests.sh b/run_all_scratch_tests.sh index 37a68e9..2d83450 100755 --- a/run_all_scratch_tests.sh +++ b/run_all_scratch_tests.sh @@ -1,36 +1,121 @@ #! /bin/bash -ScriptPath=$0 -Dir=$(cd "$(dirname "$ScriptPath")" && pwd) -Basename=$(basename "$ScriptPath") +# ########################################################## +# functions - 1 + +sis_cmake_is_truey() { + case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in + + 1|ok|on|true|yes|y) + + return 0 + ;; + *) + + return 1 + ;; + esac +} + + +# ########################################################## +# constants and variables + +Basename=$(basename "$0") +Dir=$(cd "$(dirname "$0")" && pwd) CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} -[[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make -MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} ProjectNameFile="$Dir/.sis/project_name.txt" ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +ScriptPath=$0 +AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} ListOnly=0 RunMake=1 +SisUseColours=0 Verbosity=${XTESTS_VERBOSITY:-${TEST_VERBOSITY:-3}} # ########################################################## # colours +# +# Enable when tput is available and either: +# - AlwaysUseColours is set (overrides NO_COLOR; may set TERM if +# empty/dumb), or +# - NO_COLOR is unset, stdout is a TTY, and TERM is not dumb (union of +# collect-c's "TERM set + TTY" and cstring's "TTY" — empty TERM on a +# TTY is OK). + +SisClr_Blue= +SisClr_Bold= +SisClr_Green= +SisClr_None= +SisClr_Red= +SisClr_Yellow= + +for arg in "$@"; do + + case $arg in + --always-use-colors|--always-use-colours|-A) + + AlwaysUseColours=1 + ;; + esac +done -if command -v tput > /dev/null; then +if command -v tput >/dev/null 2>&1; then - RbEnvClr_Blue=${FG_BLUE:-$(tput setaf 4)} - RbEnvClr_Red=${FG_BLUE:-$(tput setaf 1)} - RbEnvClr_Bold=${FD_BOLD:-$(tput bold)} - RbEnvClr_None=${FD_NONE:-$(tput sgr0)} -else + if [ $AlwaysUseColours -ne 0 ]; then + + if [ -z "${TERM:-}" ] || [ "$TERM" = "dumb" ]; then - RbEnvClr_Blue= - RbEnvClr_Red= - RbEnvClr_Bold= - RbEnvClr_None= + TERM=xterm-256color + fi + + SisUseColours=1 + elif [ -z "${NO_COLOR:-}" ] && [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then + + SisUseColours=1 + fi fi +if [ $SisUseColours -ne 0 ]; then + + SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} + SisClr_Bold=${FD_BOLD:-$(tput bold)} + SisClr_Green=${FG_GREEN:-$(tput setaf 2)} + SisClr_None=${FD_NONE:-$(tput sgr0)} + SisClr_Red=${FG_RED:-$(tput setaf 1)} + SisClr_Yellow=${FG_YELLOW:-$(tput setaf 3)} +fi + +CMakeDirClr="${SisClr_Blue}${SisClr_Bold}${CMakeDir}${SisClr_None}" +ProjectNameClr="${SisClr_Blue}${SisClr_Bold}${ProjectName}${SisClr_None}" +ScriptPathClr="${SisClr_Blue}${SisClr_Bold}${ScriptPath}${SisClr_None}" + + +# ########################################################## +# functions - 2 + +sis_cmake_build() { + + local config="${SIS_CMAKE_CONFIG:-Release}" + local args=(--build "$CMakeDir") + if [ -f "$CMakeDir/CMakeCache.txt" ] && grep -q '^CMAKE_CONFIGURATION_TYPES:' "$CMakeDir/CMakeCache.txt" 2>/dev/null; then + + args+=(--config "$config") + fi + if [ "$#" -gt 0 ]; then + + local t + for t in "$@"; do + + args+=(--target "$t") + done + fi + + cmake "${args[@]}" +} + # ########################################################## # command-line handling @@ -38,6 +123,10 @@ fi while [[ $# -gt 0 ]]; do case $1 in + --always-use-colors|--always-use-colours|-A) + + # AlwaysUseColours=1 - this is handled by the for loop above + ;; --list-only|-l) ListOnly=1 @@ -55,24 +144,29 @@ while [[ $# -gt 0 ]]; do [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" cat << EOF -Runs all (matching) performance-test and scratch-test programs +Runs all (matching) scratch-test programs -$ScriptPath [ ... flags/options ... ] +${ScriptPath} [ ... flags/options ... ] Flags/options: behaviour: + -A + --always-use-colors + --always-use-colours + forces use of colours even when stdout is not a TTY + -l --list-only lists the target programs but does not execute them -M --no-make - does not execute CMake and make before running tests + does not execute a build before running programs --verbosity - specifies an explicit verbosity for the unit-test(s) + specifies an explicit verbosity (forwarded when supported) standard flags: @@ -86,7 +180,7 @@ EOF ;; *) - >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + >&2 echo "${ScriptPathClr}: unrecognised argument '${SisClr_Red}${SisClr_Bold}$1${SisClr_None}'; use --help for usage" exit 1 ;; @@ -105,22 +199,28 @@ if [ $RunMake -ne 0 ]; then if [ $ListOnly -eq 0 ]; then - echo "Executing build (via command \`$MakeCmd\`) and then running all ${ProjectName} scratch (and performance) test programs" + echo + echo "Executing build of ${ProjectNameClr} (via cmake --build) and then running all scratch-test programs" - mkdir -p $CMakeDir || exit 1 + mkdir -p "$CMakeDir" || exit 1 - cd $CMakeDir + if [ ! -f "$CMakeDir/CMakeCache.txt" ]; then - $MakeCmd - status=$? + >&2 echo "${ScriptPathClr}: '${CMakeDirClr}' is not configured; use prepare_cmake.sh first" + + exit 1 + fi - cd ->/dev/null + sis_cmake_build + status=$? fi else - if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ] || [ ! -d "$CMakeDir/CMakeFiles" ]; then + if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ]; then + + >&2 echo "${ScriptPathClr}: cannot run in '--no-make' mode without a previous successful configure/build" - >&2 echo "$ScriptPath: cannot run in '--no-make' mode without a previous successful build step" + exit 1 fi fi @@ -128,18 +228,31 @@ if [ $status -eq 0 ]; then if [ $ListOnly -ne 0 ]; then - echo "Listing all ${ProjectName} scratch (and performance) test programs" + echo + echo "Listing all ${ProjectNameClr} scratch-test programs" else - echo "Running all ${ProjectName} scratch (and performance) test programs" + echo + echo "Running all ${ProjectNameClr} scratch-test programs" fi - for f in $(find $CMakeDir -type f '(' -name 'test_scratch*' -o -name 'test.scratch.*' -o -name 'test_performance*' -o -name 'test.performance.*' ')' -exec test -x {} \; -print) - do + NumPrograms=0 + + while IFS= read -r -d '' f; do + + case "$f" in + *.pdb|*.ilk|*.log|*.obj|*.o) + continue + ;; + esac + + NumPrograms=$((NumPrograms + 1)) + + fClr="${SisClr_Blue}${SisClr_Bold}${f}${SisClr_None}" if [ $ListOnly -ne 0 ]; then - echo "would execute $RbEnvClr_Blue$RbEnvClr_Bold$f$RbEnvClr_None:" + echo "would execute ${fClr}:" continue fi @@ -150,21 +263,32 @@ if [ $status -eq 0 ]; then fi if [ $Verbosity -ge 2 ]; then - echo "executing $RbEnvClr_Blue$RbEnvClr_Bold$f$RbEnvClr_None:" + echo "executing ${fClr}:" fi - if $f; then + if "$f" --verbosity="$Verbosity" 2>/dev/null; then + + : + elif "$f"; then : else status=$? + + break 1 fi - done + done < <(find "$CMakeDir" -type f \( -name 'test_scratch*' -o -name 'test.scratch.*' \) \( -perm -100 -o -name '*.exe' \) -print0 2>/dev/null | sort -z) + + if [ $NumPrograms -eq 0 ]; then + + echo "${ScriptPathClr}: found no scratch-test programs under '${CMakeDirClr}' (none found)" + + exit 0 + fi fi exit $status # ############################## end of file ############################# # - From 7e770340bd2d39db47b7758c8d3ea36b09b0bbe8 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 08:52:15 +1000 Subject: [PATCH 32/88] refactor(cstring): native CMD runners for unit, component, examples Add run_all_component_tests.cmd and run_all_examples.cmd; make run_all_unit_tests.cmd unit-only with ProjectName, benign --unit-only, and stop on first failing .exe (no Bash). --- run_all_component_tests.cmd | 94 +++++++++++++++++++++++++++++++++++++ run_all_examples.cmd | 89 +++++++++++++++++++++++++++++++++++ run_all_unit_tests.cmd | 32 +++++++++++-- 3 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 run_all_component_tests.cmd create mode 100644 run_all_examples.cmd diff --git a/run_all_component_tests.cmd b/run_all_component_tests.cmd new file mode 100644 index 0000000..970c9ba --- /dev/null +++ b/run_all_component_tests.cmd @@ -0,0 +1,94 @@ +@echo off + +SETLOCAL + +SET SCRIPT_DIRECTORY=%~dp0 +SET SCRIPT_PATH_DOC=%~n0[%~x0] + +IF DEFINED SIS_CMAKE_BUILD_DIR ( + + SET CMAKE_DIR=%SIS_CMAKE_BUILD_DIR% +) ELSE ( + + SET CMAKE_DIR=%SCRIPT_DIRECTORY%_build +) + +FOR %%a IN (%*) DO ( + + IF /I {--help}=={%%a} ( + + IF EXIST "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" ( + + type "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" + ) + ECHO ^ + +Runs all ^(matching^) component-test programs ^ + +^ + +%SCRIPT_PATH_DOC% [ ... flags/options ... ] ^ + +^ + +Flags/options: ^ + + behaviour: ^ + +^ + + --component-only ^ + + accepted for compatibility; this script always runs component tests only ^ + +^ + + standard flags: ^ + +^ + + --help ^ + + displays this help and terminates ^ + + + EXIT /B 0 + ) ELSE IF /I {--component-only}=={%%a} ( + + REM Benign: this script is already component-only + ) ELSE ( + + ECHO "%SCRIPT_DIRECTORY%: unrecognised argument '%%a'; use --help for usage" 1>&2 + + EXIT /B 1 + ) +) + +if NOT EXIST "%CMAKE_DIR%" ( + + ECHO "CMake build directory '%CMAKE_DIR%' does not exist" + + EXIT /B 1 +) + +SET "ProjectName=" +FOR /F "usebackq delims=" %%p IN ("%SCRIPT_DIRECTORY%.sis\project_name.txt") DO SET "ProjectName=%%p" + +IF NOT DEFINED ProjectName ( + + ECHO %SCRIPT_PATH_DOC%: could not read project name from .sis\project_name.txt 1>&2 + + EXIT /B 1 +) + +ECHO Running all %ProjectName% component-test programs + +FOR /F "usebackq" %%f IN (`DIR /A:-D /B /S "%CMAKE_DIR%" ^| FINDSTR /I test.*component.*\.exe$`) DO ( + + ECHO . + ECHO executing %%f + %%f + IF ERRORLEVEL 1 EXIT /B %ERRORLEVEL% +) + +ENDLOCAL diff --git a/run_all_examples.cmd b/run_all_examples.cmd new file mode 100644 index 0000000..11273cb --- /dev/null +++ b/run_all_examples.cmd @@ -0,0 +1,89 @@ +@echo off + +SETLOCAL + +SET SCRIPT_DIRECTORY=%~dp0 +SET SCRIPT_PATH_DOC=%~n0[%~x0] + +IF DEFINED SIS_CMAKE_BUILD_DIR ( + + SET CMAKE_DIR=%SIS_CMAKE_BUILD_DIR% +) ELSE ( + + SET CMAKE_DIR=%SCRIPT_DIRECTORY%_build +) + +FOR %%a IN (%*) DO ( + + IF /I {--help}=={%%a} ( + + IF EXIST "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" ( + + type "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" + ) + ECHO ^ + +Runs all ^(matching^) example programs ^ + +^ + +%SCRIPT_PATH_DOC% [ ... flags/options ... ] ^ + +^ + +Flags/options: ^ + + behaviour: ^ + +^ + + standard flags: ^ + +^ + + --help ^ + + displays this help and terminates ^ + + + EXIT /B 0 + ) ELSE ( + + ECHO "%SCRIPT_DIRECTORY%: unrecognised argument '%%a'; use --help for usage" 1>&2 + + EXIT /B 1 + ) +) + +if NOT EXIST "%CMAKE_DIR%" ( + + ECHO "CMake build directory '%CMAKE_DIR%' does not exist" + + EXIT /B 1 +) + +SET "ProjectName=" +FOR /F "usebackq delims=" %%p IN ("%SCRIPT_DIRECTORY%.sis\project_name.txt") DO SET "ProjectName=%%p" + +IF NOT DEFINED ProjectName ( + + ECHO %SCRIPT_PATH_DOC%: could not read project name from .sis\project_name.txt 1>&2 + + EXIT /B 1 +) + +REM Examples that require human input may honour SIS_EXAMPLE_SMOKE for a +REM no-arg built-in tmpfile demo (see example.c.cstring_vector). +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 diff --git a/run_all_unit_tests.cmd b/run_all_unit_tests.cmd index c124263..18b040a 100644 --- a/run_all_unit_tests.cmd +++ b/run_all_unit_tests.cmd @@ -4,6 +4,7 @@ SETLOCAL SET SCRIPT_DIRECTORY=%~dp0 SET SCRIPT_PATH_DOC=%~n0[%~x0] + IF DEFINED SIS_CMAKE_BUILD_DIR ( SET CMAKE_DIR=%SIS_CMAKE_BUILD_DIR% @@ -13,14 +14,16 @@ IF DEFINED SIS_CMAKE_BUILD_DIR ( ) FOR %%a IN (%*) DO ( + IF /I {--help}=={%%a} ( + IF EXIST "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" ( type "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" ) ECHO ^ -Runs all ^(matching^) component-test and unit-test programs ^ +Runs all ^(matching^) unit-test programs ^ ^ @@ -32,6 +35,12 @@ Flags/options: ^ behaviour: ^ +^ + + --unit-only ^ + + accepted for compatibility; this script always runs unit tests only ^ + ^ standard flags: ^ @@ -44,7 +53,11 @@ Flags/options: ^ EXIT /B 0 + ) ELSE IF /I {--unit-only}=={%%a} ( + + REM Benign: this script is already unit-only ) ELSE ( + ECHO "%SCRIPT_DIRECTORY%: unrecognised argument '%%a'; use --help for usage" 1>&2 EXIT /B 1 @@ -58,11 +71,24 @@ if NOT EXIST "%CMAKE_DIR%" ( EXIT /B 1 ) -FOR /F "usebackq" %%f IN (`DIR /A:-D /B /S %CMAKE_DIR% ^| FINDSTR /I test.*unit.*\.exe$`) DO ( +SET "ProjectName=" +FOR /F "usebackq delims=" %%p IN ("%SCRIPT_DIRECTORY%.sis\project_name.txt") DO SET "ProjectName=%%p" + +IF NOT DEFINED ProjectName ( + + ECHO %SCRIPT_PATH_DOC%: could not read project name from .sis\project_name.txt 1>&2 + + EXIT /B 1 +) + +ECHO Running all %ProjectName% unit-test programs + +FOR /F "usebackq" %%f IN (`DIR /A:-D /B /S "%CMAKE_DIR%" ^| FINDSTR /I test.*unit.*\.exe$`) DO ( + ECHO . ECHO executing %%f %%f + IF ERRORLEVEL 1 EXIT /B %ERRORLEVEL% ) ENDLOCAL - From 66fd7b49ca264a3b08312f190e4f82fe943ebca6 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 08:56:39 +1000 Subject: [PATCH 33/88] feat(cstring): add run_all_automated_tests aggregate (.sh/.cmd) Add unit-then-component aggregators with mutually exclusive --unit-only / --component-only; Bash forwards other flags, CMD CALLs native category runners (no Bash). --- run_all_automated_tests.cmd | 106 +++++++++++++++++++++++++++++++++++ run_all_automated_tests.sh | 108 ++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 run_all_automated_tests.cmd create mode 100755 run_all_automated_tests.sh diff --git a/run_all_automated_tests.cmd b/run_all_automated_tests.cmd new file mode 100644 index 0000000..b6dd937 --- /dev/null +++ b/run_all_automated_tests.cmd @@ -0,0 +1,106 @@ +@echo off + +SETLOCAL + +SET SCRIPT_DIRECTORY=%~dp0 +SET SCRIPT_PATH_DOC=%~n0[%~x0] + +SET UnitOnly=0 +SET ComponentOnly=0 + +FOR %%a IN (%*) DO ( + + IF /I {--help}=={%%a} ( + + IF EXIST "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" ( + + type "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" + ) + ECHO ^ + +Runs all ^(matching^) automated test programs ^(unit and component^) ^ + +^ + +%SCRIPT_PATH_DOC% [ ... flags/options ... ] ^ + +^ + +Flags/options: ^ + + behaviour: ^ + +^ + + --unit-only ^ + + runs only unit-test programs ^ + +^ + + --component-only ^ + + runs only component-test programs ^ + +^ + + standard flags: ^ + +^ + + --help ^ + + displays this help and terminates ^ + + + EXIT /B 0 + ) ELSE IF /I {--unit-only}=={%%a} ( + + SET UnitOnly=1 + ) ELSE IF /I {--component-only}=={%%a} ( + + SET ComponentOnly=1 + ) ELSE ( + + ECHO "%SCRIPT_DIRECTORY%: unrecognised argument '%%a'; use --help for usage" 1>&2 + + EXIT /B 1 + ) +) + +IF %UnitOnly% NEQ 0 IF %ComponentOnly% NEQ 0 ( + + ECHO %SCRIPT_PATH_DOC%: --unit-only and --component-only are mutually exclusive 1>&2 + + EXIT /B 1 +) + +IF %UnitOnly% NEQ 0 ( + CALL "%SCRIPT_DIRECTORY%run_all_unit_tests.cmd" + EXIT /B %ERRORLEVEL% +) + +IF %ComponentOnly% NEQ 0 ( + CALL "%SCRIPT_DIRECTORY%run_all_component_tests.cmd" + EXIT /B %ERRORLEVEL% +) + +SET "ProjectName=" +FOR /F "usebackq delims=" %%p IN ("%SCRIPT_DIRECTORY%.sis\project_name.txt") DO SET "ProjectName=%%p" + +IF NOT DEFINED ProjectName ( + + ECHO %SCRIPT_PATH_DOC%: could not read project name from .sis\project_name.txt 1>&2 + + EXIT /B 1 +) + +ECHO Running all %ProjectName% automated test programs ^(unit and component^) + +CALL "%SCRIPT_DIRECTORY%run_all_unit_tests.cmd" +IF ERRORLEVEL 1 EXIT /B %ERRORLEVEL% + +CALL "%SCRIPT_DIRECTORY%run_all_component_tests.cmd" +EXIT /B %ERRORLEVEL% + +ENDLOCAL diff --git a/run_all_automated_tests.sh b/run_all_automated_tests.sh new file mode 100755 index 0000000..95da3c9 --- /dev/null +++ b/run_all_automated_tests.sh @@ -0,0 +1,108 @@ +#! /bin/bash + +ScriptPath=$0 +Dir=$(cd "$(dirname "$ScriptPath")" && pwd) +Basename=$(basename "$ScriptPath") +CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") + + +UnitOnly=0 +ComponentOnly=0 +ForwardedArgs=() + + +# ########################################################## +# command-line handling + +while [[ $# -gt 0 ]]; do + + case $1 in + --unit-only) + + UnitOnly=1 + ;; + --component-only) + + ComponentOnly=1 + ;; + --help) + + [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" + cat << EOF +Runs all (matching) automated test programs (unit and component) + +$ScriptPath [ ... flags/options ... ] + +Flags/options: + + behaviour: + + --unit-only + runs only unit-test programs + + --component-only + runs only component-test programs + + (all other flags are forwarded to the category runner script) + + + standard flags: + + --help + displays this help and terminates + +EOF + + exit 0 + ;; + *) + + ForwardedArgs+=("$1") + ;; + esac + + shift +done + +if [ $UnitOnly -ne 0 ] && [ $ComponentOnly -ne 0 ]; then + + >&2 echo "$ScriptPath: --unit-only and --component-only are mutually exclusive" + + exit 1 +fi + + +# ########################################################## +# main() + +status=0 + +if [ $UnitOnly -ne 0 ]; then + + "$Dir/run_all_unit_tests.sh" "${ForwardedArgs[@]}" + exit $? +fi + +if [ $ComponentOnly -ne 0 ]; then + + "$Dir/run_all_component_tests.sh" "${ForwardedArgs[@]}" + exit $? +fi + +echo "Running all ${ProjectName} automated test programs (unit and component)" + +"$Dir/run_all_unit_tests.sh" "${ForwardedArgs[@]}" +status=$? + +if [ $status -eq 0 ]; then + + "$Dir/run_all_component_tests.sh" "${ForwardedArgs[@]}" + status=$? +fi + +exit $status + + +# ############################## end of file ############################# # From b54e4c6f5bc76e5021f7537daf920518f35e285b Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 08:59:25 +1000 Subject: [PATCH 34/88] refactor(cstring): drive run_all_performance via cmake --build dialect Align with scratch helpers: SisClr_*/-A, sis_cmake_build, and test_performance* / test.performance.* discovery (print0/.exe). --- run_all_performance_tests.sh | 294 +++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100755 run_all_performance_tests.sh diff --git a/run_all_performance_tests.sh b/run_all_performance_tests.sh new file mode 100755 index 0000000..8d7add2 --- /dev/null +++ b/run_all_performance_tests.sh @@ -0,0 +1,294 @@ +#! /bin/bash + +# ########################################################## +# functions - 1 + +sis_cmake_is_truey() { + case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in + + 1|ok|on|true|yes|y) + + return 0 + ;; + *) + + return 1 + ;; + esac +} + + +# ########################################################## +# constants and variables + +Basename=$(basename "$0") +Dir=$(cd "$(dirname "$0")" && pwd) +CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +ScriptPath=$0 + +AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} +ListOnly=0 +RunMake=1 +SisUseColours=0 +Verbosity=${XTESTS_VERBOSITY:-${TEST_VERBOSITY:-3}} + + +# ########################################################## +# colours +# +# Enable when tput is available and either: +# - AlwaysUseColours is set (overrides NO_COLOR; may set TERM if +# empty/dumb), or +# - NO_COLOR is unset, stdout is a TTY, and TERM is not dumb (union of +# collect-c's "TERM set + TTY" and cstring's "TTY" — empty TERM on a +# TTY is OK). + +SisClr_Blue= +SisClr_Bold= +SisClr_Green= +SisClr_None= +SisClr_Red= +SisClr_Yellow= + +for arg in "$@"; do + + case $arg in + --always-use-colors|--always-use-colours|-A) + + AlwaysUseColours=1 + ;; + esac +done + +if command -v tput >/dev/null 2>&1; then + + if [ $AlwaysUseColours -ne 0 ]; then + + if [ -z "${TERM:-}" ] || [ "$TERM" = "dumb" ]; then + + TERM=xterm-256color + fi + + SisUseColours=1 + elif [ -z "${NO_COLOR:-}" ] && [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then + + SisUseColours=1 + fi +fi + +if [ $SisUseColours -ne 0 ]; then + + SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} + SisClr_Bold=${FD_BOLD:-$(tput bold)} + SisClr_Green=${FG_GREEN:-$(tput setaf 2)} + SisClr_None=${FD_NONE:-$(tput sgr0)} + SisClr_Red=${FG_RED:-$(tput setaf 1)} + SisClr_Yellow=${FG_YELLOW:-$(tput setaf 3)} +fi + +CMakeDirClr="${SisClr_Blue}${SisClr_Bold}${CMakeDir}${SisClr_None}" +ProjectNameClr="${SisClr_Blue}${SisClr_Bold}${ProjectName}${SisClr_None}" +ScriptPathClr="${SisClr_Blue}${SisClr_Bold}${ScriptPath}${SisClr_None}" + + +# ########################################################## +# functions - 2 + +sis_cmake_build() { + + local config="${SIS_CMAKE_CONFIG:-Release}" + local args=(--build "$CMakeDir") + if [ -f "$CMakeDir/CMakeCache.txt" ] && grep -q '^CMAKE_CONFIGURATION_TYPES:' "$CMakeDir/CMakeCache.txt" 2>/dev/null; then + + args+=(--config "$config") + fi + if [ "$#" -gt 0 ]; then + + local t + for t in "$@"; do + + args+=(--target "$t") + done + fi + + cmake "${args[@]}" +} + + +# ########################################################## +# command-line handling + +while [[ $# -gt 0 ]]; do + + case $1 in + --always-use-colors|--always-use-colours|-A) + + # AlwaysUseColours=1 - this is handled by the for loop above + ;; + --list-only|-l) + + ListOnly=1 + ;; + --no-make|-M) + + RunMake=0 + ;; + --verbosity) + + shift + Verbosity=$1 + ;; + --help) + + [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" + cat << EOF +Runs all (matching) performance-test programs + +${ScriptPath} [ ... flags/options ... ] + +Flags/options: + + behaviour: + + -A + --always-use-colors + --always-use-colours + forces use of colours even when stdout is not a TTY + + -l + --list-only + lists the target programs but does not execute them + + -M + --no-make + does not execute a build before running programs + + --verbosity + specifies an explicit verbosity (forwarded when supported) + + + standard flags: + + --help + displays this help and terminates + +EOF + + exit 0 + ;; + *) + + >&2 echo "${ScriptPathClr}: unrecognised argument '${SisClr_Red}${SisClr_Bold}$1${SisClr_None}'; use --help for usage" + + exit 1 + ;; + esac + + shift +done + + +# ########################################################## +# main() + +status=0 + +if [ $RunMake -ne 0 ]; then + + if [ $ListOnly -eq 0 ]; then + + echo + echo "Executing build of ${ProjectNameClr} (via cmake --build) and then running all performance-test programs" + + mkdir -p "$CMakeDir" || exit 1 + + if [ ! -f "$CMakeDir/CMakeCache.txt" ]; then + + >&2 echo "${ScriptPathClr}: '${CMakeDirClr}' is not configured; use prepare_cmake.sh first" + + exit 1 + fi + + sis_cmake_build + status=$? + fi +else + + if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ]; then + + >&2 echo "${ScriptPathClr}: cannot run in '--no-make' mode without a previous successful configure/build" + + exit 1 + fi +fi + +if [ $status -eq 0 ]; then + + if [ $ListOnly -ne 0 ]; then + + echo + echo "Listing all ${ProjectNameClr} performance-test programs" + else + + echo + echo "Running all ${ProjectNameClr} performance-test programs" + fi + + NumPrograms=0 + + while IFS= read -r -d '' f; do + + case "$f" in + *.pdb|*.ilk|*.log|*.obj|*.o) + continue + ;; + esac + + NumPrograms=$((NumPrograms + 1)) + + fClr="${SisClr_Blue}${SisClr_Bold}${f}${SisClr_None}" + + if [ $ListOnly -ne 0 ]; then + + echo "would execute ${fClr}:" + + 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 + + : + elif "$f"; then + + : + else + + status=$? + + break 1 + fi + done < <(find "$CMakeDir" -type f \( -name 'test_performance*' -o -name 'test.performance.*' \) \( -perm -100 -o -name '*.exe' \) -print0 2>/dev/null | sort -z) + + if [ $NumPrograms -eq 0 ]; then + + echo "${ScriptPathClr}: found no performance-test programs under '${CMakeDirClr}' (none found)" + + exit 0 + fi +fi + +exit $status + + +# ############################## end of file ############################# # From b0b25cd633d222fec5b529fccd01553edbfccdd1 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 09:02:56 +1000 Subject: [PATCH 35/88] style(cstring): SisClr polish on run_all_automated_tests.sh Add SisClr_*/-A (forwarded), coloured status/mutex messages; keep unit-then-component aggregate behaviour. --- run_all_automated_tests.sh | 92 +++++++++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 10 deletions(-) diff --git a/run_all_automated_tests.sh b/run_all_automated_tests.sh index 95da3c9..2c13b16 100755 --- a/run_all_automated_tests.sh +++ b/run_all_automated_tests.sh @@ -1,16 +1,77 @@ #! /bin/bash -ScriptPath=$0 -Dir=$(cd "$(dirname "$ScriptPath")" && pwd) -Basename=$(basename "$ScriptPath") +# ########################################################## +# constants and variables + +Basename=$(basename "$0") +Dir=$(cd "$(dirname "$0")" && pwd) CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} ProjectNameFile="$Dir/.sis/project_name.txt" ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +ScriptPath=$0 - -UnitOnly=0 +AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} ComponentOnly=0 ForwardedArgs=() +SisUseColours=0 +UnitOnly=0 + + +# ########################################################## +# colours +# +# Enable when tput is available and either: +# - AlwaysUseColours is set (overrides NO_COLOR; may set TERM if +# empty/dumb), or +# - NO_COLOR is unset, stdout is a TTY, and TERM is not dumb (union of +# collect-c's "TERM set + TTY" and cstring's "TTY" — empty TERM on a +# TTY is OK). + +SisClr_Blue= +SisClr_Bold= +SisClr_Green= +SisClr_None= +SisClr_Red= +SisClr_Yellow= + +for arg in "$@"; do + + case $arg in + --always-use-colors|--always-use-colours|-A) + + AlwaysUseColours=1 + ;; + esac +done + +if command -v tput >/dev/null 2>&1; then + + if [ $AlwaysUseColours -ne 0 ]; then + + if [ -z "${TERM:-}" ] || [ "$TERM" = "dumb" ]; then + + TERM=xterm-256color + fi + + SisUseColours=1 + elif [ -z "${NO_COLOR:-}" ] && [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then + + SisUseColours=1 + fi +fi + +if [ $SisUseColours -ne 0 ]; then + + SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} + SisClr_Bold=${FD_BOLD:-$(tput bold)} + SisClr_Green=${FG_GREEN:-$(tput setaf 2)} + SisClr_None=${FD_NONE:-$(tput sgr0)} + SisClr_Red=${FG_RED:-$(tput setaf 1)} + SisClr_Yellow=${FG_YELLOW:-$(tput setaf 3)} +fi + +ProjectNameClr="${SisClr_Blue}${SisClr_Bold}${ProjectName}${SisClr_None}" +ScriptPathClr="${SisClr_Blue}${SisClr_Bold}${ScriptPath}${SisClr_None}" # ########################################################## @@ -19,6 +80,11 @@ ForwardedArgs=() while [[ $# -gt 0 ]]; do case $1 in + --always-use-colors|--always-use-colours|-A) + + # AlwaysUseColours=1 - handled above; forward so category runners see it + ForwardedArgs+=("$1") + ;; --unit-only) UnitOnly=1 @@ -33,18 +99,23 @@ while [[ $# -gt 0 ]]; do cat << EOF Runs all (matching) automated test programs (unit and component) -$ScriptPath [ ... flags/options ... ] +${ScriptPath} [ ... flags/options ... ] Flags/options: behaviour: - --unit-only - runs only unit-test programs + -A + --always-use-colors + --always-use-colours + forces use of colours even when stdout is not a TTY --component-only runs only component-test programs + --unit-only + runs only unit-test programs + (all other flags are forwarded to the category runner script) @@ -68,7 +139,7 @@ done if [ $UnitOnly -ne 0 ] && [ $ComponentOnly -ne 0 ]; then - >&2 echo "$ScriptPath: --unit-only and --component-only are mutually exclusive" + >&2 echo "${ScriptPathClr}: ${SisClr_Red}${SisClr_Bold}--unit-only${SisClr_None} and ${SisClr_Red}${SisClr_Bold}--component-only${SisClr_None} are mutually exclusive" exit 1 fi @@ -91,7 +162,8 @@ if [ $ComponentOnly -ne 0 ]; then exit $? fi -echo "Running all ${ProjectName} automated test programs (unit and component)" +echo +echo "Running all ${ProjectNameClr} automated test programs (unit and component)" "$Dir/run_all_unit_tests.sh" "${ForwardedArgs[@]}" status=$? From dbf5e806a731b4311b67c130c38b4838043d885b Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 09:18:08 +1000 Subject: [PATCH 36/88] ci(cstring): exercise prepare/build/clean helpers in CI Split no-cpp into prepare (no -m) then build_cmake.sh; add install-smoke prepare-clean-build-clean-build on a dedicated SIS_CMAKE_BUILD_DIR tree. --- .github/workflows/ci.yml | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fef22db..35b838a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,6 +100,27 @@ jobs: mingw-w64-x86_64-cmake mingw-w64-x86_64-make + - name: Helper scripts prepare-clean-build-clean-build + shell: bash + run: | + set -euo pipefail + # Exercise prepare/clean/build helpers on a dedicated tree (does + # not replace the install-smoke configure/build below). + export SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/_build-helper-smoke" + PREPARE_ARGS=(-E -T) + if [ "${{ matrix.c_compiler }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + PREPARE_ARGS+=(--mingw) + elif [ "${{ matrix.c_compiler }}" != "cl" ]; then + export CC="${{ matrix.c_compiler }}" + export CXX="${{ matrix.cpp_compiler }}" + fi + ./prepare_cmake.sh "${PREPARE_ARGS[@]}" + ./clean_cmake.sh + ./build_cmake.sh + ./clean_cmake.sh + ./build_cmake.sh + - name: Configure shell: bash run: | @@ -250,15 +271,22 @@ jobs: echo "SIS_DEPS=$DEPS" >> "$GITHUB_ENV" - - name: Configure and build via prepare_cmake.sh --no-cpp + - name: Configure via prepare_cmake.sh --no-cpp shell: bash run: | set -euo pipefail # Prefer CMAKE_PREFIX_PATH (find_package) over prepare_cmake -s / - # -DSTLSOFT: xTests::core links STLSoft::STLSoft, which only exists - # when the STLSoft CMake package is imported. + # -DSTLSOFT: xTests::core links STLSoft::STLSoft, which only + # exists when the STLSoft CMake package is imported. + export CMAKE_PREFIX_PATH="${{ env.SIS_DEPS }}" + ./prepare_cmake.sh --no-cpp + + - name: Build via build_cmake.sh + shell: bash + run: | + set -euo pipefail export CMAKE_PREFIX_PATH="${{ env.SIS_DEPS }}" - ./prepare_cmake.sh --no-cpp -m + ./build_cmake.sh - name: Run unit tests shell: bash From c95dc2d47609189146a2b49ae527e65a69bc045c Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 09:21:47 +1000 Subject: [PATCH 37/88] ci(cstring): dogfood native run_all *.cmd on Windows cells Invoke examples/unit/component/scratch via cmd.exe on Windows; keep Bash runners on Unix; add run_all_scratch_tests.cmd; PATH MinGW from msys2. --- .github/workflows/ci-cell.yml | 52 ++++++++++++++++----- run_all_scratch_tests.cmd | 85 +++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 12 deletions(-) create mode 100644 run_all_scratch_tests.cmd diff --git a/.github/workflows/ci-cell.yml b/.github/workflows/ci-cell.yml index 4b37038..edb287b 100644 --- a/.github/workflows/ci-cell.yml +++ b/.github/workflows/ci-cell.yml @@ -215,6 +215,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup MSYS2 MinGW + id: msys2 if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' uses: msys2/setup-msys2@v2 with: @@ -229,14 +230,20 @@ jobs: path: build - name: Run examples + if: runner.os != 'Windows' shell: bash run: | set -euo pipefail - if [ "${{ inputs.c-compiler }}" = "mingw" ]; then - export PATH="/mingw64/bin:$PATH" - fi SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_examples.sh -M + - name: Run examples + if: runner.os == 'Windows' + shell: cmd + run: | + set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build + if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" + call run_all_examples.cmd + test-unit: name: Unit tests (${{ inputs.cell-id }}) needs: build @@ -246,6 +253,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup MSYS2 MinGW + id: msys2 if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' uses: msys2/setup-msys2@v2 with: @@ -260,14 +268,20 @@ jobs: path: build - name: Run unit tests + if: runner.os != 'Windows' shell: bash run: | set -euo pipefail - if [ "${{ inputs.c-compiler }}" = "mingw" ]; then - export PATH="/mingw64/bin:$PATH" - fi SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_unit_tests.sh -M --unit-only + - name: Run unit tests + if: runner.os == 'Windows' + shell: cmd + run: | + set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build + if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" + call run_all_unit_tests.cmd + test-component: name: Component tests (${{ inputs.cell-id }}) needs: build @@ -277,6 +291,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup MSYS2 MinGW + id: msys2 if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' uses: msys2/setup-msys2@v2 with: @@ -291,14 +306,20 @@ jobs: path: build - name: Run component tests + if: runner.os != 'Windows' shell: bash run: | set -euo pipefail - if [ "${{ inputs.c-compiler }}" = "mingw" ]; then - export PATH="/mingw64/bin:$PATH" - fi SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_component_tests.sh -M --component-only + - name: Run component tests + if: runner.os == 'Windows' + shell: cmd + run: | + set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build + if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" + call run_all_component_tests.cmd + test-scratch: name: Scratch tests (${{ inputs.cell-id }}) needs: build @@ -309,6 +330,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup MSYS2 MinGW + id: msys2 if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' uses: msys2/setup-msys2@v2 with: @@ -323,10 +345,16 @@ jobs: path: build - name: Run scratch tests + if: runner.os != 'Windows' shell: bash run: | set -euo pipefail - if [ "${{ inputs.c-compiler }}" = "mingw" ]; then - export PATH="/mingw64/bin:$PATH" - fi SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_scratch_tests.sh -M + + - name: Run scratch tests + if: runner.os == 'Windows' + shell: cmd + run: | + set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build + if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" + call run_all_scratch_tests.cmd diff --git a/run_all_scratch_tests.cmd b/run_all_scratch_tests.cmd new file mode 100644 index 0000000..9b05623 --- /dev/null +++ b/run_all_scratch_tests.cmd @@ -0,0 +1,85 @@ +@echo off + +SETLOCAL + +SET SCRIPT_DIRECTORY=%~dp0 +SET SCRIPT_PATH_DOC=%~n0[%~x0] + +IF DEFINED SIS_CMAKE_BUILD_DIR ( + + SET CMAKE_DIR=%SIS_CMAKE_BUILD_DIR% +) ELSE ( + + SET CMAKE_DIR=%SCRIPT_DIRECTORY%_build +) + +FOR %%a IN (%*) DO ( + + IF /I {--help}=={%%a} ( + + IF EXIST "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" ( + + type "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" + ) + ECHO ^ + +Runs all ^(matching^) scratch-test programs ^ + +^ + +%SCRIPT_PATH_DOC% [ ... flags/options ... ] ^ + +^ + +Flags/options: ^ + + behaviour: ^ + +^ + + standard flags: ^ + +^ + + --help ^ + + displays this help and terminates ^ + + + EXIT /B 0 + ) ELSE ( + + ECHO "%SCRIPT_DIRECTORY%: unrecognised argument '%%a'; use --help for usage" 1>&2 + + EXIT /B 1 + ) +) + +if NOT EXIST "%CMAKE_DIR%" ( + + ECHO "CMake build directory '%CMAKE_DIR%' does not exist" + + EXIT /B 1 +) + +SET "ProjectName=" +FOR /F "usebackq delims=" %%p IN ("%SCRIPT_DIRECTORY%.sis\project_name.txt") DO SET "ProjectName=%%p" + +IF NOT DEFINED ProjectName ( + + ECHO %SCRIPT_PATH_DOC%: could not read project name from .sis\project_name.txt 1>&2 + + EXIT /B 1 +) + +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 ( + + ECHO . + ECHO executing %%f + %%f + IF ERRORLEVEL 1 EXIT /B %ERRORLEVEL% +) + +ENDLOCAL From 7ddfd9043cf2e11ce2f56b7503d1e55780d29844 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 09:33:21 +1000 Subject: [PATCH 38/88] ci(cstring): dogfood prepare/build in cells; order test jobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace raw cmake configure/build with prepare_cmake.sh and build_cmake.sh; - add performance job/.cmd; - chain unit→component→performance→scratch→examples. --- .github/workflows/ci-cell.yml | 129 ++++++++++++++++++++-------------- run_all_performance_tests.cmd | 85 ++++++++++++++++++++++ 2 files changed, 163 insertions(+), 51 deletions(-) create mode 100644 run_all_performance_tests.cmd diff --git a/.github/workflows/ci-cell.yml b/.github/workflows/ci-cell.yml index edb287b..8b9071e 100644 --- a/.github/workflows/ci-cell.yml +++ b/.github/workflows/ci-cell.yml @@ -157,47 +157,36 @@ jobs: cmake --install "$XTBUILD" fi - - name: Configure + - name: Configure via prepare_cmake.sh shell: bash run: | set -euo pipefail - if [ "${{ inputs.c-compiler }}" = "cl" ]; then - cmake -B build -S . \ - -DCMAKE_DISABLE_FIND_PACKAGE_MFC=TRUE \ - -DCMAKE_PREFIX_PATH="${RUNNER_TEMP}/sis-deps" \ - -DBUILD_EXAMPLES=ON \ - -DBUILD_TESTING=ON - elif [ "${{ inputs.c-compiler }}" = "mingw" ]; then + export SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" + export SIS_CMAKE_CONFIG="${{ inputs.build-type }}" + export CMAKE_PREFIX_PATH="${RUNNER_TEMP}/sis-deps" + PREPARE_ARGS=() + if [ "${{ inputs.build-type }}" = "Debug" ]; then + PREPARE_ARGS+=(-d) + fi + if [ "${{ inputs.c-compiler }}" = "mingw" ]; then export PATH="/mingw64/bin:$PATH" - cmake -B build -G "MinGW Makefiles" \ - -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ - -DCMAKE_C_COMPILER=gcc \ - -DCMAKE_CXX_COMPILER=g++ \ - -DCMAKE_PREFIX_PATH="${RUNNER_TEMP}/sis-deps" \ - -DBUILD_EXAMPLES=ON \ - -DBUILD_TESTING=ON - else - cmake -B build -S . \ - -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ - -DCMAKE_C_COMPILER="${{ inputs.c-compiler }}" \ - -DCMAKE_CXX_COMPILER="${{ inputs.cpp-compiler }}" \ - -DCMAKE_PREFIX_PATH="${RUNNER_TEMP}/sis-deps" \ - -DBUILD_EXAMPLES=ON \ - -DBUILD_TESTING=ON + PREPARE_ARGS+=(--mingw) + elif [ "${{ inputs.c-compiler }}" != "cl" ]; then + export CC="${{ inputs.c-compiler }}" + export CXX="${{ inputs.cpp-compiler }}" fi + ./prepare_cmake.sh "${PREPARE_ARGS[@]}" - - name: Build + - name: Build via build_cmake.sh shell: bash run: | set -euo pipefail - if [ "${{ inputs.c-compiler }}" = "cl" ]; then - cmake --build build --config "${{ inputs.build-type }}" --parallel - elif [ "${{ inputs.c-compiler }}" = "mingw" ]; then + export SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" + export SIS_CMAKE_CONFIG="${{ inputs.build-type }}" + if [ "${{ inputs.c-compiler }}" = "mingw" ]; then export PATH="/mingw64/bin:$PATH" - cmake --build build --parallel 2 - else - cmake --build build --parallel fi + ./build_cmake.sh - name: Upload build tree uses: actions/upload-artifact@v4 @@ -206,8 +195,8 @@ jobs: path: build retention-days: 1 - run-examples: - name: Examples (${{ inputs.cell-id }}) + test-unit: + name: Unit tests (${{ inputs.cell-id }}) needs: build runs-on: ${{ inputs.os }} @@ -229,24 +218,24 @@ jobs: name: sis-build-${{ inputs.cell-id }} path: build - - name: Run examples + - name: Run unit tests if: runner.os != 'Windows' shell: bash run: | set -euo pipefail - SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_examples.sh -M + SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_unit_tests.sh -M --unit-only - - name: Run examples + - name: Run unit tests if: runner.os == 'Windows' shell: cmd run: | set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" - call run_all_examples.cmd + call run_all_unit_tests.cmd - test-unit: - name: Unit tests (${{ inputs.cell-id }}) - needs: build + test-component: + name: Component tests (${{ inputs.cell-id }}) + needs: test-unit runs-on: ${{ inputs.os }} steps: @@ -267,24 +256,24 @@ jobs: name: sis-build-${{ inputs.cell-id }} path: build - - name: Run unit tests + - name: Run component tests if: runner.os != 'Windows' shell: bash run: | set -euo pipefail - SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_unit_tests.sh -M --unit-only + SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_component_tests.sh -M --component-only - - name: Run unit tests + - name: Run component tests if: runner.os == 'Windows' shell: cmd run: | set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" - call run_all_unit_tests.cmd + call run_all_component_tests.cmd - test-component: - name: Component tests (${{ inputs.cell-id }}) - needs: build + test-performance: + name: Performance tests (${{ inputs.cell-id }}) + needs: test-component runs-on: ${{ inputs.os }} steps: @@ -305,24 +294,24 @@ jobs: name: sis-build-${{ inputs.cell-id }} path: build - - name: Run component tests + - name: Run performance tests if: runner.os != 'Windows' shell: bash run: | set -euo pipefail - SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_component_tests.sh -M --component-only + SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_performance_tests.sh -M - - name: Run component tests + - name: Run performance tests if: runner.os == 'Windows' shell: cmd run: | set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" - call run_all_component_tests.cmd + call run_all_performance_tests.cmd test-scratch: name: Scratch tests (${{ inputs.cell-id }}) - needs: build + needs: test-performance continue-on-error: true runs-on: ${{ inputs.os }} @@ -358,3 +347,41 @@ jobs: set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" call run_all_scratch_tests.cmd + + run-examples: + name: Examples (${{ inputs.cell-id }}) + needs: test-scratch + runs-on: ${{ inputs.os }} + + steps: + - uses: actions/checkout@v4 + + - name: Setup MSYS2 MinGW + id: msys2 + if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + install: mingw-w64-x86_64-toolchain + + - name: Download build tree + uses: actions/download-artifact@v4 + with: + name: sis-build-${{ inputs.cell-id }} + path: build + + - name: Run examples + if: runner.os != 'Windows' + shell: bash + run: | + set -euo pipefail + SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_examples.sh -M + + - name: Run examples + if: runner.os == 'Windows' + shell: cmd + run: | + set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build + if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" + call run_all_examples.cmd diff --git a/run_all_performance_tests.cmd b/run_all_performance_tests.cmd new file mode 100644 index 0000000..ae97766 --- /dev/null +++ b/run_all_performance_tests.cmd @@ -0,0 +1,85 @@ +@echo off + +SETLOCAL + +SET SCRIPT_DIRECTORY=%~dp0 +SET SCRIPT_PATH_DOC=%~n0[%~x0] + +IF DEFINED SIS_CMAKE_BUILD_DIR ( + + SET CMAKE_DIR=%SIS_CMAKE_BUILD_DIR% +) ELSE ( + + SET CMAKE_DIR=%SCRIPT_DIRECTORY%_build +) + +FOR %%a IN (%*) DO ( + + IF /I {--help}=={%%a} ( + + IF EXIST "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" ( + + type "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" + ) + ECHO ^ + +Runs all ^(matching^) performance-test programs ^ + +^ + +%SCRIPT_PATH_DOC% [ ... flags/options ... ] ^ + +^ + +Flags/options: ^ + + behaviour: ^ + +^ + + standard flags: ^ + +^ + + --help ^ + + displays this help and terminates ^ + + + EXIT /B 0 + ) ELSE ( + + ECHO "%SCRIPT_DIRECTORY%: unrecognised argument '%%a'; use --help for usage" 1>&2 + + EXIT /B 1 + ) +) + +if NOT EXIST "%CMAKE_DIR%" ( + + ECHO "CMake build directory '%CMAKE_DIR%' does not exist" + + EXIT /B 1 +) + +SET "ProjectName=" +FOR /F "usebackq delims=" %%p IN ("%SCRIPT_DIRECTORY%.sis\project_name.txt") DO SET "ProjectName=%%p" + +IF NOT DEFINED ProjectName ( + + ECHO %SCRIPT_PATH_DOC%: could not read project name from .sis\project_name.txt 1>&2 + + EXIT /B 1 +) + +ECHO Running all %ProjectName% performance-test programs + +FOR /F "usebackq" %%f IN (`DIR /A:-D /B /S "%CMAKE_DIR%" ^| FINDSTR /I test.*performance.*\.exe$`) DO ( + + ECHO . + ECHO executing %%f + %%f + IF ERRORLEVEL 1 EXIT /B %ERRORLEVEL% +) + +ENDLOCAL From f29cff567450938c45d1dd2b3b984cd1f65447b2 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 09:36:48 +1000 Subject: [PATCH 39/88] ci(cstring): invoke ctest, remove, and automated helpers Run ctest_cmake.sh after each cell build; extend install-smoke through remove and re-prepare/build; use run_all_automated_tests.sh in no-cpp. --- .github/workflows/ci-cell.yml | 11 +++++++++++ .github/workflows/ci.yml | 13 ++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-cell.yml b/.github/workflows/ci-cell.yml index 8b9071e..70841c7 100644 --- a/.github/workflows/ci-cell.yml +++ b/.github/workflows/ci-cell.yml @@ -188,6 +188,17 @@ jobs: fi ./build_cmake.sh + - name: CTest via ctest_cmake.sh + shell: bash + run: | + set -euo pipefail + export SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" + export SIS_CMAKE_CONFIG="${{ inputs.build-type }}" + if [ "${{ inputs.c-compiler }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + fi + ./ctest_cmake.sh -M + - name: Upload build tree uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35b838a..a6d709b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,12 +100,12 @@ jobs: mingw-w64-x86_64-cmake mingw-w64-x86_64-make - - name: Helper scripts prepare-clean-build-clean-build + - name: Helper scripts prepare-clean-build-remove-reprepare-build shell: bash run: | set -euo pipefail - # Exercise prepare/clean/build helpers on a dedicated tree (does - # not replace the install-smoke configure/build below). + # Exercise prepare/clean/build/remove helpers on a dedicated tree + # (does not replace the install-smoke configure/build below). export SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/_build-helper-smoke" PREPARE_ARGS=(-E -T) if [ "${{ matrix.c_compiler }}" = "mingw" ]; then @@ -120,6 +120,9 @@ jobs: ./build_cmake.sh ./clean_cmake.sh ./build_cmake.sh + ./remove_cmake_artefacts.sh + ./prepare_cmake.sh "${PREPARE_ARGS[@]}" + ./build_cmake.sh - name: Configure shell: bash @@ -288,8 +291,8 @@ jobs: export CMAKE_PREFIX_PATH="${{ env.SIS_DEPS }}" ./build_cmake.sh - - name: Run unit tests + - name: Run automated tests shell: bash run: | set -euo pipefail - SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/_build" ./run_all_unit_tests.sh -M --unit-only + SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/_build" ./run_all_automated_tests.sh -M From fed0f5324be8f0fcd1674d488b5a390f661094d0 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 09:43:09 +1000 Subject: [PATCH 40/88] fix(cstring): harden prepare empty-args and multi-config ctest Avoid bash 3.2 nounset failure on empty PREPARE_ARGS in ci-cell, and pass ctest -C from SIS_CMAKE_CONFIG when CMAKE_CONFIGURATION_TYPES is present so Visual Studio cells can run. --- .github/workflows/ci-cell.yml | 4 +++- ctest_cmake.sh | 14 +++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-cell.yml b/.github/workflows/ci-cell.yml index 70841c7..87b2a4d 100644 --- a/.github/workflows/ci-cell.yml +++ b/.github/workflows/ci-cell.yml @@ -164,6 +164,8 @@ jobs: export SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" export SIS_CMAKE_CONFIG="${{ inputs.build-type }}" export CMAKE_PREFIX_PATH="${RUNNER_TEMP}/sis-deps" + # Bash 3.2 (macOS) + set -u treats an empty "${arr[@]}" as + # unbound; use the ${arr[@]+…} form so Release/non-mingw is OK. PREPARE_ARGS=() if [ "${{ inputs.build-type }}" = "Debug" ]; then PREPARE_ARGS+=(-d) @@ -175,7 +177,7 @@ jobs: export CC="${{ inputs.c-compiler }}" export CXX="${{ inputs.cpp-compiler }}" fi - ./prepare_cmake.sh "${PREPARE_ARGS[@]}" + ./prepare_cmake.sh ${PREPARE_ARGS[@]+"${PREPARE_ARGS[@]}"} - name: Build via build_cmake.sh shell: bash diff --git a/ctest_cmake.sh b/ctest_cmake.sh index 4b5aaa1..4e09ee2 100755 --- a/ctest_cmake.sh +++ b/ctest_cmake.sh @@ -217,7 +217,19 @@ if [ $status -eq 0 ]; then echo echo "Running ${ProjectNameClr} CMake tests" - ctest --test-dir "$CMakeDir" --output-on-failure $CTestVerbose + # Multi-config generators (e.g. Visual Studio) need -C ; + # mirror sis_cmake_build's CMAKE_CONFIGURATION_TYPES detection. + ctest_args=(--test-dir "$CMakeDir" --output-on-failure) + if [ -n "$CTestVerbose" ]; then + + ctest_args+=("$CTestVerbose") + fi + if [ -f "$CMakeDir/CMakeCache.txt" ] && grep -q '^CMAKE_CONFIGURATION_TYPES:' "$CMakeDir/CMakeCache.txt" 2>/dev/null; then + + ctest_args+=(-C "${SIS_CMAKE_CONFIG:-Release}") + fi + + ctest "${ctest_args[@]}" status=$? fi From 9f62cae9d090c104a39e1e62f055b1573ea35eb7 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 09:45:49 +1000 Subject: [PATCH 41/88] ci(cstring): dogfood run_all_automated_tests.cmd on Windows Drive Windows unit and component cell steps through the aggregate .cmd with --unit-only / --component-only so the orchestrator and its CALL targets are exercised without duplicating the suite. --- .github/workflows/ci-cell.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-cell.yml b/.github/workflows/ci-cell.yml index 87b2a4d..39ca1ee 100644 --- a/.github/workflows/ci-cell.yml +++ b/.github/workflows/ci-cell.yml @@ -244,7 +244,8 @@ jobs: run: | set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" - call run_all_unit_tests.cmd + REM Dogfood the aggregate .cmd; it CALLs run_all_unit_tests.cmd. + call run_all_automated_tests.cmd --unit-only test-component: name: Component tests (${{ inputs.cell-id }}) @@ -282,7 +283,8 @@ jobs: run: | set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" - call run_all_component_tests.cmd + REM Dogfood the aggregate .cmd; it CALLs run_all_component_tests.cmd. + call run_all_automated_tests.cmd --component-only test-performance: name: Performance tests (${{ inputs.cell-id }}) From 6a622c227b768336b9297576440c5d67abe3096e Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 09:52:45 +1000 Subject: [PATCH 42/88] ci(cstring): collapse cell suites into one post-build test job Replace the serial unit/component/performance/scratch/examples jobs with a single Tests job of sequential helper steps so each matrix cell pays one checkout, MinGW setup, and artifact download instead of five. --- .github/workflows/ci-cell.yml | 148 ++++++---------------------------- 1 file changed, 23 insertions(+), 125 deletions(-) diff --git a/.github/workflows/ci-cell.yml b/.github/workflows/ci-cell.yml index 39ca1ee..366ea2f 100644 --- a/.github/workflows/ci-cell.yml +++ b/.github/workflows/ci-cell.yml @@ -208,8 +208,10 @@ jobs: path: build retention-days: 1 - test-unit: - name: Unit tests (${{ inputs.cell-id }}) + # One post-build job per cell: sequential helper steps (taxonomy in the + # log), one checkout / MinGW setup / artifact download — not five jobs. + test: + name: Tests (${{ inputs.cell-id }}) needs: build runs-on: ${{ inputs.os }} @@ -231,92 +233,31 @@ jobs: name: sis-build-${{ inputs.cell-id }} path: build - - name: Run unit tests + - name: Automated tests (unit then component) if: runner.os != 'Windows' shell: bash run: | set -euo pipefail - SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_unit_tests.sh -M --unit-only + SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" \ + ./run_all_automated_tests.sh -M - - name: Run unit tests + - name: Automated tests (unit then component) if: runner.os == 'Windows' shell: cmd run: | set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" - REM Dogfood the aggregate .cmd; it CALLs run_all_unit_tests.cmd. - call run_all_automated_tests.cmd --unit-only + call run_all_automated_tests.cmd - test-component: - name: Component tests (${{ inputs.cell-id }}) - needs: test-unit - runs-on: ${{ inputs.os }} - - steps: - - uses: actions/checkout@v4 - - - name: Setup MSYS2 MinGW - id: msys2 - if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW64 - update: true - install: mingw-w64-x86_64-toolchain - - - name: Download build tree - uses: actions/download-artifact@v4 - with: - name: sis-build-${{ inputs.cell-id }} - path: build - - - name: Run component tests + - name: Performance tests if: runner.os != 'Windows' shell: bash run: | set -euo pipefail - SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_component_tests.sh -M --component-only + SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" \ + ./run_all_performance_tests.sh -M - - name: Run component tests - if: runner.os == 'Windows' - shell: cmd - run: | - set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build - if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" - REM Dogfood the aggregate .cmd; it CALLs run_all_component_tests.cmd. - call run_all_automated_tests.cmd --component-only - - test-performance: - name: Performance tests (${{ inputs.cell-id }}) - needs: test-component - runs-on: ${{ inputs.os }} - - steps: - - uses: actions/checkout@v4 - - - name: Setup MSYS2 MinGW - id: msys2 - if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW64 - update: true - install: mingw-w64-x86_64-toolchain - - - name: Download build tree - uses: actions/download-artifact@v4 - with: - name: sis-build-${{ inputs.cell-id }} - path: build - - - name: Run performance tests - if: runner.os != 'Windows' - shell: bash - run: | - set -euo pipefail - SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_performance_tests.sh -M - - - name: Run performance tests + - name: Performance tests if: runner.os == 'Windows' shell: cmd run: | @@ -324,76 +265,33 @@ jobs: if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" call run_all_performance_tests.cmd - test-scratch: - name: Scratch tests (${{ inputs.cell-id }}) - needs: test-performance - continue-on-error: true - runs-on: ${{ inputs.os }} - - steps: - - uses: actions/checkout@v4 - - - name: Setup MSYS2 MinGW - id: msys2 - if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW64 - update: true - install: mingw-w64-x86_64-toolchain - - - name: Download build tree - uses: actions/download-artifact@v4 - with: - name: sis-build-${{ inputs.cell-id }} - path: build - - - name: Run scratch tests + - name: Scratch tests if: runner.os != 'Windows' + continue-on-error: true shell: bash run: | set -euo pipefail - SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_scratch_tests.sh -M + SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" \ + ./run_all_scratch_tests.sh -M - - name: Run scratch tests + - name: Scratch tests if: runner.os == 'Windows' + continue-on-error: true shell: cmd run: | set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" call run_all_scratch_tests.cmd - run-examples: - name: Examples (${{ inputs.cell-id }}) - needs: test-scratch - runs-on: ${{ inputs.os }} - - steps: - - uses: actions/checkout@v4 - - - name: Setup MSYS2 MinGW - id: msys2 - if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW64 - update: true - install: mingw-w64-x86_64-toolchain - - - name: Download build tree - uses: actions/download-artifact@v4 - with: - name: sis-build-${{ inputs.cell-id }} - path: build - - - name: Run examples + - name: Examples if: runner.os != 'Windows' shell: bash run: | set -euo pipefail - SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_examples.sh -M + SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" \ + ./run_all_examples.sh -M - - name: Run examples + - name: Examples if: runner.os == 'Windows' shell: cmd run: | From 57b648630ac49ebc4575ac2d706150fcdbee975c Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 10:00:48 +1000 Subject: [PATCH 43/88] docs(cstring): target 4.0.16 release for 23 September 2026 Align CHANGES/NEWS to today, bump Doxyfile PROJECT_NUMBER, and refresh cstring.h Updated/edit counters; package CSTRING_VER_* already at 4.0.16. --- CHANGES.md | 10 +++++++++- Doxyfile | 2 +- NEWS.md | 2 +- include/cstring/cstring.h | 6 +++--- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f9697ba..b036e90 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,18 @@ # cstring - Changes -## 4.0.16 - 22nd September 2026 +## 4.0.16 - 23rd September 2026 * Applied **misc-dev-scripts** **0.6.0** editor/Git/`.sis` drop-in templates on **boilerplate**; * Restored historical **.gitignore** patterns as a sorted union with **misc-dev-scripts** gold section layout; +* Use computed `CSTRING_VER` with documented `CSTRING_VER_PATCH` / `CSTRING_VER_ALPHABETA` in **cstring.h**; +* Added **test.scratch.versions** (canonical scratch versions reporter; formerly **libver**); +* Modernised CMake helpers to the Phase 4b dialect (`SisClr_*` / `-A`, `sis_cmake_build`, no MinGW-from-`MSYSTEM`); +* Native Windows **`run_all_*.cmd`** runners (no Bash wrap); aggregate **`run_all_automated_tests.*`**; +* **`SIS_EXAMPLE_SMOKE`** gates the interactive **example.c.cstring_vector** no-arg demo in CI; +* CI dogfoods **`prepare_cmake.sh`** / **`build_cmake.sh`** / **`ctest_cmake.sh`** / helper smoke and Windows **`.cmd`**; +* Collapsed cell suites into one post-build **Tests** job (sequential helpers); +* **`ctest_cmake.sh`** passes **`ctest -C`** on multi-config generators; ## 4.0.15 - 6th September 2026 diff --git a/Doxyfile b/Doxyfile index 0a6dc6e..bf31d8c 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.0.15 +PROJECT_NUMBER = 4.0.16 # 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 03b7d2c..5d68546 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): | -| 22nd September 2026 | Release of [cstring 4.0.16](https://github.com/synesissoftware/cstring/releases/tag/4.0.16) | drop-in templates, `PREFIX_VER`, **versions** | +| 23rd 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 | | 6th September 2026 | Release of [cstring 4.0.15](https://github.com/synesissoftware/cstring/releases/tag/4.0.15) | Doxygen `\param`/`\retval` 76-rule | | 9th August 2026 | Release of [cstring 4.0.14](https://github.com/synesissoftware/cstring/releases/tag/4.0.14) | self-assignment safety | | 7th August 2026 | Release of [cstring 4.0.14-alpha1](https://github.com/synesissoftware/cstring/releases/tag/4.0.14-alpha1) | | diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index 4230baf..919e719 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: 22nd September 2026 + * Updated: 23rd September 2026 * * Home: http://synesis.com.au/software/ * @@ -55,8 +55,8 @@ #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION # define CSTRING_VER_CSTRING_H_CSTRING_MAJOR 3 # define CSTRING_VER_CSTRING_H_CSTRING_MINOR 11 -# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 13 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 85 +# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 14 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 86 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR From 47669c50aa7d2f0fa608c4623069f5df8dacaeb3 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 10:30:35 +1000 Subject: [PATCH 44/88] feat(cstring): add competitive performance tests for 4.0.17 Add test.performance.cstring and test.performance.cstring_vector with std::string / vector and hand-rolled baselines; optional p99 percentiles (and file_lines vs cstring_vector_readLines when linked); --no-p99 / NO_P99; --gap-groups / SIS_PERFTESTS_GROUPGAPS; CSTRING_EXTERN_C on the cstring.vector API. --- CHANGES.md | 10 + CMakeLists.txt | 32 +- INSTALL.md | 1 + NEWS.md | 1 + README.md | 3 + TODO.md | 7 +- cmake/TargetMacros.cmake | 55 ++ include/cstring/cstring.h | 7 +- include/cstring/cstring.vector.h | 12 +- prepare_cmake.sh | 11 + run_all_performance_tests.cmd | 11 + run_all_performance_tests.sh | 14 + test/performance/CMakeLists.txt | 10 +- test/performance/perf_harness.hpp | 705 ++++++++++++++++++ .../test.performance.cstring/CMakeLists.txt | 1 + .../test.performance.cstring/main.cpp | 551 ++++++++++++++ .../CMakeLists.txt | 1 + .../test.performance.cstring_vector/main.cpp | 500 +++++++++++++ 18 files changed, 1921 insertions(+), 11 deletions(-) create mode 100644 test/performance/perf_harness.hpp create mode 100644 test/performance/test.performance.cstring/CMakeLists.txt create mode 100644 test/performance/test.performance.cstring/main.cpp create mode 100644 test/performance/test.performance.cstring_vector/CMakeLists.txt create mode 100644 test/performance/test.performance.cstring_vector/main.cpp diff --git a/CHANGES.md b/CHANGES.md index b036e90..94c8717 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,16 @@ # cstring - Changes +## 4.0.17 - 24th September 2026 + +* Added competitive performance tests for **cstring** and **cstring_vector** (`test.performance.cstring`, `test.performance.cstring_vector`); +* Baselines: `std::string` / hand-rolled `realloc` for strings; `std::vector` / hand-rolled for vectors; +* Optional **p99** linkage for per-iteration percentiles throughout; filesystem `platformstl::file_lines` vs `cstring_vector_readLines` suite gated on **p99**; +* **`--no-p99`** / `NO_P99` to skip recognising **p99** (same pattern as **`--no-shwild`**); +* **`--gap-groups`** on **run_all_performance_tests.sh** sets `SIS_PERFTESTS_GROUPGAPS` for blank lines between scenario groups; +* Declared **cstring.vector** API with `CSTRING_EXTERN_C` for C++ linkage; + + ## 4.0.16 - 23rd September 2026 * Applied **misc-dev-scripts** **0.6.0** editor/Git/`.sis` drop-in templates on **boilerplate**; diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d5f13b..1bd80f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ # Purpose: Top-level CMake lists file for cstring # # Created: 21st December 2023 -# Updated: 6th August 2026 +# Updated: 23rd September 2026 # # ######################################################################## # @@ -119,6 +119,10 @@ option(BUILD_TESTING "Build tests" ON) option(NO_CSTRING_CPP_API "Disable C++ API (and associated C++ examples/tests)" OFF) +option(NO_P99 "Do not recognise p99 for performance tests" OFF) + +option(NO_SHWILD "Do not recognise shwild for xTests pattern-match assertions" OFF) + # ###################################### # features @@ -132,6 +136,9 @@ option(NO_CSTRING_CPP_API "Disable C++ API (and associated C++ examples/tests)" # - xTests - required for testing; # # optional: +# - p99 - optional for performance tests: per-iteration percentiles +# throughout when found; also gates the filesystem file_lines vs +# cstring_vector_readLines suite (unless NO_P99); # - shwild - for enhanced match constructs in xTests (unless NO_SHWILD); # # note: NO_CSTRING_CPP_API skips C++ examples and remaining C++ tests; C @@ -215,6 +222,29 @@ if(BUILD_TESTING) endif(BUILD_TESTING) +# ############################ +# p99 (optional; performance tests) + +if(BUILD_TESTING) + + if(NOT (NO_P99)) + + find_package(p99 QUIET) + + if(p99_FOUND) + + message("-- CMake package p99 found (version ${p99_VERSION})") + else() + + message("-- CMake package p99 not found; performance percentiles and file_lines suite disabled") + endif() + else() + + message("-- p99 recognition disabled (NO_P99); performance percentiles and file_lines suite disabled") + endif() +endif(BUILD_TESTING) + + # ################################################ # includes - 2 diff --git a/INSTALL.md b/INSTALL.md index d0c9b93..8e52c02 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -53,6 +53,7 @@ The primary choice for installation is by use of **CMake**. * `--no-cpp` / `-C` — omit C++ examples and remaining C++ tests (`NO_CSTRING_CPP_API`); + * `--no-p99` — do not recognise **p99** (`NO_P99`); * `--no-shwild` — do not recognise **shwild** (`NO_SHWILD`); * `--disable-examples` / `-E` — omit examples (`BUILD_EXAMPLES=OFF`); * `--disable-testing` / `-T` — omit tests (`BUILD_TESTING=OFF`); diff --git a/NEWS.md b/NEWS.md index 5d68546..5ec9787 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,7 @@ | Date | News Item | Details | | ------------------- | -------------------------------- | ------- | | Available from [**cstring** project on GitHub](https://synesissoftware.com/cstring): | +| 24th September 2026 | Release of [cstring 4.0.17](https://github.com/synesissoftware/cstring/releases/tag/4.0.17) | Competitive perf tests; optional **p99** | | 23rd 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 | | 6th September 2026 | Release of [cstring 4.0.15](https://github.com/synesissoftware/cstring/releases/tag/4.0.15) | Doxygen `\param`/`\retval` 76-rule | | 9th August 2026 | Release of [cstring 4.0.14](https://github.com/synesissoftware/cstring/releases/tag/4.0.14) | self-assignment safety | diff --git a/README.md b/README.md index 080d353..41508f0 100644 --- a/README.md +++ b/README.md @@ -172,10 +172,13 @@ The **C** API has no non-standard dependencies. | ---------- | ---- | --------- | | [**STLSoft**](https://github.com/synesissoftware/STLSoft) 1.11 | Test headers / remaining C++ tests and examples | ⚪ Tests only (`BUILD_TESTING`) | | [**xTests**](https://github.com/synesissoftware/xTests) (≥ 0.26) | Unit / component / scratch tests | ⚪ Tests only (`BUILD_TESTING`) | +| [**p99**](https://github.com/synesissoftware/p99) | Percentiles in performance tests; file_lines suite | ⚪ Optional; tests only (unless `NO_P99` / `--no-p99`) | | [**shwild**](https://github.com/synesissoftware/shwild) | Enhanced pattern-match assertions in **xTests** | ⚪ Optional; tests only (unless `NO_SHWILD` / `--no-shwild`) | When supplying `'--no-cpp'` to **prepare_cmake.sh** — sets the CMake option `NO_CSTRING_CPP_API=ON` — C++ examples and remaining C++ tests are omitted; the **C** unit-tests still require **STLSoft** and **xTests**. +When supplying `'--no-p99'` — sets `NO_P99=ON` — **p99** is not recognised; performance tests still build but omit percentiles and the filesystem file_lines suite. + When supplying `'--no-shwild'` — sets `NO_SHWILD=ON` — **shwild** is not recognised and pattern-match assertions are compiled out; other unit-tests still run. diff --git a/TODO.md b/TODO.md index f5f5309..97a3965 100644 --- a/TODO.md +++ b/TODO.md @@ -18,9 +18,10 @@ ## Performance improvements -* [ ] Performance testing and optimisation: - * [ ] Quantify performance; - * [ ] Compare with other popular implementations; +* [x] ~~~Performance testing and optimisation:~~~ - ✅: + * [x] ~~~Quantify performance~~~ - ✅; + * [x] ~~~Compare with other popular implementations~~~ - ✅; +* [ ] Analyse performance results and optimise hot paths; ## Packaging improvements diff --git a/cmake/TargetMacros.cmake b/cmake/TargetMacros.cmake index 8981dbc..8e925cc 100644 --- a/cmake/TargetMacros.cmake +++ b/cmake/TargetMacros.cmake @@ -94,5 +94,60 @@ function(define_example_program program_name entry_point_source_name) endfunction(define_example_program) +# Performance programs are built for human attention (see test-standards). +# They are discovered by run_all_performance_tests.sh and must NOT be +# registered with CTest (unlike define_automated_test_program). + +function(define_performance_test_program program_name entry_point_source_name) + + add_executable(${program_name} + ${entry_point_source_name} + ) + + set(_p99_link_lib_) + + if(p99_FOUND) + + if(TARGET p99::p99) + + set(_p99_link_lib_ p99::p99) + elseif(TARGET p99::p99_static) + + set(_p99_link_lib_ p99::p99_static) + elseif(TARGET p99::p99_shared) + + set(_p99_link_lib_ p99::p99_shared) + endif() + endif() + + target_link_libraries(${program_name} + PRIVATE + core + $<$:STLSoft::STLSoft> + ${_p99_link_lib_} + ) + + if(_p99_link_lib_) + + target_compile_definitions(${program_name} + PRIVATE + HAS_P99 + ) + endif() + + unset(_p99_link_lib_) + + if(WIN32) + + target_link_libraries(${program_name} + PRIVATE + wininet + ) + endif(WIN32) + + define_target_compile_options(${program_name}) +endfunction(define_performance_test_program) + + # ############################## end of file ############################# # diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index 919e719..49ee3f2 100644 --- a/include/cstring/cstring.h +++ b/include/cstring/cstring.h @@ -55,8 +55,8 @@ #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION # define CSTRING_VER_CSTRING_H_CSTRING_MAJOR 3 # define CSTRING_VER_CSTRING_H_CSTRING_MINOR 11 -# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 14 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 86 +# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 15 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 87 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR @@ -116,6 +116,7 @@ # define CSTRING_VER_4_0_14 0x04000eff # define CSTRING_VER_4_0_15 0x04000fff # define CSTRING_VER_4_0_16 0x040010ff +# define CSTRING_VER_4_0_17 0x040011ff #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR @@ -136,7 +137,7 @@ #define CSTRING_VER_MAJOR 4 #define CSTRING_VER_MINOR 0 -#define CSTRING_VER_PATCH 16 +#define CSTRING_VER_PATCH 17 #define CSTRING_VER_ALPHABETA 0xFF #define CSTRING_VER \ diff --git a/include/cstring/cstring.vector.h b/include/cstring/cstring.vector.h index 7a0511f..8afe3b2 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: 6th September 2026 + * Updated: 23rd September 2026 * * Home: http://synesis.com.au/software/ * @@ -54,8 +54,8 @@ #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION # define CSTRING_VER_CSTRING_H_VECTOR_MAJOR 1 # define CSTRING_VER_CSTRING_H_VECTOR_MINOR 0 -# define CSTRING_VER_CSTRING_H_VECTOR_REVISION 3 -# define CSTRING_VER_CSTRING_H_VECTOR_EDIT 9 +# define CSTRING_VER_CSTRING_H_VECTOR_REVISION 4 +# define CSTRING_VER_CSTRING_H_VECTOR_EDIT 10 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ @@ -113,6 +113,7 @@ typedef struct cstring_vector_t cstring_vector_t; * * \pre (NULL != pcsv) */ +CSTRING_EXTERN_C CSTRING_RC cstring_vector_init( cstring_vector_t* pcsv @@ -131,6 +132,7 @@ cstring_vector_init( * * \pre (NULL != pcsv) */ +CSTRING_EXTERN_C CSTRING_RC cstring_vector_destroy( cstring_vector_t* pcsv @@ -151,6 +153,7 @@ cstring_vector_destroy( * * \pre (NULL != pcsv) */ +CSTRING_EXTERN_C CSTRING_RC cstring_vector_truncate( cstring_vector_t* pcsv @@ -161,6 +164,7 @@ cstring_vector_truncate( * * \pre (NULL != pcsv) */ +CSTRING_EXTERN_C CSTRING_RC cstring_vector_create( cstring_vector_t* pcsv @@ -182,6 +186,7 @@ cstring_vector_create( * \pre (NULL != pcsv) * \pre (position <= pcsv->len) */ +CSTRING_EXTERN_C CSTRING_RC cstring_vector_insertAt( cstring_vector_t* pcsv @@ -199,6 +204,7 @@ cstring_vector_insertAt( * * \pre (NULL != pcsv) */ +CSTRING_EXTERN_C CSTRING_RC cstring_vector_readLines( FILE* stm diff --git a/prepare_cmake.sh b/prepare_cmake.sh index d98b919..d067722 100755 --- a/prepare_cmake.sh +++ b/prepare_cmake.sh @@ -35,6 +35,7 @@ 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 @@ -167,6 +168,10 @@ while [[ $# -gt 0 ]]; do NO_cxx=1 ;; + --no-p99) + + NO_p99=1 + ;; --no-shwild) NO_shwild=1 @@ -230,6 +235,10 @@ Flags/options: omits the C++ API, C++ examples, and remaining C++ tests (CMake NO_CSTRING_CPP_API). C unit-tests still require STLSoft and xTests + --no-p99 + prevents recognising p99 library (NO_P99=ON); performance tests + then omit percentiles and the file_lines suite + --no-shwild prevents recognising shwild library (NO_SHWILD=ON); xTests pattern-match assertions are then unavailable @@ -278,6 +287,7 @@ if [ $BuildSharedLibs -eq 0 ]; then CMakeBuildSharedLibsFlag="OFF" ; else CMakeB if [ $ExamplesDisabled -eq 0 ]; then CMakeBuildExamplesFlag="ON" ; else CMakeBuildExamplesFlag="OFF" ; fi if [ $MSVC_MT -eq 0 ]; then CMakeMsvcMtFlag="OFF" ; else CMakeMsvcMtFlag="ON" ; fi if [ $NO_cxx -eq 0 ]; then CMakeNoCppApiFlag="OFF" ; else CMakeNoCppApiFlag="ON" ; fi +if [ $NO_p99 -eq 0 ]; then CMakeNoP99="OFF" ; else CMakeNoP99="ON" ; fi if [ $NO_shwild -eq 0 ]; then CMakeNoShwild="OFF" ; else CMakeNoShwild="ON" ; fi if [ -z "$STLSoftDirGiven" ]; then CMakeSTLSoftVariable="" ; else CMakeSTLSoftVariable="-DSTLSOFT=$STLSoftDirGiven/" ; fi if [ $TestingDisabled -eq 0 ]; then CMakeBuildTestingFlag="ON" ; else CMakeBuildTestingFlag="OFF" ; fi @@ -307,6 +317,7 @@ cmake \ -DCMAKE_VERBOSE_MAKEFILE:BOOL=$CMakeVerboseMakefileFlag \ -DMSVC_USE_MT:BOOL=$CMakeMsvcMtFlag \ -DNO_CSTRING_CPP_API:BOOL=$CMakeNoCppApiFlag \ + -DNO_P99:BOOL=$CMakeNoP99 \ -DNO_SHWILD:BOOL=$CMakeNoShwild \ $CMakeSTLSoftVariable \ "${CMakeGeneratorArgs[@]}" \ diff --git a/run_all_performance_tests.cmd b/run_all_performance_tests.cmd index ae97766..adb8746 100644 --- a/run_all_performance_tests.cmd +++ b/run_all_performance_tests.cmd @@ -35,6 +35,14 @@ Flags/options: ^ behaviour: ^ +^ + + --gap-groups ^ + + sets SIS_PERFTESTS_GROUPGAPS=1 so performance programs emit a ^ + + blank line between scenario groups ^ + ^ standard flags: ^ @@ -47,6 +55,9 @@ Flags/options: ^ EXIT /B 0 + ) ELSE IF /I {--gap-groups}=={%%a} ( + + SET SIS_PERFTESTS_GROUPGAPS=1 ) ELSE ( ECHO "%SCRIPT_DIRECTORY%: unrecognised argument '%%a'; use --help for usage" 1>&2 diff --git a/run_all_performance_tests.sh b/run_all_performance_tests.sh index 8d7add2..077d839 100755 --- a/run_all_performance_tests.sh +++ b/run_all_performance_tests.sh @@ -29,6 +29,7 @@ ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") ScriptPath=$0 AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} +GapGroups=0 ListOnly=0 RunMake=1 SisUseColours=0 @@ -127,6 +128,10 @@ while [[ $# -gt 0 ]]; do # AlwaysUseColours=1 - this is handled by the for loop above ;; + --gap-groups) + + GapGroups=1 + ;; --list-only|-l) ListOnly=1 @@ -157,6 +162,10 @@ Flags/options: --always-use-colours forces use of colours even when stdout is not a TTY + --gap-groups + sets SIS_PERFTESTS_GROUPGAPS=1 so performance programs emit a blank + line between scenario groups + -l --list-only lists the target programs but does not execute them @@ -193,6 +202,11 @@ done # ########################################################## # main() +if [ $GapGroups -ne 0 ]; then + + export SIS_PERFTESTS_GROUPGAPS=1 +fi + status=0 if [ $RunMake -ne 0 ]; then diff --git a/test/performance/CMakeLists.txt b/test/performance/CMakeLists.txt index 5d84725..15d78ed 100644 --- a/test/performance/CMakeLists.txt +++ b/test/performance/CMakeLists.txt @@ -1,2 +1,10 @@ -# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +# Performance tests (human attention; not CTest-gated) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +add_subdirectory(test.performance.cstring) +add_subdirectory(test.performance.cstring_vector) + + +# ############################## end of file ############################# # diff --git a/test/performance/perf_harness.hpp b/test/performance/perf_harness.hpp new file mode 100644 index 0000000..afa69cf --- /dev/null +++ b/test/performance/perf_harness.hpp @@ -0,0 +1,705 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/performance/perf_harness.hpp + * + * Purpose: Shared helpers for cstring performance programs. + * + * Created: 23rd September 2026 + * Updated: 23rd September 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +#ifndef CSTRING_TEST_PERFORMANCE_PERF_HARNESS_HPP_INCLUDED +#define CSTRING_TEST_PERFORMANCE_PERF_HARNESS_HPP_INCLUDED + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include +#include +#include + +#ifdef HAS_P99 +# include +#endif + +#include +#include +#include +#include +#include +#include + +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * types + */ + +typedef stlsoft::std_chrono_hrc_stopwatch stopwatch_t; +typedef stopwatch_t::interval_type interval_t; + + +/* ///////////////////////////////////////////////////////////////////////// + * configuration + */ + +namespace cstring_perf { + +inline +std::size_t +env_size_t( + char const* name +, std::size_t default_value +) +{ + char const* const env = std::getenv(name); + + if (NULL == env || '\0' == *env) + { + return default_value; + } + + char* end = NULL; + unsigned long const v = std::strtoul(env, &end, 10); + + if (end == env || NULL == end || '\0' != *end || 0 == v) + { + return default_value; + } + + return static_cast(v); +} + +inline +std::size_t +default_iterations() +{ +#ifdef NDEBUG + + return env_size_t("CSTRING_PERF_ITERATIONS", 100000u); +#else + +return env_size_t("CSTRING_PERF_ITERATIONS", 1000u); +#endif +} + +inline +std::size_t +default_warmups() +{ + return env_size_t("CSTRING_PERF_WARMUPS", 2u); +} + +inline +std::size_t +default_file_trials() +{ +#ifdef NDEBUG + + return env_size_t("CSTRING_PERF_FILE_TRIALS", 200u); +#else + + return env_size_t("CSTRING_PERF_FILE_TRIALS", 40u); +#endif +} + + +/* ///////////////////////////////////////////////////////////////////////// + * payloads + */ + +inline +std::string +make_payload( + std::size_t n +) +{ + if (0 == n) + { + return std::string(); + } + + std::string s(n, 'x'); + + for (std::size_t i = 0; n != i; ++i) + { + s[i] = static_cast('a' + static_cast(i % 26)); + } + + return s; +} + + +/* ///////////////////////////////////////////////////////////////////////// + * display + */ + +template +inline +std::string +thousands( + T_integer const& v +) +{ + char dest[41]; + size_t n = stlsoft::format_thousands(dest, STLSOFT_NUM_ELEMENTS(dest), "3;0", v); + + return std::string(dest, n > 0 ? n - 1 : 0); +} + +inline +void +display_banner( + char const* program_name +) +{ + std::cout + << program_name + << ": competitive performance timings (human attention; not a CI gate)." + << std::endl + << " Note: std::string typically has SSO; cstring is heap-oriented —" + << std::endl + << " small sizes favour std::string. Prefer Release builds." + << std::endl +#ifdef HAS_P99 + << " p99: available — per-op percentiles reported where timed." + << std::endl + << " Env: CSTRING_PERF_ITERATIONS, CSTRING_PERF_WARMUPS" + << ", CSTRING_PERF_FILE_TRIALS" + << ", SIS_PERFTESTS_GROUPGAPS." +#else /* ? HAS_P99 */ + << " p99: not linked — mean ns/op only; file_lines suite skipped." + << std::endl + << " Env: CSTRING_PERF_ITERATIONS, CSTRING_PERF_WARMUPS" + << ", SIS_PERFTESTS_GROUPGAPS." +#endif /* HAS_P99 */ + << std::endl + ; +} +#ifdef HAS_P99 + +inline +void +append_percentile_columns( + p99::histogram const* h +) +{ + uint64_t p50 = 0; + uint64_t p90 = 0; + uint64_t p99v = 0; + uint64_t maxv = 0; + + if (NULL != h && !h->empty()) + { + (void)h->try_get_value_at_p50(&p50); + (void)h->try_get_value_at_percentile(90.0, &p90); + (void)h->try_get_value_at_p99(&p99v); + (void)h->try_get_max_event_time(&maxv); + } + + std::cout + << '\t' << std::setw(10) << std::right << thousands(p50) + << '\t' << std::setw(10) << std::right << thousands(p90) + << '\t' << std::setw(10) << std::right << thousands(p99v) + << '\t' << std::setw(10) << std::right << thousands(maxv) + ; +} +#endif /* HAS_P99 */ + +inline +bool +env_is_truey( + char const* name +) +{ + char const* const env = std::getenv(name); + + if (NULL == env || '\0' == *env) + { + return false; + } + + if (0 == ::strcmp(env, "1") || + 0 == ::strcmp(env, "ok") || + 0 == ::strcmp(env, "on") || + 0 == ::strcmp(env, "true") || + 0 == ::strcmp(env, "yes") || + 0 == ::strcmp(env, "y") || + 0 == ::strcmp(env, "OK") || + 0 == ::strcmp(env, "ON") || + 0 == ::strcmp(env, "TRUE") || + 0 == ::strcmp(env, "YES") || + 0 == ::strcmp(env, "Y")) + { + return true; + } + + return false; +} + +inline +bool +group_gaps_enabled() +{ + return env_is_truey("SIS_PERFTESTS_GROUPGAPS"); +} + +inline +void +maybe_emit_group_gap( + char const* scenario +, std::size_t size +) +{ + if (!group_gaps_enabled()) + { + return; + } + + static bool have_prev = false; + static std::string prev_scenario; + static std::size_t prev_size = 0; + + if (have_prev && + (prev_scenario != scenario || prev_size != size)) + { + std::cout << std::endl; + } + + prev_scenario = scenario; + prev_size = size; + have_prev = true; +} + +inline +void +display_results_title() +{ + std::cout + << '\t' + << std::setw(40) << std::left << "scenario" + << '\t' + << std::setw(12) << std::right << "size" + << '\t' + << std::setw(24) << std::left << "impl" + << '\t' + << std::setw(12) << std::right << "#iters" + << '\t' + << std::setw(10) << std::right << "#acts" + << '\t' + << std::setw(14) << std::right << "tm (ns)" + << '\t' + << std::setw(12) << std::right << "ns/op" + << '\t' + << std::setw(10) << std::right << "vs cstr" +#ifdef HAS_P99 + << '\t' + << std::setw(10) << std::right << "p50" + << '\t' + << std::setw(10) << std::right << "p90" + << '\t' + << std::setw(10) << std::right << "p99" + << '\t' + << std::setw(10) << std::right << "max" +#endif /* HAS_P99 */ + << '\t' + << std::setw(14) << std::right << "anchor" + << std::endl + ; +} + +inline +void +display_results( + char const* scenario +, std::size_t size +, char const* impl +, std::size_t num_iterations +, std::size_t num_actions +, interval_t tm_ns +, double ratio_vs_cstring +, std::uint64_t anchor_value +#ifdef HAS_P99 +, p99::histogram const* hist = NULL +#endif /* HAS_P99 */ +) +{ + maybe_emit_group_gap(scenario, size); + + std::size_t const denom = + (0 == num_iterations || 0 == num_actions) + ? 1u + : (num_iterations * num_actions) + ; + + std::cout + << '\t' + << std::setw(40) << std::left << scenario + << '\t' + << std::setw(12) << std::right << size + << '\t' + << std::setw(24) << std::left << impl + << '\t' + << std::setw(12) << std::right << num_iterations + << '\t' + << std::setw(10) << std::right << num_actions + << '\t' + << std::setw(14) << std::right << thousands(tm_ns) + << '\t' + << std::setw(12) << std::right << thousands(tm_ns / denom) + << '\t' + ; + + if (ratio_vs_cstring < 0.0) + { + std::cout << std::setw(10) << std::right << "-"; + } + else + { + std::cout + << std::setw(10) << std::right + << std::fixed << std::setprecision(2) << ratio_vs_cstring + ; + } +#ifdef HAS_P99 + + append_percentile_columns(hist); +#endif /* HAS_P99 */ + + std::cout + << '\t' + << std::setw(14) << std::right << anchor_value + << std::endl + ; +} + +inline +double +ratio_or_dash( + interval_t subject_ns +, interval_t baseline_ns +) +{ + if (0 == baseline_ns) + { + return -1.0; + } + + return static_cast(subject_ns) / static_cast(baseline_ns); +} + + +/* ///////////////////////////////////////////////////////////////////////// + * hand-rolled string (competitive floor) + */ + +struct raw_string +{ + char* ptr; + std::size_t len; + std::size_t capacity; +}; + +inline +void +raw_init( + raw_string* s +) +{ + s->ptr = NULL; + s->len = 0; + s->capacity = 0; +} + +inline +void +raw_destroy( + raw_string* s +) +{ + std::free(s->ptr); + raw_init(s); +} + +inline +int +raw_reserve( + raw_string* s +, std::size_t capacity +) +{ + if (capacity <= s->capacity && NULL != s->ptr) + { + return 0; + } + + char* const p = static_cast(std::realloc(s->ptr, capacity + 1u)); + + if (NULL == p) + { + return -1; + } + + s->ptr = p; + s->capacity = capacity; + s->ptr[s->len] = '\0'; + + return 0; +} + +inline +int +raw_assign_len( + raw_string* s +, char const* src +, std::size_t n +) +{ + if (0 != raw_reserve(s, n)) + { + return -1; + } + + if (0 != n) + { + std::memcpy(s->ptr, src, n); + } + + s->len = n; + s->ptr[n] = '\0'; + + return 0; +} + +inline +int +raw_append_len( + raw_string* s +, char const* src +, std::size_t n +) +{ + std::size_t const need = s->len + n; + + if (0 != raw_reserve(s, need)) + { + return -1; + } + + if (0 != n) + { + std::memcpy(s->ptr + s->len, src, n); + } + + s->len = need; + s->ptr[need] = '\0'; + + return 0; +} + +inline +int +raw_insert_len( + raw_string* s +, std::size_t pos +, char const* src +, std::size_t n +) +{ + if (pos > s->len) + { + pos = s->len; + } + + std::size_t const need = s->len + n; + + if (0 != raw_reserve(s, need)) + { + return -1; + } + + if (0 != n) + { + std::memmove(s->ptr + pos + n, s->ptr + pos, s->len - pos); + std::memcpy(s->ptr + pos, src, n); + } + + s->len = need; + s->ptr[need] = '\0'; + + return 0; +} + +inline +int +raw_copy( + raw_string* dest +, raw_string const* src +) +{ + return raw_assign_len(dest, src->ptr, src->len); +} + + +/* ///////////////////////////////////////////////////////////////////////// + * hand-rolled string vector (competitive floor) + */ + +struct raw_string_vector +{ + char** ptr; + std::size_t len; + std::size_t capacity; +}; + +inline +void +raw_vec_init( + raw_string_vector* v +) +{ + v->ptr = NULL; + v->len = 0; + v->capacity = 0; +} + +inline +void +raw_vec_destroy( + raw_string_vector* v +) +{ + if (NULL != v->ptr) + { + for (std::size_t i = 0; v->len != i; ++i) + { + std::free(v->ptr[i]); + } + + std::free(v->ptr); + } + + raw_vec_init(v); +} + +inline +int +raw_vec_reserve( + raw_string_vector* v +, std::size_t capacity +) +{ + if (capacity <= v->capacity) + { + return 0; + } + + char** const p = static_cast( + std::realloc(v->ptr, capacity * sizeof(char*)) + ); + + if (NULL == p) + { + return -1; + } + + v->ptr = p; + v->capacity = capacity; + + return 0; +} + +inline +int +raw_vec_append_cstr( + raw_string_vector* v +, char const* s +, std::size_t n +) +{ + if (v->len == v->capacity) + { + std::size_t const nc = + (0 == v->capacity) ? 8u : (v->capacity * 2u) + ; + + if (0 != raw_vec_reserve(v, nc)) + { + return -1; + } + } + + char* const copy = static_cast(std::malloc(n + 1u)); + + if (NULL == copy) + { + return -1; + } + + if (0 != n) + { + std::memcpy(copy, s, n); + } + + copy[n] = '\0'; + v->ptr[v->len++] = copy; + + return 0; +} + +inline +int +raw_vec_prepend_cstr( + raw_string_vector* v +, char const* s +, std::size_t n +) +{ + if (v->len == v->capacity) + { + std::size_t const nc = + (0 == v->capacity) ? 8u : (v->capacity * 2u) + ; + + if (0 != raw_vec_reserve(v, nc)) + { + return -1; + } + } + + char* const copy = static_cast(std::malloc(n + 1u)); + + if (NULL == copy) + { + return -1; + } + + if (0 != n) + { + std::memcpy(copy, s, n); + } + + copy[n] = '\0'; + + if (0 != v->len) + { + std::memmove(v->ptr + 1, v->ptr, v->len * sizeof(char*)); + } + + v->ptr[0] = copy; + ++v->len; + + return 0; +} +} /* namespace cstring_perf */ + + +/* ////////////////////////////////////////////////////////////////////// */ + +#endif /* !CSTRING_TEST_PERFORMANCE_PERF_HARNESS_HPP_INCLUDED */ + +/* ///////////////////////////// end of file //////////////////////////// */ diff --git a/test/performance/test.performance.cstring/CMakeLists.txt b/test/performance/test.performance.cstring/CMakeLists.txt new file mode 100644 index 0000000..2f3068e --- /dev/null +++ b/test/performance/test.performance.cstring/CMakeLists.txt @@ -0,0 +1 @@ +define_performance_test_program(test.performance.cstring main.cpp) diff --git a/test/performance/test.performance.cstring/main.cpp b/test/performance/test.performance.cstring/main.cpp new file mode 100644 index 0000000..2c2cda1 --- /dev/null +++ b/test/performance/test.performance.cstring/main.cpp @@ -0,0 +1,551 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/performance/test.performance.cstring/main.cpp + * + * Purpose: Competitive performance tests for cstring vs std::string and a + * hand-rolled realloc/memcpy floor. When p99 is available, also + * report per-iteration percentiles. + * + * Created: 23rd September 2026 + * Updated: 23rd September 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include + +#include "perf_harness.hpp" + +#include +#include + +#include +#include +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * constants + */ + +namespace { + +char const* const IMPL_CSTRING = "cstring"; +char const* const IMPL_STD = "std::string"; +char const* const IMPL_RAW = "raw_realloc"; +char const* const IMPL_BORROWED = "cstring_borrowed"; +char const* const IMPL_FIXEDBUF = "fixed_char_buf"; + +std::size_t const SIZES[] = +{ + 16u, + 64u, + 256u, + 4096u, + 65536u, +}; + +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * helpers + */ + +namespace { + +using namespace cstring_perf; + +struct run_result +{ + interval_t tm_ns; + std::uint64_t anchor; +#ifdef HAS_P99 + + p99::histogram hist; +#endif /* HAS_P99 */ +}; + +template +run_result +time_iterations( + std::size_t num_iterations +, std::size_t num_warm_loops +, F fn +) +{ + run_result result = {}; + stopwatch_t sw; + + for (std::size_t w = num_warm_loops; 0 != w; --w) + { + result.anchor = 0; +#ifdef HAS_P99 + + result.hist.clear(); +#endif /* HAS_P99 */ + interval_t tm_ns = 0; +#ifdef HAS_P99 + + for (std::size_t i = 0; num_iterations != i; ++i) + { + sw.start(); + result.anchor += fn(); + sw.stop(); + + interval_t const sample = sw.get_nanoseconds(); + + tm_ns += sample; + (void)result.hist.push_ns(static_cast(sample)); + } +#else /* ? HAS_P99 */ + + sw.start(); + for (std::size_t i = 0; num_iterations != i; ++i) + { + result.anchor += fn(); + } + sw.stop(); + tm_ns = sw.get_nanoseconds(); +#endif /* HAS_P99 */ + + if (1 == w) + { + result.tm_ns = tm_ns; + } + } + + return result; +} + +void +emit_row( + char const* scenario +, std::size_t size +, char const* impl +, std::size_t num_iterations +, std::size_t num_actions +, run_result const& r +, interval_t cstring_tm_ns +) +{ + double const ratio = + (0 == ::strcmp(impl, IMPL_CSTRING)) + ? 1.0 + : ratio_or_dash(r.tm_ns, cstring_tm_ns) + ; + + display_results( + scenario + , size + , impl + , num_iterations + , num_actions + , r.tm_ns + , ratio + , r.anchor +#ifdef HAS_P99 + , &r.hist +#endif /* HAS_P99 */ + ); +} +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * scenarios + */ + +namespace { + +void +scenario_create_destroy_empty( + std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + run_result const cs = time_iterations(num_iterations, num_warm_loops, []() -> std::uint64_t { + cstring_t s = cstring_t_DEFAULT; + cstring_create(&s, ""); + std::uint64_t const a = s.len; + cstring_destroy(&s); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, []() -> std::uint64_t { + std::string s; + return s.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, []() -> std::uint64_t { + raw_string s; + raw_init(&s); + raw_assign_len(&s, "", 0); + std::uint64_t const a = s.len; + raw_destroy(&s); + return a; + }); + + emit_row("create_destroy_empty", 0, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + emit_row("create_destroy_empty", 0, IMPL_STD, num_iterations, 1, st, cs.tm_ns); + emit_row("create_destroy_empty", 0, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); +} + +void +scenario_create_destroy_len( + std::size_t n +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + std::string const payload = make_payload(n); + char const* const p = payload.data(); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + cstring_t s = cstring_t_DEFAULT; + cstring_createLen(&s, p, n); + std::uint64_t const a = s.len + (NULL != s.ptr ? static_cast(s.ptr[0]) : 0u); + cstring_destroy(&s); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + std::string s(p, n); + return s.size() + static_cast(s[0]); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + raw_string s; + raw_init(&s); + raw_assign_len(&s, p, n); + std::uint64_t const a = s.len + static_cast(s.ptr[0]); + raw_destroy(&s); + return a; + }); + + emit_row("create_destroy_len", n, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + emit_row("create_destroy_len", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); + emit_row("create_destroy_len", n, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); +} + +void +scenario_assign_len_grow( + std::size_t n +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + std::string const payload = make_payload(n); + char const* const p = payload.data(); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + cstring_t s = cstring_t_DEFAULT; + cstring_create(&s, ""); + cstring_assignLen(&s, p, n); + std::uint64_t const a = s.len; + cstring_destroy(&s); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + std::string s; + s.assign(p, n); + return s.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + raw_string s; + raw_init(&s); + raw_assign_len(&s, p, n); + std::uint64_t const a = s.len; + raw_destroy(&s); + return a; + }); + + emit_row("assign_len_grow", n, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + emit_row("assign_len_grow", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); + emit_row("assign_len_grow", n, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); +} + +void +scenario_append_len_growth( + std::size_t chunk +, std::size_t num_appends +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + 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 (std::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 (std::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 (std::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; + }); + + char scenario[64]; + std::snprintf(scenario, sizeof(scenario), "append_len_growth_x%zu", num_appends); + + emit_row(scenario, chunk, IMPL_CSTRING, num_iterations, num_appends, cs, cs.tm_ns); + emit_row(scenario, chunk, IMPL_STD, num_iterations, num_appends, st, cs.tm_ns); + emit_row(scenario, chunk, IMPL_RAW, num_iterations, num_appends, raw, cs.tm_ns); +} + +void +scenario_append_len_reserved( + std::size_t n +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + std::string const payload = make_payload(n); + char const* const p = payload.data(); + std::size_t const chunk = (n < 16u) ? n : 16u; + std::size_t const num_appends = (0 == chunk) ? 0u : (n / chunk); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [p, chunk, n, num_appends]() -> std::uint64_t { + cstring_t s = cstring_t_DEFAULT; + cstring_create(&s, ""); + cstring_setCapacity(&s, n); + for (std::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, n, num_appends]() -> std::uint64_t { + std::string s; + s.reserve(n); + for (std::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, n, num_appends]() -> std::uint64_t { + raw_string s; + raw_init(&s); + raw_reserve(&s, n); + for (std::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_reserved", n, IMPL_CSTRING, num_iterations, num_appends, cs, cs.tm_ns); + emit_row("append_len_reserved", n, IMPL_STD, num_iterations, num_appends, st, cs.tm_ns); + emit_row("append_len_reserved", n, IMPL_RAW, num_iterations, num_appends, raw, cs.tm_ns); +} + +void +scenario_insert_len_mid( + std::size_t n +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + std::string const base = make_payload(n); + std::string const ins = make_payload(n / 4u == 0 ? 1u : n / 4u); + char const* const bp = base.data(); + char const* const ip = ins.data(); + std::size_t const in = ins.size(); + int const pos = static_cast(n / 2u); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [bp, n, ip, in, pos]() -> std::uint64_t { + cstring_t s = cstring_t_DEFAULT; + cstring_createLen(&s, bp, n); + cstring_insertLen(&s, pos, ip, in); + std::uint64_t const a = s.len; + cstring_destroy(&s); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [bp, n, ip, in, pos]() -> std::uint64_t { + std::string s(bp, n); + s.insert(pos, ip, in); + return s.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [bp, n, ip, in, pos]() -> std::uint64_t { + raw_string s; + raw_init(&s); + raw_assign_len(&s, bp, n); + raw_insert_len(&s, pos, ip, in); + std::uint64_t const a = s.len; + raw_destroy(&s); + return a; + }); + + emit_row("insert_len_mid", n, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + emit_row("insert_len_mid", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); + emit_row("insert_len_mid", n, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); +} + +void +scenario_copy( + std::size_t n +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + std::string const payload = make_payload(n); + char const* const p = payload.data(); + + cstring_t src_cs = cstring_t_DEFAULT; + cstring_createLen(&src_cs, p, n); + + std::string const src_st(p, n); + + raw_string src_raw; + raw_init(&src_raw); + raw_assign_len(&src_raw, p, n); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [&src_cs]() -> std::uint64_t { + cstring_t d = cstring_t_DEFAULT; + cstring_copy(&d, &src_cs); + std::uint64_t const a = d.len; + cstring_destroy(&d); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [&src_st]() -> std::uint64_t { + std::string d(src_st); + return d.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [&src_raw]() -> std::uint64_t { + raw_string d; + raw_init(&d); + raw_copy(&d, &src_raw); + std::uint64_t const a = d.len; + raw_destroy(&d); + return a; + }); + + cstring_destroy(&src_cs); + raw_destroy(&src_raw); + + emit_row("copy", n, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + emit_row("copy", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); + emit_row("copy", n, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); +} + +void +scenario_borrowed_fixed( + std::size_t n +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + std::string const payload = make_payload(n); + char const* const p = payload.data(); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + std::vector buf(n + 1u, '\0'); + cstring_t s = cstring_t_DEFAULT; + cstring_createEx( + &s + , "" + , CSTRING_F_MEMORY_IS_BORROWED | CSTRING_F_MEMORY_IS_FIXED + , &buf[0] + , n + ); + cstring_assignLen(&s, p, n); + std::uint64_t const a = s.len; + cstring_destroy(&s); + return a; + }); + + run_result const fx = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + std::vector buf(n + 1u, '\0'); + std::memcpy(&buf[0], p, n); + buf[n] = '\0'; + return n + static_cast(buf[0]); + }); + + emit_row("borrowed_fixed_assign", n, IMPL_BORROWED, num_iterations, 1, cs, cs.tm_ns); + emit_row("borrowed_fixed_assign", n, IMPL_FIXEDBUF, num_iterations, 1, fx, cs.tm_ns); +} +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * main + */ + +int main(int /*argc*/, char* /*argv*/[]) +{ + std::size_t const num_iterations = cstring_perf::default_iterations(); + std::size_t const num_warm_loops = cstring_perf::default_warmups(); + + // Fewer outer iters for heavy growth / large sizes + std::size_t const heavy_iters = + (num_iterations > 10000u) ? (num_iterations / 10u) : num_iterations + ; + + cstring_perf::display_banner("test.performance.cstring"); + cstring_perf::display_results_title(); + + scenario_create_destroy_empty(num_iterations, num_warm_loops); + + for (std::size_t i = 0; STLSOFT_NUM_ELEMENTS(SIZES) != i; ++i) + { + std::size_t const n = SIZES[i]; + std::size_t const iters = (n >= 4096u) ? heavy_iters : num_iterations; + + scenario_create_destroy_len(n, iters, num_warm_loops); + scenario_assign_len_grow(n, iters, num_warm_loops); + scenario_append_len_reserved(n, iters, num_warm_loops); + scenario_copy(n, iters, num_warm_loops); + scenario_borrowed_fixed(n, iters, num_warm_loops); + + if (n <= 4096u) + { + scenario_insert_len_mid(n, iters, num_warm_loops); + scenario_append_len_growth(16u, n / 16u == 0 ? 1u : n / 16u, heavy_iters, num_warm_loops); + } + } + + return EXIT_SUCCESS; +} + + +/* ///////////////////////////// end of file //////////////////////////// */ diff --git a/test/performance/test.performance.cstring_vector/CMakeLists.txt b/test/performance/test.performance.cstring_vector/CMakeLists.txt new file mode 100644 index 0000000..9a86ed6 --- /dev/null +++ b/test/performance/test.performance.cstring_vector/CMakeLists.txt @@ -0,0 +1 @@ +define_performance_test_program(test.performance.cstring_vector main.cpp) diff --git a/test/performance/test.performance.cstring_vector/main.cpp b/test/performance/test.performance.cstring_vector/main.cpp new file mode 100644 index 0000000..c9035d7 --- /dev/null +++ b/test/performance/test.performance.cstring_vector/main.cpp @@ -0,0 +1,500 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/performance/test.performance.cstring_vector/main.cpp + * + * Purpose: Competitive performance tests for cstring_vector vs + * std::vector and a hand-rolled floor. When p99 is + * available, report per-iteration percentiles throughout, and run + * the filesystem file_lines vs cstring_vector_readLines suite. + * + * Created: 23rd September 2026 + * Updated: 23rd September 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include + +#include "perf_harness.hpp" + +#ifdef HAS_P99 +# include +# include +#endif /* HAS_P99 */ + +#include +#include + +#include +#include +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * constants + */ + +namespace { + +char const* const IMPL_CSTRING_VECTOR = "cstring_vector"; +char const* const IMPL_STD_VECTOR = "vector"; +char const* const IMPL_RAW_VECTOR = "raw_char_star_vec"; +char const* const IMPL_READLINES = "cstring_vector_readLines"; +#ifdef HAS_P99 +char const* const IMPL_FILE_LINES = "platformstl::file_lines"; + +char const TEST_FILE_NAME[] = "test.performance.cstring_vector.lines.txt"; +#endif /* HAS_P99 */ +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * helpers + */ + +namespace { + +using namespace cstring_perf; + +struct run_result +{ + interval_t tm_ns; + std::uint64_t anchor; +#ifdef HAS_P99 + + p99::histogram hist; +#endif /* HAS_P99 */ +}; + +template +run_result +time_iterations( + std::size_t num_iterations +, std::size_t num_warm_loops +, F fn +) +{ + run_result result = {}; + stopwatch_t sw; + + for (std::size_t w = num_warm_loops; 0 != w; --w) + { + result.anchor = 0; +#ifdef HAS_P99 + + result.hist.clear(); +#endif /* HAS_P99 */ + interval_t tm_ns = 0; +#ifdef HAS_P99 + + for (std::size_t i = 0; num_iterations != i; ++i) + { + sw.start(); + result.anchor += fn(); + sw.stop(); + + interval_t const sample = sw.get_nanoseconds(); + + tm_ns += sample; + (void)result.hist.push_ns(static_cast(sample)); + } +#else /* ? HAS_P99 */ + + sw.start(); + for (std::size_t i = 0; num_iterations != i; ++i) + { + result.anchor += fn(); + } + sw.stop(); + tm_ns = sw.get_nanoseconds(); +#endif /* HAS_P99 */ + + if (1 == w) + { + result.tm_ns = tm_ns; + } + } + + return result; +} + +void +emit_row( + char const* scenario +, std::size_t size +, char const* impl +, std::size_t num_iterations +, std::size_t num_actions +, run_result const& r +, interval_t cstring_tm_ns +) +{ + double const ratio = + (0 == ::strcmp(impl, IMPL_CSTRING_VECTOR) || + 0 == ::strcmp(impl, IMPL_READLINES)) + ? 1.0 + : ratio_or_dash(r.tm_ns, cstring_tm_ns) + ; + + display_results( + scenario + , size + , impl + , num_iterations + , num_actions + , r.tm_ns + , ratio + , r.anchor +#ifdef HAS_P99 + , &r.hist +#endif /* HAS_P99 */ + ); +} + +cstring_t +make_cstring_payload( + std::size_t n +) +{ + std::string const s = make_payload(n); + cstring_t cs = cstring_t_DEFAULT; + + cstring_createLen(&cs, s.data(), n); + + return cs; +} + +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * scenarios + */ + +namespace { + +void +scenario_create_destroy( + std::size_t n +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + run_result const cs = time_iterations(num_iterations, num_warm_loops, [n]() -> std::uint64_t { + cstring_vector_t v = cstring_vector_t_DEFAULT; + cstring_vector_create(&v, n); + std::uint64_t const a = v.len + v.capacity; + cstring_vector_destroy(&v); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [n]() -> std::uint64_t { + std::vector v(n); + return v.size() + v.capacity(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [n]() -> std::uint64_t { + raw_string_vector v; + raw_vec_init(&v); + raw_vec_reserve(&v, n); + v.len = n; + for (std::size_t i = 0; n != i; ++i) + { + v.ptr[i] = static_cast(std::calloc(1, 1)); + } + std::uint64_t const a = v.len + v.capacity; + raw_vec_destroy(&v); + return a; + }); + + emit_row("create_destroy", n, IMPL_CSTRING_VECTOR, num_iterations, 1, cs, cs.tm_ns); + emit_row("create_destroy", n, IMPL_STD_VECTOR, num_iterations, 1, st, cs.tm_ns); + emit_row("create_destroy", n, IMPL_RAW_VECTOR, num_iterations, 1, raw, cs.tm_ns); +} + +void +scenario_append_one_by_one( + std::size_t payload_len +, std::size_t k +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + cstring_t payload = make_cstring_payload(payload_len); + std::string const payload_s = make_payload(payload_len); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [&payload, k]() -> std::uint64_t { + cstring_vector_t v = cstring_vector_t_DEFAULT; + cstring_vector_init(&v, 0); + for (std::size_t i = 0; k != i; ++i) + { + cstring_vector_append(&v, &payload, 1); + } + std::uint64_t const a = v.len; + cstring_vector_destroy(&v); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [&payload_s, k]() -> std::uint64_t { + std::vector v; + for (std::size_t i = 0; k != i; ++i) + { + v.push_back(payload_s); + } + return v.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [&payload_s, payload_len, k]() -> std::uint64_t { + raw_string_vector v; + raw_vec_init(&v); + for (std::size_t i = 0; k != i; ++i) + { + raw_vec_append_cstr(&v, payload_s.data(), payload_len); + } + std::uint64_t const a = v.len; + raw_vec_destroy(&v); + return a; + }); + + cstring_destroy(&payload); + + emit_row("append_one_by_one", payload_len, IMPL_CSTRING_VECTOR, num_iterations, k, cs, cs.tm_ns); + emit_row("append_one_by_one", payload_len, IMPL_STD_VECTOR, num_iterations, k, st, cs.tm_ns); + emit_row("append_one_by_one", payload_len, IMPL_RAW_VECTOR, num_iterations, k, raw, cs.tm_ns); +} + +void +scenario_append_batch( + std::size_t payload_len +, std::size_t k +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + std::vector payloads(k); + std::vector payloads_s(k); + std::string const proto = make_payload(payload_len); + + for (std::size_t i = 0; k != i; ++i) + { + payloads[i] = cstring_t_DEFAULT; + cstring_createLen(&payloads[i], proto.data(), payload_len); + payloads_s[i] = proto; + } + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [&payloads, k]() -> std::uint64_t { + cstring_vector_t v = cstring_vector_t_DEFAULT; + cstring_vector_init(&v, 0); + cstring_vector_append(&v, &payloads[0], k); + std::uint64_t const a = v.len; + cstring_vector_destroy(&v); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [&payloads_s]() -> std::uint64_t { + std::vector v; + v.insert(v.end(), payloads_s.begin(), payloads_s.end()); + return v.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [&payloads_s, payload_len, k]() -> std::uint64_t { + raw_string_vector v; + raw_vec_init(&v); + raw_vec_reserve(&v, k); + for (std::size_t i = 0; k != i; ++i) + { + raw_vec_append_cstr(&v, payloads_s[i].data(), payload_len); + } + std::uint64_t const a = v.len; + raw_vec_destroy(&v); + return a; + }); + + for (std::size_t i = 0; k != i; ++i) + { + cstring_destroy(&payloads[i]); + } + + emit_row("append_batch", payload_len, IMPL_CSTRING_VECTOR, num_iterations, k, cs, cs.tm_ns); + emit_row("append_batch", payload_len, IMPL_STD_VECTOR, num_iterations, k, st, cs.tm_ns); + emit_row("append_batch", payload_len, IMPL_RAW_VECTOR, num_iterations, k, raw, cs.tm_ns); +} + +void +scenario_prepend_one_by_one( + std::size_t payload_len +, std::size_t k +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + cstring_t payload = make_cstring_payload(payload_len); + std::string const payload_s = make_payload(payload_len); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [&payload, k]() -> std::uint64_t { + cstring_vector_t v = cstring_vector_t_DEFAULT; + cstring_vector_init(&v, 0); + for (std::size_t i = 0; k != i; ++i) + { + cstring_vector_prepend(&v, &payload, 1); + } + std::uint64_t const a = v.len; + cstring_vector_destroy(&v); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [&payload_s, k]() -> std::uint64_t { + std::vector v; + for (std::size_t i = 0; k != i; ++i) + { + v.insert(v.begin(), payload_s); + } + return v.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [&payload_s, payload_len, k]() -> std::uint64_t { + raw_string_vector v; + raw_vec_init(&v); + for (std::size_t i = 0; k != i; ++i) + { + raw_vec_prepend_cstr(&v, payload_s.data(), payload_len); + } + std::uint64_t const a = v.len; + raw_vec_destroy(&v); + return a; + }); + + cstring_destroy(&payload); + + emit_row("prepend_one_by_one", payload_len, IMPL_CSTRING_VECTOR, num_iterations, k, cs, cs.tm_ns); + emit_row("prepend_one_by_one", payload_len, IMPL_STD_VECTOR, num_iterations, k, st, cs.tm_ns); + emit_row("prepend_one_by_one", payload_len, IMPL_RAW_VECTOR, num_iterations, k, raw, cs.tm_ns); +} +#ifdef HAS_P99 + +bool +write_lines_file( + char const* path +, std::size_t num_lines +, std::size_t line_len +) +{ + std::ofstream out(path, std::ios::binary | std::ios::trunc); + + if (!out) + { + return false; + } + + std::string const line = make_payload(line_len); + + for (std::size_t i = 0; num_lines != i; ++i) + { + out << line << '\n'; + } + + return static_cast(out); +} + +void +scenario_file_lines_vs_readlines( + std::size_t num_lines +, std::size_t line_len +, std::size_t num_trials +, std::size_t num_warm_loops +) +{ + if (!write_lines_file(TEST_FILE_NAME, num_lines, line_len)) + { + std::cerr + << "failed to write " << TEST_FILE_NAME << "; skipping file_lines suite" + << std::endl + ; + return; + } + + std::cout + << "\nfile_lines suite (" << num_lines << " lines x " << line_len + << " chars; " << num_trials + << " trials; filesystem-sensitive — use p99 percentiles):" + << std::endl + ; + + run_result const cs = time_iterations(num_trials, num_warm_loops, []() -> std::uint64_t { + FILE* f = std::fopen(TEST_FILE_NAME, "rb"); + if (NULL == f) + { + return 0; + } + + cstring_vector_t v = cstring_vector_t_DEFAULT; + cstring_vector_init(&v, 0); + size_t num_read = 0; + cstring_vector_readLines(f, &v, &num_read); + std::uint64_t const a = v.len + num_read; + cstring_vector_destroy(&v); + std::fclose(f); + return a; + }); + + run_result const fl = time_iterations(num_trials, num_warm_loops, []() -> std::uint64_t { + platformstl::file_lines_a lines(TEST_FILE_NAME); + return lines.size() + (lines.empty() ? 0u : lines[0].size()); + }); + + emit_row("file_read_all_lines", num_lines, IMPL_READLINES, num_trials, 1, cs, cs.tm_ns); + emit_row("file_read_all_lines", num_lines, IMPL_FILE_LINES, num_trials, 1, fl, cs.tm_ns); + + std::remove(TEST_FILE_NAME); +} +#endif /* HAS_P99 */ +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * main + */ + +int main(int /*argc*/, char* /*argv*/[]) +{ + std::size_t const num_iterations = cstring_perf::default_iterations(); + std::size_t const num_warm_loops = cstring_perf::default_warmups(); + std::size_t const heavy_iters = + (num_iterations > 10000u) ? (num_iterations / 10u) : num_iterations + ; + + cstring_perf::display_banner("test.performance.cstring_vector"); + cstring_perf::display_results_title(); + + scenario_create_destroy(16u, num_iterations, num_warm_loops); + scenario_create_destroy(256u, heavy_iters, num_warm_loops); + + scenario_append_one_by_one(16u, 64u, heavy_iters, num_warm_loops); + scenario_append_one_by_one(256u, 64u, heavy_iters, num_warm_loops); + + scenario_append_batch(16u, 64u, heavy_iters, num_warm_loops); + scenario_append_batch(256u, 64u, heavy_iters, num_warm_loops); + + scenario_prepend_one_by_one(16u, 32u, heavy_iters, num_warm_loops); +#ifdef HAS_P99 + + std::size_t const file_trials = cstring_perf::default_file_trials(); + + scenario_file_lines_vs_readlines(1000u, 64u, file_trials, num_warm_loops); + scenario_file_lines_vs_readlines(5000u, 80u, file_trials, num_warm_loops); +#else /* ? HAS_P99 */ + + std::cout + << "\nfile_lines suite skipped (p99 not available)." + << std::endl + ; +#endif /* HAS_P99 */ + + return EXIT_SUCCESS; +} + + +/* ///////////////////////////// end of file //////////////////////////// */ From 205b5e3874835aa29df38f02750a06f97f118826 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 10:42:37 +1000 Subject: [PATCH 45/88] test(cstring): time ifstream getline in the file-lines suite Add an `std::ifstream` + `std::getline` row to `file_read_all_lines`, so `cstring_vector_readLines` has an owning standard-library baseline beside `platformstl::file_lines`. --- CHANGES.md | 2 +- .../test.performance.cstring_vector/main.cpp | 25 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 94c8717..7af699f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ * Added competitive performance tests for **cstring** and **cstring_vector** (`test.performance.cstring`, `test.performance.cstring_vector`); * Baselines: `std::string` / hand-rolled `realloc` for strings; `std::vector` / hand-rolled for vectors; -* Optional **p99** linkage for per-iteration percentiles throughout; filesystem `platformstl::file_lines` vs `cstring_vector_readLines` suite gated on **p99**; +* Optional **p99** linkage for per-iteration percentiles throughout; filesystem suite (`cstring_vector_readLines`, `std::ifstream` + `std::getline`, `platformstl::file_lines`) gated on **p99**; * **`--no-p99`** / `NO_P99` to skip recognising **p99** (same pattern as **`--no-shwild`**); * **`--gap-groups`** on **run_all_performance_tests.sh** sets `SIS_PERFTESTS_GROUPGAPS` for blank lines between scenario groups; * Declared **cstring.vector** API with `CSTRING_EXTERN_C` for C++ linkage; diff --git a/test/performance/test.performance.cstring_vector/main.cpp b/test/performance/test.performance.cstring_vector/main.cpp index c9035d7..f783477 100644 --- a/test/performance/test.performance.cstring_vector/main.cpp +++ b/test/performance/test.performance.cstring_vector/main.cpp @@ -4,7 +4,8 @@ * Purpose: Competitive performance tests for cstring_vector vs * std::vector and a hand-rolled floor. When p99 is * available, report per-iteration percentiles throughout, and run - * the filesystem file_lines vs cstring_vector_readLines suite. + * the filesystem suite (cstring_vector_readLines, ifstream + + * getline, platformstl::file_lines). * * Created: 23rd September 2026 * Updated: 23rd September 2026 @@ -23,6 +24,7 @@ #ifdef HAS_P99 # include # include +# include #endif /* HAS_P99 */ #include @@ -44,6 +46,7 @@ char const* const IMPL_STD_VECTOR = "vector"; char const* const IMPL_RAW_VECTOR = "raw_char_star_vec"; char const* const IMPL_READLINES = "cstring_vector_readLines"; #ifdef HAS_P99 +char const* const IMPL_GETLINE = "ifstream+getline"; char const* const IMPL_FILE_LINES = "platformstl::file_lines"; char const TEST_FILE_NAME[] = "test.performance.cstring_vector.lines.txt"; @@ -440,12 +443,32 @@ scenario_file_lines_vs_readlines( return a; }); + run_result const gl = time_iterations(num_trials, num_warm_loops, []() -> std::uint64_t { + std::ifstream in(TEST_FILE_NAME); + + if (!in) + { + return 0; + } + + std::vector lines; + std::string line; + + while (std::getline(in, line)) + { + lines.push_back(std::move(line)); + } + + return lines.size() + (lines.empty() ? 0u : lines[0].size()); + }); + run_result const fl = time_iterations(num_trials, num_warm_loops, []() -> std::uint64_t { platformstl::file_lines_a lines(TEST_FILE_NAME); return lines.size() + (lines.empty() ? 0u : lines[0].size()); }); emit_row("file_read_all_lines", num_lines, IMPL_READLINES, num_trials, 1, cs, cs.tm_ns); + emit_row("file_read_all_lines", num_lines, IMPL_GETLINE, num_trials, 1, gl, cs.tm_ns); emit_row("file_read_all_lines", num_lines, IMPL_FILE_LINES, num_trials, 1, fl, cs.tm_ns); std::remove(TEST_FILE_NAME); From 5608670316f238b8b3371f3a0a27e7d7895a3ca7 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 10:55:22 +1000 Subject: [PATCH 46/88] test(cstring): pair borrowed_fixed_assign with std::string Add the semantic std::string::assign baseline beside the existing borrowed/fixed path; fix borrowed capacity to n+1. --- test/performance/test.performance.cstring/main.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/performance/test.performance.cstring/main.cpp b/test/performance/test.performance.cstring/main.cpp index 2c2cda1..31525ab 100644 --- a/test/performance/test.performance.cstring/main.cpp +++ b/test/performance/test.performance.cstring/main.cpp @@ -133,7 +133,7 @@ emit_row( ) { double const ratio = - (0 == ::strcmp(impl, IMPL_CSTRING)) + (0 == ::strcmp(impl, IMPL_CSTRING) || 0 == ::strcmp(impl, IMPL_BORROWED)) ? 1.0 : ratio_or_dash(r.tm_ns, cstring_tm_ns) ; @@ -486,7 +486,7 @@ scenario_borrowed_fixed( , "" , CSTRING_F_MEMORY_IS_BORROWED | CSTRING_F_MEMORY_IS_FIXED , &buf[0] - , n + , n + 1u ); cstring_assignLen(&s, p, n); std::uint64_t const a = s.len; @@ -494,6 +494,12 @@ scenario_borrowed_fixed( return a; }); + run_result const st = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + std::string s; + s.assign(p, n); + return s.size(); + }); + run_result const fx = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { std::vector buf(n + 1u, '\0'); std::memcpy(&buf[0], p, n); @@ -502,6 +508,7 @@ scenario_borrowed_fixed( }); emit_row("borrowed_fixed_assign", n, IMPL_BORROWED, num_iterations, 1, cs, cs.tm_ns); + emit_row("borrowed_fixed_assign", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); emit_row("borrowed_fixed_assign", n, IMPL_FIXEDBUF, num_iterations, 1, fx, cs.tm_ns); } } // anonymous namespace From 031564c089562e46a03949fc659a50061cf8f6e5 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 11:20:16 +1000 Subject: [PATCH 47/88] prep --- CHANGES.md | 5 +++++ NEWS.md | 1 + include/cstring/cstring.h | 9 +++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7af699f..40b52e2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ # cstring - Changes +## 4.1.0 - 25th September 2026 + +* performance optimisations; + + ## 4.0.17 - 24th September 2026 * Added competitive performance tests for **cstring** and **cstring_vector** (`test.performance.cstring`, `test.performance.cstring_vector`); diff --git a/NEWS.md b/NEWS.md index 5ec9787..8e640ed 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,7 @@ | Date | News Item | Details | | ------------------- | -------------------------------- | ------- | | Available from [**cstring** project on GitHub](https://synesissoftware.com/cstring): | +| 25th September 2026 | Release of [cstring 4.1.0](https://github.com/synesissoftware/cstring/releases/tag/4.1.0) | Performance optimisations | | 24th September 2026 | Release of [cstring 4.0.17](https://github.com/synesissoftware/cstring/releases/tag/4.0.17) | Competitive perf tests; optional **p99** | | 23rd 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 | | 6th September 2026 | Release of [cstring 4.0.15](https://github.com/synesissoftware/cstring/releases/tag/4.0.15) | Doxygen `\param`/`\retval` 76-rule | diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index 49ee3f2..b746894 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: 23rd September 2026 + * Updated: 25th September 2026 * * Home: http://synesis.com.au/software/ * @@ -117,6 +117,7 @@ # define CSTRING_VER_4_0_15 0x04000fff # define CSTRING_VER_4_0_16 0x040010ff # define CSTRING_VER_4_0_17 0x040011ff +# define CSTRING_VER_4_1_0 0x04010081 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR @@ -136,9 +137,9 @@ */ #define CSTRING_VER_MAJOR 4 -#define CSTRING_VER_MINOR 0 -#define CSTRING_VER_PATCH 17 -#define CSTRING_VER_ALPHABETA 0xFF +#define CSTRING_VER_MINOR 1 +#define CSTRING_VER_PATCH 0 +#define CSTRING_VER_ALPHABETA 0x81 #define CSTRING_VER \ (0\ From fb9b3f8143a3574a800e5705572d32f285d263bf Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 11:46:00 +1000 Subject: [PATCH 48/88] perf(cstring): copy known lengths with memcpy Replace the NUL-stopping byte loop in cstring_strlcpy_safe_ by memcpy of n characters. createLen("", 5) no longer has to zero-fill past the source. 64KB create, assign, and copy fell from about 15 us to about 0.6us, level with std::string. Small strings and file-line reads stayed in the same band. --- src/cstring.core.c | 28 +++---------------------- test/unit/test.unit.cstring.1/entry.cpp | 4 +++- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/src/cstring.core.c b/src/cstring.core.c index 22631bf..c73fe74 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 September 2026 + * Updated: 23rd September 2026 * * Home: http://synesis.com.au/software/ * @@ -193,12 +193,10 @@ convert_negative_index( * \param src Source. May be \c NULL; * \param lim Maximum number of elements in \c dst; * - * \return The actual length of src if not \c NULL; 0 otherwise - * * \pre NULL != dst */ static -size_t +void cstring_strlcpy_safe_( cstring_char_t dst[] , cstring_char_t const* src @@ -210,30 +208,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) - { - break; - } - } - - memset(dst, 0, sizeof(cstring_char_t) * (lim - i)); - - for (; '\0' != *src; ++i, ++src) - { - } - - return i; + memcpy(dst, src, sizeof(cstring_char_t) * lim); } } diff --git a/test/unit/test.unit.cstring.1/entry.cpp b/test/unit/test.unit.cstring.1/entry.cpp index 424cc3a..39229de 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: 2nd September 2025 + * Updated: 23rd September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -431,11 +431,13 @@ static void test_1_3() TEST_PTR_NE(NULL, str.ptr); XTESTS_TEST_STRING_EQUAL_(CSTRING_T_(""), str.ptr); XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[0]); + /* XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[1]); XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[2]); XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[3]); XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[4]); XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[5]); + */ TEST_INT_GE(5u, str.capacity); TEST_INT_GE(str.len, str.capacity); From 6f7cde694750230e26e5bfb87d6fc894889c6eac Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 12:35:46 +1000 Subject: [PATCH 49/88] perf(cstring): grow vector inserts by 3/2 Allocate (newSize * 3) / 2 elements in cstring_vector_insertAt and keep that as capacity, so later inserts reuse the spare slots. One-by-one append and prepend are about 1.5x faster than the 11:30 run; batch insert and file reads did not move. --- src/cstring.vector.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cstring.vector.c b/src/cstring.vector.c index dda3448..bf475b7 100644 --- a/src/cstring.vector.c +++ b/src/cstring.vector.c @@ -301,7 +301,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) { @@ -310,7 +311,7 @@ cstring_vector_insertAt( else { pcsv->ptr = newPtr; - pcsv->capacity = newSize; + pcsv->capacity = newCapacity; } } From 474547c40669faee5489e2b545bac94b6ecf8df5 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 12:36:24 +1000 Subject: [PATCH 50/88] refactor(impl): simplification --- src/cstring.vector.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/cstring.vector.c b/src/cstring.vector.c index bf475b7..e1c5a7b 100644 --- a/src/cstring.vector.c +++ b/src/cstring.vector.c @@ -251,11 +251,9 @@ cstring_vector_create( if (CSTRING_RC_SUCCESS != rc2) { - while (0 != i) + for (; 0 != i; --i) { cstring_destroy(pcsv->ptr + (i - 1)); - - --i; } rc = rc2; @@ -338,11 +336,9 @@ cstring_vector_insertAt( if (CSTRING_RC_SUCCESS != rc2) { - while (0 != i) + for (; 0 != i; --i) { cstring_destroy(pcsv->ptr + pcsv->len + (i - 1)); - - --i; } rc = rc2; From 836d67a9d7f6577b8ee8c73e5b4aebabefd387de Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 12:47:54 +1000 Subject: [PATCH 51/88] perf(cstring): free on destroy instead of realloc cstring_destroy released with realloc(ptr, 0). On macOS that allocates a replacement block, and realloc(NULL, 0) allocates too. A NULL pointer is now left alone; a real block is released with free(). --- src/cstring.core.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/cstring.core.c b/src/cstring.core.c index c73fe74..0e43fa2 100644 --- a/src/cstring.core.c +++ b/src/cstring.core.c @@ -289,14 +289,18 @@ cstring_realloc_2( switch (CSTRING_F_ARENA_MASK & flags) { case CSTRING_F_USE_REALLOC: -#if defined(_MSC_VER) && \ - defined(_DEBUG) + /* realloc(pv, 0) allocates on some platforms; free the block, + * and do nothing when pv is NULL. + */ if (0 == cb) { - free(pv); + if (NULL != pv) + { + free(pv); + } + return NULL; } -#endif /* _DEBUG */ pvNew = realloc(pv, cb); break; #if defined(CSTRING_USE_WINAPI_) @@ -687,7 +691,8 @@ cstring_destroy( CSTRING_ASSERT(NULL != pcs); - if (!(CSTRING_F_MEMORY_IS_BORROWED & pcs->flags)) + if (!(CSTRING_F_MEMORY_IS_BORROWED & pcs->flags) && + NULL != pcs->ptr) { (void)cstring_realloc_(pcs->ptr, 0, pcs->flags, &rc); } From aa357f770c8c1b32c0de27dc62e11fa27f8e7be8 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 13:04:07 +1000 Subject: [PATCH 52/88] fix(cstring): shift vector elements on insertAt Move the tail at position by numStrings slots, and write the new elements into that gap. The old memmove copied spare slots past the end, so prepend appended and could write past the block. --- src/cstring.vector.c | 20 +++-- .../test.unit.cstring.cstring_vector/entry.c | 73 +++++++++++++++++++ 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/src/cstring.vector.c b/src/cstring.vector.c index e1c5a7b..e20a47d 100644 --- a/src/cstring.vector.c +++ b/src/cstring.vector.c @@ -4,11 +4,11 @@ * Purpose: The implementation of the cstring.vector API * * Created: 16th June 1994 - * Updated: 23rd February 2025 + * Updated: 23rd September 2026 * * 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. * @@ -316,12 +316,16 @@ cstring_vector_insertAt( /* Make space for new items */ if (position < pcsv->len) { - memmove(pcsv->ptr + pcsv->len + numStrings, pcsv->ptr + pcsv->len, sizeof(cstring_t) * numStrings); + memmove( + pcsv->ptr + position + numStrings + , pcsv->ptr + position + , sizeof(cstring_t) * (pcsv->len - position) + ); } for (i = 0; i != numStrings; ++i) { - cstring_t* dest = pcsv->ptr + pcsv->len + i; + cstring_t* dest = pcsv->ptr + position + i; if (NULL == strings) { @@ -338,7 +342,7 @@ cstring_vector_insertAt( { for (; 0 != i; --i) { - cstring_destroy(pcsv->ptr + pcsv->len + (i - 1)); + cstring_destroy(pcsv->ptr + position + (i - 1)); } rc = rc2; @@ -356,7 +360,11 @@ cstring_vector_insertAt( /* If it's failed, and we made space for new items, remove the gap (leaving it with increased capacity) */ if (position < pcsv->len) { - memmove(pcsv->ptr + pcsv->len, pcsv->ptr + pcsv->len + numStrings, sizeof(cstring_t) * numStrings); + memmove( + pcsv->ptr + position + , pcsv->ptr + position + numStrings + , sizeof(cstring_t) * (pcsv->len - position) + ); } } diff --git a/test/unit/test.unit.cstring.cstring_vector/entry.c b/test/unit/test.unit.cstring.cstring_vector/entry.c index b986cb0..9f6ea94 100644 --- a/test/unit/test.unit.cstring.cstring_vector/entry.c +++ b/test/unit/test.unit.cstring.cstring_vector/entry.c @@ -324,6 +324,79 @@ static void test_1_06(void) static void test_1_07(void) { + cstring_vector_t csv = cstring_vector_t_DEFAULT; + CSTRING_RC rc = cstring_vector_create(&csv, 0); + cstring_t cs = cstring_t_DEFAULT; + cstring_t pair[2]; + + pair[0] = cs; + pair[1] = cs; + + if (CSTRING_RC_SUCCESS == rc) + { + rc = cstring_createLen(&cs, "a", 1); + } + if (CSTRING_RC_SUCCESS == rc) + { + rc = cstring_vector_append(&csv, &cs, 1); + } + if (CSTRING_RC_SUCCESS == rc) + { + rc = cstring_assign(&cs, "c"); + } + if (CSTRING_RC_SUCCESS == rc) + { + rc = cstring_vector_append(&csv, &cs, 1); + } + if (CSTRING_RC_SUCCESS == rc) + { + rc = cstring_assign(&cs, "b"); + } + if (CSTRING_RC_SUCCESS == rc) + { + rc = cstring_vector_insertAt(&csv, 1, &cs, 1); + } + if (CSTRING_RC_SUCCESS == rc) + { + rc = cstring_assign(&cs, "z"); + } + if (CSTRING_RC_SUCCESS == rc) + { + rc = cstring_vector_prepend(&csv, &cs, 1); + } + if (CSTRING_RC_SUCCESS == rc) + { + rc = cstring_createLen(&pair[0], "m1", 2); + } + if (CSTRING_RC_SUCCESS == rc) + { + rc = cstring_createLen(&pair[1], "m2", 2); + } + if (CSTRING_RC_SUCCESS == rc) + { + rc = cstring_vector_insertAt(&csv, 2, pair, 2); + } + + if (CSTRING_RC_SUCCESS == rc) + { + XTESTS_REQUIRE(XTESTS_TEST_INTEGER_EQUAL(6, csv.len)); + XTESTS_TEST_MULTIBYTE_STRING_EQUAL("z", csv.ptr[0].ptr); + XTESTS_TEST_MULTIBYTE_STRING_EQUAL("a", csv.ptr[1].ptr); + XTESTS_TEST_MULTIBYTE_STRING_EQUAL("m1", csv.ptr[2].ptr); + XTESTS_TEST_MULTIBYTE_STRING_EQUAL("m2", csv.ptr[3].ptr); + XTESTS_TEST_MULTIBYTE_STRING_EQUAL("b", csv.ptr[4].ptr); + XTESTS_TEST_MULTIBYTE_STRING_EQUAL("c", csv.ptr[5].ptr); + } + else + if (CSTRING_RC_OUTOFMEMORY != rc) + { + XTESTS_TEST_FAIL("insertAt shift failed"); + } + + cstring_destroy(&pair[0]); + cstring_destroy(&pair[1]); + cstring_destroy(&cs); + cstring_vector_destroy(&csv); } static void test_1_08(void) From 7e21244797417f3e87c789d8e74896853f5ffb5e Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 13:23:17 +1000 Subject: [PATCH 53/88] perf(cstring): skip allocation for empty strings create, createLen, and createN(0) leave ptr NULL when the length is 0. Assigning an empty string into an existing buffer still keeps that capacity. Empty create/destroy no longer pays for a heap block. --- src/cstring.core.c | 131 ++++++++++++++++++++---- test/unit/test.unit.cstring.1/entry.cpp | 120 ++++++++++++++-------- test/unit/test.unit.cstring.2/entry.c | 22 ++-- 3 files changed, 205 insertions(+), 68 deletions(-) diff --git a/src/cstring.core.c b/src/cstring.core.c index 0e43fa2..fad33c9 100644 --- a/src/cstring.core.c +++ b/src/cstring.core.c @@ -365,6 +365,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; +} + /* ///////////////////////////////////////////////////////////////////////// * API @@ -475,7 +500,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); @@ -509,7 +544,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); @@ -650,6 +695,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; @@ -687,22 +742,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) && - 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; + return cstring_release_to_empty_(pcs); } #ifndef CSTRING_OBSOLETE @@ -903,12 +945,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) @@ -1003,12 +1063,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) @@ -1115,6 +1193,11 @@ cstring_appendFn( size_t const len = cstring_strlen_safe_(s); size_t const newLen = pcs->len + len; + if (0 == len) + { + return CSTRING_RC_SUCCESS; + } + if (pcs->capacity < newLen) { size_t cch; @@ -1223,6 +1306,11 @@ cstring_appendLenFn( { size_t newLen = pcs->len + len; + if (0 == len) + { + return CSTRING_RC_SUCCESS; + } + if (pcs->capacity < newLen) { size_t cch; @@ -1454,7 +1542,12 @@ cstring_write_( *numWritten = 0u; - r = fprintf(stm, fmt, (int)pcs->len, pcs->ptr); + r = fprintf( + stm + , fmt + , (int)pcs->len + , (NULL != pcs->ptr) ? pcs->ptr : "" + ); if (r < 0) { diff --git a/test/unit/test.unit.cstring.1/entry.cpp b/test/unit/test.unit.cstring.1/entry.cpp index 39229de..198f908 100644 --- a/test/unit/test.unit.cstring.1/entry.cpp +++ b/test/unit/test.unit.cstring.1/entry.cpp @@ -247,10 +247,9 @@ static void test_1_3() else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(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); @@ -272,10 +271,9 @@ static void test_1_3() else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(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); @@ -297,10 +295,9 @@ static void test_1_3() else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(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); @@ -322,10 +319,9 @@ static void test_1_3() else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(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); @@ -347,10 +343,9 @@ static void test_1_3() else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(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); @@ -372,10 +367,9 @@ static void test_1_3() else { TEST_INT_EQ(0u, str.len); - TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(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); @@ -557,10 +551,18 @@ static void test_1_5() else { TEST_INT_EQ(cch, str.len); - TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_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); + XTESTS_TEST_STRING_EQUAL_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); @@ -587,10 +589,18 @@ static void test_1_5() else { TEST_INT_EQ(cch, str.len); - TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_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); + XTESTS_TEST_STRING_EQUAL_N_(s, stlsoft::apply_const_ptr(str.ptr), int(i)); + TEST_INT_GE(cch, str.capacity); + TEST_INT_GE(str.len, str.capacity); + } } }} @@ -616,10 +626,18 @@ static void test_1_5() else { TEST_INT_EQ(cch, str.len); - TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_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); + XTESTS_TEST_STRING_EQUAL_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); @@ -839,9 +857,18 @@ static void test_2_2() cstring_createLen(&str, s, i); - TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(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); + XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + } cstring_destroy(&str); @@ -997,9 +1024,18 @@ static void test_2_4() cstring_createLenEx(&str, s, i, flags, NULL, 0); - TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(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); + XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + } cstring_destroy(&str); diff --git a/test/unit/test.unit.cstring.2/entry.c b/test/unit/test.unit.cstring.2/entry.c index 4603462..9b0c25c 100644 --- a/test/unit/test.unit.cstring.2/entry.c +++ b/test/unit/test.unit.cstring.2/entry.c @@ -4,7 +4,7 @@ * Purpose: Unit-tests for general functionality. * * Created: 4th June 2009 - * Updated: 2nd August 2026 + * Updated: 23rd September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -135,18 +135,26 @@ static void test_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); #ifdef XTESTS_HAS_SHWILD - /* 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 /* ? XTESTS_HAS_SHWILD */ - TEST_PTR_EQ(NULL, strpbrk(str.ptr, "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890,<.>/?'\";:[{]}`!@#$%^&*()-_=+\\|")); + TEST_PTR_EQ(NULL, strpbrk(str.ptr, "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890,<.>/?'\";:[{]}`!@#$%^&*()-_=+\\|")); #endif /* XTESTS_HAS_SHWILD */ + } cstring_destroy(&str); From c7f008241f91cda6eebe2feca66947ffe24fc8d2 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 13:29:39 +1000 Subject: [PATCH 54/88] perf(cstring): append in place when capacity fits When capacity already covers the write, append and appendLen copy the bytes, write the NUL, and bump len without entering the growth path. Reserved small appends should move closer to std::string. --- src/cstring.core.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/cstring.core.c b/src/cstring.core.c index fad33c9..9dc16a6 100644 --- a/src/cstring.core.c +++ b/src/cstring.core.c @@ -1198,7 +1198,18 @@ cstring_appendFn( return CSTRING_RC_SUCCESS; } - if (pcs->capacity < newLen) + /* 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; @@ -1304,18 +1315,29 @@ cstring_appendLenFn( } else { - size_t newLen = pcs->len + len; + size_t const newLen = pcs->len + len; if (0 == len) { return CSTRING_RC_SUCCESS; } - if (pcs->capacity < newLen) + /* 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; - cch = pcs->len + len; + cch = newLen; cch = (cch + (CSTRING_ALLOC_GRANULARITY - 1)) & ~(CSTRING_ALLOC_GRANULARITY - 1); if (cch < pcs->capacity * 2) @@ -1380,7 +1402,7 @@ cstring_appendLenFn( } } - cstring_strlcpy_safe_(pcs->ptr + pcs->len, s, len); + cstring_memcpy_safe_(pcs->ptr + pcs->len, s, len * sizeof(cstring_char_t)); pcs->len += len; pcs->ptr[pcs->len] = '\0'; From a78263f3069cb92af39c2f3c6d71a32aebe6418a Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 14:42:11 +1000 Subject: [PATCH 55/88] refactor(impl): added common internal helper `cstring_vector_destroy_slots_()` --- src/cstring.vector.c | 78 ++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/src/cstring.vector.c b/src/cstring.vector.c index e20a47d..4ce16ca 100644 --- a/src/cstring.vector.c +++ b/src/cstring.vector.c @@ -78,6 +78,56 @@ #define CSTRING_VECTOR_ASSERT(expr) assert(expr) +/* ///////////////////////////////////////////////////////////////////////// + * helper functions + */ + +/** 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 */ @@ -169,21 +219,11 @@ cstring_vector_destroy( cstring_vector_t* pcsv ) { - CSTRING_RC rc = CSTRING_RC_SUCCESS; - size_t i; + CSTRING_RC rc; CSTRING_VECTOR_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); @@ -201,22 +241,12 @@ cstring_vector_truncate( , size_t len ) { - CSTRING_RC rc = CSTRING_RC_SUCCESS; - size_t i; + CSTRING_RC rc; CSTRING_VECTOR_ASSERT(NULL != pcsv); CSTRING_VECTOR_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; From 3686ada01516640b6364ee5fe2847a81265e10d5 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Wed, 23 Sep 2026 14:44:09 +1000 Subject: [PATCH 56/88] refactor(impl): added common internal helper `cstring_vector_init_empty_slots_()` for improved performance --- src/cstring.vector.c | 46 +++++++++---------- .../test.unit.cstring.cstring_vector/entry.c | 10 ++++ 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/cstring.vector.c b/src/cstring.vector.c index 4ce16ca..e4da116 100644 --- a/src/cstring.vector.c +++ b/src/cstring.vector.c @@ -82,6 +82,26 @@ * 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 @@ -271,34 +291,10 @@ cstring_vector_create( if (CSTRING_RC_SUCCESS == rc) { - size_t i; - CSTRING_VECTOR_ASSERT(pcsv->capacity >= initialSize); - for (i = 0; i != initialSize; ++i) - { - CSTRING_RC rc2 = cstring_init(pcsv->ptr + i); + cstring_vector_init_empty_slots_(pcsv->ptr, initialSize); - if (CSTRING_RC_SUCCESS != rc2) - { - for (; 0 != i; --i) - { - cstring_destroy(pcsv->ptr + (i - 1)); - } - - rc = rc2; - - pcsv->len = 0; - - cstring_vector_destroy(pcsv); - - break; - } - } - } - - if (CSTRING_RC_SUCCESS == rc) - { pcsv->len = initialSize; } diff --git a/test/unit/test.unit.cstring.cstring_vector/entry.c b/test/unit/test.unit.cstring.cstring_vector/entry.c index 9f6ea94..b3b6a76 100644 --- a/test/unit/test.unit.cstring.cstring_vector/entry.c +++ b/test/unit/test.unit.cstring.cstring_vector/entry.c @@ -190,10 +190,20 @@ static void test_1_04(void) else if (CSTRING_RC_SUCCESS == rc) { + size_t i; + XTESTS_TEST_INTEGER_EQUAL(10, csv.len); XTESTS_TEST_POINTER_NOT_EQUAL(NULL, csv.ptr); XTESTS_TEST_INTEGER_GREATER_OR_EQUAL(10, csv.capacity); + for (i = 0; i != csv.len; ++i) + { + XTESTS_TEST_INTEGER_EQUAL(0, csv.ptr[i].len); + XTESTS_TEST_POINTER_EQUAL(NULL, csv.ptr[i].ptr); + XTESTS_TEST_INTEGER_EQUAL(0, csv.ptr[i].capacity); + XTESTS_TEST_INTEGER_EQUAL(0, csv.ptr[i].flags); + } + cstring_vector_destroy(&csv); } else From ecb49fad8cedf8512679d03ceea392fe17b90536 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Sat, 26 Sep 2026 14:49:14 +1000 Subject: [PATCH 57/88] tidying --- include/cstring/cstring.h | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index 919e719..f89cb56 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: 23rd September 2026 + * Updated: 25th September 2026 * * Home: http://synesis.com.au/software/ * @@ -56,24 +56,9 @@ # define CSTRING_VER_CSTRING_H_CSTRING_MAJOR 3 # define CSTRING_VER_CSTRING_H_CSTRING_MINOR 11 # define CSTRING_VER_CSTRING_H_CSTRING_REVISION 14 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 86 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 87 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ -/** \def CSTRING_VER_MAJOR - * The major version number of cstring - */ - -/** \def CSTRING_VER_MINOR - * The minor version number of cstring - */ - -/** \def CSTRING_VER_REVISION - * The revision version number of cstring - */ - -/** \def CSTRING_VER - * The current composite version number of cstring - */ #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION # define CSTRING_VER_1_0_1 0x01000100 @@ -376,7 +361,7 @@ typedef struct cstring_t cstring_t; CSTRING_EXTERN_C char const* cstring_getStatusCodeString( - CSTRING_RC rc + CSTRING_RC rc ); /** \brief Returns the length of the string returned by @@ -391,7 +376,7 @@ cstring_getStatusCodeString( CSTRING_EXTERN_C size_t cstring_getStatusCodeStringLength( - CSTRING_RC rc + CSTRING_RC rc ); /** \brief [DEPRECATED] This function is deprecated, and may be removed in a @@ -410,7 +395,7 @@ __declspec(deprecated("cstring_error() is deprecated, and may be removed in a fu CSTRING_EXTERN_C char const* cstring_error( - CSTRING_RC rc + CSTRING_RC rc ); /** \brief Initialises a cstring instance to a default form @@ -856,9 +841,9 @@ cstring_swap( CSTRING_EXTERN_C CSTRING_RC cstring_readline( - FILE* stm -, struct cstring_t* pcs -, size_t* numRead /* = NULL */ + FILE* stm +, struct cstring_t* pcs +, size_t* numRead /* = NULL */ ); /** Writes a line of text to the given text stream From 14fbc4f66c888790ab697e277befce6698ed5237 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Sun, 27 Sep 2026 10:17:11 +1000 Subject: [PATCH 58/88] squash-commit --- .github/actions/install-sis-deps/action.yml | 109 +++++++++++++++ .github/workflows/ci-cell.yml | 143 +++----------------- .github/workflows/ci.yml | 77 +++-------- CHANGES.md | 6 +- NEWS.md | 6 +- include/cstring/cstring.h | 31 ++--- test/scratch/CMakeLists.txt | 1 + 7 files changed, 167 insertions(+), 206 deletions(-) create mode 100644 .github/actions/install-sis-deps/action.yml diff --git a/.github/actions/install-sis-deps/action.yml b/.github/actions/install-sis-deps/action.yml new file mode 100644 index 0000000..73209b7 --- /dev/null +++ b/.github/actions/install-sis-deps/action.yml @@ -0,0 +1,109 @@ +name: Install Synesis dependencies +description: >- + Clones, builds, and installs the named Synesis Information Systems + dependencies into a single CMake prefix, and exports that prefix both as + the SIS_DEPS environment variable and as the `prefix` output. + +inputs: + build-type: + description: CMake build type, for single-configuration generators + required: false + default: Release + c-compiler: + description: C compiler name, or the selector `cl` + required: true + cpp-compiler: + description: C++ compiler name; unused for the `cl` selector + required: true + dependencies: + description: >- + Whitespace-separated synesissoftware repository names, in dependency + order. Each is configured against those already installed, so this + list is deliberately NOT in lexicographic order. + required: true + install-prefix: + description: Directory into which every dependency is installed + required: false + default: '' + toolchain: + description: >- + Toolchain selector, for a toolchain that is not implied by the compiler + names and needs its own generator; presently only `mingw`. Empty + otherwise, in which case the compiler names alone select the toolchain. + required: false + default: '' + +outputs: + prefix: + description: Directory into which every dependency was installed + value: ${{ steps.install.outputs.prefix }} + +runs: + using: composite + + steps: + - id: install + shell: bash + run: | + set -euo pipefail + + PREFIX="${{ inputs.install-prefix }}" + [ -n "$PREFIX" ] || PREFIX="${RUNNER_TEMP}/sis-deps" + + mkdir -p "$PREFIX" + + if [ "${{ inputs.toolchain }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + fi + + for dep in ${{ inputs.dependencies }}; do + + echo "::group::Installing $dep" + + SRC="${RUNNER_TEMP}/${dep}-src" + BUILD="${RUNNER_TEMP}/${dep}-build" + + git clone --depth 1 "https://github.com/synesissoftware/${dep}" "$SRC" + + # NOTE: the generator and the build-type/compiler options are the + # *only* things that may differ between these paths; every other + # -D option is passed in all cases, so that no branch can silently + # ignore one. + if [ "${{ inputs.c-compiler }}" = "cl" ]; then + cmake -S "$SRC" -B "$BUILD" \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF \ + -DCMAKE_DISABLE_FIND_PACKAGE_MFC=TRUE \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" \ + -DCMAKE_PREFIX_PATH="$PREFIX" + cmake --build "$BUILD" --config "${{ inputs.build-type }}" --parallel + cmake --install "$BUILD" --config "${{ inputs.build-type }}" + elif [ "${{ inputs.toolchain }}" = "mingw" ]; then + cmake -S "$SRC" -B "$BUILD" -G "MinGW Makefiles" \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF \ + -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ + -DCMAKE_C_COMPILER="${{ inputs.c-compiler }}" \ + -DCMAKE_CXX_COMPILER="${{ inputs.cpp-compiler }}" \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" \ + -DCMAKE_PREFIX_PATH="$PREFIX" + cmake --build "$BUILD" --parallel 2 + cmake --install "$BUILD" + else + cmake -S "$SRC" -B "$BUILD" \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF \ + -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ + -DCMAKE_C_COMPILER="${{ inputs.c-compiler }}" \ + -DCMAKE_CXX_COMPILER="${{ inputs.cpp-compiler }}" \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" \ + -DCMAKE_PREFIX_PATH="$PREFIX" + cmake --build "$BUILD" --parallel + cmake --install "$BUILD" + fi + + echo "::endgroup::" + done + + echo "prefix=$PREFIX" >> "$GITHUB_OUTPUT" + echo "SIS_DEPS=$PREFIX" >> "$GITHUB_ENV" diff --git a/.github/workflows/ci-cell.yml b/.github/workflows/ci-cell.yml index 366ea2f..2e657e3 100644 --- a/.github/workflows/ci-cell.yml +++ b/.github/workflows/ci-cell.yml @@ -19,6 +19,10 @@ on: required: false type: string default: Release + toolchain: + required: false + type: string + default: '' jobs: build: @@ -33,7 +37,7 @@ jobs: run: sudo apt-get update && sudo apt-get install -y clang - name: Setup MSYS2 MinGW - if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' + if: runner.os == 'Windows' && inputs.toolchain == 'mingw' uses: msys2/setup-msys2@v2 with: msystem: MINGW64 @@ -44,118 +48,13 @@ jobs: mingw-w64-x86_64-make - name: Install test dependencies (STLSoft + shwild + xTests) - shell: bash - run: | - set -euxo pipefail - - DEPS="${RUNNER_TEMP}/sis-deps" - STLSRC="${RUNNER_TEMP}/stlsoft-src" - STLBUILD="${RUNNER_TEMP}/stlsoft-build" - SHWSRC="${RUNNER_TEMP}/shwild-src" - SHWBUILD="${RUNNER_TEMP}/shwild-build" - XTSRC="${RUNNER_TEMP}/xtests-src" - XTBUILD="${RUNNER_TEMP}/xtests-build" - - mkdir -p "$DEPS" - - # 1. STLSoft - git clone --depth 1 https://github.com/synesissoftware/STLSoft "$STLSRC" - if [ "${{ inputs.c-compiler }}" = "cl" ]; then - cmake -S "$STLSRC" -B "$STLBUILD" \ - -DCMAKE_DISABLE_FIND_PACKAGE_MFC=TRUE \ - -DCMAKE_INSTALL_PREFIX="$DEPS" \ - -DBUILD_EXAMPLES=OFF \ - -DBUILD_TESTING=OFF - cmake --build "$STLBUILD" --config "${{ inputs.build-type }}" --parallel - cmake --install "$STLBUILD" --config "${{ inputs.build-type }}" - elif [ "${{ inputs.c-compiler }}" = "mingw" ]; then - export PATH="/mingw64/bin:$PATH" - cmake -S "$STLSRC" -B "$STLBUILD" -G "MinGW Makefiles" \ - -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ - -DCMAKE_INSTALL_PREFIX="$DEPS" \ - -DBUILD_EXAMPLES=OFF \ - -DBUILD_TESTING=OFF - cmake --build "$STLBUILD" --parallel 2 - cmake --install "$STLBUILD" - else - cmake -S "$STLSRC" -B "$STLBUILD" \ - -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ - -DCMAKE_C_COMPILER="${{ inputs.c-compiler }}" \ - -DCMAKE_CXX_COMPILER="${{ inputs.cpp-compiler }}" \ - -DCMAKE_INSTALL_PREFIX="$DEPS" \ - -DBUILD_EXAMPLES=OFF \ - -DBUILD_TESTING=OFF - cmake --build "$STLBUILD" --parallel - cmake --install "$STLBUILD" - fi - - # 2. shwild - git clone --depth 1 https://github.com/synesissoftware/shwild "$SHWSRC" - if [ "${{ inputs.c-compiler }}" = "cl" ]; then - cmake -S "$SHWSRC" -B "$SHWBUILD" \ - -DCMAKE_DISABLE_FIND_PACKAGE_MFC=TRUE \ - -DCMAKE_INSTALL_PREFIX="$DEPS" \ - -DCMAKE_PREFIX_PATH="$DEPS" \ - -DBUILD_EXAMPLES=OFF \ - -DBUILD_TESTING=OFF - cmake --build "$SHWBUILD" --config "${{ inputs.build-type }}" --parallel - cmake --install "$SHWBUILD" --config "${{ inputs.build-type }}" - elif [ "${{ inputs.c-compiler }}" = "mingw" ]; then - export PATH="/mingw64/bin:$PATH" - cmake -S "$SHWSRC" -B "$SHWBUILD" -G "MinGW Makefiles" \ - -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ - -DCMAKE_INSTALL_PREFIX="$DEPS" \ - -DCMAKE_PREFIX_PATH="$DEPS" \ - -DBUILD_EXAMPLES=OFF \ - -DBUILD_TESTING=OFF - cmake --build "$SHWBUILD" --parallel 2 - cmake --install "$SHWBUILD" - else - cmake -S "$SHWSRC" -B "$SHWBUILD" \ - -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ - -DCMAKE_C_COMPILER="${{ inputs.c-compiler }}" \ - -DCMAKE_CXX_COMPILER="${{ inputs.cpp-compiler }}" \ - -DCMAKE_INSTALL_PREFIX="$DEPS" \ - -DCMAKE_PREFIX_PATH="$DEPS" \ - -DBUILD_EXAMPLES=OFF \ - -DBUILD_TESTING=OFF - cmake --build "$SHWBUILD" --parallel - cmake --install "$SHWBUILD" - fi - - # 3. xTests - git clone --depth 1 https://github.com/synesissoftware/xTests "$XTSRC" - if [ "${{ inputs.c-compiler }}" = "cl" ]; then - cmake -S "$XTSRC" -B "$XTBUILD" \ - -DCMAKE_DISABLE_FIND_PACKAGE_MFC=TRUE \ - -DCMAKE_INSTALL_PREFIX="$DEPS" \ - -DCMAKE_PREFIX_PATH="$DEPS" \ - -DBUILD_EXAMPLES=OFF \ - -DBUILD_TESTING=OFF - cmake --build "$XTBUILD" --config "${{ inputs.build-type }}" --parallel - cmake --install "$XTBUILD" --config "${{ inputs.build-type }}" - elif [ "${{ inputs.c-compiler }}" = "mingw" ]; then - export PATH="/mingw64/bin:$PATH" - cmake -S "$XTSRC" -B "$XTBUILD" -G "MinGW Makefiles" \ - -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ - -DCMAKE_INSTALL_PREFIX="$DEPS" \ - -DCMAKE_PREFIX_PATH="$DEPS" \ - -DBUILD_EXAMPLES=OFF \ - -DBUILD_TESTING=OFF - cmake --build "$XTBUILD" --parallel 2 - cmake --install "$XTBUILD" - else - cmake -S "$XTSRC" -B "$XTBUILD" \ - -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ - -DCMAKE_C_COMPILER="${{ inputs.c-compiler }}" \ - -DCMAKE_CXX_COMPILER="${{ inputs.cpp-compiler }}" \ - -DCMAKE_INSTALL_PREFIX="$DEPS" \ - -DCMAKE_PREFIX_PATH="$DEPS" \ - -DBUILD_EXAMPLES=OFF \ - -DBUILD_TESTING=OFF - cmake --build "$XTBUILD" --parallel - cmake --install "$XTBUILD" - fi + uses: ./.github/actions/install-sis-deps + with: + build-type: ${{ inputs.build-type }} + c-compiler: ${{ inputs.c-compiler }} + cpp-compiler: ${{ inputs.cpp-compiler }} + dependencies: STLSoft shwild xTests + toolchain: ${{ inputs.toolchain }} - name: Configure via prepare_cmake.sh shell: bash @@ -163,14 +62,14 @@ jobs: set -euo pipefail export SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" export SIS_CMAKE_CONFIG="${{ inputs.build-type }}" - export CMAKE_PREFIX_PATH="${RUNNER_TEMP}/sis-deps" + export CMAKE_PREFIX_PATH="${{ env.SIS_DEPS }}" # Bash 3.2 (macOS) + set -u treats an empty "${arr[@]}" as # unbound; use the ${arr[@]+…} form so Release/non-mingw is OK. PREPARE_ARGS=() if [ "${{ inputs.build-type }}" = "Debug" ]; then PREPARE_ARGS+=(-d) fi - if [ "${{ inputs.c-compiler }}" = "mingw" ]; then + if [ "${{ inputs.toolchain }}" = "mingw" ]; then export PATH="/mingw64/bin:$PATH" PREPARE_ARGS+=(--mingw) elif [ "${{ inputs.c-compiler }}" != "cl" ]; then @@ -185,7 +84,7 @@ jobs: set -euo pipefail export SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" export SIS_CMAKE_CONFIG="${{ inputs.build-type }}" - if [ "${{ inputs.c-compiler }}" = "mingw" ]; then + if [ "${{ inputs.toolchain }}" = "mingw" ]; then export PATH="/mingw64/bin:$PATH" fi ./build_cmake.sh @@ -196,7 +95,7 @@ jobs: set -euo pipefail export SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" export SIS_CMAKE_CONFIG="${{ inputs.build-type }}" - if [ "${{ inputs.c-compiler }}" = "mingw" ]; then + if [ "${{ inputs.toolchain }}" = "mingw" ]; then export PATH="/mingw64/bin:$PATH" fi ./ctest_cmake.sh -M @@ -220,7 +119,7 @@ jobs: - name: Setup MSYS2 MinGW id: msys2 - if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' + if: runner.os == 'Windows' && inputs.toolchain == 'mingw' uses: msys2/setup-msys2@v2 with: msystem: MINGW64 @@ -246,7 +145,7 @@ jobs: shell: cmd run: | set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build - if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" + if /I "${{ inputs.toolchain }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" call run_all_automated_tests.cmd - name: Performance tests @@ -262,7 +161,7 @@ jobs: shell: cmd run: | set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build - if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" + if /I "${{ inputs.toolchain }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" call run_all_performance_tests.cmd - name: Scratch tests @@ -280,7 +179,7 @@ jobs: shell: cmd run: | set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build - if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" + if /I "${{ inputs.toolchain }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" call run_all_scratch_tests.cmd - name: Examples @@ -296,5 +195,5 @@ jobs: shell: cmd run: | set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build - if /I "${{ inputs.c-compiler }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" + if /I "${{ inputs.toolchain }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" call run_all_examples.cmd diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6d709b..758f139 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,10 +3,10 @@ name: CI on: push: branches: - - boilerplate + - master - dev + - boilerplate - idiomatic - - master - rc1 - rc2 - rc3 @@ -46,10 +46,13 @@ jobs: os: windows-latest c_compiler: cl cpp_compiler: cl + # `toolchain` carries what is not a compiler: MinGW needs its own + # generator and PATH, but the compilers it drives are gcc and g++ - id: windows-mingw os: windows-latest - c_compiler: mingw + c_compiler: gcc cpp_compiler: g++ + toolchain: mingw uses: ./.github/workflows/ci-cell.yml with: cell-id: ${{ matrix.id }} @@ -57,10 +60,11 @@ jobs: c-compiler: ${{ matrix.c_compiler }} cpp-compiler: ${{ matrix.cpp_compiler }} build-type: Release + toolchain: ${{ matrix.toolchain }} secrets: inherit install-smoke: - name: Install smoke (${{ matrix.os }}, ${{ matrix.c_compiler }}) + name: Install smoke (${{ matrix.os }}, ${{ matrix.toolchain || matrix.c_compiler }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -79,8 +83,9 @@ jobs: c_compiler: cl cpp_compiler: cl - os: windows-latest - c_compiler: mingw + c_compiler: gcc cpp_compiler: g++ + toolchain: mingw steps: - uses: actions/checkout@v4 @@ -90,7 +95,7 @@ jobs: run: sudo apt-get update && sudo apt-get install -y clang - name: Setup MSYS2 MinGW - if: runner.os == 'Windows' && matrix.c_compiler == 'mingw' + if: runner.os == 'Windows' && matrix.toolchain == 'mingw' uses: msys2/setup-msys2@v2 with: msystem: MINGW64 @@ -108,7 +113,7 @@ jobs: # (does not replace the install-smoke configure/build below). export SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/_build-helper-smoke" PREPARE_ARGS=(-E -T) - if [ "${{ matrix.c_compiler }}" = "mingw" ]; then + if [ "${{ matrix.toolchain }}" = "mingw" ]; then export PATH="/mingw64/bin:$PATH" PREPARE_ARGS+=(--mingw) elif [ "${{ matrix.c_compiler }}" != "cl" ]; then @@ -133,7 +138,7 @@ jobs: -DCMAKE_DISABLE_FIND_PACKAGE_MFC=TRUE \ -DBUILD_EXAMPLES=OFF \ -DBUILD_TESTING=OFF - elif [ "${{ matrix.c_compiler }}" = "mingw" ]; then + elif [ "${{ matrix.toolchain }}" = "mingw" ]; then export PATH="/mingw64/bin:$PATH" cmake -B build -G "MinGW Makefiles" \ -DCMAKE_BUILD_TYPE="${{ env.BUILD_TYPE }}" \ @@ -156,7 +161,7 @@ jobs: set -euo pipefail if [ "${{ matrix.c_compiler }}" = "cl" ]; then cmake --build build --config "${{ env.BUILD_TYPE }}" --parallel - elif [ "${{ matrix.c_compiler }}" = "mingw" ]; then + elif [ "${{ matrix.toolchain }}" = "mingw" ]; then export PATH="/mingw64/bin:$PATH" cmake --build build --parallel 2 else @@ -204,7 +209,7 @@ jobs: -DCMAKE_PREFIX_PATH="${INSTALL_PREFIX};${{ env.SIS_DEPS }}" cmake --build consumer-build --config "${{ env.BUILD_TYPE }}" --parallel consumer-build/${{ env.BUILD_TYPE }}/consumer.exe - elif [ "${{ matrix.c_compiler }}" = "mingw" ]; then + elif [ "${{ matrix.toolchain }}" = "mingw" ]; then export PATH="/mingw64/bin:$PATH" cmake -S "$CONSUMER" -B consumer-build -G "MinGW Makefiles" \ -DCMAKE_BUILD_TYPE="${{ env.BUILD_TYPE }}" \ @@ -228,51 +233,13 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install dependencies (STLSoft + shwild + xTests) - shell: bash - run: | - set -euo pipefail - - DEPS="${RUNNER_TEMP}/sis-deps" - STLSRC="${RUNNER_TEMP}/stlsoft-src" - STLBUILD="${RUNNER_TEMP}/stlsoft-build" - SHWSRC="${RUNNER_TEMP}/shwild-src" - SHWBUILD="${RUNNER_TEMP}/shwild-build" - XTSRC="${RUNNER_TEMP}/xtests-src" - XTBUILD="${RUNNER_TEMP}/xtests-build" - - mkdir -p "$DEPS" - - git clone --depth 1 https://github.com/synesissoftware/STLSoft "$STLSRC" - cmake -S "$STLSRC" -B "$STLBUILD" \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX="$DEPS" \ - -DBUILD_EXAMPLES=OFF \ - -DBUILD_TESTING=OFF - cmake --build "$STLBUILD" --parallel - cmake --install "$STLBUILD" - - git clone --depth 1 https://github.com/synesissoftware/shwild "$SHWSRC" - cmake -S "$SHWSRC" -B "$SHWBUILD" \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX="$DEPS" \ - -DCMAKE_PREFIX_PATH="$DEPS" \ - -DBUILD_EXAMPLES=OFF \ - -DBUILD_TESTING=OFF - cmake --build "$SHWBUILD" --parallel - cmake --install "$SHWBUILD" - - git clone --depth 1 https://github.com/synesissoftware/xTests "$XTSRC" - cmake -S "$XTSRC" -B "$XTBUILD" \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX="$DEPS" \ - -DCMAKE_PREFIX_PATH="$DEPS" \ - -DBUILD_EXAMPLES=OFF \ - -DBUILD_TESTING=OFF - cmake --build "$XTBUILD" --parallel - cmake --install "$XTBUILD" - - echo "SIS_DEPS=$DEPS" >> "$GITHUB_ENV" + - name: Install test dependencies (STLSoft + shwild + xTests) + uses: ./.github/actions/install-sis-deps + with: + build-type: ${{ env.BUILD_TYPE }} + c-compiler: gcc + cpp-compiler: g++ + dependencies: STLSoft shwild xTests - name: Configure via prepare_cmake.sh --no-cpp shell: bash diff --git a/CHANGES.md b/CHANGES.md index 40b52e2..d2a3791 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,12 +1,12 @@ # cstring - Changes -## 4.1.0 - 25th September 2026 +## 4.1.0 - 29th September 2026 * performance optimisations; -## 4.0.17 - 24th September 2026 +## 4.0.17 - 28th September 2026 * Added competitive performance tests for **cstring** and **cstring_vector** (`test.performance.cstring`, `test.performance.cstring_vector`); * Baselines: `std::string` / hand-rolled `realloc` for strings; `std::vector` / hand-rolled for vectors; @@ -16,7 +16,7 @@ * Declared **cstring.vector** API with `CSTRING_EXTERN_C` for C++ linkage; -## 4.0.16 - 23rd September 2026 +## 4.0.16 - 27th September 2026 * Applied **misc-dev-scripts** **0.6.0** editor/Git/`.sis` drop-in templates on **boilerplate**; * Restored historical **.gitignore** patterns as a sorted union with **misc-dev-scripts** gold section layout; diff --git a/NEWS.md b/NEWS.md index 8e640ed..f10510c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,9 +4,9 @@ | Date | News Item | Details | | ------------------- | -------------------------------- | ------- | | Available from [**cstring** project on GitHub](https://synesissoftware.com/cstring): | -| 25th September 2026 | Release of [cstring 4.1.0](https://github.com/synesissoftware/cstring/releases/tag/4.1.0) | Performance optimisations | -| 24th September 2026 | Release of [cstring 4.0.17](https://github.com/synesissoftware/cstring/releases/tag/4.0.17) | Competitive perf tests; optional **p99** | -| 23rd 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 | +| 29th September 2026 | Release of [cstring 4.1.0](https://github.com/synesissoftware/cstring/releases/tag/4.1.0) | Performance optimisations | +| 28th September 2026 | Release of [cstring 4.0.17](https://github.com/synesissoftware/cstring/releases/tag/4.0.17) | Competitive perf tests; optional **p99** | +| 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 | | 6th September 2026 | Release of [cstring 4.0.15](https://github.com/synesissoftware/cstring/releases/tag/4.0.15) | Doxygen `\param`/`\retval` 76-rule | | 9th August 2026 | Release of [cstring 4.0.14](https://github.com/synesissoftware/cstring/releases/tag/4.0.14) | self-assignment safety | | 7th August 2026 | Release of [cstring 4.0.14-alpha1](https://github.com/synesissoftware/cstring/releases/tag/4.0.14-alpha1) | | diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index b746894..70eaea4 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: 25th September 2026 + * Updated: 29th September 2026 * * Home: http://synesis.com.au/software/ * @@ -56,24 +56,9 @@ # define CSTRING_VER_CSTRING_H_CSTRING_MAJOR 3 # define CSTRING_VER_CSTRING_H_CSTRING_MINOR 11 # define CSTRING_VER_CSTRING_H_CSTRING_REVISION 15 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 87 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 88 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ -/** \def CSTRING_VER_MAJOR - * The major version number of cstring - */ - -/** \def CSTRING_VER_MINOR - * The minor version number of cstring - */ - -/** \def CSTRING_VER_REVISION - * The revision version number of cstring - */ - -/** \def CSTRING_VER - * The current composite version number of cstring - */ #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION # define CSTRING_VER_1_0_1 0x01000100 @@ -378,7 +363,7 @@ typedef struct cstring_t cstring_t; CSTRING_EXTERN_C char const* cstring_getStatusCodeString( - CSTRING_RC rc + CSTRING_RC rc ); /** \brief Returns the length of the string returned by @@ -393,7 +378,7 @@ cstring_getStatusCodeString( CSTRING_EXTERN_C size_t cstring_getStatusCodeStringLength( - CSTRING_RC rc + CSTRING_RC rc ); /** \brief [DEPRECATED] This function is deprecated, and may be removed in a @@ -412,7 +397,7 @@ __declspec(deprecated("cstring_error() is deprecated, and may be removed in a fu CSTRING_EXTERN_C char const* cstring_error( - CSTRING_RC rc + CSTRING_RC rc ); /** \brief Initialises a cstring instance to a default form @@ -858,9 +843,9 @@ cstring_swap( CSTRING_EXTERN_C CSTRING_RC cstring_readline( - FILE* stm -, struct cstring_t* pcs -, size_t* numRead /* = NULL */ + FILE* stm +, struct cstring_t* pcs +, size_t* numRead /* = NULL */ ); /** Writes a line of text to the given text stream diff --git a/test/scratch/CMakeLists.txt b/test/scratch/CMakeLists.txt index b18ccd2..c6a5e32 100644 --- a/test/scratch/CMakeLists.txt +++ b/test/scratch/CMakeLists.txt @@ -1 +1,2 @@ +# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten add_subdirectory(versions) From 8b3e261bc1c4a7c877fe2b8b255c8e71497b7472 Mon Sep 17 00:00:00 2001 From: Matt Wilson <152443343+mwsis@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:47:49 +1000 Subject: [PATCH 59/88] Perf tests (#22) * feat(cstring): add competitive performance tests for 4.0.17 Add test.performance.cstring and test.performance.cstring_vector with std::string / vector and hand-rolled baselines; optional p99 percentiles (and file_lines vs cstring_vector_readLines when linked); --no-p99 / NO_P99; --gap-groups / SIS_PERFTESTS_GROUPGAPS; CSTRING_EXTERN_C on the cstring.vector API. * test(cstring): time ifstream getline in the file-lines suite Add an `std::ifstream` + `std::getline` row to `file_read_all_lines`, so `cstring_vector_readLines` has an owning standard-library baseline beside `platformstl::file_lines`. * test(cstring): pair borrowed_fixed_assign with std::string Add the semantic std::string::assign baseline beside the existing borrowed/fixed path; fix borrowed capacity to n+1. * fix(cstring): restore legacy-compatible memory calls in performance tests Define _CRT_SECURE_NO_WARNINGS for MSVC before CRT headers are included; use global ::memcpy and ::memmove names to preserve early C/C++ compatibility; * fix(cstring): use SIS target warning suppression mechanism Register MSVC warning 4996 through X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED for each affected performance target; remove broad and direct warning suppression from the top-level build; * fix(cstring): correct vector insertion range handling Move the full tail when inserting before the vector end; construct inserted strings and clean up failures at the insertion position; prevent prepend benchmarks from corrupting vector storage; * fix * squash-commit --------- Co-authored-by: Matt Wilson --- .github/workflows/ci-cell.yml | 5 +- CHANGES.md | 10 + CMakeLists.txt | 32 +- Doxyfile | 2 +- INSTALL.md | 1 + NEWS.md | 1 + README.md | 3 + TODO.md | 7 +- cmake/TargetMacros.cmake | 55 ++ include/cstring/cstring.h | 9 +- include/cstring/cstring.vector.h | 12 +- prepare_cmake.sh | 11 + run_all_performance_tests.cmd | 11 + run_all_performance_tests.sh | 14 + src/cstring.vector.c | 16 +- test/performance/CMakeLists.txt | 10 +- test/performance/perf_harness.hpp | 705 ++++++++++++++++++ .../test.performance.cstring/CMakeLists.txt | 5 + .../test.performance.cstring/main.cpp | 558 ++++++++++++++ .../CMakeLists.txt | 5 + .../test.performance.cstring_vector/main.cpp | 523 +++++++++++++ 21 files changed, 1977 insertions(+), 18 deletions(-) create mode 100644 test/performance/perf_harness.hpp create mode 100644 test/performance/test.performance.cstring/CMakeLists.txt create mode 100644 test/performance/test.performance.cstring/main.cpp create mode 100644 test/performance/test.performance.cstring_vector/CMakeLists.txt create mode 100644 test/performance/test.performance.cstring_vector/main.cpp diff --git a/.github/workflows/ci-cell.yml b/.github/workflows/ci-cell.yml index 2e657e3..5783743 100644 --- a/.github/workflows/ci-cell.yml +++ b/.github/workflows/ci-cell.yml @@ -161,7 +161,10 @@ jobs: shell: cmd run: | set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build - if /I "${{ inputs.toolchain }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" + if /I "${{ inputs.c-compiler }}"=="mingw" ( + set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" + set "CSTRING_PERF_FILE_TRIALS=10" + ) call run_all_performance_tests.cmd - name: Scratch tests diff --git a/CHANGES.md b/CHANGES.md index ecb2855..190a5de 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,16 @@ # cstring - Changes +## 4.0.17 - 28th September 2026 + +* Added competitive performance tests for **cstring** and **cstring_vector** (`test.performance.cstring`, `test.performance.cstring_vector`); +* Baselines: `std::string` / hand-rolled `realloc` for strings; `std::vector` / hand-rolled for vectors; +* Optional **p99** linkage for per-iteration percentiles throughout; filesystem suite (`cstring_vector_readLines`, `std::ifstream` + `std::getline`, `platformstl::file_lines`) gated on **p99**; +* **`--no-p99`** / `NO_P99` to skip recognising **p99** (same pattern as **`--no-shwild`**); +* **`--gap-groups`** on **run_all_performance_tests.sh** sets `SIS_PERFTESTS_GROUPGAPS` for blank lines between scenario groups; +* Declared **cstring.vector** API with `CSTRING_EXTERN_C` for C++ linkage; + + ## 4.0.16 - 27th September 2026 * Applied **misc-dev-scripts** **0.6.0** editor/Git/`.sis` drop-in templates on **boilerplate**; diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d5f13b..1bd80f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ # Purpose: Top-level CMake lists file for cstring # # Created: 21st December 2023 -# Updated: 6th August 2026 +# Updated: 23rd September 2026 # # ######################################################################## # @@ -119,6 +119,10 @@ option(BUILD_TESTING "Build tests" ON) option(NO_CSTRING_CPP_API "Disable C++ API (and associated C++ examples/tests)" OFF) +option(NO_P99 "Do not recognise p99 for performance tests" OFF) + +option(NO_SHWILD "Do not recognise shwild for xTests pattern-match assertions" OFF) + # ###################################### # features @@ -132,6 +136,9 @@ option(NO_CSTRING_CPP_API "Disable C++ API (and associated C++ examples/tests)" # - xTests - required for testing; # # optional: +# - p99 - optional for performance tests: per-iteration percentiles +# throughout when found; also gates the filesystem file_lines vs +# cstring_vector_readLines suite (unless NO_P99); # - shwild - for enhanced match constructs in xTests (unless NO_SHWILD); # # note: NO_CSTRING_CPP_API skips C++ examples and remaining C++ tests; C @@ -215,6 +222,29 @@ if(BUILD_TESTING) endif(BUILD_TESTING) +# ############################ +# p99 (optional; performance tests) + +if(BUILD_TESTING) + + if(NOT (NO_P99)) + + find_package(p99 QUIET) + + if(p99_FOUND) + + message("-- CMake package p99 found (version ${p99_VERSION})") + else() + + message("-- CMake package p99 not found; performance percentiles and file_lines suite disabled") + endif() + else() + + message("-- p99 recognition disabled (NO_P99); performance percentiles and file_lines suite disabled") + endif() +endif(BUILD_TESTING) + + # ################################################ # includes - 2 diff --git a/Doxyfile b/Doxyfile index bf31d8c..7986df1 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.0.16 +PROJECT_NUMBER = 4.0.17 # 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/INSTALL.md b/INSTALL.md index d0c9b93..8e52c02 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -53,6 +53,7 @@ The primary choice for installation is by use of **CMake**. * `--no-cpp` / `-C` — omit C++ examples and remaining C++ tests (`NO_CSTRING_CPP_API`); + * `--no-p99` — do not recognise **p99** (`NO_P99`); * `--no-shwild` — do not recognise **shwild** (`NO_SHWILD`); * `--disable-examples` / `-E` — omit examples (`BUILD_EXAMPLES=OFF`); * `--disable-testing` / `-T` — omit tests (`BUILD_TESTING=OFF`); diff --git a/NEWS.md b/NEWS.md index 39b045b..999dbc6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,7 @@ | Date | News Item | Details | | ------------------- | -------------------------------- | ------- | | Available from [**cstring** project on GitHub](https://synesissoftware.com/cstring): | +| 28th September 2026 | Release of [cstring 4.0.17](https://github.com/synesissoftware/cstring/releases/tag/4.0.17) | Competitive perf tests; optional **p99** | | 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 | | 6th September 2026 | Release of [cstring 4.0.15](https://github.com/synesissoftware/cstring/releases/tag/4.0.15) | Doxygen `\param`/`\retval` 76-rule | | 9th August 2026 | Release of [cstring 4.0.14](https://github.com/synesissoftware/cstring/releases/tag/4.0.14) | self-assignment safety | diff --git a/README.md b/README.md index 080d353..41508f0 100644 --- a/README.md +++ b/README.md @@ -172,10 +172,13 @@ The **C** API has no non-standard dependencies. | ---------- | ---- | --------- | | [**STLSoft**](https://github.com/synesissoftware/STLSoft) 1.11 | Test headers / remaining C++ tests and examples | ⚪ Tests only (`BUILD_TESTING`) | | [**xTests**](https://github.com/synesissoftware/xTests) (≥ 0.26) | Unit / component / scratch tests | ⚪ Tests only (`BUILD_TESTING`) | +| [**p99**](https://github.com/synesissoftware/p99) | Percentiles in performance tests; file_lines suite | ⚪ Optional; tests only (unless `NO_P99` / `--no-p99`) | | [**shwild**](https://github.com/synesissoftware/shwild) | Enhanced pattern-match assertions in **xTests** | ⚪ Optional; tests only (unless `NO_SHWILD` / `--no-shwild`) | When supplying `'--no-cpp'` to **prepare_cmake.sh** — sets the CMake option `NO_CSTRING_CPP_API=ON` — C++ examples and remaining C++ tests are omitted; the **C** unit-tests still require **STLSoft** and **xTests**. +When supplying `'--no-p99'` — sets `NO_P99=ON` — **p99** is not recognised; performance tests still build but omit percentiles and the filesystem file_lines suite. + When supplying `'--no-shwild'` — sets `NO_SHWILD=ON` — **shwild** is not recognised and pattern-match assertions are compiled out; other unit-tests still run. diff --git a/TODO.md b/TODO.md index f5f5309..97a3965 100644 --- a/TODO.md +++ b/TODO.md @@ -18,9 +18,10 @@ ## Performance improvements -* [ ] Performance testing and optimisation: - * [ ] Quantify performance; - * [ ] Compare with other popular implementations; +* [x] ~~~Performance testing and optimisation:~~~ - ✅: + * [x] ~~~Quantify performance~~~ - ✅; + * [x] ~~~Compare with other popular implementations~~~ - ✅; +* [ ] Analyse performance results and optimise hot paths; ## Packaging improvements diff --git a/cmake/TargetMacros.cmake b/cmake/TargetMacros.cmake index 8981dbc..8e925cc 100644 --- a/cmake/TargetMacros.cmake +++ b/cmake/TargetMacros.cmake @@ -94,5 +94,60 @@ function(define_example_program program_name entry_point_source_name) endfunction(define_example_program) +# Performance programs are built for human attention (see test-standards). +# They are discovered by run_all_performance_tests.sh and must NOT be +# registered with CTest (unlike define_automated_test_program). + +function(define_performance_test_program program_name entry_point_source_name) + + add_executable(${program_name} + ${entry_point_source_name} + ) + + set(_p99_link_lib_) + + if(p99_FOUND) + + if(TARGET p99::p99) + + set(_p99_link_lib_ p99::p99) + elseif(TARGET p99::p99_static) + + set(_p99_link_lib_ p99::p99_static) + elseif(TARGET p99::p99_shared) + + set(_p99_link_lib_ p99::p99_shared) + endif() + endif() + + target_link_libraries(${program_name} + PRIVATE + core + $<$:STLSoft::STLSoft> + ${_p99_link_lib_} + ) + + if(_p99_link_lib_) + + target_compile_definitions(${program_name} + PRIVATE + HAS_P99 + ) + endif() + + unset(_p99_link_lib_) + + if(WIN32) + + target_link_libraries(${program_name} + PRIVATE + wininet + ) + endif(WIN32) + + define_target_compile_options(${program_name}) +endfunction(define_performance_test_program) + + # ############################## end of file ############################# # diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index d2d6185..537d52c 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: 27th September 2026 + * Updated: 28th September 2026 * * Home: http://synesis.com.au/software/ * @@ -55,8 +55,8 @@ #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION # define CSTRING_VER_CSTRING_H_CSTRING_MAJOR 3 # define CSTRING_VER_CSTRING_H_CSTRING_MINOR 11 -# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 14 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 87 +# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 15 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 88 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ @@ -101,6 +101,7 @@ # define CSTRING_VER_4_0_14 0x04000eff # define CSTRING_VER_4_0_15 0x04000fff # define CSTRING_VER_4_0_16 0x040010ff +# define CSTRING_VER_4_0_17 0x040011ff #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR @@ -121,7 +122,7 @@ #define CSTRING_VER_MAJOR 4 #define CSTRING_VER_MINOR 0 -#define CSTRING_VER_PATCH 16 +#define CSTRING_VER_PATCH 17 #define CSTRING_VER_ALPHABETA 0xFF #define CSTRING_VER \ diff --git a/include/cstring/cstring.vector.h b/include/cstring/cstring.vector.h index 7a0511f..8afe3b2 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: 6th September 2026 + * Updated: 23rd September 2026 * * Home: http://synesis.com.au/software/ * @@ -54,8 +54,8 @@ #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION # define CSTRING_VER_CSTRING_H_VECTOR_MAJOR 1 # define CSTRING_VER_CSTRING_H_VECTOR_MINOR 0 -# define CSTRING_VER_CSTRING_H_VECTOR_REVISION 3 -# define CSTRING_VER_CSTRING_H_VECTOR_EDIT 9 +# define CSTRING_VER_CSTRING_H_VECTOR_REVISION 4 +# define CSTRING_VER_CSTRING_H_VECTOR_EDIT 10 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ @@ -113,6 +113,7 @@ typedef struct cstring_vector_t cstring_vector_t; * * \pre (NULL != pcsv) */ +CSTRING_EXTERN_C CSTRING_RC cstring_vector_init( cstring_vector_t* pcsv @@ -131,6 +132,7 @@ cstring_vector_init( * * \pre (NULL != pcsv) */ +CSTRING_EXTERN_C CSTRING_RC cstring_vector_destroy( cstring_vector_t* pcsv @@ -151,6 +153,7 @@ cstring_vector_destroy( * * \pre (NULL != pcsv) */ +CSTRING_EXTERN_C CSTRING_RC cstring_vector_truncate( cstring_vector_t* pcsv @@ -161,6 +164,7 @@ cstring_vector_truncate( * * \pre (NULL != pcsv) */ +CSTRING_EXTERN_C CSTRING_RC cstring_vector_create( cstring_vector_t* pcsv @@ -182,6 +186,7 @@ cstring_vector_create( * \pre (NULL != pcsv) * \pre (position <= pcsv->len) */ +CSTRING_EXTERN_C CSTRING_RC cstring_vector_insertAt( cstring_vector_t* pcsv @@ -199,6 +204,7 @@ cstring_vector_insertAt( * * \pre (NULL != pcsv) */ +CSTRING_EXTERN_C CSTRING_RC cstring_vector_readLines( FILE* stm diff --git a/prepare_cmake.sh b/prepare_cmake.sh index d98b919..d067722 100755 --- a/prepare_cmake.sh +++ b/prepare_cmake.sh @@ -35,6 +35,7 @@ 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 @@ -167,6 +168,10 @@ while [[ $# -gt 0 ]]; do NO_cxx=1 ;; + --no-p99) + + NO_p99=1 + ;; --no-shwild) NO_shwild=1 @@ -230,6 +235,10 @@ Flags/options: omits the C++ API, C++ examples, and remaining C++ tests (CMake NO_CSTRING_CPP_API). C unit-tests still require STLSoft and xTests + --no-p99 + prevents recognising p99 library (NO_P99=ON); performance tests + then omit percentiles and the file_lines suite + --no-shwild prevents recognising shwild library (NO_SHWILD=ON); xTests pattern-match assertions are then unavailable @@ -278,6 +287,7 @@ if [ $BuildSharedLibs -eq 0 ]; then CMakeBuildSharedLibsFlag="OFF" ; else CMakeB if [ $ExamplesDisabled -eq 0 ]; then CMakeBuildExamplesFlag="ON" ; else CMakeBuildExamplesFlag="OFF" ; fi if [ $MSVC_MT -eq 0 ]; then CMakeMsvcMtFlag="OFF" ; else CMakeMsvcMtFlag="ON" ; fi if [ $NO_cxx -eq 0 ]; then CMakeNoCppApiFlag="OFF" ; else CMakeNoCppApiFlag="ON" ; fi +if [ $NO_p99 -eq 0 ]; then CMakeNoP99="OFF" ; else CMakeNoP99="ON" ; fi if [ $NO_shwild -eq 0 ]; then CMakeNoShwild="OFF" ; else CMakeNoShwild="ON" ; fi if [ -z "$STLSoftDirGiven" ]; then CMakeSTLSoftVariable="" ; else CMakeSTLSoftVariable="-DSTLSOFT=$STLSoftDirGiven/" ; fi if [ $TestingDisabled -eq 0 ]; then CMakeBuildTestingFlag="ON" ; else CMakeBuildTestingFlag="OFF" ; fi @@ -307,6 +317,7 @@ cmake \ -DCMAKE_VERBOSE_MAKEFILE:BOOL=$CMakeVerboseMakefileFlag \ -DMSVC_USE_MT:BOOL=$CMakeMsvcMtFlag \ -DNO_CSTRING_CPP_API:BOOL=$CMakeNoCppApiFlag \ + -DNO_P99:BOOL=$CMakeNoP99 \ -DNO_SHWILD:BOOL=$CMakeNoShwild \ $CMakeSTLSoftVariable \ "${CMakeGeneratorArgs[@]}" \ diff --git a/run_all_performance_tests.cmd b/run_all_performance_tests.cmd index ae97766..adb8746 100644 --- a/run_all_performance_tests.cmd +++ b/run_all_performance_tests.cmd @@ -35,6 +35,14 @@ Flags/options: ^ behaviour: ^ +^ + + --gap-groups ^ + + sets SIS_PERFTESTS_GROUPGAPS=1 so performance programs emit a ^ + + blank line between scenario groups ^ + ^ standard flags: ^ @@ -47,6 +55,9 @@ Flags/options: ^ EXIT /B 0 + ) ELSE IF /I {--gap-groups}=={%%a} ( + + SET SIS_PERFTESTS_GROUPGAPS=1 ) ELSE ( ECHO "%SCRIPT_DIRECTORY%: unrecognised argument '%%a'; use --help for usage" 1>&2 diff --git a/run_all_performance_tests.sh b/run_all_performance_tests.sh index 8d7add2..077d839 100755 --- a/run_all_performance_tests.sh +++ b/run_all_performance_tests.sh @@ -29,6 +29,7 @@ ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") ScriptPath=$0 AlwaysUseColours=${SIS_CMAKE_ALWAYS_USE_COLOURS:-${SIS_ALWAYS_USE_COLOURS:-0}} +GapGroups=0 ListOnly=0 RunMake=1 SisUseColours=0 @@ -127,6 +128,10 @@ while [[ $# -gt 0 ]]; do # AlwaysUseColours=1 - this is handled by the for loop above ;; + --gap-groups) + + GapGroups=1 + ;; --list-only|-l) ListOnly=1 @@ -157,6 +162,10 @@ Flags/options: --always-use-colours forces use of colours even when stdout is not a TTY + --gap-groups + sets SIS_PERFTESTS_GROUPGAPS=1 so performance programs emit a blank + line between scenario groups + -l --list-only lists the target programs but does not execute them @@ -193,6 +202,11 @@ done # ########################################################## # main() +if [ $GapGroups -ne 0 ]; then + + export SIS_PERFTESTS_GROUPGAPS=1 +fi + status=0 if [ $RunMake -ne 0 ]; then diff --git a/src/cstring.vector.c b/src/cstring.vector.c index dda3448..65bb9ec 100644 --- a/src/cstring.vector.c +++ b/src/cstring.vector.c @@ -317,12 +317,16 @@ cstring_vector_insertAt( /* Make space for new items */ if (position < pcsv->len) { - memmove(pcsv->ptr + pcsv->len + numStrings, pcsv->ptr + pcsv->len, sizeof(cstring_t) * numStrings); + memmove( + pcsv->ptr + position + numStrings + , pcsv->ptr + position + , sizeof(cstring_t) * (pcsv->len - position) + ); } for (i = 0; i != numStrings; ++i) { - cstring_t* dest = pcsv->ptr + pcsv->len + i; + cstring_t* dest = pcsv->ptr + position + i; if (NULL == strings) { @@ -339,7 +343,7 @@ cstring_vector_insertAt( { while (0 != i) { - cstring_destroy(pcsv->ptr + pcsv->len + (i - 1)); + cstring_destroy(pcsv->ptr + position + (i - 1)); --i; } @@ -359,7 +363,11 @@ cstring_vector_insertAt( /* If it's failed, and we made space for new items, remove the gap (leaving it with increased capacity) */ if (position < pcsv->len) { - memmove(pcsv->ptr + pcsv->len, pcsv->ptr + pcsv->len + numStrings, sizeof(cstring_t) * numStrings); + memmove( + pcsv->ptr + position + , pcsv->ptr + position + numStrings + , sizeof(cstring_t) * (pcsv->len - position) + ); } } diff --git a/test/performance/CMakeLists.txt b/test/performance/CMakeLists.txt index 5d84725..15d78ed 100644 --- a/test/performance/CMakeLists.txt +++ b/test/performance/CMakeLists.txt @@ -1,2 +1,10 @@ -# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +# Performance tests (human attention; not CTest-gated) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +add_subdirectory(test.performance.cstring) +add_subdirectory(test.performance.cstring_vector) + + +# ############################## end of file ############################# # diff --git a/test/performance/perf_harness.hpp b/test/performance/perf_harness.hpp new file mode 100644 index 0000000..157350c --- /dev/null +++ b/test/performance/perf_harness.hpp @@ -0,0 +1,705 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/performance/perf_harness.hpp + * + * Purpose: Shared helpers for cstring performance programs. + * + * Created: 23rd September 2026 + * Updated: 23rd September 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +#ifndef CSTRING_TEST_PERFORMANCE_PERF_HARNESS_HPP_INCLUDED +#define CSTRING_TEST_PERFORMANCE_PERF_HARNESS_HPP_INCLUDED + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include +#include +#include + +#ifdef HAS_P99 +# include +#endif + +#include +#include +#include +#include +#include +#include + +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * types + */ + +typedef stlsoft::std_chrono_hrc_stopwatch stopwatch_t; +typedef stopwatch_t::interval_type interval_t; + + +/* ///////////////////////////////////////////////////////////////////////// + * configuration + */ + +namespace cstring_perf { + +inline +std::size_t +env_size_t( + char const* name +, std::size_t default_value +) +{ + char const* const env = std::getenv(name); + + if (NULL == env || '\0' == *env) + { + return default_value; + } + + char* end = NULL; + unsigned long const v = std::strtoul(env, &end, 10); + + if (end == env || NULL == end || '\0' != *end || 0 == v) + { + return default_value; + } + + return static_cast(v); +} + +inline +std::size_t +default_iterations() +{ +#ifdef NDEBUG + + return env_size_t("CSTRING_PERF_ITERATIONS", 100000u); +#else + +return env_size_t("CSTRING_PERF_ITERATIONS", 1000u); +#endif +} + +inline +std::size_t +default_warmups() +{ + return env_size_t("CSTRING_PERF_WARMUPS", 2u); +} + +inline +std::size_t +default_file_trials() +{ +#ifdef NDEBUG + + return env_size_t("CSTRING_PERF_FILE_TRIALS", 200u); +#else + + return env_size_t("CSTRING_PERF_FILE_TRIALS", 40u); +#endif +} + + +/* ///////////////////////////////////////////////////////////////////////// + * payloads + */ + +inline +std::string +make_payload( + std::size_t n +) +{ + if (0 == n) + { + return std::string(); + } + + std::string s(n, 'x'); + + for (std::size_t i = 0; n != i; ++i) + { + s[i] = static_cast('a' + static_cast(i % 26)); + } + + return s; +} + + +/* ///////////////////////////////////////////////////////////////////////// + * display + */ + +template +inline +std::string +thousands( + T_integer const& v +) +{ + char dest[41]; + size_t n = stlsoft::format_thousands(dest, STLSOFT_NUM_ELEMENTS(dest), "3;0", v); + + return std::string(dest, n > 0 ? n - 1 : 0); +} + +inline +void +display_banner( + char const* program_name +) +{ + std::cout + << program_name + << ": competitive performance timings (human attention; not a CI gate)." + << std::endl + << " Note: std::string typically has SSO; cstring is heap-oriented —" + << std::endl + << " small sizes favour std::string. Prefer Release builds." + << std::endl +#ifdef HAS_P99 + << " p99: available — per-op percentiles reported where timed." + << std::endl + << " Env: CSTRING_PERF_ITERATIONS, CSTRING_PERF_WARMUPS" + << ", CSTRING_PERF_FILE_TRIALS" + << ", SIS_PERFTESTS_GROUPGAPS." +#else /* ? HAS_P99 */ + << " p99: not linked — mean ns/op only; file_lines suite skipped." + << std::endl + << " Env: CSTRING_PERF_ITERATIONS, CSTRING_PERF_WARMUPS" + << ", SIS_PERFTESTS_GROUPGAPS." +#endif /* HAS_P99 */ + << std::endl + ; +} +#ifdef HAS_P99 + +inline +void +append_percentile_columns( + p99::histogram const* h +) +{ + uint64_t p50 = 0; + uint64_t p90 = 0; + uint64_t p99v = 0; + uint64_t maxv = 0; + + if (NULL != h && !h->empty()) + { + (void)h->try_get_value_at_p50(&p50); + (void)h->try_get_value_at_percentile(90.0, &p90); + (void)h->try_get_value_at_p99(&p99v); + (void)h->try_get_max_event_time(&maxv); + } + + std::cout + << '\t' << std::setw(10) << std::right << thousands(p50) + << '\t' << std::setw(10) << std::right << thousands(p90) + << '\t' << std::setw(10) << std::right << thousands(p99v) + << '\t' << std::setw(10) << std::right << thousands(maxv) + ; +} +#endif /* HAS_P99 */ + +inline +bool +env_is_truey( + char const* name +) +{ + char const* const env = std::getenv(name); + + if (NULL == env || '\0' == *env) + { + return false; + } + + if (0 == ::strcmp(env, "1") || + 0 == ::strcmp(env, "ok") || + 0 == ::strcmp(env, "on") || + 0 == ::strcmp(env, "true") || + 0 == ::strcmp(env, "yes") || + 0 == ::strcmp(env, "y") || + 0 == ::strcmp(env, "OK") || + 0 == ::strcmp(env, "ON") || + 0 == ::strcmp(env, "TRUE") || + 0 == ::strcmp(env, "YES") || + 0 == ::strcmp(env, "Y")) + { + return true; + } + + return false; +} + +inline +bool +group_gaps_enabled() +{ + return env_is_truey("SIS_PERFTESTS_GROUPGAPS"); +} + +inline +void +maybe_emit_group_gap( + char const* scenario +, std::size_t size +) +{ + if (!group_gaps_enabled()) + { + return; + } + + static bool have_prev = false; + static std::string prev_scenario; + static std::size_t prev_size = 0; + + if (have_prev && + (prev_scenario != scenario || prev_size != size)) + { + std::cout << std::endl; + } + + prev_scenario = scenario; + prev_size = size; + have_prev = true; +} + +inline +void +display_results_title() +{ + std::cout + << '\t' + << std::setw(40) << std::left << "scenario" + << '\t' + << std::setw(12) << std::right << "size" + << '\t' + << std::setw(24) << std::left << "impl" + << '\t' + << std::setw(12) << std::right << "#iters" + << '\t' + << std::setw(10) << std::right << "#acts" + << '\t' + << std::setw(14) << std::right << "tm (ns)" + << '\t' + << std::setw(12) << std::right << "ns/op" + << '\t' + << std::setw(10) << std::right << "vs cstr" +#ifdef HAS_P99 + << '\t' + << std::setw(10) << std::right << "p50" + << '\t' + << std::setw(10) << std::right << "p90" + << '\t' + << std::setw(10) << std::right << "p99" + << '\t' + << std::setw(10) << std::right << "max" +#endif /* HAS_P99 */ + << '\t' + << std::setw(14) << std::right << "anchor" + << std::endl + ; +} + +inline +void +display_results( + char const* scenario +, std::size_t size +, char const* impl +, std::size_t num_iterations +, std::size_t num_actions +, interval_t tm_ns +, double ratio_vs_cstring +, std::uint64_t anchor_value +#ifdef HAS_P99 +, p99::histogram const* hist = NULL +#endif /* HAS_P99 */ +) +{ + maybe_emit_group_gap(scenario, size); + + std::size_t const denom = + (0 == num_iterations || 0 == num_actions) + ? 1u + : (num_iterations * num_actions) + ; + + std::cout + << '\t' + << std::setw(40) << std::left << scenario + << '\t' + << std::setw(12) << std::right << size + << '\t' + << std::setw(24) << std::left << impl + << '\t' + << std::setw(12) << std::right << num_iterations + << '\t' + << std::setw(10) << std::right << num_actions + << '\t' + << std::setw(14) << std::right << thousands(tm_ns) + << '\t' + << std::setw(12) << std::right << thousands(tm_ns / denom) + << '\t' + ; + + if (ratio_vs_cstring < 0.0) + { + std::cout << std::setw(10) << std::right << "-"; + } + else + { + std::cout + << std::setw(10) << std::right + << std::fixed << std::setprecision(2) << ratio_vs_cstring + ; + } +#ifdef HAS_P99 + + append_percentile_columns(hist); +#endif /* HAS_P99 */ + + std::cout + << '\t' + << std::setw(14) << std::right << anchor_value + << std::endl + ; +} + +inline +double +ratio_or_dash( + interval_t subject_ns +, interval_t baseline_ns +) +{ + if (0 == baseline_ns) + { + return -1.0; + } + + return static_cast(subject_ns) / static_cast(baseline_ns); +} + + +/* ///////////////////////////////////////////////////////////////////////// + * hand-rolled string (competitive floor) + */ + +struct raw_string +{ + char* ptr; + std::size_t len; + std::size_t capacity; +}; + +inline +void +raw_init( + raw_string* s +) +{ + s->ptr = NULL; + s->len = 0; + s->capacity = 0; +} + +inline +void +raw_destroy( + raw_string* s +) +{ + std::free(s->ptr); + raw_init(s); +} + +inline +int +raw_reserve( + raw_string* s +, std::size_t capacity +) +{ + if (capacity <= s->capacity && NULL != s->ptr) + { + return 0; + } + + char* const p = static_cast(std::realloc(s->ptr, capacity + 1u)); + + if (NULL == p) + { + return -1; + } + + s->ptr = p; + s->capacity = capacity; + s->ptr[s->len] = '\0'; + + return 0; +} + +inline +int +raw_assign_len( + raw_string* s +, char const* src +, std::size_t n +) +{ + if (0 != raw_reserve(s, n)) + { + return -1; + } + + if (0 != n) + { + ::memcpy(s->ptr, src, n); + } + + s->len = n; + s->ptr[n] = '\0'; + + return 0; +} + +inline +int +raw_append_len( + raw_string* s +, char const* src +, std::size_t n +) +{ + std::size_t const need = s->len + n; + + if (0 != raw_reserve(s, need)) + { + return -1; + } + + if (0 != n) + { + ::memcpy(s->ptr + s->len, src, n); + } + + s->len = need; + s->ptr[need] = '\0'; + + return 0; +} + +inline +int +raw_insert_len( + raw_string* s +, std::size_t pos +, char const* src +, std::size_t n +) +{ + if (pos > s->len) + { + pos = s->len; + } + + std::size_t const need = s->len + n; + + if (0 != raw_reserve(s, need)) + { + return -1; + } + + if (0 != n) + { + ::memmove(s->ptr + pos + n, s->ptr + pos, s->len - pos); + ::memcpy(s->ptr + pos, src, n); + } + + s->len = need; + s->ptr[need] = '\0'; + + return 0; +} + +inline +int +raw_copy( + raw_string* dest +, raw_string const* src +) +{ + return raw_assign_len(dest, src->ptr, src->len); +} + + +/* ///////////////////////////////////////////////////////////////////////// + * hand-rolled string vector (competitive floor) + */ + +struct raw_string_vector +{ + char** ptr; + std::size_t len; + std::size_t capacity; +}; + +inline +void +raw_vec_init( + raw_string_vector* v +) +{ + v->ptr = NULL; + v->len = 0; + v->capacity = 0; +} + +inline +void +raw_vec_destroy( + raw_string_vector* v +) +{ + if (NULL != v->ptr) + { + for (std::size_t i = 0; v->len != i; ++i) + { + std::free(v->ptr[i]); + } + + std::free(v->ptr); + } + + raw_vec_init(v); +} + +inline +int +raw_vec_reserve( + raw_string_vector* v +, std::size_t capacity +) +{ + if (capacity <= v->capacity) + { + return 0; + } + + char** const p = static_cast( + std::realloc(v->ptr, capacity * sizeof(char*)) + ); + + if (NULL == p) + { + return -1; + } + + v->ptr = p; + v->capacity = capacity; + + return 0; +} + +inline +int +raw_vec_append_cstr( + raw_string_vector* v +, char const* s +, std::size_t n +) +{ + if (v->len == v->capacity) + { + std::size_t const nc = + (0 == v->capacity) ? 8u : (v->capacity * 2u) + ; + + if (0 != raw_vec_reserve(v, nc)) + { + return -1; + } + } + + char* const copy = static_cast(std::malloc(n + 1u)); + + if (NULL == copy) + { + return -1; + } + + if (0 != n) + { + ::memcpy(copy, s, n); + } + + copy[n] = '\0'; + v->ptr[v->len++] = copy; + + return 0; +} + +inline +int +raw_vec_prepend_cstr( + raw_string_vector* v +, char const* s +, std::size_t n +) +{ + if (v->len == v->capacity) + { + std::size_t const nc = + (0 == v->capacity) ? 8u : (v->capacity * 2u) + ; + + if (0 != raw_vec_reserve(v, nc)) + { + return -1; + } + } + + char* const copy = static_cast(std::malloc(n + 1u)); + + if (NULL == copy) + { + return -1; + } + + if (0 != n) + { + ::memcpy(copy, s, n); + } + + copy[n] = '\0'; + + if (0 != v->len) + { + ::memmove(v->ptr + 1, v->ptr, v->len * sizeof(char*)); + } + + v->ptr[0] = copy; + ++v->len; + + return 0; +} +} /* namespace cstring_perf */ + + +/* ////////////////////////////////////////////////////////////////////// */ + +#endif /* !CSTRING_TEST_PERFORMANCE_PERF_HARNESS_HPP_INCLUDED */ + +/* ///////////////////////////// end of file //////////////////////////// */ diff --git a/test/performance/test.performance.cstring/CMakeLists.txt b/test/performance/test.performance.cstring/CMakeLists.txt new file mode 100644 index 0000000..96b4531 --- /dev/null +++ b/test/performance/test.performance.cstring/CMakeLists.txt @@ -0,0 +1,5 @@ +list(APPEND X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED + 4996 +) + +define_performance_test_program(test.performance.cstring main.cpp) diff --git a/test/performance/test.performance.cstring/main.cpp b/test/performance/test.performance.cstring/main.cpp new file mode 100644 index 0000000..0acfea5 --- /dev/null +++ b/test/performance/test.performance.cstring/main.cpp @@ -0,0 +1,558 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/performance/test.performance.cstring/main.cpp + * + * Purpose: Competitive performance tests for cstring vs std::string and a + * hand-rolled realloc/memcpy floor. When p99 is available, also + * report per-iteration percentiles. + * + * Created: 23rd September 2026 + * Updated: 23rd September 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include + +#include "perf_harness.hpp" + +#include +#include + +#include +#include +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * constants + */ + +namespace { + +char const* const IMPL_CSTRING = "cstring"; +char const* const IMPL_STD = "std::string"; +char const* const IMPL_RAW = "raw_realloc"; +char const* const IMPL_BORROWED = "cstring_borrowed"; +char const* const IMPL_FIXEDBUF = "fixed_char_buf"; + +std::size_t const SIZES[] = +{ + 16u, + 64u, + 256u, + 4096u, + 65536u, +}; + +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * helpers + */ + +namespace { + +using namespace cstring_perf; + +struct run_result +{ + interval_t tm_ns; + std::uint64_t anchor; +#ifdef HAS_P99 + + p99::histogram hist; +#endif /* HAS_P99 */ +}; + +template +run_result +time_iterations( + std::size_t num_iterations +, std::size_t num_warm_loops +, F fn +) +{ + run_result result = {}; + stopwatch_t sw; + + for (std::size_t w = num_warm_loops; 0 != w; --w) + { + result.anchor = 0; +#ifdef HAS_P99 + + result.hist.clear(); +#endif /* HAS_P99 */ + interval_t tm_ns = 0; +#ifdef HAS_P99 + + for (std::size_t i = 0; num_iterations != i; ++i) + { + sw.start(); + result.anchor += fn(); + sw.stop(); + + interval_t const sample = sw.get_nanoseconds(); + + tm_ns += sample; + (void)result.hist.push_ns(static_cast(sample)); + } +#else /* ? HAS_P99 */ + + sw.start(); + for (std::size_t i = 0; num_iterations != i; ++i) + { + result.anchor += fn(); + } + sw.stop(); + tm_ns = sw.get_nanoseconds(); +#endif /* HAS_P99 */ + + if (1 == w) + { + result.tm_ns = tm_ns; + } + } + + return result; +} + +void +emit_row( + char const* scenario +, std::size_t size +, char const* impl +, std::size_t num_iterations +, std::size_t num_actions +, run_result const& r +, interval_t cstring_tm_ns +) +{ + double const ratio = + (0 == ::strcmp(impl, IMPL_CSTRING) || 0 == ::strcmp(impl, IMPL_BORROWED)) + ? 1.0 + : ratio_or_dash(r.tm_ns, cstring_tm_ns) + ; + + display_results( + scenario + , size + , impl + , num_iterations + , num_actions + , r.tm_ns + , ratio + , r.anchor +#ifdef HAS_P99 + , &r.hist +#endif /* HAS_P99 */ + ); +} +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * scenarios + */ + +namespace { + +void +scenario_create_destroy_empty( + std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + run_result const cs = time_iterations(num_iterations, num_warm_loops, []() -> std::uint64_t { + cstring_t s = cstring_t_DEFAULT; + cstring_create(&s, ""); + std::uint64_t const a = s.len; + cstring_destroy(&s); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, []() -> std::uint64_t { + std::string s; + return s.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, []() -> std::uint64_t { + raw_string s; + raw_init(&s); + raw_assign_len(&s, "", 0); + std::uint64_t const a = s.len; + raw_destroy(&s); + return a; + }); + + emit_row("create_destroy_empty", 0, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + emit_row("create_destroy_empty", 0, IMPL_STD, num_iterations, 1, st, cs.tm_ns); + emit_row("create_destroy_empty", 0, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); +} + +void +scenario_create_destroy_len( + std::size_t n +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + std::string const payload = make_payload(n); + char const* const p = payload.data(); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + cstring_t s = cstring_t_DEFAULT; + cstring_createLen(&s, p, n); + std::uint64_t const a = s.len + (NULL != s.ptr ? static_cast(s.ptr[0]) : 0u); + cstring_destroy(&s); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + std::string s(p, n); + return s.size() + static_cast(s[0]); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + raw_string s; + raw_init(&s); + raw_assign_len(&s, p, n); + std::uint64_t const a = s.len + static_cast(s.ptr[0]); + raw_destroy(&s); + return a; + }); + + emit_row("create_destroy_len", n, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + emit_row("create_destroy_len", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); + emit_row("create_destroy_len", n, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); +} + +void +scenario_assign_len_grow( + std::size_t n +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + std::string const payload = make_payload(n); + char const* const p = payload.data(); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + cstring_t s = cstring_t_DEFAULT; + cstring_create(&s, ""); + cstring_assignLen(&s, p, n); + std::uint64_t const a = s.len; + cstring_destroy(&s); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + std::string s; + s.assign(p, n); + return s.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + raw_string s; + raw_init(&s); + raw_assign_len(&s, p, n); + std::uint64_t const a = s.len; + raw_destroy(&s); + return a; + }); + + emit_row("assign_len_grow", n, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + emit_row("assign_len_grow", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); + emit_row("assign_len_grow", n, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); +} + +void +scenario_append_len_growth( + std::size_t chunk +, std::size_t num_appends +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + 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 (std::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 (std::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 (std::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; + }); + + char scenario[64]; + std::snprintf(scenario, sizeof(scenario), "append_len_growth_x%zu", num_appends); + + emit_row(scenario, chunk, IMPL_CSTRING, num_iterations, num_appends, cs, cs.tm_ns); + emit_row(scenario, chunk, IMPL_STD, num_iterations, num_appends, st, cs.tm_ns); + emit_row(scenario, chunk, IMPL_RAW, num_iterations, num_appends, raw, cs.tm_ns); +} + +void +scenario_append_len_reserved( + std::size_t n +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + std::string const payload = make_payload(n); + char const* const p = payload.data(); + std::size_t const chunk = (n < 16u) ? n : 16u; + std::size_t const num_appends = (0 == chunk) ? 0u : (n / chunk); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [p, chunk, n, num_appends]() -> std::uint64_t { + cstring_t s = cstring_t_DEFAULT; + cstring_create(&s, ""); + cstring_setCapacity(&s, n); + for (std::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, n, num_appends]() -> std::uint64_t { + std::string s; + s.reserve(n); + for (std::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, n, num_appends]() -> std::uint64_t { + raw_string s; + raw_init(&s); + raw_reserve(&s, n); + for (std::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_reserved", n, IMPL_CSTRING, num_iterations, num_appends, cs, cs.tm_ns); + emit_row("append_len_reserved", n, IMPL_STD, num_iterations, num_appends, st, cs.tm_ns); + emit_row("append_len_reserved", n, IMPL_RAW, num_iterations, num_appends, raw, cs.tm_ns); +} + +void +scenario_insert_len_mid( + std::size_t n +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + std::string const base = make_payload(n); + std::string const ins = make_payload(n / 4u == 0 ? 1u : n / 4u); + char const* const bp = base.data(); + char const* const ip = ins.data(); + std::size_t const in = ins.size(); + int const pos = static_cast(n / 2u); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [bp, n, ip, in, pos]() -> std::uint64_t { + cstring_t s = cstring_t_DEFAULT; + cstring_createLen(&s, bp, n); + cstring_insertLen(&s, pos, ip, in); + std::uint64_t const a = s.len; + cstring_destroy(&s); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [bp, n, ip, in, pos]() -> std::uint64_t { + std::string s(bp, n); + s.insert(pos, ip, in); + return s.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [bp, n, ip, in, pos]() -> std::uint64_t { + raw_string s; + raw_init(&s); + raw_assign_len(&s, bp, n); + raw_insert_len(&s, pos, ip, in); + std::uint64_t const a = s.len; + raw_destroy(&s); + return a; + }); + + emit_row("insert_len_mid", n, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + emit_row("insert_len_mid", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); + emit_row("insert_len_mid", n, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); +} + +void +scenario_copy( + std::size_t n +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + std::string const payload = make_payload(n); + char const* const p = payload.data(); + + cstring_t src_cs = cstring_t_DEFAULT; + cstring_createLen(&src_cs, p, n); + + std::string const src_st(p, n); + + raw_string src_raw; + raw_init(&src_raw); + raw_assign_len(&src_raw, p, n); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [&src_cs]() -> std::uint64_t { + cstring_t d = cstring_t_DEFAULT; + cstring_copy(&d, &src_cs); + std::uint64_t const a = d.len; + cstring_destroy(&d); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [&src_st]() -> std::uint64_t { + std::string d(src_st); + return d.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [&src_raw]() -> std::uint64_t { + raw_string d; + raw_init(&d); + raw_copy(&d, &src_raw); + std::uint64_t const a = d.len; + raw_destroy(&d); + return a; + }); + + cstring_destroy(&src_cs); + raw_destroy(&src_raw); + + emit_row("copy", n, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + emit_row("copy", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); + emit_row("copy", n, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); +} + +void +scenario_borrowed_fixed( + std::size_t n +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + std::string const payload = make_payload(n); + char const* const p = payload.data(); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + std::vector buf(n + 1u, '\0'); + cstring_t s = cstring_t_DEFAULT; + cstring_createEx( + &s + , "" + , CSTRING_F_MEMORY_IS_BORROWED | CSTRING_F_MEMORY_IS_FIXED + , &buf[0] + , n + 1u + ); + cstring_assignLen(&s, p, n); + std::uint64_t const a = s.len; + cstring_destroy(&s); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + std::string s; + s.assign(p, n); + return s.size(); + }); + + run_result const fx = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + std::vector buf(n + 1u, '\0'); + ::memcpy(&buf[0], p, n); + buf[n] = '\0'; + return n + static_cast(buf[0]); + }); + + emit_row("borrowed_fixed_assign", n, IMPL_BORROWED, num_iterations, 1, cs, cs.tm_ns); + emit_row("borrowed_fixed_assign", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); + emit_row("borrowed_fixed_assign", n, IMPL_FIXEDBUF, num_iterations, 1, fx, cs.tm_ns); +} +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * main + */ + +int main(int /*argc*/, char* /*argv*/[]) +{ + std::size_t const num_iterations = cstring_perf::default_iterations(); + std::size_t const num_warm_loops = cstring_perf::default_warmups(); + + // Fewer outer iters for heavy growth / large sizes + std::size_t const heavy_iters = + (num_iterations > 10000u) ? (num_iterations / 10u) : num_iterations + ; + + cstring_perf::display_banner("test.performance.cstring"); + cstring_perf::display_results_title(); + + scenario_create_destroy_empty(num_iterations, num_warm_loops); + + for (std::size_t i = 0; STLSOFT_NUM_ELEMENTS(SIZES) != i; ++i) + { + std::size_t const n = SIZES[i]; + std::size_t const iters = (n >= 4096u) ? heavy_iters : num_iterations; + + scenario_create_destroy_len(n, iters, num_warm_loops); + scenario_assign_len_grow(n, iters, num_warm_loops); + scenario_append_len_reserved(n, iters, num_warm_loops); + scenario_copy(n, iters, num_warm_loops); + scenario_borrowed_fixed(n, iters, num_warm_loops); + + if (n <= 4096u) + { + scenario_insert_len_mid(n, iters, num_warm_loops); + scenario_append_len_growth(16u, n / 16u == 0 ? 1u : n / 16u, heavy_iters, num_warm_loops); + } + } + + return EXIT_SUCCESS; +} + + +/* ///////////////////////////// end of file //////////////////////////// */ diff --git a/test/performance/test.performance.cstring_vector/CMakeLists.txt b/test/performance/test.performance.cstring_vector/CMakeLists.txt new file mode 100644 index 0000000..5d7d407 --- /dev/null +++ b/test/performance/test.performance.cstring_vector/CMakeLists.txt @@ -0,0 +1,5 @@ +list(APPEND X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED + 4996 +) + +define_performance_test_program(test.performance.cstring_vector main.cpp) diff --git a/test/performance/test.performance.cstring_vector/main.cpp b/test/performance/test.performance.cstring_vector/main.cpp new file mode 100644 index 0000000..f783477 --- /dev/null +++ b/test/performance/test.performance.cstring_vector/main.cpp @@ -0,0 +1,523 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/performance/test.performance.cstring_vector/main.cpp + * + * Purpose: Competitive performance tests for cstring_vector vs + * std::vector and a hand-rolled floor. When p99 is + * available, report per-iteration percentiles throughout, and run + * the filesystem suite (cstring_vector_readLines, ifstream + + * getline, platformstl::file_lines). + * + * Created: 23rd September 2026 + * Updated: 23rd September 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include + +#include "perf_harness.hpp" + +#ifdef HAS_P99 +# include +# include +# include +#endif /* HAS_P99 */ + +#include +#include + +#include +#include +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * constants + */ + +namespace { + +char const* const IMPL_CSTRING_VECTOR = "cstring_vector"; +char const* const IMPL_STD_VECTOR = "vector"; +char const* const IMPL_RAW_VECTOR = "raw_char_star_vec"; +char const* const IMPL_READLINES = "cstring_vector_readLines"; +#ifdef HAS_P99 +char const* const IMPL_GETLINE = "ifstream+getline"; +char const* const IMPL_FILE_LINES = "platformstl::file_lines"; + +char const TEST_FILE_NAME[] = "test.performance.cstring_vector.lines.txt"; +#endif /* HAS_P99 */ +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * helpers + */ + +namespace { + +using namespace cstring_perf; + +struct run_result +{ + interval_t tm_ns; + std::uint64_t anchor; +#ifdef HAS_P99 + + p99::histogram hist; +#endif /* HAS_P99 */ +}; + +template +run_result +time_iterations( + std::size_t num_iterations +, std::size_t num_warm_loops +, F fn +) +{ + run_result result = {}; + stopwatch_t sw; + + for (std::size_t w = num_warm_loops; 0 != w; --w) + { + result.anchor = 0; +#ifdef HAS_P99 + + result.hist.clear(); +#endif /* HAS_P99 */ + interval_t tm_ns = 0; +#ifdef HAS_P99 + + for (std::size_t i = 0; num_iterations != i; ++i) + { + sw.start(); + result.anchor += fn(); + sw.stop(); + + interval_t const sample = sw.get_nanoseconds(); + + tm_ns += sample; + (void)result.hist.push_ns(static_cast(sample)); + } +#else /* ? HAS_P99 */ + + sw.start(); + for (std::size_t i = 0; num_iterations != i; ++i) + { + result.anchor += fn(); + } + sw.stop(); + tm_ns = sw.get_nanoseconds(); +#endif /* HAS_P99 */ + + if (1 == w) + { + result.tm_ns = tm_ns; + } + } + + return result; +} + +void +emit_row( + char const* scenario +, std::size_t size +, char const* impl +, std::size_t num_iterations +, std::size_t num_actions +, run_result const& r +, interval_t cstring_tm_ns +) +{ + double const ratio = + (0 == ::strcmp(impl, IMPL_CSTRING_VECTOR) || + 0 == ::strcmp(impl, IMPL_READLINES)) + ? 1.0 + : ratio_or_dash(r.tm_ns, cstring_tm_ns) + ; + + display_results( + scenario + , size + , impl + , num_iterations + , num_actions + , r.tm_ns + , ratio + , r.anchor +#ifdef HAS_P99 + , &r.hist +#endif /* HAS_P99 */ + ); +} + +cstring_t +make_cstring_payload( + std::size_t n +) +{ + std::string const s = make_payload(n); + cstring_t cs = cstring_t_DEFAULT; + + cstring_createLen(&cs, s.data(), n); + + return cs; +} + +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * scenarios + */ + +namespace { + +void +scenario_create_destroy( + std::size_t n +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + run_result const cs = time_iterations(num_iterations, num_warm_loops, [n]() -> std::uint64_t { + cstring_vector_t v = cstring_vector_t_DEFAULT; + cstring_vector_create(&v, n); + std::uint64_t const a = v.len + v.capacity; + cstring_vector_destroy(&v); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [n]() -> std::uint64_t { + std::vector v(n); + return v.size() + v.capacity(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [n]() -> std::uint64_t { + raw_string_vector v; + raw_vec_init(&v); + raw_vec_reserve(&v, n); + v.len = n; + for (std::size_t i = 0; n != i; ++i) + { + v.ptr[i] = static_cast(std::calloc(1, 1)); + } + std::uint64_t const a = v.len + v.capacity; + raw_vec_destroy(&v); + return a; + }); + + emit_row("create_destroy", n, IMPL_CSTRING_VECTOR, num_iterations, 1, cs, cs.tm_ns); + emit_row("create_destroy", n, IMPL_STD_VECTOR, num_iterations, 1, st, cs.tm_ns); + emit_row("create_destroy", n, IMPL_RAW_VECTOR, num_iterations, 1, raw, cs.tm_ns); +} + +void +scenario_append_one_by_one( + std::size_t payload_len +, std::size_t k +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + cstring_t payload = make_cstring_payload(payload_len); + std::string const payload_s = make_payload(payload_len); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [&payload, k]() -> std::uint64_t { + cstring_vector_t v = cstring_vector_t_DEFAULT; + cstring_vector_init(&v, 0); + for (std::size_t i = 0; k != i; ++i) + { + cstring_vector_append(&v, &payload, 1); + } + std::uint64_t const a = v.len; + cstring_vector_destroy(&v); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [&payload_s, k]() -> std::uint64_t { + std::vector v; + for (std::size_t i = 0; k != i; ++i) + { + v.push_back(payload_s); + } + return v.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [&payload_s, payload_len, k]() -> std::uint64_t { + raw_string_vector v; + raw_vec_init(&v); + for (std::size_t i = 0; k != i; ++i) + { + raw_vec_append_cstr(&v, payload_s.data(), payload_len); + } + std::uint64_t const a = v.len; + raw_vec_destroy(&v); + return a; + }); + + cstring_destroy(&payload); + + emit_row("append_one_by_one", payload_len, IMPL_CSTRING_VECTOR, num_iterations, k, cs, cs.tm_ns); + emit_row("append_one_by_one", payload_len, IMPL_STD_VECTOR, num_iterations, k, st, cs.tm_ns); + emit_row("append_one_by_one", payload_len, IMPL_RAW_VECTOR, num_iterations, k, raw, cs.tm_ns); +} + +void +scenario_append_batch( + std::size_t payload_len +, std::size_t k +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + std::vector payloads(k); + std::vector payloads_s(k); + std::string const proto = make_payload(payload_len); + + for (std::size_t i = 0; k != i; ++i) + { + payloads[i] = cstring_t_DEFAULT; + cstring_createLen(&payloads[i], proto.data(), payload_len); + payloads_s[i] = proto; + } + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [&payloads, k]() -> std::uint64_t { + cstring_vector_t v = cstring_vector_t_DEFAULT; + cstring_vector_init(&v, 0); + cstring_vector_append(&v, &payloads[0], k); + std::uint64_t const a = v.len; + cstring_vector_destroy(&v); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [&payloads_s]() -> std::uint64_t { + std::vector v; + v.insert(v.end(), payloads_s.begin(), payloads_s.end()); + return v.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [&payloads_s, payload_len, k]() -> std::uint64_t { + raw_string_vector v; + raw_vec_init(&v); + raw_vec_reserve(&v, k); + for (std::size_t i = 0; k != i; ++i) + { + raw_vec_append_cstr(&v, payloads_s[i].data(), payload_len); + } + std::uint64_t const a = v.len; + raw_vec_destroy(&v); + return a; + }); + + for (std::size_t i = 0; k != i; ++i) + { + cstring_destroy(&payloads[i]); + } + + emit_row("append_batch", payload_len, IMPL_CSTRING_VECTOR, num_iterations, k, cs, cs.tm_ns); + emit_row("append_batch", payload_len, IMPL_STD_VECTOR, num_iterations, k, st, cs.tm_ns); + emit_row("append_batch", payload_len, IMPL_RAW_VECTOR, num_iterations, k, raw, cs.tm_ns); +} + +void +scenario_prepend_one_by_one( + std::size_t payload_len +, std::size_t k +, std::size_t num_iterations +, std::size_t num_warm_loops +) +{ + cstring_t payload = make_cstring_payload(payload_len); + std::string const payload_s = make_payload(payload_len); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [&payload, k]() -> std::uint64_t { + cstring_vector_t v = cstring_vector_t_DEFAULT; + cstring_vector_init(&v, 0); + for (std::size_t i = 0; k != i; ++i) + { + cstring_vector_prepend(&v, &payload, 1); + } + std::uint64_t const a = v.len; + cstring_vector_destroy(&v); + return a; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [&payload_s, k]() -> std::uint64_t { + std::vector v; + for (std::size_t i = 0; k != i; ++i) + { + v.insert(v.begin(), payload_s); + } + return v.size(); + }); + + run_result const raw = time_iterations(num_iterations, num_warm_loops, [&payload_s, payload_len, k]() -> std::uint64_t { + raw_string_vector v; + raw_vec_init(&v); + for (std::size_t i = 0; k != i; ++i) + { + raw_vec_prepend_cstr(&v, payload_s.data(), payload_len); + } + std::uint64_t const a = v.len; + raw_vec_destroy(&v); + return a; + }); + + cstring_destroy(&payload); + + emit_row("prepend_one_by_one", payload_len, IMPL_CSTRING_VECTOR, num_iterations, k, cs, cs.tm_ns); + emit_row("prepend_one_by_one", payload_len, IMPL_STD_VECTOR, num_iterations, k, st, cs.tm_ns); + emit_row("prepend_one_by_one", payload_len, IMPL_RAW_VECTOR, num_iterations, k, raw, cs.tm_ns); +} +#ifdef HAS_P99 + +bool +write_lines_file( + char const* path +, std::size_t num_lines +, std::size_t line_len +) +{ + std::ofstream out(path, std::ios::binary | std::ios::trunc); + + if (!out) + { + return false; + } + + std::string const line = make_payload(line_len); + + for (std::size_t i = 0; num_lines != i; ++i) + { + out << line << '\n'; + } + + return static_cast(out); +} + +void +scenario_file_lines_vs_readlines( + std::size_t num_lines +, std::size_t line_len +, std::size_t num_trials +, std::size_t num_warm_loops +) +{ + if (!write_lines_file(TEST_FILE_NAME, num_lines, line_len)) + { + std::cerr + << "failed to write " << TEST_FILE_NAME << "; skipping file_lines suite" + << std::endl + ; + return; + } + + std::cout + << "\nfile_lines suite (" << num_lines << " lines x " << line_len + << " chars; " << num_trials + << " trials; filesystem-sensitive — use p99 percentiles):" + << std::endl + ; + + run_result const cs = time_iterations(num_trials, num_warm_loops, []() -> std::uint64_t { + FILE* f = std::fopen(TEST_FILE_NAME, "rb"); + if (NULL == f) + { + return 0; + } + + cstring_vector_t v = cstring_vector_t_DEFAULT; + cstring_vector_init(&v, 0); + size_t num_read = 0; + cstring_vector_readLines(f, &v, &num_read); + std::uint64_t const a = v.len + num_read; + cstring_vector_destroy(&v); + std::fclose(f); + return a; + }); + + run_result const gl = time_iterations(num_trials, num_warm_loops, []() -> std::uint64_t { + std::ifstream in(TEST_FILE_NAME); + + if (!in) + { + return 0; + } + + std::vector lines; + std::string line; + + while (std::getline(in, line)) + { + lines.push_back(std::move(line)); + } + + return lines.size() + (lines.empty() ? 0u : lines[0].size()); + }); + + run_result const fl = time_iterations(num_trials, num_warm_loops, []() -> std::uint64_t { + platformstl::file_lines_a lines(TEST_FILE_NAME); + return lines.size() + (lines.empty() ? 0u : lines[0].size()); + }); + + emit_row("file_read_all_lines", num_lines, IMPL_READLINES, num_trials, 1, cs, cs.tm_ns); + emit_row("file_read_all_lines", num_lines, IMPL_GETLINE, num_trials, 1, gl, cs.tm_ns); + emit_row("file_read_all_lines", num_lines, IMPL_FILE_LINES, num_trials, 1, fl, cs.tm_ns); + + std::remove(TEST_FILE_NAME); +} +#endif /* HAS_P99 */ +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * main + */ + +int main(int /*argc*/, char* /*argv*/[]) +{ + std::size_t const num_iterations = cstring_perf::default_iterations(); + std::size_t const num_warm_loops = cstring_perf::default_warmups(); + std::size_t const heavy_iters = + (num_iterations > 10000u) ? (num_iterations / 10u) : num_iterations + ; + + cstring_perf::display_banner("test.performance.cstring_vector"); + cstring_perf::display_results_title(); + + scenario_create_destroy(16u, num_iterations, num_warm_loops); + scenario_create_destroy(256u, heavy_iters, num_warm_loops); + + scenario_append_one_by_one(16u, 64u, heavy_iters, num_warm_loops); + scenario_append_one_by_one(256u, 64u, heavy_iters, num_warm_loops); + + scenario_append_batch(16u, 64u, heavy_iters, num_warm_loops); + scenario_append_batch(256u, 64u, heavy_iters, num_warm_loops); + + scenario_prepend_one_by_one(16u, 32u, heavy_iters, num_warm_loops); +#ifdef HAS_P99 + + std::size_t const file_trials = cstring_perf::default_file_trials(); + + scenario_file_lines_vs_readlines(1000u, 64u, file_trials, num_warm_loops); + scenario_file_lines_vs_readlines(5000u, 80u, file_trials, num_warm_loops); +#else /* ? HAS_P99 */ + + std::cout + << "\nfile_lines suite skipped (p99 not available)." + << std::endl + ; +#endif /* HAS_P99 */ + + return EXIT_SUCCESS; +} + + +/* ///////////////////////////// end of file //////////////////////////// */ From cc85bf7f5ef8e80de024ac7732ea113f8c4ab3de Mon Sep 17 00:00:00 2001 From: synesissoftware Date: Sun, 27 Sep 2026 16:23:21 +1000 Subject: [PATCH 60/88] More component tests (#23) * test(cstring): rename component readline/writeline cases descriptively Adopt TEST__ names for cstring_readline and cstring_writeline/cstring_write component cases; drop empty writeline stubs; refresh Updated banners and prefer char* argv[] in main. * test(cstring): adopt terse xTests API in component suites Switch readline/writeline component assertions to TEST_ENUM_EQ, TEST_INT_EQ, TEST_MS_EQ, TEST_PASSED, and REQUIRE; mark vector APIs CSTRING_EXTERN_C for C++ linkage; wire test.component.cstring_vector_readLines into the component CMake tree. * test(cstring): expand cstring_readline component coverage Fill empty readline cases for empty file, CRLF/mixed EOL, final EOL, consecutive empties, long and many short lines, and cstring reuse; add binary fixture helpers; correct the vector header File: banner. * test(cstring): add cstring_vector_readLines component suite Add test.component.cstring_vector_readLines covering empty file, EOL variants, empty-at-EOF omission, append-onto-existing, long and many short lines; use the xTests terse API. * ci * test(cstring): finish terse xTests API in readline coverage Convert the remaining verbose XTESTS_* assertions in the expanded cstring_readline cases to REQUIRE, TEST_ENUM_EQ, TEST_INT_EQ, and TEST_MS_EQ. --- .github/workflows/ci.yml | 1 + include/cstring/cstring.vector.h | 4 +- test/component/CMakeLists.txt | 1 + .../test.component.cstring_readline/entry.cpp | 433 ++++++++++++-- .../CMakeLists.txt | 2 + .../entry.cpp | 541 ++++++++++++++++++ .../entry.cpp | 81 +-- 7 files changed, 941 insertions(+), 122 deletions(-) create mode 100644 test/component/test.component.cstring_vector_readLines/CMakeLists.txt create mode 100644 test/component/test.component.cstring_vector_readLines/entry.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 758f139..9ac7ff1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ on: - rc1 - rc2 - rc3 + - component-tests pull_request: concurrency: diff --git a/include/cstring/cstring.vector.h b/include/cstring/cstring.vector.h index 8afe3b2..715341d 100644 --- a/include/cstring/cstring.vector.h +++ b/include/cstring/cstring.vector.h @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: cstring/vector.h + * File: cstring/cstring.vector.h * * Purpose: Definition of the cstring.vector API. * * Created: 21st January 2012 - * Updated: 23rd September 2026 + * Updated: 27th September 2026 * * Home: http://synesis.com.au/software/ * diff --git a/test/component/CMakeLists.txt b/test/component/CMakeLists.txt index ab18c18..fd37bfb 100644 --- a/test/component/CMakeLists.txt +++ b/test/component/CMakeLists.txt @@ -1,5 +1,6 @@ if(NOT NO_CSTRING_CPP_API) add_subdirectory(test.component.cstring_readline) + add_subdirectory(test.component.cstring_vector_readLines) add_subdirectory(test.component.cstring_writeline) endif() diff --git a/test/component/test.component.cstring_readline/entry.cpp b/test/component/test.component.cstring_readline/entry.cpp index 0fd4a27..7bb3c7c 100644 --- a/test/component/test.component.cstring_readline/entry.cpp +++ b/test/component/test.component.cstring_readline/entry.cpp @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// * File: test.component.cstring_readline/entry.cpp * - * Purpose: Unit-tests `cstring_readline()`. + * Purpose: Component-tests `cstring_readline()`. * * Created: 23rd May 2009 - * Updated: 12th January 2024 + * Updated: 27th September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -22,15 +22,22 @@ /* xTests header files */ #include +#include /* STLSoft header files */ #include -#include #include #include +/* Standard C++ header files */ +#include +#include + /* Standard C header files */ +#include #include +#include + /* ///////////////////////////////////////////////////////////////////////// * forward declarations @@ -39,28 +46,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_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); int setup(void*); int teardown(void*); - } // anonymous namespace + /* ///////////////////////////////////////////////////////////////////////// * constants & definitions */ const char TEST_FILE_NAME[] = "test.component.cstring_readline.txt"; + /* ///////////////////////////////////////////////////////////////////////// * compiler compatibility */ @@ -72,11 +80,12 @@ const char TEST_FILE_NAME[] = "test.component.cstring_readline.txt"; # pragma warning(disable : 4702) #endif /* compiler */ + /* ///////////////////////////////////////////////////////////////////////// * main */ -int main(int argc, char **argv) +int main(int argc, char* argv[]) { int retCode = EXIT_SUCCESS; int verbosity = 2; @@ -85,16 +94,16 @@ int main(int argc, char **argv) if (XTESTS_START_RUNNER_WITH_SETUP_FNS("test.component.cstring_readline", verbosity, setup, teardown, (void*)TEST_FILE_NAME)) { - XTESTS_RUN_CASE(test_1_0); - XTESTS_RUN_CASE(test_1_1); - XTESTS_RUN_CASE(test_1_2); - XTESTS_RUN_CASE(test_1_3); - XTESTS_RUN_CASE(test_1_4); - XTESTS_RUN_CASE(test_1_5); - XTESTS_RUN_CASE(test_1_6); - XTESTS_RUN_CASE(test_1_7); - XTESTS_RUN_CASE(test_1_8); - XTESTS_RUN_CASE(test_1_9); + 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_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_LONG_LINES); + XTESTS_RUN_CASE(TEST_cstring_readline_MANY_SHORT_LINES); + XTESTS_RUN_CASE(TEST_cstring_readline_REUSE_AFTER_LONG_LINE); XTESTS_PRINT_RESULTS(); @@ -104,6 +113,7 @@ int main(int argc, char **argv) return retCode; } + /* ///////////////////////////////////////////////////////////////////////// * compiler compatibility */ @@ -114,14 +124,15 @@ int main(int argc, char **argv) # endif /* compiler */ #endif /* compiler */ + /* ///////////////////////////////////////////////////////////////////////// - * test function implementations + * helper functions */ namespace { - static FILE* fopen_or_throw(char const* fileName, char const* mode /* = "r" */) + static FILE* fopen_or_throw(char const* fileName, char const* mode) { #ifdef CSTRING_USING_SAFE_STR_FUNCTIONS FILE* f; @@ -139,6 +150,28 @@ namespace return f; } + static void write_bytes(char const* fileName, void const* bytes, size_t cb) + { + FILE* f = fopen_or_throw(fileName, "wb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + if (cb != ::fwrite(bytes, 1, cb, f)) + { + throw platformstl::platform_exception("Could not write test file", platformstl::system_traits::get_last_error()); + } + } + + static void write_string_bytes(char const* fileName, std::string const& bytes) + { + write_bytes(fileName, bytes.data(), bytes.size()); + } + + static std::string make_filled(size_t n, char ch) + { + return std::string(n, ch); + } + int setup(void*) { return 0; @@ -152,28 +185,35 @@ namespace return 0; } +} // anonymous namespace +/* ///////////////////////////////////////////////////////////////////////// + * test function implementations + */ + +namespace +{ -static void test_1_0() +static void TEST_cstring_readline_CALLABILITY() { false && cstring_readline(stdout, NULL, NULL); - XTESTS_TEST_PASSED(); + TEST_PASSED(); } -static void test_1_1() +static void TEST_cstring_readline_INVALID_STREAM() { cstring_t cs = cstring_t_DEFAULT; - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_INVALIDSTREAM, cstring_readline(NULL, &cs, NULL)); + TEST_ENUM_EQ(CSTRING_RC_INVALIDSTREAM, cstring_readline(NULL, &cs, NULL)); size_t n; - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_INVALIDSTREAM, cstring_readline(NULL, &cs, &n)); + TEST_ENUM_EQ(CSTRING_RC_INVALIDSTREAM, cstring_readline(NULL, &cs, &n)); } -static void test_1_2() +static void TEST_cstring_readline_SHORT_MULTILINE() { { FILE* f = fopen_or_throw(TEST_FILE_NAME, "w"); @@ -195,84 +235,351 @@ static void test_1_2() stlsoft::scoped_handle scoper(f, ::fclose); CSTRING_RC rc; - cstring_t strings[7] = { cstring_t_DEFAULT, cstring_t_DEFAULT, cstring_t_DEFAULT, cstring_t_DEFAULT, cstring_t_DEFAULT, cstring_t_DEFAULT }; + cstring_t strings[7] = { + cstring_t_DEFAULT, + cstring_t_DEFAULT, + cstring_t_DEFAULT, + cstring_t_DEFAULT, + cstring_t_DEFAULT, + cstring_t_DEFAULT, + cstring_t_DEFAULT, + }; size_t lengths[7]; rc = cstring_readline(f, &strings[0], &lengths[0]); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); - XTESTS_TEST_INTEGER_EQUAL(0u, lengths[0]); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("", strings[0]); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(0u, lengths[0]); + TEST_MS_EQ("", strings[0]); rc = cstring_readline(f, &strings[1], &lengths[1]); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); - XTESTS_TEST_INTEGER_EQUAL(3u, lengths[1]); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("abc", strings[1]); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(3u, lengths[1]); + TEST_MS_EQ("abc", strings[1]); rc = cstring_readline(f, &strings[2], &lengths[2]); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); - XTESTS_TEST_INTEGER_EQUAL(6u, lengths[2]); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("abcdef", strings[2]); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(6u, lengths[2]); + TEST_MS_EQ("abcdef", strings[2]); rc = cstring_readline(f, &strings[3], &lengths[3]); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); - XTESTS_TEST_INTEGER_EQUAL(12u, lengths[3]); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("abcdefghijkl", strings[3]); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(12u, lengths[3]); + TEST_MS_EQ("abcdefghijkl", strings[3]); rc = cstring_readline(f, &strings[4], &lengths[4]); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); - XTESTS_TEST_INTEGER_EQUAL(26u, lengths[4]); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("abcdefghijklmnopqrstuvwxyz", strings[4]); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(26u, lengths[4]); + TEST_MS_EQ("abcdefghijklmnopqrstuvwxyz", strings[4]); rc = cstring_readline(f, &strings[5], &lengths[5]); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); - XTESTS_TEST_INTEGER_EQUAL(62u, lengths[5]); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", strings[5]); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(62u, lengths[5]); + TEST_MS_EQ("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", strings[5]); rc = cstring_readline(f, &strings[6], &lengths[6]); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_EOF, rc)); - XTESTS_TEST_INTEGER_EQUAL(3u, lengths[6]); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("xyz", strings[6]); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(3u, lengths[6]); + TEST_MS_EQ("xyz", strings[6]); + + { for (size_t i = 0; i != 7; ++i) + { + cstring_destroy(&strings[i]); + }} } } -static void test_1_3() +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_1_4() +static void TEST_cstring_readline_CRLF_AND_MIXED_EOL() { + /* CRLF, mixed EOL, and lone CR mid-line (binary I/O so CR is visible) */ + { + std::string bytes; + + bytes.append("one\r\n"); + bytes.append("two\n"); + bytes.append("three\r\n"); + bytes.append("has\rembed\r\n"); + bytes.append("end"); + + write_string_bytes(TEST_FILE_NAME, bytes); + } + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t n; + CSTRING_RC rc; + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + /* CRLF: CR is appended then stripped; numRead still counts the CR */ + TEST_INT_EQ(4u, n); + TEST_INT_EQ(3u, cs.len); + TEST_MS_EQ("one", cs); + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(3u, n); + TEST_INT_EQ(3u, cs.len); + TEST_MS_EQ("two", cs); + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(6u, n); + TEST_INT_EQ(5u, cs.len); + TEST_MS_EQ("three", cs); + + rc = cstring_readline(f, &cs, &n); + 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); + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(3u, n); + TEST_INT_EQ(3u, cs.len); + TEST_MS_EQ("end", cs); + + cstring_destroy(&cs); } -static void test_1_5() +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); + + cstring_t cs = cstring_t_DEFAULT; + size_t n; + CSTRING_RC rc; + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_MS_EQ("alpha", cs); + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_MS_EQ("beta", cs); + + 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); + + 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); + + cstring_t cs = cstring_t_DEFAULT; + size_t n; + CSTRING_RC rc; + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_MS_EQ("alpha", cs); + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(4u, n); + TEST_MS_EQ("beta", cs); + + cstring_destroy(&cs); + } } -static void test_1_6() +static void TEST_cstring_readline_CONSECUTIVE_EMPTY_LINES() { + /* consecutive empty lines */ + write_string_bytes(TEST_FILE_NAME, std::string("\n\n\n")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t n; + CSTRING_RC rc; + + { for (int i = 0; i != 3; ++i) + { + rc = cstring_readline(f, &cs, &n); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(0u, n); + TEST_INT_EQ(0u, cs.len); + TEST_MS_EQ("", cs); + }} + + rc = cstring_readline(f, &cs, &n); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(0u, n); + + cstring_destroy(&cs); } -static void test_1_7() +static void TEST_cstring_readline_LONG_LINES() { + /* long lines at several lengths */ + static size_t const lengths[] = { + 1024u, + 4096u, + 16384u, + 65536u, + }; + + { for (size_t i = 0; i != sizeof(lengths) / sizeof(lengths[0]); ++i) + { + size_t const len = lengths[i]; + std::string line = make_filled(len, static_cast('A' + static_cast(i % 26))); + std::string bytes = line; + + bytes.push_back('\n'); + bytes.append(make_filled(len / 2u == 0 ? 1u : len / 2u, 'z')); + + write_string_bytes(TEST_FILE_NAME, bytes); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t n; + CSTRING_RC rc; + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(len, n); + TEST_INT_EQ(len, cs.len); + REQUIRE(TEST_MS_EQ(line.c_str(), cs)); + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(len / 2u == 0 ? 1u : len / 2u, n); + + cstring_destroy(&cs); + }} } -static void test_1_8() +static void TEST_cstring_readline_MANY_SHORT_LINES() { + /* many short lines */ + size_t const num_lines = 1000u; + std::string bytes; + std::vector expected; + + expected.reserve(num_lines); + + { for (size_t i = 0; i != num_lines; ++i) + { + char buf[32]; + + snprintf(buf, sizeof(buf), "line-%04u", static_cast(i)); + + expected.push_back(buf); + bytes.append(buf); + bytes.push_back('\n'); + }} + + write_string_bytes(TEST_FILE_NAME, bytes); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t n; + CSTRING_RC rc; + + { for (size_t i = 0; i != num_lines; ++i) + { + rc = cstring_readline(f, &cs, &n); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(expected[i].size(), n); + TEST_MS_EQ(expected[i].c_str(), cs); + }} + + rc = cstring_readline(f, &cs, &n); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(0u, n); + + cstring_destroy(&cs); } -static void test_1_9() +static void TEST_cstring_readline_REUSE_AFTER_LONG_LINE() { -} + /* reuse one cstring_t after a long line then a short line; numRead may be NULL */ + std::string const long_line = make_filled(8192u, 'L'); + std::string bytes = long_line; + + bytes.push_back('\n'); + bytes.append("short"); + write_string_bytes(TEST_FILE_NAME, bytes); + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + CSTRING_RC rc; + + rc = cstring_readline(f, &cs, NULL); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(8192u, cs.len); + TEST_MS_EQ(long_line.c_str(), cs); + + rc = cstring_readline(f, &cs, NULL); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(5u, cs.len); + TEST_MS_EQ("short", cs); + + cstring_destroy(&cs); +} } // anonymous namespace + /* ///////////////////////////// end of file //////////////////////////// */ diff --git a/test/component/test.component.cstring_vector_readLines/CMakeLists.txt b/test/component/test.component.cstring_vector_readLines/CMakeLists.txt new file mode 100644 index 0000000..08eb713 --- /dev/null +++ b/test/component/test.component.cstring_vector_readLines/CMakeLists.txt @@ -0,0 +1,2 @@ +# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +define_automated_test_program(test_component_cstring_cstring_vector_readLines entry.cpp) diff --git a/test/component/test.component.cstring_vector_readLines/entry.cpp b/test/component/test.component.cstring_vector_readLines/entry.cpp new file mode 100644 index 0000000..5e6810b --- /dev/null +++ b/test/component/test.component.cstring_vector_readLines/entry.cpp @@ -0,0 +1,541 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test.component.cstring_vector_readLines/entry.cpp + * + * Purpose: Component-tests `cstring_vector_readLines()`. + * + * Created: 27th September 2026 + * Updated: 27th September 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * test component header file include(s) + */ + +#include +#include + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +/* xTests header files */ +#include +#include + +/* STLSoft header files */ +#include +#include +#include + +/* Standard C++ header files */ +#include +#include + +/* Standard C header files */ +#include +#include +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * forward declarations + */ + +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); + + int setup(void*); + int teardown(void*); +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * constants & definitions + */ + +const char TEST_FILE_NAME[] = "test.component.cstring_vector_readLines.txt"; + +/* ///////////////////////////////////////////////////////////////////////// + * compiler compatibility + */ + +#if defined(STLSOFT_COMPILER_IS_MSVC) +# if _MSC_VER >= 1200 +# pragma warning(push) +# endif /* compiler */ +# pragma warning(disable : 4702) +#endif /* compiler */ + +/* ///////////////////////////////////////////////////////////////////////// + * main + */ + +int main(int argc, char* argv[]) +{ + int retCode = EXIT_SUCCESS; + int verbosity = 2; + + XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); + + if (XTESTS_START_RUNNER_WITH_SETUP_FNS("test.component.cstring_vector_readLines", verbosity, setup, teardown, (void*)TEST_FILE_NAME)) + { + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_CALLABILITY); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_EMPTY_FILE); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_SHORT_MULTILINE); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_CRLF_AND_MIXED_EOL); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_FINAL_EOL_AND_EMPTY_AT_EOF); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_CONSECUTIVE_EMPTY_LINES); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_APPEND_ONTO_EXISTING); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_LONG_LINES); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_MANY_SHORT_LINES); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_EMPTY_LINE_IN_MIDDLE); + + XTESTS_PRINT_RESULTS(); + + XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); + } + + return retCode; +} + +/* ///////////////////////////////////////////////////////////////////////// + * compiler compatibility + */ + +#if defined(STLSOFT_COMPILER_IS_MSVC) +# if _MSC_VER >= 1200 +# pragma warning(pop) +# endif /* compiler */ +#endif /* compiler */ + +/* ///////////////////////////////////////////////////////////////////////// + * helper functions + */ + +namespace +{ + + static FILE* fopen_or_throw(char const* fileName, char const* mode) + { +#ifdef CSTRING_USING_SAFE_STR_FUNCTIONS + FILE* f; + + if (0 != ::fopen_s(&f, fileName, mode)) +#else /* ? CSTRING_USING_SAFE_STR_FUNCTIONS */ + FILE* f = ::fopen(fileName, mode); + + if (NULL == f) +#endif /* CSTRING_USING_SAFE_STR_FUNCTIONS */ + { + throw platformstl::platform_exception((std::string("Could not open file '") + fileName + "'").c_str(), platformstl::system_traits::get_last_error()); + } + + return f; + } + + static void write_bytes(char const* fileName, void const* bytes, size_t cb) + { + FILE* f = fopen_or_throw(fileName, "wb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + if (cb != ::fwrite(bytes, 1, cb, f)) + { + throw platformstl::platform_exception("Could not write test file", platformstl::system_traits::get_last_error()); + } + } + + static void write_string_bytes(char const* fileName, std::string const& bytes) + { + write_bytes(fileName, bytes.data(), bytes.size()); + } + + static std::string make_filled(size_t n, char ch) + { + return std::string(n, ch); + } + + int setup(void*) + { + return 0; + } + + int teardown(void* arg) + { + char const* filename = static_cast(arg); + + ::remove(filename); + + return 0; + } +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * test function implementations + */ + +namespace +{ + +static void TEST_cstring_vector_readLines_CALLABILITY() +{ + false && cstring_vector_readLines(stdout, NULL, NULL); + + TEST_PASSED(); +} + +static void TEST_cstring_vector_readLines_EMPTY_FILE() +{ + /* empty file → EOF, no lines pushed */ + write_bytes(TEST_FILE_NAME, "", 0); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 99u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(0u, numLinesRead); + TEST_INT_EQ(0u, csv.len); + + cstring_vector_destroy(&csv); +} + +static void TEST_cstring_vector_readLines_SHORT_MULTILINE() +{ + /* short multi-line file ending without EOL (mirrors readline baseline) */ + { + FILE* f = fopen_or_throw(TEST_FILE_NAME, "w"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + fprintf(f, "\n"); + fprintf(f, "abc\n"); + fprintf(f, "abcdef\n"); + fprintf(f, "abcdefghijkl\n"); + fprintf(f, "abcdefghijklmnopqrstuvwxyz\n"); + fprintf(f, "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"); + fprintf(f, "xyz"); + } + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "r"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(7u, numLinesRead); + REQUIRE(TEST_INT_EQ(7u, csv.len)); + TEST_MS_EQ("", csv.ptr[0]); + TEST_MS_EQ("abc", csv.ptr[1]); + TEST_MS_EQ("abcdef", csv.ptr[2]); + TEST_MS_EQ("abcdefghijkl", csv.ptr[3]); + TEST_MS_EQ("abcdefghijklmnopqrstuvwxyz", csv.ptr[4]); + TEST_MS_EQ("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", csv.ptr[5]); + TEST_MS_EQ("xyz", csv.ptr[6]); + + cstring_vector_destroy(&csv); +} + +static void TEST_cstring_vector_readLines_CRLF_AND_MIXED_EOL() +{ + /* CRLF / mixed EOL / lone CR (binary I/O) */ + { + std::string bytes; + + bytes.append("one\r\n"); + bytes.append("two\n"); + bytes.append("three\r\n"); + bytes.append("has\rembed\r\n"); + bytes.append("end"); + + write_string_bytes(TEST_FILE_NAME, bytes); + } + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + 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_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]); + + cstring_vector_destroy(&csv); +} + +static void TEST_cstring_vector_readLines_FINAL_EOL_AND_EMPTY_AT_EOF() +{ + /* final EOL vs no final EOL; empty trailing unterminated line omitted */ + { + write_string_bytes(TEST_FILE_NAME, std::string("a\nb\n")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(2u, numLinesRead); + REQUIRE(TEST_INT_EQ(2u, csv.len)); + TEST_MS_EQ("a", csv.ptr[0]); + TEST_MS_EQ("b", csv.ptr[1]); + + cstring_vector_destroy(&csv); + } + + { + write_string_bytes(TEST_FILE_NAME, std::string("a\nb")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(2u, numLinesRead); + REQUIRE(TEST_INT_EQ(2u, csv.len)); + TEST_MS_EQ("a", csv.ptr[0]); + TEST_MS_EQ("b", csv.ptr[1]); + + cstring_vector_destroy(&csv); + } + + { + /* "a\n" then EOF on empty → only "a" (empty-at-EOF omitted) */ + write_string_bytes(TEST_FILE_NAME, std::string("a\n")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(1u, numLinesRead); + REQUIRE(TEST_INT_EQ(1u, csv.len)); + TEST_MS_EQ("a", csv.ptr[0]); + + cstring_vector_destroy(&csv); + } + + { + /* unterminated single line */ + write_string_bytes(TEST_FILE_NAME, std::string("xyz")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(1u, numLinesRead); + REQUIRE(TEST_INT_EQ(1u, csv.len)); + TEST_MS_EQ("xyz", csv.ptr[0]); + + cstring_vector_destroy(&csv); + } +} + +static void TEST_cstring_vector_readLines_CONSECUTIVE_EMPTY_LINES() +{ + /* consecutive empty lines (SUCCESS empties are kept) */ + write_string_bytes(TEST_FILE_NAME, std::string("\n\n")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(2u, numLinesRead); + REQUIRE(TEST_INT_EQ(2u, csv.len)); + TEST_INT_EQ(0u, csv.ptr[0].len); + TEST_INT_EQ(0u, csv.ptr[1].len); + TEST_MS_EQ("", csv.ptr[0]); + TEST_MS_EQ("", csv.ptr[1]); + + cstring_vector_destroy(&csv); +} + +static void TEST_cstring_vector_readLines_APPEND_ONTO_EXISTING() +{ + /* append onto a non-empty vector; numLinesRead may be NULL */ + write_string_bytes(TEST_FILE_NAME, std::string("new-one\nnew-two\n")); + + cstring_t prior = cstring_t_DEFAULT; + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, cstring_create(&prior, "prior"))); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, cstring_vector_append(&csv, &prior, 1))); + + cstring_destroy(&prior); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + CSTRING_RC rc = cstring_vector_readLines(f, &csv, NULL); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + REQUIRE(TEST_INT_EQ(3u, csv.len)); + TEST_MS_EQ("prior", csv.ptr[0]); + TEST_MS_EQ("new-one", csv.ptr[1]); + TEST_MS_EQ("new-two", csv.ptr[2]); + + cstring_vector_destroy(&csv); +} + +static void TEST_cstring_vector_readLines_LONG_LINES() +{ + /* long lines */ + static size_t const lengths[] = { + 1024u, + 4096u, + 16384u, + 65536u, + }; + + { for (size_t i = 0; i != sizeof(lengths) / sizeof(lengths[0]); ++i) + { + size_t const len = lengths[i]; + std::string line = make_filled(len, static_cast('A' + static_cast(i % 26))); + std::string bytes = line; + + bytes.push_back('\n'); + bytes.append("tail"); + + write_string_bytes(TEST_FILE_NAME, bytes); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(2u, numLinesRead); + REQUIRE(TEST_INT_EQ(2u, csv.len)); + TEST_INT_EQ(len, csv.ptr[0].len); + TEST_MS_EQ(line.c_str(), csv.ptr[0]); + TEST_MS_EQ("tail", csv.ptr[1]); + + cstring_vector_destroy(&csv); + }} +} + +static void TEST_cstring_vector_readLines_MANY_SHORT_LINES() +{ + /* many short lines */ + size_t const num_lines = 1000u; + std::string bytes; + std::vector expected; + + expected.reserve(num_lines); + + { for (size_t i = 0; i != num_lines; ++i) + { + char buf[32]; + + snprintf(buf, sizeof(buf), "line-%04u", static_cast(i)); + + expected.push_back(buf); + bytes.append(buf); + bytes.push_back('\n'); + }} + + write_string_bytes(TEST_FILE_NAME, bytes); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(num_lines, numLinesRead); + REQUIRE(TEST_INT_EQ(num_lines, csv.len)); + + { for (size_t i = 0; i != num_lines; ++i) + { + TEST_MS_EQ(expected[i].c_str(), csv.ptr[i]); + }} + + cstring_vector_destroy(&csv); +} + +static void TEST_cstring_vector_readLines_EMPTY_LINE_IN_MIDDLE() +{ + /* empty lines in the middle */ + write_string_bytes(TEST_FILE_NAME, std::string("top\n\nbottom\n")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(3u, numLinesRead); + REQUIRE(TEST_INT_EQ(3u, csv.len)); + TEST_MS_EQ("top", csv.ptr[0]); + TEST_MS_EQ("", csv.ptr[1]); + TEST_MS_EQ("bottom", csv.ptr[2]); + + cstring_vector_destroy(&csv); +} +} // anonymous namespace + + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/component/test.component.cstring_writeline/entry.cpp b/test/component/test.component.cstring_writeline/entry.cpp index e9d7a99..fa52b71 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: 12th January 2024 + * Updated: 27th September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -22,6 +22,7 @@ /* xTests header files */ #include +#include #include /* STLSoft header files */ @@ -40,16 +41,10 @@ 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_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); int setup(void*); int teardown(void*); @@ -77,7 +72,7 @@ const char TEST_FILE_NAME[] = "test.component.cstring_writeline.txt"; * main */ -int main(int argc, char **argv) +int main(int argc, char* argv[]) { int retCode = EXIT_SUCCESS; int verbosity = 2; @@ -86,16 +81,10 @@ int main(int argc, char **argv) if (XTESTS_START_RUNNER_WITH_SETUP_FNS("test.component.cstring_writeline", verbosity, setup, teardown, (void*)TEST_FILE_NAME)) { - XTESTS_RUN_CASE(test_1_0); - XTESTS_RUN_CASE(test_1_1); - XTESTS_RUN_CASE(test_1_2); - XTESTS_RUN_CASE(test_1_3); - XTESTS_RUN_CASE(test_1_4); - XTESTS_RUN_CASE(test_1_5); - XTESTS_RUN_CASE(test_1_6); - XTESTS_RUN_CASE(test_1_7); - XTESTS_RUN_CASE(test_1_8); - XTESTS_RUN_CASE(test_1_9); + XTESTS_RUN_CASE(TEST_cstring_writeline_CALLABILITY); + XTESTS_RUN_CASE(TEST_cstring_writeline_INVALID_STREAM); + XTESTS_RUN_CASE(TEST_cstring_writeline_MULTIPLE_LINES); + XTESTS_RUN_CASE(TEST_cstring_write_CONCATENATED); XTESTS_PRINT_RESULTS(); @@ -140,27 +129,29 @@ namespace -static void test_1_0() +static void TEST_cstring_writeline_CALLABILITY() { false && cstring_writeline(stdout, NULL, NULL); - XTESTS_TEST_PASSED(); + TEST_PASSED(); } -static void test_1_1() +static void TEST_cstring_writeline_INVALID_STREAM() { #if 0 cstring_t cs = cstring_t_DEFAULT; - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_INVALIDSTREAM, cstring_writeline(NULL, &cs, NULL)); + TEST_ENUM_EQ(CSTRING_RC_INVALIDSTREAM, cstring_writeline(NULL, &cs, NULL)); size_t n; - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_INVALIDSTREAM, cstring_writeline(NULL, &cs, &n)); + TEST_ENUM_EQ(CSTRING_RC_INVALIDSTREAM, cstring_writeline(NULL, &cs, &n)); #endif /* 0 */ + + TEST_PASSED(); } -static void test_1_2() +static void TEST_cstring_writeline_MULTIPLE_LINES() { static char const* const s_lines[] = { @@ -204,15 +195,15 @@ static void test_1_2() platformstl::file_lines out_lines(tf_out); - XTESTS_REQUIRE(XTESTS_TEST_INTEGER_EQUAL(6u, out_lines.size())); + REQUIRE(TEST_INT_EQ(6u, out_lines.size())); { for (size_t i = 0; STLSOFT_NUM_ELEMENTS(s_lines) != i; ++i) { - XTESTS_TEST_MULTIBYTE_STRING_EQUAL(s_lines[i], out_lines[i]); + TEST_MS_EQ(s_lines[i], out_lines[i]); }} } } -static void test_1_3() +static void TEST_cstring_write_CONCATENATED() { static char const* const s_lines[] = { @@ -256,7 +247,7 @@ static void test_1_3() platformstl::file_lines out_lines(tf_out); - XTESTS_REQUIRE(XTESTS_TEST_INTEGER_EQUAL(1u, out_lines.size())); + REQUIRE(TEST_INT_EQ(1u, out_lines.size())); std::string expected; @@ -265,34 +256,10 @@ static void test_1_3() expected.append(s_lines[i]); }} - XTESTS_TEST_MULTIBYTE_STRING_EQUAL(expected, out_lines[0]); + TEST_MS_EQ(expected, out_lines[0]); } } -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() -{ -} - } // anonymous namespace From 589c51fe89b2140f020ba0ace99cb120e30cb7c3 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Sun, 27 Sep 2026 16:37:24 +1000 Subject: [PATCH 61/88] fix --- test/performance/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/performance/CMakeLists.txt b/test/performance/CMakeLists.txt index 15d78ed..6c8958d 100644 --- a/test/performance/CMakeLists.txt +++ b/test/performance/CMakeLists.txt @@ -4,6 +4,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(test.performance.cstring) +add_subdirectory(test.performance.cstring_readline) add_subdirectory(test.performance.cstring_vector) From f17597a06ec9b7b09453de90277981517966ebbd Mon Sep 17 00:00:00 2001 From: synesissoftware Date: Sun, 27 Sep 2026 17:43:02 +1000 Subject: [PATCH 62/88] Flags fixes (#24) * tidy * chore: replacing backwards-compatibility flags uses with the replacements * fix(flags): correcting (currently unused) flag(s) * fix(cstring): drop unused bits from the cstring arena mask Narrow CSTRING_F_ARENA_MASK from 0xff00 to 0x3700, the union of the defined arena selectors, so unused high bits are not treated as an arena. Retire the unsupported/unused custom-arena calling-convention macros, clearing the old stdcall value 0x4000 that sat inside the mask. * squash-commit --- examples/c/example.c.cstring/main.c | 15 ++++++++------- .../cpp/example.cpp.HGLOBAL_on_x64/main.cpp | 2 +- .../cpp/example.cpp.cstring.dynload/main.cpp | 4 ++-- include/cstring/cstring.h | 18 ++++++++++++------ src/cstring.core.c | 11 ++++++++--- test/unit/test.unit.cstring.1/entry.cpp | 12 ++++++------ 6 files changed, 37 insertions(+), 25 deletions(-) diff --git a/examples/c/example.c.cstring/main.c b/examples/c/example.c.cstring/main.c index 0539361..daaf369 100644 --- a/examples/c/example.c.cstring/main.c +++ b/examples/c/example.c.cstring/main.c @@ -6,7 +6,7 @@ * where available). * * Created: 7th July 2005 - * Updated: 2nd August 2026 + * Updated: 27th September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -83,11 +83,12 @@ int main(int argc, char *argv[]) #if 0 #elif defined(WIN32) + printf("\n========================================\n"); - printf("cstring_createLenEx(CSTRING_F_USE_WIN32_PROCESSHEAP_MEMORY):\n"); + printf("cstring_createLenEx(CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY):\n"); { struct cstring_t cs1; - CSTRING_RC rc = cstring_createLenEx(&cs1, "string-#2", 9, CSTRING_F_USE_WIN32_PROCESSHEAP_MEMORY, NULL, 0); + CSTRING_RC rc = cstring_createLenEx(&cs1, "string-#2", 9, CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY, NULL, 0); if (CSTRING_RC_SUCCESS == rc) { @@ -98,10 +99,10 @@ int main(int argc, char *argv[]) } printf("\n========================================\n"); - printf("cstring_createLenFn(CSTRING_F_USE_WIN32_PROCESSHEAP_MEMORY):\n"); + printf("cstring_createLenFn(CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY):\n"); { struct cstring_t cs1; - CSTRING_RC rc = cstring_createLenFn(&cs1, "string-#2", 9, CSTRING_F_USE_WIN32_PROCESSHEAP_MEMORY, NULL, 0, on_allocFail, NULL); + CSTRING_RC rc = cstring_createLenFn(&cs1, "string-#2", 9, CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY, NULL, 0, on_allocFail, NULL); if (CSTRING_RC_SUCCESS == rc) { @@ -114,10 +115,10 @@ int main(int argc, char *argv[]) } printf("\n========================================\n"); - printf("cstring_createLenEx(CSTRING_F_USE_WIN32_COM_TASK_MEMORY):\n"); + printf("cstring_createLenEx(CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY):\n"); { struct cstring_t cs1; - CSTRING_RC rc = cstring_createEx(&cs1, "string-#3", CSTRING_F_USE_WIN32_COM_TASK_MEMORY, NULL, 0); + CSTRING_RC rc = cstring_createEx(&cs1, "string-#3", CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY, NULL, 0); if (CSTRING_RC_SUCCESS == rc) { diff --git a/examples/cpp/example.cpp.HGLOBAL_on_x64/main.cpp b/examples/cpp/example.cpp.HGLOBAL_on_x64/main.cpp index ec7aa1e..4b30a7e 100644 --- a/examples/cpp/example.cpp.HGLOBAL_on_x64/main.cpp +++ b/examples/cpp/example.cpp.HGLOBAL_on_x64/main.cpp @@ -26,7 +26,7 @@ int main() }} cstring_t payload = cstring_t_DEFAULT; - cstring_flags_t flags = CSTRING_F_USE_WIN32_GLOBAL_MEMORY; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY; CSTRING_RC rc = CSTRING_RC_SUCCESS; rc = ::cstring_createLenFn(&payload, NULL, 0, flags, NULL, cchTotal, NULL, NULL); diff --git a/examples/cpp/example.cpp.cstring.dynload/main.cpp b/examples/cpp/example.cpp.cstring.dynload/main.cpp index c43d034..e2b327c 100644 --- a/examples/cpp/example.cpp.cstring.dynload/main.cpp +++ b/examples/cpp/example.cpp.cstring.dynload/main.cpp @@ -5,7 +5,7 @@ * `LoadLibrary` / `GetProcAddress`. * * Created: 19th August 2005 - * Updated: 2nd August 2026 + * Updated: 27th September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -122,7 +122,7 @@ static int main_(int /* argc */, char ** /*argv*/) #if 0 CSTRING_RC rc = _create(&cs, "Hello"); #else /* ? 0 */ - CSTRING_RC rc = _createEx(&cs, "Hello", CSTRING_F_USE_WIN32_GLOBAL_MEMORY, NULL, 100); + CSTRING_RC rc = _createEx(&cs, "Hello", CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY, NULL, 100); #endif /* 0 */ char* p; diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index 537d52c..6fcb4d7 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 11 # define CSTRING_VER_CSTRING_H_CSTRING_REVISION 15 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 88 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 89 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ @@ -315,6 +315,8 @@ typedef struct cstring_t cstring_t; * @{ */ +/* memory */ + #define CSTRING_F_TYPE_MASK (0x003f) #define CSTRING_F_MEMORY_IS_INTERNAL_HEAP (0x0000) /*!< Memory is allocated by the heap function, and is owned by the cstring instance */ #define CSTRING_F_MEMORY_IS_BORROWED (0x0001) /*!< Memory is supplied external to the API, and may not be deallocated, moved or expanded. \note Implies CSTRING_F_MEMORY_IS_FIXED, unless CSTRING_F_MEMORY_CAN_GROW_TO_HEAP is also specified */ @@ -323,10 +325,11 @@ typedef struct cstring_t cstring_t; #define CSTRING_F_MEMORY_IS_CONST CSTRING_F_MEMORY_IS_READONLY /*!< [DEPRECATED] Equivalent to CSTRING_F_MEMORY_IS_READONLY \deprecated Use CSTRING_F_MEMORY_IS_READONLY instead. */ #define CSTRING_F_MEMORY_IS_OFFSET (0x0040) /*!< cstring instance may not be modified. This flag cannot be set by client code. */ - #define CSTRING_F_MEMORY_CAN_GROW_TO_HEAP (0x0080) /*!< Allows borrowed strings to move their contents to the heap and continue to grow when their borrowed capacity would be exceeded: note that they do not subsequently return to use the borrowed capacity if the size subsequently shrinks to within capacity. */ -#define CSTRING_F_ARENA_MASK (0xff00) +/* arena */ + +#define CSTRING_F_ARENA_MASK (0x3700) #define CSTRING_F_USE_REALLOC (0x0000) /*!< Uses C standard library function realloc() for all memory (de-)allocation */ #if 0 #elif 0 ||\ @@ -334,7 +337,7 @@ typedef struct cstring_t cstring_t; defined(WIN64) ||\ 0 # define CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY (0x0100) /*!< Uses Win32 Global Memory API for all memory (de-)allocation (Windows only); arena parameter is ignored. */ -# define CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY (0x0200) /*!< Uses Win32 Process Heap for all memory (de-)allocation (Windows only); arena parameter is ignored. */ +# define CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY (0x0200) /*!< Uses Win32 Process Heap for all memory (de-)allocation (Windows only); arena parameter is ignored. */ # define CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY (0x0400) /*!< Uses COM Task Allocator for all memory (de-)allocation (Windows only); arena parameter is ignored. */ #endif /* WIN32 || WIN64 */ #if defined(CSTRING_USE_SYNESIS_APIS) @@ -342,8 +345,11 @@ typedef struct cstring_t cstring_t; #endif /* CSTRING_USE_SYNESIS_APIS */ #define CSTRING_F_USE_CUSTOMARENAFUNCTIONS (0x2000) /*!< Uses an externally supplied allocator function for all memory (de-)allocation (NOT CURRENTLY SUPPORTED) */ -#define CSTRING_F_CUSTOMARENA_CC_CDECL (0x0000) /*!< Treats the externally supplied allocator function as having CDecl calling convention (de-)allocation (NOT CURRENTLY SUPPORTED) */ -#define CSTRING_F_CUSTOMARENA_CC_STDCALL (0x0040) /*!< Treats the externally supplied allocator function as having StdCall calling convention (de-)allocation (NOT CURRENTLY SUPPORTED) */ +#define _CSTRING_F_CUSTOMARENA_CC_CDECL_unused (0x0000) /*!< Treats the externally supplied allocator function as having CDecl calling convention (de-)allocation (NOT CURRENTLY SUPPORTED) */ +#define _CSTRING_F_CUSTOMARENA_CC_STDCALL_unused (0x0000) /*!< Treats the externally supplied allocator function as having StdCall calling convention (de-)allocation (NOT CURRENTLY SUPPORTED) */ + +/* I/O */ +#define CSTRING_F_IO_MASK (0x0000) /** @} */ diff --git a/src/cstring.core.c b/src/cstring.core.c index 22631bf..1a9ae71 100644 --- a/src/cstring.core.c +++ b/src/cstring.core.c @@ -311,6 +311,7 @@ cstring_realloc_2( switch (CSTRING_F_ARENA_MASK & flags) { case CSTRING_F_USE_REALLOC: + #if defined(_MSC_VER) && \ defined(_DEBUG) if (0 == cb) @@ -322,13 +323,16 @@ cstring_realloc_2( pvNew = realloc(pv, cb); break; #if defined(CSTRING_USE_WINAPI_) - case CSTRING_F_USE_WIN32_GLOBAL_MEMORY: + case CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY: + pvNew = win32_global_realloc(pv, cb); break; - case CSTRING_F_USE_WIN32_PROCESSHEAP_MEMORY: + case CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY: + pvNew = win32_processheap_realloc(pv, cb); break; - case CSTRING_F_USE_WIN32_COM_TASK_MEMORY: + case CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY: + pvNew = win32_comtask_realloc(pv, cb); break; #endif /* CSTRING_USE_WINAPI_ */ @@ -337,6 +341,7 @@ cstring_realloc_2( #endif /* CSTRING_USE_SYNESIS_APIS */ case CSTRING_F_USE_CUSTOMARENAFUNCTIONS: default: + *prc = CSTRING_RC_INVALIDARENA; return NULL; } diff --git a/test/unit/test.unit.cstring.1/entry.cpp b/test/unit/test.unit.cstring.1/entry.cpp index 424cc3a..e671b54 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: 2nd September 2025 + * Updated: 27th September 2025 * * ////////////////////////////////////////////////////////////////////// */ @@ -943,7 +943,7 @@ static void test_2_3() cstring_t str; const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; cstring_char_t const* const s = alphabet + n; - cstring_flags_t flags = CSTRING_F_USE_WIN32_PROCESSHEAP_MEMORY; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY; cstring_createEx(&str, s, flags, NULL, 0); @@ -965,7 +965,7 @@ static void test_2_3() cstring_t str; const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; cstring_char_t const* const s = alphabet + n; - cstring_flags_t flags = CSTRING_F_USE_WIN32_COM_TASK_MEMORY; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY; cstring_createEx(&str, s, flags, NULL, 0); @@ -1055,7 +1055,7 @@ static void test_2_4() cstring_t str; const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; cstring_char_t const* const s = alphabet + n; - cstring_flags_t flags = CSTRING_F_USE_WIN32_GLOBAL_MEMORY; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY; cstring_createLenEx(&str, s, i, flags, NULL, 0); @@ -1077,7 +1077,7 @@ static void test_2_4() cstring_t str; const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; cstring_char_t const* const s = alphabet + n; - cstring_flags_t flags = CSTRING_F_USE_WIN32_PROCESSHEAP_MEMORY; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY; cstring_createLenEx(&str, s, i, flags, NULL, 0); @@ -1099,7 +1099,7 @@ static void test_2_4() cstring_t str; const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; cstring_char_t const* const s = alphabet + n; - cstring_flags_t flags = CSTRING_F_USE_WIN32_COM_TASK_MEMORY; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY; cstring_createLenEx(&str, s, i, flags, NULL, 0); From eb9fd45f2fa30e86d80a2c04be4d49ce9634feef Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Sun, 27 Sep 2026 17:51:17 +1000 Subject: [PATCH 63/88] squash-commit --- .github/workflows/ci-cell.yml | 5 +- .github/workflows/ci.yml | 1 + Doxyfile | 2 +- NEWS.md | 4 +- examples/c/example.c.cstring/main.c | 15 +- .../cpp/example.cpp.HGLOBAL_on_x64/main.cpp | 2 +- .../cpp/example.cpp.cstring.dynload/main.cpp | 4 +- include/cstring/cstring.h | 20 +- include/cstring/cstring.vector.h | 4 +- src/cstring.core.c | 12 +- test/component/CMakeLists.txt | 1 + .../test.component.cstring_readline/entry.cpp | 433 +++++++++++-- .../CMakeLists.txt | 2 + .../entry.cpp | 541 ++++++++++++++++ .../entry.cpp | 81 +-- test/performance/CMakeLists.txt | 1 + test/performance/perf_harness.hpp | 14 +- .../test.performance.cstring/CMakeLists.txt | 4 + .../test.performance.cstring/main.cpp | 2 +- .../CMakeLists.txt | 5 + .../main.cpp | 609 ++++++++++++++++++ .../CMakeLists.txt | 4 + test/unit/test.unit.cstring.1/entry.cpp | 12 +- 23 files changed, 1618 insertions(+), 160 deletions(-) create mode 100644 test/component/test.component.cstring_vector_readLines/CMakeLists.txt create mode 100644 test/component/test.component.cstring_vector_readLines/entry.cpp create mode 100644 test/performance/test.performance.cstring_readline/CMakeLists.txt create mode 100644 test/performance/test.performance.cstring_readline/main.cpp diff --git a/.github/workflows/ci-cell.yml b/.github/workflows/ci-cell.yml index 2e657e3..5783743 100644 --- a/.github/workflows/ci-cell.yml +++ b/.github/workflows/ci-cell.yml @@ -161,7 +161,10 @@ jobs: shell: cmd run: | set SIS_CMAKE_BUILD_DIR=%GITHUB_WORKSPACE%\build - if /I "${{ inputs.toolchain }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" + if /I "${{ inputs.c-compiler }}"=="mingw" ( + set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%" + set "CSTRING_PERF_FILE_TRIALS=10" + ) call run_all_performance_tests.cmd - name: Scratch tests diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 758f139..9ac7ff1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ on: - rc1 - rc2 - rc3 + - component-tests pull_request: concurrency: diff --git a/Doxyfile b/Doxyfile index bf31d8c..7986df1 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.0.16 +PROJECT_NUMBER = 4.0.17 # 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 f10510c..6bcc611 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,8 +4,8 @@ | Date | News Item | Details | | ------------------- | -------------------------------- | ------- | | Available from [**cstring** project on GitHub](https://synesissoftware.com/cstring): | -| 29th September 2026 | Release of [cstring 4.1.0](https://github.com/synesissoftware/cstring/releases/tag/4.1.0) | Performance optimisations | -| 28th September 2026 | Release of [cstring 4.0.17](https://github.com/synesissoftware/cstring/releases/tag/4.0.17) | Competitive perf tests; optional **p99** | +| 28th September 2026 | Release of [cstring 4.1.0](https://github.com/synesissoftware/cstring/releases/tag/4.1.0) | Performance optimisations | +| 27th September 2026 | Release of [cstring 4.0.17](https://github.com/synesissoftware/cstring/releases/tag/4.0.17) | Competitive perf tests; optional **p99** | | 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 | | 6th September 2026 | Release of [cstring 4.0.15](https://github.com/synesissoftware/cstring/releases/tag/4.0.15) | Doxygen `\param`/`\retval` 76-rule | | 9th August 2026 | Release of [cstring 4.0.14](https://github.com/synesissoftware/cstring/releases/tag/4.0.14) | self-assignment safety | diff --git a/examples/c/example.c.cstring/main.c b/examples/c/example.c.cstring/main.c index 0539361..daaf369 100644 --- a/examples/c/example.c.cstring/main.c +++ b/examples/c/example.c.cstring/main.c @@ -6,7 +6,7 @@ * where available). * * Created: 7th July 2005 - * Updated: 2nd August 2026 + * Updated: 27th September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -83,11 +83,12 @@ int main(int argc, char *argv[]) #if 0 #elif defined(WIN32) + printf("\n========================================\n"); - printf("cstring_createLenEx(CSTRING_F_USE_WIN32_PROCESSHEAP_MEMORY):\n"); + printf("cstring_createLenEx(CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY):\n"); { struct cstring_t cs1; - CSTRING_RC rc = cstring_createLenEx(&cs1, "string-#2", 9, CSTRING_F_USE_WIN32_PROCESSHEAP_MEMORY, NULL, 0); + CSTRING_RC rc = cstring_createLenEx(&cs1, "string-#2", 9, CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY, NULL, 0); if (CSTRING_RC_SUCCESS == rc) { @@ -98,10 +99,10 @@ int main(int argc, char *argv[]) } printf("\n========================================\n"); - printf("cstring_createLenFn(CSTRING_F_USE_WIN32_PROCESSHEAP_MEMORY):\n"); + printf("cstring_createLenFn(CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY):\n"); { struct cstring_t cs1; - CSTRING_RC rc = cstring_createLenFn(&cs1, "string-#2", 9, CSTRING_F_USE_WIN32_PROCESSHEAP_MEMORY, NULL, 0, on_allocFail, NULL); + CSTRING_RC rc = cstring_createLenFn(&cs1, "string-#2", 9, CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY, NULL, 0, on_allocFail, NULL); if (CSTRING_RC_SUCCESS == rc) { @@ -114,10 +115,10 @@ int main(int argc, char *argv[]) } printf("\n========================================\n"); - printf("cstring_createLenEx(CSTRING_F_USE_WIN32_COM_TASK_MEMORY):\n"); + printf("cstring_createLenEx(CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY):\n"); { struct cstring_t cs1; - CSTRING_RC rc = cstring_createEx(&cs1, "string-#3", CSTRING_F_USE_WIN32_COM_TASK_MEMORY, NULL, 0); + CSTRING_RC rc = cstring_createEx(&cs1, "string-#3", CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY, NULL, 0); if (CSTRING_RC_SUCCESS == rc) { diff --git a/examples/cpp/example.cpp.HGLOBAL_on_x64/main.cpp b/examples/cpp/example.cpp.HGLOBAL_on_x64/main.cpp index ec7aa1e..4b30a7e 100644 --- a/examples/cpp/example.cpp.HGLOBAL_on_x64/main.cpp +++ b/examples/cpp/example.cpp.HGLOBAL_on_x64/main.cpp @@ -26,7 +26,7 @@ int main() }} cstring_t payload = cstring_t_DEFAULT; - cstring_flags_t flags = CSTRING_F_USE_WIN32_GLOBAL_MEMORY; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY; CSTRING_RC rc = CSTRING_RC_SUCCESS; rc = ::cstring_createLenFn(&payload, NULL, 0, flags, NULL, cchTotal, NULL, NULL); diff --git a/examples/cpp/example.cpp.cstring.dynload/main.cpp b/examples/cpp/example.cpp.cstring.dynload/main.cpp index c43d034..e2b327c 100644 --- a/examples/cpp/example.cpp.cstring.dynload/main.cpp +++ b/examples/cpp/example.cpp.cstring.dynload/main.cpp @@ -5,7 +5,7 @@ * `LoadLibrary` / `GetProcAddress`. * * Created: 19th August 2005 - * Updated: 2nd August 2026 + * Updated: 27th September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -122,7 +122,7 @@ static int main_(int /* argc */, char ** /*argv*/) #if 0 CSTRING_RC rc = _create(&cs, "Hello"); #else /* ? 0 */ - CSTRING_RC rc = _createEx(&cs, "Hello", CSTRING_F_USE_WIN32_GLOBAL_MEMORY, NULL, 100); + CSTRING_RC rc = _createEx(&cs, "Hello", CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY, NULL, 100); #endif /* 0 */ char* p; diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index 70eaea4..d66c9fc 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: 29th September 2026 + * Updated: 28th September 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 11 # define CSTRING_VER_CSTRING_H_CSTRING_REVISION 15 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 88 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 89 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ @@ -316,6 +316,8 @@ typedef struct cstring_t cstring_t; * @{ */ +/* memory */ + #define CSTRING_F_TYPE_MASK (0x003f) #define CSTRING_F_MEMORY_IS_INTERNAL_HEAP (0x0000) /*!< Memory is allocated by the heap function, and is owned by the cstring instance */ #define CSTRING_F_MEMORY_IS_BORROWED (0x0001) /*!< Memory is supplied external to the API, and may not be deallocated, moved or expanded. \note Implies CSTRING_F_MEMORY_IS_FIXED, unless CSTRING_F_MEMORY_CAN_GROW_TO_HEAP is also specified */ @@ -324,10 +326,11 @@ typedef struct cstring_t cstring_t; #define CSTRING_F_MEMORY_IS_CONST CSTRING_F_MEMORY_IS_READONLY /*!< [DEPRECATED] Equivalent to CSTRING_F_MEMORY_IS_READONLY \deprecated Use CSTRING_F_MEMORY_IS_READONLY instead. */ #define CSTRING_F_MEMORY_IS_OFFSET (0x0040) /*!< cstring instance may not be modified. This flag cannot be set by client code. */ - #define CSTRING_F_MEMORY_CAN_GROW_TO_HEAP (0x0080) /*!< Allows borrowed strings to move their contents to the heap and continue to grow when their borrowed capacity would be exceeded: note that they do not subsequently return to use the borrowed capacity if the size subsequently shrinks to within capacity. */ -#define CSTRING_F_ARENA_MASK (0xff00) +/* arena */ + +#define CSTRING_F_ARENA_MASK (0x3700) #define CSTRING_F_USE_REALLOC (0x0000) /*!< Uses C standard library function realloc() for all memory (de-)allocation */ #if 0 #elif 0 ||\ @@ -335,7 +338,7 @@ typedef struct cstring_t cstring_t; defined(WIN64) ||\ 0 # define CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY (0x0100) /*!< Uses Win32 Global Memory API for all memory (de-)allocation (Windows only); arena parameter is ignored. */ -# define CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY (0x0200) /*!< Uses Win32 Process Heap for all memory (de-)allocation (Windows only); arena parameter is ignored. */ +# define CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY (0x0200) /*!< Uses Win32 Process Heap for all memory (de-)allocation (Windows only); arena parameter is ignored. */ # define CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY (0x0400) /*!< Uses COM Task Allocator for all memory (de-)allocation (Windows only); arena parameter is ignored. */ #endif /* WIN32 || WIN64 */ #if defined(CSTRING_USE_SYNESIS_APIS) @@ -343,8 +346,11 @@ typedef struct cstring_t cstring_t; #endif /* CSTRING_USE_SYNESIS_APIS */ #define CSTRING_F_USE_CUSTOMARENAFUNCTIONS (0x2000) /*!< Uses an externally supplied allocator function for all memory (de-)allocation (NOT CURRENTLY SUPPORTED) */ -#define CSTRING_F_CUSTOMARENA_CC_CDECL (0x0000) /*!< Treats the externally supplied allocator function as having CDecl calling convention (de-)allocation (NOT CURRENTLY SUPPORTED) */ -#define CSTRING_F_CUSTOMARENA_CC_STDCALL (0x0040) /*!< Treats the externally supplied allocator function as having StdCall calling convention (de-)allocation (NOT CURRENTLY SUPPORTED) */ +#define _CSTRING_F_CUSTOMARENA_CC_CDECL_unused (0x0000) /*!< Treats the externally supplied allocator function as having CDecl calling convention (de-)allocation (NOT CURRENTLY SUPPORTED) */ +#define _CSTRING_F_CUSTOMARENA_CC_STDCALL_unused (0x0000) /*!< Treats the externally supplied allocator function as having StdCall calling convention (de-)allocation (NOT CURRENTLY SUPPORTED) */ + +/* I/O */ +#define CSTRING_F_IO_MASK (0x0000) /** @} */ diff --git a/include/cstring/cstring.vector.h b/include/cstring/cstring.vector.h index 8afe3b2..d3534c4 100644 --- a/include/cstring/cstring.vector.h +++ b/include/cstring/cstring.vector.h @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: cstring/vector.h + * File: cstring/cstring.vector.h * * Purpose: Definition of the cstring.vector API. * * Created: 21st January 2012 - * Updated: 23rd September 2026 + * Updated: 28th September 2026 * * Home: http://synesis.com.au/software/ * diff --git a/src/cstring.core.c b/src/cstring.core.c index 9dc16a6..446f21f 100644 --- a/src/cstring.core.c +++ b/src/cstring.core.c @@ -289,6 +289,7 @@ cstring_realloc_2( switch (CSTRING_F_ARENA_MASK & flags) { case CSTRING_F_USE_REALLOC: + /* realloc(pv, 0) allocates on some platforms; free the block, * and do nothing when pv is NULL. */ @@ -301,16 +302,20 @@ cstring_realloc_2( return NULL; } + pvNew = realloc(pv, cb); break; #if defined(CSTRING_USE_WINAPI_) - case CSTRING_F_USE_WIN32_GLOBAL_MEMORY: + case CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY: + pvNew = win32_global_realloc(pv, cb); break; - case CSTRING_F_USE_WIN32_PROCESSHEAP_MEMORY: + case CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY: + pvNew = win32_processheap_realloc(pv, cb); break; - case CSTRING_F_USE_WIN32_COM_TASK_MEMORY: + case CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY: + pvNew = win32_comtask_realloc(pv, cb); break; #endif /* CSTRING_USE_WINAPI_ */ @@ -319,6 +324,7 @@ cstring_realloc_2( #endif /* CSTRING_USE_SYNESIS_APIS */ case CSTRING_F_USE_CUSTOMARENAFUNCTIONS: default: + *prc = CSTRING_RC_INVALIDARENA; return NULL; } diff --git a/test/component/CMakeLists.txt b/test/component/CMakeLists.txt index ab18c18..fd37bfb 100644 --- a/test/component/CMakeLists.txt +++ b/test/component/CMakeLists.txt @@ -1,5 +1,6 @@ if(NOT NO_CSTRING_CPP_API) add_subdirectory(test.component.cstring_readline) + add_subdirectory(test.component.cstring_vector_readLines) add_subdirectory(test.component.cstring_writeline) endif() diff --git a/test/component/test.component.cstring_readline/entry.cpp b/test/component/test.component.cstring_readline/entry.cpp index 0fd4a27..7bb3c7c 100644 --- a/test/component/test.component.cstring_readline/entry.cpp +++ b/test/component/test.component.cstring_readline/entry.cpp @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// * File: test.component.cstring_readline/entry.cpp * - * Purpose: Unit-tests `cstring_readline()`. + * Purpose: Component-tests `cstring_readline()`. * * Created: 23rd May 2009 - * Updated: 12th January 2024 + * Updated: 27th September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -22,15 +22,22 @@ /* xTests header files */ #include +#include /* STLSoft header files */ #include -#include #include #include +/* Standard C++ header files */ +#include +#include + /* Standard C header files */ +#include #include +#include + /* ///////////////////////////////////////////////////////////////////////// * forward declarations @@ -39,28 +46,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_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); int setup(void*); int teardown(void*); - } // anonymous namespace + /* ///////////////////////////////////////////////////////////////////////// * constants & definitions */ const char TEST_FILE_NAME[] = "test.component.cstring_readline.txt"; + /* ///////////////////////////////////////////////////////////////////////// * compiler compatibility */ @@ -72,11 +80,12 @@ const char TEST_FILE_NAME[] = "test.component.cstring_readline.txt"; # pragma warning(disable : 4702) #endif /* compiler */ + /* ///////////////////////////////////////////////////////////////////////// * main */ -int main(int argc, char **argv) +int main(int argc, char* argv[]) { int retCode = EXIT_SUCCESS; int verbosity = 2; @@ -85,16 +94,16 @@ int main(int argc, char **argv) if (XTESTS_START_RUNNER_WITH_SETUP_FNS("test.component.cstring_readline", verbosity, setup, teardown, (void*)TEST_FILE_NAME)) { - XTESTS_RUN_CASE(test_1_0); - XTESTS_RUN_CASE(test_1_1); - XTESTS_RUN_CASE(test_1_2); - XTESTS_RUN_CASE(test_1_3); - XTESTS_RUN_CASE(test_1_4); - XTESTS_RUN_CASE(test_1_5); - XTESTS_RUN_CASE(test_1_6); - XTESTS_RUN_CASE(test_1_7); - XTESTS_RUN_CASE(test_1_8); - XTESTS_RUN_CASE(test_1_9); + 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_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_LONG_LINES); + XTESTS_RUN_CASE(TEST_cstring_readline_MANY_SHORT_LINES); + XTESTS_RUN_CASE(TEST_cstring_readline_REUSE_AFTER_LONG_LINE); XTESTS_PRINT_RESULTS(); @@ -104,6 +113,7 @@ int main(int argc, char **argv) return retCode; } + /* ///////////////////////////////////////////////////////////////////////// * compiler compatibility */ @@ -114,14 +124,15 @@ int main(int argc, char **argv) # endif /* compiler */ #endif /* compiler */ + /* ///////////////////////////////////////////////////////////////////////// - * test function implementations + * helper functions */ namespace { - static FILE* fopen_or_throw(char const* fileName, char const* mode /* = "r" */) + static FILE* fopen_or_throw(char const* fileName, char const* mode) { #ifdef CSTRING_USING_SAFE_STR_FUNCTIONS FILE* f; @@ -139,6 +150,28 @@ namespace return f; } + static void write_bytes(char const* fileName, void const* bytes, size_t cb) + { + FILE* f = fopen_or_throw(fileName, "wb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + if (cb != ::fwrite(bytes, 1, cb, f)) + { + throw platformstl::platform_exception("Could not write test file", platformstl::system_traits::get_last_error()); + } + } + + static void write_string_bytes(char const* fileName, std::string const& bytes) + { + write_bytes(fileName, bytes.data(), bytes.size()); + } + + static std::string make_filled(size_t n, char ch) + { + return std::string(n, ch); + } + int setup(void*) { return 0; @@ -152,28 +185,35 @@ namespace return 0; } +} // anonymous namespace +/* ///////////////////////////////////////////////////////////////////////// + * test function implementations + */ + +namespace +{ -static void test_1_0() +static void TEST_cstring_readline_CALLABILITY() { false && cstring_readline(stdout, NULL, NULL); - XTESTS_TEST_PASSED(); + TEST_PASSED(); } -static void test_1_1() +static void TEST_cstring_readline_INVALID_STREAM() { cstring_t cs = cstring_t_DEFAULT; - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_INVALIDSTREAM, cstring_readline(NULL, &cs, NULL)); + TEST_ENUM_EQ(CSTRING_RC_INVALIDSTREAM, cstring_readline(NULL, &cs, NULL)); size_t n; - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_INVALIDSTREAM, cstring_readline(NULL, &cs, &n)); + TEST_ENUM_EQ(CSTRING_RC_INVALIDSTREAM, cstring_readline(NULL, &cs, &n)); } -static void test_1_2() +static void TEST_cstring_readline_SHORT_MULTILINE() { { FILE* f = fopen_or_throw(TEST_FILE_NAME, "w"); @@ -195,84 +235,351 @@ static void test_1_2() stlsoft::scoped_handle scoper(f, ::fclose); CSTRING_RC rc; - cstring_t strings[7] = { cstring_t_DEFAULT, cstring_t_DEFAULT, cstring_t_DEFAULT, cstring_t_DEFAULT, cstring_t_DEFAULT, cstring_t_DEFAULT }; + cstring_t strings[7] = { + cstring_t_DEFAULT, + cstring_t_DEFAULT, + cstring_t_DEFAULT, + cstring_t_DEFAULT, + cstring_t_DEFAULT, + cstring_t_DEFAULT, + cstring_t_DEFAULT, + }; size_t lengths[7]; rc = cstring_readline(f, &strings[0], &lengths[0]); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); - XTESTS_TEST_INTEGER_EQUAL(0u, lengths[0]); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("", strings[0]); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(0u, lengths[0]); + TEST_MS_EQ("", strings[0]); rc = cstring_readline(f, &strings[1], &lengths[1]); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); - XTESTS_TEST_INTEGER_EQUAL(3u, lengths[1]); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("abc", strings[1]); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(3u, lengths[1]); + TEST_MS_EQ("abc", strings[1]); rc = cstring_readline(f, &strings[2], &lengths[2]); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); - XTESTS_TEST_INTEGER_EQUAL(6u, lengths[2]); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("abcdef", strings[2]); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(6u, lengths[2]); + TEST_MS_EQ("abcdef", strings[2]); rc = cstring_readline(f, &strings[3], &lengths[3]); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); - XTESTS_TEST_INTEGER_EQUAL(12u, lengths[3]); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("abcdefghijkl", strings[3]); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(12u, lengths[3]); + TEST_MS_EQ("abcdefghijkl", strings[3]); rc = cstring_readline(f, &strings[4], &lengths[4]); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); - XTESTS_TEST_INTEGER_EQUAL(26u, lengths[4]); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("abcdefghijklmnopqrstuvwxyz", strings[4]); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(26u, lengths[4]); + TEST_MS_EQ("abcdefghijklmnopqrstuvwxyz", strings[4]); rc = cstring_readline(f, &strings[5], &lengths[5]); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); - XTESTS_TEST_INTEGER_EQUAL(62u, lengths[5]); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", strings[5]); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(62u, lengths[5]); + TEST_MS_EQ("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", strings[5]); rc = cstring_readline(f, &strings[6], &lengths[6]); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_EOF, rc)); - XTESTS_TEST_INTEGER_EQUAL(3u, lengths[6]); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("xyz", strings[6]); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(3u, lengths[6]); + TEST_MS_EQ("xyz", strings[6]); + + { for (size_t i = 0; i != 7; ++i) + { + cstring_destroy(&strings[i]); + }} } } -static void test_1_3() +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_1_4() +static void TEST_cstring_readline_CRLF_AND_MIXED_EOL() { + /* CRLF, mixed EOL, and lone CR mid-line (binary I/O so CR is visible) */ + { + std::string bytes; + + bytes.append("one\r\n"); + bytes.append("two\n"); + bytes.append("three\r\n"); + bytes.append("has\rembed\r\n"); + bytes.append("end"); + + write_string_bytes(TEST_FILE_NAME, bytes); + } + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t n; + CSTRING_RC rc; + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + /* CRLF: CR is appended then stripped; numRead still counts the CR */ + TEST_INT_EQ(4u, n); + TEST_INT_EQ(3u, cs.len); + TEST_MS_EQ("one", cs); + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(3u, n); + TEST_INT_EQ(3u, cs.len); + TEST_MS_EQ("two", cs); + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(6u, n); + TEST_INT_EQ(5u, cs.len); + TEST_MS_EQ("three", cs); + + rc = cstring_readline(f, &cs, &n); + 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); + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(3u, n); + TEST_INT_EQ(3u, cs.len); + TEST_MS_EQ("end", cs); + + cstring_destroy(&cs); } -static void test_1_5() +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); + + cstring_t cs = cstring_t_DEFAULT; + size_t n; + CSTRING_RC rc; + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_MS_EQ("alpha", cs); + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_MS_EQ("beta", cs); + + 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); + + 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); + + cstring_t cs = cstring_t_DEFAULT; + size_t n; + CSTRING_RC rc; + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_MS_EQ("alpha", cs); + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(4u, n); + TEST_MS_EQ("beta", cs); + + cstring_destroy(&cs); + } } -static void test_1_6() +static void TEST_cstring_readline_CONSECUTIVE_EMPTY_LINES() { + /* consecutive empty lines */ + write_string_bytes(TEST_FILE_NAME, std::string("\n\n\n")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t n; + CSTRING_RC rc; + + { for (int i = 0; i != 3; ++i) + { + rc = cstring_readline(f, &cs, &n); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(0u, n); + TEST_INT_EQ(0u, cs.len); + TEST_MS_EQ("", cs); + }} + + rc = cstring_readline(f, &cs, &n); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(0u, n); + + cstring_destroy(&cs); } -static void test_1_7() +static void TEST_cstring_readline_LONG_LINES() { + /* long lines at several lengths */ + static size_t const lengths[] = { + 1024u, + 4096u, + 16384u, + 65536u, + }; + + { for (size_t i = 0; i != sizeof(lengths) / sizeof(lengths[0]); ++i) + { + size_t const len = lengths[i]; + std::string line = make_filled(len, static_cast('A' + static_cast(i % 26))); + std::string bytes = line; + + bytes.push_back('\n'); + bytes.append(make_filled(len / 2u == 0 ? 1u : len / 2u, 'z')); + + write_string_bytes(TEST_FILE_NAME, bytes); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t n; + CSTRING_RC rc; + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(len, n); + TEST_INT_EQ(len, cs.len); + REQUIRE(TEST_MS_EQ(line.c_str(), cs)); + + rc = cstring_readline(f, &cs, &n); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(len / 2u == 0 ? 1u : len / 2u, n); + + cstring_destroy(&cs); + }} } -static void test_1_8() +static void TEST_cstring_readline_MANY_SHORT_LINES() { + /* many short lines */ + size_t const num_lines = 1000u; + std::string bytes; + std::vector expected; + + expected.reserve(num_lines); + + { for (size_t i = 0; i != num_lines; ++i) + { + char buf[32]; + + snprintf(buf, sizeof(buf), "line-%04u", static_cast(i)); + + expected.push_back(buf); + bytes.append(buf); + bytes.push_back('\n'); + }} + + write_string_bytes(TEST_FILE_NAME, bytes); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + size_t n; + CSTRING_RC rc; + + { for (size_t i = 0; i != num_lines; ++i) + { + rc = cstring_readline(f, &cs, &n); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(expected[i].size(), n); + TEST_MS_EQ(expected[i].c_str(), cs); + }} + + rc = cstring_readline(f, &cs, &n); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(0u, n); + + cstring_destroy(&cs); } -static void test_1_9() +static void TEST_cstring_readline_REUSE_AFTER_LONG_LINE() { -} + /* reuse one cstring_t after a long line then a short line; numRead may be NULL */ + std::string const long_line = make_filled(8192u, 'L'); + std::string bytes = long_line; + + bytes.push_back('\n'); + bytes.append("short"); + write_string_bytes(TEST_FILE_NAME, bytes); + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs = cstring_t_DEFAULT; + CSTRING_RC rc; + + rc = cstring_readline(f, &cs, NULL); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(8192u, cs.len); + TEST_MS_EQ(long_line.c_str(), cs); + + rc = cstring_readline(f, &cs, NULL); + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(5u, cs.len); + TEST_MS_EQ("short", cs); + + cstring_destroy(&cs); +} } // anonymous namespace + /* ///////////////////////////// end of file //////////////////////////// */ diff --git a/test/component/test.component.cstring_vector_readLines/CMakeLists.txt b/test/component/test.component.cstring_vector_readLines/CMakeLists.txt new file mode 100644 index 0000000..08eb713 --- /dev/null +++ b/test/component/test.component.cstring_vector_readLines/CMakeLists.txt @@ -0,0 +1,2 @@ +# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +define_automated_test_program(test_component_cstring_cstring_vector_readLines entry.cpp) diff --git a/test/component/test.component.cstring_vector_readLines/entry.cpp b/test/component/test.component.cstring_vector_readLines/entry.cpp new file mode 100644 index 0000000..5e6810b --- /dev/null +++ b/test/component/test.component.cstring_vector_readLines/entry.cpp @@ -0,0 +1,541 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test.component.cstring_vector_readLines/entry.cpp + * + * Purpose: Component-tests `cstring_vector_readLines()`. + * + * Created: 27th September 2026 + * Updated: 27th September 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * test component header file include(s) + */ + +#include +#include + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +/* xTests header files */ +#include +#include + +/* STLSoft header files */ +#include +#include +#include + +/* Standard C++ header files */ +#include +#include + +/* Standard C header files */ +#include +#include +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * forward declarations + */ + +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); + + int setup(void*); + int teardown(void*); +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * constants & definitions + */ + +const char TEST_FILE_NAME[] = "test.component.cstring_vector_readLines.txt"; + +/* ///////////////////////////////////////////////////////////////////////// + * compiler compatibility + */ + +#if defined(STLSOFT_COMPILER_IS_MSVC) +# if _MSC_VER >= 1200 +# pragma warning(push) +# endif /* compiler */ +# pragma warning(disable : 4702) +#endif /* compiler */ + +/* ///////////////////////////////////////////////////////////////////////// + * main + */ + +int main(int argc, char* argv[]) +{ + int retCode = EXIT_SUCCESS; + int verbosity = 2; + + XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); + + if (XTESTS_START_RUNNER_WITH_SETUP_FNS("test.component.cstring_vector_readLines", verbosity, setup, teardown, (void*)TEST_FILE_NAME)) + { + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_CALLABILITY); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_EMPTY_FILE); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_SHORT_MULTILINE); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_CRLF_AND_MIXED_EOL); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_FINAL_EOL_AND_EMPTY_AT_EOF); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_CONSECUTIVE_EMPTY_LINES); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_APPEND_ONTO_EXISTING); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_LONG_LINES); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_MANY_SHORT_LINES); + XTESTS_RUN_CASE(TEST_cstring_vector_readLines_EMPTY_LINE_IN_MIDDLE); + + XTESTS_PRINT_RESULTS(); + + XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); + } + + return retCode; +} + +/* ///////////////////////////////////////////////////////////////////////// + * compiler compatibility + */ + +#if defined(STLSOFT_COMPILER_IS_MSVC) +# if _MSC_VER >= 1200 +# pragma warning(pop) +# endif /* compiler */ +#endif /* compiler */ + +/* ///////////////////////////////////////////////////////////////////////// + * helper functions + */ + +namespace +{ + + static FILE* fopen_or_throw(char const* fileName, char const* mode) + { +#ifdef CSTRING_USING_SAFE_STR_FUNCTIONS + FILE* f; + + if (0 != ::fopen_s(&f, fileName, mode)) +#else /* ? CSTRING_USING_SAFE_STR_FUNCTIONS */ + FILE* f = ::fopen(fileName, mode); + + if (NULL == f) +#endif /* CSTRING_USING_SAFE_STR_FUNCTIONS */ + { + throw platformstl::platform_exception((std::string("Could not open file '") + fileName + "'").c_str(), platformstl::system_traits::get_last_error()); + } + + return f; + } + + static void write_bytes(char const* fileName, void const* bytes, size_t cb) + { + FILE* f = fopen_or_throw(fileName, "wb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + if (cb != ::fwrite(bytes, 1, cb, f)) + { + throw platformstl::platform_exception("Could not write test file", platformstl::system_traits::get_last_error()); + } + } + + static void write_string_bytes(char const* fileName, std::string const& bytes) + { + write_bytes(fileName, bytes.data(), bytes.size()); + } + + static std::string make_filled(size_t n, char ch) + { + return std::string(n, ch); + } + + int setup(void*) + { + return 0; + } + + int teardown(void* arg) + { + char const* filename = static_cast(arg); + + ::remove(filename); + + return 0; + } +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * test function implementations + */ + +namespace +{ + +static void TEST_cstring_vector_readLines_CALLABILITY() +{ + false && cstring_vector_readLines(stdout, NULL, NULL); + + TEST_PASSED(); +} + +static void TEST_cstring_vector_readLines_EMPTY_FILE() +{ + /* empty file → EOF, no lines pushed */ + write_bytes(TEST_FILE_NAME, "", 0); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 99u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(0u, numLinesRead); + TEST_INT_EQ(0u, csv.len); + + cstring_vector_destroy(&csv); +} + +static void TEST_cstring_vector_readLines_SHORT_MULTILINE() +{ + /* short multi-line file ending without EOL (mirrors readline baseline) */ + { + FILE* f = fopen_or_throw(TEST_FILE_NAME, "w"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + fprintf(f, "\n"); + fprintf(f, "abc\n"); + fprintf(f, "abcdef\n"); + fprintf(f, "abcdefghijkl\n"); + fprintf(f, "abcdefghijklmnopqrstuvwxyz\n"); + fprintf(f, "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"); + fprintf(f, "xyz"); + } + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "r"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(7u, numLinesRead); + REQUIRE(TEST_INT_EQ(7u, csv.len)); + TEST_MS_EQ("", csv.ptr[0]); + TEST_MS_EQ("abc", csv.ptr[1]); + TEST_MS_EQ("abcdef", csv.ptr[2]); + TEST_MS_EQ("abcdefghijkl", csv.ptr[3]); + TEST_MS_EQ("abcdefghijklmnopqrstuvwxyz", csv.ptr[4]); + TEST_MS_EQ("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", csv.ptr[5]); + TEST_MS_EQ("xyz", csv.ptr[6]); + + cstring_vector_destroy(&csv); +} + +static void TEST_cstring_vector_readLines_CRLF_AND_MIXED_EOL() +{ + /* CRLF / mixed EOL / lone CR (binary I/O) */ + { + std::string bytes; + + bytes.append("one\r\n"); + bytes.append("two\n"); + bytes.append("three\r\n"); + bytes.append("has\rembed\r\n"); + bytes.append("end"); + + write_string_bytes(TEST_FILE_NAME, bytes); + } + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + 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_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]); + + cstring_vector_destroy(&csv); +} + +static void TEST_cstring_vector_readLines_FINAL_EOL_AND_EMPTY_AT_EOF() +{ + /* final EOL vs no final EOL; empty trailing unterminated line omitted */ + { + write_string_bytes(TEST_FILE_NAME, std::string("a\nb\n")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(2u, numLinesRead); + REQUIRE(TEST_INT_EQ(2u, csv.len)); + TEST_MS_EQ("a", csv.ptr[0]); + TEST_MS_EQ("b", csv.ptr[1]); + + cstring_vector_destroy(&csv); + } + + { + write_string_bytes(TEST_FILE_NAME, std::string("a\nb")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(2u, numLinesRead); + REQUIRE(TEST_INT_EQ(2u, csv.len)); + TEST_MS_EQ("a", csv.ptr[0]); + TEST_MS_EQ("b", csv.ptr[1]); + + cstring_vector_destroy(&csv); + } + + { + /* "a\n" then EOF on empty → only "a" (empty-at-EOF omitted) */ + write_string_bytes(TEST_FILE_NAME, std::string("a\n")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(1u, numLinesRead); + REQUIRE(TEST_INT_EQ(1u, csv.len)); + TEST_MS_EQ("a", csv.ptr[0]); + + cstring_vector_destroy(&csv); + } + + { + /* unterminated single line */ + write_string_bytes(TEST_FILE_NAME, std::string("xyz")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(1u, numLinesRead); + REQUIRE(TEST_INT_EQ(1u, csv.len)); + TEST_MS_EQ("xyz", csv.ptr[0]); + + cstring_vector_destroy(&csv); + } +} + +static void TEST_cstring_vector_readLines_CONSECUTIVE_EMPTY_LINES() +{ + /* consecutive empty lines (SUCCESS empties are kept) */ + write_string_bytes(TEST_FILE_NAME, std::string("\n\n")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(2u, numLinesRead); + REQUIRE(TEST_INT_EQ(2u, csv.len)); + TEST_INT_EQ(0u, csv.ptr[0].len); + TEST_INT_EQ(0u, csv.ptr[1].len); + TEST_MS_EQ("", csv.ptr[0]); + TEST_MS_EQ("", csv.ptr[1]); + + cstring_vector_destroy(&csv); +} + +static void TEST_cstring_vector_readLines_APPEND_ONTO_EXISTING() +{ + /* append onto a non-empty vector; numLinesRead may be NULL */ + write_string_bytes(TEST_FILE_NAME, std::string("new-one\nnew-two\n")); + + cstring_t prior = cstring_t_DEFAULT; + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, cstring_create(&prior, "prior"))); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, cstring_vector_append(&csv, &prior, 1))); + + cstring_destroy(&prior); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + CSTRING_RC rc = cstring_vector_readLines(f, &csv, NULL); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + REQUIRE(TEST_INT_EQ(3u, csv.len)); + TEST_MS_EQ("prior", csv.ptr[0]); + TEST_MS_EQ("new-one", csv.ptr[1]); + TEST_MS_EQ("new-two", csv.ptr[2]); + + cstring_vector_destroy(&csv); +} + +static void TEST_cstring_vector_readLines_LONG_LINES() +{ + /* long lines */ + static size_t const lengths[] = { + 1024u, + 4096u, + 16384u, + 65536u, + }; + + { for (size_t i = 0; i != sizeof(lengths) / sizeof(lengths[0]); ++i) + { + size_t const len = lengths[i]; + std::string line = make_filled(len, static_cast('A' + static_cast(i % 26))); + std::string bytes = line; + + bytes.push_back('\n'); + bytes.append("tail"); + + write_string_bytes(TEST_FILE_NAME, bytes); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(2u, numLinesRead); + REQUIRE(TEST_INT_EQ(2u, csv.len)); + TEST_INT_EQ(len, csv.ptr[0].len); + TEST_MS_EQ(line.c_str(), csv.ptr[0]); + TEST_MS_EQ("tail", csv.ptr[1]); + + cstring_vector_destroy(&csv); + }} +} + +static void TEST_cstring_vector_readLines_MANY_SHORT_LINES() +{ + /* many short lines */ + size_t const num_lines = 1000u; + std::string bytes; + std::vector expected; + + expected.reserve(num_lines); + + { for (size_t i = 0; i != num_lines; ++i) + { + char buf[32]; + + snprintf(buf, sizeof(buf), "line-%04u", static_cast(i)); + + expected.push_back(buf); + bytes.append(buf); + bytes.push_back('\n'); + }} + + write_string_bytes(TEST_FILE_NAME, bytes); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(num_lines, numLinesRead); + REQUIRE(TEST_INT_EQ(num_lines, csv.len)); + + { for (size_t i = 0; i != num_lines; ++i) + { + TEST_MS_EQ(expected[i].c_str(), csv.ptr[i]); + }} + + cstring_vector_destroy(&csv); +} + +static void TEST_cstring_vector_readLines_EMPTY_LINE_IN_MIDDLE() +{ + /* empty lines in the middle */ + write_string_bytes(TEST_FILE_NAME, std::string("top\n\nbottom\n")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_vector_t csv = cstring_vector_t_DEFAULT; + size_t numLinesRead = 0u; + CSTRING_RC rc = cstring_vector_readLines(f, &csv, &numLinesRead); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(3u, numLinesRead); + REQUIRE(TEST_INT_EQ(3u, csv.len)); + TEST_MS_EQ("top", csv.ptr[0]); + TEST_MS_EQ("", csv.ptr[1]); + TEST_MS_EQ("bottom", csv.ptr[2]); + + cstring_vector_destroy(&csv); +} +} // anonymous namespace + + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/component/test.component.cstring_writeline/entry.cpp b/test/component/test.component.cstring_writeline/entry.cpp index e9d7a99..fa52b71 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: 12th January 2024 + * Updated: 27th September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -22,6 +22,7 @@ /* xTests header files */ #include +#include #include /* STLSoft header files */ @@ -40,16 +41,10 @@ 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_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); int setup(void*); int teardown(void*); @@ -77,7 +72,7 @@ const char TEST_FILE_NAME[] = "test.component.cstring_writeline.txt"; * main */ -int main(int argc, char **argv) +int main(int argc, char* argv[]) { int retCode = EXIT_SUCCESS; int verbosity = 2; @@ -86,16 +81,10 @@ int main(int argc, char **argv) if (XTESTS_START_RUNNER_WITH_SETUP_FNS("test.component.cstring_writeline", verbosity, setup, teardown, (void*)TEST_FILE_NAME)) { - XTESTS_RUN_CASE(test_1_0); - XTESTS_RUN_CASE(test_1_1); - XTESTS_RUN_CASE(test_1_2); - XTESTS_RUN_CASE(test_1_3); - XTESTS_RUN_CASE(test_1_4); - XTESTS_RUN_CASE(test_1_5); - XTESTS_RUN_CASE(test_1_6); - XTESTS_RUN_CASE(test_1_7); - XTESTS_RUN_CASE(test_1_8); - XTESTS_RUN_CASE(test_1_9); + XTESTS_RUN_CASE(TEST_cstring_writeline_CALLABILITY); + XTESTS_RUN_CASE(TEST_cstring_writeline_INVALID_STREAM); + XTESTS_RUN_CASE(TEST_cstring_writeline_MULTIPLE_LINES); + XTESTS_RUN_CASE(TEST_cstring_write_CONCATENATED); XTESTS_PRINT_RESULTS(); @@ -140,27 +129,29 @@ namespace -static void test_1_0() +static void TEST_cstring_writeline_CALLABILITY() { false && cstring_writeline(stdout, NULL, NULL); - XTESTS_TEST_PASSED(); + TEST_PASSED(); } -static void test_1_1() +static void TEST_cstring_writeline_INVALID_STREAM() { #if 0 cstring_t cs = cstring_t_DEFAULT; - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_INVALIDSTREAM, cstring_writeline(NULL, &cs, NULL)); + TEST_ENUM_EQ(CSTRING_RC_INVALIDSTREAM, cstring_writeline(NULL, &cs, NULL)); size_t n; - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_INVALIDSTREAM, cstring_writeline(NULL, &cs, &n)); + TEST_ENUM_EQ(CSTRING_RC_INVALIDSTREAM, cstring_writeline(NULL, &cs, &n)); #endif /* 0 */ + + TEST_PASSED(); } -static void test_1_2() +static void TEST_cstring_writeline_MULTIPLE_LINES() { static char const* const s_lines[] = { @@ -204,15 +195,15 @@ static void test_1_2() platformstl::file_lines out_lines(tf_out); - XTESTS_REQUIRE(XTESTS_TEST_INTEGER_EQUAL(6u, out_lines.size())); + REQUIRE(TEST_INT_EQ(6u, out_lines.size())); { for (size_t i = 0; STLSOFT_NUM_ELEMENTS(s_lines) != i; ++i) { - XTESTS_TEST_MULTIBYTE_STRING_EQUAL(s_lines[i], out_lines[i]); + TEST_MS_EQ(s_lines[i], out_lines[i]); }} } } -static void test_1_3() +static void TEST_cstring_write_CONCATENATED() { static char const* const s_lines[] = { @@ -256,7 +247,7 @@ static void test_1_3() platformstl::file_lines out_lines(tf_out); - XTESTS_REQUIRE(XTESTS_TEST_INTEGER_EQUAL(1u, out_lines.size())); + REQUIRE(TEST_INT_EQ(1u, out_lines.size())); std::string expected; @@ -265,34 +256,10 @@ static void test_1_3() expected.append(s_lines[i]); }} - XTESTS_TEST_MULTIBYTE_STRING_EQUAL(expected, out_lines[0]); + TEST_MS_EQ(expected, out_lines[0]); } } -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() -{ -} - } // anonymous namespace diff --git a/test/performance/CMakeLists.txt b/test/performance/CMakeLists.txt index 15d78ed..6c8958d 100644 --- a/test/performance/CMakeLists.txt +++ b/test/performance/CMakeLists.txt @@ -4,6 +4,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(test.performance.cstring) +add_subdirectory(test.performance.cstring_readline) add_subdirectory(test.performance.cstring_vector) diff --git a/test/performance/perf_harness.hpp b/test/performance/perf_harness.hpp index afa69cf..157350c 100644 --- a/test/performance/perf_harness.hpp +++ b/test/performance/perf_harness.hpp @@ -466,7 +466,7 @@ raw_assign_len( if (0 != n) { - std::memcpy(s->ptr, src, n); + ::memcpy(s->ptr, src, n); } s->len = n; @@ -492,7 +492,7 @@ raw_append_len( if (0 != n) { - std::memcpy(s->ptr + s->len, src, n); + ::memcpy(s->ptr + s->len, src, n); } s->len = need; @@ -524,8 +524,8 @@ raw_insert_len( if (0 != n) { - std::memmove(s->ptr + pos + n, s->ptr + pos, s->len - pos); - std::memcpy(s->ptr + pos, src, n); + ::memmove(s->ptr + pos + n, s->ptr + pos, s->len - pos); + ::memcpy(s->ptr + pos, src, n); } s->len = need; @@ -642,7 +642,7 @@ raw_vec_append_cstr( if (0 != n) { - std::memcpy(copy, s, n); + ::memcpy(copy, s, n); } copy[n] = '\0'; @@ -680,14 +680,14 @@ raw_vec_prepend_cstr( if (0 != n) { - std::memcpy(copy, s, n); + ::memcpy(copy, s, n); } copy[n] = '\0'; if (0 != v->len) { - std::memmove(v->ptr + 1, v->ptr, v->len * sizeof(char*)); + ::memmove(v->ptr + 1, v->ptr, v->len * sizeof(char*)); } v->ptr[0] = copy; diff --git a/test/performance/test.performance.cstring/CMakeLists.txt b/test/performance/test.performance.cstring/CMakeLists.txt index 2f3068e..96b4531 100644 --- a/test/performance/test.performance.cstring/CMakeLists.txt +++ b/test/performance/test.performance.cstring/CMakeLists.txt @@ -1 +1,5 @@ +list(APPEND X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED + 4996 +) + define_performance_test_program(test.performance.cstring main.cpp) diff --git a/test/performance/test.performance.cstring/main.cpp b/test/performance/test.performance.cstring/main.cpp index 31525ab..0acfea5 100644 --- a/test/performance/test.performance.cstring/main.cpp +++ b/test/performance/test.performance.cstring/main.cpp @@ -502,7 +502,7 @@ scenario_borrowed_fixed( run_result const fx = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { std::vector buf(n + 1u, '\0'); - std::memcpy(&buf[0], p, n); + ::memcpy(&buf[0], p, n); buf[n] = '\0'; return n + static_cast(buf[0]); }); diff --git a/test/performance/test.performance.cstring_readline/CMakeLists.txt b/test/performance/test.performance.cstring_readline/CMakeLists.txt new file mode 100644 index 0000000..7588498 --- /dev/null +++ b/test/performance/test.performance.cstring_readline/CMakeLists.txt @@ -0,0 +1,5 @@ +list(APPEND X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED + 4996 +) + +define_performance_test_program(test.performance.cstring_readline main.cpp) diff --git a/test/performance/test.performance.cstring_readline/main.cpp b/test/performance/test.performance.cstring_readline/main.cpp new file mode 100644 index 0000000..8c40189 --- /dev/null +++ b/test/performance/test.performance.cstring_readline/main.cpp @@ -0,0 +1,609 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/performance/test.performance.cstring_readline/main.cpp + * + * Purpose: Competitive performance tests for cstring_readline() against + * std::getline, fgets (bounded buffer), and a char-at-a-time fgetc + * floor. Scenarios cover reused capacity, CRLF stripping, a fresh + * instance per line, a pre-reserved buffer, empty lines, and an + * unterminated final line. Percentiles and this filesystem suite + * require p99. + * + * Created: 27th September 2026 + * Updated: 27th September 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include + +#include "perf_harness.hpp" + +#ifdef HAS_P99 +# include +# include +# include + +# include +# include +#endif /* HAS_P99 */ + +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * constants + */ + +#ifdef HAS_P99 +namespace { + +char const* const IMPL_READLINE = "cstring_readline"; +char const* const IMPL_GETLINE = "std::getline"; +char const* const IMPL_FGETS = "fgets"; +char const* const IMPL_RAW = "raw_fgetc"; + +char const TEST_FILE_NAME[] = "test.performance.cstring_readline.lines.txt"; + +enum line_ending_t +{ + LINE_ENDING_LF = 0, + LINE_ENDING_CRLF, + LINE_ENDING_NONE, +}; + +enum instance_mode_t +{ + INSTANCE_REUSE = 0, + INSTANCE_FRESH, + INSTANCE_PRERESERVED, +}; + +struct readline_case +{ + char const* scenario; + std::size_t num_lines; + std::size_t line_len; + line_ending_t ending; + instance_mode_t mode; +}; + +readline_case const CASES[] = +{ + { "readline_empty_lines", 2000u, 0u, LINE_ENDING_LF, INSTANCE_REUSE, }, + { "readline_fresh_each_line", 200u, 80u, LINE_ENDING_LF, INSTANCE_FRESH, }, + { "readline_prereserved", 100u, 1024u, LINE_ENDING_LF, INSTANCE_PRERESERVED, }, + { "readline_reuse_crlf", 1000u, 80u, LINE_ENDING_CRLF, INSTANCE_REUSE, }, + { "readline_reuse_lf", 1000u, 16u, LINE_ENDING_LF, INSTANCE_REUSE, }, + { "readline_reuse_lf", 1000u, 80u, LINE_ENDING_LF, INSTANCE_REUSE, }, + { "readline_reuse_lf", 100u, 1024u, LINE_ENDING_LF, INSTANCE_REUSE, }, + { "readline_unterminated_eof", 1u, 16384u, LINE_ENDING_NONE, INSTANCE_REUSE, }, +}; +} // anonymous namespace +#endif /* HAS_P99 */ + + +/* ///////////////////////////////////////////////////////////////////////// + * helpers + */ + +#ifdef HAS_P99 +namespace { + +using namespace cstring_perf; + +struct run_result +{ + interval_t tm_ns; + std::uint64_t anchor; + p99::histogram hist; +}; + +template +run_result +time_iterations( + std::size_t num_iterations +, std::size_t num_warm_loops +, F fn +) +{ + run_result result = {}; + stopwatch_t sw; + + for (std::size_t w = num_warm_loops; 0 != w; --w) + { + result.anchor = 0; + result.hist.clear(); + + interval_t tm_ns = 0; + + for (std::size_t i = 0; num_iterations != i; ++i) + { + sw.start(); + result.anchor += fn(); + sw.stop(); + + interval_t const sample = sw.get_nanoseconds(); + + tm_ns += sample; + (void)result.hist.push_ns(static_cast(sample)); + } + + if (1 == w) + { + result.tm_ns = tm_ns; + } + } + + return result; +} + +void +emit_row( + char const* scenario +, std::size_t size +, char const* impl +, std::size_t num_iterations +, std::size_t num_actions +, run_result const& r +, interval_t cstring_tm_ns +) +{ + double const ratio = + (0 == ::strcmp(impl, IMPL_READLINE)) + ? 1.0 + : ratio_or_dash(r.tm_ns, cstring_tm_ns) + ; + + display_results( + scenario + , size + , impl + , num_iterations + , num_actions + , r.tm_ns + , ratio + , r.anchor + , &r.hist + ); +} + +std::uint64_t +line_anchor( + std::size_t len +, unsigned char head +) +{ + return static_cast(len) + 1u + static_cast(head); +} + +unsigned char +content_head( + char const* p +, std::size_t n +) +{ + if (0 == n || NULL == p) + { + return 0; + } + + return static_cast(p[0]); +} + +bool +write_lines_file( + char const* path +, std::size_t num_lines +, std::size_t line_len +, line_ending_t ending +) +{ + std::ofstream out(path, std::ios::binary | std::ios::trunc); + + if (!out) + { + return false; + } + + std::string const line = make_payload(line_len); + + for (std::size_t i = 0; num_lines != i; ++i) + { + out.write(line.data(), static_cast(line.size())); + + if (LINE_ENDING_CRLF == ending) + { + out.write("\r\n", 2); + } + else if (LINE_ENDING_LF == ending) + { + out.put('\n'); + } + } + + return static_cast(out); +} + +std::uint64_t +read_all_cstring( + char const* path +, std::size_t line_len +, instance_mode_t mode +) +{ + FILE* const f = std::fopen(path, "rb"); + + if (NULL == f) + { + return 0; + } + + cstring_t cs = cstring_t_DEFAULT; + std::uint64_t anchor = 0; + + if (INSTANCE_PRERESERVED == mode && 0 != line_len) + { + if (CSTRING_RC_SUCCESS != cstring_setCapacity(&cs, line_len)) + { + cstring_destroy(&cs); + std::fclose(f); + + return 0; + } + } + + for (;;) + { + if (INSTANCE_FRESH == mode && NULL != cs.ptr) + { + cstring_destroy(&cs); + cs = cstring_t_DEFAULT; + } + + CSTRING_RC const rc = cstring_readline(f, &cs, NULL); + + if (CSTRING_RC_SUCCESS != rc && CSTRING_RC_EOF != rc) + { + anchor = 0; + break; + } + + if (0 != cs.len || CSTRING_RC_EOF != rc) + { + anchor += line_anchor(cs.len, content_head(cs.ptr, cs.len)); + } + + if (CSTRING_RC_EOF == rc) + { + break; + } + } + + cstring_destroy(&cs); + std::fclose(f); + + return anchor; +} + +std::uint64_t +read_all_getline( + char const* path +, std::size_t line_len +, instance_mode_t mode +) +{ + std::ifstream in(path, std::ios::in | std::ios::binary); + + if (!in) + { + return 0; + } + + std::string line; + std::uint64_t anchor = 0; + + if (INSTANCE_PRERESERVED == mode && 0 != line_len) + { + line.reserve(line_len); + } + + while (std::getline(in, line)) + { + if (!line.empty() && '\r' == line[line.size() - 1u]) + { + line.pop_back(); + } + + anchor += line_anchor(line.size(), content_head(line.data(), line.size())); + + if (INSTANCE_FRESH == mode) + { + std::string empty; + + line.swap(empty); + } + } + + return anchor; +} + +std::uint64_t +read_all_fgets( + char const* path +, std::size_t line_len +) +{ + FILE* const f = std::fopen(path, "rb"); + + if (NULL == f) + { + return 0; + } + + /* line_len + 4 covers content, optional CR, LF, and the NUL. */ + std::vector buf(line_len + 4u); + std::uint64_t anchor = 0; + + while (NULL != std::fgets(&buf[0], static_cast(buf.size()), f)) + { + std::size_t n = std::strlen(&buf[0]); + + if (0 != n && '\n' == buf[n - 1u]) + { + --n; + + if (0 != n && '\r' == buf[n - 1u]) + { + --n; + } + } + + anchor += line_anchor(n, content_head(&buf[0], n)); + } + + std::fclose(f); + + return anchor; +} + +std::uint64_t +read_all_raw_fgetc( + char const* path +, std::size_t line_len +, instance_mode_t mode +) +{ + FILE* const f = std::fopen(path, "rb"); + + if (NULL == f) + { + return 0; + } + + raw_string s; + raw_init(&s); + + if (INSTANCE_PRERESERVED == mode && 0 != line_len) + { + if (0 != raw_reserve(&s, line_len)) + { + raw_destroy(&s); + std::fclose(f); + + return 0; + } + } + + std::uint64_t anchor = 0; + int previous = '\0'; + + for (;;) + { + int const ch = std::fgetc(f); + + if (EOF == ch) + { + if (0 != s.len) + { + anchor += line_anchor(s.len, content_head(s.ptr, s.len)); + } + + break; + } + + if ('\n' == ch) + { + if ('\r' == previous && 0 != s.len) + { + --s.len; + s.ptr[s.len] = '\0'; + } + + anchor += line_anchor(s.len, content_head(s.ptr, s.len)); + s.len = 0; + + if (NULL != s.ptr) + { + s.ptr[0] = '\0'; + } + + previous = '\0'; + + if (INSTANCE_FRESH == mode) + { + raw_destroy(&s); + raw_init(&s); + } + + continue; + } + + char const c = static_cast(ch); + + if (0 != raw_append_len(&s, &c, 1u)) + { + anchor = 0; + break; + } + + previous = ch; + } + + raw_destroy(&s); + std::fclose(f); + + return anchor; +} + +bool +anchors_agree( + char const* scenario +, std::size_t line_len +, run_result const& cs +, run_result const& gl +, run_result const& fg +, run_result const& raw +) +{ + if (0 != cs.anchor && + cs.anchor == gl.anchor && + cs.anchor == fg.anchor && + cs.anchor == raw.anchor) + { + return true; + } + + std::cerr + << "anchor mismatch in " << scenario + << " size " << line_len + << ": cstring_readline=" << cs.anchor + << " std::getline=" << gl.anchor + << " fgets=" << fg.anchor + << " raw_fgetc=" << raw.anchor + << std::endl + ; + + return false; +} + +bool +scenario_readline( + readline_case const& spec +, std::size_t num_trials +, std::size_t num_warm_loops +) +{ + if (LINE_ENDING_NONE == spec.ending && 1u != spec.num_lines) + { + std::cerr + << spec.scenario + << ": unterminated input must be a single line" + << std::endl + ; + + return false; + } + + if (!write_lines_file(TEST_FILE_NAME, spec.num_lines, spec.line_len, spec.ending)) + { + std::cerr + << "failed to write " << TEST_FILE_NAME + << "; skipping " << spec.scenario + << std::endl + ; + std::remove(TEST_FILE_NAME); + + return true; + } + + char const* const path = TEST_FILE_NAME; + std::size_t const line_len = spec.line_len; + instance_mode_t const mode = spec.mode; + + run_result const cs = time_iterations(num_trials, num_warm_loops, [path, line_len, mode]() -> std::uint64_t { + return read_all_cstring(path, line_len, mode); + }); + + run_result const gl = time_iterations(num_trials, num_warm_loops, [path, line_len, mode]() -> std::uint64_t { + return read_all_getline(path, line_len, mode); + }); + + run_result const fg = time_iterations(num_trials, num_warm_loops, [path, line_len]() -> std::uint64_t { + return read_all_fgets(path, line_len); + }); + + run_result const raw = time_iterations(num_trials, num_warm_loops, [path, line_len, mode]() -> std::uint64_t { + return read_all_raw_fgetc(path, line_len, mode); + }); + + bool const ok = anchors_agree(spec.scenario, spec.line_len, cs, gl, fg, raw); + + if (ok) + { + emit_row(spec.scenario, spec.line_len, IMPL_READLINE, num_trials, spec.num_lines, cs, cs.tm_ns); + emit_row(spec.scenario, spec.line_len, IMPL_GETLINE, num_trials, spec.num_lines, gl, cs.tm_ns); + emit_row(spec.scenario, spec.line_len, IMPL_FGETS, num_trials, spec.num_lines, fg, cs.tm_ns); + emit_row(spec.scenario, spec.line_len, IMPL_RAW, num_trials, spec.num_lines, raw, cs.tm_ns); + } + + std::remove(TEST_FILE_NAME); + + return ok; +} +} // anonymous namespace +#endif /* HAS_P99 */ + + +/* ///////////////////////////////////////////////////////////////////////// + * main + */ + +int main(int /*argc*/, char* /*argv*/[]) +{ +#ifdef HAS_P99 + + std::size_t const num_trials = cstring_perf::default_file_trials(); + std::size_t const num_warm_loops = cstring_perf::default_warmups(); + + cstring_perf::display_banner("test.performance.cstring_readline"); + + std::cout + << "readline suite (#acts = lines, so ns/op is per line;" + << std::endl + << " cstring_readline is fgetc + one-character append;" + << std::endl + << " fgets uses a buffer sized to the known line):" + << std::endl + ; + + cstring_perf::display_results_title(); + + for (std::size_t i = 0; STLSOFT_NUM_ELEMENTS(CASES) != i; ++i) + { + if (!scenario_readline(CASES[i], num_trials, num_warm_loops)) + { + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +#else /* ? HAS_P99 */ + + cstring_perf::display_banner("test.performance.cstring_readline"); + + std::cout + << "\nreadline suite skipped (p99 not available)." + << std::endl + ; + + return EXIT_SUCCESS; +#endif /* HAS_P99 */ +} + + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/performance/test.performance.cstring_vector/CMakeLists.txt b/test/performance/test.performance.cstring_vector/CMakeLists.txt index 9a86ed6..5d7d407 100644 --- a/test/performance/test.performance.cstring_vector/CMakeLists.txt +++ b/test/performance/test.performance.cstring_vector/CMakeLists.txt @@ -1 +1,5 @@ +list(APPEND X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED + 4996 +) + define_performance_test_program(test.performance.cstring_vector main.cpp) diff --git a/test/unit/test.unit.cstring.1/entry.cpp b/test/unit/test.unit.cstring.1/entry.cpp index 198f908..951d6f0 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: 23rd September 2026 + * Updated: 27th September 2025 * * ////////////////////////////////////////////////////////////////////// */ @@ -972,7 +972,7 @@ static void test_2_3() cstring_t str; const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; cstring_char_t const* const s = alphabet + n; - cstring_flags_t flags = CSTRING_F_USE_WIN32_PROCESSHEAP_MEMORY; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY; cstring_createEx(&str, s, flags, NULL, 0); @@ -994,7 +994,7 @@ static void test_2_3() cstring_t str; const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; cstring_char_t const* const s = alphabet + n; - cstring_flags_t flags = CSTRING_F_USE_WIN32_COM_TASK_MEMORY; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY; cstring_createEx(&str, s, flags, NULL, 0); @@ -1093,7 +1093,7 @@ static void test_2_4() cstring_t str; const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; cstring_char_t const* const s = alphabet + n; - cstring_flags_t flags = CSTRING_F_USE_WIN32_GLOBAL_MEMORY; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY; cstring_createLenEx(&str, s, i, flags, NULL, 0); @@ -1115,7 +1115,7 @@ static void test_2_4() cstring_t str; const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; cstring_char_t const* const s = alphabet + n; - cstring_flags_t flags = CSTRING_F_USE_WIN32_PROCESSHEAP_MEMORY; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY; cstring_createLenEx(&str, s, i, flags, NULL, 0); @@ -1137,7 +1137,7 @@ static void test_2_4() cstring_t str; const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; cstring_char_t const* const s = alphabet + n; - cstring_flags_t flags = CSTRING_F_USE_WIN32_COM_TASK_MEMORY; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY; cstring_createLenEx(&str, s, i, flags, NULL, 0); From d0840e6d378e29d7aa72004b39cd6bc5bc9ca80e Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Sun, 27 Sep 2026 17:53:24 +1000 Subject: [PATCH 64/88] version --- CHANGES.md | 2 +- NEWS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 190a5de..ee7532b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ # cstring - Changes -## 4.0.17 - 28th September 2026 +## 4.0.17 - 27th September 2026 * Added competitive performance tests for **cstring** and **cstring_vector** (`test.performance.cstring`, `test.performance.cstring_vector`); * Baselines: `std::string` / hand-rolled `realloc` for strings; `std::vector` / hand-rolled for vectors; diff --git a/NEWS.md b/NEWS.md index 999dbc6..1dda1a5 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): | -| 28th September 2026 | Release of [cstring 4.0.17](https://github.com/synesissoftware/cstring/releases/tag/4.0.17) | Competitive perf tests; optional **p99** | +| 27th September 2026 | Release of [cstring 4.0.17](https://github.com/synesissoftware/cstring/releases/tag/4.0.17) | Competitive perf tests; optional **p99** | | 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 | | 6th September 2026 | Release of [cstring 4.0.15](https://github.com/synesissoftware/cstring/releases/tag/4.0.15) | Doxygen `\param`/`\retval` 76-rule | | 9th August 2026 | Release of [cstring 4.0.14](https://github.com/synesissoftware/cstring/releases/tag/4.0.14) | self-assignment safety | From 91e63f1eb313da3939239ebf398ee086e1544480 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Sun, 27 Sep 2026 18:58:07 +1000 Subject: [PATCH 65/88] chore: trivial ordering --- src/cstring.core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cstring.core.c b/src/cstring.core.c index 446f21f..8eac8b0 100644 --- a/src/cstring.core.c +++ b/src/cstring.core.c @@ -1495,14 +1495,14 @@ cstring_readline( numRead = &numRead_; } + *numRead = 0u; + if (NULL == stm || 0 != ferror(stm)) { return CSTRING_RC_INVALIDSTREAM; } - *numRead = 0u; - cstring_truncate(pcs, 0); for (;;) From 9dd8d4c4e5b012939407d969c00608655d4e4584 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Mon, 28 Sep 2026 06:31:32 +1000 Subject: [PATCH 66/88] TODO.md --- TODO.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 97a3965..5ef8f01 100644 --- a/TODO.md +++ b/TODO.md @@ -14,6 +14,9 @@ * [x] ~~~Scratch **libver** retained under **test/scratch**~~~ - ✅; * [x] ~~~Delete Visual Studio 98 files~~~ - ✅; * [x] ~~~Delete Visual Studio 2003+ files~~~ - ✅; +* [ ] discriminate on `_WIN32` in implementation (and maybe also in API); +* [ ] check `CSTRING_USE_WINAPI_` +* [ ] custom arena(s); ## Performance improvements @@ -22,6 +25,8 @@ * [x] ~~~Quantify performance~~~ - ✅; * [x] ~~~Compare with other popular implementations~~~ - ✅; * [ ] Analyse performance results and optimise hot paths; +* [ ] `cstring_readline()` to use block logic on regular files; +* [ ] `cstring_readlines()` to use block logic on regular files; ## Packaging improvements @@ -43,7 +48,10 @@ * [x] ~~~Windows~~~ - ✅; * [ ] Debug configuration; * [ ] Release configuration; - * [ ] exercise `--no-cpp` / `--no-shwild` permutations beyond the current jobs; + * [ ] exercise CI permutations: + * [ ] `--no-cpp`; + * [ ] `--no-p99`; + * [ ] `--no-shwild`; * [-] ~~~Makefiles (legacy build trees removed; CMake-only)~~~ - ❌; * [ ] Packages: * [ ] vcpkg; From aa0b4e0adede124087bfad8a6ff6313ba4552cad Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Mon, 28 Sep 2026 06:31:32 +1000 Subject: [PATCH 67/88] TODO.md --- TODO.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 97a3965..5ef8f01 100644 --- a/TODO.md +++ b/TODO.md @@ -14,6 +14,9 @@ * [x] ~~~Scratch **libver** retained under **test/scratch**~~~ - ✅; * [x] ~~~Delete Visual Studio 98 files~~~ - ✅; * [x] ~~~Delete Visual Studio 2003+ files~~~ - ✅; +* [ ] discriminate on `_WIN32` in implementation (and maybe also in API); +* [ ] check `CSTRING_USE_WINAPI_` +* [ ] custom arena(s); ## Performance improvements @@ -22,6 +25,8 @@ * [x] ~~~Quantify performance~~~ - ✅; * [x] ~~~Compare with other popular implementations~~~ - ✅; * [ ] Analyse performance results and optimise hot paths; +* [ ] `cstring_readline()` to use block logic on regular files; +* [ ] `cstring_readlines()` to use block logic on regular files; ## Packaging improvements @@ -43,7 +48,10 @@ * [x] ~~~Windows~~~ - ✅; * [ ] Debug configuration; * [ ] Release configuration; - * [ ] exercise `--no-cpp` / `--no-shwild` permutations beyond the current jobs; + * [ ] exercise CI permutations: + * [ ] `--no-cpp`; + * [ ] `--no-p99`; + * [ ] `--no-shwild`; * [-] ~~~Makefiles (legacy build trees removed; CMake-only)~~~ - ❌; * [ ] Packages: * [ ] vcpkg; From 0af6b716e315591c92ebd56d959c3da285eb7497 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Mon, 28 Sep 2026 07:05:31 +1000 Subject: [PATCH 68/88] fix --- test/performance/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/test/performance/CMakeLists.txt b/test/performance/CMakeLists.txt index 6c8958d..15d78ed 100644 --- a/test/performance/CMakeLists.txt +++ b/test/performance/CMakeLists.txt @@ -4,7 +4,6 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(test.performance.cstring) -add_subdirectory(test.performance.cstring_readline) add_subdirectory(test.performance.cstring_vector) From 860617000206218f28b40e7c7f9e10d19fabf683 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Mon, 28 Sep 2026 07:08:07 +1000 Subject: [PATCH 69/88] fix --- test/performance/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/performance/CMakeLists.txt b/test/performance/CMakeLists.txt index 15d78ed..6c8958d 100644 --- a/test/performance/CMakeLists.txt +++ b/test/performance/CMakeLists.txt @@ -4,6 +4,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(test.performance.cstring) +add_subdirectory(test.performance.cstring_readline) add_subdirectory(test.performance.cstring_vector) From 85c52c81b977f3d28fc023e73f2326975267561f Mon Sep 17 00:00:00 2001 From: synesissoftware Date: Tue, 29 Sep 2026 10:12:19 +1000 Subject: [PATCH 70/88] fix(cstring): gate Windows arenas on `_WIN32` for 4.1.0 (#25) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * general boilerplate / project-support improvements (#16) * squash-commit * 4.0.14 * fix * 4.0.15 (#20) * Updated all `\param` and `\retval` Doxygen documentation comments in `cstring.h`, `cstring.vector.h`, and `cstring.core.c` to obey the 76-rule and terminate with a semicolon; * Bumped version to 4.0.15 in `cstring.h`, `Doxyfile`, `CHANGES.md`, and `NEWS.md`; * Updated `Updated:` header banners to 6th September 2026; * markdown * chore(c-cpp): union files.associations from ~/dev vscode settings Give the 28 in-wave C/C++ projects (and gold **c** / **cxx** / **c_cxx** templates) a sorted **files.associations** union of all associations found under **~/dev**; * .gitattributes * .vscode/settings.json * .vimrc * CHANGES.md * chore(): use computed PREFIX_VER with documented PATCH Align library version macros to c-cpp-standards. Define MAJOR, MINOR, PATCH, and ALPHABETA; compute PREFIX_VER by shifts; keep REVISION as a skip-section alias of PATCH (target at column 60); retain named historical VER_* constants only for comparison and documentation. * test(): ship scratch libver version reporter Add or normalise test/scratch/libver to print the hosting PREFIX_VER as “Name: vM.m.p.ab (PREFIX_VER = 0x........)” and, when sibling public headers are included, an “efferent dependencies:” block. * test(): rename scratch libver to versions Move test/scratch/libver to test/scratch/versions and retarget the executable as versions so C/C++ reporters match the Phase 3 freeze. * test: add canonical scratch versions reporter Add a Phase 3 gold version printer under test/scratch/versions, wire it from the scratch parent list, and use CMake target/argv0 versions; * .gitattributes * fix(c-cpp): quote Dir resolution in CMake helper scripts Use Dir=$(cd "$(dirname "$ScriptPath")" && pwd) so paths with spaces resolve reliably; apply the same one-line fix across in-wave helper scripts (prepare/build/clean/ctest/remove and run_all_*). * chore(scratch test): **versions** => **test.scratch.version** * doc(README.md): adding afferent dependencies (**errni**, **rstrip**) * feat(cstring): colour prepare_cmake and unify cmake configure Add SisClr_* status colour (NO_COLOR, AlwaysUseColours, TTY gate), --build-shared-libs, and a single cmake invoke with CMakeGeneratorArgs so every -D is always passed; tidy prologue groups, quoting, and help text. * feat(cstring): force colours via env and -A in prepare_cmake Initialise AlwaysUseColours from SIS_CMAKE_ALWAYS_USE_COLOURS / SIS_ALWAYS_USE_COLOURS; let -A/--always-use-colours override NO_COLOR (pre-scan args so help and errors are coloured). * refactor(cstring): drive prepare builds with cmake --build Drop MSYSTEM→MinGW and MakeCmd; honour SIS_CMAKE_MINGW via `sis_cmake_is_truey()`; add sis_cmake_build for -m; configure in-place with -S/-B and || exit 1; tighten --mingw/--msvc-mt/--run-make help. * refactor(cstring): drive build_cmake via cmake --build Replace MakeCmd/Makefile gating with sis_cmake_build; require a configured CMakeCache; add SisClr_* colour and -A to match prepare_cmake.sh. * refactor(cstring): drive clean_cmake via cmake --build --target clean Replace MakeCmd/Makefile gating with sis_cmake_build clean; require a configured CMakeCache; add SisClr_* colour and -A to match build_cmake.sh. * refactor(cstring): align remove_cmake_artefacts with helper dialect Add SisClr_* colour and -A; quote inventory loops; recognise Windows_NT; remove artefacts without cd into the build directory. * refactor(cstring): drive ctest_cmake via cmake --build then ctest Replace MakeCmd with sis_cmake_build; require CMakeCache; fail -M when unconfigured; add --output-on-failure, SisClr_* colour, and -A. * refactor(cstring): unit/component runners use sis_cmake_build dialect Make run_all_unit_tests.sh unit-only; align both category runners with SisClr_*/-A, cmake --build, and gold basename discovery (.exe, print0). * fix(cstring): accept category-only flags; fix CI component runner Treat --unit-only / --component-only as no-ops on the matching category scripts; point ci-cell component tests at run_all_component_tests.sh. * refactor(cstring): drive run_all_examples via cmake --build dialect Replace MakeCmd with sis_cmake_build; add SisClr_*/-A; discover example* executables with print0/.exe and optional --verbosity. * fix(cstring): gate cstring_vector no-arg demo on SIS_EXAMPLE_SMOKE Fail without input for humans; when SIS_EXAMPLE_SMOKE is set, use an in-process tmpfile sample. run_all_examples exports the variable for CI. * refactor(cstring): drive run_all_scratch via cmake --build dialect Replace MakeCmd with sis_cmake_build; run scratch-only (drop performance); add SisClr_*/-A; discover test_scratch* / test.scratch.* via print0/.exe. * refactor(cstring): native CMD runners for unit, component, examples Add run_all_component_tests.cmd and run_all_examples.cmd; make run_all_unit_tests.cmd unit-only with ProjectName, benign --unit-only, and stop on first failing .exe (no Bash). * feat(cstring): add run_all_automated_tests aggregate (.sh/.cmd) Add unit-then-component aggregators with mutually exclusive --unit-only / --component-only; Bash forwards other flags, CMD CALLs native category runners (no Bash). * refactor(cstring): drive run_all_performance via cmake --build dialect Align with scratch helpers: SisClr_*/-A, sis_cmake_build, and test_performance* / test.performance.* discovery (print0/.exe). * style(cstring): SisClr polish on run_all_automated_tests.sh Add SisClr_*/-A (forwarded), coloured status/mutex messages; keep unit-then-component aggregate behaviour. * ci(cstring): exercise prepare/build/clean helpers in CI Split no-cpp into prepare (no -m) then build_cmake.sh; add install-smoke prepare-clean-build-clean-build on a dedicated SIS_CMAKE_BUILD_DIR tree. * ci(cstring): dogfood native run_all *.cmd on Windows cells Invoke examples/unit/component/scratch via cmd.exe on Windows; keep Bash runners on Unix; add run_all_scratch_tests.cmd; PATH MinGW from msys2. * ci(cstring): dogfood prepare/build in cells; order test jobs - Replace raw cmake configure/build with prepare_cmake.sh and build_cmake.sh; - add performance job/.cmd; - chain unit→component→performance→scratch→examples. * ci(cstring): invoke ctest, remove, and automated helpers Run ctest_cmake.sh after each cell build; extend install-smoke through remove and re-prepare/build; use run_all_automated_tests.sh in no-cpp. * fix(cstring): harden prepare empty-args and multi-config ctest Avoid bash 3.2 nounset failure on empty PREPARE_ARGS in ci-cell, and pass ctest -C from SIS_CMAKE_CONFIG when CMAKE_CONFIGURATION_TYPES is present so Visual Studio cells can run. * ci(cstring): dogfood run_all_automated_tests.cmd on Windows Drive Windows unit and component cell steps through the aggregate .cmd with --unit-only / --component-only so the orchestrator and its CALL targets are exercised without duplicating the suite. * ci(cstring): collapse cell suites into one post-build test job Replace the serial unit/component/performance/scratch/examples jobs with a single Tests job of sequential helper steps so each matrix cell pays one checkout, MinGW setup, and artifact download instead of five. * docs(cstring): target 4.0.16 release for 23 September 2026 Align CHANGES/NEWS to today, bump Doxyfile PROJECT_NUMBER, and refresh cstring.h Updated/edit counters; package CSTRING_VER_* already at 4.0.16. * tidying * Perf tests (#22) * feat(cstring): add competitive performance tests for 4.0.17 Add test.performance.cstring and test.performance.cstring_vector with std::string / vector and hand-rolled baselines; optional p99 percentiles (and file_lines vs cstring_vector_readLines when linked); --no-p99 / NO_P99; --gap-groups / SIS_PERFTESTS_GROUPGAPS; CSTRING_EXTERN_C on the cstring.vector API. * test(cstring): time ifstream getline in the file-lines suite Add an `std::ifstream` + `std::getline` row to `file_read_all_lines`, so `cstring_vector_readLines` has an owning standard-library baseline beside `platformstl::file_lines`. * test(cstring): pair borrowed_fixed_assign with std::string Add the semantic std::string::assign baseline beside the existing borrowed/fixed path; fix borrowed capacity to n+1. * fix(cstring): restore legacy-compatible memory calls in performance tests Define _CRT_SECURE_NO_WARNINGS for MSVC before CRT headers are included; use global ::memcpy and ::memmove names to preserve early C/C++ compatibility; * fix(cstring): use SIS target warning suppression mechanism Register MSVC warning 4996 through X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED for each affected performance target; remove broad and direct warning suppression from the top-level build; * fix(cstring): correct vector insertion range handling Move the full tail when inserting before the vector end; construct inserted strings and clean up failures at the insertion position; prevent prepend benchmarks from corrupting vector storage; * fix * squash-commit --------- Co-authored-by: Matt Wilson * More component tests (#23) * test(cstring): rename component readline/writeline cases descriptively Adopt TEST__ names for cstring_readline and cstring_writeline/cstring_write component cases; drop empty writeline stubs; refresh Updated banners and prefer char* argv[] in main. * test(cstring): adopt terse xTests API in component suites Switch readline/writeline component assertions to TEST_ENUM_EQ, TEST_INT_EQ, TEST_MS_EQ, TEST_PASSED, and REQUIRE; mark vector APIs CSTRING_EXTERN_C for C++ linkage; wire test.component.cstring_vector_readLines into the component CMake tree. * test(cstring): expand cstring_readline component coverage Fill empty readline cases for empty file, CRLF/mixed EOL, final EOL, consecutive empties, long and many short lines, and cstring reuse; add binary fixture helpers; correct the vector header File: banner. * test(cstring): add cstring_vector_readLines component suite Add test.component.cstring_vector_readLines covering empty file, EOL variants, empty-at-EOF omission, append-onto-existing, long and many short lines; use the xTests terse API. * ci * test(cstring): finish terse xTests API in readline coverage Convert the remaining verbose XTESTS_* assertions in the expanded cstring_readline cases to REQUIRE, TEST_ENUM_EQ, TEST_INT_EQ, and TEST_MS_EQ. * fix * Flags fixes (#24) * tidy * chore: replacing backwards-compatibility flags uses with the replacements * fix(flags): correcting (currently unused) flag(s) * fix(cstring): drop unused bits from the cstring arena mask Narrow CSTRING_F_ARENA_MASK from 0xff00 to 0x3700, the union of the defined arena selectors, so unused high bits are not treated as an arena. Retire the unsupported/unused custom-arena calling-convention macros, clearing the old stdcall value 0x4000 that sat inside the mask. * squash-commit * version * TODO.md * fix(cstring): gate Windows arenas on `_WIN32` for 4.1.0 Define the Windows arena flags and compile the WinAPI allocators when the compiler predefines `_WIN32`, rather than when `WIN32` or `WIN64` is set; record the 28th September 2026 release in **CHANGES.md**, **NEWS.md**, **Doxyfile**, and **TODO.md**. * fix * fix * tidy * version * refactor(cstring): move Windows arena allocators into win.c (#26) * refactor(cstring): move Windows arena allocators into win.c Move the Win32 Global, process-heap, and COM task reallocators out of **cstring.core.c** into **src/win.c**, declared from **src/internal.h**; - Windows builds compile **win.c**, which does not re-check **CSTRING_USE_WINAPI_**; core still dispatches those arenas; - **dllmain.c** and **mmap.c** are unchanged. * clean * tidy * ci * chore(cstring): stamp 4.0.18 Updated banners Set Updated to 29th September 2026 on the implementation files changed since 4.0.17 (**src/CMakeLists.txt**, **cstring.core.c**, **cstring.vector.c**, **internal.h**, **win.c**); **CHANGES.md**, **NEWS.md**, **Doxyfile**, and `CSTRING_VER` were already at 4.0.18. --------- Co-authored-by: Matt Wilson <152443343+mwsis@users.noreply.github.com> --- .github/workflows/ci.yml | 1 + CHANGES.md | 5 + CMakeLists.txt | 2 +- Doxyfile | 2 +- NEWS.md | 25 ++-- TODO.md | 2 +- include/cstring/cstring.h | 19 ++- src/CMakeLists.txt | 9 +- src/cstring.core.c | 205 +------------------------- src/cstring.vector.c | 33 +---- src/internal.h | 103 +++++++++++++ src/win.c | 232 ++++++++++++++++++++++++++++++ test/performance/perf_harness.hpp | 1 - 13 files changed, 384 insertions(+), 255 deletions(-) create mode 100644 src/internal.h create mode 100644 src/win.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 758f139..5353d38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ on: - rc1 - rc2 - rc3 + - win32 pull_request: concurrency: diff --git a/CHANGES.md b/CHANGES.md index 8c06ada..d255912 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ # cstring - Changes +## 4.0.18 - 29th September 2026 + +* Gated Windows arena flags and WinAPI allocators on `_WIN32` in **cstring.h** and **cstring.core.c**, so 32- and 64-bit Windows builds expose them without a `WIN32` or `WIN64` define; + + ## 4.0.17 - 29th September 2026 * Added competitive performance tests for **cstring** and **cstring_vector** (`test.performance.cstring`, `test.performance.cstring_vector`); diff --git a/CMakeLists.txt b/CMakeLists.txt index 91ab814..82e1af4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ # Purpose: Top-level CMake lists file for cstring # # Created: 21st December 2023 -# Updated: 28th September 2026 +# Updated: 29th September 2026 # # ######################################################################## # diff --git a/Doxyfile b/Doxyfile index 7986df1..361b9a9 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.0.17 +PROJECT_NUMBER = 4.0.18 # 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 cfc07e4..4f27286 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,7 @@ | Date | News Item | Details | | ------------------- | -------------------------------- | ------- | | Available from [**cstring** project on GitHub](https://synesissoftware.com/cstring): | +| 29th September 2026 | Release of [cstring 4.0.18](https://github.com/synesissoftware/cstring/releases/tag/4.0.18) | Windows arena flags keyed on `_WIN32` | | 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 | | 6th September 2026 | Release of [cstring 4.0.15](https://github.com/synesissoftware/cstring/releases/tag/4.0.15) | Doxygen `\param`/`\retval` 76-rule | @@ -16,18 +17,18 @@ | 28th January 2024 | Release of cstring 4.0.9 | CMake C11/C++14, `CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY` | | 12th January 2024 | Release of cstring 4.0.8 | `cstring.vector`, `cstring_write()`, wide strings, CMake | | Available from **http://synesis.com.au/software/cstring**: | -| 20th February 2012 | Release of 3.6.2 | -| 24th January 2012 | Release of 3.6.1 | -| 22nd January 2012 | Release of 3.5.4 | -| 11th January 2010 | Release of 3.5.3 | -| 11th July 2009 | Release of 3.5.2 | -| 10th July 2009 | Release of 3.5.1 | -| 4th May 2008 | Release of 3.4.4 | -| 26th January 2007 | Release of 3.4.3 | -| 30th April 2006 | Release of 3.4.2 | -| 7th October 2005 | Release of 3.4.1 | -| 8th August 2005 | Release of 3.3.1 | -| 29th July 2005 | Release of 3.2.1 | +| 20th February 2012 | Release of 3.6.2 | | +| 24th January 2012 | Release of 3.6.1 | | +| 22nd January 2012 | Release of 3.5.4 | | +| 11th January 2010 | Release of 3.5.3 | | +| 11th July 2009 | Release of 3.5.2 | | +| 10th July 2009 | Release of 3.5.1 | | +| 4th May 2008 | Release of 3.4.4 | | +| 26th January 2007 | Release of 3.4.3 | | +| 30th April 2006 | Release of 3.4.2 | | +| 7th October 2005 | Release of 3.4.1 | | +| 8th August 2005 | Release of 3.3.1 | | +| 29th July 2005 | Release of 3.2.1 | | diff --git a/TODO.md b/TODO.md index 286ecd0..a6c3bf2 100644 --- a/TODO.md +++ b/TODO.md @@ -14,7 +14,7 @@ * [x] ~~~Scratch **libver** retained under **test/scratch**~~~ - ✅; * [x] ~~~Delete Visual Studio 98 files~~~ - ✅; * [x] ~~~Delete Visual Studio 2003+ files~~~ - ✅; -* [ ] discriminate on `_WIN32` in implementation (and maybe also in API); +* [x] ~~~discriminate on `_WIN32` in implementation (and maybe also in API)~~~ - ✅; * [ ] check `CSTRING_USE_WINAPI_`; * [ ] custom arena(s); diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index 804bb51..c63faca 100644 --- a/include/cstring/cstring.h +++ b/include/cstring/cstring.h @@ -54,9 +54,9 @@ #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION # define CSTRING_VER_CSTRING_H_CSTRING_MAJOR 3 -# define CSTRING_VER_CSTRING_H_CSTRING_MINOR 11 -# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 15 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 89 +# define CSTRING_VER_CSTRING_H_CSTRING_MINOR 12 +# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 1 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 90 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ @@ -102,6 +102,7 @@ # define CSTRING_VER_4_0_15 0x04000fff # define CSTRING_VER_4_0_16 0x040010ff # define CSTRING_VER_4_0_17 0x040011ff +# define CSTRING_VER_4_0_18 0x040012ff #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR @@ -122,7 +123,7 @@ #define CSTRING_VER_MAJOR 4 #define CSTRING_VER_MINOR 0 -#define CSTRING_VER_PATCH 17 +#define CSTRING_VER_PATCH 18 #define CSTRING_VER_ALPHABETA 0xFF #define CSTRING_VER \ @@ -330,16 +331,12 @@ typedef struct cstring_t cstring_t; /* arena */ #define CSTRING_F_ARENA_MASK (0x3700) -#define CSTRING_F_USE_REALLOC (0x0000) /*!< Uses C standard library function realloc() for all memory (de-)allocation */ -#if 0 -#elif 0 ||\ - defined(WIN32) ||\ - defined(WIN64) ||\ - 0 +#define CSTRING_F_USE_REALLOC (0x0000) /*!< Uses C standard library function realloc() (and related) for all memory (de-)allocation */ +#ifdef _WIN32 # define CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY (0x0100) /*!< Uses Win32 Global Memory API for all memory (de-)allocation (Windows only); arena parameter is ignored. */ # define CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY (0x0200) /*!< Uses Win32 Process Heap for all memory (de-)allocation (Windows only); arena parameter is ignored. */ # define CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY (0x0400) /*!< Uses COM Task Allocator for all memory (de-)allocation (Windows only); arena parameter is ignored. */ -#endif /* WIN32 || WIN64 */ +#endif /* Windows */ #if defined(CSTRING_USE_SYNESIS_APIS) # define CSTRING_F_USE_SYNESIS_HATOR (0x1000) /*!< Assumes that the arena variable is a Synesis Software memory allocator */ #endif /* CSTRING_USE_SYNESIS_APIS */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3c14f3c..d966f41 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,7 +5,7 @@ # Purpose: CMake lists file for cstring core library # # Created: ... -# Updated: 2nd August 2026 +# Updated: 29th September 2026 # # ######################################################################## # @@ -49,6 +49,13 @@ set(CORE_SRCS cstring.core.c ) +if(_BUILD_AS_WIN32) + + list(APPEND CORE_SRCS + win.c + ) +endif(_BUILD_AS_WIN32) + add_library(core ${CORE_SRCS} ) diff --git a/src/cstring.core.c b/src/cstring.core.c index 4ded455..00f77c3 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: 28th September 2026 + * Updated: 29th September 2026 * * Home: http://synesis.com.au/software/ * @@ -51,6 +51,7 @@ /* cstring header files */ #include +#include "internal.h" #ifndef CSTRING_INCL_CSTRING_INTERNAL_H_SAFESTR # include @@ -76,12 +77,6 @@ #define CSTRING_OFFSET_SIZE (16) -#if defined(WIN32) || \ - defined(WIN64) - -# define CSTRING_USE_WINAPI_ -#endif - /* ///////////////////////////////////////////////////////////////////////// * debugging @@ -90,22 +85,6 @@ #define CSTRING_ASSERT(expr) assert(expr) -/* ///////////////////////////////////////////////////////////////////////// - * 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 */ - - /* ///////////////////////////////////////////////////////////////////////// * character encoding */ @@ -147,6 +126,7 @@ strncpy_safe( #if defined(UNIX) || \ defined(unix) + /* This required, otherwise get name shadowing warning from Linux /usr/include/string.h */ # define index index_disambiguated_1 #endif @@ -269,12 +249,6 @@ cstring_memcpy_safe_( * allocation functions */ -#if defined(CSTRING_USE_WINAPI_) -static void* win32_global_realloc(void* pv, size_t cb); -static void* win32_processheap_realloc(void* pv, size_t cb); -static void* win32_comtask_realloc(void* pv, size_t cb); -#endif /* CSTRING_USE_WINAPI_ */ - static void* cstring_realloc_2_( @@ -327,7 +301,7 @@ cstring_realloc_2_( #endif /* _DEBUG */ pvNew = realloc(pv, cb); break; -#if defined(CSTRING_USE_WINAPI_) +#ifdef CSTRING_USE_WINAPI_ case CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY: pvNew = win32_global_realloc(pv, cb); @@ -1384,7 +1358,7 @@ cstring_swap( } -/* ///////////////////////////////////////////////////////////////////////// +/* ///////////////////////////////////////////////////////// * file functions */ @@ -1518,7 +1492,7 @@ cstring_write( } -/* ///////////////////////////////////////////////////////////////////////// +/* ///////////////////////////////////////////////////////// * search/replace functions */ @@ -1730,172 +1704,5 @@ cstring_replaceAll( } -/* ///////////////////////////////////////////////////////////////////////// - * Win32 functions - */ - -#if defined(CSTRING_USE_WINAPI_) - -# ifndef CSTRING_DOCUMENTATION_SKIP_SECTION -# if defined(__MWERKS__) -# define GMEM_FIXED 0 -# define GMEM_MOVEABLE 2 -# define WINAPI __stdcall -typedef int BOOL; -typedef void* HANDLE; -typedef void* HGLOBAL; -typedef void* HMODULE; -typedef void* HINSTANCE; -typedef long LONG; -__declspec(dllimport) long __stdcall InterlockedExchange(LONG volatile *, LONG); -__declspec(dllimport) HANDLE __stdcall GetProcessHeap(void); -__declspec(dllimport) HGLOBAL __stdcall GlobalAlloc(unsigned int, unsigned long); -__declspec(dllimport) HGLOBAL __stdcall GlobalFree(HGLOBAL); -__declspec(dllimport) HGLOBAL __stdcall GlobalReAlloc(HGLOBAL, unsigned long, unsigned int); -__declspec(dllimport) void* __stdcall HeapAlloc(HANDLE, unsigned long, unsigned int); -__declspec(dllimport) BOOL __stdcall HeapFree(HANDLE, unsigned long, void* ); -__declspec(dllimport) void* __stdcall HeapReAlloc(HANDLE, unsigned long, void*, unsigned int); -__declspec(dllimport) void __stdcall Sleep(unsigned long); -__declspec(dllimport) HINSTANCE __stdcall LoadLibraryA(char const* ); -__declspec(dllimport) BOOL __stdcall FreeLibrary(HMODULE ); -__declspec(dllimport) void* __stdcall GetProcAddress(HMODULE, char const*); -# else /* ? compiler */ -# include -# endif /* compiler */ -# endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ - -/* ////////////////////////////////////////////////////////////////////// */ - -static -void* -win32_global_realloc( - void* pv -, size_t cb -) -{ - /* Logic borrowed from implementation of SynesisWin::GlobalAtor class (in - * file MWAtors.h) from the Synesis Software Public Domain Source Code - * Library (http://synesis.com.au/software). - */ - if (NULL != pv) - { - if (0 == cb) - { - return (GlobalFree((HGLOBAL)pv), (void*)NULL); - } - else - { - return (void*)GlobalReAlloc((HGLOBAL)pv, cb, GMEM_MOVEABLE); - } - } - else - { - return (void*)GlobalAlloc(GMEM_FIXED, cb); - } -} - -static -void* -win32_processheap_realloc( - void* pv -, size_t cb -) -{ - /* Logic borrowed from implementation of SynesisWin::HeapAtor class (in - * file MWAtors.h) from the Synesis Software Public Domain Source Code - * Library (http://synesis.com.au/software). - */ - if (NULL != pv) - { - if (0 == cb) - { - return (HeapFree(GetProcessHeap(), 0, (HGLOBAL)pv), (void*)NULL); - } - else - { - return (void*)HeapReAlloc(GetProcessHeap(), 0, (HGLOBAL)pv, cb); - } - } - else - { - return (void*)HeapAlloc(GetProcessHeap(), 0, cb); - } -} - -static -void* -win32_comtask_realloc( - void* pv -, size_t cb -) -{ - typedef void* (WINAPI *PfnCoTaskMemRealloc)(void* , size_t ); - - static LONG s_cstring_PfnCoTaskMemRealloc_init = 0; - static LONG s_cstring_PfnCoTaskMemRealloc_spin = 0; - static HINSTANCE s_cstring_ole32_HINSTANCE = NULL; - static PfnCoTaskMemRealloc s_cstring_pfnCoTaskMemRealloc = NULL; - - if (NULL == pv) - { - for (; 0 != InterlockedExchange(&s_cstring_PfnCoTaskMemRealloc_spin, 1); Sleep(0)) - {} - - if (1 == ++s_cstring_PfnCoTaskMemRealloc_init) - { -#if defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-function-type" -#endif /* __GNUC__ */ - - s_cstring_ole32_HINSTANCE = LoadLibraryA("OLE32"); - s_cstring_pfnCoTaskMemRealloc = (PfnCoTaskMemRealloc)GetProcAddress(s_cstring_ole32_HINSTANCE, "CoTaskMemRealloc"); - -#if defined(__GNUC__) -# pragma GCC diagnostic pop -#endif /* __GNUC__ */ - - if (NULL == s_cstring_ole32_HINSTANCE || - NULL == s_cstring_pfnCoTaskMemRealloc) - { - --s_cstring_PfnCoTaskMemRealloc_init; - } - } - - InterlockedExchange(&s_cstring_PfnCoTaskMemRealloc_spin, 0); - } - - pv = (NULL != s_cstring_pfnCoTaskMemRealloc) ? s_cstring_pfnCoTaskMemRealloc(pv, cb) : NULL; - - if (0 == cb) - { - for (; 0 != InterlockedExchange(&s_cstring_PfnCoTaskMemRealloc_spin, 1); Sleep(0)) - {} - - if (0 == --s_cstring_PfnCoTaskMemRealloc_init) - { - FreeLibrary(s_cstring_ole32_HINSTANCE); - } - - InterlockedExchange(&s_cstring_PfnCoTaskMemRealloc_spin, 0); - } - - return pv; -} - -/* ////////////////////////////////////////////////////////////////////// */ - -#endif /* CSTRING_USE_WINAPI_ */ - - -/* ///////////////////////////////////////////////////////////////////////// - * compiler warnings - */ - -#if defined(_MSC_VER) && \ - _MSC_VER >= 1200 -# pragma warning(pop) -#endif /* compiler */ - /* ///////////////////////////// end of file //////////////////////////// */ diff --git a/src/cstring.vector.c b/src/cstring.vector.c index 65bb9ec..59152c8 100644 --- a/src/cstring.vector.c +++ b/src/cstring.vector.c @@ -4,7 +4,7 @@ * Purpose: The implementation of the cstring.vector API * * Created: 16th June 1994 - * Updated: 23rd February 2025 + * Updated: 29th September 2026 * * Home: http://synesis.com.au/software/ * @@ -78,27 +78,12 @@ #define CSTRING_VECTOR_ASSERT(expr) assert(expr) -/* ///////////////////////////////////////////////////////////////////////// - * 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 */ #if defined(__BORLANDC__) + char* strncpy_safe(char* dest, char const* src, size_t len) { if (0 != len) @@ -108,13 +93,14 @@ char* strncpy_safe(char* dest, char const* src, size_t len) return dest; } -# define strncpy strncpy_safe +# define strncpy strncpy_safe #endif /* compiler */ #if defined(UNIX) || \ defined(unix) + /* This required, otherwise get name shadowing warning from Linux /usr/include/string.h */ -# define index index_disambiguated_1 +# define index index_disambiguated_1 #endif @@ -435,14 +421,5 @@ cstring_vector_readLines( } -/* ///////////////////////////////////////////////////////////////////////// - * compiler warnings - */ - -#if defined(_MSC_VER) && \ - _MSC_VER >= 1200 -# pragma warning(pop) -#endif /* compiler */ - /* ///////////////////////////// end of file //////////////////////////// */ diff --git a/src/internal.h b/src/internal.h new file mode 100644 index 0000000..c4f1c69 --- /dev/null +++ b/src/internal.h @@ -0,0 +1,103 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: internal.h + * + * Purpose: Private declarations for the cstring implementation units. + * + * Created: 28th September 2026 + * Updated: 29th September 2026 + * + * Home: http://synesis.com.au/software/ + * + * Copyright (c) 2019-2026, Matthew Wilson and Synesis Information Systems + * Copyright (c) 1994-2019, Matthew Wilson and Synesis Software + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the names of Matthew Wilson and Synesis Information Systems nor + * the names of any contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ////////////////////////////////////////////////////////////////////// */ + + +/** \file internal.h Private declarations for the cstring implementation units + */ + +#ifndef CSTRING_INCL_SRC_H_INTERNAL +#define CSTRING_INCL_SRC_H_INTERNAL + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * constants & definitions + */ + +#if defined(WIN32) || \ + defined(WIN64) + +# define CSTRING_USE_WINAPI_ +#endif /* WIN32 || WIN64 */ + + +/* ///////////////////////////////////////////////////////////////////////// + * Windows arena allocators + */ + +#if defined(CSTRING_USE_WINAPI_) + +CSTRING_EXTERN_C +void* +win32_global_realloc( + void* pv +, size_t cb +); + +CSTRING_EXTERN_C +void* +win32_processheap_realloc( + void* pv +, size_t cb +); + +CSTRING_EXTERN_C +void* +win32_comtask_realloc( + void* pv +, size_t cb +); +#endif /* CSTRING_USE_WINAPI_ */ + + +/* ////////////////////////////////////////////////////////////////////// */ + +#endif /* CSTRING_INCL_SRC_H_INTERNAL */ + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/src/win.c b/src/win.c new file mode 100644 index 0000000..48f7621 --- /dev/null +++ b/src/win.c @@ -0,0 +1,232 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: win.c + * + * Purpose: Windows memory arenas for the cstring core API. + * + * Created: 28th September 2026 + * Updated: 29th September 2026 + * + * Home: http://synesis.com.au/software/ + * + * Copyright (c) 2019-2026, Matthew Wilson and Synesis Information Systems + * Copyright (c) 1994-2019, Matthew Wilson and Synesis Software + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the names of Matthew Wilson and Synesis Information Systems nor + * the names of any contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ////////////////////////////////////////////////////////////////////// */ + + +/** \file win.c Windows memory arenas for the cstring core API + */ + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include "internal.h" + + +/* ///////////////////////////////////////////////////////////////////////// + * 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 */ + + +/* ///////////////////////////////////////////////////////////////////////// + * Win32 functions + */ + +#ifndef CSTRING_DOCUMENTATION_SKIP_SECTION +# if defined(__MWERKS__) +# define GMEM_FIXED 0 +# define GMEM_MOVEABLE 2 +# define WINAPI __stdcall +typedef int BOOL; +typedef void* HANDLE; +typedef void* HGLOBAL; +typedef void* HMODULE; +typedef void* HINSTANCE; +typedef long LONG; +__declspec(dllimport) long __stdcall InterlockedExchange(LONG volatile *, LONG); +__declspec(dllimport) HANDLE __stdcall GetProcessHeap(void); +__declspec(dllimport) HGLOBAL __stdcall GlobalAlloc(unsigned int, unsigned long); +__declspec(dllimport) HGLOBAL __stdcall GlobalFree(HGLOBAL); +__declspec(dllimport) HGLOBAL __stdcall GlobalReAlloc(HGLOBAL, unsigned long, unsigned int); +__declspec(dllimport) void* __stdcall HeapAlloc(HANDLE, unsigned long, unsigned int); +__declspec(dllimport) BOOL __stdcall HeapFree(HANDLE, unsigned long, void* ); +__declspec(dllimport) void* __stdcall HeapReAlloc(HANDLE, unsigned long, void*, unsigned int); +__declspec(dllimport) void __stdcall Sleep(unsigned long); +__declspec(dllimport) HINSTANCE __stdcall LoadLibraryA(char const* ); +__declspec(dllimport) BOOL __stdcall FreeLibrary(HMODULE ); +__declspec(dllimport) void* __stdcall GetProcAddress(HMODULE, char const*); +# else /* ? compiler */ + +# include +# endif /* compiler */ +#endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ + +/* ////////////////////////////////////////////////////////////////////// */ + +void* +win32_global_realloc( + void* pv +, size_t cb +) +{ + /* Logic borrowed from implementation of SynesisWin::GlobalAtor class + * (in file MWAtors.h) from the Synesis Software Public Domain Source + * Code Library (http://synesis.com.au/software). + */ + if (NULL != pv) + { + if (0 == cb) + { + return (GlobalFree((HGLOBAL)pv), (void*)NULL); + } + else + { + return (void*)GlobalReAlloc((HGLOBAL)pv, cb, GMEM_MOVEABLE); + } + } + else + { + return (void*)GlobalAlloc(GMEM_FIXED, cb); + } +} + +void* +win32_processheap_realloc( + void* pv +, size_t cb +) +{ + /* Logic borrowed from implementation of SynesisWin::HeapAtor class (in + * file MWAtors.h) from the Synesis Software Public Domain Source Code + * Library (http://synesis.com.au/software). + */ + if (NULL != pv) + { + if (0 == cb) + { + return (HeapFree(GetProcessHeap(), 0, (HGLOBAL)pv), (void*)NULL); + } + else + { + return (void*)HeapReAlloc(GetProcessHeap(), 0, (HGLOBAL)pv, cb); + } + } + else + { + return (void*)HeapAlloc(GetProcessHeap(), 0, cb); + } +} + +void* +win32_comtask_realloc( + void* pv +, size_t cb +) +{ + typedef void* (WINAPI *PfnCoTaskMemRealloc)(void* , size_t ); + + static LONG s_cstring_PfnCoTaskMemRealloc_init = 0; + static LONG s_cstring_PfnCoTaskMemRealloc_spin = 0; + static HINSTANCE s_cstring_ole32_HINSTANCE = NULL; + static PfnCoTaskMemRealloc s_cstring_pfnCoTaskMemRealloc = NULL; + + if (NULL == pv) + { + for (; 0 != InterlockedExchange(&s_cstring_PfnCoTaskMemRealloc_spin, 1); Sleep(0)) + {} + + if (1 == ++s_cstring_PfnCoTaskMemRealloc_init) + { +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-function-type" +#endif /* __GNUC__ */ + + s_cstring_ole32_HINSTANCE = LoadLibraryA("OLE32"); + s_cstring_pfnCoTaskMemRealloc = (PfnCoTaskMemRealloc)GetProcAddress(s_cstring_ole32_HINSTANCE, "CoTaskMemRealloc"); + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif /* __GNUC__ */ + + if (NULL == s_cstring_ole32_HINSTANCE || + NULL == s_cstring_pfnCoTaskMemRealloc) + { + --s_cstring_PfnCoTaskMemRealloc_init; + } + } + + InterlockedExchange(&s_cstring_PfnCoTaskMemRealloc_spin, 0); + } + + pv = (NULL != s_cstring_pfnCoTaskMemRealloc) ? s_cstring_pfnCoTaskMemRealloc(pv, cb) : NULL; + + if (0 == cb) + { + for (; 0 != InterlockedExchange(&s_cstring_PfnCoTaskMemRealloc_spin, 1); Sleep(0)) + {} + + if (0 == --s_cstring_PfnCoTaskMemRealloc_init) + { + FreeLibrary(s_cstring_ole32_HINSTANCE); + } + + InterlockedExchange(&s_cstring_PfnCoTaskMemRealloc_spin, 0); + } + + return pv; +} + +/* ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * compiler warnings + */ + +#if defined(_MSC_VER) && \ + _MSC_VER >= 1200 +# pragma warning(pop) +#endif /* compiler */ + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/performance/perf_harness.hpp b/test/performance/perf_harness.hpp index 60bed2f..fbca686 100644 --- a/test/performance/perf_harness.hpp +++ b/test/performance/perf_harness.hpp @@ -23,7 +23,6 @@ #include #include - #ifdef HAS_P99 # include #endif From def3924ce51787edde375a353d9afc16a0f460d4 Mon Sep 17 00:00:00 2001 From: synesissoftware Date: Tue, 29 Sep 2026 11:05:17 +1000 Subject: [PATCH 71/88] test(cstring): time Windows global, process-heap, and COM task arenas (#27) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * general boilerplate / project-support improvements (#16) * squash-commit * 4.0.14 * fix * 4.0.15 (#20) * Updated all `\param` and `\retval` Doxygen documentation comments in `cstring.h`, `cstring.vector.h`, and `cstring.core.c` to obey the 76-rule and terminate with a semicolon; * Bumped version to 4.0.15 in `cstring.h`, `Doxyfile`, `CHANGES.md`, and `NEWS.md`; * Updated `Updated:` header banners to 6th September 2026; * markdown * chore(c-cpp): union files.associations from ~/dev vscode settings Give the 28 in-wave C/C++ projects (and gold **c** / **cxx** / **c_cxx** templates) a sorted **files.associations** union of all associations found under **~/dev**; * .gitattributes * .vscode/settings.json * .vimrc * CHANGES.md * chore(): use computed PREFIX_VER with documented PATCH Align library version macros to c-cpp-standards. Define MAJOR, MINOR, PATCH, and ALPHABETA; compute PREFIX_VER by shifts; keep REVISION as a skip-section alias of PATCH (target at column 60); retain named historical VER_* constants only for comparison and documentation. * test(): ship scratch libver version reporter Add or normalise test/scratch/libver to print the hosting PREFIX_VER as “Name: vM.m.p.ab (PREFIX_VER = 0x........)” and, when sibling public headers are included, an “efferent dependencies:” block. * test(): rename scratch libver to versions Move test/scratch/libver to test/scratch/versions and retarget the executable as versions so C/C++ reporters match the Phase 3 freeze. * test: add canonical scratch versions reporter Add a Phase 3 gold version printer under test/scratch/versions, wire it from the scratch parent list, and use CMake target/argv0 versions; * .gitattributes * fix(c-cpp): quote Dir resolution in CMake helper scripts Use Dir=$(cd "$(dirname "$ScriptPath")" && pwd) so paths with spaces resolve reliably; apply the same one-line fix across in-wave helper scripts (prepare/build/clean/ctest/remove and run_all_*). * chore(scratch test): **versions** => **test.scratch.version** * doc(README.md): adding afferent dependencies (**errni**, **rstrip**) * feat(cstring): colour prepare_cmake and unify cmake configure Add SisClr_* status colour (NO_COLOR, AlwaysUseColours, TTY gate), --build-shared-libs, and a single cmake invoke with CMakeGeneratorArgs so every -D is always passed; tidy prologue groups, quoting, and help text. * feat(cstring): force colours via env and -A in prepare_cmake Initialise AlwaysUseColours from SIS_CMAKE_ALWAYS_USE_COLOURS / SIS_ALWAYS_USE_COLOURS; let -A/--always-use-colours override NO_COLOR (pre-scan args so help and errors are coloured). * refactor(cstring): drive prepare builds with cmake --build Drop MSYSTEM→MinGW and MakeCmd; honour SIS_CMAKE_MINGW via `sis_cmake_is_truey()`; add sis_cmake_build for -m; configure in-place with -S/-B and || exit 1; tighten --mingw/--msvc-mt/--run-make help. * refactor(cstring): drive build_cmake via cmake --build Replace MakeCmd/Makefile gating with sis_cmake_build; require a configured CMakeCache; add SisClr_* colour and -A to match prepare_cmake.sh. * refactor(cstring): drive clean_cmake via cmake --build --target clean Replace MakeCmd/Makefile gating with sis_cmake_build clean; require a configured CMakeCache; add SisClr_* colour and -A to match build_cmake.sh. * refactor(cstring): align remove_cmake_artefacts with helper dialect Add SisClr_* colour and -A; quote inventory loops; recognise Windows_NT; remove artefacts without cd into the build directory. * refactor(cstring): drive ctest_cmake via cmake --build then ctest Replace MakeCmd with sis_cmake_build; require CMakeCache; fail -M when unconfigured; add --output-on-failure, SisClr_* colour, and -A. * refactor(cstring): unit/component runners use sis_cmake_build dialect Make run_all_unit_tests.sh unit-only; align both category runners with SisClr_*/-A, cmake --build, and gold basename discovery (.exe, print0). * fix(cstring): accept category-only flags; fix CI component runner Treat --unit-only / --component-only as no-ops on the matching category scripts; point ci-cell component tests at run_all_component_tests.sh. * refactor(cstring): drive run_all_examples via cmake --build dialect Replace MakeCmd with sis_cmake_build; add SisClr_*/-A; discover example* executables with print0/.exe and optional --verbosity. * fix(cstring): gate cstring_vector no-arg demo on SIS_EXAMPLE_SMOKE Fail without input for humans; when SIS_EXAMPLE_SMOKE is set, use an in-process tmpfile sample. run_all_examples exports the variable for CI. * refactor(cstring): drive run_all_scratch via cmake --build dialect Replace MakeCmd with sis_cmake_build; run scratch-only (drop performance); add SisClr_*/-A; discover test_scratch* / test.scratch.* via print0/.exe. * refactor(cstring): native CMD runners for unit, component, examples Add run_all_component_tests.cmd and run_all_examples.cmd; make run_all_unit_tests.cmd unit-only with ProjectName, benign --unit-only, and stop on first failing .exe (no Bash). * feat(cstring): add run_all_automated_tests aggregate (.sh/.cmd) Add unit-then-component aggregators with mutually exclusive --unit-only / --component-only; Bash forwards other flags, CMD CALLs native category runners (no Bash). * refactor(cstring): drive run_all_performance via cmake --build dialect Align with scratch helpers: SisClr_*/-A, sis_cmake_build, and test_performance* / test.performance.* discovery (print0/.exe). * style(cstring): SisClr polish on run_all_automated_tests.sh Add SisClr_*/-A (forwarded), coloured status/mutex messages; keep unit-then-component aggregate behaviour. * ci(cstring): exercise prepare/build/clean helpers in CI Split no-cpp into prepare (no -m) then build_cmake.sh; add install-smoke prepare-clean-build-clean-build on a dedicated SIS_CMAKE_BUILD_DIR tree. * ci(cstring): dogfood native run_all *.cmd on Windows cells Invoke examples/unit/component/scratch via cmd.exe on Windows; keep Bash runners on Unix; add run_all_scratch_tests.cmd; PATH MinGW from msys2. * ci(cstring): dogfood prepare/build in cells; order test jobs - Replace raw cmake configure/build with prepare_cmake.sh and build_cmake.sh; - add performance job/.cmd; - chain unit→component→performance→scratch→examples. * ci(cstring): invoke ctest, remove, and automated helpers Run ctest_cmake.sh after each cell build; extend install-smoke through remove and re-prepare/build; use run_all_automated_tests.sh in no-cpp. * fix(cstring): harden prepare empty-args and multi-config ctest Avoid bash 3.2 nounset failure on empty PREPARE_ARGS in ci-cell, and pass ctest -C from SIS_CMAKE_CONFIG when CMAKE_CONFIGURATION_TYPES is present so Visual Studio cells can run. * ci(cstring): dogfood run_all_automated_tests.cmd on Windows Drive Windows unit and component cell steps through the aggregate .cmd with --unit-only / --component-only so the orchestrator and its CALL targets are exercised without duplicating the suite. * ci(cstring): collapse cell suites into one post-build test job Replace the serial unit/component/performance/scratch/examples jobs with a single Tests job of sequential helper steps so each matrix cell pays one checkout, MinGW setup, and artifact download instead of five. * docs(cstring): target 4.0.16 release for 23 September 2026 Align CHANGES/NEWS to today, bump Doxyfile PROJECT_NUMBER, and refresh cstring.h Updated/edit counters; package CSTRING_VER_* already at 4.0.16. * tidying * Perf tests (#22) * feat(cstring): add competitive performance tests for 4.0.17 Add test.performance.cstring and test.performance.cstring_vector with std::string / vector and hand-rolled baselines; optional p99 percentiles (and file_lines vs cstring_vector_readLines when linked); --no-p99 / NO_P99; --gap-groups / SIS_PERFTESTS_GROUPGAPS; CSTRING_EXTERN_C on the cstring.vector API. * test(cstring): time ifstream getline in the file-lines suite Add an `std::ifstream` + `std::getline` row to `file_read_all_lines`, so `cstring_vector_readLines` has an owning standard-library baseline beside `platformstl::file_lines`. * test(cstring): pair borrowed_fixed_assign with std::string Add the semantic std::string::assign baseline beside the existing borrowed/fixed path; fix borrowed capacity to n+1. * fix(cstring): restore legacy-compatible memory calls in performance tests Define _CRT_SECURE_NO_WARNINGS for MSVC before CRT headers are included; use global ::memcpy and ::memmove names to preserve early C/C++ compatibility; * fix(cstring): use SIS target warning suppression mechanism Register MSVC warning 4996 through X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED for each affected performance target; remove broad and direct warning suppression from the top-level build; * fix(cstring): correct vector insertion range handling Move the full tail when inserting before the vector end; construct inserted strings and clean up failures at the insertion position; prevent prepend benchmarks from corrupting vector storage; * fix * squash-commit --------- Co-authored-by: Matt Wilson * More component tests (#23) * test(cstring): rename component readline/writeline cases descriptively Adopt TEST__ names for cstring_readline and cstring_writeline/cstring_write component cases; drop empty writeline stubs; refresh Updated banners and prefer char* argv[] in main. * test(cstring): adopt terse xTests API in component suites Switch readline/writeline component assertions to TEST_ENUM_EQ, TEST_INT_EQ, TEST_MS_EQ, TEST_PASSED, and REQUIRE; mark vector APIs CSTRING_EXTERN_C for C++ linkage; wire test.component.cstring_vector_readLines into the component CMake tree. * test(cstring): expand cstring_readline component coverage Fill empty readline cases for empty file, CRLF/mixed EOL, final EOL, consecutive empties, long and many short lines, and cstring reuse; add binary fixture helpers; correct the vector header File: banner. * test(cstring): add cstring_vector_readLines component suite Add test.component.cstring_vector_readLines covering empty file, EOL variants, empty-at-EOF omission, append-onto-existing, long and many short lines; use the xTests terse API. * ci * test(cstring): finish terse xTests API in readline coverage Convert the remaining verbose XTESTS_* assertions in the expanded cstring_readline cases to REQUIRE, TEST_ENUM_EQ, TEST_INT_EQ, and TEST_MS_EQ. * fix * Flags fixes (#24) * tidy * chore: replacing backwards-compatibility flags uses with the replacements * fix(flags): correcting (currently unused) flag(s) * fix(cstring): drop unused bits from the cstring arena mask Narrow CSTRING_F_ARENA_MASK from 0xff00 to 0x3700, the union of the defined arena selectors, so unused high bits are not treated as an arena. Retire the unsupported/unused custom-arena calling-convention macros, clearing the old stdcall value 0x4000 that sat inside the mask. * squash-commit * version * TODO.md * fix(cstring): gate Windows arenas on `_WIN32` for 4.1.0 Define the Windows arena flags and compile the WinAPI allocators when the compiler predefines `_WIN32`, rather than when `WIN32` or `WIN64` is set; record the 28th September 2026 release in **CHANGES.md**, **NEWS.md**, **Doxyfile**, and **TODO.md**. * fix * refactor(cstring): move Windows arena allocators into win.c Move the Win32 Global, process-heap, and COM task reallocators out of **cstring.core.c** into **src/win.c**, declared from **src/internal.h**; - Windows builds compile **win.c**, which does not re-check **CSTRING_USE_WINAPI_**; core still dispatches those arenas; - **dllmain.c** and **mmap.c** are unchanged. * clean * test(cstring): time Windows global, process-heap, and COM task arenas Time the three Win32 arenas beside the realloc baseline: - `cstring_win_global` uses `CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY`; - `cstring_win_processheap` uses `CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY`; - `cstring_win_comtask` uses `CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY`; * refactor(cstring): static-link COM task arena via ole32 Replace LoadLibrary/GetProcAddress of CoTaskMemRealloc with direct CoTaskMemAlloc/Free/Realloc calls in win32_comtask_realloc(); link **ole32** from the Windows core target. --------- Co-authored-by: Matt Wilson <152443343+mwsis@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- CHANGES.md | 3 + NEWS.md | 2 +- include/cstring/cstring.h | 2 +- src/CMakeLists.txt | 9 + src/win.c | 98 ++----- test/performance/perf_harness.hpp | 12 +- .../test.performance.cstring/main.cpp | 253 +++++++++++++++++- 8 files changed, 286 insertions(+), 95 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5353d38..a6e40f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ on: - rc1 - rc2 - rc3 - - win32 + - perf-tests-win pull_request: concurrency: diff --git a/CHANGES.md b/CHANGES.md index d255912..ad45af0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,9 @@ ## 4.0.18 - 29th September 2026 * Gated Windows arena flags and WinAPI allocators on `_WIN32` in **cstring.h** and **cstring.core.c**, so 32- and 64-bit Windows builds expose them without a `WIN32` or `WIN64` define; +* Moved Windows Global / process-heap / COM task allocators from **cstring.core.c** into **src/win.c** (declared from **src/internal.h**); +* Implemented `win32_comtask_realloc()` via statically linked `CoTaskMemAlloc` / `CoTaskMemFree` / `CoTaskMemRealloc` (**ole32**), replacing the former `LoadLibrary` / `GetProcAddress` path; +* Timed Windows Global, process-heap, and COM task arenas in **test.performance.cstring** (`cstring_win_global`, `cstring_win_processheap`, `cstring_win_comtask`) against the realloc `cstring` baseline; ## 4.0.17 - 29th September 2026 diff --git a/NEWS.md b/NEWS.md index 4f27286..b1106c3 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): | -| 29th September 2026 | Release of [cstring 4.0.18](https://github.com/synesissoftware/cstring/releases/tag/4.0.18) | Windows arena flags keyed on `_WIN32` | +| 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 | | 6th September 2026 | Release of [cstring 4.0.15](https://github.com/synesissoftware/cstring/releases/tag/4.0.15) | Doxygen `\param`/`\retval` 76-rule | diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index c63faca..edd5026 100644 --- a/include/cstring/cstring.h +++ b/include/cstring/cstring.h @@ -837,7 +837,7 @@ cstring_swap( * ferror()); * \retval CSTRING_RC_SUCCESS A line terminated by a carriage return was * read in; - * \retval CSTRING_RC_EOF A line terminted by the end-of-file 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(); * * \pre (NULL != pcs) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d966f41..5200656 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -60,6 +60,15 @@ add_library(core ${CORE_SRCS} ) +if(_BUILD_AS_WIN32) + + # win.c uses the COM Task Allocator (CoTaskMem*) from ole32 + target_link_libraries(core + PRIVATE + ole32 + ) +endif(_BUILD_AS_WIN32) + # specify the primary public headers for the library, which controls whether # they'll be installed # diff --git a/src/win.c b/src/win.c index 48f7621..2490256 100644 --- a/src/win.c +++ b/src/win.c @@ -51,22 +51,6 @@ #include "internal.h" -/* ///////////////////////////////////////////////////////////////////////// - * 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 */ - - /* ///////////////////////////////////////////////////////////////////////// * Win32 functions */ @@ -75,14 +59,9 @@ # if defined(__MWERKS__) # define GMEM_FIXED 0 # define GMEM_MOVEABLE 2 -# define WINAPI __stdcall typedef int BOOL; typedef void* HANDLE; typedef void* HGLOBAL; -typedef void* HMODULE; -typedef void* HINSTANCE; -typedef long LONG; -__declspec(dllimport) long __stdcall InterlockedExchange(LONG volatile *, LONG); __declspec(dllimport) HANDLE __stdcall GetProcessHeap(void); __declspec(dllimport) HGLOBAL __stdcall GlobalAlloc(unsigned int, unsigned long); __declspec(dllimport) HGLOBAL __stdcall GlobalFree(HGLOBAL); @@ -90,13 +69,13 @@ __declspec(dllimport) HGLOBAL __stdcall GlobalReAlloc(HGLOBAL, unsigned __declspec(dllimport) void* __stdcall HeapAlloc(HANDLE, unsigned long, unsigned int); __declspec(dllimport) BOOL __stdcall HeapFree(HANDLE, unsigned long, void* ); __declspec(dllimport) void* __stdcall HeapReAlloc(HANDLE, unsigned long, void*, unsigned int); -__declspec(dllimport) void __stdcall Sleep(unsigned long); -__declspec(dllimport) HINSTANCE __stdcall LoadLibraryA(char const* ); -__declspec(dllimport) BOOL __stdcall FreeLibrary(HMODULE ); -__declspec(dllimport) void* __stdcall GetProcAddress(HMODULE, char const*); +__declspec(dllimport) void* __stdcall CoTaskMemAlloc(unsigned long); +__declspec(dllimport) void __stdcall CoTaskMemFree(void* ); +__declspec(dllimport) void* __stdcall CoTaskMemRealloc(void* , unsigned long); # else /* ? compiler */ # include +# include # endif /* compiler */ #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ @@ -162,71 +141,26 @@ win32_comtask_realloc( , size_t cb ) { - typedef void* (WINAPI *PfnCoTaskMemRealloc)(void* , size_t ); - - static LONG s_cstring_PfnCoTaskMemRealloc_init = 0; - static LONG s_cstring_PfnCoTaskMemRealloc_spin = 0; - static HINSTANCE s_cstring_ole32_HINSTANCE = NULL; - static PfnCoTaskMemRealloc s_cstring_pfnCoTaskMemRealloc = NULL; - - if (NULL == pv) + /* Direct (statically linked) COM Task Allocator API — CoTaskMemAlloc / + * CoTaskMemFree / CoTaskMemRealloc from ole32. + */ + if (NULL != pv) { - for (; 0 != InterlockedExchange(&s_cstring_PfnCoTaskMemRealloc_spin, 1); Sleep(0)) - {} - - if (1 == ++s_cstring_PfnCoTaskMemRealloc_init) + if (0 == cb) { -#if defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-function-type" -#endif /* __GNUC__ */ - - s_cstring_ole32_HINSTANCE = LoadLibraryA("OLE32"); - s_cstring_pfnCoTaskMemRealloc = (PfnCoTaskMemRealloc)GetProcAddress(s_cstring_ole32_HINSTANCE, "CoTaskMemRealloc"); - -#if defined(__GNUC__) -# pragma GCC diagnostic pop -#endif /* __GNUC__ */ - - if (NULL == s_cstring_ole32_HINSTANCE || - NULL == s_cstring_pfnCoTaskMemRealloc) - { - --s_cstring_PfnCoTaskMemRealloc_init; - } + return (CoTaskMemFree(pv), (void*)NULL); } - - InterlockedExchange(&s_cstring_PfnCoTaskMemRealloc_spin, 0); - } - - pv = (NULL != s_cstring_pfnCoTaskMemRealloc) ? s_cstring_pfnCoTaskMemRealloc(pv, cb) : NULL; - - if (0 == cb) - { - for (; 0 != InterlockedExchange(&s_cstring_PfnCoTaskMemRealloc_spin, 1); Sleep(0)) - {} - - if (0 == --s_cstring_PfnCoTaskMemRealloc_init) + else { - FreeLibrary(s_cstring_ole32_HINSTANCE); + return CoTaskMemRealloc(pv, cb); } - - InterlockedExchange(&s_cstring_PfnCoTaskMemRealloc_spin, 0); } - - return pv; + else + { + return CoTaskMemAlloc(cb); + } } -/* ////////////////////////////////////////////////////////////////////// */ - - -/* ///////////////////////////////////////////////////////////////////////// - * compiler warnings - */ - -#if defined(_MSC_VER) && \ - _MSC_VER >= 1200 -# pragma warning(pop) -#endif /* compiler */ /* ///////////////////////////// end of file //////////////////////////// */ diff --git a/test/performance/perf_harness.hpp b/test/performance/perf_harness.hpp index fbca686..5b91e6f 100644 --- a/test/performance/perf_harness.hpp +++ b/test/performance/perf_harness.hpp @@ -424,9 +424,9 @@ ratio_or_dash( struct raw_string { - char* ptr; - size_t len; - size_t capacity; + char* ptr; + size_t len; + size_t capacity; }; inline @@ -576,9 +576,9 @@ raw_copy( struct raw_string_vector { - char** ptr; - size_t len; - size_t capacity; + char** ptr; + size_t len; + size_t capacity; }; inline diff --git a/test/performance/test.performance.cstring/main.cpp b/test/performance/test.performance.cstring/main.cpp index 0acfea5..f7b8c48 100644 --- a/test/performance/test.performance.cstring/main.cpp +++ b/test/performance/test.performance.cstring/main.cpp @@ -2,11 +2,12 @@ * File: test/performance/test.performance.cstring/main.cpp * * Purpose: Competitive performance tests for cstring vs std::string and a - * hand-rolled realloc/memcpy floor. When p99 is available, also - * report per-iteration percentiles. + * hand-rolled realloc/memcpy floor. On Windows, the same scenarios + * also time the Global, process-heap, and COM task arenas. When + * p99 is available, also report per-iteration percentiles. * * Created: 23rd September 2026 - * Updated: 23rd September 2026 + * Updated: 29th September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -39,6 +40,25 @@ char const* const IMPL_RAW = "raw_realloc"; char const* const IMPL_BORROWED = "cstring_borrowed"; char const* const IMPL_FIXEDBUF = "fixed_char_buf"; +#ifdef _WIN32 + +/* Realloc row is the ratio baseline. These arenas follow unit-test order + * (global, process heap, COM task), which is also flag-value order. + */ +struct cstring_arena +{ + char const* impl; + cstring_flags_t flags; +}; + +cstring_arena const WINDOWS_CSTRING_ARENAS[] = +{ + { "cstring_win_global", CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY }, + { "cstring_win_processheap", CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY }, + { "cstring_win_comtask", CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY }, +}; +#endif /* _WIN32 */ + std::size_t const SIZES[] = { 16u, @@ -152,6 +172,59 @@ emit_row( #endif /* HAS_P99 */ ); } + +/* Windows rows share the scenario's realloc cstring total as the ratio + * baseline. createEx / createLenEx selects the arena. Flags on a live + * instance must not be written by the caller. + */ +template +void +emit_windows_cstring_arenas( + char const* scenario +, std::size_t size +, std::size_t num_iterations +, std::size_t num_warm_loops +, std::size_t num_actions +, interval_t baseline_ns +, F body +) +{ +#ifdef _WIN32 + + for (std::size_t i = 0; STLSOFT_NUM_ELEMENTS(WINDOWS_CSTRING_ARENAS) != i; ++i) + { + cstring_flags_t const flags = WINDOWS_CSTRING_ARENAS[i].flags; + + run_result const r = time_iterations( + num_iterations + , num_warm_loops + , [&body, flags]() -> std::uint64_t + { + return body(flags); + } + ); + + emit_row( + scenario + , size + , WINDOWS_CSTRING_ARENAS[i].impl + , num_iterations + , num_actions + , r + , baseline_ns + ); + } +#else /* ? _WIN32 */ + + ((void)scenario); + ((void)size); + ((void)num_iterations); + ((void)num_warm_loops); + ((void)num_actions); + ((void)baseline_ns); + ((void)body); +#endif /* _WIN32 */ +} } // anonymous namespace @@ -190,6 +263,26 @@ scenario_create_destroy_empty( }); emit_row("create_destroy_empty", 0, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + emit_windows_cstring_arenas( + "create_destroy_empty" + , 0 + , num_iterations + , num_warm_loops + , 1 + , cs.tm_ns + , [](cstring_flags_t flags) -> std::uint64_t + { + cstring_t s = cstring_t_DEFAULT; + + cstring_createEx(&s, "", flags, NULL, 0); + + std::uint64_t const a = s.len; + + cstring_destroy(&s); + + return a; + } + ); emit_row("create_destroy_empty", 0, IMPL_STD, num_iterations, 1, st, cs.tm_ns); emit_row("create_destroy_empty", 0, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); } @@ -227,6 +320,28 @@ scenario_create_destroy_len( }); emit_row("create_destroy_len", n, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + emit_windows_cstring_arenas( + "create_destroy_len" + , n + , num_iterations + , num_warm_loops + , 1 + , cs.tm_ns + , [p, n](cstring_flags_t flags) -> std::uint64_t + { + cstring_t s = cstring_t_DEFAULT; + + cstring_createLenEx(&s, p, n, flags, NULL, 0); + + std::uint64_t const a = + s.len + (NULL != s.ptr ? static_cast(s.ptr[0]) : 0u) + ; + + cstring_destroy(&s); + + return a; + } + ); emit_row("create_destroy_len", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); emit_row("create_destroy_len", n, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); } @@ -266,6 +381,27 @@ scenario_assign_len_grow( }); emit_row("assign_len_grow", n, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + emit_windows_cstring_arenas( + "assign_len_grow" + , n + , num_iterations + , num_warm_loops + , 1 + , cs.tm_ns + , [p, n](cstring_flags_t flags) -> std::uint64_t + { + cstring_t s = cstring_t_DEFAULT; + + cstring_createEx(&s, "", flags, NULL, 0); + cstring_assignLen(&s, p, n); + + std::uint64_t const a = s.len; + + cstring_destroy(&s); + + return a; + } + ); emit_row("assign_len_grow", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); emit_row("assign_len_grow", n, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); } @@ -318,6 +454,31 @@ scenario_append_len_growth( std::snprintf(scenario, sizeof(scenario), "append_len_growth_x%zu", num_appends); emit_row(scenario, chunk, IMPL_CSTRING, num_iterations, num_appends, cs, cs.tm_ns); + emit_windows_cstring_arenas( + scenario + , chunk + , 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 (std::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(scenario, chunk, IMPL_STD, num_iterations, num_appends, st, cs.tm_ns); emit_row(scenario, chunk, IMPL_RAW, num_iterations, num_appends, raw, cs.tm_ns); } @@ -371,6 +532,32 @@ scenario_append_len_reserved( }); emit_row("append_len_reserved", n, IMPL_CSTRING, num_iterations, num_appends, cs, cs.tm_ns); + emit_windows_cstring_arenas( + "append_len_reserved" + , n + , num_iterations + , num_warm_loops + , num_appends + , cs.tm_ns + , [p, chunk, n, num_appends](cstring_flags_t flags) -> std::uint64_t + { + cstring_t s = cstring_t_DEFAULT; + + cstring_createEx(&s, "", flags, NULL, 0); + cstring_setCapacity(&s, n); + + for (std::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_reserved", n, IMPL_STD, num_iterations, num_appends, st, cs.tm_ns); emit_row("append_len_reserved", n, IMPL_RAW, num_iterations, num_appends, raw, cs.tm_ns); } @@ -415,6 +602,27 @@ scenario_insert_len_mid( }); emit_row("insert_len_mid", n, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + emit_windows_cstring_arenas( + "insert_len_mid" + , n + , num_iterations + , num_warm_loops + , 1 + , cs.tm_ns + , [bp, n, ip, in, pos](cstring_flags_t flags) -> std::uint64_t + { + cstring_t s = cstring_t_DEFAULT; + + cstring_createLenEx(&s, bp, n, flags, NULL, 0); + cstring_insertLen(&s, pos, ip, in); + + std::uint64_t const a = s.len; + + cstring_destroy(&s); + + return a; + } + ); emit_row("insert_len_mid", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); emit_row("insert_len_mid", n, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); } @@ -460,10 +668,34 @@ scenario_copy( return a; }); - cstring_destroy(&src_cs); raw_destroy(&src_raw); emit_row("copy", n, IMPL_CSTRING, num_iterations, 1, cs, cs.tm_ns); + /* cstring_copy allocates with the destination's existing flags, so a + * default instance stays on realloc. createLenEx is the supported way + * to duplicate a payload in a selected arena. + */ + emit_windows_cstring_arenas( + "copy" + , n + , num_iterations + , num_warm_loops + , 1 + , cs.tm_ns + , [&src_cs, n](cstring_flags_t flags) -> std::uint64_t + { + cstring_t d = cstring_t_DEFAULT; + + cstring_createLenEx(&d, src_cs.ptr, n, flags, NULL, 0); + + std::uint64_t const a = d.len; + + cstring_destroy(&d); + + return a; + } + ); + cstring_destroy(&src_cs); emit_row("copy", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); emit_row("copy", n, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); } @@ -529,6 +761,19 @@ int main(int /*argc*/, char* /*argv*/[]) ; cstring_perf::display_banner("test.performance.cstring"); +#ifdef _WIN32 + + std::cout + << " Windows arenas (ratio vs realloc cstring): cstring_win_global," + << std::endl + << " cstring_win_processheap, cstring_win_comtask." + << std::endl + << " cstring_win_comtask loads and unloads OLE32 when no COM-task" + << std::endl + << " allocation stays live across the call." + << std::endl + ; +#endif /* _WIN32 */ cstring_perf::display_results_title(); scenario_create_destroy_empty(num_iterations, num_warm_loops); From e460f9fe0044e764f0f0bdb3da113e5a0f0cf820 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Sun, 27 Sep 2026 17:51:17 +1000 Subject: [PATCH 72/88] squash-commit (test.performance.cstring_readline) --- .github/workflows/ci.yml | 1 - include/cstring/cstring.h | 2 +- src/cstring.core.c | 9 +- test/performance/CMakeLists.txt | 1 + .../CMakeLists.txt | 5 + .../main.cpp | 609 ++++++++++++++++++ 6 files changed, 621 insertions(+), 6 deletions(-) create mode 100644 test/performance/test.performance.cstring_readline/CMakeLists.txt create mode 100644 test/performance/test.performance.cstring_readline/main.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6e40f3..758f139 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,6 @@ on: - rc1 - rc2 - rc3 - - perf-tests-win pull_request: concurrency: diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index edd5026..0db2dee 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: 29th September 2026 + * Updated: 28th September 2026 * * Home: http://synesis.com.au/software/ * diff --git a/src/cstring.core.c b/src/cstring.core.c index 00f77c3..b7668dd 100644 --- a/src/cstring.core.c +++ b/src/cstring.core.c @@ -291,17 +291,18 @@ cstring_realloc_2_( { case CSTRING_F_USE_REALLOC: -#if defined(_MSC_VER) && \ - defined(_DEBUG) + /* realloc(pv, 0) allocates on some platforms; free the block, + * and do nothing when pv is NULL. + */ if (0 == cb) { free(pv); return NULL; } -#endif /* _DEBUG */ + pvNew = realloc(pv, cb); break; -#ifdef CSTRING_USE_WINAPI_ +#if defined(CSTRING_USE_WINAPI_) case CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY: pvNew = win32_global_realloc(pv, cb); diff --git a/test/performance/CMakeLists.txt b/test/performance/CMakeLists.txt index 15d78ed..6c8958d 100644 --- a/test/performance/CMakeLists.txt +++ b/test/performance/CMakeLists.txt @@ -4,6 +4,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(test.performance.cstring) +add_subdirectory(test.performance.cstring_readline) add_subdirectory(test.performance.cstring_vector) diff --git a/test/performance/test.performance.cstring_readline/CMakeLists.txt b/test/performance/test.performance.cstring_readline/CMakeLists.txt new file mode 100644 index 0000000..7588498 --- /dev/null +++ b/test/performance/test.performance.cstring_readline/CMakeLists.txt @@ -0,0 +1,5 @@ +list(APPEND X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED + 4996 +) + +define_performance_test_program(test.performance.cstring_readline main.cpp) diff --git a/test/performance/test.performance.cstring_readline/main.cpp b/test/performance/test.performance.cstring_readline/main.cpp new file mode 100644 index 0000000..8c40189 --- /dev/null +++ b/test/performance/test.performance.cstring_readline/main.cpp @@ -0,0 +1,609 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/performance/test.performance.cstring_readline/main.cpp + * + * Purpose: Competitive performance tests for cstring_readline() against + * std::getline, fgets (bounded buffer), and a char-at-a-time fgetc + * floor. Scenarios cover reused capacity, CRLF stripping, a fresh + * instance per line, a pre-reserved buffer, empty lines, and an + * unterminated final line. Percentiles and this filesystem suite + * require p99. + * + * Created: 27th September 2026 + * Updated: 27th September 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include + +#include "perf_harness.hpp" + +#ifdef HAS_P99 +# include +# include +# include + +# include +# include +#endif /* HAS_P99 */ + +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * constants + */ + +#ifdef HAS_P99 +namespace { + +char const* const IMPL_READLINE = "cstring_readline"; +char const* const IMPL_GETLINE = "std::getline"; +char const* const IMPL_FGETS = "fgets"; +char const* const IMPL_RAW = "raw_fgetc"; + +char const TEST_FILE_NAME[] = "test.performance.cstring_readline.lines.txt"; + +enum line_ending_t +{ + LINE_ENDING_LF = 0, + LINE_ENDING_CRLF, + LINE_ENDING_NONE, +}; + +enum instance_mode_t +{ + INSTANCE_REUSE = 0, + INSTANCE_FRESH, + INSTANCE_PRERESERVED, +}; + +struct readline_case +{ + char const* scenario; + std::size_t num_lines; + std::size_t line_len; + line_ending_t ending; + instance_mode_t mode; +}; + +readline_case const CASES[] = +{ + { "readline_empty_lines", 2000u, 0u, LINE_ENDING_LF, INSTANCE_REUSE, }, + { "readline_fresh_each_line", 200u, 80u, LINE_ENDING_LF, INSTANCE_FRESH, }, + { "readline_prereserved", 100u, 1024u, LINE_ENDING_LF, INSTANCE_PRERESERVED, }, + { "readline_reuse_crlf", 1000u, 80u, LINE_ENDING_CRLF, INSTANCE_REUSE, }, + { "readline_reuse_lf", 1000u, 16u, LINE_ENDING_LF, INSTANCE_REUSE, }, + { "readline_reuse_lf", 1000u, 80u, LINE_ENDING_LF, INSTANCE_REUSE, }, + { "readline_reuse_lf", 100u, 1024u, LINE_ENDING_LF, INSTANCE_REUSE, }, + { "readline_unterminated_eof", 1u, 16384u, LINE_ENDING_NONE, INSTANCE_REUSE, }, +}; +} // anonymous namespace +#endif /* HAS_P99 */ + + +/* ///////////////////////////////////////////////////////////////////////// + * helpers + */ + +#ifdef HAS_P99 +namespace { + +using namespace cstring_perf; + +struct run_result +{ + interval_t tm_ns; + std::uint64_t anchor; + p99::histogram hist; +}; + +template +run_result +time_iterations( + std::size_t num_iterations +, std::size_t num_warm_loops +, F fn +) +{ + run_result result = {}; + stopwatch_t sw; + + for (std::size_t w = num_warm_loops; 0 != w; --w) + { + result.anchor = 0; + result.hist.clear(); + + interval_t tm_ns = 0; + + for (std::size_t i = 0; num_iterations != i; ++i) + { + sw.start(); + result.anchor += fn(); + sw.stop(); + + interval_t const sample = sw.get_nanoseconds(); + + tm_ns += sample; + (void)result.hist.push_ns(static_cast(sample)); + } + + if (1 == w) + { + result.tm_ns = tm_ns; + } + } + + return result; +} + +void +emit_row( + char const* scenario +, std::size_t size +, char const* impl +, std::size_t num_iterations +, std::size_t num_actions +, run_result const& r +, interval_t cstring_tm_ns +) +{ + double const ratio = + (0 == ::strcmp(impl, IMPL_READLINE)) + ? 1.0 + : ratio_or_dash(r.tm_ns, cstring_tm_ns) + ; + + display_results( + scenario + , size + , impl + , num_iterations + , num_actions + , r.tm_ns + , ratio + , r.anchor + , &r.hist + ); +} + +std::uint64_t +line_anchor( + std::size_t len +, unsigned char head +) +{ + return static_cast(len) + 1u + static_cast(head); +} + +unsigned char +content_head( + char const* p +, std::size_t n +) +{ + if (0 == n || NULL == p) + { + return 0; + } + + return static_cast(p[0]); +} + +bool +write_lines_file( + char const* path +, std::size_t num_lines +, std::size_t line_len +, line_ending_t ending +) +{ + std::ofstream out(path, std::ios::binary | std::ios::trunc); + + if (!out) + { + return false; + } + + std::string const line = make_payload(line_len); + + for (std::size_t i = 0; num_lines != i; ++i) + { + out.write(line.data(), static_cast(line.size())); + + if (LINE_ENDING_CRLF == ending) + { + out.write("\r\n", 2); + } + else if (LINE_ENDING_LF == ending) + { + out.put('\n'); + } + } + + return static_cast(out); +} + +std::uint64_t +read_all_cstring( + char const* path +, std::size_t line_len +, instance_mode_t mode +) +{ + FILE* const f = std::fopen(path, "rb"); + + if (NULL == f) + { + return 0; + } + + cstring_t cs = cstring_t_DEFAULT; + std::uint64_t anchor = 0; + + if (INSTANCE_PRERESERVED == mode && 0 != line_len) + { + if (CSTRING_RC_SUCCESS != cstring_setCapacity(&cs, line_len)) + { + cstring_destroy(&cs); + std::fclose(f); + + return 0; + } + } + + for (;;) + { + if (INSTANCE_FRESH == mode && NULL != cs.ptr) + { + cstring_destroy(&cs); + cs = cstring_t_DEFAULT; + } + + CSTRING_RC const rc = cstring_readline(f, &cs, NULL); + + if (CSTRING_RC_SUCCESS != rc && CSTRING_RC_EOF != rc) + { + anchor = 0; + break; + } + + if (0 != cs.len || CSTRING_RC_EOF != rc) + { + anchor += line_anchor(cs.len, content_head(cs.ptr, cs.len)); + } + + if (CSTRING_RC_EOF == rc) + { + break; + } + } + + cstring_destroy(&cs); + std::fclose(f); + + return anchor; +} + +std::uint64_t +read_all_getline( + char const* path +, std::size_t line_len +, instance_mode_t mode +) +{ + std::ifstream in(path, std::ios::in | std::ios::binary); + + if (!in) + { + return 0; + } + + std::string line; + std::uint64_t anchor = 0; + + if (INSTANCE_PRERESERVED == mode && 0 != line_len) + { + line.reserve(line_len); + } + + while (std::getline(in, line)) + { + if (!line.empty() && '\r' == line[line.size() - 1u]) + { + line.pop_back(); + } + + anchor += line_anchor(line.size(), content_head(line.data(), line.size())); + + if (INSTANCE_FRESH == mode) + { + std::string empty; + + line.swap(empty); + } + } + + return anchor; +} + +std::uint64_t +read_all_fgets( + char const* path +, std::size_t line_len +) +{ + FILE* const f = std::fopen(path, "rb"); + + if (NULL == f) + { + return 0; + } + + /* line_len + 4 covers content, optional CR, LF, and the NUL. */ + std::vector buf(line_len + 4u); + std::uint64_t anchor = 0; + + while (NULL != std::fgets(&buf[0], static_cast(buf.size()), f)) + { + std::size_t n = std::strlen(&buf[0]); + + if (0 != n && '\n' == buf[n - 1u]) + { + --n; + + if (0 != n && '\r' == buf[n - 1u]) + { + --n; + } + } + + anchor += line_anchor(n, content_head(&buf[0], n)); + } + + std::fclose(f); + + return anchor; +} + +std::uint64_t +read_all_raw_fgetc( + char const* path +, std::size_t line_len +, instance_mode_t mode +) +{ + FILE* const f = std::fopen(path, "rb"); + + if (NULL == f) + { + return 0; + } + + raw_string s; + raw_init(&s); + + if (INSTANCE_PRERESERVED == mode && 0 != line_len) + { + if (0 != raw_reserve(&s, line_len)) + { + raw_destroy(&s); + std::fclose(f); + + return 0; + } + } + + std::uint64_t anchor = 0; + int previous = '\0'; + + for (;;) + { + int const ch = std::fgetc(f); + + if (EOF == ch) + { + if (0 != s.len) + { + anchor += line_anchor(s.len, content_head(s.ptr, s.len)); + } + + break; + } + + if ('\n' == ch) + { + if ('\r' == previous && 0 != s.len) + { + --s.len; + s.ptr[s.len] = '\0'; + } + + anchor += line_anchor(s.len, content_head(s.ptr, s.len)); + s.len = 0; + + if (NULL != s.ptr) + { + s.ptr[0] = '\0'; + } + + previous = '\0'; + + if (INSTANCE_FRESH == mode) + { + raw_destroy(&s); + raw_init(&s); + } + + continue; + } + + char const c = static_cast(ch); + + if (0 != raw_append_len(&s, &c, 1u)) + { + anchor = 0; + break; + } + + previous = ch; + } + + raw_destroy(&s); + std::fclose(f); + + return anchor; +} + +bool +anchors_agree( + char const* scenario +, std::size_t line_len +, run_result const& cs +, run_result const& gl +, run_result const& fg +, run_result const& raw +) +{ + if (0 != cs.anchor && + cs.anchor == gl.anchor && + cs.anchor == fg.anchor && + cs.anchor == raw.anchor) + { + return true; + } + + std::cerr + << "anchor mismatch in " << scenario + << " size " << line_len + << ": cstring_readline=" << cs.anchor + << " std::getline=" << gl.anchor + << " fgets=" << fg.anchor + << " raw_fgetc=" << raw.anchor + << std::endl + ; + + return false; +} + +bool +scenario_readline( + readline_case const& spec +, std::size_t num_trials +, std::size_t num_warm_loops +) +{ + if (LINE_ENDING_NONE == spec.ending && 1u != spec.num_lines) + { + std::cerr + << spec.scenario + << ": unterminated input must be a single line" + << std::endl + ; + + return false; + } + + if (!write_lines_file(TEST_FILE_NAME, spec.num_lines, spec.line_len, spec.ending)) + { + std::cerr + << "failed to write " << TEST_FILE_NAME + << "; skipping " << spec.scenario + << std::endl + ; + std::remove(TEST_FILE_NAME); + + return true; + } + + char const* const path = TEST_FILE_NAME; + std::size_t const line_len = spec.line_len; + instance_mode_t const mode = spec.mode; + + run_result const cs = time_iterations(num_trials, num_warm_loops, [path, line_len, mode]() -> std::uint64_t { + return read_all_cstring(path, line_len, mode); + }); + + run_result const gl = time_iterations(num_trials, num_warm_loops, [path, line_len, mode]() -> std::uint64_t { + return read_all_getline(path, line_len, mode); + }); + + run_result const fg = time_iterations(num_trials, num_warm_loops, [path, line_len]() -> std::uint64_t { + return read_all_fgets(path, line_len); + }); + + run_result const raw = time_iterations(num_trials, num_warm_loops, [path, line_len, mode]() -> std::uint64_t { + return read_all_raw_fgetc(path, line_len, mode); + }); + + bool const ok = anchors_agree(spec.scenario, spec.line_len, cs, gl, fg, raw); + + if (ok) + { + emit_row(spec.scenario, spec.line_len, IMPL_READLINE, num_trials, spec.num_lines, cs, cs.tm_ns); + emit_row(spec.scenario, spec.line_len, IMPL_GETLINE, num_trials, spec.num_lines, gl, cs.tm_ns); + emit_row(spec.scenario, spec.line_len, IMPL_FGETS, num_trials, spec.num_lines, fg, cs.tm_ns); + emit_row(spec.scenario, spec.line_len, IMPL_RAW, num_trials, spec.num_lines, raw, cs.tm_ns); + } + + std::remove(TEST_FILE_NAME); + + return ok; +} +} // anonymous namespace +#endif /* HAS_P99 */ + + +/* ///////////////////////////////////////////////////////////////////////// + * main + */ + +int main(int /*argc*/, char* /*argv*/[]) +{ +#ifdef HAS_P99 + + std::size_t const num_trials = cstring_perf::default_file_trials(); + std::size_t const num_warm_loops = cstring_perf::default_warmups(); + + cstring_perf::display_banner("test.performance.cstring_readline"); + + std::cout + << "readline suite (#acts = lines, so ns/op is per line;" + << std::endl + << " cstring_readline is fgetc + one-character append;" + << std::endl + << " fgets uses a buffer sized to the known line):" + << std::endl + ; + + cstring_perf::display_results_title(); + + for (std::size_t i = 0; STLSOFT_NUM_ELEMENTS(CASES) != i; ++i) + { + if (!scenario_readline(CASES[i], num_trials, num_warm_loops)) + { + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +#else /* ? HAS_P99 */ + + cstring_perf::display_banner("test.performance.cstring_readline"); + + std::cout + << "\nreadline suite skipped (p99 not available)." + << std::endl + ; + + return EXIT_SUCCESS; +#endif /* HAS_P99 */ +} + + +/* ///////////////////////////// end of file //////////////////////////// */ + From 0de2177a122b234e4ae2f7a6ca7ce4dbaf945857 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 29 Sep 2026 12:03:37 +1000 Subject: [PATCH 73/88] date --- include/cstring/cstring.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index 0db2dee..edd5026 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: 28th September 2026 + * Updated: 29th September 2026 * * Home: http://synesis.com.au/software/ * From 218afc47fd1d6299367668cb703bcea39ed0720f Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 29 Sep 2026 12:22:23 +1000 Subject: [PATCH 74/88] portability --- .../test.performance.cstring/main.cpp | 129 +++++++++--------- 1 file changed, 64 insertions(+), 65 deletions(-) diff --git a/test/performance/test.performance.cstring/main.cpp b/test/performance/test.performance.cstring/main.cpp index f7b8c48..fb76447 100644 --- a/test/performance/test.performance.cstring/main.cpp +++ b/test/performance/test.performance.cstring/main.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -39,7 +40,6 @@ char const* const IMPL_STD = "std::string"; char const* const IMPL_RAW = "raw_realloc"; char const* const IMPL_BORROWED = "cstring_borrowed"; char const* const IMPL_FIXEDBUF = "fixed_char_buf"; - #ifdef _WIN32 /* Realloc row is the ratio baseline. These arenas follow unit-test order @@ -59,7 +59,7 @@ cstring_arena const WINDOWS_CSTRING_ARENAS[] = }; #endif /* _WIN32 */ -std::size_t const SIZES[] = +size_t const SIZES[] = { 16u, 64u, @@ -67,7 +67,6 @@ std::size_t const SIZES[] = 4096u, 65536u, }; - } // anonymous namespace @@ -92,15 +91,15 @@ struct run_result template run_result time_iterations( - std::size_t num_iterations -, std::size_t num_warm_loops -, F fn + size_t num_iterations +, size_t num_warm_loops +, F fn ) { run_result result = {}; stopwatch_t sw; - for (std::size_t w = num_warm_loops; 0 != w; --w) + for (size_t w = num_warm_loops; 0 != w; --w) { result.anchor = 0; #ifdef HAS_P99 @@ -110,7 +109,7 @@ time_iterations( interval_t tm_ns = 0; #ifdef HAS_P99 - for (std::size_t i = 0; num_iterations != i; ++i) + for (size_t i = 0; num_iterations != i; ++i) { sw.start(); result.anchor += fn(); @@ -124,7 +123,7 @@ time_iterations( #else /* ? HAS_P99 */ sw.start(); - for (std::size_t i = 0; num_iterations != i; ++i) + for (size_t i = 0; num_iterations != i; ++i) { result.anchor += fn(); } @@ -143,13 +142,13 @@ time_iterations( void emit_row( - char const* scenario -, std::size_t size -, char const* impl -, std::size_t num_iterations -, std::size_t num_actions -, run_result const& r -, interval_t cstring_tm_ns + char const* scenario +, size_t size +, char const* impl +, size_t num_iterations +, size_t num_actions +, run_result const& r +, interval_t cstring_tm_ns ) { double const ratio = @@ -180,18 +179,18 @@ emit_row( template void emit_windows_cstring_arenas( - char const* scenario -, std::size_t size -, std::size_t num_iterations -, std::size_t num_warm_loops -, std::size_t num_actions -, interval_t baseline_ns -, F body + char const* scenario +, size_t size +, size_t num_iterations +, size_t num_warm_loops +, size_t num_actions +, interval_t baseline_ns +, F body ) { #ifdef _WIN32 - for (std::size_t i = 0; STLSOFT_NUM_ELEMENTS(WINDOWS_CSTRING_ARENAS) != i; ++i) + for (size_t i = 0; STLSOFT_NUM_ELEMENTS(WINDOWS_CSTRING_ARENAS) != i; ++i) { cstring_flags_t const flags = WINDOWS_CSTRING_ARENAS[i].flags; @@ -236,8 +235,8 @@ namespace { void scenario_create_destroy_empty( - std::size_t num_iterations -, std::size_t num_warm_loops + size_t num_iterations +, size_t num_warm_loops ) { run_result const cs = time_iterations(num_iterations, num_warm_loops, []() -> std::uint64_t { @@ -289,9 +288,9 @@ scenario_create_destroy_empty( void scenario_create_destroy_len( - std::size_t n -, std::size_t num_iterations -, std::size_t num_warm_loops + size_t n +, size_t num_iterations +, size_t num_warm_loops ) { std::string const payload = make_payload(n); @@ -348,9 +347,9 @@ scenario_create_destroy_len( void scenario_assign_len_grow( - std::size_t n -, std::size_t num_iterations -, std::size_t num_warm_loops + size_t n +, size_t num_iterations +, size_t num_warm_loops ) { std::string const payload = make_payload(n); @@ -408,10 +407,10 @@ scenario_assign_len_grow( void scenario_append_len_growth( - std::size_t chunk -, std::size_t num_appends -, std::size_t num_iterations -, std::size_t num_warm_loops + size_t chunk +, size_t num_appends +, size_t num_iterations +, size_t num_warm_loops ) { std::string const payload = make_payload(chunk); @@ -420,7 +419,7 @@ scenario_append_len_growth( 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 (std::size_t i = 0; num_appends != i; ++i) + for (size_t i = 0; num_appends != i; ++i) { cstring_appendLen(&s, p, chunk); } @@ -431,7 +430,7 @@ scenario_append_len_growth( run_result const st = time_iterations(num_iterations, num_warm_loops, [p, chunk, num_appends]() -> std::uint64_t { std::string s; - for (std::size_t i = 0; num_appends != i; ++i) + for (size_t i = 0; num_appends != i; ++i) { s.append(p, chunk); } @@ -441,7 +440,7 @@ scenario_append_len_growth( 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 (std::size_t i = 0; num_appends != i; ++i) + for (size_t i = 0; num_appends != i; ++i) { raw_append_len(&s, p, chunk); } @@ -467,7 +466,7 @@ scenario_append_len_growth( cstring_createEx(&s, "", flags, NULL, 0); - for (std::size_t i = 0; num_appends != i; ++i) + for (size_t i = 0; num_appends != i; ++i) { cstring_appendLen(&s, p, chunk); } @@ -485,21 +484,21 @@ scenario_append_len_growth( void scenario_append_len_reserved( - std::size_t n -, std::size_t num_iterations -, std::size_t num_warm_loops + size_t n +, size_t num_iterations +, size_t num_warm_loops ) { std::string const payload = make_payload(n); char const* const p = payload.data(); - std::size_t const chunk = (n < 16u) ? n : 16u; - std::size_t const num_appends = (0 == chunk) ? 0u : (n / chunk); + size_t const chunk = (n < 16u) ? n : 16u; + size_t const num_appends = (0 == chunk) ? 0u : (n / chunk); run_result const cs = time_iterations(num_iterations, num_warm_loops, [p, chunk, n, num_appends]() -> std::uint64_t { cstring_t s = cstring_t_DEFAULT; cstring_create(&s, ""); cstring_setCapacity(&s, n); - for (std::size_t i = 0; num_appends != i; ++i) + for (size_t i = 0; num_appends != i; ++i) { cstring_appendLen(&s, p, chunk); } @@ -511,7 +510,7 @@ scenario_append_len_reserved( run_result const st = time_iterations(num_iterations, num_warm_loops, [p, chunk, n, num_appends]() -> std::uint64_t { std::string s; s.reserve(n); - for (std::size_t i = 0; num_appends != i; ++i) + for (size_t i = 0; num_appends != i; ++i) { s.append(p, chunk); } @@ -522,7 +521,7 @@ scenario_append_len_reserved( raw_string s; raw_init(&s); raw_reserve(&s, n); - for (std::size_t i = 0; num_appends != i; ++i) + for (size_t i = 0; num_appends != i; ++i) { raw_append_len(&s, p, chunk); } @@ -546,7 +545,7 @@ scenario_append_len_reserved( cstring_createEx(&s, "", flags, NULL, 0); cstring_setCapacity(&s, n); - for (std::size_t i = 0; num_appends != i; ++i) + for (size_t i = 0; num_appends != i; ++i) { cstring_appendLen(&s, p, chunk); } @@ -564,16 +563,16 @@ scenario_append_len_reserved( void scenario_insert_len_mid( - std::size_t n -, std::size_t num_iterations -, std::size_t num_warm_loops + size_t n +, size_t num_iterations +, size_t num_warm_loops ) { std::string const base = make_payload(n); std::string const ins = make_payload(n / 4u == 0 ? 1u : n / 4u); char const* const bp = base.data(); char const* const ip = ins.data(); - std::size_t const in = ins.size(); + size_t const in = ins.size(); int const pos = static_cast(n / 2u); run_result const cs = time_iterations(num_iterations, num_warm_loops, [bp, n, ip, in, pos]() -> std::uint64_t { @@ -629,9 +628,9 @@ scenario_insert_len_mid( void scenario_copy( - std::size_t n -, std::size_t num_iterations -, std::size_t num_warm_loops + size_t n +, size_t num_iterations +, size_t num_warm_loops ) { std::string const payload = make_payload(n); @@ -702,9 +701,9 @@ scenario_copy( void scenario_borrowed_fixed( - std::size_t n -, std::size_t num_iterations -, std::size_t num_warm_loops + size_t n +, size_t num_iterations +, size_t num_warm_loops ) { std::string const payload = make_payload(n); @@ -752,11 +751,11 @@ scenario_borrowed_fixed( int main(int /*argc*/, char* /*argv*/[]) { - std::size_t const num_iterations = cstring_perf::default_iterations(); - std::size_t const num_warm_loops = cstring_perf::default_warmups(); + size_t const num_iterations = cstring_perf::default_iterations(); + size_t const num_warm_loops = cstring_perf::default_warmups(); // Fewer outer iters for heavy growth / large sizes - std::size_t const heavy_iters = + size_t const heavy_iters = (num_iterations > 10000u) ? (num_iterations / 10u) : num_iterations ; @@ -778,10 +777,10 @@ int main(int /*argc*/, char* /*argv*/[]) scenario_create_destroy_empty(num_iterations, num_warm_loops); - for (std::size_t i = 0; STLSOFT_NUM_ELEMENTS(SIZES) != i; ++i) + for (size_t i = 0; STLSOFT_NUM_ELEMENTS(SIZES) != i; ++i) { - std::size_t const n = SIZES[i]; - std::size_t const iters = (n >= 4096u) ? heavy_iters : num_iterations; + size_t const n = SIZES[i]; + size_t const iters = (n >= 4096u) ? heavy_iters : num_iterations; scenario_create_destroy_len(n, iters, num_warm_loops); scenario_assign_len_grow(n, iters, num_warm_loops); From 36603b0ef9e56eec4106babbf05bc84511845832 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 29 Sep 2026 12:32:46 +1000 Subject: [PATCH 75/88] test(cstring): use auto_buffer in borrowed_fixed_assign harness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace per-iteration std::vector arenas with stlsoft::auto_buffer (512 stack elements) so sizes ≤256 avoid a heap alloc in the timed path; rename scenario_borrowed_fixed to scenario_borrowed_fixed_assign to match the measured operation. --- test/performance/test.performance.cstring/main.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/performance/test.performance.cstring/main.cpp b/test/performance/test.performance.cstring/main.cpp index fb76447..a08618d 100644 --- a/test/performance/test.performance.cstring/main.cpp +++ b/test/performance/test.performance.cstring/main.cpp @@ -20,8 +20,9 @@ #include "perf_harness.hpp" +#include + #include -#include #include #include @@ -67,6 +68,8 @@ size_t const SIZES[] = 4096u, 65536u, }; + +const size_t NUM_STACK_ELEMENTS = 512; } // anonymous namespace @@ -700,7 +703,7 @@ scenario_copy( } void -scenario_borrowed_fixed( +scenario_borrowed_fixed_assign( size_t n , size_t num_iterations , size_t num_warm_loops @@ -710,7 +713,7 @@ scenario_borrowed_fixed( char const* const p = payload.data(); run_result const cs = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { - std::vector buf(n + 1u, '\0'); + stlsoft::auto_buffer buf(n + 1); cstring_t s = cstring_t_DEFAULT; cstring_createEx( &s @@ -732,7 +735,7 @@ scenario_borrowed_fixed( }); run_result const fx = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { - std::vector buf(n + 1u, '\0'); + stlsoft::auto_buffer buf(n + 1); ::memcpy(&buf[0], p, n); buf[n] = '\0'; return n + static_cast(buf[0]); @@ -786,7 +789,7 @@ int main(int /*argc*/, char* /*argv*/[]) scenario_assign_len_grow(n, iters, num_warm_loops); scenario_append_len_reserved(n, iters, num_warm_loops); scenario_copy(n, iters, num_warm_loops); - scenario_borrowed_fixed(n, iters, num_warm_loops); + scenario_borrowed_fixed_assign(n, iters, num_warm_loops); if (n <= 4096u) { From 1e0bc91f17b647646b0024b80305dc6ce958bebb Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 29 Sep 2026 12:48:47 +1000 Subject: [PATCH 76/88] (fix(performance tests): `scenario_borrowed_fixed_assign()` all anchor values calculated consistently --- test/performance/test.performance.cstring/main.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/performance/test.performance.cstring/main.cpp b/test/performance/test.performance.cstring/main.cpp index a08618d..b8f99ad 100644 --- a/test/performance/test.performance.cstring/main.cpp +++ b/test/performance/test.performance.cstring/main.cpp @@ -723,22 +723,25 @@ scenario_borrowed_fixed_assign( , n + 1u ); cstring_assignLen(&s, p, n); - std::uint64_t const a = s.len; + std::uint64_t const r = s.len + (s.len == 0 ? 0 : size_t(s.ptr[s.len - 1])); cstring_destroy(&s); - return a; + + return r; }); run_result const st = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { std::string s; s.assign(p, n); - return s.size(); + + return s.size() + (s.empty() ? 0 : size_t(s.back())); }); run_result const fx = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { stlsoft::auto_buffer buf(n + 1); ::memcpy(&buf[0], p, n); buf[n] = '\0'; - return n + static_cast(buf[0]); + + return n + (n == 0 ? 0 : size_t(buf[n - 1])); }); emit_row("borrowed_fixed_assign", n, IMPL_BORROWED, num_iterations, 1, cs, cs.tm_ns); From e1ff75e186c039d3bd171ae9f87b19c425d8e966 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 29 Sep 2026 13:01:44 +1000 Subject: [PATCH 77/88] feature(performance tests): added `scenario_borrowed_fixed_construct()` --- .../test.performance.cstring/main.cpp | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/performance/test.performance.cstring/main.cpp b/test/performance/test.performance.cstring/main.cpp index b8f99ad..3690c93 100644 --- a/test/performance/test.performance.cstring/main.cpp +++ b/test/performance/test.performance.cstring/main.cpp @@ -702,6 +702,53 @@ scenario_copy( emit_row("copy", n, IMPL_RAW, num_iterations, 1, raw, cs.tm_ns); } +void +scenario_borrowed_fixed_construct( + size_t n +, size_t num_iterations +, size_t num_warm_loops +) +{ + std::string const payload = make_payload(n); + char const* const p = payload.data(); + + run_result const cs = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + stlsoft::auto_buffer buf(n + 1); + cstring_t s = cstring_t_DEFAULT; + cstring_createLenEx( + &s + , p + , n + , CSTRING_F_MEMORY_IS_BORROWED | CSTRING_F_MEMORY_IS_FIXED + , &buf[0] + , n + 1u + ); + + std::uint64_t const r = s.len + (s.len == 0 ? 0 : size_t(s.ptr[s.len - 1])); + cstring_destroy(&s); + + return r; + }); + + run_result const st = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + std::string s(p, n); + + return s.size() + (s.empty() ? 0 : size_t(s.back())); + }); + + run_result const fx = time_iterations(num_iterations, num_warm_loops, [p, n]() -> std::uint64_t { + stlsoft::auto_buffer buf(n + 1); + ::memcpy(&buf[0], p, n); + buf[n] = '\0'; + + return n + (n == 0 ? 0 : size_t(buf[n - 1])); + }); + + emit_row("borrowed_fixed_construct", n, IMPL_BORROWED, num_iterations, 1, cs, cs.tm_ns); + emit_row("borrowed_fixed_construct", n, IMPL_STD, num_iterations, 1, st, cs.tm_ns); + emit_row("borrowed_fixed_construct", n, IMPL_FIXEDBUF, num_iterations, 1, fx, cs.tm_ns); +} + void scenario_borrowed_fixed_assign( size_t n @@ -792,6 +839,7 @@ int main(int /*argc*/, char* /*argv*/[]) scenario_assign_len_grow(n, iters, num_warm_loops); scenario_append_len_reserved(n, iters, num_warm_loops); scenario_copy(n, iters, num_warm_loops); + scenario_borrowed_fixed_construct(n, iters, num_warm_loops); scenario_borrowed_fixed_assign(n, iters, num_warm_loops); if (n <= 4096u) From a467c4a0aa40899b1ba6c4460c9c5b2484c2c358 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 29 Sep 2026 14:08:30 +1000 Subject: [PATCH 78/88] fix(cstring): silence GCC free-nonheap-object in the perf harness GCC 15 with -Werror flags an inlined stlsoft::auto_buffer destructor as freeing its internal buffer. Suppress -Wfree-nonheap-object on test.performance.cstring; the destructor deallocates external storage only. --- test/performance/test.performance.cstring/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/performance/test.performance.cstring/CMakeLists.txt b/test/performance/test.performance.cstring/CMakeLists.txt index 96b4531..348b004 100644 --- a/test/performance/test.performance.cstring/CMakeLists.txt +++ b/test/performance/test.performance.cstring/CMakeLists.txt @@ -1,3 +1,8 @@ +# GCC 15 -Wfree-nonheap-object false positive: inlined +# stlsoft::auto_buffer::~auto_buffer() only deallocates external storage. +list(APPEND X_GCC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED + free-nonheap-object +) list(APPEND X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED 4996 ) From e0575361fa7d1098df7186f161aaafc907e28adb Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 29 Sep 2026 14:20:18 +1000 Subject: [PATCH 79/88] misc --- CMakeLists.txt | 8 ++++---- prepare_cmake.sh | 2 +- test/performance/perf_harness.hpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 82e1af4..aca4361 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,8 +137,8 @@ option(NO_SHWILD "Do not recognise shwild for xTests pattern-match assertions" O # # optional: # - p99 - optional for performance tests: per-iteration percentiles -# throughout when found; also gates the filesystem file_lines vs -# cstring_vector_readLines suite (unless NO_P99); +# throughout when found; also gates the filesystem suites +# (file_lines, cstring_readline) unless NO_P99; # - shwild - for enhanced match constructs in xTests (unless NO_SHWILD); # # note: NO_CSTRING_CPP_API skips C++ examples and remaining C++ tests; C @@ -236,11 +236,11 @@ if(BUILD_TESTING) message("-- CMake package p99 found (version ${p99_VERSION})") else() - message("-- CMake package p99 not found; performance percentiles and file_lines suite disabled") + message("-- CMake package p99 not found; performance percentiles and filesystem suites disabled") endif() else() - message("-- p99 recognition disabled (NO_P99); performance percentiles and file_lines suite disabled") + message("-- p99 recognition disabled (NO_P99); performance percentiles and filesystem suites disabled") endif() endif(BUILD_TESTING) diff --git a/prepare_cmake.sh b/prepare_cmake.sh index d067722..c9ce8f3 100755 --- a/prepare_cmake.sh +++ b/prepare_cmake.sh @@ -237,7 +237,7 @@ Flags/options: --no-p99 prevents recognising p99 library (NO_P99=ON); performance tests - then omit percentiles and the file_lines suite + then omit percentiles and the filesystem suites --no-shwild prevents recognising shwild library (NO_SHWILD=ON); xTests diff --git a/test/performance/perf_harness.hpp b/test/performance/perf_harness.hpp index 5b91e6f..24a6eca 100644 --- a/test/performance/perf_harness.hpp +++ b/test/performance/perf_harness.hpp @@ -186,7 +186,7 @@ display_banner( << ", CSTRING_PERF_FILE_TRIALS" << ", SIS_PERFTESTS_GROUPGAPS." #else /* ? HAS_P99 */ - << " p99: not linked — mean ns/op only; file_lines suite skipped." + << " p99: not linked — mean ns/op only; filesystem suites skipped." << std::endl << " Env: CSTRING_PERF_ITERATIONS, CSTRING_PERF_WARMUPS" << ", SIS_PERFTESTS_GROUPGAPS." From 72ef8b85d0be53e2bcf0be85fd31dc8c58228f78 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 29 Sep 2026 14:31:54 +1000 Subject: [PATCH 80/88] fix(cstring): initialise COM before Windows task-allocator tests test_unit_cstring_1 calls CoTaskMem* and CoGetMalloc. Hold a comstl::com_init for the process, and skip arena size probes when an empty string has a NULL pointer. --- test/unit/test.unit.cstring.1/entry.cpp | 103 +++++++++++++++++++----- 1 file changed, 82 insertions(+), 21 deletions(-) diff --git a/test/unit/test.unit.cstring.1/entry.cpp b/test/unit/test.unit.cstring.1/entry.cpp index ec4df43..0c29be7 100644 --- a/test/unit/test.unit.cstring.1/entry.cpp +++ b/test/unit/test.unit.cstring.1/entry.cpp @@ -36,6 +36,7 @@ /* STLSoft header files */ #ifdef WIN32 # include +# include #endif #include @@ -108,6 +109,12 @@ int main(int argc, char **argv) int retCode = EXIT_SUCCESS; int verbosity = 2; +#ifdef _WIN32 + + /* CoTaskMem* / CoGetMalloc require COM on this thread. */ + comstl::com_init init; +#endif /* Windows */ + XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); if (XTESTS_START_RUNNER("test.unit.cstring.1", verbosity)) @@ -955,9 +962,18 @@ static void test_2_3() cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(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); + XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, ::GlobalSize(str.ptr)); + } cstring_destroy(&str); @@ -977,9 +993,18 @@ static void test_2_3() cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(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); + XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, ::HeapSize(::GetProcessHeap(), 0, str.ptr)); + } cstring_destroy(&str); @@ -999,9 +1024,18 @@ static void test_2_3() cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(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); + XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, comstl::CoTaskMemGetSize(str.ptr)); + } cstring_destroy(&str); @@ -1097,10 +1131,19 @@ static void test_2_4() cstring_createLenEx(&str, s, i, flags, NULL, 0); - TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(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); + XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, ::GlobalSize(str.ptr)); + } cstring_destroy(&str); @@ -1119,10 +1162,19 @@ static void test_2_4() cstring_createLenEx(&str, s, i, flags, NULL, 0); - TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(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); + XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, ::HeapSize(::GetProcessHeap(), 0, str.ptr)); + } cstring_destroy(&str); @@ -1141,10 +1193,19 @@ static void test_2_4() cstring_createLenEx(&str, s, i, flags, NULL, 0); - TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(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); + XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + TEST_INT_GE(str.capacity, comstl::CoTaskMemGetSize(str.ptr)); + } cstring_destroy(&str); From 06111acfe9bd30e209597f60b1a1f52a76378045 Mon Sep 17 00:00:00 2001 From: synesissoftware Date: Tue, 29 Sep 2026 15:55:53 +1000 Subject: [PATCH 81/88] Internal refac (#35) * misc * chore(internal): removed `CSTRING_USE_WINAPI_` and now discriminate on `_WIN32` * chore(internal): simplified includes * refactor(cstring): centralise CSTRING_ASSERT in internal.h Define CSTRING_ASSERT once in src/internal.h, with , and drop the copies in cstring.core.c and cstring.vector.c. Vector call sites use that macro in place of CSTRING_VECTOR_ASSERT. dllmain.c includes internal.h. * fix --- TODO.md | 2 +- src/cstring.core.c | 12 ++---------- src/cstring.vector.c | 20 ++++++++++---------- src/dllmain.c | 4 +++- src/internal.h | 35 ++++++++++++++++++++++++++++------- src/win.c | 1 + 6 files changed, 45 insertions(+), 29 deletions(-) diff --git a/TODO.md b/TODO.md index a6c3bf2..4f1564b 100644 --- a/TODO.md +++ b/TODO.md @@ -15,7 +15,7 @@ * [x] ~~~Delete Visual Studio 98 files~~~ - ✅; * [x] ~~~Delete Visual Studio 2003+ files~~~ - ✅; * [x] ~~~discriminate on `_WIN32` in implementation (and maybe also in API)~~~ - ✅; -* [ ] check `CSTRING_USE_WINAPI_`; +* [x] ~~~check `CSTRING_USE_WINAPI_`~~~; * [ ] custom arena(s); diff --git a/src/cstring.core.c b/src/cstring.core.c index b7668dd..b00bdec 100644 --- a/src/cstring.core.c +++ b/src/cstring.core.c @@ -59,7 +59,6 @@ /* Standard C header files */ -#include #include #include #include @@ -78,13 +77,6 @@ #define CSTRING_OFFSET_SIZE (16) -/* ///////////////////////////////////////////////////////////////////////// - * debugging - */ - -#define CSTRING_ASSERT(expr) assert(expr) - - /* ///////////////////////////////////////////////////////////////////////// * character encoding */ @@ -302,7 +294,7 @@ cstring_realloc_2_( pvNew = realloc(pv, cb); break; -#if defined(CSTRING_USE_WINAPI_) +#ifdef _WIN32 case CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY: pvNew = win32_global_realloc(pv, cb); @@ -315,7 +307,7 @@ cstring_realloc_2_( pvNew = win32_comtask_realloc(pv, cb); break; -#endif /* CSTRING_USE_WINAPI_ */ +#endif /* _WIN32 */ #if defined(CSTRING_USE_SYNESIS_APIS) case CSTRING_F_USE_SYNESIS_HATOR: #endif /* CSTRING_USE_SYNESIS_APIS */ diff --git a/src/cstring.vector.c b/src/cstring.vector.c index 59152c8..81f43ed 100644 --- a/src/cstring.vector.c +++ b/src/cstring.vector.c @@ -51,6 +51,7 @@ /* cstring header files */ #include +#include "internal.h" #ifndef CSTRING_INCL_CSTRING_INTERNAL_H_SAFESTR # include @@ -58,7 +59,6 @@ /* Standard C header files */ -#include #include #include #include @@ -114,7 +114,7 @@ cstring_vector_init( , size_t minCapacity ) { - CSTRING_VECTOR_ASSERT(NULL != pcsv); + CSTRING_ASSERT(NULL != pcsv); if (cstring_vector_DEFAULT_CAPACITY == minCapacity) { @@ -158,7 +158,7 @@ cstring_vector_destroy( CSTRING_RC rc = CSTRING_RC_SUCCESS; size_t i; - CSTRING_VECTOR_ASSERT(NULL != pcsv); + CSTRING_ASSERT(NULL != pcsv); for (i = 0; i != pcsv->len; ++i) { @@ -190,8 +190,8 @@ cstring_vector_truncate( CSTRING_RC rc = CSTRING_RC_SUCCESS; size_t i; - CSTRING_VECTOR_ASSERT(NULL != pcsv); - CSTRING_VECTOR_ASSERT(len <= pcsv->len); + CSTRING_ASSERT(NULL != pcsv); + CSTRING_ASSERT(len <= pcsv->len); for (i = len; i != pcsv->len; ++i) { @@ -215,7 +215,7 @@ cstring_vector_create( , size_t initialSize ) { - CSTRING_VECTOR_ASSERT(NULL != pcsv); + CSTRING_ASSERT(NULL != pcsv); if (cstring_vector_DEFAULT_CAPACITY == initialSize) { @@ -229,7 +229,7 @@ cstring_vector_create( { size_t i; - CSTRING_VECTOR_ASSERT(pcsv->capacity >= initialSize); + CSTRING_ASSERT(pcsv->capacity >= initialSize); for (i = 0; i != initialSize; ++i) { @@ -272,8 +272,8 @@ cstring_vector_insertAt( , size_t numStrings ) { - CSTRING_VECTOR_ASSERT(NULL != pcsv); - CSTRING_VECTOR_ASSERT(NULL != strings || 0 == numStrings); + CSTRING_ASSERT(NULL != pcsv); + CSTRING_ASSERT(NULL != strings || 0 == numStrings); if (0 == numStrings) { @@ -373,7 +373,7 @@ cstring_vector_readLines( size_t numLinesRead_; size_t initialLen; - CSTRING_VECTOR_ASSERT(NULL != pcsv); + CSTRING_ASSERT(NULL != pcsv); initialLen = pcsv->len; diff --git a/src/dllmain.c b/src/dllmain.c index 382b994..432a924 100644 --- a/src/dllmain.c +++ b/src/dllmain.c @@ -4,7 +4,7 @@ * Purpose: Definition of DllMain for the Win32 platform * * Created: 19th August 2005 - * Updated: 2nd August 2026 + * Updated: 29th September 2026 * * Home: http://synesis.com.au/software/ * @@ -49,6 +49,8 @@ */ #include +#include "internal.h" + #if !defined(__MWERKS__) # include #else /* ? compiler */ diff --git a/src/internal.h b/src/internal.h index c4f1c69..74cb2a4 100644 --- a/src/internal.h +++ b/src/internal.h @@ -48,29 +48,50 @@ #define CSTRING_INCL_SRC_H_INTERNAL +/* ///////////////////////////////////////////////////////////////////////// + * checks + */ + +#ifndef CSTRING_INCL_CSTRING_H_CSTRING +# error cstring/cstring.h must be included before this file +#endif /* !CSTRING_INCL_CSTRING_H_CSTRING */ + + /* ///////////////////////////////////////////////////////////////////////// * includes */ -#include +#include /* ///////////////////////////////////////////////////////////////////////// * constants & definitions */ -#if defined(WIN32) || \ - defined(WIN64) -# define CSTRING_USE_WINAPI_ -#endif /* WIN32 || WIN64 */ +/* ///////////////////////////////////////////////////////////////////////// + * macros + */ + + +/* ///////////////////////////////////////////////////////// + * constract enforcements + */ + +#define CSTRING_ASSERT(expr) assert(expr) /* ///////////////////////////////////////////////////////////////////////// + * internal functions + */ + + + +/* ///////////////////////////////////////////////////////// * Windows arena allocators */ -#if defined(CSTRING_USE_WINAPI_) +#ifdef _WIN32 CSTRING_EXTERN_C void* @@ -92,7 +113,7 @@ win32_comtask_realloc( void* pv , size_t cb ); -#endif /* CSTRING_USE_WINAPI_ */ +#endif /* _WIN32 */ /* ////////////////////////////////////////////////////////////////////// */ diff --git a/src/win.c b/src/win.c index 2490256..1d07a52 100644 --- a/src/win.c +++ b/src/win.c @@ -48,6 +48,7 @@ * includes */ +#include #include "internal.h" From d25a6546b2a57a10ebfc469699e9316961179fe6 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 29 Sep 2026 17:35:25 +1000 Subject: [PATCH 82/88] misc --- src/cstring.core.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cstring.core.c b/src/cstring.core.c index b00bdec..5bee299 100644 --- a/src/cstring.core.c +++ b/src/cstring.core.c @@ -1364,13 +1364,13 @@ cstring_readline( ) { int previous = '\0'; - size_t numRead_; + size_t dummy; CSTRING_ASSERT(NULL != pcs); if (NULL == numRead) { - numRead = &numRead_; + numRead = &dummy; } if (NULL == stm || @@ -1431,13 +1431,13 @@ cstring_write_( ) { int r; - size_t numWritten_; + size_t dummy; CSTRING_ASSERT(NULL != pcs); if (NULL == numWritten) { - numWritten = &numWritten_; + numWritten = &dummy; } if (NULL == stm || @@ -1650,13 +1650,13 @@ cstring_replaceAll( , size_t* numReplaced /* = NULL */ ) { - size_t numReplaced_; + size_t dummy; CSTRING_ASSERT(NULL != pcs); if (NULL == numReplaced) { - numReplaced = &numReplaced_; + numReplaced = &dummy; } *numReplaced = 0u; From 13a628da5a702526a3b7d88e6f6fada61fdc63ca Mon Sep 17 00:00:00 2001 From: synesissoftware Date: Sat, 3 Oct 2026 11:52:48 +1000 Subject: [PATCH 83/88] `cstring_readline()` fix for readonly string (#38) * dependencies * misc * test(cstring): show readline ignoring a failed readonly clear Add a component case that passes a non-empty readonly cstring to cstring_readline() for an empty line and for immediate EOF. The clear must return CSTRING_RC_READONLY, leave the payload in place, and leave the stream unconsumed. * docs(cstring): record the 4.0.19 readonly readline clear Describe the cstring_readline() truncate check in CHANGES.md and NEWS.md, set Doxyfile PROJECT_NUMBER to 4.0.19, and date the cstring.core.c banner. --- CHANGES.md | 6 ++ CMakeLists.txt | 2 +- Doxyfile | 2 +- NEWS.md | 1 + TODO.md | 2 + include/cstring/cstring.h | 9 ++- src/cstring.core.c | 19 ++++- .../test.component.cstring_readline/entry.cpp | 79 ++++++++++++++++++- 8 files changed, 109 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ad45af0..1b2a41c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,12 @@ # cstring - Changes +## 4.0.19 - 3rd October 2026 + +* `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`; + + ## 4.0.18 - 29th September 2026 * Gated Windows arena flags and WinAPI allocators on `_WIN32` in **cstring.h** and **cstring.core.c**, so 32- and 64-bit Windows builds expose them without a `WIN32` or `WIN64` define; diff --git a/CMakeLists.txt b/CMakeLists.txt index aca4361..330b3e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -214,7 +214,7 @@ endif(BUILD_TESTING) if(BUILD_TESTING) - set(xTests_REQUIRED_VERSION_ 0.26) + set(xTests_REQUIRED_VERSION_ 0.26.5) find_package(xTests ${xTests_REQUIRED_VERSION_} REQUIRED) diff --git a/Doxyfile b/Doxyfile index 361b9a9..b549b16 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.0.18 +PROJECT_NUMBER = 4.0.19 # 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 b1106c3..02d9c20 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +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 | | 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/TODO.md b/TODO.md index 4f1564b..cf57dcf 100644 --- a/TODO.md +++ b/TODO.md @@ -17,6 +17,8 @@ * [x] ~~~discriminate on `_WIN32` in implementation (and maybe also in API)~~~ - ✅; * [x] ~~~check `CSTRING_USE_WINAPI_`~~~; * [ ] custom arena(s); +* [ ] `cstring_vector_readlineEx()` that takes a flag to prevent truncate, thereby allowing client code to add to an existing string; +* [ ] when go to 5.x, change the name of `cstring_vector_readLines()` to `cstring_vector_readlines()`; ## Performance improvements diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index edd5026..663ba1c 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: 29th September 2026 + * Updated: 3rd October 2026 * * Home: http://synesis.com.au/software/ * @@ -55,8 +55,8 @@ #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION # define CSTRING_VER_CSTRING_H_CSTRING_MAJOR 3 # define CSTRING_VER_CSTRING_H_CSTRING_MINOR 12 -# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 1 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 90 +# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 2 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 91 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ @@ -103,6 +103,7 @@ # define CSTRING_VER_4_0_16 0x040010ff # define CSTRING_VER_4_0_17 0x040011ff # define CSTRING_VER_4_0_18 0x040012ff +# define CSTRING_VER_4_0_19 0x040013ff #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR @@ -123,7 +124,7 @@ #define CSTRING_VER_MAJOR 4 #define CSTRING_VER_MINOR 0 -#define CSTRING_VER_PATCH 18 +#define CSTRING_VER_PATCH 19 #define CSTRING_VER_ALPHABETA 0xFF #define CSTRING_VER \ diff --git a/src/cstring.core.c b/src/cstring.core.c index 5bee299..7fbfa3f 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: 29th September 2026 + * Updated: 3rd October 2026 * * Home: http://synesis.com.au/software/ * @@ -1363,8 +1363,9 @@ cstring_readline( , size_t* numRead /* = NULL */ ) { - int previous = '\0'; - size_t dummy; + int previous = '\0'; + size_t dummy; + CSTRING_RC rc0; CSTRING_ASSERT(NULL != pcs); @@ -1381,7 +1382,17 @@ cstring_readline( *numRead = 0u; - cstring_truncate(pcs, 0); + rc0 = cstring_truncate(pcs, 0); + + if (CSTRING_RC_SUCCESS != rc0) + { + /* NOTE: have to check for failure here, because a non-empty + * readonly string could be left if the stream is empty or its first + * character is LF. + */ + + return rc0; + } for (;;) { diff --git a/test/component/test.component.cstring_readline/entry.cpp b/test/component/test.component.cstring_readline/entry.cpp index 7bb3c7c..ad5d765 100644 --- a/test/component/test.component.cstring_readline/entry.cpp +++ b/test/component/test.component.cstring_readline/entry.cpp @@ -4,7 +4,7 @@ * Purpose: Component-tests `cstring_readline()`. * * Created: 23rd May 2009 - * Updated: 27th September 2026 + * Updated: 3rd October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -56,6 +56,7 @@ namespace 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); int setup(void*); int teardown(void*); @@ -104,6 +105,7 @@ int main(int argc, char* argv[]) 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); + XTESTS_RUN_CASE(TEST_cstring_readline_READONLY_RETAINS_PAYLOAD); XTESTS_PRINT_RESULTS(); @@ -578,6 +580,81 @@ static void TEST_cstring_readline_REUSE_AFTER_LONG_LINE() cstring_destroy(&cs); } + +static void TEST_cstring_readline_READONLY_RETAINS_PAYLOAD() +{ + /* non-empty readonly string: a failed clear must be reported, and the + * stream must not be consumed + */ + + { + write_string_bytes(TEST_FILE_NAME, std::string("\nnext\n")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs; + CSTRING_RC rc; + + rc = cstring_createEx(&cs, "stale", CSTRING_F_MEMORY_IS_READONLY, NULL, 0); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + + size_t n = 99u; + + rc = cstring_readline(f, &cs, &n); + + TEST_ENUM_EQ(CSTRING_RC_READONLY, rc); + TEST_INT_EQ(5u, cs.len); + TEST_MS_EQ("stale", cs); + + cstring_t fresh = cstring_t_DEFAULT; + + rc = cstring_readline(f, &fresh, &n); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(0u, n); + TEST_MS_EQ("", fresh); + + cstring_destroy(&fresh); + cstring_destroy(&cs); + } + + { + 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_RC rc; + + rc = cstring_createEx(&cs, "stale", CSTRING_F_MEMORY_IS_READONLY, NULL, 0); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + + size_t n = 99u; + + rc = cstring_readline(f, &cs, &n); + + TEST_ENUM_EQ(CSTRING_RC_READONLY, rc); + TEST_INT_EQ(5u, cs.len); + TEST_MS_EQ("stale", cs); + + cstring_t fresh = cstring_t_DEFAULT; + + rc = cstring_readline(f, &fresh, &n); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(0u, n); + TEST_INT_EQ(0u, fresh.len); + + cstring_destroy(&fresh); + cstring_destroy(&cs); + } +} } // anonymous namespace From 87803635e6a5c8ba7e6f6d09c061a814aa5f42ab Mon Sep 17 00:00:00 2001 From: synesissoftware Date: Sat, 3 Oct 2026 12:06:15 +1000 Subject: [PATCH 84/88] fix: `cstring_readline()` (#37) * dependencies * chore(tests): canonicalised form * refactoring(tests): canonicalised form * tests(readline): more test cases * refactor(readline): canonicalising tests * fix(scripts): drop --verbosity from non-automated runners Example, performance, and scratch runners do not take --verbosity and do not pass it to the programs they launch. Each program is invoked with no extra arguments. Unit and component runners still accept the flag and forward it. Apply the same change to the cstring runners and to the cmake-helpers templates they are copied from. * fix(cstring): count the newline in cstring_readline() numRead Count every character read from the stream in numRead, including the terminating newline. A CRLF's CR is still counted and still stripped from the string. Previously that newline was omitted, so a line containing only LF reported zero. Add single-line LF and CRLF component cases and align the existing numRead expectations. Record the correction as 4.0.19. * chore(tests): simplified form(s) * fix(cstring): treat CR, LF, and CRLF as cstring_readline terminators Count every character read for the line in numRead, including the terminator, and do not store CR or LF. A lone CR, including CR at end of stream, ends the line with CSTRING_RC_SUCCESS and pushes the following character back. Add single-line LF, CRLF, and CR component cases. An embedded CR now splits a cstring_vector_readLines line. Record the correction as 4.0.19. * tests(readline): more test cases * tests(readline): more test cases * test(cstring): close readline temp files before reopen xtests::temp_file creates its Windows file with an exclusive handle and leaves that handle open. The readline component tests then fopen the same path, which Windows rejects as a sharing violation. Pass CloseOnOpen so the creating handle is closed before fopen. Drop the local argc == 0 guard, which skipped the suite on every normal invocation. * fix * fix --- CHANGES.md | 4 + NEWS.md | 2 +- include/cstring/cstring.h | 12 +- run_all_component_tests.sh | 7 +- run_all_examples.sh | 24 +- run_all_performance_tests.sh | 24 +- run_all_scratch_tests.sh | 24 +- run_all_unit_tests.sh | 7 +- src/cstring.core.c | 36 +- .../test.component.cstring_readline/entry.cpp | 382 +++++++++++++----- .../entry.cpp | 31 +- .../entry.cpp | 16 +- .../test.performance.cstring_vector/main.cpp | 3 +- test/unit/test.unit.cstring.1/entry.cpp | 48 +-- 14 files changed, 381 insertions(+), 239 deletions(-) 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 From 41319ff59cdf224b0000676011d1d54c5e580fc6 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Sat, 3 Oct 2026 13:51:59 +1000 Subject: [PATCH 85/88] feature(performance tests): added `scenario_append_len_inc8()` --- .../test.performance.cstring/main.cpp | 96 ++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/test/performance/test.performance.cstring/main.cpp b/test/performance/test.performance.cstring/main.cpp index 3690c93..0e96722 100644 --- a/test/performance/test.performance.cstring/main.cpp +++ b/test/performance/test.performance.cstring/main.cpp @@ -7,7 +7,7 @@ * p99 is available, also report per-iteration percentiles. * * Created: 23rd September 2026 - * Updated: 29th September 2026 + * Updated: 3rd October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -70,6 +70,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 @@ -485,6 +496,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 @@ -849,8 +940,11 @@ int main(int /*argc*/, char* /*argv*/[]) } } + scenario_append_len_inc8(heavy_iters, num_warm_loops); + return EXIT_SUCCESS; } /* ///////////////////////////// end of file //////////////////////////// */ + From 178d40a9b0cc626667e402141b97013d6405f45b Mon Sep 17 00:00:00 2001 From: synesissoftware Date: Sun, 4 Oct 2026 07:02:26 +1100 Subject: [PATCH 86/88] test(cstring): shorten test directories and name cases in `TEST_` form (#39) * test(cstring): shorten test directories and name cases in TEST_ form Shorten test directories to the subject under test//, and name each CMake target test... Spell every case TEST_ in shouting snake case, keeping each API or type in its real spelling, and switch assertions to the terse xTests API. - Drop empty cases and unused temp-file names; - Rename **auto_buffer** to **cstring.borrowed**; - Use `size_t` in the performance programs; - Run CI on the **directories** branch. * docs(cstring): complete the 4.0.19 release notes Record post-4.0.18 behaviour and tests in **CHANGES.md** and **NEWS.md**; Cover zero-size realloc frees and the readline performance suite; borrowed-fixed scenarios, and shortened test directories with `TEST_` names; * chore(tests): refactored --- .github/workflows/ci.yml | 1 + CHANGES.md | 9 + CMakeLists.txt | 2 +- NEWS.md | 2 +- examples/c/example.c.cstring/main.c | 1 - examples/c/example.c.cstring_create/main.c | 5 +- examples/c/example.c.cstring_vector/main.c | 4 +- .../cpp/example.cpp.cstring.dynload/main.cpp | 1 + src/CMakeLists.txt | 4 +- test/component/CMakeLists.txt | 14 +- test/component/component_fixture.hpp | 152 ++++++ .../CMakeLists.txt | 2 +- .../entry.cpp | 84 +-- .../cstring_vector_readLines/CMakeLists.txt | 2 + .../entry.cpp | 86 +-- .../cstring_writeline/CMakeLists.txt | 5 + .../entry.cpp | 43 +- .../CMakeLists.txt | 2 - .../CMakeLists.txt | 5 - test/performance/CMakeLists.txt | 6 +- .../CMakeLists.txt | 0 .../main.cpp | 86 +-- .../CMakeLists.txt | 0 .../main.cpp | 162 ++---- .../CMakeLists.txt | 0 .../main.cpp | 187 ++----- test/performance/perf_harness.hpp | 174 +++++- test/unit/CMakeLists.txt | 10 +- .../CMakeLists.txt | 2 +- test/unit/cstring.borrowed/entry.c | 506 +++++++++++++++++ .../CMakeLists.txt | 2 +- .../entry.c | 117 ++-- .../CMakeLists.txt | 2 +- .../entry.cpp | 350 ++++++------ .../CMakeLists.txt | 5 + test/unit/cstring_getStatusCodeString/entry.c | 232 ++++++++ .../CMakeLists.txt | 0 test/unit/cstring_vector/entry.c | 318 +++++++++++ .../test.unit.cstring.auto_buffer/entry.c | 508 ------------------ .../CMakeLists.txt | 5 - .../entry.c | 233 -------- .../test.unit.cstring.cstring_vector/entry.c | 378 ------------- 42 files changed, 1805 insertions(+), 1902 deletions(-) create mode 100644 test/component/component_fixture.hpp rename test/component/{test.component.cstring_readline => cstring_readline}/CMakeLists.txt (53%) rename test/component/{test.component.cstring_readline => cstring_readline}/entry.cpp (92%) create mode 100644 test/component/cstring_vector_readLines/CMakeLists.txt rename test/component/{test.component.cstring_vector_readLines => cstring_vector_readLines}/entry.cpp (88%) create mode 100644 test/component/cstring_writeline/CMakeLists.txt rename test/component/{test.component.cstring_writeline => cstring_writeline}/entry.cpp (91%) delete mode 100644 test/component/test.component.cstring_vector_readLines/CMakeLists.txt delete mode 100644 test/component/test.component.cstring_writeline/CMakeLists.txt rename test/performance/{test.performance.cstring => cstring}/CMakeLists.txt (100%) rename test/performance/{test.performance.cstring => cstring}/main.cpp (93%) rename test/performance/{test.performance.cstring_readline => cstring_readline}/CMakeLists.txt (100%) rename test/performance/{test.performance.cstring_readline => cstring_readline}/main.cpp (78%) rename test/performance/{test.performance.cstring_vector => cstring_vector}/CMakeLists.txt (100%) rename test/performance/{test.performance.cstring_vector => cstring_vector}/main.cpp (77%) rename test/unit/{test.unit.cstring.2 => cstring.borrowed}/CMakeLists.txt (58%) create mode 100644 test/unit/cstring.borrowed/entry.c rename test/unit/{test.unit.cstring.auto_buffer => cstring.insert-replace}/CMakeLists.txt (56%) rename test/unit/{test.unit.cstring.2 => cstring.insert-replace}/entry.c (82%) rename test/unit/{test.unit.cstring.1 => cstring.instance}/CMakeLists.txt (58%) rename test/unit/{test.unit.cstring.1 => cstring.instance}/entry.cpp (78%) create mode 100644 test/unit/cstring_getStatusCodeString/CMakeLists.txt create mode 100644 test/unit/cstring_getStatusCodeString/entry.c rename test/unit/{test.unit.cstring.cstring_vector => cstring_vector}/CMakeLists.txt (100%) create mode 100644 test/unit/cstring_vector/entry.c delete mode 100644 test/unit/test.unit.cstring.auto_buffer/entry.c delete mode 100644 test/unit/test.unit.cstring.cstring_getStatusCodeString/CMakeLists.txt delete mode 100644 test/unit/test.unit.cstring.cstring_getStatusCodeString/entry.c delete mode 100644 test/unit/test.unit.cstring.cstring_vector/entry.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 758f139..9967548 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ on: - rc1 - rc2 - rc3 + - directories pull_request: concurrency: diff --git a/CHANGES.md b/CHANGES.md index c8cb447..9412878 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,15 @@ * 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`; +* Freed a zero-size realloc-arena block on every platform (`realloc(pv, 0)` allocates on some), and left a NULL pointer unchanged; +* Added **test.performance.cstring_readline** (filesystem timings gated on **p99**); +* Added borrowed-fixed construct and assign scenarios to **test.performance.cstring**, using `stlsoft::auto_buffer` for the borrowed anchor; +* Suppressed the GCC 15 `-Wfree-nonheap-object` false positive on inlined `stlsoft::auto_buffer` destruction in **test.performance.cstring**; +* Shortened test directories under **test/** to the subject, and renamed the unit suites to **test.unit.cstring.instance**, **test.unit.cstring.insert-replace**, and **test.unit.cstring.borrowed** (formerly **test.unit.cstring.1**, **test.unit.cstring.2**, and **test.unit.cstring.auto_buffer**); +* Named unit cases `TEST_` in shouting snake case, keeping each API or type in its real spelling, and switched assertions to the terse xTests API; +* Removed empty unit cases and unused temporary-file names; +* Used `size_t` in the performance programs; +* Shared component file fixtures in **test/component/component_fixture.hpp**, and shared `time_iterations`, `emit_row`, and `write_lines_file` in **test/performance/perf_harness.hpp**; ## 4.0.18 - 29th September 2026 diff --git a/CMakeLists.txt b/CMakeLists.txt index 330b3e0..0a13548 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ # Purpose: Top-level CMake lists file for cstring # # Created: 21st December 2023 -# Updated: 29th September 2026 +# Updated: 3rd October 2026 # # ######################################################################## # diff --git a/NEWS.md b/NEWS.md index b9c2f48..b6e87cf 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) | lone CR ends a line; `cstring_readline()` `numRead` includes EOL; `cstring_readline()` reports failed readonly | +| 3rd 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 directories | | 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/examples/c/example.c.cstring/main.c b/examples/c/example.c.cstring/main.c index daaf369..3df3645 100644 --- a/examples/c/example.c.cstring/main.c +++ b/examples/c/example.c.cstring/main.c @@ -154,7 +154,6 @@ int main(int argc, char *argv[]) } - /* ///////////////////////////////////////////////////////////////////////// * function implementations */ diff --git a/examples/c/example.c.cstring_create/main.c b/examples/c/example.c.cstring_create/main.c index fcca051..af630bc 100644 --- a/examples/c/example.c.cstring_create/main.c +++ b/examples/c/example.c.cstring_create/main.c @@ -3,8 +3,8 @@ * * Purpose: Minimal example of `cstring_create()` / `cstring_destroy()`. * - * Created: ... - * Updated: 2nd August 2026 + * Created: 12th January 2024 + * Updated: 3rd October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -15,6 +15,7 @@ /* Standard C header files */ #include + /* ///////////////////////////////////////////////////////////////////////// * main() */ diff --git a/examples/c/example.c.cstring_vector/main.c b/examples/c/example.c.cstring_vector/main.c index a460381..ce4b24b 100644 --- a/examples/c/example.c.cstring_vector/main.c +++ b/examples/c/example.c.cstring_vector/main.c @@ -4,8 +4,8 @@ * Purpose: Example reading lines into a `cstring_vector_t` and sorting * them. * - * Created: ... - * Updated: 23rd September 2026 + * Created: 12th January 2024 + * Updated: 3rd October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/examples/cpp/example.cpp.cstring.dynload/main.cpp b/examples/cpp/example.cpp.cstring.dynload/main.cpp index e2b327c..11c17b4 100644 --- a/examples/cpp/example.cpp.cstring.dynload/main.cpp +++ b/examples/cpp/example.cpp.cstring.dynload/main.cpp @@ -38,6 +38,7 @@ namespace std /* Standard C header files */ #include + /* ///////////////////////////////////////////////////////////////////////// * forward declarations */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5200656..9a592f8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,8 +4,8 @@ # # Purpose: CMake lists file for cstring core library # -# Created: ... -# Updated: 29th September 2026 +# Created: 21st December 2023 +# Updated: 3rd October 2026 # # ######################################################################## # diff --git a/test/component/CMakeLists.txt b/test/component/CMakeLists.txt index fd37bfb..f31e871 100644 --- a/test/component/CMakeLists.txt +++ b/test/component/CMakeLists.txt @@ -1,6 +1,14 @@ +# Shared component fixtures (component_fixture.hpp) + + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + if(NOT NO_CSTRING_CPP_API) - add_subdirectory(test.component.cstring_readline) - add_subdirectory(test.component.cstring_vector_readLines) - add_subdirectory(test.component.cstring_writeline) + add_subdirectory(cstring_readline) + add_subdirectory(cstring_vector_readLines) + add_subdirectory(cstring_writeline) endif() + + +# ############################## end of file ############################# # diff --git a/test/component/component_fixture.hpp b/test/component/component_fixture.hpp new file mode 100644 index 0000000..4f144bc --- /dev/null +++ b/test/component/component_fixture.hpp @@ -0,0 +1,152 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/component/component_fixture.hpp + * + * Purpose: Shared file fixtures and setup/teardown for cstring component + * tests. + * + * Created: 3rd October 2026 + * Updated: 3rd October 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +#ifndef CSTRING_TEST_COMPONENT_COMPONENT_FIXTURE_HPP_INCLUDED +#define CSTRING_TEST_COMPONENT_COMPONENT_FIXTURE_HPP_INCLUDED + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include + +#include +#include +#include + +#include + +#include +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * namespace + */ + +namespace cstring_component +{ + + +/* ///////////////////////////////////////////////////////////////////////// + * file fixtures + */ + +inline +FILE* +fopen_or_throw( + char const* fileName +, char const* mode +) +{ +#ifdef CSTRING_USING_SAFE_STR_FUNCTIONS + FILE* f; + + if (0 != ::fopen_s(&f, fileName, mode)) +#else /* ? CSTRING_USING_SAFE_STR_FUNCTIONS */ + FILE* f = ::fopen(fileName, mode); + + if (NULL == f) +#endif /* CSTRING_USING_SAFE_STR_FUNCTIONS */ + { + throw platformstl::platform_exception((std::string("Could not open file '") + fileName + "'").c_str(), platformstl::system_traits::get_last_error()); + } + + return f; +} + +inline +void +write_bytes( + char const* fileName +, void const* bytes +, size_t cb +) +{ + FILE* f = fopen_or_throw(fileName, "wb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + if (cb != ::fwrite(bytes, 1, cb, f)) + { + throw platformstl::platform_exception("Could not write test file", platformstl::system_traits::get_last_error()); + } +} + +inline +void +write_string_bytes( + char const* fileName +, std::string const& bytes +) +{ + write_bytes(fileName, bytes.data(), bytes.size()); +} + +inline +std::string +make_filled( + size_t n +, char ch +) +{ + return std::string(n, ch); +} + + +/* ///////////////////////////////////////////////////////////////////////// + * xTests runner + */ + +inline +int +setup(void*) +{ + return 0; +} + +inline +int +teardown(void* arg) +{ + char const* path = static_cast(arg); + + if (NULL != path && + '\0' != path[0]) + { + ::remove(path); + } + + return 0; +} + + +/* ///////////////////////////////////////////////////////////////////////// + * namespace + */ + +} // namespace cstring_component + + +/* ///////////////////////////////////////////////////////////////////////// + * inclusion control + */ + +#ifdef STLSOFT_PPF_pragma_once_SUPPORT +# pragma once +#endif /* STLSOFT_PPF_pragma_once_SUPPORT */ + +#endif /* !CSTRING_TEST_COMPONENT_COMPONENT_FIXTURE_HPP_INCLUDED */ + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/component/test.component.cstring_readline/CMakeLists.txt b/test/component/cstring_readline/CMakeLists.txt similarity index 53% rename from test/component/test.component.cstring_readline/CMakeLists.txt rename to test/component/cstring_readline/CMakeLists.txt index af6840b..9d95889 100644 --- a/test/component/test.component.cstring_readline/CMakeLists.txt +++ b/test/component/cstring_readline/CMakeLists.txt @@ -1,2 +1,2 @@ # SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten -define_automated_test_program(test_component_cstring_cstring_readline entry.cpp) +define_automated_test_program(test.component.cstring_readline entry.cpp) diff --git a/test/component/test.component.cstring_readline/entry.cpp b/test/component/cstring_readline/entry.cpp similarity index 92% rename from test/component/test.component.cstring_readline/entry.cpp rename to test/component/cstring_readline/entry.cpp index c1b5484..0bcf330 100644 --- a/test/component/test.component.cstring_readline/entry.cpp +++ b/test/component/cstring_readline/entry.cpp @@ -10,24 +10,26 @@ /* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +/* ///////////////////////////////////// * test component header file include(s) */ #include -#include -/* ///////////////////////////////////////////////////////////////////////// - * includes +/* ///////////////////////////////////// + * general includes */ +#include "component_fixture.hpp" + /* xTests header files */ -#include #include #include /* STLSoft header files */ -#include -#include #include /* Standard C++ header files */ @@ -65,9 +67,6 @@ namespace 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*); } // anonymous namespace @@ -101,7 +100,7 @@ int main(int argc, char* argv[]) XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); - if (XTESTS_START_RUNNER_WITH_SETUP_FNS("test.component.cstring_readline", verbosity, setup, teardown, (void*)TEST_FILE_NAME)) + if (XTESTS_START_RUNNER_WITH_SETUP_FNS("test.component.cstring_readline", verbosity, cstring_component::setup, cstring_component::teardown, (void*)TEST_FILE_NAME)) { XTESTS_RUN_CASE(TEST_cstring_readline_CALLABILITY); XTESTS_RUN_CASE(TEST_cstring_readline_INVALID_STREAM); @@ -142,75 +141,14 @@ int main(int argc, char* argv[]) #endif /* compiler */ -/* ///////////////////////////////////////////////////////////////////////// - * helper functions - */ - -namespace -{ - - static FILE* fopen_or_throw(char const* fileName, char const* mode) - { -#ifdef CSTRING_USING_SAFE_STR_FUNCTIONS - FILE* f; - - if (0 != ::fopen_s(&f, fileName, mode)) -#else /* ? CSTRING_USING_SAFE_STR_FUNCTIONS */ - FILE* f = ::fopen(fileName, mode); - - if (NULL == f) -#endif /* CSTRING_USING_SAFE_STR_FUNCTIONS */ - { - throw platformstl::platform_exception((std::string("Could not open file '") + fileName + "'").c_str(), platformstl::system_traits::get_last_error()); - } - - return f; - } - - static void write_bytes(char const* fileName, void const* bytes, size_t cb) - { - FILE* f = fopen_or_throw(fileName, "wb"); - - stlsoft::scoped_handle scoper(f, ::fclose); - - if (cb != ::fwrite(bytes, 1, cb, f)) - { - throw platformstl::platform_exception("Could not write test file", platformstl::system_traits::get_last_error()); - } - } - - static void write_string_bytes(char const* fileName, std::string const& bytes) - { - write_bytes(fileName, bytes.data(), bytes.size()); - } - - static std::string make_filled(size_t n, char ch) - { - return std::string(n, ch); - } - - int setup(void*) - { - return 0; - } - - int teardown(void* arg) - { - char const* filename = static_cast(arg); - - ::remove(filename); - - return 0; - } -} // anonymous namespace - - /* ///////////////////////////////////////////////////////////////////////// * test function implementations */ namespace { + + using namespace cstring_component; using ::xtests::cpp::util::temp_file; diff --git a/test/component/cstring_vector_readLines/CMakeLists.txt b/test/component/cstring_vector_readLines/CMakeLists.txt new file mode 100644 index 0000000..f22eb90 --- /dev/null +++ b/test/component/cstring_vector_readLines/CMakeLists.txt @@ -0,0 +1,2 @@ +# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +define_automated_test_program(test.component.cstring_vector_readLines entry.cpp) diff --git a/test/component/test.component.cstring_vector_readLines/entry.cpp b/test/component/cstring_vector_readLines/entry.cpp similarity index 88% rename from test/component/test.component.cstring_vector_readLines/entry.cpp rename to test/component/cstring_vector_readLines/entry.cpp index be3b18c..f2ef821 100644 --- a/test/component/test.component.cstring_vector_readLines/entry.cpp +++ b/test/component/cstring_vector_readLines/entry.cpp @@ -10,23 +10,25 @@ /* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +/* ///////////////////////////////////// * test component header file include(s) */ #include -#include -/* ///////////////////////////////////////////////////////////////////////// - * includes +/* ///////////////////////////////////// + * general includes */ +#include "component_fixture.hpp" + /* xTests header files */ -#include #include /* STLSoft header files */ -#include -#include #include /* Standard C++ header files */ @@ -56,9 +58,6 @@ namespace 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*); } // anonymous namespace @@ -68,6 +67,7 @@ namespace const char TEST_FILE_NAME[] = "test.component.cstring_vector_readLines.txt"; + /* ///////////////////////////////////////////////////////////////////////// * compiler compatibility */ @@ -79,6 +79,7 @@ const char TEST_FILE_NAME[] = "test.component.cstring_vector_readLines.txt"; # pragma warning(disable : 4702) #endif /* compiler */ + /* ///////////////////////////////////////////////////////////////////////// * main */ @@ -90,7 +91,7 @@ int main(int argc, char* argv[]) XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); - if (XTESTS_START_RUNNER_WITH_SETUP_FNS("test.component.cstring_vector_readLines", verbosity, setup, teardown, (void*)TEST_FILE_NAME)) + if (XTESTS_START_RUNNER_WITH_SETUP_FNS("test.component.cstring_vector_readLines", verbosity, cstring_component::setup, cstring_component::teardown, (void*)TEST_FILE_NAME)) { XTESTS_RUN_CASE(TEST_cstring_vector_readLines_CALLABILITY); XTESTS_RUN_CASE(TEST_cstring_vector_readLines_EMPTY_FILE); @@ -121,68 +122,6 @@ int main(int argc, char* argv[]) # endif /* compiler */ #endif /* compiler */ -/* ///////////////////////////////////////////////////////////////////////// - * helper functions - */ - -namespace -{ - - static FILE* fopen_or_throw(char const* fileName, char const* mode) - { -#ifdef CSTRING_USING_SAFE_STR_FUNCTIONS - FILE* f; - - if (0 != ::fopen_s(&f, fileName, mode)) -#else /* ? CSTRING_USING_SAFE_STR_FUNCTIONS */ - FILE* f = ::fopen(fileName, mode); - - if (NULL == f) -#endif /* CSTRING_USING_SAFE_STR_FUNCTIONS */ - { - throw platformstl::platform_exception((std::string("Could not open file '") + fileName + "'").c_str(), platformstl::system_traits::get_last_error()); - } - - return f; - } - - static void write_bytes(char const* fileName, void const* bytes, size_t cb) - { - FILE* f = fopen_or_throw(fileName, "wb"); - - stlsoft::scoped_handle scoper(f, ::fclose); - - if (cb != ::fwrite(bytes, 1, cb, f)) - { - throw platformstl::platform_exception("Could not write test file", platformstl::system_traits::get_last_error()); - } - } - - static void write_string_bytes(char const* fileName, std::string const& bytes) - { - write_bytes(fileName, bytes.data(), bytes.size()); - } - - static std::string make_filled(size_t n, char ch) - { - return std::string(n, ch); - } - - int setup(void*) - { - return 0; - } - - int teardown(void* arg) - { - char const* filename = static_cast(arg); - - ::remove(filename); - - return 0; - } -} // anonymous namespace - /* ///////////////////////////////////////////////////////////////////////// * test function implementations @@ -191,6 +130,9 @@ namespace namespace { + using namespace cstring_component; + + static void TEST_cstring_vector_readLines_CALLABILITY() { false && cstring_vector_readLines(stdout, NULL, NULL); diff --git a/test/component/cstring_writeline/CMakeLists.txt b/test/component/cstring_writeline/CMakeLists.txt new file mode 100644 index 0000000..1174d61 --- /dev/null +++ b/test/component/cstring_writeline/CMakeLists.txt @@ -0,0 +1,5 @@ +list(APPEND X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED + 4996 +) + +define_automated_test_program(test.component.cstring_writeline entry.cpp) diff --git a/test/component/test.component.cstring_writeline/entry.cpp b/test/component/cstring_writeline/entry.cpp similarity index 91% rename from test/component/test.component.cstring_writeline/entry.cpp rename to test/component/cstring_writeline/entry.cpp index 05017c3..3ca1090 100644 --- a/test/component/test.component.cstring_writeline/entry.cpp +++ b/test/component/cstring_writeline/entry.cpp @@ -4,36 +4,39 @@ * Purpose: Unit-tests of `cstring_write()` and `cstring_writeline()`. * * Created: 10th August 2020 - * Updated: 2nd October 2026 + * Updated: 3rd October 2026 * * ////////////////////////////////////////////////////////////////////// */ /* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +/* ///////////////////////////////////// * test component header file include(s) */ #include -#include -/* ///////////////////////////////////////////////////////////////////////// - * includes +/* ///////////////////////////////////// + * general includes */ +#include "component_fixture.hpp" + /* xTests header files */ -#include #include #include /* STLSoft header files */ -#include #include -#include #include /* Standard C header files */ #include + /* ///////////////////////////////////////////////////////////////////////// * forward declarations */ @@ -45,9 +48,6 @@ namespace 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 @@ -57,6 +57,7 @@ namespace const char TEST_FILE_NAME[] = "test.component.cstring_writeline.txt"; + /* ///////////////////////////////////////////////////////////////////////// * compiler compatibility */ @@ -68,6 +69,7 @@ const char TEST_FILE_NAME[] = "test.component.cstring_writeline.txt"; # pragma warning(disable : 4702) #endif /* compiler */ + /* ///////////////////////////////////////////////////////////////////////// * main */ @@ -79,7 +81,7 @@ int main(int argc, char* argv[]) XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); - if (XTESTS_START_RUNNER_WITH_SETUP_FNS("test.component.cstring_writeline", verbosity, setup, teardown, (void*)TEST_FILE_NAME)) + if (XTESTS_START_RUNNER_WITH_SETUP_FNS("test.component.cstring_writeline", verbosity, cstring_component::setup, cstring_component::teardown, (void*)TEST_FILE_NAME)) { XTESTS_RUN_CASE(TEST_cstring_writeline_CALLABILITY); XTESTS_RUN_CASE(TEST_cstring_writeline_INVALID_STREAM); @@ -94,6 +96,7 @@ int main(int argc, char* argv[]) return retCode; } + /* ///////////////////////////////////////////////////////////////////////// * compiler compatibility */ @@ -104,29 +107,15 @@ int main(int argc, char* argv[]) # endif /* compiler */ #endif /* compiler */ + /* ///////////////////////////////////////////////////////////////////////// * test function implementations */ namespace { - using ::xtests::cpp::util::temp_file; - - - int setup(void*) - { - return 0; - } - - int teardown(void* arg) - { - char const* filename = static_cast(arg); - - ::remove(filename); - - return 0; - } + using ::xtests::cpp::util::temp_file; static void TEST_cstring_writeline_CALLABILITY() diff --git a/test/component/test.component.cstring_vector_readLines/CMakeLists.txt b/test/component/test.component.cstring_vector_readLines/CMakeLists.txt deleted file mode 100644 index 08eb713..0000000 --- a/test/component/test.component.cstring_vector_readLines/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten -define_automated_test_program(test_component_cstring_cstring_vector_readLines entry.cpp) diff --git a/test/component/test.component.cstring_writeline/CMakeLists.txt b/test/component/test.component.cstring_writeline/CMakeLists.txt deleted file mode 100644 index db887ae..0000000 --- a/test/component/test.component.cstring_writeline/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -list(APPEND X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED - 4996 -) - -define_automated_test_program(test_component_cstring_cstring_writeline entry.cpp) diff --git a/test/performance/CMakeLists.txt b/test/performance/CMakeLists.txt index 6c8958d..9b58e9a 100644 --- a/test/performance/CMakeLists.txt +++ b/test/performance/CMakeLists.txt @@ -3,9 +3,9 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -add_subdirectory(test.performance.cstring) -add_subdirectory(test.performance.cstring_readline) -add_subdirectory(test.performance.cstring_vector) +add_subdirectory(cstring) +add_subdirectory(cstring_readline) +add_subdirectory(cstring_vector) # ############################## end of file ############################# # diff --git a/test/performance/test.performance.cstring/CMakeLists.txt b/test/performance/cstring/CMakeLists.txt similarity index 100% rename from test/performance/test.performance.cstring/CMakeLists.txt rename to test/performance/cstring/CMakeLists.txt diff --git a/test/performance/test.performance.cstring/main.cpp b/test/performance/cstring/main.cpp similarity index 93% rename from test/performance/test.performance.cstring/main.cpp rename to test/performance/cstring/main.cpp index 3690c93..af5fec3 100644 --- a/test/performance/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: 29th September 2026 + * Updated: 3rd October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -81,68 +81,6 @@ namespace { using namespace cstring_perf; -struct run_result -{ - interval_t tm_ns; - std::uint64_t anchor; -#ifdef HAS_P99 - - p99::histogram hist; -#endif /* HAS_P99 */ -}; - -template -run_result -time_iterations( - size_t num_iterations -, size_t num_warm_loops -, F fn -) -{ - run_result result = {}; - stopwatch_t sw; - - for (size_t w = num_warm_loops; 0 != w; --w) - { - result.anchor = 0; -#ifdef HAS_P99 - - result.hist.clear(); -#endif /* HAS_P99 */ - interval_t tm_ns = 0; -#ifdef HAS_P99 - - for (size_t i = 0; num_iterations != i; ++i) - { - sw.start(); - result.anchor += fn(); - sw.stop(); - - interval_t const sample = sw.get_nanoseconds(); - - tm_ns += sample; - (void)result.hist.push_ns(static_cast(sample)); - } -#else /* ? HAS_P99 */ - - sw.start(); - for (size_t i = 0; num_iterations != i; ++i) - { - result.anchor += fn(); - } - sw.stop(); - tm_ns = sw.get_nanoseconds(); -#endif /* HAS_P99 */ - - if (1 == w) - { - result.tm_ns = tm_ns; - } - } - - return result; -} - void emit_row( char const* scenario @@ -154,24 +92,22 @@ emit_row( , interval_t cstring_tm_ns ) { - double const ratio = - (0 == ::strcmp(impl, IMPL_CSTRING) || 0 == ::strcmp(impl, IMPL_BORROWED)) - ? 1.0 - : ratio_or_dash(r.tm_ns, cstring_tm_ns) - ; + char const* const baselines[] = + { + IMPL_CSTRING, + IMPL_BORROWED, + }; - display_results( + cstring_perf::emit_row( scenario , size , impl , num_iterations , num_actions - , r.tm_ns - , ratio - , r.anchor -#ifdef HAS_P99 - , &r.hist -#endif /* HAS_P99 */ + , r + , cstring_tm_ns + , baselines + , STLSOFT_NUM_ELEMENTS(baselines) ); } diff --git a/test/performance/test.performance.cstring_readline/CMakeLists.txt b/test/performance/cstring_readline/CMakeLists.txt similarity index 100% rename from test/performance/test.performance.cstring_readline/CMakeLists.txt rename to test/performance/cstring_readline/CMakeLists.txt diff --git a/test/performance/test.performance.cstring_readline/main.cpp b/test/performance/cstring_readline/main.cpp similarity index 78% rename from test/performance/test.performance.cstring_readline/main.cpp rename to test/performance/cstring_readline/main.cpp index 8c40189..fa16a88 100644 --- a/test/performance/test.performance.cstring_readline/main.cpp +++ b/test/performance/cstring_readline/main.cpp @@ -9,7 +9,7 @@ * require p99. * * Created: 27th September 2026 - * Updated: 27th September 2026 + * Updated: 3rd October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -48,12 +48,7 @@ char const* const IMPL_RAW = "raw_fgetc"; char const TEST_FILE_NAME[] = "test.performance.cstring_readline.lines.txt"; -enum line_ending_t -{ - LINE_ENDING_LF = 0, - LINE_ENDING_CRLF, - LINE_ENDING_NONE, -}; +using namespace cstring_perf; enum instance_mode_t { @@ -65,8 +60,8 @@ enum instance_mode_t struct readline_case { char const* scenario; - std::size_t num_lines; - std::size_t line_len; + size_t num_lines; + size_t line_len; line_ending_t ending; instance_mode_t mode; }; @@ -95,85 +90,38 @@ namespace { using namespace cstring_perf; -struct run_result -{ - interval_t tm_ns; - std::uint64_t anchor; - p99::histogram hist; -}; - -template -run_result -time_iterations( - std::size_t num_iterations -, std::size_t num_warm_loops -, F fn -) -{ - run_result result = {}; - stopwatch_t sw; - - for (std::size_t w = num_warm_loops; 0 != w; --w) - { - result.anchor = 0; - result.hist.clear(); - - interval_t tm_ns = 0; - - for (std::size_t i = 0; num_iterations != i; ++i) - { - sw.start(); - result.anchor += fn(); - sw.stop(); - - interval_t const sample = sw.get_nanoseconds(); - - tm_ns += sample; - (void)result.hist.push_ns(static_cast(sample)); - } - - if (1 == w) - { - result.tm_ns = tm_ns; - } - } - - return result; -} - void emit_row( char const* scenario -, std::size_t size +, size_t size , char const* impl -, std::size_t num_iterations -, std::size_t num_actions +, size_t num_iterations +, size_t num_actions , run_result const& r , interval_t cstring_tm_ns ) { - double const ratio = - (0 == ::strcmp(impl, IMPL_READLINE)) - ? 1.0 - : ratio_or_dash(r.tm_ns, cstring_tm_ns) - ; + char const* const baselines[] = + { + IMPL_READLINE, + }; - display_results( + cstring_perf::emit_row( scenario , size , impl , num_iterations , num_actions - , r.tm_ns - , ratio - , r.anchor - , &r.hist + , r + , cstring_tm_ns + , baselines + , STLSOFT_NUM_ELEMENTS(baselines) ); } std::uint64_t line_anchor( - std::size_t len + size_t len , unsigned char head ) { @@ -183,7 +131,7 @@ line_anchor( unsigned char content_head( char const* p -, std::size_t n +, size_t n ) { if (0 == n || NULL == p) @@ -194,45 +142,11 @@ content_head( return static_cast(p[0]); } -bool -write_lines_file( - char const* path -, std::size_t num_lines -, std::size_t line_len -, line_ending_t ending -) -{ - std::ofstream out(path, std::ios::binary | std::ios::trunc); - - if (!out) - { - return false; - } - - std::string const line = make_payload(line_len); - - for (std::size_t i = 0; num_lines != i; ++i) - { - out.write(line.data(), static_cast(line.size())); - - if (LINE_ENDING_CRLF == ending) - { - out.write("\r\n", 2); - } - else if (LINE_ENDING_LF == ending) - { - out.put('\n'); - } - } - - return static_cast(out); -} - std::uint64_t read_all_cstring( - char const* path -, std::size_t line_len -, instance_mode_t mode + char const* path +, size_t line_len +, instance_mode_t mode ) { FILE* const f = std::fopen(path, "rb"); @@ -291,9 +205,9 @@ read_all_cstring( std::uint64_t read_all_getline( - char const* path -, std::size_t line_len -, instance_mode_t mode + char const* path +, size_t line_len +, instance_mode_t mode ) { std::ifstream in(path, std::ios::in | std::ios::binary); @@ -334,7 +248,7 @@ read_all_getline( std::uint64_t read_all_fgets( char const* path -, std::size_t line_len +, size_t line_len ) { FILE* const f = std::fopen(path, "rb"); @@ -350,7 +264,7 @@ read_all_fgets( while (NULL != std::fgets(&buf[0], static_cast(buf.size()), f)) { - std::size_t n = std::strlen(&buf[0]); + size_t n = std::strlen(&buf[0]); if (0 != n && '\n' == buf[n - 1u]) { @@ -372,9 +286,9 @@ read_all_fgets( std::uint64_t read_all_raw_fgetc( - char const* path -, std::size_t line_len -, instance_mode_t mode + char const* path +, size_t line_len +, instance_mode_t mode ) { FILE* const f = std::fopen(path, "rb"); @@ -462,7 +376,7 @@ read_all_raw_fgetc( bool anchors_agree( char const* scenario -, std::size_t line_len +, size_t line_len , run_result const& cs , run_result const& gl , run_result const& fg @@ -493,8 +407,8 @@ anchors_agree( bool scenario_readline( readline_case const& spec -, std::size_t num_trials -, std::size_t num_warm_loops +, size_t num_trials +, size_t num_warm_loops ) { if (LINE_ENDING_NONE == spec.ending && 1u != spec.num_lines) @@ -520,9 +434,9 @@ scenario_readline( return true; } - char const* const path = TEST_FILE_NAME; - std::size_t const line_len = spec.line_len; - instance_mode_t const mode = spec.mode; + char const* const path = TEST_FILE_NAME; + size_t const line_len = spec.line_len; + instance_mode_t const mode = spec.mode; run_result const cs = time_iterations(num_trials, num_warm_loops, [path, line_len, mode]() -> std::uint64_t { return read_all_cstring(path, line_len, mode); @@ -566,8 +480,8 @@ int main(int /*argc*/, char* /*argv*/[]) { #ifdef HAS_P99 - std::size_t const num_trials = cstring_perf::default_file_trials(); - std::size_t const num_warm_loops = cstring_perf::default_warmups(); + size_t const num_trials = cstring_perf::default_file_trials(); + size_t const num_warm_loops = cstring_perf::default_warmups(); cstring_perf::display_banner("test.performance.cstring_readline"); @@ -582,7 +496,7 @@ int main(int /*argc*/, char* /*argv*/[]) cstring_perf::display_results_title(); - for (std::size_t i = 0; STLSOFT_NUM_ELEMENTS(CASES) != i; ++i) + for (size_t i = 0; STLSOFT_NUM_ELEMENTS(CASES) != i; ++i) { if (!scenario_readline(CASES[i], num_trials, num_warm_loops)) { diff --git a/test/performance/test.performance.cstring_vector/CMakeLists.txt b/test/performance/cstring_vector/CMakeLists.txt similarity index 100% rename from test/performance/test.performance.cstring_vector/CMakeLists.txt rename to test/performance/cstring_vector/CMakeLists.txt diff --git a/test/performance/test.performance.cstring_vector/main.cpp b/test/performance/cstring_vector/main.cpp similarity index 77% rename from test/performance/test.performance.cstring_vector/main.cpp rename to test/performance/cstring_vector/main.cpp index 5de587b..b30cfe1 100644 --- a/test/performance/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: 2nd October 2026 + * Updated: 3rd October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -62,104 +62,39 @@ namespace { using namespace cstring_perf; -struct run_result -{ - interval_t tm_ns; - std::uint64_t anchor; -#ifdef HAS_P99 - - p99::histogram hist; -#endif /* HAS_P99 */ -}; - -template -run_result -time_iterations( - std::size_t num_iterations -, std::size_t num_warm_loops -, F fn -) -{ - run_result result = {}; - stopwatch_t sw; - - for (std::size_t w = num_warm_loops; 0 != w; --w) - { - result.anchor = 0; -#ifdef HAS_P99 - - result.hist.clear(); -#endif /* HAS_P99 */ - interval_t tm_ns = 0; -#ifdef HAS_P99 - - for (std::size_t i = 0; num_iterations != i; ++i) - { - sw.start(); - result.anchor += fn(); - sw.stop(); - - interval_t const sample = sw.get_nanoseconds(); - - tm_ns += sample; - (void)result.hist.push_ns(static_cast(sample)); - } -#else /* ? HAS_P99 */ - - sw.start(); - for (std::size_t i = 0; num_iterations != i; ++i) - { - result.anchor += fn(); - } - sw.stop(); - tm_ns = sw.get_nanoseconds(); -#endif /* HAS_P99 */ - - if (1 == w) - { - result.tm_ns = tm_ns; - } - } - - return result; -} - void emit_row( char const* scenario -, std::size_t size +, size_t size , char const* impl -, std::size_t num_iterations -, std::size_t num_actions +, size_t num_iterations +, size_t num_actions , run_result const& r , interval_t cstring_tm_ns ) { - double const ratio = - (0 == ::strcmp(impl, IMPL_CSTRING_VECTOR) || - 0 == ::strcmp(impl, IMPL_READLINES)) - ? 1.0 - : ratio_or_dash(r.tm_ns, cstring_tm_ns) - ; + char const* const baselines[] = + { + IMPL_CSTRING_VECTOR, + IMPL_READLINES, + }; - display_results( + cstring_perf::emit_row( scenario , size , impl , num_iterations , num_actions - , r.tm_ns - , ratio - , r.anchor -#ifdef HAS_P99 - , &r.hist -#endif /* HAS_P99 */ + , r + , cstring_tm_ns + , baselines + , STLSOFT_NUM_ELEMENTS(baselines) ); } cstring_t make_cstring_payload( - std::size_t n + size_t n ) { std::string const s = make_payload(n); @@ -180,9 +115,9 @@ namespace { void scenario_create_destroy( - std::size_t n -, std::size_t num_iterations -, std::size_t num_warm_loops + size_t n +, size_t num_iterations +, size_t num_warm_loops ) { run_result const cs = time_iterations(num_iterations, num_warm_loops, [n]() -> std::uint64_t { @@ -203,7 +138,7 @@ scenario_create_destroy( raw_vec_init(&v); raw_vec_reserve(&v, n); v.len = n; - for (std::size_t i = 0; n != i; ++i) + for (size_t i = 0; n != i; ++i) { v.ptr[i] = static_cast(std::calloc(1, 1)); } @@ -219,10 +154,10 @@ scenario_create_destroy( void scenario_append_one_by_one( - std::size_t payload_len -, std::size_t k -, std::size_t num_iterations -, std::size_t num_warm_loops + size_t payload_len +, size_t k +, size_t num_iterations +, size_t num_warm_loops ) { cstring_t payload = make_cstring_payload(payload_len); @@ -231,7 +166,7 @@ scenario_append_one_by_one( run_result const cs = time_iterations(num_iterations, num_warm_loops, [&payload, k]() -> std::uint64_t { cstring_vector_t v = cstring_vector_t_DEFAULT; cstring_vector_init(&v, 0); - for (std::size_t i = 0; k != i; ++i) + for (size_t i = 0; k != i; ++i) { cstring_vector_append(&v, &payload, 1); } @@ -242,7 +177,7 @@ scenario_append_one_by_one( run_result const st = time_iterations(num_iterations, num_warm_loops, [&payload_s, k]() -> std::uint64_t { std::vector v; - for (std::size_t i = 0; k != i; ++i) + for (size_t i = 0; k != i; ++i) { v.push_back(payload_s); } @@ -252,7 +187,7 @@ scenario_append_one_by_one( run_result const raw = time_iterations(num_iterations, num_warm_loops, [&payload_s, payload_len, k]() -> std::uint64_t { raw_string_vector v; raw_vec_init(&v); - for (std::size_t i = 0; k != i; ++i) + for (size_t i = 0; k != i; ++i) { raw_vec_append_cstr(&v, payload_s.data(), payload_len); } @@ -270,17 +205,17 @@ scenario_append_one_by_one( void scenario_append_batch( - std::size_t payload_len -, std::size_t k -, std::size_t num_iterations -, std::size_t num_warm_loops + size_t payload_len +, size_t k +, size_t num_iterations +, size_t num_warm_loops ) { std::vector payloads(k); std::vector payloads_s(k); std::string const proto = make_payload(payload_len); - for (std::size_t i = 0; k != i; ++i) + for (size_t i = 0; k != i; ++i) { payloads[i] = cstring_t_DEFAULT; cstring_createLen(&payloads[i], proto.data(), payload_len); @@ -306,7 +241,7 @@ scenario_append_batch( raw_string_vector v; raw_vec_init(&v); raw_vec_reserve(&v, k); - for (std::size_t i = 0; k != i; ++i) + for (size_t i = 0; k != i; ++i) { raw_vec_append_cstr(&v, payloads_s[i].data(), payload_len); } @@ -315,7 +250,7 @@ scenario_append_batch( return a; }); - for (std::size_t i = 0; k != i; ++i) + for (size_t i = 0; k != i; ++i) { cstring_destroy(&payloads[i]); } @@ -327,10 +262,10 @@ scenario_append_batch( void scenario_prepend_one_by_one( - std::size_t payload_len -, std::size_t k -, std::size_t num_iterations -, std::size_t num_warm_loops + size_t payload_len +, size_t k +, size_t num_iterations +, size_t num_warm_loops ) { cstring_t payload = make_cstring_payload(payload_len); @@ -339,7 +274,7 @@ scenario_prepend_one_by_one( run_result const cs = time_iterations(num_iterations, num_warm_loops, [&payload, k]() -> std::uint64_t { cstring_vector_t v = cstring_vector_t_DEFAULT; cstring_vector_init(&v, 0); - for (std::size_t i = 0; k != i; ++i) + for (size_t i = 0; k != i; ++i) { cstring_vector_prepend(&v, &payload, 1); } @@ -350,7 +285,7 @@ scenario_prepend_one_by_one( run_result const st = time_iterations(num_iterations, num_warm_loops, [&payload_s, k]() -> std::uint64_t { std::vector v; - for (std::size_t i = 0; k != i; ++i) + for (size_t i = 0; k != i; ++i) { v.insert(v.begin(), payload_s); } @@ -360,7 +295,7 @@ scenario_prepend_one_by_one( run_result const raw = time_iterations(num_iterations, num_warm_loops, [&payload_s, payload_len, k]() -> std::uint64_t { raw_string_vector v; raw_vec_init(&v); - for (std::size_t i = 0; k != i; ++i) + for (size_t i = 0; k != i; ++i) { raw_vec_prepend_cstr(&v, payload_s.data(), payload_len); } @@ -377,39 +312,15 @@ scenario_prepend_one_by_one( } #ifdef HAS_P99 -bool -write_lines_file( - char const* path -, std::size_t num_lines -, std::size_t line_len -) -{ - std::ofstream out(path, std::ios::binary | std::ios::trunc); - - if (!out) - { - return false; - } - - std::string const line = make_payload(line_len); - - for (std::size_t i = 0; num_lines != i; ++i) - { - out << line << '\n'; - } - - return static_cast(out); -} - void scenario_file_lines_vs_readlines( - std::size_t num_lines -, std::size_t line_len -, std::size_t num_trials -, std::size_t num_warm_loops + size_t num_lines +, size_t line_len +, size_t num_trials +, size_t num_warm_loops ) { - if (!write_lines_file(TEST_FILE_NAME, num_lines, line_len)) + if (!write_lines_file(TEST_FILE_NAME, num_lines, line_len, LINE_ENDING_LF)) { std::cerr << "failed to write " << TEST_FILE_NAME << "; skipping file_lines suite" @@ -482,9 +393,9 @@ scenario_file_lines_vs_readlines( int main(int /*argc*/, char* /*argv*/[]) { - std::size_t const num_iterations = cstring_perf::default_iterations(); - std::size_t const num_warm_loops = cstring_perf::default_warmups(); - std::size_t const heavy_iters = + size_t const num_iterations = cstring_perf::default_iterations(); + size_t const num_warm_loops = cstring_perf::default_warmups(); + size_t const heavy_iters = (num_iterations > 10000u) ? (num_iterations / 10u) : num_iterations ; @@ -503,7 +414,7 @@ int main(int /*argc*/, char* /*argv*/[]) scenario_prepend_one_by_one(16u, 32u, heavy_iters, num_warm_loops); #ifdef HAS_P99 - std::size_t const file_trials = cstring_perf::default_file_trials(); + size_t const file_trials = cstring_perf::default_file_trials(); scenario_file_lines_vs_readlines(1000u, 64u, file_trials, num_warm_loops); scenario_file_lines_vs_readlines(5000u, 80u, file_trials, num_warm_loops); diff --git a/test/performance/perf_harness.hpp b/test/performance/perf_harness.hpp index 24a6eca..8f24f17 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: 29th September 2026 + * Updated: 3rd October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -27,6 +27,7 @@ # include #endif +#include #include #include #include @@ -148,6 +149,120 @@ make_payload( } +/* ///////////////////////////////////////////////////////////////////////// + * filesystem fixtures + */ + +enum line_ending_t +{ + LINE_ENDING_LF = 0, + LINE_ENDING_CRLF, + LINE_ENDING_NONE, +}; + +inline +bool +write_lines_file( + char const* path +, size_t num_lines +, size_t line_len +, line_ending_t ending +) +{ + std::ofstream out(path, std::ios::binary | std::ios::trunc); + + if (!out) + { + return false; + } + + std::string const line = make_payload(line_len); + + for (size_t i = 0; num_lines != i; ++i) + { + out.write(line.data(), static_cast(line.size())); + + if (LINE_ENDING_CRLF == ending) + { + out.write("\r\n", 2); + } + else if (LINE_ENDING_LF == ending) + { + out.put('\n'); + } + } + + return static_cast(out); +} + + +/* ///////////////////////////////////////////////////////////////////////// + * timing + */ + +struct run_result +{ + interval_t tm_ns; + std::uint64_t anchor; +#ifdef HAS_P99 + + p99::histogram hist; +#endif /* HAS_P99 */ +}; + +template +run_result +time_iterations( + size_t num_iterations +, size_t num_warm_loops +, F fn +) +{ + run_result result = {}; + stopwatch_t sw; + + for (size_t w = num_warm_loops; 0 != w; --w) + { + result.anchor = 0; +#ifdef HAS_P99 + + result.hist.clear(); +#endif /* HAS_P99 */ + interval_t tm_ns = 0; +#ifdef HAS_P99 + + for (size_t i = 0; num_iterations != i; ++i) + { + sw.start(); + result.anchor += fn(); + sw.stop(); + + interval_t const sample = sw.get_nanoseconds(); + + tm_ns += sample; + (void)result.hist.push_ns(static_cast(sample)); + } +#else /* ? HAS_P99 */ + + sw.start(); + for (size_t i = 0; num_iterations != i; ++i) + { + result.anchor += fn(); + } + sw.stop(); + tm_ns = sw.get_nanoseconds(); +#endif /* HAS_P99 */ + + if (1 == w) + { + result.tm_ns = tm_ns; + } + } + + return result; +} + + /* ///////////////////////////////////////////////////////////////////////// * display */ @@ -417,6 +532,63 @@ ratio_or_dash( return static_cast(subject_ns) / static_cast(baseline_ns); } +inline +bool +impl_is_baseline( + char const* impl +, char const* const* baseline_impls +, size_t num_baselines +) +{ + for (size_t i = 0; num_baselines != i; ++i) + { + if (0 == ::strcmp(impl, baseline_impls[i])) + { + return true; + } + } + + return false; +} + +/* Baseline impl names are ratio 1.0. Every other impl is timed against + * baseline_ns. + */ +inline +void +emit_row( + char const* scenario +, size_t size +, char const* impl +, size_t num_iterations +, size_t num_actions +, run_result const& r +, interval_t baseline_ns +, char const* const* baseline_impls +, size_t num_baselines +) +{ + double const ratio = + impl_is_baseline(impl, baseline_impls, num_baselines) + ? 1.0 + : ratio_or_dash(r.tm_ns, baseline_ns) + ; + + display_results( + scenario + , size + , impl + , num_iterations + , num_actions + , r.tm_ns + , ratio + , r.anchor +#ifdef HAS_P99 + , &r.hist +#endif /* HAS_P99 */ + ); +} + /* ///////////////////////////////////////////////////////////////////////// * hand-rolled string (competitive floor) diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 5fb1b6f..3ea2254 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -1,12 +1,12 @@ +add_subdirectory(cstring_getStatusCodeString) +add_subdirectory(cstring_vector) +add_subdirectory(cstring.borrowed) if(NOT NO_CSTRING_CPP_API) - add_subdirectory(test.unit.cstring.1) + add_subdirectory(cstring.instance) endif() -add_subdirectory(test.unit.cstring.2) -add_subdirectory(test.unit.cstring.auto_buffer) -add_subdirectory(test.unit.cstring.cstring_getStatusCodeString) -add_subdirectory(test.unit.cstring.cstring_vector) +add_subdirectory(cstring.insert-replace) # ############################## end of file ############################# # diff --git a/test/unit/test.unit.cstring.2/CMakeLists.txt b/test/unit/cstring.borrowed/CMakeLists.txt similarity index 58% rename from test/unit/test.unit.cstring.2/CMakeLists.txt rename to test/unit/cstring.borrowed/CMakeLists.txt index 1912881..ae01815 100644 --- a/test/unit/test.unit.cstring.2/CMakeLists.txt +++ b/test/unit/cstring.borrowed/CMakeLists.txt @@ -1,2 +1,2 @@ # SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten -define_automated_test_program(test_unit_cstring_2 entry.c) +define_automated_test_program(test.unit.cstring.borrowed entry.c) diff --git a/test/unit/cstring.borrowed/entry.c b/test/unit/cstring.borrowed/entry.c new file mode 100644 index 0000000..4e923da --- /dev/null +++ b/test/unit/cstring.borrowed/entry.c @@ -0,0 +1,506 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/unit/cstring.borrowed/entry.c + * + * Purpose: Tests borrowed string functionality. + * + * Created: 28th July 2011 + * Updated: 3rd October 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +/* ///////////////////////////////////// + * test component header file include(s) + */ + +#include + +/* ///////////////////////////////////// + * general includes + */ + +/* xTests header files */ +#include + +/* STLSoft header files */ +#include + +/* Standard C header files */ +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * forward declarations + */ + +static void TEST_cstring_createEx_FLAG_COMBINATIONS(void); +static void TEST_cstring_createEx_GROW_TO_HEAP(void); +static void TEST_cstring_createLenEx_GROW_TO_HEAP(void); +static void TEST_cstring_setCapacity_GROW_TO_HEAP(void); +static void TEST_cstring_assign_GROW_TO_HEAP(void); +static void TEST_cstring_assignLen_GROW_TO_HEAP(void); +static void TEST_cstring_append_GROW_TO_HEAP(void); +static void TEST_cstring_appendLen_GROW_TO_HEAP(void); + + +/* ///////////////////////////////////////////////////////////////////////// + * constants & definitions + */ + + +/* ///////////////////////////////////////////////////////////////////////// + * compiler compatibility + */ + +#if defined(STLSOFT_COMPILER_IS_MSVC) +# if _MSC_VER >= 1200 +# pragma warning(push) +# endif /* compiler */ +# pragma warning(disable : 4702) +#endif /* compiler */ + + +/* ///////////////////////////////////////////////////////////////////////// + * main + */ + +int main(int argc, char **argv) +{ + int retCode = EXIT_SUCCESS; + int verbosity = 2; + + XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); + + if (XTESTS_START_RUNNER("cstring.borrowed", verbosity)) + { + XTESTS_RUN_CASE(TEST_cstring_createEx_FLAG_COMBINATIONS); + XTESTS_RUN_CASE(TEST_cstring_createEx_GROW_TO_HEAP); + XTESTS_RUN_CASE(TEST_cstring_createLenEx_GROW_TO_HEAP); + XTESTS_RUN_CASE(TEST_cstring_setCapacity_GROW_TO_HEAP); + XTESTS_RUN_CASE(TEST_cstring_assign_GROW_TO_HEAP); + XTESTS_RUN_CASE(TEST_cstring_assignLen_GROW_TO_HEAP); + XTESTS_RUN_CASE(TEST_cstring_append_GROW_TO_HEAP); + XTESTS_RUN_CASE(TEST_cstring_appendLen_GROW_TO_HEAP); + + XTESTS_PRINT_RESULTS(); + + XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); + } + + return retCode; +} + + +/* ///////////////////////////////////////////////////////////////////////// + * compiler compatibility + */ + +#if defined(STLSOFT_COMPILER_IS_MSVC) +# if _MSC_VER >= 1200 +# pragma warning(pop) +# endif /* compiler */ +#endif /* compiler */ + + +/* ///////////////////////////////////////////////////////////////////////// + * test function implementations + */ + +static void TEST_cstring_createEx_FLAG_COMBINATIONS(void) +{ + static int const goods[] = + { + CSTRING_F_MEMORY_IS_BORROWED + , CSTRING_F_MEMORY_IS_BORROWED | CSTRING_F_MEMORY_IS_FIXED + , CSTRING_F_MEMORY_IS_BORROWED | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP + }; + + static int const bads[] = + { + 0 + , CSTRING_F_MEMORY_IS_FIXED + , CSTRING_F_MEMORY_IS_OFFSET + }; + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(goods); ++i) + { + int const flags = 0 + | goods[i] + ; + char buff[10]; + cstring_t cs; + CSTRING_RC rc; + + rc = cstring_createEx(&cs, "string: ", flags, buff, sizeof(buff)); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + }} + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(bads); ++i) + { + int const flags = 0 + | bads[i] + ; + char buff[10]; + cstring_t cs; + CSTRING_RC rc; + + rc = cstring_createEx(&cs, "string: ", flags, buff, sizeof(buff)); + + TEST_ENUM_NE(CSTRING_RC_SUCCESS, rc); + }} +} + +static void TEST_cstring_createEx_GROW_TO_HEAP(void) +{ + int const flags = 0 + | CSTRING_F_MEMORY_IS_BORROWED + | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP + ; + char buff[8]; + cstring_t cs; + CSTRING_RC rc; + + + /* must succeed in stack buffer. */ + + rc = cstring_createEx(&cs, "small", flags, buff, sizeof(buff)); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + TEST_INT_EQ(5u, cs.len); + TEST_INT_EQ(sizeof(buff) - 1u, cs.capacity); + + cstring_destroy(&cs); + + + /* try to resize. */ + + rc = cstring_createEx(&cs, "oversized", flags, buff, sizeof(buff)); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_ENUM_EQ(CSTRING_RC_OUTOFMEMORY, rc); + TEST_INT_EQ(flags, cs.flags); + TEST_INT_EQ(5u, cs.len); + TEST_PTR_EQ(buff, cs.ptr); + TEST_MS_EQ("small", cs.ptr); + TEST_INT_EQ(sizeof(buff) - 1u, cs.capacity); + } + else + { + TEST_INT_EQ(0, cs.flags); + TEST_MS_EQ("oversized", cs.ptr); + TEST_PTR_NE(buff, cs.ptr); + + cstring_destroy(&cs); + } +} + +static void TEST_cstring_createLenEx_GROW_TO_HEAP(void) +{ + int const flags = 0 + | CSTRING_F_MEMORY_IS_BORROWED + | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP + ; + char buff[8]; + cstring_t cs; + CSTRING_RC rc; + + + /* must succeed in stack buffer. */ + + rc = cstring_createEx(&cs, "small", flags, buff, sizeof(buff)); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + TEST_INT_EQ(5u, cs.len); + TEST_INT_EQ(sizeof(buff) - 1u, cs.capacity); + + cstring_destroy(&cs); + + + /* try to resize. */ + + rc = cstring_createLenEx(&cs, "oversized string", 9, flags, buff, sizeof(buff)); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_ENUM_EQ(CSTRING_RC_OUTOFMEMORY, rc); + TEST_INT_EQ(flags, cs.flags); + TEST_INT_EQ(5u, cs.len); + TEST_PTR_EQ(buff, cs.ptr); + TEST_MS_EQ("small", cs.ptr); + TEST_INT_EQ(sizeof(buff) - 1u, cs.capacity); + } + else + { + TEST_INT_EQ(0, cs.flags); + TEST_MS_EQ("oversized", cs.ptr); + TEST_PTR_NE(buff, cs.ptr); + + cstring_destroy(&cs); + } +} + +static void TEST_cstring_setCapacity_GROW_TO_HEAP(void) +{ + int const flags = 0 + | CSTRING_F_MEMORY_IS_BORROWED + | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP + ; + char buff[8]; + cstring_t cs; + CSTRING_RC rc; + + + /* must succeed in stack buffer. */ + + rc = cstring_createEx(&cs, "small", flags, buff, sizeof(buff)); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + TEST_INT_EQ(5u, cs.len); + TEST_INT_EQ(sizeof(buff) - 1u, cs.capacity); + + + /* try to resize. */ + + rc = cstring_setCapacity(&cs, 9); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_ENUM_EQ(CSTRING_RC_OUTOFMEMORY, rc); + TEST_INT_EQ(flags, cs.flags); + TEST_INT_EQ(5u, cs.len); + TEST_PTR_EQ(buff, cs.ptr); + TEST_MS_EQ("small", cs.ptr); + TEST_INT_EQ(sizeof(buff) - 1u, cs.capacity); + } + else + { + TEST_INT_EQ(0, cs.flags); + TEST_INT_EQ(5u, cs.len); + TEST_PTR_NE(buff, cs.ptr); + TEST_MS_EQ("small", cs.ptr); + TEST_INT_GE(9u - 1u, cs.capacity); + } + + cstring_destroy(&cs); +} + +static void TEST_cstring_assign_GROW_TO_HEAP(void) +{ + int const flags = 0 + | CSTRING_F_MEMORY_IS_BORROWED + | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP + ; + char buff[8]; + cstring_t cs; + CSTRING_RC rc; + + + /* must succeed in stack buffer. */ + + rc = cstring_createEx(&cs, "small", flags, buff, sizeof(buff)); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + TEST_INT_EQ(5u, cs.len); + TEST_INT_EQ(sizeof(buff) - 1u, cs.capacity); + + + /* try to resize. */ + + rc = cstring_assign(&cs, "oversized"); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_ENUM_EQ(CSTRING_RC_OUTOFMEMORY, rc); + TEST_INT_EQ(flags, cs.flags); + TEST_INT_EQ(5u, cs.len); + TEST_PTR_EQ(buff, cs.ptr); + TEST_MS_EQ("small", cs.ptr); + TEST_INT_EQ(sizeof(buff) - 1u, cs.capacity); + } + else + { + TEST_INT_EQ(0, cs.flags); + TEST_MS_EQ("oversized", cs.ptr); + TEST_PTR_NE(buff, cs.ptr); + + + /* shrink again */ + rc = cstring_assign(&cs, "small"); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + TEST_INT_EQ(0, cs.flags); + TEST_INT_EQ(5u, cs.len); + TEST_PTR_NE(buff, cs.ptr); + TEST_MS_EQ("small", cs.ptr); + } + + cstring_destroy(&cs); +} + +static void TEST_cstring_assignLen_GROW_TO_HEAP(void) +{ + int const flags = 0 + | CSTRING_F_MEMORY_IS_BORROWED + | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP + ; + char buff[8]; + cstring_t cs; + CSTRING_RC rc; + + + /* must succeed in stack buffer. */ + + rc = cstring_createEx(&cs, "small", flags, buff, sizeof(buff)); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + TEST_INT_EQ(5u, cs.len); + TEST_INT_EQ(sizeof(buff) - 1u, cs.capacity); + + + /* try to resize. */ + + rc = cstring_assignLen(&cs, "oversized", 9); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_ENUM_EQ(CSTRING_RC_OUTOFMEMORY, rc); + TEST_INT_EQ(flags, cs.flags); + TEST_INT_EQ(5u, cs.len); + TEST_PTR_EQ(buff, cs.ptr); + TEST_MS_EQ("small", cs.ptr); + TEST_INT_EQ(sizeof(buff) - 1u, cs.capacity); + } + else + { + TEST_INT_EQ(0, cs.flags); + TEST_MS_EQ("oversized", cs.ptr); + TEST_PTR_NE(buff, cs.ptr); + + + /* shrink again */ + rc = cstring_assign(&cs, "small"); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + TEST_INT_EQ(0, cs.flags); + TEST_INT_EQ(5u, cs.len); + TEST_PTR_NE(buff, cs.ptr); + TEST_MS_EQ("small", cs.ptr); + } + + cstring_destroy(&cs); +} + +static void TEST_cstring_append_GROW_TO_HEAP(void) +{ + int const flags = 0 + | CSTRING_F_MEMORY_IS_BORROWED + | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP + ; + char buff[8]; + cstring_t cs; + CSTRING_RC rc; + + + /* must succeed in stack buffer. */ + + rc = cstring_createEx(&cs, "small", flags, buff, sizeof(buff)); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + TEST_INT_EQ(5u, cs.len); + TEST_INT_EQ(sizeof(buff) - 1u, cs.capacity); + + + /* try to resize. */ + + rc = cstring_append(&cs, "ish, but big enough"); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_ENUM_EQ(CSTRING_RC_OUTOFMEMORY, rc); + TEST_INT_EQ(flags, cs.flags); + TEST_INT_EQ(5u, cs.len); + TEST_PTR_EQ(buff, cs.ptr); + TEST_MS_EQ("small", cs.ptr); + TEST_INT_EQ(sizeof(buff) - 1u, cs.capacity); + } + else + { + TEST_INT_EQ(0, cs.flags); + TEST_MS_EQ("smallish, but big enough", cs.ptr); + TEST_PTR_NE(buff, cs.ptr); + + + /* shrink again */ + rc = cstring_assign(&cs, "small"); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + TEST_INT_EQ(0, cs.flags); + TEST_INT_EQ(5u, cs.len); + TEST_PTR_NE(buff, cs.ptr); + TEST_MS_EQ("small", cs.ptr); + } + + cstring_destroy(&cs); +} + +static void TEST_cstring_appendLen_GROW_TO_HEAP(void) +{ + int const flags = 0 + | CSTRING_F_MEMORY_IS_BORROWED + | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP + ; + char buff[8]; + cstring_t cs; + CSTRING_RC rc; + + + /* must succeed in stack buffer. */ + + rc = cstring_createEx(&cs, "small", flags, buff, sizeof(buff)); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + TEST_INT_EQ(5u, cs.len); + TEST_INT_EQ(sizeof(buff) - 1u, cs.capacity); + + + /* try to resize. */ + + rc = cstring_appendLen(&cs, "ish, but big enough", 19); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_ENUM_EQ(CSTRING_RC_OUTOFMEMORY, rc); + TEST_INT_EQ(flags, cs.flags); + TEST_INT_EQ(5u, cs.len); + TEST_PTR_EQ(buff, cs.ptr); + TEST_MS_EQ("small", cs.ptr); + TEST_INT_EQ(sizeof(buff) - 1u, cs.capacity); + } + else + { + TEST_INT_EQ(0, cs.flags); + TEST_MS_EQ("smallish, but big enough", cs.ptr); + TEST_PTR_NE(buff, cs.ptr); + + + /* shrink again */ + rc = cstring_assign(&cs, "small"); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + TEST_INT_EQ(0, cs.flags); + TEST_INT_EQ(5u, cs.len); + TEST_PTR_NE(buff, cs.ptr); + TEST_MS_EQ("small", cs.ptr); + } + + cstring_destroy(&cs); +} + + +/* ///////////////////////////// end of file //////////////////////////// */ diff --git a/test/unit/test.unit.cstring.auto_buffer/CMakeLists.txt b/test/unit/cstring.insert-replace/CMakeLists.txt similarity index 56% rename from test/unit/test.unit.cstring.auto_buffer/CMakeLists.txt rename to test/unit/cstring.insert-replace/CMakeLists.txt index 6bfad14..12ea3b4 100644 --- a/test/unit/test.unit.cstring.auto_buffer/CMakeLists.txt +++ b/test/unit/cstring.insert-replace/CMakeLists.txt @@ -1,2 +1,2 @@ # SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten -define_automated_test_program(test_unit_cstring_auto_buffer entry.c) +define_automated_test_program(test.unit.cstring.insert-replace entry.c) diff --git a/test/unit/test.unit.cstring.2/entry.c b/test/unit/cstring.insert-replace/entry.c similarity index 82% rename from test/unit/test.unit.cstring.2/entry.c rename to test/unit/cstring.insert-replace/entry.c index 4603462..bcfbf2a 100644 --- a/test/unit/test.unit.cstring.2/entry.c +++ b/test/unit/cstring.insert-replace/entry.c @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: test.unit.cstring.2/entry.c + * File: test/unit/insert-replace/entry.c * - * Purpose: Unit-tests for general functionality. + * Purpose: Unit-tests for cstring insert and replace functionality. * * Created: 4th June 2009 - * Updated: 2nd August 2026 + * Updated: 3rd October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -45,22 +45,20 @@ * forward declarations */ -static void test_createN(void); -static void test_insert(void); -static void test_insertLen(void); -static void test_replace(void); -static void test_replaceLen(void); -static void test_replaceAll_1(void); -static void test_replaceAll_2(void); -static void test_replaceAll_3(void); +static void TEST_cstring_createN(void); +static void TEST_cstring_insert(void); +static void TEST_cstring_insertLen(void); +static void TEST_cstring_replace(void); +static void TEST_cstring_replaceLen(void); +static void TEST_cstring_replaceAll_EMPTY(void); +static void TEST_cstring_replaceAll_SAME_LENGTH(void); +static void TEST_cstring_replaceAll_CHAINED(void); /* ///////////////////////////////////////////////////////////////////////// * constants & definitions */ -static char const TEST_FILE_NAME[] = "test.unit.cstring.2.txt"; - static char const alphabet[] = "abcdefghijklmnopqrstuvwxyz"; @@ -89,22 +87,20 @@ int main(int argc, char **argv) if (XTESTS_START_RUNNER("test.unit.cstring.2", verbosity)) { - XTESTS_RUN_CASE(test_createN); - XTESTS_RUN_CASE(test_insert); - XTESTS_RUN_CASE(test_insertLen); - XTESTS_RUN_CASE(test_replace); - XTESTS_RUN_CASE(test_replaceLen); - XTESTS_RUN_CASE(test_replaceAll_1); - XTESTS_RUN_CASE(test_replaceAll_2); - XTESTS_RUN_CASE(test_replaceAll_3); + XTESTS_RUN_CASE(TEST_cstring_createN); + XTESTS_RUN_CASE(TEST_cstring_insert); + XTESTS_RUN_CASE(TEST_cstring_insertLen); + XTESTS_RUN_CASE(TEST_cstring_replace); + XTESTS_RUN_CASE(TEST_cstring_replaceLen); + XTESTS_RUN_CASE(TEST_cstring_replaceAll_EMPTY); + XTESTS_RUN_CASE(TEST_cstring_replaceAll_SAME_LENGTH); + XTESTS_RUN_CASE(TEST_cstring_replaceAll_CHAINED); XTESTS_PRINT_RESULTS(); XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); } - STLSOFT_SUPPRESS_UNUSED(TEST_FILE_NAME); - return retCode; } @@ -120,11 +116,16 @@ int main(int argc, char **argv) #endif /* compiler */ +/* ///////////////////////////////////////////////////////////////////////// + * helper functions + */ + + /* ///////////////////////////////////////////////////////////////////////// * test function implementations */ -static void test_createN(void) +static void TEST_cstring_createN(void) { { for (size_t volatile i = 0; i != 1000000u; i = (0u == i) ? 1u : i * 10u) { @@ -157,12 +158,12 @@ static void test_createN(void) } else { - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_OUTOFMEMORY, rc); + TEST_ENUM_EQ(CSTRING_RC_OUTOFMEMORY, rc); } }} } -static void test_insert(void) +static void TEST_cstring_insert(void) { { /* Forwards */ @@ -178,7 +179,7 @@ static void test_insert(void) char const sz[2] = { alphabet[i], '\0' }; cstring_insert(&str, (int)i, sz); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL_N(alphabet, str.ptr, (int)i); + TEST_MS_EQ_N(alphabet, str.ptr, (int)i); }} cstring_destroy(&str); @@ -203,7 +204,7 @@ static void test_insert(void) char const sz[2] = { alphabet[(STLSOFT_NUM_ELEMENTS(alphabet) - 1) - (1 + i)], '\0' }; cstring_insert(&str, 0, sz); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL_N(alphabet + ((STLSOFT_NUM_ELEMENTS(alphabet) - 1) - (1 + i)), (char const*)str.ptr, (int)i); + TEST_MS_EQ_N(alphabet + ((STLSOFT_NUM_ELEMENTS(alphabet) - 1) - (1 + i)), (char const*)str.ptr, (int)i); }} cstring_destroy(&str); @@ -215,7 +216,7 @@ static void test_insert(void) } } -static void test_insertLen(void) +static void TEST_cstring_insertLen(void) { cstring_t str = cstring_t_DEFAULT; @@ -287,7 +288,7 @@ static void test_insertLen(void) TEST_INT_EQ(0, str.flags); } -static void test_replace(void) +static void TEST_cstring_replace(void) { cstring_t str = cstring_t_DEFAULT; @@ -317,14 +318,14 @@ static void test_replace(void) TEST_INT_EQ(0, str.flags); } -static void test_replaceLen(void) +static void TEST_cstring_replaceLen(void) { cstring_t str; CSTRING_RC rc = cstring_create(&str, "abcdefghijklmnopqrstuvwxyz"); if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not create string", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not create string", cstring_getStatusCodeString(rc)); } else { @@ -354,7 +355,7 @@ static void test_replaceLen(void) } } -static void test_replaceAll_1(void) +static void TEST_cstring_replaceAll_EMPTY(void) { { cstring_t str = cstring_t_DEFAULT; @@ -363,7 +364,7 @@ static void test_replaceAll_1(void) rc = cstring_replaceAll(&str, NULL, NULL, NULL); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(0u, str.len); TEST_PTR_EQ(NULL, str.ptr); TEST_INT_EQ(0u, str.capacity); @@ -371,7 +372,7 @@ static void test_replaceAll_1(void) rc = cstring_replaceAll(&str, NULL, NULL, &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(0u, str.len); TEST_PTR_EQ(NULL, str.ptr); TEST_INT_EQ(0u, str.capacity); @@ -380,7 +381,7 @@ static void test_replaceAll_1(void) rc = cstring_replaceAll(&str, "abc", "def", &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(0u, str.len); TEST_PTR_EQ(NULL, str.ptr); TEST_INT_EQ(0u, str.capacity); @@ -396,18 +397,18 @@ static void test_replaceAll_1(void) } } -static void test_replaceAll_2(void) +static void TEST_cstring_replaceAll_SAME_LENGTH(void) { { cstring_t str; CSTRING_RC rc = cstring_create(&str, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"); size_t n; - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); rc = cstring_replaceAll(&str, NULL, NULL, NULL); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(52u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", str.ptr); @@ -416,7 +417,7 @@ static void test_replaceAll_2(void) rc = cstring_replaceAll(&str, NULL, NULL, &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(52u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", str.ptr); @@ -426,7 +427,7 @@ static void test_replaceAll_2(void) rc = cstring_replaceAll(&str, "abc", "def", &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(52u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("defdefghijklmnopqrstuvwxyzdefdefghijklmnopqrstuvwxyz", str.ptr); @@ -443,18 +444,18 @@ static void test_replaceAll_2(void) } } -static void test_replaceAll_3(void) +static void TEST_cstring_replaceAll_CHAINED(void) { { cstring_t str; CSTRING_RC rc = cstring_create(&str, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"); size_t n; - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); rc = cstring_replaceAll(&str, NULL, NULL, NULL); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(52u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", str.ptr); @@ -463,7 +464,7 @@ static void test_replaceAll_3(void) rc = cstring_replaceAll(&str, NULL, NULL, &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(52u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", str.ptr); @@ -473,7 +474,7 @@ static void test_replaceAll_3(void) rc = cstring_replaceAll(&str, "abc", "de", &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(50u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("dedefghijklmnopqrstuvwxyzdedefghijklmnopqrstuvwxyz", str.ptr); @@ -483,7 +484,7 @@ static void test_replaceAll_3(void) rc = cstring_replaceAll(&str, "de", "g", &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(46u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("ggfghijklmnopqrstuvwxyzggfghijklmnopqrstuvwxyz", str.ptr); @@ -493,7 +494,7 @@ static void test_replaceAll_3(void) rc = cstring_replaceAll(&str, "fg", "", &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(42u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("gghijklmnopqrstuvwxyzgghijklmnopqrstuvwxyz", str.ptr); @@ -503,7 +504,7 @@ static void test_replaceAll_3(void) rc = cstring_replaceAll(&str, "g", "", &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(38u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("hijklmnopqrstuvwxyzhijklmnopqrstuvwxyz", str.ptr); @@ -513,7 +514,7 @@ static void test_replaceAll_3(void) rc = cstring_replaceAll(&str, "xyzhij", "", &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(32u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("hijklmnopqrstuvwklmnopqrstuvwxyz", str.ptr); @@ -523,7 +524,7 @@ static void test_replaceAll_3(void) rc = cstring_replaceAll(&str, "mno", "MNO", &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(32u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("hijklMNOpqrstuvwklMNOpqrstuvwxyz", str.ptr); @@ -533,7 +534,7 @@ static void test_replaceAll_3(void) rc = cstring_replaceAll(&str, "MNO", "<>", &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(40u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("hijkl<>pqrstuvwkl<>pqrstuvwxyz", str.ptr); @@ -543,7 +544,7 @@ static void test_replaceAll_3(void) rc = cstring_replaceAll(&str, ">>", ">>>", &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(42u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("hijkl<>>pqrstuvwkl<>>pqrstuvwxyz", str.ptr); @@ -553,7 +554,7 @@ static void test_replaceAll_3(void) rc = cstring_replaceAll(&str, "<<", "<< <<", &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(50u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("hijkl<< <>>pqrstuvwkl<< <>>pqrstuvwxyz", str.ptr); @@ -563,7 +564,7 @@ static void test_replaceAll_3(void) rc = cstring_replaceAll(&str, "hijkl<< <>>", NULL, &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(33u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("pqrstuvwkl<< <>>pqrstuvwxyz", str.ptr); @@ -573,7 +574,7 @@ static void test_replaceAll_3(void) rc = cstring_replaceAll(&str, "<< <>>", NULL, &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(21u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("pqrstuvwklpqrstuvwxyz", str.ptr); @@ -583,7 +584,7 @@ static void test_replaceAll_3(void) rc = cstring_replaceAll(&str, "kl", "xyz", &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(22u, str.len); TEST_PTR_NE(NULL, str.ptr); TEST_MS_EQ("pqrstuvwxyzpqrstuvwxyz", str.ptr); @@ -597,7 +598,7 @@ static void test_replaceAll_3(void) rc = cstring_replaceAll(&str, sz, NULL, &n); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(22u - 2 * (1 + i), str.len); TEST_PTR_NE(NULL, str.ptr); /* TEST_MS_EQ("pqrstuvwklpqrstuvwxyz", str.ptr); */ diff --git a/test/unit/test.unit.cstring.1/CMakeLists.txt b/test/unit/cstring.instance/CMakeLists.txt similarity index 58% rename from test/unit/test.unit.cstring.1/CMakeLists.txt rename to test/unit/cstring.instance/CMakeLists.txt index f8fb9c2..58f4b47 100644 --- a/test/unit/test.unit.cstring.1/CMakeLists.txt +++ b/test/unit/cstring.instance/CMakeLists.txt @@ -1,2 +1,2 @@ # SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten -define_automated_test_program(test_unit_cstring_1 entry.cpp) +define_automated_test_program(test.unit.cstring.instance entry.cpp) diff --git a/test/unit/test.unit.cstring.1/entry.cpp b/test/unit/cstring.instance/entry.cpp similarity index 78% rename from test/unit/test.unit.cstring.1/entry.cpp rename to test/unit/cstring.instance/entry.cpp index ac49435..1d2a1f9 100644 --- a/test/unit/test.unit.cstring.1/entry.cpp +++ b/test/unit/cstring.instance/entry.cpp @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: test.unit.cstring.1/entry.cpp + * File: test/unit/cstring.instance/entry.cpp * - * Purpose: Unit-tests for general functionality. + * Purpose: Unit-tests for cstring instance general functionality. * * Created: 23rd May 2009 - * Updated: 2nd October 2026 + * Updated: 3rd October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -51,29 +51,27 @@ namespace { - 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(); + static void TEST_cstring_t_DEFAULT(); + static void TEST_cstring_init(); + static void TEST_cstring_setCapacity(); + static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AND_EMPTY(); + static void TEST_cstring_assign_AND_cstring_create(); + static void TEST_cstring_assignLen_AND_cstring_createLen(); + static void TEST_cstring_append_AND_cstring_appendLen(); + static void TEST_cstring_truncate(); + static void TEST_cstring_appendLen_KEEPS_CAPACITY(); + + static void TEST_cstring_init_AND_cstring_destroy(); + static void TEST_cstring_create(); + static void TEST_cstring_createLen(); + static void TEST_cstring_createEx(); + static void TEST_cstring_createLenEx(); + static void TEST_cstring_assignLen_KEEPS_CAPACITY(); + static void TEST_cstring_assign_KEEPS_CAPACITY(); + static void TEST_cstring_copy(); + static void TEST_cstring_yield(); + static void TEST_cstring_yield2(); + static void TEST_cstring_swap(); } // anonymous namespace @@ -81,8 +79,6 @@ namespace * constants & definitions */ -const char TEST_FILE_NAME[] = "test.unit.cstring.1.txt"; - const size_t APPEND_ITERATIONS = 1000000u; const size_t ASSIGN_ITERATIONS = 100000u; @@ -112,37 +108,33 @@ int main(int argc, char **argv) if (XTESTS_START_RUNNER("test.unit.cstring.1", verbosity)) { - XTESTS_RUN_CASE(test_1_0); - XTESTS_RUN_CASE(test_1_1); - XTESTS_RUN_CASE(test_1_2); - XTESTS_RUN_CASE(test_1_3); - XTESTS_RUN_CASE(test_1_4); - XTESTS_RUN_CASE(test_1_5); - XTESTS_RUN_CASE(test_1_6); - XTESTS_RUN_CASE(test_1_7); - XTESTS_RUN_CASE(test_1_8); - XTESTS_RUN_CASE(test_1_9); - - XTESTS_RUN_CASE(test_2_0); - XTESTS_RUN_CASE(test_2_1); - XTESTS_RUN_CASE(test_2_2); - XTESTS_RUN_CASE(test_2_3); - XTESTS_RUN_CASE(test_2_4); - XTESTS_RUN_CASE(test_2_5); - XTESTS_RUN_CASE(test_2_6); - XTESTS_RUN_CASE(test_2_7); - XTESTS_RUN_CASE(test_2_8); - XTESTS_RUN_CASE(test_2_9); - XTESTS_RUN_CASE(test_2_10); - XTESTS_RUN_CASE(test_2_11); + XTESTS_RUN_CASE(TEST_cstring_t_DEFAULT); + XTESTS_RUN_CASE(TEST_cstring_init); + XTESTS_RUN_CASE(TEST_cstring_setCapacity); + XTESTS_RUN_CASE(TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AND_EMPTY); + XTESTS_RUN_CASE(TEST_cstring_assign_AND_cstring_create); + XTESTS_RUN_CASE(TEST_cstring_assignLen_AND_cstring_createLen); + XTESTS_RUN_CASE(TEST_cstring_append_AND_cstring_appendLen); + XTESTS_RUN_CASE(TEST_cstring_truncate); + XTESTS_RUN_CASE(TEST_cstring_appendLen_KEEPS_CAPACITY); + + XTESTS_RUN_CASE(TEST_cstring_init_AND_cstring_destroy); + XTESTS_RUN_CASE(TEST_cstring_create); + XTESTS_RUN_CASE(TEST_cstring_createLen); + XTESTS_RUN_CASE(TEST_cstring_createEx); + XTESTS_RUN_CASE(TEST_cstring_createLenEx); + XTESTS_RUN_CASE(TEST_cstring_assignLen_KEEPS_CAPACITY); + XTESTS_RUN_CASE(TEST_cstring_assign_KEEPS_CAPACITY); + XTESTS_RUN_CASE(TEST_cstring_copy); + XTESTS_RUN_CASE(TEST_cstring_yield); + XTESTS_RUN_CASE(TEST_cstring_yield2); + XTESTS_RUN_CASE(TEST_cstring_swap); XTESTS_PRINT_RESULTS(); XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); } - STLSOFT_SUPPRESS_UNUSED(TEST_FILE_NAME); - return retCode; } @@ -159,22 +151,31 @@ int main(int argc, char **argv) /* ///////////////////////////////////////////////////////////////////////// - * test function implementations + * compatibility */ #ifdef CSTRING_USE_WIDE_STRINGS # define CSTRING_T_(x) L ## x -# define XTESTS_TEST_STRING_EQUAL_ XTESTS_TEST_WIDE_STRING_EQUAL -# define XTESTS_TEST_STRING_EQUAL_N_ XTESTS_TEST_WIDE_STRING_EQUAL_N +# define TEST_STR_EQ_ TEST_WS_EQ +# define TEST_STR_EQ_N_ TEST_WS_EQ_N #else /* ? CSTRING_USE_WIDE_STRINGS */ # define CSTRING_T_(x) x -# define XTESTS_TEST_STRING_EQUAL_ XTESTS_TEST_MULTIBYTE_STRING_EQUAL -# define XTESTS_TEST_STRING_EQUAL_N_ XTESTS_TEST_MULTIBYTE_STRING_EQUAL_N +# define TEST_STR_EQ_ TEST_MS_EQ +# define TEST_STR_EQ_N_ TEST_MS_EQ_N #endif /* CSTRING_USE_WIDE_STRINGS */ +/* ///////////////////////////////////////////////////////////////////////// + * helper functions + */ + + +/* ///////////////////////////////////////////////////////////////////////// + * test function implementations + */ + namespace { @@ -187,7 +188,7 @@ namespace static const cstring_char_t alphabet[] = CSTRING_T_("abcdefghijklmnopqrstuvwxyz"); -static void test_1_0() +static void TEST_cstring_t_DEFAULT() { cstring_t str = cstring_t_DEFAULT; @@ -197,7 +198,7 @@ static void test_1_0() TEST_INT_GE(str.len, str.capacity); } -static void test_1_1() +static void TEST_cstring_init() { cstring_t str; @@ -209,14 +210,14 @@ static void test_1_1() TEST_INT_GE(str.len, str.capacity); } -static void test_1_2() +static void TEST_cstring_setCapacity() { cstring_t str = cstring_t_DEFAULT; CSTRING_RC rc = cstring_setCapacity(&str, 10); if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not set capacity", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not set capacity", cstring_getStatusCodeString(rc)); } else { @@ -234,7 +235,7 @@ static void test_1_2() } } -static void test_1_3() +static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AND_EMPTY() { { cstring_t str = cstring_t_DEFAULT; @@ -242,13 +243,13 @@ static void test_1_3() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not assign", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not assign", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(0u, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(CSTRING_T_(""), str.ptr); + TEST_STR_EQ_(CSTRING_T_(""), str.ptr); TEST_INT_GT(0u, str.capacity); TEST_INT_GE(str.len, str.capacity); @@ -267,13 +268,13 @@ static void test_1_3() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not assign", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not assign", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(0u, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(CSTRING_T_(""), str.ptr); + TEST_STR_EQ_(CSTRING_T_(""), str.ptr); TEST_INT_GT(0u, str.capacity); TEST_INT_GE(str.len, str.capacity); @@ -292,13 +293,13 @@ static void test_1_3() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not create", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not create", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(0u, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(CSTRING_T_(""), str.ptr); + TEST_STR_EQ_(CSTRING_T_(""), str.ptr); TEST_INT_GT(0u, str.capacity); TEST_INT_GE(str.len, str.capacity); @@ -317,13 +318,13 @@ static void test_1_3() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not create", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not create", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(0u, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(CSTRING_T_(""), str.ptr); + TEST_STR_EQ_(CSTRING_T_(""), str.ptr); TEST_INT_GT(0u, str.capacity); TEST_INT_GE(str.len, str.capacity); @@ -342,13 +343,13 @@ static void test_1_3() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(0u, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(CSTRING_T_(""), str.ptr); + TEST_STR_EQ_(CSTRING_T_(""), str.ptr); TEST_INT_GT(0u, str.capacity); TEST_INT_GE(str.len, str.capacity); @@ -367,13 +368,13 @@ static void test_1_3() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(0u, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(CSTRING_T_(""), str.ptr); + TEST_STR_EQ_(CSTRING_T_(""), str.ptr); TEST_INT_GT(0u, str.capacity); TEST_INT_GE(str.len, str.capacity); @@ -392,19 +393,19 @@ static void test_1_3() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(5u, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(CSTRING_T_(""), str.ptr); - XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[0]); - XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[1]); - XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[2]); - XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[3]); - XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[4]); - XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[5]); + TEST_STR_EQ_(CSTRING_T_(""), str.ptr); + TEST_CHAR_EQ('\0', str.ptr[0]); + TEST_CHAR_EQ('\0', str.ptr[1]); + TEST_CHAR_EQ('\0', str.ptr[2]); + TEST_CHAR_EQ('\0', str.ptr[3]); + TEST_CHAR_EQ('\0', str.ptr[4]); + TEST_CHAR_EQ('\0', str.ptr[5]); TEST_INT_GE(5u, str.capacity); TEST_INT_GE(str.len, str.capacity); @@ -423,19 +424,19 @@ static void test_1_3() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(5u, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(CSTRING_T_(""), str.ptr); - XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[0]); - XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[1]); - XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[2]); - XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[3]); - XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[4]); - XTESTS_TEST_CHARACTER_EQUAL('\0', str.ptr[5]); + TEST_STR_EQ_(CSTRING_T_(""), str.ptr); + TEST_CHAR_EQ('\0', str.ptr[0]); + TEST_CHAR_EQ('\0', str.ptr[1]); + TEST_CHAR_EQ('\0', str.ptr[2]); + TEST_CHAR_EQ('\0', str.ptr[3]); + TEST_CHAR_EQ('\0', str.ptr[4]); + TEST_CHAR_EQ('\0', str.ptr[5]); TEST_INT_GE(5u, str.capacity); TEST_INT_GE(str.len, str.capacity); @@ -449,7 +450,7 @@ static void test_1_3() } } -static void test_1_4() +static void TEST_cstring_assign_AND_cstring_create() { { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) { @@ -460,13 +461,13 @@ static void test_1_4() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not assign", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not assign", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(cch, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(s, stlsoft::apply_const_ptr(str.ptr)); + TEST_STR_EQ_(s, stlsoft::apply_const_ptr(str.ptr)); TEST_INT_GE(cch, str.capacity); TEST_INT_GE(str.len, str.capacity); @@ -490,13 +491,13 @@ static void test_1_4() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not assign", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not assign", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(cch, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(s, stlsoft::apply_const_ptr(str.ptr)); + TEST_STR_EQ_(s, stlsoft::apply_const_ptr(str.ptr)); TEST_INT_GT(cch, str.capacity); TEST_INT_GE(str.len, str.capacity); } @@ -519,13 +520,13 @@ static void test_1_4() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not create", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not create", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(cch, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(s, stlsoft::apply_const_ptr(str.ptr)); + TEST_STR_EQ_(s, stlsoft::apply_const_ptr(str.ptr)); TEST_INT_GE(cch, str.capacity); TEST_INT_GE(str.len, str.capacity); @@ -539,7 +540,7 @@ static void test_1_4() }} } -static void test_1_5() +static void TEST_cstring_assignLen_AND_cstring_createLen() { { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) { @@ -550,13 +551,13 @@ static void test_1_5() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not assign (with length)", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not assign (with length)", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(cch, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_N_(s, stlsoft::apply_const_ptr(str.ptr), int(i)); + 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); @@ -580,13 +581,13 @@ static void test_1_5() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not assign (with length)", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not assign (with length)", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(cch, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_N_(s, stlsoft::apply_const_ptr(str.ptr), int(i)); + 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); } @@ -609,13 +610,13 @@ static void test_1_5() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(cch, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_N_(s, stlsoft::apply_const_ptr(str.ptr), int(i)); + 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); @@ -629,7 +630,7 @@ static void test_1_5() }} } -static void test_1_6() +static void TEST_cstring_append_AND_cstring_appendLen() { { cstring_t str = cstring_t_DEFAULT; @@ -641,13 +642,13 @@ static void test_1_6() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not append", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not append", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(i + 1, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_N_(alphabet, str.ptr, int(i)); + TEST_STR_EQ_N_(alphabet, str.ptr, int(i)); TEST_INT_GE(i + 1, str.capacity); TEST_INT_GE(str.len, str.capacity); } @@ -670,13 +671,13 @@ static void test_1_6() if (CSTRING_RC_SUCCESS != rc) { - XTESTS_TEST_FAIL_WITH_QUALIFIER("could not append (with length)", cstring_getStatusCodeString(rc)); + TEST_FAIL_WITH_QUALIFIER("could not append (with length)", cstring_getStatusCodeString(rc)); } else { TEST_INT_EQ(i + 1, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_N_(alphabet, str.ptr, int(i)); + TEST_STR_EQ_N_(alphabet, str.ptr, int(i)); TEST_INT_GT(i, str.capacity); TEST_INT_GE(str.len, str.capacity); } @@ -691,24 +692,24 @@ static void test_1_6() } } -static void test_1_7() +static void TEST_cstring_truncate() { { cstring_t str = cstring_t_DEFAULT; - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, cstring_truncate(&str, 0u)); + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, cstring_truncate(&str, 0u)); TEST_INT_EQ(0u, str.len); TEST_INT_EQ(0u, str.capacity); - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, cstring_truncate(&str, 1u)); + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, cstring_truncate(&str, 1u)); TEST_INT_EQ(0u, str.len); TEST_INT_EQ(0u, str.capacity); - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, cstring_truncate(&str, 100u)); + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, cstring_truncate(&str, 100u)); TEST_INT_EQ(0u, str.len); TEST_INT_EQ(0u, str.capacity); - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, cstring_truncate(&str, 100000u)); + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, cstring_truncate(&str, 100000u)); TEST_INT_EQ(0u, str.len); TEST_INT_EQ(0u, str.capacity); @@ -729,7 +730,7 @@ static void test_1_7() { cstring_truncate(&str, i); - XTESTS_TEST_INTEGER_LESS_OR_EQUAL(26u, str.len); + TEST_INT_LE(26u, str.len); if (i < 26u) { @@ -752,7 +753,7 @@ static void test_1_7() } } -static void test_1_8() +static void TEST_cstring_appendLen_KEEPS_CAPACITY() { cstring_t str = cstring_t_DEFAULT; @@ -782,13 +783,8 @@ static void test_1_8() TEST_INT_EQ(0, str.flags); } -static void test_1_9() -{ -} - - -static void test_2_0() +static void TEST_cstring_init_AND_cstring_destroy() { cstring_t str; @@ -804,7 +800,7 @@ static void test_2_0() TEST_INT_EQ(0, str.flags); } -static void test_2_1() +static void TEST_cstring_create() { { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) { @@ -815,7 +811,7 @@ static void test_2_1() cstring_create(&str, s); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_STR_EQ_(s, str.ptr); TEST_INT_GE(str.len, str.capacity); cstring_destroy(&str); @@ -827,7 +823,7 @@ static void test_2_1() }} } -static void test_2_2() +static void TEST_cstring_createLen() { { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) { @@ -838,7 +834,7 @@ static void test_2_2() cstring_createLen(&str, s, i); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_STR_EQ_(s, str.ptr); TEST_INT_GE(str.len, str.capacity); cstring_destroy(&str); @@ -850,7 +846,7 @@ static void test_2_2() }} } -static void test_2_3() +static void TEST_cstring_createEx() { { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) { @@ -862,7 +858,7 @@ static void test_2_3() cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_STR_EQ_(s, str.ptr); TEST_INT_GE(str.len, str.capacity); cstring_destroy(&str); @@ -884,16 +880,16 @@ static void test_2_3() rc = cstring_createEx(&str, s, flags, &buff[0] + 1, STLSOFT_NUM_ELEMENTS(buff) - 3); - XTESTS_TEST_CHARACTER_EQUAL(CSTRING_T_('['), buff[0]); - XTESTS_TEST_CHARACTER_EQUAL(CSTRING_T_(']'), buff[STLSOFT_NUM_ELEMENTS(buff) - 2]); - XTESTS_TEST_CHARACTER_EQUAL(CSTRING_T_('\0'), buff[STLSOFT_NUM_ELEMENTS(buff) - 1]); + TEST_CHAR_EQ(CSTRING_T_('['), buff[0]); + TEST_CHAR_EQ(CSTRING_T_(']'), buff[STLSOFT_NUM_ELEMENTS(buff) - 2]); + TEST_CHAR_EQ(CSTRING_T_('\0'), buff[STLSOFT_NUM_ELEMENTS(buff) - 1]); if (i < STLSOFT_NUM_ELEMENTS(buff) - 3) { - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_STR_EQ_(s, str.ptr); TEST_INT_GE(str.len, str.capacity); cstring_destroy(&str); @@ -905,7 +901,7 @@ static void test_2_3() } else { - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_EXCEEDBORROWEDCAPACITY, rc); + TEST_ENUM_EQ(CSTRING_RC_EXCEEDBORROWEDCAPACITY, rc); TEST_INT_EQ(0u, str.len); TEST_PTR_EQ(NULL, str.ptr); @@ -926,7 +922,7 @@ static void test_2_3() cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_STR_EQ_(s, str.ptr); TEST_INT_GE(str.len, str.capacity); TEST_INT_GE(str.capacity, ::GlobalSize(str.ptr)); @@ -948,7 +944,7 @@ static void test_2_3() cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(s, 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)); @@ -970,7 +966,7 @@ static void test_2_3() cstring_createEx(&str, s, flags, NULL, 0); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_STR_EQ_(s, str.ptr); TEST_INT_GE(str.len, str.capacity); TEST_INT_GE(str.capacity, comstl::CoTaskMemGetSize(str.ptr)); @@ -984,7 +980,7 @@ static void test_2_3() #endif /* WIN32 */ } -static void test_2_4() +static void TEST_cstring_createLenEx() { { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) { @@ -996,7 +992,7 @@ static void test_2_4() cstring_createLenEx(&str, s, i, flags, NULL, 0); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_STR_EQ_(s, str.ptr); TEST_INT_GE(str.len, str.capacity); cstring_destroy(&str); @@ -1018,16 +1014,16 @@ static void test_2_4() rc = cstring_createLenEx(&str, s, i, flags, &buff[0] + 1, STLSOFT_NUM_ELEMENTS(buff) - 3); - XTESTS_TEST_CHARACTER_EQUAL(CSTRING_T_('['), buff[0]); - XTESTS_TEST_CHARACTER_EQUAL(CSTRING_T_(']'), buff[STLSOFT_NUM_ELEMENTS(buff) - 2]); - XTESTS_TEST_CHARACTER_EQUAL(CSTRING_T_('\0'), buff[STLSOFT_NUM_ELEMENTS(buff) - 1]); + TEST_CHAR_EQ(CSTRING_T_('['), buff[0]); + TEST_CHAR_EQ(CSTRING_T_(']'), buff[STLSOFT_NUM_ELEMENTS(buff) - 2]); + TEST_CHAR_EQ(CSTRING_T_('\0'), buff[STLSOFT_NUM_ELEMENTS(buff) - 1]); if (i < STLSOFT_NUM_ELEMENTS(buff) - 3) { - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_STR_EQ_(s, str.ptr); TEST_INT_GE(str.len, str.capacity); cstring_destroy(&str); @@ -1039,7 +1035,7 @@ static void test_2_4() } else { - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_EXCEEDBORROWEDCAPACITY, rc); + TEST_ENUM_EQ(CSTRING_RC_EXCEEDBORROWEDCAPACITY, rc); TEST_INT_EQ(0u, str.len); TEST_PTR_EQ(NULL, str.ptr); @@ -1060,7 +1056,7 @@ static void test_2_4() cstring_createLenEx(&str, s, i, flags, NULL, 0); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_STR_EQ_(s, str.ptr); TEST_INT_GE(str.len, str.capacity); TEST_INT_GE(str.capacity, ::GlobalSize(str.ptr)); @@ -1082,7 +1078,7 @@ static void test_2_4() cstring_createLenEx(&str, s, i, flags, NULL, 0); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(s, 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)); @@ -1104,7 +1100,7 @@ static void test_2_4() cstring_createLenEx(&str, s, i, flags, NULL, 0); TEST_INT_GE(i, str.len); - XTESTS_TEST_STRING_EQUAL_(s, str.ptr); + TEST_STR_EQ_(s, str.ptr); TEST_INT_GE(str.len, str.capacity); TEST_INT_GE(str.capacity, comstl::CoTaskMemGetSize(str.ptr)); @@ -1118,7 +1114,7 @@ static void test_2_4() #endif /* WIN32 */ } -static void test_2_5() +static void TEST_cstring_assignLen_KEEPS_CAPACITY() { cstring_t str = cstring_t_DEFAULT; @@ -1142,7 +1138,7 @@ static void test_2_5() TEST_INT_EQ(0u, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_INTEGER_NOT_EQUAL(0u, str.capacity); + TEST_INT_NE(0u, str.capacity); TEST_INT_EQ(0, str.flags); }} @@ -1154,7 +1150,7 @@ static void test_2_5() TEST_INT_EQ(0, str.flags); } -static void test_2_6() +static void TEST_cstring_assign_KEEPS_CAPACITY() { cstring_t str = cstring_t_DEFAULT; @@ -1178,7 +1174,7 @@ static void test_2_6() TEST_INT_EQ(0u, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_INTEGER_NOT_EQUAL(0u, str.capacity); + TEST_INT_NE(0u, str.capacity); TEST_INT_EQ(0, str.flags); }} @@ -1190,7 +1186,7 @@ static void test_2_6() TEST_INT_EQ(0, str.flags); } -static void test_2_7() +static void TEST_cstring_copy() { { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) { @@ -1200,19 +1196,19 @@ static void test_2_7() cstring_t str2 = cstring_t_DEFAULT; CSTRING_RC rc = cstring_assign(&str1, s); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(cch, str1.len); TEST_PTR_NE(NULL, str1.ptr); - XTESTS_TEST_STRING_EQUAL_(s, stlsoft::apply_const_ptr(str1.ptr)); + TEST_STR_EQ_(s, stlsoft::apply_const_ptr(str1.ptr)); TEST_INT_GE(cch, str1.capacity); TEST_INT_GE(str1.len, str1.capacity); rc = cstring_copy(&str2, &str1); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(cch, str2.len); TEST_PTR_NE(NULL, str2.ptr); - XTESTS_TEST_STRING_EQUAL_(s, stlsoft::apply_const_ptr(str2.ptr)); + TEST_STR_EQ_(s, stlsoft::apply_const_ptr(str2.ptr)); TEST_INT_GE(cch, str2.capacity); TEST_INT_GE(str2.len, str2.capacity); @@ -1233,14 +1229,14 @@ static void test_2_7() }} } -static void test_2_8() +static void TEST_cstring_yield() { { cstring_t str = cstring_t_DEFAULT; cstring_char_t* p; CSTRING_RC rc = cstring_yield(&str, &p); - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); TEST_PTR_EQ(NULL, stlsoft::apply_const_ptr(stlsoft::apply_const_ptr(str.ptr))); TEST_INT_EQ(0u, str.capacity); @@ -1264,10 +1260,10 @@ static void test_2_8() CSTRING_RC rc = cstring_assign(&str, s); cstring_char_t* p; - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(cch, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(s, stlsoft::apply_const_ptr(str.ptr)); + TEST_STR_EQ_(s, stlsoft::apply_const_ptr(str.ptr)); TEST_INT_GE(cch, str.capacity); TEST_INT_GE(str.len, str.capacity); @@ -1275,13 +1271,13 @@ static void test_2_8() stlsoft::scoped_handle scoper(p, ::free); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_PTR_EQ(NULL, str.ptr); TEST_INT_EQ(0u, str.capacity); TEST_INT_EQ(0, str.flags); TEST_PTR_NE(NULL, p); - XTESTS_TEST_STRING_EQUAL_(s, stlsoft::apply_const_ptr(p)); + TEST_STR_EQ_(s, stlsoft::apply_const_ptr(p)); cstring_destroy(&str); @@ -1292,7 +1288,7 @@ static void test_2_8() }} } -static void test_2_9() +static void TEST_cstring_yield2() { { cstring_t str = cstring_t_DEFAULT; @@ -1300,7 +1296,7 @@ static void test_2_9() void* raw; CSTRING_RC rc = cstring_yield2(&str, &p, &raw); - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); TEST_PTR_EQ(NULL, str.ptr); TEST_INT_EQ(0u, str.capacity); @@ -1325,10 +1321,10 @@ static void test_2_9() cstring_char_t* p; void* raw; - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(cch, str.len); TEST_PTR_NE(NULL, str.ptr); - XTESTS_TEST_STRING_EQUAL_(s, stlsoft::apply_const_ptr(str.ptr)); + TEST_STR_EQ_(s, stlsoft::apply_const_ptr(str.ptr)); TEST_INT_GE(cch, str.capacity); TEST_INT_GE(str.len, str.capacity); @@ -1336,13 +1332,13 @@ static void test_2_9() stlsoft::scoped_handle scoper(raw, ::free); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_PTR_EQ(NULL, str.ptr); TEST_INT_EQ(0u, str.capacity); TEST_INT_EQ(0, str.flags); TEST_PTR_NE(NULL, p); - XTESTS_TEST_STRING_EQUAL_(s, stlsoft::apply_const_ptr(p)); + TEST_STR_EQ_(s, stlsoft::apply_const_ptr(p)); cstring_destroy(&str); @@ -1353,7 +1349,7 @@ static void test_2_9() }} } -static void test_2_10() +static void TEST_cstring_swap() { { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) { @@ -1363,10 +1359,10 @@ static void test_2_10() cstring_t str2 = cstring_t_DEFAULT; CSTRING_RC rc = cstring_assign(&str1, s); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(cch, str1.len); TEST_PTR_NE(NULL, str1.ptr); - XTESTS_TEST_STRING_EQUAL_(s, stlsoft::apply_const_ptr(str1.ptr)); + TEST_STR_EQ_(s, stlsoft::apply_const_ptr(str1.ptr)); TEST_INT_GE(cch, str1.capacity); TEST_INT_GE(str1.len, str1.capacity); @@ -1377,7 +1373,7 @@ static void test_2_10() rc = cstring_swap(&str2, &str1); - XTESTS_REQUIRE(XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc)); + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); TEST_INT_EQ(0u, str1.len); TEST_PTR_EQ(NULL, str1.ptr); TEST_INT_EQ(0u, str1.capacity); @@ -1385,7 +1381,7 @@ static void test_2_10() TEST_INT_EQ(cch, str2.len); TEST_PTR_NE(NULL, str2.ptr); - XTESTS_TEST_STRING_EQUAL_(s, stlsoft::apply_const_ptr(str2.ptr)); + TEST_STR_EQ_(s, stlsoft::apply_const_ptr(str2.ptr)); TEST_INT_GE(cch, str2.capacity); TEST_INT_GE(str2.len, str2.capacity); @@ -1405,10 +1401,6 @@ static void test_2_10() TEST_INT_EQ(0, str2.flags); }} } - -static void test_2_11() -{ -} } // anonymous namespace diff --git a/test/unit/cstring_getStatusCodeString/CMakeLists.txt b/test/unit/cstring_getStatusCodeString/CMakeLists.txt new file mode 100644 index 0000000..e949cff --- /dev/null +++ b/test/unit/cstring_getStatusCodeString/CMakeLists.txt @@ -0,0 +1,5 @@ +list(APPEND X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED + 4996 +) + +define_automated_test_program(test.unit.cstring_getStatusCodeString entry.c) diff --git a/test/unit/cstring_getStatusCodeString/entry.c b/test/unit/cstring_getStatusCodeString/entry.c new file mode 100644 index 0000000..1644575 --- /dev/null +++ b/test/unit/cstring_getStatusCodeString/entry.c @@ -0,0 +1,232 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/unit/cstring_getStatusCodeString/entry.c + * + * Purpose: Unit-tests `cstring_error()`, `cstring_getStatusCodeString()`. + * + * Created: 28th July 2011 + * Updated: 3rd October 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +/* ///////////////////////////////////// + * test component header file include(s) + */ + +#include + +/* ///////////////////////////////////// + * general includes + */ + +#ifdef XTESTS_HAS_SHWILD + /* shwild header files */ +# include +#endif /* XTESTS_HAS_SHWILD */ + +/* xTests header files */ +#include + +/* STLSoft header files */ +#include + +/* Standard C header files */ +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * forward declarations + */ + +static void TEST_cstring_getStatusCodeString_SUCCESS(void); +static void TEST_cstring_getStatusCodeString_KNOWN_CODES(void); +static void TEST_cstring_getStatusCodeString_UNKNOWN_CODES(void); + +static void TEST_cstring_error_SUCCESS(void); +static void TEST_cstring_error_KNOWN_CODES(void); +static void TEST_cstring_error_UNKNOWN_CODES(void); + + +/* ///////////////////////////////////////////////////////////////////////// + * constants & definitions + */ + +static CSTRING_RC const knownCodes[] = +{ + CSTRING_RC_SUCCESS + , CSTRING_RC_OUTOFMEMORY + , CSTRING_RC_FIXED + , CSTRING_RC_BORROWED + , CSTRING_RC_READONLY + , CSTRING_RC_INVALIDARENA + , CSTRING_RC_CUSTOMARENANOTSUPPORTED + , CSTRING_RC_EXCEEDFIXEDCAPACITY + , CSTRING_RC_EXCEEDBORROWEDCAPACITY + , CSTRING_RC_CANNOTYIELDFROMSO + , CSTRING_RC_ARENAOVERLOADED + /* cstring 3.5+ */ + , CSTRING_RC_INVALIDSTREAM + , CSTRING_RC_EOF + , CSTRING_RC_INVALIDSECTION + , CSTRING_RC_IOERROR + /* cstring 3.6.2+ */ + , CSTRING_RC_SYSTEMSPECIFICFAILURE + /* cstring 4.0+ */ + , CSTRING_RC_REQUESTTOOLARGE +}; + + +/* ///////////////////////////////////////////////////////////////////////// + * compiler compatibility + */ + +#if defined(STLSOFT_COMPILER_IS_MSVC) +# if _MSC_VER >= 1200 +# pragma warning(push) +# endif /* compiler */ +# pragma warning(disable : 4702) +#endif /* compiler */ + + +/* ///////////////////////////////////////////////////////////////////////// + * main + */ + +int main(int argc, char **argv) +{ + int retCode = EXIT_SUCCESS; + int verbosity = 2; + + XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); + + if (XTESTS_START_RUNNER("test.unit.cstring_getStatusCodeString", verbosity)) + { + XTESTS_RUN_CASE(TEST_cstring_getStatusCodeString_SUCCESS); + XTESTS_RUN_CASE(TEST_cstring_getStatusCodeString_KNOWN_CODES); + XTESTS_RUN_CASE(TEST_cstring_getStatusCodeString_UNKNOWN_CODES); + + XTESTS_RUN_CASE(TEST_cstring_error_SUCCESS); + XTESTS_RUN_CASE(TEST_cstring_error_KNOWN_CODES); + XTESTS_RUN_CASE(TEST_cstring_error_UNKNOWN_CODES); + + XTESTS_PRINT_RESULTS(); + + XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); + } + + return retCode; +} + + +/* ///////////////////////////////////////////////////////////////////////// + * compiler compatibility + */ + +#if defined(STLSOFT_COMPILER_IS_MSVC) +# if _MSC_VER >= 1200 +# pragma warning(pop) +# endif /* compiler */ +#endif /* compiler */ + + +/* ///////////////////////////////////////////////////////////////////////// + * test function implementations + */ + +static void TEST_cstring_getStatusCodeString_SUCCESS(void) +{ + TEST_MS_EQ("operation completed successfully", cstring_getStatusCodeString((CSTRING_RC)0)); +} + +static void TEST_cstring_getStatusCodeString_KNOWN_CODES(void) +{ + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(knownCodes); ++i) + { + TEST_MS_NE("<>", cstring_getStatusCodeString(knownCodes[i])); + }} + +#ifdef XTESTS_HAS_SHWILD + + TEST_MS_MATCHES("operation completed successfully", cstring_getStatusCodeString(CSTRING_RC_SUCCESS)); + TEST_MS_MATCHES("out of memory", cstring_getStatusCodeString(CSTRING_RC_OUTOFMEMORY)); + TEST_MS_MATCHES("operation cannot procede because the cstring *", cstring_getStatusCodeString(CSTRING_RC_FIXED)); + TEST_MS_MATCHES("operation cannot procede because the cstring *", cstring_getStatusCodeString(CSTRING_RC_BORROWED)); + TEST_MS_MATCHES("operation cannot procede because the cstring *", cstring_getStatusCodeString(CSTRING_RC_READONLY)); + TEST_MS_MATCHES("*invalid*", cstring_getStatusCodeString(CSTRING_RC_INVALIDARENA)); + TEST_MS_MATCHES("custom arena functionality not currently supported", cstring_getStatusCodeString(CSTRING_RC_CUSTOMARENANOTSUPPORTED)); + TEST_MS_MATCHES("operation cannot procede because the current capacity would be exceeded*", cstring_getStatusCodeString(CSTRING_RC_EXCEEDFIXEDCAPACITY)); + TEST_MS_MATCHES("operation cannot procede because the current capacity would be exceeded*", cstring_getStatusCodeString(CSTRING_RC_EXCEEDBORROWEDCAPACITY)); + TEST_MS_MATCHES("yield operation from a dynamic-library version of cstring was not allowed", cstring_getStatusCodeString(CSTRING_RC_CANNOTYIELDFROMSO)); + TEST_MS_MATCHES("cannot use arena parameter for both borrowed memory and custom arena", cstring_getStatusCodeString(CSTRING_RC_ARENAOVERLOADED)); + TEST_MS_MATCHES("*invalid*", cstring_getStatusCodeString(CSTRING_RC_INVALIDSTREAM)); + TEST_MS_MATCHES("reached the end of stream", cstring_getStatusCodeString(CSTRING_RC_EOF)); + TEST_MS_MATCHES("*invalid*", cstring_getStatusCodeString(CSTRING_RC_INVALIDSECTION)); + TEST_MS_MATCHES("an I/O error occured", cstring_getStatusCodeString(CSTRING_RC_IOERROR)); + TEST_MS_MATCHES("system-specific failure occurred", cstring_getStatusCodeString(CSTRING_RC_SYSTEMSPECIFICFAILURE)); + TEST_MS_MATCHES("request exceeded inherent or runtime limit", cstring_getStatusCodeString(CSTRING_RC_REQUESTTOOLARGE)); +#endif /* XTESTS_HAS_SHWILD */ +} + +static void TEST_cstring_getStatusCodeString_UNKNOWN_CODES(void) +{ + { for (size_t i = 0; i != 100000; ++i) + { + int r = rand(); + CSTRING_RC rc = (CSTRING_RC)(STLSOFT_NUM_ELEMENTS(knownCodes) + r + 1); + + TEST_MS_EQ("<>", cstring_getStatusCodeString(rc)); + }} +} + + +static void TEST_cstring_error_SUCCESS(void) +{ + TEST_MS_EQ("operation completed successfully", cstring_error((CSTRING_RC)0)); +} + +static void TEST_cstring_error_KNOWN_CODES(void) +{ + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(knownCodes); ++i) + { + TEST_MS_NE("<>", cstring_error(knownCodes[i])); + }} + +#ifdef XTESTS_HAS_SHWILD + + TEST_MS_MATCHES("operation completed successfully", cstring_error(CSTRING_RC_SUCCESS)); + TEST_MS_MATCHES("out of memory", cstring_error(CSTRING_RC_OUTOFMEMORY)); + TEST_MS_MATCHES("operation cannot procede because the cstring *", cstring_error(CSTRING_RC_FIXED)); + TEST_MS_MATCHES("operation cannot procede because the cstring *", cstring_error(CSTRING_RC_BORROWED)); + TEST_MS_MATCHES("operation cannot procede because the cstring *", cstring_error(CSTRING_RC_READONLY)); + TEST_MS_MATCHES("*invalid*", cstring_error(CSTRING_RC_INVALIDARENA)); + TEST_MS_MATCHES("custom arena functionality not currently supported", cstring_error(CSTRING_RC_CUSTOMARENANOTSUPPORTED)); + TEST_MS_MATCHES("operation cannot procede because the current capacity would be exceeded*", cstring_error(CSTRING_RC_EXCEEDFIXEDCAPACITY)); + TEST_MS_MATCHES("operation cannot procede because the current capacity would be exceeded*", cstring_error(CSTRING_RC_EXCEEDBORROWEDCAPACITY)); + TEST_MS_MATCHES("yield operation from a dynamic-library version of cstring was not allowed", cstring_error(CSTRING_RC_CANNOTYIELDFROMSO)); + TEST_MS_MATCHES("cannot use arena parameter for both borrowed memory and custom arena", cstring_error(CSTRING_RC_ARENAOVERLOADED)); + TEST_MS_MATCHES("*invalid*", cstring_error(CSTRING_RC_INVALIDSTREAM)); + TEST_MS_MATCHES("reached the end of stream", cstring_error(CSTRING_RC_EOF)); + TEST_MS_MATCHES("*invalid*", cstring_error(CSTRING_RC_INVALIDSECTION)); + TEST_MS_MATCHES("an I/O error occured", cstring_error(CSTRING_RC_IOERROR)); + TEST_MS_MATCHES("system-specific failure occurred", cstring_error(CSTRING_RC_SYSTEMSPECIFICFAILURE)); + TEST_MS_MATCHES("request exceeded inherent or runtime limit", cstring_error(CSTRING_RC_REQUESTTOOLARGE)); +#endif /* XTESTS_HAS_SHWILD */ +} + +static void TEST_cstring_error_UNKNOWN_CODES(void) +{ + { for (size_t i = 0; i != 100000; ++i) + { + int r = rand(); + CSTRING_RC rc = (CSTRING_RC)(STLSOFT_NUM_ELEMENTS(knownCodes) + r + 1); + + TEST_MS_EQ("<>", cstring_error(rc)); + }} +} + + +/* ///////////////////////////// end of file //////////////////////////// */ diff --git a/test/unit/test.unit.cstring.cstring_vector/CMakeLists.txt b/test/unit/cstring_vector/CMakeLists.txt similarity index 100% rename from test/unit/test.unit.cstring.cstring_vector/CMakeLists.txt rename to test/unit/cstring_vector/CMakeLists.txt diff --git a/test/unit/cstring_vector/entry.c b/test/unit/cstring_vector/entry.c new file mode 100644 index 0000000..4a5cf2a --- /dev/null +++ b/test/unit/cstring_vector/entry.c @@ -0,0 +1,318 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/unit/cstring_vector/entry.c + * + * Purpose: Tests `cstring_vector_t` functionality. + * + * Created: 12th January 2024 + * Updated: 3rd October 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + + +#include + +#ifdef USE_PANTHEIOS_EXTRAS_DIAGUTIL_MAIN + +# include +#endif + +#include + +#include +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * forward declarations + */ + +static void TEST_cstring_vector_t_DEFAULT(void); +static void TEST_cstring_vector_init_ZERO(void); +static void TEST_cstring_vector_init_WITH_CAPACITY(void); +static void TEST_cstring_vector_create_ZERO(void); +static void TEST_cstring_vector_create_N(void); +static void TEST_cstring_vector_insertAt_ONE(void); +static void TEST_cstring_vector_insertAt_LIST(void); + + +/* ///////////////////////////////////////////////////////////////////////// + * constants & definitions + */ + + +/* ///////////////////////////////////////////////////////////////////////// + * main() + */ + +static +int main0(int argc, char **argv) +{ + int retCode = EXIT_SUCCESS; + int verbosity = 2; + + XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); + + if (XTESTS_START_RUNNER("test.unit.cstring_vector", verbosity)) + { + XTESTS_RUN_CASE(TEST_cstring_vector_t_DEFAULT); + XTESTS_RUN_CASE(TEST_cstring_vector_init_ZERO); + XTESTS_RUN_CASE(TEST_cstring_vector_init_WITH_CAPACITY); + XTESTS_RUN_CASE(TEST_cstring_vector_create_ZERO); + XTESTS_RUN_CASE(TEST_cstring_vector_create_N); + XTESTS_RUN_CASE(TEST_cstring_vector_insertAt_ONE); + XTESTS_RUN_CASE(TEST_cstring_vector_insertAt_LIST); + + XTESTS_PRINT_RESULTS(); + + XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); + } + + return retCode; +} + +int main(int argc, char **argv) +{ +#ifdef USE_PANTHEIOS_EXTRAS_DIAGUTIL_MAIN + + return pantheios_extras_diagutil_main_leak_trace_invoke(argc, argv, main0); +#else + + return main0(argc, argv); +#endif +} + + +/* ///////////////////////////////////////////////////////////////////////// + * test function implementations + */ + +static void TEST_cstring_vector_t_DEFAULT(void) +{ + cstring_vector_t csv = cstring_vector_t_DEFAULT; + + TEST_INT_EQ(0, csv.len); + TEST_PTR_EQ(NULL, csv.ptr); + TEST_INT_EQ(0, csv.capacity); + + cstring_vector_destroy(&csv); +} + +static void TEST_cstring_vector_init_ZERO(void) +{ + cstring_vector_t csv; + CSTRING_RC rc = cstring_vector_init(&csv, 0); + + if (CSTRING_RC_OUTOFMEMORY == rc) + { + /*XTESTS_TEST_WARN(*/ + } + else + if (CSTRING_RC_SUCCESS == rc) + { + TEST_INT_EQ(0, csv.len); + TEST_PTR_EQ(NULL, csv.ptr); + TEST_INT_EQ(0, csv.capacity); + + cstring_vector_destroy(&csv); + } + else + { + TEST_FAIL("should only fail cstring_vector_init() with CSTRING_RC_OUTOFMEMORY"); + } +} + +static void TEST_cstring_vector_init_WITH_CAPACITY(void) +{ + cstring_vector_t csv; + CSTRING_RC rc = cstring_vector_init(&csv, 10); + + if (CSTRING_RC_OUTOFMEMORY == rc) + { + /*XTESTS_TEST_WARN(*/ + } + else + if (CSTRING_RC_SUCCESS == rc) + { + TEST_INT_EQ(0, csv.len); + TEST_PTR_NE(NULL, csv.ptr); + TEST_INT_GE(10, csv.capacity); + + cstring_vector_destroy(&csv); + } + else + { + TEST_FAIL("should only fail cstring_vector_init() with CSTRING_RC_OUTOFMEMORY"); + } +} + +static void TEST_cstring_vector_create_ZERO(void) +{ + cstring_vector_t csv; + CSTRING_RC rc = cstring_vector_create(&csv, 0); + + if (CSTRING_RC_OUTOFMEMORY == rc) + { + /*XTESTS_TEST_WARN(*/ + } + else + if (CSTRING_RC_SUCCESS == rc) + { + TEST_INT_EQ(0, csv.len); + TEST_PTR_EQ(NULL, csv.ptr); + TEST_INT_EQ(0, csv.capacity); + + cstring_vector_destroy(&csv); + } + else + { + TEST_FAIL("should only fail cstring_vector_create() with CSTRING_RC_OUTOFMEMORY"); + } +} + +static void TEST_cstring_vector_create_N(void) +{ + cstring_vector_t csv; + CSTRING_RC rc = cstring_vector_create(&csv, 10); + + if (CSTRING_RC_OUTOFMEMORY == rc) + { + /*XTESTS_TEST_WARN(*/ + } + else + if (CSTRING_RC_SUCCESS == rc) + { + TEST_INT_EQ(10, csv.len); + TEST_PTR_NE(NULL, csv.ptr); + TEST_INT_GE(10, csv.capacity); + + cstring_vector_destroy(&csv); + } + else + { + TEST_FAIL("should only fail cstring_vector_create() with CSTRING_RC_OUTOFMEMORY"); + } +} + +static void TEST_cstring_vector_insertAt_ONE(void) +{ + cstring_vector_t csv; + CSTRING_RC rc = cstring_vector_create(&csv, 0); + + if (CSTRING_RC_OUTOFMEMORY == rc) + { + /*XTESTS_TEST_WARN(*/ + } + else + if (CSTRING_RC_SUCCESS == rc) + { + cstring_t cs = cstring_t_DEFAULT; + + TEST_INT_EQ(0, csv.len); + TEST_PTR_EQ(NULL, csv.ptr); + TEST_INT_EQ(0, csv.capacity); + + rc = cstring_vector_insertAt(&csv, 0, &cs, 1); + + if (CSTRING_RC_OUTOFMEMORY == rc) + { + /*XTESTS_TEST_WARN(*/ + } + else + if (CSTRING_RC_SUCCESS == rc) + { + TEST_INT_EQ(1, csv.len); + TEST_PTR_NE(NULL, csv.ptr); + TEST_INT_GE(1, csv.capacity); + } + else + { + TEST_FAIL("should only fail cstring_vector_insertAt() with CSTRING_RC_OUTOFMEMORY"); + } + + cstring_vector_destroy(&csv); + } + else + { + TEST_FAIL("should only fail cstring_vector_create() with CSTRING_RC_OUTOFMEMORY"); + } +} + +static void TEST_cstring_vector_insertAt_LIST(void) +{ + char const* const strings[] = + { + "blah", + "", + "abcdefghijklmnopqrstuvwxyz", + "My Ever Changing Moods", + "I Can Give You Everything", + "Newborn Friend", + "Escape Velocity", + "Weapon Of Choice", + "Ain't No Doubt", + "Eye Know", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + }; + + cstring_vector_t csv; + CSTRING_RC rc = cstring_vector_create(&csv, 0); + + if (CSTRING_RC_OUTOFMEMORY == rc) + { + /*XTESTS_TEST_WARN(*/ + } + else + if (CSTRING_RC_SUCCESS == rc) + { + cstring_t cs = cstring_t_DEFAULT; + size_t i; + + TEST_INT_EQ(0, csv.len); + TEST_PTR_EQ(NULL, csv.ptr); + TEST_INT_EQ(0, csv.capacity); + + for (i = 0; i != STLSOFT_NUM_ELEMENTS(strings) && CSTRING_RC_SUCCESS == rc; ++i) + { + rc = cstring_assign(&cs, strings[i]); + + if (CSTRING_RC_SUCCESS == rc) + { + rc = cstring_vector_insertAt(&csv, i, &cs, 1); + } + } + + cstring_destroy(&cs); + + if (CSTRING_RC_OUTOFMEMORY == rc) + { + /*XTESTS_TEST_WARN(*/ + } + else + if (CSTRING_RC_SUCCESS == rc) + { + XTESTS_REQUIRE(TEST_INT_EQ(STLSOFT_NUM_ELEMENTS(strings), csv.len)); + TEST_PTR_NE(NULL, csv.ptr); + TEST_INT_GE(STLSOFT_NUM_ELEMENTS(strings), csv.capacity); + + for (i = 0; i != STLSOFT_NUM_ELEMENTS(strings); ++i) + { + TEST_INT_EQ(strlen(strings[i]), csv.ptr[i].len); + TEST_MS_EQ(strings[i], csv.ptr[i].ptr); + } + } + else + { + TEST_FAIL("should only fail cstring_vector_insertAt() with CSTRING_RC_OUTOFMEMORY"); + } + + cstring_vector_destroy(&csv); + } + else + { + TEST_FAIL("should only fail cstring_vector_create() with CSTRING_RC_OUTOFMEMORY"); + } +} + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/unit/test.unit.cstring.auto_buffer/entry.c b/test/unit/test.unit.cstring.auto_buffer/entry.c deleted file mode 100644 index 5bd10b2..0000000 --- a/test/unit/test.unit.cstring.auto_buffer/entry.c +++ /dev/null @@ -1,508 +0,0 @@ -/* ///////////////////////////////////////////////////////////////////////// - * File: test.unit.cstring.auto_buffer/entry.c - * - * Purpose: Implementation file for the test.unit.cstring.auto_buffer - * project. - * - * Created: 28th July 2011 - * Updated: 2nd August 2026 - * - * ////////////////////////////////////////////////////////////////////// */ - - -/* ///////////////////////////////////////////////////////////////////////// - * test component header file include(s) - */ - -#include - - -/* ///////////////////////////////////////////////////////////////////////// - * includes - */ - -/* xTests header files */ -#include - -/* STLSoft header files */ -#include - -/* Standard C header files */ -#include - - -/* ///////////////////////////////////////////////////////////////////////// - * forward declarations - */ - -static void test_badFlags(void); -static void test_createEx(void); -static void test_createLenEx(void); -static void test_setCapacity(void); -static void test_assign(void); -static void test_assignLen(void); -static void test_append(void); -static void test_appendLen(void); - - -/* ///////////////////////////////////////////////////////////////////////// - * constants & definitions - */ - -static char const TEST_FILE_NAME[] = "test.unit.cstring.auto_buffer.txt"; - - -/* ///////////////////////////////////////////////////////////////////////// - * compiler compatibility - */ - -#if defined(STLSOFT_COMPILER_IS_MSVC) -# if _MSC_VER >= 1200 -# pragma warning(push) -# endif /* compiler */ -# pragma warning(disable : 4702) -#endif /* compiler */ - - -/* ///////////////////////////////////////////////////////////////////////// - * main - */ - -int main(int argc, char **argv) -{ - int retCode = EXIT_SUCCESS; - int verbosity = 2; - - XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); - - if (XTESTS_START_RUNNER("test.unit.cstring.auto_buffer", verbosity)) - { - XTESTS_RUN_CASE(test_badFlags); - XTESTS_RUN_CASE(test_createEx); - XTESTS_RUN_CASE(test_createLenEx); - XTESTS_RUN_CASE(test_setCapacity); - XTESTS_RUN_CASE(test_assign); - XTESTS_RUN_CASE(test_assignLen); - XTESTS_RUN_CASE(test_append); - XTESTS_RUN_CASE(test_appendLen); - - XTESTS_PRINT_RESULTS(); - - XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); - } - - STLSOFT_SUPPRESS_UNUSED(TEST_FILE_NAME); - - return retCode; -} - - -/* ///////////////////////////////////////////////////////////////////////// - * compiler compatibility - */ - -#if defined(STLSOFT_COMPILER_IS_MSVC) -# if _MSC_VER >= 1200 -# pragma warning(pop) -# endif /* compiler */ -#endif /* compiler */ - - -/* ///////////////////////////////////////////////////////////////////////// - * test function implementations - */ - -static void test_badFlags(void) -{ - static int const goods[] = - { - CSTRING_F_MEMORY_IS_BORROWED - , CSTRING_F_MEMORY_IS_BORROWED | CSTRING_F_MEMORY_IS_FIXED - , CSTRING_F_MEMORY_IS_BORROWED | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP - }; - - static int const bads[] = - { - 0 - , CSTRING_F_MEMORY_IS_FIXED - , CSTRING_F_MEMORY_IS_OFFSET - }; - - { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(goods); ++i) - { - int const flags = 0 - | goods[i] - ; - char buff[10]; - cstring_t cs; - CSTRING_RC rc; - - rc = cstring_createEx(&cs, "string: ", flags, buff, sizeof(buff)); - - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); - }} - - { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(bads); ++i) - { - int const flags = 0 - | bads[i] - ; - char buff[10]; - cstring_t cs; - CSTRING_RC rc; - - rc = cstring_createEx(&cs, "string: ", flags, buff, sizeof(buff)); - - XTESTS_TEST_ENUM_NOT_EQUAL(CSTRING_RC_SUCCESS, rc); - }} -} - -static void test_createEx(void) -{ - int const flags = 0 - | CSTRING_F_MEMORY_IS_BORROWED - | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP - ; - char buff[8]; - cstring_t cs; - CSTRING_RC rc; - - - /* must succeed in stack buffer. */ - - rc = cstring_createEx(&cs, "small", flags, buff, sizeof(buff)); - - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_INTEGER_EQUAL(sizeof(buff) - 1u, cs.capacity); - - cstring_destroy(&cs); - - - /* try to resize. */ - - rc = cstring_createEx(&cs, "oversized", flags, buff, sizeof(buff)); - - if (CSTRING_RC_SUCCESS != rc) - { - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_OUTOFMEMORY, rc); - XTESTS_TEST_INTEGER_EQUAL(flags, cs.flags); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_POINTER_EQUAL(buff, cs.ptr); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("small", cs.ptr); - XTESTS_TEST_INTEGER_EQUAL(sizeof(buff) - 1u, cs.capacity); - } - else - { - XTESTS_TEST_INTEGER_EQUAL(0, cs.flags); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("oversized", cs.ptr); - XTESTS_TEST_POINTER_NOT_EQUAL(buff, cs.ptr); - - cstring_destroy(&cs); - } -} - -static void test_createLenEx(void) -{ - int const flags = 0 - | CSTRING_F_MEMORY_IS_BORROWED - | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP - ; - char buff[8]; - cstring_t cs; - CSTRING_RC rc; - - - /* must succeed in stack buffer. */ - - rc = cstring_createEx(&cs, "small", flags, buff, sizeof(buff)); - - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_INTEGER_EQUAL(sizeof(buff) - 1u, cs.capacity); - - cstring_destroy(&cs); - - - /* try to resize. */ - - rc = cstring_createLenEx(&cs, "oversized string", 9, flags, buff, sizeof(buff)); - - if (CSTRING_RC_SUCCESS != rc) - { - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_OUTOFMEMORY, rc); - XTESTS_TEST_INTEGER_EQUAL(flags, cs.flags); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_POINTER_EQUAL(buff, cs.ptr); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("small", cs.ptr); - XTESTS_TEST_INTEGER_EQUAL(sizeof(buff) - 1u, cs.capacity); - } - else - { - XTESTS_TEST_INTEGER_EQUAL(0, cs.flags); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("oversized", cs.ptr); - XTESTS_TEST_POINTER_NOT_EQUAL(buff, cs.ptr); - - cstring_destroy(&cs); - } -} - -static void test_setCapacity(void) -{ - int const flags = 0 - | CSTRING_F_MEMORY_IS_BORROWED - | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP - ; - char buff[8]; - cstring_t cs; - CSTRING_RC rc; - - - /* must succeed in stack buffer. */ - - rc = cstring_createEx(&cs, "small", flags, buff, sizeof(buff)); - - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_INTEGER_EQUAL(sizeof(buff) - 1u, cs.capacity); - - - /* try to resize. */ - - rc = cstring_setCapacity(&cs, 9); - - if (CSTRING_RC_SUCCESS != rc) - { - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_OUTOFMEMORY, rc); - XTESTS_TEST_INTEGER_EQUAL(flags, cs.flags); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_POINTER_EQUAL(buff, cs.ptr); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("small", cs.ptr); - XTESTS_TEST_INTEGER_EQUAL(sizeof(buff) - 1u, cs.capacity); - } - else - { - XTESTS_TEST_INTEGER_EQUAL(0, cs.flags); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_POINTER_NOT_EQUAL(buff, cs.ptr); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("small", cs.ptr); - XTESTS_TEST_INTEGER_GREATER_OR_EQUAL(9u - 1u, cs.capacity); - } - - cstring_destroy(&cs); -} - -static void test_assign(void) -{ - int const flags = 0 - | CSTRING_F_MEMORY_IS_BORROWED - | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP - ; - char buff[8]; - cstring_t cs; - CSTRING_RC rc; - - - /* must succeed in stack buffer. */ - - rc = cstring_createEx(&cs, "small", flags, buff, sizeof(buff)); - - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_INTEGER_EQUAL(sizeof(buff) - 1u, cs.capacity); - - - /* try to resize. */ - - rc = cstring_assign(&cs, "oversized"); - - if (CSTRING_RC_SUCCESS != rc) - { - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_OUTOFMEMORY, rc); - XTESTS_TEST_INTEGER_EQUAL(flags, cs.flags); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_POINTER_EQUAL(buff, cs.ptr); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("small", cs.ptr); - XTESTS_TEST_INTEGER_EQUAL(sizeof(buff) - 1u, cs.capacity); - } - else - { - XTESTS_TEST_INTEGER_EQUAL(0, cs.flags); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("oversized", cs.ptr); - XTESTS_TEST_POINTER_NOT_EQUAL(buff, cs.ptr); - - - /* shrink again */ - rc = cstring_assign(&cs, "small"); - - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); - XTESTS_TEST_INTEGER_EQUAL(0, cs.flags); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_POINTER_NOT_EQUAL(buff, cs.ptr); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("small", cs.ptr); - } - - cstring_destroy(&cs); -} - -static void test_assignLen(void) -{ - int const flags = 0 - | CSTRING_F_MEMORY_IS_BORROWED - | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP - ; - char buff[8]; - cstring_t cs; - CSTRING_RC rc; - - - /* must succeed in stack buffer. */ - - rc = cstring_createEx(&cs, "small", flags, buff, sizeof(buff)); - - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_INTEGER_EQUAL(sizeof(buff) - 1u, cs.capacity); - - - /* try to resize. */ - - rc = cstring_assignLen(&cs, "oversized", 9); - - if (CSTRING_RC_SUCCESS != rc) - { - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_OUTOFMEMORY, rc); - XTESTS_TEST_INTEGER_EQUAL(flags, cs.flags); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_POINTER_EQUAL(buff, cs.ptr); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("small", cs.ptr); - XTESTS_TEST_INTEGER_EQUAL(sizeof(buff) - 1u, cs.capacity); - } - else - { - XTESTS_TEST_INTEGER_EQUAL(0, cs.flags); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("oversized", cs.ptr); - XTESTS_TEST_POINTER_NOT_EQUAL(buff, cs.ptr); - - - /* shrink again */ - rc = cstring_assign(&cs, "small"); - - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); - XTESTS_TEST_INTEGER_EQUAL(0, cs.flags); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_POINTER_NOT_EQUAL(buff, cs.ptr); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("small", cs.ptr); - } - - cstring_destroy(&cs); -} - -static void test_append(void) -{ - int const flags = 0 - | CSTRING_F_MEMORY_IS_BORROWED - | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP - ; - char buff[8]; - cstring_t cs; - CSTRING_RC rc; - - - /* must succeed in stack buffer. */ - - rc = cstring_createEx(&cs, "small", flags, buff, sizeof(buff)); - - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_INTEGER_EQUAL(sizeof(buff) - 1u, cs.capacity); - - - /* try to resize. */ - - rc = cstring_append(&cs, "ish, but big enough"); - - if (CSTRING_RC_SUCCESS != rc) - { - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_OUTOFMEMORY, rc); - XTESTS_TEST_INTEGER_EQUAL(flags, cs.flags); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_POINTER_EQUAL(buff, cs.ptr); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("small", cs.ptr); - XTESTS_TEST_INTEGER_EQUAL(sizeof(buff) - 1u, cs.capacity); - } - else - { - XTESTS_TEST_INTEGER_EQUAL(0, cs.flags); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("smallish, but big enough", cs.ptr); - XTESTS_TEST_POINTER_NOT_EQUAL(buff, cs.ptr); - - - /* shrink again */ - rc = cstring_assign(&cs, "small"); - - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); - XTESTS_TEST_INTEGER_EQUAL(0, cs.flags); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_POINTER_NOT_EQUAL(buff, cs.ptr); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("small", cs.ptr); - } - - cstring_destroy(&cs); -} - -static void test_appendLen(void) -{ - int const flags = 0 - | CSTRING_F_MEMORY_IS_BORROWED - | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP - ; - char buff[8]; - cstring_t cs; - CSTRING_RC rc; - - - /* must succeed in stack buffer. */ - - rc = cstring_createEx(&cs, "small", flags, buff, sizeof(buff)); - - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_INTEGER_EQUAL(sizeof(buff) - 1u, cs.capacity); - - - /* try to resize. */ - - rc = cstring_appendLen(&cs, "ish, but big enough", 19); - - if (CSTRING_RC_SUCCESS != rc) - { - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_OUTOFMEMORY, rc); - XTESTS_TEST_INTEGER_EQUAL(flags, cs.flags); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_POINTER_EQUAL(buff, cs.ptr); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("small", cs.ptr); - XTESTS_TEST_INTEGER_EQUAL(sizeof(buff) - 1u, cs.capacity); - } - else - { - XTESTS_TEST_INTEGER_EQUAL(0, cs.flags); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("smallish, but big enough", cs.ptr); - XTESTS_TEST_POINTER_NOT_EQUAL(buff, cs.ptr); - - - /* shrink again */ - rc = cstring_assign(&cs, "small"); - - XTESTS_TEST_ENUM_EQUAL(CSTRING_RC_SUCCESS, rc); - XTESTS_TEST_INTEGER_EQUAL(0, cs.flags); - XTESTS_TEST_INTEGER_EQUAL(5u, cs.len); - XTESTS_TEST_POINTER_NOT_EQUAL(buff, cs.ptr); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("small", cs.ptr); - } - - cstring_destroy(&cs); -} - - -/* ///////////////////////////// end of file //////////////////////////// */ diff --git a/test/unit/test.unit.cstring.cstring_getStatusCodeString/CMakeLists.txt b/test/unit/test.unit.cstring.cstring_getStatusCodeString/CMakeLists.txt deleted file mode 100644 index d34b3dd..0000000 --- a/test/unit/test.unit.cstring.cstring_getStatusCodeString/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -list(APPEND X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED - 4996 -) - -define_automated_test_program(test_unit_cstring_cstring_getStatusCodeString entry.c) diff --git a/test/unit/test.unit.cstring.cstring_getStatusCodeString/entry.c b/test/unit/test.unit.cstring.cstring_getStatusCodeString/entry.c deleted file mode 100644 index 9b6347c..0000000 --- a/test/unit/test.unit.cstring.cstring_getStatusCodeString/entry.c +++ /dev/null @@ -1,233 +0,0 @@ -/* ///////////////////////////////////////////////////////////////////////// - * File: test.unit.cstring.cstring_getStatusCodeString/entry.c - * - * Purpose: Unit-tests `cstring_error()`, `cstring_getStatusCodeString()`. - * - * Created: 28th July 2011 - * Updated: 2nd August 2026 - * - * ////////////////////////////////////////////////////////////////////// */ - - -/* ///////////////////////////////////////////////////////////////////////// - * test component header file include(s) - */ - -#include - - -/* ///////////////////////////////////////////////////////////////////////// - * includes - */ - -#ifdef XTESTS_HAS_SHWILD - /* shwild header files */ -# include -#endif /* XTESTS_HAS_SHWILD */ - -/* xTests header files */ -#include - -/* STLSoft header files */ -#include - -/* Standard C header files */ -#include - - -/* ///////////////////////////////////////////////////////////////////////// - * forward declarations - */ - -static void test_0(void); -static void test_known(void); -static void test_random(void); - -static void test_0_deprecated(void); -static void test_known_deprecated(void); -static void test_random_deprecated(void); - - -/* ///////////////////////////////////////////////////////////////////////// - * constants & definitions - */ - -static char const TEST_FILE_NAME[] = "test.unit.cstring.cstring_getStatusCodeString.txt"; - -static CSTRING_RC const knownCodes[] = -{ - CSTRING_RC_SUCCESS - , CSTRING_RC_OUTOFMEMORY - , CSTRING_RC_FIXED - , CSTRING_RC_BORROWED - , CSTRING_RC_READONLY - , CSTRING_RC_INVALIDARENA - , CSTRING_RC_CUSTOMARENANOTSUPPORTED - , CSTRING_RC_EXCEEDFIXEDCAPACITY - , CSTRING_RC_EXCEEDBORROWEDCAPACITY - , CSTRING_RC_CANNOTYIELDFROMSO - , CSTRING_RC_ARENAOVERLOADED - /* cstring 3.5+ */ - , CSTRING_RC_INVALIDSTREAM - , CSTRING_RC_EOF - , CSTRING_RC_INVALIDSECTION - , CSTRING_RC_IOERROR - /* cstring 3.6.2+ */ - , CSTRING_RC_SYSTEMSPECIFICFAILURE - /* cstring 4.0+ */ - , CSTRING_RC_REQUESTTOOLARGE -}; - - -/* ///////////////////////////////////////////////////////////////////////// - * compiler compatibility - */ - -#if defined(STLSOFT_COMPILER_IS_MSVC) -# if _MSC_VER >= 1200 -# pragma warning(push) -# endif /* compiler */ -# pragma warning(disable : 4702) -#endif /* compiler */ - - -/* ///////////////////////////////////////////////////////////////////////// - * main - */ - -int main(int argc, char **argv) -{ - int retCode = EXIT_SUCCESS; - int verbosity = 2; - - XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); - - if (XTESTS_START_RUNNER("test.unit.cstring.cstring_getStatusCodeString", verbosity)) - { - XTESTS_RUN_CASE(test_0); - XTESTS_RUN_CASE(test_known); - XTESTS_RUN_CASE(test_random); - - XTESTS_RUN_CASE(test_0_deprecated); - XTESTS_RUN_CASE(test_known_deprecated); - XTESTS_RUN_CASE(test_random_deprecated); - - XTESTS_PRINT_RESULTS(); - - XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); - } - - STLSOFT_SUPPRESS_UNUSED(TEST_FILE_NAME); - - return retCode; -} - - -/* ///////////////////////////////////////////////////////////////////////// - * compiler compatibility - */ - -#if defined(STLSOFT_COMPILER_IS_MSVC) -# if _MSC_VER >= 1200 -# pragma warning(pop) -# endif /* compiler */ -#endif /* compiler */ - - -/* ///////////////////////////////////////////////////////////////////////// - * test function implementations - */ - -static void test_0(void) -{ - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("operation completed successfully", cstring_getStatusCodeString((CSTRING_RC)0)); -} - -static void test_known(void) -{ - { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(knownCodes); ++i) - { - XTESTS_TEST_MULTIBYTE_STRING_NOT_EQUAL("<>", cstring_getStatusCodeString(knownCodes[i])); - }} - -#ifdef XTESTS_HAS_SHWILD - - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("operation completed successfully", cstring_getStatusCodeString(CSTRING_RC_SUCCESS)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("out of memory", cstring_getStatusCodeString(CSTRING_RC_OUTOFMEMORY)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("operation cannot procede because the cstring *", cstring_getStatusCodeString(CSTRING_RC_FIXED)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("operation cannot procede because the cstring *", cstring_getStatusCodeString(CSTRING_RC_BORROWED)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("operation cannot procede because the cstring *", cstring_getStatusCodeString(CSTRING_RC_READONLY)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("*invalid*", cstring_getStatusCodeString(CSTRING_RC_INVALIDARENA)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("custom arena functionality not currently supported", cstring_getStatusCodeString(CSTRING_RC_CUSTOMARENANOTSUPPORTED)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("operation cannot procede because the current capacity would be exceeded*", cstring_getStatusCodeString(CSTRING_RC_EXCEEDFIXEDCAPACITY)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("operation cannot procede because the current capacity would be exceeded*", cstring_getStatusCodeString(CSTRING_RC_EXCEEDBORROWEDCAPACITY)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("yield operation from a dynamic-library version of cstring was not allowed", cstring_getStatusCodeString(CSTRING_RC_CANNOTYIELDFROMSO)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("cannot use arena parameter for both borrowed memory and custom arena", cstring_getStatusCodeString(CSTRING_RC_ARENAOVERLOADED)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("*invalid*", cstring_getStatusCodeString(CSTRING_RC_INVALIDSTREAM)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("reached the end of stream", cstring_getStatusCodeString(CSTRING_RC_EOF)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("*invalid*", cstring_getStatusCodeString(CSTRING_RC_INVALIDSECTION)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("an I/O error occured", cstring_getStatusCodeString(CSTRING_RC_IOERROR)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("system-specific failure occurred", cstring_getStatusCodeString(CSTRING_RC_SYSTEMSPECIFICFAILURE)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("request exceeded inherent or runtime limit", cstring_getStatusCodeString(CSTRING_RC_REQUESTTOOLARGE)); -#endif /* XTESTS_HAS_SHWILD */ -} - -static void test_random(void) -{ - { for (size_t i = 0; i != 100000; ++i) - { - int r = rand(); - CSTRING_RC rc = (CSTRING_RC)(STLSOFT_NUM_ELEMENTS(knownCodes) + r + 1); - - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("<>", cstring_getStatusCodeString(rc)); - }} -} - - -static void test_0_deprecated(void) -{ - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("operation completed successfully", cstring_error((CSTRING_RC)0)); -} - -static void test_known_deprecated(void) -{ - { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(knownCodes); ++i) - { - XTESTS_TEST_MULTIBYTE_STRING_NOT_EQUAL("<>", cstring_error(knownCodes[i])); - }} - -#ifdef XTESTS_HAS_SHWILD - - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("operation completed successfully", cstring_error(CSTRING_RC_SUCCESS)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("out of memory", cstring_error(CSTRING_RC_OUTOFMEMORY)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("operation cannot procede because the cstring *", cstring_error(CSTRING_RC_FIXED)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("operation cannot procede because the cstring *", cstring_error(CSTRING_RC_BORROWED)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("operation cannot procede because the cstring *", cstring_error(CSTRING_RC_READONLY)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("*invalid*", cstring_error(CSTRING_RC_INVALIDARENA)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("custom arena functionality not currently supported", cstring_error(CSTRING_RC_CUSTOMARENANOTSUPPORTED)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("operation cannot procede because the current capacity would be exceeded*", cstring_error(CSTRING_RC_EXCEEDFIXEDCAPACITY)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("operation cannot procede because the current capacity would be exceeded*", cstring_error(CSTRING_RC_EXCEEDBORROWEDCAPACITY)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("yield operation from a dynamic-library version of cstring was not allowed", cstring_error(CSTRING_RC_CANNOTYIELDFROMSO)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("cannot use arena parameter for both borrowed memory and custom arena", cstring_error(CSTRING_RC_ARENAOVERLOADED)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("*invalid*", cstring_error(CSTRING_RC_INVALIDSTREAM)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("reached the end of stream", cstring_error(CSTRING_RC_EOF)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("*invalid*", cstring_error(CSTRING_RC_INVALIDSECTION)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("an I/O error occured", cstring_error(CSTRING_RC_IOERROR)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("system-specific failure occurred", cstring_error(CSTRING_RC_SYSTEMSPECIFICFAILURE)); - XTESTS_TEST_MULTIBYTE_STRING_MATCHES("request exceeded inherent or runtime limit", cstring_error(CSTRING_RC_REQUESTTOOLARGE)); -#endif /* XTESTS_HAS_SHWILD */ -} - -static void test_random_deprecated(void) -{ - { for (size_t i = 0; i != 100000; ++i) - { - int r = rand(); - CSTRING_RC rc = (CSTRING_RC)(STLSOFT_NUM_ELEMENTS(knownCodes) + r + 1); - - XTESTS_TEST_MULTIBYTE_STRING_EQUAL("<>", cstring_error(rc)); - }} -} - - -/* ///////////////////////////// end of file //////////////////////////// */ diff --git a/test/unit/test.unit.cstring.cstring_vector/entry.c b/test/unit/test.unit.cstring.cstring_vector/entry.c deleted file mode 100644 index b986cb0..0000000 --- a/test/unit/test.unit.cstring.cstring_vector/entry.c +++ /dev/null @@ -1,378 +0,0 @@ - - -#include - -#ifdef USE_PANTHEIOS_EXTRAS_DIAGUTIL_MAIN - -# include -#endif - -#include - -#include -#include - -/* ///////////////////////////////////////////////////////////////////////// - * forward declarations - */ - -static void test_1_00(void); -static void test_1_01(void); -static void test_1_02(void); -static void test_1_03(void); -static void test_1_04(void); -static void test_1_05(void); -static void test_1_06(void); -static void test_1_07(void); -static void test_1_08(void); -static void test_1_09(void); -static void test_1_10(void); -static void test_1_11(void); -static void test_1_12(void); -static void test_1_13(void); -static void test_1_14(void); -static void test_1_15(void); -static void test_1_16(void); -static void test_1_17(void); -static void test_1_18(void); -static void test_1_19(void); - -/* ///////////////////////////////////////////////////////////////////////// - * constants & definitions - */ - -/* ///////////////////////////////////////////////////////////////////////// - * main() - */ - -static -int main0(int argc, char **argv) -{ - int retCode = EXIT_SUCCESS; - int verbosity = 2; - - XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); - - if (XTESTS_START_RUNNER("test.unit.cstring.cstring_vector", verbosity)) - { - XTESTS_RUN_CASE(test_1_00); - XTESTS_RUN_CASE(test_1_01); - XTESTS_RUN_CASE(test_1_02); - XTESTS_RUN_CASE(test_1_03); - XTESTS_RUN_CASE(test_1_04); - XTESTS_RUN_CASE(test_1_05); - XTESTS_RUN_CASE(test_1_06); - XTESTS_RUN_CASE(test_1_07); - XTESTS_RUN_CASE(test_1_08); - XTESTS_RUN_CASE(test_1_09); - XTESTS_RUN_CASE(test_1_10); - XTESTS_RUN_CASE(test_1_11); - XTESTS_RUN_CASE(test_1_12); - XTESTS_RUN_CASE(test_1_13); - XTESTS_RUN_CASE(test_1_14); - XTESTS_RUN_CASE(test_1_15); - XTESTS_RUN_CASE(test_1_16); - XTESTS_RUN_CASE(test_1_17); - XTESTS_RUN_CASE(test_1_18); - XTESTS_RUN_CASE(test_1_19); - - XTESTS_PRINT_RESULTS(); - - XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); - } - - return retCode; -} - -int main(int argc, char **argv) -{ -#ifdef USE_PANTHEIOS_EXTRAS_DIAGUTIL_MAIN - - return pantheios_extras_diagutil_main_leak_trace_invoke(argc, argv, main0); -#else - - return main0(argc, argv); -#endif -} - -static void test_1_00(void) -{ - cstring_vector_t csv = cstring_vector_t_DEFAULT; - - XTESTS_TEST_INTEGER_EQUAL(0, csv.len); - XTESTS_TEST_POINTER_EQUAL(NULL, csv.ptr); - XTESTS_TEST_INTEGER_EQUAL(0, csv.capacity); - - cstring_vector_destroy(&csv); -} - -static void test_1_01(void) -{ - cstring_vector_t csv; - CSTRING_RC rc = cstring_vector_init(&csv, 0); - - if (CSTRING_RC_OUTOFMEMORY == rc) - { - /*XTESTS_TEST_WARN(*/ - } - else - if (CSTRING_RC_SUCCESS == rc) - { - XTESTS_TEST_INTEGER_EQUAL(0, csv.len); - XTESTS_TEST_POINTER_EQUAL(NULL, csv.ptr); - XTESTS_TEST_INTEGER_EQUAL(0, csv.capacity); - - cstring_vector_destroy(&csv); - } - else - { - XTESTS_TEST_FAIL("should only fail cstring_vector_init() with CSTRING_RC_OUTOFMEMORY"); - } -} - -static void test_1_02(void) -{ - cstring_vector_t csv; - CSTRING_RC rc = cstring_vector_init(&csv, 10); - - if (CSTRING_RC_OUTOFMEMORY == rc) - { - /*XTESTS_TEST_WARN(*/ - } - else - if (CSTRING_RC_SUCCESS == rc) - { - XTESTS_TEST_INTEGER_EQUAL(0, csv.len); - XTESTS_TEST_POINTER_NOT_EQUAL(NULL, csv.ptr); - XTESTS_TEST_INTEGER_GREATER_OR_EQUAL(10, csv.capacity); - - cstring_vector_destroy(&csv); - } - else - { - XTESTS_TEST_FAIL("should only fail cstring_vector_init() with CSTRING_RC_OUTOFMEMORY"); - } -} - -static void test_1_03(void) -{ - cstring_vector_t csv; - CSTRING_RC rc = cstring_vector_create(&csv, 0); - - if (CSTRING_RC_OUTOFMEMORY == rc) - { - /*XTESTS_TEST_WARN(*/ - } - else - if (CSTRING_RC_SUCCESS == rc) - { - XTESTS_TEST_INTEGER_EQUAL(0, csv.len); - XTESTS_TEST_POINTER_EQUAL(NULL, csv.ptr); - XTESTS_TEST_INTEGER_EQUAL(0, csv.capacity); - - cstring_vector_destroy(&csv); - } - else - { - XTESTS_TEST_FAIL("should only fail cstring_vector_create() with CSTRING_RC_OUTOFMEMORY"); - } -} - -static void test_1_04(void) -{ - cstring_vector_t csv; - CSTRING_RC rc = cstring_vector_create(&csv, 10); - - if (CSTRING_RC_OUTOFMEMORY == rc) - { - /*XTESTS_TEST_WARN(*/ - } - else - if (CSTRING_RC_SUCCESS == rc) - { - XTESTS_TEST_INTEGER_EQUAL(10, csv.len); - XTESTS_TEST_POINTER_NOT_EQUAL(NULL, csv.ptr); - XTESTS_TEST_INTEGER_GREATER_OR_EQUAL(10, csv.capacity); - - cstring_vector_destroy(&csv); - } - else - { - XTESTS_TEST_FAIL("should only fail cstring_vector_create() with CSTRING_RC_OUTOFMEMORY"); - } -} - -static void test_1_05(void) -{ - cstring_vector_t csv; - CSTRING_RC rc = cstring_vector_create(&csv, 0); - - if (CSTRING_RC_OUTOFMEMORY == rc) - { - /*XTESTS_TEST_WARN(*/ - } - else - if (CSTRING_RC_SUCCESS == rc) - { - cstring_t cs = cstring_t_DEFAULT; - - XTESTS_TEST_INTEGER_EQUAL(0, csv.len); - XTESTS_TEST_POINTER_EQUAL(NULL, csv.ptr); - XTESTS_TEST_INTEGER_EQUAL(0, csv.capacity); - - rc = cstring_vector_insertAt(&csv, 0, &cs, 1); - - if (CSTRING_RC_OUTOFMEMORY == rc) - { - /*XTESTS_TEST_WARN(*/ - } - else - if (CSTRING_RC_SUCCESS == rc) - { - XTESTS_TEST_INTEGER_EQUAL(1, csv.len); - XTESTS_TEST_POINTER_NOT_EQUAL(NULL, csv.ptr); - XTESTS_TEST_INTEGER_GREATER_OR_EQUAL(1, csv.capacity); - } - else - { - XTESTS_TEST_FAIL("should only fail cstring_vector_insertAt() with CSTRING_RC_OUTOFMEMORY"); - } - - cstring_vector_destroy(&csv); - } - else - { - XTESTS_TEST_FAIL("should only fail cstring_vector_create() with CSTRING_RC_OUTOFMEMORY"); - } -} - -static void test_1_06(void) -{ - char const* const strings[] = - { - "blah", - "", - "abcdefghijklmnopqrstuvwxyz", - "My Ever Changing Moods", - "I Can Give You Everything", - "Newborn Friend", - "Escape Velocity", - "Weapon Of Choice", - "Ain't No Doubt", - "Eye Know", - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", - }; - - cstring_vector_t csv; - CSTRING_RC rc = cstring_vector_create(&csv, 0); - - if (CSTRING_RC_OUTOFMEMORY == rc) - { - /*XTESTS_TEST_WARN(*/ - } - else - if (CSTRING_RC_SUCCESS == rc) - { - cstring_t cs = cstring_t_DEFAULT; - size_t i; - - XTESTS_TEST_INTEGER_EQUAL(0, csv.len); - XTESTS_TEST_POINTER_EQUAL(NULL, csv.ptr); - XTESTS_TEST_INTEGER_EQUAL(0, csv.capacity); - - for (i = 0; i != STLSOFT_NUM_ELEMENTS(strings) && CSTRING_RC_SUCCESS == rc; ++i) - { - rc = cstring_assign(&cs, strings[i]); - - if (CSTRING_RC_SUCCESS == rc) - { - rc = cstring_vector_insertAt(&csv, i, &cs, 1); - } - } - - cstring_destroy(&cs); - - if (CSTRING_RC_OUTOFMEMORY == rc) - { - /*XTESTS_TEST_WARN(*/ - } - else - if (CSTRING_RC_SUCCESS == rc) - { - XTESTS_REQUIRE(XTESTS_TEST_INTEGER_EQUAL(STLSOFT_NUM_ELEMENTS(strings), csv.len)); - XTESTS_TEST_POINTER_NOT_EQUAL(NULL, csv.ptr); - XTESTS_TEST_INTEGER_GREATER_OR_EQUAL(STLSOFT_NUM_ELEMENTS(strings), csv.capacity); - - for (i = 0; i != STLSOFT_NUM_ELEMENTS(strings); ++i) - { - XTESTS_TEST_INTEGER_EQUAL(strlen(strings[i]), csv.ptr[i].len); - XTESTS_TEST_MULTIBYTE_STRING_EQUAL(strings[i], csv.ptr[i].ptr); - } - } - else - { - XTESTS_TEST_FAIL("should only fail cstring_vector_insertAt() with CSTRING_RC_OUTOFMEMORY"); - } - - cstring_vector_destroy(&csv); - } - else - { - XTESTS_TEST_FAIL("should only fail cstring_vector_create() with CSTRING_RC_OUTOFMEMORY"); - } -} - -static void test_1_07(void) -{ -} - -static void test_1_08(void) -{ -} - -static void test_1_09(void) -{ -} - -static void test_1_10(void) -{ -} - -static void test_1_11(void) -{ -} - -static void test_1_12(void) -{ -} - -static void test_1_13(void) -{ -} - -static void test_1_14(void) -{ -} - -static void test_1_15(void) -{ -} - -static void test_1_16(void) -{ -} - -static void test_1_17(void) -{ -} - -static void test_1_18(void) -{ -} - -static void test_1_19(void) -{ -} - -/* ///////////////////////////// end of file //////////////////////////// */ - From af193976c1c94563f21ef973e0b27f890574014e Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Sun, 4 Oct 2026 15:41:29 +1100 Subject: [PATCH 87/88] release --- CHANGES.md | 2 +- NEWS.md | 2 +- include/cstring/cstring.h | 4 ++-- test/performance/cstring/main.cpp | 2 +- test/performance/cstring_readline/main.cpp | 2 +- test/performance/cstring_vector/main.cpp | 2 +- test/performance/perf_harness.hpp | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9412878..b5a9ad0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ # cstring - Changes -## 4.0.19 - 3rd October 2026 +## 4.0.19 - 4th 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; diff --git a/NEWS.md b/NEWS.md index b6e87cf..239b42f 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) | lone CR ends a line; `numRead` includes EOL; readonly truncate; zero-size realloc frees; **test.performance.cstring_readline**; shortened test directories | +| 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 directories | | 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 7c3f9ed..c2c5b90 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: 3rd October 2026 + * Updated: 4th 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 12 # define CSTRING_VER_CSTRING_H_CSTRING_REVISION 2 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 93 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 94 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ diff --git a/test/performance/cstring/main.cpp b/test/performance/cstring/main.cpp index af5fec3..bf68ed1 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: 3rd October 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/test/performance/cstring_readline/main.cpp b/test/performance/cstring_readline/main.cpp index fa16a88..da1a1e0 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: 3rd October 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/test/performance/cstring_vector/main.cpp b/test/performance/cstring_vector/main.cpp index b30cfe1..b0a066f 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: 3rd October 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/test/performance/perf_harness.hpp b/test/performance/perf_harness.hpp index 8f24f17..4e5ea63 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: 3rd October 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -392,7 +392,7 @@ maybe_emit_group_gap( static bool have_prev = false; static std::string prev_scenario; - static size_t prev_size = 0; + static size_t prev_size = 0; if (have_prev && ( prev_scenario != scenario || From 91d029ac62999c456536acbbed4121585d3df8e4 Mon Sep 17 00:00:00 2001 From: synesissoftware Date: Sun, 4 Oct 2026 18:28:24 +1100 Subject: [PATCH 88/88] test(cstring): add a C unit suite and shorten example and test names (#40) Add **test.unit.cstring** as a C port of **test.unit.cstring.cxx**, and shorten example and unit-test directories to the subject. Document memory contracts, allocators, and a default-use sketch in **README.md**, and note 5.x SSO, vector-owned payloads, and a macOS dylib in **TODO.md**. Record the split in **FAQ.md**, **NEWS.md**, and **CHANGES.md**. --- CHANGES.md | 18 +- FAQ.md | 6 +- NEWS.md | 2 +- README.md | 107 +- TODO.md | 3 + examples/c/CMakeLists.txt | 8 +- .../auto-buffer}/CMakeLists.txt | 2 +- .../README.md | 4 +- .../main.c | 4 +- .../CMakeLists.txt | 0 .../{example.c.cstring => cstring}/README.md | 18 +- .../c/{example.c.cstring => cstring}/main.c | 4 +- .../CMakeLists.txt | 0 .../README.md | 2 +- .../main.c | 4 +- .../CMakeLists.txt | 0 .../README.md | 6 +- .../main.c | 4 +- examples/cpp/CMakeLists.txt | 4 +- .../CMakeLists.txt | 0 .../README.md | 2 +- .../main.cpp | 4 +- .../cpp/cstring.global_memory}/CMakeLists.txt | 2 +- .../README.md | 4 +- .../main.cpp | 0 test/component/cstring_readline/entry.cpp | 4 +- .../cstring_vector_readLines/entry.cpp | 4 +- test/component/cstring_writeline/entry.cpp | 4 +- test/unit/CMakeLists.txt | 9 +- .../CMakeLists.txt | 2 +- .../{cstring.borrowed => auto-buffer}/entry.c | 6 +- test/unit/cstring.cxx/CMakeLists.txt | 2 + .../entry.cpp | 6 +- .../cstring.insert-replace/CMakeLists.txt | 2 - .../unit/cstring}/CMakeLists.txt | 2 +- test/unit/cstring/entry.c | 1434 +++++++++++++++++ .../CMakeLists.txt | 5 - test/unit/cstring_vector/CMakeLists.txt | 2 +- test/unit/insert-replace/CMakeLists.txt | 2 + .../entry.c | 2 +- test/unit/status-codes/CMakeLists.txt | 5 + .../entry.c | 6 +- 42 files changed, 1615 insertions(+), 90 deletions(-) rename examples/{cpp/example.cpp.HGLOBAL_on_x64 => c/auto-buffer}/CMakeLists.txt (60%) rename examples/c/{example.c.auto_buffer => auto-buffer}/README.md (83%) rename examples/c/{example.c.auto_buffer => auto-buffer}/main.c (96%) rename examples/c/{example.c.cstring => cstring}/CMakeLists.txt (100%) rename examples/c/{example.c.cstring => cstring}/README.md (86%) rename examples/c/{example.c.cstring => cstring}/main.c (99%) rename examples/c/{example.c.cstring_create => cstring_create}/CMakeLists.txt (100%) rename examples/c/{example.c.cstring_create => cstring_create}/README.md (85%) rename examples/c/{example.c.cstring_create => cstring_create}/main.c (93%) rename examples/c/{example.c.cstring_vector => cstring_vector}/CMakeLists.txt (100%) rename examples/c/{example.c.cstring_vector => cstring_vector}/README.md (79%) rename examples/c/{example.c.cstring_vector => cstring_vector}/main.c (98%) rename examples/cpp/{example.cpp.cstring.dynload => cstring.dynload}/CMakeLists.txt (100%) rename examples/cpp/{example.cpp.cstring.dynload => cstring.dynload}/README.md (90%) rename examples/cpp/{example.cpp.cstring.dynload => cstring.dynload}/main.cpp (97%) rename {test/unit/cstring.instance => examples/cpp/cstring.global_memory}/CMakeLists.txt (58%) rename examples/cpp/{example.cpp.HGLOBAL_on_x64 => cstring.global_memory}/README.md (75%) rename examples/cpp/{example.cpp.HGLOBAL_on_x64 => cstring.global_memory}/main.cpp (100%) rename test/unit/{cstring.borrowed => auto-buffer}/CMakeLists.txt (58%) rename test/unit/{cstring.borrowed => auto-buffer}/entry.c (98%) create mode 100644 test/unit/cstring.cxx/CMakeLists.txt rename test/unit/{cstring.instance => cstring.cxx}/entry.cpp (99%) delete mode 100644 test/unit/cstring.insert-replace/CMakeLists.txt rename {examples/c/example.c.auto_buffer => test/unit/cstring}/CMakeLists.txt (62%) create mode 100644 test/unit/cstring/entry.c delete mode 100644 test/unit/cstring_getStatusCodeString/CMakeLists.txt create mode 100644 test/unit/insert-replace/CMakeLists.txt rename test/unit/{cstring.insert-replace => insert-replace}/entry.c (99%) create mode 100644 test/unit/status-codes/CMakeLists.txt rename test/unit/{cstring_getStatusCodeString => status-codes}/entry.c (98%) diff --git a/CHANGES.md b/CHANGES.md index b5a9ad0..061e099 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,19 +5,21 @@ * 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; +* Freed a zero-size realloc-arena block on every platform (`realloc(pv, 0)` allocates on some), leaving a `NULL` pointer unchanged; * `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`; -* Freed a zero-size realloc-arena block on every platform (`realloc(pv, 0)` allocates on some), and left a NULL pointer unchanged; +* Suppressed the GCC 15 `-Wfree-nonheap-object` false positive on inlined `stlsoft::auto_buffer` destruction in **test.performance.cstring**; +* Documented memory contracts, allocators, and a default-use sketch in **README.md**, including `cstring_getStatusCodeStringLength()`, `CSTRING_VER`, and the default and index macros; * Added **test.performance.cstring_readline** (filesystem timings gated on **p99**); +* Added **test.component.cstring_readline** case `TEST_cstring_readline_READONLY_RETAINS_PAYLOAD`; +* 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; * Added borrowed-fixed construct and assign scenarios to **test.performance.cstring**, using `stlsoft::auto_buffer` for the borrowed anchor; -* Suppressed the GCC 15 `-Wfree-nonheap-object` false positive on inlined `stlsoft::auto_buffer` destruction in **test.performance.cstring**; -* Shortened test directories under **test/** to the subject, and renamed the unit suites to **test.unit.cstring.instance**, **test.unit.cstring.insert-replace**, and **test.unit.cstring.borrowed** (formerly **test.unit.cstring.1**, **test.unit.cstring.2**, and **test.unit.cstring.auto_buffer**); +* Split regular unit-test into **test.unit.cstring** (C) and **unit.cstring.cxx** (C++); +* Shared component file fixtures in **test/component/component_fixture.hpp**, and shared `time_iterations`, `emit_row`, and `write_lines_file` in **test/performance/perf_harness.hpp**; * Named unit cases `TEST_` in shouting snake case, keeping each API or type in its real spelling, and switched assertions to the terse xTests API; +* 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; * Removed empty unit cases and unused temporary-file names; -* Used `size_t` in the performance programs; -* Shared component file fixtures in **test/component/component_fixture.hpp**, and shared `time_iterations`, `emit_row`, and `write_lines_file` in **test/performance/perf_harness.hpp**; +* Shortened unit-test directories under **test/unit/** to the subject: **auto-buffer** (**test.unit.auto-buffer**, formerly **test.unit.cstring.auto_buffer**), **cstring.cxx** (**test.unit.cstring.cxx**, formerly **test.unit.cstring.1**), **insert-replace** (**test.unit.insert-replace**, formerly **test.unit.cstring.2**), **status-codes** (**test.unit.status-codes**, formerly **test.unit.cstring_getStatusCodeString**), and **cstring_vector** (**test.unit.cstring_vector**); +* Shortened example directories under **examples/** to the subject (**auto-buffer**, **cstring**, **cstring_create**, **cstring_vector**, **cstring.dynload**, **cstring.global_memory**), with CMake targets **example.c.cstring.auto_buffer**, **example.c.cstring**, **example.c.cstring_create**, **example.c.cstring_vector**, **example.cpp.cstring.dynload**, and **example.cpp.cstring.global_memory** (formerly **example.cpp.HGLOBAL_on_x64**); ## 4.0.18 - 29th September 2026 diff --git a/FAQ.md b/FAQ.md index 1f6b307..cbfe4b4 100644 --- a/FAQ.md +++ b/FAQ.md @@ -96,10 +96,10 @@ require **STLSoft** and **xTests** (and may optionally recognise **shwild**). ## Q5: "How do I build without the C++ examples and tests?" Pass `--no-cpp` (or `-C`) to **prepare_cmake.sh**, which sets CMake -`NO_CSTRING_CPP_API=ON`. That omits C++ examples and remaining C++ tests. +`NO_CSTRING_CPP_API=ON`. That omits C++ examples and **test.unit.cstring.cxx**. -The **C** unit-tests still require **STLSoft** and **xTests** unless you also -pass `--disable-testing` / `-T`. +The **C** unit-tests, including **test.unit.cstring**, still require +**STLSoft** and **xTests** unless you also pass `--disable-testing` / `-T`. ## Q6: "Where are the examples?" diff --git a/NEWS.md b/NEWS.md index 239b42f..b5c9dde 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): | -| 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 directories | +| 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**) | | 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/README.md b/README.md index 5ab4fff..bce147c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # cstring -**C**-style **string**s is a small, standalone library, that provides extensible C-style string instances and extensible arrays of such, for Unix and Windows. +Small standalone C library that provides extensible C-style strings and extensible arrays of those strings, for Unix and Windows. ![C](https://img.shields.io/badge/C-00599C?style=flat&logo=c&logoColor=white) -![C++](https://img.shields.io/badge/C%2B%2B-00599C?style=flat&logo=c%2B%2B&logoColor=white) [![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![GitHub release](https://img.shields.io/github/v/release/synesissoftware/cstring.svg)](https://github.com/synesissoftware/cstring/releases/latest) [![Last Commit](https://img.shields.io/github/last-commit/synesissoftware/cstring)](https://github.com/synesissoftware/cstring/commits/master) @@ -14,9 +13,14 @@ ## Table of Contents - [Introduction](#introduction) +- [Usage modes](#usage-modes) + - [Default use](#default-use) + - [Storage contract](#storage-contract) + - [Allocators](#allocators) - [Installation](#installation) - [Components](#components) - [Types](#types) + - [Constants](#constants) - [String API](#string-api) - [Status and capacity](#status-and-capacity) - [Creation/destruction functions](#creationdestruction-functions) @@ -34,9 +38,79 @@ ## Introduction -**cstring** is a small, standalone library that provides extensible C-style string instances and extensible arrays of such, for Unix and Windows. +**cstring** provides one resizeable string, `cstring_t`, and a vector of those strings, `cstring_vector_t`. A `cstring_t` is always a length, a pointer, a capacity, and flags. The flags select a memory contract. An owned growable string is the default. Fixed, borrowed, auto-buffer, and readonly are the other contracts. Which heap owns the memory is a separate choice: `realloc` by default, and the Windows heaps where those flags exist. -The **C** API has no non-standard dependencies. Optional C++ examples and remaining C++ tests may be omitted with `--no-cpp` / `NO_CSTRING_CPP_API`. Building tests requires **STLSoft** and **xTests** (and may optionally recognise **shwild**). +The **C** API has no non-standard dependencies. Building tests requires **STLSoft** and **xTests** (and may optionally recognise **shwild**). + + +## Usage modes + +Ordinary code uses the default contract and never sets a flag. The other contracts exist so a caller can cap growth, write into a buffer they already have, or select a Windows heap, without a second string type. + + +### Default use + +```c +#include + +#include +#include + +int main(void) +{ + cstring_t cs; + CSTRING_RC rc = cstring_create(&cs, "Hello"); + + if (CSTRING_RC_SUCCESS != rc) + { + return EXIT_FAILURE; + } + + printf("%s\n", cs.ptr); + + cstring_destroy(&cs); + + return EXIT_SUCCESS; +} +``` + + +### Storage contract + +Pass memory flags to `cstring_createEx()` or `cstring_createLenEx()`. For a borrowed buffer, `arena` is that buffer and `capacity` is its size. With no memory flags, those parameters are ignored and the string is an owned heap allocation. + +| Mode | Flags | Familiar form | If it cannot grow | +| --- | --- | --- | --- | +| Owned, growable | (none) | A heap `std::string`, or Rust `String` | `CSTRING_RC_OUTOFMEMORY` | +| Owned, fixed | `CSTRING_F_MEMORY_IS_FIXED` | That same owned string, with a hard ceiling | `CSTRING_RC_EXCEEDFIXEDCAPACITY` | +| Borrowed | `CSTRING_F_MEMORY_IS_BORROWED` (implies fixed) | A caller-owned `char buf[N]`, writable up to `N` | `CSTRING_RC_EXCEEDBORROWEDCAPACITY` | +| Auto-buffer | `CSTRING_F_MEMORY_IS_BORROWED` \| `CSTRING_F_MEMORY_CAN_GROW_TO_HEAP` | **`stlsoft::auto_buffer`** / `llvm::SmallString` | Spills to the heap, then stays there | +| Readonly | `CSTRING_F_MEMORY_IS_READONLY` | A frozen instance; with borrowed, `std::string_view` or Rust `&str` | `CSTRING_RC_READONLY` | + +`std::string` SSO keeps a small buffer inside the object; **cstring**'s auto-buffer uses a buffer you supply, of a size you choose, and `cstring_t` stays four fields; after a spill the instance stays on the heap (Rust's standard `String` has no SSO). + +* Owned, fixed, and borrowed (including Windows allocators, where the host has them): [**example.c.cstring**](./examples/c/cstring/); +* Auto-buffer: [**example.c.cstring.auto_buffer**](./examples/c/auto-buffer/); +* Win32 global memory: [**example.cpp.cstring.global_memory**](./examples/cpp/cstring.global_memory/). + + +### Allocators + +The arena flags apply to memory the library owns: the default heap, a fixed owned buffer, and the heap side of an auto-buffer. + +| Arena | Flag | Where | +| --- | --- | --- | +| `realloc` | `CSTRING_F_USE_REALLOC` (the default) | Unix and Windows | +| Win32 global memory | `CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY` | Windows | +| Process heap | `CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY` | Windows | +| COM task allocator | `CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY` | Windows | + +A few further rules: + +* `cstring_init()` stores `cstring_t_DEFAULT`. That instance does not need `cstring_destroy()`. Every `cstring_create*` does; +* `cstring_yield2()` hands back an owned payload. Borrowed and readonly instances refuse it. A Windows DLL built on `realloc` returns `CSTRING_RC_CANNOTYIELDFROMSO`; +* The character type is `char` unless `CSTRING_USE_WIDE_STRINGS` is set (normally both `UNICODE` and `_UNICODE` on Windows). `CSTRING_NO_USE_WIDE_STRINGS` forces `char`. That choice is made at compile time; +* Custom arenas (`CSTRING_F_USE_CUSTOMARENAFUNCTIONS`) are declared and return `CSTRING_RC_CUSTOMARENANOTSUPPORTED`. `CSTRING_F_MEMORY_IS_OFFSET` is set by the implementation and is not a client mode. ## Installation @@ -76,6 +150,15 @@ The C API is based around two structures: ``` +### Constants + +* `CSTRING_VER` — the composite library version; +* `cstring_t_DEFAULT` — `{ 0, NULL, 0, 0 }`, an uninitialised `cstring_t`. `cstring_init()` assigns this; +* `cstring_vector_t_DEFAULT` — the same shape for a `cstring_vector_t`; +* `cstring_vector_DEFAULT_CAPACITY` — sentinel (`~(size_t)0`) passed to creators so the implementation chooses the capacity; +* `CSTRING_FROM_END(x)` — reverse index for `cstring_insert()`, `cstring_insertLen()`, `cstring_replace()`, and `cstring_replaceLen()`; + + ### String API Defined in **cstring/cstring.h**: @@ -84,6 +167,7 @@ Defined in **cstring/cstring.h**: #### Status and capacity * `cstring_getStatusCodeString()` — returns a nul-terminated description of a `CSTRING_RC` code; +* `cstring_getStatusCodeStringLength()` — returns the length of that description, or 0 if the code is not recognised; * `cstring_setCapacity()` — adjusts capacity (subject to fixed / borrowed / readonly rules); * `cstring_yield2()` — yields ownership of the payload (and raw buffer) to the caller; @@ -134,16 +218,16 @@ Defined in **cstring/cstring.vector.h**: ## Examples -Examples live under **examples/** (`c/` and `cpp/`), each with a short **README.md**. Build them with `BUILD_EXAMPLES` (on by default); run via **run_all_examples.sh**. +Examples live under **examples/** (`c/` and `cpp/`). The directory is the subject; the built program is `example..`. Each has a short **README.md**. Build them with `BUILD_EXAMPLES` (on by default); run via **run_all_examples.sh**. | Example | Language | Notes | | ------- | -------- | ----- | -| [**example.c.auto_buffer**](./examples/c/example.c.auto_buffer/) | C | Borrowed buffer that may grow to the heap | -| [**example.c.cstring**](./examples/c/example.c.cstring/) | C | Core `cstring_t` create / assign / append / truncate / copy / swap | -| [**example.c.cstring_create**](./examples/c/example.c.cstring_create/) | C | Minimal `cstring_create()` | -| [**example.c.cstring_vector**](./examples/c/example.c.cstring_vector/) | C | Read lines into `cstring_vector_t` and sort (input path or `--`; `SIS_EXAMPLE_SMOKE` enables no-arg demo) | -| [**example.cpp.cstring.dynload**](./examples/cpp/example.cpp.cstring.dynload/) | C++ | Windows-only dynamic load of the cstring DLL | -| [**example.cpp.HGLOBAL_on_x64**](./examples/cpp/example.cpp.HGLOBAL_on_x64/) | C++ | Windows-only `CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY` | +| [**example.c.cstring**](./examples/c/cstring/) | C | Core `cstring_t` create / assign / append / truncate / copy / swap | +| [**example.c.cstring.auto_buffer**](./examples/c/auto-buffer/) | C | Borrowed buffer that may grow to the heap | +| [**example.c.cstring_create**](./examples/c/cstring_create/) | C | Minimal `cstring_create()` | +| [**example.c.cstring_vector**](./examples/c/cstring_vector/) | C | Read lines into `cstring_vector_t` and sort (input path or `--`; `SIS_EXAMPLE_SMOKE` enables no-arg demo) | +| [**example.cpp.cstring.dynload**](./examples/cpp/cstring.dynload/) | C++ | Windows-only dynamic load of the cstring DLL | +| [**example.cpp.cstring.global_memory**](./examples/cpp/cstring.global_memory/) | C++ | Windows-only `CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY` | ## Project Information @@ -197,4 +281,3 @@ Projects in which **cstring** is used include: - diff --git a/TODO.md b/TODO.md index cf57dcf..e936f3a 100644 --- a/TODO.md +++ b/TODO.md @@ -19,6 +19,8 @@ * [ ] custom arena(s); * [ ] `cstring_vector_readlineEx()` that takes a flag to prevent truncate, thereby allowing client code to add to an existing string; * [ ] 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; ## Performance improvements @@ -40,6 +42,7 @@ * [x] ~~~`CMAKE_INSTALL_LIBDIR` (replaces legacy `LIB_INSTALL_DIR`)~~~ - ✅; * [x] ~~~**CTest**~~~ - ✅; * [ ] build DLL on Windows; + * [ ] build dylib on macOS; * [x] ~~~`/MT` build option for Visual C++ (`--msvc-mt` / `MSVC_USE_MT`)~~~ - ✅; * [x] ~~~**shwild** dependency (testing only; `--no-shwild` / `NO_SHWILD`)~~~ - ✅; * [x] ~~~Doxygen (**Doxyfile**, **doc/mainpage.md**, **generate_doxygen.sh**)~~~ - ✅; diff --git a/examples/c/CMakeLists.txt b/examples/c/CMakeLists.txt index bd7b7b5..8343e22 100644 --- a/examples/c/CMakeLists.txt +++ b/examples/c/CMakeLists.txt @@ -1,5 +1,5 @@ # SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten -add_subdirectory(example.c.auto_buffer) -add_subdirectory(example.c.cstring) -add_subdirectory(example.c.cstring_create) -add_subdirectory(example.c.cstring_vector) +add_subdirectory(auto-buffer) +add_subdirectory(cstring) +add_subdirectory(cstring_create) +add_subdirectory(cstring_vector) diff --git a/examples/cpp/example.cpp.HGLOBAL_on_x64/CMakeLists.txt b/examples/c/auto-buffer/CMakeLists.txt similarity index 60% rename from examples/cpp/example.cpp.HGLOBAL_on_x64/CMakeLists.txt rename to examples/c/auto-buffer/CMakeLists.txt index 722def6..5139df0 100644 --- a/examples/cpp/example.cpp.HGLOBAL_on_x64/CMakeLists.txt +++ b/examples/c/auto-buffer/CMakeLists.txt @@ -1,2 +1,2 @@ # SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten -define_example_program(example.cpp.HGLOBAL_on_x64 main.cpp) +define_example_program(example.c.cstring.auto_buffer main.c) diff --git a/examples/c/example.c.auto_buffer/README.md b/examples/c/auto-buffer/README.md similarity index 83% rename from examples/c/example.c.auto_buffer/README.md rename to examples/c/auto-buffer/README.md index 265181a..d069e1e 100644 --- a/examples/c/example.c.auto_buffer/README.md +++ b/examples/c/auto-buffer/README.md @@ -1,4 +1,4 @@ -# example.c.auto_buffer +# example.c.cstring.auto_buffer ## Purpose @@ -14,7 +14,7 @@ Built when `BUILD_EXAMPLES` is enabled (the default). ## Run ```sh -${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/example.c.auto_buffer/example.c.auto_buffer +${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/auto-buffer/example.c.cstring.auto_buffer ``` diff --git a/examples/c/example.c.auto_buffer/main.c b/examples/c/auto-buffer/main.c similarity index 96% rename from examples/c/example.c.auto_buffer/main.c rename to examples/c/auto-buffer/main.c index 75bfe32..578a322 100644 --- a/examples/c/example.c.auto_buffer/main.c +++ b/examples/c/auto-buffer/main.c @@ -1,12 +1,12 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: examples/c/example.c.auto_buffer/main.c + * File: examples/c/auto-buffer/main.c * * Purpose: Example illustrating borrowed buffer growth to the heap * (`CSTRING_F_MEMORY_IS_BORROWED` + * `CSTRING_F_MEMORY_CAN_GROW_TO_HEAP`). * * Created: 28th July 2011 - * Updated: 2nd August 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/examples/c/example.c.cstring/CMakeLists.txt b/examples/c/cstring/CMakeLists.txt similarity index 100% rename from examples/c/example.c.cstring/CMakeLists.txt rename to examples/c/cstring/CMakeLists.txt diff --git a/examples/c/example.c.cstring/README.md b/examples/c/cstring/README.md similarity index 86% rename from examples/c/example.c.cstring/README.md rename to examples/c/cstring/README.md index 8d1810f..6fe641c 100644 --- a/examples/c/example.c.cstring/README.md +++ b/examples/c/cstring/README.md @@ -14,7 +14,7 @@ Built when `BUILD_EXAMPLES` is enabled (the default). ## Run ```sh -${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/example.c.cstring/example.c.cstring +${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/cstring/example.c.cstring ``` @@ -34,7 +34,7 @@ cstring_createLen(): Contents: some more content Appending another C-style string to the instance: - Contents: some more content - + Contents: some more content - Appending part of another C-style string to the instance: Contents: some more content - and another @@ -46,19 +46,19 @@ cstring_createLen(): Contents: some more content - and another; and a whole lot more stringy stuff to try and precipitate a reallocation, in order to check that the 3.3 change will be tested; and a last bit to try and get the remaining reallocation Truncating the instance to 10 characters: - Contents: some more + Contents: some more Copying the instance: - Contents: 0x… some more - Contents of copy: 0x… some more + Contents: 0x… some more + Contents of copy: 0x… some more Copying the instance (by C-style string): - Contents: 0x… some more - Contents of copy: 0x… some more + Contents: 0x… some more + Contents of copy: 0x… some more Swapping with the copy: - Contents: 0x… some more - Contents of copy: 0x… some more + Contents: 0x… some more + Contents of copy: 0x… some more Destroying the copy: diff --git a/examples/c/example.c.cstring/main.c b/examples/c/cstring/main.c similarity index 99% rename from examples/c/example.c.cstring/main.c rename to examples/c/cstring/main.c index 3df3645..84f9208 100644 --- a/examples/c/example.c.cstring/main.c +++ b/examples/c/cstring/main.c @@ -1,12 +1,12 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: examples/c/example.c.cstring/main.c + * File: examples/c/cstring/main.c * * Purpose: Example exercising core `cstring_t` create/assign/append/ * truncate/copy/swap paths (including Windows allocator flags * where available). * * Created: 7th July 2005 - * Updated: 27th September 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/examples/c/example.c.cstring_create/CMakeLists.txt b/examples/c/cstring_create/CMakeLists.txt similarity index 100% rename from examples/c/example.c.cstring_create/CMakeLists.txt rename to examples/c/cstring_create/CMakeLists.txt diff --git a/examples/c/example.c.cstring_create/README.md b/examples/c/cstring_create/README.md similarity index 85% rename from examples/c/example.c.cstring_create/README.md rename to examples/c/cstring_create/README.md index 117f0c9..6b56703 100644 --- a/examples/c/example.c.cstring_create/README.md +++ b/examples/c/cstring_create/README.md @@ -14,7 +14,7 @@ Built when `BUILD_EXAMPLES` is enabled (the default). ## Run ```sh -${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/example.c.cstring_create/example.c.cstring_create +${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/cstring_create/example.c.cstring_create ``` diff --git a/examples/c/example.c.cstring_create/main.c b/examples/c/cstring_create/main.c similarity index 93% rename from examples/c/example.c.cstring_create/main.c rename to examples/c/cstring_create/main.c index af630bc..fff221d 100644 --- a/examples/c/example.c.cstring_create/main.c +++ b/examples/c/cstring_create/main.c @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: examples/c/example.c.cstring_create/main.c + * File: examples/c/cstring_create/main.c * * Purpose: Minimal example of `cstring_create()` / `cstring_destroy()`. * * Created: 12th January 2024 - * Updated: 3rd October 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/examples/c/example.c.cstring_vector/CMakeLists.txt b/examples/c/cstring_vector/CMakeLists.txt similarity index 100% rename from examples/c/example.c.cstring_vector/CMakeLists.txt rename to examples/c/cstring_vector/CMakeLists.txt diff --git a/examples/c/example.c.cstring_vector/README.md b/examples/c/cstring_vector/README.md similarity index 79% rename from examples/c/example.c.cstring_vector/README.md rename to examples/c/cstring_vector/README.md index 98d25e2..568575e 100644 --- a/examples/c/example.c.cstring_vector/README.md +++ b/examples/c/cstring_vector/README.md @@ -16,9 +16,9 @@ Built when `BUILD_EXAMPLES` is enabled (the default). Requires an input source (path, or `--` for stdin). Intended for interactive / scripted human use: ```sh -${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/example.c.cstring_vector/example.c.cstring_vector path/to/file.txt -${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/example.c.cstring_vector/example.c.cstring_vector -- # stdin -printf 'beta\nalpha\ngamma\n' | ${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/example.c.cstring_vector/example.c.cstring_vector -- +${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/cstring_vector/example.c.cstring_vector path/to/file.txt +${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/cstring_vector/example.c.cstring_vector -- # stdin +printf 'beta\nalpha\ngamma\n' | ${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/cstring_vector/example.c.cstring_vector -- ``` With **`SIS_EXAMPLE_SMOKE`** set, a no-argument invocation uses a small in-process `tmpfile()` sample (no child re-exec). **`run_all_examples.sh`** exports this for automated smoke runs. Without the variable, no arguments fails with a usage error. diff --git a/examples/c/example.c.cstring_vector/main.c b/examples/c/cstring_vector/main.c similarity index 98% rename from examples/c/example.c.cstring_vector/main.c rename to examples/c/cstring_vector/main.c index ce4b24b..35972ed 100644 --- a/examples/c/example.c.cstring_vector/main.c +++ b/examples/c/cstring_vector/main.c @@ -1,11 +1,11 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: examples/c/example.c.cstring_vector/main.c + * File: examples/c/cstring_vector/main.c * * Purpose: Example reading lines into a `cstring_vector_t` and sorting * them. * * Created: 12th January 2024 - * Updated: 3rd October 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt index cffad6f..5d84add 100644 --- a/examples/cpp/CMakeLists.txt +++ b/examples/cpp/CMakeLists.txt @@ -1,7 +1,7 @@ if(_BUILD_AS_WIN32) - add_subdirectory(example.cpp.cstring.dynload) - add_subdirectory(example.cpp.HGLOBAL_on_x64) + add_subdirectory(cstring.dynload) + add_subdirectory(cstring.global_memory) endif(_BUILD_AS_WIN32) diff --git a/examples/cpp/example.cpp.cstring.dynload/CMakeLists.txt b/examples/cpp/cstring.dynload/CMakeLists.txt similarity index 100% rename from examples/cpp/example.cpp.cstring.dynload/CMakeLists.txt rename to examples/cpp/cstring.dynload/CMakeLists.txt diff --git a/examples/cpp/example.cpp.cstring.dynload/README.md b/examples/cpp/cstring.dynload/README.md similarity index 90% rename from examples/cpp/example.cpp.cstring.dynload/README.md rename to examples/cpp/cstring.dynload/README.md index e33bffc..0f4bb01 100644 --- a/examples/cpp/example.cpp.cstring.dynload/README.md +++ b/examples/cpp/cstring.dynload/README.md @@ -14,7 +14,7 @@ Built when `BUILD_EXAMPLES` is enabled, `NO_CSTRING_CPP_API` is off, and the tar ## Run ```sh -${SIS_CMAKE_BUILD_DIR:-./_build}/examples/cpp/example.cpp.cstring.dynload/example.cpp.cstring.dynload +${SIS_CMAKE_BUILD_DIR:-./_build}/examples/cpp/cstring.dynload/example.cpp.cstring.dynload ``` Expects a suitable **cstring** DLL (for example `cstring.3.debug.dll`) to be loadable on the process search path. diff --git a/examples/cpp/example.cpp.cstring.dynload/main.cpp b/examples/cpp/cstring.dynload/main.cpp similarity index 97% rename from examples/cpp/example.cpp.cstring.dynload/main.cpp rename to examples/cpp/cstring.dynload/main.cpp index 11c17b4..5b0378f 100644 --- a/examples/cpp/example.cpp.cstring.dynload/main.cpp +++ b/examples/cpp/cstring.dynload/main.cpp @@ -1,11 +1,11 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: examples/cpp/example.cpp.cstring.dynload/main.cpp + * File: examples/cpp/cstring.dynload/main.cpp * * Purpose: Windows-only example loading the cstring DLL dynamically via * `LoadLibrary` / `GetProcAddress`. * * Created: 19th August 2005 - * Updated: 27th September 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/test/unit/cstring.instance/CMakeLists.txt b/examples/cpp/cstring.global_memory/CMakeLists.txt similarity index 58% rename from test/unit/cstring.instance/CMakeLists.txt rename to examples/cpp/cstring.global_memory/CMakeLists.txt index 58f4b47..7011da3 100644 --- a/test/unit/cstring.instance/CMakeLists.txt +++ b/examples/cpp/cstring.global_memory/CMakeLists.txt @@ -1,2 +1,2 @@ # SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten -define_automated_test_program(test.unit.cstring.instance entry.cpp) +define_example_program(example.cpp.cstring.global_memory main.cpp) diff --git a/examples/cpp/example.cpp.HGLOBAL_on_x64/README.md b/examples/cpp/cstring.global_memory/README.md similarity index 75% rename from examples/cpp/example.cpp.HGLOBAL_on_x64/README.md rename to examples/cpp/cstring.global_memory/README.md index 16bc989..5bc0cea 100644 --- a/examples/cpp/example.cpp.HGLOBAL_on_x64/README.md +++ b/examples/cpp/cstring.global_memory/README.md @@ -1,4 +1,4 @@ -# example.cpp.HGLOBAL_on_x64 +# example.cpp.cstring.global_memory ## Purpose @@ -14,7 +14,7 @@ Built when `BUILD_EXAMPLES` is enabled, `NO_CSTRING_CPP_API` is off, and the tar ## Run ```sh -${SIS_CMAKE_BUILD_DIR:-./_build}/examples/cpp/example.cpp.HGLOBAL_on_x64/example.cpp.HGLOBAL_on_x64 +${SIS_CMAKE_BUILD_DIR:-./_build}/examples/cpp/cstring.global_memory/example.cpp.cstring.global_memory ``` diff --git a/examples/cpp/example.cpp.HGLOBAL_on_x64/main.cpp b/examples/cpp/cstring.global_memory/main.cpp similarity index 100% rename from examples/cpp/example.cpp.HGLOBAL_on_x64/main.cpp rename to examples/cpp/cstring.global_memory/main.cpp diff --git a/test/component/cstring_readline/entry.cpp b/test/component/cstring_readline/entry.cpp index 0bcf330..fa4ef14 100644 --- a/test/component/cstring_readline/entry.cpp +++ b/test/component/cstring_readline/entry.cpp @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: test.component.cstring_readline/entry.cpp + * File: test/component/cstring_readline/entry.cpp * * Purpose: Component-tests `cstring_readline()`. * * Created: 23rd May 2009 - * Updated: 3rd October 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/test/component/cstring_vector_readLines/entry.cpp b/test/component/cstring_vector_readLines/entry.cpp index f2ef821..e45cad6 100644 --- a/test/component/cstring_vector_readLines/entry.cpp +++ b/test/component/cstring_vector_readLines/entry.cpp @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: test.component.cstring_vector_readLines/entry.cpp + * File: test/component/cstring_vector_readLines/entry.cpp * * Purpose: Component-tests `cstring_vector_readLines()`. * * Created: 27th September 2026 - * Updated: 3rd October 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/test/component/cstring_writeline/entry.cpp b/test/component/cstring_writeline/entry.cpp index 3ca1090..8ef8166 100644 --- a/test/component/cstring_writeline/entry.cpp +++ b/test/component/cstring_writeline/entry.cpp @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: test.component.cstring_writeline/entry.cpp + * File: test/component/cstring_writeline/entry.cpp * * Purpose: Unit-tests of `cstring_write()` and `cstring_writeline()`. * * Created: 10th August 2020 - * Updated: 3rd October 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 3ea2254..ecb3f12 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -1,12 +1,13 @@ -add_subdirectory(cstring_getStatusCodeString) +add_subdirectory(auto-buffer) +add_subdirectory(cstring) add_subdirectory(cstring_vector) -add_subdirectory(cstring.borrowed) if(NOT NO_CSTRING_CPP_API) - add_subdirectory(cstring.instance) + add_subdirectory(cstring.cxx) endif() -add_subdirectory(cstring.insert-replace) +add_subdirectory(insert-replace) +add_subdirectory(status-codes) # ############################## end of file ############################# # diff --git a/test/unit/cstring.borrowed/CMakeLists.txt b/test/unit/auto-buffer/CMakeLists.txt similarity index 58% rename from test/unit/cstring.borrowed/CMakeLists.txt rename to test/unit/auto-buffer/CMakeLists.txt index ae01815..57a2d51 100644 --- a/test/unit/cstring.borrowed/CMakeLists.txt +++ b/test/unit/auto-buffer/CMakeLists.txt @@ -1,2 +1,2 @@ # SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten -define_automated_test_program(test.unit.cstring.borrowed entry.c) +define_automated_test_program(test.unit.auto-buffer entry.c) diff --git a/test/unit/cstring.borrowed/entry.c b/test/unit/auto-buffer/entry.c similarity index 98% rename from test/unit/cstring.borrowed/entry.c rename to test/unit/auto-buffer/entry.c index 4e923da..c301667 100644 --- a/test/unit/cstring.borrowed/entry.c +++ b/test/unit/auto-buffer/entry.c @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: test/unit/cstring.borrowed/entry.c + * File: test/unit/auto-buffer/entry.c * * Purpose: Tests borrowed string functionality. * * Created: 28th July 2011 - * Updated: 3rd October 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -75,7 +75,7 @@ int main(int argc, char **argv) XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); - if (XTESTS_START_RUNNER("cstring.borrowed", verbosity)) + if (XTESTS_START_RUNNER("test.unit.auto-buffer", verbosity)) { XTESTS_RUN_CASE(TEST_cstring_createEx_FLAG_COMBINATIONS); XTESTS_RUN_CASE(TEST_cstring_createEx_GROW_TO_HEAP); diff --git a/test/unit/cstring.cxx/CMakeLists.txt b/test/unit/cstring.cxx/CMakeLists.txt new file mode 100644 index 0000000..0a5aa85 --- /dev/null +++ b/test/unit/cstring.cxx/CMakeLists.txt @@ -0,0 +1,2 @@ +# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +define_automated_test_program(test.unit.cstring.cxx entry.cpp) diff --git a/test/unit/cstring.instance/entry.cpp b/test/unit/cstring.cxx/entry.cpp similarity index 99% rename from test/unit/cstring.instance/entry.cpp rename to test/unit/cstring.cxx/entry.cpp index 1d2a1f9..60a18fa 100644 --- a/test/unit/cstring.instance/entry.cpp +++ b/test/unit/cstring.cxx/entry.cpp @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: test/unit/cstring.instance/entry.cpp + * File: test/unit/cstring.cxx/entry.cpp * * Purpose: Unit-tests for cstring instance general functionality. * * Created: 23rd May 2009 - * Updated: 3rd October 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -106,7 +106,7 @@ int main(int argc, char **argv) XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); - if (XTESTS_START_RUNNER("test.unit.cstring.1", verbosity)) + if (XTESTS_START_RUNNER("test.unit.cstring.cxx", verbosity)) { XTESTS_RUN_CASE(TEST_cstring_t_DEFAULT); XTESTS_RUN_CASE(TEST_cstring_init); diff --git a/test/unit/cstring.insert-replace/CMakeLists.txt b/test/unit/cstring.insert-replace/CMakeLists.txt deleted file mode 100644 index 12ea3b4..0000000 --- a/test/unit/cstring.insert-replace/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten -define_automated_test_program(test.unit.cstring.insert-replace entry.c) diff --git a/examples/c/example.c.auto_buffer/CMakeLists.txt b/test/unit/cstring/CMakeLists.txt similarity index 62% rename from examples/c/example.c.auto_buffer/CMakeLists.txt rename to test/unit/cstring/CMakeLists.txt index 8b26151..f622ed8 100644 --- a/examples/c/example.c.auto_buffer/CMakeLists.txt +++ b/test/unit/cstring/CMakeLists.txt @@ -1,2 +1,2 @@ # SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten -define_example_program(example.c.auto_buffer main.c) +define_automated_test_program(test.unit.cstring entry.c) diff --git a/test/unit/cstring/entry.c b/test/unit/cstring/entry.c new file mode 100644 index 0000000..5c41fea --- /dev/null +++ b/test/unit/cstring/entry.c @@ -0,0 +1,1434 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/unit/cstring/entry.c + * + * Purpose: Unit-tests for cstring instance general functionality. + * + * Created: 23rd May 2009 + * Updated: 4th October 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * compatibility + */ + +#define CSTRING_OBSOLETE + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +/* ///////////////////////////////////// + * test component header file include(s) + */ + +#include + +/* ///////////////////////////////////// + * general includes + */ + +/* xTests header files */ +#include + +/* STLSoft header files */ +#include +#ifdef _WIN32 +# include +# include +#endif /* _WIN32 */ + +/* Standard C header files */ +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * forward declarations + */ + +static void TEST_cstring_t_DEFAULT(void); +static void TEST_cstring_init(void); +static void TEST_cstring_setCapacity(void); +static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AND_EMPTY(void); +static void TEST_cstring_assign_AND_cstring_create(void); +static void TEST_cstring_assignLen_AND_cstring_createLen(void); +static void TEST_cstring_append_AND_cstring_appendLen(void); +static void TEST_cstring_truncate(void); +static void TEST_cstring_appendLen_KEEPS_CAPACITY(void); + +static void TEST_cstring_init_AND_cstring_destroy(void); +static void TEST_cstring_create(void); +static void TEST_cstring_createLen(void); +static void TEST_cstring_createEx(void); +static void TEST_cstring_createLenEx(void); +static void TEST_cstring_assignLen_KEEPS_CAPACITY(void); +static void TEST_cstring_assign_KEEPS_CAPACITY(void); +static void TEST_cstring_copy(void); +static void TEST_cstring_yield(void); +static void TEST_cstring_yield2(void); +static void TEST_cstring_swap(void); + + +/* ///////////////////////////////////////////////////////////////////////// + * constants & definitions + */ + +static size_t const APPEND_ITERATIONS = 1000000u; +static size_t const ASSIGN_ITERATIONS = 100000u; + + +/* ///////////////////////////////////////////////////////////////////////// + * compiler compatibility + */ + +#if defined(STLSOFT_COMPILER_IS_MSVC) +# if _MSC_VER >= 1200 +# pragma warning(push) +# endif /* compiler */ +# pragma warning(disable : 4702) +#endif /* compiler */ + + +/* ///////////////////////////////////////////////////////////////////////// + * main + */ + +int main(int argc, char* argv[]) +{ + int retCode = EXIT_SUCCESS; + int verbosity = 2; + + XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); + + if (XTESTS_START_RUNNER("test.unit.cstring", verbosity)) + { + XTESTS_RUN_CASE(TEST_cstring_t_DEFAULT); + XTESTS_RUN_CASE(TEST_cstring_init); + XTESTS_RUN_CASE(TEST_cstring_setCapacity); + XTESTS_RUN_CASE(TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AND_EMPTY); + XTESTS_RUN_CASE(TEST_cstring_assign_AND_cstring_create); + XTESTS_RUN_CASE(TEST_cstring_assignLen_AND_cstring_createLen); + XTESTS_RUN_CASE(TEST_cstring_append_AND_cstring_appendLen); + XTESTS_RUN_CASE(TEST_cstring_truncate); + XTESTS_RUN_CASE(TEST_cstring_appendLen_KEEPS_CAPACITY); + + XTESTS_RUN_CASE(TEST_cstring_init_AND_cstring_destroy); + XTESTS_RUN_CASE(TEST_cstring_create); + XTESTS_RUN_CASE(TEST_cstring_createLen); + XTESTS_RUN_CASE(TEST_cstring_createEx); + XTESTS_RUN_CASE(TEST_cstring_createLenEx); + XTESTS_RUN_CASE(TEST_cstring_assignLen_KEEPS_CAPACITY); + XTESTS_RUN_CASE(TEST_cstring_assign_KEEPS_CAPACITY); + XTESTS_RUN_CASE(TEST_cstring_copy); + XTESTS_RUN_CASE(TEST_cstring_yield); + XTESTS_RUN_CASE(TEST_cstring_yield2); + XTESTS_RUN_CASE(TEST_cstring_swap); + + XTESTS_PRINT_RESULTS(); + + XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); + } + + return retCode; +} + + +/* ///////////////////////////////////////////////////////////////////////// + * compiler compatibility + */ + +#if defined(STLSOFT_COMPILER_IS_MSVC) +# if _MSC_VER >= 1200 +# pragma warning(pop) +# endif /* compiler */ +#endif /* compiler */ + + +/* ///////////////////////////////////////////////////////////////////////// + * compatibility + */ + +#ifdef CSTRING_USE_WIDE_STRINGS + +# define CSTRING_T_(x) L ## x +# define TEST_STR_EQ_ TEST_WS_EQ +# define TEST_STR_EQ_N_ TEST_WS_EQ_N +#else /* ? CSTRING_USE_WIDE_STRINGS */ + +# define CSTRING_T_(x) x +# define TEST_STR_EQ_ TEST_MS_EQ +# define TEST_STR_EQ_N_ TEST_MS_EQ_N +#endif /* CSTRING_USE_WIDE_STRINGS */ + + +/* ///////////////////////////////////////////////////////////////////////// + * helper functions + */ + + +/* ///////////////////////////////////////////////////////////////////////// + * test function implementations + */ + + +static cstring_char_t* make_tilde_string_(size_t n) +{ + cstring_char_t* s = (cstring_char_t*)malloc((n + 1u) * sizeof(cstring_char_t)); + size_t i; + + if (NULL == s) + { + return NULL; + } + + { for (i = 0; i != n; ++i) + { + s[i] = CSTRING_T_('~'); + }} + s[n] = CSTRING_T_('\0'); + + return s; +} + + +static cstring_char_t const alphabet[] = CSTRING_T_("abcdefghijklmnopqrstuvwxyz"); + + +static void TEST_cstring_t_DEFAULT(void) +{ + cstring_t str = cstring_t_DEFAULT; + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_GE(str.len, str.capacity); +} + +static void TEST_cstring_init(void) +{ + cstring_t str; + + cstring_init(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_GE(str.len, str.capacity); +} + +static void TEST_cstring_setCapacity(void) +{ + cstring_t str = cstring_t_DEFAULT; + CSTRING_RC rc = cstring_setCapacity(&str, 10); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not set capacity", cstring_getStatusCodeString(rc)); + } + else + { + TEST_INT_EQ(0u, str.len); + TEST_PTR_NE(NULL, str.ptr); + TEST_INT_GE(10u, str.capacity); + TEST_INT_GE(str.len, str.capacity); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } +} + +static void TEST_cstring_assign_AND_cstring_create_AND_cstring_createLen_NULL_AND_EMPTY(void) +{ + { + cstring_t str = cstring_t_DEFAULT; + CSTRING_RC rc = cstring_assign(&str, NULL); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not assign", cstring_getStatusCodeString(rc)); + } + 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); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + } + + { + cstring_t str = cstring_t_DEFAULT; + CSTRING_RC rc = cstring_assign(&str, CSTRING_T_("")); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not assign", cstring_getStatusCodeString(rc)); + } + 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); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + } + + { + cstring_t str; + CSTRING_RC rc = cstring_create(&str, NULL); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not create", cstring_getStatusCodeString(rc)); + } + 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); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + } + + { + cstring_t str; + CSTRING_RC rc = cstring_create(&str, CSTRING_T_("")); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not create", cstring_getStatusCodeString(rc)); + } + 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); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + } + + { + cstring_t str; + CSTRING_RC rc = cstring_createLen(&str, NULL, 0u); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); + } + 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); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + } + + { + cstring_t str; + CSTRING_RC rc = cstring_createLen(&str, CSTRING_T_(""), 0u); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); + } + 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); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + } + + { + cstring_t str; + CSTRING_RC rc = cstring_createLen(&str, NULL, 5u); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); + } + else + { + TEST_INT_EQ(5u, str.len); + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(CSTRING_T_(""), str.ptr); + TEST_CHAR_EQ('\0', str.ptr[0]); + TEST_CHAR_EQ('\0', str.ptr[1]); + TEST_CHAR_EQ('\0', str.ptr[2]); + TEST_CHAR_EQ('\0', str.ptr[3]); + TEST_CHAR_EQ('\0', str.ptr[4]); + TEST_CHAR_EQ('\0', str.ptr[5]); + TEST_INT_GE(5u, str.capacity); + TEST_INT_GE(str.len, str.capacity); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + } + + { + cstring_t str; + CSTRING_RC rc = cstring_createLen(&str, CSTRING_T_(""), 5u); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); + } + else + { + TEST_INT_EQ(5u, str.len); + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(CSTRING_T_(""), str.ptr); + TEST_CHAR_EQ('\0', str.ptr[0]); + TEST_CHAR_EQ('\0', str.ptr[1]); + TEST_CHAR_EQ('\0', str.ptr[2]); + TEST_CHAR_EQ('\0', str.ptr[3]); + TEST_CHAR_EQ('\0', str.ptr[4]); + TEST_CHAR_EQ('\0', str.ptr[5]); + TEST_INT_GE(5u, str.capacity); + TEST_INT_GE(str.len, str.capacity); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + } +} + +static void TEST_cstring_assign_AND_cstring_create(void) +{ + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_char_t const* s = alphabet + i; + const size_t cch = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_t str = cstring_t_DEFAULT; + CSTRING_RC rc = cstring_assign(&str, s); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not assign", cstring_getStatusCodeString(rc)); + } + else + { + TEST_INT_EQ(cch, str.len); + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(cch, str.capacity); + TEST_INT_GE(str.len, str.capacity); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + }} + + { + cstring_t str = cstring_t_DEFAULT; + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_char_t const* s = alphabet + i; + const size_t cch = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + CSTRING_RC rc = cstring_assign(&str, s); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not assign", cstring_getStatusCodeString(rc)); + } + else + { + TEST_INT_EQ(cch, str.len); + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GT(cch, str.capacity); + TEST_INT_GE(str.len, str.capacity); + } + }} + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_char_t const* s = alphabet + i; + const size_t cch = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_t str; + CSTRING_RC rc = cstring_create(&str, s); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not create", cstring_getStatusCodeString(rc)); + } + else + { + TEST_INT_EQ(cch, str.len); + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(cch, str.capacity); + TEST_INT_GE(str.len, str.capacity); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + }} +} + +static void TEST_cstring_assignLen_AND_cstring_createLen(void) +{ + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_char_t const* s = alphabet; + const size_t cch = i; + cstring_t str = cstring_t_DEFAULT; + CSTRING_RC rc = cstring_assignLen(&str, s, i); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not assign (with length)", cstring_getStatusCodeString(rc)); + } + 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); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + }} + + { + cstring_t str = cstring_t_DEFAULT; + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_char_t const* s = alphabet; + const size_t cch = i; + CSTRING_RC rc = cstring_assignLen(&str, s, i); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not assign (with length)", cstring_getStatusCodeString(rc)); + } + 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); + } + }} + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_char_t const* s = alphabet; + const size_t cch = i; + cstring_t str; + CSTRING_RC rc = cstring_createLen(&str, s, i); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not create (with length)", cstring_getStatusCodeString(rc)); + } + 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); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + }} +} + +static void TEST_cstring_append_AND_cstring_appendLen(void) +{ + { + cstring_t str = cstring_t_DEFAULT; + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_char_t s[2] = { alphabet[i], '\0' }; + CSTRING_RC rc = cstring_append(&str, s); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not append", cstring_getStatusCodeString(rc)); + } + else + { + TEST_INT_EQ(i + 1, str.len); + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_N_(alphabet, str.ptr, (int)i); + TEST_INT_GE(i + 1, str.capacity); + TEST_INT_GE(str.len, str.capacity); + } + }} + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + + { + cstring_t str = cstring_t_DEFAULT; + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + CSTRING_RC rc = cstring_appendLen(&str, &alphabet[i], 1); + + if (CSTRING_RC_SUCCESS != rc) + { + TEST_FAIL_WITH_QUALIFIER("could not append (with length)", cstring_getStatusCodeString(rc)); + } + else + { + TEST_INT_EQ(i + 1, str.len); + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_N_(alphabet, str.ptr, (int)i); + TEST_INT_GT(i, str.capacity); + TEST_INT_GE(str.len, str.capacity); + } + }} + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } +} + +static void TEST_cstring_truncate(void) +{ + { + cstring_t str = cstring_t_DEFAULT; + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, cstring_truncate(&str, 0u)); + TEST_INT_EQ(0u, str.len); + TEST_INT_EQ(0u, str.capacity); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, cstring_truncate(&str, 1u)); + TEST_INT_EQ(0u, str.len); + TEST_INT_EQ(0u, str.capacity); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, cstring_truncate(&str, 100u)); + TEST_INT_EQ(0u, str.len); + TEST_INT_EQ(0u, str.capacity); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, cstring_truncate(&str, 100000u)); + TEST_INT_EQ(0u, str.len); + TEST_INT_EQ(0u, str.capacity); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + + { + cstring_t str; + + if (CSTRING_RC_SUCCESS == cstring_create(&str, alphabet)) + { + { for (size_t i = 1000; ; --i) + { + cstring_truncate(&str, i); + + TEST_INT_LE(26u, str.len); + + if (i < 26u) + { + TEST_INT_EQ(i, str.len); + } + + if (0u == i) + { + break; + } + }} + } + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } +} + +static void TEST_cstring_appendLen_KEEPS_CAPACITY(void) +{ + cstring_t str = cstring_t_DEFAULT; + + { for (size_t c = 1u; c != APPEND_ITERATIONS; c *= 10u) + { + cstring_setCapacity(&str, c); + + cstring_char_t const* const p = str.ptr; + + { for (size_t i = 0; i != str.capacity; ++i) + { + cstring_appendLen(&str, CSTRING_T_("~"), 1); + + TEST_INT_EQ(i + 1, str.len); + TEST_PTR_EQ(p, str.ptr); + TEST_INT_GE(c, str.capacity); + }} + + cstring_truncate(&str, 0u); + }} + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); +} + + +static void TEST_cstring_init_AND_cstring_destroy(void) +{ + cstring_t str; + + cstring_init(&str); + + TEST_INT_GE(str.len, str.capacity); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); +} + +static void TEST_cstring_create(void) +{ + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_t str; + const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_char_t const* const s = alphabet + n; + + cstring_create(&str, s); + + TEST_INT_GE(i, str.len); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + }} +} + +static void TEST_cstring_createLen(void) +{ + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_t str; + const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_char_t const* const s = alphabet + n; + + cstring_createLen(&str, s, i); + + TEST_INT_GE(i, str.len); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + }} +} + +static void TEST_cstring_createEx(void) +{ + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_t str; + const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_char_t const* const s = alphabet + n; + cstring_flags_t flags = 0; + + 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); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + }} + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_t str; + const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_char_t const* const s = alphabet + n; + cstring_flags_t flags = CSTRING_F_MEMORY_IS_BORROWED; + cstring_char_t buff[] = CSTRING_T_("[01234567890123456789]"); + CSTRING_RC rc; + + rc = cstring_createEx(&str, s, flags, &buff[0] + 1, STLSOFT_NUM_ELEMENTS(buff) - 3); + + TEST_CHAR_EQ(CSTRING_T_('['), buff[0]); + TEST_CHAR_EQ(CSTRING_T_(']'), buff[STLSOFT_NUM_ELEMENTS(buff) - 2]); + TEST_CHAR_EQ(CSTRING_T_('\0'), buff[STLSOFT_NUM_ELEMENTS(buff) - 1]); + + if (i < STLSOFT_NUM_ELEMENTS(buff) - 3) + { + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + + TEST_INT_GE(i, str.len); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + else + { + TEST_ENUM_EQ(CSTRING_RC_EXCEEDBORROWEDCAPACITY, rc); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + }} + +#ifdef _WIN32 + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_t str; + const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_char_t const* const s = alphabet + n; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY; + + 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)); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + }} + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_t str; + const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_char_t const* const s = alphabet + n; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY; + + 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)); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + }} + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_t str; + const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_char_t const* const s = alphabet + n; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY; + + 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)); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + }} +#endif /* _WIN32 */ +} + +static void TEST_cstring_createLenEx(void) +{ + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_t str; + const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_char_t const* const s = alphabet + n; + cstring_flags_t flags = 0; + + 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); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + }} + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_t str; + const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_char_t const* const s = alphabet + n; + cstring_flags_t flags = CSTRING_F_MEMORY_IS_BORROWED; + cstring_char_t buff[] = CSTRING_T_("[01234567890123456789]"); + CSTRING_RC rc; + + rc = cstring_createLenEx(&str, s, i, flags, &buff[0] + 1, STLSOFT_NUM_ELEMENTS(buff) - 3); + + TEST_CHAR_EQ(CSTRING_T_('['), buff[0]); + TEST_CHAR_EQ(CSTRING_T_(']'), buff[STLSOFT_NUM_ELEMENTS(buff) - 2]); + TEST_CHAR_EQ(CSTRING_T_('\0'), buff[STLSOFT_NUM_ELEMENTS(buff) - 1]); + + if (i < STLSOFT_NUM_ELEMENTS(buff) - 3) + { + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + + TEST_INT_GE(i, str.len); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(str.len, str.capacity); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + else + { + TEST_ENUM_EQ(CSTRING_RC_EXCEEDBORROWEDCAPACITY, rc); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + }} + +#ifdef _WIN32 + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_t str; + const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_char_t const* const s = alphabet + n; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY; + + 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)); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + }} + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_t str; + const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_char_t const* const s = alphabet + n; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY; + + 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)); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + }} + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_t str; + const size_t n = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_char_t const* const s = alphabet + n; + cstring_flags_t flags = CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY; + + 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)); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + }} +#endif /* _WIN32 */ +} + +static void TEST_cstring_assignLen_KEEPS_CAPACITY(void) +{ + cstring_t str = cstring_t_DEFAULT; + + { for (size_t c = 1u; c != ASSIGN_ITERATIONS; c *= 10u) + { + cstring_setCapacity(&str, c); + + cstring_char_t* const s = make_tilde_string_(str.capacity); + cstring_char_t const* const p = str.ptr; + + if (NULL == s) + { + TEST_FAIL_WITH_QUALIFIER("could not allocate comparison string", NULL); + } + else + { + { for (size_t i = 0; i != str.capacity + 1; ++i) + { + cstring_assignLen(&str, s, i); + + TEST_INT_EQ(i, str.len); + TEST_PTR_EQ(p, str.ptr); + TEST_INT_GE(c, str.capacity); + }} + + free(s); + } + + cstring_truncate(&str, 0u); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_NE(NULL, str.ptr); + TEST_INT_NE(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + }} + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); +} + +static void TEST_cstring_assign_KEEPS_CAPACITY(void) +{ + cstring_t str = cstring_t_DEFAULT; + + { for (size_t c = 1u; c != ASSIGN_ITERATIONS; c *= 10u) + { + cstring_setCapacity(&str, c); + + size_t const n = str.capacity; + cstring_char_t* const s = make_tilde_string_(n); + cstring_char_t const* const p = str.ptr; + + if (NULL == s) + { + TEST_FAIL_WITH_QUALIFIER("could not allocate comparison string", NULL); + } + else + { + { for (size_t i = 0; i != str.capacity + 1; ++i) + { + cstring_assign(&str, s + i); + + TEST_INT_EQ(n - i, str.len); + TEST_PTR_EQ(p, str.ptr); + TEST_INT_GE(c, str.capacity); + }} + + free(s); + } + + cstring_truncate(&str, 0u); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_NE(NULL, str.ptr); + TEST_INT_NE(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + }} + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); +} + +static void TEST_cstring_copy(void) +{ + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_char_t const* s = alphabet + i; + const size_t cch = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_t str1 = cstring_t_DEFAULT; + cstring_t str2 = cstring_t_DEFAULT; + CSTRING_RC rc = cstring_assign(&str1, s); + + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(cch, str1.len); + TEST_PTR_NE(NULL, str1.ptr); + TEST_STR_EQ_(s, str1.ptr); + TEST_INT_GE(cch, str1.capacity); + TEST_INT_GE(str1.len, str1.capacity); + + rc = cstring_copy(&str2, &str1); + + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(cch, str2.len); + TEST_PTR_NE(NULL, str2.ptr); + TEST_STR_EQ_(s, str2.ptr); + TEST_INT_GE(cch, str2.capacity); + TEST_INT_GE(str2.len, str2.capacity); + + TEST_PTR_NE(str1.ptr, str2.ptr); + + cstring_destroy(&str1); + cstring_destroy(&str2); + + TEST_INT_EQ(0u, str1.len); + TEST_PTR_EQ(NULL, str1.ptr); + TEST_INT_EQ(0u, str1.capacity); + TEST_INT_EQ(0, str1.flags); + + TEST_INT_EQ(0u, str2.len); + TEST_PTR_EQ(NULL, str2.ptr); + TEST_INT_EQ(0u, str2.capacity); + TEST_INT_EQ(0, str2.flags); + }} +} + +static void TEST_cstring_yield(void) +{ + { + cstring_t str = cstring_t_DEFAULT; + cstring_char_t* p; + CSTRING_RC rc = cstring_yield(&str, &p); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + + TEST_PTR_EQ(NULL, p); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_char_t const* s = alphabet + i; + const size_t cch = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_t str = cstring_t_DEFAULT; + CSTRING_RC rc = cstring_assign(&str, s); + cstring_char_t* p; + + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(cch, str.len); + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(cch, str.capacity); + TEST_INT_GE(str.len, str.capacity); + + rc = cstring_yield(&str, &p); + + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + + TEST_PTR_NE(NULL, p); + TEST_STR_EQ_(s, p); + + free(p); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + }} +} + +static void TEST_cstring_yield2(void) +{ + { + cstring_t str = cstring_t_DEFAULT; + cstring_char_t* p; + void* raw; + CSTRING_RC rc = cstring_yield2(&str, &p, &raw); + + TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc); + + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + + TEST_PTR_EQ(NULL, p); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + } + + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_char_t const* s = alphabet + i; + const size_t cch = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_t str = cstring_t_DEFAULT; + CSTRING_RC rc = cstring_assign(&str, s); + cstring_char_t* p; + void* raw; + + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(cch, str.len); + TEST_PTR_NE(NULL, str.ptr); + TEST_STR_EQ_(s, str.ptr); + TEST_INT_GE(cch, str.capacity); + TEST_INT_GE(str.len, str.capacity); + + rc = cstring_yield2(&str, &p, &raw); + + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + + TEST_PTR_NE(NULL, p); + TEST_STR_EQ_(s, p); + + free(raw); + + cstring_destroy(&str); + + TEST_INT_EQ(0u, str.len); + TEST_PTR_EQ(NULL, str.ptr); + TEST_INT_EQ(0u, str.capacity); + TEST_INT_EQ(0, str.flags); + }} +} + +static void TEST_cstring_swap(void) +{ + { for (size_t i = 0; i != STLSOFT_NUM_ELEMENTS(alphabet) - 1; ++i) + { + cstring_char_t const* s = alphabet + i; + const size_t cch = (STLSOFT_NUM_ELEMENTS(alphabet) - 1) - i; + cstring_t str1 = cstring_t_DEFAULT; + cstring_t str2 = cstring_t_DEFAULT; + CSTRING_RC rc = cstring_assign(&str1, s); + + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(cch, str1.len); + TEST_PTR_NE(NULL, str1.ptr); + TEST_STR_EQ_(s, str1.ptr); + TEST_INT_GE(cch, str1.capacity); + TEST_INT_GE(str1.len, str1.capacity); + + TEST_INT_EQ(0u, str2.len); + TEST_PTR_EQ(NULL, str2.ptr); + TEST_INT_EQ(0u, str2.capacity); + TEST_INT_EQ(0, str2.flags); + + rc = cstring_swap(&str2, &str1); + + XTESTS_REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(0u, str1.len); + TEST_PTR_EQ(NULL, str1.ptr); + TEST_INT_EQ(0u, str1.capacity); + TEST_INT_EQ(0, str1.flags); + + TEST_INT_EQ(cch, str2.len); + TEST_PTR_NE(NULL, str2.ptr); + TEST_STR_EQ_(s, str2.ptr); + TEST_INT_GE(cch, str2.capacity); + TEST_INT_GE(str2.len, str2.capacity); + + TEST_PTR_NE(str1.ptr, str2.ptr); + + cstring_destroy(&str1); + cstring_destroy(&str2); + + TEST_INT_EQ(0u, str1.len); + TEST_PTR_EQ(NULL, str1.ptr); + TEST_INT_EQ(0u, str1.capacity); + TEST_INT_EQ(0, str1.flags); + + TEST_INT_EQ(0u, str2.len); + TEST_PTR_EQ(NULL, str2.ptr); + TEST_INT_EQ(0u, str2.capacity); + TEST_INT_EQ(0, str2.flags); + }} +} + + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/unit/cstring_getStatusCodeString/CMakeLists.txt b/test/unit/cstring_getStatusCodeString/CMakeLists.txt deleted file mode 100644 index e949cff..0000000 --- a/test/unit/cstring_getStatusCodeString/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -list(APPEND X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED - 4996 -) - -define_automated_test_program(test.unit.cstring_getStatusCodeString entry.c) diff --git a/test/unit/cstring_vector/CMakeLists.txt b/test/unit/cstring_vector/CMakeLists.txt index e1d68cc..9ccfe97 100644 --- a/test/unit/cstring_vector/CMakeLists.txt +++ b/test/unit/cstring_vector/CMakeLists.txt @@ -1,2 +1,2 @@ # SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten -define_automated_test_program(test_unit_cstring_cstring_vector entry.c) +define_automated_test_program(test.unit.cstring_vector entry.c) diff --git a/test/unit/insert-replace/CMakeLists.txt b/test/unit/insert-replace/CMakeLists.txt new file mode 100644 index 0000000..76417de --- /dev/null +++ b/test/unit/insert-replace/CMakeLists.txt @@ -0,0 +1,2 @@ +# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +define_automated_test_program(test.unit.insert-replace entry.c) diff --git a/test/unit/cstring.insert-replace/entry.c b/test/unit/insert-replace/entry.c similarity index 99% rename from test/unit/cstring.insert-replace/entry.c rename to test/unit/insert-replace/entry.c index bcfbf2a..c752baf 100644 --- a/test/unit/cstring.insert-replace/entry.c +++ b/test/unit/insert-replace/entry.c @@ -85,7 +85,7 @@ int main(int argc, char **argv) XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); - if (XTESTS_START_RUNNER("test.unit.cstring.2", verbosity)) + if (XTESTS_START_RUNNER("test.unit.insert-replace", verbosity)) { XTESTS_RUN_CASE(TEST_cstring_createN); XTESTS_RUN_CASE(TEST_cstring_insert); diff --git a/test/unit/status-codes/CMakeLists.txt b/test/unit/status-codes/CMakeLists.txt new file mode 100644 index 0000000..6a57f74 --- /dev/null +++ b/test/unit/status-codes/CMakeLists.txt @@ -0,0 +1,5 @@ +list(APPEND X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED + 4996 +) + +define_automated_test_program(test.unit.status-codes entry.c) diff --git a/test/unit/cstring_getStatusCodeString/entry.c b/test/unit/status-codes/entry.c similarity index 98% rename from test/unit/cstring_getStatusCodeString/entry.c rename to test/unit/status-codes/entry.c index 1644575..bb457be 100644 --- a/test/unit/cstring_getStatusCodeString/entry.c +++ b/test/unit/status-codes/entry.c @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: test/unit/cstring_getStatusCodeString/entry.c + * File: test/unit/status-codes/entry.c * * Purpose: Unit-tests `cstring_error()`, `cstring_getStatusCodeString()`. * * Created: 28th July 2011 - * Updated: 3rd October 2026 + * Updated: 4th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -103,7 +103,7 @@ int main(int argc, char **argv) XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); - if (XTESTS_START_RUNNER("test.unit.cstring_getStatusCodeString", verbosity)) + if (XTESTS_START_RUNNER("test.unit.status-codes", verbosity)) { XTESTS_RUN_CASE(TEST_cstring_getStatusCodeString_SUCCESS); XTESTS_RUN_CASE(TEST_cstring_getStatusCodeString_KNOWN_CODES);