Testing
Embody includes a comprehensive automated test suite with 57 test suites and 1,377 test methods covering core externalization, MCP tools, TDN format, the Envoy server/bridge, and palette catalogs. Tests run inside TouchDesigner using a custom test runner with sandbox isolation.
Running Tests
From TouchDesigner
# All tests, one per frame (non-blocking, default)
op.unit_tests.RunTests()
# Single suite
op.unit_tests.RunTests(suite_name='test_path_utils')
# Single test method
op.unit_tests.RunTests(suite_name='test_path_utils', test_name='test_normalizePath_backslashes_converted')
# All in one frame (blocks TD until complete)
op.unit_tests.RunTestsSync()
# One suite per frame
op.unit_tests.RunTestsDeferred()
# Get results
results = op.unit_tests.GetResults()
# Returns: {'total': 1328, 'passed': 1328, 'failed': 0, 'errors': 0, 'skipped': 0, 'results': [...]}
Via MCP
Use the run_tests Envoy tool:
run_tests() # Run all suites
run_tests(suite_name='test_path_utils') # Run one suite
Test Coverage
Core Embody (18 suites, 288 tests)
| Suite |
Tests |
Coverage |
test_externalization |
9 |
Externalization lifecycle |
test_crud_operators |
21 |
Create, read, update, delete operations |
test_file_management |
8 |
File I/O, path handling, tracked-file delete safety |
test_tag_management |
29 |
Tagging operators for externalization |
test_tag_lifecycle |
17 |
Tag application and removal |
test_rename_move_lifecycle |
26 |
Rename and move tracking |
test_ancestor_rename |
19 |
Ancestor-rename detection and folder migration |
test_delete_cleanup |
16 |
Deletion and file cleanup |
test_duplicate_handling |
31 |
Duplicate / clone / replicant resolution |
test_strategy_handlers |
15 |
TOX/TDN strategy switch, remove, DAT convert |
test_update_sync |
7 |
Sync between .toe and externalized files |
test_path_utils |
18 |
Path normalization and utilities |
test_param_tracker |
13 |
Parameter change tracking |
test_operator_queries |
6 |
Operator discovery and queries |
test_logging |
10 |
Logging system and ring buffer |
test_custom_parameters |
32 |
Custom parameter behavior (Folder, Disable/Enable, Update, TDN controls, Logs, Envoy) |
test_settings_persistence |
3 |
Settings serialization (byte-stable, sorted keys) |
test_os_label |
8 |
OS label resolution (Win10/11 build thresholds, macOS) |
| Suite |
Tests |
Coverage |
test_mcp_operators |
20 |
Create, delete, copy, rename, query, find |
test_mcp_parameters |
11 |
Get/set parameters, modes, expressions |
test_mcp_dat_content |
19 |
DAT text/table ops + surgical edit_dat_content + wipe guards |
test_mcp_connections |
8 |
Wiring operators together |
test_mcp_annotations |
19 |
Creating and managing annotations |
test_mcp_extensions |
6 |
Extension creation and setup |
test_mcp_diagnostics |
16 |
Error checking, class introspection, module help, log retrieval |
test_mcp_flags_position |
16 |
Operator flags, positioning, and get_network_layout |
test_mcp_code_execution |
7 |
Executing Python in TD |
test_mcp_externalization |
22 |
Embody integration via MCP (tag, save, status) |
test_mcp_batch |
9 |
Batched multi-operation requests |
test_mcp_top_capture |
11 |
TOP image capture (format, quality, resolution) |
test_mcp_tdn_tools |
10 |
read_tdn, export_network/import_network round-trip |
test_mcp_performance |
5 |
Per-operator performance monitoring |
test_mcp_project_performance |
14 |
Project-level FPS, memory, hotspots |
| Suite |
Tests |
Coverage |
test_tdn_reconstruction |
208 |
Reconstruction round-trip fidelity |
test_tdn_file_io |
92 |
TDN file output, per-comp splitting, stale cleanup, tdn_ref / tox_ref pointers |
test_tdn_helpers |
53 |
TDN serialization utility functions |
test_tdn_crash_safety |
35 |
Atomic writes, backup rotation, validation |
test_tdn_export_import |
32 |
Network export/import + storage round-trip |
test_tdn_sequences |
27 |
Parameter / operator sequence round-trip |
test_tdn_palette_catalog |
23 |
Palette-clone detection and handling |
test_tdn_mode |
15 |
Tdnmode gating (off / export / full) |
test_dat_restoration |
14 |
DAT restoration from disk on startup |
test_tdn_safety_guards |
11 |
At-risk storage / callback-DAT protection |
test_tdn_fingerprint |
6 |
Param-aware dirty detection (fingerprint) |
test_tdn_external_connections |
6 |
External wire capture/restore across strip |
Envoy Server & Bridge (6 suites, 284 tests)
| Suite |
Tests |
Coverage |
test_envoy_bridge |
156 |
STDIO bridge: forwarding, reconciler, registry, meta-tools |
test_claude_config |
71 |
AI client config generation (Claude/Cursor/Copilot/Windsurf) |
test_envoy_thread_comm |
20 |
Worker/main thread queues and throttling |
test_server_lifecycle |
17 |
Envoy MCP server start/stop |
test_envoy_registry |
17 |
Instance registry and PID liveness |
test_envoy_setup_environment |
3 |
MCP import verification (pydantic_core safety) |
Catalog & Release (3 suites, 41 tests)
| Suite |
Tests |
Coverage |
test_smoke_release |
25 |
Release smoke checks (extensions loaded, Envoy state) |
test_catalog_bootstrap_palette |
10 |
Bootstrap palette table parsing + build coverage |
test_catalog_palette_scan |
6 |
Palette scan time-state snapshot/restore |
Execution Modes
| Mode |
Method |
Behavior |
| Per-test deferred |
RunTests() |
One test per frame. Best for heavy suites. Non-blocking. Default. |
| Per-suite deferred |
RunTestsDeferred() |
One suite per frame. Keeps TD responsive. |
| Synchronous |
RunTestsSync() |
All tests in one frame. Blocks TD. Use for MCP. |
Test Framework Features
- Sandbox isolation: Each suite gets a fresh
baseCOMP for test fixtures
- 20+ assertion methods:
assertEqual, assertTrue, assertIn, assertIsInstance, etc.
- Lifecycle hooks:
setUp/tearDown per test, setUpSuite/tearDownSuite per suite
- Skip support:
self.skip(reason) to conditionally skip tests
- Results tracking: Table DAT with pass/fail/error/skip counts and durations
Writing New Tests
Create a test file in the unit tests directory:
"""Test suite: description of what this tests."""
# Base class is auto-injected by the test runner
class TestMyFeature(EmbodyTestCase):
def test_something(self):
"""Test description."""
# Create test fixtures in self.sandbox
op = self.sandbox.create(baseCOMP, 'test_op')
# Access Embody extension
result = self.embody_ext.someMethod(op)
# Assertions
self.assertEqual(result, expected_value)
self.assertTrue(op.valid)
self.assertIn('foo', result)
def setUp(self):
"""Called before each test (optional)."""
pass
def tearDown(self):
"""Called after each test (auto-destroys sandbox children)."""
super().tearDown()
Available Objects
| Object |
Description |
self.sandbox |
baseCOMP for creating temporary operators |
self.embody |
Reference to op.Embody |
self.embody_ext |
Direct access to op.Embody.ext.Embody |
self.runner |
TestRunnerExt instance |
op, parent, root |
All TD globals available |
What Cannot Be Unit Tested
Some areas require manual testing:
- UI interactions (clicking, dragging, network editor)
- Cross-session persistence (requires closing/reopening
.toe)
- Keyboard shortcuts (actual key press detection)
- Modal dialogs (file pickers, prompts)
- Undo/redo behavior
- Graphics rendering (visual output validation)
- Real-time performance (sustained load, frame-rate stability)
- External hardware (MIDI, OSC, DMX, serial)