Skip to content

Commit 87b9383

Browse files
authored
Add ROS 2 integration tests, make existing tests more robust (#1024)
1 parent a0c4e0b commit 87b9383

File tree

13 files changed

+397
-304
lines changed

13 files changed

+397
-304
lines changed

.github/workflows/main.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ jobs:
1717
strategy:
1818
fail-fast: false
1919
matrix:
20-
ros_distro: [noetic]
20+
ros_distro:
21+
- noetic
22+
- humble
23+
- jazzy
24+
- kilted
2125
node_version: [20, 22, 24]
2226
env:
2327
ROS_DISTRO: ${{ matrix.ros_distro }}

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ COPY test/examples/ /workspace/test/examples/
1717
EXPOSE 9090
1818

1919
# Default command runs the ROS backend for testing
20-
CMD ["bash", "-c", "source /opt/ros/$ROS_DISTRO/setup.bash && roslaunch /workspace/test/examples/setup_examples.launch"]
20+
CMD ["bash", "-c", "source /opt/ros/$ROS_DISTRO/setup.bash && bash /workspace/test/examples/setup_examples.bash"]

package.xml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" ?>
2-
<package>
2+
<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/ros-infrastructure/rep/master/xsd/package_format3.xsd" format="3">
33
<name>roslibjs</name>
44
<version>2.0.0</version>
55
<description>The Robot Web Tools ROS JavaScript Library</description>
@@ -18,7 +18,11 @@
1818
<test_depend>libnss3-dev</test_depend>
1919
<test_depend>rosbridge_server</test_depend>
2020
<test_depend>tf2_web_republisher</test_depend>
21-
<test_depend>common_tutorials</test_depend>
22-
<test_depend>ros_tutorials</test_depend>
21+
<test_depend condition="$ROS_VERSION == 1">common_tutorials</test_depend>
22+
<test_depend condition="$ROS_VERSION == 1">ros_tutorials</test_depend>
23+
<test_depend condition="$ROS_VERSION == 2">action_tutorials_cpp</test_depend>
24+
<test_depend condition="$ROS_VERSION == 2">examples_rclpy_minimal_service</test_depend>
25+
<test_depend condition="$ROS_VERSION == 2">tf2_ros</test_depend>
26+
2327

2428
</package>

src/core/Param.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export default class Param<T = unknown> {
6464
* @param [failedCallback] - The callback function when the service call failed or the parameter setting was unsuccessful.
6565
*/
6666
set(
67-
value: object,
67+
value: T,
6868
callback?: (message: rosapi.SetParamResponse) => void,
6969
failedCallback: (error: string) => void = console.error,
7070
) {
Lines changed: 40 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,57 @@
1-
import { describe, it, expect } from "vitest";
1+
import { describe, it, expect, vi } from "vitest";
22
import * as ROSLIB from "../../src/RosLib.js";
33

4-
const expectedTopics = [
5-
/*
6-
* '/turtle1/cmd_vel', '/turtle1/color_sensor', '/turtle1/pose',
7-
* '/turtle2/cmd_vel', '/turtle2/color_sensor', '/turtle2/pose',
8-
*/
9-
"/tf2_web_republisher/status",
10-
"/tf2_web_republisher/feedback",
11-
// '/tf2_web_republisher/goal', '/tf2_web_republisher/result',
12-
"/fibonacci/feedback",
13-
"/fibonacci/status",
14-
"/fibonacci/result",
15-
];
4+
const expectedTopics = ["/listener"];
165

176
describe("Example topics are live", function () {
187
const ros = new ROSLIB.Ros({
198
url: "ws://localhost:9090",
209
});
2110

22-
it("getTopics", () =>
23-
new Promise((done) => {
24-
ros.getTopics(function (result) {
25-
expectedTopics.forEach(function (topic) {
26-
expect(result.topics).to.contain(
27-
topic,
28-
"Couldn't find topic: " + topic,
29-
);
30-
});
31-
done(result);
32-
});
33-
}));
11+
it("getTopics", async () => {
12+
const callback = vi.fn();
13+
ros.getTopics(callback);
14+
await vi.waitFor(() => {
15+
expect(callback).toHaveBeenCalledOnce();
16+
for (const topic of expectedTopics) {
17+
expect(callback.mock.calls[0][0].topics).to.contain(topic);
18+
}
19+
});
20+
});
3421

3522
const example = ros.Topic({
3623
name: "/some_test_topic",
3724
messageType: "std_msgs/String",
3825
});
3926

40-
it("doesn't automatically advertise the topic", () =>
41-
new Promise((done) => {
42-
ros.getTopics(function (result) {
43-
expect(result.topics).not.to.contain("/some_test_topic");
44-
example.advertise();
45-
done(result);
46-
});
47-
}));
27+
it("doesn't automatically advertise the topic", async () => {
28+
const callback = vi.fn();
29+
ros.getTopics(callback);
30+
await vi.waitFor(() => {
31+
expect(callback).toHaveBeenCalledOnce();
32+
expect(callback.mock.calls[0][0].topics).not.to.contain(
33+
"/some_test_topic",
34+
);
35+
});
36+
example.advertise();
37+
});
4838

49-
it("advertise broadcasts the topic", () =>
50-
new Promise((done) => {
51-
ros.getTopics(function (result) {
52-
expect(result.topics).to.contain("/some_test_topic");
53-
example.unadvertise();
54-
done(result);
55-
});
56-
}));
39+
it("advertise broadcasts the topic", async () => {
40+
const callback = vi.fn();
41+
ros.getTopics(callback);
42+
await vi.waitFor(() => {
43+
expect(callback).toHaveBeenCalledOnce();
44+
expect(callback.mock.calls[0][0].topics).to.contain("/some_test_topic");
45+
});
46+
example.unadvertise();
47+
});
5748

58-
it(
59-
"unadvertise will end the topic (if it's the last around)",
60-
() =>
61-
new Promise((done) => {
62-
console.log("Unadvertisement test. Wait for 15 seconds..");
63-
setTimeout(function () {
64-
ros.getTopics(function (result) {
65-
expect(result.topics).not.to.contain("/some_test_topic");
66-
done(result);
67-
});
68-
}, 15000);
69-
}),
70-
20000,
71-
);
49+
it("unadvertise will end the topic (if it's the last around)", async () => {
50+
const callback = vi.fn();
51+
ros.getTopics(callback);
52+
vi.waitFor(function () {
53+
expect(callback).toHaveBeenCalledOnce();
54+
expect(callback.mock.calls[0][0]).not.to.contain("/some_test_topic");
55+
}, 15000);
56+
});
7257
});

test/examples/fibonacci.example.ts

Lines changed: 58 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,65 @@
11
import { describe, it, expect } from "vitest";
22
import * as ROSLIB from "../../src/RosLib.js";
33

4-
describe("Fibonacci Example", function () {
5-
it(
6-
"Fibonacci",
7-
() =>
8-
new Promise<void>((done) => {
9-
const ros = new ROSLIB.Ros({
10-
url: "ws://localhost:9090",
11-
});
12-
/*
13-
* The ActionClient
14-
* ----------------
15-
*/
4+
// Noetic is the only version of ROS 1 we support, so we skip based on distro name
5+
// instead of adding extra plumbing for ROS_VERSION.
6+
describe.skipIf(process.env.ROS_DISTRO !== "noetic")(
7+
"ROS 1 Fibonacci Example",
8+
function () {
9+
it(
10+
"Fibonacci",
11+
() =>
12+
new Promise<void>((done) => {
13+
const ros = new ROSLIB.Ros({
14+
url: "ws://localhost:9090",
15+
});
16+
/*
17+
* The ActionClient
18+
* ----------------
19+
*/
1620

17-
const fibonacciClient = new ROSLIB.ActionClient({
18-
ros: ros,
19-
serverName: "/fibonacci",
20-
actionName: "actionlib_tutorials/FibonacciAction",
21-
});
21+
const fibonacciClient = new ROSLIB.ActionClient({
22+
ros: ros,
23+
serverName: "/fibonacci",
24+
actionName: "actionlib_tutorials/FibonacciAction",
25+
});
2226

23-
// Create a goal.
24-
const goal = new ROSLIB.Goal({
25-
actionClient: fibonacciClient,
26-
goalMessage: {
27-
order: 7,
28-
},
29-
});
27+
// Create a goal.
28+
const goal = new ROSLIB.Goal({
29+
actionClient: fibonacciClient,
30+
goalMessage: {
31+
order: 7,
32+
},
33+
});
3034

31-
// Print out their output into the terminal.
32-
const items = [
33-
{ sequence: [0, 1, 1] },
34-
{ sequence: [0, 1, 1, 2] },
35-
{ sequence: [0, 1, 1, 2, 3] },
36-
{ sequence: [0, 1, 1, 2, 3, 5] },
37-
{ sequence: [0, 1, 1, 2, 3, 5, 8] },
38-
{ sequence: [0, 1, 1, 2, 3, 5, 8, 13] },
39-
{ sequence: [0, 1, 1, 2, 3, 5, 8, 13, 21] },
40-
];
41-
goal.on("feedback", function (feedback) {
42-
console.log("Feedback:", feedback);
43-
expect(feedback).to.eql(items.shift());
44-
});
45-
goal.on("result", function (result) {
46-
console.log("Result:", result);
47-
expect(result).to.eql({ sequence: [0, 1, 1, 2, 3, 5, 8, 13, 21] });
48-
done();
49-
});
35+
// Print out their output into the terminal.
36+
const items = [
37+
{ sequence: [0, 1, 1] },
38+
{ sequence: [0, 1, 1, 2] },
39+
{ sequence: [0, 1, 1, 2, 3] },
40+
{ sequence: [0, 1, 1, 2, 3, 5] },
41+
{ sequence: [0, 1, 1, 2, 3, 5, 8] },
42+
{ sequence: [0, 1, 1, 2, 3, 5, 8, 13] },
43+
{ sequence: [0, 1, 1, 2, 3, 5, 8, 13, 21] },
44+
];
45+
goal.on("feedback", function (feedback) {
46+
expect(feedback).to.eql(items.shift());
47+
});
48+
goal.on("result", function (result) {
49+
expect(result).to.eql({ sequence: [0, 1, 1, 2, 3, 5, 8, 13, 21] });
50+
done();
51+
});
5052

51-
/*
52-
* Send the goal to the action server.
53-
* The timeout is to allow rosbridge to properly subscribe all the
54-
* Action topics - otherwise, the first feedback message might get lost
55-
*/
56-
setTimeout(function () {
57-
goal.send();
58-
}, 100);
59-
}),
60-
8000,
61-
);
62-
});
53+
/*
54+
* Send the goal to the action server.
55+
* The timeout is to allow rosbridge to properly subscribe all the
56+
* Action topics - otherwise, the first feedback message might get lost
57+
*/
58+
setTimeout(function () {
59+
goal.send();
60+
}, 100);
61+
}),
62+
8000,
63+
);
64+
},
65+
);

0 commit comments

Comments
 (0)