Skip to content

Commit 22b60fb

Browse files
committed
fix: correct revit_launch & auto-capture print output
- Wire revit_launch option properly: force-launch spawns new Revit instead of reusing existing free instances - Fix double-run bug: wait_for_revit_pipe now filters by exact PID of newly-spawned process, preventing connection to old instances - Disconnect old bridge before force-launching to avoid stale pipe state - Auto-enable -rP (show captured stdout for passing tests) by default - Remove dead code: DiscoverRequest/Response models, BRIDGE_METHOD_TESTS_DISCOVER constant, _prune_stale method, prefer_fresh parameter - Fix _opt_bool to correctly read INI fallback when CLI default is False
1 parent 163a0da commit 22b60fb

7 files changed

Lines changed: 50 additions & 59 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,5 @@ packages = ["src/revitdevtool_pytest"]
4141
testpaths = ["tests"]
4242
revit_version = "2025"
4343
revit_timeout = "60"
44-
revit_launch = true
44+
revit_launch = false
4545
revit_launch_timeout = "180"

src/revitdevtool_pytest/connection.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,17 @@ def ensure_bridge(
5858
explicit_pipe: str | None,
5959
suite_key: str,
6060
suite_path: str,
61-
prefer_fresh: bool = False,
61+
force_launch: bool = False,
6262
) -> ConnectionResult:
63-
"""Main entry point: return a connected bridge or an error."""
64-
if not prefer_fresh and current_bridge is not None and current_bridge.connected:
63+
"""Main entry point: return a connected bridge or an error.
64+
65+
When *force_launch* is True, skip reusing existing instances and always
66+
spawn a fresh Revit process (requires --revit-version).
67+
"""
68+
if not force_launch and current_bridge is not None and current_bridge.connected:
6569
return ConnectionResult(bridge=current_bridge)
6670

67-
if explicit_pipe and not prefer_fresh:
71+
if explicit_pipe and not force_launch:
6872
return _connect_explicit_pipe_or_exit(explicit_pipe)
6973

