Skip to content

Commit e6ee5b1

Browse files
KyleKincerclaude
andcommitted
feat(testing): add call chain debugging for test failures
Enhanced the testing framework with comprehensive call chain debugging capabilities using 4D's Call chain command. When tests fail, the framework now captures and displays the complete method call sequence leading to the failure. Key improvements: - Added failureCallChain property to Testing context - Enhanced Assert methods to automatically capture call stack on failures - Updated all output formats (human, JSON, JUnit XML) to include call chain data - Added formatCallChain() method for readable stack trace formatting - Integrated call chain information in CI/CD reports for better debugging This provides developers with detailed execution context when tests fail, showing exact line numbers, method types, and the complete call sequence from test failure back through helper methods to the test runner. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent e584952 commit e6ee5b1

File tree

3 files changed

+114
-3
lines changed

3 files changed

+114
-3
lines changed

testing/Project/Sources/Classes/TestRunner.4dm

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ Function _logHeader()
115115
LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)
116116

117117
Function _collectSuiteResults($testSuite : cs:C1710._TestSuite)
118+
// Skip suites with no tests
119+
If ($testSuite.testFunctions.length=0)
120+
return
121+
End if
122+
118123
var $suiteResult : Object
119124
$suiteResult:=New object:C1471(\
120125
"name"; $testSuite.class.name; \
@@ -158,6 +163,11 @@ Function _collectSuiteResults($testSuite : cs:C1710._TestSuite)
158163
$errorDetails:=" ["+$testResult.logMessages[0]+"]"
159164
End if
160165
End if
166+
167+
// Add call chain information if available
168+
If ($testResult.callChain#Null)
169+
$errorDetails:=$errorDetails+"\r\n"+This:C1470._formatCallChain($testResult.callChain)
170+
End if
161171
LOG EVENT:C667(Into system standard outputs:K38:9; " ✗ "+$testResult.name+" ("+String:C10($testResult.duration)+"ms)"+$errorDetails+"\r\n"; Error message:K38:3)
162172
End if
163173
End if
@@ -216,6 +226,11 @@ Function _generateHumanReport()
216226
End if
217227

218228
LOG EVENT:C667(Into system standard outputs:K38:9; "- "+$failedTest.name+$failureReason+"\r\n"; Error message:K38:3)
229+
230+
// Add detailed call chain if available
231+
If ($failedTest.callChain#Null)
232+
LOG EVENT:C667(Into system standard outputs:K38:9; This:C1470._formatCallChain($failedTest.callChain)+"\r\n"; Error message:K38:3)
233+
End if
219234
End for each
220235
End if
221236

@@ -266,6 +281,11 @@ Function _generateJSONReport()
266281
$terseFailure.reason:=$failedTest.logMessages[0]
267282
End if
268283
End if
284+
285+
// Include call chain in verbose JSON output
286+
If (This:C1470.verboseOutput) && ($failedTest.callChain#Null)
287+
$terseFailure.callChain:=$failedTest.callChain
288+
End if
269289
$failedTests.push($terseFailure)
270290
End for each
271291
$jsonReport.failures:=$failedTests
@@ -422,7 +442,14 @@ Function _buildFailureXML($test : Object) : Text
422442
End if
423443

424444
$xml:=" <"+$elementType+" message=\""+This:C1470._escapeXMLAttribute($failureMessage)+"\">"
425-
$xml:=$xml+"<![CDATA[\n"+$failureDetails+"\nLocation: "+$test.suite+"."+$test.name+"\n]]>"
445+
$xml:=$xml+"<![CDATA[\n"+$failureDetails+"\nLocation: "+$test.suite+"."+$test.name
446+
447+
// Include call chain in JUnit XML if available
448+
If ($test.callChain#Null)
449+
$xml:=$xml+"\n\n"+This:C1470._formatCallChain($test.callChain)
450+
End if
451+
452+
$xml:=$xml+"\n]]>"
426453
$xml:=$xml+"</"+$elementType+">\r\n"
427454

428455
return $xml
@@ -715,4 +742,41 @@ Function _shouldIncludeTestByTags($testFunction : cs:C1710._TestFunction) : Bool
715742
End if
716743

717744
// If we have exclude or require filters but no include filters, default to true
718-
return True:C214
745+
return True:C214
746+
747+
Function _formatCallChain($callChain : Collection) : Text
748+
// Format the call chain into a readable string for debugging
749+
var $result : Text
750+
var $i : Integer
751+
var $callInfo : Object
752+
753+
$result:=""
754+
755+
If ($callChain#Null)
756+
$result:="Call Stack:"
757+
758+
For ($i; 0; $callChain.length-1)
759+
$callInfo:=$callChain[$i]
760+
$result:=$result+"\r\n "+String:C10($i+1)+". "
761+
762+
If ($callInfo.name#Null)
763+
$result:=$result+$callInfo.name
764+
Else
765+
$result:=$result+"<unnamed>"
766+
End if
767+
768+
If ($callInfo.type#Null)
769+
$result:=$result+" ("+$callInfo.type+")"
770+
End if
771+
772+
If ($callInfo.line#Null)
773+
$result:=$result+" at line "+String:C10($callInfo.line)
774+
End if
775+
776+
If ($callInfo.database#Null)
777+
$result:=$result+" in "+$callInfo.database
778+
End if
779+
End for
780+
End if
781+
782+
return $result

testing/Project/Sources/Classes/Testing.4dm

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,36 @@ property done : Boolean
55
property logMessages : Collection
66
property assert : cs:C1710.Assert
77
property stats : cs:C1710.UnitStatsTracker
8+
property failureCallChain : Collection
89

910
Class constructor()
1011
This:C1470.failed:=False:C215
1112
This:C1470.done:=False:C215
1213
This:C1470.logMessages:=[]
1314
This:C1470.assert:=cs:C1710.Assert.new()
1415
This:C1470.stats:=cs:C1710.UnitStatsTracker.new()
16+
This:C1470.failureCallChain:=Null
1517

1618
Function log($message : Text)
1719
This:C1470.logMessages.push($message)
1820

1921
Function fail()
2022
This:C1470.failed:=True:C214
23+
// Capture call chain when test fails for debugging
24+
This:C1470.failureCallChain:=Call chain:C1662
2125

2226
Function fatal()
2327
This:C1470.failed:=True:C214
2428
This:C1470.done:=True:C214
29+
// Capture call chain when test fails fatally for debugging
30+
This:C1470.failureCallChain:=Call chain:C1662
2531

2632
Function resetForNewTest()
2733
This:C1470.failed:=False:C215
2834
This:C1470.done:=False:C215
2935
This:C1470.logMessages:=[]
3036
This:C1470.stats.resetStatistics()
37+
This:C1470.failureCallChain:=Null
3138

3239
Function run($name : Text; $subtest : 4D:C1709.Function)
3340
// This will be implemented later
@@ -117,4 +124,43 @@ Function withTransactionValidate($operation : 4D:C1709.Function) : Boolean
117124
End if
118125

119126
return $success
127+
128+
Function formatCallChain() : Text
129+
// Format the call chain into a readable string for debugging
130+
var $result : Text
131+
var $i : Integer
132+
var $callInfo : Object
133+
134+
$result:=""
135+
136+
If (This:C1470.failureCallChain#Null)
137+
$result:="Call Stack:\n"
138+
139+
For ($i; 0; This:C1470.failureCallChain.length-1)
140+
$callInfo:=This:C1470.failureCallChain[$i]
141+
$result:=$result+" "+String:C10($i+1)+". "
142+
143+
If ($callInfo.name#Null)
144+
$result:=$result+$callInfo.name
145+
Else
146+
$result:=$result+"<unnamed>"
147+
End if
148+
149+
If ($callInfo.type#Null)
150+
$result:=$result+" ("+$callInfo.type+")"
151+
End if
152+
153+
If ($callInfo.line#Null)
154+
$result:=$result+" at line "+String:C10($callInfo.line)
155+
End if
156+
157+
If ($callInfo.database#Null)
158+
$result:=$result+" in "+$callInfo.database
159+
End if
160+
161+
$result:=$result+"\n"
162+
End for
163+
End if
164+
165+
return $result
120166

testing/Project/Sources/Classes/_TestFunction.4dm

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ Function getResult() : Object
101101
"suite"; This:C1470.class.name; \
102102
"runtimeErrors"; This:C1470.runtimeErrors; \
103103
"logMessages"; This:C1470.t.logMessages; \
104-
"tags"; This:C1470.tags\
104+
"tags"; This:C1470.tags; \
105+
"callChain"; This:C1470.t.failureCallChain\
105106
)
106107

107108
Function shouldSkip() : Boolean

0 commit comments

Comments
 (0)