Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ make test-parallel-json # Run tests in parallel with JSON output
make test-parallel-unit # Run unit tests in parallel
make test-parallel-workers WORKERS=4 # Run with custom worker count

# Debugging tool4d output (stderr is suppressed by default)
make test debug=true # Show tool4d diagnostic stderr

# Show all available commands
make help
```
Expand Down Expand Up @@ -101,7 +104,7 @@ If you need more control or the Makefile doesn't meet your needs:
--user-param "format=junit tags=unit"
--user-param "format=junit outputPath=results/junit.xml"

# JSON / JUnit file output (clean, sidesteps any debug noise on stdout)
# JSON / JUnit file output (writes report to disk)
--user-param "format=json outputPath=test-results/report.json"

# Include callChain on failed tests in terse JSON without going full verbose
Expand Down
5 changes: 4 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ make test-parallel-json # Run tests in parallel with JSON output
make test-parallel-unit # Run unit tests in parallel
make test-parallel-workers WORKERS=4 # Run with custom worker count

# Debugging tool4d output (stderr is suppressed by default)
make test debug=true # Show tool4d diagnostic stderr

# Show all available commands
make help
```
Expand Down Expand Up @@ -95,7 +98,7 @@ If you need more control or the Makefile doesn't meet your needs:
--user-param "format=junit tags=unit"
--user-param "format=junit outputPath=results/junit.xml"

# JSON / JUnit file output (clean, sidesteps any debug noise on stdout)
# JSON / JUnit file output (writes report to disk)
--user-param "format=json outputPath=test-results/report.json"

# Include callChain on failed tests in terse JSON without going full verbose
Expand Down
30 changes: 25 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# 4D Unit Testing Framework Makefile
SHELL := /bin/bash

# Get current user's home directory
HOME_DIR := $(shell echo $$HOME)
Expand Down Expand Up @@ -35,7 +36,14 @@ EXCLUDE_TAGS_COMBINED := $(strip $(DEFAULT_EXCLUDE_TAGS) $(excludeTags))
BASE_PARAMS := $(if $(EXCLUDE_TAGS_COMBINED),excludeTags=$(subst $(space),$(comma),$(EXCLUDE_TAGS_COMBINED)))

# Build user parameters from make variables
USER_PARAMS := $(strip $(BASE_PARAMS) $(if $(format),format=$(format)) $(if $(tags),tags=$(tags)) $(if $(test),test=$(test)) $(if $(requireTags),requireTags=$(requireTags)) $(if $(parallel),parallel=$(parallel)) $(if $(maxWorkers),maxWorkers=$(maxWorkers)) $(if $(outputPath),outputPath=$(outputPath)) $(if $(verbose),verbose=$(verbose)) $(if $(callchain),callchain=$(callchain)))
USER_PARAMS := $(strip $(BASE_PARAMS) $(if $(format),format=$(format)) $(if $(tags),tags=$(tags)) $(if $(test),test=$(test)) $(if $(requireTags),requireTags=$(requireTags)) $(if $(parallel),parallel=$(parallel)) $(if $(maxWorkers),maxWorkers=$(maxWorkers)) $(if $(outputPath),outputPath=$(outputPath)) $(if $(verbose),verbose=$(verbose)) $(if $(callchain),callchain=$(callchain)) $(if $(triggers),triggers=$(triggers)))

# Output filtering: suppress tool4d diagnostic noise by default.
# - stderr redirected to /dev/null (tool4d.4DRT errors)
# - stdout cooperative-yield warnings stripped via FIFO + grep
# Pass debug=true to disable stderr suppression.
STDERR_REDIRECT := $(if $(debug),,2>/dev/null)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Gate stderr suppression only on explicit debug=true.

Current condition treats any non-empty debug value (including debug=false) as debug mode.

