fix preflight config path check
This commit is contained in:
+11
-1
@@ -103,7 +103,17 @@ def _runtime_roots() -> list[Path]:
|
||||
except (OSError, ValueError): # pragma: no cover - defensive
|
||||
continue
|
||||
|
||||
return [path for path in _unique_paths(anchors) if _is_sdk_runtime_root(path)]
|
||||
anchors = _unique_paths(anchors)
|
||||
# Keep the *innermost* matching directories. Walking up from the launcher
|
||||
# also visits every parent, and a parent that happens to carry an
|
||||
# ``examples/`` directory must not outrank the real root below it: doing so
|
||||
# made a workspace directory shadow the release root's own bundle.
|
||||
return [
|
||||
path
|
||||
for path in anchors
|
||||
if _is_sdk_runtime_root(path)
|
||||
and not any(other != path and other.is_relative_to(path) for other in anchors)
|
||||
]
|
||||
|
||||
|
||||
def _candidate_config_dirs() -> list[Path]:
|
||||
|
||||
@@ -1271,9 +1271,11 @@ class Sensor:
|
||||
that root had coefficients.
|
||||
"""
|
||||
attempts = self._force_cal_attempts or []
|
||||
found = [entry for entry in attempts if entry.get("found_root") is not None]
|
||||
# ``found_root`` is keyed even when nothing was found (valued None), so
|
||||
# test its value, not the key.
|
||||
found = [entry for entry in attempts if entry.get("found_root")]
|
||||
with_cal = [entry for entry in found if entry.get("force_calibration_json")]
|
||||
chosen = (with_cal or found or attempts or [None])[0]
|
||||
chosen = (with_cal or found or [None])[0]
|
||||
searched: list = []
|
||||
for entry in attempts:
|
||||
for root in entry.get("searched") or ():
|
||||
|
||||
@@ -293,16 +293,22 @@ _CAL_DOC = {
|
||||
|
||||
|
||||
def _make_root(base, *, name="anything", sensor_json=True, calibration=True):
|
||||
"""A runnable SDK root: ``examples/`` beside ``sensors/<name>/``."""
|
||||
"""A runnable SDK root: ``examples/`` beside ``sensors/<name>/``.
|
||||
|
||||
``sensor.json`` is copied from the shipped bundle because it is a *sensor
|
||||
config*: a stub document would make ``_load_config`` depend on whichever
|
||||
config the machine happens to resolve, which is not what these tests are
|
||||
about.
|
||||
"""
|
||||
root = base / name
|
||||
(root / "examples").mkdir(parents=True, exist_ok=True)
|
||||
if sensor_json:
|
||||
(root / "examples" / "basic_pipeline.py").write_text("# demo\n", encoding="utf-8")
|
||||
bundle = root / "sensors" / "mini"
|
||||
bundle.mkdir(parents=True, exist_ok=True)
|
||||
if sensor_json:
|
||||
shipped = Path(__file__).resolve().parents[1] / "sensors" / "mini" / "sensor.json"
|
||||
(bundle / "sensor.json").write_text(
|
||||
json.dumps({"name": "prototype"}), encoding="utf-8"
|
||||
shipped.read_text(encoding="utf-8"), encoding="utf-8"
|
||||
)
|
||||
if calibration:
|
||||
(bundle / "force_calibration.json").write_text(
|
||||
@@ -311,6 +317,18 @@ def _make_root(base, *, name="anything", sensor_json=True, calibration=True):
|
||||
return root
|
||||
|
||||
|
||||
def _bare_sensor(**attrs):
|
||||
"""A Sensor instance without running ``__init__``/``_load_config``.
|
||||
|
||||
The note-formatting helpers touch no sensor state, so a whole sensor (and
|
||||
therefore a whole machine config) is not needed to test them.
|
||||
"""
|
||||
sensor = object.__new__(Sensor)
|
||||
for key, value in attrs.items():
|
||||
setattr(sensor, key, value)
|
||||
return sensor
|
||||
|
||||
|
||||
def test_runtime_root_anchor_keys_on_layout_not_on_the_folder_name(tmp_path):
|
||||
named = _make_root(tmp_path, name="some-random-name")
|
||||
assert configs._is_sdk_runtime_root(named) is True
|
||||
@@ -332,6 +350,24 @@ def test_runtime_roots_walk_up_from_a_subdirectory(tmp_path, monkeypatch):
|
||||
assert str(root) in roots
|
||||
|
||||
|
||||
def test_a_parent_root_never_outranks_the_root_below_it(tmp_path, monkeypatch):
|
||||
"""Walking up must not let a parent's bundle shadow the real run root.
|
||||
|
||||
A directory that happens to carry ``examples/`` above the launcher (a
|
||||
workspace holding several checkouts, one of which keeps an ``examples/``
|
||||
folder) used to be treated as a run root too, and its ``sensors/`` came
|
||||
first — silently shadowing the root the operator was actually running from.
|
||||
"""
|
||||
parent = tmp_path / "workspace"
|
||||
inner = _make_root(parent, name="inner-release")
|
||||
(parent / "examples").mkdir(parents=True, exist_ok=True)
|
||||
assert configs._is_sdk_runtime_root(parent) is True
|
||||
|
||||
monkeypatch.setattr(configs.Path, "cwd", classmethod(lambda cls: inner))
|
||||
roots = [str(path) for path in configs._runtime_roots()]
|
||||
assert roots == [str(inner)], roots
|
||||
|
||||
|
||||
def test_the_running_root_bundle_beats_a_calibration_less_checkout(
|
||||
tmp_path, monkeypatch
|
||||
):
|
||||
@@ -339,6 +375,13 @@ def test_the_running_root_bundle_beats_a_calibration_less_checkout(
|
||||
running = _make_root(tmp_path, name="running-root")
|
||||
checkout = _make_root(tmp_path, name="importable-checkout", calibration=False)
|
||||
monkeypatch.setattr(configs, "_runtime_roots", lambda: [running, checkout])
|
||||
# Pinned to real directories, so a leftover or unrelated bundle elsewhere on
|
||||
# the machine cannot decide the outcome.
|
||||
monkeypatch.setattr(
|
||||
configs,
|
||||
"_candidate_sensor_roots",
|
||||
lambda: [running / "sensors", checkout / "sensors"],
|
||||
)
|
||||
|
||||
bundle = configs.resolve_sensor_bundle("mini")
|
||||
assert bundle is not None
|
||||
@@ -347,17 +390,16 @@ def test_the_running_root_bundle_beats_a_calibration_less_checkout(
|
||||
running / "sensors" / "mini" / "force_calibration.json"
|
||||
).resolve()
|
||||
|
||||
# The whole point: the running root's document is the one that reaches the
|
||||
# sensor, split shear and all.
|
||||
sensor = Sensor("external", config_name="mini", verbose=False, backend="cpu")
|
||||
try:
|
||||
assert sensor.force_calibrated is True
|
||||
assert sensor.force_unit == "N"
|
||||
assert sensor.calibration.source_path == (
|
||||
# The whole point: the running root's document is the one the SDK loads.
|
||||
from orisys.calibration import load as load_calibration
|
||||
|
||||
cal = load_calibration(sensor_type="mini")
|
||||
assert cal is not None
|
||||
assert cal.schema_version == 2
|
||||
assert sorted(cal.channels) == ["fn", "fx", "fy"]
|
||||
assert cal.source_path == (
|
||||
running / "sensors" / "mini" / "force_calibration.json"
|
||||
).resolve()
|
||||
finally:
|
||||
sensor.disconnect()
|
||||
|
||||
|
||||
def test_a_nearer_bundle_wins_even_without_coefficients(tmp_path, monkeypatch):
|
||||
@@ -370,6 +412,11 @@ def test_a_nearer_bundle_wins_even_without_coefficients(tmp_path, monkeypatch):
|
||||
checkout = _make_root(tmp_path, name="checkout", calibration=False)
|
||||
running = _make_root(tmp_path, name="run-root")
|
||||
monkeypatch.setattr(configs, "_runtime_roots", lambda: [checkout, running])
|
||||
monkeypatch.setattr(
|
||||
configs,
|
||||
"_candidate_sensor_roots",
|
||||
lambda: [checkout / "sensors", running / "sensors"],
|
||||
)
|
||||
bundle = configs.resolve_sensor_bundle("mini")
|
||||
assert bundle is not None
|
||||
assert bundle.root == (checkout / "sensors" / "mini").resolve()
|
||||
@@ -402,19 +449,31 @@ def test_describe_bundle_resolution_names_the_bundle_and_the_searched_roots(
|
||||
assert missing["force_calibration_json"] is False
|
||||
|
||||
|
||||
def test_the_calibration_note_names_the_bundle_that_was_found(
|
||||
tmp_path, monkeypatch
|
||||
):
|
||||
def test_the_calibration_note_names_the_bundle_that_was_found(tmp_path, monkeypatch):
|
||||
"""The note explains a bundle with no coefficients, not a "missing file"."""
|
||||
checkout = _make_root(tmp_path, name="checkout", calibration=False)
|
||||
monkeypatch.setattr(configs, "_runtime_roots", lambda: [checkout])
|
||||
sensor = Sensor("external", config_name="mini", verbose=False, backend="cpu")
|
||||
try:
|
||||
assert sensor.calibration is None
|
||||
# Still None: callers read a non-None value here as "calibrated".
|
||||
assert sensor._force_cal_sensor_name is None
|
||||
roots = [checkout / "sensors"]
|
||||
monkeypatch.setattr(configs, "_candidate_sensor_roots", lambda: list(roots))
|
||||
|
||||
# Composed via _bare_sensor: the note reads only _force_cal_attempts, so this
|
||||
# needs no machine config and cannot fail on config resolution.
|
||||
sensor = _bare_sensor(
|
||||
_force_cal_attempts=[configs.describe_bundle_resolution("mini")],
|
||||
_force_cal_sensor_name=None,
|
||||
)
|
||||
note = sensor._force_calibration_note()
|
||||
assert "has no force_calibration.json" in note
|
||||
assert str((checkout / "sensors" / "mini").resolve()) in note
|
||||
assert "roots searched" in note
|
||||
finally:
|
||||
sensor.disconnect()
|
||||
|
||||
# No bundle anywhere: say that instead of naming a root.
|
||||
(checkout / "sensors" / "mini" / "sensor.json").unlink()
|
||||
sensor = _bare_sensor(
|
||||
_force_cal_attempts=[configs.describe_bundle_resolution("mini")],
|
||||
_force_cal_sensor_name=None,
|
||||
)
|
||||
note = sensor._force_calibration_note()
|
||||
assert "no sensor bundle resolved" in note
|
||||
# A dict always keys ``found_root``, so guard its value: an unguarded copy of
|
||||
# this branch prints "bundle found at None".
|
||||
assert "None" not in note
|
||||
|
||||
Reference in New Issue
Block a user