Skip to content

fix(test): render txt test report inline so format=txt no longer 404s on missing asset - #2676

Merged
bpamiri merged 3 commits into
developfrom
fix/bot-2675-txt-test-report-format-fails-assets-text-cfm-not-f
May 14, 2026
Merged

fix(test): render txt test report inline so format=txt no longer 404s on missing asset#2676
bpamiri merged 3 commits into
developfrom
fix/bot-2675-txt-test-report-format-fails-assets-text-cfm-not-f

Conversation

@wheels-bot

@wheels-bot wheels-bot Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Summary

vendor/wheels/wheelstest/system/reports/TextReporter.cfc did include "assets/text.cfm", but the assets/ directory was never carried over from upstream TestBox when the framework vendored its reporter CFCs. Selecting format=txt from /wheels/app/tests or /wheels/core/tests therefore threw Page [/wheels/wheelstest/system/reports/assets/text.cfm] not found while html / json / junit kept working.

This PR refactors TextReporter.runReport to render its plain-text output inline from the TestResult.bundleStats tree (mirroring the inline shape of vendor/wheels/public/tests/txt.cfm), keeping the existing getStatusIndicator / getBundleIndicator helpers and dropping the broken include. A small recursive $renderSuiteText helper handles nested suite stats.

Fixes #2675

Related Issue

Closes #2675

Type of Change

  • Bug fix
  • New feature
  • Enhancement to existing feature
  • Documentation update
  • Refactoring

Feature Completeness Checklist

  • DCO sign-off -- Commit carries Signed-off-by: trailer
  • Tests -- New vendor/wheels/tests/specs/wheelstest/TextReporterSpec.cfc reproduces the bug (failing → passing) and asserts the report is a non-empty plain-text string
  • Framework Docs -- N/A (handled by bot-update-docs.yml follow-up if needed)
  • AI Reference Docs -- N/A (handled by bot-update-docs.yml follow-up if needed)
  • CLAUDE.md -- N/A (handled by bot-update-docs.yml follow-up if needed)
  • CHANGELOG.md -- Entry added under [Unreleased] / Fixed
  • Test runner passes -- curl http://localhost:60007/wheels/core/tests?db=sqlite&format=json&directory=wheels.tests.specs.wheelstest returns {pass: 123, fail: 0, error: 0}; view/ regression check returns {pass: 513, fail: 0, error: 0}; format=txt end-to-end now returns HTTP 200 with a populated report

Test Plan

  • vendor/wheels/tests/specs/wheelstest/TextReporterSpec.cfc — direct unit test of TextReporter.runReport() with a fresh TestResult / TestBox (failed before the implementation, passes after)
  • Manual end-to-end: curl http://localhost:60007/wheels/core/tests?db=sqlite&format=txt&directory=wheels.tests.specs.wheelstest now returns HTTP 200 with the expected TEST RESULTS header, totals, and per-spec lines

Screenshots / Output

================================================================
TEST RESULTS - Lucee 7.0.1.100
================================================================
Duration: 97ms
Bundles:  8
Suites:   35
Specs:    123
Passed:   123
Failed:   0
Errored:  0
Skipped:  0
================================================================
…

… on missing asset

TextReporter.cfc included a vendored template at assets/text.cfm that was
never carried over from upstream TestBox. Hitting /wheels/app/tests or
/wheels/core/tests with format=txt therefore threw a missinginclude
exception, while format=html/json/junit kept working. The reporter now
builds its plain-text output inline from the TestResult bundleStats tree,
mirroring the shape of vendor/wheels/public/tests/txt.cfm and keeping
the existing getStatusIndicator/getBundleIndicator helpers.

Fixes #2675

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
@wheels-bot

wheels-bot Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — No doc updates

Reviewed this PR's diff and found no docs that need updating (purely a bug fix that restores format=txt to working order — no existing guide, .ai/wheels/testing/ reference, or CLAUDE.md section documents format=txt as a specific option; all examples use format=json, and the CHANGELOG entry already covers the fix).

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A

TL;DR: This PR correctly fixes the format=txt 404 by replacing the un-vendored include "assets/text.cfm" with equivalent inline rendering. The implementation is clean, uses the right struct key names, and has no cross-engine issues. One minor gap: the recursive $renderSuiteText helper is never exercised by the tests because both specs use an empty TestResult, leaving that path untested. Overall verdict: comment (no blocking issues).


Correctness

getCFMLEngine() / getCFMLEngineVersion() exist: Confirmed — TestResult.cfc declares them as CFML property entries (lines 38–39) with auto-generated getters, initialized in init() (lines 96–97). No missing-method risk.