Suggested fix
-STDERR_REDIRECT := $(if $(debug),,2>/dev/null)
+STDERR_REDIRECT := $(if $(filter true,$(debug)),,2>/dev/null)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Makefile` at line 47, The current STDREDIRECT assignment uses a truthy check
on the debug variable so values like "false" disable stderr redirection; change
the condition to test explicitly for debug being "true" (e.g., use a Makefile
equality/filter check against the debug variable) so stderr is only left
unredirected when debug is exactly "true"; update the STDERROR_REDIRECT
(STDERR_REDIRECT) assignment to use this explicit check referencing the debug
variable.

YIELD_FILTER := grep --line-buffered -v '^tool4d\.APPL Cooperative process doesn.t yield enough'

# Ensure tool4d is installed (currently implemented for Linux only)
$(TOOL4D):
Expand All @@ -57,11 +65,19 @@ $(TOOL4D):
# Usage: make test [key=value key2=value2 ...]
# Example: make test format=json tags=unit
test: $(TOOL4D)
@if [ -n "$(USER_PARAMS)" ]; then \
$(TOOL4D) $(BASE_OPTS) --user-param "$(USER_PARAMS)"; \
@fifo=$$(mktemp -u).fifo; mkfifo $$fifo; \
$(YIELD_FILTER) < $$fifo & filter_pid=$$!; \
if [ -n "$(USER_PARAMS)" ]; then \
$(TOOL4D) $(BASE_OPTS) --user-param "$(USER_PARAMS)" $(STDERR_REDIRECT) > $$fifo & \
else \
$(TOOL4D) $(BASE_OPTS); \
fi
$(TOOL4D) $(BASE_OPTS) $(STDERR_REDIRECT) > $$fifo & \
fi; \
pid=$$!; \
trap 'disown $$pid $$filter_pid 2>/dev/null; kill -KILL $$pid $$filter_pid 2>/dev/null; rm -f $$fifo; exit 130' INT TERM; \
wait $$pid; ret=$$?; \
kill $$filter_pid 2>/dev/null; wait $$filter_pid 2>/dev/null; \
rm -f $$fifo; \
exit $$ret
Comment on lines +70 to +83

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear stale $(ERROR_FILE) before each run to avoid false failures.

If a stale marker exists (e.g., interrupted previous run), this target exits 1 even when current tests pass.

Suggested fix
 test: $(TOOL4D)
-	`@fifo`=$$(mktemp -u).fifo; mkfifo $$fifo; \
+	`@rm` -f "$(ERROR_FILE)"; \
+	fifo=$$(mktemp -u).fifo; mkfifo $$fifo; \
 	$(YIELD_FILTER) < $$fifo & filter_pid=$$!; \
 	if [ -n "$(USER_PARAMS)" ]; then \
 	        $(TOOL4D) $(BASE_OPTS) --user-param "$(USER_PARAMS)" $(STDERR_REDIRECT) > $$fifo & \
 	else \
 	        $(TOOL4D) $(BASE_OPTS) $(STDERR_REDIRECT) > $$fifo & \
 	fi; \
 	pid=$$!; \
-	trap 'disown $$pid $$filter_pid 2>/dev/null; kill -KILL $$pid $$filter_pid 2>/dev/null; rm -f $$fifo; exit 130' INT TERM; \
+	trap 'disown $$pid $$filter_pid 2>/dev/null; kill -KILL $$pid $$filter_pid 2>/dev/null; rm -f $$fifo "$(ERROR_FILE)"; exit 130' INT TERM; \
 	wait $$pid; ret=$$?; \
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@fifo=$$(mktemp -u).fifo; mkfifo $$fifo; \
$(YIELD_FILTER) < $$fifo & filter_pid=$$!; \
if [ -n "$(USER_PARAMS)" ]; then \
$(TOOL4D) $(BASE_OPTS) --user-param "$(USER_PARAMS)" $(STDERR_REDIRECT) > $$fifo & \
else \
$(TOOL4D) $(BASE_OPTS); \
fi
$(TOOL4D) $(BASE_OPTS) $(STDERR_REDIRECT) > $$fifo & \
fi; \
pid=$$!; \
trap 'disown $$pid $$filter_pid 2>/dev/null; kill -KILL $$pid $$filter_pid 2>/dev/null; rm -f $$fifo; exit 130' INT TERM; \
wait $$pid; ret=$$?; \
kill $$filter_pid 2>/dev/null; wait $$filter_pid 2>/dev/null; \
rm -f $$fifo; \
if [ -f "$(ERROR_FILE)" ]; then rm -f "$(ERROR_FILE)"; exit 1; fi; \
exit $$ret
`@rm` -f "$(ERROR_FILE)"; \
fifo=$$(mktemp -u).fifo; mkfifo $$fifo; \
$(YIELD_FILTER) < $$fifo & filter_pid=$$!; \
if [ -n "$(USER_PARAMS)" ]; then \
$(TOOL4D) $(BASE_OPTS) --user-param "$(USER_PARAMS)" $(STDERR_REDIRECT) > $$fifo & \
else \
$(TOOL4D) $(BASE_OPTS) $(STDERR_REDIRECT) > $$fifo & \
fi; \
pid=$$!; \
trap 'disown $$pid $$filter_pid 2>/dev/null; kill -KILL $$pid $$filter_pid 2>/dev/null; rm -f $$fifo "$(ERROR_FILE)"; exit 130' INT TERM; \
wait $$pid; ret=$$?; \
kill $$filter_pid 2>/dev/null; wait $$filter_pid 2>/dev/null; \
rm -f $$fifo; \
if [ -f "$(ERROR_FILE)" ]; then rm -f "$(ERROR_FILE)"; exit 1; fi; \
exit $$ret
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Makefile` around lines 70 - 83, The rule can fail from a stale
"$(ERROR_FILE)" left by a previous run; before creating the fifo and launching
processes (the block that uses mkfifo, $(YIELD_FILTER), $(TOOL4D), etc.) ensure
you delete any existing "$(ERROR_FILE)" at the start of the recipe so old
markers don't trigger a false exit; add a step that checks for and removes
"$(ERROR_FILE)" (or truncates it) prior to creating $$fifo and starting
$(TOOL4D) and the filter, preserving the rest of the trap/wait/cleanup logic.


