Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
14e674d
Add Testmon subprocess coverage tracking
mataylor-nvidia Jul 3, 2026
7126265
add testmon to docker for tests
mataylor-nvidia Jul 3, 2026
e73da43
Address greptile review feedback on subprocess coverage
mataylor-nvidia Jul 3, 2026
1e431bd
Guard testmon conftest import against missing tools dir
mataylor-nvidia Jul 3, 2026
179cd1d
update
mataylor-nvidia Jul 4, 2026
97346f0
update
mataylor-nvidia Jul 6, 2026
426c00a
Merge branch 'develop' into mataylor/testmon-subprocess-coverage
mataylor-nvidia Jul 6, 2026
7e98744
run all post-merge
mataylor-nvidia Jul 6, 2026
a5f9bb0
Check out repo in install-ci Detect Changes job
mataylor-nvidia Jul 6, 2026
4fe7d1b
Fix testmon arg wiring in run-tests action
mataylor-nvidia Jul 6, 2026
f603ddd
Treat all workflow YAML changes as testmon collect
mataylor-nvidia Jul 6, 2026
5209535
Fix readonly testmon DB in install CI container
mataylor-nvidia Jul 6, 2026
37362c2
upload testmon data
mataylor-nvidia Jul 6, 2026
63c07cb
Remove dead node-ID diagnostic prints in run-tests action
mataylor-nvidia Jul 6, 2026
c79b57c
Harden testmon coverage lookup against private-API changes
mataylor-nvidia Jul 6, 2026
63e963b
always run smoke tests
mataylor-nvidia Jul 6, 2026
bd1ad49
Force full collect for .github/actions Python changes
mataylor-nvidia Jul 6, 2026
2bef006
fix test
mataylor-nvidia Jul 6, 2026
85758e1
remove leftover
mataylor-nvidia Jul 6, 2026
ea523dc
update
mataylor-nvidia Jul 6, 2026
16df4db
Test Kit configuration selection
mataylor-nvidia Jul 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion .github/actions/install-ci-run/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,23 @@ name: 'Run Installation CI Tests'
description: >
Runs the install_ci suite inside the project's Docker harness via
tools/run_install_ci.py and uploads the JUnit XML report as an artifact
named install-ci-junit-<runner.arch>.
named install-ci-junit-<runner.arch>. When a Testmon data dir is provided,
the .testmondata database is also uploaded as install-ci-testmon-<runner.arch>
for triage.

inputs:
base-image:
description: 'Docker base image for the test container'
required: false
default: 'ubuntu:24.04'
testmon-data-dir:
description: 'Host directory for the persisted Testmon dependency database'
required: false
default: ''
testmon-mode:
description: 'Testmon mode: select affected tests, collect a full baseline, or off'
required: false
default: 'off'
test-filter:
description: 'pytest -k expression (empty = run everything)'
required: false
Expand All @@ -33,19 +43,43 @@ runs:
shell: bash
env:
BASE_IMAGE: ${{ inputs.base-image }}
TESTMON_DATA_DIR: ${{ inputs.testmon-data-dir }}
TESTMON_MODE: ${{ inputs.testmon-mode }}
TEST_FILTER: ${{ inputs.test-filter }}
run: |
args=(docker --gpu --base-image "$BASE_IMAGE"
--build-wheel
--testmon-data-dir "$TESTMON_DATA_DIR"
--results-dir "${{ github.workspace }}/results"
-- --tb=short -sv)
case "$TESTMON_MODE" in
select)
if [ -s "$TESTMON_DATA_DIR/.testmondata" ]; then
args+=(--testmon --testmon-forceselect)
else
echo "::warning::Testmon data is missing; collecting a full baseline"
args+=(--testmon --testmon-noselect)
fi
;;
collect) args+=(--testmon --testmon-noselect) ;;
off) ;;
*) echo "Unknown Testmon mode: $TESTMON_MODE"; exit 1 ;;
esac
[ -n "$TEST_FILTER" ] && args+=(-k "$TEST_FILTER")
# Make `python3` resolve to the uv-managed 3.12 for build.sh's gen_pyproject.py.
# --seed installs pip/setuptools/wheel into the venv so build.sh's `python3 -m pip install`
# works (uv venv is otherwise pip-free).
uv venv --seed --python 3.12 "${RUNNER_TEMP}/venv-runner"
source "${RUNNER_TEMP}/venv-runner/bin/activate"
tools/run_install_ci.py "${args[@]}"
- name: Upload Testmon data
if: always() && inputs.testmon-data-dir != ''
uses: actions/upload-artifact@v7
with:
name: install-ci-testmon-${{ runner.arch }}
path: ${{ inputs.testmon-data-dir }}/.testmondata
if-no-files-found: warn
retention-days: 7
- name: Upload JUnit XML report
if: always()
id: upload-junit-report
Expand Down
43 changes: 33 additions & 10 deletions .github/actions/run-package-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,6 @@ inputs:
description: 'Comma-separated list of specific test files to include'
default: ''
required: false
test-node-ids-file:
description: 'TOML file containing exact pytest node IDs to run'
default: ''
required: false
test-node-ids-key:
description: 'Top-level key in test-node-ids-file containing the node IDs for this job'
default: ''
required: false
pytest-options:
description: 'Additional pytest options'
default: ''
Expand All @@ -83,6 +75,10 @@ inputs:
description: 'Space-separated pip packages to install inside the Docker container before pytest starts'
default: ''
required: false
testmon-mode:
description: 'Override Testmon mode with select, collect, or off'
default: ''
required: false
wheelhouse-resource:
description: 'Optional NGC resource containing wheelhouse/ and manifest.json for offline pip installs'
default: ''
Expand Down Expand Up @@ -158,6 +154,33 @@ runs:
printf "🔵 Image pull took %dm %ds\n" $((elapsed/60)) $((elapsed%60))
echo "🔵 Docker Image Pulled in ${elapsed}s" >> "$GITHUB_STEP_SUMMARY"

- name: Select Testmon Mode
id: testmon-mode
shell: bash
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
REQUESTED_MODE: ${{ inputs.testmon-mode }}
run: |
mode="${REQUESTED_MODE:-collect}"
if [ -z "$REQUESTED_MODE" ] && [ "${{ github.event_name }}" = "pull_request" ]; then
if changed_files="$(gh api --paginate "repos/$REPO/pulls/$PR_NUMBER/files" --jq '.[].filename')"; then
mode="$(printf '%s\n' "$changed_files" | python3 .github/actions/run-package-tests/select_testmon_mode.py)"
else
echo "::warning::Could not list changed files; running the full test suite"
fi
fi
echo "mode=$mode" >> "$GITHUB_OUTPUT"
echo "Testmon mode: $mode" >> "$GITHUB_STEP_SUMMARY"

- name: Restore Testmon Data
if: steps.testmon-mode.outputs.mode != 'off'
uses: actions/cache@v4
with:
path: .testmon/${{ inputs.container-name }}
key: testmon-${{ runner.arch }}-${{ inputs.container-name }}-${{ hashFiles('conftest.py', 'tools/conftest.py', 'tools/testmon_subprocess_coverage.py', 'tools/test_settings.py', '.github/actions/run-tests/action.yml', 'docker/Dockerfile.base', 'docker/Dockerfile.curobo', '.github/workflows/config.yaml') }}-${{ github.sha }}
restore-keys: testmon-${{ runner.arch }}-${{ inputs.container-name }}-${{ hashFiles('conftest.py', 'tools/conftest.py', 'tools/testmon_subprocess_coverage.py', 'tools/test_settings.py', '.github/actions/run-tests/action.yml', 'docker/Dockerfile.base', 'docker/Dockerfile.curobo', '.github/workflows/config.yaml') }}-
- name: Restore NGC CLI cache
if: inputs.wheelhouse-resource != '' && env.NGC_API_KEY != ''
uses: actions/cache@v4
Expand Down Expand Up @@ -298,10 +321,10 @@ runs:
curobo-only: ${{ inputs.curobo-only }}
quarantined-only: ${{ inputs.quarantined-only }}
include-files: ${{ inputs.include-files }}
test-node-ids-file: ${{ inputs.test-node-ids-file }}
test-node-ids-key: ${{ inputs.test-node-ids-key }}
volume-mount-source: ${{ github.workspace }}
extra-pip-packages: ${{ inputs.extra-pip-packages }}
testmon-data-dir: .testmon/${{ inputs.container-name }}
testmon-mode: ${{ steps.testmon-mode.outputs.mode }}
wheelhouse-host-dir: ${{ steps.extract-wheelhouse.outputs.wheelhouse_host_dir }}
wheelhouse-packages: ${{ inputs.wheelhouse-packages }}
omni-github-test-type: ${{ inputs.omni-github-test-type }}
Expand Down
39 changes: 39 additions & 0 deletions .github/actions/run-package-tests/select_testmon_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Choose whether Testmon can safely select affected tests for a change."""

from __future__ import annotations

import re
import sys

_RELEVANT = re.compile(
r"^(?:source|docker|tools|apps|scripts)/"
r"|^\.github/(?:workflows/[^/]+\.ya?ml|actions/)"
r"|^\.gitmodules$"
r"|^[^/]+\.(?:toml|yaml|yml|json|ini|cfg|conf|lock|sh|bat|ps1)$"
)
_IGNORED_SUFFIXES = (".md", ".rst", ".skip")


def _is_tracked_python(path: str) -> bool:
"""Return whether ``path`` is a Python source file whose dependencies Testmon tracks.

Python files under ``.github/`` (e.g. action helper scripts) run outside the pytest
process, so Testmon has no dependency data for them; changes to those files must fall
back to a full collection rather than the Python-only fast path.
"""
return path.endswith(".py") and not path.startswith(".github/")


def select_testmon_mode(paths: list[str]) -> str:
"""Return ``select`` for tracked Python-only changes, otherwise ``collect``."""
relevant = [path for path in paths if _RELEVANT.search(path) and not path.endswith(_IGNORED_SUFFIXES)]
return "select" if not relevant or all(_is_tracked_python(path) for path in relevant) else "collect"


if __name__ == "__main__":
print(select_testmon_mode([line.strip() for line in sys.stdin if line.strip()]))
45 changes: 45 additions & 0 deletions .github/actions/run-package-tests/test_select_testmon_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Tests for changed-file Testmon mode selection."""

import importlib.util
from pathlib import Path

_MODULE_PATH = Path(__file__).with_name("select_testmon_mode.py")
_SPEC = importlib.util.spec_from_file_location("select_testmon_mode", _MODULE_PATH)
assert _SPEC and _SPEC.loader
_MODULE = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(_MODULE)
select_testmon_mode = _MODULE.select_testmon_mode


def test_python_only_relevant_changes_select_affected_tests() -> None:
assert select_testmon_mode(["source/isaaclab/isaaclab/app.py", "tools/helper.py"]) == "select"


def test_irrelevant_changes_select_no_affected_tests() -> None:
assert select_testmon_mode(["docs/guide.md", "source/isaaclab/changelog.d/change.skip"]) == "select"


def test_static_relevant_change_collects_full_suite() -> None:
assert select_testmon_mode(["source/isaaclab/config/extension.toml"]) == "collect"


def test_mixed_change_collects_full_suite() -> None:
assert select_testmon_mode(["source/isaaclab/code.py", "docker/Dockerfile.base"]) == "collect"


def test_workflow_yaml_change_collects_full_suite() -> None:
# A workflow change that testmon cannot reason about must run the full suite,
# otherwise a workflow-only PR would trigger a job that deselects every test.
assert select_testmon_mode([".github/workflows/install-ci.yml"]) == "collect"


def test_action_python_change_collects_full_suite() -> None:
# Python helpers under .github/actions/ run outside pytest, so testmon has no
# dependency data for them; a change to one must run the full suite rather than
# taking the Python-only fast path and deselecting every test.
assert select_testmon_mode([".github/actions/run-package-tests/select_testmon_mode.py"]) == "collect"
70 changes: 35 additions & 35 deletions .github/actions/run-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,6 @@ inputs:
description: 'Comma-separated list of specific test file paths to include (e.g., source/pkg/test/test_a.py,source/pkg/test/test_b.py)'
default: ''
required: false
test-node-ids-file:
description: 'TOML file containing exact pytest node IDs to run'
default: ''
required: false
test-node-ids-key:
description: 'Top-level key in test-node-ids-file containing the node IDs for this job'
default: ''
required: false
shard-index:
description: 'Zero-based index of this shard (used with shard-count to split tests across parallel jobs)'
default: ''
Expand All @@ -85,6 +77,13 @@ inputs:
description: 'Space-separated pip packages to install inside the Docker container before pytest starts'
default: ''
required: false
testmon-data-dir:
description: 'Workspace-relative directory for the Testmon dependency database'
default: ''
required: false
testmon-mode:
description: 'Testmon mode: select affected tests, collect a full baseline, or off'
default: 'off'
wheelhouse-host-dir:
description: 'Host directory containing wheelhouse/ and manifest.json for offline pip installs'
default: ''
Expand Down Expand Up @@ -126,10 +125,10 @@ runs:
local shard_count="${13}"
local volume_mount_source="${14}"
local extra_pip_packages="${15}"
local test_node_ids_file="${16}"
local test_node_ids_key="${17}"
local wheelhouse_host_dir="${18}"
local wheelhouse_packages="${19}"
local wheelhouse_host_dir="${16}"
local wheelhouse_packages="${17}"
local testmon_data_dir="${18}"
local testmon_mode="${19}"
local test_k_expr="${20}"
local logs_pid=""
local wait_pid=""
Expand All @@ -156,6 +155,9 @@ runs:
if [ -n "$extra_pip_packages" ]; then
echo "With extra pip packages: $extra_pip_packages"
fi
if [ "$testmon_mode" != "off" ]; then
echo "Testmon mode: $testmon_mode"
fi
if [ -n "$wheelhouse_host_dir" ]; then
echo "With wheelhouse host directory: $wheelhouse_host_dir"
fi
Expand All @@ -174,25 +176,10 @@ runs:
if [ -n "$include_files" ]; then
echo "Include files: $include_files"
fi
if [ -n "$test_node_ids_file" ]; then
echo "Test node IDs file: $test_node_ids_file"
fi
if [ -n "$test_node_ids_key" ]; then
echo "Test node IDs key: $test_node_ids_key"
fi
if [ -n "$shard_index" ] && [ -n "$shard_count" ]; then
echo "Shard: $shard_index of $shard_count"
fi

if [ -n "$test_node_ids_file" ] || [ -n "$test_node_ids_key" ]; then
if [ -z "$test_node_ids_file" ] || [ -z "$test_node_ids_key" ]; then
echo "Both test-node-ids-file and test-node-ids-key must be set together"
return 1
fi
export TEST_NODE_IDS_FILE="$test_node_ids_file"
export TEST_NODE_IDS_KEY="$test_node_ids_key"
fi

# Create reports directory
mkdir -p "$reports_dir"

Expand Down Expand Up @@ -234,11 +221,6 @@ runs:
echo "Setting TEST_NODE_IDS"
fi

if [ -n "${TEST_NODE_IDS_FILE:-}" ]; then
docker_env_vars="$docker_env_vars -e TEST_NODE_IDS_FILE -e TEST_NODE_IDS_KEY"
echo "Setting TEST_NODE_IDS_FILE=$TEST_NODE_IDS_FILE TEST_NODE_IDS_KEY=$TEST_NODE_IDS_KEY"
fi

if [ -n "$shard_index" ] && [ -n "$shard_count" ]; then
docker_env_vars="$docker_env_vars -e TEST_SHARD_INDEX=$shard_index -e TEST_SHARD_COUNT=$shard_count"
echo "Setting TEST_SHARD_INDEX=$shard_index TEST_SHARD_COUNT=$shard_count"
Expand Down Expand Up @@ -273,6 +255,24 @@ runs:
docker_env_vars="$docker_env_vars -e TEST_EXTRA_PIP_PACKAGES"
fi

testmon_options=""
if [ -n "$testmon_data_dir" ]; then
mkdir -p "$testmon_data_dir"
docker_env_vars="$docker_env_vars -e TESTMON_DATAFILE=/workspace/isaaclab/$testmon_data_dir/.testmondata"
case "$testmon_mode" in
select)
if [ -s "$testmon_data_dir/.testmondata" ]; then
testmon_options="--testmon --testmon-forceselect"
else
echo "::warning::Testmon data is missing; collecting a full baseline"
testmon_options="--testmon --testmon-noselect"
fi
;;
collect) testmon_options="--testmon --testmon-noselect" ;;
off) ;;
*) echo "Unknown Testmon mode: $testmon_mode"; return 1 ;;
esac
fi
if [ -n "$test_k_expr" ]; then
export TEST_K_EXPR="$test_k_expr"
docker_env_vars="$docker_env_vars -e TEST_K_EXPR"
Expand Down Expand Up @@ -383,7 +383,7 @@ runs:
# set this detect-only flag, which makes cold asset downloads
# fall back to slow repeated retries.
unset HUB__ARGS__DETECT_ONLY
./isaaclab.sh -p -m pip install pytest pytest-mock junitparser flatdict flaky \"coverage>=7.6.1\"
./isaaclab.sh -p -m pip install pytest pytest-mock \"pytest-testmon>=2.2,<3\" junitparser flatdict flaky \"coverage>=7.6.1\"
if [ -n \"\${TEST_WHEELHOUSE_PACKAGES:-}\" ]; then
if [ ! -d \"\${TEST_WHEELHOUSE_PATH:-}\" ]; then
echo \"Wheelhouse path is missing: \${TEST_WHEELHOUSE_PATH:-}\"
Expand Down Expand Up @@ -415,7 +415,7 @@ runs:
esac
fi
echo 'Starting pytest with path: $test_path'
./isaaclab.sh -p -m pytest --ignore=tools/conftest.py $test_path $pytest_options -v --junitxml=tests/$result_file
./isaaclab.sh -p -m pytest --ignore=tools/conftest.py $test_path $pytest_options $testmon_options -v --junitxml=tests/$result_file
"

# Stream container logs in background.
Expand Down Expand Up @@ -512,7 +512,7 @@ runs:
}

# Call the function with provided parameters
run_tests "${{ inputs.test-path }}" "${{ inputs.result-file }}" "${{ inputs.container-name }}" "${{ inputs.image-tag }}" "${{ inputs.reports-dir }}" "$PYTEST_OPTIONS" "${{ inputs.filter-pattern }}" "${{ inputs.exclude-pattern }}" "${{ inputs.curobo-only }}" "${{ inputs.include-files }}" "${{ inputs.quarantined-only }}" "${{ inputs.shard-index }}" "${{ inputs.shard-count }}" "${{ inputs.volume-mount-source }}" "${{ inputs.extra-pip-packages }}" "${{ inputs.test-node-ids-file }}" "${{ inputs.test-node-ids-key }}" "${{ inputs.wheelhouse-host-dir }}" "${{ inputs.wheelhouse-packages }}" "$TEST_K_EXPR_INPUT"
run_tests "${{ inputs.test-path }}" "${{ inputs.result-file }}" "${{ inputs.container-name }}" "${{ inputs.image-tag }}" "${{ inputs.reports-dir }}" "$PYTEST_OPTIONS" "${{ inputs.filter-pattern }}" "${{ inputs.exclude-pattern }}" "${{ inputs.curobo-only }}" "${{ inputs.include-files }}" "${{ inputs.quarantined-only }}" "${{ inputs.shard-index }}" "${{ inputs.shard-count }}" "${{ inputs.volume-mount-source }}" "${{ inputs.extra-pip-packages }}" "${{ inputs.wheelhouse-host-dir }}" "${{ inputs.wheelhouse-packages }}" "${{ inputs.testmon-data-dir }}" "${{ inputs.testmon-mode }}" "$TEST_K_EXPR_INPUT"

- name: Kill container on cancellation
if: cancelled()
Expand Down
Loading
Loading