7074
return _connect_discovered_or_launched(
@@ -73,7 +77,7 @@ def ensure_bridge(
7377
lease_store=lease_store,
7478
version=version,
7579
launch_timeout_s=launch_timeout_s,
76-
prefer_fresh=prefer_fresh,
80+
force_launch=force_launch,
7781
)
7882

7983

@@ -84,17 +88,17 @@ def _connect_discovered_or_launched(
8488
lease_store: SuiteLeaseStore | None,
8589
version: int | None,
8690
launch_timeout_s: float,
87-
prefer_fresh: bool,
91+
force_launch: bool,
8892
) -> ConnectionResult:
8993
instances = instances_for_version(version)
9094

91-
if lease_store is not None:
92-
bridge, _ = _try_reconnect_leased(lease_store, suite_key, suite_path, instances)
93-
if bridge is not None:
94-
return ConnectionResult(bridge=bridge)
95-
instances = instances_for_version(version)
95+
if not force_launch:
96+
if lease_store is not None:
97+
bridge, _ = _try_reconnect_leased(lease_store, suite_key, suite_path, instances)
98+
if bridge is not None:
99+
return ConnectionResult(bridge=bridge)
100+
instances = instances_for_version(version)
96101

97-
if not prefer_fresh:
98102
free = lease_store.find_free(suite_key, instances) if lease_store else instances
99103
bridge, error = _connect_and_lease(free, suite_key, suite_path, lease_store, "Assigned free instance")
100104
if bridge is not None:
@@ -136,7 +140,7 @@ def auto_launch(version: int, launch_timeout_s: float) -> LaunchResult:
136140
except ImportError:
137141
pass
138142

139-
instance = wait_for_revit_pipe(version, timeout_s=launch_timeout_s)
143+
instance = wait_for_revit_pipe(version, timeout_s=launch_timeout_s, process_id=process_id)
140144
if instance is None:
141145
pytest.exit(
142146
f"{PLUGIN_NAME}: Revit {version} launched but Named Pipe did not appear within {launch_timeout_s}s.",

src/revitdevtool_pytest/constants.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
PLUGIN_NAME: Final = "RevitDevTool.PyTest"
66

7-
BRIDGE_METHOD_TESTS_DISCOVER: Final = "tests/discover"
87
BRIDGE_METHOD_TESTS_RUN: Final = "tests/run"
98
BRIDGE_MSG_TYPE_NOTIFICATION: Final = "notification"
109
BRIDGE_NOTIFY_TEST_PROGRESS: Final = "notifications/tests/progress"

src/revitdevtool_pytest/discovery.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,20 @@ def wait_for_revit_pipe(
9191
version: int | None = None,
9292
timeout_s: float = DEFAULT_LAUNCH_TIMEOUT_S,
9393
poll_interval_s: float = DEFAULT_POLL_INTERVAL_S,
94+
process_id: int | None = None,
9495
) -> RevitInstance | None:
95-
"""Block until a Revit pipe matching *version* appears."""
96+
"""Block until a Revit pipe appears.
97+
98+
When *process_id* is given, wait for that exact process to register its pipe.
99+
Otherwise fall back to version-based selection.
100+
"""
96101
deadline = time.monotonic() + timeout_s
97102
while time.monotonic() < deadline:
98-
match = select_instance(find_revit_pipes(), version)
103+
instances = find_revit_pipes()
104+
if process_id is not None:
105+
match = next((i for i in instances if i.process_id == process_id), None)
106+
else:
107+
match = select_instance(instances, version)
99108
if match is not None:
100109
return match
101110
time.sleep(poll_interval_s)

src/revitdevtool_pytest/models.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,6 @@ def from_json(cls, data: dict[str, Any]) -> BridgeResponse:
6060
# -- Request contracts (mirrors PytestContracts.cs requests) -----------------
6161

6262

63-
@dataclass(slots=True)
64-
class DiscoverRequest:
65-
workspace_root: str = ""
66-
test_root: str = ""
67-
pytest_args: list[str] = field(default_factory=list)
68-
69-
def to_params(self) -> dict[str, Any]:
70-
return asdict(self)
71-
72-
7363
@dataclass(slots=True)
7464
class RunRequest:
7565
workspace_root: str = ""
@@ -126,23 +116,6 @@ def from_dict(cls, data: dict[str, Any]) -> RunSummary:
126116
return _deserialize(cls, data)
127117

128118

129-
@dataclass(frozen=True, slots=True)
130-
class DiscoverResponse:
131-
rootdir: str = ""
132-
nodeids: tuple[str, ...] = ()
133-
collection_errors: tuple[CollectionError, ...] = ()
134-
135-
@classmethod
136-
def from_dict(cls, data: dict[str, Any]) -> DiscoverResponse:
137-
return cls(
138-
rootdir=data.get("rootdir", ""),
139-
nodeids=tuple(data.get("nodeids", [])),
140-
collection_errors=tuple(
141-
CollectionError.from_dict(e) for e in data.get("collection_errors", [])
142-
),
143-
)
144-
145-
146119
@dataclass(frozen=True, slots=True)
147120
class RunResponse:
148121
exit_code: int = 1

src/revitdevtool_pytest/plugin.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def pytest_addoption(parser: pytest.Parser) -> None:
7474
)
7575
grp.addoption(
7676
"--revit-launch", dest=OPT_LAUNCH, action="store_true", default=False,
77-
help="Auto-launch Revit if no running instance is found. Requires --revit-version.",
77+
help="Force-launch a new Revit instance (skip reusing existing). Requires --revit-version.",
7878
)
7979
grp.addoption(
8080
"--revit-launch-timeout", dest=OPT_LAUNCH_TIMEOUT, default=None, type=float,
@@ -84,7 +84,7 @@ def pytest_addoption(parser: pytest.Parser) -> None:
8484
parser.addini(OPT_VERSION, "Revit version year", type="string", default=None)
8585
parser.addini(OPT_TIMEOUT, "Per-test timeout (seconds)", type="string", default=str(DEFAULT_TEST_TIMEOUT_S))
8686
parser.addini(OPT_PIPE, "Explicit pipe name", type="string", default=None)
87-
parser.addini(OPT_LAUNCH, "Auto-launch Revit", type="bool", default=False)
87+
parser.addini(OPT_LAUNCH, "Force-launch a new Revit instance (skip reusing existing)", type="bool", default=False)
8888
parser.addini(OPT_LAUNCH_TIMEOUT, "Launch timeout (seconds)", type="string", default=str(DEFAULT_LAUNCH_TIMEOUT_S))
8989

9090

@@ -96,6 +96,10 @@ def pytest_addoption(parser: pytest.Parser) -> None:
9696
def pytest_configure(config: pytest.Config) -> None:
9797
config.addinivalue_line("markers", "revit: mark test to run inside Revit process")
9898

99+
reportchars = getattr(config.option, "reportchars", "") or ""
100+
if "P" not in reportchars:
101+
config.option.reportchars = reportchars + "P"
102+
99103
global _lease_store # noqa: PLW0603
100104
_lease_store = SuiteLeaseStore()
101105

@@ -192,6 +196,11 @@ def _ensure_bridge(session: pytest.Session) -> bool:
192196
returncode=EXIT_CODE_CONFIG_ERROR,
193197
)
194198

199+
force_launch = _opt_bool(config, OPT_LAUNCH, OPT_LAUNCH)
200+
if force_launch and _bridge is not None:
201+
_bridge.disconnect()
202+
_bridge = None
203+
195204
result = ensure_bridge(
196205
current_bridge=_bridge,
197206
lease_store=_lease_store,
@@ -200,6 +209,7 @@ def _ensure_bridge(session: pytest.Session) -> bool:
200209
explicit_pipe=explicit_pipe,
201210
suite_key=suite_key,
202211
suite_path=suite_path,
212+
force_launch=force_launch,
203213
)
204214
if result.dialog_resolver is not None:
205215
_dialog_resolver = result.dialog_resolver
@@ -235,6 +245,14 @@ def _opt_float(config: pytest.Config, cli: str, ini: str) -> float | None:
235245
return float(raw) if raw else None
236246

237247

248+
def _opt_bool(config: pytest.Config, cli: str, ini: str) -> bool:
249+
cli_val = config.getoption(cli, default=None)
250+
if cli_val:
251+
return True
252+
ini_val = config.getini(ini)
253+
return bool(ini_val)
254+
255+
238256
def _is_collect_only(config: pytest.Config) -> bool:
239257
option = getattr(config, "option", None)
240258
return bool(getattr(option, "collectonly", False))

src/revitdevtool_pytest/suite_leasing.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -99,18 +99,6 @@ def clear_suite(self, suite_key: str) -> None:
9999
self._leases.pop(suite_key, None)
100100
self._save_leases()
101101

102-
def _prune_stale(self, active_by_pid: dict[int, RevitInstance]) -> None:
103-
stale = [
104-
key
105-
for key, lease in self._leases.items()
106-
if lease.process_id not in active_by_pid
107-
]
108-
if not stale:
109-
return
110-
for key in stale:
111-
self._leases.pop(key, None)
112-
self._save_leases()
113-
114102
def _load_leases(self) -> dict[str, SuiteLease]:
115103
if not self._state_file.is_file():
116104
return {}
@@ -156,7 +144,7 @@ def _save_leases(self) -> None:
156144
except PermissionError:
157145
try:
158146
tmp_file.unlink(missing_ok=True)
159-
except Exception:
147+
except Exception: # noqa
160148
pass
161149
if delay is None:
162150
raise

0 commit comments

Comments
 (0)