# Run all tests with JSON output
test-json:
Expand Down Expand Up @@ -150,6 +166,9 @@ help:
@echo " test-parallel-workers - Run tests in parallel with custom worker count"
@echo " help - Show this help message"
@echo ""
@echo "Options:"
@echo " debug=true - Show tool4d diagnostic stderr (suppressed by default)"
@echo ""
@echo "Examples:"
@echo " make test"
@echo " make test format=json"
Expand All @@ -167,6 +186,7 @@ help:
@echo " make test-parallel-json"
@echo " make test-parallel-workers WORKERS=4"
@echo " make test parallel=true maxWorkers=6"
@echo " make test debug=true # Show tool4d stderr output"

tool4d: $(TOOL4D)
@echo "tool4d ready at $(TOOL4D)"
Expand Down
4 changes: 2 additions & 2 deletions testing/Project/Sources/Classes/ParallelTestRunner.4dm
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ Function _waitForCompletionAndCollectResults()

If ((Milliseconds:C459-$startWait)>$timeout)
// Timeout - log error and break
LOG EVENT:C667(Into system standard outputs:K38:9; "Parallel test execution timeout after 5 minutes\r\n"; Error message:K38:3)
LOG EVENT:C667(Into system standard outputs:K38:9; "Parallel test execution timeout after 5 minutes\r\n"; Information message:K38:1)
break
End if

Expand Down Expand Up @@ -269,7 +269,7 @@ Function _processWorkerResults($suiteResults : Object)
End if
End if

LOG EVENT:C667(Into system standard outputs:K38:9; " ✗ "+$test.name+" ("+String:C10($test.duration)+"ms)"+$errorDetails+"\r\n"; Error message:K38:3)
LOG EVENT:C667(Into system standard outputs:K38:9; " ✗ "+$test.name+" ("+String:C10($test.duration)+"ms)"+$errorDetails+"\r\n"; Information message:K38:1)
End if
End if
End if
Expand Down
108 changes: 60 additions & 48 deletions testing/Project/Sources/Classes/TestRunner.4dm
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,9 @@ Function run()
This:C1470._prepareErrorHandlingStorage()
var $handlerState : Object
$handlerState:=This:C1470._installErrorHandler()
LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] run: before _runInternal\r\n"; Information message:K38:1)
This:C1470._runInternal()
LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] run: after _runInternal\r\n"; Information message:K38:1)
This:C1470._captureGlobalErrors()
LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] run: after _captureGlobalErrors\r\n"; Information message:K38:1)
This:C1470._generateReport()
LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] run: after _generateReport\r\n"; Information message:K38:1)
This:C1470._restoreErrorHandler($handlerState)

Function _determineTriggerDefaultBehavior()
Expand Down Expand Up @@ -139,15 +135,10 @@ Function _runSuitesSequentially()
End if

var $testSuite : cs:C1710._TestSuite
var $suiteIndex : Integer
$suiteIndex:=0
For each ($testSuite; This:C1470.testSuites)
$suiteIndex+=1
LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] suite "+String:C10($suiteIndex)+"/"+String:C10(This:C1470.testSuites.length)+": "+$testSuite.class.name+" ("+String:C10($testSuite.testFunctions.length)+" tests)\r\n"; Information message:K38:1)
$testSuite.run()
This:C1470._collectSuiteResults($testSuite)
End for each
LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] _runSuitesSequentially: all suites done\r\n"; Information message:K38:1)

