Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Demo and Testing

How to manually demo spice-lsp and automate tests at each layer. Use this page before and after every milestone in the MVP guide.

Testing pyramid

                    ┌─────────────┐
                    │  Manual VS  │  F5 extension, eyeball squiggles
                    │  Code demo  │
                    └──────┬──────┘
               ┌───────────┴───────────┐
               │  LSP integration tests │  JSON-RPC over stdio
               └───────────┬───────────┘
          ┌────────────────┴────────────────┐
          │     Parser / grammar tests       │  Fixtures in test-data/
          └─────────────────────────────────┘

Lower layers run faster and should carry most coverage.


Parser tests

Location: crates/spice-parser/tests/ or inline #[cfg(test)] modules.

Pattern — golden diagnostics:

#![allow(unused)]
fn main() {
#[test]
fn unclosed_subckt_reports_error() {
    let source = std::fs::read_to_string("test-data/invalid/unclosed-subckt.cir").unwrap();
    let result = spice_parser::analyze(&source);
    assert!(!result.diagnostics.is_empty());
    assert!(result.diagnostics[0].message.contains("ends"));
}
}

Grammar tests (Tree-sitter): Corpus files under tree-sitter-spice/test/corpus/:

==========
simple RC
==========
R1 in out 1k
---
(source_file (instance_line ...))

Run: pixi run cargo test -p spice-parser


LSP integration tests

Test the binary without an editor by driving stdio.

Option A — Custom test harness

Spawn spice-lsp as a child process, write JSON-RPC messages with Content-Length headers, read responses:

#![allow(unused)]
fn main() {
// Pseudocode
let mut child = Command::new("target/debug/spice-lsp")
    .stdin(Stdio::piped())
    .stdout(Stdio::piped())
    .spawn()?;

write_message(&mut child, initialize_request());
let init_resp = read_message(&mut child);
assert_eq!(init_resp["result"]["capabilities"]["textDocumentSync"]["change"], 2);

write_message(&mut child, did_open("file:///test.cir", INVALID_SOURCE));
let diag = read_until_method(&mut child, "textDocument/publishDiagnostics");
assert!(!diag["params"]["diagnostics"].as_array().unwrap().is_empty());
}

Option B — tower-lsp in-process tests

Test Backend methods directly with a mock Client that records publish_diagnostics calls — faster, no subprocess.

Use both: in-process for logic, one subprocess smoke test for the full binary.

Run: pixi run cargo test -p spice-lsp


Manual LSP smoke test (no VS Code)

Use a generic LSP inspector or minimal script.

With languageclient CLI (if installed)

Some ecosystems ship an inspector; alternatively use the VS Code Output → SPICE LSP trace.

Raw JSON-RPC with Python (example)

#!/usr/bin/env python3
"""Send initialize to spice-lsp over stdio. Requires built binary on PATH."""
import json, subprocess, struct, sys

proc = subprocess.Popen(
    ["spice-lsp"],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
)

def send(msg):
    body = json.dumps(msg).encode()
    header = f"Content-Length: {len(body)}\r\n\r\n".encode()
    proc.stdin.write(header + body)
    proc.stdin.flush()

def read():
    headers = {}
    while True:
        line = proc.stdout.readline().decode()
        if line in ("\r\n", "\n", ""):
            break
        k, v = line.split(":", 1)
        headers[k.strip()] = int(v.strip())
    body = proc.stdout.read(headers["Content-Length"])
    return json.loads(body)

send({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {
    "processId": None,
    "rootUri": None,
    "capabilities": {},
}})
resp = read()
print(json.dumps(resp, indent=2))
assert "capabilities" in resp.get("result", {}), resp
print("OK: initialize succeeded", file=sys.stderr)

Run after build:

pixi run build
export PATH="$PWD/target/debug:$PATH"
python3 scripts/lsp_smoke.py

Add scripts/lsp_smoke.py to the repo when the server exists.


VS Code extension demo

Development host (primary demo path)

  1. Build Rust binary: pixi run build
  2. cd editors/vscode && npm install && npm run compile
  3. Open editors/vscode in VS Code
  4. Run and Debug → Launch Extension (F5)
  5. In the new [Extension Development Host] window:
    • Open test-data/invalid/unclosed-subckt.cir
    • Confirm Problems panel lists diagnostics
    • Fix syntax, confirm clearing

Launch configuration

.vscode/launch.json in the extension folder:

{
  "version": "0.2.0",
  "configurations": [{
    "name": "Launch Extension",
    "type": "extensionHost",
    "request": "launch",
    "args": ["--extensionDevelopmentPath=${workspaceFolder}"],
    "env": {},
    "preLaunchTask": "npm: compile"
  }]
}

Set user/workspace setting in Development Host:

{
  "spiceLsp.serverPath": "/absolute/path/to/spice-lsp/target/debug/spice-lsp"
}

Trace LSP traffic

Enable verbose logging during demo debugging:

{
  "spiceLsp.trace.server": "verbose"
}

View Output panel → channel SPICE LSP (or Language Client name).

Side-load packaged extension

cd editors/vscode
npx vsce package
code --install-extension spice-lsp-0.1.0.vsix

Demo checklist for stakeholders

Use this script in reviews:

StepActionExpected
1pixi run testAll tests pass
2F5 extensionDevelopment Host opens
3Open invalid netlistRed squiggle + Problems entry
4Edit to fixDiagnostic disappears
5Open valid netlistNo errors
6SPICE LSP: Restart Server (if command added)Server reconnects, diagnostics return

CI expectations

Every push should run:

pixi install
pixi run test

Optional nightly or pre-release:

pixi run build --release
pixi run cargo test --release

Extension CI (when added):

cd editors/vscode && npm ci && npm run compile && npm test

Benchmarks (post-MVP)

Add criterion benches for parse + analyze on large fixtures:

pixi run cargo bench -p spice-parser

Track regressions against targets in Architecture.