Skip to content

Test framework, stage 3: the small libraries' tests in Catch2 - #449

Merged
RyeMutt merged 7 commits into
developfrom
rye/tests-tm18
Oct 12, 2026
Merged

RyeMutt merged 7 commits into
developfrom
rye/tests-tm18

Conversation

@RyeMutt

@RyeMutt RyeMutt commented Oct 12, 2026

Copy link
Copy Markdown
Member

Description

The first wave of stage 3 of the test framework's move from TUT to Catch2 (TM18, "the small libraries"). 25 TUT files with 290 tests, from seven libraries, become eight Catch2 binaries. Each test file still runs in a process of its own, as each was an executable before.

Binary Files Notes
llxml_tests 3
llcharacter_tests 7 llbvhloader's paths to the shipped anim.ini and skeleton are defines of the whole binary
llinventory_tests 6
llfilesystem_tests 2
llprimitive_tests 4 llgltfmaterial_test.cpp stays the one file with tinygltf's and stb's implementations
llprimitive_test 1 alone: it stubs LLMaterialID, LLPrimTextureList and LLMessageSystem, so it compiles llprimitive.cpp in
llimageworker_test 1 alone: it stubs the image classes, so it compiles llimageworker.cpp in
lllogin_test 1 alone: its cases need an LLApp, and clear every event pump as they end

