<feed xmlns='http://www.w3.org/2005/Atom'>
<title>catch3/tests, branch security/bounded-strings</title>
<subtitle>Progmasoft Catch3: an independently maintained C++17 test framework with a parallel runner, property checks, snapshots and structured results.</subtitle>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/catch3/'/>
<entry>
<title>Merge branch 'devel' into security/bounded-strings</title>
<updated>2026-11-15T19:58:40+00:00</updated>
<author>
<name>Leitwolf</name>
</author>
<published>2026-11-15T19:58:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/catch3/commit/?id=21ce423a12523559abf83f7a2ed83e042efc418d'/>
<id>21ce423a12523559abf83f7a2ed83e042efc418d</id>
<content type='text'>
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
</pre>
</div>
</content>
</entry>
<entry>
<title>Bound three string and XML reads that analysis flagged</title>
<updated>2026-11-15T19:29:48+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-11-15T19:29:48+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/catch3/commit/?id=79f624d693b4fc3167c239c3eca47c858799333f'/>
<id>79f624d693b4fc3167c239c3eca47c858799333f</id>
<content type='text'>
- On macOS, the path from _NSGetExecutablePath is cut at its first NUL
  found inside the buffer, in place of strlen, which reads until a NUL
  wherever it is.
- The table test measures its C strings with std::string_view.
- The Bazel reporter test reads the JUnit report and rejects a document
  type before parsing it, so no entity, external or internal, is ever
  expanded.

Claude-Session: https://claude.ai/code/session_01Ji7xZMYjc4zankoc7hGBE1
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
- On macOS, the path from _NSGetExecutablePath is cut at its first NUL
  found inside the buffer, in place of strlen, which reads until a NUL
  wherever it is.
- The table test measures its C strings with std::string_view.
- The Bazel reporter test reads the JUnit report and rejects a document
  type before parsing it, so no entity, external or internal, is ever
  expanded.

Claude-Session: https://claude.ai/code/session_01Ji7xZMYjc4zankoc7hGBE1
</pre>
</div>
</content>
</entry>
<entry>
<title>License Catch3's Progmasoft files under LGPL-3.0-or-later</title>
<updated>2026-11-15T11:01:10+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-11-15T11:01:10+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/catch3/commit/?id=5c865488e762dec616a0c483d0fc07250123c387'/>
<id>5c865488e762dec616a0c483d0fc07250123c387</id>
<content type='text'>
Every Progmasoft-authored file moves from MPL-2.0 WITH
AdditionRef-Progmasoft-Exception-2.0 to LGPL-3.0-or-later, from 0.5.0
on. Catch2-originated files keep BSL-1.0, and the vendored yyjson and
pugixml keep MIT.

- LICENSES/ holds the LGPL-3.0 text and the GPL-3.0 text it builds on,
  both as published by the FSF; the MPL-2.0 and Progmasoft Exception
  texts go, since no file uses them any more.
- Five files that came from Catch2 and were rewritten by Progmasoft
  (.clang-format, the issue and pull request templates, SECURITY.md)
  keep Catch2's notice next to Progmasoft's and say
  LGPL-3.0-or-later AND BSL-1.0.
- //tools:check expects the new header of Progmasoft files; the
  amalgamation notes say LGPL-3.0-or-later and the amalgamated files
  are regenerated; meson's project license is BSL-1.0 and
  LGPL-3.0-or-later.
- README gains a License section; LICENSES/README.md, contributing.md
  and progmasoft-catch3.md describe the new split and that releases
  before 0.5.0 keep the license they were published under.

Every commit to Progmasoft's files since the fork is the maintainer's.

Claude-Session: https://claude.ai/code/session_01Ji7xZMYjc4zankoc7hGBE1
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Every Progmasoft-authored file moves from MPL-2.0 WITH
AdditionRef-Progmasoft-Exception-2.0 to LGPL-3.0-or-later, from 0.5.0
on. Catch2-originated files keep BSL-1.0, and the vendored yyjson and
pugixml keep MIT.

- LICENSES/ holds the LGPL-3.0 text and the GPL-3.0 text it builds on,
  both as published by the FSF; the MPL-2.0 and Progmasoft Exception
  texts go, since no file uses them any more.
