Testing helpers¶
FakeDtolBackend is a faithful, hardware-free stand-in for the real backend —
it enforces the same ordering and capability rules the SDK does. The
dtollib.testing factories build pre-canned backends and capability sets for
the DT9805 / DT9806 and multi-sensor modules. See the
Testing guide.
Fixtures¶
dtollib.testing ¶
Test ergonomics — pre-populated fake boards and capability sets.
Importable from production code (it lives in dtollib proper, not
under tests/) so downstream consumers — including the sibling
*lib packages — can spin up a fake DT9805 / DT9806 without
copy-pasting capability flags.
The realistic capability sets here come from cross-referencing the DataAcq SDK manual with the DT9805 / DT9806 datasheets. They are not authoritative replacements for live capability queries against real hardware — they exist so unit tests can target a plausible "this is what a DT9805 looks like" without requiring a board on the bench.
Bench-confirmed capability snapshots replace these defaults in
:func:make_fake_dt9805 / :func:make_fake_dt9806.
make_counter_capabilities ¶
Realistic capabilities for a C/T-family subsystem (CT / QUAD / TACH).
Counter subsystems read on demand (single-value) after start; they have no multi-sensor inputs and no DMA.
Source code in src/dtollib/testing.py
make_dt9805_capabilities ¶
A/D capabilities for a DT9805 — bench-verified on real hardware.
Snapshot taken 2026-05-28 via olDaGetSSCaps against a live
DT9806(00) A/D subsystem (the DT9805 A/D is identical). Note what
this disproves about the earlier datasheet-guessed values: these
boards are not multi-sensor and do not return linearised
floats — they return raw codes and the wrapper applies NIST ITS-90
itself, keyed off supports_thermocouples (see docs/decisions.md
and :func:dtollib.utils.convert_volts_to_temperature).
Source code in src/dtollib/testing.py
make_dt9806_ao_capabilities ¶
Realistic D/A capabilities for a DT9806.
Two AO channels, simultaneous-update support, no multi-sensor inputs (this is the output subsystem).
supports_continuous=True here models a streaming D/A so the
play() buffer-pool / output-bridge software path stays fully
unit-tested. The physical DT9806 D/A is single-value only — it
reports OLSSC_SUP_CONTINUOUS=0 and play() raises
:class:~dtollib.errors.DtolCapabilityError on it (bench-confirmed
2026-05-28; see docs/decisions.md). Same fake-models-the-ideal pattern as
:func:make_counter_capabilities (QUAD/TACH/MEASURE).
Source code in src/dtollib/testing.py
make_dt9806_capabilities ¶
make_fake_backend ¶
Construct a :class:FakeDtolBackend with optional pre-populated boards.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
boards
|
list[FakeBoard] | None
|
Explicit list of fake boards. Combined with the
|
None
|
include_dt9805
|
bool
|
Prepend a default fake DT9805 to the board list. |
False
|
include_dt9806
|
bool
|
Prepend a default fake DT9806 to the board list. |
False
|
Returns:
| Type | Description |
|---|---|
FakeDtolBackend
|
A configured :class: |
Source code in src/dtollib/testing.py
make_fake_dt9805 ¶
Construct a :class:FakeBoard mimicking a connected DT9805.
Source code in src/dtollib/testing.py
make_fake_dt9806 ¶
Construct a :class:FakeBoard mimicking a connected DT9806.
Exposes the full subsystem set: A/D, D/A, digital in/out, counter/timer, quadrature decoder, and tachometer. The QUAD/TACH subsystems are modelled so the counter/timer software path is fully unit-tested even where physical hardware may not expose them (OQ-5b — see docs/decisions.md).
Source code in src/dtollib/testing.py
make_fake_multisensor ¶
Construct a :class:FakeBoard mimicking an intelligent multi-sensor module.
The single A/D subsystem reports supports_multisensor=True, so the
builder exercises the full multi-sensor configure path (set_multi_sensor_type
→ per-sensor setters) that the owned DT9805/06 reject with ECODE 36.
Source code in src/dtollib/testing.py
make_firmware_tc_capabilities ¶
Synthetic A/D caps for a firmware-linearising thermocouple board.
No DT board in this project actually linearises thermocouples in
firmware (the DT9805/06 do not — see :func:make_dt9805_capabilities),
but the wrapper still supports that path for hypothetical boards that
report OLSSC_RETURNS_FLOATS. This fixture keeps that branch under
test: returns_floats=True so olDaGetSingleFloat is used and the
device's TC sentinels are honoured directly.
Source code in src/dtollib/testing.py
make_multisensor_capabilities ¶
Synthetic A/D caps for an intelligent multi-sensor DT module.
Models a DT9828/9829/9837-class board: supports_multisensor=True so
the :class:~dtollib.tasks.TaskBuilder re-types each channel via
set_multi_sensor_type and the multi-sensor per-sensor configure path runs
end to end on the fake. No board in this project actually owns these
capabilities — the owned DT9805/06 report supports_multisensor=False
(see :func:make_dt9805_capabilities) — so this fixture is the only way
to exercise the RTD/thermistor/strain/bridge/IEPE configure path in CI.
Source code in src/dtollib/testing.py
Fake backend¶
dtollib.backend.fake ¶
In-memory fake backend — :class:FakeDtolBackend.
Cross-platform, no SDK, no hardware. Every unit test in
tests/unit/ exercises the same code paths as the real backend by
swapping in a :class:FakeDtolBackend instance.
The fake is not a stub — it enforces the same ordering and capability rules as the real SDK so unit tests catch the same bugs hardware would. The fake's invariant table is the canonical statement of contract; it grows monotonically as capabilities land.
Discovery / lifecycle / capability invariants:
initialize(name)ref-counts HDRVRs; first call opens the device, last :meth:terminatecloses it; intermediate calls just bump / decrement the refcount.get_dass(hdrvr, type, element)returns a stable handle until :meth:release_dassis called. Re-acquiring after release is permitted (the SDK behaves the same way).- :meth:
enum_subsystemsreturns only the subsystems the fake board was constructed with. Capability queries against an HDASS reflect the constructed-time CapabilitySet exactly. - :meth:
get_versionreturns the strings stored on the fake; default"fake-7.8.5"for both DLLs.
Construction helpers in :mod:dtollib.testing pre-populate the fake
with realistic DT9805 / DT9806 capability sets so downstream tests
stay short.
FakeBoard
dataclass
¶
One scripted board.
Mutable; constructed via :class:FakeDtolBackend arguments or via
direct manipulation of :attr:FakeDtolBackend.boards.
FakeDtolBackend ¶
In-memory backend satisfying :class:~dtollib.backend.DtolBackend.
Construction takes a list of :class:FakeBoard instances. An
empty list represents "SDK installed but no boards plugged in" —
the SDK contract for that state is "olDaEnumBoards returns
success with zero callbacks", which translates to
enum_boards() -> [] here.
Attributes:
| Name | Type | Description |
|---|---|---|
boards |
list[FakeBoard]
|
List of scripted boards. Mutable for tests. |
operations |
list[tuple[str, object]]
|
Ordered log of |
scripted_failures |
dict[str, int]
|
|
oldaapi_version |
Reported by :meth: |
|
olmem_version |
Reported by :meth: |
Source code in src/dtollib/backend/fake.py
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 | |
abort ¶
Transition RUNNING → ABORTING → INITIALIZED.
Source code in src/dtollib/backend/fake.py
add_channel ¶
Add a channel to the channel/gain list, enforcing MULTI_SENSOR ordering.
Source code in src/dtollib/backend/fake.py
alloc_buffer ¶
Allocate a synthetic HBUF backed by a numpy ndarray.
Source code in src/dtollib/backend/fake.py
arm ¶
olDaConfig #2 — continuous mode only; arm for start.
Enforces the bench-proven ordering: arm runs after the
notification window is wired (:meth:register_notification) and
the Ready queue is seeded. It raises if either precondition is
unmet — register-BEFORE-arm and queue-BEFORE-arm — and refuses on a
single-value subsystem (which has no second config).
Source code in src/dtollib/backend/fake.py
code_to_volts ¶
Offset-binary conversion mirroring the real DT9805/06 A/D.
Matches :class:~dtollib.backend.dataacq.DataAcqBackend.code_to_volts
on a ±10 V, 16-bit, offset-binary subsystem so the application-side
thermocouple read path is exercised faithfully in unit tests: code
32768 → 0 V, 0 → −10 V, 65535 → +10 V, referred through
gain.
Source code in src/dtollib/backend/fake.py
commit ¶
olDaConfig #1 — INITIALIZED → CONFIGURED_FOR_{SINGLE_VALUE|CONTINUOUS}.
This is config #1: it runs after channel/clock/wrap setup and
before the notification window + Ready queue are wired. It does
NOT require a registered notification or queued buffers — those are
preconditions of :meth:arm (config #2), matching the bench-proven
ordering (docs/decisions.md).
Source code in src/dtollib/backend/fake.py
copy_buffer ¶
Copy a synthetic HBUF's samples back out to a host byte payload.
Source code in src/dtollib/backend/fake.py
copy_inprocess_buffer ¶
Copy the in-process HBUF's valid samples without waiting.
Models olDmCopyFromBuffer: returns at most n_samples of the
HBUF's recorded valid samples as raw bytes (empty when the buffer
holds nothing yet). sample_dtype_bytes is accepted for signature
parity; the synthetic payload already carries its own dtype.
Source code in src/dtollib/backend/fake.py
copy_to_buffer ¶
Fill a synthetic HBUF from a host byte payload (waveform pre-fill).
Source code in src/dtollib/backend/fake.py
ct_mode_of ¶
enum_boards ¶
Satisfies :meth:DtolBackend.enum_boards — returns scripted boards.
Source code in src/dtollib/backend/fake.py
enum_subsystems ¶
Satisfies :meth:DtolBackend.enum_subsystems — scripted subsystems.
Source code in src/dtollib/backend/fake.py
fail_next ¶
Cause the next call to operation to raise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operation
|
str
|
Operation name as logged in
:attr: |
required |
code
|
int
|
SDK-style ECODE. Used to construct an
:class: |
required |
Source code in src/dtollib/backend/fake.py
fire_buffer_done ¶
Promote the next Ready HBUF to Done and fire BUFFER_DONE.
Returns the HBUF that was moved (None if Ready queue empty). Optionally writes a synthetic payload into the HBUF before firing.
Source code in src/dtollib/backend/fake.py
fire_event ¶
Synchronously fire an SDK notification event on the registered callback.
Used by continuous callback-bridge tests to inject events without a
real SDK driver thread. Raises :class:DtolTaskStateError if no
callback is registered.
Source code in src/dtollib/backend/fake.py
flush_buffers ¶
Empty the Ready and Done queues.
Source code in src/dtollib/backend/fake.py
force_hbuf_state ¶
free_buffer ¶
Release a synthetic HBUF; rejects free while INPROCESS.
Source code in src/dtollib/backend/fake.py
get_buffer ¶
Pop the next HBUF from the Done queue (None if empty).
Source code in src/dtollib/backend/fake.py
get_buffer_valid_samples ¶
Return the recorded valid-sample count for the HBUF.
Source code in src/dtollib/backend/fake.py
get_cjc_temperature ¶
Return a plausible CJC temperature; tests override via scalar_values.
Source code in src/dtollib/backend/fake.py
get_clock_frequency ¶
Return the recorded clock rate (driver may quantise the request).
Source code in src/dtollib/backend/fake.py
get_dass ¶
Satisfies :meth:DtolBackend.get_dass — returns synthetic HDASS.
Source code in src/dtollib/backend/fake.py
get_queue_size ¶
Return depth of the Ready / Inprocess / Done queue (0 / 1 / 2).
Source code in src/dtollib/backend/fake.py
get_scaling ¶
Return the fake DT9805/06 scaling: ±10 V, 16-bit, offset-binary.
Mirrors :meth:code_to_volts so the continuous block path builds a
:class:~dtollib.capi.conversion.BlockConversion plan consistent with
the fake's single-value scaling. twos_complement is False
(offset/straight binary, like the real A/D).
Source code in src/dtollib/backend/fake.py
get_single_float ¶
Return scripted scalar or TC sentinel for the channel.
Source code in src/dtollib/backend/fake.py
get_single_floats ¶
Return per-channel scripted floats with TC sentinel substitution.
Source code in src/dtollib/backend/fake.py
get_single_value ¶
Return scripted scalar_values[(hdass, channel)] cast to int.
Source code in src/dtollib/backend/fake.py
get_single_values ¶
Return per-channel scripted ints; missing channels default to 0.
Source code in src/dtollib/backend/fake.py
get_ss_list ¶
Open a new simultaneous-start list bound to hdrvr.
Source code in src/dtollib/backend/fake.py
get_state ¶
get_version ¶
Satisfies :meth:DtolBackend.get_version — returns scripted strings.
Source code in src/dtollib/backend/fake.py
hbuf_state ¶
initialize ¶
Satisfies :meth:DtolBackend.initialize — ref-counted fake HDRVR.
Source code in src/dtollib/backend/fake.py
is_muted ¶
is_running ¶
measure_frequency ¶
Return the scripted frequency; requires a RUNNING counter.
Source code in src/dtollib/backend/fake.py
mute ¶
Mark the D/A output muted.
put_buffer ¶
Push an HBUF onto the Ready queue for hdass.
On a D/A (output) subsystem this enforces Fill-before-Queue: the HBUF
must have been copy_to_buffer-filled since it was last queued.
Queuing an unfilled DAC buffer would emit silence/garbage on real
hardware, so the fake rejects it loudly.
Source code in src/dtollib/backend/fake.py
put_dass_to_ss_list ¶
Add hdass to the list; rejects after pre-start (ordering invariant).
Source code in src/dtollib/backend/fake.py
put_single_value ¶
One-shot raw-code write; records the value for test inspection.
Source code in src/dtollib/backend/fake.py
put_single_values ¶
Simultaneous raw-code write; requires simultaneous-D/A support.
Source code in src/dtollib/backend/fake.py
query_capabilities ¶
Satisfies :meth:DtolBackend.query_capabilities — returns the scripted set.
Source code in src/dtollib/backend/fake.py
read_bridge_sensor_hardware_teds ¶
Return the scripted bridge TEDS for (hdass, channel).
Source code in src/dtollib/backend/fake.py
read_bridge_sensor_virtual_teds ¶
Return the scripted bridge virtual TEDS for path.
Source code in src/dtollib/backend/fake.py
read_buffer_payload ¶
Return the synthetic payload ndarray view for the HBUF.
Source code in src/dtollib/backend/fake.py
read_events ¶
Return the scripted counter value; requires a RUNNING counter.
Source code in src/dtollib/backend/fake.py
read_strain_gage_hardware_teds ¶
Return the scripted strain-gage TEDS for (hdass, channel).
Source code in src/dtollib/backend/fake.py
read_strain_gage_virtual_teds ¶
Return the scripted strain-gage virtual TEDS for path.
Source code in src/dtollib/backend/fake.py
register_notification ¶
Install a synthetic notification callback.
Mirrors olDaSetWndHandle: registration happens after config #1
(commit) but before arm (config #2). Rejects if the task is
already armed or running — registering then would not be wired into
the buffer-rotation state machine.
Source code in src/dtollib/backend/fake.py
release_dass ¶
Satisfies :meth:DtolBackend.release_dass — forgets the HDASS.
Source code in src/dtollib/backend/fake.py
release_ss_list ¶
Release the list handle; using it afterwards raises.
Source code in src/dtollib/backend/fake.py
script_count ¶
Set the value the next :meth:read_events returns for the channel.
script_frequency ¶
Set the value the next :meth:measure_frequency returns.
script_state ¶
set_cascade_mode ¶
Record the cascade-mode flag.
Source code in src/dtollib/backend/fake.py
set_channel_list ¶
Record the channel list on the fake HDASS.
Source code in src/dtollib/backend/fake.py
set_clock ¶
Record the clock configuration; quantise rate to 0.001 Hz precision.
Source code in src/dtollib/backend/fake.py
set_ct_clock ¶
Record the counter clock configuration.
Source code in src/dtollib/backend/fake.py
set_ct_mode ¶
Record the C/T mode; marks the HDASS eligible for other CT setters.
Source code in src/dtollib/backend/fake.py
set_data_flow ¶
Track data-flow mode via fake state. Records the call.
Source code in src/dtollib/backend/fake.py
set_digital_io_list_entry ¶
Record a digital-I/O list entry for the subsystem.
Source code in src/dtollib/backend/fake.py
set_dma_usage ¶
Record DMA usage.
Source code in src/dtollib/backend/fake.py
set_gate_type ¶
Record the gate type.
Source code in src/dtollib/backend/fake.py
set_inprocess_payload ¶
Test-only: give the head Ready HBUF a partial in-process payload.
Marks the buffer the SDK would currently be filling (the head of the
Ready queue) as Inprocess and writes fill into it WITHOUT moving
it to the Done queue, so :meth:DtolSession.read_inprocess can drain
it mid-fill. Returns the affected HBUF (None if the Ready queue is
empty).
Source code in src/dtollib/backend/fake.py
set_measure_edges ¶
Record the measurement edges.
Source code in src/dtollib/backend/fake.py
set_multi_sensor_type ¶
Re-type a MULTI_SENSOR channel. Marks it eligible for per-type setters.
Source code in src/dtollib/backend/fake.py
set_pulse ¶
Record the output pulse configuration.
Source code in src/dtollib/backend/fake.py
set_return_cjc_in_stream ¶
Record the interleaved-CJC setting for assertion by tests.
Source code in src/dtollib/backend/fake.py
set_stop_on_error ¶
Track the stop-on-error setting; no state change.
Source code in src/dtollib/backend/fake.py
set_synchronous_digital_io_usage ¶
Record synchronous digital-I/O usage for the subsystem.
Source code in src/dtollib/backend/fake.py
set_thermocouple_type ¶
Record TC type per channel for test inspection.
Source code in src/dtollib/backend/fake.py
set_trigger ¶
Record trigger configuration.
Source code in src/dtollib/backend/fake.py
set_triggered_scan ¶
Record the triggered-scan retrigger configuration.
Source code in src/dtollib/backend/fake.py
set_wrap_mode ¶
Record wrap mode.
Source code in src/dtollib/backend/fake.py
simultaneous_pre_start ¶
Arm every subsystem in the list → PRESTARTED.
Source code in src/dtollib/backend/fake.py
simultaneous_start ¶
Start every subsystem in the list → RUNNING; requires pre-start first.
Source code in src/dtollib/backend/fake.py
start ¶
Transition CONFIGURED_* → RUNNING (idempotent on already-RUNNING).
Source code in src/dtollib/backend/fake.py
state_of ¶
Current scripted :class:SubsystemState for hdass.
Defaults to INITIALIZED for an open-but-untouched HDASS.
Tests use this to assert exact transitions.
Source code in src/dtollib/backend/fake.py
stop ¶
Transition RUNNING → STOPPING → IO_COMPLETE → INITIALIZED.
Source code in src/dtollib/backend/fake.py
terminate ¶
Satisfies :meth:DtolBackend.terminate — refcount-aware fake close.
Source code in src/dtollib/backend/fake.py
unmute ¶
Release a muted D/A output.
unregister_notification ¶
Uninstall the notification callback.
Enforces the §12.3.2 stop-BEFORE-unregister invariant.
Source code in src/dtollib/backend/fake.py
volts_to_bridge_based_sensor ¶
volts_to_bridge_based_sensor(
v_unstrained,
v_strained,
v_excitation,
temperature_coefficient,
gage_resistance,
lead_resistance,
rated_output_mv_per_v,
shunt_correction,
)
Deterministic bridge-sensor approximation for tests.
value ≈ (Vr / rated_output) × full-scale, with Vr in mV/V; here
simplified to Vr·1000 / rated_output_mv_per_v.
Source code in src/dtollib/backend/fake.py
volts_to_strain ¶
volts_to_strain(
config,
v_unstrained,
v_strained,
v_excitation,
gage_factor,
gage_resistance,
lead_resistance,
poisson_ratio,
shunt_correction,
)
Deterministic quarter-bridge strain approximation for tests.
ε ≈ -4·Vr / GF with Vr = (Vstrained - Vunstrained) / Vexcitation. Not physically exact (lead/Poisson/shunt ignored) — it only needs to be deterministic so wiring tests can assert a stable value.
Source code in src/dtollib/backend/fake.py
FakeSubsystem
dataclass
¶
One scripted subsystem on a :class:FakeBoard.
Mutable: tests script per-subsystem behaviour by tweaking the fields after construction. Capability set is required; the rest are derived from it where sensible.