rosotacom-dev 2.5.dev4__py3-none-any.whl → 2.5.dev6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rosotacom/cli.py +49 -2
- rosotacom/status_eval.py +21 -0
- {rosotacom_dev-2.5.dev4.dist-info → rosotacom_dev-2.5.dev6.dist-info}/METADATA +1 -1
- {rosotacom_dev-2.5.dev4.dist-info → rosotacom_dev-2.5.dev6.dist-info}/RECORD +8 -8
- {rosotacom_dev-2.5.dev4.dist-info → rosotacom_dev-2.5.dev6.dist-info}/WHEEL +0 -0
- {rosotacom_dev-2.5.dev4.dist-info → rosotacom_dev-2.5.dev6.dist-info}/entry_points.txt +0 -0
- {rosotacom_dev-2.5.dev4.dist-info → rosotacom_dev-2.5.dev6.dist-info}/licenses/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev4.dist-info → rosotacom_dev-2.5.dev6.dist-info}/top_level.txt +0 -0
rosotacom/cli.py
CHANGED
|
@@ -4249,13 +4249,40 @@ def _create_scenario_tmux(
|
|
|
4249
4249
|
return session_name
|
|
4250
4250
|
|
|
4251
4251
|
|
|
4252
|
+
def _local_ipc_run_args(network_isolated: bool) -> list[str]:
|
|
4253
|
+
"""Share the host IPC namespace, unless the run is network-isolated.
|
|
4254
|
+
|
|
4255
|
+
A peer has to exchange local-domain data with application containers on the
|
|
4256
|
+
same machine — a replay, the control centre. With `--network host` but
|
|
4257
|
+
`ipc=private` each container has its own `/dev/shm`, FastDDS sees two
|
|
4258
|
+
participants on one host, negotiates shared memory, and writes into a
|
|
4259
|
+
segment the other side cannot map. Discovery goes over UDP and works;
|
|
4260
|
+
delivery goes over SHM and is dropped without a word.
|
|
4261
|
+
|
|
4262
|
+
The result is the worst shape a failure can take: the link comes up, the
|
|
4263
|
+
heartbeat crosses at 10 Hz with 0% loss, every pipeline reads `pub=1 sub=1`
|
|
4264
|
+
— and no payload ever enters it. Measured on 2026-08-09: 399 messages in
|
|
4265
|
+
eight seconds inside the publisher's container, 0 in the peer's.
|
|
4266
|
+
|
|
4267
|
+
The OTA path never showed it, because the generated unicast profile pins
|
|
4268
|
+
explicit locators and therefore forces UDP.
|
|
4269
|
+
|
|
4270
|
+
Not under `--network-name`. Sharing SHM there would let DDS bypass the very
|
|
4271
|
+
boundary the isolation exists to simulate, and a test that passes through a
|
|
4272
|
+
hole in its own isolation is worse than no test at all.
|
|
4273
|
+
"""
|
|
4274
|
+
return [] if network_isolated else ["--ipc", "host"]
|
|
4275
|
+
|
|
4276
|
+
|
|
4252
4277
|
def _base_extra_run_args(
|
|
4253
4278
|
runtime: RuntimeConfig,
|
|
4254
4279
|
session: ResolvedSession,
|
|
4255
4280
|
cfg: dict[str, Any],
|
|
4256
4281
|
instance: SessionInstance,
|
|
4282
|
+
*,
|
|
4283
|
+
network_isolated: bool = False,
|
|
4257
4284
|
) -> list[str]:
|
|
4258
|
-
args: list[str] =
|
|
4285
|
+
args: list[str] = _local_ipc_run_args(network_isolated)
|
|
4259
4286
|
ros2docker_cfg = load_config(runtime.ros2docker_config)
|
|
4260
4287
|
|
|
4261
4288
|
if not ros2docker_cfg.get("mount_ws"):
|
|
@@ -4425,7 +4452,7 @@ def start_session(args: argparse.Namespace) -> str:
|
|
|
4425
4452
|
" (or pass --force to replace it, --skip-conflict-check to ignore it).",
|
|
4426
4453
|
)
|
|
4427
4454
|
image_name = _scoped_image_name(runtime)
|
|
4428
|
-
extra_run_args = _base_extra_run_args(runtime, session, cfg, instance)
|
|
4455
|
+
extra_run_args = _base_extra_run_args(runtime, session, cfg, instance, network_isolated=network_isolated)
|
|
4429
4456
|
run_override: dict[str, object] = {}
|
|
4430
4457
|
network_name = getattr(args, "network_name", None)
|
|
4431
4458
|
if network_name:
|
|
@@ -6513,6 +6540,26 @@ def test_command(args: argparse.Namespace) -> int:
|
|
|
6513
6540
|
from . import bag_ground_truth
|
|
6514
6541
|
|
|
6515
6542
|
ground_truth = bag_ground_truth.bag_ground_truth(bag)
|
|
6543
|
+
elif not getattr(args, "suggest", False):
|
|
6544
|
+
# A `vs_bag_ratio` needs the bag's native rate, and without one it is
|
|
6545
|
+
# skipped rather than failed -- so the run would print TEST OK having
|
|
6546
|
+
# checked nothing about completeness. Refuse instead: the declaration
|
|
6547
|
+
# is the statement of intent, and honouring it or failing loudly is
|
|
6548
|
+
# this command's job.
|
|
6549
|
+
needs_bag = status_eval.topics_requiring_bag(expect_by_topic, profile)
|
|
6550
|
+
if needs_bag:
|
|
6551
|
+
listed = "\n".join(f" {base}" for base in needs_bag)
|
|
6552
|
+
print(
|
|
6553
|
+
f"rosotacom test: {len(needs_bag)} topic(s) declare "
|
|
6554
|
+
f"`completeness.vs_bag_ratio`, which compares the delivered rate "
|
|
6555
|
+
f"to the source's native rate and can only be evaluated against "
|
|
6556
|
+
f"the replay bag:\n{listed}\n"
|
|
6557
|
+
f"Pass `--bag <dir>`, or drop the assertion from a session that "
|
|
6558
|
+
f"is not replay-driven. Without a bag it would be skipped in "
|
|
6559
|
+
f"silence and this run would report success.",
|
|
6560
|
+
file=sys.stderr,
|
|
6561
|
+
)
|
|
6562
|
+
return 1
|
|
6516
6563
|
reports: dict[str, Any] = {}
|
|
6517
6564
|
failures: list[str] = []
|
|
6518
6565
|
|
rosotacom/status_eval.py
CHANGED
|
@@ -105,6 +105,27 @@ def resolve_expect_for_profile(expect: dict[str, Any], profile: str | None) -> d
|
|
|
105
105
|
return resolved
|
|
106
106
|
|
|
107
107
|
|
|
108
|
+
def topics_requiring_bag(expect_by_topic: dict[str, dict[str, Any]], profile: str | None = None) -> list[str]:
|
|
109
|
+
"""Topics whose expectations can only be evaluated against a replay bag.
|
|
110
|
+
|
|
111
|
+
`completeness.vs_bag_ratio` compares the delivered rate to the *source's*
|
|
112
|
+
native rate, which only a bag knows. `_check_bag_completeness` returns
|
|
113
|
+
early when no ground truth was supplied, so without `--bag` such an
|
|
114
|
+
expectation is skipped in silence and the run still prints `TEST OK`.
|
|
115
|
+
|
|
116
|
+
A skipped assertion reads as coverage, which is worse than no assertion at
|
|
117
|
+
all: it is the exact shape of the defect these contracts exist to catch.
|
|
118
|
+
The caller uses this to refuse the run instead.
|
|
119
|
+
"""
|
|
120
|
+
required = []
|
|
121
|
+
for base, raw in sorted((expect_by_topic or {}).items()):
|
|
122
|
+
expect = resolve_expect_for_profile(raw or {}, profile)
|
|
123
|
+
completeness = expect.get("completeness") or {}
|
|
124
|
+
if isinstance(completeness, dict) and completeness.get("vs_bag_ratio") is not None:
|
|
125
|
+
required.append(base)
|
|
126
|
+
return required
|
|
127
|
+
|
|
128
|
+
|
|
108
129
|
def expectations_from_cfg(cfg: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|
109
130
|
"""Map a topic's base name -> its `expect` block, from a session config."""
|
|
110
131
|
out: dict[str, dict[str, Any]] = {}
|
|
@@ -5,7 +5,7 @@ rosotacom/bag_ground_truth.py,sha256=FlL34fA2CWZZxsrz7uxfhPt2VntPuS0ldrOPy58XPMk
|
|
|
5
5
|
rosotacom/benched_set.py,sha256=PGybWzNKJhg5wouIGo_zxlb7Fu41UzmtgEBxUR-Gkvg,19874
|
|
6
6
|
rosotacom/benchmark.py,sha256=Xda8WwFKQEIRMO0Qe1Hf7rvykYIgM86gUHBuyX9VvI8,74447
|
|
7
7
|
rosotacom/bundle_check.py,sha256=l_3BSuYk6juXQmsNU6jkxRbH8ayH2GkiW9ONKMQ1oJY,18270
|
|
8
|
-
rosotacom/cli.py,sha256=
|
|
8
|
+
rosotacom/cli.py,sha256=5nztYmxzS0rwY1RIFCzvLYcMAN4X-DqcdzMHp-obH-I,363053
|
|
9
9
|
rosotacom/cli_benchmark.py,sha256=bhdtvvO73g3-vlWTX0hf7I70lKQVT0pyXbIg3YnXfLQ,342197
|
|
10
10
|
rosotacom/deployment.py,sha256=XVhB8JjxhAsglSEEPtiVSeV6fCG0rgGmDRulh14oIYQ,7318
|
|
11
11
|
rosotacom/ffmpeg_packet.py,sha256=zB1BHgY1kexr5sETVMkm4bkVnRytj9k2t0uOYiHUZ6M,4267
|
|
@@ -15,7 +15,7 @@ rosotacom/network_profiles.py,sha256=fCsZYoSKBjAAYkC7Gbyv4s5L9OVmZs_hAHWTf0xMfHU
|
|
|
15
15
|
rosotacom/network_shaper.py,sha256=kjn9ZtiO1SMm8UHKIjmn92JBu_hE5TJF6vaNF1XKtNU,5463
|
|
16
16
|
rosotacom/plots.py,sha256=YRGRxuGHSHDZmtL0sv1JS7cPVSL9VzjGYuvMxeHAj54,22674
|
|
17
17
|
rosotacom/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
rosotacom/status_eval.py,sha256=
|
|
18
|
+
rosotacom/status_eval.py,sha256=V9KVho5DEBYCD_iG1X2Yg6qd6ZEzopdKSY5QJ6K1sP4,23115
|
|
19
19
|
rosotacom/stream_stats.py,sha256=ey09g4B7y0E-NnsOBW8Ng9kqyMbo2xuabNut9op6uM4,23744
|
|
20
20
|
rosotacom/trace_profiles.py,sha256=KNM1RExHDXkppWZdqbSPXTjwJ8f7io8IU48HnKzmHKY,20567
|
|
21
21
|
rosotacom/transit.py,sha256=0BqDO3nfYJ1PMXRz8b3084sDOrCjhO5zwdqC_-qU6bo,10531
|
|
@@ -187,9 +187,9 @@ rosotacom/resources/ws/session/creation/generate_session_files.py,sha256=R4HFetZ
|
|
|
187
187
|
rosotacom/resources/ws/session/creation/run_session.py,sha256=G2hJhrmLp11gm3JZ6_C-zZcqu_reXlE_wmobB7BiCfU,16849
|
|
188
188
|
rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py,sha256=Ub_5f-ttUUc-rAPrZmg_Op34s8Z4C91hTyEn8fgyt-E,2633
|
|
189
189
|
rosotacom/resources/ws/session/creation/strip_ansi.py,sha256=vNjpemcq4xHr0VG4x0bZqvgwEBEfOgN-b4PzbrM5nfM,851
|
|
190
|
-
rosotacom_dev-2.5.
|
|
191
|
-
rosotacom_dev-2.5.
|
|
192
|
-
rosotacom_dev-2.5.
|
|
193
|
-
rosotacom_dev-2.5.
|
|
194
|
-
rosotacom_dev-2.5.
|
|
195
|
-
rosotacom_dev-2.5.
|
|
190
|
+
rosotacom_dev-2.5.dev6.dist-info/licenses/LICENSE.txt,sha256=FDB3-fao3lqZ94wsJHKAH7U88kAnfvZhZC8Hm5rN__g,1459
|
|
191
|
+
rosotacom_dev-2.5.dev6.dist-info/METADATA,sha256=ExbIFkLKV4qhOiGTz1NqBTBKAmk8TctpZ81GsPR2dMU,46417
|
|
192
|
+
rosotacom_dev-2.5.dev6.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
193
|
+
rosotacom_dev-2.5.dev6.dist-info/entry_points.txt,sha256=-Hl9WKAfkLjMHUonDTxvz86EgSfZf6oyatRMxypS5iA,147
|
|
194
|
+
rosotacom_dev-2.5.dev6.dist-info/top_level.txt,sha256=BZrtQcIVDWn_pQZR3Mlssqy5SaRXHjOa6B-1D9Fy4Ao,10
|
|
195
|
+
rosotacom_dev-2.5.dev6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|