Skip to content

Commit 37f7344

Browse files
committed
Updated Converter CLI. Added unit test(testCli.py).
1 parent 189dde4 commit 37f7344

File tree

6 files changed

+146
-6
lines changed

6 files changed

+146
-6
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Repository = "https://github.com/newton-physics/urdf-usd-converter"
1818
Changelog = "https://github.com/newton-physics/urdf-usd-converter/blob/main/CHANGELOG.md"
1919

2020
[project.scripts]
21-
urdf_usd_converter = "urdf_usd_converter.__main__:cli_main"
21+
urdf_usd_converter = "urdf_usd_converter.__main__:run"
2222

2323
[dependency-groups]
2424
dev = [

tests/data/simple_box.urdf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml verison="1.0"?>
2+
<robot name="simple_box">
3+
<material name="blue">
4+
<color rgba="0.0 0.0 1.0 1.0"/>
5+
</material>
6+
7+
<link name="link1">
8+
<visual>
9+
<geometry>
10+
<box size="2.0 2.0 2.0"/>
11+
</geometry>
12+
<material name="blue"/>
13+
</visual>
14+
</link>
15+
</robot>

tests/testCli.py

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,116 @@
11
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
3+
import pathlib
4+
import shutil
5+
from unittest.mock import patch
6+
7+
import usdex.test
8+
from pxr import Tf
39

410
from tests.util.ConverterTestCase import ConverterTestCase
11+
from urdf_usd_converter._impl.cli import run
512

613

714
class TestCli(ConverterTestCase):
815

916
def test_run(self):
10-
pass # dummy test for now
17+
for robot in pathlib.Path("tests/data").glob("*.urdf"):
18+
robot_name = robot.stem
19+
with patch("sys.argv", ["urdf_usd_converter", str(robot), self.tmpDir()]):
20+
self.assertEqual(run(), 0, f"Failed to convert {robot}")
21+
self.assertTrue((pathlib.Path(self.tmpDir()) / f"{robot_name}.usda").exists())
22+
23+
# TODO: test_no_layer_structure, test_no_physics_scene, test_comment
24+
25+
def test_invalid_input(self):
26+
with (
27+
patch("sys.argv", ["urdf_usd_converter", "tests/data/invalid.xml", self.tmpDir()]),
28+
usdex.test.ScopedDiagnosticChecker(self, [(Tf.TF_DIAGNOSTIC_WARNING_TYPE, "Input file does not exist.*")]),
29+
):
30+
self.assertEqual(run(), 1, "Expected non-zero exit code for invalid input")
31+
32+
def test_invalid_output(self):
33+
# create a file that is not a directory
34+
pathlib.Path("tests/output").mkdir(parents=True, exist_ok=True)
35+
pathlib.Path("tests/output/invalid").touch()
36+
with (
37+
patch("sys.argv", ["urdf_usd_converter", "tests/data/simple_box.urdf", "tests/output/invalid"]),
38+
usdex.test.ScopedDiagnosticChecker(self, [(Tf.TF_DIAGNOSTIC_WARNING_TYPE, "Output path exists but is not a directory.*")]),
39+
):
40+
self.assertEqual(run(), 1, "Expected non-zero exit code for invalid output")
41+
42+
def test_input_path_is_directory(self):
43+
# Create a directory as input_file (should fail)
44+
input_dir = pathlib.Path("tests/data/input_dir")
45+
input_dir.mkdir(parents=True, exist_ok=True)
46+
try:
47+
with (
48+
patch("sys.argv", ["urdf_usd_converter", str(input_dir), self.tmpDir()]),
49+
usdex.test.ScopedDiagnosticChecker(self, [(Tf.TF_DIAGNOSTIC_WARNING_TYPE, "Input path is not a file.*")]),
50+
):
51+
self.assertEqual(run(), 1, "Expected non-zero exit code for input path as directory")
52+
finally:
53+
shutil.rmtree(input_dir)
54+
55+
def test_input_file_not_xml(self):
56+
# Create a non-xml file as input_file (should fail)
57+
not_xml = pathlib.Path("tests/data/not_xml.txt")
58+
not_xml.write_text("dummy content")
59+
try:
60+
with (
61+
patch("sys.argv", ["urdf_usd_converter", str(not_xml), self.tmpDir()]),
62+
usdex.test.ScopedDiagnosticChecker(self, [(Tf.TF_DIAGNOSTIC_WARNING_TYPE, "Only URDF.*are supported as input.*")]),
63+
):
64+
self.assertEqual(run(), 1, "Expected non-zero exit code for non-xml input file")
65+
finally:
66+
not_xml.unlink()
67+
68+
def test_output_dir_cannot_create(self):
69+
# Simulate output_dir.mkdir raising an exception (should fail)
70+
robot = "tests/data/simple_box.urdf"
71+
output_dir = pathlib.Path("tests/output/cannot_create")
72+
with (
73+
patch("pathlib.Path.mkdir", side_effect=OSError("Permission denied")),
74+
patch("sys.argv", ["urdf_usd_converter", robot, str(output_dir)]),
75+
usdex.test.ScopedDiagnosticChecker(self, [(Tf.TF_DIAGNOSTIC_WARNING_TYPE, "Failed to create output directory.*")]),
76+
):
77+
self.assertEqual(run(), 1, "Expected non-zero exit code when output dir cannot be created")
78+
79+
def test_conversion_returns_none(self):
80+
# Test the case where converter.convert() returns None/false value
81+
robot = "tests/data/simple_box.urdf"
82+
with (
83+
patch("urdf_usd_converter.Converter.convert", return_value=None),
84+
patch("sys.argv", ["urdf_usd_converter", robot, self.tmpDir()]),
85+
usdex.test.ScopedDiagnosticChecker(
86+
self,
87+
[(Tf.TF_DIAGNOSTIC_WARNING_TYPE, "Conversion failed for unknown reason.*")],
88+
level=usdex.core.DiagnosticsLevel.eWarning,
89+
),
90+
):
91+
self.assertEqual(run(), 1, "Expected non-zero exit code when conversion returns None")
92+
93+
def test_conversion_exception_non_verbose(self):
94+
# Test exception handling when verbose=False (should not re-raise)
95+
robot = "tests/data/simple_box.urdf"
96+
with (
97+
patch("urdf_usd_converter.Converter.convert", side_effect=RuntimeError("Test conversion error")),
98+
patch("sys.argv", ["urdf_usd_converter", robot, self.tmpDir()]),
99+
usdex.test.ScopedDiagnosticChecker(
100+
self,
101+
[(Tf.TF_DIAGNOSTIC_WARNING_TYPE, "Conversion failed: Test conversion error.*")],
102+
level=usdex.core.DiagnosticsLevel.eWarning,
103+
),
104+
):
105+
self.assertEqual(run(), 1, "Expected non-zero exit code when conversion raises exception")
106+
107+
def test_conversion_exception_verbose(self):
108+
# Test exception handling when verbose=True (should re-raise)
109+
robot = "tests/data/simple_box.urdf"
110+
with (
111+
patch("urdf_usd_converter.Converter.convert", side_effect=RuntimeError("Test conversion error")),
112+
patch("sys.argv", ["urdf_usd_converter", robot, self.tmpDir(), "--verbose"]),
113+
self.assertRaises(RuntimeError),
114+
usdex.test.ScopedDiagnosticChecker(self, [], level=usdex.core.DiagnosticsLevel.eWarning),
115+
):
116+
run()

urdf_usd_converter/_impl/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def __create_parser() -> argparse.ArgumentParser:
7878
"output_dir",
7979
type=Path,
8080
help="""
81-
Path to the output USD directory. The primary USD file will be <output_dir>/<modelname>.usda
81+
Path to the output USD directory. The primary USD file will be <output_dir>/<robotname>.usda
8282
and it will be an Atomic Component with Asset Interface layer and payloaded contents
8383
(unless --no-layer-structure is used)
8484
""",

urdf_usd_converter/_impl/convert.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import pathlib
44
from dataclasses import dataclass
55

6-
from pxr import Sdf, Tf
6+
import usdex.core
7+
from pxr import Sdf, Tf, UsdGeom
8+
9+
from .utils import get_authoring_metadata
710

811
__all__ = ["Converter"]
912

@@ -45,9 +48,16 @@ def convert(self, input_file: str, output_dir: str) -> Sdf.AssetPath:
4548
if not output_path.exists():
4649
output_path.mkdir(parents=True)
4750

48-
# TODO: Still a dummy.
4951
file_name = f"{input_file.stem}.usda"
5052
asset_identifier = str(output_dir / file_name)
51-
5253
Tf.Status(f"Converting {input_path} into {output_path}")
54+
asset_stage = usdex.core.createStage(
55+
asset_identifier,
56+
defaultPrimName="Robot", # TODO: use parsed name
57+
upAxis=UsdGeom.Tokens.z,
58+
linearUnits=UsdGeom.LinearUnits.meters,
59+
authoringMetadata=get_authoring_metadata(),
60+
)
61+
# TODO: implement core logic
62+
usdex.core.saveStage(asset_stage, comment=self.params.comment)
5363
return Sdf.AssetPath(asset_identifier)

urdf_usd_converter/_impl/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
from .._version import __version__
4+
5+
__all__ = ["get_authoring_metadata"]
6+
7+
8+
def get_authoring_metadata() -> str:
9+
return f"URDF USD Converter v{__version__}"

0 commit comments

Comments
 (0)