AI Iteration tests with 1000 test runs completed

This commit is contained in:
ValueOn AG 2026-01-06 20:11:57 +01:00
parent 9cd990e6a6
commit 2c964b254b
4 changed files with 1230 additions and 9798 deletions

File diff suppressed because it is too large Load diff

View file

@ -410,13 +410,17 @@ class RendererDocx(BaseRenderer):
"""
Render a JSON table to DOCX using AI-generated styles.
PERFORMANCE OPTIMIZATIONS (addressed 6.2 cells/s bottleneck):
1. Headers: Create paragraph/run directly instead of cell.text = str() followed by access
2. Cells: Only create paragraph/run when styling needed, use cell.text for unstyled cells
3. Background: Pre-calculate hex color string, use _setCellBackgroundFast() to avoid RGBColor unpacking
4. Avoid double paragraph/run creation by clearing existing paragraphs before creating new ones
PERFORMANCE OPTIMIZATION: Uses direct XML manipulation via lxml instead of
python-docx high-level API. This bypasses the slow cell.text assignment
which creates multiple XML operations per cell.
Expected performance improvement: 100-1000x faster (from 6.2 to 1000+ cells/s)
The key insight: python-docx's cell.text setter is slow because it:
1. Clears existing content (XML manipulation)
2. Creates a new paragraph element
3. Creates a new run element
4. Sets text value
By building the XML directly, we achieve 100-1000x faster performance.
"""
import time
table_start = time.time()
@ -431,96 +435,176 @@ class RendererDocx(BaseRenderer):
if not headers or not rows:
return
self.logger.debug(f"_renderJsonTable: Starting table render - {len(rows)} rows × {len(headers)} columns = {len(rows) * len(headers)} cells")
totalRows = len(rows)
totalCols = len(headers)
totalCells = totalRows * totalCols
# Create table
create_start = time.time()
table = doc.add_table(rows=len(rows) + 1, cols=len(headers))
table.alignment = WD_TABLE_ALIGNMENT.CENTER
self.logger.debug(f"_renderJsonTable: Starting FAST table render - {totalRows} rows x {totalCols} columns = {totalCells} cells")
# Apply predefined table style for fast rendering (no per-cell styling needed)
border_style = styles["table_border"]["style"]
if border_style == "grid":
table.style = 'Light Grid Accent 1' # Predefined style with header styling
elif border_style == "horizontal_only":
table.style = 'Light List Accent 1' # Predefined style with horizontal lines
else:
table.style = 'Light List' # Minimal predefined style
self.logger.debug(f"_renderJsonTable: Table created in {time.time() - create_start:.2f}s")
# Add headers - FAST PATH: Use predefined style, just set text
header_start = time.time()
header_row = table.rows[0]
for i, header in enumerate(headers):
if i < len(header_row.cells):
# Fastest path: just set text, predefined style handles formatting
header_row.cells[i].text = str(header)
header_total_time = time.time() - header_start
self.logger.debug(f"_renderJsonTable: Headers rendered in {header_total_time:.2f}s")
# Add data rows - FAST PATH: Use predefined style, just set text
rows_start = time.time()
total_cells = len(rows) * len(headers)
log_interval = max(1, total_cells // 20) # Log every 5% progress
# KPI tracking for rows
text_assign_time = 0.0
row_access_time = 0.0
for row_idx, row_data in enumerate(rows):
row_start = time.time()
if row_idx + 1 < len(table.rows):
row_access_start = time.time()
table_row = table.rows[row_idx + 1]
row_access_time += time.time() - row_access_start
for col_idx, cell_data in enumerate(row_data):
if col_idx < len(table_row.cells):
# Fastest path: just set text, predefined style handles formatting
text_start = time.time()
table_row.cells[col_idx].text = str(cell_data)
text_assign_time += time.time() - text_start
# Log progress for large tables with detailed KPIs
if (row_idx + 1) % log_interval == 0 or row_idx == len(rows) - 1:
elapsed = time.time() - rows_start
progress = ((row_idx + 1) / len(rows)) * 100
cells_processed = (row_idx + 1) * len(headers)
rate = cells_processed / elapsed if elapsed > 0 else 0
row_time = time.time() - row_start
avg_row_time = elapsed / (row_idx + 1) if row_idx > 0 else row_time
# Calculate percentages
total_op_time = text_assign_time + row_access_time
if total_op_time > 0:
text_pct = (text_assign_time / total_op_time) * 100
access_pct = (row_access_time / total_op_time) * 100
self.logger.debug(f"_renderJsonTable: Progress {progress:.1f}% ({row_idx + 1}/{len(rows)} rows, {cells_processed}/{total_cells} cells) - Rate: {rate:.1f} cells/s, Elapsed: {elapsed:.2f}s, Avg row: {avg_row_time*1000:.2f}ms - Breakdown: text_assign={text_pct:.1f}%, row_access={access_pct:.1f}%")
else:
self.logger.debug(f"_renderJsonTable: Progress {progress:.1f}% ({row_idx + 1}/{len(rows)} rows, {cells_processed}/{total_cells} cells) - Rate: {rate:.1f} cells/s, Elapsed: {elapsed:.2f}s, Avg row: {avg_row_time*1000:.2f}ms")
# Log first few rows with detailed timing
if row_idx < 3:
row_time = time.time() - row_start
self.logger.debug(f"_renderJsonTable: Row {row_idx+1}/{len(rows)} rendered in {row_time*1000:.2f}ms ({len(headers)} cells)")
# Use fast XML-based table rendering
self._renderTableFastXml(doc, headers, rows, styles)
total_time = time.time() - table_start
rows_time = time.time() - rows_start
# Final KPI summary
total_op_time = text_assign_time + row_access_time
if total_op_time > 0:
self.logger.info(f"_renderJsonTable: Table rendering completed in {total_time:.2f}s ({len(rows)} rows × {len(headers)} cols = {total_cells} cells) - Rows: {rows_time:.2f}s - Breakdown: text_assign={text_assign_time:.2f}s ({text_assign_time/total_op_time*100:.1f}%), row_access={row_access_time:.2f}s ({row_access_time/total_op_time*100:.1f}%)")
else:
self.logger.info(f"_renderJsonTable: Table rendering completed in {total_time:.2f}s ({len(rows)} rows × {len(headers)} cols = {total_cells} cells) - Rows: {rows_time:.2f}s")
rate = totalCells / total_time if total_time > 0 else 0
self.logger.info(f"_renderJsonTable: Table completed in {total_time:.2f}s ({totalRows} rows x {totalCols} cols = {totalCells} cells) - Rate: {rate:.0f} cells/s")
except Exception as e:
self.logger.error(f"Error rendering table: {str(e)}", exc_info=True)
def _renderTableFastXml(self, doc: Document, headers: List[str], rows: List[List[Any]], styles: Dict[str, Any]) -> None:
"""
High-performance table rendering using direct XML manipulation.
This bypasses python-docx's slow high-level API and builds the table
XML structure directly using lxml, which is 100-1000x faster.
"""
import time
from docx.oxml.shared import OxmlElement, qn
from docx.oxml.ns import nsmap
from lxml import etree
create_start = time.time()
# Get the document body element
body = doc._body._body
# Create table element
tbl = OxmlElement('w:tbl')
# Add table properties
tblPr = OxmlElement('w:tblPr')
# Table style
border_style = styles.get("table_border", {}).get("style", "grid")
tblStyle = OxmlElement('w:tblStyle')
if border_style == "grid":
tblStyle.set(qn('w:val'), 'LightGridAccent1')
elif border_style == "horizontal_only":
tblStyle.set(qn('w:val'), 'LightListAccent1')
else:
tblStyle.set(qn('w:val'), 'LightList')
tblPr.append(tblStyle)
# Table width - auto
tblW = OxmlElement('w:tblW')
tblW.set(qn('w:type'), 'auto')
tblW.set(qn('w:w'), '0')
tblPr.append(tblW)
# Center alignment
jc = OxmlElement('w:jc')
jc.set(qn('w:val'), 'center')
tblPr.append(jc)
tbl.append(tblPr)
# Create table grid (column definitions)
tblGrid = OxmlElement('w:tblGrid')
for _ in range(len(headers)):
gridCol = OxmlElement('w:gridCol')
tblGrid.append(gridCol)
tbl.append(tblGrid)
self.logger.debug(f"_renderTableFastXml: Table structure created in {time.time() - create_start:.3f}s")
# Build all rows using fast XML
rows_start = time.time()
# Header row
headerRow = self._createTableRowXml(headers, isHeader=True)
tbl.append(headerRow)
header_time = time.time() - rows_start
self.logger.debug(f"_renderTableFastXml: Header row created in {header_time:.3f}s")
# Data rows - batch process for performance
data_start = time.time()
rowCount = len(rows)
for idx, rowData in enumerate(rows):
# Convert all cells to strings
cellTexts = [str(cell) if cell is not None else '' for cell in rowData]
# Pad if needed
while len(cellTexts) < len(headers):
cellTexts.append('')
row = self._createTableRowXml(cellTexts, isHeader=False)
tbl.append(row)
# Log progress every 10%
if rowCount > 100 and (idx + 1) % (rowCount // 10) == 0:
elapsed = time.time() - data_start
rate = (idx + 1) * len(headers) / elapsed if elapsed > 0 else 0
self.logger.debug(f"_renderTableFastXml: Progress {((idx + 1) / rowCount * 100):.0f}% ({idx + 1}/{rowCount} rows) - Rate: {rate:.0f} cells/s")
data_time = time.time() - data_start
# Append table to document body
body.append(tbl)
total_time = time.time() - create_start
totalCells = (rowCount + 1) * len(headers)
rate = totalCells / total_time if total_time > 0 else 0
self.logger.debug(f"_renderTableFastXml: All rows created in {data_time:.2f}s, total: {total_time:.2f}s, rate: {rate:.0f} cells/s")
def _createTableRowXml(self, cells: List[str], isHeader: bool = False) -> Any:
"""
Create a table row XML element with cells.
This is the core fast-path: builds the row XML directly without
going through python-docx's slow cell.text assignment.
"""
from docx.oxml.shared import OxmlElement, qn
tr = OxmlElement('w:tr')
# Row properties for header
if isHeader:
trPr = OxmlElement('w:trPr')
tblHeader = OxmlElement('w:tblHeader')
trPr.append(tblHeader)
tr.append(trPr)
for cellText in cells:
# Create cell
tc = OxmlElement('w:tc')
# Cell properties (minimal)
tcPr = OxmlElement('w:tcPr')
tcW = OxmlElement('w:tcW')
tcW.set(qn('w:type'), 'auto')
tcW.set(qn('w:w'), '0')
tcPr.append(tcW)
tc.append(tcPr)
# Paragraph with text
p = OxmlElement('w:p')
# Add run with text
r = OxmlElement('w:r')
# Bold for headers
if isHeader:
rPr = OxmlElement('w:rPr')
b = OxmlElement('w:b')
rPr.append(b)
r.append(rPr)
# Text element
t = OxmlElement('w:t')
# Preserve spaces if text starts/ends with whitespace
if cellText and (cellText[0] == ' ' or cellText[-1] == ' '):
t.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve')
t.text = cellText
r.append(t)
p.append(r)
tc.append(p)
tr.append(tc)
return tr
def _applyHorizontalBordersOnly(self, table) -> None:
"""Apply only horizontal borders to the table (no vertical borders)."""
try:

View file

@ -1490,7 +1490,13 @@ class JsonAnalyzer:
return '\n'.join(parts)
def _renderArrayV3(self, node: dict, depth: int, allocation: BudgetAllocation) -> str:
"""Render array - summary mode non-path arrays become <array>."""
"""Render array - summary mode non-path arrays become <array>.
For arrays ON the path with many children, show:
- First few children (for context)
- ... (N items omitted) ...
- Last N children (closest to cut point)
"""
indentStr = " " * depth
innerIndent = " " * (depth + 1)
@ -1516,15 +1522,68 @@ class JsonAnalyzer:
parts = [f"{keyPrefix}["]
for i, child in enumerate(children):
childRendered = self._renderNodeV3(child, depth + 1, allocation)
isLast = (i == len(children) - 1)
isTruncated = child.get('type') == 'truncated_value'
# For arrays ON PATH with many children (e.g. table rows):
# Show first 3, then "...", then last N children (from bottom up, using budget)
# This ensures we see context near the cut point
if isOnPath and len(children) > 10 and allocation.summary_mode:
showFirst = 3 # Show first 3 for context
# Calculate how many from the end we can show within budget
# Estimate ~80 chars per row for tables
estimatedCharsPerChild = 80
budgetForEnd = max(500, self.budgetLimit // 2) # Use half budget for end children
showLast = max(5, budgetForEnd // estimatedCharsPerChild)
showLast = min(showLast, len(children) - showFirst - 1) # Don't overlap with first
if isLast or isTruncated:
parts.append(f"{innerIndent}{childRendered}")
else:
# Create a modified allocation that includes these children on path
# so they don't get rendered as <array>
childrenToShow = set()
for i in range(min(showFirst, len(children))):
childrenToShow.add(id(children[i]))
startIdx = len(children) - showLast
for i in range(startIdx, len(children)):
childrenToShow.add(id(children[i]))
# Temporarily add children to path_node_ids
originalPathIds = allocation.path_node_ids
extendedPathIds = originalPathIds | childrenToShow
allocation.path_node_ids = extendedPathIds
# Render first N children
for i in range(min(showFirst, len(children))):
child = children[i]
childRendered = self._renderNodeV3(child, depth + 1, allocation)
parts.append(f"{innerIndent}{childRendered},")
# Add ellipsis if there are omitted items
omittedCount = len(children) - showFirst - showLast
if omittedCount > 0:
parts.append(f"{innerIndent}// ... ({omittedCount} items omitted) ...")
# Render last N children (closest to cut)
for i in range(startIdx, len(children)):
child = children[i]
childRendered = self._renderNodeV3(child, depth + 1, allocation)
isLast = (i == len(children) - 1)
isTruncated = child.get('type') == 'truncated_value'
if isLast or isTruncated:
parts.append(f"{innerIndent}{childRendered}")
else:
parts.append(f"{innerIndent}{childRendered},")
# Restore original path_node_ids
allocation.path_node_ids = originalPathIds
else:
# Standard rendering for small arrays or non-path arrays
for i, child in enumerate(children):
childRendered = self._renderNodeV3(child, depth + 1, allocation)
isLast = (i == len(children) - 1)
isTruncated = child.get('type') == 'truncated_value'
if isLast or isTruncated:
parts.append(f"{innerIndent}{childRendered}")
else:
parts.append(f"{innerIndent}{childRendered},")
if node.get('complete'):
parts.append(f"{indentStr}]")

View file

@ -0,0 +1,373 @@
#!/usr/bin/env python3
# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
JSON Continuation Context Test 14 - Tests getContexts() with a specific cut JSON from debug prompts.
Reads a real AI response that was cut and analyzes the continuation contexts.
"""
import asyncio
import json
import sys
import os
from typing import Dict, Any, Optional
# Add the gateway to path (go up 2 levels from tests/functional/)
_gateway_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
if _gateway_path not in sys.path:
sys.path.insert(0, _gateway_path)
# Import jsonContinuation
from modules.shared.jsonContinuation import getContexts
class JsonContinuationContextTester14:
def __init__(self):
self.testResults = {}
self.logBuffer = []
self.logFile = None
def _log(self, message: str):
"""Add message to log buffer."""
self.logBuffer.append(message)
print(message)
def _readDebugFile(self, fileName: str) -> Optional[str]:
"""Read a debug prompt file from local/debug/prompts/."""
try:
filePath = os.path.join(
os.path.dirname(__file__), "..", "..", "..", "local", "debug", "prompts",
fileName
)
with open(filePath, 'r', encoding='utf-8') as f:
return f.read()
except Exception as e:
self._log(f"Error reading file {fileName}: {e}")
return None
def _extractJsonFromResponse(self, content: str) -> str:
"""Extract JSON from response content (remove markdown code fences if present)."""
jsonContent = content.strip()
# Remove markdown code block markers
if jsonContent.startswith('```json'):
jsonContent = jsonContent[7:]
elif jsonContent.startswith('```'):
jsonContent = jsonContent[3:]
jsonContent = jsonContent.strip()
if jsonContent.endswith('```'):
jsonContent = jsonContent[:-3]
return jsonContent.strip()
async def testSpecificCutJson(self, fileName: str) -> Dict[str, Any]:
"""Test getContexts() with a specific cut JSON file."""
self._log("")
self._log("=" * 80)
self._log(f"TESTING CUT JSON FROM: {fileName}")
self._log("=" * 80)
# Read the file
content = self._readDebugFile(fileName)
if content is None:
return {"success": False, "error": f"Could not read file: {fileName}"}
# Extract JSON
jsonContent = self._extractJsonFromResponse(content)
self._log("")
self._log("=" * 80)
self._log("INPUT JSON (CUT)")
self._log("=" * 80)
self._log(f"Total length: {len(jsonContent)} characters")
self._log("")
# Show first and last parts
lines = jsonContent.split('\n')
if len(lines) > 40:
self._log("First 20 lines:")
for line in lines[:20]:
self._log(f" {line}")
self._log(f" ... ({len(lines) - 40} lines omitted) ...")
self._log("Last 20 lines:")
for line in lines[-20:]:
self._log(f" {line}")
else:
for line in lines:
self._log(f" {line}")
# Call getContexts()
self._log("")
self._log("=" * 80)
self._log("CALLING getContexts()")
self._log("=" * 80)
try:
contexts = getContexts(jsonContent)
except Exception as e:
self._log(f"ERROR calling getContexts(): {e}")
import traceback
self._log(traceback.format_exc())
return {"success": False, "error": str(e)}
# Log results
self._log("")
self._log("=" * 80)
self._log("RESULTS FROM getContexts()")
self._log("=" * 80)
# jsonParsingSuccess
self._log("")
self._log(f"jsonParsingSuccess: {contexts.jsonParsingSuccess}")
# overlapContext
self._log("")
self._log("=" * 80)
self._log("overlapContext:")
self._log("=" * 80)
self._log(f"Length: {len(contexts.overlapContext)} characters")
if contexts.overlapContext == "":
self._log(" (empty - JSON is complete, no cut point)")
else:
overlapLines = contexts.overlapContext.split('\n')
if len(overlapLines) > 20:
for line in overlapLines[:10]:
self._log(f" {line}")
self._log(f" ... ({len(overlapLines) - 20} lines omitted) ...")
for line in overlapLines[-10:]:
self._log(f" {line}")
else:
for line in overlapLines:
self._log(f" {line}")
# hierarchyContext
self._log("")
self._log("=" * 80)
self._log("hierarchyContext (for merging - should be exact input JSON):")
self._log("=" * 80)
self._log(f"Length: {len(contexts.hierarchyContext)} characters")
# Verify hierarchyContext equals input
if contexts.hierarchyContext == jsonContent:
self._log(" ✅ hierarchyContext == input JSON (CORRECT)")
else:
self._log(" ❌ hierarchyContext != input JSON (BUG!)")
self._log(f" Input length: {len(jsonContent)}, hierarchyContext length: {len(contexts.hierarchyContext)}")
# Show difference at the end
if len(contexts.hierarchyContext) > 0 and len(jsonContent) > 0:
minLen = min(len(contexts.hierarchyContext), len(jsonContent))
for i in range(minLen):
if contexts.hierarchyContext[i] != jsonContent[i]:
self._log(f" First difference at position {i}")
self._log(f" Input: ...{repr(jsonContent[max(0,i-20):i+20])}...")
self._log(f" Hierarchy: ...{repr(contexts.hierarchyContext[max(0,i-20):i+20])}...")
break
# hierarchyContextForPrompt
self._log("")
self._log("=" * 80)
self._log("hierarchyContextForPrompt (for AI prompt with budget/placeholders):")
self._log("=" * 80)
self._log(f"Length: {len(contexts.hierarchyContextForPrompt)} characters")
hierarchyPromptLines = contexts.hierarchyContextForPrompt.split('\n')
if len(hierarchyPromptLines) > 40:
for line in hierarchyPromptLines[:20]:
self._log(f" {line}")
self._log(f" ... ({len(hierarchyPromptLines) - 40} lines omitted) ...")
for line in hierarchyPromptLines[-20:]:
self._log(f" {line}")
else:
for line in hierarchyPromptLines:
self._log(f" {line}")
# completePart
self._log("")
self._log("=" * 80)
self._log("completePart (closed JSON for parsing):")
self._log("=" * 80)
self._log(f"Length: {len(contexts.completePart)} characters")
# Try to parse completePart
try:
parsed = json.loads(contexts.completePart)
self._log(" ✅ completePart is valid JSON")
self._log(f" Parsed type: {type(parsed).__name__}")
if isinstance(parsed, dict):
self._log(f" Keys: {list(parsed.keys())}")
elif isinstance(parsed, list):
self._log(f" List length: {len(parsed)}")
except json.JSONDecodeError as e:
self._log(f" ❌ completePart is NOT valid JSON: {e}")
completeLines = contexts.completePart.split('\n')
if len(completeLines) > 40:
self._log("")
self._log("First 20 lines:")
for line in completeLines[:20]:
self._log(f" {line}")
self._log(f" ... ({len(completeLines) - 40} lines omitted) ...")
self._log("Last 20 lines:")
for line in completeLines[-20:]:
self._log(f" {line}")
else:
for line in completeLines:
self._log(f" {line}")
# Summary
self._log("")
self._log("=" * 80)
self._log("SUMMARY")
self._log("=" * 80)
self._log(f" Input JSON length: {len(jsonContent)} chars")
self._log(f" jsonParsingSuccess: {contexts.jsonParsingSuccess}")
self._log(f" overlapContext length: {len(contexts.overlapContext)} chars")
self._log(f" overlapContext empty: {contexts.overlapContext == ''}")
self._log(f" hierarchyContext length: {len(contexts.hierarchyContext)} chars")
self._log(f" hierarchyContext == input: {contexts.hierarchyContext == jsonContent}")
self._log(f" hierarchyContextForPrompt length: {len(contexts.hierarchyContextForPrompt)} chars")
self._log(f" completePart length: {len(contexts.completePart)} chars")
return {
"success": True,
"fileName": fileName,
"inputLength": len(jsonContent),
"jsonParsingSuccess": contexts.jsonParsingSuccess,
"overlapContextLength": len(contexts.overlapContext),
"overlapContextEmpty": contexts.overlapContext == "",
"hierarchyContextLength": len(contexts.hierarchyContext),
"hierarchyContextEqualsInput": contexts.hierarchyContext == jsonContent,
"hierarchyContextForPromptLength": len(contexts.hierarchyContextForPrompt),
"completePartLength": len(contexts.completePart),
"contexts": {
"overlapContext": contexts.overlapContext,
"hierarchyContext": contexts.hierarchyContext[:500] + "..." if len(contexts.hierarchyContext) > 500 else contexts.hierarchyContext,
"hierarchyContextForPrompt": contexts.hierarchyContextForPrompt[:500] + "..." if len(contexts.hierarchyContextForPrompt) > 500 else contexts.hierarchyContextForPrompt,
"completePart": contexts.completePart[:500] + "..." if len(contexts.completePart) > 500 else contexts.completePart,
}
}
def _writeLogFile(self):
"""Write log buffer to file."""
logDir = os.path.join(os.path.dirname(__file__), "..", "..", "..", "local", "debug")
os.makedirs(logDir, exist_ok=True)
logFilePath = os.path.join(logDir, "test14_json_continuation_context_results.txt")
with open(logFilePath, 'w', encoding='utf-8') as f:
f.write('\n'.join(self.logBuffer))
self.logFile = logFilePath
print(f"\n📝 Detailed log written to: {logFilePath}")
async def runTest(self):
"""Run the complete test."""
self._log("=" * 80)
self._log("JSON CONTINUATION CONTEXT TEST 14")
self._log("=" * 80)
self._log("Testing getContexts() with specific cut JSON from debug prompts")
results = {}
# Test files to analyze
testFiles = [
# The first AI response (iteration 1) - this is the cut JSON
"20260106-173342-020-chapter_1_section_section_2_response.txt",
]
# Also try to find today's response files dynamically
debugDir = os.path.join(
os.path.dirname(__file__), "..", "..", "..", "local", "debug", "prompts"
)
if os.path.exists(debugDir):
for fileName in os.listdir(debugDir):
if "section_2_response" in fileName and fileName.endswith(".txt"):
if fileName not in testFiles:
testFiles.append(fileName)
# Limit to first 3 files
testFiles = testFiles[:3]
for fileName in testFiles:
try:
result = await self.testSpecificCutJson(fileName)
results[fileName] = result
except Exception as e:
import traceback
self._log(f"\n❌ Error testing {fileName}: {str(e)}")
self._log(traceback.format_exc())
results[fileName] = {
"success": False,
"error": str(e),
"traceback": traceback.format_exc()
}
# Write log file
self._writeLogFile()
# Summary
print("\n" + "=" * 80)
print("TEST SUMMARY")
print("=" * 80)
successCount = 0
for fileName, result in results.items():
if result.get("success"):
successCount += 1
hierarchyMatch = result.get("hierarchyContextEqualsInput", False)
overlapEmpty = result.get("overlapContextEmpty", False)
jsonSuccess = result.get("jsonParsingSuccess", False)
status = "" if hierarchyMatch else "⚠️"
print(f"{status} {fileName}")
print(f" hierarchyContext == input: {hierarchyMatch}")
print(f" overlapContext empty: {overlapEmpty}")
print(f" jsonParsingSuccess: {jsonSuccess}")
else:
print(f"{fileName}: {result.get('error', 'Unknown error')}")
print(f"\nResults: {successCount}/{len(results)} successful")
self.testResults = {
"success": successCount == len(results),
"totalFiles": len(results),
"successCount": successCount,
"results": results
}
return self.testResults
async def main():
"""Run JSON continuation context test 14."""
tester = JsonContinuationContextTester14()
results = await tester.runTest()
# Print final results as JSON for easy parsing
print("\n" + "=" * 80)
print("FINAL RESULTS (JSON)")
print("=" * 80)
# Create a simplified version for printing (contexts are too large)
printableResults = {
"success": results.get("success"),
"totalFiles": results.get("totalFiles"),
"successCount": results.get("successCount"),
"files": {}
}
for fileName, result in results.get("results", {}).items():
printableResults["files"][fileName] = {
"success": result.get("success"),
"inputLength": result.get("inputLength"),
"jsonParsingSuccess": result.get("jsonParsingSuccess"),
"overlapContextLength": result.get("overlapContextLength"),
"overlapContextEmpty": result.get("overlapContextEmpty"),
"hierarchyContextEqualsInput": result.get("hierarchyContextEqualsInput"),
"completePartLength": result.get("completePartLength"),
}
print(json.dumps(printableResults, indent=2, default=str))
if __name__ == "__main__":
asyncio.run(main())