Testing & Debugging#
Comprehensive guide to testing and debugging your Jac applications.
Testing Framework#
Writing Tests#
Tests in Jac use the test keyword followed by an identifier name:
def add(a: int, b: int) -> int {
return a + b;
}
obj Circle {
has radius: float;
def area() -> float {
return 3.14159 * self.radius ** 2;
}
}
# Test names are identifiers (not strings)
test test_add {
assert add(2, 3) == 5;
}
test test_circle {
c = Circle(radius=5.0);
assert c.radius == 5.0;
assert c.area() > 78;
}
test test_list {
items: list[int] = [1, 2, 3];
assert len(items) == 3;
assert sum(items) == 6;
}
Assertions#
test test_assertions {
# Basic equality
assert 1 + 1 == 2;
# With custom message
assert 1 == 1, "Math should work";
# Comparisons
assert 5 > 3;
assert 3 < 5;
assert 5 >= 5;
assert 5 <= 5;
assert 5 != 3;
# Boolean assertions
assert True;
assert not False;
# Collection assertions
assert 3 in [1, 2, 3];
assert "key" in {"key": "value"};
}
Testing Walkers and Graphs#
node Counter {
has count: int = 0;
}
walker Incrementer {
can increment with Counter entry {
here.count += 1;
}
}
test test_walker {
# Create graph
counter = root ++> Counter();
# Spawn walker
Incrementer() spawn root;
# Verify result
assert counter[0].count == 1;
}
Running Tests#
Command: jac test#
Options#
| Option | Short | Default | Description |
|---|---|---|---|
--test_name |
-t |
"" | Run specific test by name |
--filter |
-f |
"" | Filter tests by pattern |
--xit |
-x |
false | Exit on first failure |
--maxfail |
-m |
None | Stop after N failures |
--directory |
-d |
"" | Test directory |
--verbose |
-v |
false | Verbose output |
Examples#
# Run all tests in a file
jac test main.jac
# Run with verbose output
jac test main.jac -v
# Run specific test
jac test main.jac -t test_add
# Filter tests by pattern
jac test main.jac -f "test_circle"
# Stop on first failure
jac test main.jac -x
# Stop after 3 failures
jac test main.jac -m 3
# Run tests in a directory
jac test -d tests/
# Combine options
jac test -d tests/ -v -x
Test Output#
unittest.case.FunctionTestCase (test_add) ... ok
unittest.case.FunctionTestCase (test_circle) ... ok
unittest.case.FunctionTestCase (test_list) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
Passed successfully.
Failed Test Output#
unittest.case.FunctionTestCase (test_fail) ... FAIL
======================================================================
FAIL: unittest.case.FunctionTestCase (test_fail)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test.jac", line 8, in test_fail
assert 1 == 2, "This will fail";
AssertionError: 1 != 2
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
Interactive Debugger#
Command: jac debug#
Options#
| Option | Short | Default | Description |
|---|---|---|---|
--main |
-m |
true | Run as main module |
--cache |
-c |
false | Use bytecode cache |
Starting the Debugger#
This launches an interactive debugger where you can:
- Set breakpoints
- Step through code
- Inspect variables
- Examine the call stack
Graph Visualization#
Command: jac dot#
Generate DOT format graph visualizations of your program's graph state.
Options#
| Option | Short | Default | Description |
|---|---|---|---|
--session |
-s |
"" | Session name for persistence |
--initial |
-i |
"" | Initial node ID |
--depth |
-d |
-1 | Traversal depth (-1 = unlimited) |
--traverse |
-t |
false | Traverse connections |
--bfs |
-b |
false | Use BFS (default: DFS) |
--edge_limit |
-e |
512 | Maximum edges |
--node_limit |
-n |
512 | Maximum nodes |
--saveto |
-sa |
"" | Save to file |
--to_screen |
-to |
false | Print to stdout |
--format |
-f |
"dot" | Output format |
Examples#
# Print graph to screen
jac dot main.jac --to_screen
# Save to file
jac dot main.jac --saveto graph.dot
# Use existing session
jac dot main.jac -s my_session --to_screen
# Limit traversal depth
jac dot main.jac -d 3 --to_screen
# Use BFS instead of DFS
jac dot main.jac -b --to_screen
# Limit output size
jac dot main.jac -e 100 -n 100 --to_screen
Sample Output#
Rendering the Graph#
# Generate DOT and render with Graphviz
jac dot main.jac --saveto graph.dot
dot -Tpng graph.dot -o graph.png
# Or pipe directly
jac dot main.jac --to_screen | dot -Tpng -o graph.png
IR Inspection Tools#
Command: jac tool ir#
Inspect the internal representation of your Jac code.
Subcommands#
| Subcommand | Description |
|---|---|
py |
View generated Python code |
sym |
View symbol table (text) |
sym. |
View symbol table (DOT format) |
ast |
View abstract syntax tree (text) |
ast. |
View AST (DOT format) |
docir |
View document IR |
pyast |
View Python AST |
unparse |
View unparsed code |
esast |
View ES (JavaScript) AST |
es |
View generated JavaScript |
View Generated Python#
Output:
"""Simple test examples for documentation."""
from __future__ import annotations
from jaclang.pycore.jaclib import Obj, jac_test
def add(a: int, b: int) -> int:
return a + b
class Circle(Obj):
radius: float
def area(self) -> float:
return 3.14159 * self.radius ** 2
View Symbol Table#
Output:
###############
# main #
###############
SymTable::Module(main)
+-- Symbols
| +-- Circle
| | +-- public object
| | +-- decl: line 7, col 5
| | +-- defn
| | | +-- line 7, col 5
| | +-- uses
| +-- add
| +-- public ability
| +-- decl: line 3, col 5
+-- Sub Tables
+-- SymTable::Ability(add)
| +-- Symbols
| | +-- a
| | | +-- public variable
| | | +-- decl: line 3, col 9
View AST#
Generate DOT Graphs#
# Symbol table as DOT
jac tool ir sym. main.jac > symbols.dot
dot -Tpng symbols.dot -o symbols.png
# AST as DOT
jac tool ir ast. main.jac > ast.dot
dot -Tpng ast.dot -o ast.png
Troubleshooting Guide#
Common Errors#
"MongoDB not available, using ShelfMemory"#
Cause: MongoDB is not running or not configured.
Solution: This is informational. ShelfMemory (file-based) persistence will be used. To use MongoDB:
# Start MongoDB (local)
mongod
# Or configure in jac.toml
[plugins.scale.database]
mongodb_uri = "mongodb://localhost:27017"
Stale State Errors#
Cause: ShelfMemory persistence (anchor_store.db) contains stale state from previous runs.
Solution: Delete the anchor store file:
"non-default argument follows default argument"#
Cause: In inheritance, child class has required attributes after parent's optional attributes.
Solution: Give all child attributes defaults:
obj Parent {
has name: str = "Parent";
}
obj Child(Parent) {
has child_attr: str = "default"; # Must have default
}
Walker Not Executing#
Cause: Walker doesn't have a root handler to start traversal.
Solution: Add can start with \root entry`:
walker MyWalker {
can start with `root entry {
visit [-->]; # Start traversal
}
can process with Person entry {
print(here.name);
visit [-->];
}
}
Import Errors#
Cause: Module not found or incorrect import syntax.
Solution: Check import syntax:
# Correct
import from byllm.lib { Model }
import os;
# Incorrect
from byllm.lib import Model # Python syntax, won't work
Debugging Tips#
- Print Debugging
- Check Generated Python
- Inspect Symbol Table
- Visualize Graph State
- Run Single Test
- Clear Cached State
Best Practices#
Test Organization#
myproject/
├── jac.toml
├── src/
│ └── main.jac
└── tests/
├── test_models.jac
├── test_walkers.jac
└── test_integration.jac
Test Naming#
# Good: Descriptive names
test test_user_creation_with_valid_email { ... }
test test_walker_visits_all_nodes { ... }
# Avoid: Vague names
test test1 { ... }
test my_test { ... }
Isolate Tests#
test test_isolated {
# Create fresh graph for this test
node = root ++> TestNode();
# Test operates on isolated state
walker spawn root;
# Assertions on isolated state
assert node[0].value == expected;
}
Test Configuration#
See Also#
| Topic | Resource |
|---|---|
| The Jac Book: Testing | Chapter 17 |
| Debugger Guide | Debugger |
| CLI Reference | All Commands |
| Configuration | jac.toml Reference |