Skip to content

1.11.3 - #83

Merged
synesissoftware merged 6 commits into
masterfrom
dev
Oct 27, 2026
Merged

synesissoftware merged 6 commits into
masterfrom
dev

Conversation

@synesissoftware

Copy link
Copy Markdown
Owner

No description provided.

synesissoftware and others added 2 commits October 25, 2026 20:23
* test(integer_to_string): share one short performance schedule

Hoist the per-section warmup, loop, and iteration counts to one shared
schedule of 2 warmups, 1 loop, and 1,000,000 iterations. The previous
4 x 100 x 1,000,000 schedule was repeated in every section and took about
ten minutes.

* tidy

* tidy

* tidy

* misc

---------

Co-authored-by: Matt Wilson <matthew.wilson.synesis@gmail.com>
@synesissoftware
synesissoftware requested a review from mwsis October 26, 2026 00:43
@synesissoftware synesissoftware self-assigned this Oct 26, 2026
@synesissoftware
synesissoftware marked this pull request as ready for review October 26, 2026 00:44
* boilerplate

* CI: align push branches with Synesis canonical set

Use lexicographic boilerplate/dev/idiomatic/master/rc1–rc3 triggers;
drop rc4/rc5/update. Keep unfiltered pull_request.

* Scripts: add .sis/project_name.txt and wire ProjectName

Canonical short name for helper-script status/--help messages,
loaded via tr -d whitespace per Synesis convention.

* Scripts: MSYS auto-MinGW in prepare_cmake; add ctest_cmake.sh

Detect MSYSTEM to enable MinGW configure/make by default, and
add a ProjectName-aware CTest wrapper aligned with peer libs.

* CMake: set CMP0177 and fix BuildType default cache set

Enable install-path normalisation on CMake 3.31+, and correct
CMAKE_BUILD_TYPE CACHE assignment / .git detection in BuildType.cmake.

* Docs: review and refresh TODO.md against current packaging

Mark completed README/CI/.sis/CMake modernisation items, consolidate
duplicate blog/Doxygen bullets, and clarify GHA vs dialect-matrix coverage.

* Modernise STLSoft build/CI boilerplate and rewrite TODO roadmap.

Align helper scripts and CMake with peer libraries (canonical CI branches,
.sis project name, MSYS MinGW/ctest, CMP0177/BuildType, coloured runners)
and replace TODO.md with a versioned 1.11/1.12 plan.

* Colourise STLSoft test/example runners and add CI update.3/update.4 branches.

Match peer tput/RbEnvClr_ output on execute/skip lines, and extend the
push-branch list for the active update.* line of work.

* CMake/CI: optional ACE discovery and dedicated ACE cell

Add lean FindACE, NO_ACE/--no-ace, and a linux-gcc cell-ace job
(libace-dev) so ACESTL can be exercised without burdening the
default matrix; keep ace on push.branches for this branch.

* test: ACESTL version smoke when ACE is present

Gate _ACESTL_VER output in versions.iostreams and test.unit.versions
on STLSOFT_HAS_ACE (link ACE::ACE), and assert ACE discovery in the
ACE CI cell so enablement is visible in the logs.

* fix

* test: add ACESTL string-access shim unit tests

Add HAS_ACE-gated unit tests for ACE_Time_Value and ACE_String_Base
string-access shims. Includes a temporary deliberate failure in
time_value to confirm GHA ACE unit-test failure reporting.

* fix: ACE_Time_Value stream_insert shadowing; shim_string::data()

Rename stream_insert's seconds local so it does not shadow the stream
parameter (breaks -Werror builds), correct its length asserts, and use
basic_shim_string::data() in the time_value unit test.

* CI: restore +x after artifact download; fail on zero unit tests

GitHub Actions artifacts drop execute bits, so find -exec test -x
discovered nothing and unit/scratch jobs exited green. Chmod matching
binaries after download (as Pantheios), and make run_all_unit_tests.sh
fail when no executables are found.

* CI/scripts: do not chmod or run CMake artefacts as examples

Narrow post-artifact +x restore under examples/, and harden
run_all_examples.sh to skip CMake files (e.g. CTestTestfile.cmake)
so example jobs do not try to execute them.

* test: remove deliberate ACESTL time_value failure used for GHA check

* ACESTL: implement invoke_ACE_OS_snprintf via stlsoft_C_snprintf

Replace ACE_OS::snprintf in the ACE_Time_Value string-access helpers
with stlsoft_C_snprintf, and route stream_insert through the same
invoke_ACE_OS_snprintf overloads (TODO 1.11.1).

* CI: exercise MFCSTL on Windows via with-mfc cell

Gate FindMFC behind with-mfc (cell-mfc / windows-cl-mfc), verify discovery,
and apply CMAKE_MFC_FLAG/_AFXDLL only to MFC-consuming test targets.

* fix

* CI: exercise ATLSTL on Windows via with-atl cell

Add FindATL, gate discovery behind with-atl (cell-atl / windows-cl-atl),
verify atlbase.h, and smoke-test via test.unit.versions.

* TODO

* squash-commit

* squash-commit

* refactor(stlsoft): suffix C-API programs with `.C`

Move C-API example and test names from a leading `C.` to a trailing `.C`,
matching the Synesis convention for a C program beside a C++ counterpart:
- rename directories and CMake/xTests runner ids across examples and tests;
- skip `include/` in generate_cmakelists.rb and accept Proc ignore entries;
- add a `sinteger64` copy constructor and fix the monitor `const_cast`;
- call `try_string_to_integer_4slre_` from `string_to_integer_range`;
- refresh copyright years and mark the MFCSTL CI TODO complete;

* squash-commit

* tidy