- Five files that came from Catch2 and were rewritten by Progmasoft
  (.clang-format, the issue and pull request templates, SECURITY.md)
  keep Catch2's notice next to Progmasoft's and say
  LGPL-3.0-or-later AND BSL-1.0.
- //tools:check expects the new header of Progmasoft files; the
  amalgamation notes say LGPL-3.0-or-later and the amalgamated files
  are regenerated; meson's project license is BSL-1.0 and
  LGPL-3.0-or-later.
- README gains a License section; LICENSES/README.md, contributing.md
  and progmasoft-catch3.md describe the new split and that releases
  before 0.5.0 keep the license they were published under.

Every commit to Progmasoft's files since the fork is the maintainer's.

Claude-Session: https://claude.ai/code/session_01Ji7xZMYjc4zankoc7hGBE1
</pre>
</div>
</content>
</entry>
<entry>
<title>Fix the coverage review findings: branches, stale reports, exit status</title>
<updated>2026-11-13T18:55:26+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-11-13T18:55:26+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/catch3/commit/?id=454cca9b63991416f4d9bd1ca350f1ae14e278e7'/>
<id>454cca9b63991416f4d9bd1ca350f1ae14e278e7</id>
<content type='text'>
- gcov is run with --branch-probabilities; without it the JSON has no
  branches, so GCC runs had none in coverage.json and coverage.lcov.
- PrepareCoverageDirectory also removes coverage.json, coverage.lcov and
  coverage.profdata of an earlier run, so a run that writes no reports
  leaves none to be read as its own.
- WriteCoverageReports returns notes and failures apart. A profile the
  tools cannot read or a report that cannot be written counts as a missing
  report and fails a run whose tests passed; no profiles or missing tools
  stay notes. The paths are announced only for the reports written.
- ProfiledAttempt takes the last attempt whose profile exists, so a final
  retry or repeat that crashed before writing one no longer hides the
  coverage of the attempt before it.
- The per-test llvm-profdata/llvm-cov and gcov runs use as many threads
  as the run had workers; results keep the order of the tests.

Tests: earlier reports removed and no failure without profiles; an
attempt without a profile gives way to an earlier one; the fixture gains a
branch taken one way, checked in the combined LCOV for both LLVM and gcov.
Amalgamation regenerated.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
- gcov is run with --branch-probabilities; without it the JSON has no
  branches, so GCC runs had none in coverage.json and coverage.lcov.
- PrepareCoverageDirectory also removes coverage.json, coverage.lcov and
  coverage.profdata of an earlier run, so a run that writes no reports
  leaves none to be read as its own.
- WriteCoverageReports returns notes and failures apart. A profile the
  tools cannot read or a report that cannot be written counts as a missing
  report and fails a run whose tests passed; no profiles or missing tools
  stay notes. The paths are announced only for the reports written.
- ProfiledAttempt takes the last attempt whose profile exists, so a final
  retry or repeat that crashed before writing one no longer hides the
  coverage of the attempt before it.
- The per-test llvm-profdata/llvm-cov and gcov runs use as many threads
  as the run had workers; results keep the order of the tests.

Tests: earlier reports removed and no failure without profiles; an
attempt without a profile gives way to an earlier one; the fixture gains a
branch taken one way, checked in the combined LCOV for both LLVM and gcov.
Amalgamation regenerated.
</pre>
</div>
</content>
</entry>
<entry>
<title>Keep per-test coverage with --coverage (roadmap step 4)</title>
<updated>2026-11-13T04:46:08+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-11-13T04:46:08+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/catch3/commit/?id=17248837a78462596809f902dd47cf113a39670b'/>
<id>17248837a78462596809f902dd47cf113a39670b</id>
<content type='text'>
--coverage=DIR runs every attempt in a process of its own and keeps the
coverage of each test, for binaries built with LLVM source-based
coverage (Clang: -fprofile-instr-generate -fcoverage-mapping) or gcov
(GCC: --coverage).

How it works:

- The scheduler gives each attempt a fresh worker, as --isolate does,
  and sets LLVM_PROFILE_FILE and GCOV_PREFIX for it: the profile the
  process writes at exit is then the coverage of that one test, in
  DIR/profiles/t&lt;index&gt;-a&lt;attempt&gt;.profraw or a .gcov directory. Each
  runtime reads its own variable, so no symbol of either runtime is
  looked up: weak references would not pull them out of their static
  archives, and Mach-O does not allow undefined weak symbols.
- A process that runs other tests first (bisection) writes beside the
  profiles, never into one. The attempt records its profile, also in
  the JSON results.
- The profiles of an earlier run are removed when the run starts,
  because gcov adds to the .gcda files it finds.
- After the run, CoverageReport reads the last profile of every test:
  llvm-profdata and llvm-cov (PATH, xcrun on macOS, or
  CATCH3_LLVM_PROFDATA / CATCH3_LLVM_COV) export LCOV, gcov (or
  CATCH3_GCOV) writes JSON next to a copy of the .gcno. The result is
  DIR/coverage.json, the files, lines and branches each test reached,
  and DIR/coverage.lcov for the whole run. Without the tools the
  profiles stay and the run says so.
- --coverage with --in-process or --break is refused.

Tests: option parsing, LCOV reading and writing, gcov JSON, and an
integration test on CoverageFixture, a binary CMake builds with the
coverage flags of Clang or GCC: two tests reach different lines, each
map holds only its own, the LCOV report holds both, and a stale profile
of an earlier run is gone. Locally all 84 CTest tests pass with Clang,
and the coverage tests pass with GCC (built with
CATCH3_ENABLE_WERROR=OFF: Catch3 does not build with GCC's warnings as
errors, before and after this change).

CI: the Linux jobs install LLVM's coverage tools, a new job checks the
gcov path with GCC, and CATCH3_COVERAGE_REQUIRE_MAP turns a missing map
into a failure on Linux and macOS instead of a skip. MSVC has no
source-based coverage, so its builds skip those tests; the roadmap
lists step 4 as in progress until Windows with clang-cl is tested.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
--coverage=DIR runs every attempt in a process of its own and keeps the
coverage of each test, for binaries built with LLVM source-based
coverage (Clang: -fprofile-instr-generate -fcoverage-mapping) or gcov
(GCC: --coverage).

How it works:

- The scheduler gives each attempt a fresh worker, as --isolate does,
  and sets LLVM_PROFILE_FILE and GCOV_PREFIX for it: the profile the
  process writes at exit is then the coverage of that one test, in
  DIR/profiles/t&lt;index&gt;-a&lt;attempt&gt;.profraw or a .gcov directory. Each
  runtime reads its own variable, so no symbol of either runtime is
  looked up: weak references would not pull them out of their static
  archives, and Mach-O does not allow undefined weak symbols.
- A process that runs other tests first (bisection) writes beside the
  profiles, never into one. The attempt records its profile, also in
  the JSON results.
- The profiles of an earlier run are removed when the run starts,
  because gcov adds to the .gcda files it finds.
- After the run, CoverageReport reads the last profile of every test:
  llvm-profdata and llvm-cov (PATH, xcrun on macOS, or
  CATCH3_LLVM_PROFDATA / CATCH3_LLVM_COV) export LCOV, gcov (or
  CATCH3_GCOV) writes JSON next to a copy of the .gcno. The result is
  DIR/coverage.json, the files, lines and branches each test reached,
  and DIR/coverage.lcov for the whole run. Without the tools the
  profiles stay and the run says so.
- --coverage with --in-process or --break is refused.

Tests: option parsing, LCOV reading and writing, gcov JSON, and an
integration test on CoverageFixture, a binary CMake builds with the
coverage flags of Clang or GCC: two tests reach different lines, each
map holds only its own, the LCOV report holds both, and a stale profile
of an earlier run is gone. Locally all 84 CTest tests pass with Clang,
and the coverage tests pass with GCC (built with
CATCH3_ENABLE_WERROR=OFF: Catch3 does not build with GCC's warnings as
errors, before and after this change).

