cf9ce91e51
The first and only thing in the MTConnect workstream that exercises the driver
against a real Agent; everything else runs on canned XML.
Fixture (Docker/): two services, both stock images with this folder bind-mounted.
- `agent` mtconnect/agent:2.7.0.12 on :5000. NOTE the repo name: the source
project is mtconnect/cppagent but the PUBLISHED image is
mtconnect/agent; mtconnect/cppagent does not exist on Docker Hub.
- `adapter` a stdlib SHDR feeder. Required, not optional: the agent image ships
only the binary + schemas/styles, so a lone Agent answers /probe and
then reports every observation UNAVAILABLE forever, which can prove
nothing about reads or streaming.
Devices.xml seeds one DataItem per inference branch (SAMPLE+units, TIME_SERIES,
PART_COUNT/LINE_NUMBER, controlled vocab, DATA_SET, CONDITION), gives every named
item `name != id` (the inverse of the hand-authored unit fixtures), and leaves one
EVENT permanently unfed so UNAVAILABLE -> BadNoCommunication has a live subject.
Suite (12 tests) asserts STRUCTURALLY against the Agent's own /probe response, never
by literal id, so it survives an edit to Devices.xml and can be pointed at a real
machine tool via MTCONNECT_AGENT_ENDPOINT. It skips cleanly (12/12) when no Agent
answers, and each test carries a hard [Fact(Timeout)] so a half-up fixture cannot
wedge a build.
Three findings from bringing the fixture up, each now pinned in a comment:
- agent.cfg must be pure ASCII; one non-ASCII byte in a COMMENT makes the config
parser reject the whole file with a bare "Failed / Stopped at line: N" and exit.
- `sampleCount` is an attribute of a TIME_SERIES observation, not of a DataItem
declaration. A real Agent meeting one on a declaration DROPS THE ENTIRE DATA ITEM
from the device model, so the inference only ever sees null and a live TIME_SERIES
tag is always variable-length. Asserted, not ignored.
- The Agent's own <Agent> self-model publishes update-rate SAMPLEs that tick whether
or not any adapter is attached. Excluding them is load-bearing: with them included
the "live stream delivered a changed value" test passed with the fixture's data
source deliberately stopped.
MTConnectError-under-HTTP-200 is NOT covered live and cannot be: cppagent 2.7 answers
an unknown device 404 and an out-of-range sequence 400, each with a well-formed error
body. That shape stays canned-only, and the suite says so.
80 lines
3.5 KiB
YAML
80 lines
3.5 KiB
YAML
# MTConnect integration-test fixture — the official MTConnect C++ Agent, fed live data by
|
|
# a standard-library SHDR adapter.
|
|
#
|
|
# TWO services, and both are required:
|
|
#
|
|
# agent mtconnect/agent (the cppagent image). Serves /probe, /current and /sample on
|
|
# :5000. NOTE the repository name — the source project is `mtconnect/cppagent`
|
|
# but the published image is `mtconnect/agent`; `mtconnect/cppagent` does not
|
|
# exist on Docker Hub.
|
|
# adapter The data source. The agent image ships only the agent binary plus schemas and
|
|
# styles — there is NO bundled simulator — so without this every observation is
|
|
# UNAVAILABLE forever and the suite could prove nothing about reads or streaming.
|
|
# The Agent dials OUT to it (agent.cfg's Adapters block); it is not published to
|
|
# the host.
|
|
#
|
|
# Why pinned: the `latest` tag moves and a fixture that silently changes device-model
|
|
# behaviour turns a driver regression into an unexplained red. Bump deliberately.
|
|
#
|
|
# Usage (from the docker host, stack dir /opt/otopcua-mtconnect):
|
|
# docker compose up -d --wait
|
|
# docker compose down
|
|
#
|
|
# Or from a dev box, via the helper (see CLAUDE.md "Docker Workflow"):
|
|
# lmxopcua-fix sync mtconnect
|
|
# lmxopcua-fix up mtconnect # single-service-shaped stack, no profile argument
|
|
services:
|
|
agent:
|
|
image: mtconnect/agent:2.7.0.12
|
|
container_name: otopcua-mtconnect-agent
|
|
restart: "no"
|
|
labels:
|
|
project: lmxopcua
|
|
depends_on:
|
|
adapter:
|
|
condition: service_started
|
|
ports:
|
|
# Host port is overridable because macOS squats :5000 — AirPlay Receiver
|
|
# (ControlCenter) binds *:5000 and WINS the race, so `docker port` reports a healthy
|
|
# publish while every request answers "403 Forbidden / Server: AirTunes". On the
|
|
# Linux docker host the default is correct and needs no override. To run the fixture
|
|
# on a Mac:
|
|
# MTCONNECT_AGENT_HOST_PORT=5555 docker compose up -d --wait
|
|
# MTCONNECT_AGENT_ENDPOINT=http://127.0.0.1:5555 dotnet test ...
|
|
- "${MTCONNECT_AGENT_HOST_PORT:-5000}:5000"
|
|
volumes:
|
|
# Individual FILE binds, not a directory bind: /mtconnect/config is a VOLUME declared
|
|
# by the image, and mounting this whole folder over it would also drop docker-compose.yml
|
|
# and adapter.py into the Agent's config directory.
|
|
- ./agent.cfg:/mtconnect/config/agent.cfg:ro
|
|
- ./Devices.xml:/mtconnect/config/Devices.xml:ro
|
|
healthcheck:
|
|
# The image is alpine-based, so busybox wget is present (there is no curl). A 200 from
|
|
# /probe is the real readiness signal — the port accepts connections before the device
|
|
# model is parsed.
|
|
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:5000/probe || exit 1"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 12
|
|
start_period: 5s
|
|
|
|
adapter:
|
|
image: python:3.13-alpine
|
|
container_name: otopcua-mtconnect-adapter
|
|
restart: "no"
|
|
labels:
|
|
project: lmxopcua
|
|
volumes:
|
|
- ./adapter.py:/fixtures/adapter.py:ro
|
|
# Stock image + a bind-mounted script: this fixture needs no `build:` step at all, unlike
|
|
# the Modbus / S7 / AB fixtures whose simulators are built from a Dockerfile.
|
|
command: ["python", "-u", "/fixtures/adapter.py", "--host", "0.0.0.0", "--port", "7878"]
|
|
expose:
|
|
- "7878"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "python -c \"import socket; socket.create_connection(('127.0.0.1', 7878), timeout=2).close()\" || exit 1"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 6
|
|
start_period: 3s
|