* refactor(stlsoft): shorten test directory names (#25)

* refactor(stlsoft): shorten the uds_helpers test directory

Point add_subdirectory at uds_helpers. The executable name stays
test.component.inetstl.util.uds_helpers.

* refactor(stlsoft): shorten component-test directories

Rename each test/component leaf to its subject segment and retarget
add_subdirectory. Refresh each entry file's Updated and File banner.
Executable names stay test.component.*.

* refactor(stlsoft): shorten unit-test directories

Rename each test/unit leaf except test.unit.versions to its subject segment
and retarget add_subdirectory. Refresh each entry file's Updated and File
banner. Executable names stay test.unit.*. test.unit.versions is unchanged.

* refactor(stlsoft): shorten scratch-test directories

Rename each test/scratch leaf to its subject segment and retarget
add_subdirectory. Refresh Updated and File banners where present.
Executable names stay test.scratch.*.

* refactor(stlsoft): deepen performance-test directories

Move each performance test onto its component header path, such as
platformstl/diagnostics/stopwatch and stlsoft/memory/auto_buffer, and
retarget add_subdirectory. Keep the C++ standard guards and the executable
names.

* refactor(stlsoft): shorten the test.unit.versions directory

Rename test/unit/test.unit.versions to versions and retarget
add_subdirectory. Refresh the entry file's Updated and File banner. The
executable name stays test.unit.versions.

* chore(stlsoft): record 1.11.1-rc7 for the test-tree reorganisation

Advance `_STLSOFT_VER` to `_STLSOFT_VER_1_11_1_RC7` (`0x010b01c7`) and
align the release notes with the shortened test directories and nested
**test/performance** layout:
- **CHANGES.md**, **NEWS.md**, and **INSTALL.md** updated for 30th
  September 2026;
- **test.unit.versions** expects **_STLSOFT_VER_1_11_1_RC7**;

* Remove empty test cases (#26)

* refactor(stlsoft): shorten the uds_helpers test directory

Point add_subdirectory at uds_helpers. The executable name stays
test.component.inetstl.util.uds_helpers.

* refactor(stlsoft): shorten component-test directories

Rename each test/component leaf to its subject segment and retarget
add_subdirectory. Refresh each entry file's Updated and File banner.
Executable names stay test.component.*.

* refactor(stlsoft): shorten unit-test directories

Rename each test/unit leaf except test.unit.versions to its subject segment
and retarget add_subdirectory. Refresh each entry file's Updated and File
banner. Executable names stay test.unit.*. test.unit.versions is unchanged.

* refactor(stlsoft): shorten scratch-test directories

Rename each test/scratch leaf to its subject segment and retarget
add_subdirectory. Refresh Updated and File banners where present.
Executable names stay test.scratch.*.

* refactor(stlsoft): deepen performance-test directories

Move each performance test onto its component header path, such as
platformstl/diagnostics/stopwatch and stlsoft/memory/auto_buffer, and
retarget add_subdirectory. Keep the C++ standard guards and the executable
names.

* refactor(stlsoft): shorten the test.unit.versions directory

Rename test/unit/test.unit.versions to versions and retarget
add_subdirectory. Refresh the entry file's Updated and File banner. The
executable name stays test.unit.versions.

* test(STLSoft): remove empty unit-test case functions

Remove empty xTests case functions (bodies with no statements), together
with their forward declarations and XTESTS_RUN_CASE registrations, across
19 STLSoft unit-test entry files;
- nine allocator suites keep a runner and no cases, because every case body
  was empty;
- the Unix-built suites were rebuilt and passed;

* test(STLSoft): flesh out empty allocator unit-test cases

Give each of the nine allocator suites its seven cases:
- round-trip 0, 4, 256, and 4096 ints, and reject a count past max_size();
- specialise std::list and std::vector with that allocator;
- null_allocator must throw out_of_memory_exception on every request,
  including container insertion;
- malloc_allocator, new_allocator, and null_allocator passed on this host;

* fix

* `platformstl::file_lines` automated tests (#20)

* test(stlsoft): cover platformstl::file_lines component behavior

Add a platformstl filesystem component suite covering empty files and
one-line, terminated-line, and three-line inputs; verify embedded NUL data
raises platformstl::invalid_file_type_exception; register the filesystem
subtree with the existing platformstl component tests;

* feat(platformstl): add FILE_stream::write_binary()

Add a byte-oriented write that preserves embedded NULs, and deprecate
write(void const*, size_t) in its favour. Cover CR and CRLF files in the
file_lines component tests, and write the NUL fixture through
write_binary().

* Bit functions: refactoring, extension, and optimisation (#23)

* style(stlsoft): normalise bit-function header section banners

Replace the short separator above each namespace close with the long
section banner, and drop the short separator that precedes the
include-guard close:
- Refresh **Updated:** to 24th September 2026 and extend the Synesis
  Information Systems copyright through 2026;
- Bump each header **EDIT** counter;

* chore(tests): canonicalisation of test function names

* test(stlsoft): cover all bit-function overloads by width

Expand **test.unit.stlsoft.util.bit_functions** so every public C++
overload is called for each supported width, plus `count_bits(int)` for
signedness;
- Add Kernighan and 8-bit-table cases for **uint64_t**;
- Add **count_bits** for **uint32_t**, **uint64_t**, and **int**;
- Add **find_highest_bit** for **uint8_t** and **uint16_t**;
- Add **calculate_xor_over_range** for 16-, 32-, and 64-bit ranges;
- Remove in-unit stopwatch timing;

* test(stlsoft): add bit-functions performance suite

Add **test.performance.stlsoft.bit_functions** under a nested **stlsoft/**
performance tree, with an aligned width × function timing table covering
every public bit-function overload;
- Wire **test/performance/CMakeLists.txt** → **stlsoft** →
  **bit_functions**;
- Time Kernighan and 8-bit-table popcount, **count_bits**,
  **find_highest_bit**, and **calculate_xor_over_range**;

* fix: `count_bits()` overloads were broken when `STLSOFT_BIT_COUNT_BY_Kernighan` is defined

* test(stlsoft): vary popcount density in bit-functions perf

Extend **test.performance.stlsoft.bit_functions** with denser popcount
workloads so Kernighan vs 8-bit-table can be compared beyond the sparse
sequential-i series;
- Add /~i (bitwise-not), /ones (volatile all-ones), and /ones^1 (all-ones
  with one rotating bit cleared);
- Widen the function-name column for the longer row labels;

* fix(stlsoft): avoid uint64_t clash in bit-functions perf

Stop importing stlsoft::uint64_t into the global namespace: it conflicts
with ::uint64_t from <cstdint> (pulled in via std_chrono_hrc_stopwatch) and
made 64-bit overloads ambiguous on Linux GCC.
- Use stlsoft::ss_uintN_t aliases for variables and casts; keep column
  headers as uintN_t labels;

* test(stlsoft.b): report median bit-function timings with a 64-bit anchor

Time each row as one discarded warmup plus five samples and print the
median. Accumulate the anchor in ss_uint64_t. Print
STLSOFT_COMPILER_VERSION_STRING, whether optimisation is size, speed, or
off, and whether this is a release or debug-asserts build.

* fix(stlsoft): silence maybe-uninitialized in bit_functions median

Zero-initialise the local sorted[] buffer in median_sample, guard empty n,
and clamp n to NUM_SAMPLES so GCC -Werror=maybe-uninitialized no longer
fails the performance harness on CI.

* test(stlsoft.b): time more bit patterns and print per-call rates

Add volatile-zero, rotating-bit, and both-halves-live rows for popcount
and find_highest_bit, so the 64-bit high half is exercised. Print ns/call
on the same line as the loop time, ns/element for XOR, and the anchor last.
Honor SIS_PERFTESTS_GROUPGAPS with a blank line between operation and
input-pattern groups.

* docs(stlsoft.b): record bit-function timings and the default choice

Keep the 8-bit table as the default for count_bits(). Apple Clang rewrites
Kernighan into a hardware popcount; Linux and Windows compilers execute the
source loop and the table is faster there.

* feat(stlsoft): bit-function intrinsics for 1.11.1-rc7

Add unselected `count_bits_by_intrinsic()` /
`find_highest_bit_by_intrinsic()`, with external/internal bitfns detection
(GCC/Clang builtins preferred over MSVC), 8/16-bit count overloads, Clang
`STLSOFT_CLANG_VER`, unit and performance coverage, and strategy notes that
keep the 8-bit table as the `count_bits()` default;

* fix(stlsoft): cast popcnt64; enable perf group gaps in CI

Cast MSVC `__popcnt64` through `STLSOFT_C_CAST(int, …)` so `/WX` builds
stop on C4244. Export `SIS_PERFTESTS_GROUPGAPS=1` for the performance job
and only print the harness banner when that env is truey. Quiet getenv
C4996 and narrow `~` promotions on uint8/16 ones patterns. Record Linux
GCC 13.3 (~3.1 ns soft popcount vs table) and Apple Clang 21 CI findings in
`strategy/BIT_FUNCTIONS.md`.

* docs(stlsoft): fold full CI bit-function matrix into strategy

Replace partial CI notes in strategy/BIT_FUNCTIONS.md with the six-cell
wave (Linux Clang/GCC, macOS Clang/“gcc”, MSVC, MinGW): soft vs hard
popcount, /bit deletions, and find_highest comparisons. Keep table as
portable default; leave both intrinsics unselected pending a narrow gate.

* test(stlsoft): add component tests for environment_variable_exists() (#24)

* test(stlsoft): add component tests for environment_variable_exists()

Cover stlsoft::environment_variable_exists() against the process
environment, using platformstl::environment_variable_scope to set and
restore each variable: absent, present, erased, value replacement, exact
name match, std::string and simple_string shims, UNIX empty values, and
case sensitivity.

* refactor(stlsoft): rename environment_variable_exists_a to _m

Rename `stlsoft_C_environment_variable_exists_a()` to
`stlsoft_C_environment_variable_exists_m()` and keep the `_a` spelling as a
deprecated forwarder; the C++ overloads and component tests call `_m`.

* `platformstl::file_lines` performance tests (#21)

* test(stlsoft): cover platformstl::file_lines component behavior

Add a platformstl filesystem component suite covering empty files and
one-line, terminated-line, and three-line inputs; verify embedded NUL data
raises platformstl::invalid_file_type_exception; register the filesystem
subtree with the existing platformstl component tests;

* test(stlsoft): benchmark platformstl::file_lines

Add a standalone comparative performance test for platformstl::file_lines_a
and std::ifstream plus std::getline; follow existing STLSoft warm-up,
stopwatch, anchoring, and tabular output conventions;

* test(platformstl): improved performance test program for `platformstl::basic_file_lines<>`

* fix(platformstl::file_lines): compatible with `std::basic_string_view`

* perf(platformstl::file_lines): adjusted preallocation of number of reserved lines

* perf(platformstl::file_lines): moved file-contents check into in-loop processing, so only pass once

* perf(platformstl::file_lines): use of `#emplace_back()` (C++ 11+)

* feat(platformstl): add FILE_stream::write_binary()

Add a byte-oriented write that preserves embedded NULs, and deprecate
write(void const*, size_t) in its favour. Cover CR and CRLF files in the
file_lines component tests, and write the NUL fixture through
write_binary().

* feat(platformstl): add FILE_stream::write_binary()

Add a byte-oriented write that preserves embedded NULs, and deprecate
write(void const*, size_t) in its favour. Cover CR and CRLF files in the
file_lines component tests, and write the NUL fixture through
write_binary().

* test(platformstl): cover CR, CRLF, and small files in file_lines perf

Time LF, CRLF, and CR at the existing 1000×64 and 5000×80 shapes, and at
1×16, 8×32, and 32×64, so a later small-file path has a fixed-cost region
to beat. Read the getline baseline in binary mode so the CR bytes are the
same on every platform.

* refactor(platformstl): parse file_lines directly from mapping

Remove the full-file m_contents heap copy; parse directly from the mmap'd
base pointer for lower memory bandwidth use. Drop the unused m_contents
member and early-return on empty maps.

* fix(platformstl): use C stdio in file_lines perf harness

Avoid MinGW std::ifstream/std::getline crashes by switching the perf
fixture writer/reader to fopen/fwrite/fgetc while preserving split on '\n'
semantics (keeping '\r' in-line).

* fix(performance): use STLSOFT fopen wrapper in file_lines benchmark

* test(stlsoft): compare getline and getc file-line baselines

- Rename the existing per-character stdio baseline to vector<std::string>
  plus getc;
- add a genuine std::getline baseline using the appropriate LF or CR
  delimiter and normalise CRLF line endings;

* ci

* ci(stlsoft): trace the MinGW file_lines performance crash

Temporarily run only the Windows/MinGW CI cell and write flushed stderr
markers through the file_lines performance program;
- Each scenario, warmup, and sampled iteration logs enter and leave;
- `basic_file_lines` traces construct, iterate, and walk on those samples;

* fix(platformstl): stop the MinGW getline baseline segfaulting

`std::ifstream` dies on open under MinGW, which the progress log showed on
the first getline call. Read that baseline through FILE* and still time
`std::getline`;
- Other compilers keep `std::ifstream`;

* test(platformstl): stage the MinGW file_lines perf crash probe

Print flushed C traces from constructors before main, then walk one small
file through write, fgetc, getline, file_lines, and ifstream. The full
matrix runs only when SIS_FILE_LINES_PERF_FULL is set.

* test(platformstl): leave an on-disk trail for the MinGW file_lines death

Add a pure-C boot probe and cwd breadcrumb file, fix the performance
harness so it keeps the real child exit status, dump PE imports before
exec, and restrict the MinGW cell to file_lines only.

* ci(stlsoft): treat MinGW file_lines 127 as a loader failure

Static-link libgcc/libstdc++ for the file_lines perf target, run that step
under the msys2 shell, and on child-exit 127 dump MinGW runtimes, copy them
beside the exe, and retry once.

* ci(stlsoft): re-enable the file_lines full perf matrix on MinGW

The staged probe passed end-to-end after static-linking libgcc/libstdc++,
including std::ifstream. Set SIS_FILE_LINES_PERF_FULL so the next cell run
hits the real workload with traces still enabled.

* fix(cmake): static-link MinGW libgcc/libstdc++ for PE load

MinGW performance exes were exiting 127 before any user code when the
runner PATH did not match the toolchain that built them. Static-link libgcc
and libstdc++ for Windows GNU targets in TargetMacros, restore the
file_lines perf baseline and full CI, and capture the real child exit
status in execute_performance_tests.sh.

* docs(stlsoft): record file_lines.perf and restore GROUPGAPS in CI

Restore SIS_PERFTESTS_GROUPGAPS=1 on the performance job; document
platformstl::basic_file_lines<> mmap parse (~10–20× vs fgetc; ~parity /
~1.2× vs getline), the file_lines perf harness, MinGW static libgcc /
libstdc++, and the execute_performance_tests.sh child_ec fix in
CHANGES.md / NEWS.md for 1.11.1-rc7.

* String comparison unit-tests (#27)

* refactor(stlsoft): shorten the uds_helpers test directory

Point add_subdirectory at uds_helpers. The executable name stays
test.component.inetstl.util.uds_helpers.

* refactor(stlsoft): shorten component-test directories

Rename each test/component leaf to its subject segment and retarget
add_subdirectory. Refresh each entry file's Updated and File banner.
Executable names stay test.component.*.

* refactor(stlsoft): shorten unit-test directories

Rename each test/unit leaf except test.unit.versions to its subject segment
and retarget add_subdirectory. Refresh each entry file's Updated and File
banner. Executable names stay test.unit.*. test.unit.versions is unchanged.

* refactor(stlsoft): shorten scratch-test directories

Rename each test/scratch leaf to its subject segment and retarget
add_subdirectory. Refresh Updated and File banners where present.
Executable names stay test.scratch.*.

* refactor(stlsoft): deepen performance-test directories

Move each performance test onto its component header path, such as
platformstl/diagnostics/stopwatch and stlsoft/memory/auto_buffer, and
retarget add_subdirectory. Keep the C++ standard guards and the executable
names.

* refactor(stlsoft): shorten the test.unit.versions directory

Rename test/unit/test.unit.versions to versions and retarget
add_subdirectory. Refresh the entry file's Updated and File banner. The
executable name stays test.unit.versions.

* test(STLSoft): remove empty unit-test case functions

Remove empty xTests case functions (bodies with no statements), together
with their forward declarations and XTESTS_RUN_CASE registrations, across
19 STLSoft unit-test entry files;
- nine allocator suites keep a runner and no cases, because every case body
  was empty;
- the Unix-built suites were rebuilt and passed;

* test(STLSoft): flesh out empty allocator unit-test cases

Give each of the nine allocator suites its seven cases:
- round-trip 0, 4, 256, and 4096 ints, and reject a count past max_size();
- specialise std::list and std::vector with that allocator;
- null_allocator must throw out_of_memory_exception on every request,
  including container insertion;
- malloc_allocator, new_allocator, and null_allocator passed on this host;

* test(stlsoft): cover stlsoft_C_strnicmp and the C++ wrappers

Add unit programs for stlsoft_C_strnicmp(), stlsoft_C_wcsnicmp(), and the
stlsoft::c_string wrappers:
- char and wchar_t equal, less, and greater;
- n==0, stop at NUL, and ignore the byte past n;
- ASCII case-fold, and high-bit bytes that share a sign;

* test(stlsoft): cover c_string_traits string comparison

Add a unit program for stlsoft::c_string_traits compare, n-compare, and the
ASCII case-insensitive forms, for char and wchar_t:
- equal, less, greater, and empty strings;
- n==0, stop at NUL, and ignore the byte past n;

* test(stlsoft): cover char_traits and char_traits_safe comparison

Add a unit program for stlsoft::char_traits and char_traits_safe, for char
and wchar_t:
- eq, lt, and compare equal, less, and greater;
- n==0, exact-n past NUL, and compare_max stopping at NUL;
- compare_null for both-null and one-null;
- char compare unsigned high-bit order;
- char_traits_safe compare, and compare_max with null pointers;

* test(stlsoft): cover stlsoft::strcmp and stlsoft::strncmp

Add a unit program for the pointer overloads and the S const& shims:
- char and wchar_t equal, less, greater, and empty;
- n==0, the byte past n, and stop at NUL;
- simple_string and string_view, including embedded NUL;

* test(stlsoft): cover BSTR comparison wrappers and length edges

Extend the C BSTR program and add a C++ program for the wrappers:
- empty BSTR versus NULL and versus a live string;
- embedded NUL keeps SysStringLen and wcscmp stops;
- a length-limited prefix compares as that prefix;
- BSTR_compare and one comstl__bstr_compare call match the C function;
- bstr::equal for equal, unequal, empty, and a raw BSTR;

* test(stlsoft): cover string_begins_with prefix checks

Add a unit program for stlsoft::string_begins_with on char, including the
simple_string shim:
- match, reject, empty prefix, and a longer prefix;
- case-sensitive ASCII;
- a prefix containing NUL is only the part before that NUL;

* test(stlsoft): cover special_string_instance::equal

Add a unit program for special_string_instance::equal, using a fixed policy
so the cases do not touch the environment:
- case-sensitive and ASCII case-insensitive, for char and wchar_t;
- length mismatch, including empty;
- an embedded NUL stops the compare when both lengths include it;

* test(stlsoft): require simple_string to keep embedded NULs

Amend the simple_string unit so every write path is expected to keep an
embedded NUL, for char and wchar_t:
- construction from a counted range;
- append, including a tail that begins with NUL, and push_back;
- assign into an empty string, a reserved buffer, and a NUL fill, plus copy
  of a preserved string;
- compare treats a difference after that NUL as unequal;

The suite now fails where construction, append, push_back, and assign into
an empty string still drop the NUL.

* whitespace

* fix(stlsoft): preserve counted NULs in simple_string

append(pointer, count) copied only up to the first NUL, so push_back of NUL
and a counted tail that begins with NUL were dropped. Copy the given count,
and assign a single character by that count so '\0' is stored.

Counted construction and assign already kept the full count.

* test(stlsoft): cover static_string compare

Add unit cases for stlsoft::basic_static_string compare:
- char equal, less, greater, and unsigned high-bit order;
- empty char strings;
- a counted embedded NUL stays in the length and compare sees the byte
  after it, for char and wchar_t;
- wchar_t order and empty strings;

* test(stlsoft): cover string_view compare edges

Add unit cases for stlsoft::basic_string_view compare:
- empty char views;
- a counted embedded NUL stays in the length and compare sees the byte
  after it;
- wchar_t order, empty, and the same embedded NUL;
- char_traits_safe order, empty, and the same embedded NUL;

* test(stlsoft): cover string_slice compare edges

Add unit cases for stlsoft::string_slice compare:
- empty char and wchar_t slices;
- a counted embedded NUL stays in the length and compare sees the byte
  after it, for char and wchar_t;
- char high-bit order, with 0x01 before 0xFF;

* fix(stlsoft): let string_begins_with accept wchar_t

operator() called strncmp, so a wchar_t prefix did not compile. Compare
with char_traits<C>::compare_max, which is strncmp for char and wcsncmp for
wchar_t, and cover the wide match, reject, empty, longer, and
case-sensitive cases, including a prefix that contains NUL.

* fix(stlsoft): define char_traits_safe compare of a null pointer

compare_null() called the parent compare(), so a null pointer and a
non-zero count were not defined. A count of 0 still compares equal. A
non-zero count orders a null pointer before a live pointer, for char and
wchar_t.

* fix(stlsoft): build cleanly with MSVC and MinGW

windows-cl-atl treats the Windows small macro, truncated char casts, CRT
deprecation, and an unreachable fail as errors. windows-mingw does not link
NetApiBufferAllocate.
- store high-bit bytes in unsigned char and view them as char;
- suppress deprecation around strcpy, strcat, and strdup wrappers;
- accept that null_allocator::allocate always throws;
- pass a DWORD byte count to NetApiBufferAllocate and link netapi32;

* fix(stlsoft): stop null_allocator test failing MSVC /WX

allocate() always throws, so a fail after the call is unreachable in a
Release build, and the warning disable does not cover that. Record the
exception and assert the flag after the try.

* release

* fix

* docs(stlsoft): complete the 1.11.1-rc7 notes

Add the rc6-to-HEAD items missing from the 1.11.1-rc7 notes: FILE_stream
write_binary(), file_lines tests, filled allocator units, and removal of
empty placeholder cases. Counted concatenation stays with the simple_string
NUL contract.

* `platformstl::basic_file_lines<>` perf-tests improvements: gap-groups (#30)

* test(stlsoft): cover platformstl::file_lines component behavior

Add a platformstl filesystem component suite covering empty files and
one-line, terminated-line, and three-line inputs; verify embedded NUL data
raises platformstl::invalid_file_type_exception; register the filesystem
subtree with the existing platformstl component tests;

* test(stlsoft): benchmark platformstl::file_lines

Add a standalone comparative performance test for platformstl::file_lines_a
and std::ifstream plus std::getline; follow existing STLSoft warm-up,
stopwatch, anchoring, and tabular output conventions;

* test(platformstl): improved performance test program for `platformstl::basic_file_lines<>`

* fix(platformstl::file_lines): compatible with `std::basic_string_view`

* perf(platformstl::file_lines): adjusted preallocation of number of reserved lines

* perf(platformstl::file_lines): moved file-contents check into in-loop processing, so only pass once

* perf(platformstl::file_lines): use of `#emplace_back()` (C++ 11+)

* feat(platformstl): add FILE_stream::write_binary()

Add a byte-oriented write that preserves embedded NULs, and deprecate
write(void const*, size_t) in its favour. Cover CR and CRLF files in the
file_lines component tests, and write the NUL fixture through
write_binary().

* feat(platformstl): add FILE_stream::write_binary()

Add a byte-oriented write that preserves embedded NULs, and deprecate
write(void const*, size_t) in its favour. Cover CR and CRLF files in the
file_lines component tests, and write the NUL fixture through
write_binary().

* test(platformstl): cover CR, CRLF, and small files in file_lines perf

Time LF, CRLF, and CR at the existing 1000×64 and 5000×80 shapes, and at
1×16, 8×32, and 32×64, so a later small-file path has a fixed-cost region
to beat. Read the getline baseline in binary mode so the CR bytes are the
same on every platform.

* refactor(platformstl): parse file_lines directly from mapping

Remove the full-file m_contents heap copy; parse directly from the mmap'd
base pointer for lower memory bandwidth use. Drop the unused m_contents
member and early-return on empty maps.

* fix(platformstl): use C stdio in file_lines perf harness

Avoid MinGW std::ifstream/std::getline crashes by switching the perf
fixture writer/reader to fopen/fwrite/fgetc while preserving split on '\n'
semantics (keeping '\r' in-line).

* fix(performance): use STLSOFT fopen wrapper in file_lines benchmark

* test(stlsoft): compare getline and getc file-line baselines

- Rename the existing per-character stdio baseline to vector<std::string>
  plus getc;
- add a genuine std::getline baseline using the appropriate LF or CR
  delimiter and normalise CRLF line endings;

* ci

* ci(stlsoft): trace the MinGW file_lines performance crash

Temporarily run only the Windows/MinGW CI cell and write flushed stderr
markers through the file_lines performance program;
- Each scenario, warmup, and sampled iteration logs enter and leave;
- `basic_file_lines` traces construct, iterate, and walk on those samples;

* fix(platformstl): stop the MinGW getline baseline segfaulting

`std::ifstream` dies on open under MinGW, which the progress log showed on
the first getline call. Read that baseline through FILE* and still time
`std::getline`;
- Other compilers keep `std::ifstream`;

* test(platformstl): stage the MinGW file_lines perf crash probe

Print flushed C traces from constructors before main, then walk one small
file through write, fgetc, getline, file_lines, and ifstream. The full
matrix runs only when SIS_FILE_LINES_PERF_FULL is set.

* test(platformstl): leave an on-disk trail for the MinGW file_lines death

Add a pure-C boot probe and cwd breadcrumb file, fix the performance
harness so it keeps the real child exit status, dump PE imports before
exec, and restrict the MinGW cell to file_lines only.

* ci(stlsoft): treat MinGW file_lines 127 as a loader failure

Static-link libgcc/libstdc++ for the file_lines perf target, run that step
under the msys2 shell, and on child-exit 127 dump MinGW runtimes, copy them
beside the exe, and retry once.

* ci(stlsoft): re-enable the file_lines full perf matrix on MinGW

The staged probe passed end-to-end after static-linking libgcc/libstdc++,
including std::ifstream. Set SIS_FILE_LINES_PERF_FULL so the next cell run
hits the real workload with traces still enabled.

* fix(cmake): static-link MinGW libgcc/libstdc++ for PE load

MinGW performance exes were exiting 127 before any user code when the
runner PATH did not match the toolchain that built them. Static-link libgcc
and libstdc++ for Windows GNU targets in TargetMacros, restore the
file_lines perf baseline and full CI, and capture the real child exit
status in execute_performance_tests.sh.

* docs(stlsoft): record file_lines.perf and restore GROUPGAPS in CI

Restore SIS_PERFTESTS_GROUPGAPS=1 on the performance job; document
platformstl::basic_file_lines<> mmap parse (~10–20× vs fgetc; ~parity /
~1.2× vs getline), the file_lines perf harness, MinGW static libgcc /
libstdc++, and the execute_performance_tests.sh child_ec fix in
CHANGES.md / NEWS.md for 1.11.1-rc7.

* test(performance): roll out GROUPGAPS helpers and --gap-groups

Add shared perf_group_gaps.hpp on the test/performance include path and
honour SIS_PERFTESTS_GROUPGAPS in every performance main. TTY emits a
blank line between logical groups; non-TTY / CI emits `\t----------`.
Add --gap-groups on execute_performance_tests.sh/.cmd; CI uses
-M --gap-groups. bit_functions keeps family/pattern key derivation via
the shared helpers.

* docs(stlsoft): align remaining project notes with 1.11.1-rc7

Point the bit-function strategy note at the nested performance program and
close the matching **TODO.md** item; name the versions test by its current
directory in the MFC comment in **CMakeLists.txt**.

* Boilerplate, CI (#53)

* .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_*).

* 1.11.1-rc7 (#29)

* refactor(stlsoft): shorten test directory names (#25)

* refactor(stlsoft): shorten the uds_helpers test directory

Point add_subdirectory at uds_helpers. The executable name stays
test.component.inetstl.util.uds_helpers.

* refactor(stlsoft): shorten component-test directories

Rename each test/component leaf to its subject segment and retarget
add_subdirectory. Refresh each entry file's Updated and File banner.
Executable names stay test.component.*.

* refactor(stlsoft): shorten unit-test directories

Rename each test/unit leaf except test.unit.versions to its subject segment
and retarget add_subdirectory. Refresh each entry file's Updated and File
banner. Executable names stay test.unit.*. test.unit.versions is unchanged.

* refactor(stlsoft): shorten scratch-test directories

Rename each test/scratch leaf to its subject segment and retarget
add_subdirectory. Refresh Updated and File banners where present.
Executable names stay test.scratch.*.

* refactor(stlsoft): deepen performance-test directories

Move each performance test onto its component header path, such as
platformstl/diagnostics/stopwatch and stlsoft/memory/auto_buffer, and
retarget add_subdirectory. Keep the C++ standard guards and the executable
names.

* refactor(stlsoft): shorten the test.unit.versions directory

Rename test/unit/test.unit.versions to versions and retarget
add_subdirectory. Refresh the entry file's Updated and File banner. The
executable name stays test.unit.versions.

* chore(stlsoft): record 1.11.1-rc7 for the test-tree reorganisation

Advance `_STLSOFT_VER` to `_STLSOFT_VER_1_11_1_RC7` (`0x010b01c7`) and
align the release notes with the shortened test directories and nested
**test/performance** layout:
- **CHANGES.md**, **NEWS.md**, and **INSTALL.md** updated for 30th
  September 2026;
- **test.unit.versions** expects **_STLSOFT_VER_1_11_1_RC7**;

* Remove empty test cases (#26)

* refactor(stlsoft): shorten the uds_helpers test directory

Point add_subdirectory at uds_helpers. The executable name stays
test.component.inetstl.util.uds_helpers.

* refactor(stlsoft): shorten component-test directories

Rename each test/component leaf to its subject segment and retarget
add_subdirectory. Refresh each entry file's Updated and File banner.
Executable names stay test.component.*.

* refactor(stlsoft): shorten unit-test directories

Rename each test/unit leaf except test.unit.versions to its subject segment
and retarget add_subdirectory. Refresh each entry file's Updated and File
banner. Executable names stay test.unit.*. test.unit.versions is unchanged.

* refactor(stlsoft): shorten scratch-test directories

Rename each test/scratch leaf to its subject segment and retarget
add_subdirectory. Refresh Updated and File banners where present.
Executable names stay test.scratch.*.

* refactor(stlsoft): deepen performance-test directories

Move each performance test onto its component header path, such as
platformstl/diagnostics/stopwatch and stlsoft/memory/auto_buffer, and
retarget add_subdirectory. Keep the C++ standard guards and the executable
names.

* refactor(stlsoft): shorten the test.unit.versions directory

Rename test/unit/test.unit.versions to versions and retarget
add_subdirectory. Refresh the entry file's Updated and File banner. The
executable name stays test.unit.versions.

* test(STLSoft): remove empty unit-test case functions

Remove empty xTests case functions (bodies with no statements), together
with their forward declarations and XTESTS_RUN_CASE registrations, across
19 STLSoft unit-test entry files;
- nine allocator suites keep a runner and no cases, because every case body
  was empty;
- the Unix-built suites were rebuilt and passed;

* test(STLSoft): flesh out empty allocator unit-test cases

Give each of the nine allocator suites its seven cases:
- round-trip 0, 4, 256, and 4096 ints, and reject a count past max_size();
- specialise std::list and std::vector with that allocator;
- null_allocator must throw out_of_memory_exception on every request,
  including container insertion;
- malloc_allocator, new_allocator, and null_allocator passed on this host;

* fix

* `platformstl::file_lines` automated tests (#20)

* test(stlsoft): cover platformstl::file_lines component behavior

Add a platformstl filesystem component suite covering empty files and
one-line, terminated-line, and three-line inputs; verify embedded NUL data
raises platformstl::invalid_file_type_exception; register the filesystem
subtree with the existing platformstl component tests;

* feat(platformstl): add FILE_stream::write_binary()

Add a byte-oriented write that preserves embedded NULs, and deprecate
write(void const*, size_t) in its favour. Cover CR and CRLF files in the
file_lines component tests, and write the NUL fixture through
write_binary().

* Bit functions: refactoring, extension, and optimisation (#23)

* style(stlsoft): normalise bit-function header section banners

Replace the short separator above each namespace close with the long
section banner, and drop the short separator that precedes the
include-guard close:
- Refresh **Updated:** to 24th September 2026 and extend the Synesis
  Information Systems copyright through 2026;
- Bump each header **EDIT** counter;

* chore(tests): canonicalisation of test function names

* test(stlsoft): cover all bit-function overloads by width

Expand **test.unit.stlsoft.util.bit_functions** so every public C++
overload is called for each supported width, plus `count_bits(int)` for
signedness;
- Add Kernighan and 8-bit-table cases for **uint64_t**;
- Add **count_bits** for **uint32_t**, **uint64_t**, and **int**;
- Add **find_highest_bit** for **uint8_t** and **uint16_t**;
- Add **calculate_xor_over_range** for 16-, 32-, and 64-bit ranges;
- Remove in-unit stopwatch timing;

* test(stlsoft): add bit-functions performance suite

Add **test.performance.stlsoft.bit_functions** under a nested **stlsoft/**
performance tree, with an aligned width × function timing table covering
every public bit-function overload;
- Wire **test/performance/CMakeLists.txt** → **stlsoft** →
  **bit_functions**;
- Time Kernighan and 8-bit-table popcount, **count_bits**,
  **find_highest_bit**, and **calculate_xor_over_range**;

* fix: `count_bits()` overloads were broken when `STLSOFT_BIT_COUNT_BY_Kernighan` is defined

* test(stlsoft): vary popcount density in bit-functions perf

Extend **test.performance.stlsoft.bit_functions** with denser popcount
workloads so Kernighan vs 8-bit-table can be compared beyond the sparse
sequential-i series;
- Add /~i (bitwise-not), /ones (volatile all-ones), and /ones^1 (all-ones
  with one rotating bit cleared);
- Widen the function-name column for the longer row labels;

* fix(stlsoft): avoid uint64_t clash in bit-functions perf

Stop importing stlsoft::uint64_t into the global namespace: it conflicts
with ::uint64_t from <cstdint> (pulled in via std_chrono_hrc_stopwatch) and
made 64-bit overloads ambiguous on Linux GCC.
- Use stlsoft::ss_uintN_t aliases for variables and casts; keep column
  headers as uintN_t labels;

* test(stlsoft.b): report median bit-function timings with a 64-bit anchor

Time each row as one discarded warmup plus five samples and print the
median. Accumulate the anchor in ss_uint64_t. Print
STLSOFT_COMPILER_VERSION_STRING, whether optimisation is size, speed, or
off, and whether this is a release or debug-asserts build.

* fix(stlsoft): silence maybe-uninitialized in bit_functions median

Zero-initialise the local sorted[] buffer in median_sample, guard empty n,
and clamp n to NUM_SAMPLES so GCC -Werror=maybe-uninitialized no longer
fails the performance harness on CI.

* test(stlsoft.b): time more bit patterns and print per-call rates

Add volatile-zero, rotating-bit, and both-halves-live rows for popcount
and find_highest_bit, so the 64-bit high half is exercised. Print ns/call
on the same line as the loop time, ns/element for XOR, and the anchor last.
Honor SIS_PERFTESTS_GROUPGAPS with a blank line between operation and
input-pattern groups.

* docs(stlsoft.b): record bit-function timings and the default choice

Keep the 8-bit table as the default for count_bits(). Apple Clang rewrites
Kernighan into a hardware popcount; Linux and Windows compilers execute the
source loop and the table is faster there.

* feat(stlsoft): bit-function intrinsics for 1.11.1-rc7

Add unselected `count_bits_by_intrinsic()` /
`find_highest_bit_by_intrinsic()`, with external/internal bitfns detection
(GCC/Clang builtins preferred over MSVC), 8/16-bit count overloads, Clang
`STLSOFT_CLANG_VER`, unit and performance coverage, and strategy notes that
keep the 8-bit table as the `count_bits()` default;

* fix(stlsoft): cast popcnt64; enable perf group gaps in CI

Cast MSVC `__popcnt64` through `STLSOFT_C_CAST(int, …)` so `/WX` builds
stop on C4244. Export `SIS_PERFTESTS_GROUPGAPS=1` for the performance job
and only print the harness banner when that env is truey. Quiet getenv
C4996 and narrow `~` promotions on uint8/16 ones patterns. Record Linux
GCC 13.3 (~3.1 ns soft popcount vs table) and Apple Clang 21 CI findings in
`strategy/BIT_FUNCTIONS.md`.

* docs(stlsoft): fold full CI bit-function matrix into strategy

Replace partial CI notes in strategy/BIT_FUNCTIONS.md with the six-cell
wave (Linux Clang/GCC, macOS Clang/“gcc”, MSVC, MinGW): soft vs hard
popcount, /bit deletions, and find_highest comparisons. Keep table as
portable default; leave both intrinsics unselected pending a narrow gate.

* test(stlsoft): add component tests for environment_variable_exists() (#24)

* test(stlsoft): add component tests for environment_variable_exists()

Cover stlsoft::environment_variable_exists() against the process
environment, using platformstl::environment_variable_scope to set and
restore each variable: absent, present, erased, value replacement, exact
name match, std::string and simple_string shims, UNIX empty values, and
case sensitivity.

* refactor(stlsoft): rename environment_variable_exists_a to _m

Rename `stlsoft_C_environment_variable_exists_a()` to
`stlsoft_C_environment_variable_exists_m()` and keep the `_a` spelling as a
deprecated forwarder; the C++ overloads and component tests call `_m`.

* `platformstl::file_lines` performance tests (#21)

* test(stlsoft): cover platformstl::file_lines component behavior

Add a platformstl filesystem component suite covering empty files and
one-line, terminated-line, and three-line inputs; verify embedded NUL data
raises platformstl::invalid_file_type_exception; register the filesystem
subtree with the existing platformstl component tests;

* test(stlsoft): benchmark platformstl::file_lines

Add a standalone comparative performance test for platformstl::file_lines_a
and std::ifstream plus std::getline; follow existing STLSoft warm-up,
stopwatch, anchoring, and tabular output conventions;

* test(platformstl): improved performance test program for `platformstl::basic_file_lines<>`

* fix(platformstl::file_lines): compatible with `std::basic_string_view`

* perf(platformstl::file_lines): adjusted preallocation of number of reserved lines

* perf(platformstl::file_lines): moved file-contents check into in-loop processing, so only pass once

* perf(platformstl::file_lines): use of `#emplace_back()` (C++ 11+)

* feat(platformstl): add FILE_stream::write_binary()

Add a byte-oriented write that preserves embedded NULs, and deprecate
write(void const*, size_t) in its favour. Cover CR and CRLF files in the
file_lines component tests, and write the NUL fixture through
write_binary().

* feat(platformstl): add FILE_stream::write_binary()

Add a byte-oriented write that preserves embedded NULs, and deprecate
write(void const*, size_t) in its favour. Cover CR and CRLF files in the
file_lines component tests, and write the NUL fixture through
write_binary().

* test(platformstl): cover CR, CRLF, and small files in file_lines perf

Time LF, CRLF, and CR at the existing 1000×64 and 5000×80 shapes, and at
1×16, 8×32, and 32×64, so a later small-file path has a fixed-cost region
to beat. Read the getline baseline in binary mode so the CR bytes are the
same on every platform.

* refactor(platformstl): parse file_lines directly from mapping

Remove the full-file m_contents heap copy; parse directly from the mmap'd
base pointer for lower memory bandwidth use. Drop the unused m_contents
member and early-return on empty maps.

* fix(platformstl): use C stdio in file_lines perf harness

Avoid MinGW std::ifstream/std::getline crashes by switching the perf
fixture writer/reader to fopen/fwrite/fgetc while preserving split on '\n'
semantics (keeping '\r' in-line).

* fix(performance): use STLSOFT fopen wrapper in file_lines benchmark

* test(stlsoft): compare getline and getc file-line baselines

- Rename the existing per-character stdio baseline to vector<std::string>
  plus getc;
- add a genuine std::getline baseline using the appropriate LF or CR
  delimiter and normalise CRLF line endings;

* ci

* ci(stlsoft): trace the MinGW file_lines performance crash

Temporarily run only the Windows/MinGW CI cell and write flushed stderr
markers through the file_lines performance program;
- Each scenario, warmup, and sampled iteration logs enter and leave;
- `basic_file_lines` traces construct, iterate, and walk on those samples;

* fix(platformstl): stop the MinGW getline baseline segfaulting

`std::ifstream` dies on open under MinGW, which the progress log showed on
the first getline call. Read that baseline through FILE* and still time
`std::getline`;
- Other compilers keep `std::ifstream`;

* test(platformstl): stage the MinGW file_lines perf crash probe

Print flushed C traces from constructors before main, then walk one small
file through write, fgetc, getline, file_lines, and ifstream. The full
matrix runs only when SIS_FILE_LINES_PERF_FULL is set.

* test(platformstl): leave an on-disk trail for the MinGW file_lines death

Add a pure-C boot probe and cwd breadcrumb file, fix the performance
harness so it keeps the real child exit status, dump PE imports before
exec, and restrict the MinGW cell to file_lines only.

* ci(stlsoft): treat MinGW file_lines 127 as a loader failure

Static-link libgcc/libstdc++ for the file_lines perf target, run that step
under the msys2 shell, and on child-exit 127 dump MinGW runtimes, copy them
beside the exe, and retry once.

* ci(stlsoft): re-enable the file_lines full perf matrix on MinGW

The staged probe passed end-to-end after static-linking libgcc/libstdc++,
including std::ifstream. Set SIS_FILE_LINES_PERF_FULL so the next cell run
hits the real workload with traces still enabled.

* fix(cmake): static-link MinGW libgcc/libstdc++ for PE load

MinGW performance exes were exiting 127 before any user code when the
runner PATH did not match the toolchain that built them. Static-link libgcc
and libstdc++ for Windows GNU targets in TargetMacros, restore the
file_lines perf baseline and full CI, and capture the real child exit
status in execute_performance_tests.sh.

* docs(stlsoft): record file_lines.perf and restore GROUPGAPS in CI

Restore SIS_PERFTESTS_GROUPGAPS=1 on the performance job; document
platformstl::basic_file_lines<> mmap parse (~10–20× vs fgetc; ~parity /
~1.2× vs getline), the file_lines perf harness, MinGW static libgcc /
libstdc++, and the execute_performance_tests.sh child_ec fix in
CHANGES.md / NEWS.md for 1.11.1-rc7.

* String comparison unit-tests (#27)

* refactor(stlsoft): shorten the uds_helpers test directory

Point add_subdirectory at uds_helpers. The executable name stays
test.component.inetstl.util.uds_helpers.

* refactor(stlsoft): shorten component-test directories

Rename each test/component leaf to its subject segment and retarget
add_subdirectory. Refresh each entry file's Updated and File banner.
Executable names stay test.component.*.

* refactor(stlsoft): shorten unit-test directories

Rename each test/unit leaf except test.unit.versions to its subject segment
and retarget add_subdirectory. Refresh each entry file's Updated and File
banner. Executable names stay test.unit.*. test.unit.versions is unchanged.

* refactor(stlsoft): shorten scratch-test directories

Rename each test/scratch leaf to its subject segment and retarget
add_subdirectory. Refresh Updated and File banners where present.
Executable names stay test.scratch.*.

* refactor(stlsoft): deepen performance-test directories

Move each performance test onto its component header path, such as
platformstl/diagnostics/stopwatch and stlsoft/memory/auto_buffer, and
retarget add_subdirectory. Keep the C++ standard guards and the executable
names.

* refactor(stlsoft): shorten the test.unit.versions directory

Rename test/unit/test.unit.versions to versions and retarget
add_subdirectory. Refresh the entry file's Updated and File banner. The
executable name stays test.unit.versions.

* test(STLSoft): remove empty unit-test case functions

Remove empty xTests case functions (bodies with no statements), together
with their forward declarations and XTESTS_RUN_CASE registrations, across
19 STLSoft unit-test entry files;
- nine allocator suites keep a runner and no cases, because every case body
  was empty;
- the Unix-built suites were rebuilt and passed;

* test(STLSoft): flesh out empty allocator unit-test cases

Give each of the nine allocator suites its seven cases:
- round-trip 0, 4, 256, and 4096 ints, and reject a count past max_size();
- specialise std::list and std::vector with that allocator;
- null_allocator must throw out_of_memory_exception on every request,
  including container insertion;
- malloc_allocator, new_allocator, and null_allocator passed on this host;

* test(stlsoft): cover stlsoft_C_strnicmp and the C++ wrappers

Add unit programs for stlsoft_C_strnicmp(), stlsoft_C_wcsnicmp(), and the
stlsoft::c_string wrappers:
- char and wchar_t equal, less, and greater;
- n==0, stop at NUL, and ignore the byte past n;
- ASCII case-fold, and high-bit bytes that share a sign;

* test(stlsoft): cover c_string_traits string comparison

Add a unit program for stlsoft::c_string_traits compare, n-compare, and the
ASCII case-insensitive forms, for char and wchar_t:
- equal, less, greater, and empty strings;
- n==0, stop at NUL, and ignore the byte past n;

* test(stlsoft): cover char_traits and char_traits_safe comparison

Add a unit program for stlsoft::char_traits and char_traits_safe, for char
and wchar_t:
- eq, lt, and compare equal, less, and greater;
- n==0, exact-n past NUL, and compare_max stopping at NUL;
- compare_null for both-null and one-null;
- char compare unsigned high-bit order;
- char_traits_safe compare, and compare_max with null pointers;

* test(stlsoft): cover stlsoft::strcmp and stlsoft::strncmp

Add a unit program for the pointer overloads and the S const& shims:
- char and wchar_t equal, less, greater, and empty;
- n==0, the byte past n, and stop at NUL;
- simple_string and string_view, including embedded NUL;

* test(stlsoft): cover BSTR comparison wrappers and length edges

Extend the C BSTR program and add a C++ program for the wrappers:
- empty BSTR versus NULL and versus a live string;
- embedded NUL keeps SysStringLen and wcscmp stops;
- a length-limited prefix compares as that prefix;
- BSTR_compare and one comstl__bstr_compare call match the C function;
- bstr::equal for equal, unequal, empty, and a raw BSTR;

* test(stlsoft): cover string_begins_with prefix checks

Add a unit program for stlsoft::string_begins_with on char, including the
simple_string shim:
- match, reject, empty prefix, and a longer prefix;
- case-sensitive ASCII;
- a prefix containing NUL is only the part before that NUL;

* test(stlsoft): cover special_string_instance::equal

Add a unit program for special_string_instance::equal, using a fixed policy
so the cases do not touch the environment:
- case-sensitive and ASCII case-insensitive, for char and wchar_t;
- length mismatch, including empty;
- an embedded NUL stops the compare when both lengths include it;

* test(stlsoft): require simple_string to keep embedded NULs

Amend the simple_string unit so every write path is expected to keep an
embedded NUL, for char and wchar_t:
- construction from a counted range;
- append, including a tail that begins with NUL, and push_back;
- assign into an empty string, a reserved buffer, and a NUL fill, plus copy
  of a preserved string;
- compare treats a difference after that NUL as unequal;

The suite now fails where construction, append, push_back, and assign into
an empty string still drop the NUL.

* whitespace

* fix(stlsoft): preserve counted NULs in simple_string

append(pointer, count) copied only up to the first NUL, so push_back of NUL
and a counted tail that begins with NUL were dropped. Copy the given count,
and assign a single character by that count so '\0' is stored.

Counted construction and assign already kept the full count.

* test(stlsoft): cover static_string compare

Add unit cases for stlsoft::basic_static_string compare:
- char equal, less, greater, and unsigned high-bit order;
- empty char strings;
- a counted embedded NUL stays in the length and compare sees the byte
  after it, for char and wchar_t;
- wchar_t order and empty strings;

* test(stlsoft): cover string_view compare edges

Add unit cases for stlsoft::basic_string_view compare:
- empty char views;
- a counted embedded NUL stays in the length and compare sees the byte
  after it;
- wchar_t order, empty, and the same embedded NUL;
- char_traits_safe order, empty, and the same embedded NUL;

* test(stlsoft): cover string_slice compare edges

Add unit cases for stlsoft::string_slice compare:
- empty char and wchar_t slices;
- a counted embedded NUL stays in the length and compare sees the byte
  after it, for char and wchar_t;
- char high-bit order, with 0x01 before 0xFF;

* fix(stlsoft): let string_begins_with accept wchar_t

operator() called strncmp, so a wchar_t prefix did not compile. Compare
with char_traits<C>::compare_max, which is strncmp for char and wcsncmp for
wchar_t, and cover the wide match, reject, empty, longer, and
case-sensitive cases, including a prefix that contains NUL.

* fix(stlsoft): define char_traits_safe compare of a null pointer

compare_null() called the parent compare(), so a null pointer and a
non-zero count were not defined. A count of 0 still compares equal. A
non-zero count orders a null pointer before a live pointer, for char and
wchar_t.

* fix(stlsoft): build cleanly with MSVC and MinGW

windows-cl-atl treats the Windows small macro, truncated char casts, CRT
deprecation, and an unreachable fail as errors. windows-mingw does not link
NetApiBufferAllocate.
- store high-bit bytes in unsigned char and view them as char;
- suppress deprecation around strcpy, strcat, and strdup wrappers;
- accept that null_allocator::allocate always throws;
- pass a DWORD byte count to NetApiBufferAllocate and link netapi32;

* fix(stlsoft): stop null_allocator test failing MSVC /WX

allocate() always throws, so a fail after the call is unreachable in a
Release build, and the warning disable does not cover that. Record the
exception and assert the flag after the try.

* release

* fix

* docs(stlsoft): complete the 1.11.1-rc7 notes

Add the rc6-to-HEAD items missing from the 1.11.1-rc7 notes: FILE_stream
write_binary(), file_lines tests, filled allocator units, and removal of
empty placeholder cases. Counted concatenation stays with the simple_string
NUL contract.

* `platformstl::basic_file_lines<>` perf-tests improvements: gap-groups (#30)

* test(stlsoft): cover platformstl::file_lines component behavior

Add a platformstl filesystem component suite covering empty files and
one-line, terminated-line, and three-line inputs; verify embedded NUL data
raises platformstl::invalid_file_type_exception; register the filesystem
subtree with the existing platformstl component tests;

* test(stlsoft): benchmark platformstl::file_lines

Add a standalone comparative performance test for platformstl::file_lines_a
and std::ifstream plus std::getline; follow existing STLSoft warm-up,
stopwatch, anchoring, and tabular output conventions;

* test(platformstl): improved performance test program for `platformstl::basic_file_lines<>`

* fix(platformstl::file_lines): compatible with `std::basic_string_view`

* perf(platformstl::file_lines): adjusted preallocation of number of reserved lines

* perf(platformstl::file_lines): moved file-contents check into in-loop processing, so only pass once

* perf(platformstl::file_lines): use of `#emplace_back()` (C++ 11+)

* feat(platformstl): add FILE_stream::write_binary()

Add a byte-oriented write that preserves embedded NULs, and deprecate
write(void const*, size_t) in its favour. Cover CR and CRLF files in the
file_lines component tests, and write the NUL fixture through
write_binary().

* feat(platformstl): add FILE_stream::write_binary()

Add a byte-oriented write that preserves embedded NULs, and deprecate
write(void const*, size_t) in its favour. Cover CR and CRLF files in the
file_lines component tests, and write the NUL fixture through
write_binary().

* test(platformstl): cover CR, CRLF, and small files in file_lines perf

Time LF, CRLF, and CR at the existing 1000×64 and 5000×80 shapes, and at
1×16, 8×32, and 32×64, so a later small-file path has a fixed-cost region
to beat. Read the getline baseline in binary mode so the CR bytes are the
same on every platform.

* refactor(platformstl): parse file_lines directly from mapping

Remove the full-file m_contents heap copy; parse directly from the mmap'd
base pointer for lower memory bandwidth use. Drop the unused m_contents
member and early-return on empty maps.

* fix(platformstl): use C stdio in file_lines perf harness

Avoid MinGW std::ifstream/std::getline crashes by switching the perf
fixture writer/reader to fopen/fwrite/fgetc while preserving split on '\n'
semantics (keeping '\r' in-line).

* fix(performance): use STLSOFT fopen wrapper in file_lines benchmark

* test(stlsoft): compare getline and getc file-line baselines

- Rename the existing per-character stdio baseline to vector<std::string>
  plus getc;
- add a genuine std::getline baseline using the appropriate LF or CR
  delimiter and normalise CRLF line endings;

* ci

* ci(stlsoft): trace the MinGW file_lines performance crash

Temporarily run only the Windows/MinGW CI cell and write flushed stderr
markers through the file_lines performance program;
- Each scenario, warmup, and sampled iteration logs enter and leave;
- `basic_file_lines` traces construct, iterate, and walk on those samples;

* fix(platformstl): stop the MinGW getline baseline segfaulting

`std::ifstream` dies on open under MinGW, which the progress log showed on
the first getline call. Read that baseline through FILE* and still time
`std::getline`;
- Other compilers keep `std::ifstream`;

* test(platformstl): stage the MinGW file_lines perf crash probe

Print flushed C traces from constructors before main, then walk one small
file through write, fgetc, getline, file_lines, and ifstream. The full
matrix runs only when SIS_FILE_LINES_PERF_FULL is set.

* test(platformstl): leave an on-disk trail for the MinGW file_lines death

Add a pure-C boot probe and cwd breadcrumb file, fix the performance
harness so it keeps the real child exit status, dump PE imports before
exec, and restrict the MinGW cell to file_lines only.

* ci(stlsoft): treat MinGW file_lines 127 as a loader failure

Static-link libgcc/libstdc++ for the file_lines perf target, run that step
under the msys2 shell, and on child-exit 127 dump MinGW runtimes, copy them
beside the exe, and retry once.

* ci(stlsoft): re-enable the file_lines full perf matrix on MinGW

The staged probe passed end-to-end after static-linking libgcc/libstdc++,
including std::ifstream. Set SIS_FILE_LINES_PERF_FULL so the next cell run
hits the real workload with traces still enabled.

* fix(cmake): static-link MinGW libgcc/libstdc++ for PE load

MinGW performance exes were exiting 127 before any user code when the
runner PATH did not match the toolchain that built them. Static-link libgcc
and libstdc++ for Windows GNU targets in TargetMacros, restore the
file_lines perf baseline and full CI, and capture the real child exit
status in execute_performance_tests.sh.

* docs(stlsoft): record file_lines.perf and restore GROUPGAPS in CI

Restore SIS_PERFTESTS_GROUPGAPS=1 on the performance job; document
platformstl::basic_file_lines<> mmap parse (~10–20× vs fgetc; ~parity /
~1.2× vs getline), the file_lines perf harness, MinGW static libgcc /
libstdc++, and the execute_performance_tests.sh child_ec fix in
CHANGES.md / NEWS.md for 1.11.1-rc7.

* test(performance): roll out GROUPGAPS helpers and --gap-groups

Add shared perf_group_gaps.hpp on the test/performance include path and
honour SIS_PERFTESTS_GROUPGAPS in every performance main. TTY emits a
blank line between logical groups; non-TTY / CI emits `\t----------`.
Add --gap-groups on execute_performance_tests.sh/.cmd; CI uses
-M --gap-groups. bit_functions keeps family/pattern key derivation via
the shared helpers.

* docs(stlsoft): align remaining project notes with 1.11.1-rc7

Point the bit-function strategy note at the nested performance program and
close the matching **TODO.md** item; name the versions test by its current
directory in the MFC comment in **CMakeLists.txt**.

---------

Co-authored-by: Matt Wilson <152443343+mwsis@users.noreply.github.com>

* boilerplate

---------

Co-authored-by: Matt Wilson <152443343+mwsis@users.noreply.github.com>

* chore(stlsoft): release 1.11.1-rc8

Release **STLSoft** 1.11.1-rc8 (10th October 2026), restoring behaviour
lost in the boilerplate helper-script rewrite and adding the Doxygen inputs
that **generate_doxygen.sh** requires:
- add **_STLSOFT_VER_1_11_1_RC8** and align **test.unit.versions**,
  **CHANGES.md**, **NEWS.md**, and **INSTALL.md**;
- restore **prepare_cmake.sh** `--no-ace`, `--no-cstring`, and
  `--use-unixem`, with their `-D` options;
- restore **run_all_examples.sh** / **.cmd** `--skip-interactive`, and its
  use in CI to avoid hung runs;
- fail **run_all_unit_tests.sh** and **run_all_component_tests.sh** when no
  programs are discovered;
- run component tests in **ci-cell.yml** via
  **run_all_component_tests.sh**;
- add **Doxyfile** and **doc/mainpage.md**;

* Environment functions (#49)

* test(stlsoft): add component tests for environment_variable_exists()

Cover stlsoft::environment_variable_exists() against the process
environment, using platformstl::environment_variable_scope to set and
restore each variable: absent, present, erased, value replacement, exact
name match, std::string and simple_string shims, UNIX empty values, and
case sensitivity.

* refactor(stlsoft): rename environment_variable_exists_a to _m

Rename `stlsoft_C_environment_variable_exists_a()` to
`stlsoft_C_environment_variable_exists_m()` and keep the `_a` spelling as a
deprecated forwarder; the C++ overloads and component tests call `_m`.

* version

* fix(stlsoft): detect empty environment variables with getenv_s

Use the required-size result from getenv_s() to distinguish an absent
variable from an existing empty variable;

Extend the environment-variable component test to cover empty values
through the C API and C++ overloads, replacement, and erasure;

* Traits conformance (#51)

* chore(layout): trivial whitespace change…
synesissoftware and others added 3 commits October 27, 2026 20:48
* build: publish HAS_<Pkg> for detected Synesis packages

After each successful detection of a Synesis package other than STLSoft,
define HAS_<Pkg> and HAS_<PKG>. Dots in package names become underscores.
BDUT and CLASP, already all-caps, get the single macro. Client-prefixed
presence macros were removed where only the client read them. Dependency
switches such as XTESTS_HAS_SHWILD, PANTHEIOS_HAS_SHWILD, and the *_NO_*
contracts stay.

* fix: align versions links and current release symbols

Keep versions target_link_libraries in the signature the project already
uses, link header-only packages by their imported target, and compare
version tests with the current release symbol.

* feat(stlsoft): add opt-in woad and rework scratch versions programs

Add an opt-in woad dependency and bring the three scratch versions
programs into line with each other for the 1.11.3 release:
- add --use-woad to prepare_cmake.sh, passing -DUSE_WOAD:BOOL, and find
  woad 0.2.0 in CMakeLists.txt when USE_WOAD and BUILD_TESTING are set,
  defining HAS_woad / HAS_WOAD and the HAS_woad CMake variable;
- link woad::woad into the versions.format, versions.iostreams and
  versions.stdio targets when HAS_woad is set;
- define STLSOFT_HAS_COM on WIN32 so that COMSTL is reported;
- rework versions.stdio and versions.iostreams to a common layout with
  headings, language and compiler lines, subproject entries and a
  conditional efferent-dependencies section (cstring, Diagnosticism,
  shwild, UNIXem, woad, xTests), rendering versions via Diagnosticism
  when available;
- colour versions.iostreams output via woad (magenta headings, blue names,
  green versions and values, cyan macros), falling back to plain text when
  woad is not in use;
- include the ACESTL, ATLSTL, COMSTL and MFCSTL headers in the versions
  programs, ordered consistently;
- record the work in CHANGES.md, NEWS.md and INSTALL.md, and add a TODO.md
  item to colour versions.stdio and versions.format;

* fix: correct the Diagnosticism line in versions.stdio

The fprintf passed an undeclared prefix and one argument too many. Print
the name, the Diagnosticism string, and the macro, as the fallback line
does.

* fix(stlsoft): link xTests into the scratch versions reporters

HAS_xTests is defined for the whole test build, so the scratch versions
programs include xtests/xtests.h. They are example targets, which do not
link xTests::core, so CI cannot see that header. Link it, as the unit tests
already do.

* fix(stlsoft): apply shared MFC settings to scratch versions

versions.iostreams and versions.format include mfcstl.hpp when
STLSOFT_HAS_MFC is set, but example targets never receive _AFXDLL. The
windows-cl-mfc cell then rejects afx.h under /MD. Use the same per-target
MFC settings as test/unit/versions.

---------

Co-authored-by: Matt Wilson <152443343+mwsis@users.noreply.github.com>
* 1.11.2-alpha1

* test(integer_to_string): share one short performance schedule

Hoist the per-section warmup, loop, and iteration counts to one shared
schedule of 2 warmups, 1 loop, and 1,000,000 iterations. The previous
4 x 100 x 1,000,000 schedule was repeated in every section and took about
ten minutes.

* feature(C-string functions): added **test.performance.stlsoft.string.c_string**

* test(stlsoft): add exhaustive c_string unit tests

Cover strnchr, strnicmp, strnistrn, strnpbrkn, strnrchr, and strnstrn,
plus each wchar analogue, with C and C++ programs. strnistrn stays on
the current inner loop, so the first-character false-match cases fail;

* fix(stlsoft): compare each strnistrn character, not only the first

The inner loops of stlsoft_C_strnistrn() and stlsoft_C_wcsnistrn() compared
*s1 with *s2 on every index, so a first-character hit was treated as a full
match. Index the pair, and fold the wide form with towupper.

* fix(stlsoft): quiet MSVC C4310 and GCC 15 free-nonheap-object

Store high-bit c_string test bytes in unsigned char so MSVC does not warn
C4310. Keep auto_buffer's deallocate out of line on GCC 12+, and suppress
-Wfree-nonheap-object, which mis-reports a delete of the internal buffer
from simple_string's unit test.

* version

* test(stlsoft): compare a full sun_path with a counted string

A 108-character path fills Linux sun_path and is not NUL-terminated.
TEST_MS_EQ then read past the array. Use TEST_MS_EQ_N when the path
length is the capacity, as the 104-character case already does.

* perf(stlsoft): fold ASCII case in strnistrn without toupper

Replace per-byte toupper and towupper on the strnistrn hot path with an
unsigned-char ASCII fold. The needle's first unit is folded once.
wchar_t units outside 0-127 still use towupper. Non-ASCII bytes are no
longer folded by the current locale.

* perf(stlsoft): fold ASCII case in strnicmp without toupper

Replace per-byte toupper and towupper on the strnicmp mismatch path with
an unsigned-char ASCII fold. Equal bytes still skip the fold. wchar_t units
outside 0-127 still use towupper.

* tidy

* perf(stlsoft): search ASCII strnchr with memchr

Call memchr and wmemchr when the search value is in 0-127. Values outside
that range stay on the scalar loop, so a high byte still compares as a
promoted char.

* perf(stlsoft): search strnstrn with memchr and memcmp

Find the first needle byte with memchr over the positions that can still
hold the needle, then memcmp the remaining bytes. A partial hit resumes one
past that byte. wchar_t uses wmemchr and wmemcmp. An empty needle still
returns the haystack.

* tidy

* canonicalisation

* tidy

* `auto_buffer` extraneous warning (#74)

* 1.11.2 version elements

* fix(stlsoft): silence GCC 12+ free-nonheap warning in auto_buffer

Mark `deallocate_()` `STLSOFT_NOINLINE` on GCC 12+ so `operator delete`
stays out of the inlined destructor, and suppress `-Wfree-nonheap-object`
around the external-buffer `delete`, which GCC misattributes to the
internal buffer.

* test(stlsoft): compare a full sun_path with a counted string

A 108-character path fills Linux sun_path and is not NUL-terminated.
TEST_MS_EQ then read past the array. Use TEST_MS_EQ_N when the path
length is the capacity, as the 104-character case already does.

---------

Co-authored-by: Matt Wilson <matthew.wilson.synesis@gmail.com>

* refactor(unit-tests): applying xTests' null macros (#70)

* refactor(unit-tests): applying xTests' null macros

* 1.11.2-alpha1

* `auto_buffer` extraneous warning (#74)

* 1.11.2 version elements

* fix(stlsoft): silence GCC 12+ free-nonheap warning in auto_buffer

Mark `deallocate_()` `STLSOFT_NOINLINE` on GCC 12+ so `operator delete`
stays out of the inlined destructor, and suppress `-Wfree-nonheap-object`
around the external-buffer `delete`, which GCC misattributes to the
internal buffer.

* test(stlsoft): compare a full sun_path with a counted string

A 108-character path fills Linux sun_path and is not NUL-terminated.
TEST_MS_EQ then read past the array. Use TEST_MS_EQ_N when the path
length is the capacity, as the 104-character case already does.

---------

Co-authored-by: Matt Wilson <matthew.wilson.synesis@gmail.com>

---------

Co-authored-by: synesissoftware <matthew@synesis.com.au>

* chore(nomenclature): standardise use of alpha/beta/rc nomenclature throughout the repo (#75)

* `auto_buffer` extraneous warning (#74)

* 1.11.2 version elements

* fix(stlsoft): silence GCC 12+ free-nonheap warning in auto_buffer

Mark `deallocate_()` `STLSOFT_NOINLINE` on GCC 12+ so `operator delete`
stays out of the inlined destructor, and suppress `-Wfree-nonheap-object`
around the external-buffer `delete`, which GCC misattributes to the
internal buffer.

* test(stlsoft): compare a full sun_path with a counted string

A 108-character path fills Linux sun_path and is not NUL-terminated.
TEST_MS_EQ then read past the array. Use TEST_MS_EQ_N when the path
length is the capacity, as the 104-character case already does.

---------

Co-authored-by: Matt Wilson <matthew.wilson.synesis@gmail.com>

* chore(nomenclature): standardise use of alpha/beta/rc nomenclature throughout the repo

---------

Co-authored-by: Matt Wilson <matthew.wilson.synesis@gmail.com>

* C-string function fixes (#77)

* test(stlsoft): add exhaustive c_string unit tests

Cover strnchr, strnicmp, strnistrn, strnpbrkn, strnrchr, and strnstrn,
plus each wchar analogue, with C and C++ programs. strnistrn stays on
the current inner loop, so the first-character false-match cases fail;

* fix(stlsoft): compare each strnistrn character, not only the first

The inner loops of stlsoft_C_strnistrn() and stlsoft_C_wcsnistrn() compared
*s1 with *s2 on every index, so a first-character hit was treated as a full
match. Index the pair, and fold the wide form with towupper.

* chore(automated tests): namespace commenting

* fix(stlsoft): quiet MSVC C4310

Store high-bit c_string test bytes in unsigned char so MSVC does not warn
C4310.

---------

Co-authored-by: Matt Wilson <matthew.wilson.synesis@gmail.com>

* tidying

* docs(stlsoft): summarise c_string performance by function

Add one table per helper covering the local Mac and the six CI cells.
Paired cells are baseline time divided by the STLSoft time, so a value
above 1 means the STLSoft function was faster. strnistrn stays as total
nanoseconds because it has no paired baseline.

---------

Co-authored-by: synesissoftware <matthew@synesis.com.au>
Rewrite the **1.11.3** section for the work on dev since **1.11.2**:
- ASCII case-fold and memchr/memcmp in the counted-string functions;
- HAS_<Pkg> / HAS_<PKG>, opt-in woad, and the scratch version reporters;
- subproject headers require STLSoft 1.11.2;
- Doxyfile PROJECT_NUMBER is 1.11.3; unfinished 1.11.3 items move to
  1.11.4;
@synesissoftware
synesissoftware merged commit 7a9cfd8 into master Oct 27, 2026
116 of 118 checks passed
@synesissoftware
synesissoftware deleted the dev branch October 27, 2026 10:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants