Date: 2026-08-30
Accepted (shipped). Engine selection matches script execution — pyRevit first, embedded IronPython 3.4.2 otherwise. The pipeline (collect, wire, in-engine runner, test source) runs on IronPython 2.7 and 3.4.2.
RevitDevTool runs Python on two unrelated runtimes.
ExecutionMode.Python uses Python.NET / CPython. In-host tests go through
the pytest bridge (tests/run, RevitDevTool.PyTest). pytest 9 cannot run
on IronPython.
ExecutionMode.IronPython (*_ipy_script.py) already has one execution
flow: RevitIPyExecutionStrategy uses pyRevit Labs/PyRevitLoader when
loaded, else embedded IronPython 3.4.2. pyRevit clones may be IronPython
2.7 or 3.x. That auto-pick is the product IronPython runtime, not a second
test product.
unittest.main() already executes on the embedded path (ModuleName = "__main__"), but SystemExitException is reported as success.
pyRevit IsSuccessResultCode treats Succeeded = 0 and SysExited = 1 as
success. A failing suite currently reports success on both backends.
unittest.TestCase is not a runtime signal: the same form already runs on
CPython via pytest. A file named test_*_ipy_script.py is collected by
pytest test_*.py and executed on Python.NET.
Review judgments: do not pin embedded vs pyrevit as two test protocols;
keep one _ipy flow; design the pipeline for 2.7 and 3.4.2 because the
engine behind that flow is whichever IronPython execution would use.
-
One IronPython flow, same engine policy as scripts. Tests reuse
RevitIPyExecutionStrategy(and AutoCAD's embedded IPy where pyRevit does not exist). Noengine=request field, no*_pyrevit_test.py, no test-only override of pyRevit-first. Which engine ran may be echoed in diagnostics; it is not a selector. -
Pytest collect may use
test_*_ipy.py; the host does not. Local pytest needs a fail-closed way to split CPythontests/runfrom IronPythonipytests/runwithout importing the module. Filenametest_*_ipy.pyis that client convention (pytesttest_plus an_ipymarker). It is not a runtime identity:ipytests/runruns unittest on the requested paths.unittest.TestCaseis the in-engine runner, not the classifier.Pattern (pytest collect) Runtime Protocol test_*.pyexcludingtest_*_ipy.pyCPython.NET pytest tests/runtest_*_ipy.pyIronPython unittest ipytests/run*_ipy_script.pyexecution-tree script not a test file A
test_*_ipy.pyfile that is notunittest.TestCaseis a collection error on the client. Ordinarytest_*.pystays on CPython pytest. Production / library modules must not usetest_*_ipy.py— that name exists only so pytest can route. -
The IPy pipeline is a 2.7 / 3.4.2 intersection. Anything executed inside the IronPython engine — user tests and any hosted runner snippet — must parse and run on both. Forbidden in that dialect: f-strings,
async/await, type annotations that 2.7 cannot parse, theprint xstatement (useprint(x)— valid on 2.7 and 3.4 without__future__), pytest fixtures, PEP 723, CPython-only stdlib. Prefer driving unittest from C# (DLR / pyRevit script path) so the runner itself is not a Python 3 file. -
RevitDevTool.PyTestmay expand as collect/report shell only. Intercepttest_*_ipy.py(pytest_collect_file), build nodeids without importing the module in CPython, dispatch onipytests/run. Do not send those files throughtests/run/pytest.main(). -
Local collect must not assume Python 3. Do not
importIPy test files. Do not rely onast.parse(Python 3). Use filename plus a 2/3-safe scan (tokenize/ line patterns) forunittest.TestCaseandtest_*. If per-test names cannot be recovered, one nodeid = the file (whole-file suite). That is the compatible default, not a failure. -
tests/runstays CPython + Python.NET. Do not overloadPytestRunner.py. Python testing never enters the MTP kernel. -
*_ipy_script.pyremains script execution. A__main__unittest self-check is allowed.SystemExit/ pyRevitSysExitedmust yield a truthfulExecutionResult.
-
Two pinned engines (
test_*_ipy.pyvs*_pyrevit_test.py). Rejected after review. Duplicates a distinction the execution tree already resolves, and authors would maintain two suites for one product flow. -
IronPython stays scripts-only. Rejected. Automated collect/report through the existing pytest client is still wanted; engine count is what collapsed, not the client shell.
-
Identify IPy tests by
unittest.TestCaseor a pytest marker. Rejected as primary identity. Filename_ipyis fail-closed and matches_ipy_script.py. -
Route
test_*_ipy.pythroughtests/run. Rejected. Hostpytest.main()cannot run on IronPython 2.7 or 3.4. -
Author tests in Python 3.4-only and skip 2.7. Rejected. pyRevit-first means 2.7 is in the production path on many machines.
Positive:
- one
_ipycontract for scripts and tests; - pytest IDE tree can list IPy tests without executing them on Python.NET;
- CPython
PytestContractsstay a single mirror; - tests exercise the same engine users get when they run
*_ipy_script.py.
Tradeoffs:
- results differ across machines (pyRevit present vs only 3.4.2); that is accepted as matching script execution, and should be visible in the run diagnostic;
- test source is stuck on the 2.7/3.4 intersection — no f-strings, no pytest fixtures;
- per-test nodeids may be unavailable when the file is 2.7-only; whole-file suites are first-class;
- host needs an IronPython unittest runner plus
ipytests/run; - truthful
SystemExitmapping remains a defect fix on both backends.
- Spike:
SystemExitExceptionon 3.4.2 and pyRevitSysExitedvs a failed unittest exit code — both backends, one flow. - Spike: selected test ids vs whole-file on the pyRevit script-path API; whole-file is the compatible v1 if selection cannot be 2.7-safe.
- Plugin: collect
test_*_ipy.pywithout CPython import; 2/3-safe scan. - IPy scripts stay
*_ipy_script.py(notest_prefix) so pytest does not collect them. - After accept: product layer
docs/product/execution.md— one_ipyauthoring dialect. One layer only.