Skip to content

Test framework, stage 3: llrender's tests in Catch2 - #468

Merged
RyeMutt merged 4 commits into
developfrom
rye/tests-tm22
Oct 13, 2026
Merged

RyeMutt merged 4 commits into
developfrom
rye/tests-tm22

Conversation

@RyeMutt

@RyeMutt RyeMutt commented Oct 13, 2026

Copy link
Copy Markdown
Member

Description

The fifth wave of stage 3 of the test framework's move from TUT to Catch2 (TM22): llrender's tests. 26 TUT files with 392 tests become three Catch2 binaries, cut where the GL context's lifetime differs. Each test file still runs in a process of its own.

Binary Files What they share
llrender_tests 7 no GL: the UI transform, the matrix stack and projection helpers, the font tables and shaping
llrender_gl_fresh_tests 6 each case makes a context and ends the GL classes with it: the font libraries' rasterizing paths, and llimagegl, which also re-creates its context mid-case
llrender_gl_shared_tests 13 one context for the process, through sharedHeadlessGL() and SharedGLScope

A fresh case ends the classes the shared context needs, so the two kinds never share a binary.

The conversion:

  • 6e4f183, the conversion: tut2catch.py's output, with the names the idiom pass chose, given through --names. It marked nothing for review, and nothing was edited by hand.

  • bb3f752, the idiom pass, by seven agents:

    • Names: 388 of 392 changed. Most were the comment's first sentence above the test, cut off mid-phrase; 85 had commas, which a Catch2 test spec reads as "or".
    • Messages: 2,040 to 576, with 66 CAPTUREs. Most removed ones restated their check.
    • Helpers: 12 asserting helpers are named require_*.
    • Strength: 105 CHECKs whose object the next lines use (a program, texture, target or size) are REQUIREs, and 128 REQUIREs that guarded nothing are CHECKs.

    No case and no assertion was added, removed or moved.

  • 4ca70e1, a production fix: LLFontGL::initClass's explicit-path overload.

    • The bug: called again once fonts existed, it warned that it was ignoring the call, but it had already set the DPI, scale and application directory.
    • Impact: only tests call that overload, so the viewer was unaffected. But llfontgl's "a second initClass" case left 72 DPI and half scale for whatever ran next. In one process with llfonttextcache, an ellipsized 40-pixel render then drew nothing before the dots, in 3 of 10 random orders.
    • Test: a new llfontgl case fails without the fix.
  • 5b26ba4, guard checks for this wave, and the shared context's end.

    • Checks after every case, in all three binaries:
      • the current GL context;
      • gGL's matrix stack depths, through ll_test::RenderProbe, which LLRender friends as LLUIImage and LLFontTextCache friend theirs;
      • the bound shader, reverse-Z and compute, and the viewport;
      • unread GL errors;
      • GL images outliving their context;
      • the font manager;
      • LLFontGL's DPI and scale, and its UI transform.
    • The shared context and its white texture end in the main's teardown, after the last case, not at static destruction. The guard takes what making the context changes as its baseline. llui's and newview's TUT GL tests include the same fixture header and keep their statics as they were.
    • Tested: I broke a case for each check 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 #462 (TM21).


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, a GL 4.1 core context (no compute, no clip control):
    • the full build is clean;
    • all 459 tests of ctest -LE manual pass, llui's and newview's TUT GL tests on the changed fixture header among them, and so do all 15 pending combined runs, llrender's three among them;
    • each binary, run whole, passes 10 random orders and 20 repeats with no state guard report;
    • no anonymous type is used with a name-keyed registry in two files of one binary.
    • clang's unused warnings show only two in-class defaulted destructors, which GCC does not flag, and alfontshaping's a_addr, as in TUT; that goes in the follow-up.
  • Per-case mode: with AL_TEST_PER_CASE, the three binaries give 187, 72 and 133 case entries. All pass.
  • Parity: tut_parity.py holds every file to the TUT executables' recorded results: 392 tests, 362 ok and 30 skipped here.
    • At the conversion commit: it holds plain for all 26 files.
    • Never-run sites: 7 assertion sites were reviewed as never running in a passing case on this host:
      • alfontshaping's two-glyph branch and its fail-only else;
      • algpubuffer's GPU clear of a static buffer;
      • three of llglslshader's checks, which run only with compute.
    • After the idiom pass: it holds under --recounted, every case's count unchanged.
  • Much of this wave never runs on a Mac. alimageslot's five cases and half of llglslshader's skip without compute, and five of alinfiniteprojection's skip without clip control. CI's Linux and Windows rows run them; their idiom edits were syntax-checked only here.
  • The plan's watch item: altessellation's Apple driver regression case passes in the shared binary, alone and in every order.
  • Old test bugs the idiom pass found are listed in bb3f752's message and left as they were, for a follow-up like Test framework: fix the old test bugs in alscript's and lscript's tests #463.