Commits

  1. 32ca1c6, parity's line numbers. mask_source() masked a line splice inside a string literal, newline and all, so tut_parity.py numbered every line after one too low. llmediaentry's LLSD macros span 93 lines, and parity said 18 CHECKs that had run never ran. Over all 379 TUT files only two mask differently with the fix, and both convert to the same bytes as before.

  2. 843599a, the state guard. A check is described as it is added. A library's checks come from its own listener at the start of the run, and Catch2 calls listeners in static-init order. Before this, a check added after the guard's snapshot blamed the first case of every run. The harness's tests now include such a listener; without the fix, four of their entries fail.

  3. 04caa02, al_add_test_binary.

    • UNIT <file>... compiles in the library files a stubbing test needs, as al_add_test's UNIT did.
    • A binary of one file gets no combined entry, which ran its file's cases a second time.
  4. 51b9e23, the conversion. tut2catch.py's output, with the case names the idiom pass chose given through --names. Two files carry hand edits:

    • lldir: <set>, which lltut.h used to bring, and the twelve ensure_equals calls in its Linux-only case, which the converter met only as text.
    • lllogin: one LLTestApp for the whole process, ended by the harness's teardown. An LLApp sets up LLCommon as it is made and tears it down as it ends, APR and LLTrace's master recorder included. Under TUT each test made one and ended it, freeing APR for whatever ran after; the state guard reported it.
  5. 3bbc58d, the idiom pass, as llmath had in Test framework, stage 2: Catch2, its harness and converter, and llmath in one binary #446:

    • every case named for what it checks (206 were #N);
    • messages that restated their assertion removed: 1,263 became 431, plus 44 CAPTUREs;
    • asserting helpers named require_*;
    • 113 REQUIREs that guarded nothing made CHECKs, and 7 CHECKs that guard an index after them made REQUIREs.

    No case and no assertion was added, removed or moved. Per case, each assertion compares what it compared before.

  6. fac126b, this wave's guard checks. These are OBJECT libraries any binary linking the library can take, watching:

    • llxml: the live settings groups;
    • llcharacter: the live characters, and the keyframe data cache.

    I broke a test for each on purpose, and each check named the case responsible.

Related Issues

  • Please link to a relevant GitHub issue for additional context.

Issue Link: none. This follows #446 (stage 2), on develop.


Checklist

  • I have provided a clear title and detailed description for this pull request.
  • If useful, I have included media such as screenshots and video to show off my changes.
  • I have tested the changes locally and verified they work as intended.
  • All new and existing tests pass.
  • Code follows the project's style guidelines.
  • Documentation has been updated if needed.
  • Any dependent changes have been merged and published in downstream modules.
  • I have reviewed the contributing guidelines.

Additional Notes

  • Tested on macOS arm64, RelWithDebInfo, on develop at a78b91f:
    • the full build is clean, and all 433 tests of ctest -LE manual pass;
    • all six pending combined entries pass;
    • each of the eight binaries passes 10 random orders and 20 repeats, with no state guard report;
    • no anonymous type is used with a name-keyed registry (LLSingleton, LLInstanceTracker and the like) in two files of one binary.
  • Parity. tut_parity.py holds all 25 files to the TUT binaries' recorded results:
    • at the conversion commit: 23 files as the converter wrote them, and lldir and lllogin under --recounted;
    • after the idiom pass: all 25 under --recounted.
  • Not compiled on macOS: lldir's four Linux-only cases and llmaterialid's three under !SHOW_ASSERT. The Linux cases were syntax-checked against an unguarded copy of lldir_linux.h. CI's Linux rows are the first to build and run them.
  • Old test bugs the idiom pass found are listed in 3bbc58d's message and left as they were, since a conversion keeps behaviour; a follow-up fixes them, as llmath's tests check what they say they check #448 did for llmath. For example:
    • llpermissions' allowCopyBy case calls allowModifyBy;
    • llgltfmaterial's "texture transforms only" material is given texture IDs.

🤖 Generated with Claude Code

RyeMutt and others added 6 commits October 12, 2026 02:23
…r several

mask_source() turned the whole inside of a string literal into
underscores, newlines too where the literal was continued with a
backslash. tut_parity.py counts lines in that masked text, so every
assertion after such a literal got a line number too low. It then said
CHECKs that had run in passing cases never ran: llmediaentry's three
LLSD macros span 93 lines, and parity reported 18 of its CHECKs that
way.

A line splice in a literal now stays as it is, backslash and newline, as
one in code does. Lines then count as in the text, and a #define whose
literal goes on over lines still reads as one directive. Over the
tree's 379 TUT files, only llmediaentry and llmessagetemplateparser mask
differently, and tut2catch.py converts both to the same bytes and the
same mapping as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A library's own guard checks will come from a listener of its own, which
adds them when the run starts. Catch2 calls listeners in the order they
registered, which across files is static-init order. Where the library's
listener ran after the guard's, the guard's snapshot had already been
taken. The new check had nothing to compare with, and the first case of
every run was blamed for changing it.

StateGuard::add now describes the check as it adds it, so the order of
the listeners no longer matters. The harness's own tests have such a
listener now, with a hidden case that changes its state. That entry
names its case, and the entries that expect a clean exit pass whichever
listener runs first. Without the change, four of them fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a one-file binary without a combined entry

UNIT names files of the library compiled into the binary rather than
linked, with the library's include directories, as al_add_test's UNIT
did. It is for a test that stubs what its file calls, and so cannot link
the library its stubs would duplicate: llprimitive_test, whose stubs
replace LLMaterialID, LLPrimTextureList and LLMessageSystem, and
llimageworker_test, whose stubs replace the image classes. Each is a
binary of its own.

A binary of one file had a combined entry that ran exactly what the
file's entry ran, a second time. It no longer gets one. That drops
test_support_tests' combined entry, and its COMBINED with it, and the
combined entries of llmath's one-file ISA tier binaries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h.py

TM18, the first wave of stage 3: 25 TUT files in seven libraries become
eight Catch2 binaries, each file still run in a process of its own:
- llxml_tests (3 files), llcharacter_tests (7), llinventory_tests (6),
  llfilesystem_tests (2) and llprimitive_tests (4), each linking its
  library;
- llprimitive_test, llimageworker_test and lllogin_test alone. The first
  two stub what their file calls and so compile it in (UNIT). lllogin's
  cases make an LLApp, and clear every event pump as they end.

llprimitive_tests' one tinygltf and stb implementation stays in
llgltfmaterial_test.cpp. llbvhloader's paths to the shipped anim.ini and
skeleton are defines of the whole of llcharacter_tests.

The files are tut2catch.py's output, with the case names the idiom pass
that follows chose given through --names. Two carry hand edits:
- lldir_test.cpp: <set>, which lltut.h used to bring, and the twelve
  ensure_equals() calls in its Linux-only case, which the converter met
  only as text and so marked. They compare LLDirXDG::Layout's
  std::string fields, so each is a CHECK(a == b).
- lllogin_test.cpp: one LLTestApp for the process, made by the first
  case and ended by the harness's teardown. An LLApp sets up LLCommon as
  it is made and tears it down as it ends, APR and LLTrace's master
  recorder included. Under TUT each test's fixture made one and ended
  it, and so freed APR for whatever ran after it. The state guard
  reported that against the first case.

tut_parity.py, against the TUT binaries' recorded results (290 tests,
all passing): every file holds, 23 as the converter wrote them and
lldir and lllogin under --recounted. lldir's four Linux-only cases and
llmaterialid's three under !SHOW_ASSERT are not compiled here, and are
listed. All 38 entries pass, and so do the five combined runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The idiom pass over TM18's 25 converted files, as llmath had in stage 2:
- every case named for what it checks (206 were #N, and some names
  taken from comments were cut off or carried commas);
