Skip to content
Test Station Setup

What Are Test Sockets and How to Use Them

A test socket is one fixture position plus the context that owns its unit. Learn how sockets isolate results and how to configure them as slots.

JJulien Buteau
intermediate9 min readSeptember 9, 2026

What Are Test Sockets and How to Use Them

A test socket is one position on a fixture that holds a unit under test, together with the software context that tracks it. A four-position fixture has four sockets, and the test executive runs four copies of the test, one per socket, each with its own unit, its own measurements, and its own result.

The term comes from NI TestStand, where sockets are the mechanism behind parallel and batch models. The TofuPilot Framework calls the same concept a slot. This guide explains what sockets do, what a socket identifier is for, and how to configure them.

Why the Concept Exists

Running the same test on four units at once creates an identity problem. Four voltage measurements arrive, and every one of them is called supply_voltage. Without a per-position identity, there is no way to say which unit failed, which fixture position is drifting, or which serial number to attach to a result.

A socket solves this by being the thing that owns the unit for the duration of the test:

A socket ownsExample
The unit under testSerial SN-2024-003, in position 3
Its instrument channelsDMM channel 3, PSU output 3
Its measurementsThe voltage read from that unit
Its outcomePass, independent of the other positions
Its result recordOne run on the dashboard

Four sockets means four independent test contexts sharing one station, one procedure, and one operator.

Sockets are the mechanism behind parallel and batch execution; for choosing between those strategies, see Sequential vs Parallel vs Batch Testing.

The Socket Identifier

Every socket carries an identifier your test code can read. This is the single most important thing a socket gives you, because it is how a generic test becomes position-aware.

The problem it solves: your phase code is written once but runs N times. Something has to tell instance 3 to talk to instrument channel 3. That something is the socket identifier.

Most phases never need it. When each socket has its own instrument, the plug is socket-scoped and the phase stays generic:

phases/measure_voltage.py
def measure_voltage(measurements, power_supply):    measurements.voltage = power_supply.read_voltage()

When a phase does need to know its position — addressing a channel on a shared instrument, say — it reads run.slot_id, the key declared in execution.slots:

phases/measure_rail.py
CHANNELS = {"s1": 101, "s2": 102, "s3": 103, "s4": 104}def measure_rail(measurements, run, dmm):    measurements.rail_voltage = dmm.measure_dc_volts(CHANNELS[run.slot_id])

Keep per-socket wiring in configuration and lookup tables rather than branching on position inside the test logic. That is what keeps the procedure readable as the fixture grows from four positions to eighty.

Configuring Sockets

In the TofuPilot Framework, sockets are declared as slots under execution:

procedure.yaml
execution:  slots:    - name: USB0    - name: USB1    - name: USB2    - name: USB3

Each entry takes a name and an optional key, the identifier phases see. When the key is omitted it is derived from the name. Naming slots after the physical thing they correspond to — a USB port, a nest number, a probe head — is what makes a failure report actionable, because "USB2 has failed 40 times today" points at hardware.

For a fixture with many identical positions, give a count:

procedure.yaml
execution:  slots: 80

The count expands to keys slot_01 through slot_80, zero-padded so positions sort correctly everywhere. The count is capped at 1024, and the engine bounds slots times phases at 10,000 jobs per run. Use the list form when positions have meaningful names.

Per-Socket Unit Identification

Each socket identifies its own unit. For a station where an operator scans each position, the unit prompt appears per socket. For a rack where serials are derived from position, use the {slot} and {slot_name} placeholders:

procedure.yaml
execution:  slots:    - key: s01      name: Nest 1    - key: s02      name: Nest 2unit:  auto_identify: true  serial_number:    default_value: "BURNIN-{slot}"  part_number:    default_value: "PCB-MAIN-V2"  batch_number:    default_value: "{slot_name}"

With auto_identify: true the rack starts with no operator input at all. Without it, the expanded value pre-fills each socket's prompt, which is the useful middle ground: the operator confirms rather than types.

Single-slot procedures never see the placeholders and keep them as literal text, so adding them early does not break a single-position station.

One Run Per Socket

Each socket uploads its own run: its unit, its phases plus any shared setup and teardown, its measurements, logs, attachments, and its own outcome.

The runs from one start share an execution_id, and each carries its slot_key and slot_name. That gives two views of the same data:

  • Group by execution_id to see everything that happened in one cycle.
  • Filter by slot_key to see one fixture position over time.

The second view is what catches a bad position. If nest 3 fails 12 percent while the other seven sit at 2 percent, the problem is the fixture, not the product — and no amount of aggregate yield analysis would have shown it.

Socket-Scoped and Shared Resources

Instruments are attached with a scope that decides how many instances exist:

ScopeInstanceUse for
slot (default)One per socketPer-position PSU channel, per-position serial port
executionOne shared by all socketsA single DMM behind a mux, a chamber
stationOne per station, held across runsSlow-to-open VISA or TCP connections
procedure.yaml
plugs:  - name: power_supply    python: plugs.psu:PowerSupply    scope: slot  - name: chamber    python: plugs.chamber:ThermalChamber    scope: execution

A plug instance runs one method call at a time. When several sockets call the same execution or station plug, the calls queue and execute one at a time. This is the main throughput trap in socket-based testing: a shared instrument method that takes two seconds costs eight seconds across four sockets, and the parallelism you configured quietly does not exist. Keep shared plug methods short and put long waits in phases. Share Instruments Across Parallel Slots covers this in detail.

Failure Isolation

Sockets fail independently. A failing phase under the default stop behavior, a phase.stop(), an error, or a timeout skips the remaining phases of that socket and runs its socket-scoped teardown, while the other sockets continue. Each socket gets its own outcome, and the run outcome is the worst of them.

Only three things stop every socket: a failure in a phase shared by all of them (setup or teardown with scope: execution), a plug that fails to initialize, or the operator stopping the run.

This creates one rule worth stating plainly, because violating it produces intermittent failures that are very hard to diagnose:

A socket-scoped teardown can run while its neighbours are still testing. Anything it touches must belong to that socket alone. A rack power supply, a chamber, a fixture lock, a shared mux belongs in a teardown phase with scope: execution, which runs once after every socket is done.

procedure.yaml
teardown:  - name: Discharge This Position    python: phases.discharge  - name: Power Down Rack Supply    python: phases.rack_off    scope: execution

If Power Down Rack Supply were socket-scoped, the first socket to finish would cut power to every unit still under test. The symptom is a handful of unexplained failures on the slowest positions, which looks like a product problem and is not.

Common Pitfalls

Hardcoding an instrument address in a socket-scoped plug. Four sockets then talk to the same physical channel, and four units report the first unit's measurements. Pass the address through per-socket plug config.

Assuming sockets speed up a shared instrument. They do not. Queuing on an execution-scoped plug serializes those calls by design.

Naming sockets by index only. slot_03 is harder to act on than Nest 3 or Probe C. Names appear in every failure report.

Forgetting that shared setup failure stops everything. A chamber that will not reach temperature correctly aborts all sockets. That is the intended behavior, but it means shared setup phases deserve retries and clear error messages more than per-socket phases do.

Key Points

  • A test socket is one fixture position plus the software context that owns its unit, measurements, and outcome.
  • The socket identifier, read from run.slot_id, is what makes one generic test position-aware.
  • In the TofuPilot Framework, sockets are execution.slots, and each uploads its own run tagged with slot_key and execution_id.
  • Filtering results by socket is how you catch a bad fixture position that aggregate yield hides.
  • Shared instruments serialize across sockets, and shared resources must use scope: execution in teardown.

More Guides

Put this guide into practice