You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

127 lines
4.8 KiB

"""Tests for the SpiceRunner interface and NgspiceRunner."""
import os
import pytest
from pyngspice.runner import NgspiceRunner, SubprocessRunner, SimulationError, get_runner
class TestNgspiceRunnerDetect:
"""Test NgspiceRunner.detect() and get_executable_path()."""
def test_detect(self):
"""NgspiceRunner should detect the embedded C++ extension."""
from pyngspice import _cpp_available
assert NgspiceRunner.detect() == _cpp_available
def test_executable_path_is_none(self):
"""Embedded runner has no separate executable."""
assert NgspiceRunner.get_executable_path() is None
@pytest.mark.skipif(
not NgspiceRunner.detect(),
reason="C++ extension not built"
)
class TestNgspiceRunnerSimulation:
"""Integration tests for NgspiceRunner (require built C++ extension)."""
def test_run_simple_rc(self, rc_netlist, tmp_workdir):
"""Run a simple RC circuit and verify output files exist."""
runner = NgspiceRunner(working_directory=tmp_workdir)
raw_file, log_file = runner.run(rc_netlist)
assert os.path.isfile(raw_file)
assert raw_file.endswith(".raw")
def test_run_with_rser_preprocessing(self, inductor_rser_netlist, tmp_workdir):
"""Run a netlist with Rser= inductors (tests pre-processing)."""
runner = NgspiceRunner(working_directory=tmp_workdir)
raw_file, log_file = runner.run(inductor_rser_netlist)
assert os.path.isfile(raw_file)
def test_run_returns_absolute_paths(self, rc_netlist, tmp_workdir):
"""Output paths should be absolute."""
runner = NgspiceRunner(working_directory=tmp_workdir)
raw_file, log_file = runner.run(rc_netlist)
assert os.path.isabs(raw_file)
assert os.path.isabs(log_file)
def test_run_with_savecurrents(self, tesla_coil_netlist, tmp_workdir):
"""Run Tesla coil netlist with .options savecurrents — verify cap currents."""
runner = NgspiceRunner(working_directory=tmp_workdir)
raw_file, log_file = runner.run(tesla_coil_netlist)
assert os.path.isfile(raw_file)
assert raw_file.endswith(".raw")
# Verify raw file contains renamed capacitor current traces
from pyngspice import RawRead
raw = RawRead(raw_file)
trace_names = [n.lower() for n in raw.get_trace_names()]
# Capacitor currents should appear with original names (post-processed)
assert 'i(c_mmc)' in trace_names, f"Missing i(c_mmc) in {trace_names}"
assert 'i(c_topload)' in trace_names, f"Missing i(c_topload) in {trace_names}"
# Probe names should NOT appear (renamed away)
assert 'i(v_probe_c_mmc)' not in trace_names
assert 'i(v_probe_c_topload)' not in trace_names
def test_run_nonexistent_netlist(self, tmp_workdir):
"""Should raise FileNotFoundError for missing netlist."""
runner = NgspiceRunner(working_directory=tmp_workdir)
with pytest.raises(FileNotFoundError):
runner.run("/nonexistent/file.net")
def test_working_directory_created(self, rc_netlist):
"""Working directory should be created if it doesn't exist."""
import tempfile
workdir = os.path.join(tempfile.gettempdir(), "pyngspice_test_auto_create")
try:
runner = NgspiceRunner(working_directory=workdir)
assert os.path.isdir(workdir)
finally:
if os.path.isdir(workdir):
os.rmdir(workdir)
class TestSubprocessRunner:
"""Tests for SubprocessRunner."""
def test_detect(self):
"""detect() should return bool without error."""
result = SubprocessRunner.detect()
assert isinstance(result, bool)
def test_get_executable_path(self):
"""get_executable_path() should return str or None."""
result = SubprocessRunner.get_executable_path()
assert result is None or isinstance(result, str)
class TestGetRunner:
"""Tests for the get_runner factory function."""
def test_auto_returns_runner(self):
"""Auto mode should return some runner if anything is available."""
try:
runner = get_runner(backend="auto")
assert isinstance(runner, (NgspiceRunner, SubprocessRunner))
except RuntimeError:
pytest.skip("No ngspice backend available")
def test_invalid_backend(self):
"""Invalid backend name should raise ValueError."""
with pytest.raises(ValueError, match="Unknown backend"):
get_runner(backend="invalid")
@pytest.mark.skipif(
not NgspiceRunner.detect(),
reason="C++ extension not built"
)
def test_embedded_backend(self, tmp_workdir):
"""Explicitly requesting embedded should return NgspiceRunner."""
runner = get_runner(tmp_workdir, backend="embedded")
assert isinstance(runner, NgspiceRunner)