CI: the Linux jobs install LLVM's coverage tools, a new job checks the
gcov path with GCC, and CATCH3_COVERAGE_REQUIRE_MAP turns a missing map
into a failure on Linux and macOS instead of a skip. MSVC has no
source-based coverage, so its builds skip those tests; the roadmap
lists step 4 as in progress until Windows with clang-cl is tested.
</pre>
</div>
</content>
</entry>
<entry>
<title>Withhold secret-looking variables from the processes the runner starts</title>
<updated>2026-11-05T15:30:13+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-11-05T15:30:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/catch3/commit/?id=89b4769c35986444a0297ad0806a584c7181c85c'/>
<id>89b4769c35986444a0297ad0806a584c7181c85c</id>
<content type='text'>
Workers and the other processes the runner starts copied its whole
environment, tokens and passwords included. ChildProcess::Start now
leaves out every variable whose name contains, in any case, TOKEN,
SECRET, PASSWORD, PASSWD, CREDENTIAL, API_KEY, APIKEY, PRIVATE_KEY or
ACCESS_KEY (IsSecretVariable), unless ChildOptions::keptEnvironment
names it; names compare without case on Windows.

--pass-env=NAME, repeatable, and "pass-env" in catch3.json keep a
variable for the tests. The runner itself still starts with the whole
environment, so code that runs before the first worker sees it;
docs/progmasoft-runner.md says so.

A runner test starts the shell's environment listing with a secret and
a plain variable set, sees only the plain one, and sees the secret once
it is kept.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Workers and the other processes the runner starts copied its whole
environment, tokens and passwords included. ChildProcess::Start now
leaves out every variable whose name contains, in any case, TOKEN,
SECRET, PASSWORD, PASSWD, CREDENTIAL, API_KEY, APIKEY, PRIVATE_KEY or
ACCESS_KEY (IsSecretVariable), unless ChildOptions::keptEnvironment
names it; names compare without case on Windows.

--pass-env=NAME, repeatable, and "pass-env" in catch3.json keep a
variable for the tests. The runner itself still starts with the whole
environment, so code that runs before the first worker sees it;
docs/progmasoft-runner.md says so.

A runner test starts the shell's environment listing with a secret and
a plain variable set, sees only the plain one, and sees the secret once
it is kept.
</pre>
</div>
</content>
</entry>
<entry>
<title>Move the tools from Python to C++17 and Bazel</title>
<updated>2026-11-01T19:16:14+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-11-01T19:16:14+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/catch3/commit/?id=da3c9d0880dbd4a74e0e43f092d10f5ae42c5043'/>
<id>da3c9d0880dbd4a74e0e43f092d10f5ae42c5043</id>
<content type='text'>
The scripts in use become programs run with `bazelisk run //tools:&lt;tool&gt;`,
and arguments follow `--`:

  amalgamate     the single-file distribution; --check only compares it
  check          licenses, convenience headers, header guards, file names,
                 versions and whether the distribution is up to date
  third-party    compares third_party/ with its archives, or writes it
  version        prints the version, or moves every place that states it
  release-notes  prints a section of the release notes
  repo-info      reports the checkout and the tools on PATH; --json writes
                 it through yyjson, with the fields of Tala's repo-info

amalgamate writes what the Python script wrote, byte for byte, except for
the time of generation. That line is gone so that the output is
deterministic and --check can compare it.

MODULE.bazel now pins the archives of third_party/ by version and sha256,
so Bazel fetches and checks them instead of a tool of ours, and Renovate
reads the versions there.

Scripts nobody used are gone: fixWhitespace, updateDocumentSnippets,
buildAndTest, and updateDocumentToC, which rewrote unrelated documents.
CheckConvenienceHeaders leaves CTest, since //tools:check runs it in CI.
The Python of tests/TestScripts stays: those are tests, not tools.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The scripts in use become programs run with `bazelisk run //tools:&lt;tool&gt;`,
and arguments follow `--`:

  amalgamate     the single-file distribution; --check only compares it
  check          licenses, convenience headers, header guards, file names,
                 versions and whether the distribution is up to date
  third-party    compares third_party/ with its archives, or writes it
  version        prints the version, or moves every place that states it
  release-notes  prints a section of the release notes
  repo-info      reports the checkout and the tools on PATH; --json writes
                 it through yyjson, with the fields of Tala's repo-info

