Skip to content

Commit 6aad90b

Browse files
authored
[Test] Optimize Newton test runtime (#7306)
# Description Reduces `isaaclab_newton` CI runtime while preserving distinct behavioral coverage. - Tests actuator target-mode configuration directly instead of launching a simulation for pure configuration behavior. - Preserves singleton and batched coverage at initialization boundaries, then uses representative batched layouts for downstream behavior. - Consolidates redundant rigid-object and rigid-object-collection parameter matrices. - Keeps the complete contact lifecycle shape matrix while narrowing the secondary horizontal-collision matrix to representative primitive and mesh cases. - Combines contact-sensor metadata checks into one scene. - Removes simulation-backed print/string smoke tests and replaces tautological self-comparisons with cross-view behavioral assertions. ## Before and after measurement Measured with the CI-style per-file test runner on the same workstation, using a new empty Warp cache for each revision. Both revisions use the repository-default Warp backward configuration: | Revision | Cases | Result | Wall time | |---|---:|---|---:| | `upstream/develop` (`c4a275975`) | 1,180 | 0 failures, 0 errors | 30m 44.38s | | This PR (`552b329a8`) | 907 | 0 failures, 0 errors | 21m 41.56s | This saves **9m 02.82s (29.4%)** in a cold-cache run. Both measurements executed all 35 Newton test files. Measurement command: ```bash TEST_FILTER_PATTERN=isaaclab_newton \ WARP_CACHE_PATH=<new-empty-cache> \ uv run --no-sync python -m pytest tools -q ``` ## Type of change - Test and CI performance improvement (non-breaking) ## Validation - Full optimized Newton suite: 35 files, 907 cases, 0 failures, 0 errors. - Full untouched baseline suite: 35 files, 1,180 cases, 0 failures, 0 errors. - Focused changed suites all pass: articulation, rigid object, rigid object collection, contact sensor, frame transformer, IMU, joint wrench, and PVA. - Ruff and Ruff format hooks pass for all changed Python files. - `git diff --check` passes. - `uv run --no-sync isaaclab -f` passes all applicable hooks; the repository-wide changelog checker reports pre-existing fragment immutability/missing-fragment issues in unrelated packages. This PR includes an `isaaclab_newton` `.skip` fragment. ## Checklist - [x] I have read and followed the contribution guidelines. - [x] I have run the relevant tests and included the results above. - [x] I have formatted and linted the changed files. - [x] I have added the appropriate changelog fragment. ## Screenshots Not applicable.
1 parent 3fcc9c6 commit 6aad90b

9 files changed

Lines changed: 125 additions & 281 deletions

source/isaaclab_newton/changelog.d/test-optimize-newton-tests.skip

Whitespace-only changes.

source/isaaclab_newton/test/assets/test_articulation.py

Lines changed: 61 additions & 97 deletions
Large diffs are not rendered by default.

source/isaaclab_newton/test/assets/test_rigid_object.py

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ def test_external_force_buffer(device):
298298

299299

300300
@pytest.mark.isaacsim_ci
301-
@pytest.mark.parametrize("num_cubes", [2, 4])
301+
@pytest.mark.parametrize("num_cubes", [4])
302302
@pytest.mark.parametrize("device", test_devices())
303303
def test_external_force_on_single_body(num_cubes, device):
304304
"""Test application of external force on the base of the object.
@@ -373,7 +373,7 @@ def test_external_force_on_single_body(num_cubes, device):
373373
assert torch.all(cube_object.data.root_pos_w.torch[1::2, 2] < 1.0)
374374

375375

376-
@pytest.mark.parametrize("num_cubes", [2, 4])
376+
@pytest.mark.parametrize("num_cubes", [4])
377377
@pytest.mark.parametrize("device", test_devices())
378378
def test_external_force_on_single_body_at_position(num_cubes, device):
379379
"""Test application of external force on the base of the object at a specific position.
@@ -462,7 +462,7 @@ def test_external_force_on_single_body_at_position(num_cubes, device):
462462

463463

464464
@pytest.mark.isaacsim_ci
465-
@pytest.mark.parametrize("num_cubes", [1, 2])
465+
@pytest.mark.parametrize("num_cubes", [2])
466466
@pytest.mark.parametrize("device", test_devices())
467467
def test_set_rigid_object_state(num_cubes, device):
468468
"""Test setting the state of the rigid object.
@@ -530,7 +530,7 @@ def test_set_rigid_object_state(num_cubes, device):
530530

531531

532532
@pytest.mark.isaacsim_ci
533-
@pytest.mark.parametrize("num_cubes", [1, 2])
533+
@pytest.mark.parametrize("num_cubes", [2])
534534
@pytest.mark.parametrize("device", test_devices())
535535
def test_reset_rigid_object(num_cubes, device):
536536
"""Test resetting the state of the rigid object."""
@@ -573,7 +573,7 @@ def test_reset_rigid_object(num_cubes, device):
573573

574574

575575
@pytest.mark.isaacsim_ci
576-
@pytest.mark.parametrize("num_cubes", [1, 2])
576+
@pytest.mark.parametrize("num_cubes", [2])
577577
@pytest.mark.parametrize("device", test_devices())
578578
def test_rigid_body_set_material_properties(num_cubes, device):
579579
"""Test getting and setting material properties of rigid object via view-level APIs."""
@@ -919,7 +919,7 @@ def test_rigid_body_set_mass(num_cubes, device):
919919

920920

921921
@pytest.mark.isaacsim_ci
922-
@pytest.mark.parametrize("num_cubes", [1, 2])
922+
@pytest.mark.parametrize("num_cubes", [2])
923923
@pytest.mark.parametrize("device", test_devices())
924924
@pytest.mark.parametrize("gravity_enabled", [True, False])
925925
def test_gravity_vec_w(num_cubes, device, gravity_enabled):
@@ -957,7 +957,7 @@ def test_gravity_vec_w(num_cubes, device, gravity_enabled):
957957

958958

959959
@pytest.mark.isaacsim_ci
960-
@pytest.mark.parametrize("num_cubes", [2, 3])
960+
@pytest.mark.parametrize("num_cubes", [3])
961961
@pytest.mark.parametrize("device", ["cuda:0", "cpu"])
962962
def test_gravity_vec_w_tracks_model_gravity(num_cubes, device):
963963
"""Per-env mutations to Newton's ``model.gravity`` reach ``GRAVITY_VEC_W`` and ``projected_gravity_b``.
@@ -999,7 +999,7 @@ def test_gravity_vec_w_tracks_model_gravity(num_cubes, device):
999999

10001000

10011001
@pytest.mark.isaacsim_ci
1002-
@pytest.mark.parametrize("num_cubes", [1, 2])
1002+
@pytest.mark.parametrize("num_cubes", [2])
10031003
@pytest.mark.parametrize("device", test_devices())
10041004
@pytest.mark.parametrize("with_offset", [True, False])
10051005
@flaky(max_runs=3, min_passes=1)
@@ -1058,11 +1058,11 @@ def test_body_root_state_properties(num_cubes, device, with_offset):
10581058
if not with_offset:
10591059
torch.testing.assert_close(root_link_pose_w, root_com_pose_w)
10601060
torch.testing.assert_close(root_com_vel_w, root_link_vel_w)
1061-
torch.testing.assert_close(root_link_pose_w, root_link_pose_w)
1061+
torch.testing.assert_close(root_link_pose_w, body_link_pose_w.squeeze(-2))
10621062
torch.testing.assert_close(root_com_vel_w, root_link_vel_w)
10631063
torch.testing.assert_close(body_link_pose_w, body_com_pose_w)
10641064
torch.testing.assert_close(body_com_vel_w, body_link_vel_w)
1065-
torch.testing.assert_close(body_link_pose_w, body_link_pose_w)
1065+
torch.testing.assert_close(root_com_pose_w, body_com_pose_w.squeeze(-2))
10661066
torch.testing.assert_close(body_com_vel_w, body_link_vel_w)
10671067
else:
10681068
# cubes are spinning around center of mass
@@ -1089,9 +1089,8 @@ def test_body_root_state_properties(num_cubes, device, with_offset):
10891089
torch.testing.assert_close(com_quat_w, body_com_pose_w[..., 3:], **_tol)
10901090
torch.testing.assert_close(com_quat_w.squeeze(-2), root_com_pose_w[..., 3:], **_tol)
10911091

1092-
# orientation of link will match root state will always match
1093-
torch.testing.assert_close(root_link_pose_w[..., 3:], root_link_pose_w[..., 3:], **_tol)
1094-
torch.testing.assert_close(body_link_pose_w[..., 3:], body_link_pose_w[..., 3:], **_tol)
1092+
# root and body link orientations describe the same rigid body
1093+
torch.testing.assert_close(root_link_pose_w[..., 3:], body_link_pose_w[..., 3:].squeeze(-2), **_tol)
10951094

10961095
# lin_vel will not match
10971096
# center of mass vel will be constant (i.e. spinning around com)
@@ -1105,14 +1104,13 @@ def test_body_root_state_properties(num_cubes, device, with_offset):
11051104
torch.testing.assert_close(lin_vel_rel_gt, lin_vel_rel_body_gt.squeeze(-2), **_tol)
11061105

11071106
# ang_vel will always match
1108-
torch.testing.assert_close(root_com_vel_w[..., 3:], root_com_vel_w[..., 3:])
11091107
torch.testing.assert_close(root_com_vel_w[..., 3:], root_link_vel_w[..., 3:])
1110-
torch.testing.assert_close(body_com_vel_w[..., 3:], body_com_vel_w[..., 3:])
1108+
torch.testing.assert_close(root_com_vel_w[..., 3:], body_com_vel_w[..., 3:].squeeze(-2))
11111109
torch.testing.assert_close(body_com_vel_w[..., 3:], body_link_vel_w[..., 3:])
11121110

11131111

11141112
@pytest.mark.isaacsim_ci
1115-
@pytest.mark.parametrize("num_cubes", [1, 2])
1113+
@pytest.mark.parametrize("num_cubes", [2])
11161114
@pytest.mark.parametrize("device", test_devices())
11171115
@pytest.mark.parametrize("with_offset", [True, False])
11181116
@pytest.mark.parametrize("state_location", ["com", "link"])
@@ -1199,7 +1197,7 @@ def test_write_root_state(num_cubes, device, with_offset, state_location):
11991197

12001198

12011199
@pytest.mark.isaacsim_ci
1202-
@pytest.mark.parametrize("num_cubes", [1, 2])
1200+
@pytest.mark.parametrize("num_cubes", [2])
12031201
@pytest.mark.parametrize("device", test_devices())
12041202
@pytest.mark.parametrize("with_offset", [True])
12051203
@pytest.mark.parametrize("state_location", ["com", "link", "root"])

source/isaaclab_newton/test/assets/test_rigid_object_collection.py

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,7 @@ def test_initialization_ignores_unrelated_sibling_rigid_objects(device):
147147
assert object_collection.data.default_body_pose.torch.shape == (num_envs, num_cubes, 7)
148148

149149

150-
@pytest.mark.parametrize("num_envs", [1, 2])
151-
@pytest.mark.parametrize("num_cubes", [1, 3])
150+
@pytest.mark.parametrize(("num_envs", "num_cubes"), [(1, 1), (2, 3)])
152151
@pytest.mark.parametrize("device", test_devices())
153152
def test_initialization(num_envs, num_cubes, device):
154153
"""Test initialization for prim with rigid body API at the provided prim path."""
@@ -282,7 +281,7 @@ def test_initialization_with_kinematic_enabled(num_envs, num_cubes, device):
282281
torch.testing.assert_close(object_collection.data.body_link_vel_w.torch, default_body_vel)
283282

284283

285-
@pytest.mark.parametrize("num_cubes", [1, 2])
284+
@pytest.mark.parametrize("num_cubes", [2])
286285
@pytest.mark.parametrize("device", test_devices())
287286
def test_initialization_with_no_rigid_body(num_cubes, device):
288287
"""Test that initialization fails when no rigid body is found at the provided prim path."""
@@ -352,8 +351,8 @@ def test_external_force_buffer(device):
352351
object_collection.update(sim.cfg.dt)
353352

354353

355-
@pytest.mark.parametrize("num_envs", [1, 2])
356-
@pytest.mark.parametrize("num_cubes", [1, 4])
354+
@pytest.mark.parametrize("num_envs", [2])
355+
@pytest.mark.parametrize("num_cubes", [4])
357356
@pytest.mark.parametrize("device", test_devices())
358357
def test_external_force_on_single_body(num_envs, num_cubes, device):
359358
"""Test application of external force on the base of the object."""
@@ -414,8 +413,8 @@ def test_external_force_on_single_body(num_envs, num_cubes, device):
414413
assert torch.all(object_collection.data.body_link_pos_w.torch[:, 1::2, 2] < 1.0)
415414

416415

417-
@pytest.mark.parametrize("num_envs", [1, 2])
418-
@pytest.mark.parametrize("num_cubes", [1, 4])
416+
@pytest.mark.parametrize("num_envs", [2])
417+
@pytest.mark.parametrize("num_cubes", [4])
419418
@pytest.mark.parametrize("device", test_devices())
420419
def test_external_force_on_single_body_at_position(num_envs, num_cubes, device):
421420
"""Test application of external force on the base of the object at a specific position.
@@ -497,8 +496,8 @@ def test_external_force_on_single_body_at_position(num_envs, num_cubes, device):
497496
assert torch.all(object_collection.data.body_link_pos_w.torch[:, 1::2, 2] < 1.0)
498497

499498

500-
@pytest.mark.parametrize("num_envs", [1, 3])
501-
@pytest.mark.parametrize("num_cubes", [1, 2])
499+
@pytest.mark.parametrize("num_envs", [3])
500+
@pytest.mark.parametrize("num_cubes", [2])
502501
@pytest.mark.parametrize("device", test_devices())
503502
def test_set_object_state(num_envs, num_cubes, device):
504503
"""Test setting the state of the object.
@@ -569,8 +568,8 @@ def test_set_object_state(num_envs, num_cubes, device):
569568
object_collection.update(sim.cfg.dt)
570569

571570

572-
@pytest.mark.parametrize("num_envs", [1, 3])
573-
@pytest.mark.parametrize("num_cubes", [1, 2])
571+
@pytest.mark.parametrize("num_envs", [3])
572+
@pytest.mark.parametrize("num_cubes", [2])
574573
@pytest.mark.parametrize("device", test_devices())
575574
def test_reset_object_collection(num_envs, num_cubes, device):
576575
"""Test resetting the state of the rigid object."""
@@ -604,8 +603,8 @@ def test_reset_object_collection(num_envs, num_cubes, device):
604603
assert torch.count_nonzero(object_collection._permanent_wrench_composer.out_torque_b.torch) == 0
605604

606605

607-
@pytest.mark.parametrize("num_envs", [1, 3])
608-
@pytest.mark.parametrize("num_cubes", [1, 2])
606+
@pytest.mark.parametrize("num_envs", [3])
607+
@pytest.mark.parametrize("num_cubes", [2])
609608
@pytest.mark.parametrize("device", test_devices())
610609
def test_set_material_properties(num_envs, num_cubes, device):
611610
"""Test getting and setting material properties of rigid object collection via view-level APIs."""
@@ -643,8 +642,8 @@ def test_set_material_properties(num_envs, num_cubes, device):
643642
torch.testing.assert_close(restitution_check, restitution)
644643

645644

646-
@pytest.mark.parametrize("num_envs", [1, 3])
647-
@pytest.mark.parametrize("num_cubes", [1, 2])
645+
@pytest.mark.parametrize("num_envs", [3])
646+
@pytest.mark.parametrize("num_cubes", [2])
648647
@pytest.mark.parametrize("device", test_devices())
649648
@pytest.mark.parametrize("gravity_enabled", [True, False])
650649
def test_gravity_vec_w(num_envs, num_cubes, device, gravity_enabled):
@@ -680,8 +679,8 @@ def test_gravity_vec_w(num_envs, num_cubes, device, gravity_enabled):
680679

681680

682681
@pytest.mark.isaacsim_ci
683-
@pytest.mark.parametrize("num_envs", [2, 3])
684-
@pytest.mark.parametrize("num_cubes", [1, 2])
682+
@pytest.mark.parametrize("num_envs", [3])
683+
@pytest.mark.parametrize("num_cubes", [2])
685684
@pytest.mark.parametrize("device", ["cuda:0", "cpu"])
686685
def test_gravity_vec_w_tracks_model_gravity(num_envs, num_cubes, device):
687686
"""Per-env mutations to Newton's ``model.gravity`` reach ``GRAVITY_VEC_W`` and ``projected_gravity_b``.
@@ -720,8 +719,8 @@ def test_gravity_vec_w_tracks_model_gravity(num_envs, num_cubes, device):
720719
torch.testing.assert_close(object_collection.data.projected_gravity_b.torch, expected, atol=1e-5, rtol=1e-5)
721720

722721

723-
@pytest.mark.parametrize("num_envs", [1, 4])
724-
@pytest.mark.parametrize("num_cubes", [1, 2])
722+
@pytest.mark.parametrize("num_envs", [4])
723+
@pytest.mark.parametrize("num_cubes", [2])
725724
@pytest.mark.parametrize("device", test_devices())
726725
@pytest.mark.parametrize("with_offset", [True, False])
727726
def test_object_state_properties(num_envs, num_cubes, device, with_offset):
@@ -794,9 +793,6 @@ def test_object_state_properties(num_envs, num_cubes, device, with_offset):
794793
com_quat_w = quat_mul(object_link_pose_w[..., 3:], com_quat_b)
795794
torch.testing.assert_close(com_quat_w, object_com_pose_w[..., 3:], **_tol)
796795

797-
# orientation of link will match object state will always match
798-
torch.testing.assert_close(object_link_pose_w[..., 3:], object_link_pose_w[..., 3:])
799-
800796
# lin_vel will not match
801797
# center of mass vel will be constant (i.e. spinning around com)
802798
torch.testing.assert_close(
@@ -811,12 +807,11 @@ def test_object_state_properties(num_envs, num_cubes, device, with_offset):
811807
torch.testing.assert_close(lin_vel_rel_gt, lin_vel_rel_object_gt, **_tol)
812808

813809
# ang_vel will always match
814-
torch.testing.assert_close(object_com_vel_w[..., 3:], object_com_vel_w[..., 3:])
815810
torch.testing.assert_close(object_com_vel_w[..., 3:], object_link_vel_w[..., 3:])
816811

817812

818-
@pytest.mark.parametrize("num_envs", [1, 3])
819-
@pytest.mark.parametrize("num_cubes", [1, 2])
813+
@pytest.mark.parametrize("num_envs", [3])
814+
@pytest.mark.parametrize("num_cubes", [2])
820815
@pytest.mark.parametrize("device", test_devices())
821816
@pytest.mark.parametrize("with_offset", [True, False])
822817
@pytest.mark.parametrize("state_location", ["com", "link"])
@@ -893,8 +888,8 @@ def test_write_object_state(num_envs, num_cubes, device, with_offset, state_loca
893888
torch.testing.assert_close(rand_state[..., 7:], cube_object.data.body_link_vel_w.torch)
894889

895890

896-
@pytest.mark.parametrize("num_envs", [1, 3])
897-
@pytest.mark.parametrize("num_cubes", [1, 2])
891+
@pytest.mark.parametrize("num_envs", [3])
892+
@pytest.mark.parametrize("num_cubes", [2])
898893
@pytest.mark.parametrize("device", test_devices())
899894
@pytest.mark.parametrize("with_offset", [True])
900895
@pytest.mark.parametrize("state_location", ["com", "link", "root"])

0 commit comments

Comments
 (0)