Browse Source
parse_testrun previously required a failing test's opening banner '==== <name> <description> FAILED' to be one physical line: a description with embedded newlines spans lines (single tcltest puts, ends-only trim), the opener never matched, the closing banner was misread as an opener and the parser wedged in failure-capture state - every later event including the summary line was swallowed and the file degraded to warn/missing-cleanupTests with no failure detail. Now (G-161, settled on start-event anchoring + bounded banner buffering): - '---- <name> start' lines (-verbose start - runtests.tcl always passes it) track the current test name. - A line '==== <name> ...' for the current test that does not end ' FAILED' opens a banner-continuation stage (exact prefix compare, no glob), buffered until the ' FAILED'-terminated line completes the banner; recognisable event lines (took/PASSED/SKIPPED/start/summary) or a 100-line bound abandon capture and reprocess the line normally, and a stray '==== Contents of test case:' completes the banner instead of wedging. - Streams without start events, and every single-line parse path (including the emulated-banner confounder heuristics), behave exactly as before - pinned by the new characterization suite. Also fixes result_expected capture (G-161 acceptance surface): the '---- Result should have been' opener always arrives while the result_was capture is active, and the active-capture reset branch fell through past the opener elseif - result_expected was absent from every FAILED report (verified in the current full-suite baselines) and the expected-value lines leaked into test_body. The active branch now transitions result_was -> result_expected directly. New synthetic characterization suite src/tests/runner/testsuites/parser/parsetestrun.test (6 tests: multi-line fidelity incl summary flow, single-line regression shape, multi-line ERROR capture, anchored false-open abandonment, historic confounder pin, no-start-events fallback pin) green on tclsh90 (9.0.3) and native tclsh 8.6. Streams mirror real tcltest emission (verified against a live tcltest 2.5 capture). E2E fixture suite + bootsupport promotion follow in this arc. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.commaster
3 changed files with 315 additions and 3 deletions
@ -0,0 +1,216 @@
|
||||
# -*- tcl -*- |
||||
# Synthetic captured-output suite for punk::tcltestrun::parse_testrun (G-161): |
||||
# multi-line opening-banner tolerance. tcltest prints a failing test's opening |
||||
# banner as ONE puts of "==== <name> <description> FAILED" and trims only the |
||||
# description's ENDS, so a description with embedded newlines makes the banner |
||||
# span physical lines. parse_testrun 0.4.0+ recognises the spanned banner by |
||||
# anchoring on the "---- <name> start" event (-verbose start, which the runner |
||||
# always passes) and buffering continuation lines until the " FAILED"-ending |
||||
# terminator - reporting such failures with full fidelity (name, description, |
||||
# result_was/result_expected) while every later event including the summary |
||||
# line keeps flowing. Streams WITHOUT start events retain the historic |
||||
# single-line-banner-only behaviour, as do all single-line parse paths |
||||
# (confounding-output heuristics unchanged - pinned here). |
||||
# |
||||
# Streams below mirror real tcltest emission shapes (tcltest.tcl failure |
||||
# block): blank separator, the -verbose line "error: test failed:" line (which |
||||
# also spans lines for a multi-line description), the opening banner, |
||||
# "==== Contents of test case:", body, "---- Result was:" / |
||||
# "---- Result should have been (exact matching):" blocks, the closing |
||||
# "==== <name> FAILED" line, and the tab-separated cleanupTests summary line. |
||||
# |
||||
# Run: tclsh src/tests/runtests.tcl -report compact -show-passes 0 -include-paths runner/testsuites/parser parsetestrun.test |
||||
|
||||
package require tcltest |
||||
package require punk::tcltestrun 0.4.0- |
||||
package require punk::ansi ;#parse_testrun's stderr branch uses punk::ansi::ansistring |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
|
||||
variable took_usec "took 42 \u00b5s" ;#microseconds unit as emitted by tcltest -verbose usec |
||||
|
||||
proc parsed {stdout_lines} { |
||||
#run a fabricated stdout stream (list of lines) through parse_testrun the |
||||
#way runtests.tcl does (stderr/exitcode keys present) |
||||
set stream [join $stdout_lines \n] |
||||
return [punk::tcltestrun::parse_testrun [dict create stdout $stream stderr "" exitcode 0] synthfixture] |
||||
} |
||||
|
||||
proc summary_facts {r} { |
||||
#compact comparable extract of the summary + event-set fields |
||||
set facts [list] |
||||
lappend facts detected [dict get $r summaryline_detected] |
||||
lappend facts totals [dict get $r summaryline_total]/[dict get $r summaryline_passed]/[dict get $r summaryline_skipped]/[dict get $r summaryline_failed] |
||||
lappend facts fails [dict size [dict get $r testcase_fails]] |
||||
lappend facts passes [dict size [dict get $r testcase_passes]] |
||||
lappend facts skips [dict size [dict get $r testcase_constraintskips]] |
||||
return $facts |
||||
} |
||||
|
||||
#added 2026-08-03 (agent, G-161) |
||||
test parsetestrun-multiline-1.0 {multi-line-description failure parses with full fidelity and later events plus summary keep flowing} -body { |
||||
set desc [join {{first descriptive line} {second descriptive line} {third descriptive line}} \n] |
||||
variable took_usec |
||||
set lines [list] |
||||
lappend lines "---- t-multi-1.0 start" |
||||
lappend lines "" "" |
||||
lappend lines "C:/fab/synth.test:5: error: test failed: t-multi-1.0 first descriptive line" |
||||
lappend lines "second descriptive line" |
||||
lappend lines "third descriptive line" |
||||
lappend lines "==== t-multi-1.0 first descriptive line" |
||||
lappend lines "second descriptive line" |
||||
lappend lines "third descriptive line FAILED" |
||||
lappend lines "==== Contents of test case:" |
||||
lappend lines "" |
||||
lappend lines " set x got" |
||||
lappend lines " set x" |
||||
lappend lines "" |
||||
lappend lines "---- Result was:" |
||||
lappend lines "got" |
||||
lappend lines "---- Result should have been (exact matching):" |
||||
lappend lines "want" |
||||
lappend lines "==== t-multi-1.0 FAILED" |
||||
lappend lines "" |
||||
lappend lines "---- t-pass-1.1 start" |
||||
lappend lines "++++ t-pass-1.1 $took_usec" |
||||
lappend lines "++++ t-pass-1.1 PASSED" |
||||
lappend lines "++++ t-skip-1.2 SKIPPED: knownBug" |
||||
lappend lines "synth.test:\tTotal\t3\tPassed\t1\tSkipped\t1\tFailed\t1" |
||||
set r [parsed $lines] |
||||
set facts [summary_facts $r] |
||||
set f [dict get $r testcase_fails t-multi-1.0] |
||||
lappend facts status [dict get $f test_status] |
||||
lappend facts desc_ok [string equal [dict get $f test_description] $desc] |
||||
lappend facts was_ok [string equal [dict get $f result_was] "got\n"] |
||||
lappend facts expected_ok [string equal [dict get $f result_expected] "want\n"] |
||||
lappend facts body_ok [string match "*set x got*" [dict get $f test_body]] |
||||
#the expected-value lines must not leak into test_body (the pre-G-161 |
||||
#result_expected capture defect routed them there) |
||||
lappend facts body_noleak [expr {![string match "*want*" [dict get $f test_body]]}] |
||||
lappend facts pass_usec [dict get $r testcase_passes t-pass-1.1 microseconds] |
||||
lappend facts skip_reason [dict get $r testcase_constraintskips t-skip-1.2 reason] |
||||
set facts |
||||
} -result {detected 1 totals 3/1/1/1 fails 1 passes 1 skips 1 status FAILED desc_ok 1 was_ok 1 expected_ok 1 body_ok 1 body_noleak 1 pass_usec 42 skip_reason knownBug} |
||||
|
||||
#added 2026-08-03 (agent, G-161) |
||||
test parsetestrun-singleline-2.0 {single-line-description failure keeps its existing report shape} -body { |
||||
variable took_usec |
||||
set lines [list] |
||||
lappend lines "---- t-single-2.0 start" |
||||
lappend lines "" "" |
||||
lappend lines "==== t-single-2.0 one plain single-line description FAILED" |
||||
lappend lines "==== Contents of test case:" |
||||
lappend lines "" |
||||
lappend lines " set y actual" |
||||
lappend lines "" |
||||
lappend lines "---- Result was:" |
||||
lappend lines "actual" |
||||
lappend lines "---- Result should have been (exact matching):" |
||||
lappend lines "wanted" |
||||
lappend lines "==== t-single-2.0 FAILED" |
||||
lappend lines "---- t-pass-2.1 start" |
||||
lappend lines "++++ t-pass-2.1 $took_usec" |
||||
lappend lines "++++ t-pass-2.1 PASSED" |
||||
lappend lines "synth.test:\tTotal\t2\tPassed\t1\tSkipped\t0\tFailed\t1" |
||||
set r [parsed $lines] |
||||
set facts [summary_facts $r] |
||||
set f [dict get $r testcase_fails t-single-2.0] |
||||
lappend facts status [dict get $f test_status] |
||||
lappend facts desc [dict get $f test_description] |
||||
lappend facts was_ok [string equal [dict get $f result_was] "actual\n"] |
||||
lappend facts expected_ok [string equal [dict get $f result_expected] "wanted\n"] |
||||
set facts |
||||
} -result {detected 1 totals 2/1/0/1 fails 1 passes 1 skips 0 status FAILED desc {one plain single-line description} was_ok 1 expected_ok 1} |
||||
|
||||
#added 2026-08-03 (agent, G-161) |
||||
test parsetestrun-multiline-error-3.0 {multi-line-description ERROR-status failure captures returncode and errorInfo} -body { |
||||
set lines [list] |
||||
lappend lines "---- t-err-3.0 start" |
||||
lappend lines "" "" |
||||
lappend lines "==== t-err-3.0 erroring test whose description" |
||||
lappend lines "spans two lines FAILED" |
||||
lappend lines "==== Contents of test case:" |
||||
lappend lines "" |
||||
lappend lines " error boom" |
||||
lappend lines "" |
||||
lappend lines "---- Test generated error; Return code was: 1" |
||||
lappend lines "---- Return code should have been one of: 0 2" |
||||
lappend lines "---- errorInfo: boom" |
||||
lappend lines " while executing" |
||||
lappend lines "\"error boom\"" |
||||
lappend lines "---- errorCode: NONE" |
||||
lappend lines "==== t-err-3.0 FAILED" |
||||
lappend lines "synth.test:\tTotal\t1\tPassed\t0\tSkipped\t0\tFailed\t1" |
||||
set r [parsed $lines] |
||||
set facts [summary_facts $r] |
||||
set f [dict get $r testcase_fails t-err-3.0] |
||||
lappend facts status [dict get $f test_status] |
||||
lappend facts desc_ok [string equal [dict get $f test_description] "erroring test whose description\nspans two lines"] |
||||
#errorcode/errorinfo carry a historic leading space ("---- errorCode: " is |
||||
#16 chars, the capture ranges from index 15) - tolerated, not pinned |
||||
lappend facts returncode [dict get $f returncode] |
||||
lappend facts errorcode [string trim [dict get $f errorcode]] |
||||
lappend facts errorinfo_ok [string match "*boom*while executing*" [dict get $f errorinfo]] |
||||
set facts |
||||
} -result {detected 1 totals 1/0/0/1 fails 1 passes 0 skips 0 status ERROR desc_ok 1 returncode 1 errorcode NONE errorinfo_ok 1} |
||||
|
||||
#added 2026-08-03 (agent, G-161) - a test's own output emitting an anchored |
||||
#"==== <currentname> ..." line (no FAILED terminator) must not swallow the |
||||
#following events: banner capture abandons at the next recognisable event |
||||
#line and reprocesses it normally |
||||
test parsetestrun-falseopen-4.0 {anchored banner-like output line for a passing test abandons capture at the next event line} -body { |
||||
variable took_usec |
||||
set lines [list] |
||||
lappend lines "---- t-conf-4.0 start" |
||||
lappend lines "==== t-conf-4.0 emulated banner-ish output from the test body" |
||||
lappend lines "more ordinary output" |
||||
lappend lines "++++ t-conf-4.0 $took_usec" |
||||
lappend lines "++++ t-conf-4.0 PASSED" |
||||
lappend lines "synth.test:\tTotal\t1\tPassed\t1\tSkipped\t0\tFailed\t0" |
||||
set r [parsed $lines] |
||||
set facts [summary_facts $r] |
||||
lappend facts pass_usec [dict get $r testcase_passes t-conf-4.0 microseconds] |
||||
set facts |
||||
} -result {detected 1 totals 1/1/0/0 fails 0 passes 1 skips 0 pass_usec 42} |
||||
|
||||
#added 2026-08-03 (agent, G-161) - characterization of the PRE-EXISTING |
||||
#single-line confounder cost, unchanged by G-161: an emulated single-line |
||||
#banner in ordinary output still opens failure-capture state and swallows |
||||
#subsequent events (historic heuristic behaviour preserved) |
||||
test parsetestrun-confounder-5.0 {emulated single-line banner in output still opens failure capture exactly as before} -body { |
||||
set lines [list] |
||||
lappend lines "---- t-conf-5.0 start" |
||||
lappend lines "==== bogus-test emulated single-line banner FAILED" |
||||
lappend lines "++++ t-conf-5.0 PASSED" |
||||
lappend lines "synth.test:\tTotal\t1\tPassed\t1\tSkipped\t0\tFailed\t0" |
||||
set r [parsed $lines] |
||||
summary_facts $r |
||||
} -result {detected 0 totals 0/0/0/0 fails 0 passes 0 skips 0} |
||||
|
||||
#added 2026-08-03 (agent, G-161) - without -verbose start events there is no |
||||
#anchor: the historic limitation stands (multi-line banner unrecognised, the |
||||
#closing banner misread as an opener, later events and summary swallowed). |
||||
#Documents the tolerance boundary rather than desired behaviour. |
||||
test parsetestrun-nostart-6.0 {stream without start events keeps the historic single-line-only parsing} -body { |
||||
set lines [list] |
||||
lappend lines "==== t-nostart-6.0 first descriptive line" |
||||
lappend lines "second descriptive line FAILED" |
||||
lappend lines "==== Contents of test case:" |
||||
lappend lines "" |
||||
lappend lines " set z 1" |
||||
lappend lines "" |
||||
lappend lines "---- Result was:" |
||||
lappend lines "1" |
||||
lappend lines "---- Result should have been (exact matching):" |
||||
lappend lines "2" |
||||
lappend lines "==== t-nostart-6.0 FAILED" |
||||
lappend lines "++++ t-pass-6.1 PASSED" |
||||
lappend lines "synth.test:\tTotal\t2\tPassed\t1\tSkipped\t0\tFailed\t1" |
||||
set r [parsed $lines] |
||||
summary_facts $r |
||||
} -result {detected 0 totals 0/0/0/0 fails 0 passes 0 skips 0} |
||||
|
||||
cleanupTests |
||||
} |
||||
namespace delete ::testspace |
||||
Loading…
Reference in new issue