From 74beea3b76406c4d11e22450606e0529282bdf23 Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Tue, 11 Feb 2025 16:03:30 +0800 Subject: [PATCH 1/7] Levrage BigInt to represent int64/uint64 --- lib/parameter.js | 59 +++--- package.json | 1 + rosidl_gen/message_translator.js | 21 +- rosidl_gen/packages.js | 8 +- rosidl_gen/templates/message.dot | 27 ++- rosidl_parser/rosidl_parser.js | 26 ++- rostsd_gen/index.js | 6 +- test/client_setup.js | 9 +- test/publisher_msg.js | 5 + test/test-bounded-sequences.js | 20 +- test/test-fixed-array.js | 22 +- test/test-interactive.js | 4 +- test/test-message-translator-primitive.js | 235 +++++++++++----------- test/test-message-type.js | 14 +- test/test-parameter-service.js | 14 +- test/test-parameters.js | 58 +++--- test/test-primitive-msg-type-check.js | 12 +- test/test-security-related.js | 2 +- test/test-service-introspection.js | 4 +- test/test-service-with-async-callback.js | 8 +- test/test-single-process.js | 46 ++--- 21 files changed, 332 insertions(+), 269 deletions(-) diff --git a/lib/parameter.js b/lib/parameter.js index a7911cfe..4709d5ea 100644 --- a/lib/parameter.js +++ b/lib/parameter.js @@ -61,6 +61,8 @@ const ParameterType = { PARAMETER_DOUBLE_ARRAY: 8, /** @member {number} */ PARAMETER_STRING_ARRAY: 9, + /** @member {number} */ + PARAMETER_BYTE: 10, }; /** @@ -125,7 +127,9 @@ class Parameter { constructor(name, type, value) { this._name = name; this._type = type; - this._value = value; + // Convert to bigint if it's type of integer. + this._value = + this._type == ParameterType.PARAMETER_INTEGER ? BigInt(value) : value; this._isDirty = true; this.validate(); @@ -240,10 +244,10 @@ class Parameter { msg.double_array_value = this.value; break; case ParameterType.PARAMETER_INTEGER: - msg.integer_value = Math.trunc(this.value); + msg.integer_value = this.value; break; case ParameterType.PARAMETER_INTEGER_ARRAY: - msg.integer_array_value = this.value.map((val) => Math.trunc(val)); + msg.integer_array_value = this.value; break; case ParameterType.PARAMETER_STRING: msg.string_value = this.value; @@ -540,7 +544,7 @@ class Range { * A TypeError is thrown when value is not a number. * Subclasses should override and call this method for basic type checking. * - * @param {number} value - The number to check. + * @param {number|bigint} value - The number or bigint to check. * @return {boolean} - True if value satisfies the range; false otherwise. */ inRange(value) { @@ -550,8 +554,8 @@ class Range { (inRange, val) => inRange && this.inRange(val), true ); - } else if (typeof value !== 'number') { - throw new TypeError('Value must be a number'); + } else if (typeof value !== 'number' && typeof value !== 'bigint') { + throw new TypeError('Value must be a number or bigint'); } return true; @@ -652,27 +656,16 @@ class FloatingPointRange extends Range { * Defines a range for integer values. * @class */ -class IntegerRange extends FloatingPointRange { +class IntegerRange extends Range { /** * Create a new instance. * @constructor - * @param {number} fromValue - The lowest inclusive value in range - * @param {number} toValue - The highest inclusive value in range - * @param {number} step - The internal unit size. - * @param {number} tolerance - The plus/minus tolerance for number equivalence. + * @param {bigint} fromValue - The lowest inclusive value in range + * @param {bigint} toValue - The highest inclusive value in range + * @param {bigint} step - The internal unit size. */ - constructor( - fromValue, - toValue, - step = 1, - tolerance = DEFAULT_NUMERIC_RANGE_TOLERANCE - ) { - super( - Math.trunc(fromValue), - Math.trunc(toValue), - Math.trunc(step), - tolerance - ); + constructor(fromValue, toValue, step = 1n) { + super(fromValue, toValue, step); } /** @@ -689,6 +682,19 @@ class IntegerRange extends FloatingPointRange { parameterType === ParameterType.PARAMETER_INTEGER_ARRAY; return result; } + + inRange(value) { + const min = this.fromValue; + const max = this.toValue; + if (value < min || value > max) { + return false; + } + + if (this.step != 0n && (value - min) % this.step !== 0n) { + return false; + } + return true; + } } /** @@ -763,10 +769,13 @@ function validValue(value, type) { case ParameterType.PARAMETER_STRING: result = typeof value === 'string'; break; - case ParameterType.PARAMETER_INTEGER: case ParameterType.PARAMETER_DOUBLE: + case ParameterType.PARAMETER_BYTE: result = typeof value === 'number'; break; + case ParameterType.PARAMETER_INTEGER: + result = typeof value === 'bigint'; + break; case ParameterType.PARAMETER_BOOL_ARRAY: case ParameterType.PARAMETER_BYTE_ARRAY: case ParameterType.PARAMETER_INTEGER_ARRAY: @@ -789,7 +798,7 @@ function _validArray(values, type) { if (type === ParameterType.PARAMETER_BOOL_ARRAY) { arrayElementType = ParameterType.PARAMETER_BOOL; } else if (type === ParameterType.PARAMETER_BYTE_ARRAY) { - arrayElementType = ParameterType.PARAMETER_INTEGER; + arrayElementType = ParameterType.PARAMETER_BYTE; } if (type === ParameterType.PARAMETER_INTEGER_ARRAY) { arrayElementType = ParameterType.PARAMETER_INTEGER; diff --git a/package.json b/package.json index 84691c1b..ce7ff3c2 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "dot": "^1.1.3", "dtslint": "^4.2.1", "fs-extra": "^11.2.0", + "json-bigint": "^1.0.0", "int64-napi": "^1.0.2", "is-close": "^1.3.3", "mkdirp": "^3.0.1", diff --git a/rosidl_gen/message_translator.js b/rosidl_gen/message_translator.js index b8bb048f..7d5417bd 100644 --- a/rosidl_gen/message_translator.js +++ b/rosidl_gen/message_translator.js @@ -27,7 +27,12 @@ function copyMsgObject(msg, obj) { for (let i in obj) { if (msg.hasMember(i)) { const type = typeof obj[i]; - if (type === 'string' || type === 'number' || type === 'boolean') { + if ( + type === 'string' || + type === 'number' || + type === 'boolean' || + type === 'bigint' + ) { // A primitive-type value msg[i] = obj[i]; } else if (Array.isArray(obj[i]) || isTypedArray(obj[i])) { @@ -80,17 +85,20 @@ function verifyMessage(message, obj) { case 'char': case 'int16': case 'int32': - case 'int64': case 'byte': case 'uint16': case 'uint32': - case 'uint64': case 'float32': case 'float64': if (typeof obj[name] != 'number') { return false; } break; + case 'int64': + case 'uint64': + if (typeof obj[name] != 'bigint') { + return false; + } case 'bool': if (typeof obj[name] != 'boolean') { return false; @@ -171,7 +179,12 @@ function toROSMessage(TypeClass, obj) { function constructFromPlanObject(msg, obj) { const type = typeof obj; - if (type === 'string' || type === 'number' || type === 'boolean') { + if ( + type === 'string' || + type === 'number' || + type === 'boolean' || + type === 'bigint' + ) { msg.data = obj; } else if (type === 'object') { copyMsgObject(msg, obj); diff --git a/rosidl_gen/packages.js b/rosidl_gen/packages.js index a7ee7a54..8efefe43 100644 --- a/rosidl_gen/packages.js +++ b/rosidl_gen/packages.js @@ -161,9 +161,7 @@ async function generateMsgForSrv(filePath, interfaceInfo, pkgMap) { async function addInterfaceInfos(filePath, dir, pkgMap) { const interfaceInfo = grabInterfaceInfo(filePath, true); const ignore = pkgFilters.matchesAny(interfaceInfo); - if (ignore) { - console.log('Omitting filtered interface: ', interfaceInfo); - } else { + if (!ignore) { if (path.extname(filePath) === '.msg') { // Some .msg files were generated prior to 0.3.2 for .action files, // which has been disabled. So these files should be ignored here. @@ -232,9 +230,7 @@ async function findPackagesInDirectory(dir) { amentExecuted ); const ignore = pkgFilters.matchesAny(interfaceInfo); - if (ignore) { - console.log('Omitting filtered interface: ', interfaceInfo); - } else { + if (!ignore) { if (path.extname(file.name) === '.msg') { // Some .msg files were generated prior to 0.3.2 for .action files, // which has been disabled. So these files should be ignored here. diff --git a/rosidl_gen/templates/message.dot b/rosidl_gen/templates/message.dot index c43393f2..546e34f3 100644 --- a/rosidl_gen/templates/message.dot +++ b/rosidl_gen/templates/message.dot @@ -157,6 +157,10 @@ function isTypedArrayType(type) { return typedArrayType.indexOf(type.type.toLowerCase()) !== -1; } +function isBigInt(type) { + return ['int64', 'uint64'].indexOf(type.type.toLowerCase()) !== -1; +} + const willUseTypedArray = isTypedArrayType(it.spec.baseType); const currentTypedArray = getTypedArrayName(it.spec.baseType); const currentTypedArrayElementType = getTypedArrayElementName(it.spec.baseType); @@ -303,7 +307,11 @@ class {{=objectWrapper}} { this._refObject.{{=field.name}} = {{=field.default_value}}; {{?}} {{?? field.type.isPrimitiveType && !isTypedArrayType(field.type) && field.default_value}} - this._{{=field.name}}Array = {{=JSON.stringify(field.default_value)}}; + {{? isBigInt(field.type)}} + this._{{=field.name}}Array = {{=JSON.stringify(field.default_value)}}.map(num => BigInt(num)); + {{??}} + this._{{=field.name}}Array = {{=JSON.stringify(field.default_value)}}; + {{?}} {{?? field.type.isPrimitiveType && isTypedArrayType(field.type) && field.default_value}} this._wrapperFields.{{=field.name}}.fill({{=getTypedArrayName(field.type)}}.from({{=JSON.stringify(field.default_value)}})); {{?}} @@ -376,8 +384,11 @@ class {{=objectWrapper}} { } } } + {{?? isBigInt(field.type)}} + {{/* For non-TypedArray like int64/uint64. */}} + this._refObject.{{=field.name}} = this._{{=field.name}}Array.map(num => num.toString()); {{??}} - {{/* For non-TypedArray like int64/uint64/bool. */}} + {{/* For non-TypedArray like bool. */}} this._refObject.{{=field.name}} = this._{{=field.name}}Array; {{?}} {{?? field.type.isArray && field.type.isPrimitiveType && isTypedArrayType(field.type) && field.type.isFixedSizeArray}} @@ -527,6 +538,8 @@ class {{=objectWrapper}} { return this._wrapperFields.{{=field.name}}; {{?? !field.type.isArray && field.type.type === 'string' && it.spec.msgName !== 'String'}} return this._wrapperFields.{{=field.name}}.data; + {{?? isBigInt(field.type)}} + return BigInt(this._refObject.{{=field.name}}); {{??}} return this._refObject.{{=field.name}}; {{?}} @@ -559,6 +572,16 @@ class {{=objectWrapper}} { } {{?? !field.type.isArray && field.type.type === 'string' && it.spec.msgName !== 'String'}} this._wrapperFields.{{=field.name}}.data = value; + {{?? isBigInt(field.type)}} + if (typeof value !== "bigint") { + throw new TypeError('Must be type of bigint'); + } + this._refObject.{{=field.name}} = value.toString(); + /*if (typeof value === "bigint") { + this._refObject.{{=field.name}} = value.toString(); + } else { + this._refObject.{{=field.name}} = value + }*/ {{??}} {{? it.spec.msgName === 'String'}} this._refObject.size = Buffer.byteLength(value); diff --git a/rosidl_parser/rosidl_parser.js b/rosidl_parser/rosidl_parser.js index 93d19a3a..8971db4c 100644 --- a/rosidl_parser/rosidl_parser.js +++ b/rosidl_parser/rosidl_parser.js @@ -14,11 +14,19 @@ 'use strict'; +const compareVersions = require('compare-versions'); const path = require('path'); const execFile = require('child_process').execFile; const pythonExecutable = require('./py_utils').getPythonExecutable('python3'); +const version = process.version; +const isContextSupported = compareVersions.compare( + version.substring(1, version.length), + '21.0.0.0', + '>=' +); + const rosidlParser = { parseMessageFile(packageName, filePath) { return this._parseFile('parse_message_file', packageName, filePath); @@ -32,6 +40,22 @@ const rosidlParser = { return this._parseFile('parse_action_file', packageName, filePath); }, + _parseJSONObject(str) { + if (isContextSupported) { + return JSON.parse(str, (key, value, context) => { + if ( + Number.isInteger(value) && + !Number.isSafeInteger(Number(context.source)) + ) { + return context.source; + } + return value; + }); + } + const JSONbigString = require('json-bigint')({ storeAsString: true }); + return JSONbigString.parse(str); + }, + _parseFile(command, packageName, filePath) { return new Promise((resolve, reject) => { const args = [ @@ -54,7 +78,7 @@ const rosidlParser = { ) ); } else { - resolve(JSON.parse(stdout)); + resolve(this._parseJSONObject(stdout)); } } ); diff --git a/rostsd_gen/index.js b/rostsd_gen/index.js index 07c0ab7d..2ddbdb10 100644 --- a/rostsd_gen/index.js +++ b/rostsd_gen/index.js @@ -478,7 +478,6 @@ function primitiveType2JSName(type) { case 'int8': case 'int16': case 'int32': - case 'int64': // signed explicit float types case 'float32': @@ -488,7 +487,6 @@ function primitiveType2JSName(type) { case 'uint8': case 'uint16': case 'uint32': - case 'uint64': jsName = 'number'; break; case 'bool': @@ -499,6 +497,10 @@ function primitiveType2JSName(type) { case 'wstring': jsName = 'string'; break; + case 'int64': + case 'uint64': + jsName = 'bigint'; + break; } return jsName; diff --git a/test/client_setup.js b/test/client_setup.js index 5319efd8..f2dbc836 100644 --- a/test/client_setup.js +++ b/test/client_setup.js @@ -14,6 +14,7 @@ 'use strict'; +const assert = require('assert'); const rclnodejs = require('../index.js'); rclnodejs @@ -24,13 +25,15 @@ rclnodejs const Int8 = 'std_msgs/msg/Int8'; var client = node.createClient(AddTwoInts, 'add_two_ints'); const request = { - a: 1, - b: 2, + a: 1n, + b: 2n, }; var publisher = node.createPublisher(Int8, 'back_add_two_ints'); client.waitForService().then(() => { client.sendRequest(request, (response) => { - publisher.publish(response.sum); + // Conver `response.sum` from bigint to number. + assert.equal(typeof response.sum, 'bigint'); + publisher.publish(Number(response.sum)); }); }); diff --git a/test/publisher_msg.js b/test/publisher_msg.js index a263dcc5..4f55f9c6 100644 --- a/test/publisher_msg.js +++ b/test/publisher_msg.js @@ -19,6 +19,11 @@ const rclnodejs = require('../index.js'); var rclType = process.argv[2]; var rclValue = eval(process.argv[3]); +if (['int64', 'uint64'].indexOf(rclType.toLowerCase()) !== -1) { + rclValue = BigInt(rclValue); + 0; +} + rclnodejs .init() .then(() => { diff --git a/test/test-bounded-sequences.js b/test/test-bounded-sequences.js index 3bb655ff..d4ddf146 100644 --- a/test/test-bounded-sequences.js +++ b/test/test-bounded-sequences.js @@ -35,8 +35,8 @@ describe('Test bounded sequeces', function () { uint16_value: 1, int32_value: 1, uint32_value: 1, - int64_value: 2, - uint64_value: 2, + int64_value: 2n, + uint64_value: 2n, }; const msg = { @@ -51,10 +51,10 @@ describe('Test bounded sequeces', function () { uint16_values: Uint16Array.from([1, 2]), int32_values: Int32Array.from([1, 2]), uint32_values: Uint32Array.from([1, 2]), - int64_values: [1, 2], - uint64_values: [1, 2], - int64_values_default: [-1, 2, 3], - uint64_values_default: [1, 2, 3], + int64_values: [1n, 2n], + uint64_values: [1n, 2n], + int64_values_default: [0n, 9223372036854775807n, -9223372036854775808n], + uint64_values_default: [0n, 1n, 18446744073709551615n], string_values: ['hello', 'world'], basic_types_values: [primitives, primitives], alignment_check: 100, @@ -72,10 +72,10 @@ describe('Test bounded sequeces', function () { uint16_values: Uint16Array.from([1, 2]), int32_values: Int32Array.from([1, 2]), uint32_values: Uint32Array.from([1, 2]), - int64_values: [1, 2], - uint64_values: [1, 2], - int64_values_default: [-1, 2, 3], - uint64_values_default: [1, 2, 3], + int64_values: [1n, 2n], + uint64_values: [1n, 2n], + int64_values_default: [0n, 9223372036854775807n, -9223372036854775808n], + uint64_values_default: [0n, 1n, 18446744073709551615n], string_values: ['hello', 'world'], basic_types_values: [primitives, primitives], bool_values_default: [false, true, false], diff --git a/test/test-fixed-array.js b/test/test-fixed-array.js index a8f60caa..00427a7e 100644 --- a/test/test-fixed-array.js +++ b/test/test-fixed-array.js @@ -145,8 +145,8 @@ describe('Test message which has a fixed array of 36', function () { uint16_value: 1, int32_value: 1, uint32_value: 1, - int64_value: 2, - uint64_value: 2, + int64_value: 2n, + uint64_value: 2n, }; const defaultValue = { @@ -161,8 +161,8 @@ describe('Test message which has a fixed array of 36', function () { uint16_value: 2000, int32_value: -30000, uint32_value: 60000, - int64_value: -40000000, - uint64_value: 50000000, + int64_value: -40000000n, + uint64_value: 50000000n, }; const msg = { @@ -177,14 +177,14 @@ describe('Test message which has a fixed array of 36', function () { uint16_values: Uint16Array.from([1, 2, 3]), int32_values: Int32Array.from([1, 2, 3]), uint32_values: Uint32Array.from([1, 2, 3]), - int64_values: [1, 2, 3], - uint64_values: [1, 2, 3], + int64_values: [1n, 2n, 3n], + uint64_values: [1n, 2n, 3n], string_values: ['hello', 'world', 'abc'], basic_types_values: [bacicType, bacicType, bacicType], defaults_values: [defaultValue, defaultValue, defaultValue], // Use a string, '18446744073709551615', representing UINT64_MAX for ref, // see details https://github.com/node-ffi-napi/ref-napi/blob/latest/test/uint64.js#L17. - uint64_values_default: [0, 1, '18446744073709551615'], + uint64_values_default: [0, 1, 18446744073709551615n], alignment_check: 100, }; @@ -201,8 +201,8 @@ describe('Test message which has a fixed array of 36', function () { uint16_values: Uint16Array.from([1, 2, 3]), int32_values: Int32Array.from([1, 2, 3]), uint32_values: Uint32Array.from([1, 2, 3]), - int64_values: [1, 2, 3], - uint64_values: [1, 2, 3], + int64_values: [1n, 2n, 3n], + uint64_values: [1n, 2n, 3n], string_values: ['hello', 'world', 'abc'], basic_types_values: [bacicType, bacicType, bacicType], defaults_values: [defaultValue, defaultValue, defaultValue], @@ -217,8 +217,8 @@ describe('Test message which has a fixed array of 36', function () { uint16_values_default: Uint16Array.from([0, 1, 65535]), int32_values_default: Int32Array.from([0, 2147483647, -2147483648]), uint32_values_default: Uint32Array.from([0, 1, 4294967295]), - int64_values_default: [0, 9223372036854775807, -9223372036854775808], - uint64_values_default: [0, 1, '18446744073709551615'], + int64_values_default: [0n, 9223372036854775807n, -9223372036854775808n], + uint64_values_default: [0n, 1n, 18446744073709551615n], string_values_default: ['', 'max value', 'min value'], alignment_check: 100, constants_values: [], diff --git a/test/test-interactive.js b/test/test-interactive.js index 20c9a851..a124a768 100644 --- a/test/test-interactive.js +++ b/test/test-interactive.js @@ -61,9 +61,9 @@ describe('rclnodejs interactive testing', function () { 'add_two_ints', (request, response) => { assert.ok('a' in request); - assert.deepStrictEqual(typeof request.a, 'number'); + assert.deepStrictEqual(typeof request.a, 'bigint'); assert.ok('b' in request); - assert.deepStrictEqual(typeof request.b, 'number'); + assert.deepStrictEqual(typeof request.b, 'bigint'); let result = response.template; result.sum = request.a + request.b; response.send(result); diff --git a/test/test-message-translator-primitive.js b/test/test-message-translator-primitive.js index e6646ef9..34575b65 100644 --- a/test/test-message-translator-primitive.js +++ b/test/test-message-translator-primitive.js @@ -46,17 +46,16 @@ describe('Rclnodejs message translation: primitive types', function () { { type: 'Int64', values: [ - -32768, - -2, - -1, - 0, - 1, - 2, - 3, - 32767, - 2147483648, - 4294967295, - Number.MAX_SAFE_INTEGER, + -32768n, + -2n, + -1n, + 0n, + 1n, + 2n, + 3n, + 32767n, + 2147483648n, + 4294967295n, ], }, { type: 'Int8', values: [-128, -127, -2, -1, 0, 1, 2, 3, 127] }, @@ -68,17 +67,7 @@ describe('Rclnodejs message translation: primitive types', function () { }, { type: 'UInt64', - values: [ - 0, - 1, - 2, - 3, - 32767, - 65535, - 2147483648, - 4294967295, - Number.MAX_SAFE_INTEGER, - ], + values: [0n, 1n, 2n, 3n, 32767n, 65535n, 2147483648n, 4294967295n], }, { type: 'UInt8', values: [0, 1, 2, 3, 127, 255] }, ].forEach((testData) => { @@ -137,110 +126,110 @@ describe('Rclnodejs message translation: primitive types', function () { }); }); -describe('Rclnodejs message translation: primitive types array', function () { - this.timeout(60 * 1000); +// describe('Rclnodejs message translation: primitive types array', function () { +// this.timeout(60 * 1000); - before(function () { - return rclnodejs.init(); - }); +// before(function () { +// return rclnodejs.init(); +// }); - after(function () { - rclnodejs.shutdown(); - }); +// after(function () { +// rclnodejs.shutdown(); +// }); - [ - { type: 'ByteMultiArray', values: [0, 1, 2, 3, 255] }, - { - type: 'Float32MultiArray', - values: [ - -5, - 0, - 1.25, - 89.75, - 72.5, - 3.141592e8, - Number.POSITIVE_INFINITY, - Number.NEGATIVE_INFINITY, - ], - }, - { - type: 'Float64MultiArray', - values: [ - -5, - 0, - 1.25, - 89.75, - 72.5, - 3.141592e8, - Number.POSITIVE_INFINITY, - Number.NEGATIVE_INFINITY, - ], - }, - { type: 'Int8MultiArray', values: [-128, -127, -2, -1, 0, 1, 2, 3, 127] }, - { type: 'Int16MultiArray', values: [-32768, -2, -1, 0, 1, 2, 3, 32767] }, - { - type: 'Int32MultiArray', - values: [ - -2147483648, -2147483647, -32768, -2, -1, 0, 1, 2, 3, 32767, 2147483647, - ], - }, - { - type: 'Int64MultiArray', - values: [ - -Number.MAX_SAFE_INTEGER, - -32768, - -2, - -1, - 0, - 1, - 2, - 3, - 32767, - Number.MAX_SAFE_INTEGER, - ], - }, - { type: 'UInt8MultiArray', values: [0, 1, 2, 3, 127, 255] }, - { type: 'UInt16MultiArray', values: [0, 1, 2, 3, 32767, 65535] }, - { - type: 'UInt32MultiArray', - values: [0, 1, 2, 3, 32767, 65535, 4294967294, 4294967295], - }, - { - type: 'UInt64MultiArray', - values: [0, 1, 2, 3, 32767, 65535, Number.MAX_SAFE_INTEGER], - }, - ].forEach((testData) => { - const topic = testData.topic || 'topic' + testData.type; - it( - 'Test translation of ' + testData.type + ' msg, value ' + testData.values, - function () { - const node = rclnodejs.createNode('test_message_translation_node'); - const MessageType = 'std_msgs/msg/' + testData.type; - const publisher = node.createPublisher(MessageType, topic); - return new Promise((resolve, reject) => { - const sub = node.createSubscription(MessageType, topic, (value) => { - // For primitive types, msgs are defined as a single `.data` field - if (deepEqual(value.data, testData.values)) { - node.destroy(); - resolve(); - } else { - node.destroy(); - reject('Expected: ' + testData.values + ', Got: ' + value.data); - } - }); - publisher.publish({ - layout: { - dim: [{ label: 'length', size: 0, stride: 0 }], - data_offset: 0, - }, - data: testData.values, - }); - rclnodejs.spin(node); - }); - } - ); - }); -}); +// [ +// { type: 'ByteMultiArray', values: [0, 1, 2, 3, 255] }, +// { +// type: 'Float32MultiArray', +// values: [ +// -5, +// 0, +// 1.25, +// 89.75, +// 72.5, +// 3.141592e8, +// Number.POSITIVE_INFINITY, +// Number.NEGATIVE_INFINITY, +// ], +// }, +// { +// type: 'Float64MultiArray', +// values: [ +// -5, +// 0, +// 1.25, +// 89.75, +// 72.5, +// 3.141592e8, +// Number.POSITIVE_INFINITY, +// Number.NEGATIVE_INFINITY, +// ], +// }, +// { type: 'Int8MultiArray', values: [-128, -127, -2, -1, 0, 1, 2, 3, 127] }, +// { type: 'Int16MultiArray', values: [-32768, -2, -1, 0, 1, 2, 3, 32767] }, +// { +// type: 'Int32MultiArray', +// values: [ +// -2147483648, -2147483647, -32768, -2, -1, 0, 1, 2, 3, 32767, 2147483647, +// ], +// }, +// { +// type: 'Int64MultiArray', +// values: [ +// -Number.MAX_SAFE_INTEGER, +// -32768, +// -2, +// -1, +// 0, +// 1, +// 2, +// 3, +// 32767, +// Number.MAX_SAFE_INTEGER, +// ], +// }, +// { type: 'UInt8MultiArray', values: [0, 1, 2, 3, 127, 255] }, +// { type: 'UInt16MultiArray', values: [0, 1, 2, 3, 32767, 65535] }, +// { +// type: 'UInt32MultiArray', +// values: [0, 1, 2, 3, 32767, 65535, 4294967294, 4294967295], +// }, +// { +// type: 'UInt64MultiArray', +// values: [0, 1, 2, 3, 32767, 65535, Number.MAX_SAFE_INTEGER], +// }, +// ].forEach((testData) => { +// const topic = testData.topic || 'topic' + testData.type; +// it( +// 'Test translation of ' + testData.type + ' msg, value ' + testData.values, +// function () { +// const node = rclnodejs.createNode('test_message_translation_node'); +// const MessageType = 'std_msgs/msg/' + testData.type; +// const publisher = node.createPublisher(MessageType, topic); +// return new Promise((resolve, reject) => { +// const sub = node.createSubscription(MessageType, topic, (value) => { +// // For primitive types, msgs are defined as a single `.data` field +// if (deepEqual(value.data, testData.values)) { +// node.destroy(); +// resolve(); +// } else { +// node.destroy(); +// reject('Expected: ' + testData.values + ', Got: ' + value.data); +// } +// }); +// publisher.publish({ +// layout: { +// dim: [{ label: 'length', size: 0, stride: 0 }], +// data_offset: 0, +// }, +// data: testData.values, +// }); +// rclnodejs.spin(node); +// }); +// } +// ); +// }); +// }); // describe('Rclnodejs message translation: primitive types array - exception', function() { // this.timeout(60 * 1000); diff --git a/test/test-message-type.js b/test/test-message-type.js index 5034a35e..2098efcb 100644 --- a/test/test-message-type.js +++ b/test/test-message-type.js @@ -271,11 +271,8 @@ describe('Rclnodejs message type testing', function () { 'Int64_channel', (msg) => { publisher.kill('SIGINT'); - // assert.deepStrictEqual(typeof msg.data, 'string'); - assert.deepStrictEqual( - parseInt(msg.data, 10), - Number.MAX_SAFE_INTEGER - ); + assert.deepStrictEqual(typeof msg.data, 'bigint'); + assert.deepStrictEqual(msg.data, BigInt('0x1fffffffffffff')); done(); } ); @@ -294,11 +291,8 @@ describe('Rclnodejs message type testing', function () { 'UInt64_channel', (msg) => { publisher.kill('SIGINT'); - // assert.deepStrictEqual(typeof msg.data, 'string'); - assert.deepStrictEqual( - parseInt(msg.data, 10), - Number.MAX_SAFE_INTEGER - ); + assert.deepStrictEqual(typeof msg.data, 'bigint'); + assert.deepStrictEqual(msg.data, BigInt('0x1fffffffffffff')); done(); } ); diff --git a/test/test-parameter-service.js b/test/test-parameter-service.js index 507b0a6e..522a9fa5 100644 --- a/test/test-parameter-service.js +++ b/test/test-parameter-service.js @@ -71,7 +71,7 @@ describe('Parameter_server tests', function () { ) ); node.declareParameter( - new Parameter('p2', ParameterType.PARAMETER_INTEGER, 123), + new Parameter('p2', ParameterType.PARAMETER_INTEGER, 123n), new ParameterDescriptor('p2', ParameterType.PARAMETER_INTEGER) ); node.declareParameter( @@ -84,7 +84,11 @@ describe('Parameter_server tests', function () { ) ); node.declareParameter( - new Parameter('A.B.p4', ParameterType.PARAMETER_INTEGER_ARRAY, [1, 2, 3]), + new Parameter('A.B.p4', ParameterType.PARAMETER_INTEGER_ARRAY, [ + 1n, + 2n, + 3n, + ]), new ParameterDescriptor( 'A.B.p4', ParameterType.PARAMETER_INTEGER_ARRAY, @@ -115,7 +119,7 @@ describe('Parameter_server tests', function () { const request = new (rclnodejs.require( 'rcl_interfaces/srv/ListParameters' ).Request)(); - request.depth = 1; + request.depth = 1n; request.prefixes = []; let success = false; @@ -141,7 +145,7 @@ describe('Parameter_server tests', function () { const request = new (rclnodejs.require( 'rcl_interfaces/srv/ListParameters' ).Request)(); - request.depth = 2; + request.depth = 2n; request.prefixes = ['A']; let success = false; @@ -168,7 +172,7 @@ describe('Parameter_server tests', function () { const request = new (rclnodejs.require( 'rcl_interfaces/srv/ListParameters' ).Request)(); - request.depth = 3; + request.depth = 3n; request.prefixes = ['A']; let success = false; diff --git a/test/test-parameters.js b/test/test-parameters.js index 056d4a1a..eaf4892c 100644 --- a/test/test-parameters.js +++ b/test/test-parameters.js @@ -92,12 +92,12 @@ describe('rclnodejs parameters test suite', function () { let param = new Parameter( 'int_param', ParameterType.PARAMETER_INTEGER, - 100 + 100n ); - assert.strictEqual(param.value, 100); + assert.strictEqual(param.value, 100n); - param.value = 101; - assert.strictEqual(param.value, 101); + param.value = 101n; + assert.strictEqual(param.value, 101n); assertThrowsError(() => (param.value = 'hello world'), TypeError); }); @@ -211,7 +211,7 @@ describe('rclnodejs parameters test suite', function () { ))(); param_msg.name = 'integer_param'; param_msg.value.type = ParameterType.PARAMETER_INTEGER; - param_msg.value.integer_value = 123; + param_msg.value.integer_value = 123n; let param; assert.doesNotThrow(() => { @@ -219,7 +219,7 @@ describe('rclnodejs parameters test suite', function () { }); assert.strictEqual(param.name, 'integer_param'); assert.strictEqual(param.type, ParameterType.PARAMETER_INTEGER); - assert.strictEqual(param.value, 123); + assert.strictEqual(param.value, 123n); }); it('should convert PARAMETER_INTEGER_ARRAY type', function () { @@ -228,7 +228,7 @@ describe('rclnodejs parameters test suite', function () { ))(); param_msg.name = 'integer_array_param'; param_msg.value.type = ParameterType.PARAMETER_INTEGER_ARRAY; - param_msg.value.integer_array_value = [1, 2, 3, 4, 5]; + param_msg.value.integer_array_value = [1n, 2n, 3n, 4n, 5n]; let param; assert.doesNotThrow(() => { @@ -236,7 +236,7 @@ describe('rclnodejs parameters test suite', function () { }); assert.strictEqual(param.name, 'integer_array_param'); assert.strictEqual(param.type, ParameterType.PARAMETER_INTEGER_ARRAY); - assert.deepStrictEqual(param.value, [1, 2, 3, 4, 5]); + assert.deepStrictEqual(param.value, [1n, 2n, 3n, 4n, 5n]); }); it('should convert PARAMETER_STRING type', function () { @@ -282,25 +282,25 @@ describe('rclnodejs parameters test suite', function () { }); it('IntegerRange test', function () { - const range = new IntegerRange(0, 100, 1); + const range = new IntegerRange(0n, 100n, 1n); // test property accessors - assert.equal(range.fromValue, 0); - assert.equal(range.toValue, 100); - assert.equal(range.step, 1); + assert.equal(range.fromValue, 0n); + assert.equal(range.toValue, 100n); + assert.equal(range.step, 1n); // test midpoint - assert.ok(range.inRange(50)); + assert.ok(range.inRange(50n)); // test lower boundary - assert.ok(range.inRange(1)); - assert.ok(range.inRange(0)); - assert.ok(!range.inRange(-1)); + assert.ok(range.inRange(1n)); + assert.ok(range.inRange(0n)); + assert.ok(!range.inRange(-1n)); // test upper boundary - assert.ok(range.inRange(99)); - assert.ok(range.inRange(100)); - assert.ok(!range.inRange(101)); + assert.ok(range.inRange(99n)); + assert.ok(range.inRange(100n)); + assert.ok(!range.inRange(101n)); }); it('FloatingPointRange test', function () { @@ -345,22 +345,22 @@ describe('rclnodejs parameters test suite', function () { 'int_param', ParameterType.PARAMETER_INTEGER ); - descriptor.range = new IntegerRange(0, 255); + descriptor.range = new IntegerRange(0n, 255n); const param = new Parameter( 'int_param', ParameterType.PARAMETER_INTEGER, - 100 + 100n ); assert.ifError(descriptor.validateParameter(param)); - param.value = 255; + param.value = 255n; assert.ifError(descriptor.validateParameter(param)); - param.value = -1; + param.value = -1n; assertThrowsError(() => descriptor.validateParameter(param), RangeError); - param.value = 256; + param.value = 256n; assertThrowsError(() => descriptor.validateParameter(param), RangeError); }); @@ -369,22 +369,22 @@ describe('rclnodejs parameters test suite', function () { 'int_param', ParameterType.PARAMETER_INTEGER ); - descriptor.range = new IntegerRange(0, 255, 5); + descriptor.range = new IntegerRange(0n, 255n, 5n); const param = new Parameter( 'int_param', ParameterType.PARAMETER_INTEGER, - 100 + 100n ); assert.ifError(descriptor.validateParameter(param)); - param.value = 255; + param.value = 255n; assert.ifError(descriptor.validateParameter(param)); - param.value = 1; + param.value = 1n; assertThrowsError(() => descriptor.validateParameter(param), RangeError); - param.value = 256; + param.value = 256n; assertThrowsError(() => descriptor.validateParameter(param), RangeError); }); }); diff --git a/test/test-primitive-msg-type-check.js b/test/test-primitive-msg-type-check.js index ea112f24..800cb34c 100644 --- a/test/test-primitive-msg-type-check.js +++ b/test/test-primitive-msg-type-check.js @@ -301,9 +301,9 @@ describe('Rclnodejs message type data testing', function () { var msgInt64 = rclnodejs.require('std_msgs').msg.Int64; var msg = new msgInt64(); - msg.data = 0x1fffffffffffff; - assert.deepStrictEqual(typeof msg.data, 'number'); - assert.deepStrictEqual(msg.data, Number.MAX_SAFE_INTEGER); + msg.data = BigInt('0x1fffffffffffff'); + assert.deepStrictEqual(typeof msg.data, 'bigint'); + assert.deepStrictEqual(msg.data, BigInt(Number.MAX_SAFE_INTEGER)); }); it('UInt64 data checking', function () { @@ -311,9 +311,9 @@ describe('Rclnodejs message type data testing', function () { var msgUInt64 = rclnodejs.require('std_msgs').msg.UInt64; var msg = new msgUInt64(); - msg.data = 0x1fffffffffffff; - assert.deepStrictEqual(typeof msg.data, 'number'); - assert.deepStrictEqual(msg.data, Number.MAX_SAFE_INTEGER); + msg.data = BigInt('0x1fffffffffffff'); + assert.deepStrictEqual(typeof msg.data, 'bigint'); + assert.deepStrictEqual(msg.data, BigInt(Number.MAX_SAFE_INTEGER)); }); it('Float32 data checking', function () { diff --git a/test/test-security-related.js b/test/test-security-related.js index 319238d4..670920db 100644 --- a/test/test-security-related.js +++ b/test/test-security-related.js @@ -276,7 +276,7 @@ describe('Fuzzing API calls testing', function () { done(); }); - let request = { a: 1, b: 2 }; + let request = { a: 1n, b: 2n }; client.sendRequest(request, (response) => { throw new Error('never reached'); }); diff --git a/test/test-service-introspection.js b/test/test-service-introspection.js index f6801393..60de54f0 100644 --- a/test/test-service-introspection.js +++ b/test/test-service-introspection.js @@ -86,8 +86,8 @@ describe('service introspection', function () { } this.request = { - a: Math.floor(Math.random() * 100), - b: Math.floor(Math.random() * 100), + a: BigInt(Math.floor(Math.random() * 100)), + b: BigInt(Math.floor(Math.random() * 100)), }; this.eventQueue = []; diff --git a/test/test-service-with-async-callback.js b/test/test-service-with-async-callback.js index ad549bbe..7c01a5b0 100644 --- a/test/test-service-with-async-callback.js +++ b/test/test-service-with-async-callback.js @@ -38,8 +38,8 @@ describe('Test creating a service with an async callback', function () { AddTwoInts, 'single_ps_channel2', async (request, response) => { - assert.deepStrictEqual(request.a, 1); - assert.deepStrictEqual(request.b, 2); + assert.deepStrictEqual(request.a, 1n); + assert.deepStrictEqual(request.b, 2n); let result = response.template; result.sum = request.a + request.b; // to trigger the bug, two conditions must hold: @@ -53,12 +53,12 @@ describe('Test creating a service with an async callback', function () { } ); var client = clientNode.createClient(AddTwoInts, 'single_ps_channel2'); - const request = { a: 1, b: 2 }; + const request = { a: 1n, b: 2n }; var timer = clientNode.createTimer(100, () => { client.sendRequest(request, (response) => { timer.cancel(); - assert.deepStrictEqual(response.sum, 3); + assert.deepStrictEqual(response.sum, 3n); serviceNode.destroy(); clientNode.destroy(); done(); diff --git a/test/test-single-process.js b/test/test-single-process.js index 223a64eb..ac68e9ea 100644 --- a/test/test-single-process.js +++ b/test/test-single-process.js @@ -91,20 +91,20 @@ describe('Test rclnodejs nodes in a single process', function () { AddTwoInts, 'single_ps_channel2', (request, response) => { - assert.deepStrictEqual(request.a, 1); - assert.deepStrictEqual(request.b, 2); + assert.deepStrictEqual(request.a, 1n); + assert.deepStrictEqual(request.b, 2n); let result = response.template; result.sum = request.a + request.b; return result; } ); var client = clientNode.createClient(AddTwoInts, 'single_ps_channel2'); - const request = { a: 1, b: 2 }; + const request = { a: 1n, b: 2n }; var timer = clientNode.createTimer(100, () => { client.sendRequest(request, (response) => { timer.cancel(); - assert.deepStrictEqual(response.sum, 3); + assert.deepStrictEqual(response.sum, 3n); serviceNode.destroy(); clientNode.destroy(); done(); @@ -123,20 +123,20 @@ describe('Test rclnodejs nodes in a single process', function () { AddTwoInts, 'single_ps_channel2_2', (request, response) => { - assert.deepStrictEqual(request.a, 1); - assert.deepStrictEqual(request.b, 2); + assert.deepStrictEqual(request.a, 1n); + assert.deepStrictEqual(request.b, 2n); let result = response.template; result.sum = request.a + request.b; response.send(result); } ); var client = clientNode.createClient(AddTwoInts, 'single_ps_channel2_2'); - const request = { a: 1, b: 2 }; + const request = { a: 1n, b: 2n }; var timer = clientNode.createTimer(100, () => { client.sendRequest(request, (response) => { timer.cancel(); - assert.deepStrictEqual(response.sum, 3); + assert.deepStrictEqual(response.sum, 3n); serviceNode.destroy(); clientNode.destroy(); done(); @@ -155,18 +155,18 @@ describe('Test rclnodejs nodes in a single process', function () { AddTwoInts, 'single_ps_channel2_3', (request, response) => { - assert.deepStrictEqual(request.a, 1); - assert.deepStrictEqual(request.b, 2); + assert.deepStrictEqual(request.a, 1n); + assert.deepStrictEqual(request.b, 2n); response.send({ sum: request.a + request.b }); } ); var client = clientNode.createClient(AddTwoInts, 'single_ps_channel2_3'); - const request = { a: 1, b: 2 }; + const request = { a: 1n, b: 2n }; var timer = clientNode.createTimer(100, () => { client.sendRequest(request, (response) => { timer.cancel(); - assert.deepStrictEqual(response.sum, 3); + assert.deepStrictEqual(response.sum, 3n); serviceNode.destroy(); clientNode.destroy(); done(); @@ -184,8 +184,8 @@ describe('Test rclnodejs nodes in a single process', function () { AddTwoInts, 'new_style_require2', (request, response) => { - assert.deepStrictEqual(request.a, 1); - assert.deepStrictEqual(request.b, 2); + assert.deepStrictEqual(request.a, 1n); + assert.deepStrictEqual(request.b, 2n); let result = response.template; result.sum = request.a + request.b; return result; @@ -193,13 +193,13 @@ describe('Test rclnodejs nodes in a single process', function () { ); var client = node.createClient(AddTwoInts, 'new_style_require2'); let request = new AddTwoInts.Request(); - request.a = 1; - request.b = 2; + request.a = 1n; + request.b = 2n; var timer = node.createTimer(100, () => { client.sendRequest(request, (response) => { timer.cancel(); - assert.deepStrictEqual(response.sum, 3); + assert.deepStrictEqual(response.sum, 3n); node.destroy(); done(); }); @@ -225,11 +225,11 @@ describe('Test rclnodejs nodes in a single process', function () { ); var client = node.createClient(AddTwoInts, 'multi_request_service'); let request = new AddTwoInts.Request(); - request.a = 1; - request.b = 2; + request.a = 1n; + request.b = 2n; let request2 = new AddTwoInts.Request(); - request2.a = 3; - request2.b = 4; + request2.a = 3n; + request2.b = 4n; let pendingRequests = 2; let request1Succeeded = false; @@ -241,7 +241,7 @@ describe('Test rclnodejs nodes in a single process', function () { logger.info( `[Callback 1] received response: ${response.sum}, pending requests: ${pendingRequests}` ); - assert.deepStrictEqual(response.sum, 3); + assert.deepStrictEqual(response.sum, 3n); request1Succeeded = true; if (pendingRequests == 0 && request1Succeeded && request2Succeeded) { logger.info( @@ -257,7 +257,7 @@ describe('Test rclnodejs nodes in a single process', function () { logger.info( `[Callback 2] received response: ${response.sum}, pending requests: ${pendingRequests}` ); - assert.deepStrictEqual(response.sum, 7); + assert.deepStrictEqual(response.sum, 7n); request2Succeeded = true; if (pendingRequests == 0 && request1Succeeded && request2Succeeded) { logger.info( From 28fda83f31912549d24bda6f3600906a68d031b2 Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Thu, 13 Feb 2025 14:37:45 +0800 Subject: [PATCH 2/7] Cleanup implementation --- lib/parameter.js | 2 +- rosidl_gen/templates/message.dot | 9 +- rosidl_parser/rosidl_parser.js | 10 +- test/client_setup.js | 3 - test/publisher_msg.js | 1 - test/test-fixed-array.js | 2 - test/test-message-translator-primitive.js | 323 ++++++++-------------- 7 files changed, 124 insertions(+), 226 deletions(-) diff --git a/lib/parameter.js b/lib/parameter.js index 4709d5ea..b5181a23 100644 --- a/lib/parameter.js +++ b/lib/parameter.js @@ -127,7 +127,7 @@ class Parameter { constructor(name, type, value) { this._name = name; this._type = type; - // Convert to bigint if it's type of integer. + // Convert to bigint if it's type of `PARAMETER_INTEGER`. this._value = this._type == ParameterType.PARAMETER_INTEGER ? BigInt(value) : value; this._isDirty = true; diff --git a/rosidl_gen/templates/message.dot b/rosidl_gen/templates/message.dot index 546e34f3..e8f228e3 100644 --- a/rosidl_gen/templates/message.dot +++ b/rosidl_gen/templates/message.dot @@ -308,8 +308,10 @@ class {{=objectWrapper}} { {{?}} {{?? field.type.isPrimitiveType && !isTypedArrayType(field.type) && field.default_value}} {{? isBigInt(field.type)}} + {{/* For non-TypedArray like int64/uint64. */}} this._{{=field.name}}Array = {{=JSON.stringify(field.default_value)}}.map(num => BigInt(num)); {{??}} + {{/* For non-TypedArray like bool. */}} this._{{=field.name}}Array = {{=JSON.stringify(field.default_value)}}; {{?}} {{?? field.type.isPrimitiveType && isTypedArrayType(field.type) && field.default_value}} @@ -574,14 +576,9 @@ class {{=objectWrapper}} { this._wrapperFields.{{=field.name}}.data = value; {{?? isBigInt(field.type)}} if (typeof value !== "bigint") { - throw new TypeError('Must be type of bigint'); + throw new TypeError('int64/uint64 must be type of bigint'); } this._refObject.{{=field.name}} = value.toString(); - /*if (typeof value === "bigint") { - this._refObject.{{=field.name}} = value.toString(); - } else { - this._refObject.{{=field.name}} = value - }*/ {{??}} {{? it.spec.msgName === 'String'}} this._refObject.size = Buffer.byteLength(value); diff --git a/rosidl_parser/rosidl_parser.js b/rosidl_parser/rosidl_parser.js index 8971db4c..50a4d672 100644 --- a/rosidl_parser/rosidl_parser.js +++ b/rosidl_parser/rosidl_parser.js @@ -20,10 +20,11 @@ const execFile = require('child_process').execFile; const pythonExecutable = require('./py_utils').getPythonExecutable('python3'); -const version = process.version; +const contextSupportedVersion = '21.0.0.0'; +const currentVersion = process.version; const isContextSupported = compareVersions.compare( - version.substring(1, version.length), - '21.0.0.0', + currentVersion.substring(1, currentVersion.length), + contextSupportedVersion, '>=' ); @@ -41,6 +42,9 @@ const rosidlParser = { }, _parseJSONObject(str) { + // For nodejs >= `contextSupportedVersion`, we leverage context parameter to + // convert unsafe integer to string, otherwise, json-bigint is used. + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse if (isContextSupported) { return JSON.parse(str, (key, value, context) => { if ( diff --git a/test/client_setup.js b/test/client_setup.js index f2dbc836..03eb2deb 100644 --- a/test/client_setup.js +++ b/test/client_setup.js @@ -14,7 +14,6 @@ 'use strict'; -const assert = require('assert'); const rclnodejs = require('../index.js'); rclnodejs @@ -31,8 +30,6 @@ rclnodejs var publisher = node.createPublisher(Int8, 'back_add_two_ints'); client.waitForService().then(() => { client.sendRequest(request, (response) => { - // Conver `response.sum` from bigint to number. - assert.equal(typeof response.sum, 'bigint'); publisher.publish(Number(response.sum)); }); }); diff --git a/test/publisher_msg.js b/test/publisher_msg.js index 4f55f9c6..e905db7e 100644 --- a/test/publisher_msg.js +++ b/test/publisher_msg.js @@ -21,7 +21,6 @@ var rclValue = eval(process.argv[3]); if (['int64', 'uint64'].indexOf(rclType.toLowerCase()) !== -1) { rclValue = BigInt(rclValue); - 0; } rclnodejs diff --git a/test/test-fixed-array.js b/test/test-fixed-array.js index 00427a7e..647260bd 100644 --- a/test/test-fixed-array.js +++ b/test/test-fixed-array.js @@ -182,8 +182,6 @@ describe('Test message which has a fixed array of 36', function () { string_values: ['hello', 'world', 'abc'], basic_types_values: [bacicType, bacicType, bacicType], defaults_values: [defaultValue, defaultValue, defaultValue], - // Use a string, '18446744073709551615', representing UINT64_MAX for ref, - // see details https://github.com/node-ffi-napi/ref-napi/blob/latest/test/uint64.js#L17. uint64_values_default: [0, 1, 18446744073709551615n], alignment_check: 100, }; diff --git a/test/test-message-translator-primitive.js b/test/test-message-translator-primitive.js index 34575b65..ab093e4c 100644 --- a/test/test-message-translator-primitive.js +++ b/test/test-message-translator-primitive.js @@ -55,7 +55,7 @@ describe('Rclnodejs message translation: primitive types', function () { 3n, 32767n, 2147483648n, - 4294967295n, + BigInt(Number.MAX_SAFE_INTEGER) + 1n, ], }, { type: 'Int8', values: [-128, -127, -2, -1, 0, 1, 2, 3, 127] }, @@ -67,7 +67,17 @@ describe('Rclnodejs message translation: primitive types', function () { }, { type: 'UInt64', - values: [0n, 1n, 2n, 3n, 32767n, 65535n, 2147483648n, 4294967295n], + values: [ + 0n, + 1n, + 2n, + 3n, + 32767n, + 65535n, + 2147483648n, + 4294967295n, + BigInt(Number.MAX_SAFE_INTEGER) + 1n, + ], }, { type: 'UInt8', values: [0, 1, 2, 3, 127, 255] }, ].forEach((testData) => { @@ -126,217 +136,110 @@ describe('Rclnodejs message translation: primitive types', function () { }); }); -// describe('Rclnodejs message translation: primitive types array', function () { -// this.timeout(60 * 1000); - -// before(function () { -// return rclnodejs.init(); -// }); - -// after(function () { -// rclnodejs.shutdown(); -// }); - -// [ -// { type: 'ByteMultiArray', values: [0, 1, 2, 3, 255] }, -// { -// type: 'Float32MultiArray', -// values: [ -// -5, -// 0, -// 1.25, -// 89.75, -// 72.5, -// 3.141592e8, -// Number.POSITIVE_INFINITY, -// Number.NEGATIVE_INFINITY, -// ], -// }, -// { -// type: 'Float64MultiArray', -// values: [ -// -5, -// 0, -// 1.25, -// 89.75, -// 72.5, -// 3.141592e8, -// Number.POSITIVE_INFINITY, -// Number.NEGATIVE_INFINITY, -// ], -// }, -// { type: 'Int8MultiArray', values: [-128, -127, -2, -1, 0, 1, 2, 3, 127] }, -// { type: 'Int16MultiArray', values: [-32768, -2, -1, 0, 1, 2, 3, 32767] }, -// { -// type: 'Int32MultiArray', -// values: [ -// -2147483648, -2147483647, -32768, -2, -1, 0, 1, 2, 3, 32767, 2147483647, -// ], -// }, -// { -// type: 'Int64MultiArray', -// values: [ -// -Number.MAX_SAFE_INTEGER, -// -32768, -// -2, -// -1, -// 0, -// 1, -// 2, -// 3, -// 32767, -// Number.MAX_SAFE_INTEGER, -// ], -// }, -// { type: 'UInt8MultiArray', values: [0, 1, 2, 3, 127, 255] }, -// { type: 'UInt16MultiArray', values: [0, 1, 2, 3, 32767, 65535] }, -// { -// type: 'UInt32MultiArray', -// values: [0, 1, 2, 3, 32767, 65535, 4294967294, 4294967295], -// }, -// { -// type: 'UInt64MultiArray', -// values: [0, 1, 2, 3, 32767, 65535, Number.MAX_SAFE_INTEGER], -// }, -// ].forEach((testData) => { -// const topic = testData.topic || 'topic' + testData.type; -// it( -// 'Test translation of ' + testData.type + ' msg, value ' + testData.values, -// function () { -// const node = rclnodejs.createNode('test_message_translation_node'); -// const MessageType = 'std_msgs/msg/' + testData.type; -// const publisher = node.createPublisher(MessageType, topic); -// return new Promise((resolve, reject) => { -// const sub = node.createSubscription(MessageType, topic, (value) => { -// // For primitive types, msgs are defined as a single `.data` field -// if (deepEqual(value.data, testData.values)) { -// node.destroy(); -// resolve(); -// } else { -// node.destroy(); -// reject('Expected: ' + testData.values + ', Got: ' + value.data); -// } -// }); -// publisher.publish({ -// layout: { -// dim: [{ label: 'length', size: 0, stride: 0 }], -// data_offset: 0, -// }, -// data: testData.values, -// }); -// rclnodejs.spin(node); -// }); -// } -// ); -// }); -// }); - -// describe('Rclnodejs message translation: primitive types array - exception', function() { -// this.timeout(60 * 1000); - -// before(function() { -// return rclnodejs.init(); -// }); - -// after(function() { -// rclnodejs.shutdown(); -// }); - -// [ -// {type: 'ByteMultiArray', values: [0, 1, 2, 3, 255, 256]}, -// {type: 'ByteMultiArray', values: [-1, 0, 1, 2, 3, 255]}, -// {type: 'ByteMultiArray', values: [-100, 0, 1, 2, 3, 255]}, -// {type: 'ByteMultiArray', values: [0, 1, 2, 3, 32767, Number.MAX_SAFE_INTEGER]}, -// {type: 'ByteMultiArray', values: [0, 1, 2, 3, 32767, Number.POSITIVE_INFINITY]}, - -// {type: 'Int8MultiArray', values: [-128, -127, -2, -1, 0, 1, 2, 3, 127, 128]}, -// {type: 'Int8MultiArray', values: [-128, -127, -2, -1, 0, 1, 2, 3, 127, 129]}, -// {type: 'Int8MultiArray', values: [-128, -127, -2, -1, 0, 1, 2, 3, 127, 1000]}, -// {type: 'Int8MultiArray', values: [-129, -127, -2, -1, 0, 1, 2, 3, 127]}, -// {type: 'Int8MultiArray', values: [-1000, -127, -2, -1, 0, 1, 2, 3, 127]}, -// {type: 'Int8MultiArray', values: [-Number.MAX_SAFE_INTEGER, -2, -1, 0, 1, 2, 3, 32767]}, -// {type: 'Int8MultiArray', values: [Number.NEGATIVE_INFINITY, -2, -1, 0, 1, 2, 3, 32767]}, - -// {type: 'Int16MultiArray', values: [-32768, -2, -1, 0, 1, 2, 3, 32767, 32768]}, -// {type: 'Int16MultiArray', values: [-32768, -2, -1, 0, 1, 2, 3, 32767, 32769]}, -// {type: 'Int16MultiArray', values: [-32768, -2, -1, 0, 1, 2, 3, 32767, 100000]}, -// {type: 'Int16MultiArray', values: [-32768, -2, -1, 0, 1, 2, 3, 32767, 1000000]}, -// {type: 'Int16MultiArray', values: [-32768, -2, -1, 0, 1, 2, 3, 32767, Number.MAX_SAFE_INTEGER]}, -// {type: 'Int16MultiArray', values: [-32768, -2, -1, 0, 1, 2, 3, 32767, Number.POSITIVE_INFINITY]}, -// {type: 'Int16MultiArray', values: [-32769, -2, -1, 0, 1, 2, 3, 32767]}, -// {type: 'Int16MultiArray', values: [-100000, -2, -1, 0, 1, 2, 3, 32767]}, -// {type: 'Int16MultiArray', values: [-Number.MAX_SAFE_INTEGER, -2, -1, 0, 1, 2, 3, 32767]}, -// {type: 'Int16MultiArray', values: [Number.NEGATIVE_INFINITY, -2, -1, 0, 1, 2, 3, 32767]}, - -// {type: 'Int32MultiArray', values: [-3, -2, -1, 0, 1, 2, 3, 32767, 2147483647, 2147483648]}, -// {type: 'Int32MultiArray', values: [-2147483649, -2, -1, 0, 1, 2, 3, 32767, 2147483647]}, -// {type: 'Int32MultiArray', values: [-3, -2, -1, 0, 1, 2, 3, 32767, 2147483647, Number.MAX_SAFE_INTEGER]}, -// {type: 'Int32MultiArray', values: [-3, -2, -1, 0, 1, 2, 3, 32767, 2147483647, Number.POSITIVE_INFINITY]}, -// {type: 'Int32MultiArray', values: [-Number.MAX_SAFE_INTEGER, -2, -1, 0, 1, 2, 3, 32767, 2147483647]}, -// {type: 'Int32MultiArray', values: [Number.NEGATIVE_INFINITY, -2, -1, 0, 1, 2, 3, 32767, 2147483647]}, +describe('Rclnodejs message translation: primitive types array', function () { + this.timeout(60 * 1000); -// {type: 'UInt8MultiArray', values: [0, 1, 2, 3, 127, 255, 256]}, -// {type: 'UInt8MultiArray', values: [0, 1, 2, 3, 127, 255, 257]}, -// {type: 'UInt8MultiArray', values: [0, 1, 2, 3, 127, 255, 1000]}, -// {type: 'UInt8MultiArray', values: [0, 1, 2, 3, 127, 255, 10000]}, -// {type: 'UInt8MultiArray', values: [0, 1, 2, 3, 127, 255, Number.MAX_SAFE_INTEGER]}, -// {type: 'UInt8MultiArray', values: [0, 1, 2, 3, 127, 255, Number.POSITIVE_INFINITY]}, -// {type: 'UInt8MultiArray', values: [-1, 1, 2, 3, 127, 255]}, -// {type: 'UInt8MultiArray', values: [-100, 1, 2, 3, 127, 255]}, -// {type: 'UInt8MultiArray', values: [-1000, 1, 2, 3, 127, 255]}, -// {type: 'UInt8MultiArray', values: [-Number.MAX_SAFE_INTEGER, -2, -1, 0, 1, 2, 3]}, -// {type: 'UInt8MultiArray', values: [Number.NEGATIVE_INFINITY, -2, -1, 0, 1, 2, 3]}, + before(function () { + return rclnodejs.init(); + }); -// {type: 'UInt16MultiArray', values: [0, 1, 2, 3, 32767, 65535, 65536]}, -// {type: 'UInt16MultiArray', values: [0, 1, 2, 3, 32767, 65535, 100000]}, -// {type: 'UInt16MultiArray', values: [0, 1, 2, 3, 32767, 65535, 1000000]}, -// {type: 'UInt16MultiArray', values: [0, 1, 2, 3, 32767, 65535, Number.MAX_SAFE_INTEGER]}, -// {type: 'UInt16MultiArray', values: [0, 1, 2, 3, 32767, 65535, Number.POSITIVE_INFINITY]}, -// {type: 'UInt16MultiArray', values: [-1, 1, 2, 3, 32767, 65535]}, -// {type: 'UInt16MultiArray', values: [-10000, 1, 2, 3, 32767, 65535]}, -// {type: 'UInt16MultiArray', values: [-Number.MAX_SAFE_INTEGER, 1, 2, 3, 32767, 65535]}, -// {type: 'UInt16MultiArray', values: [Number.NEGATIVE_INFINITY, 1, 2, 3, 32767, 65535]}, + after(function () { + rclnodejs.shutdown(); + }); -// {type: 'UInt32MultiArray', values: [0, 1, 2, 3, 32767, 65535, 4294967296]}, -// {type: 'UInt32MultiArray', values: [0, 1, 2, 3, 32767, 65535, 4294967297]}, -// {type: 'UInt32MultiArray', values: [0, 1, 2, 3, 32767, 65535, 10000000000]}, -// {type: 'UInt32MultiArray', values: [0, 1, 2, 3, 32767, 65535, 100000000000]}, -// {type: 'UInt32MultiArray', values: [0, 1, 2, 3, 32767, 65535, Number.MAX_SAFE_INTEGER]}, -// {type: 'UInt32MultiArray', values: [0, 1, 2, 3, 32767, 65535, Number.POSITIVE_INFINITY]}, -// {type: 'UInt32MultiArray', values: [-1, 1, 2, 3, 32767, 65535, 4294967295]}, -// {type: 'UInt32MultiArray', values: [-2, 1, 2, 3, 32767, 65535, 4294967295]}, -// {type: 'UInt32MultiArray', values: [-10000, 1, 2, 3, 32767, 65535, 4294967295]}, -// {type: 'UInt32MultiArray', values: [-Number.MAX_SAFE_INTEGER, 1, 2, 3, 32767, 65535, 4294967295]}, -// ].forEach((testData) => { -// const topic = testData.topic || 'topic' + testData.type; -// const testCaseName = 'Test translation of ' + testData.type + -// ' msg, value ' + testData.values + ' (exception)'; -// it(testCaseName, function() { -// const node = rclnodejs.createNode('test_message_translation_node'); -// const MessageType = 'std_msgs/msg/' + testData.type; -// const publisher = node.createPublisher(MessageType, topic); -// return new Promise((resolve, reject) => { -// try { -// publisher.publish({ -// layout: { -// dim: [ -// {label: 'length', size: 0, stride: 0}, -// ], -// data_offset: 0, -// }, -// data: testData.values, -// }); -// node.destroy(); -// reject('Exception is expected for: ' + testData.values); -// } catch (e) { -// node.destroy(); -// resolve(); -// } -// rclnodejs.spin(node); -// }); -// }); -// }); -// }); + [ + { type: 'ByteMultiArray', values: [0, 1, 2, 3, 255] }, + { + type: 'Float32MultiArray', + values: [ + -5, + 0, + 1.25, + 89.75, + 72.5, + 3.141592e8, + Number.POSITIVE_INFINITY, + Number.NEGATIVE_INFINITY, + ], + }, + { + type: 'Float64MultiArray', + values: [ + -5, + 0, + 1.25, + 89.75, + 72.5, + 3.141592e8, + Number.POSITIVE_INFINITY, + Number.NEGATIVE_INFINITY, + ], + }, + { type: 'Int8MultiArray', values: [-128, -127, -2, -1, 0, 1, 2, 3, 127] }, + { type: 'Int16MultiArray', values: [-32768, -2, -1, 0, 1, 2, 3, 32767] }, + { + type: 'Int32MultiArray', + values: [ + -2147483648, -2147483647, -32768, -2, -1, 0, 1, 2, 3, 32767, 2147483647, + ], + }, + { + type: 'Int64MultiArray', + values: [ + BigInt(-Number.MAX_SAFE_INTEGER), + -32768n, + -2n, + -1n, + 0n, + 1n, + 2n, + 3n, + 32767n, + BigInt(Number.MAX_SAFE_INTEGER), + ], + }, + { type: 'UInt8MultiArray', values: [0, 1, 2, 3, 127, 255] }, + { type: 'UInt16MultiArray', values: [0, 1, 2, 3, 32767, 65535] }, + { + type: 'UInt32MultiArray', + values: [0, 1, 2, 3, 32767, 65535, 4294967294, 4294967295], + }, + { + type: 'UInt64MultiArray', + values: [0n, 1n, 2n, 3n, 32767n, 65535n, BigInt(Number.MAX_SAFE_INTEGER)], + }, + ].forEach((testData) => { + const topic = testData.topic || 'topic' + testData.type; + it( + 'Test translation of ' + testData.type + ' msg, value ' + testData.values, + function () { + const node = rclnodejs.createNode('test_message_translation_node'); + const MessageType = 'std_msgs/msg/' + testData.type; + const publisher = node.createPublisher(MessageType, topic); + return new Promise((resolve, reject) => { + const sub = node.createSubscription(MessageType, topic, (value) => { + // For primitive types, msgs are defined as a single `.data` field + if (deepEqual(value.data, testData.values)) { + node.destroy(); + resolve(); + } else { + node.destroy(); + reject('Expected: ' + testData.values + ', Got: ' + value.data); + } + }); + publisher.publish({ + layout: { + dim: [{ label: 'length', size: 0, stride: 0 }], + data_offset: 0, + }, + data: testData.values, + }); + rclnodejs.spin(node); + }); + } + ); + }); +}); describe('Rclnodejs message translation: TypedArray large data', function () { this.timeout(60 * 1000); From 2d9b1302290556b0dd8fd590080de3ba396575ec Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Thu, 13 Feb 2025 16:49:06 +0800 Subject: [PATCH 3/7] Update the ts for paramter interface --- types/parameter.d.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/types/parameter.d.ts b/types/parameter.d.ts index 6f02905a..7794959b 100644 --- a/types/parameter.d.ts +++ b/types/parameter.d.ts @@ -261,16 +261,15 @@ declare module 'rclnodejs' { /** * */ - class IntegerRange extends FloatingPointRange { + class IntegerRange extends Range { /** * Create a new instance. * @constructor - * @param {number} fromValue - The lowest inclusive value in range - * @param {number} toValue - The highest inclusive value in range - * @param {number} step - The internal unit size. - * @param {number} tolerance - The plus/minus tolerance for number equivalence. + * @param {bigint} fromValue - The lowest inclusive value in range + * @param {bigint} toValue - The highest inclusive value in range + * @param {bigint} step - The internal unit size. */ - constructor(from: number, to: number, step?: number, tolerance?: number); + constructor(from: bigint, to: bigint, step?: bigint); /** * Determine if a ParameterType is compatible. From 2f0e20465f756975c49d511c6a5723be26c4167f Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Thu, 13 Feb 2025 17:23:30 +0800 Subject: [PATCH 4/7] Correct the param type --- types/parameter.d.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/types/parameter.d.ts b/types/parameter.d.ts index 7794959b..dc9d5252 100644 --- a/types/parameter.d.ts +++ b/types/parameter.d.ts @@ -265,11 +265,15 @@ declare module 'rclnodejs' { /** * Create a new instance. * @constructor - * @param {bigint} fromValue - The lowest inclusive value in range - * @param {bigint} toValue - The highest inclusive value in range - * @param {bigint} step - The internal unit size. + * @param {bigint|number} fromValue - The lowest inclusive value in range + * @param {bigint|number} toValue - The highest inclusive value in range + * @param {bigint|number} step - The internal unit size. */ - constructor(from: bigint, to: bigint, step?: bigint); + constructor( + from: bigint | number, + to: bigint | number, + step?: bigint | number + ); /** * Determine if a ParameterType is compatible. From c31879b570241102ffacf1f68792668bb54f145f Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Fri, 14 Feb 2025 12:43:49 +0800 Subject: [PATCH 5/7] Add ts test for Paramter --- test/types/main.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/types/main.ts b/test/types/main.ts index ae1ccf97..4a0a518e 100644 --- a/test/types/main.ts +++ b/test/types/main.ts @@ -680,3 +680,14 @@ actionUuid.toMessage(); // $ExpectType UUID rclnodejs.ActionUuid.randomMessage(); + +// ---- Parameter ----- +// $ExpectType Parameter +const param = rclnodejs.createMessageObject('rcl_interfaces/msg/Parameter'); +param.name = 'integer_param'; +param.value.type = rclnodejs.ParameterType.PARAMETER_INTEGER; +param.value.integer_value = BigInt(123); + +param.name = 'byte_array_param'; +param.value.type = rclnodejs.ParameterType.PARAMETER_BYTE_ARRAY; +param.value.byte_array_value = [1, 2, 3]; From 4fb1fc17a7bc79a39772b5cd7f7734830c12fb7b Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Fri, 14 Feb 2025 13:14:48 +0800 Subject: [PATCH 6/7] Correct comments --- lib/parameter.js | 2 +- rosidl_gen/templates/message.dot | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/parameter.js b/lib/parameter.js index b5181a23..dee17b1f 100644 --- a/lib/parameter.js +++ b/lib/parameter.js @@ -541,7 +541,7 @@ class Range { /** * Determine if a value is within this range. - * A TypeError is thrown when value is not a number. + * A TypeError is thrown when value is not a number or bigint. * Subclasses should override and call this method for basic type checking. * * @param {number|bigint} value - The number or bigint to check. diff --git a/rosidl_gen/templates/message.dot b/rosidl_gen/templates/message.dot index e8f228e3..d9b2ca56 100644 --- a/rosidl_gen/templates/message.dot +++ b/rosidl_gen/templates/message.dot @@ -576,7 +576,7 @@ class {{=objectWrapper}} { this._wrapperFields.{{=field.name}}.data = value; {{?? isBigInt(field.type)}} if (typeof value !== "bigint") { - throw new TypeError('int64/uint64 must be type of bigint'); + throw new TypeError('{{=field.name}} must be type of bigint'); } this._refObject.{{=field.name}} = value.toString(); {{??}} From 32916c5ecfd6cc3d3471332826ac0e9e5418d129 Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Fri, 14 Feb 2025 13:53:40 +0800 Subject: [PATCH 7/7] Correct parameter type --- lib/parameter.js | 9 +++------ types/parameter.d.ts | 14 +++++--------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/lib/parameter.js b/lib/parameter.js index dee17b1f..9b938f47 100644 --- a/lib/parameter.js +++ b/lib/parameter.js @@ -34,6 +34,7 @@ const rclnodejs = require('bindings')('rclnodejs'); const DEFAULT_NUMERIC_RANGE_TOLERANCE = 1e-6; const PARAMETER_SEPARATOR = '.'; +const PARAMETER_BYTE = 10; /** * Enum for ParameterType @@ -61,8 +62,6 @@ const ParameterType = { PARAMETER_DOUBLE_ARRAY: 8, /** @member {number} */ PARAMETER_STRING_ARRAY: 9, - /** @member {number} */ - PARAMETER_BYTE: 10, }; /** @@ -676,8 +675,6 @@ class IntegerRange extends Range { */ isValidType(parameterType) { const result = - parameterType === ParameterType.PARAMETER_BYTE || - parameterType === ParameterType.PARAMETER_BYTE_ARRAY || parameterType === ParameterType.PARAMETER_INTEGER || parameterType === ParameterType.PARAMETER_INTEGER_ARRAY; return result; @@ -770,7 +767,7 @@ function validValue(value, type) { result = typeof value === 'string'; break; case ParameterType.PARAMETER_DOUBLE: - case ParameterType.PARAMETER_BYTE: + case PARAMETER_BYTE: result = typeof value === 'number'; break; case ParameterType.PARAMETER_INTEGER: @@ -798,7 +795,7 @@ function _validArray(values, type) { if (type === ParameterType.PARAMETER_BOOL_ARRAY) { arrayElementType = ParameterType.PARAMETER_BOOL; } else if (type === ParameterType.PARAMETER_BYTE_ARRAY) { - arrayElementType = ParameterType.PARAMETER_BYTE; + arrayElementType = PARAMETER_BYTE; } if (type === ParameterType.PARAMETER_INTEGER_ARRAY) { arrayElementType = ParameterType.PARAMETER_INTEGER; diff --git a/types/parameter.d.ts b/types/parameter.d.ts index dc9d5252..f0dde114 100644 --- a/types/parameter.d.ts +++ b/types/parameter.d.ts @@ -45,7 +45,7 @@ declare module 'rclnodejs' { * * @param name - The parameter name, must be a valid name. * @param type - The type identifier. - * @param {value - The parameter value. + * @param value - The parameter value. */ constructor(name: string, type: ParameterType, value?: any); @@ -265,15 +265,11 @@ declare module 'rclnodejs' { /** * Create a new instance. * @constructor - * @param {bigint|number} fromValue - The lowest inclusive value in range - * @param {bigint|number} toValue - The highest inclusive value in range - * @param {bigint|number} step - The internal unit size. + * @param {bigint} fromValue - The lowest inclusive value in range + * @param {bigint} toValue - The highest inclusive value in range + * @param {bigint} step - The internal unit size. */ - constructor( - from: bigint | number, - to: bigint | number, - step?: bigint | number - ); + constructor(from: bigint, to: bigint, step?: bigint); /** * Determine if a ParameterType is compatible.