amalgamate writes what the Python script wrote, byte for byte, except for
the time of generation. That line is gone so that the output is
deterministic and --check can compare it.

MODULE.bazel now pins the archives of third_party/ by version and sha256,
so Bazel fetches and checks them instead of a tool of ours, and Renovate
reads the versions there.

Scripts nobody used are gone: fixWhitespace, updateDocumentSnippets,
buildAndTest, and updateDocumentToC, which rewrote unrelated documents.
CheckConvenienceHeaders leaves CTest, since //tools:check runs it in CI.
The Python of tests/TestScripts stays: those are tests, not tools.
</pre>
</div>
</content>
</entry>
<entry>
<title>Carry mocks in the amalgamated files</title>
<updated>2026-11-01T19:05:27+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-11-01T19:05:27+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/catch3/commit/?id=241b5567d09d59e82bff18287c32ada480deb8c0'/>
<id>241b5567d09d59e82bff18287c32ada480deb8c0</id>
<content type='text'>
Mock.hpp goes into catch_amalgamated.hpp, since tests use it directly, and
Mock.cpp into catch_amalgamated.cpp. The test of the amalgamated files
now uses a mock, so a file left out fails it.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Mock.hpp goes into catch_amalgamated.hpp, since tests use it directly, and
Mock.cpp into catch_amalgamated.cpp. The test of the amalgamated files
now uses a mock, so a file left out fails it.
</pre>
</div>
</content>
</entry>
<entry>
<title>Answer the review of the mocks</title>
<updated>2026-11-01T17:05:56+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-11-01T17:05:56+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/catch3/commit/?id=8603d31fd8c1a72e30cb444f278d7a0592383ba7'/>
<id>8603d31fd8c1a72e30cb444f278d7a0592383ba7</id>
<content type='text'>
A report no longer throws, so a noexcept mock survives an aborting run.
A call that has nothing to return still leaves the test case, which ends
the process in a noexcept function; the documentation says so.

RunContext clears the result capture when it goes, so a mock of static
lifetime finds none and writes to standard error.

Decision owns its source line, since Verify can free the expectation
while a call is on its way. Moving a result out takes the lock. Vector
matchers refer to their vector, so they are rejected at compile time
like composed ones. A null text expectation accepts only null, and
integers compare by value, so 256 no longer accepts an unsigned char.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
A report no longer throws, so a noexcept mock survives an aborting run.
A call that has nothing to return still leaves the test case, which ends
the process in a noexcept function; the documentation says so.

RunContext clears the result capture when it goes, so a mock of static
lifetime finds none and writes to standard error.

Decision owns its source line, since Verify can free the expectation
while a call is on its way. Moving a result out takes the lock. Vector
matchers refer to their vector, so they are rejected at compile time
like composed ones. A null text expectation accepts only null, and
integers compare by value, so 256 no longer accepts an unsigned char.
</pre>
</div>
</content>
</entry>
<entry>
<title>Keep the mock lock alive to the end of the process</title>
<updated>2026-11-01T16:11:27+00:00</updated>
<author>
<name>Leitwolf11</name>
</author>
<published>2026-11-01T16:11:27+00:00</published>
<link rel='alternate' type='text/html' href='https://git.progmasoft.com/catch3/commit/?id=2aea9dafd283d69bec459ea0d495c25d1f6a0f94'/>
<id>2aea9dafd283d69bec459ea0d495c25d1f6a0f94</id>
<content type='text'>
A static mutex needs an exit-time destructor, which Clang's warnings
refuse, and a mock of static lifetime can outlive it. The test seam is
held the same way, and a test covers a mock method with nothing after
its parameters, which needs an empty variadic argument to stay valid
under -Wpedantic.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
A static mutex needs an exit-time destructor, which Clang's warnings
refuse, and a mock of static lifetime can outlive it. The test seam is
held the same way, and a test covers a mock method with nothing after
its parameters, which needs an empty variadic argument to stay valid
under -Wpedantic.
</pre>
</div>
</content>
</entry>
</feed>
