Skip to content

Commit 2125391

Browse files
committed
Support hyphenated ROSIDL subdirs (#1390)
This PR adds support for ROSIDL packages with hyphenated subdirectory names (e.g., `msg-common`, `msg-ros2`) by updating the subfolder extraction logic to normalize these to their base interface types (`msg`, `srv`, `action`). **Changes:** - Modified `getSubFolder()` to extract base interface type from hyphenated subdirectory names using `split('-')[0]` - Added comprehensive test case using `mrpt_msgs` package to validate hyphenated subfolder support - Refactored test setup in `test-native-loader.js` to avoid repeated module reloading - Updated CI workflows to install `mrpt-msgs` package for testing - Simplified process exit handling in test runner script Fix: #1388
1 parent 7f04819 commit 2125391

File tree

6 files changed

+77
-31
lines changed

6 files changed

+77
-31
lines changed

.github/workflows/linux-arm64-build-and-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ jobs:
4949
with:
5050
required-ros-distributions: ${{ matrix.ros_distribution }}
5151

52-
- name: Install test-msgs on Linux
52+
- name: Install test-msgs and mrpt_msgs on Linux
5353
run: |
54-
sudo apt install ros-${{ matrix.ros_distribution }}-test-msgs
54+
sudo apt install ros-${{ matrix.ros_distribution }}-test-msgs ros-${{ matrix.ros_distribution }}-mrpt-msgs
5555
# Adjust dependencies based on Ubuntu version
5656
LIBASOUND_PKG="libasound2"
5757
if grep -q "24.04" /etc/os-release; then

.github/workflows/linux-x64-build-and-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ jobs:
5353
required-ros-distributions: ${{ matrix.ros_distribution }}
5454
use-ros2-testing: ${{ matrix.ros_distribution == 'rolling' }}
5555

56-
- name: Install test-msgs on Linux
56+
- name: Install test-msgs and mrpt_msgs on Linux
5757
run: |
58-
sudo apt install ros-${{ matrix.ros_distribution }}-test-msgs
58+
sudo apt install ros-${{ matrix.ros_distribution }}-test-msgs ros-${{ matrix.ros_distribution }}-mrpt-msgs
5959
# Adjust dependencies based on Ubuntu version
6060
LIBASOUND_PKG="libasound2"
6161
if grep -q "24.04" /etc/os-release; then

rosidl_gen/packages.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ function getSubFolder(filePath, amentExecuted) {
5252
}
5353

5454
if (amentExecuted) {
55-
return filePath.match(/\w+\/share\/\w+\/(\w+)\//)[1];
55+
const match = filePath.match(/\w+\/share\/\w+\/([\w-]+)\//);
56+
if (match) {
57+
// Handle non-standard subfolder names (e.g., msg-common, msg-ros2)
58+
// by extracting only the base interface type before any hyphen.
59+
return match[1].split('-')[0];
60+
}
5661
}
5762
// If the |amentExecuted| equals to false, the file's extension will be assigned as
5863
// the name of sub folder.

scripts/run_test.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,8 @@ utils
4343
});
4444

4545
mocha.run(function (failures) {
46-
process.on('exit', () => {
47-
process.exit(failures);
48-
});
46+
process.exitCode = failures ? 1 : 0;
47+
process.exit(process.exitCode);
4948
});
5049
})
5150
.catch((err) => {

test/test-native-loader.js

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,35 @@ const sinon = require('sinon');
66
const fs = require('fs');
77
const child_process = require('child_process');
88

9+
// Require the module once without clearing cache to avoid triggering
10+
// loadNativeAddon() repeatedly. The TestHelpers functions read process
11+
// state at call time, so they can be tested with platform/env changes
12+
// without re-requiring the module.
13+
// NOTE: The module may have been loaded by other tests before NODE_ENV was set,
14+
// so TestHelpers might not be present. We clear cache and re-require with
15+
// NODE_ENV='test' to ensure TestHelpers is exported.
16+
const nativeLoaderPath = require.resolve('../lib/native_loader.js');
17+
if (
18+
!require.cache[nativeLoaderPath] ||
19+
!require.cache[nativeLoaderPath].exports.TestHelpers
20+
) {
21+
delete require.cache[nativeLoaderPath];
22+
process.env.NODE_ENV = 'test';
23+
}
24+
const nativeLoader = require('../lib/native_loader.js');
25+
926
describe('NativeLoader testing', function () {
1027
const sandbox = sinon.createSandbox();
1128
let originalPlatform;
1229
let originalArch;
1330
let originalEnv;
31+
let loader;
1432

1533
beforeEach(function () {
1634
originalPlatform = process.platform;
1735
originalArch = process.arch;
1836
originalEnv = { ...process.env };
19-
process.env.NODE_ENV = 'test';
20-
21-
// Clear cache to reload module
22-
delete require.cache[require.resolve('../lib/native_loader.js')];
37+
loader = nativeLoader.TestHelpers;
2338
});
2439

2540
afterEach(function () {
@@ -29,20 +44,14 @@ describe('NativeLoader testing', function () {
2944
process.env = originalEnv;
3045
});
3146

32-
function getLoader() {
33-
return require('../lib/native_loader.js').TestHelpers;
34-
}
35-
3647
it('customFallbackLoader returns null on non-linux', function () {
3748
Object.defineProperty(process, 'platform', { value: 'win32' });
38-
const loader = getLoader();
3949
assert.strictEqual(loader.customFallbackLoader(), null);
4050
});
4151

4252
it('customFallbackLoader returns null if env info missing', function () {
4353
Object.defineProperty(process, 'platform', { value: 'linux' });
4454
process.env.ROS_DISTRO = '';
45-
const loader = getLoader();
4655
assert.strictEqual(loader.customFallbackLoader(), null);
4756
});
4857

@@ -52,8 +61,6 @@ describe('NativeLoader testing', function () {
5261
process.env.ROS_DISTRO = 'humble';
5362

5463
sandbox.stub(fs, 'existsSync').returns(false);
55-
56-
const loader = getLoader();
5764
assert.strictEqual(loader.customFallbackLoader(), null);
5865
});
5966

@@ -64,8 +71,6 @@ describe('NativeLoader testing', function () {
6471

6572
// Stub fs.existsSync to return true
6673
const existsSync = sandbox.stub(fs, 'existsSync').returns(true);
67-
68-
const loader = getLoader();
6974
assert.strictEqual(loader.customFallbackLoader(), null);
7075

7176
// Verify it checked for the file
@@ -79,17 +84,12 @@ describe('NativeLoader testing', function () {
7984
process.env.RCLNODEJS_FORCE_BUILD = '1';
8085
const execSync = sandbox.stub(child_process, 'execSync');
8186

82-
// We expect it to try loading bindings after build
83-
// Since we don't mock bindings, it will load the real one (if present) or fail.
84-
// If it loads real one, test passes.
85-
// If it fails, loadNativeAddon throws. We might need to handle that.
86-
// But usually in dev env, the binding exists.
87+
// Clear cache and re-require to trigger loadNativeAddon with RCLNODEJS_FORCE_BUILD.
88+
// execSync is stubbed so no real rebuild (which deletes generated/) occurs.
89+
delete require.cache[require.resolve('../lib/native_loader.js')];
8790

8891
try {
89-
getLoader();
90-
// Wait, getLoader requires the file.
91-
// The file calls loadNativeAddon() immediately.
92-
// So valid test is just requiring the file.
92+
require('../lib/native_loader.js');
9393
} catch (e) {
9494
// Ignore if loading binding fails, as long as execSync was called
9595
}

test/test-rosidl-message-generator.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,4 +308,46 @@ describe('ROSIDL Node.js message generator test suite', function () {
308308
process.env.AMENT_PREFIX_PATH = amentPrefixPathOriginal;
309309
}
310310
});
311+
312+
it('Testing mrpt_msgs/msg/GraphSlamAgents from non-standard msg subfolder', function () {
313+
// GraphSlamAgents.msg lives under msg-common/ (non-standard subfolder name)
314+
// and references GraphSlamAgent.msg from msg-ros2/. This verifies that
315+
// packages with hyphenated subfolder names are generated and loadable.
316+
const GraphSlamAgents = rclnodejs.require('mrpt_msgs/msg/GraphSlamAgents');
317+
const GraphSlamAgent = rclnodejs.require('mrpt_msgs/msg/GraphSlamAgent');
318+
319+
// Verify GraphSlamAgents can be instantiated with an empty list
320+
const agents = new GraphSlamAgents();
321+
assert.ok(agents.list);
322+
assert.equal(agents.list.size, 0);
323+
324+
// Verify GraphSlamAgent fields and defaults
325+
const agent = new GraphSlamAgent();
326+
assert.equal(agent.name.data, '');
327+
assert.equal(agent.hostname.data, '');
328+
assert.equal(agent.ip_addr.data, '');
329+
assert.equal(agent.port, 0);
330+
assert.equal(agent.is_online.data, false);
331+
assert.equal(agent.last_seen_time.sec, 0);
332+
assert.equal(agent.last_seen_time.nanosec, 0);
333+
assert.equal(agent.topic_namespace.data, '');
334+
assert.equal(agent.agent_id, 0);
335+
336+
// Verify field assignment
337+
agent.name.data = 'robot_1';
338+
agent.hostname.data = 'host1';
339+
agent.ip_addr.data = '192.168.1.17';
340+
agent.port = 11311;
341+
agent.is_online.data = true;
342+
agent.topic_namespace.data = 'robot_1';
343+
agent.agent_id = 17;
344+
345+
assert.equal(agent.name.data, 'robot_1');
346+
assert.equal(agent.hostname.data, 'host1');
347+
assert.equal(agent.ip_addr.data, '192.168.1.17');
348+
assert.equal(agent.port, 11311);
349+
assert.equal(agent.is_online.data, true);
350+
assert.equal(agent.topic_namespace.data, 'robot_1');
351+
assert.equal(agent.agent_id, 17);
352+
});
311353
});

0 commit comments

Comments
 (0)