This:C1470.results.endTime:=Milliseconds:C459
This:C1470.results.duration:=This:C1470.results.endTime-This:C1470.results.startTime
Expand Down Expand Up @@ -329,7 +320,7 @@ Function _collectSuiteResults($testSuite : cs:C1710._TestSuite)
If (This:C1470.verboseOutput) && ($testResult.callChain#Null:C1517)
$errorDetails:=$errorDetails+"\r\n"+This:C1470._formatCallChain($testResult.callChain)
End if
LOG EVENT:C667(Into system standard outputs:K38:9; " ✗ "+$testResult.name+" ("+String:C10($testResult.duration)+"ms)"+$errorDetails+"\r\n"; Error message:K38:3)
LOG EVENT:C667(Into system standard outputs:K38:9; " ✗ "+$testResult.name+" ("+String:C10($testResult.duration)+"ms)"+$errorDetails+"\r\n"; Information message:K38:1)
End if
End if
End if
Expand Down Expand Up @@ -416,8 +407,27 @@ Function _formatGlobalErrorForLog($error : Object) : Text

return $message

Function _groupGlobalErrors() : Object
var $grouped : Object
$grouped:=New object:C1471

var $error : Object
For each ($error; This:C1470.results.globalErrors)
var $key : Text
$key:=String:C10($error.code || 0)+"_"+($error.text || "")+"_"+String:C10($error.line || 0)

If ($grouped[$key]=Null:C1517)
$grouped[$key]:=New object:C1471(\
"message"; This:C1470._formatGlobalErrorForLog($error); \
"count"; 1)
Else
$grouped[$key].count+=1
End if
End for each

return $grouped

Function _generateReport()
LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] _generateReport: format="+This:C1470.outputFormat+"\r\n"; Information message:K38:1)
If (This:C1470.outputFormat="json")
This:C1470._generateJSONReport()
Else
Expand All @@ -429,10 +439,8 @@ Function _generateReport()
End if
End if
End if
LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] _generateReport: done\r\n"; Information message:K38:1)

Function _generateHumanReport()
LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] _generateHumanReport: start\r\n"; Information message:K38:1)
var $passRate : Real
var $effectiveTotal : Integer
$effectiveTotal:=This:C1470.results.totalTests-This:C1470.results.skipped
Expand All @@ -442,23 +450,25 @@ Function _generateHumanReport()
$passRate:=0
End if

LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "=== Test Results Summary ===\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "Total Tests: "+String:C10(This:C1470.results.totalTests)+"\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "Passed: "+String:C10(This:C1470.results.passed)+"\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "Failed: "+String:C10(This:C1470.results.failed)+"\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "Skipped: "+String:C10(This:C1470.results.skipped)+"\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "Assertions: "+String:C10(This:C1470.results.assertions)+"\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "Pass Rate: "+String:C10($passRate; "##0.0")+"%\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "Duration: "+String:C10(This:C1470.results.duration)+"ms\r\n"; Information message:K38:1)

var $externalMessageType : Integer
$externalMessageType:=Choose:C955(This:C1470.results.globalErrorCount>0; Error message:K38:3; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "External Errors: "+String:C10(This:C1470.results.globalErrorCount)+"\r\n"; $externalMessageType)
If (This:C1470.results.hasGlobalErrors)
LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "=== Runtime Errors Outside Test Processes ===\r\n"; Information message:K38:1)

var $grouped : Object
$grouped:=This:C1470._groupGlobalErrors()
var $key : Text
var $entry : Object
For each ($key; $grouped)
$entry:=$grouped[$key]
var $countSuffix : Text
$countSuffix:=($entry.count>1) ? " (x"+String:C10($entry.count)+")" : ""
LOG EVENT:C667(Into system standard outputs:K38:9; $entry.message+$countSuffix+"\r\n"; Information message:K38:1)
End for each
End if

If (This:C1470.results.failed>0)
LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "=== Failed Tests ===\r\n"; Error message:K38:3)
LOG EVENT:C667(Into system standard outputs:K38:9; "=== Failed Tests ===\r\n"; Information message:K38:1)

var $failedTest : Object
For each ($failedTest; This:C1470.results.failedTests)
Expand All @@ -473,28 +483,29 @@ Function _generateHumanReport()
End if
End if

LOG EVENT:C667(Into system standard outputs:K38:9; "- "+$failedTest.name+$failureReason+"\r\n"; Error message:K38:3)
LOG EVENT:C667(Into system standard outputs:K38:9; "- "+$failedTest.name+$failureReason+"\r\n"; Information message:K38:1)

If (This:C1470.verboseOutput) && ($failedTest.callChain#Null:C1517)
LOG EVENT:C667(Into system standard outputs:K38:9; This:C1470._formatCallChain($failedTest.callChain)+"\r\n"; Error message:K38:3)
LOG EVENT:C667(Into system standard outputs:K38:9; This:C1470._formatCallChain($failedTest.callChain)+"\r\n"; Information message:K38:1)
End if
End for each
End if

If (This:C1470.results.hasGlobalErrors)
LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "=== Runtime Errors Outside Test Processes ===\r\n"; Error message:K38:3)

