rosotacom-dev 2.5.dev82__py3-none-any.whl → 2.5.dev83__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 CHANGED
@@ -2437,9 +2437,19 @@ def _ota_stop_parts(
2437
2437
  #: How a benchmark's load maps onto `publish-test-topics` flags. The keys are
2438
2438
  #: `_sized_publisher_param_args`'s own, so a load dict travels from the
2439
2439
  #: benchmark to the peer's publisher without being rewritten on the way.
2440
+ #:
2441
+ #: `size_pattern` and not `pattern`, and that distinction is the whole reason a
2442
+ #: mixed-size load never reached a peer. `parse_size_pattern_load` turns one CLI
2443
+ #: string into five keys -- `sizes`, `pattern`, `size_pattern`, and one
2444
+ #: `size_<label>` per distinct size -- and only three of them used to cross the
2445
+ #: hop. A two-size pattern therefore arrived as `pattern=a*6,b*1` with `size_a`
2446
+ #: and no `size_b`, and the publisher died on `Pattern references size 'b' but
2447
+ #: it was not provided` inside a detached `docker exec`, which the orchestrator
2448
+ #: reported as the topic not advertising. Sending the *source* string instead
2449
+ #: cannot lose a size, whatever the pattern turns out to contain.
2440
2450
  _OTA_LOAD_FLAGS: tuple[tuple[str, str], ...] = (
2441
2451
  ("size", "--load-size"),
2442
- ("pattern", "--load-size-pattern"),
2452
+ ("size_pattern", "--load-size-pattern"),
2443
2453
  ("rate", "--load-rate-hz"),
2444
2454
  ("streams", "--load-streams"),
2445
2455
  ("interval_jitter_ms", "--load-interval-jitter-ms"),
@@ -2469,6 +2479,16 @@ def _ota_publish_parts(
2469
2479
  merged = dict(load or {})
2470
2480
  if "size" not in merged and merged.get("size_a") is not None:
2471
2481
  merged["size"] = merged["size_a"]
2482
+ if merged.get("pattern") and not merged.get("size_pattern"):
2483
+ # Only `parse_size_pattern_load` produces `pattern`, and it always
2484
+ # produces `size_pattern` beside it. A load carrying one without the
2485
+ # other was assembled by hand, and sending the expanded form would
2486
+ # drop every size but the first -- loudly here rather than as a peer
2487
+ # whose publisher will not start.
2488
+ raise RuntimeError(
2489
+ f"load carries pattern {merged['pattern']!r} without the size pattern it came from; "
2490
+ "build it with parse_size_pattern_load so every size reaches the peer."
2491
+ )
2472
2492
  for key, flag in _OTA_LOAD_FLAGS:
2473
2493
  if merged.get(key) is not None:
2474
2494
  parts.extend([flag, str(merged[key])])
@@ -7580,6 +7600,33 @@ def _smoke_publish_specs(cfg: dict[str, Any], source_peer_key: str | None = None
7580
7600
  return specs
7581
7601
 
7582
7602
 
7603
+ #: Where a detached smoke publisher's own output goes inside its container.
7604
+ #: `docker exec -d` returns before the process says anything and keeps nothing,
7605
+ #: so without this the only evidence of a publisher that refused to start is the
7606
+ #: topic never appearing -- which reads as a broken graph rather than as a bad
7607
+ #: parameter. One file per topic, overwritten per attempt.
7608
+ _SMOKE_PUBLISHER_LOG_DIR = "/tmp"
7609
+
7610
+
7611
+ def _smoke_publisher_log_path(topic: str) -> str:
7612
+ slug = re.sub(r"[^A-Za-z0-9]+", "_", topic).strip("_") or "topic"
7613
+ return f"{_SMOKE_PUBLISHER_LOG_DIR}/rosotacom-smoke-publisher-{slug}.log"
7614
+
7615
+
7616
+ def _smoke_publisher_failure_detail(container: str, log_path: str) -> str:
7617
+ """What the publisher said before it stopped, if it said anything."""
7618
+ result = subprocess.run(
7619
+ ["docker", "exec", container, "tail", "-n", "20", log_path],
7620
+ capture_output=True,
7621
+ text=True,
7622
+ check=False,
7623
+ )
7624
+ detail = ((result.stdout or "") + (result.stderr or "")).strip()
7625
+ if not detail:
7626
+ return f" Nothing was written to {log_path} in the container, so it never got as far as starting."
7627
+ return f"\nWhat it wrote to {log_path}:\n{detail}"
7628
+
7629
+
7583
7630
  def _start_smoke_topic_publishers(
7584
7631
  containers: dict[str, str],
7585
7632
  ros_setups: dict[str, str],
@@ -7606,8 +7653,9 @@ def _start_smoke_topic_publishers(
7606
7653
  f"Starting smoke publisher {spec.source_peer_key}->{spec.receiver_peer_key} "
7607
7654
  f"{spec.publish_topic} ({spec.publish_type}) in {container}{offered}"
7608
7655
  )
7656
+ log_path = _smoke_publisher_log_path(spec.publish_topic)
7609
7657
  subprocess.run(
7610
- ["docker", "exec", "-d", container, "bash", "-c", cmd],
7658
+ ["docker", "exec", "-d", container, "bash", "-c", f"{{ {cmd} ; }} > {shlex.quote(log_path)} 2>&1"],
7611
7659
  capture_output=True,
7612
7660
  text=True,
7613
7661
  check=False,
@@ -7626,6 +7674,7 @@ def _start_smoke_topic_publishers(
7626
7674
  raise RuntimeError(
7627
7675
  f"Smoke publisher {spec.source_peer_key}->{spec.receiver_peer_key} "
7628
7676
  f"{spec.publish_topic} ({spec.publish_type}) did not advertise in {container}."
7677
+ + _smoke_publisher_failure_detail(container, log_path)
7629
7678
  )
7630
7679
  return started
7631
7680
 
@@ -7981,7 +8030,21 @@ def _publish_load_override(args: argparse.Namespace) -> dict[str, Any] | None:
7981
8030
  load["size"] = int(args.load_size)
7982
8031
  load["size_a"] = int(args.load_size)
7983
8032
  if getattr(args, "load_size_pattern", None):
7984
- load["pattern"] = args.load_size_pattern
8033
+ # Re-derived here rather than carried in pieces: the orchestrator sends
8034
+ # the pattern it was given, and this side expands it into `sizes`,
8035
+ # `pattern` and every `size_<label>` the publisher needs. `size_a` from
8036
+ # `--load-size` above is overwritten on purpose -- a pattern names its
8037
+ # own sizes, and the scalar is only a fallback for callers without one.
8038
+ from .benchmark import parse_size_pattern_load
8039
+
8040
+ try:
8041
+ load.update(parse_size_pattern_load(str(args.load_size_pattern)))
8042
+ except ValueError as error:
8043
+ raise RuntimeError(
8044
+ f"--load-size-pattern {args.load_size_pattern!r} is not a size pattern ({error}). "
8045
+ "An orchestrator older than this peer sends the expanded token form here instead of the "
8046
+ "pattern; the two sides have to agree, so bring them to the same rosotacom."
8047
+ ) from error
7985
8048
  if getattr(args, "load_rate_hz", None) is not None:
7986
8049
  load["rate"] = float(args.load_rate_hz)
7987
8050
  if getattr(args, "load_streams", None) is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosotacom-dev
3
- Version: 2.5.dev82
3
+ Version: 2.5.dev83
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -6,7 +6,7 @@ rosotacom/benched_set.py,sha256=lBA1Rj04dbr2e-lIvYTWoL55_LdUpCAC-y7eIvJZ8lc,2721
6
6
  rosotacom/benchmark.py,sha256=3QoXJtWUGQPwvDWyQT_fOjx6K9G2pmvtnVurA21G2QY,76612
7
7
  rosotacom/benchmark_delta.py,sha256=qXzriBkbXOeJdlLKM4ziov1F-JfwlseYHE48S9hjFsQ,10005
8
8
  rosotacom/bundle_check.py,sha256=l_3BSuYk6juXQmsNU6jkxRbH8ayH2GkiW9ONKMQ1oJY,18270
9
- rosotacom/cli.py,sha256=LNawoIvPNgyi8OezEjcmY1yViCrH8ZmGFZiH7Ckn964,448335
9
+ rosotacom/cli.py,sha256=bva0K-UnwzapfVpI2M4Eu_KxS--NLLftzhCv8YQ2EGg,451959
10
10
  rosotacom/cli_benchmark.py,sha256=LsSwy42afqLv7euMVg5l_E9G1HHI0IyNWGQ_UDfFz0Y,356428
11
11
  rosotacom/deployment.py,sha256=XVhB8JjxhAsglSEEPtiVSeV6fCG0rgGmDRulh14oIYQ,7318
12
12
  rosotacom/ffmpeg_packet.py,sha256=zB1BHgY1kexr5sETVMkm4bkVnRytj9k2t0uOYiHUZ6M,4267
@@ -205,9 +205,9 @@ rosotacom/resources/ws/session/creation/generate_session_files.py,sha256=3Sx4xTS
205
205
  rosotacom/resources/ws/session/creation/run_session.py,sha256=G2hJhrmLp11gm3JZ6_C-zZcqu_reXlE_wmobB7BiCfU,16849
206
206
  rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py,sha256=Ub_5f-ttUUc-rAPrZmg_Op34s8Z4C91hTyEn8fgyt-E,2633
207
207
  rosotacom/resources/ws/session/creation/strip_ansi.py,sha256=vNjpemcq4xHr0VG4x0bZqvgwEBEfOgN-b4PzbrM5nfM,851
208
- rosotacom_dev-2.5.dev82.dist-info/licenses/LICENSE.txt,sha256=FDB3-fao3lqZ94wsJHKAH7U88kAnfvZhZC8Hm5rN__g,1459
209
- rosotacom_dev-2.5.dev82.dist-info/METADATA,sha256=LsLR74hGwoUePIz8usF8LvnJo4JoNAQ-Zdgb_qAgKq8,58884
210
- rosotacom_dev-2.5.dev82.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
211
- rosotacom_dev-2.5.dev82.dist-info/entry_points.txt,sha256=im11ZWqBbgjQUBz2Ik2RNfMu54wlMwgiVpsBKeI7dCI,49
212
- rosotacom_dev-2.5.dev82.dist-info/top_level.txt,sha256=BZrtQcIVDWn_pQZR3Mlssqy5SaRXHjOa6B-1D9Fy4Ao,10
213
- rosotacom_dev-2.5.dev82.dist-info/RECORD,,
208
+ rosotacom_dev-2.5.dev83.dist-info/licenses/LICENSE.txt,sha256=FDB3-fao3lqZ94wsJHKAH7U88kAnfvZhZC8Hm5rN__g,1459
209
+ rosotacom_dev-2.5.dev83.dist-info/METADATA,sha256=qMaFIUOcgQlyYNW4Cp-tJx9O26eyge_qBvxH6AASNUg,58884
210
+ rosotacom_dev-2.5.dev83.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
211
+ rosotacom_dev-2.5.dev83.dist-info/entry_points.txt,sha256=im11ZWqBbgjQUBz2Ik2RNfMu54wlMwgiVpsBKeI7dCI,49
212
+ rosotacom_dev-2.5.dev83.dist-info/top_level.txt,sha256=BZrtQcIVDWn_pQZR3Mlssqy5SaRXHjOa6B-1D9Fy4Ao,10
213
+ rosotacom_dev-2.5.dev83.dist-info/RECORD,,