revitdevtool-pytest 0.3.0__tar.gz → 0.5.0__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 (81) hide show
  1. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/.gitignore +1 -0
  2. revitdevtool_pytest-0.5.0/.run/Attach.run.xml +8 -0
  3. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/.vscode/settings.json +1 -1
  4. revitdevtool_pytest-0.5.0/AGENTS.md +202 -0
  5. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/CHANGELOG.md +48 -0
  6. revitdevtool_pytest-0.5.0/PKG-INFO +222 -0
  7. revitdevtool_pytest-0.5.0/README.md +195 -0
  8. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/pyproject.toml +15 -11
  9. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/src/revitdevtool_pytest/__init__.py +1 -1
  10. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/src/revitdevtool_pytest/bridge.py +26 -1
  11. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/src/revitdevtool_pytest/connection.py +15 -19
  12. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/src/revitdevtool_pytest/constants.py +44 -56
  13. revitdevtool_pytest-0.5.0/src/revitdevtool_pytest/discovery.py +117 -0
  14. revitdevtool_pytest-0.5.0/src/revitdevtool_pytest/hosts/__init__.py +29 -0
  15. revitdevtool_pytest-0.5.0/src/revitdevtool_pytest/hosts/acad.py +127 -0
  16. revitdevtool_pytest-0.5.0/src/revitdevtool_pytest/hosts/revit.py +59 -0
  17. revitdevtool_pytest-0.5.0/src/revitdevtool_pytest/hosts/version.py +39 -0
  18. revitdevtool_pytest-0.5.0/src/revitdevtool_pytest/hosts/winreg.py +24 -0
  19. revitdevtool_pytest-0.5.0/src/revitdevtool_pytest/ipy_collect.py +91 -0
  20. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/src/revitdevtool_pytest/models.py +3 -0
  21. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/src/revitdevtool_pytest/plugin.py +94 -55
  22. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/src/revitdevtool_pytest/reporting.py +105 -19
  23. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/src/revitdevtool_pytest/suite_leasing.py +30 -28
  24. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/src/revitdevtool_pytest/suite_lock.py +21 -13
  25. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Civil3d/test_active_state.py +2 -2
  26. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/conftest.py +2 -2
  27. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/schedule/contracts.py +1 -1
  28. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/schedule/custom_workflow.py +3 -3
  29. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/schedule/field_mapping.py +1 -1
  30. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/schedule/header_workflow.py +3 -3
  31. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/schedule/serializer.py +3 -3
  32. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/schedule/template_workflow.py +6 -6
  33. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/test_schedule_custom_copy_revit.py +9 -9
  34. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/test_schedule_field_mapping_revit.py +3 -3
  35. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/test_schedule_header_capture_revit.py +7 -7
  36. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/test_schedule_header_copy_revit.py +6 -6
  37. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/test_scheduleformatting_revit.py +16 -16
  38. revitdevtool_pytest-0.5.0/tests/Revit_Ipy/conftest.py +5 -0
  39. revitdevtool_pytest-0.5.0/tests/Revit_Ipy/ipylib/__init__.py +16 -0
  40. revitdevtool_pytest-0.5.0/tests/Revit_Ipy/ipylib/geom.py +14 -0
  41. revitdevtool_pytest-0.5.0/tests/Revit_Ipy/nested/__init__.py +1 -0
  42. revitdevtool_pytest-0.5.0/tests/Revit_Ipy/nested/test_nested_import_ipy.py +11 -0
  43. revitdevtool_pytest-0.5.0/tests/Revit_Ipy/pyproject.toml +5 -0
  44. revitdevtool_pytest-0.5.0/tests/Revit_Ipy/test_document_ipy.py +41 -0
  45. revitdevtool_pytest-0.5.0/tests/Revit_Ipy/test_ipylib_ipy.py +22 -0
  46. revitdevtool_pytest-0.5.0/tests/plugin/conftest.py +9 -0
  47. revitdevtool_pytest-0.5.0/tests/plugin/test_acad_discovery.py +45 -0
  48. revitdevtool_pytest-0.5.0/tests/plugin/test_fan_suite_results.py +50 -0
  49. revitdevtool_pytest-0.5.0/tests/plugin/test_ipy_collect.py +122 -0
  50. revitdevtool_pytest-0.5.0/tests/plugin/test_plugin_runtestloop.py +23 -0
  51. revitdevtool_pytest-0.5.0/tests/plugin/test_reporting.py +212 -0
  52. revitdevtool_pytest-0.5.0/tests/plugin/test_suite_leasing.py +25 -0
  53. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/uv.lock +27 -24
  54. revitdevtool_pytest-0.3.0/AGENTS.md +0 -164
  55. revitdevtool_pytest-0.3.0/PKG-INFO +0 -234
  56. revitdevtool_pytest-0.3.0/README.md +0 -207
  57. revitdevtool_pytest-0.3.0/src/revitdevtool_pytest/discovery.py +0 -303
  58. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/.github/workflows/publish.yml +0 -0
  59. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/CLAUDE.md +0 -0
  60. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/LICENSE +0 -0
  61. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/src/revitdevtool_pytest/dialog_resolver.py +0 -0
  62. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Civil3d/conftest.py +0 -0
  63. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Civil3d/test_civil3d_api.py +0 -0
  64. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Civil3d/test_document.py +0 -0
  65. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Civil3d/test_geometry.py +0 -0
  66. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Civil3d/test_smoke.py +0 -0
  67. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/_session_probe.py +0 -0
  68. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/schedule/__init__.py +0 -0
  69. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/schedule/constants.py +0 -0
  70. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/schedule/model.py +0 -0
  71. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/schedule/table_cell.py +0 -0
  72. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/schedule/test.json +0 -0
  73. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/test_active_state.py +0 -0
  74. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/test_dependencies.py +0 -0
  75. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/test_image_export.py +0 -0
  76. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/test_revit_basic.py +0 -0
  77. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/test_revit_document.py +0 -0
  78. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/test_revit_transactions.py +0 -0
  79. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/test_session_cleanliness.py +0 -0
  80. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/test_smoke.py +0 -0
  81. {revitdevtool_pytest-0.3.0 → revitdevtool_pytest-0.5.0}/tests/Revit/test_streaming.py +0 -0
@@ -16,3 +16,4 @@ build/
16
16
  venv/
17
17
  .idea/
18
18
  .gitnexus
19
+ .cocoindex_code/
@@ -0,0 +1,8 @@
1
+ <component name="ProjectRunConfigurationManager">
2
+ <configuration default="false" name="Attach Host: Python" type="PythonDapAttachConfiguration">
3
+ <option name="localRoot" value="$PROJECT_DIR$" />
4
+ <option name="remoteAddress" value="localhost:5678" />
5
+ <option name="remoteRoot" value="$PROJECT_DIR$" />
6
+ <method v="2" />
7
+ </configuration>
8
+ </component>
@@ -21,5 +21,5 @@
21
21
  ],
22
22
  "basedpyright.importStrategy": "useBundled",
23
23
  "basedpyright.analysis.typeCheckingMode": "off",
24
- "python.testing.pytestEnabled": true,
24
+ "python.testing.pytestEnabled": true
25
25
  }
@@ -0,0 +1,202 @@
1
+ # AGENTS
2
+
3
+ ## Overview
4
+
5
+ pytest client plugin for [RevitDevTool](https://github.com/trgiangv/RevitDevTool). Collects tests locally, executes them inside a live host via Named Pipe JSON-RPC (`BridgeMessage` framing).
6
+
7
+ **Supported today:** **Revit** and **AutoCAD family** (AutoCAD, Civil 3D, Plant 3D, Architecture / Mechanical / MEP / Electrical / Map 3D). Other hosts may appear in the registry but are **in progress** — not validated end-to-end yet.
8
+
9
+ **Package:** `revitdevtool_pytest` (PyPI) · **Entry point:** `revitdevtool` · **Version:** see `pyproject.toml`
10
+
11
+ ## Architecture
12
+
13
+ ```mermaid
14
+ flowchart LR
15
+ Collect[Local collect] --> Pipe["DevTools_*"]
16
+ Pipe --> CPython[PytestRunner.py]
17
+ Pipe --> IPy[IpyTestDriver.py]
18
+ CPython --> Report[Local report]
19
+ IPy --> Report
20
+ ```
21
+
22
+ Do **not** connect to `DevToolsMcp_*` (MCP SDK). Pytest uses `DevTools_{Host}_{Version}_{PID}` on `DevToolsPipeServer`.
23
+
24
+ ### Client modules (`src/revitdevtool_pytest/`)
25
+
26
+ | Module | Role |
27
+ |--------|------|
28
+ | `plugin.py` | Hook orchestrator — lifecycle, options, bridge setup |
29
+ | `ipy_collect.py` | Pytest collect convention `test_*_ipy.py` → `ipytests/run` (no CPython import) |
30
+ | `connection.py` | Discover, connect, lease, launch, retry |
31
+ | `bridge.py` | Named Pipe framing, `tests/run` / `ipytests/run` RPC |
32
+ | `discovery.py` | Pipe scan, host registry, process launch |
33
+ | `reporting.py` | Remote `CaseResult` → pytest `TestReport` |
34
+ | `suite_leasing.py` | File-based suite → host PID allocation |
35
+ | `suite_lock.py` | Windows mutex — same suite, one pytest process |
36
+ | `dialog_resolver.py` | Auto-dismiss host startup dialogs (Win32) |
37
+ | `models.py` | Wire models (mirror C# `PytestContracts`) |
38
+ | `constants.py` | Options, host registry, pipe pattern |
39
+
40
+ ### Host-side (RevitDevTool repo)
41
+
42
+ | Location | Role |
43
+ |----------|------|
44
+ | `DevTools.Execution/External/DevToolsPipeServer.cs` | Pytest/control pipe server |
45
+ | `DevTools.Execution/External/Handlers/PytestRequestHandler.cs` | Routes `tests/run` |
46
+ | `DevTools.Execution/External/Handlers/IpyTestRequestHandler.cs` | Routes `ipytests/run` |
47
+ | `DevTools.Execution/External/Testing/` | `PytestExecutionService`, `IpyTestExecutionService`, `PytestContracts` |
48
+ | `DevTools.Execution/Resources/scripts/PytestRunner.py` | In-host `pytest.main()` runner |
49
+ | `DevTools.Execution/Resources/scripts/IpyTestDriver.py` | In-host IronPython test driver (2.7 / 3.4) |
50
+
51
+ Wire methods: `tests/run` (CPython pytest), `ipytests/run` (IronPython unittest, pyRevit-first). Progress notifications: `notifications/tests/progress`.
52
+
53
+ **IPy collect convention is `test_*_ipy.py`** (pytest `test_` + `_ipy` marker). That name is only how the plugin routes onto `ipytests/run`; the host runs unittest on the requested paths and does not require it. Dialect: 2.7 / 3.4, `unittest.TestCase`, no pytest fixtures / PEP 723 (CPython `tests/run` still installs `# /// script` via pixi). `*_ipy_script.py` is execution-tree script, not a test file. Library modules use normal names (`ipylib/geom.py`).
54
+
55
+ ## This repo layout
56
+
57
+ Integration tests for the plugin — not shipped in the wheel.
58
+
59
+ ```
60
+ tests/
61
+ plugin/ # local plugin unit tests (no host); sets REVITDEVTOOL_PYTEST_DISABLE
62
+ Revit/ # default testpaths in pyproject.toml
63
+ conftest.py # PEP 723 deps (CPython tests/run only), revit_doc, rollback fixtures
64
+ schedule/ # shared schedule helpers (import as schedule.*)
65
+ test_*.py # CPython pytest → tests/run
66
+ test_*_ipy.py # mixed-tree IronPython unittest → ipytests/run
67
+ Revit_Ipy/ # IPy libs + more test_*_ipy.py (folder is not identity)
68
+ ipylib/ # 2.7/3.4 helpers imported by tests
69
+ test_*_ipy.py
70
+ Civil3d/
71
+ conftest.py # acad_app, acad_doc, transaction fixtures
72
+ test_*.py
73
+ ```
74
+
75
+ Switch suites via `pyproject.toml`:
76
+
77
+ ```toml
78
+ testpaths = ["tests/Revit"] # or tests/Civil3d
79
+ host_name = "revit" # or civil3d, autocad, …
80
+ host_version = "2025"
81
+ ```
82
+
83
+ Schedule helpers live under `tests/Revit/schedule/`. Imports use the `schedule` package name (e.g. `from schedule.constants import …`), with `pythonpath = ["tests/Revit"]` in `pyproject.toml`.
84
+
85
+ `revit_doc` opens `REVIT_TEST_MODEL_PATH` (default `F:\Project1.rvt`) when set.
86
+
87
+ ## Host support
88
+
89
+ ### Supported (tested)
90
+
91
+ | Host Name | Pipe Prefix | Executable | Product ID |
92
+ |-----------|------------|------------|------------|
93
+ | `revit` | `Revit` | `Revit.exe` | — |
94
+ | `autocad` | `AutoCad` | `acad.exe` | `01` |
95
+ | `civil3d` | `Civil3D` | `acad.exe` | `00` |
96
+ | `plant3d` | `Plant3D` | `acad.exe` | `17` |
97
+ | `acadarch` | `AcadArch` | `acad.exe` | `04` |
98
+ | `acadmech` | `AcadMech` | `acad.exe` | `05` |
99
+ | `acadmep` | `AcadMep` | `acad.exe` | `06` |
100
+ | `acadelec` | `AcadElec` | `acad.exe` | `07` |
101
+ | `acadmap3d` | `AcadMap3D` | `acad.exe` | `02` |
102
+
103
+ ### In progress (not supported yet)
104
+
105
+ | Host Name | Pipe Prefix | Notes |
106
+ |-----------|------------|--------|
107
+ | `navisworks` | `Navisworks` | Registry stub only |
108
+ | `rhino` | `Rhino` | Registry stub only |
109
+ | `tekla` | `Tekla` | Registry stub only |
110
+
111
+ Unknown host names still get a fallback `HostConfig(pipe_prefix=host_name)` for experimentation, but are not supported. Do not document or rely on them until promoted to **Supported**.
112
+
113
+ ### Pipe format
114
+
115
+ `DevTools_{Host}_{Version}_{PID}` — regex `^DevTools_\w+_[^_]+_\d+$`
116
+
117
+ Examples: `DevTools_Revit_2025_12345`, `DevTools_AutoCad_2026_7890`
118
+
119
+ ## Key design decisions
120
+
121
+ - **Dual pytest model:** collect locally, execute in-host (IDE tree + host thread).
122
+ - **`--capture=sys`:** required in embedded Python.NET (no fd-level `dup2`).
123
+ - **`sys.__pytest_running__`:** set by `PytestRunner.py`; setup scripts skip I/O hijack during runs.
124
+ - **Streaming vs batch:** CLI streams progress; IDE adapters (`vscode_pytest`, `TEST_RUN_PIPE`) get one batch.
125
+ - **Suite leasing + mutex:** one pytest process per host+version+workspace; CPython and IronPython tests in that workspace reuse the same host PID. One invocation still cannot mix two `conftest.py` trees — run `tests/Revit` then `tests/Revit_Ipy` separately (same Revit). Mixed `test_*.py` + `test_*_ipy.py` under one conftest is fine (`tests/run` then `ipytests/run` on the same pipe).
126
+ - **`force_launch`:** force new host + wait for that PID's pipe; skip reuse.
127
+ - **No matching instance:** after discovery fails, plugin auto-launches when `host_version` is set.
128
+
129
+ ## Running tests
130
+
131
+ Always **`uv run pytest`** from this repo root — do not use system Python or bare `pytest`.
132
+
133
+ ```powershell
134
+ cd c:\Users\truon\source\repos\RevitDevTool.PyTest
135
+ uv run pytest -v
136
+ uv run pytest tests/Revit/test_active_state.py::test_active_view_info -v
137
+ uv run pytest --host-version=2025 -v
138
+ uv run pytest --host autocad --host-version 2026 -v
139
+ uv run pytest --force-launch --host-version=2025 -v
140
+ uv run pytest tests/Revit_Ipy -v --host-version=2025
141
+ ```
142
+
143
+ Plugin auto-enables `-rP` (stdout for passing tests).
144
+
145
+ ## Configuration
146
+
147
+ `[tool.pytest.ini_options]` in `pyproject.toml`:
148
+
149
+ ```toml
150
+ testpaths = ["tests/Revit"]
151
+ pythonpath = ["tests/Revit"]
152
+ host_name = "revit"
153
+ host_version = "2025"
154
+ force_launch = false
155
+ per_test_timeout = "60"
156
+ launch_timeout = "180"
157
+ host_pipe = ""
158
+ ```
159
+
160
+ CLI: `--host`, `--host-version`, `--per-test-timeout`, `--host-pipe`, `--force-launch`, `--launch-timeout`.
161
+
162
+ ## Fixtures pattern
163
+
164
+ ```python
165
+ # conftest.py (Revit)
166
+ @pytest.fixture(scope="session")
167
+ def revit_uiapp():
168
+ return __revit__ # noqa: F821
169
+
170
+ @pytest.fixture(scope="session")
171
+ def revit_doc(revit_uiapp):
172
+ return revit_uiapp.ActiveUIDocument.Document
173
+
174
+ @pytest.fixture
175
+ def revit_auto_rollback(revit_transaction_service):
176
+ revit_transaction_service.StartChanges()
177
+ try:
178
+ yield revit_transaction_service
179
+ finally:
180
+ revit_transaction_service.RevertChanges()
181
+ ```
182
+
183
+ Host API imports belong **inside test bodies** (or fixture bodies), not at module top level.
184
+
185
+ ## Common traps
186
+
187
+ - Tests run **inside the host**, not locally.
188
+ - `__revit__` only exists at runtime — use fixtures.
189
+ - Host APIs need the main thread; execution is sequential via `IHostContextExecutor`.
190
+ - Pytest pipe ≠ MCP pipe (`DevTools_*` vs `DevToolsMcp_*`).
191
+ - PEP 723 deps in CPython `conftest.py` / `test_*.py` are installed by `PytestDependencyService` before `tests/run`. **IronPython `test_*_ipy.py` does not use PEP 723** — `ipytests/run` never calls that service; IPy 2.7/3.4 cannot import CPython wheels.
192
+ - `--force-launch` requires `--host-version`.
193
+ - `print()` captured via `--capture=sys` → `CaseResult.stdout`.
194
+ - IronPython tests: no pytest fixtures; **no PEP 723**; host APIs inside methods; `test_*_ipy.py` is pytest routing only, not a host filename contract.
195
+
196
+ ## Change rules
197
+
198
+ - Wire protocol: sync `models.py` ↔ `PytestContracts.cs` / `PytestBridgeMethods.cs`.
199
+ - New CLI options: `pytest_addoption` + `parser.addini`.
200
+ - `connection.py` stays stateless; only `plugin.py` holds session globals.
201
+ - After connection/discovery changes: test `force_launch = true` and `false`.
202
+ - `HOST_REGISTRY` ↔ C# `HostApp` enum + `AcadPathResolver.ProductIdMap`.
@@ -2,6 +2,54 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.5.0] - 2026-10-12
6
+
7
+ ### Changed
8
+
9
+ - AutoCAD-family discovery reads `HKLM\SOFTWARE\Autodesk\AutoCAD\*\InstalledProducts`.
10
+ The default value is the install directory. The subkey is the product code
11
+ (`ACAD`, `C3D`, `PLNT3D`, `ACA`, `ACADM`, `MEP`, `ACADE`, `MAP`). The year is
12
+ the last token of the folder name (`AutoCAD 2026`, `Civil 3D 2026`), at or
13
+ above 2022. The filesystem fallback applies only to `autocad`.
14
+ - Revit discovery lives in its own resolver: `Autodesk Revit {version}` in the
15
+ registry, then `Revit {version}` on disk.
16
+ - `host_name` is the only key. The names are the lowercase `HostApp` values
17
+ (`Civil3D` → `civil3d`) and are declared once in `constants.py`.
18
+ `HOST_REGISTRY` and the AutoCAD product-code map both use those constants.
19
+ - `HostConfig` keeps `pipe_prefix`, `exe_name`, and `launch_args`. Install
20
+ paths stay in the resolver for that host.
21
+ - Requires `pytest>=9.1.1,<10` and `pywin32>=312`, matching the host pixi runtime.
22
+
23
+ ### Removed
24
+
25
+ - `find_host_executable()`. Callers use `find_executable(host_name, version)`.
26
+ - `HostConfig.acad_product_code` and `is_acad_family()`.
27
+ - Registry product-id lookup (`ACAD-XXNN`, `UPIRELEASE`, `GlobUPILocation`,
28
+ `AcadLocation`).
29
+
30
+ ### Fixed
31
+
32
+ - A local plugin test run no longer swallows pytest's own test loop when the
33
+ session has no remote results.
34
+
35
+ ## [0.4.0] - 2026-08-31
36
+
37
+ ### Added
38
+
39
+ - IronPython tests: files named `test_*_ipy.py` run on the host via `ipytests/run`
40
+ (`unittest`). CPython `test_*.py` still uses `tests/run`.
41
+ - CPython and IronPython suites in the same workspace reuse one host instance.
42
+
43
+ ### Changed
44
+
45
+ - Pipe name is `DevTools_{Host}_{Version}_{PID}` (was `{Host}_{Version}_{PID}`).
46
+ Pytest does not use the MCP pipe `DevToolsMcp_*`.
47
+ - CLI/INI names match RevitDevTool.TestAdapter (breaking vs 0.3.0):
48
+ `--host-launch` → `--force-launch`, `--host-timeout` → `--per-test-timeout`,
49
+ `--host-launch-timeout` → `--launch-timeout`. `--host` / `--host-version` unchanged.
50
+ - PEP 723 `# /// script` is installed only for CPython `tests/run`. IronPython
51
+ tests cannot use CPython wheels.
52
+
5
53
  ## [0.3.0] - 2026-05-31
6
54
 
7
55
  ### Added
@@ -0,0 +1,222 @@
1
+ Metadata-Version: 2.5
2
+ Name: revitdevtool_pytest
3
+ Version: 0.5.0
4
+ Summary: pytest plugin for testing CAD/BIM API code inside host applications via RevitDevTool Named Pipe bridge
5
+ Project-URL: Homepage, https://github.com/trgiangv/RevitDevTool.PyTest
6
+ Project-URL: Repository, https://github.com/trgiangv/RevitDevTool.PyTest
7
+ Project-URL: Issues, https://github.com/trgiangv/RevitDevTool.PyTest/issues
8
+ Author: Truong Giang Vu
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: autocad,bim,civil3d,multi-host,pytest,revit,revit-api,revitdevtool,testing
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: Pytest
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Testing
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: pytest<10,>=9.1.1
25
+ Requires-Dist: pywin32>=312
26
+ Description-Content-Type: text/markdown
27
+
28
+ # RevitDevTool.PyTest
29
+
30
+ [![PyPI version](https://img.shields.io/pypi/v/revitdevtool_pytest)](https://pypi.org/project/revitdevtool-pytest/)
31
+ [![Python](https://img.shields.io/pypi/pyversions/revitdevtool_pytest)](https://pypi.org/project/revitdevtool-pytest/)
32
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
33
+
34
+ pytest plugin for testing CAD/BIM API code inside live host applications via the RevitDevTool Named Pipe bridge.
35
+
36
+ Write tests locally with pytest; execution happens inside the host process. **Currently supported:** **Revit** and **AutoCAD family** only. Navisworks, Rhino, Tekla, and other hosts are **in progress**.
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install revitdevtool_pytest
42
+ # or
43
+ uv add revitdevtool_pytest
44
+ ```
45
+
46
+ | Requirement | Version |
47
+ |---|---|
48
+ | Python | >= 3.10 |
49
+ | pytest | >= 9.1.1, < 10 |
50
+ | pywin32 | >= 311 |
51
+ | OS | Windows (Named Pipes) |
52
+ | Host | [RevitDevTool](https://github.com/trgiangv/RevitDevTool) add-in loaded |
53
+
54
+ ## Supported hosts
55
+
56
+ `host_name` is the lowercase name of the RevitDevTool `HostApp` enum (`Revit` → `revit`, `Civil3D` → `civil3d`). Each name is declared once in `src/revitdevtool_pytest/constants.py`. `HOST_REGISTRY` and the AutoCAD `InstalledProducts` map (`ACAD`, `C3D`, `PLNT3D`, …) both use those constants as keys. Do not add a second spelling for the same host.
57
+
58
+ ### Supported today
59
+
60
+ | `host_name` | Application | Pipe prefix |
61
+ |---|---|---|
62
+ | `revit` | Autodesk Revit | `Revit` |
63
+ | `autocad` | AutoCAD | `AutoCad` |
64
+ | `civil3d` | Civil 3D | `Civil3D` |
65
+ | `plant3d` | Plant 3D | `Plant3D` |
66
+ | `acadarch` | AutoCAD Architecture | `AcadArch` |
67
+ | `acadmech` | AutoCAD Mechanical | `AcadMech` |
68
+ | `acadmep` | AutoCAD MEP | `AcadMep` |
69
+ | `acadelec` | AutoCAD Electrical | `AcadElec` |
70
+ | `acadmap3d` | AutoCAD Map 3D | `AcadMap3D` |
71
+
72
+ ### In progress
73
+
74
+ | `host_name` | Application | Status |
75
+ |---|---|---|
76
+ | `navisworks` | Navisworks | Not validated |
77
+ | `rhino` | Rhino | Not validated |
78
+ | `tekla` | Tekla Structures | Not validated |
79
+
80
+ The plugin registry may list additional names for future work. Treat them as experimental until documented here as **Supported today**.
81
+
82
+ ### Pipe names (pytest only)
83
+
84
+ The plugin connects to the **pytest/control** pipe:
85
+
86
+ `DevTools_{Host}_{Version}_{PID}`
87
+
88
+ Examples: `DevTools_Revit_2025_12345`, `DevTools_AutoCad_2026_7890`
89
+
90
+ MCP clients use a separate pipe: `DevToolsMcp_{Host}_{Version}_{PID}`. Do not point pytest at an MCP pipe.
91
+
92
+ ## Quick start (consumer project)
93
+
94
+ ```bash
95
+ uv init my-host-tests && cd my-host-tests
96
+ uv add revitdevtool_pytest pytest
97
+ ```
98
+
99
+ `pyproject.toml`:
100
+
101
+ ```toml
102
+ [tool.pytest.ini_options]
103
+ host_name = "revit"
104
+ host_version = "2025"
105
+ force_launch = false
106
+ per_test_timeout = "60"
107
+ launch_timeout = "180"
108
+ ```
109
+
110
+ `tests/conftest.py`:
111
+
112
+ ```python
113
+ import pytest
114
+
115
+ @pytest.fixture(scope="session")
116
+ def revit_uiapp():
117
+ return __revit__
118
+
119
+ @pytest.fixture(scope="session")
120
+ def revit_doc(revit_uiapp):
121
+ return revit_uiapp.ActiveUIDocument.Document
122
+ ```
123
+
124
+ `tests/test_smoke.py`:
125
+
126
+ ```python
127
+ def test_active_view(revit_doc):
128
+ assert revit_doc.ActiveView is not None
129
+ ```
130
+
131
+ ```bash
132
+ uv run pytest -v
133
+ ```
134
+
135
+ ## Configuration
136
+
137
+ | Option | Default | Description |
138
+ |---|---|---|
139
+ | `host_name` | `"revit"` | Target host — use **Supported today** names only. |
140
+ | `host_version` | — | Version string (`"2025"`, `"8.0"`, …). Required for launch. |
141
+ | `force_launch` | `false` | Force a **new** host instance (skip reuse). Requires `host_version`. |
142
+ | `per_test_timeout` | `"60"` | Per-test budget (seconds). The `tests/run` pipe wait is this × collected tests. |
143
+ | `launch_timeout` | `"180"` | Wait for host pipe after launch (seconds). |
144
+ | `host_pipe` | — | Explicit pipe name (skip discovery). |
145
+
146
+ CLI flags override INI: `--host`, `--host-version`, `--force-launch`, `--per-test-timeout`, `--launch-timeout`, `--host-pipe`.
147
+
148
+ ### Connection behavior
149
+
150
+ 1. **`force_launch = false` (default):** scan for `DevTools_*` pipes matching `host_name` / `host_version`, reuse a free instance (suite leasing), or reconnect to a leased PID.
151
+ 2. **No matching instance:** auto-launch host when `host_version` is set (unless `--host-pipe` pins an existing pipe).
152
+ 3. **`force_launch = true`:** always spawn a new process and wait for its pipe.
153
+
154
+ ### Print output
155
+
156
+ Captured `print()` from in-host tests is shown for passing tests (same as `-rP`). No extra flags needed.
157
+
158
+ ## Writing tests
159
+
160
+ - Put **host API imports inside functions** — collection runs on your machine, execution in the host.
161
+ - Use fixtures for `__revit__`, documents, and rollback helpers.
162
+ - Declare suite dependencies with **PEP 723** in CPython `conftest.py` (or `test_*.py`); RevitDevTool installs them before `tests/run`. IronPython `test_*_ipy.py` **does not** use PEP 723 — `ipytests/run` never installs packages.
163
+
164
+ ```python
165
+ # /// script
166
+ # dependencies = ["numpy>=2.0", "polars>=1.0"]
167
+ # ///
168
+ ```
169
+
170
+ ### Revit rollback fixture
171
+
172
+ ```python
173
+ @pytest.fixture
174
+ def revit_auto_rollback(revit_transaction_service):
175
+ revit_transaction_service.StartChanges()
176
+ try:
177
+ yield revit_transaction_service
178
+ finally:
179
+ revit_transaction_service.RevertChanges()
180
+ ```
181
+
182
+ ## How it works
183
+
184
+ 1. Local pytest collects tests.
185
+ 2. Plugin connects to `DevTools_{Host}_{Version}_{PID}`.
186
+ 3. Node IDs sent via JSON-RPC method `tests/run` (`BridgeMessage` length-prefixed frames).
187
+ 4. Host runs `PytestRunner.py` embedded in RevitDevTool (`PytestExecutionService` on main thread).
188
+ 5. Results and optional progress notifications return to the client.
189
+ 6. Plugin maps `CaseResult` → standard pytest reports.
190
+
191
+ ## Developing this repository
192
+
193
+ Clone and run integration tests against a live host:
194
+
195
+ ```powershell
196
+ cd RevitDevTool.PyTest
197
+ uv run pytest -v # Revit suite (default)
198
+ uv run pytest tests/Revit/test_smoke.py -v
199
+ uv run pytest --host civil3d --host-version 2026 # after switching testpaths/host in pyproject.toml
200
+ ```
201
+
202
+ ### Layout
203
+
204
+ | Path | Purpose |
205
+ |---|---|
206
+ | `src/revitdevtool_pytest/` | Plugin source (published wheel) |
207
+ | `tests/Revit/` | Revit integration tests + `schedule/` helpers |
208
+ | `tests/Civil3d/` | Civil 3D / AutoCAD API tests |
209
+
210
+ Revit suite settings in `pyproject.toml`: `testpaths = ["tests/Revit"]`, `pythonpath = ["tests/Revit"]` for local `schedule` imports.
211
+
212
+ Optional env: `REVIT_TEST_MODEL_PATH` — path to `.rvt` for `revit_doc` fixture (default `F:\Project1.rvt`).
213
+
214
+ ## IDE integration
215
+
216
+ **VS Code / Cursor** — `python.testing.pytestEnabled: true`. Plugin detects `vscode_pytest` / `TEST_RUN_PIPE` and disables CLI streaming to avoid duplicate tree entries.
217
+
218
+ **PyCharm** — enable pytest as test runner.
219
+
220
+ ## License
221
+
222
+ [MIT](LICENSE)