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.
- revitdevtool_pytest-0.3.0/AGENTS.md +164 -0
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/CHANGELOG.md +36 -0
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/PKG-INFO +64 -23
- revitdevtool_pytest-0.3.0/README.md +207 -0
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/pyproject.toml +12 -8
- revitdevtool_pytest-0.3.0/src/revitdevtool_pytest/__init__.py +3 -0
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/bridge.py +3 -3
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/connection.py +56 -47
- revitdevtool_pytest-0.3.0/src/revitdevtool_pytest/constants.py +168 -0
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/dialog_resolver.py +6 -5
- revitdevtool_pytest-0.3.0/src/revitdevtool_pytest/discovery.py +303 -0
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/plugin.py +38 -26
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/reporting.py +5 -5
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/suite_leasing.py +6 -6
- revitdevtool_pytest-0.3.0/tests/Civil3d/conftest.py +70 -0
- revitdevtool_pytest-0.3.0/tests/Civil3d/test_active_state.py +56 -0
- revitdevtool_pytest-0.3.0/tests/Civil3d/test_civil3d_api.py +149 -0
- revitdevtool_pytest-0.3.0/tests/Civil3d/test_document.py +110 -0
- revitdevtool_pytest-0.3.0/tests/Civil3d/test_geometry.py +129 -0
- revitdevtool_pytest-0.3.0/tests/Civil3d/test_smoke.py +25 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/conftest.py +1 -1
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/uv.lock +1 -1
- revitdevtool_pytest-0.2.0/AGENTS.md +0 -112
- revitdevtool_pytest-0.2.0/README.md +0 -166
- revitdevtool_pytest-0.2.0/src/revitdevtool_pytest/__init__.py +0 -3
- revitdevtool_pytest-0.2.0/src/revitdevtool_pytest/constants.py +0 -42
- revitdevtool_pytest-0.2.0/src/revitdevtool_pytest/discovery.py +0 -132
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/.github/workflows/publish.yml +0 -0
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/.gitignore +0 -0
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/.vscode/settings.json +0 -0
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/CLAUDE.md +0 -0
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/LICENSE +0 -0
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/models.py +0 -0
- {revitdevtool_pytest-0.2.0 → revitdevtool_pytest-0.3.0}/src/revitdevtool_pytest/suite_lock.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/_session_probe.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/__init__.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/constants.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/contracts.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/custom_workflow.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/field_mapping.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/header_workflow.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/model.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/serializer.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/table_cell.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/template_workflow.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/schedule/test.json +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_active_state.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_dependencies.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_image_export.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_revit_basic.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_revit_document.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_revit_transactions.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_schedule_custom_copy_revit.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_schedule_field_mapping_revit.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_schedule_header_capture_revit.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_schedule_header_copy_revit.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_scheduleformatting_revit.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_session_cleanliness.py +0 -0
- {revitdevtool_pytest-0.2.0/tests → revitdevtool_pytest-0.3.0/tests/Revit}/test_smoke.py +0 -0
- {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.
|
|
4
|
-
Summary: pytest plugin for testing
|
|
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
|
[](https://pypi.org/project/RevitDevTool.PyTest/)
|
|
32
32
|
[](LICENSE)
|
|
33
33
|
|
|
34
|
-
pytest plugin for testing
|
|
35
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
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
|
-
|
|
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** (`
|
|
100
|
-
- **Force launch** (`
|
|
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
|
|
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
|
|
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 --
|
|
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
|
|
171
|
-
3. Test nodeids are sent to
|
|
172
|
-
4. Tests execute inside
|
|
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
|
+
[](https://pypi.org/project/RevitDevTool.PyTest/)
|
|
4
|
+
[](https://pypi.org/project/RevitDevTool.PyTest/)
|
|
5
|
+
[](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.
|
|
8
|
-
description = "pytest plugin for testing
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Named Pipe client for the
|
|
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
|
|
40
|
-
"""Synchronous Named Pipe client for the
|
|
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,
|