From 0256d64c6a3162174d9ec93aec9207d3869443eb Mon Sep 17 00:00:00 2001 From: Matthew Taylor Date: Mon, 29 Jun 2026 15:38:27 -0400 Subject: [PATCH 1/4] update --- .github/actions/install-ci-run/action.yml | 18 +++- .github/actions/run-package-tests/action.yml | 43 +++++++--- .github/actions/run-tests/action.yml | 66 ++++++-------- .github/test-subsets/postmerge-rendering.toml | 34 -------- .github/workflows/build.yaml | 34 ++------ .github/workflows/install-ci.yml | 30 ++++++- .gitignore | 2 + .../changelog.d/testmon-affected-tests.skip | 1 + .../test/install_ci/Dockerfile.installci | 1 + .../test_cli_install_in_globalenv_smoke.py | 1 + .../cli/test_cli_install_in_uvenv_smoke.py | 1 + source/isaaclab/test/install_ci/conftest.py | 14 ++- .../misc/test_wheel_builder_smoke.py | 1 + source/isaaclab/test/install_ci/pytest.ini | 1 + tools/conftest.py | 85 +------------------ tools/run_install_ci.py | 11 +++ 16 files changed, 145 insertions(+), 198 deletions(-) delete mode 100644 .github/test-subsets/postmerge-rendering.toml create mode 100644 source/isaaclab/changelog.d/testmon-affected-tests.skip diff --git a/.github/actions/install-ci-run/action.yml b/.github/actions/install-ci-run/action.yml index f86ef7105a33..48a0afefca4c 100644 --- a/.github/actions/install-ci-run/action.yml +++ b/.github/actions/install-ci-run/action.yml @@ -14,6 +14,14 @@ inputs: description: 'Docker base image for the test container' required: false default: 'ubuntu:24.04' + premerge: + description: 'Run smoke tests plus tests selected by Testmon coverage' + required: false + default: 'false' + testmon-data-dir: + description: 'Host directory for the persisted Testmon dependency database' + required: false + default: '' test-filter: description: 'pytest -k expression (empty = run everything)' required: false @@ -33,12 +41,20 @@ runs: shell: bash env: BASE_IMAGE: ${{ inputs.base-image }} + PREMERGE: ${{ inputs.premerge }} + TESTMON_DATA_DIR: ${{ inputs.testmon-data-dir }} 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) + -- --tb=short -sv --testmon) + if [ "$PREMERGE" = "true" ]; then + args+=(--testmon-forceselect) + else + args+=(--testmon-noselect) + fi [ -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` diff --git a/.github/actions/run-package-tests/action.yml b/.github/actions/run-package-tests/action.yml index 211707e48c19..8b1a2713b981 100644 --- a/.github/actions/run-package-tests/action.yml +++ b/.github/actions/run-package-tests/action.yml @@ -61,14 +61,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: '' @@ -81,6 +73,10 @@ inputs: description: 'Test type stored on each uploaded omni-github test row' default: 'pytest' required: false + testmon-mode: + description: 'Override Testmon mode with select or collect' + default: '' + required: false container-name: description: 'Docker container name prefix (run-id is appended automatically)' required: true @@ -145,6 +141,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 + grep -qvE '\.py$' <<< "$changed_files" || mode=select + 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 + uses: actions/cache@v4 + with: + path: .testmon/${{ inputs.container-name }} + key: testmon-${{ runner.arch }}-${{ inputs.container-name }}-${{ hashFiles('tools/conftest.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('tools/conftest.py', 'tools/test_settings.py', '.github/actions/run-tests/action.yml', 'docker/Dockerfile.base', 'docker/Dockerfile.curobo', '.github/workflows/config.yaml') }}- + - name: Run Tests uses: ./.github/actions/run-tests with: @@ -160,11 +183,11 @@ 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 }} omni-github-test-type: ${{ inputs.omni-github-test-type }} + testmon-data-dir: .testmon/${{ inputs.container-name }} + testmon-mode: ${{ steps.testmon-mode.outputs.mode }} - name: Check Test Results if: always() diff --git a/.github/actions/run-tests/action.yml b/.github/actions/run-tests/action.yml index 9a335aeb9ee3..3e95b8ac34b7 100644 --- a/.github/actions/run-tests/action.yml +++ b/.github/actions/run-tests/action.yml @@ -53,14 +53,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: '' @@ -81,6 +73,14 @@ inputs: description: 'Test type stored on each uploaded omni-github test row' default: 'pytest' 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' + required: false runs: using: composite @@ -105,8 +105,8 @@ 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 testmon_data_dir="${16}" + local testmon_mode="${17}" local logs_pid="" local wait_pid="" local docker_wait_file="/tmp/.docker_exit_${container_name}" @@ -132,6 +132,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 "$filter_pattern" ]; then echo "With filter pattern: $filter_pattern" fi @@ -144,25 +147,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" @@ -199,16 +187,6 @@ runs: echo "Setting TEST_INCLUDE_FILES=$include_files_compact" fi - if [ -n "${TEST_NODE_IDS:-}" ]; then - docker_env_vars="$docker_env_vars -e TEST_NODE_IDS" - 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" @@ -243,6 +221,18 @@ 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) testmon_options="--testmon --testmon-forceselect" ;; + collect) testmon_options="--testmon --testmon-noselect" ;; + off) ;; + *) echo "Unknown Testmon mode: $testmon_mode"; return 1 ;; + esac + fi + # Volume mount for deps-cache-hit mode: bind-mount the checked-out # source code over /workspace/isaaclab instead of baking it into the image. docker_volume_args="" @@ -322,7 +312,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_EXTRA_PIP_PACKAGES:-}\" ]; then echo \"Installing extra pip packages: \${TEST_EXTRA_PIP_PACKAGES}\" ./isaaclab.sh -p -m pip install \${TEST_EXTRA_PIP_PACKAGES} @@ -334,7 +324,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. @@ -431,7 +421,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 }}" "${{ inputs.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 }}" + run_tests "${{ inputs.test-path }}" "${{ inputs.result-file }}" "${{ inputs.container-name }}" "${{ inputs.image-tag }}" "${{ inputs.reports-dir }}" "${{ inputs.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.testmon-data-dir }}" "${{ inputs.testmon-mode }}" - name: Kill container on cancellation if: cancelled() diff --git a/.github/test-subsets/postmerge-rendering.toml b/.github/test-subsets/postmerge-rendering.toml deleted file mode 100644 index ee52d8f1f548..000000000000 --- a/.github/test-subsets/postmerge-rendering.toml +++ /dev/null @@ -1,34 +0,0 @@ -# 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 - -# Stable rendering correctness tests used by post-merge CI. -rendering-correctness = [ - "source/isaaclab_tasks/test/core/test_rendering_cartpole.py::test_rendering_cartpole[newton-isaacsim_rtx-albedo]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole.py::test_rendering_cartpole[newton-isaacsim_rtx-depth]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole.py::test_rendering_cartpole[newton-isaacsim_rtx-rgb]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole.py::test_rendering_cartpole[newton-isaacsim_rtx-semantic_segmentation]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole.py::test_rendering_cartpole[physx-isaacsim_rtx-albedo]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole.py::test_rendering_cartpole[physx-isaacsim_rtx-depth]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole.py::test_rendering_cartpole[physx-isaacsim_rtx-rgb]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole.py::test_rendering_cartpole[physx-isaacsim_rtx-semantic_segmentation]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole.py::test_rendering_cartpole[physx-newton_warp-depth]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole.py::test_rendering_cartpole[physx-newton_warp-rgb]", - "source/isaaclab_tasks/test/core/test_rendering_registered_tasks.py::test_rendering_registered_tasks[Isaac-Cartpole-Camera-Direct-albedo-cartpole]", - "source/isaaclab_tasks/test/core/test_rendering_registered_tasks.py::test_rendering_registered_tasks[Isaac-Cartpole-Camera-Direct-depth-cartpole]", - "source/isaaclab_tasks/test/core/test_rendering_registered_tasks.py::test_rendering_registered_tasks[Isaac-Cartpole-Camera-Direct-None-cartpole]", - "source/isaaclab_tasks/test/core/test_rendering_registered_tasks.py::test_rendering_registered_tasks[Isaac-Cartpole-Camera-Direct-rgb-cartpole]", -] - -rendering-correctness-kitless = [ - "source/isaaclab_tasks/test/core/test_rendering_cartpole_kitless.py::test_rendering_cartpole_kitless[newton-newton_warp-depth]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole_kitless.py::test_rendering_cartpole_kitless[newton-newton_warp-rgb]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole_kitless.py::test_rendering_cartpole_kitless[newton-ovrtx-albedo]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole_kitless.py::test_rendering_cartpole_kitless[newton-ovrtx-depth]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole_kitless.py::test_rendering_cartpole_kitless[ovphysx-newton_warp-depth]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole_kitless.py::test_rendering_cartpole_kitless[ovphysx-newton_warp-rgb]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole_kitless.py::test_rendering_cartpole_kitless[ovphysx-ovrtx-albedo]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole_kitless.py::test_rendering_cartpole_kitless[ovphysx-ovrtx-rgb]", - "source/isaaclab_tasks/test/core/test_rendering_cartpole_kitless.py::test_rendering_cartpole_kitless[ovphysx-ovrtx-semantic_segmentation]", -] diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9bf03560cddd..d77e3d8b2dad 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -105,13 +105,10 @@ jobs: $'^\\.github/workflows/build\\.yaml$\tThis workflow file' $'^\\.github/workflows/config\\.yaml$\tBase image config' $'^\\.github/actions/\tCI actions' - $'^\\.github/test-subsets/\tCI test subset config' + $'^\\.gitmodules$\tGit submodule config' + $'^[^/]+\\.(toml|yaml|yml|json|ini|cfg|conf|lock|sh|bat|ps1)$\tRoot configuration and scripts' ) - if [ "$EVENT_NAME" = "push" ]; then - triggered_jobs="Docker base build job + rendering-correctness + rendering-correctness-kitless" - else - triggered_jobs="Docker build jobs + all test-* matrix jobs" - fi + triggered_jobs="Docker build jobs + all test-* matrix jobs" render_table() { local files="$1" entry regex desc count sample shown @@ -253,7 +250,6 @@ jobs: runs-on: [self-hosted, gpu] needs: [changes, config] if: >- - github.event_name != 'push' && needs.changes.outputs.run_docker_tests == 'true' steps: - name: Checkout Code @@ -281,7 +277,6 @@ jobs: continue-on-error: true needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -306,7 +301,6 @@ jobs: continue-on-error: true needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -331,7 +325,6 @@ jobs: continue-on-error: true needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -355,7 +348,6 @@ jobs: timeout-minutes: 180 needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -378,7 +370,6 @@ jobs: timeout-minutes: 180 needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -401,7 +392,6 @@ jobs: timeout-minutes: 180 needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -424,7 +414,6 @@ jobs: timeout-minutes: 180 needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -446,7 +435,6 @@ jobs: timeout-minutes: 180 needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -467,7 +455,6 @@ jobs: timeout-minutes: 180 needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -488,7 +475,6 @@ jobs: timeout-minutes: 180 needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -509,7 +495,6 @@ jobs: timeout-minutes: 180 needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -530,7 +515,6 @@ jobs: timeout-minutes: 180 needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -551,7 +535,6 @@ jobs: timeout-minutes: 180 needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -572,7 +555,6 @@ jobs: timeout-minutes: 180 needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -593,7 +575,6 @@ jobs: timeout-minutes: 180 needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -642,7 +623,6 @@ jobs: continue-on-error: true needs: [build-curobo, config] if: >- - github.event_name != 'push' && needs.build-curobo.result == 'success' steps: - uses: actions/checkout@v6 @@ -692,7 +672,6 @@ jobs: continue-on-error: true needs: [build-curobo, config] if: >- - github.event_name != 'push' && needs.build-curobo.result == 'success' steps: - uses: actions/checkout@v6 @@ -716,7 +695,6 @@ jobs: continue-on-error: true needs: [build, config] if: >- - github.event_name != 'push' && needs.build.result == 'success' steps: - uses: actions/checkout@v6 @@ -757,8 +735,7 @@ jobs: test_rendering_dexsuite_kuka_hetero.py, test_rendering_registered_tasks.py, test_rendering_shadow_hand.py - test-node-ids-file: ${{ github.event_name == 'push' && '.github/test-subsets/postmerge-rendering.toml' || '' }} - test-node-ids-key: ${{ github.event_name == 'push' && 'rendering-correctness' || '' }} + testmon-mode: collect container-name: isaac-lab-rendering-correctness-test omni-github-test-type: rendering-correctness @@ -786,8 +763,7 @@ jobs: test_rendering_dexsuite_kuka_homo_kitless.py, test_rendering_dexsuite_kuka_hetero_kitless.py, test_rendering_shadow_hand_kitless.py - test-node-ids-file: ${{ github.event_name == 'push' && '.github/test-subsets/postmerge-rendering.toml' || '' }} - test-node-ids-key: ${{ github.event_name == 'push' && 'rendering-correctness-kitless' || '' }} + testmon-mode: collect container-name: isaac-lab-rendering-correctness-kitless-test omni-github-test-type: rendering-correctness-kitless #endregion diff --git a/.github/workflows/install-ci.yml b/.github/workflows/install-ci.yml index 226222d425bd..1be728b7550a 100644 --- a/.github/workflows/install-ci.yml +++ b/.github/workflows/install-ci.yml @@ -37,6 +37,7 @@ jobs: runs-on: ubuntu-latest outputs: run_install_tests: ${{ steps.detect.outputs.run_install_tests }} + premerge: ${{ steps.detect.outputs.premerge }} steps: - id: detect env: @@ -53,14 +54,18 @@ jobs: # filter (a not-triggered required check would block the PR forever). patterns=( $'^apps/\tStandalone apps' + $'^docker/\tContainer build inputs' + $'^scripts/\tStandalone scripts' $'^tools/\tBuild tooling' $'^source/\tLibrary source code' $'^\\.github/actions/run-package-tests/\tTest action' $'^\\.github/actions/install-ci-(collect|run)/\tInstall-ci composite actions' $'^\\.github/workflows/install-ci\\.yml$\tThis workflow file' + $'^\\.gitmodules$\tGit submodule config' $'^VERSION$\tVersion file' $'(^|/)pyproject\\.toml$\tPython project metadata' $'(^|/)environment\\.ya?ml$\tConda environment file' + $'^[^/]+\\.(toml|yaml|yml|json|ini|cfg|conf|lock|sh|bat|ps1)$\tRoot configuration and scripts' ) render_table() { @@ -94,9 +99,10 @@ jobs: } decide() { - local decision="$1" reason="$2" files="${3:-}" + local decision="$1" reason="$2" files="${3:-}" premerge="${4:-false}" echo "Decision: run_install_tests=$decision ($reason)" echo "run_install_tests=$decision" >> "$GITHUB_OUTPUT" + echo "premerge=$premerge" >> "$GITHUB_OUTPUT" { if [ -n "$files" ]; then render_table "$files" @@ -119,7 +125,11 @@ jobs: printf '%s\n' "$changed_files" if any_match "$changed_files"; then - decide true "relevant paths changed" "$changed_files" + if ! grep -qvE '\.py$' <<< "$changed_files"; then + decide true "relevant Python paths changed" "$changed_files" true + else + decide true "static paths changed; full suite required" "$changed_files" + fi else decide false "no relevant paths changed" "$changed_files" fi @@ -133,6 +143,12 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + - name: Restore Testmon Data + uses: actions/cache@v4 + with: + path: .testmon + key: install-ci-testmon-${{ runner.arch }}-${{ hashFiles('source/isaaclab/test/install_ci/Dockerfile.installci', 'source/isaaclab/test/install_ci/pytest.ini') }}-${{ github.sha }} + restore-keys: install-ci-testmon-${{ runner.arch }}-${{ hashFiles('source/isaaclab/test/install_ci/Dockerfile.installci', 'source/isaaclab/test/install_ci/pytest.ini') }}- - name: Show Collected Tests uses: ./.github/actions/install-ci-collect with: @@ -141,6 +157,8 @@ jobs: uses: ./.github/actions/install-ci-run with: base-image: ${{ inputs.base_image || 'ubuntu:24.04' }} + premerge: ${{ needs.changes.outputs.premerge }} + testmon-data-dir: ${{ github.workspace }}/.testmon/${{ runner.arch }} test-filter: ${{ inputs.test_filter }} install-tests-arm: @@ -152,6 +170,12 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + - name: Restore Testmon Data + uses: actions/cache@v4 + with: + path: .testmon + key: install-ci-testmon-${{ runner.arch }}-${{ hashFiles('source/isaaclab/test/install_ci/Dockerfile.installci', 'source/isaaclab/test/install_ci/pytest.ini') }}-${{ github.sha }} + restore-keys: install-ci-testmon-${{ runner.arch }}-${{ hashFiles('source/isaaclab/test/install_ci/Dockerfile.installci', 'source/isaaclab/test/install_ci/pytest.ini') }}- - name: Show Collected Tests uses: ./.github/actions/install-ci-collect with: @@ -160,4 +184,6 @@ jobs: uses: ./.github/actions/install-ci-run with: base-image: ${{ inputs.base_image || 'ubuntu:24.04' }} + premerge: ${{ needs.changes.outputs.premerge }} + testmon-data-dir: ${{ github.workspace }}/.testmon/${{ runner.arch }} test-filter: ${{ inputs.test_filter }} diff --git a/.gitignore b/.gitignore index d78a978c60a2..c9a3930ad6bf 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ **/*.egg-info/ **/__pycache__/ **/.pytest_cache/ +**/.testmon/ +**/.testmondata **/*.pyc **/*.pb diff --git a/source/isaaclab/changelog.d/testmon-affected-tests.skip b/source/isaaclab/changelog.d/testmon-affected-tests.skip new file mode 100644 index 000000000000..9f59b1e1f3f3 --- /dev/null +++ b/source/isaaclab/changelog.d/testmon-affected-tests.skip @@ -0,0 +1 @@ +CI and test-selection changes only. diff --git a/source/isaaclab/test/install_ci/Dockerfile.installci b/source/isaaclab/test/install_ci/Dockerfile.installci index 68b127df7b34..8de10959b6c8 100644 --- a/source/isaaclab/test/install_ci/Dockerfile.installci +++ b/source/isaaclab/test/install_ci/Dockerfile.installci @@ -52,6 +52,7 @@ RUN ln -sf /usr/bin/python3 /usr/bin/python # Install test runner dependencies into the system Python RUN pip install --break-system-packages \ pytest>=8.0 \ + "pytest-testmon>=2.2,<3" \ pytest-timeout>=2.0 # Install uv system-wide so non-root users can invoke it without PATH trickery diff --git a/source/isaaclab/test/install_ci/cli/test_cli_install_in_globalenv_smoke.py b/source/isaaclab/test/install_ci/cli/test_cli_install_in_globalenv_smoke.py index 1d39de3137ac..10d0ff5ff345 100644 --- a/source/isaaclab/test/install_ci/cli/test_cli_install_in_globalenv_smoke.py +++ b/source/isaaclab/test/install_ci/cli/test_cli_install_in_globalenv_smoke.py @@ -19,6 +19,7 @@ from utils import run_cmd +@pytest.mark.smoke class Test_Cli_Install_In_Globalenv_Smoke: """./isaaclab.sh -i with no uv/conda env active (system Python).""" diff --git a/source/isaaclab/test/install_ci/cli/test_cli_install_in_uvenv_smoke.py b/source/isaaclab/test/install_ci/cli/test_cli_install_in_uvenv_smoke.py index 322c4b29352a..7de51d7afc73 100644 --- a/source/isaaclab/test/install_ci/cli/test_cli_install_in_uvenv_smoke.py +++ b/source/isaaclab/test/install_ci/cli/test_cli_install_in_uvenv_smoke.py @@ -33,6 +33,7 @@ def _skip_if_isaacsim_unavailable() -> None: pytest.skip("isaacsim is not importable and _isaac_sim link not found, skipping") +@pytest.mark.smoke class Test_Cli_Install_In_Uvenv_Smoke(UV_Mixin): """./isaaclab.sh -u/-i smoke checks plus optional submodule (mimic) and feature (newton) installs.""" diff --git a/source/isaaclab/test/install_ci/conftest.py b/source/isaaclab/test/install_ci/conftest.py index 747330dfd849..3aa6cffc416f 100644 --- a/source/isaaclab/test/install_ci/conftest.py +++ b/source/isaaclab/test/install_ci/conftest.py @@ -11,6 +11,7 @@ import platform import subprocess import sys +from collections.abc import Generator from pathlib import Path import pytest @@ -100,6 +101,7 @@ def pytest_addoption(parser: pytest.Parser) -> None: def pytest_configure(config: pytest.Config) -> None: + config.addinivalue_line("markers", "smoke: quick installation smoke tests") config.addinivalue_line("markers", "bug: bug-regression tests (use bug id as argument)") config.addinivalue_line("markers", "gpu: tests that require a GPU") config.addinivalue_line("markers", "docker: tests that only run inside Docker") @@ -134,8 +136,8 @@ def pytest_runtest_logreport(report: pytest.TestReport) -> None: sys.stdout.write("\n") -@pytest.hookimpl(tryfirst=True) -def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None: +@pytest.hookimpl(wrapper=True, tryfirst=True) +def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> Generator[None, None, None]: """Map dynamic bug markers and deselect items with mismatched env markers. This allows filtering by bug ID natively in pytest: `-m ""` @@ -144,6 +146,7 @@ def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item Tests whose ``docker``/``native`` marker doesn't match the current execution environment are *deselected* (removed from collection) so they don't appear in ``--collect-only`` output or skew skipped-count metrics. + Smoke tests are restored after Testmon selection so they always run on PRs. """ execution_environment = config.stash[_EXECUTION_ENVIRONMENT_KEY] known_bugs = set() @@ -182,3 +185,10 @@ def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item if config.getoption("--noskip"): for item in items: item.own_markers = [m for m in item.own_markers if m.name != "skip"] + + smoke_items = [item for item in items if item.get_closest_marker("smoke")] + yield + + if "--testmon-forceselect" in config.invocation_params.args: + selected = set(items) + items.extend(item for item in smoke_items if item not in selected) diff --git a/source/isaaclab/test/install_ci/misc/test_wheel_builder_smoke.py b/source/isaaclab/test/install_ci/misc/test_wheel_builder_smoke.py index e111b0fec3ce..fe79a3842284 100644 --- a/source/isaaclab/test/install_ci/misc/test_wheel_builder_smoke.py +++ b/source/isaaclab/test/install_ci/misc/test_wheel_builder_smoke.py @@ -33,6 +33,7 @@ from utils import UV_Mixin, run_cmd +@pytest.mark.smoke class Test_Wheel_Builder_Smoke(UV_Mixin): """Test building the isaaclab wheel and installing it in a uv environment.""" diff --git a/source/isaaclab/test/install_ci/pytest.ini b/source/isaaclab/test/install_ci/pytest.ini index ab7e9d50137a..db573cf9ecf2 100644 --- a/source/isaaclab/test/install_ci/pytest.ini +++ b/source/isaaclab/test/install_ci/pytest.ini @@ -6,6 +6,7 @@ python_files = *_test.py *_tests.py markers = + smoke: installation smoke tests bug: regression tests (bug id as argument) gpu: tests that require a GPU docker: tests that ONLY run inside Docker diff --git a/tools/conftest.py b/tools/conftest.py index a34812ddccab..78a507cd5492 100644 --- a/tools/conftest.py +++ b/tools/conftest.py @@ -13,7 +13,6 @@ from dataclasses import dataclass import pytest -import tomllib from junitparser import Error, JUnitXml, TestCase, TestSuite from prettytable import PrettyTable @@ -731,14 +730,12 @@ def _run_one_pass( ) -def run_individual_tests(test_files, workspace_root, isaacsim_ci, test_node_ids_by_file=None): +def run_individual_tests(test_files, workspace_root, isaacsim_ci): """Run each test file separately, ensuring one finishes before starting the next.""" failed_tests = [] test_status = {} xml_reports = [] cold_cache_applied = False - test_node_ids_by_file = test_node_ids_by_file or {} - for test_file in test_files: print(f"\n\nšŸš€ Running {test_file} independently...\n") file_name = os.path.basename(test_file) @@ -765,8 +762,6 @@ def run_individual_tests(test_files, workspace_root, isaacsim_ci, test_node_ids_ extra = COLD_CACHE_BUFFER if is_cold_cache_test else 0 startup_deadline = min(timeout, STARTUP_DEADLINE + extra) - pytest_targets = test_node_ids_by_file.get(os.path.normpath(test_file), [str(test_file)]) - ctx = _PassContext( test_file=test_file, file_name=file_name, @@ -775,7 +770,7 @@ def run_individual_tests(test_files, workspace_root, isaacsim_ci, test_node_ids_ timeout=timeout, startup_deadline=startup_deadline, env=env, - pytest_targets=pytest_targets, + pytest_targets=[str(test_file)], ) if is_device_split_file(test_file, source=test_content): @@ -873,60 +868,6 @@ def _collect_test_files( return test_files -def _load_test_node_ids_from_toml(workspace_root: str) -> list[str]: - """Load exact pytest node IDs from a TOML file configured in the environment.""" - node_ids_file = os.environ.get("TEST_NODE_IDS_FILE") - node_ids_key = os.environ.get("TEST_NODE_IDS_KEY") - if not (node_ids_file or node_ids_key): - return [] - if not (node_ids_file and node_ids_key): - pytest.exit("Both TEST_NODE_IDS_FILE and TEST_NODE_IDS_KEY must be set together", returncode=1) - - path = node_ids_file if os.path.isabs(node_ids_file) else os.path.join(workspace_root, node_ids_file) - - try: - with open(os.path.normpath(path), "rb") as stream: - node_ids = tomllib.load(stream).get(node_ids_key) - except OSError as exc: - pytest.exit(f"Could not read TEST_NODE_IDS_FILE {node_ids_file!r}: {exc}", returncode=1) - except tomllib.TOMLDecodeError as exc: - pytest.exit(f"{node_ids_file}: invalid TOML: {exc}", returncode=1) - - if not node_ids: - pytest.exit(f"{node_ids_key!r} not found or empty in {node_ids_file}", returncode=1) - if not isinstance(node_ids, list) or not all(isinstance(node_id, str) for node_id in node_ids): - pytest.exit(f"{node_ids_key!r} must be a TOML array of strings in {node_ids_file}", returncode=1) - - return node_ids - - -def _collect_test_node_ids_by_file(workspace_root: str) -> dict[str, list[str]]: - """Group exact pytest node IDs by absolute test file path.""" - node_ids = [line.strip() for line in os.environ.get("TEST_NODE_IDS", "").splitlines() if line.strip()] - node_ids.extend(_load_test_node_ids_from_toml(workspace_root)) - if len(node_ids) != len(set(node_ids)): - pytest.exit("Configured test node IDs contain duplicates", returncode=1) - - grouped: dict[str, list[str]] = {} - for node_id in node_ids: - normalized_node_id = node_id.replace("\\", "/") - if "::" not in normalized_node_id: - pytest.exit(f"Configured test node ID must include '::': {node_id}", returncode=1) - - file_part, test_part = normalized_node_id.split("::", 1) - if os.path.isabs(file_part): - abs_file = os.path.normpath(file_part) - else: - abs_file = os.path.normpath(os.path.join(workspace_root, file_part)) - - if not os.path.exists(abs_file): - pytest.exit(f"Configured test node ID file does not exist: {node_id}", returncode=1) - - grouped.setdefault(abs_file, []).append(f"{normalized_node_id.split('::', 1)[0]}::{test_part}") - - return grouped - - def _write_empty_report(): """Write an empty JUnit XML report so downstream CI steps find a valid file.""" os.makedirs("tests", exist_ok=True) @@ -954,8 +895,6 @@ def pytest_sessionstart(session): isaacsim_ci = os.environ.get("ISAACSIM_CI_SHORT", "false") == "true" - test_node_ids_by_file = _collect_test_node_ids_by_file(workspace_root) - # Parse include files list (comma-separated paths) include_files = set() if include_files_str: @@ -963,8 +902,6 @@ def pytest_sessionstart(session): f = f.strip() if f: include_files.add(os.path.basename(f)) - include_files.update(os.path.basename(path) for path in test_node_ids_by_file) - # Also try to get from pytest config if hasattr(session.config, "option") and hasattr(session.config.option, "filter_pattern"): filter_pattern = filter_pattern or getattr(session.config.option, "filter_pattern", "") @@ -977,15 +914,11 @@ def pytest_sessionstart(session): print(f"Filter pattern: '{filter_pattern}'") print(f"Exclude pattern: '{exclude_pattern}'") print(f"Include files: {include_files if include_files else 'none'}") - print(f"Test node IDs: {sum(len(node_ids) for node_ids in test_node_ids_by_file.values())}") print(f"Quarantined-only mode: {quarantined_only}") print(f"Curobo-only mode: {curobo_only}") print(f"TEST_FILTER_PATTERN env var: '{os.environ.get('TEST_FILTER_PATTERN', 'NOT_SET')}'") print(f"TEST_EXCLUDE_PATTERN env var: '{os.environ.get('TEST_EXCLUDE_PATTERN', 'NOT_SET')}'") print(f"TEST_INCLUDE_FILES env var: '{os.environ.get('TEST_INCLUDE_FILES', 'NOT_SET')}'") - print(f"TEST_NODE_IDS env var: '{'SET' if os.environ.get('TEST_NODE_IDS') else 'NOT_SET'}'") - print(f"TEST_NODE_IDS_FILE env var: '{os.environ.get('TEST_NODE_IDS_FILE', 'NOT_SET')}'") - print(f"TEST_NODE_IDS_KEY env var: '{os.environ.get('TEST_NODE_IDS_KEY', 'NOT_SET')}'") print(f"TEST_QUARANTINED_ONLY env var: '{os.environ.get('TEST_QUARANTINED_ONLY', 'NOT_SET')}'") print(f"TEST_CUROBO_ONLY env var: '{os.environ.get('TEST_CUROBO_ONLY', 'NOT_SET')}'") print("=" * 50) @@ -1008,13 +941,6 @@ def pytest_sessionstart(session): new_test_files.append(test_file) test_files = new_test_files - if test_node_ids_by_file: - configured_files = set(test_node_ids_by_file) - test_files = [test_file for test_file in test_files if os.path.normpath(test_file) in configured_files] - missing_files = sorted(configured_files - {os.path.normpath(test_file) for test_file in test_files}) - if missing_files: - pytest.exit(f"Configured test node ID files were not collected: {missing_files}", returncode=1) - if not test_files: if quarantined_only: print("No quarantined tests configured — nothing to run.") @@ -1030,14 +956,9 @@ def pytest_sessionstart(session): print(f"Found {len(test_files)} test files after filtering:") for test_file in test_files: print(f" - {test_file}") - if test_node_ids_by_file: - for node_id in test_node_ids_by_file[os.path.normpath(test_file)]: - print(f" {node_id}") # Run all tests individually - failed_tests, test_status, xml_reports = run_individual_tests( - test_files, workspace_root, isaacsim_ci, test_node_ids_by_file - ) + failed_tests, test_status, xml_reports = run_individual_tests(test_files, workspace_root, isaacsim_ci) print("failed tests:", failed_tests) diff --git a/tools/run_install_ci.py b/tools/run_install_ci.py index 5b48dc90ab45..7a5c4dd4a0b0 100755 --- a/tools/run_install_ci.py +++ b/tools/run_install_ci.py @@ -317,6 +317,12 @@ def _cmd_docker(args: argparse.Namespace) -> int: if not args.no_uv_cache: docker_run_cmd.extend(["-v", "isaaclab-install-ci-uv-cache:/home/isaaclab/.cache/uv"]) + if args.testmon_data_dir: + testmon_data_dir = Path(args.testmon_data_dir).resolve() + testmon_data_dir.mkdir(parents=True, exist_ok=True) + docker_run_cmd.extend(["-v", f"{testmon_data_dir}:/tmp/testmon"]) + docker_run_cmd.extend(["-e", "TESTMON_DATAFILE=/tmp/testmon/.testmondata"]) + # Pass environment variables docker_run_cmd.extend(["-e", "OMNI_KIT_ACCEPT_EULA=Y"]) docker_run_cmd.extend(["-e", "ACCEPT_EULA=Y"]) @@ -408,6 +414,8 @@ def main() -> int: --no-pip-cache Disable persistent pip cache volume --no-uv-cache Disable persistent uv cache volume --results-dir DIR Host directory for test results (auto-adds --junitxml) + --testmon-data-dir DIR + Host directory persisted as the Testmon dependency database --wheel PATH Path to pre-built isaaclab wheel file --build-wheel Build the isaaclab wheel and pass it to tests (mutually exclusive with --wheel) --debug Stream wheel-build output live (default: quiet, only dumped on failure) @@ -453,6 +461,9 @@ def main() -> int: docker_p.add_argument( "--results-dir", type=str, default=None, help="Host directory for test results (auto-adds --junitxml)" ) + docker_p.add_argument( + "--testmon-data-dir", type=str, default=None, help="Host directory for the Testmon dependency database" + ) docker_p.add_argument("--wheel", type=str, default=None, help="Path to pre-built isaaclab wheel file") docker_p.add_argument( "--build-wheel", From 54ca69ecd8392b8f827feafce7b5444ac2eb5e40 Mon Sep 17 00:00:00 2001 From: Matthew Taylor Date: Mon, 29 Jun 2026 16:05:36 -0400 Subject: [PATCH 2/4] fix --- .github/actions/run-package-tests/action.yml | 5 ++++- .github/actions/run-tests/action.yml | 9 ++++++++- .github/workflows/install-ci.yml | 14 ++++++-------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.github/actions/run-package-tests/action.yml b/.github/actions/run-package-tests/action.yml index 8b1a2713b981..6a59216901fe 100644 --- a/.github/actions/run-package-tests/action.yml +++ b/.github/actions/run-package-tests/action.yml @@ -153,7 +153,10 @@ runs: 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 - grep -qvE '\.py$' <<< "$changed_files" || mode=select + relevant_files="$(grep -E '^(source|docker|tools|apps|scripts)/|^\.github/(workflows/(build|config)\.yaml|actions/)|^\.gitmodules$|^[^/]+\.(toml|yaml|yml|json|ini|cfg|conf|lock|sh|bat|ps1)$' <<< "$changed_files" | grep -vE '\.(md|rst|skip)$' || true)" + if [ -z "$relevant_files" ] || ! grep -qvE '\.py$' <<< "$relevant_files"; then + mode=select + fi else echo "::warning::Could not list changed files; running the full test suite" fi diff --git a/.github/actions/run-tests/action.yml b/.github/actions/run-tests/action.yml index 3e95b8ac34b7..1d0dd2ee1316 100644 --- a/.github/actions/run-tests/action.yml +++ b/.github/actions/run-tests/action.yml @@ -226,7 +226,14 @@ runs: 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) testmon_options="--testmon --testmon-forceselect" ;; + 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 ;; diff --git a/.github/workflows/install-ci.yml b/.github/workflows/install-ci.yml index 1be728b7550a..97fd63d7b3d2 100644 --- a/.github/workflows/install-ci.yml +++ b/.github/workflows/install-ci.yml @@ -87,15 +87,12 @@ jobs: done } - any_match() { + matching_files() { local files="$1" entry regex for entry in "${patterns[@]}"; do IFS=$'\t' read -r regex _ <<< "$entry" - if grep -qE "$regex" <<< "$files"; then - return 0 - fi - done - return 1 + grep -E "$regex" <<< "$files" || true + done | grep -vE '\.(md|rst|skip)$' | sort -u || true } decide() { @@ -124,8 +121,9 @@ jobs: printf '%s\n' "$changed_files" - if any_match "$changed_files"; then - if ! grep -qvE '\.py$' <<< "$changed_files"; then + relevant_files="$(matching_files "$changed_files")" + if [ -n "$relevant_files" ]; then + if ! grep -qvE '\.py$' <<< "$relevant_files"; then decide true "relevant Python paths changed" "$changed_files" true else decide true "static paths changed; full suite required" "$changed_files" From 8c192f5ab1d9aeb5ad59e86864824f3889a86dd0 Mon Sep 17 00:00:00 2001 From: Matthew Taylor Date: Mon, 29 Jun 2026 16:13:10 -0400 Subject: [PATCH 3/4] fix --- tools/run_install_ci.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/run_install_ci.py b/tools/run_install_ci.py index 7a5c4dd4a0b0..60213d7a8423 100755 --- a/tools/run_install_ci.py +++ b/tools/run_install_ci.py @@ -320,6 +320,10 @@ def _cmd_docker(args: argparse.Namespace) -> int: if args.testmon_data_dir: testmon_data_dir = Path(args.testmon_data_dir).resolve() testmon_data_dir.mkdir(parents=True, exist_ok=True) + testmon_data_file = testmon_data_dir / ".testmondata" + testmon_data_dir.chmod(0o777) + if testmon_data_file.exists(): + testmon_data_file.chmod(0o666) docker_run_cmd.extend(["-v", f"{testmon_data_dir}:/tmp/testmon"]) docker_run_cmd.extend(["-e", "TESTMON_DATAFILE=/tmp/testmon/.testmondata"]) From 4bb5dd77691f0f12c43c0a35b9e325fef31057e5 Mon Sep 17 00:00:00 2001 From: Matthew Taylor Date: Mon, 29 Jun 2026 23:56:46 -0400 Subject: [PATCH 4/4] Test multiple Python module changes --- source/isaaclab/isaaclab/utils/array.py | 2 +- source/isaaclab/isaaclab/utils/string.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/isaaclab/isaaclab/utils/array.py b/source/isaaclab/isaaclab/utils/array.py index d15fbc275dc7..7d514e4ee49e 100644 --- a/source/isaaclab/isaaclab/utils/array.py +++ b/source/isaaclab/isaaclab/utils/array.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Sub-module containing utilities for working with different array backends.""" +"""Utilities for working consistently with different array backends.""" # needed to import for allowing type-hinting: torch.device | str | None from __future__ import annotations diff --git a/source/isaaclab/isaaclab/utils/string.py b/source/isaaclab/isaaclab/utils/string.py index fa83688154d6..dba6104c916f 100644 --- a/source/isaaclab/isaaclab/utils/string.py +++ b/source/isaaclab/isaaclab/utils/string.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Sub-module containing utilities for transforming strings and regular expressions.""" +"""Utilities for transforming strings and regular-expression patterns.""" import ast import functools