- messages that only restated their assertion are gone: 1,263 became
  431, and 44 CAPTUREs say what string-building said before;
- asserting helpers are named for what they do now (require_*), with a
  scoped INFO or CAPTURE in place of a message built by hand;
- 113 REQUIREs that guarded nothing are CHECKs, and the 7 CHECKs that
  guard an index after them are REQUIREs;
- lldir's Linux-only cases, which the converter met only as text, are
  written as the rest are, and its empty fixture and nested anonymous
  namespaces are gone;
- lllogin loses skip macros nothing used, which still called TUT's skip.

No case was added, removed or moved, and no assertion: per case, each
assertion compares what it compared before, in the same order. Beyond a
CHECK becoming REQUIRE or the reverse, nine changed only in form:
literals moved right, string-building turned into streaming, and an
UNSCOPED_INFO and REQUIRE(false) became a FAIL.

tut_parity.py --recounted holds for all 25 files, and all 38 entries
pass. Bugs in the tests that the pass found are left for their own
change, since a conversion keeps behaviour:
- lljoint: after addChild, a check repeats an earlier one, where it
  seems to have meant lljoint's parent;
- llcharacter: a joint's parent, and a motion findMotion found, are
  dereferenced unchecked;
- llpose: a case's comment says a joint is left out of the motion's
  signature, which the case never checks;
- llmultigesture: the sound step's flags are never read back, and the
  first case checks only steps 0 and 2;
- llbvhloader: results are indexed with no count checked first, and two
  refusals share one message;
- llkeyframemotion: a U32 cursor is compared through LLSDEquals, as TUT's
  overload for a string message chose;
- alxmllayermerge: a check on an observer nothing installed cannot fail;
- llpermissions: the allowCopyBy case calls allowModifyBy, the allow*By
  cases pass only through the default everyone mask, and a getValue
  result is discarded;
- llcontrol: the listener flag is a static never reset;
- inventorymisc: a fromLLSD result is never checked; it and llsaleinfo
  each keep an empty placeholder case, and llsaleinfo builds a sale info
  it never uses;
- llsettingssky: one result is indexed with no size check;
- llgltfmaterial: the texture-transforms-only material is given texture
  ids, and one block sets the override material where it meant the
  render material;
- llprimitive: createPrimitive's result is never deleted;
- llimageworker: the worker's done flag is a plain bool polled without
  synchronisation, and written through a dangling reference after a
  timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… libraries keep for the process

Each wave adds its domain's guard checks. TM18's libraries keep three
things for the process that a case could leave behind for the next:
- llxml: the live settings groups, by name. Two may not share one, so
  a group a case leaves alive is one a later case cannot make.
- llcharacter: the live characters, every one of which an update walks;
  and the keyframe data cache, which llkeyframemotion's cases expect to
  find empty.

Each library's checks are an OBJECT library of their own, whose listener
adds them as the run starts, for every binary that links the library:
llxml_tests and llcharacter_tests now, and llui's and newview's later.
llinventory, llfilesystem, llprimitive and llimage keep nothing a case
could leave changed; lllogin's event pumps are llcommon's, and come with
its wave.

Both binaries pass 10 random orders and 20 repeats with no report. With
llkeyframemotion's fixture not clearing the cache, the guard names each
case that left data in it; with a settings group leaked, the case that
leaked it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 3 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1a9d956e-1475-441e-8e43-b736206817c0

