@tracecode/harness 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/browser.cjs +16 -4
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +16 -4
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +0 -0
- package/dist/core.cjs +16 -4
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +9 -6
- package/dist/core.d.ts +9 -6
- package/dist/core.js +16 -4
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +305 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +305 -13
- package/dist/index.js.map +1 -1
- package/dist/internal/browser.d.cts +1 -1
- package/dist/internal/browser.d.ts +1 -1
- package/dist/javascript.cjs +227 -9
- package/dist/javascript.cjs.map +1 -1
- package/dist/javascript.d.cts +4 -4
- package/dist/javascript.d.ts +4 -4
- package/dist/javascript.js +227 -9
- package/dist/javascript.js.map +1 -1
- package/dist/python.cjs +62 -0
- package/dist/python.cjs.map +1 -1
- package/dist/python.d.cts +2 -2
- package/dist/python.d.ts +2 -2
- package/dist/python.js +62 -0
- package/dist/python.js.map +1 -1
- package/dist/{runtime-types-Dvgn07z9.d.cts → runtime-types--lBQ6rYu.d.cts} +1 -1
- package/dist/{runtime-types-C7d1LFbx.d.ts → runtime-types-DtaaAhHL.d.ts} +1 -1
- package/dist/{types-Bzr1Ohcf.d.cts → types-DwIYM3Ku.d.cts} +5 -2
- package/dist/{types-Bzr1Ohcf.d.ts → types-DwIYM3Ku.d.ts} +5 -2
- package/package.json +12 -10
- package/workers/javascript/javascript-worker.js +455 -31
- package/workers/python/generated-python-harness-snippets.js +1 -1
- package/workers/python/pyodide-worker.js +31 -0
- package/workers/python/runtime-core.js +235 -8
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
scope.__TRACECODE_PYTHON_HARNESS__ = Object.freeze({
|
|
12
12
|
PYTHON_CLASS_DEFINITIONS: "\nclass TreeNode:\n def __init__(self, val=0, left=None, right=None):\n self.val = val\n self.value = val\n self.left = left\n self.right = right\n def __getitem__(self, key):\n if key == 'val': return getattr(self, 'val', getattr(self, 'value', None))\n if key == 'value': return getattr(self, 'value', getattr(self, 'val', None))\n if key == 'left': return self.left\n if key == 'right': return self.right\n raise KeyError(key)\n def get(self, key, default=None):\n if key == 'val': return getattr(self, 'val', getattr(self, 'value', default))\n if key == 'value': return getattr(self, 'value', getattr(self, 'val', default))\n if key == 'left': return self.left\n if key == 'right': return self.right\n return default\n def __repr__(self):\n return f\"TreeNode({getattr(self, 'val', getattr(self, 'value', None))})\"\n\nclass ListNode:\n def __init__(self, val=0, next=None):\n self.val = val\n self.value = val\n self.next = next\n def __getitem__(self, key):\n if key == 'val': return getattr(self, 'val', getattr(self, 'value', None))\n if key == 'value': return getattr(self, 'value', getattr(self, 'val', None))\n if key == 'next': return self.next\n raise KeyError(key)\n def get(self, key, default=None):\n if key == 'val': return getattr(self, 'val', getattr(self, 'value', default))\n if key == 'value': return getattr(self, 'value', getattr(self, 'val', default))\n if key == 'next': return self.next\n return default\n def __repr__(self):\n return f\"ListNode({getattr(self, 'val', getattr(self, 'value', None))})\"\n",
|
|
13
13
|
PYTHON_CONVERSION_HELPERS: "\ndef _ensure_node_value_aliases(node):\n if node is None:\n return node\n try:\n has_val = hasattr(node, 'val')\n has_value = hasattr(node, 'value')\n if has_value and not has_val:\n try:\n setattr(node, 'val', getattr(node, 'value'))\n except Exception:\n pass\n elif has_val and not has_value:\n try:\n setattr(node, 'value', getattr(node, 'val'))\n except Exception:\n pass\n except Exception:\n pass\n return node\n\ndef _dict_to_tree(d):\n if d is None:\n return None\n if not isinstance(d, dict):\n return d\n if 'val' not in d and 'value' not in d:\n return d\n node = TreeNode(d.get('val', d.get('value', 0)))\n _ensure_node_value_aliases(node)\n node.left = _dict_to_tree(d.get('left'))\n node.right = _dict_to_tree(d.get('right'))\n return node\n\ndef _dict_to_list(d, _refs=None):\n if _refs is None:\n _refs = {}\n if d is None:\n return None\n if not isinstance(d, dict):\n return d\n if '__ref__' in d:\n return _refs.get(d.get('__ref__'))\n if 'val' not in d and 'value' not in d:\n return d\n node = ListNode(d.get('val', d.get('value', 0)))\n _ensure_node_value_aliases(node)\n node_id = d.get('__id__')\n if isinstance(node_id, str) and node_id:\n _refs[node_id] = node\n node.next = _dict_to_list(d.get('next'), _refs)\n return node\n",
|
|
14
|
-
PYTHON_TRACE_SERIALIZE_FUNCTION: "\n# Sentinel to mark skipped values (functions, etc.) - distinct from None\n_SKIP_SENTINEL = \"__TRACECODE_SKIP__\"\n_MAX_SERIALIZE_DEPTH = 48\n\ndef _serialize(obj, depth=0, node_refs=None):\n if node_refs is None:\n node_refs = {}\n if isinstance(obj, (bool, int, str, type(None))):\n return obj\n elif isinstance(obj, float):\n if not math.isfinite(obj):\n if math.isnan(obj):\n return \"NaN\"\n return \"Infinity\" if obj > 0 else \"-Infinity\"\n return obj\n if depth > _MAX_SERIALIZE_DEPTH:\n return \"<max depth>\"\n elif isinstance(obj, (list, tuple)):\n return [_serialize(x, depth + 1, node_refs) for x in obj]\n elif getattr(obj, '__class__', None) and getattr(obj.__class__, '__name__', '') == 'deque':\n return [_serialize(x, depth + 1, node_refs) for x in obj]\n elif isinstance(obj, dict):\n return {str(k): _serialize(v, depth + 1, node_refs) for k, v in obj.items()}\n elif isinstance(obj, set):\n # Use try/except for sorting to handle heterogeneous sets\n try:\n sorted_vals = sorted([_serialize(x, depth + 1, node_refs) for x in obj])\n except TypeError:\n sorted_vals = [_serialize(x, depth + 1, node_refs) for x in obj]\n return {\"__type__\": \"set\", \"values\": sorted_vals}\n elif (hasattr(obj, 'val') or hasattr(obj, 'value')) and (hasattr(obj, 'left') or hasattr(obj, 'right')):\n obj_ref = id(obj)\n if obj_ref in node_refs:\n return {\"__ref__\": node_refs[obj_ref]}\n node_id = f\"tree-{obj_ref}\"\n node_refs[obj_ref] = node_id\n result = {\n \"__type__\": \"TreeNode\",\n \"__id__\": node_id,\n \"val\": _serialize(getattr(obj, 'val', getattr(obj, 'value', None)), depth + 1, node_refs),\n }\n if hasattr(obj, 'left'):\n result[\"left\"] = _serialize(obj.left, depth + 1, node_refs)\n if hasattr(obj, 'right'):\n result[\"right\"] = _serialize(obj.right, depth + 1, node_refs)\n return result\n elif (hasattr(obj, 'val') or hasattr(obj, 'value')) and hasattr(obj, 'next'):\n obj_ref = id(obj)\n if obj_ref in node_refs:\n return {\"__ref__\": node_refs[obj_ref]}\n node_id = f\"list-{obj_ref}\"\n node_refs[obj_ref] = node_id\n result = {\n \"__type__\": \"ListNode\",\n \"__id__\": node_id,\n \"val\": _serialize(getattr(obj, 'val', getattr(obj, 'value', None)), depth + 1, node_refs),\n }\n result[\"next\"] = _serialize(obj.next, depth + 1, node_refs)\n return result\n elif callable(obj):\n # Skip functions entirely - return sentinel\n return _SKIP_SENTINEL\n else:\n repr_str = repr(obj)\n # Filter out function-like representations (e.g., <function foo at 0x...>)\n if repr_str.startswith('<') and repr_str.endswith('>'):\n return _SKIP_SENTINEL\n return repr_str\n",
|
|
14
|
+
PYTHON_TRACE_SERIALIZE_FUNCTION: "\n# Sentinel to mark skipped values (functions, etc.) - distinct from None\n_SKIP_SENTINEL = \"__TRACECODE_SKIP__\"\n_MAX_SERIALIZE_DEPTH = 48\n_MAX_OBJECT_FIELDS = 32\n\ndef _serialize(obj, depth=0, node_refs=None):\n if node_refs is None:\n node_refs = {}\n if isinstance(obj, (bool, int, str, type(None))):\n return obj\n elif isinstance(obj, float):\n if not math.isfinite(obj):\n if math.isnan(obj):\n return \"NaN\"\n return \"Infinity\" if obj > 0 else \"-Infinity\"\n return obj\n if depth > _MAX_SERIALIZE_DEPTH:\n return \"<max depth>\"\n elif isinstance(obj, (list, tuple)):\n return [_serialize(x, depth + 1, node_refs) for x in obj]\n elif getattr(obj, '__class__', None) and getattr(obj.__class__, '__name__', '') == 'deque':\n return [_serialize(x, depth + 1, node_refs) for x in obj]\n elif isinstance(obj, dict):\n return {str(k): _serialize(v, depth + 1, node_refs) for k, v in obj.items()}\n elif isinstance(obj, set):\n # Use try/except for sorting to handle heterogeneous sets\n try:\n sorted_vals = sorted([_serialize(x, depth + 1, node_refs) for x in obj])\n except TypeError:\n sorted_vals = [_serialize(x, depth + 1, node_refs) for x in obj]\n return {\"__type__\": \"set\", \"values\": sorted_vals}\n elif (hasattr(obj, 'val') or hasattr(obj, 'value')) and (hasattr(obj, 'left') or hasattr(obj, 'right')):\n obj_ref = id(obj)\n if obj_ref in node_refs:\n return {\"__ref__\": node_refs[obj_ref]}\n node_id = f\"tree-{obj_ref}\"\n node_refs[obj_ref] = node_id\n result = {\n \"__type__\": \"TreeNode\",\n \"__id__\": node_id,\n \"val\": _serialize(getattr(obj, 'val', getattr(obj, 'value', None)), depth + 1, node_refs),\n }\n if hasattr(obj, 'left'):\n result[\"left\"] = _serialize(obj.left, depth + 1, node_refs)\n if hasattr(obj, 'right'):\n result[\"right\"] = _serialize(obj.right, depth + 1, node_refs)\n return result\n elif (hasattr(obj, 'val') or hasattr(obj, 'value')) and hasattr(obj, 'next'):\n obj_ref = id(obj)\n if obj_ref in node_refs:\n return {\"__ref__\": node_refs[obj_ref]}\n node_id = f\"list-{obj_ref}\"\n node_refs[obj_ref] = node_id\n result = {\n \"__type__\": \"ListNode\",\n \"__id__\": node_id,\n \"val\": _serialize(getattr(obj, 'val', getattr(obj, 'value', None)), depth + 1, node_refs),\n }\n result[\"next\"] = _serialize(obj.next, depth + 1, node_refs)\n return result\n elif hasattr(obj, '__dict__'):\n obj_ref = id(obj)\n if obj_ref in node_refs:\n return {\"__ref__\": node_refs[obj_ref]}\n node_id = f\"object-{obj_ref}\"\n node_refs[obj_ref] = node_id\n class_name = getattr(getattr(obj, '__class__', None), '__name__', 'object')\n result = {\n \"__type__\": \"object\",\n \"__class__\": class_name,\n \"__id__\": node_id,\n }\n try:\n raw_fields = getattr(obj, '__dict__', None)\n except Exception:\n raw_fields = None\n if isinstance(raw_fields, dict):\n added = 0\n for key, value in raw_fields.items():\n key_str = str(key)\n if key_str.startswith('_'):\n continue\n if callable(value):\n continue\n result[key_str] = _serialize(value, depth + 1, node_refs)\n added += 1\n if added >= _MAX_OBJECT_FIELDS:\n result[\"__truncated__\"] = True\n break\n return result\n elif callable(obj):\n # Skip functions entirely - return sentinel\n return _SKIP_SENTINEL\n else:\n repr_str = repr(obj)\n # Filter out function-like representations (e.g., <function foo at 0x...>)\n if repr_str.startswith('<') and repr_str.endswith('>'):\n return _SKIP_SENTINEL\n return repr_str\n",
|
|
15
15
|
PYTHON_EXECUTE_SERIALIZE_FUNCTION: "\n_MAX_SERIALIZE_DEPTH = 48\n\ndef _serialize(obj, depth=0):\n if isinstance(obj, (bool, int, str, type(None))):\n return obj\n elif isinstance(obj, float):\n if not math.isfinite(obj):\n if math.isnan(obj):\n return \"NaN\"\n return \"Infinity\" if obj > 0 else \"-Infinity\"\n return obj\n if depth > _MAX_SERIALIZE_DEPTH:\n return \"<max depth>\"\n elif isinstance(obj, (list, tuple)):\n return [_serialize(x, depth + 1) for x in obj]\n elif getattr(obj, '__class__', None) and getattr(obj.__class__, '__name__', '') == 'deque':\n return [_serialize(x, depth + 1) for x in obj]\n elif isinstance(obj, dict):\n return {str(k): _serialize(v, depth + 1) for k, v in obj.items()}\n elif isinstance(obj, set):\n try:\n return {\"__type__\": \"set\", \"values\": sorted([_serialize(x, depth + 1) for x in obj])}\n except TypeError:\n return {\"__type__\": \"set\", \"values\": [_serialize(x, depth + 1) for x in obj]}\n elif (hasattr(obj, 'val') or hasattr(obj, 'value')) and (hasattr(obj, 'left') or hasattr(obj, 'right')):\n result = {\"__type__\": \"TreeNode\", \"val\": _serialize(getattr(obj, 'val', getattr(obj, 'value', None)), depth + 1)}\n if hasattr(obj, 'left'):\n result[\"left\"] = _serialize(obj.left, depth + 1)\n if hasattr(obj, 'right'):\n result[\"right\"] = _serialize(obj.right, depth + 1)\n return result\n elif (hasattr(obj, 'val') or hasattr(obj, 'value')) and hasattr(obj, 'next'):\n result = {\"__type__\": \"ListNode\", \"val\": _serialize(getattr(obj, 'val', getattr(obj, 'value', None)), depth + 1)}\n result[\"next\"] = _serialize(obj.next, depth + 1)\n return result\n elif callable(obj):\n return None\n else:\n repr_str = repr(obj)\n if repr_str.startswith('<') and repr_str.endswith('>'):\n return None\n return repr_str\n",
|
|
16
16
|
PYTHON_SERIALIZE_FUNCTION: "\n_MAX_SERIALIZE_DEPTH = 48\n\ndef _serialize(obj, depth=0):\n if isinstance(obj, (bool, int, str, type(None))):\n return obj\n elif isinstance(obj, float):\n if not math.isfinite(obj):\n if math.isnan(obj):\n return \"NaN\"\n return \"Infinity\" if obj > 0 else \"-Infinity\"\n return obj\n if depth > _MAX_SERIALIZE_DEPTH:\n return \"<max depth>\"\n elif isinstance(obj, (list, tuple)):\n return [_serialize(x, depth + 1) for x in obj]\n elif getattr(obj, '__class__', None) and getattr(obj.__class__, '__name__', '') == 'deque':\n return [_serialize(x, depth + 1) for x in obj]\n elif isinstance(obj, dict):\n return {str(k): _serialize(v, depth + 1) for k, v in obj.items()}\n elif isinstance(obj, set):\n try:\n return {\"__type__\": \"set\", \"values\": sorted([_serialize(x, depth + 1) for x in obj])}\n except TypeError:\n return {\"__type__\": \"set\", \"values\": [_serialize(x, depth + 1) for x in obj]}\n elif (hasattr(obj, 'val') or hasattr(obj, 'value')) and (hasattr(obj, 'left') or hasattr(obj, 'right')):\n result = {\"__type__\": \"TreeNode\", \"val\": _serialize(getattr(obj, 'val', getattr(obj, 'value', None)), depth + 1)}\n if hasattr(obj, 'left'):\n result[\"left\"] = _serialize(obj.left, depth + 1)\n if hasattr(obj, 'right'):\n result[\"right\"] = _serialize(obj.right, depth + 1)\n return result\n elif (hasattr(obj, 'val') or hasattr(obj, 'value')) and hasattr(obj, 'next'):\n result = {\"__type__\": \"ListNode\", \"val\": _serialize(getattr(obj, 'val', getattr(obj, 'value', None)), depth + 1)}\n result[\"next\"] = _serialize(obj.next, depth + 1)\n return result\n elif callable(obj):\n return None\n else:\n repr_str = repr(obj)\n if repr_str.startswith('<') and repr_str.endswith('>'):\n return None\n return repr_str\n",
|
|
17
17
|
});
|
|
@@ -232,6 +232,7 @@ const PYTHON_TRACE_SERIALIZE_FUNCTION_SNIPPET = resolveSharedPythonSnippet(
|
|
|
232
232
|
# Sentinel to mark skipped values (functions, etc.) - distinct from None
|
|
233
233
|
_SKIP_SENTINEL = "__TRACECODE_SKIP__"
|
|
234
234
|
_MAX_SERIALIZE_DEPTH = 48
|
|
235
|
+
_MAX_OBJECT_FIELDS = 32
|
|
235
236
|
|
|
236
237
|
def _serialize(obj, depth=0, node_refs=None):
|
|
237
238
|
if node_refs is None:
|
|
@@ -288,6 +289,36 @@ def _serialize(obj, depth=0, node_refs=None):
|
|
|
288
289
|
}
|
|
289
290
|
result["next"] = _serialize(obj.next, depth + 1, node_refs)
|
|
290
291
|
return result
|
|
292
|
+
elif hasattr(obj, '__dict__'):
|
|
293
|
+
obj_ref = id(obj)
|
|
294
|
+
if obj_ref in node_refs:
|
|
295
|
+
return {"__ref__": node_refs[obj_ref]}
|
|
296
|
+
node_id = f"object-{obj_ref}"
|
|
297
|
+
node_refs[obj_ref] = node_id
|
|
298
|
+
class_name = getattr(getattr(obj, '__class__', None), '__name__', 'object')
|
|
299
|
+
result = {
|
|
300
|
+
"__type__": "object",
|
|
301
|
+
"__class__": class_name,
|
|
302
|
+
"__id__": node_id,
|
|
303
|
+
}
|
|
304
|
+
try:
|
|
305
|
+
raw_fields = getattr(obj, '__dict__', None)
|
|
306
|
+
except Exception:
|
|
307
|
+
raw_fields = None
|
|
308
|
+
if isinstance(raw_fields, dict):
|
|
309
|
+
added = 0
|
|
310
|
+
for key, value in raw_fields.items():
|
|
311
|
+
key_str = str(key)
|
|
312
|
+
if key_str.startswith('_'):
|
|
313
|
+
continue
|
|
314
|
+
if callable(value):
|
|
315
|
+
continue
|
|
316
|
+
result[key_str] = _serialize(value, depth + 1, node_refs)
|
|
317
|
+
added += 1
|
|
318
|
+
if added >= _MAX_OBJECT_FIELDS:
|
|
319
|
+
result["__truncated__"] = True
|
|
320
|
+
break
|
|
321
|
+
return result
|
|
291
322
|
elif callable(obj):
|
|
292
323
|
# Skip functions entirely - return sentinel
|
|
293
324
|
return _SKIP_SENTINEL
|
|
@@ -38,6 +38,8 @@ _original_print = _builtins.print
|
|
|
38
38
|
_target_function = "${targetFunction}"
|
|
39
39
|
_MIRROR_PRINT_TO_WORKER_CONSOLE = ${mirrorPrintToConsole ? 'True' : 'False'}
|
|
40
40
|
_MINIMAL_TRACE = ${minimalTrace ? 'True' : 'False'}
|
|
41
|
+
_SCRIPT_MODE = ${functionName ? 'False' : 'True'}
|
|
42
|
+
_TRACE_INPUT_NAMES = set(${JSON.stringify(Object.keys(inputs))})
|
|
41
43
|
|
|
42
44
|
class _InfiniteLoopDetected(Exception):
|
|
43
45
|
pass
|
|
@@ -71,6 +73,7 @@ _internal_funcs = {'_serialize', '_tracer', '_custom_print', '_dict_to_tree', '_
|
|
|
71
73
|
_internal_locals = {
|
|
72
74
|
'_trace_data', '_console_output', '_original_print', '_target_function',
|
|
73
75
|
'_MIRROR_PRINT_TO_WORKER_CONSOLE', '_MINIMAL_TRACE', '_SKIP_SENTINEL',
|
|
76
|
+
'_SCRIPT_MODE', '_TRACE_INPUT_NAMES', '_SCRIPT_PRE_USER_GLOBALS',
|
|
74
77
|
'_call_stack', '_pending_accesses', '_prev_hashmap_snapshots', '_TRACE_MUTATING_METHODS', '_internal_funcs', '_internal_locals', '_max_trace_steps',
|
|
75
78
|
'_trace_limit_exceeded', '_timeout_reason', '_total_line_events', '_max_line_events',
|
|
76
79
|
'_line_hit_count', '_max_single_line_hits', '_infinite_loop_line',
|
|
@@ -134,18 +137,235 @@ def _snapshot_call_stack():
|
|
|
134
137
|
return []
|
|
135
138
|
return [f.copy() for f in _call_stack]
|
|
136
139
|
|
|
137
|
-
def
|
|
140
|
+
def _is_serialized_ref(value):
|
|
141
|
+
return isinstance(value, dict) and len(value) == 1 and isinstance(value.get('__ref__'), str)
|
|
142
|
+
|
|
143
|
+
def _is_serialized_list_node(value):
|
|
144
|
+
return isinstance(value, dict) and value.get('__type__') == 'ListNode' and isinstance(value.get('__id__'), str)
|
|
145
|
+
|
|
146
|
+
def _serialized_list_root_id(value):
|
|
147
|
+
if _is_serialized_list_node(value):
|
|
148
|
+
return value.get('__id__')
|
|
149
|
+
if _is_serialized_ref(value):
|
|
150
|
+
return value.get('__ref__')
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
def _collect_serialized_list_component(value, node_ids=None, ref_ids=None, seen=None):
|
|
154
|
+
if node_ids is None:
|
|
155
|
+
node_ids = set()
|
|
156
|
+
if ref_ids is None:
|
|
157
|
+
ref_ids = set()
|
|
158
|
+
if seen is None:
|
|
159
|
+
seen = set()
|
|
160
|
+
|
|
161
|
+
if _is_serialized_ref(value):
|
|
162
|
+
ref_ids.add(value.get('__ref__'))
|
|
163
|
+
return (node_ids, ref_ids)
|
|
164
|
+
|
|
165
|
+
if not _is_serialized_list_node(value):
|
|
166
|
+
return (node_ids, ref_ids)
|
|
167
|
+
|
|
168
|
+
marker = id(value)
|
|
169
|
+
if marker in seen:
|
|
170
|
+
return (node_ids, ref_ids)
|
|
171
|
+
seen.add(marker)
|
|
172
|
+
|
|
173
|
+
node_id = value.get('__id__')
|
|
174
|
+
if isinstance(node_id, str):
|
|
175
|
+
node_ids.add(node_id)
|
|
176
|
+
|
|
177
|
+
for field_name in ('next', 'prev'):
|
|
178
|
+
if field_name in value:
|
|
179
|
+
_collect_serialized_list_component(value.get(field_name), node_ids, ref_ids, seen)
|
|
180
|
+
|
|
181
|
+
return (node_ids, ref_ids)
|
|
182
|
+
|
|
183
|
+
def _clone_serialized_value(value):
|
|
184
|
+
if isinstance(value, dict):
|
|
185
|
+
return {key: _clone_serialized_value(nested) for key, nested in value.items()}
|
|
186
|
+
if isinstance(value, list):
|
|
187
|
+
return [_clone_serialized_value(item) for item in value]
|
|
188
|
+
return value
|
|
189
|
+
|
|
190
|
+
def _inline_component_list_refs(value, root_payloads, seen_root_ids=None):
|
|
191
|
+
if seen_root_ids is None:
|
|
192
|
+
seen_root_ids = set()
|
|
193
|
+
|
|
194
|
+
if _is_serialized_ref(value):
|
|
195
|
+
ref_id = value.get('__ref__')
|
|
196
|
+
if not isinstance(ref_id, str):
|
|
197
|
+
return value
|
|
198
|
+
target = root_payloads.get(ref_id)
|
|
199
|
+
if target is None or ref_id in seen_root_ids:
|
|
200
|
+
return value
|
|
201
|
+
next_seen = set(seen_root_ids)
|
|
202
|
+
next_seen.add(ref_id)
|
|
203
|
+
return _inline_component_list_refs(_clone_serialized_value(target), root_payloads, next_seen)
|
|
204
|
+
|
|
205
|
+
if isinstance(value, list):
|
|
206
|
+
return [_inline_component_list_refs(item, root_payloads, seen_root_ids) for item in value]
|
|
207
|
+
|
|
208
|
+
if not isinstance(value, dict):
|
|
209
|
+
return value
|
|
210
|
+
|
|
211
|
+
out = {}
|
|
212
|
+
next_seen = set(seen_root_ids)
|
|
213
|
+
value_id = value.get('__id__')
|
|
214
|
+
if isinstance(value_id, str):
|
|
215
|
+
next_seen.add(value_id)
|
|
216
|
+
|
|
217
|
+
for key, nested in value.items():
|
|
218
|
+
out[key] = _inline_component_list_refs(nested, root_payloads, next_seen)
|
|
219
|
+
return out
|
|
220
|
+
|
|
221
|
+
def _normalize_top_level_linked_list_locals(local_vars):
|
|
222
|
+
if not isinstance(local_vars, dict) or len(local_vars) < 2:
|
|
223
|
+
return local_vars
|
|
224
|
+
|
|
225
|
+
ordered_names = list(local_vars.keys())
|
|
226
|
+
candidates = []
|
|
227
|
+
|
|
228
|
+
for index, name in enumerate(ordered_names):
|
|
229
|
+
value = local_vars.get(name)
|
|
230
|
+
root_id = _serialized_list_root_id(value)
|
|
231
|
+
if not isinstance(root_id, str):
|
|
232
|
+
continue
|
|
233
|
+
node_ids, ref_ids = _collect_serialized_list_component(value)
|
|
234
|
+
all_ids = set(node_ids) | set(ref_ids)
|
|
235
|
+
if not all_ids:
|
|
236
|
+
all_ids.add(root_id)
|
|
237
|
+
candidates.append({
|
|
238
|
+
'name': name,
|
|
239
|
+
'index': index,
|
|
240
|
+
'value': value,
|
|
241
|
+
'root_id': root_id,
|
|
242
|
+
'is_ref_only': _is_serialized_ref(value),
|
|
243
|
+
'node_ids': node_ids,
|
|
244
|
+
'ref_ids': ref_ids,
|
|
245
|
+
'all_ids': all_ids,
|
|
246
|
+
'incoming': 0,
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
if len(candidates) < 2:
|
|
250
|
+
return local_vars
|
|
251
|
+
|
|
252
|
+
parent = list(range(len(candidates)))
|
|
253
|
+
|
|
254
|
+
def _find(i):
|
|
255
|
+
while parent[i] != i:
|
|
256
|
+
parent[i] = parent[parent[i]]
|
|
257
|
+
i = parent[i]
|
|
258
|
+
return i
|
|
259
|
+
|
|
260
|
+
def _union(a, b):
|
|
261
|
+
ra = _find(a)
|
|
262
|
+
rb = _find(b)
|
|
263
|
+
if ra != rb:
|
|
264
|
+
parent[rb] = ra
|
|
265
|
+
|
|
266
|
+
for i in range(len(candidates)):
|
|
267
|
+
left = candidates[i]
|
|
268
|
+
for j in range(i + 1, len(candidates)):
|
|
269
|
+
right = candidates[j]
|
|
270
|
+
if left['all_ids'].intersection(right['all_ids']):
|
|
271
|
+
_union(i, j)
|
|
272
|
+
if left['root_id'] in right['all_ids'] or right['root_id'] in left['all_ids']:
|
|
273
|
+
_union(i, j)
|
|
274
|
+
|
|
275
|
+
for i in range(len(candidates)):
|
|
276
|
+
left = candidates[i]
|
|
277
|
+
for j in range(len(candidates)):
|
|
278
|
+
if i == j:
|
|
279
|
+
continue
|
|
280
|
+
right = candidates[j]
|
|
281
|
+
if left['root_id'] in right['all_ids'] and left['root_id'] != right['root_id']:
|
|
282
|
+
left['incoming'] += 1
|
|
283
|
+
|
|
284
|
+
groups = {}
|
|
285
|
+
for index, candidate in enumerate(candidates):
|
|
286
|
+
groups.setdefault(_find(index), []).append(candidate)
|
|
287
|
+
|
|
288
|
+
for group in groups.values():
|
|
289
|
+
if len(group) < 2:
|
|
290
|
+
continue
|
|
291
|
+
|
|
292
|
+
root_payloads = {}
|
|
293
|
+
for candidate in group:
|
|
294
|
+
root_id = candidate.get('root_id')
|
|
295
|
+
value = candidate.get('value')
|
|
296
|
+
if isinstance(root_id, str) and _is_serialized_list_node(value):
|
|
297
|
+
root_payloads[root_id] = _clone_serialized_value(value)
|
|
298
|
+
|
|
299
|
+
canonical = max(
|
|
300
|
+
group,
|
|
301
|
+
key=lambda candidate: (
|
|
302
|
+
0 if candidate['is_ref_only'] else 1,
|
|
303
|
+
1 if candidate['incoming'] == 0 else 0,
|
|
304
|
+
len(candidate['node_ids']) + len(candidate['ref_ids']),
|
|
305
|
+
-candidate['index'],
|
|
306
|
+
),
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
if _is_serialized_list_node(canonical.get('value')):
|
|
310
|
+
local_vars[canonical['name']] = _inline_component_list_refs(
|
|
311
|
+
_clone_serialized_value(canonical['value']),
|
|
312
|
+
root_payloads,
|
|
313
|
+
set([canonical.get('root_id')]) if isinstance(canonical.get('root_id'), str) else set(),
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
for candidate in group:
|
|
317
|
+
if candidate is canonical:
|
|
318
|
+
continue
|
|
319
|
+
root_id = candidate.get('root_id')
|
|
320
|
+
if isinstance(root_id, str):
|
|
321
|
+
local_vars[candidate['name']] = {'__ref__': root_id}
|
|
322
|
+
|
|
323
|
+
return local_vars
|
|
324
|
+
|
|
325
|
+
_SCRIPT_PRE_USER_GLOBALS = set()
|
|
326
|
+
|
|
327
|
+
def _snapshot_local_sources(frame):
|
|
138
328
|
if _MINIMAL_TRACE:
|
|
139
329
|
return {}
|
|
330
|
+
try:
|
|
331
|
+
func_name = frame.f_code.co_name
|
|
332
|
+
sources = {}
|
|
333
|
+
for name in frame.f_locals.keys():
|
|
334
|
+
if name in _internal_locals or name == '_' or name.startswith('__'):
|
|
335
|
+
continue
|
|
336
|
+
if _SCRIPT_MODE and func_name == '<module>':
|
|
337
|
+
if name in _TRACE_INPUT_NAMES:
|
|
338
|
+
sources[name] = 'user-input'
|
|
339
|
+
elif name in _SCRIPT_PRE_USER_GLOBALS:
|
|
340
|
+
sources[name] = 'harness-prelude'
|
|
341
|
+
else:
|
|
342
|
+
sources[name] = 'user'
|
|
343
|
+
else:
|
|
344
|
+
sources[name] = 'user'
|
|
345
|
+
return sources
|
|
346
|
+
except Exception:
|
|
347
|
+
return {}
|
|
348
|
+
|
|
349
|
+
def _snapshot_locals(frame, with_sources=False):
|
|
350
|
+
if _MINIMAL_TRACE:
|
|
351
|
+
return ({}, {}) if with_sources else {}
|
|
140
352
|
try:
|
|
141
353
|
_node_refs = {}
|
|
142
|
-
|
|
354
|
+
_sources = _snapshot_local_sources(frame)
|
|
355
|
+
local_vars = {
|
|
143
356
|
k: v
|
|
144
|
-
for k, v in (
|
|
357
|
+
for k, v in (
|
|
358
|
+
(k, _serialize(v, 0, _node_refs))
|
|
359
|
+
for k, v in frame.f_locals.items()
|
|
360
|
+
if k not in _internal_locals and k != '_' and not k.startswith('__') and _sources.get(k) != 'harness-prelude'
|
|
361
|
+
)
|
|
145
362
|
if v != _SKIP_SENTINEL
|
|
146
363
|
}
|
|
364
|
+
local_vars = _normalize_top_level_linked_list_locals(local_vars)
|
|
365
|
+
local_sources = {name: _sources.get(name, 'user') for name in local_vars.keys()}
|
|
366
|
+
return (local_vars, local_sources) if with_sources else local_vars
|
|
147
367
|
except Exception:
|
|
148
|
-
return {}
|
|
368
|
+
return ({}, {}) if with_sources else {}
|
|
149
369
|
|
|
150
370
|
def __tracecode_record_access(frame, event):
|
|
151
371
|
if frame is None or not isinstance(event, dict):
|
|
@@ -681,12 +901,13 @@ def _tracer(frame, event, arg):
|
|
|
681
901
|
_trace_limit_exceeded = True
|
|
682
902
|
_timeout_reason = 'single-line-limit'
|
|
683
903
|
_infinite_loop_line = frame.f_lineno
|
|
684
|
-
local_vars = _snapshot_locals(frame)
|
|
904
|
+
local_vars, local_sources = _snapshot_locals(frame, with_sources=True)
|
|
685
905
|
local_vars['timeoutReason'] = _timeout_reason
|
|
686
906
|
_trace_data.append({
|
|
687
907
|
'line': frame.f_lineno,
|
|
688
908
|
'event': 'timeout',
|
|
689
909
|
'variables': local_vars,
|
|
910
|
+
'variableSources': local_sources,
|
|
690
911
|
'function': func_name,
|
|
691
912
|
'callStack': _snapshot_call_stack(),
|
|
692
913
|
'stdoutLineCount': len(_console_output),
|
|
@@ -715,7 +936,7 @@ def _tracer(frame, event, arg):
|
|
|
715
936
|
raise _InfiniteLoopDetected(f"Exceeded {_max_trace_steps} trace steps")
|
|
716
937
|
|
|
717
938
|
if event == 'call':
|
|
718
|
-
local_vars = _snapshot_locals(frame)
|
|
939
|
+
local_vars, local_sources = _snapshot_locals(frame, with_sources=True)
|
|
719
940
|
if func_name != '<module>':
|
|
720
941
|
_call_stack.append({
|
|
721
942
|
'function': func_name,
|
|
@@ -728,6 +949,7 @@ def _tracer(frame, event, arg):
|
|
|
728
949
|
'line': frame.f_lineno,
|
|
729
950
|
'event': 'call',
|
|
730
951
|
'variables': local_vars,
|
|
952
|
+
'variableSources': local_sources,
|
|
731
953
|
'function': func_name,
|
|
732
954
|
'callStack': _snapshot_call_stack(),
|
|
733
955
|
'stdoutLineCount': len(_console_output),
|
|
@@ -737,11 +959,12 @@ def _tracer(frame, event, arg):
|
|
|
737
959
|
elif event == 'line':
|
|
738
960
|
if _MINIMAL_TRACE:
|
|
739
961
|
return _tracer
|
|
740
|
-
local_vars = _snapshot_locals(frame)
|
|
962
|
+
local_vars, local_sources = _snapshot_locals(frame, with_sources=True)
|
|
741
963
|
_trace_data.append({
|
|
742
964
|
'line': frame.f_lineno,
|
|
743
965
|
'event': event,
|
|
744
966
|
'variables': local_vars,
|
|
967
|
+
'variableSources': local_sources,
|
|
745
968
|
'function': func_name,
|
|
746
969
|
'callStack': _snapshot_call_stack(),
|
|
747
970
|
'stdoutLineCount': len(_console_output),
|
|
@@ -750,11 +973,12 @@ def _tracer(frame, event, arg):
|
|
|
750
973
|
})
|
|
751
974
|
elif event == 'return':
|
|
752
975
|
if not _MINIMAL_TRACE:
|
|
753
|
-
local_vars = _snapshot_locals(frame)
|
|
976
|
+
local_vars, local_sources = _snapshot_locals(frame, with_sources=True)
|
|
754
977
|
_trace_data.append({
|
|
755
978
|
'line': frame.f_lineno,
|
|
756
979
|
'event': 'return',
|
|
757
980
|
'variables': local_vars,
|
|
981
|
+
'variableSources': local_sources,
|
|
758
982
|
'function': func_name,
|
|
759
983
|
'returnValue': _serialize(arg),
|
|
760
984
|
'callStack': _snapshot_call_stack(),
|
|
@@ -893,6 +1117,9 @@ ${listConversions}
|
|
|
893
1117
|
|
|
894
1118
|
${preloadUserDefinitions}
|
|
895
1119
|
|
|
1120
|
+
if _SCRIPT_MODE:
|
|
1121
|
+
_SCRIPT_PRE_USER_GLOBALS = set(globals().keys()) - _TRACE_INPUT_NAMES
|
|
1122
|
+
|
|
896
1123
|
sys.settrace(_tracer)
|
|
897
1124
|
_trace_failed = False
|
|
898
1125
|
|