Chapter 16 - Type System Deep Dive
Chapter 16: Testing and Debugging#
Testing and debugging in Jac requires unique approaches due to its object-spatial nature, graph-based architecture, and scale-agnostic features. This chapter explores comprehensive strategies for ensuring your Jac applications work correctly from development through production.
16.1 Testing Framework#
Built-in test
Blocks#
Jac provides first-class support for testing through the test
keyword, making tests an integral part of the language:
// Basic test structure
test "basic arithmetic operations" {
assert 2 + 2 == 4;
assert 10 - 5 == 5;
assert 3 * 4 == 12;
assert 15 / 3 == 5.0;
}
// Testing with setup and teardown
test "user creation and validation" {
// Setup
let user = User(name="Alice", email="alice@example.com");
// Test assertions
assert user.name == "Alice";
assert user.email == "alice@example.com";
assert user.is_valid();
// Cleanup happens automatically when test block ends
}
// Parameterized testing
test "edge cases for division" {
let test_cases = [
(10, 2, 5.0),
(7, 2, 3.5),
(0, 5, 0.0),
(-10, 2, -5.0)
];
for (a, b, expected) in test_cases {
assert divide(a, b) == expected;
}
// Test error cases
assert_raises(ZeroDivisionError) {
divide(10, 0);
};
}
// Test utilities
can assert_raises(exception_type: type) -> callable {
can wrapper(code_block: callable) {
try {
code_block();
assert False, f"Expected {exception_type.__name__} but no exception raised";
} except exception_type {
// Expected exception was raised
pass;
} except Exception as e {
assert False, f"Expected {exception_type.__name__} but got {type(e).__name__}";
}
}
return wrapper;
}
can assert_almost_equal(a: float, b: float, tolerance: float = 0.0001) {
assert abs(a - b) < tolerance, f"{a} != {b} within tolerance {tolerance}";
}
Testing Graph Structures#
Testing nodes, edges, and their relationships requires specialized approaches:
// Graph structure test utilities
obj GraphTestCase {
has root_node: node;
can setup {
self.root_node = create_test_root();
}
can teardown {
// Clean up test graph
clean_graph(self.root_node);
}
can assert_connected(source: node, target: node, edge_type: type? = None) {
let edges = [source --> target];
assert len(edges) > 0, f"No connection from {source} to {target}";
if edge_type {
let typed_edges = [e for e in edges if isinstance(e, edge_type)];
assert len(typed_edges) > 0,
f"No {edge_type.__name__} edge from {source} to {target}";
}
}
can assert_not_connected(source: node, target: node) {
let edges = [source --> target];
assert len(edges) == 0, f"Unexpected connection from {source} to {target}";
}
can assert_node_count(expected: int, node_type: type? = None) {
if node_type {
let nodes = find_all_nodes(self.root_node, node_type);
assert len(nodes) == expected,
f"Expected {expected} {node_type.__name__} nodes, found {len(nodes)}";
} else {
let nodes = find_all_nodes(self.root_node);
assert len(nodes) == expected,
f"Expected {expected} total nodes, found {len(nodes)}";
}
}
}
// Example graph structure test
test "social network graph structure" {
let tc = GraphTestCase();
tc.setup();
// Create test graph
let alice = tc.root_node ++> User(name="Alice");
let bob = tc.root_node ++> User(name="Bob");
let charlie = tc.root_node ++> User(name="Charlie");
alice ++>:Follows:++> bob;
bob ++>:Follows:++> charlie;
charlie ++>:Follows:++> alice; // Circular
// Test structure
tc.assert_connected(alice, bob, Follows);
tc.assert_connected(bob, charlie, Follows);
tc.assert_connected(charlie, alice, Follows);
tc.assert_not_connected(alice, charlie); // No direct connection
tc.assert_node_count(4); // root + 3 users
tc.assert_node_count(3, User);
tc.teardown();
}
// Testing graph algorithms
test "shortest path algorithm" {
// Create test graph
let start = create_node("Start");
let a = create_node("A");
let b = create_node("B");
let c = create_node("C");
let end = create_node("End");
// Create paths with weights
start ++>:WeightedEdge(weight=1):++> a;
start ++>:WeightedEdge(weight=4):++> b;
a ++>:WeightedEdge(weight=2):++> c;
b ++>:WeightedEdge(weight=1):++> c;
c ++>:WeightedEdge(weight=1):++> end;
// Test shortest path
let path = find_shortest_path(start, end);
assert path == [start, a, c, end];
assert calculate_path_weight(path) == 4;
// Test alternate path
let all_paths = find_all_paths(start, end);
assert len(all_paths) == 2;
}
Testing Walker Behavior#
Walker testing requires simulating graph traversal and verifying behavior:
// Walker test framework
obj WalkerTestCase {
has test_graph: node;
has walker_results: list = [];
can setup_graph -> node abs; // Abstract, must implement
can spawn_and_collect[W: walker](
walker_type: type[W],
spawn_node: node,
**kwargs: dict
) -> list {
// Create walker with test parameters
let w = walker_type(**kwargs);
// Capture results
self.walker_results = spawn w on spawn_node;
return self.walker_results;
}
can assert_visited_sequence(walker: walker, expected_nodes: list[node]) {
assert walker.visited_nodes == expected_nodes,
f"Expected visit sequence {expected_nodes}, got {walker.visited_nodes}";
}
can assert_walker_state(walker: walker, **expected_state: dict) {
for key, expected_value in expected_state.items() {
actual_value = getattr(walker, key);
assert actual_value == expected_value,
f"Expected {key}={expected_value}, got {actual_value}";
}
}
}
// Walker behavior test
test "data aggregator walker" {
obj AggregatorTest(WalkerTestCase) {
can setup_graph -> node {
self.test_graph = create_test_root();
// Create data nodes
let n1 = self.test_graph ++> DataNode(value=10);
let n2 = self.test_graph ++> DataNode(value=20);
let n3 = self.test_graph ++> DataNode(value=30);
n1 ++> n2 ++> n3; // Linear chain
return self.test_graph;
}
}
let tc = AggregatorTest();
let start_node = tc.setup_graph();
// Test walker
walker DataAggregator {
has sum: float = 0;
has count: int = 0;
has visited_nodes: list = [];
can aggregate with DataNode entry {
self.sum += here.value;
self.count += 1;
self.visited_nodes.append(here);
visit [-->];
}
can finalize with `root exit {
report {
"sum": self.sum,
"count": self.count,
"average": self.sum / self.count if self.count > 0 else 0
};
}
}
let results = tc.spawn_and_collect(DataAggregator, start_node);
assert len(results) == 1;
assert results[0]["sum"] == 60;
assert results[0]["count"] == 3;
assert results[0]["average"] == 20;
}
// Testing walker interactions
test "walker communication" {
// Create communicating walkers
walker Sender {
has message: str;
has recipient: node;
can send with entry {
visit self.recipient {
here.received_messages.append(self.message);
};
}
}
walker Receiver {
has received_messages: list = [];
has response: str = "ACK";
can respond with entry {
if self.received_messages {
report {
"messages": self.received_messages,
"response": self.response
};
}
}
}
// Test communication
let sender_node = create_node();
let receiver_node = create_node() with {
has received_messages: list = [];
};
sender_node ++> receiver_node;
// Send message
spawn Sender(
message="Hello",
recipient=receiver_node
) on sender_node;
// Check reception
assert len(receiver_node.received_messages) == 1;
assert receiver_node.received_messages[0] == "Hello";
// Get response
let response = spawn Receiver() on receiver_node;
assert response[0]["response"] == "ACK";
}
Testing with Mocks and Stubs#
// Mock framework for Jac
obj Mock {
has name: str;
has return_values: dict = {};
has call_history: list = [];
has side_effects: dict = {};
can __getattr__(attr: str) -> callable {
can mock_method(*args: list, **kwargs: dict) -> any {
// Record call
self.call_history.append({
"method": attr,
"args": args,
"kwargs": kwargs,
"timestamp": now()
});
// Execute side effects
if attr in self.side_effects {
self.side_effects[attr](*args, **kwargs);
}
// Return configured value
if attr in self.return_values {
return self.return_values[attr];
}
return None;
}
return mock_method;
}
can assert_called(method: str, times: int? = None) {
let calls = [c for c in self.call_history if c["method"] == method];
if times is not None {
assert len(calls) == times,
f"{method} called {len(calls)} times, expected {times}";
} else {
assert len(calls) > 0,
f"{method} was not called";
}
}
can assert_called_with(method: str, *args: list, **kwargs: dict) {
let calls = [c for c in self.call_history if c["method"] == method];
for call in calls {
if call["args"] == args and call["kwargs"] == kwargs {
return; // Found matching call
}
}
assert False, f"{method} not called with args={args}, kwargs={kwargs}";
}
}
// Using mocks in tests
test "payment processor with mocks" {
// Create mock payment gateway
let mock_gateway = Mock(name="PaymentGateway");
mock_gateway.return_values["charge"] = {"status": "success", "id": "12345"};
mock_gateway.side_effects["log"] = lambda msg: print(f"Mock log: {msg}");
// Test payment processing
walker PaymentProcessor {
has gateway: any;
has amount: float;
can process with Order entry {
let result = self.gateway.charge(
amount=self.amount,
currency="USD",
order_id=here.id
);
if result["status"] == "success" {
here.payment_id = result["id"];
here.status = "paid";
self.gateway.log(f"Payment successful for order {here.id}");
}
report result;
}
}
// Create test order
let order = create_node() with {
has id: str = "ORDER-001";
has status: str = "pending";
has payment_id: str? = None;
};
// Process payment
let result = spawn PaymentProcessor(
gateway=mock_gateway,
amount=99.99
) on order;
// Verify mock interactions
mock_gateway.assert_called("charge", times=1);
mock_gateway.assert_called_with(
"charge",
amount=99.99,
currency="USD",
order_id="ORDER-001"
);
mock_gateway.assert_called("log");
// Verify order state
assert order.status == "paid";
assert order.payment_id == "12345";
}
16.2 Debugging Techniques#
Traversal Visualization#
Understanding walker paths through complex graphs is crucial for debugging:
// Traversal tracer
walker TraversalTracer {
has trace: list = [];
has show_properties: list[str] = [];
has max_depth: int = 10;
has current_depth: int = 0;
can trace_traversal with entry {
// Record current position
let trace_entry = {
"depth": self.current_depth,
"node_type": type(here).__name__,
"node_id": here.__id__ if hasattr(here, "__id__") else id(here),
"timestamp": now()
};
// Add requested properties
for prop in self.show_properties {
if hasattr(here, prop) {
trace_entry[prop] = getattr(here, prop);
}
}
self.trace.append(trace_entry);
// Continue traversal with depth limit
if self.current_depth < self.max_depth {
self.current_depth += 1;
visit [-->];
self.current_depth -= 1;
}
}
can visualize with `root exit {
print("\n=== Traversal Trace ===");
for entry in self.trace {
indent = " " * entry["depth"];
print(f"{indent}→ {entry['node_type']} (id: {entry['node_id']})");
for key, value in entry.items() {
if key not in ["depth", "node_type", "node_id", "timestamp"] {
print(f"{indent} {key}: {value}");
}
}
print(f"\nTotal nodes visited: {len(self.trace)}");
print("===================\n");
}
}
// Visual graph representation
walker GraphVisualizer {
has format: str = "mermaid"; // or "dot", "ascii"
has visited: set = set();
has edges: list = [];
has nodes: dict = {};
can visualize with entry {
if here in self.visited {
return;
}
self.visited.add(here);
// Record node
self.nodes[id(here)] = {
"type": type(here).__name__,
"label": self.get_node_label(here)
};
// Record edges
for edge in [<-->] {
self.edges.append({
"from": id(here),
"to": id(edge.target),
"type": type(edge).__name__,
"label": self.get_edge_label(edge)
});
}
// Continue traversal
visit [-->];
}
can get_node_label(n: node) -> str {
if hasattr(n, "name") {
return f"{n.name}";
} elif hasattr(n, "id") {
return f"#{n.id}";
}
return "";
}
can get_edge_label(e: edge) -> str {
if hasattr(e, "label") {
return e.label;
}
return "";
}
can render with `root exit -> str {
if self.format == "mermaid" {
return self.render_mermaid();
} elif self.format == "dot" {
return self.render_dot();
} else {
return self.render_ascii();
}
}
can render_mermaid -> str {
let output = ["graph TD"];
// Add nodes
for node_id, info in self.nodes.items() {
let label = f"{info['type']}";
if info['label'] {
label += f": {info['label']}";
}
output.append(f" N{node_id}[{label}]");
}
// Add edges
for edge in self.edges {
let arrow = "-->";
if edge['type'] != "Edge" {
arrow = f"-->|{edge['type']}|";
}
output.append(f" N{edge['from']} {arrow} N{edge['to']}");
}
return "\n".join(output);
}
}
// Usage in debugging
with entry:debug {
// Trace a specific walker's path
let tracer = TraversalTracer(
show_properties=["name", "value", "status"],
max_depth=5
);
spawn tracer on problematic_node;
// Visualize graph structure
let viz = GraphVisualizer(format="mermaid");
let graph_diagram = spawn viz on root;
print(graph_diagram);
}
State Inspection#
Tools for inspecting walker and node state during execution:
// State inspector
obj StateInspector {
has breakpoints: dict[str, callable] = {};
has watch_list: list[str] = [];
has history: list[dict] = [];
can add_breakpoint(location: str, condition: callable) {
self.breakpoints[location] = condition;
}
can add_watch(expression: str) {
self.watch_list.append(expression);
}
can checkpoint(location: str, context: dict) {
// Check if we should break
if location in self.breakpoints {
if self.breakpoints[location](context) {
self.interactive_debug(location, context);
}
}
// Record state
let state_snapshot = {
"location": location,
"timestamp": now(),
"context": context.copy(),
"watches": {}
};
// Evaluate watch expressions
for expr in self.watch_list {
try {
state_snapshot["watches"][expr] = eval(expr, context);
} except Exception as e {
state_snapshot["watches"][expr] = f"Error: {e}";
}
}
self.history.append(state_snapshot);
}
can interactive_debug(location: str, context: dict) {
print(f"\n🔴 Breakpoint hit at {location}");
print("Context variables:");
for key, value in context.items() {
print(f" {key}: {value}");
}
// Simple REPL for inspection
while True {
let cmd = input("debug> ");
if cmd == "continue" or cmd == "c" {
break;
} elif cmd == "quit" or cmd == "q" {
raise DebuggerExit();
} elif cmd.startswith("print ") {
let expr = cmd[6:];
try {
let result = eval(expr, context);
print(result);
} except Exception as e {
print(f"Error: {e}");
}
} elif cmd == "help" {
print("Commands: continue (c), quit (q), print <expr>");
}
}
}
}
// Instrumented walker for debugging
walker DebuggedWalker {
has inspector: StateInspector;
has _original_process: callable;
can __init__ {
self.inspector = StateInspector();
self._original_process = self.process;
self.process = self._debug_process;
}
can _debug_process with entry {
// Pre-process checkpoint
self.inspector.checkpoint("pre_process", {
"walker": self,
"here": here,
"here_type": type(here).__name__
});
// Call original process
self._original_process();
// Post-process checkpoint
self.inspector.checkpoint("post_process", {
"walker": self,
"here": here,
"here_type": type(here).__name__
});
}
}
// Property change tracking
node DebugNode {
has _properties: dict = {};
has _change_log: list = [];
can __setattr__(name: str, value: any) {
let old_value = self._properties.get(name, "<unset>");
self._properties[name] = value;
self._change_log.append({
"property": name,
"old_value": old_value,
"new_value": value,
"timestamp": now(),
"stack_trace": get_stack_trace()
});
super.__setattr__(name, value);
}
can get_change_history(property_name: str? = None) -> list {
if property_name {
return [c for c in self._change_log if c["property"] == property_name];
}
return self._change_log;
}
}
Distributed Debugging#
Debugging across multiple machines requires special tools:
// Distributed debugger
walker DistributedDebugger {
has trace_id: str = generate_trace_id();
has machine_traces: dict[str, list] = {};
has correlation_id: str;
can trace_cross_machine with entry {
let machine_id = here.__machine_id__;
# Initialize trace for this machine
if machine_id not in self.machine_traces {
self.machine_traces[machine_id] = [];
}
# Record entry
self.machine_traces[machine_id].append({
"event": "walker_arrival",
"node": type(here).__name__,
"timestamp": now(),
"machine": machine_id,
"correlation_id": self.correlation_id
});
# Check for cross-machine edges
for edge in [<-->] {
if edge.__is_cross_machine__ {
self.machine_traces[machine_id].append({
"event": "cross_machine_edge",
"from_machine": machine_id,
"to_machine": edge.target.__machine_id__,
"edge_type": type(edge).__name__,
"timestamp": now()
});
}
}
# Continue traversal
visit [-->];
}
can generate_distributed_timeline with `root exit {
// Merge all machine traces
let all_events = [];
for machine_id, events in self.machine_traces.items() {
for event in events {
event["machine_id"] = machine_id;
all_events.append(event);
}
}
// Sort by timestamp
all_events.sort(key=lambda e: e["timestamp"]);
// Generate timeline
print(f"\n=== Distributed Execution Timeline (Trace: {self.trace_id}) ===");
for event in all_events {
let time_str = event["timestamp"];
let machine = event["machine_id"];
match event["event"] {
case "walker_arrival":
print(f"{time_str} [{machine}] Walker arrived at {event['node']}");
case "cross_machine_edge":
print(f"{time_str} [{machine}] Cross-machine edge to "
f"[{event['to_machine']}] via {event['edge_type']}");
}
}
print("=====================================\n");
}
}
// Remote debugging session
obj RemoteDebugSession {
has session_id: str;
has target_machines: list[str];
has debug_port: int = 5678;
can attach_to_walker(walker_id: str, machine_id: str) {
// Establish remote debug connection
let connection = establish_debug_connection(machine_id, self.debug_port);
// Set remote breakpoints
connection.set_breakpoint(
walker_type="*",
condition=f"self.id == '{walker_id}'"
);
// Start monitoring
self.monitor_walker(connection, walker_id);
}
can monitor_walker(connection: any, walker_id: str) {
while True {
let event = connection.get_next_event();
match event.type {
case "breakpoint_hit":
print(f"Walker {walker_id} hit breakpoint on "
f"{event.machine_id} at {event.location}");
self.remote_inspect(connection, event);
case "state_change":
print(f"Walker {walker_id} state changed: "
f"{event.property} = {event.new_value}");
case "exception":
print(f"Walker {walker_id} raised {event.exception_type}: "
f"{event.message}");
self.analyze_remote_exception(connection, event);
}
}
}
}
// Distributed assertion framework
walker DistributedAssertion {
has assertions: list[callable] = [];
has machine_results: dict = {};
can add_assertion(name: str, check: callable) {
self.assertions.append({"name": name, "check": check});
}
can verify_distributed_state with entry {
let machine_id = here.__machine_id__;
let results = [];
for assertion in self.assertions {
try {
assertion["check"](here);
results.append({
"name": assertion["name"],
"status": "passed",
"machine": machine_id
});
} except AssertionError as e {
results.append({
"name": assertion["name"],
"status": "failed",
"machine": machine_id,
"error": str(e)
});
}
}
self.machine_results[machine_id] = results;
// Continue to other machines
visit [-->];
}
can report_results with `root exit {
let total_passed = 0;
let total_failed = 0;
print("\n=== Distributed Assertion Results ===");
for machine_id, results in self.machine_results.items() {
print(f"\nMachine: {machine_id}");
for result in results {
if result["status"] == "passed" {
print(f" ✓ {result['name']}");
total_passed += 1;
} else {
print(f" ✗ {result['name']}: {result['error']}");
total_failed += 1;
}
}
}
print(f"\nTotal: {total_passed} passed, {total_failed} failed");
print("===================================\n");
}
}
Performance Profiling#
// Performance profiler for walkers
walker PerformanceProfiler {
has profile_data: dict = {};
has _start_times: dict = {};
can start_timer(operation: str) {
self._start_times[operation] = time.perf_counter();
}
can stop_timer(operation: str) {
if operation in self._start_times {
elapsed = time.perf_counter() - self._start_times[operation];
if operation not in self.profile_data {
self.profile_data[operation] = {
"count": 0,
"total_time": 0.0,
"min_time": float('inf'),
"max_time": 0.0
};
}
let stats = self.profile_data[operation];
stats["count"] += 1;
stats["total_time"] += elapsed;
stats["min_time"] = min(stats["min_time"], elapsed);
stats["max_time"] = max(stats["max_time"], elapsed);
del self._start_times[operation];
}
}
can profile_ability(ability_name: str) -> callable {
can profiled_ability(original_ability: callable) -> callable {
can wrapper(*args, **kwargs) {
self.start_timer(ability_name);
try {
result = original_ability(*args, **kwargs);
return result;
} finally {
self.stop_timer(ability_name);
}
}
return wrapper;
}
return profiled_ability;
}
can generate_report with `root exit {
print("\n=== Performance Profile ===");
print(f"{'Operation':<30} {'Count':<10} {'Total(s)':<10} "
f"{'Avg(ms)':<10} {'Min(ms)':<10} {'Max(ms)':<10}");
print("-" * 80);
for op, stats in self.profile_data.items() {
avg_ms = (stats["total_time"] / stats["count"]) * 1000;
print(f"{op:<30} {stats['count']:<10} "
f"{stats['total_time']:<10.3f} {avg_ms:<10.2f} "
f"{stats['min_time']*1000:<10.2f} "
f"{stats['max_time']*1000:<10.2f}");
}
print("========================\n");
}
}
// Memory profiler
walker MemoryProfiler {
has snapshots: list = [];
has track_types: list[type] = [node, edge, walker];
can take_snapshot(label: str) {
import:py gc;
import:py sys;
gc.collect(); // Force garbage collection
let snapshot = {
"label": label,
"timestamp": now(),
"total_objects": 0,
"by_type": {},
"memory_usage": self.get_memory_usage()
};
for obj in gc.get_objects() {
obj_type = type(obj);
if any(isinstance(obj, t) for t in self.track_types) {
type_name = obj_type.__name__;
if type_name not in snapshot["by_type"] {
snapshot["by_type"][type_name] = {
"count": 0,
"size": 0
};
}
snapshot["by_type"][type_name]["count"] += 1;
snapshot["by_type"][type_name]["size"] += sys.getsizeof(obj);
snapshot["total_objects"] += 1;
}
}
self.snapshots.append(snapshot);
}
can get_memory_usage -> dict {
import:py psutil;
import:py os;
process = psutil.Process(os.getpid());
mem_info = process.memory_info();
return {
"rss": mem_info.rss, // Resident Set Size
"vms": mem_info.vms, // Virtual Memory Size
"percent": process.memory_percent()
};
}
can compare_snapshots(label1: str, label2: str) {
let snap1 = [s for s in self.snapshots if s["label"] == label1][0];
let snap2 = [s for s in self.snapshots if s["label"] == label2][0];
print(f"\n=== Memory Comparison: {label1} → {label2} ===");
// Overall memory change
let mem_diff = snap2["memory_usage"]["rss"] - snap1["memory_usage"]["rss"];
print(f"Memory change: {mem_diff / 1024 / 1024:.2f} MB");
// Object count changes
print("\nObject count changes:");
let all_types = set(snap1["by_type"].keys()) | set(snap2["by_type"].keys());
for type_name in sorted(all_types) {
let count1 = snap1["by_type"].get(type_name, {}).get("count", 0);
let count2 = snap2["by_type"].get(type_name, {}).get("count", 0);
let diff = count2 - count1;
if diff != 0 {
print(f" {type_name}: {count1} → {count2} ({diff:+})");
}
}
print("================================\n");
}
}
Error Diagnostics#
// Enhanced error reporting
obj ErrorDiagnostics {
has error_handlers: dict[type, callable] = {};
has context_collectors: list[callable] = [];
can register_handler(error_type: type, handler: callable) {
self.error_handlers[error_type] = handler;
}
can add_context_collector(collector: callable) {
self.context_collectors.append(collector);
}
can diagnose(error: Exception, context: dict = {}) -> dict {
let diagnosis = {
"error_type": type(error).__name__,
"message": str(error),
"timestamp": now(),
"context": context,
"stack_trace": get_detailed_stack_trace(),
"suggestions": []
};
// Collect additional context
for collector in self.context_collectors {
try {
additional_context = collector(error, context);
diagnosis["context"].update(additional_context);
} except Exception as e {
diagnosis["context"][f"collector_error_{collector.__name__}"] = str(e);
}
}
// Run specific handler if available
error_type = type(error);
if error_type in self.error_handlers {
let handler_result = self.error_handlers[error_type](error, diagnosis);
diagnosis["suggestions"].extend(handler_result.get("suggestions", []));
diagnosis["probable_cause"] = handler_result.get("probable_cause");
}
return diagnosis;
}
}
// Graph-specific error diagnostics
can diagnose_graph_errors(error: Exception, context: dict) -> dict {
let result = {"suggestions": []};
match type(error) {
case NodeNotFoundError:
result["probable_cause"] = "Attempting to access non-existent node";
result["suggestions"] = [
"Check if node was deleted",
"Verify node creation completed",
"Check for race conditions in concurrent access"
];
case CircularDependencyError:
result["probable_cause"] = "Circular reference detected in graph";
result["suggestions"] = [
"Use cycle detection before traversal",
"Implement maximum depth limit",
"Consider using visited set"
];
case CrossMachineError:
result["probable_cause"] = "Failed cross-machine operation";
result["suggestions"] = [
"Check network connectivity",
"Verify remote machine availability",
"Check for version mismatches",
"Enable distributed tracing"
];
}
return result;
}
// Automatic error reporting
walker ErrorReporter {
has diagnostics: ErrorDiagnostics;
has report_endpoint: str?;
can __init__ {
self.diagnostics = ErrorDiagnostics();
self.diagnostics.register_handler(GraphError, diagnose_graph_errors);
}
can safe_traverse with entry {
try {
// Normal traversal
self.process_node();
visit [-->];
} except Exception as e {
// Diagnose error
let diagnosis = self.diagnostics.diagnose(e, {
"walker_type": type(self).__name__,
"node_type": type(here).__name__,
"node_id": here.__id__ if hasattr(here, "__id__") else None,
"machine_id": here.__machine_id__ if hasattr(here, "__machine_id__") else None
});
// Report error
self.report_error(diagnosis);
// Decide whether to continue
if self.should_continue_after_error(e) {
visit [-->] else {
report {"error": diagnosis};
disengage;
};
} else {
disengage;
}
}
}
can report_error(diagnosis: dict) {
// Log locally
print(f"\n🔴 Error Diagnostic Report");
print(f"Type: {diagnosis['error_type']}");
print(f"Message: {diagnosis['message']}");
if diagnosis.get("probable_cause") {
print(f"Probable Cause: {diagnosis['probable_cause']}");
}
if diagnosis["suggestions"] {
print("Suggestions:");
for suggestion in diagnosis["suggestions"] {
print(f" • {suggestion}");
}
}
// Send to monitoring service if configured
if self.report_endpoint {
send_error_report(self.report_endpoint, diagnosis);
}
}
}
Summary#
This chapter covered comprehensive testing and debugging strategies for Jac applications:
Testing Framework#
- Built-in Tests: First-class
test
blocks with assertions - Graph Testing: Specialized utilities for structure verification
- Walker Testing: Simulating traversals and verifying behavior
- Mock Framework: Isolating components for unit testing
Debugging Techniques#
- Traversal Visualization: Understanding walker paths
- State Inspection: Interactive debugging and property tracking
- Distributed Debugging: Cross-machine tracing and correlation
- Performance Profiling: Identifying bottlenecks and optimization opportunities
Best Practices#
- Test at Multiple Levels: Unit, integration, and system tests
- Use Visualization: Graph structure and traversal paths
- Profile Early: Identify performance issues before scaling
- Embrace Distributed Debugging: Essential for production systems
- Automate Error Reporting: Comprehensive diagnostics for rapid fixes
These tools and techniques ensure that Jac applications are robust, performant, and maintainable at any scale, from development to global deployment.