Skip to content

Commit ad346fe

Browse files
authored
Merge pull request #18 from KyleKincer/codex/add-global-error-handling-support
Handle global error handler fallback
2 parents c49224f + 662923c commit ad346fe

File tree

7 files changed

+494
-125
lines changed

7 files changed

+494
-125
lines changed

AGENTS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ If you need more control or the Makefile doesn't meet your needs:
7373
/Applications/tool4d.app/Contents/MacOS/tool4d --project $(PWD)/testing/Project/testing.4DProject --skip-onstartup --dataless --startup-method "test" --user-param "format=junit"
7474
```
7575

76+
### Linux setup notes
77+
78+
- See [docs/running-tests-linux.md](docs/running-tests-linux.md) for a step-by-step walkthrough of provisioning the experimental Linux build of **tool4d**.
79+
- Running `make test` on Linux installs the required system libraries (`libc++1`, `uuid-runtime`, `libfreeimage3`, `xdg-user-dirs`, `libtinfo5`, `libncurses5`) and downloads `tool4d` to `/opt/tool4d` before executing the suite.
80+
- Use `make tool4d` when you only need to bootstrap the runtime without immediately running the tests.
81+
- After installation you can invoke `/opt/tool4d/tool4d --project ... --user-param "excludeTags=no-linux"` directly for manual, headless runs.
82+
7683
### Test Filtering Parameters
7784

7885
```bash

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

Lines changed: 206 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,22 @@ Class constructor($cs : 4D:C1709.Object)
1717
This:C1470._parseTagFilters()
1818

1919
Function run()
20+
This:C1470._prepareErrorHandlingStorage()
2021
var $handlerState : Object
2122
$handlerState:=This:C1470._installErrorHandler()
2223
This:C1470._runInternal()
24+
This:C1470._captureGlobalErrors()
2325
This:C1470._restoreErrorHandler($handlerState)
2426

27+
Function _prepareErrorHandlingStorage()
28+
Use (Storage:C1525)
29+
If (Storage:C1525.testErrors=Null:C1517)
30+
Storage:C1525.testErrors:=New shared collection:C1527
31+
Else
32+
Storage:C1525.testErrors.clear()
33+
End if
34+
End use
35+
2536
Function _runInternal()
2637
This:C1470._prepareSuites()
2738
This:C1470._runSuitesSequentially()
@@ -50,26 +61,37 @@ Function _runSuitesSequentially()
5061

5162
Function _installErrorHandler() : Object
5263
var $previousErrorHandler : Text
53-
var $shouldInstall : Boolean
64+
var $previousGlobalHandler : Text
65+
var $shouldInstallLocal : Boolean
66+
var $shouldInstallGlobal : Boolean
5467

