11 tests over auto plot-type detection (bode/spectrum/time), missing-signal and multi-signal-overlay errors, dimension/height defaults, downsampling, and x-range clipping -- all with hand-built RawFiles, no .raw read or SVG write.
104 lines
3.9 KiB
Python
104 lines
3.9 KiB
Python
"""Tests for build_waveform_svg -- the plot_waveform core, sans file I/O.
|
|
|
|
Hand-built RawFiles drive every branch (auto plot-type detection, clipping,
|
|
multi-signal, errors) without LTspice or writing an SVG to disk.
|
|
"""
|
|
|
|
import numpy as np
|
|
|
|
from mcltspice.plotting import build_waveform_svg
|
|
from mcltspice.raw_parser import RawFile, Variable
|
|
|
|
|
|
def _ac_raw() -> RawFile:
|
|
"""Frequency axis + complex V(out), V(in) -> AC analysis."""
|
|
freq = np.logspace(0, 6, 400)
|
|
hout = 1.0 / (1.0 + 1j * (freq / 1000.0))
|
|
data = np.array([freq.astype(complex), hout, np.ones_like(freq, dtype=complex)])
|
|
return RawFile(
|
|
title="ac", date="", plotname="AC Analysis", flags=["complex"],
|
|
variables=[
|
|
Variable(0, "frequency", "frequency"),
|
|
Variable(1, "V(out)", "voltage"),
|
|
Variable(2, "V(in)", "voltage"),
|
|
],
|
|
points=len(freq), data=data,
|
|
)
|
|
|
|
|
|
def _tran_raw() -> RawFile:
|
|
"""Time axis + two real signals -> transient."""
|
|
t = np.linspace(0, 0.01, 500)
|
|
data = np.array([t, np.sin(2 * np.pi * 1000 * t), np.cos(2 * np.pi * 1000 * t)])
|
|
return RawFile(
|
|
title="tran", date="", plotname="Transient Analysis", flags=["real"],
|
|
variables=[
|
|
Variable(0, "time", "time"),
|
|
Variable(1, "V(out)", "voltage"),
|
|
Variable(2, "V(ref)", "voltage"),
|
|
],
|
|
points=len(t), data=data,
|
|
)
|
|
|
|
|
|
class TestAutoPlotType:
|
|
def test_complex_frequency_is_bode(self):
|
|
r = build_waveform_svg(_ac_raw(), ["V(out)"])
|
|
assert r["plot_type"] == "bode"
|
|
assert r["svg"].lstrip().startswith("<") # real SVG markup
|
|
|
|
def test_real_frequency_is_spectrum(self):
|
|
# frequency axis but real signal data -> spectrum
|
|
freq = np.logspace(0, 6, 200)
|
|
data = np.array([freq, np.abs(1.0 / (1.0 + 1j * freq / 1000.0))])
|
|
raw = RawFile(
|
|
title="", date="", plotname="AC Analysis", flags=["real"],
|
|
variables=[Variable(0, "frequency", "frequency"), Variable(1, "V(out)", "voltage")],
|
|
points=len(freq), data=data,
|
|
)
|
|
assert build_waveform_svg(raw, ["V(out)"])["plot_type"] == "spectrum"
|
|
|
|
def test_time_axis_is_time(self):
|
|
assert build_waveform_svg(_tran_raw(), ["V(out)"])["plot_type"] == "time"
|
|
|
|
def test_explicit_overrides_auto(self):
|
|
assert build_waveform_svg(_tran_raw(), ["V(out)"], plot_type="time")["plot_type"] == "time"
|
|
|
|
|
|
class TestErrorsAndMetadata:
|
|
def test_missing_signal(self):
|
|
r = build_waveform_svg(_ac_raw(), ["V(ghost)"])
|
|
assert "error" in r and "V(ghost)" in r["error"]
|
|
assert r["available_signals"] == ["frequency", "V(out)", "V(in)"]
|
|
|
|
def test_multi_signal_non_time_is_error(self):
|
|
r = build_waveform_svg(_ac_raw(), ["V(out)", "V(in)"], plot_type="bode")
|
|
assert "error" in r and "time-domain" in r["error"]
|
|
|
|
def test_dimensions_and_signal_metadata(self):
|
|
r = build_waveform_svg(_tran_raw(), ["V(out)"], width=1000, height=600)
|
|
assert r["dimensions"] == "1000x600"
|
|
assert r["signal"] == "V(out)"
|
|
|
|
def test_default_height_depends_on_plot_type(self):
|
|
assert build_waveform_svg(_ac_raw(), ["V(out)"])["dimensions"] == "800x500" # bode
|
|
assert build_waveform_svg(_tran_raw(), ["V(out)"])["dimensions"] == "800x400" # time
|
|
|
|
|
|
class TestSamplingAndMulti:
|
|
def test_max_points_downsamples(self):
|
|
r = build_waveform_svg(_tran_raw(), ["V(out)"], max_points=100)
|
|
assert r["points"] <= 100
|
|
|
|
def test_x_range_clip_reduces_points(self):
|
|
full = build_waveform_svg(_tran_raw(), ["V(out)"], max_points=10000)["points"]
|
|
clipped = build_waveform_svg(
|
|
_tran_raw(), ["V(out)"], x_min=0.002, x_max=0.004, max_points=10000
|
|
)["points"]
|
|
assert clipped < full
|
|
|
|
def test_multi_signal_time_overlay(self):
|
|
r = build_waveform_svg(_tran_raw(), ["V(out)", "V(ref)"])
|
|
assert r["plot_type"] == "time"
|
|
assert r["signal"] == "V(out), V(ref)"
|