🤖 Generated with Claude Code

RyeMutt and others added 4 commits October 13, 2026 07:21
TM22, the fifth wave of stage 3: llrender's 26 TUT files, 392 tests,
become three Catch2 binaries, each file still run in a process of its
own, cut where the GL context's lifetime differs:
- llrender_tests, 7 files that need no GL: the UI transform, the matrix
  stack and projection helpers, and the font tables and shaping. It
  links ICU for alfontshaping, which holds HarfBuzz's tables against it;
- llrender_gl_fresh_tests, 6 files whose cases each make a context and
  end the GL classes with it: the rasterizing paths of the font
  libraries, and llimagegl, which also makes its context again in the
  middle of a case;
- llrender_gl_shared_tests, 13 files whose cases share one context for
  the process through sharedHeadlessGL() and SharedGLScope.
A fresh case ends the classes a shared context needs, so the two kinds
never share a binary.

The files are tut2catch.py's output, with the names the idiom pass that
follows chose. It marked nothing TM-CHECK, and nothing was edited by
hand.

tut_parity.py, against the TUT executables' recorded results (392 tests:
362 ok and 30 skipped here, on a GL 4.1 context with no compute and no
clip control), holds plain for all 26 files, with seven assertion sites
reviewed as never running in a passing case on this host:
- alfontshaping's two-glyph branch and its fail-only else, where this
  font composes the combining pair into one glyph;
- algpubuffer's clear of a static buffer, only where it clears on the
  GPU;
- three of llglslshader's checks, only where the context runs compute.
All 32 entries pass, and so do the three combined runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The idiom pass over TM22's conversion, by seven agents on copies, merged
back onto it. Names, messages and assertion strength only: every case and
every assertion is where the conversion put it.
- Names: 388 of 392 changed. Most were the first sentence of the comment
  above the test, cut off mid-phrase, and 85 had commas, which a Catch2
  test spec reads as "or".
