diff --git a/.github/workflows/identify-ros-distro.yml b/.github/workflows/identify-ros-distro.yml index 84604b4d..b3b9500d 100644 --- a/.github/workflows/identify-ros-distro.yml +++ b/.github/workflows/identify-ros-distro.yml @@ -26,6 +26,7 @@ jobs: id: identify run: | if ${ROLLING_VAR} == true; then + # echo "::set-output name=distro::rolling" echo "::set-output name=distro::rolling" echo "::set-output name=linuxos::ubuntu-22.04" elif ${HUMBLE_VAR} == true; then diff --git a/.gitignore b/.gitignore index d46783b6..94581f07 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,6 @@ generated types/interfaces.d.ts dist build -install log .vscode .project diff --git a/example/publisher-content-filter-example.js b/example/publisher-content-filter-example.js index 79595dbd..8cae8a18 100644 --- a/example/publisher-content-filter-example.js +++ b/example/publisher-content-filter-example.js @@ -45,7 +45,7 @@ async function main() { console.log( `Publish temerature message-${++count}: ${temperature} degrees` ); - }, 750); + }, 100); node.spin(); } diff --git a/example/subscription-content-filter-example.js b/example/subscription-content-filter-example.js index 0dfc0e91..8303091b 100644 --- a/example/subscription-content-filter-example.js +++ b/example/subscription-content-filter-example.js @@ -62,7 +62,7 @@ ${temperatureMsg.temperature}C`); if (subscription.hasContentFilter()) { console.log('Clearing filter'); subscription.clearContentFilter(); - } else { + } else if (param < 100) { param += 10; console.log('Update topic content-filter, temperature > ', param); const contentFilter = { diff --git a/lib/subscription.js b/lib/subscription.js index b200a087..8f599e90 100644 --- a/lib/subscription.js +++ b/lib/subscription.js @@ -113,7 +113,7 @@ class Subscription extends Entity { */ clearContentFilter() { return this.hasContentFilter() - ? rclnodejs.setContentFilter(this.handle, { expression: '' }) + ? rclnodejs.clearContentFilter(this.handle) : true; } } diff --git a/package.json b/package.json index b50fdacc..102307d7 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "husky": "^4.2.5", "jsdoc": "^3.6.7", "lint-staged": "^10.2.11", - "mocha": "^8.0.1", + "mocha": "^10.2.0", "prettier": "^2.0.5", "rimraf": "^3.0.2", "sinon": "^9.0.2", diff --git a/rosidl_gen/idl_generator.js b/rosidl_gen/idl_generator.js index 0bb7abab..8984058a 100644 --- a/rosidl_gen/idl_generator.js +++ b/rosidl_gen/idl_generator.js @@ -30,6 +30,13 @@ function removeEmptyLines(str) { return str.replace(/^\s*\n/gm, ''); } +/** + * Output generated code to disk. Do not overwrite + * an existing file. If file already exists do nothing. + * @param {string} dir + * @param {string} fileName + * @param {string} code + */ async function writeGeneratedCode(dir, fileName, code) { await fse.mkdirs(dir); await fse.writeFile(path.join(dir, fileName), code); diff --git a/rosidl_gen/index.js b/rosidl_gen/index.js index 118e4a17..fdaff6af 100644 --- a/rosidl_gen/index.js +++ b/rosidl_gen/index.js @@ -20,9 +20,10 @@ const packages = require('./packages.js'); const path = require('path'); const generatedRoot = path.join(__dirname, '../generated/'); -const installedPackagePaths = process.env.AMENT_PREFIX_PATH.split( - path.delimiter -); + +function getInstalledPackagePaths() { + return process.env.AMENT_PREFIX_PATH.split(path.delimiter); +} async function generateInPath(path) { const pkgs = await packages.findPackagesInDirectory(path); @@ -40,14 +41,23 @@ async function generateAll(forcedGenerating) { // all the JavaScript files will be created. const exist = await fse.exists(generatedRoot); if (forcedGenerating || !exist) { + if (exist) { + // recursively clear all previously generated struct files + await fse.emptyDir(generatedRoot); + } + await fse.copy( path.join(__dirname, 'generator.json'), path.join(generatedRoot, 'generator.json') ); - await Promise.all( - installedPackagePaths.map((path) => generateInPath(path)) - ); + // Process in AMENT_PREFIX_PATH in reverse order to + // such that interfaces defined earlier on the AMENX_PREFIX_PATH + // have higher priority over earlier versions and will override + // them - occurences of this are expected to be rare. + for (let path of getInstalledPackagePaths().reverse()) { + await generateInPath(path); + } } } diff --git a/scripts/run_test.js b/scripts/run_test.js index ac123e6a..129157c3 100644 --- a/scripts/run_test.js +++ b/scripts/run_test.js @@ -20,12 +20,6 @@ const os = require('os'); const path = require('path'); let rootDir = path.dirname(__dirname); -let actionPath = path.join(rootDir, 'test', 'ros1_actions'); -process.env.AMENT_PREFIX_PATH = - process.env.AMENT_PREFIX_PATH + path.delimiter + actionPath; -let msgPath = path.join(rootDir, 'test', 'rclnodejs_test_msgs'); -process.env.AMENT_PREFIX_PATH = - process.env.AMENT_PREFIX_PATH + path.delimiter + msgPath; fs.remove(path.join(path.dirname(__dirname), 'generated'), (err) => { if (!err) { diff --git a/src/rcl_bindings.cpp b/src/rcl_bindings.cpp index 59e78b0d..160e18a4 100644 --- a/src/rcl_bindings.cpp +++ b/src/rcl_bindings.cpp @@ -36,6 +36,7 @@ #include #endif +#include #include #include #include @@ -775,21 +776,21 @@ NAN_METHOD(SetContentFilter) { info.GetReturnValue().Set(Nan::False()); return; #else - v8::Local currentContent = Nan::GetCurrentContext(); + v8::Local currentContext = Nan::GetCurrentContext(); RclHandle* subscription_handle = RclHandle::Unwrap( Nan::To(info[0]).ToLocalChecked()); rcl_subscription_t* subscription = reinterpret_cast(subscription_handle->ptr()); v8::Local contentFilter = - info[1]->ToObject(currentContent).ToLocalChecked(); + info[1]->ToObject(currentContext).ToLocalChecked(); - // expression property is required - std::string expression(*Nan::Utf8String( - Nan::Get(contentFilter, Nan::New("expression").ToLocalChecked()) - .ToLocalChecked() - ->ToString(currentContent) - .ToLocalChecked())); + Nan::MaybeLocal jsExpression = + Nan::Get(contentFilter, Nan::New("expression").ToLocalChecked()); + Nan::Utf8String utf8_arg(jsExpression.ToLocalChecked()); + int len = utf8_arg.length() + 1; + char* expression = reinterpret_cast(malloc(len * sizeof(char*))); + snprintf(expression, len, "%s", *utf8_arg); // parameters property (string[]) is optional int argc = 0; @@ -817,17 +818,20 @@ NAN_METHOD(SetContentFilter) { rcl_subscription_content_filter_options_t options = rcl_get_zero_initialized_subscription_content_filter_options(); - THROW_ERROR_IF_NOT_EQUAL( - RCL_RET_OK, - rcl_subscription_content_filter_options_init( - subscription, expression.c_str(), argc, (const char**)argv, &options), - rcl_get_error_string().str); + THROW_ERROR_IF_NOT_EQUAL(RCL_RET_OK, + rcl_subscription_content_filter_options_set( + subscription, + expression, // expression.c_str(), + argc, (const char**)argv, &options), + rcl_get_error_string().str); THROW_ERROR_IF_NOT_EQUAL( RCL_RET_OK, rcl_subscription_set_content_filter(subscription, &options), rcl_get_error_string().str); if (argc) { + free(expression); + for (int i = 0; i < argc; i++) { free(argv[i]); } @@ -838,6 +842,34 @@ NAN_METHOD(SetContentFilter) { #endif } +NAN_METHOD(ClearContentFilter) { +#if ROS_VERSION < 2205 // 2205 => Humble+ + info.GetReturnValue().Set(Nan::False()); + return; +#else + RclHandle* subscription_handle = RclHandle::Unwrap( + Nan::To(info[0]).ToLocalChecked()); + rcl_subscription_t* subscription = + reinterpret_cast(subscription_handle->ptr()); + + // create ctf options + rcl_subscription_content_filter_options_t options = + rcl_get_zero_initialized_subscription_content_filter_options(); + + THROW_ERROR_IF_NOT_EQUAL( + RCL_RET_OK, + rcl_subscription_content_filter_options_init( + subscription, "", 0, (const char**)nullptr, &options), + rcl_get_error_string().str); + + THROW_ERROR_IF_NOT_EQUAL( + RCL_RET_OK, rcl_subscription_set_content_filter(subscription, &options), + rcl_get_error_string().str); + + info.GetReturnValue().Set(Nan::True()); +#endif +} + NAN_METHOD(CreatePublisher) { v8::Local currentContent = Nan::GetCurrentContext(); // Extract arguments @@ -1933,6 +1965,7 @@ std::vector binding_methods = { {"createSubscription", CreateSubscription}, {"hasContentFilter", HasContentFilter}, {"setContentFilter", SetContentFilter}, + {"clearContentFilter", ClearContentFilter}, {"createPublisher", CreatePublisher}, {"publish", Publish}, {"getTopic", GetTopic}, diff --git a/test/blocklist.json b/test/blocklist.json index 2b29c9f5..7112afba 100644 --- a/test/blocklist.json +++ b/test/blocklist.json @@ -20,7 +20,7 @@ "test-msg-type-py-node.js", "test-msg-type-cpp-node.js", "test-cross-lang.js", - "test-generate-messages-bin.js", + "test-message-generation-bin.js", "test-subscription-content-filter.js" ] } diff --git a/test/overlay_test_ws/README.md b/test/overlay_test_ws/README.md new file mode 100644 index 00000000..aacb9ef4 --- /dev/null +++ b/test/overlay_test_ws/README.md @@ -0,0 +1,9 @@ +This folder contains an overlay workspace that extends the +geometry_msgs/msg/Point with the additional `data` attribute. + +``` +float64 x +float64 y +float64 z +string data <--- +``` diff --git a/test/overlay_test_ws/geometry_msgs/CMakeLists.txt b/test/overlay_test_ws/geometry_msgs/CMakeLists.txt new file mode 100644 index 00000000..405590a6 --- /dev/null +++ b/test/overlay_test_ws/geometry_msgs/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.8) +project(geometry_msgs) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(rosidl_default_generators REQUIRED) + +# uncomment the following section in order to fill in +# further dependencies manually. +# find_package(std_msgs1 REQUIRED) + +rosidl_generate_interfaces(${PROJECT_NAME} + "msg/Point.msg" +) + +ament_package() diff --git a/test/overlay_test_ws/geometry_msgs/msg/Point.msg b/test/overlay_test_ws/geometry_msgs/msg/Point.msg new file mode 100644 index 00000000..aa676f9c --- /dev/null +++ b/test/overlay_test_ws/geometry_msgs/msg/Point.msg @@ -0,0 +1,5 @@ +# This contains the position of a point in free space +float64 x +float64 y +float64 z +string data \ No newline at end of file diff --git a/test/overlay_test_ws/geometry_msgs/package.xml b/test/overlay_test_ws/geometry_msgs/package.xml new file mode 100644 index 00000000..fd45c73f --- /dev/null +++ b/test/overlay_test_ws/geometry_msgs/package.xml @@ -0,0 +1,19 @@ + + + + geometry_msgs + 0.1.0 + TODO: Package description + wayne + TODO: License declaration + + ament_cmake + rosidl_default_generators + + rosidl_default_runtime + rosidl_interface_packages + + + ament_cmake + + diff --git a/test/overlay_test_ws/install/geometry_msgs/share/ament_index/resource_index/rosidl_interfaces/geometry_msgs b/test/overlay_test_ws/install/geometry_msgs/share/ament_index/resource_index/rosidl_interfaces/geometry_msgs new file mode 100644 index 00000000..767d5900 --- /dev/null +++ b/test/overlay_test_ws/install/geometry_msgs/share/ament_index/resource_index/rosidl_interfaces/geometry_msgs @@ -0,0 +1,2 @@ +msg/Point.idl +msg/Point.msg \ No newline at end of file diff --git a/test/overlay_test_ws/install/geometry_msgs/share/geometry_msgs/msg/Point.idl b/test/overlay_test_ws/install/geometry_msgs/share/geometry_msgs/msg/Point.idl new file mode 100644 index 00000000..d879c2ba --- /dev/null +++ b/test/overlay_test_ws/install/geometry_msgs/share/geometry_msgs/msg/Point.idl @@ -0,0 +1,20 @@ +// generated from rosidl_adapter/resource/msg.idl.em +// with input from geometry_msgs/msg/Point.msg +// generated code does not contain a copyright notice + + +module geometry_msgs { + module msg { + @verbatim (language="comment", text= + "This contains the position of a point in free space") + struct Point { + double x; + + double y; + + double z; + + string data; + }; + }; +}; diff --git a/test/overlay_test_ws/install/geometry_msgs/share/geometry_msgs/msg/Point.msg b/test/overlay_test_ws/install/geometry_msgs/share/geometry_msgs/msg/Point.msg new file mode 100644 index 00000000..aa676f9c --- /dev/null +++ b/test/overlay_test_ws/install/geometry_msgs/share/geometry_msgs/msg/Point.msg @@ -0,0 +1,5 @@ +# This contains the position of a point in free space +float64 x +float64 y +float64 z +string data \ No newline at end of file diff --git a/test/test-lifecycle.js b/test/test-lifecycle.js index 408665fb..27ff3b59 100644 --- a/test/test-lifecycle.js +++ b/test/test-lifecycle.js @@ -333,9 +333,6 @@ describe('LifecycleNode test suite', function () { let rosDistro = process.env.ROS_DISTRO; let firstChar = rosDistro.charAt(0); - console.log('1st char: ', firstChar); - console.log('> ', firstChar > 'g'); - let testNode = new rclnodejs.lifecycle.LifecycleNode( 'TEST_NODE', undefined, diff --git a/test/test-generate-messages-bin.js b/test/test-message-generation-bin.js similarity index 93% rename from test/test-generate-messages-bin.js rename to test/test-message-generation-bin.js index 1e7b61d7..f9b902c6 100644 --- a/test/test-generate-messages-bin.js +++ b/test/test-message-generation-bin.js @@ -56,12 +56,12 @@ describe('rclnodejs generate-messages binary-script tests', function () { fs.mkdirSync(this.tmpPkg); childProcess.spawnSync('npm', ['init', '-y'], { - stdio: 'inherit', + // stdio: 'inherit', shell: true, cwd: this.tmpPkg, }); childProcess.spawnSync('npm', ['pack', this.cwd], { - stdio: 'inherit', + // stdio: 'inherit', shell: true, cwd: this.tmpPkg, }); @@ -80,7 +80,7 @@ describe('rclnodejs generate-messages binary-script tests', function () { } let tgzPath = path.join(this.tmpPkg, tgz); childProcess.spawnSync('npm', ['install', tgzPath], { - stdio: 'inherit', + // stdio: 'inherit', shell: true, cwd: this.tmpPkg, }); @@ -90,7 +90,7 @@ describe('rclnodejs generate-messages binary-script tests', function () { if (getNodeVersionInfo()[0] === 10) { rimraf.sync(generatedFolderPath); } else { - fs.rmdirSync(generatedFolderPath, { recursive: true }); + fs.rmSync(generatedFolderPath, { recursive: true }); } } }); @@ -100,7 +100,7 @@ describe('rclnodejs generate-messages binary-script tests', function () { if (getNodeVersionInfo()[0] === 10) { rimraf.sync(this.tmpPkg); } else { - fs.rmdirSync(this.tmpPkg, { recursive: true }); + fs.rmSync(this.tmpPkg, { recursive: true }); } }); @@ -113,7 +113,10 @@ describe('rclnodejs generate-messages binary-script tests', function () { it('test generate-ros-messages script operation', function (done) { let script = createScriptFolderPath(this.tmpPkg); - childProcess.spawnSync(script, [], { stdio: 'inherit', shell: true }); + childProcess.spawnSync(script, [], { + // stdio: 'inherit', + shell: true, + }); let generatedFolderPath = createGeneratedFolderPath(this.tmpPkg); assert.ok( @@ -129,7 +132,7 @@ describe('rclnodejs generate-messages binary-script tests', function () { it('test npx generate-ros-messages script operation', function (done) { childProcess.spawnSync('npx', [SCRIPT_NAME], { - stdio: 'inherit', + // stdio: 'inherit', shell: true, cwd: this.tmpPkg, }); diff --git a/test/test-messsage-generation-overlay.js b/test/test-messsage-generation-overlay.js new file mode 100644 index 00000000..08edc862 --- /dev/null +++ b/test/test-messsage-generation-overlay.js @@ -0,0 +1,95 @@ +// Copyright (c) 2021 Wayne Parrott. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const assert = require('assert'); +const childProcess = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const rclnodejs = require('../index.js'); +const generator = require('../rosidl_gen/index.js'); + +const GENERATED_PATH = path.join(__dirname, '..', 'generated'); +const POINT_PATH = path.join( + __dirname, + '..', + 'generated', + 'geometry_msgs', + 'geometry_msgs__msg__Point.js' +); +const POSE_PATH = path.join( + __dirname, + '..', + 'generated', + 'geometry_msgs', + 'geometry_msgs__msg__Pose.js' +); + +function clearRequireCache(key) { + delete require.cache[key]; +} + +function generateMessages() { + return generator.generateAll(true); +} + +describe('override interface in overlay tests', function () { + this.timeout(60 * 1000); // 90 seconds to run this test suite + + before(() => { + // clear required cache of prior + clearRequireCache(POINT_PATH); + + if (fs.existsSync(GENERATED_PATH)) { + fs.rmSync(GENERATED_PATH, { recursive: true }); + } + }); + + after(async () => { + if (fs.existsSync(path.join(__dirname, '..', 'generated'))) { + fs.rmSync(path.join(__dirname, '..', 'generated'), { recursive: true }); + } + + clearRequireCache(POINT_PATH); + clearRequireCache(POSE_PATH); + + await generateMessages(); + }); + + it('overlay msg generation test', async () => { + const amentPrefixPathOriginal = process.env.AMENT_PREFIX_PATH; + assert.ok(amentPrefixPathOriginal, 'AMENT_PREFIX_PATH not found'); + + const wsPath = path.join( + __dirname, + 'overlay_test_ws', + 'install', + 'geometry_msgs' + ); + let amentPrefixPath = wsPath + path.delimiter + amentPrefixPathOriginal; + process.env.AMENT_PREFIX_PATH = amentPrefixPath; + + await generateMessages(); + + const pointMsg = rclnodejs.createMessageObject('geometry_msgs/msg/Point'); + assert.ok( + pointMsg.hasOwnProperty('data'), + 'Expected overridden Point msg to include "data" property' + ); + + // restore original AMENT_PREFIX_PATH envar setting + process.env.AMENT_PREFIX_PATH = amentPrefixPathOriginal; + }); +}); diff --git a/test/test-subscription-content-filter.js b/test/test-subscription-content-filter.js index 9961e632..2fa1b795 100644 --- a/test/test-subscription-content-filter.js +++ b/test/test-subscription-content-filter.js @@ -6,6 +6,9 @@ const rclnodejs = require('../index.js'); const DistroUtils = rclnodejs.DistroUtils; const RMWUtils = rclnodejs.RMWUtils; +const TIME1 = 5000; // ms +const TIME2 = 10000; // ms + function isContentFilteringSupported() { return ( DistroUtils.getDistroId() >= DistroUtils.getDistroId('humble') && @@ -14,10 +17,10 @@ function isContentFilteringSupported() { } describe('subscription content-filtering', function () { - this.timeout(10 * 1000); + this.timeout(30 * 1000); - beforeEach(function () { - return rclnodejs.init(); + beforeEach(async function () { + return await rclnodejs.init(); }); afterEach(function () { @@ -25,7 +28,7 @@ describe('subscription content-filtering', function () { }); it('isContentFilteringEnabled', function (done) { - let node = rclnodejs.createNode('string_subscription'); + let node = new rclnodejs.Node('string_subscription'); let msgString = 'std_msgs/msg/Int16'; let options = rclnodejs.Node.getDefaultOptions(); options.contentFilter = { @@ -59,7 +62,7 @@ describe('subscription content-filtering', function () { this.skip(); } - let node = rclnodejs.createNode('string_subscription'); + let node = new rclnodejs.Node('string_subscription'); let msgString = 'std_msgs/msg/String'; let options = rclnodejs.Node.getDefaultOptions(); options.contentFilter = { @@ -99,7 +102,7 @@ describe('subscription content-filtering', function () { done(); }, 1000); - rclnodejs.spin(node); + node.spin(node); }); it('single parameter', function (done) { @@ -107,7 +110,7 @@ describe('subscription content-filtering', function () { this.skip(); } - let node = rclnodejs.createNode('string_subscription'); + let node = new rclnodejs.Node('string_subscription'); let msgString = 'std_msgs/msg/String'; let options = rclnodejs.Node.getDefaultOptions(); options.contentFilter = { @@ -148,7 +151,7 @@ describe('subscription content-filtering', function () { done(); }, 1000); - rclnodejs.spin(node); + node.spin(node); }); it('multiple parameters', function (done) { @@ -156,7 +159,7 @@ describe('subscription content-filtering', function () { this.skip(); } - let node = rclnodejs.createNode('int32_subscription'); + let node = new rclnodejs.Node('int32_subscription'); let msgString = 'std_msgs/msg/Int32'; let options = rclnodejs.Node.getDefaultOptions(); options.contentFilter = { @@ -197,7 +200,7 @@ describe('subscription content-filtering', function () { done(); }, 1000); - rclnodejs.spin(node); + node.spin(node); }); it('setContentFilter', function (done) { @@ -205,7 +208,7 @@ describe('subscription content-filtering', function () { this.skip(); } - let node = rclnodejs.createNode('int32_subscription'); + let node = new rclnodejs.Node('int32_subscription'); let msgString = 'std_msgs/msg/Int32'; let options = rclnodejs.Node.getDefaultOptions(); options.contentFilter = { @@ -260,69 +263,7 @@ describe('subscription content-filtering', function () { done(); }, 1000); - rclnodejs.spin(node); - }); - - it('setContentFilter(undefined)', function (done) { - if (!isContentFilteringSupported()) { - this.skip(); - } - - let node = rclnodejs.createNode('int32_subscription'); - let msgString = 'std_msgs/msg/Int32'; - let options = rclnodejs.Node.getDefaultOptions(); - options.contentFilter = { - expression: 'data = %0', - parameters: [5], - }; - - let msgCnt0 = 0; - let msgCnt5 = 0; - let fail = false; - let subscription = node.createSubscription( - msgString, - 'Int32_channel', - options, - (msg) => { - switch (msg.data) { - case 0: - msgCnt0++; - break; - case 5: - msgCnt5++; - break; - default: - fail = true; - } - } - ); - - assert.ok(subscription.hasContentFilter()); - - let publisher1 = childProcess.fork(`${__dirname}/publisher_msg.js`, [ - 'Int32', - '0', - ]); - - let publisher2 = childProcess.fork(`${__dirname}/publisher_msg.js`, [ - 'Int32', - '5', - ]); - - setTimeout(() => { - assert.ok(msgCnt5 && !msgCnt0 && !fail); - subscription.setContentFilter(); - }, 500); - - setTimeout(() => { - publisher1.kill('SIGINT'); - publisher2.kill('SIGINT'); - assert.ok(!subscription.hasContentFilter()); - assert.ok(!fail && msgCnt5 && msgCnt0); - done(); - }, 1000); - - rclnodejs.spin(node); + node.spin(); }); it('clearContentFilter', function (done) { @@ -330,7 +271,7 @@ describe('subscription content-filtering', function () { this.skip(); } - let node = rclnodejs.createNode('int32_subscription'); + let node = new rclnodejs.Node('int32_subscription'); let msgString = 'std_msgs/msg/Int32'; let options = rclnodejs.Node.getDefaultOptions(); options.contentFilter = { @@ -338,14 +279,17 @@ describe('subscription content-filtering', function () { parameters: [5], }; + let msgCnt = 0; let msgCnt0 = 0; let msgCnt5 = 0; let fail = false; + let filterCleared = false; let subscription = node.createSubscription( msgString, 'Int32_channel', options, (msg) => { + msgCnt++; switch (msg.data) { case 0: msgCnt0++; @@ -374,7 +318,7 @@ describe('subscription content-filtering', function () { setTimeout(() => { assert.ok(msgCnt5 && !msgCnt0 && !fail); subscription.clearContentFilter(); - }, 500); + }, TIME1); setTimeout(() => { publisher1.kill('SIGINT'); @@ -382,9 +326,9 @@ describe('subscription content-filtering', function () { assert.ok(!subscription.hasContentFilter()); assert.ok(!fail && msgCnt5 && msgCnt0); done(); - }, 1000); + }, TIME2); - rclnodejs.spin(node); + node.spin(); }); it('multiple clearContentFilter', function (done) { @@ -392,7 +336,7 @@ describe('subscription content-filtering', function () { this.skip(); } - let node = rclnodejs.createNode('int32_subscription'); + let node = new rclnodejs.Node('int32_subscription'); let msgString = 'std_msgs/msg/Int32'; let options = rclnodejs.Node.getDefaultOptions(); options.contentFilter = { @@ -418,7 +362,7 @@ describe('subscription content-filtering', function () { this.skip(); } - let node = rclnodejs.createNode('string_subscription'); + let node = new rclnodejs.Node('string_subscription'); let msgString = 'std_msgs/msg/String'; let msgCnt = 0; @@ -441,9 +385,9 @@ describe('subscription content-filtering', function () { publisher.kill('SIGINT'); assert.ok(msgCnt > 0); done(); - }, 1000); + }, 2000); - rclnodejs.spin(node); + node.spin(node); }); it('bad expression', function (done) { @@ -451,7 +395,7 @@ describe('subscription content-filtering', function () { this.skip(); } - let node = rclnodejs.createNode('string_subscription'); + let node = new rclnodejs.Node('string_subscription'); let msgString = 'std_msgs/msg/String'; let options = rclnodejs.Node.getDefaultOptions(); options.contentFilter = { @@ -471,4 +415,69 @@ describe('subscription content-filtering', function () { assert.ok(!subscription || !subscription.hasContentFilter()); done(); }); + + it('setContentFilter(undefined)', function (done) { + if (!isContentFilteringSupported()) { + this.skip(); + } + + let node = new rclnodejs.Node('int32_subscription'); + let msgString = 'std_msgs/msg/Int32'; + let options = rclnodejs.Node.getDefaultOptions(); + options.contentFilter = { + expression: 'data = %0', + parameters: [5], + }; + + let msgCnt = 0; + let msgCnt0 = 0; + let msgCnt5 = 0; + let fail = false; + let filterCleared = false; + let subscription = node.createSubscription( + msgString, + 'Int32_channel', + options, + (msg) => { + msgCnt++; + switch (msg.data) { + case 0: + msgCnt0++; + break; + case 5: + msgCnt5++; + break; + default: + fail = true; + } + } + ); + + assert.ok(subscription.hasContentFilter()); + + let publisher1 = childProcess.fork(`${__dirname}/publisher_msg.js`, [ + 'Int32', + '0', + ]); + + let publisher2 = childProcess.fork(`${__dirname}/publisher_msg.js`, [ + 'Int32', + '5', + ]); + + setTimeout(() => { + assert.ok(msgCnt5 && !msgCnt0 && !fail); + subscription.setContentFilter(); + }, TIME1); + + setTimeout(() => { + publisher1.kill('SIGINT'); + publisher2.kill('SIGINT'); + assert.ok(!subscription.hasContentFilter()); + assert.ok(!fail && msgCnt5 && msgCnt0); + done(); + }, TIME2); + + node.spin(); + }); });