📥 Commits

Reviewing files that changed from the base of the PR and between a78b91f and 4ddc9d0.


📒 Files selected for processing (41)
  • indra/cmake/ALAddTestBinary.cmake
  • indra/llcharacter/CMakeLists.txt
  • indra/llcharacter/tests/llbvhloader_test.cpp
  • indra/llcharacter/tests/llcharacter_test.cpp
  • indra/llcharacter/tests/llcharacter_test_checks.cpp
  • indra/llcharacter/tests/lljoint_test.cpp
  • indra/llcharacter/tests/llkeyframemotion_test.cpp
  • indra/llcharacter/tests/llmotioncontroller_test.cpp
  • indra/llcharacter/tests/llmultigesture_test.cpp
  • indra/llcharacter/tests/llpose_test.cpp
  • indra/llfilesystem/CMakeLists.txt
  • indra/llfilesystem/tests/lldir_test.cpp
  • indra/llfilesystem/tests/lldiriterator_test.cpp
  • indra/llimage/CMakeLists.txt
  • indra/llimage/tests/llimageworker_test.cpp
  • indra/llinventory/CMakeLists.txt
  • indra/llinventory/tests/inventorymisc_test.cpp
  • indra/llinventory/tests/llparcel_test.cpp
  • indra/llinventory/tests/llpermissions_test.cpp
  • indra/llinventory/tests/llsaleinfo_test.cpp
  • indra/llinventory/tests/llsettingssky_test.cpp
  • indra/llinventory/tests/lluserrelations_test.cpp
  • indra/llprimitive/CMakeLists.txt
  • indra/llprimitive/tests/llgltfmaterial_test.cpp
  • indra/llprimitive/tests/llmaterial_test.cpp
  • indra/llprimitive/tests/llmaterialid_test.cpp
  • indra/llprimitive/tests/llmediaentry_test.cpp
  • indra/llprimitive/tests/llprimitive_test.cpp
  • indra/llxml/CMakeLists.txt
  • indra/llxml/tests/alxmllayermerge_test.cpp
  • indra/llxml/tests/llcontrol_test.cpp
  • indra/llxml/tests/llxml_test_checks.cpp
  • indra/llxml/tests/llxmlnode_test.cpp
  • indra/test/CMakeLists.txt
  • indra/test/altestmatchers.cpp
  • indra/test/alteststateguard.cpp
  • indra/test/alteststateguard.h
  • indra/test/tests/altestsupport_test.cpp
  • indra/viewer_components/login/CMakeLists.txt
  • indra/viewer_components/login/tests/lllogin_test.cpp
  • scripts/testing/tut2catch.py

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The optimised Windows builds compile with /fp:fast, under which MSVC
may compile == without its test for an unordered result. A NaN then
compares equal to any number. LLSDEquals compared reals and dates' seconds
with ==, so on Windows x64 a NaN date matched LLDate(0.0), and
test_support_tests failed there, as it has on develop since #446. clang
and GCC keep IEEE comparisons under the flags the tree gives them, so no
other row saw it.

Reals and dates now go through a compare that asks about NaN apart, with
std::isnan, which classifies the bits and so holds under /fp:fast. The
harness's tests check a NaN real as well as a NaN date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RyeMutt
RyeMutt merged commit a0682b5 into develop Oct 12, 2026
15 checks passed
@RyeMutt
RyeMutt deleted the rye/tests-tm18 branch October 12, 2026 10:43
RyeMutt added a commit that referenced this pull request Oct 12, 2026
Two pull requests merged into develop on the same morning. #451 added
a TUT test<13> to llgltfmaterial_test.cpp, which #449 had converted to
Catch2, so llprimitive's tests no longer compiled anywhere:
llgltfmaterial_object_t no longer exists.

Its two parts are now two TEST_CASEs, as the rest of the file is
written. Each finding of a tracked texture is a REQUIRE before the
entry is read; the rest are CHECKs. What they check is unchanged: the
override's local texture tracked beside the material's own, both
found by updateTextureTracking and covered by the digest, and the
override's world id winning where both track the same texture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant