revitdevtool-pytest 0.2.0__tar.gz → 0.3.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 (60) hide show
  1. revitdevtool_pytest-0.3.0/AGENTS.md +164 -0
  2. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/CHANGELOG.md +36 -0
  3. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/PKG-INFO +64 -23
  4. revitdevtool_pytest-0.3.0/README.md +207 -0
  5. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/pyproject.toml +12 -8
  6. revitdevtool_pytest-0.3.0/src/revitdevtool_pytest/__init__.py +3 -0
  7. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/bridge.py +3 -3
  8. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/connection.py +56 -47
  9. revitdevtool_pytest-0.3.0/src/revitdevtool_pytest/constants.py +168 -0
  10. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/dialog_resolver.py +6 -5
  11. revitdevtool_pytest-0.3.0/src/revitdevtool_pytest/discovery.py +303 -0
  12. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/plugin.py +38 -26
  13. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/reporting.py +5 -5
  14. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/suite_leasing.py +6 -6
  15. revitdevtool_pytest-0.3.0/tests/Civil3d/conftest.py +70 -0
  16. revitdevtool_pytest-0.3.0/tests/Civil3d/test_active_state.py +56 -0
  17. revitdevtool_pytest-0.3.0/tests/Civil3d/test_civil3d_api.py +149 -0
  18. revitdevtool_pytest-0.3.0/tests/Civil3d/test_document.py +110 -0
  19. revitdevtool_pytest-0.3.0/tests/Civil3d/test_geometry.py +129 -0
  20. revitdevtool_pytest-0.3.0/tests/Civil3d/test_smoke.py +25 -0
  21. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/conftest.py +1 -1
  22. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/uv.lock +1 -1
  23. revitdevtool_pytest-0.2.0/AGENTS.md +0 -112
  24. revitdevtool_pytest-0.2.0/README.md +0 -166
  25. revitdevtool_pytest-0.2.0/src/revitdevtool_pytest/__init__.py +0 -3
  26. revitdevtool_pytest-0.2.0/src/revitdevtool_pytest/constants.py +0 -42
  27. revitdevtool_pytest-0.2.0/src/revitdevtool_pytest/discovery.py +0 -132
  28. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/.github/workflows/publish.yml +0 -0
  29. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/.gitignore +0 -0
  30. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/.vscode/settings.json +0 -0
  31. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/CLAUDE.md +0 -0
  32. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/LICENSE +0 -0
  33. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/models.py +0 -0
  34. {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/suite_lock.py +0 -0
  35. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/_session_probe.py +0 -0
  36. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/__init__.py +0 -0
  37. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/constants.py +0 -0
  38. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/contracts.py +0 -0
  39. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/custom_workflow.py +0 -0
  40. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/field_mapping.py +0 -0
  41. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/header_workflow.py +0 -0
  42. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/model.py +0 -0
  43. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/serializer.py +0 -0
  44. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/table_cell.py +0 -0
  45. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/template_workflow.py +0 -0
  46. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/test.json +0 -0
  47. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_active_state.py +0 -0
  48. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_dependencies.py +0 -0
  49. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_image_export.py +0 -0
  50. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_revit_basic.py +0 -0
  51. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_revit_document.py +0 -0
  52. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_revit_transactions.py +0 -0
  53. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_schedule_custom_copy_revit.py +0 -0
  54. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_schedule_field_mapping_revit.py +0 -0
  55. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_schedule_header_capture_revit.py +0 -0
  56. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_schedule_header_copy_revit.py +0 -0
  57. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_scheduleformatting_revit.py +0 -0
  58. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_session_cleanliness.py +0 -0
  59. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_smoke.py +0 -0
  60. {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_streaming.py +0 -0
@@ -0,0 +1,164 @@
1
+ # AGENTS
2
+
3
+ ## Overview
4
+
5
+ This is the pytest client plugin for RevitDevTool. It bridges local pytest with a live host process via Named Pipe JSON-RPC. Supports Revit, AutoCAD-family, and any host that exposes a `DevToolsPipeServer` pipe.
6
+
7
+ ## Architecture
8
+
9
+ ```
10
+ Local pytest (collect) → Named Pipe → Host process (PytestRunner.py) → Results → Local pytest (report)
11
+ ```
12
+
13
+ ### Module Map
14
+
15
+ | Module | Role |
16
+ |--------|------|
17
+ | `plugin.py` | Hook orchestrator — lifecycle, options, bridge setup |
18
+ | `connection.py` | Bridge lifecycle — discover, connect, lease, launch |
19
+ | `bridge.py` | Wire protocol — Named Pipe framing, request/response |
20
+ | `discovery.py` | Pipe scan, host registry lookup, process launch |
21
+ | `reporting.py` | Map remote CaseResult → pytest TestReport |
22
+ | `suite_leasing.py` | Cross-process host instance allocation (file-based) |
23
+ | `suite_lock.py` | Windows mutex for same-suite concurrency guard |
24
+ | `dialog_resolver.py` | Auto-dismiss host startup dialogs via Win32 |
25
+ | `models.py` | Wire protocol data models (mirrors C# PytestContracts) |
26
+ | `constants.py` | Shared constants, option names, host registry, defaults |
27
+
28
+ ### Host-Side (in RevitDevTool repo)
29
+
30
+ | File | Role |
31
+ |------|------|
32
+ | `PytestRunner.py` | Embedded script — runs `pytest.main()` inside host |
33
+ | `SetupRevit.py` / `SetupAcad.py` | Runtime setup — API refs, I/O redirection |
34
+ | `PytestExecutionService.cs` | C# orchestrator — receives pipe request, invokes Python |
35
+ | `PytestContracts.cs` | C# wire models (mirrors `models.py`) |
36
+
37
+ ## Multi-Host Support
38
+
39
+ The plugin supports any host that registers a DevToolsPipeServer Named Pipe.
40
+
41
+ ### Pre-registered Hosts
42
+
43
+ | Host Name | Pipe Prefix | Executable | Product ID |
44
+ |-----------|------------|------------|------------|
45
+ | `revit` | `Revit` | `Revit.exe` | — |
46
+ | `autocad` | `AutoCad` | `acad.exe` | `01` |
47
+ | `civil3d` | `Civil3D` | `acad.exe` | `00` |
48
+ | `plant3d` | `Plant3D` | `acad.exe` | `17` |
49
+ | `acadarch` | `AcadArch` | `acad.exe` | `04` |
50
+ | `acadmech` | `AcadMech` | `acad.exe` | `05` |
51
+ | `acadmep` | `AcadMep` | `acad.exe` | `06` |
52
+ | `acadelec` | `AcadElec` | `acad.exe` | `07` |
53
+ | `acadmap3d` | `AcadMap3D` | `acad.exe` | `02` |
54
+ | `navisworks` | `Navisworks` | — | — |
55
+ | `rhino` | `Rhino` | — | — |
56
+ | `tekla` | `Tekla` | — | — |
57
+
58
+ Hosts without an `exe_name` (e.g. Rhino, Tekla) can only be connected via pipe auto-discovery or `--host-pipe`. Any unknown host name also gets a fallback config using the host name as pipe prefix.
59
+
60
+ ### Pipe Name Format
61
+
62
+ Pipes follow `{Host}_{Version}_{PID}` — mirrors C# `InstanceManager` pattern `^\w+_[^_]+_\d+$`.
63
+
64
+ Examples:
65
+ - `Revit_2025_12345` (year-based version)
66
+ - `AutoCad_2026_7890` (year-based)
67
+ - `Rhino_8.0_9999` (semver)
68
+ - `Tekla_2024.1_1111` (dotted version)
69
+
70
+ Version is any non-underscore string — not limited to 4-digit years.
71
+
72
+ ### Executable Discovery
73
+
74
+ - **Revit**: `HKLM\SOFTWARE\Autodesk\Revit\Autodesk Revit {version}` → `InstallationLocation`
75
+ - **AutoCAD family**: `HKLM\SOFTWARE\Autodesk\AutoCAD` → enumerate releases → match product ID via `ACAD-XXNN` pattern → `GlobUPILocation` / `AcadLocation`
76
+ - **Generic**: Hosts with `registry_key` + `registry_value` use registry lookup, then filesystem fallback under `Program Files\Autodesk\`
77
+ - **No exe_name**: Discovery returns `None`; connect via existing pipes only
78
+
79
+ ## Key Design Decisions
80
+
81
+ - **Dual pytest model**: Local pytest collects tests; remote pytest executes them. This enables IDE integration (test tree, navigation) while executing in the host's thread.
82
+ - **`--capture=sys`**: Required because fd-level capture (`os.dup2`) doesn't work in embedded Python.NET.
83
+ - **`--disable-plugin-autoload`**: Prevents third-party plugins from interfering with in-host execution.
84
+ - **`sys.__pytest_running__`**: Flag set by PytestRunner to prevent setup scripts from hijacking stdout/stderr during test runs.
85
+ - **Streaming vs batch**: CLI gets real-time progress notifications; IDE adapters get one batch to avoid double-counting.
86
+ - **Suite leasing**: File-based lease store binds suite → host PID across processes. Mutex prevents same-suite parallel runs.
87
+ - **force_launch**: When enabled, spawns new host and waits for its exact PID pipe (ignores existing instances).
88
+ - **Open host registry**: Unknown host names get a fallback `HostConfig(pipe_prefix=host_name)` — any host exposing a DevToolsPipeServer pipe works without pre-registration.
89
+
90
+ ## Running Tests
91
+
92
+ ```bash
93
+ # From project root with .venv activated:
94
+ pytest
95
+
96
+ # Or via uv / pixi:
97
+ uv run pytest
98
+ pixi run pytest
99
+
100
+ # Specific test:
101
+ pytest tests/test_active_state.py::test_active_view_info -v
102
+
103
+ # Target a different host:
104
+ pytest --host autocad --host-version 2026
105
+ pytest --host rhino --host-version 8.0
106
+ ```
107
+
108
+ Plugin auto-enables `-rP` (show captured stdout for passing tests).
109
+
110
+ ## Configuration
111
+
112
+ All options in `pyproject.toml` under `[tool.pytest.ini_options]`:
113
+
114
+ ```toml
115
+ host_name = "revit" # Host to connect to (revit, autocad, civil3d, rhino, etc.)
116
+ host_version = "2025" # Required for launch (accepts any string: year, semver, etc.)
117
+ host_launch = false # true = always spawn new instance
118
+ host_timeout = "60" # Per-test timeout (seconds)
119
+ host_launch_timeout = "180" # Startup wait (seconds)
120
+ host_pipe = "" # Explicit pipe (bypass discovery)
121
+ ```
122
+
123
+ CLI equivalents: `--host`, `--host-version`, `--host-timeout`, `--host-pipe`, `--host-launch`, `--host-launch-timeout`.
124
+
125
+ ## Fixtures Pattern
126
+
127
+ ```python
128
+ # conftest.py (Revit)
129
+ @pytest.fixture(scope="session")
130
+ def revit_uiapp():
131
+ return __revit__ # UIApplication injected by host
132
+
133
+ @pytest.fixture(scope="session")
134
+ def revit_doc(revit_uiapp):
135
+ return revit_uiapp.ActiveUIDocument.Document
136
+
137
+ @pytest.fixture
138
+ def revit_auto_rollback(revit_transaction_service):
139
+ """Start undo tracking, revert after test."""
140
+ revit_transaction_service.StartChanges()
141
+ try:
142
+ yield revit_transaction_service
143
+ finally:
144
+ revit_transaction_service.RevertChanges()
145
+ ```
146
+
147
+ ## Common Traps
148
+
149
+ - Tests execute **inside the host**, not locally. `import` statements for host APIs only work at test runtime.
150
+ - `__revit__` (Revit) or equivalent builtins are injected by host setup scripts — always access via fixtures.
151
+ - Host APIs require main-thread access. All tests run sequentially via FIFO queue (`IHostContextExecutor`).
152
+ - Named Pipe format: `{Host}_{Version}_{PID}` (e.g. `Revit_2025_12345`, `Rhino_8.0_9999`). Version is any non-underscore string.
153
+ - PEP 723 dependencies in `conftest.py` are auto-installed by `PytestDependencyService` before execution.
154
+ - `--host-launch` requires `--host-version` to be set — otherwise exits with config error.
155
+ - Print output inside tests is captured by pytest's `--capture=sys` mechanism and returned via `CaseResult.stdout`.
156
+
157
+ ## Change Rules
158
+
159
+ - Wire protocol changes must update both `models.py` (Python) and `PytestContracts.cs` (C#).
160
+ - Adding CLI options: register in both `pytest_addoption` (CLI) and `parser.addini` (INI).
161
+ - Connection logic is stateless by design — all functions receive parameters, no module-level mutable state except `plugin.py` globals.
162
+ - After modifying connection/discovery logic, manually test with both `host_launch = true` and `false`.
163
+ - Host registry changes: keep `HOST_REGISTRY` in `constants.py` in sync with `HostApp` enum and `AcadPathResolver.ProductIdMap` in the C# codebase.
164
+ - Any host with a DevToolsPipeServer pipe can be reached without pre-registration (fallback config).
@@ -2,6 +2,42 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.3.0] - 2026-05-31
6
+
7
+ ### Added
8
+
9
+ - **Multi-host support:** plugin now supports Revit, AutoCAD, Civil3D, Plant3D, Navisworks, Rhino, Tekla, and any host exposing a DevToolsPipeServer Named Pipe.
10
+ - **`--host` option:** new CLI flag and `host_name` INI key to select the target host application (default: `revit`).
11
+ - **`HostConfig` dataclass:** centralized executable discovery and launch configuration per host product, stored in `HOST_REGISTRY`.
12
+ - **AutoCAD-family executable discovery:** registry-based resolver mirrors C# `AcadPathResolver` — enumerates `HKLM\SOFTWARE\Autodesk\AutoCAD` releases, matches product IDs (`ACAD-XXNN`), resolves install path via `GlobUPILocation` / `AcadLocation`.
13
+ - **Open host registry:** `get_host_config()` returns a fallback `HostConfig(pipe_prefix=host_name)` for unknown host names — any host with a DevToolsPipeServer pipe works without pre-registration.
14
+ - **Multi-host dialog resolver:** `StartupDialogResolver` keywords expanded to cover AutoCAD, Civil, and Plant startup dialogs.
15
+
16
+ ### Changed
17
+
18
+ - **CLI/INI options renamed:** all `--revit-*` / `revit_*` options replaced with `--host-*` / `host_*` equivalents (`--host-version`, `--host-timeout`, `--host-pipe`, `--host-launch`, `--host-launch-timeout`).
19
+ - **Flexible pipe pattern:** `PIPE_PATTERN` changed from `^\w+_\d{4}_\d+$` to `^\w+_[^_]+_\d+$` — version segment accepts any non-underscore string (year, semver, dotted), matching C# `InstanceManager`.
20
+ - **`HostInstance.version` type:** changed from `int` to `str` to support non-year version formats (e.g. `8.0`, `2024.1`, `v3.2.1`).
21
+ - **`--host-version` accepts string:** no longer restricted to 4-digit year integers.
22
+ - **`RevitBridge` → `HostBridge`:** bridge class renamed for host-agnostic semantics.
23
+ - **`RevitInstance` → `HostInstance`:** discovery dataclass renamed.
24
+ - **`find_revit_pipes()` → `find_host_pipes()`:** accepts optional `host_name` filter; returns all matching pipes including unregistered prefixes.
25
+ - **`find_revit_path()` → `find_host_executable()`:** dispatches to Revit registry, AutoCAD-family registry, or returns `None` for hosts without exe discovery.
26
+ - **`start_revit()` → `start_host()`:** uses `HostConfig.launch_args` per host.
27
+ - **`wait_for_revit_pipe()` → `wait_for_host_pipe()`:** host-agnostic pipe polling.
28
+ - **`HostConfig.exe_name` optional:** hosts without a known executable (Rhino, Tekla, etc.) connect via pipe auto-discovery or `--host-pipe` only.
29
+ - **`is_process_alive()`:** handles `exe_name=None` gracefully — checks PID liveness without executable name verification.
30
+
31
+ ### Removed
32
+
33
+ - **`_opt_int()` helper:** version is now parsed as string, no integer conversion needed.
34
+
35
+ ### Notes
36
+
37
+ - `0.3.0` is a breaking change from `0.2.x`: all CLI flags and INI keys are renamed. Existing `revit_*` / `--revit-*` options are no longer recognized.
38
+ - The pipe pattern is now aligned with the C# `InstanceManager.HostPipePattern` regex (`^\w+_[^_]+_\d+$`).
39
+ - Revit users: add `host_name = "revit"` to `pyproject.toml` (or rely on the default).
40
+
5
41
  ## [0.2.0] - 2026-05-31
6
42
 
7
43
  ### Added
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: revitdevtool_pytest
3
- Version: 0.2.0
4
- Summary: pytest plugin for testing Revit API code via RevitDevTool Named Pipe bridge
3
+ Version: 0.3.0
4
+ Summary: pytest plugin for testing CAD/BIM API code inside host applications via RevitDevTool Named Pipe bridge
5
5
  Project-URL: Homepage, https://github.com/trgiangv/RevitDevTool.PyTest
6
6
  Project-URL: Repository, https://github.com/trgiangv/RevitDevTool.PyTest
7
7
  Project-URL: Issues, https://github.com/trgiangv/RevitDevTool.PyTest/issues
8
8
  Author: Truong Giang Vu
9
9
  License-Expression: MIT
10
10
  License-File: LICENSE
11
- Keywords: bim,pytest,revit,revit-api,revitdevtool,testing
11
+ Keywords: autocad,bim,civil3d,multi-host,pytest,revit,revit-api,revitdevtool,testing
12
12
  Classifier: Development Status :: 3 - Alpha
13
13
  Classifier: Framework :: Pytest
14
14
  Classifier: Intended Audience :: Developers
@@ -31,8 +31,9 @@ Description-Content-Type: text/markdown
31
31
  [![Python](https://img.shields.io/pypi/pyversions/RevitDevTool.PyTest)](https://pypi.org/project/RevitDevTool.PyTest/)
32
32
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
33
33
 
34
- pytest plugin for testing Revit API code via RevitDevTool Named Pipe bridge.
35
- Tests run inside a live Revit process — write standard pytest, execute remotely.
34
+ pytest plugin for testing CAD/BIM API code inside host applications via RevitDevTool Named Pipe bridge.
35
+ Supports Revit, AutoCAD-family, and any host exposing a DevToolsPipeServer pipe.
36
+ Tests run inside a live host process — write standard pytest, execute remotely.
36
37
 
37
38
  ## Installation
38
39
 
@@ -51,7 +52,34 @@ pip install revitdevtool_pytest
51
52
  ## Requirements
52
53
 
53
54
  - Windows (Named Pipes)
54
- - Revit with [RevitDevTool](https://github.com/trgiangv/RevitDevTool) add-in installed and loaded
55
+ - A host application with [RevitDevTool](https://github.com/trgiangv/RevitDevTool) add-in installed and loaded
56
+
57
+ ## Supported Hosts
58
+
59
+ ### Pre-registered
60
+
61
+ | Host Name | Application | Pipe Prefix |
62
+ |---|---|---|
63
+ | `revit` | Autodesk Revit | `Revit` |
64
+ | `autocad` | AutoCAD | `AutoCad` |
65
+ | `civil3d` | Civil 3D | `Civil3D` |
66
+ | `plant3d` | Plant 3D | `Plant3D` |
67
+ | `acadarch` | AutoCAD Architecture | `AcadArch` |
68
+ | `acadmech` | AutoCAD Mechanical | `AcadMech` |
69
+ | `acadmep` | AutoCAD MEP | `AcadMep` |
70
+ | `acadelec` | AutoCAD Electrical | `AcadElec` |
71
+ | `acadmap3d` | AutoCAD Map 3D | `AcadMap3D` |
72
+ | `navisworks` | Navisworks | `Navisworks` |
73
+ | `rhino` | Rhino | `Rhino` |
74
+ | `tekla` | Tekla Structures | `Tekla` |
75
+
76
+ ### Any host
77
+
78
+ Any host name works — unknown names get a fallback config using the name as pipe prefix. Connect via auto-discovery or `--host-pipe`.
79
+
80
+ ### Pipe Name Format
81
+
82
+ Pipes follow `{Host}_{Version}_{PID}` (e.g. `Revit_2025_12345`, `Rhino_8.0_9999`). Version can be any format — year, semver, etc.
55
83
 
56
84
  ## Project Setup
57
85
 
@@ -75,43 +103,53 @@ Add plugin settings to `[tool.pytest.ini_options]` in your `pyproject.toml`:
75
103
 
76
104
  ```toml
77
105
  [tool.pytest.ini_options]
78
- revit_version = "2025"
79
- revit_launch = false
80
- revit_launch_timeout = "180"
106
+ host_name = "revit"
107
+ host_version = "2025"
108
+ host_launch = false
109
+ host_launch_timeout = "180"
81
110
  ```
82
111
 
83
112
  | Option | Type | Default | Description |
84
113
  |---|---|---|---|
85
- | `revit_version` | string | — | Revit version year (e.g. `"2025"`). Required when `revit_launch = true`. |
86
- | `revit_launch` | bool | `false` | Force-launch a **new** Revit instance (skip reusing existing free instances). |
87
- | `revit_timeout` | string | `"60"` | Per-test execution timeout in seconds. |
88
- | `revit_launch_timeout` | string | `"120"` | Seconds to wait for Revit to start. |
89
- | `revit_pipe` | string | — | Explicit pipe name (bypasses auto-discovery). |
114
+ | `host_name` | string | `"revit"` | Host application name (see Supported Hosts above). |
115
+ | `host_version` | string | — | Host version (e.g. `"2025"`, `"8.0"`). Required when `host_launch = true`. |
116
+ | `host_launch` | bool | `false` | Force-launch a **new** host instance (skip reusing existing free instances). |
117
+ | `host_timeout` | string | `"60"` | Per-test execution timeout in seconds. |
118
+ | `host_launch_timeout` | string | `"120"` | Seconds to wait for host to start. |
119
+ | `host_pipe` | string | — | Explicit pipe name (bypasses auto-discovery). |
90
120
 
91
121
  CLI flags override INI settings:
92
122
 
93
123
  ```bash
94
- pytest --revit-launch --revit-version=2025 -v
124
+ # Revit
125
+ pytest --host-launch --host-version=2025 -v
126
+
127
+ # AutoCAD
128
+ pytest --host autocad --host-version=2026 -v
129
+
130
+ # Civil 3D
131
+ pytest --host civil3d --host-version=2026 -v
95
132
  ```
96
133
 
97
134
  ### Connection Behavior
98
135
 
99
- - **Default** (`revit_launch = false`): plugin auto-discovers a running Revit instance matching `revit_version` via Named Pipe scan. If none found, exits with error.
100
- - **Force launch** (`revit_launch = true`): always spawns a new Revit process, waits for its pipe to appear, then connects. Existing instances are ignored.
136
+ - **Default** (`host_launch = false`): plugin auto-discovers a running host instance matching `host_version` via Named Pipe scan. If none found, exits with error.
137
+ - **Force launch** (`host_launch = true`): always spawns a new host process, waits for its pipe to appear, then connects. Existing instances are ignored.
101
138
 
102
139
  ### Print Output
103
140
 
104
- Captured `print()` output from tests running inside Revit is automatically shown in the terminal for passing tests (equivalent to `-rP`). No extra flags needed.
141
+ Captured `print()` output from tests running inside the host is automatically shown in the terminal for passing tests (equivalent to `-rP`). No extra flags needed.
105
142
 
106
143
  ## Usage
107
144
 
108
145
  ### Fixtures
109
146
 
110
- Define fixtures in `conftest.py` that provide Revit API objects:
147
+ Define fixtures in `conftest.py` that provide host API objects:
111
148
 
112
149
  ```python
113
150
  import pytest
114
151
 
152
+ # Revit fixtures
115
153
  @pytest.fixture(scope="session")
116
154
  def revit_uiapp():
117
155
  return __revit__ # UIApplication injected by RevitDevTool
@@ -157,7 +195,10 @@ Declare test-suite dependencies in `conftest.py` using PEP 723 metadata. RevitDe
157
195
  pytest
158
196
 
159
197
  # Override version for a single run:
160
- pytest --revit-version=2026 -v
198
+ pytest --host-version=2026 -v
199
+
200
+ # Target a different host:
201
+ pytest --host autocad --host-version=2026 -v
161
202
 
162
203
  # Using uv or pixi:
163
204
  uv run pytest
@@ -167,9 +208,9 @@ pixi run pytest
167
208
  ## How It Works
168
209
 
169
210
  1. pytest discovers and collects tests locally
170
- 2. Plugin intercepts `pytest_runtestloop`, connects to Revit via Named Pipe
171
- 3. Test nodeids are sent to Revit's `PytestRunner.py` (embedded in RevitDevTool)
172
- 4. Tests execute inside Revit's Python.NET environment with full API access
211
+ 2. Plugin intercepts `pytest_runtestloop`, connects to the host via Named Pipe
212
+ 3. Test nodeids are sent to the host's `PytestRunner.py` (embedded in RevitDevTool)
213
+ 4. Tests execute inside the host's Python.NET environment with full API access
173
214
  5. Results (pass/fail/skip, stdout, stderr, tracebacks) are returned via pipe
174
215
  6. Plugin maps results back to standard pytest reports
175
216
 
@@ -0,0 +1,207 @@
1
+ # RevitDevTool.PyTest
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/RevitDevTool.PyTest)](https://pypi.org/project/RevitDevTool.PyTest/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/RevitDevTool.PyTest)](https://pypi.org/project/RevitDevTool.PyTest/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+
7
+ pytest plugin for testing CAD/BIM API code inside host applications via RevitDevTool Named Pipe bridge.
8
+ Supports Revit, AutoCAD-family, and any host exposing a DevToolsPipeServer pipe.
9
+ Tests run inside a live host process — write standard pytest, execute remotely.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install revitdevtool_pytest
15
+ ```
16
+
17
+ ## Dependencies
18
+
19
+ | Package | Version |
20
+ |---|---|
21
+ | Python | >= 3.10 |
22
+ | pytest | >= 9.0 |
23
+ | pywin32 | >= 311 |
24
+
25
+ ## Requirements
26
+
27
+ - Windows (Named Pipes)
28
+ - A host application with [RevitDevTool](https://github.com/trgiangv/RevitDevTool) add-in installed and loaded
29
+
30
+ ## Supported Hosts
31
+
32
+ ### Pre-registered
33
+
34
+ | Host Name | Application | Pipe Prefix |
35
+ |---|---|---|
36
+ | `revit` | Autodesk Revit | `Revit` |
37
+ | `autocad` | AutoCAD | `AutoCad` |
38
+ | `civil3d` | Civil 3D | `Civil3D` |
39
+ | `plant3d` | Plant 3D | `Plant3D` |
40
+ | `acadarch` | AutoCAD Architecture | `AcadArch` |
41
+ | `acadmech` | AutoCAD Mechanical | `AcadMech` |
42
+ | `acadmep` | AutoCAD MEP | `AcadMep` |
43
+ | `acadelec` | AutoCAD Electrical | `AcadElec` |
44
+ | `acadmap3d` | AutoCAD Map 3D | `AcadMap3D` |
45
+ | `navisworks` | Navisworks | `Navisworks` |
46
+ | `rhino` | Rhino | `Rhino` |
47
+ | `tekla` | Tekla Structures | `Tekla` |
48
+
49
+ ### Any host
50
+
51
+ Any host name works — unknown names get a fallback config using the name as pipe prefix. Connect via auto-discovery or `--host-pipe`.
52
+
53
+ ### Pipe Name Format
54
+
55
+ Pipes follow `{Host}_{Version}_{PID}` (e.g. `Revit_2025_12345`, `Rhino_8.0_9999`). Version can be any format — year, semver, etc.
56
+
57
+ ## Project Setup
58
+
59
+ **Recommended:** scaffold your project with [uv](https://docs.astral.sh/uv/) or [pixi](https://pixi.sh/):
60
+
61
+ ```bash
62
+ # uv
63
+ uv init my-revit-tests
64
+ cd my-revit-tests
65
+ uv add revitdevtool_pytest
66
+
67
+ # pixi (uses pyproject.toml as manifest)
68
+ pixi init --format pyproject my-revit-tests
69
+ cd my-revit-tests
70
+ pixi add --pypi revitdevtool_pytest
71
+ ```
72
+
73
+ ## Configuration
74
+
75
+ Add plugin settings to `[tool.pytest.ini_options]` in your `pyproject.toml`:
76
+
77
+ ```toml
78
+ [tool.pytest.ini_options]
79
+ host_name = "revit"
80
+ host_version = "2025"
81
+ host_launch = false
82
+ host_launch_timeout = "180"
83
+ ```
84
+
85
+ | Option | Type | Default | Description |
86
+ |---|---|---|---|
87
+ | `host_name` | string | `"revit"` | Host application name (see Supported Hosts above). |
88
+ | `host_version` | string | — | Host version (e.g. `"2025"`, `"8.0"`). Required when `host_launch = true`. |
89
+ | `host_launch` | bool | `false` | Force-launch a **new** host instance (skip reusing existing free instances). |
90
+ | `host_timeout` | string | `"60"` | Per-test execution timeout in seconds. |
91
+ | `host_launch_timeout` | string | `"120"` | Seconds to wait for host to start. |
92
+ | `host_pipe` | string | — | Explicit pipe name (bypasses auto-discovery). |
93
+
94
+ CLI flags override INI settings:
95
+
96
+ ```bash
97
+ # Revit
98
+ pytest --host-launch --host-version=2025 -v
99
+
100
+ # AutoCAD
101
+ pytest --host autocad --host-version=2026 -v
102
+
103
+ # Civil 3D
104
+ pytest --host civil3d --host-version=2026 -v
105
+ ```
106
+
107
+ ### Connection Behavior
108
+
109
+ - **Default** (`host_launch = false`): plugin auto-discovers a running host instance matching `host_version` via Named Pipe scan. If none found, exits with error.
110
+ - **Force launch** (`host_launch = true`): always spawns a new host process, waits for its pipe to appear, then connects. Existing instances are ignored.
111
+
112
+ ### Print Output
113
+
114
+ Captured `print()` output from tests running inside the host is automatically shown in the terminal for passing tests (equivalent to `-rP`). No extra flags needed.
115
+
116
+ ## Usage
117
+
118
+ ### Fixtures
119
+
120
+ Define fixtures in `conftest.py` that provide host API objects:
121
+
122
+ ```python
123
+ import pytest
124
+
125
+ # Revit fixtures
126
+ @pytest.fixture(scope="session")
127
+ def revit_uiapp():
128
+ return __revit__ # UIApplication injected by RevitDevTool
129
+
130
+ @pytest.fixture(scope="session")
131
+ def revit_app(revit_uiapp):
132
+ return revit_uiapp.Application
133
+
134
+ @pytest.fixture(scope="session")
135
+ def revit_doc(revit_uiapp):
136
+ return revit_uiapp.ActiveUIDocument.Document
137
+ ```
138
+
139
+ ### Writing Tests
140
+
141
+ ```python
142
+ def test_active_view(revit_doc):
143
+ view = revit_doc.ActiveView
144
+ print(f"Active View: {view.Name}")
145
+ assert view.Name is not None
146
+
147
+ def test_revit_version(revit_app):
148
+ assert "2025" in revit_app.VersionName
149
+ ```
150
+
151
+ ### PEP 723 Dependencies
152
+
153
+ Declare test-suite dependencies in `conftest.py` using PEP 723 metadata. RevitDevTool auto-installs them before test execution:
154
+
155
+ ```python
156
+ # /// script
157
+ # dependencies = [
158
+ # "numpy>=2.0",
159
+ # "polars>=1.0",
160
+ # ]
161
+ # ///
162
+ ```
163
+
164
+ ### Running Tests
165
+
166
+ ```bash
167
+ # With pyproject.toml configured:
168
+ pytest
169
+
170
+ # Override version for a single run:
171
+ pytest --host-version=2026 -v
172
+
173
+ # Target a different host:
174
+ pytest --host autocad --host-version=2026 -v
175
+
176
+ # Using uv or pixi:
177
+ uv run pytest
178
+ pixi run pytest
179
+ ```
180
+
181
+ ## How It Works
182
+
183
+ 1. pytest discovers and collects tests locally
184
+ 2. Plugin intercepts `pytest_runtestloop`, connects to the host via Named Pipe
185
+ 3. Test nodeids are sent to the host's `PytestRunner.py` (embedded in RevitDevTool)
186
+ 4. Tests execute inside the host's Python.NET environment with full API access
187
+ 5. Results (pass/fail/skip, stdout, stderr, tracebacks) are returned via pipe
188
+ 6. Plugin maps results back to standard pytest reports
189
+
190
+ ## IDE Integration
191
+
192
+ ### VS Code / Cursor
193
+
194
+ ```json
195
+ {
196
+ "python.testing.pytestEnabled": true,
197
+ "python.testing.pytestArgs": ["tests"]
198
+ }
199
+ ```
200
+
201
+ ### PyCharm
202
+
203
+ Enable pytest as the test runner. Plugin auto-detects IDE adapters and disables streaming to avoid duplicate results in the test tree.
204
+
205
+ ## License
206
+
207
+ [MIT](LICENSE)
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "revitdevtool_pytest"
7
- version = "0.2.0"
8
- description = "pytest plugin for testing Revit API code via RevitDevTool Named Pipe bridge"
7
+ version = "0.3.0"
8
+ description = "pytest plugin for testing CAD/BIM API code inside host applications via RevitDevTool Named Pipe bridge"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
11
  requires-python = ">=3.10"
12
12
  authors = [{ name = "Truong Giang Vu" }]
13
- keywords = ["pytest", "revit", "revit-api", "testing", "bim", "revitdevtool"]
13
+ keywords = ["pytest", "revit", "autocad", "civil3d", "revit-api", "testing", "bim", "multi-host", "revitdevtool"]
14
14
  classifiers = [
15
15
  "Development Status :: 3 - Alpha",
16
16
  "Framework :: Pytest",
@@ -38,8 +38,12 @@ Issues = "https://github.com/trgiangv/RevitDevTool.PyTest/issues"
38
38
  packages = ["src/revitdevtool_pytest"]
39
39
 
40
40
  [tool.pytest.ini_options]
41
- testpaths = ["tests"]
42
- revit_version = "2025"
43
- revit_timeout = "60"
44
- revit_launch = false
45
- revit_launch_timeout = "180"
41
+ # testpaths = ["tests/Revit"]
42
+ # host_name = "revit"
43
+ # host_version = "2025"
44
+ testpaths = ["tests/Civil3d"]
45
+ host_name = "civil3d"
46
+ host_version = "2026"
47
+ host_timeout = "60"
48
+ host_launch = false
49
+ host_launch_timeout = "180"
@@ -0,0 +1,3 @@
1
+ """RevitDevTool.PyTest: Run pytest tests inside a live Autodesk host (Revit, AutoCAD, Civil3D, etc.) via Named Pipe bridge."""
2
+
3
+ __version__ = "0.3.0"
@@ -1,4 +1,4 @@
1
- """Named Pipe client for the RevitDevTool bridge.
1
+ """Named Pipe client for the DevTool bridge.
2
2
 
3
3
  Protocol: ``[4-byte LE body length][UTF-8 JSON body]``
4
4
  Matches ``RevitDevTool.McpParser.Models.BridgePipeConnection``.
@@ -36,8 +36,8 @@ _HEADER_LEN = struct.calcsize(_HEADER_FMT)
36
36
  NotificationCallback = Callable[[str, Any], None]
37
37
 
38
38
 
39
- class RevitBridge:
40
- """Synchronous Named Pipe client for the RevitDevTool bridge."""
39
+ class HostBridge:
40
+ """Synchronous Named Pipe client for the DevTool bridge."""
41
41
 
42
42
  def __init__(
43
43
  self, pipe_name: str, *, connect_timeout_ms: int = DEFAULT_CONNECT_TIMEOUT_MS,