var $globalError : Object
For each ($globalError; This:C1470.results.globalErrors)
LOG EVENT:C667(Into system standard outputs:K38:9; This:C1470._formatGlobalErrorForLog($globalError)+"\r\n"; Error message:K38:3)
End for each
End if
LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "=== Test Results Summary ===\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "Total Tests: "+String:C10(This:C1470.results.totalTests)+"\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "Passed: "+String:C10(This:C1470.results.passed)+"\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "Failed: "+String:C10(This:C1470.results.failed)+"\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "Skipped: "+String:C10(This:C1470.results.skipped)+"\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "Assertions: "+String:C10(This:C1470.results.assertions)+"\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "Pass Rate: "+String:C10($passRate; "##0.0")+"%\r\n"; Information message:K38:1)
LOG EVENT:C667(Into system standard outputs:K38:9; "Duration: "+String:C10(This:C1470.results.duration)+"ms\r\n"; Information message:K38:1)

LOG EVENT:C667(Into system standard outputs:K38:9; "External Errors: "+String:C10(This:C1470.results.globalErrorCount)+"\r\n"; Information message:K38:1)

This:C1470._logFooter()

Function _generateJSONReport()
LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] _generateJSONReport: start, totalTests="+String:C10(This:C1470.results.totalTests)+"\r\n"; Information message:K38:1)
var $passRate : Real
var $effectiveTotal : Integer
$effectiveTotal:=This:C1470.results.totalTests-This:C1470.results.skipped
Expand All @@ -511,12 +522,10 @@ Function _generateJSONReport()

If (This:C1470.verboseOutput)
// Verbose mode: include all details (original format)
LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] _generateJSONReport: verbose OB Copy\r\n"; Information message:K38:1)
$jsonReport:=OB Copy:C1225(This:C1470.results)
$jsonReport.passRate:=$passRate
$jsonReport.status:=$hasFailures ? "failure" : "success"
Else
LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] _generateJSONReport: terse mode\r\n"; Information message:K38:1)
// Terse mode: minimal information
$jsonReport:=New object:C1471(\
"tests"; This:C1470.results.totalTests; \
Expand Down Expand Up @@ -604,14 +613,11 @@ Function _generateJSONReport()
End if
End if

LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] _generateJSONReport: before JSON Stringify\r\n"; Information message:K38:1)
var $jsonString : Text
$jsonString:=JSON Stringify:C1217($jsonReport; *)
LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] _generateJSONReport: after JSON Stringify, length="+String:C10(Length:C16($jsonString))+"\r\n"; Information message:K38:1)

var $outputPath : Text
$outputPath:=This:C1470.userParams.outputPath || ""
LOG EVENT:C667(Into system standard outputs:K38:9; "[DEBUG] _generateJSONReport: outputPath='"+$outputPath+"'\r\n"; Information message:K38:1)

If ($outputPath#"")
This:C1470._writeJSONToFile($jsonString; $outputPath)
Expand Down Expand Up @@ -789,9 +795,15 @@ Function _buildGlobalErrorsSystemErr() : Text
$xml:=" <system-err><![CDATA[\n"
$xml:=$xml+"External runtime errors detected: "+String:C10(This:C1470.results.globalErrorCount)+"\n"

var $error : Object
For each ($error; This:C1470.results.globalErrors)
$xml:=$xml+This:C1470._formatGlobalErrorForLog($error)+"\n"
var $grouped : Object
$grouped:=This:C1470._groupGlobalErrors()
var $key : Text
var $entry : Object
For each ($key; $grouped)
$entry:=$grouped[$key]
var $countSuffix : Text
$countSuffix:=($entry.count>1) ? " (x"+String:C10($entry.count)+")" : ""
$xml:=$xml+$entry.message+$countSuffix+"\n"
End for each

$xml:=$xml+"]]></system-err>\r\n"
Expand Down Expand Up @@ -873,7 +885,7 @@ Function _logFooter()
$summaryMessage:=$summaryMessage+String:C10(This:C1470.results.globalErrorCount)+" external runtime error(s)"
End if

LOG EVENT:C667(Into system standard outputs:K38:9; $summaryMessage+"\r\n"; Error message:K38:3)
LOG EVENT:C667(Into system standard outputs:K38:9; $summaryMessage+"\r\n"; Information message:K38:1)
End if
LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)

Expand Down