globalException guard is correct: The check in TextReporter.cfc:

if ( isStruct( bundle.globalException ) && structKeyExists( bundle.globalException, "message" ) ) {

is sound. TestResult.cfc:215 initialises globalException as "" (empty string); BDDRunner.cfc:146 and UnitRunner.cfc:156 assign the caught exception struct on error. isStruct("") == false on all engines, so the guard fires correctly.

writeOutput() inside $renderSuiteText reaches the savecontent buffer: Standard CFML output-buffer semantics guarantee that writeOutput() from any synchronously-called CFC method writes to the active savecontent buffer in the parent scope. Correct pattern.


Tests

Recursive path is never executed — minor gap. Both specs instantiate a fresh new wheels.wheelstest.system.TestResult() which carries zero bundles. The outer for ( var bundle in variables.bundleStats ) loop in runReport is never entered, so $renderSuiteText is never called. A typo in key names (specStats, suiteStats) or a bug in the recursive call would not be caught.

vendor/wheels/tests/specs/wheelstest/TextReporterSpec.cfc — consider adding a third spec that seeds one bundle + one suite + one passing spec and asserts the output contains the bundle path and spec name. This is a suggestion rather than a blocker — the two existing specs do prove the regression (Page not found) is fixed.


Commits

fix(test): render txt test report inline so format=txt no longer 404s on missing asset

Scope test is accurate (TextReporter lives under wheelstest/), subject is under 100 chars, not ALL-CAPS. Passes commitlint.


Docs

CHANGELOG entry under [Unreleased] / Fixed is present and well-worded. No .ai/wheels/ doc update is needed for this internal reporter fix.

@wheels-bot

wheels-bot Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A's review is accurate and well-grounded. I verified every technical claim against the actual source and found no sycophancy, no false positives, and no missed issues. The test-coverage gap A flagged is the only substantive finding, and A correctly categorised it as non-blocking. Converging on approve.

Sycophancy

None detected. A gave a "comment" verdict (not "approve") specifically because of the untested recursive path, which is the right call for an incomplete-but-not-blocking concern.

False positives

None detected. I verified each claim against the source:

  • getCFMLEngine() / getCFMLEngineVersion() exist: confirmed — TestResult.cfc declares property name="CFMLEngine" and property name="CFMLEngineVersion" with accessors="true", producing auto-generated getters.
  • globalException initialised as "" at line 215: exact. TestResult.cfc:215 is "globalException" : "" inside startBundleStats. isStruct("") == false on all engines — the guard is sound.
  • writeOutput() inside $renderSuiteText reaches the savecontent buffer: correct. CFML's savecontent captures all output on the current thread, including output from synchronously-called CFC methods — this holds on Lucee, Adobe, and BoxLang.
  • Key name risk (specStats, suiteStats, failMessage): A raised this as motivation for fuller tests, not as a bug claim. In practice the keys are all correct (specStats at line 302, suiteStats at line 300, failMessage at line 368 in TestResult.cfc), but A's caution was reasonable since the tests don't exercise those paths.

Missed issues

None detected. The cross-engine surface is minimal (repeatString, arrayLen, arrayToList, isStruct, structKeyExists, chr, lCase, len — all portable). The bare $renderSuiteText(...) self-call resolves through the variables scope, which is standard across all three engines. No private access modifier issues (the function is public by default; $ prefix signals internal intent per Wheels convention).

Verdict alignment

A's "comment" verdict is consistent with the findings: one non-blocking suggestion, everything else verified correct. Appropriate.

Convergence

Aligned. A identified the only real gap (recursive path untested), correctly marked it as a suggestion rather than a blocker, and verified the rest of the implementation. I agree on both counts. The PR fixes a real regression with tests that prove it, and the code is correct for all paths including the untested recursive one. Recommending approve.

@bpamiri
bpamiri marked this pull request as ready for review May 14, 2026 22:37

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A

TL;DR: The PR fixes the format=txt 404 by replacing the un-vendored include "assets/text.cfm" with equivalent inline rendering inside TextReporter.runReport. Since the previous review (ddb1368) the only new commit is a merge of develop into the branch; the PR-specific code is unchanged. The fix is correct, the savecontent + writeOutput() pattern is portable across all engines, and the existing tests prove the regression is resolved. One minor gap carries forward from the last review: the recursive $renderSuiteText helper is never exercised by the specs. Overall verdict: comment (no blocking issues, no changes needed to merge).


Correctness

The savecontent + writeOutput() call-through pattern is sound on all three engines. During savecontent execution the page output buffer is redirected to the capture variable; any synchronously-invoked method that calls writeOutput() writes into that same buffer. Both the summary block and the per-bundle/suite recursive calls are therefore captured correctly.

The two guard conditions are correct:

  • isStruct( bundle.globalException ) && structKeyExists( bundle.globalException, "message" )TestResult.cfc initialises globalException as "" (empty string), making isStruct("") == false on all engines; the guard fires only when an exception struct has been assigned.
  • getCFMLEngine() / getCFMLEngineVersion() — both are property-declared auto-getters in TestResult.cfc, initialised in init(). No missing-method risk.

Tests

Recursive path still unexercised — minor gap carried from the prior review. Both specs instantiate a zero-bundle TestResult, so the for ( var bundle in variables.bundleStats ) loop never executes, and $renderSuiteText is never called. A typo in spec.failMessage, a wrong key name in suiteStats, or a regression in the recursive depth logic would not be caught.

vendor/wheels/tests/specs/wheelstest/TextReporterSpec.cfc — consider a third spec that seeds one bundle + one suite + one passing spec into a TestResult and asserts the output contains the bundle path and spec name. This is a suggestion, not a blocker; the two existing specs are sufficient to confirm the 404 regression is fixed.


Cross-engine

No cross-engine concerns. The implementation uses only core CFML builtins (repeatString, writeOutput, arrayLen, arrayToList, chr, lCase, len) and the savecontent tag — all of which behave identically on Lucee, Adobe CF, and BoxLang. The $renderSuiteText recursive call uses named arguments only, avoiding the mixed positional/named anti-pattern.


Commits

fix(test): render txt test report inline so format=txt no longer 404s on missing asset

Valid type (fix), valid scope (test), 86 chars, not ALL-CAPS. Passes commitlint. DCO Signed-off-by: trailer is present. No issues.


Docs

CHANGELOG [Unreleased] / Fixed entry is present and clearly describes both the symptom and the root cause. No .ai/wheels/ doc update is needed for an internal reporter fix.

@wheels-bot

wheels-bot Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A’s review accurately characterises a merge-only SHA where no PR-specific code changed. I spot-checked every technical claim against the actual source and found nothing to dispute. Converging on approve.

Sycophancy

None detected. A used the “comment” (non-binding) GitHub verdict rather than “approve”, which is the right choice when a minor suggestion remains outstanding. The overall tone matches the finding weight.

False positives

None detected. I verified each claim against the actual source:

  • getCFMLEngine() / getCFMLEngineVersion() — both are property-declared at TestResult.cfc lines 38–39, producing auto-generated getters.
  • globalException initialised as "" — confirmed at line 215; isStruct("") == false on all engines.
  • Key names suiteStats (line 300), specStats (line 302), failMessage (line 368) — all exist in TestResult.cfc.
  • tab(), getStatusIndicator(), getBundleIndicator() — all three defined in TextReporter.cfc itself (lines 105, 117, 132).
  • getLabels()property name="labels" type="array" at line 22; auto-getter present.
  • savecontent + writeOutput() cross-engine claim — savecontent redirects the page output buffer for the current thread; writeOutput() calls inside synchronously-invoked methods (including recursive $renderSuiteText calls) write into that same buffer. Standard behaviour on Lucee, Adobe CF, and BoxLang.

Missed issues

None detected. A correctly skipped re-analysis of the unchanged implementation (the merge commit carries no PR-specific diff). I confirmed the $renderSuiteText self-call via variables scope is sound on all three engines — the prior round’s B comment already verified this and A correctly defers to that analysis. Cross-engine surface (repeatString, arrayLen, arrayToList, chr, lCase, len, savecontent) is entirely portable. Commit and CHANGELOG are clean.

Verdict alignment

A’s “comment” verdict is consistent with the findings: one non-blocking suggestion (recursive path unexercised by specs), everything else verified correct.

Convergence

Aligned. A and B agree: no blocking issues, no changes required, the 404 regression is fixed with adequate tests. Recommending approve.

@bpamiri
bpamiri merged commit 855e9b7 into develop May 14, 2026
5 checks passed
@bpamiri
bpamiri deleted the fix/bot-2675-txt-test-report-format-fails-assets-text-cfm-not-f branch May 14, 2026 23:00

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A

TL;DR: This PR fixes the format=txt 404 by replacing the never-vendored include "assets/text.cfm" with equivalent inline rendering derived from the TestResult.bundleStats tree. The implementation is correct and portable across all engines. The only change since the previous review (SHA 9dfd953f9) is a develop merge that brought in an unrelated commit (dc08f8fe5 — lockingSpec advisory-lock guard); the PR-specific files are byte-identical. One minor gap carries forward: the recursive $renderSuiteText helper is never exercised by the specs. Verdict: comment (no blocking issues).


Correctness

All TestResult getters confirmed. vendor/wheels/wheelstest/system/TestResult.cfc declares every method called in runReport as a CFML property, generating auto-getters at compile time:

  • Lines 10-21: totalDuration, totalBundles, totalSuites, totalSpecs, totalPass, totalFail, totalError, totalSkipped
  • Line 22: labels (type="array") generates getLabels()
  • Lines 38-39: CFMLEngine, CFMLEngineVersion generate getCFMLEngine(), getCFMLEngineVersion()

No missing-method risk.

isStruct guard is sound (TextReporter.cfc:62). TestResult initialises globalException as an empty string; exception structs are only assigned by the runners on error. isStruct("") == false on all engines, so the guard fires correctly in both the happy path and the exception path.

writeOutput() inside $renderSuiteText is correctly captured by the parent's savecontent block (TextReporter.cfc:43-68). CFML redirects the page output buffer for the duration of the savecontent block; any synchronously-invoked CFC method's writeOutput() writes into that same buffer. The recursive calls at TextReporter.cfc:66 and TextReporter.cfc:96 are both synchronous, so capture is guaranteed.


Tests

Recursive path still unexercised — minor gap carried from prior reviews. Both specs in vendor/wheels/tests/specs/wheelstest/TextReporterSpec.cfc (lines 7-33) instantiate a zero-bundle TestResult, so the for ( var bundle in variables.bundleStats ) loop at TextReporter.cfc:59 never executes. A typo in spec.failMessage, a wrong key name in suiteStats, or a bug in the recursion depth arithmetic would not be caught.

Suggestion (not a blocker): add a third spec that seeds one bundle + one suite + one passing spec into a TestResult before calling runReport, then asserts the output contains the bundle path and spec name. The two existing specs are sufficient to prove the 404 regression is resolved.


Cross-engine

No concerns. The implementation uses only universal CFML builtins — repeatString, chr, lCase, len, arrayLen, arrayToList, isStruct, structKeyExists, writeOutput, savecontent, reReplace, trim — all of which behave identically on Lucee, Adobe CF, and BoxLang. Named arguments are used exclusively in the $renderSuiteText calls (TextReporter.cfc:66, TextReporter.cfc:96), avoiding the mixed positional/named anti-pattern.


Commits

fix(test): render txt test report inline so format=txt no longer 404s on missing asset

Valid type (fix), valid scope (test), 86 chars, not ALL-CAPS. DCO Signed-off-by: trailer is present. Passes commitlint.


Docs

CHANGELOG.md [Unreleased] / Fixed entry is present and correctly describes both the symptom (Page not found on assets/text.cfm) and the root cause (template never carried over from upstream TestBox). No .ai/wheels/ doc update is needed for an internal reporter fix.

@wheels-bot

wheels-bot Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A's review is accurate on this merge-only SHA. I spot-checked every claim against the actual source and found nothing to dispute. Converging on approve.

Sycophancy

None detected. A used the "comment" (non-binding) verdict rather than "approve" because the recursive path in $renderSuiteText remains unexercised by the specs — the appropriate call given that outstanding gap.

False positives

None detected. I verified each claim against the live file:

  • isStruct guard at line 62: isStruct( bundle.globalException ) && structKeyExists( bundle.globalException, "message" ) — exactly as described; isStruct("") == false on all engines.
  • Named-arg $renderSuiteText calls at lines 66 and 96: both confirmed. No mixed positional/named anti-pattern.
  • writeOutput() inside $renderSuiteText captured by the savecontent block: correct — savecontent redirects the page output buffer for the current thread; synchronous CFC method calls write into the same buffer on Lucee, Adobe CF, and BoxLang.
  • Cross-engine surface (repeatString, arrayLen, arrayToList, chr, lCase, len, savecontent) — all portable.

Missed issues

None detected. A correctly characterised the SHA as a merge-only commit with no PR-specific code changes. The $renderSuiteText bare self-call resolves through the variables scope, which is standard on all three engines. The test spec's direct instantiation of TestResult and TestBox is safe.

Verdict alignment

A's "comment" verdict is consistent with the one remaining gap (recursive path unexercised). No blocking issues identified.

Convergence

Aligned. A and B agree: the 404 regression is fixed with correct, engine-portable code and tests that prove the regression scenario. The untested recursive path is a non-blocking suggestion carried across all prior rounds. Recommending approve.

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.

TXT test report format fails: assets/text.cfm not found

1 participant