Skip to content

Commit d3a9c15

Browse files
Add Kit test markers and a shared launch_kit() helper
Kit-dependence is currently a property of importing a test file: 156 test modules construct AppLauncher at module scope, so Isaac Sim boots during pytest collection. Because nothing declares that dependency, tools/conftest.py has to run every test file in its own subprocess, paying Kit startup once per file. Introduce the two pieces needed to change that: launch_kit() is an idempotent module-scope replacement for AppLauncher. The first test module in a process boots Kit; later modules receive the running app, so a pytest run covering several files pays startup once. It raises rather than silently returning a mismatched app when a file asks for cameras after a camera-less boot. The kit / kit_cameras / kitless markers let a file declare which launch configuration it needs, so files that can share a process can be grouped without importing them. kit_solo opts a file out of any such grouping. test_kit_marker_contract.py keeps the markers from drifting: it checks by AST that a file's declaration matches what it does at module scope. The checks are AST-based rather than text-based because several kit-free files mention AppLauncher only in a docstring saying they do not use it. Files are not yet required to carry a marker; _ENFORCED_ROOTS is empty and grows per package as files are migrated. No test file changes behaviour: nothing is marked kit or kitless yet, and no file calls launch_kit() yet. The guard found one pre-existing bug on its first run. test_operational_space assigned pytestmark twice, and the second assignment discarded arm_ci, so the file had been excluded from the ARM CI lane. Merged into a single list.
1 parent 90ee100 commit d3a9c15

5 files changed

Lines changed: 453 additions & 3 deletions

File tree

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,11 @@ markers = [
338338
"benchmark: test covers the Isaac Lab benchmark framework and infrastructure",
339339
"rendering: test exercises the rendering / camera / visualizer pipeline",
340340
"smoke: tests for core installation, task, and RL functionality",
341+
"kit: test file needs a booted headless Kit app; it calls isaaclab.test.launch.launch_kit() at module scope rather than constructing AppLauncher",
342+
"kit_cameras: like `kit`, but the app is booted with cameras enabled via launch_kit(cameras=True)",
343+
"kitless: test file runs without Kit; no AppLauncher and no module-scope import of omni/carb/isaacsim",
344+
"kit_solo: keep this file in its own process; it is never grouped with other files",
345+
"newton_ci: mark test to run in the Newton CI lane",
341346
]
342347

343348
# Add pypi.nvidia.com so that `uv pip install isaaclab[isaacsim]` works without --extra-index-url.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Added
2+
^^^^^
3+
4+
* Added :func:`~isaaclab.test.launch.launch_kit` so test modules can share one Kit app per
5+
pytest process instead of each launching their own. It is idempotent: the first module to
6+
call it boots Kit and later modules receive the running app.
7+
* Added the ``kit``, ``kit_cameras``, ``kitless``, and ``kit_solo`` pytest markers so a test
8+
file can declare its Kit launch configuration, plus a test that checks each file's markers
9+
against what it actually does at module scope.
10+
11+
Fixed
12+
^^^^^
13+
14+
* Fixed ``test_operational_space.py`` assigning ``pytestmark`` twice, which silently dropped
15+
its ``arm_ci`` marker and kept the file out of the ARM CI lane.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
"""Shared Kit launch helper for Isaac Lab tests.
7+
8+
Test modules that need Isaac Sim call :func:`launch_kit` at module scope in place of
9+
constructing :class:`~isaaclab.app.AppLauncher` directly::
10+
11+
from isaaclab.test.launch import launch_kit
12+
13+
launch_kit() # or launch_kit(cameras=True)
14+
15+
The call must stay at module scope: a test module's own imports (``pxr``, ``omni``,
16+
``isaaclab_physx``, ...) run during pytest collection, before any fixture executes, so Kit
17+
must already be running by then.
18+
19+
:func:`launch_kit` is idempotent within a process. The first test module to call it boots
20+
Kit; every later module gets the running app back. A pytest process covering several test
21+
files therefore pays Kit startup once rather than once per file.
22+
23+
Declare the matching marker on the module so the test runner can group files that share a
24+
launch configuration into one process::
25+
26+
pytestmark = pytest.mark.kit # launch_kit()
27+
pytestmark = pytest.mark.kit_cameras # launch_kit(cameras=True)
28+
"""
29+
30+
from __future__ import annotations
31+
32+
from typing import Any
33+
34+
_app: Any = None
35+
"""The Kit application booted by :func:`launch_kit`, or None before the first call."""
36+
37+
_cameras: bool = False
38+
"""Whether :attr:`_app` was booted with camera and render extensions enabled."""
39+
40+
41+
def launch_kit(*, cameras: bool = False) -> Any:
42+
"""Boot the shared Kit app for this process, or return the one already running.
43+
44+
Args:
45+
cameras: Whether the app must be booted with camera and render extensions enabled.
46+
Passed through to :paramref:`~isaaclab.app.AppLauncher.enable_cameras`.
47+
48+
Returns:
49+
The running ``SimulationApp``.
50+
51+
Raises:
52+
RuntimeError: If a camera-enabled app is requested but Kit is already running in
53+
this process without cameras, or if Kit was started by something other than
54+
this function. Both mean the test files sharing this process do not share a
55+
launch configuration and must be split across processes.
56+
"""
57+
global _app, _cameras
58+
59+
if _app is not None:
60+
if cameras and not _cameras:
61+
raise RuntimeError(
62+
"launch_kit(cameras=True) was called, but Kit is already running in this process"
63+
" without cameras. Camera extensions cannot be enabled after startup. Mark this"
64+
" file `pytest.mark.kit_cameras` so it is grouped with other camera tests instead"
65+
" of with plain `pytest.mark.kit` files."
66+
)
67+
return _app
68+
69+
from isaaclab.utils import has_kit
70+
71+
if has_kit():
72+
raise RuntimeError(
73+
"Kit is already running but was not started by launch_kit(), so its launch"
74+
" configuration is unknown. Another test file in this process still constructs"
75+
" AppLauncher directly; run that file in its own process."
76+
)
77+
78+
from isaaclab.app import AppLauncher
79+
80+
from .utils import resolve_test_sim_device
81+
82+
_app = AppLauncher(headless=True, enable_cameras=cameras, device=resolve_test_sim_device()).app
83+
_cameras = cameras
84+
return _app

source/isaaclab/test/controllers/test_operational_space.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
import torch
1717
from flaky import flaky
1818

19-
pytestmark = pytest.mark.arm_ci
20-
2119
import isaaclab.envs.mdp as mdp
2220
import isaaclab.sim as sim_utils
2321
from isaaclab import cloner
@@ -51,7 +49,7 @@
5149

5250
from isaaclab_assets import FRANKA_PANDA_CFG, G1_29DOF_CFG # isort:skip
5351

54-
pytestmark = pytest.mark.integration
52+
pytestmark = [pytest.mark.arm_ci, pytest.mark.integration]
5553

5654

5755
@pytest.fixture

0 commit comments

Comments
 (0)