godot-cli-control 0.2.15__tar.gz → 0.2.17__tar.gz

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.
Files changed (78) hide show
  1. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/PKG-INFO +1 -1
  2. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bridge/render_api.gd +11 -4
  3. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/tests/gut/test_render_api.gd +19 -0
  4. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/_version.py +2 -2
  5. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/cli.py +4 -2
  6. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/daemon.py +31 -8
  7. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_daemon.py +89 -0
  8. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/.gitignore +0 -0
  9. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/LICENSE +0 -0
  10. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/CHANGELOG.md +0 -0
  11. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/LICENSE +0 -0
  12. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/README.md +0 -0
  13. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bin/run_cli_control.ps1 +0 -0
  14. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bin/run_cli_control.sh +0 -0
  15. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bridge/capture_logger.gd +0 -0
  16. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bridge/diagnostics_api.gd +0 -0
  17. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bridge/error_codes.gd +0 -0
  18. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bridge/game_bridge.gd +0 -0
  19. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bridge/game_bridge.gd.uid +0 -0
  20. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bridge/input_simulation_api.gd +0 -0
  21. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bridge/input_simulation_api.gd.uid +0 -0
  22. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bridge/low_level_api.gd +0 -0
  23. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bridge/low_level_api.gd.uid +0 -0
  24. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bridge/scene_api.gd +0 -0
  25. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bridge/time_api.gd +0 -0
  26. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bridge/variant_codec.gd +0 -0
  27. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/bridge/wait_api.gd +0 -0
  28. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/plugin.cfg +0 -0
  29. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/plugin.gd +0 -0
  30. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/plugin.gd.uid +0 -0
  31. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/tests/gut/test_diagnostics_api.gd +0 -0
  32. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/tests/gut/test_game_bridge.gd +0 -0
  33. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/tests/gut/test_input_simulation_api.gd +0 -0
  34. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/tests/gut/test_low_level_api.gd +0 -0
  35. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/tests/gut/test_scene_api.gd +0 -0
  36. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/tests/gut/test_time_api.gd +0 -0
  37. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/tests/gut/test_variant_codec.gd +0 -0
  38. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/tests/gut/test_wait_api.gd +0 -0
  39. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/tests/run_gut.py +0 -0
  40. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/tests/run_gut.sh +0 -0
  41. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/tests/test_init_walkthrough.py +0 -0
  42. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/addons/godot_cli_control/tests/test_walkthrough.sh +0 -0
  43. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/pyproject.toml +0 -0
  44. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/README.md +0 -0
  45. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/__init__.py +0 -0
  46. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/__main__.py +0 -0
  47. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/_duration.py +0 -0
  48. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/bridge.py +0 -0
  49. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/client.py +0 -0
  50. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/init_cmd.py +0 -0
  51. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/pytest_plugin.py +0 -0
  52. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/registry.py +0 -0
  53. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/runner.py +0 -0
  54. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/skills_install.py +0 -0
  55. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/templates/__init__.py +0 -0
  56. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/templates/skill/SKILL.md +0 -0
  57. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/godot_cli_control/templates/skill/__init__.py +0 -0
  58. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/__init__.py +0 -0
  59. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_bridge.py +0 -0
  60. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_cli.py +0 -0
  61. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_cli_helpers.py +0 -0
  62. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_client.py +0 -0
  63. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_duration.py +0 -0
  64. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_e2e_client_direct.py +0 -0
  65. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_e2e_diag.py +0 -0
  66. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_e2e_example.py +0 -0
  67. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_e2e_input.py +0 -0
  68. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_e2e_record.py +0 -0
  69. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_e2e_render.py +0 -0
  70. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_e2e_scene.py +0 -0
  71. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_e2e_screenshot_gui.py +0 -0
  72. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_e2e_time.py +0 -0
  73. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_init.py +0 -0
  74. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_issue_fixes.py +0 -0
  75. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_pytest_plugin.py +0 -0
  76. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_registry.py +0 -0
  77. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_runner.py +0 -0
  78. {godot_cli_control-0.2.15 → godot_cli_control-0.2.17}/python/tests/test_skills_install.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: godot-cli-control
3
- Version: 0.2.15
3
+ Version: 0.2.17
4
4
  Summary: WebSocket bridge for headless / scripted control of Godot scenes.
5
5
  Project-URL: Homepage, https://github.com/ClaymanTwinkle/godot-cli-control
6
6
  Project-URL: Repository, https://github.com/ClaymanTwinkle/godot-cli-control
@@ -30,8 +30,8 @@ func handle_sprite_info(params: Dictionary) -> Dictionary:
30
30
  )
31
31
 
32
32
 
33
- ## screenshot --node 的边界计算:节点 local rect → 经 canvas/camera 变换的
34
- ## 视口像素坐标 AABB。返回 Rect2,失败返回 error Dictionary(1010)。
33
+ ## screenshot --node 的边界计算:节点 local rect → 经 canvas/camera + content
34
+ ## scale 变换的物理像素坐标 AABB。返回 Rect2,失败返回 error Dictionary(1010)。
35
35
  ## static:只依赖传入节点自身的变换链,便于 GUT 直接单测(无需真渲染)。
36
36
  static func compute_node_screen_rect(node: Node) -> Variant:
37
37
  if not (node is CanvasItem):
@@ -51,9 +51,16 @@ static func compute_node_screen_rect(node: Node) -> Variant:
51
51
  % node.get_class(),
52
52
  }
53
53
  }
54
- # get_global_transform_with_canvas 含 CanvasLayer / Camera2D 变换,
55
- # 结果即视口像素坐标;Transform2D * Rect2 返回旋转后四角的 AABB。
54
+ # get_global_transform_with_canvas 含 CanvasLayer / Camera2D 变换,结果是画布
55
+ # (逻辑设计分辨率)坐标;再乘 viewport.get_final_transform()(content scale
56
+ # stretch 缩放 + letterbox 偏移)才与取图侧 get_image() 的物理像素系对齐——
57
+ # 漏乘时 hiDPI / 拉伸窗口下裁剪整体偏左上且偏小(#137)。平窗(物理 == 逻辑)
58
+ # 环境 final transform 为 identity,行为不变。Transform2D * Rect2 返回旋转后
59
+ # 四角的 AABB。
56
60
  var xform: Transform2D = (node as CanvasItem).get_global_transform_with_canvas()
61
+ var viewport: Viewport = (node as CanvasItem).get_viewport()
62
+ if viewport != null:
63
+ xform = viewport.get_final_transform() * xform
57
64
  return xform * (local_rect_or_null as Rect2)
58
65
 
59
66
 
@@ -184,6 +184,25 @@ func test_screen_rect_sprite2d_centered_with_scale() -> void:
184
184
  assert_eq(rect as Rect2, Rect2(92, 46, 16, 8))
185
185
 
186
186
 
187
+ func test_screen_rect_applies_viewport_final_transform() -> void:
188
+ # content scale(hiDPI / stretch 窗口)回归(#137):rect 必须落在取图侧
189
+ # get_image() 的物理像素系。SubViewport size(200,100) + size_2d_override(100,50)
190
+ # + stretch 构造 final transform = scale 2:Control (10,20,30,40) → (20,40,60,80)。
191
+ # 漏乘 final transform 时返回画布坐标 (10,20,30,40),hiDPI 下裁剪整体错位。
192
+ var viewport := SubViewport.new()
193
+ viewport.size = Vector2i(200, 100)
194
+ viewport.size_2d_override = Vector2i(100, 50)
195
+ viewport.size_2d_override_stretch = true
196
+ add_child_autofree(viewport)
197
+ var ctl := Control.new()
198
+ ctl.position = Vector2(10, 20)
199
+ ctl.size = Vector2(30, 40)
200
+ viewport.add_child(ctl)
201
+ var rect: Variant = RenderApiScript.compute_node_screen_rect(ctl)
202
+ assert_true(rect is Rect2, "stretch viewport 内 Control 应能算出 Rect2,实际: %s" % [rect])
203
+ assert_eq(rect as Rect2, Rect2(20, 40, 60, 80))
204
+
205
+
187
206
  func test_screen_rect_non_canvas_item_returns_1010() -> void:
188
207
  var plain := Node.new()
189
208
  plain.name = "PlainNode"
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.15'
22
- __version_tuple__ = version_tuple = (0, 2, 15)
21
+ __version__ = version = '0.2.17'
22
+ __version_tuple__ = version_tuple = (0, 2, 17)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -2223,8 +2223,10 @@ def build_parser() -> argparse.ArgumentParser:
2223
2223
  "GODOT_BIN 查找顺序:\n"
2224
2224
  " 1. 环境变量 GODOT_BIN\n"
2225
2225
  " 2. 项目根 .cli_control/godot_bin 文件(init 检测到时会写入)\n"
2226
- " 3. macOS /Applications/Godot*.app/Contents/MacOS/Godot\n"
2227
- " 4. PATH 上的 godot4 / godot / Godot\n"
2226
+ " 3. macOS /Applications 与 ~/Applications 下的 "
2227
+ "Godot*.app/Contents/MacOS/Godot(系统级优先)\n"
2228
+ " 4. PATH 上的 godot4 / godot / Godot,"
2229
+ "及 mono 别名 godot4-mono / godot-mono / godot_mono / Godot_mono\n"
2228
2230
  " 5. Windows Program Files\\Godot*\\Godot*.exe\n"
2229
2231
  "都没找到时 init 会打 warning,daemon start 会直接报错。\n"
2230
2232
  "可以手动 `export GODOT_BIN=/path/to/godot` 或写到 .cli_control/godot_bin。"
@@ -415,23 +415,46 @@ def discover_port(project_root: Path | None = None) -> int | None:
415
415
  # ── Godot 二进制发现 ──
416
416
 
417
417
 
418
+ def _macos_app_dirs() -> tuple[Path, ...]:
419
+ """macOS .app 扫描目录序:系统级 /Applications 优先于用户级(#135)。
420
+
421
+ 无管理员权限的用户常把 app 拖进 ~/Applications;目录不存在时 glob
422
+ 安静返回空,无需预检。单测靠 monkeypatch 本函数注入 tmp 目录。
423
+ """
424
+ return (Path("/Applications"), Path.home() / "Applications")
425
+
426
+
418
427
  def find_godot_binary() -> str | None:
419
428
  """查找可用的 Godot 二进制。
420
429
 
421
- 优先级:``GODOT_BIN`` env > macOS /Applications > PATH > Windows Program Files。
430
+ 优先级:``GODOT_BIN`` env > macOS /Applications + ~/Applications(系统级
431
+ 优先)> PATH > Windows Program Files。
432
+ PATH 候选名含 .NET/mono 版常见别名(godot4-mono / godot-mono / godot_mono /
433
+ Godot_mono),排标准名之后——两版都装时优先轻量的标准版。macOS Applications
434
+ 与 Windows Program Files 两级的 glob 天然匹配 ``Godot_mono.app`` / mono exe
435
+ (mono 版 .app 内部可执行文件仍叫 ``Godot``),无需单列。
422
436
  """
423
437
  env_bin = os.environ.get("GODOT_BIN")
424
438
  if env_bin and Path(env_bin).is_file() and os.access(env_bin, os.X_OK):
425
439
  return env_bin
426
440
 
427
441
  if sys.platform == "darwin":
428
- # macOS 默认安装位置 + 同目录所有 Godot*.app
429
- for app in sorted(Path("/Applications").glob("Godot*.app")):
430
- candidate = app / "Contents" / "MacOS" / "Godot"
431
- if candidate.is_file() and os.access(candidate, os.X_OK):
432
- return str(candidate)
433
-
434
- for name in ("godot4", "godot", "Godot"):
442
+ # macOS 默认安装位置 + 各目录下所有 Godot*.app
443
+ for base in _macos_app_dirs():
444
+ for app in sorted(base.glob("Godot*.app")):
445
+ candidate = app / "Contents" / "MacOS" / "Godot"
446
+ if candidate.is_file() and os.access(candidate, os.X_OK):
447
+ return str(candidate)
448
+
449
+ for name in (
450
+ "godot4",
451
+ "godot",
452
+ "Godot",
453
+ "godot4-mono",
454
+ "godot-mono",
455
+ "godot_mono",
456
+ "Godot_mono",
457
+ ):
435
458
  p = shutil.which(name)
436
459
  if p:
437
460
  return p
@@ -17,6 +17,7 @@ from godot_cli_control.daemon import (
17
17
  _allocate_port,
18
18
  _process_alive,
19
19
  _wait_port_ready,
20
+ find_godot_binary,
20
21
  )
21
22
 
22
23
 
@@ -140,6 +141,94 @@ def test_read_godot_bin_pref_ignores_dead_path(tmp_path: Path) -> None:
140
141
  assert daemon.read_godot_bin_pref() is None
141
142
 
142
143
 
144
+ @pytest.mark.parametrize(
145
+ "alias", ["godot4-mono", "godot-mono", "godot_mono", "Godot_mono"]
146
+ )
147
+ def test_find_godot_binary_finds_mono_alias_on_path(
148
+ alias: str, monkeypatch: pytest.MonkeyPatch
149
+ ) -> None:
150
+ """标准名都不在 PATH 时,.NET/mono 版的常见别名也要能命中。
151
+
152
+ 平台固定成 linux:跳过 macOS /Applications glob(开发机上真有
153
+ Godot.app 会先命中)与 Windows Program Files glob,让 PATH 这级
154
+ 在三平台 CI 上都被确定性地测到。
155
+ """
156
+ monkeypatch.delenv("GODOT_BIN", raising=False)
157
+ monkeypatch.setattr(sys, "platform", "linux")
158
+ monkeypatch.setattr(
159
+ "godot_cli_control.daemon.shutil.which",
160
+ lambda name: f"/usr/bin/{name}" if name == alias else None,
161
+ )
162
+ assert find_godot_binary() == f"/usr/bin/{alias}"
163
+
164
+
165
+ def test_find_godot_binary_prefers_standard_over_mono(
166
+ monkeypatch: pytest.MonkeyPatch,
167
+ ) -> None:
168
+ """标准版与 mono 版同时在 PATH 时优先标准版(更轻量)。"""
169
+ monkeypatch.delenv("GODOT_BIN", raising=False)
170
+ monkeypatch.setattr(sys, "platform", "linux")
171
+ table = {
172
+ "godot": "/usr/bin/godot",
173
+ "godot-mono": "/usr/bin/godot-mono",
174
+ "Godot_mono": "/usr/bin/Godot_mono",
175
+ }
176
+ monkeypatch.setattr("godot_cli_control.daemon.shutil.which", table.get)
177
+ assert find_godot_binary() == "/usr/bin/godot"
178
+
179
+
180
+ def _fake_godot_app(base: Path, app_name: str) -> Path:
181
+ """在 base 下伪造 <app_name>/Contents/MacOS/Godot 可执行结构,返回二进制路径。"""
182
+ binary = base / app_name / "Contents" / "MacOS" / "Godot"
183
+ binary.parent.mkdir(parents=True)
184
+ binary.write_text("")
185
+ binary.chmod(0o755)
186
+ return binary
187
+
188
+
189
+ def _patch_macos_scan(
190
+ monkeypatch: pytest.MonkeyPatch, system_dir: Path, user_dir: Path
191
+ ) -> None:
192
+ """把 macOS .app 扫描钉到两个 tmp 目录:平台固定 darwin、PATH 全空。
193
+
194
+ 扫描目录必须可注入——开发机 /Applications 里有真 Godot.app,
195
+ 不注入的话用户级兜底永远测不到。
196
+ """
197
+ monkeypatch.delenv("GODOT_BIN", raising=False)
198
+ monkeypatch.setattr(sys, "platform", "darwin")
199
+ monkeypatch.setattr("godot_cli_control.daemon.shutil.which", lambda name: None)
200
+ monkeypatch.setattr(
201
+ "godot_cli_control.daemon._macos_app_dirs",
202
+ lambda: (system_dir, user_dir),
203
+ raising=False,
204
+ )
205
+
206
+
207
+ def test_find_godot_binary_scans_user_applications(
208
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
209
+ ) -> None:
210
+ """系统 /Applications 没有时兜底扫 ~/Applications(#135);空目录不炸。"""
211
+ system_dir = tmp_path / "Applications" # 不存在:glob 应安静返回空
212
+ user_dir = tmp_path / "home" / "Applications"
213
+ binary = _fake_godot_app(user_dir, "Godot_mono.app")
214
+
215
+ _patch_macos_scan(monkeypatch, system_dir, user_dir)
216
+ assert find_godot_binary() == str(binary)
217
+
218
+
219
+ def test_find_godot_binary_prefers_system_applications_over_user(
220
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
221
+ ) -> None:
222
+ """系统级与用户级都有时优先 /Applications。"""
223
+ system_dir = tmp_path / "Applications"
224
+ user_dir = tmp_path / "home" / "Applications"
225
+ system_binary = _fake_godot_app(system_dir, "Godot.app")
226
+ _fake_godot_app(user_dir, "Godot.app")
227
+
228
+ _patch_macos_scan(monkeypatch, system_dir, user_dir)
229
+ assert find_godot_binary() == str(system_binary)
230
+
231
+
143
232
  def test_stop_handles_missing_pid_file(tmp_path: Path) -> None:
144
233
  daemon = Daemon(tmp_path)
145
234
  assert daemon.stop() == 0 # noop