5568
$previousErrorHandler:=Method called on error:C704
56-
$shouldInstall:=($previousErrorHandler#"TestErrorHandler")
69+
$shouldInstallLocal:=($previousErrorHandler#"TestErrorHandler")
5770

58-
If ($shouldInstall)
71+
If ($shouldInstallLocal)
5972
ON ERR CALL:C155("TestErrorHandler")
6073
End if
6174

75+
$previousGlobalHandler:=Method called on error:C704(1)
76+
$shouldInstallGlobal:=($previousGlobalHandler#"TestGlobalErrorHandler")
77+
78+
If ($shouldInstallGlobal)
79+
ON ERR CALL:C155("TestGlobalErrorHandler"; 1)
80+
End if
81+
6282
return New object:C1471(\
6383
"previousHandler"; $previousErrorHandler; \
64-
"installedNewHandler"; $shouldInstall\
84+
"installedLocalHandler"; $shouldInstallLocal; \
85+
"previousGlobalHandler"; $previousGlobalHandler; \
86+
"installedGlobalHandler"; $shouldInstallGlobal\
6587
)
6688

6789
Function _restoreErrorHandler($handlerState : Object)
6890
If ($handlerState=Null:C1517)
6991
return
7092
End if
7193

72-
If ($handlerState.installedNewHandler)
94+
If (Bool:C1537($handlerState.installedLocalHandler))
7395
var $previousErrorHandler : Text
7496
$previousErrorHandler:=$handlerState.previousHandler
7597

@@ -79,7 +101,18 @@ Function _restoreErrorHandler($handlerState : Object)
79101
ON ERR CALL:C155("")
80102
End if
81103
End if
82-
104+
105+
If (Bool:C1537($handlerState.installedGlobalHandler))
106+
var $previousGlobalHandler : Text
107+
$previousGlobalHandler:=$handlerState.previousGlobalHandler
108+
109+
If ($previousGlobalHandler#"")
110+
ON ERR CALL:C155($previousGlobalHandler; 1)
111+
Else
112+
ON ERR CALL:C155(""; 1)
113+
End if
114+
End if
115+
83116
Function discoverTests()
84117
var $class : 4D:C1709.Class
85118
For each ($class; This:C1470._getTestClasses())
@@ -127,17 +160,20 @@ Function _filterTestClasses($classStore : Object) : Collection
127160
return $classes
128161

129162
Function _initializeResults()
130-
This:C1470.results:=New object:C1471(\
131-
"totalTests"; 0; \
132-
"passed"; 0; \
163+
This:C1470.results:=New object:C1471(\
164+
"totalTests"; 0; \
165+
"passed"; 0; \
133166
"failed"; 0; \
134167
"skipped"; 0; \
135168
"startTime"; 0; \
136169
"endTime"; 0; \
137170
"duration"; 0; \
138171
"suites"; []; \
139172
"failedTests"; []; \
140-
"assertions"; 0\
173+
"assertions"; 0; \
174+
"globalErrors"; []; \
175+
"hasGlobalErrors"; False:C215; \
176+
"globalErrorCount"; 0\
141177
)
142178

143179
Function _logHeader()
@@ -211,8 +247,68 @@ Function _collectSuiteResults($testSuite : cs:C1710._TestSuite)
211247
$suiteResult.assertions+=($testResult.assertionCount)
212248
End for each
213249

214-
This:C1470.results.suites.push($suiteResult)
215-
250+
This:C1470.results.suites.push($suiteResult)
251+
252+
Function _captureGlobalErrors()
253+
var $globalErrors : Collection
254+
$globalErrors:=This:C1470._drainGlobalErrorsFromStorage()
255+
256+
This:C1470.results.globalErrors:=$globalErrors
257+
This:C1470.results.globalErrorCount:=$globalErrors.length
258+
This:C1470.results.hasGlobalErrors:=Bool:C1537($globalErrors.length>0)
259+
260+
Function _drainGlobalErrorsFromStorage() : Collection
261+
var $globalErrors : Collection
262+
$globalErrors:=New collection:C1472
263+
264+
If (Storage:C1525.testErrors#Null:C1517)
265+
Use (Storage:C1525.testErrors)
266+
var $index : Integer
267+
For ($index; Storage:C1525.testErrors.length-1; 0; -1)
268+
var $error : Object
269+
$error:=Storage:C1525.testErrors[$index]
270+
271+
var $context : Text
272+
$context:=$error.context || ""
273+
274+
If ($context="global")
275+
$globalErrors.push(OB Copy:C1225($error))
276+
Storage:C1525.testErrors.remove($index)
277+
End if
278+
End for
279+
End use
280+
End if
281+
282+
return $globalErrors
283+
284+
Function _formatGlobalErrorForLog($error : Object) : Text
285+
var $codeText : Text
286+
var $processText : Text
287+
var $methodText : Text
288+
var $lineText : Text
289+
var $formulaText : Text
290+
var $message : Text
291+
292+
$codeText:=($error.code#Null:C1517) ? String:C10($error.code) : "?"
293+
$processText:=($error.processNumber#Null:C1517) ? String:C10($error.processNumber) : "?"
294+
$methodText:=$error.text || "Unknown location"
295+
296+
If ($error.line#Null:C1517)
297+
$lineText:=" line "+String:C10($error.line)
298+
Else
299+
$lineText:=""
300+
End if
301+
302+
$formulaText:=$error.method || ""
303+
304+
$message:="- ["+$codeText+"] Process "+$processText+": "+$methodText+$lineText
305+
306+
If ($formulaText#"")
307+
$message:=$message+"\r\n "+$formulaText
308+
End if
309+
310+
return $message
311+
216312
Function _generateReport()
217313
If (This:C1470.outputFormat="json")
218314
This:C1470._generateJSONReport()
@@ -245,10 +341,14 @@ Function _generateHumanReport()
245341
LOG EVENT:C667(Into system standard outputs:K38:9; "Assertions: "+String:C10(This:C1470.results.assertions)+"\r\n"; Information message:K38:1)
246342
LOG EVENT:C667(Into system standard outputs:K38:9; "Pass Rate: "+String:C10($passRate; "##0.0")+"%\r\n"; Information message:K38:1)
247343
LOG EVENT:C667(Into system standard outputs:K38:9; "Duration: "+String:C10(This:C1470.results.duration)+"ms\r\n"; Information message:K38:1)
248-
249-
If (This:C1470.results.failed>0)
250-
LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)
251-
LOG EVENT:C667(Into system standard outputs:K38:9; "=== Failed Tests ===\r\n"; Error message:K38:3)
344+
345+
var $externalMessageType : Integer
346+
$externalMessageType:=Choose:C955(This:C1470.results.globalErrorCount>0; Error message:K38:3; Information message:K38:1)
347+
LOG EVENT:C667(Into system standard outputs:K38:9; "External Errors: "+String:C10(This:C1470.results.globalErrorCount)+"\r\n"; $externalMessageType)
348+
349+
If (This:C1470.results.failed>0)
350+
LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)
351+
LOG EVENT:C667(Into system standard outputs:K38:9; "=== Failed Tests ===\r\n"; Error message:K38:3)
252352

253353
var $failedTest : Object
254354
For each ($failedTest; This:C1470.results.failedTests)
@@ -269,10 +369,20 @@ Function _generateHumanReport()
269369
If ($failedTest.callChain#Null)
270370
LOG EVENT:C667(Into system standard outputs:K38:9; This:C1470._formatCallChain($failedTest.callChain)+"\r\n"; Error message:K38:3)
271371
End if
272-
End for each
273-
End if
274-
275-
This:C1470._logFooter()
372+
End for each
373+
End if
374+
375+
If (This:C1470.results.hasGlobalErrors)
376+
LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)
377+
LOG EVENT:C667(Into system standard outputs:K38:9; "=== Runtime Errors Outside Test Processes ===\r\n"; Error message:K38:3)
378+
379+
var $globalError : Object
380+
For each ($globalError; This:C1470.results.globalErrors)
381+
LOG EVENT:C667(Into system standard outputs:K38:9; This:C1470._formatGlobalErrorForLog($globalError)+"\r\n"; Error message:K38:3)
382+
End for each
383+
End if
384+
385+
This:C1470._logFooter()
276386

277387
Function _generateJSONReport()
278388
var $passRate : Real
@@ -284,15 +394,18 @@ Function _generateJSONReport()
284394
$passRate:=0
285395
End if
286396

287-
var $jsonReport : Object
288-
289-
If (This:C1470.verboseOutput)
290-
// Verbose mode: include all details (original format)
291-
$jsonReport:=OB Copy:C1225(This:C1470.results)
292-
$jsonReport.passRate:=$passRate
293-
$jsonReport.status:=(This:C1470.results.failed=0) ? "success" : "failure"
294-
Else
295-
// Terse mode: minimal information
397+
var $jsonReport : Object
398+
399+
var $hasFailures : Boolean
400+
$hasFailures:=(This:C1470.results.failed>0) || This:C1470.results.hasGlobalErrors
401+
402+
If (This:C1470.verboseOutput)
403+
// Verbose mode: include all details (original format)
404+
$jsonReport:=OB Copy:C1225(This:C1470.results)
405+
$jsonReport.passRate:=$passRate
406+
$jsonReport.status:=$hasFailures ? "failure" : "success"
407+
Else
408+
// Terse mode: minimal information
296409
$jsonReport:=New object:C1471(\
297410
"tests"; This:C1470.results.totalTests; \
298411
"passed"; This:C1470.results.passed; \
@@ -301,7 +414,10 @@ Function _generateJSONReport()
301414
"assertions"; This:C1470.results.assertions; \
302415
"rate"; Round:C94($passRate; 1); \
303416
"duration"; This:C1470.results.duration; \
304-
"status"; (This:C1470.results.failed=0) ? "ok" : "fail"\
417+
"globalErrors"; This:C1470.results.globalErrors; \
418+
"globalErrorCount"; This:C1470.results.globalErrorCount; \
419+
"hasGlobalErrors"; This:C1470.results.hasGlobalErrors; \
420+
"status"; $hasFailures ? "fail" : "ok"\
305421
)
306422

307423
// Include individual test results with assertions
@@ -400,9 +516,12 @@ Function _buildJUnitXML() : Text
400516
$totalTime:=This:C1470.results.duration/1000 // Convert ms to seconds
401517

402518
// Calculate errors and failures separately
403-
var $totalErrors; $totalFailures : Integer
404-
$totalErrors:=This:C1470._countTestsWithRuntimeErrors()
405-
$totalFailures:=This:C1470.results.failed-$totalErrors // Failures are failed tests without runtime errors
519+
var $totalErrors; $totalFailures : Integer
520+
var $globalErrorCount : Integer
521+
$totalErrors:=This:C1470._countTestsWithRuntimeErrors()
522+
$globalErrorCount:=This:C1470.results.globalErrorCount
523+
$totalErrors:=$totalErrors+$globalErrorCount
524+
$totalFailures:=This:C1470.results.failed-$totalErrors // Failures are failed tests without runtime errors
406525

407526
// XML header and root testsuites element
408527
$xml:="<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
@@ -414,12 +533,16 @@ Function _buildJUnitXML() : Text
414533
$xml:=$xml+" timestamp=\""+This:C1470._formatTimestamp(This:C1470.results.startTime)+"\">\r\n"
415534

416535
// Build testsuite elements
417-
var $suite : Object
418-
For each ($suite; This:C1470.results.suites)
419-
$xml:=$xml+This:C1470._buildTestSuiteXML($suite)
420-
End for each
421-
422-
$xml:=$xml+"</testsuites>\r\n"
536+
var $suite : Object
537+
For each ($suite; This:C1470.results.suites)
538+
$xml:=$xml+This:C1470._buildTestSuiteXML($suite)
539+
End for each
540+
541+
If (This:C1470.results.hasGlobalErrors)
542+
$xml:=$xml+This:C1470._buildGlobalErrorsSystemErr()
543+
End if
544+
545+
$xml:=$xml+"</testsuites>\r\n"
423546

424547
return $xml
425548

@@ -480,10 +603,10 @@ Function _buildTestCaseXML($test : Object) : Text
480603
return $xml
481604

482605
Function _buildFailureXML($test : Object) : Text
483-
var $xml : Text
484-
var $failureMessage : Text
485-
var $failureDetails : Text
486-
var $elementType : Text
606+
var $xml : Text
607+
var $failureMessage : Text
608+
var $failureDetails : Text
609+
var $elementType : Text
487610

488611
// Extract failure message and details, determine element type
489612
If ($test.runtimeErrors.length>0)
@@ -510,10 +633,25 @@ Function _buildFailureXML($test : Object) : Text
510633
End if
511634

512635
$xml:=$xml+"\n]]>"
513-
$xml:=$xml+"</"+$elementType+">\r\n"
514-
515-
return $xml
516-
636+
$xml:=$xml+"</"+$elementType+">\r\n"
637+
638+
return $xml
639+
640+
Function _buildGlobalErrorsSystemErr() : Text
641+
var $xml : Text
642+
643+
$xml:=" <system-err><![CDATA[\n"
644+
$xml:=$xml+"External runtime errors detected: "+String:C10(This:C1470.results.globalErrorCount)+"\n"
645+
646+
var $error : Object
647+
For each ($error; This:C1470.results.globalErrors)
648+
$xml:=$xml+This:C1470._formatGlobalErrorForLog($error)+"\n"
649+
End for each
650+
651+
$xml:=$xml+"]]></system-err>\r\n"
652+
653+
return $xml
654+
517655
Function _writeJUnitXMLToFile($xmlContent : Text; $outputPath : Text)
518656
// Parse the path to determine folder and filename
519657
var $pathParts : Collection
@@ -563,13 +701,27 @@ Function _formatTimestamp($milliseconds : Integer) : Text
563701
return $timestamp
564702

565703
Function _logFooter()
566-
LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)
567-
If (This:C1470.results.failed=0)
568-
LOG EVENT:C667(Into system standard outputs:K38:9; "All tests passed! 🎉\r\n"; Information message:K38:1)
569-
Else
570-
LOG EVENT:C667(Into system standard outputs:K38:9; String:C10(This:C1470.results.failed)+" test(s) failed\r\n"; Error message:K38:3)
571-
End if
572-
LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)
704+
LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)
705+
If ((This:C1470.results.failed=0) && Not:C34(This:C1470.results.hasGlobalErrors))
706+
LOG EVENT:C667(Into system standard outputs:K38:9; "All tests passed! 🎉\r\n"; Information message:K38:1)
707+
Else
708+
var $summaryMessage : Text
709+
$summaryMessage:=""
710+
711+
If (This:C1470.results.failed>0)
712+
$summaryMessage:=String:C10(This:C1470.results.failed)+" test(s) failed"
713+
End if
714+
715+
If (This:C1470.results.hasGlobalErrors)
716+
If ($summaryMessage#"")
717+
$summaryMessage:=$summaryMessage+"; "
718+
End if
719+
$summaryMessage:=$summaryMessage+String:C10(This:C1470.results.globalErrorCount)+" external runtime error(s)"
720+
End if
721+
722+
LOG EVENT:C667(Into system standard outputs:K38:9; $summaryMessage+"\r\n"; Error message:K38:3)
723+
End if
724+
LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)
573725

574726
Function getResults() : Object
575727
return This:C1470.results

0 commit comments

Comments
 (0)