Skip to content

Commit c9c9744

Browse files
committed
validate xml schema test added
1 parent 9281169 commit c9c9744

File tree

6 files changed

+354
-3
lines changed

6 files changed

+354
-3
lines changed

ros2_control_test_assets/CMakeLists.txt

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ install(
1515
DESTINATION include/ros2_control_test_assets
1616
)
1717
install(
18-
FILES urdf/test_hardware_components.urdf
19-
DESTINATION share/ros2_control_test_assets/urdf
18+
DIRECTORY schema urdf
19+
DESTINATION share/ros2_control_test_assets
2020
)
2121
install(TARGETS ros2_control_test_assets
2222
EXPORT export_ros2_control_test_assets
@@ -26,4 +26,32 @@ install(TARGETS ros2_control_test_assets
2626
)
2727

2828
ament_export_targets(export_ros2_control_test_assets HAS_LIBRARY_TARGET)
29+
find_package(ament_cmake_gtest REQUIRED)
30+
find_package(ament_cmake_gmock REQUIRED)
31+
find_package(ament_cmake REQUIRED)
32+
find_package(ament_lint_auto REQUIRED)
33+
find_package(ament_lint_common REQUIRED)
34+
find_package(ament_index_cpp REQUIRED)
35+
find_package(rclcpp REQUIRED)
36+
find_package(ament_cmake_gtest REQUIRED)
37+
find_package(ament_cmake_gmock REQUIRED)
38+
find_package(ament_index_cpp REQUIRED)
39+
40+
# Use pkg-config to find libxml2
41+
find_package(PkgConfig REQUIRED)
42+
pkg_check_modules(LIBXML2 REQUIRED IMPORTED_TARGET libxml-2.0)
43+
44+
45+
# Unit test for XML schema validation
46+
47+
ament_add_gmock(
48+
test_validate_xml_schema
49+
test/validate_xml_schema.cpp
50+
)
51+
target_link_libraries(test_validate_xml_schema
52+
PkgConfig::LIBXML2
53+
ament_index_cpp::ament_index_cpp
54+
)
55+
56+
2957
ament_package()

ros2_control_test_assets/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<license>Apache License 2.0</license>
1212

1313
<buildtool_depend>ament_cmake</buildtool_depend>
14+
<test_depend>ament_index_cpp</test_depend>
1415
<export>
1516
<build_type>ament_cmake</build_type>
1617
</export>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
3+
elementFormDefault="qualified">
4+
5+
<!-- Reusable types -->
6+
<xs:complexType name="paramType">
7+
<xs:simpleContent>
8+
<xs:extension base="xs:string">
9+
<xs:attribute name="name" type="xs:string" use="required"/>
10+
</xs:extension>
11+
</xs:simpleContent>
12+
</xs:complexType>
13+
14+
<xs:complexType name="interfaceType">
15+
<xs:attribute name="name" type="xs:string" use="required"/>
16+
</xs:complexType>
17+
18+
<xs:complexType name="commandInterfaceType">
19+
<xs:sequence>
20+
<xs:element name="param" type="paramType" minOccurs="0" maxOccurs="unbounded"/>
21+
</xs:sequence>
22+
<xs:attribute name="name" type="xs:string" use="required"/>
23+
</xs:complexType>
24+
25+
<xs:complexType name="jointOrGpioType">
26+
<xs:sequence>
27+
<xs:element name="command_interface" type="commandInterfaceType" minOccurs="0" maxOccurs="unbounded"/>
28+
<xs:element name="state_interface" type="interfaceType" minOccurs="0" maxOccurs="unbounded"/>
29+
</xs:sequence>
30+
<xs:attribute name="name" type="xs:string" use="required"/>
31+
</xs:complexType>
32+
33+
34+
35+
<!-- Root robot element -->
36+
<xs:element name="robot">
37+
<xs:complexType>
38+
<xs:choice minOccurs="0" maxOccurs="unbounded">
39+
<xs:element ref="link"/>
40+
<xs:element ref="joint"/>
41+
<xs:element ref="ros2_control"/>
42+
<xs:element ref="transmission" minOccurs="0" maxOccurs="unbounded"/>
43+
<xs:element ref="material" minOccurs="0" maxOccurs="unbounded"/>
44+
<!-- Add more URDF elements here as needed -->
45+
</xs:choice>
46+
<xs:attribute name="name" type="xs:string" use="required"/>
47+
</xs:complexType>
48+
</xs:element>
49+
50+
51+
<!-- Minimal link and joint element definitions for validation -->
52+
<xs:element name="link">
53+
<xs:complexType mixed="true">
54+
<xs:sequence minOccurs="0" maxOccurs="unbounded">
55+
<xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
56+
</xs:sequence>
57+
<xs:attribute name="name" type="xs:string" use="required"/>
58+
</xs:complexType>
59+
</xs:element>
60+
61+
<xs:element name="joint">
62+
<xs:complexType mixed="true">
63+
<xs:sequence minOccurs="0" maxOccurs="unbounded">
64+
<xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
65+
</xs:sequence>
66+
<xs:attribute name="name" type="xs:string" use="required"/>
67+
<xs:attribute name="type" type="xs:string" use="required"/>
68+
</xs:complexType>
69+
</xs:element>
70+
71+
<xs:element name="transmission" type="xs:anyType"/>
72+
<xs:element name="material" type="xs:anyType"/>
73+
74+
75+
<!-- ros2_control block -->
76+
<xs:element name="ros2_control">
77+
<xs:complexType>
78+
<xs:sequence>
79+
<xs:element name="hardware">
80+
<xs:complexType>
81+
<xs:sequence>
82+
<xs:element name="plugin" type="xs:string"/>
83+
<xs:element name="param" type="paramType" minOccurs="0" maxOccurs="unbounded"/>
84+
</xs:sequence>
85+
</xs:complexType>
86+
</xs:element>
87+
<xs:choice minOccurs="0" maxOccurs="unbounded">
88+
<xs:element name="joint" type="jointOrGpioType" minOccurs="0" maxOccurs="unbounded"/>
89+
<xs:element name="gpio" type="jointOrGpioType" minOccurs="0" maxOccurs="unbounded"/>
90+
<xs:element name="sensor" minOccurs="0" maxOccurs="unbounded">
91+
<xs:complexType>
92+
<xs:sequence>
93+
<xs:element name="state_interface" type="interfaceType" minOccurs="0" maxOccurs="unbounded"/>
94+
</xs:sequence>
95+
<xs:attribute name="name" type="xs:string" use="required"/>
96+
</xs:complexType>
97+
</xs:element>
98+
</xs:choice>
99+
</xs:sequence>
100+
<xs:attribute name="name" type="xs:string" use="required"/>
101+
<xs:attribute name="type" type="xs:string" use="required"/>
102+
</xs:complexType>
103+
</xs:element>
104+
105+
</xs:schema>
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2025 ros2_control development team
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <gmock/gmock.h>
16+
#include <gtest/gtest.h>
17+
#include <libxml/parser.h>
18+
#include <libxml/xmlschemas.h>
19+
20+
#include <string>
21+
22+
#include <ament_index_cpp/get_package_share_directory.hpp>
23+
24+
class XmlParser
25+
{
26+
public:
27+
XmlParser(const std::string & xmlFile, const std::string & xsdFile)
28+
: xmlFile(xmlFile), xsdFile(xsdFile), doc(nullptr), schema(nullptr), schemaCtx(nullptr)
29+
{
30+
}
31+
32+
~XmlParser()
33+
{
34+
if (doc)
35+
{
36+
xmlFreeDoc(doc);
37+
}
38+
if (schemaCtx)
39+
{
40+
xmlSchemaFreeParserCtxt(schemaCtx);
41+
}
42+
if (schema)
43+
{
44+
xmlSchemaFree(schema);
45+
}
46+
xmlCleanupParser();
47+
}
48+
49+
bool parseAndValidate()
50+
{
51+
doc = xmlReadFile(xmlFile.c_str(), nullptr, 0);
52+
if (!doc)
53+
{
54+
return false;
55+
}
56+
57+
schemaCtx = xmlSchemaNewParserCtxt(xsdFile.c_str());
58+
if (!schemaCtx)
59+
{
60+
return false;
61+
}
62+
schema = xmlSchemaParse(schemaCtx);
63+
if (!schema)
64+
{
65+
return false;
66+
}
67+
68+
xmlSchemaValidCtxtPtr validCtx = xmlSchemaNewValidCtxt(schema);
69+
if (!validCtx)
70+
{
71+
return false;
72+
}
73+
int ret = xmlSchemaValidateDoc(validCtx, doc);
74+
xmlSchemaFreeValidCtxt(validCtx);
75+
76+
return ret == 0;
77+
}
78+
79+
private:
80+
std::string xmlFile;
81+
std::string xsdFile;
82+
xmlDocPtr doc;
83+
xmlSchemaPtr schema;
84+
xmlSchemaParserCtxtPtr schemaCtx;
85+
};
86+
87+
// Test fixture for XML schema validation
88+
class XmlSchemaValidationTest : public ::testing::Test
89+
{
90+
protected:
91+
std::string valid_xml;
92+
std::string invalid_xml;
93+
std::string xsd;
94+
95+
void SetUp() override
96+
{
97+
// Use ament_index_cpp to get the package share directory
98+
std::string package_share_dir =
99+
ament_index_cpp::get_package_share_directory("ros2_control_test_assets");
100+
valid_xml = package_share_dir + "/urdf/test_hardware_components.urdf";
101+
invalid_xml = package_share_dir + "/urdf/test_hardware_components_with_error.urdf";
102+
xsd = package_share_dir + "/schema/ros2_control.xsd";
103+
}
104+
};
105+
106+
TEST_F(XmlSchemaValidationTest, ValidXmlPasses)
107+
{
108+
XmlParser parser(valid_xml, xsd);
109+
EXPECT_TRUE(parser.parseAndValidate());
110+
}
111+
112+
TEST_F(XmlSchemaValidationTest, InvalidXmlFails)
113+
{
114+
XmlParser parser(invalid_xml, xsd);
115+
EXPECT_FALSE(parser.parseAndValidate());
116+
}

ros2_control_test_assets/urdf/test_hardware_components.urdf

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
<ros2_control name="TestSensorComponent" type="sensor">
5353
<hardware>
5454
<plugin>test_hardware_components/TestForceTorqueSensor</plugin>
55+
<param name="test_param">0</param>
5556
</hardware>
5657
<sensor name="ft_sensor">
5758
<state_interface name="fx"/>
@@ -68,12 +69,22 @@
6869
<plugin>test_hardware_components/TestTwoJointSystem</plugin>
6970
</hardware>
7071
<joint name="joint1">
71-
<command_interface name="position"/>
72+
<command_interface name="position">
73+
<param name="min">-1</param>
74+
<param name="max">1</param>
75+
</command_interface>
7276
<state_interface name="position"/>
7377
</joint>
7478
<joint name="joint2">
7579
<command_interface name="position"/>
7680
<state_interface name="position"/>
7781
</joint>
82+
<gpio name="flange_analog_IOs">
83+
<command_interface name="analog_output1">
84+
<param name="min">0</param>
85+
<param name="max">1</param>
86+
</command_interface>
87+
<state_interface name="analog_input1"/>
88+
</gpio>
7889
</ros2_control>
7990
</robot>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<robot name="MinimalRobot">
3+
<link name="world" />
4+
<joint name="base_joint" type="fixed">
5+
<origin rpy="0 0 0" xyz="0 0 0"/>
6+
<parent link="world"/>
7+
<child link="base_link"/>
8+
</joint>
9+
<link name="base_link">
10+
<collision>
11+
<origin rpy="0 0 0" xyz="0 0 0"/>
12+
<geometry>
13+
<cylinder length="1" radius="0.1"/>
14+
</geometry>
15+
</collision>
16+
</link>
17+
<joint name="joint1" type="revolute">
18+
<origin rpy="-1.57079632679 0 0" xyz="0 0 0.2"/>
19+
<parent link="base_link"/>
20+
<child link="link1"/>
21+
<limit effort="0.1" lower="-3.14159265359" upper="3.14159265359" velocity="0.2"/>
22+
</joint>
23+
<link name="link1">
24+
<collision>
25+
<origin rpy="0 0 0" xyz="0 0 0"/>
26+
<geometry>
27+
<cylinder length="1" radius="0.1"/>
28+
</geometry>
29+
</collision>
30+
</link>
31+
<joint name="joint2" type="revolute">
32+
<origin rpy="1.57079632679 0 0" xyz="0 0 0.9"/>
33+
<parent link="link1"/>
34+
<child link="link2"/>
35+
<limit effort="0.1" lower="-3.14159265359" upper="3.14159265359" velocity="0.2"/>
36+
</joint>
37+
<link name="link2">
38+
<collision>
39+
<origin rpy="0 0 0" xyz="0 0 0"/>
40+
<geometry>
41+
<cylinder length="1" radius="0.1"/>
42+
</geometry>
43+
</collision>
44+
</link>
45+
<joint name="tool_joint" type="fixed">
46+
<origin rpy="0 0 0" xyz="0 0 1"/>
47+
<parent link="link2"/>
48+
<child link="tool_link"/>
49+
</joint>
50+
<link name="tool_link" />
51+
52+
<ros2_control name="TestSensorComponent" type="sensor">
53+
<hardware>
54+
<plugin>test_hardware_components/TestForceTorqueSensor</plugin>
55+
<param name="test_param">0</param>
56+
</hardware>
57+
<sensor name="ft_sensor">
58+
<state_interface name="fx"/>
59+
<state_interface name="fy"/>
60+
<state_interface name="fz"/>
61+
<state_interface name="tx"/>
62+
<state_interface name="ty"/>
63+
<state_interface name="tz"/>
64+
</sensor>
65+
</ros2_control>
66+
67+
<ros2_control name="TestSystemComponent" type="system">
68+
<hardware>
69+
<plugin>test_hardware_components/TestTwoJointSystem</plugin>
70+
</hardware>
71+
<joint name="joint1">
72+
<command_interface2 name="position">
73+
<param name="min">-1</param>
74+
<param name="max">1</param>
75+
</command_interface2>
76+
<state_interface name="position"/>
77+
</joint>
78+
<joint name="joint2">
79+
<command_interface name="position"/>
80+
<state_interface name="position"/>
81+
</joint>
82+
<gpio name="flange_analog_IOs">
83+
<command_interface name="analog_output1">
84+
<param name="min">0</param>
85+
<param name="max">1</param>
86+
</command_interface>
87+
<state_interface name="analog_input1"/>
88+
</gpio>
89+
</ros2_control>
90+
</robot>

0 commit comments

Comments
 (0)