- Messages: 2,040 to 576, with 66 CAPTUREs. Most of those removed
  restated their check ("no GL error" before a check for none, "font
  resolves" before a null check); most that remain say which pass,
  pixel or driver case a failure is in.
- 12 asserting helpers are named require_*: require_ints, require_near,
  require_matrix_close, require_program and the like.
- 105 CHECKs are REQUIREs: a program built, a texture or target made,
  or a size read, that the next lines use, where going on would draw
  with a failed GL object or end the process in an llassert. 128
  REQUIREs that guarded nothing are CHECKs.
- One FAIL's message, built from six numbers, is a CAPTURE of them.

tut_parity.py holds all 26 files under --recounted, every case's count
unchanged, with the conversion's seven sites reviewed as never running
here.

Old test bugs the agents found, left as they were:
- checks that cannot fail: CHECK(true) in llfonttextcache, llfontfreetype
  and llfontregistry; checks after an if (!x) SKIP of the same x in
  alfontshaping, alfontcolrv1 and llfontfreetype; isValid() on a face
  getOrCreateFace returned, which is never invalid; llimagegl's mip
  width, which glTexStorage2D gives every level, and a binding read
  just after the test bound it; alfontcolrv1's force-mono check, which
  tests the test's own ternary;
- reads with no guard, where a failure would crash rather than fail:
  getFontFreetype(), getFontFace(), templateFor(), getBitmapCache() and
  loaded faces dereferenced in llfontgl, llfontregistry, llfontfreetype,
  alfontshaping and alfontcolrv1; getFontFiles()[0] and mPhaseSlots[0]
  unsized; llimagegl's texel reader unbounded; llglslshader's uniform
  locations, storage binding points and texture units never checked
  for -1, so a missing uniform reads zero and every zero expected
  passes;
- checks that test something else or less than they say: alfontshaping
  compares a codepoint count with a byte count; llfontbitmapcache reads
  the generation before init(), whose reset bumps it; llfontfreetype's
  address compare cannot tell a cache hit from a reused block; comments
  promise checks that are not there (alfontcolrv1, alfontshaping,
  llfontregistry, llfonttextcache, llfontfreetype, lluitransform's Y
  scale);
- state a failure leaks: alfontcolrv1_gl's sForceMonochromeEmoji when a
  REQUIRE fails before it is put back; alinfiniteprojection's queries
  and alfontshaping's mm;
- leftovers: alfontshaping's a_addr, set and never read; a case
  llfonttextcache has twice; alimageslot's fixture comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The overload that reads fonts.xml from a path, which only the tests call,
is single-shot: with a registry already made it warns and returns. But it
set the DPI, the scale and the application directory first, so a second
call with other values left the fonts already made at one DPI and every
later layout and measurement reading another. It now returns before
setting anything.

llfontgl's "a second initClass leaves the fonts already resolved in
place" calls it again at 72 DPI and half scale, and left those for
whatever ran next. In its own file that was the next case, whose own
call at 96 put them back. In one process with llfonttextcache, an
ellipsized render 40 pixels wide could follow it instead, and drew no
character before the dots, in three of ten random orders. A new llfontgl
case holds the DPI and scale to what they were, and fails without the
change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…shared GL context ends with the main

llrender_test_checks, a listener in each of llrender's three binaries,
gives the state guard its checks after every case:
- the GL context current on the test's thread: a case that makes one
  ends it, and one on the shared context leaves that one current;
- how deep each of gGL's matrix stacks is, and which one its calls go
  to: a push left behind stays for every later case, and the stacks are
  32 deep. LLRender keeps its stacks private, so it friends
  ll_test::RenderProbe, as lluiimage.h and llfonttextcache.h friend
  theirs;
- the bound shader, reverse-Z and compute, and the window's viewport;
- GL errors left unread, which the next case's own check for none would
  find. Reading them clears them, so once it has reported, the guard
  reads what is left, and the case after is not blamed for clearing
  them;
- GL images alive with no context current: in the fresh binary, an image
  that outlives its case holds a name in a context that is gone. While
  the shared context lives, the font atlas pages rasterized into it
  outlive the cases that filled them, by design;
- the font manager, LLFontGL's DPI and scale, which a later case's text
  is laid out and measured at, and its UI origin, depth, scale and
  pushed transforms.
Each binary takes llcommon's checks too. I broke a case each way, and
each check named it. The DPI and scale check names llfontgl's second
initClass case without the change before this one.

In a Catch2 binary, sharedHeadlessGL() and installWhiteTexture() hand
their ends to the main's teardown, which runs after the last case while
APR and LLError are still up, not to static destruction; the white
texture goes first, before the context it was made in. Making the shared
context changes the context, the bound program, the pushed matrices and
whatever the driver chooses for the process, so the guard takes what it
leaves as the state to compare with, rather than blaming the case that
first asked. The fixture header is llui's and newview's TUT tests' too,
so they keep the function-local statics they had.

Each binary, run whole, passes 10 random orders and 20 repeats with no
guard report.

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

coderabbitai Bot commented Oct 13, 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 52 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: 57e06dd8-7737-40ed-9067-f221c9880e33
📥 Commits

Reviewing files that changed from the base of the PR and between 12ce979 and 5b26ba4.

📒 Files selected for processing (31)
  • indra/llrender/CMakeLists.txt
  • indra/llrender/llfontgl.cpp
  • indra/llrender/llrender.h
  • indra/llrender/tests/alfontcolrv1_gl_test.cpp
  • indra/llrender/tests/alfontcolrv1_test.cpp
  • indra/llrender/tests/alfontshaping_gl_test.cpp
  • indra/llrender/tests/alfontshaping_test.cpp
  • indra/llrender/tests/algpubuffer_gl_test.cpp
  • indra/llrender/tests/alimageslot_gl_test.cpp
  • indra/llrender/tests/alinfiniteprojection_gl_test.cpp
  • indra/llrender/tests/alinstanceddraw_gl_test.cpp
  • indra/llrender/tests/alrenderstate_gl_test.cpp
  • indra/llrender/tests/altessellation_gl_test.cpp
  • indra/llrender/tests/alwindowsdlheadless_test.cpp
  • indra/llrender/tests/llfontbitmapcache_gl_test.cpp
  • indra/llrender/tests/llfontbitmapcache_test.cpp
  • indra/llrender/tests/llfontfreetype_gl_test.cpp
  • indra/llrender/tests/llfontfreetype_test.cpp
  • indra/llrender/tests/llfontgl_test.cpp
  • indra/llrender/tests/llfontregistry_gl_test.cpp
  • indra/llrender/tests/llfontregistry_test.cpp
  • indra/llrender/tests/llfonttextcache_test.cpp
  • indra/llrender/tests/llglslshader_gl_test.cpp
  • indra/llrender/tests/llheadlessgl_fixture.h
  • indra/llrender/tests/llimagegl_test.cpp
  • indra/llrender/tests/llrender2dutils_gl_test.cpp
  • indra/llrender/tests/llrender_test_checks.cpp
  • indra/llrender/tests/llrendermatrix_test.cpp
  • indra/llrender/tests/llrendertarget_gl_test.cpp
  • indra/llrender/tests/lluiimage_test.cpp
  • indra/llrender/tests/lluitransform_test.cpp
  • 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.

@RyeMutt
RyeMutt added this pull request to stack #470 October 13, 2026 18:14
@RyeMutt
RyeMutt merged commit 9649a04 into develop Oct 13, 2026
21 checks passed
@RyeMutt
RyeMutt deleted the rye/tests-tm22 branch October 13, 2026 20:20
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