diff --git a/PORTING.md b/PORTING.md index 657cbb3..4eae61a 100644 --- a/PORTING.md +++ b/PORTING.md @@ -71,7 +71,7 @@ Tests covering the engine-specific part of Node-API, defined in `js_native_api.h | `test_sharedarraybuffer` | Not ported | Medium | | `test_string` | Not ported | Medium | | `test_symbol` | Ported ✅ | Easy | -| `test_typedarray` | Not ported | Medium | +| `test_typedarray` | Ported ✅ | Medium | ## Runtime-specific (`node-api`) diff --git a/tests/js-native-api/test_typedarray/CMakeLists.txt b/tests/js-native-api/test_typedarray/CMakeLists.txt new file mode 100644 index 0000000..e6aa327 --- /dev/null +++ b/tests/js-native-api/test_typedarray/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_typedarray test_typedarray.c) diff --git a/tests/js-native-api/test_typedarray/test.js b/tests/js-native-api/test_typedarray/test.js new file mode 100644 index 0000000..9eb428b --- /dev/null +++ b/tests/js-native-api/test_typedarray/test.js @@ -0,0 +1,132 @@ +"use strict"; + +// Testing api calls for arrays +const test_typedarray = loadAddon("test_typedarray"); + +const byteArray = new Uint8Array(3); +byteArray[0] = 0; +byteArray[1] = 1; +byteArray[2] = 2; +assert.strictEqual(byteArray.length, 3); + +const doubleArray = new Float64Array(3); +doubleArray[0] = 0.0; +doubleArray[1] = 1.1; +doubleArray[2] = 2.2; +assert.strictEqual(doubleArray.length, 3); + +const byteResult = test_typedarray.Multiply(byteArray, 3); +assert.ok(byteResult instanceof Uint8Array); +assert.strictEqual(byteResult.length, 3); +assert.strictEqual(byteResult[0], 0); +assert.strictEqual(byteResult[1], 3); +assert.strictEqual(byteResult[2], 6); + +const doubleResult = test_typedarray.Multiply(doubleArray, -3); +assert.ok(doubleResult instanceof Float64Array); +assert.strictEqual(doubleResult.length, 3); +assert.strictEqual(doubleResult[0], -0); +assert.strictEqual(Math.round(10 * doubleResult[1]) / 10, -3.3); +assert.strictEqual(Math.round(10 * doubleResult[2]) / 10, -6.6); + +const externalResult = test_typedarray.External(); +assert.ok(externalResult instanceof Int8Array); +assert.strictEqual(externalResult.length, 3); +assert.strictEqual(externalResult[0], 0); +assert.strictEqual(externalResult[1], 1); +assert.strictEqual(externalResult[2], 2); + +// Validate creation of all kinds of TypedArrays +const buffer = new ArrayBuffer(128); +const arrayTypes = [ + Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float16Array, + Float32Array, + Float64Array, + BigInt64Array, + BigUint64Array, +]; + +arrayTypes.forEach((currentType) => { + const template = Reflect.construct(currentType, buffer); + const theArray = test_typedarray.CreateTypedArray(template, buffer); + + assert.ok( + theArray instanceof currentType, + "Type of new array should match that of the template. " + + `Expected type: ${currentType.name}, ` + + `actual type: ${template.constructor.name}`, + ); + assert.notStrictEqual(theArray, template); + assert.strictEqual(theArray.buffer, buffer); +}); + +arrayTypes.forEach((currentType) => { + const template = Reflect.construct(currentType, buffer); + assert.throws(() => { + test_typedarray.CreateTypedArray(template, buffer, 0, 136); + }, RangeError); +}); + +const nonByteArrayTypes = [ + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float16Array, + Float32Array, + Float64Array, + BigInt64Array, + BigUint64Array, +]; +nonByteArrayTypes.forEach((currentType) => { + const template = Reflect.construct(currentType, buffer); + assert.throws(() => { + test_typedarray.CreateTypedArray( + template, + buffer, + currentType.BYTES_PER_ELEMENT + 1, + 1, + ); + console.log(`start of offset ${currentType}`); + }, RangeError); +}); + +// Test detaching +arrayTypes.forEach((currentType) => { + const buffer = Reflect.construct(currentType, [8]); + assert.strictEqual(buffer.length, 8); + assert.ok(!test_typedarray.IsDetached(buffer.buffer)); + test_typedarray.Detach(buffer); + assert.ok(test_typedarray.IsDetached(buffer.buffer)); + assert.strictEqual(buffer.length, 0); +}); +{ + const buffer = test_typedarray.External(); + assert.ok(externalResult instanceof Int8Array); + assert.strictEqual(externalResult.length, 3); + assert.strictEqual(externalResult.byteLength, 3); + assert.ok(!test_typedarray.IsDetached(buffer.buffer)); + test_typedarray.Detach(buffer); + assert.ok(test_typedarray.IsDetached(buffer.buffer)); + assert.ok(externalResult instanceof Int8Array); + assert.strictEqual(buffer.length, 0); + assert.strictEqual(buffer.byteLength, 0); +} + +{ + const buffer = new ArrayBuffer(128); + assert.ok(!test_typedarray.IsDetached(buffer)); +} + +{ + const buffer = test_typedarray.NullArrayBuffer(); + assert.ok(buffer instanceof ArrayBuffer); + assert.ok(test_typedarray.IsDetached(buffer)); +} diff --git a/tests/js-native-api/test_typedarray/test_typedarray.c b/tests/js-native-api/test_typedarray/test_typedarray.c new file mode 100644 index 0000000..7b4241b --- /dev/null +++ b/tests/js-native-api/test_typedarray/test_typedarray.c @@ -0,0 +1,248 @@ +#include +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value Multiply(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects a typed array as first argument."); + + napi_value input_array = args[0]; + bool is_typedarray; + NODE_API_CALL(env, napi_is_typedarray(env, input_array, &is_typedarray)); + + NODE_API_ASSERT(env, is_typedarray, + "Wrong type of arguments. Expects a typed array as first argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NODE_API_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects a number as second argument."); + + double multiplier; + NODE_API_CALL(env, napi_get_value_double(env, args[1], &multiplier)); + + napi_typedarray_type type; + napi_value input_buffer; + size_t byte_offset; + size_t i, length; + NODE_API_CALL(env, napi_get_typedarray_info( + env, input_array, &type, &length, NULL, &input_buffer, &byte_offset)); + + void* data; + size_t byte_length; + NODE_API_CALL(env, napi_get_arraybuffer_info( + env, input_buffer, &data, &byte_length)); + + napi_value output_buffer; + void* output_ptr = NULL; + NODE_API_CALL(env, napi_create_arraybuffer( + env, byte_length, &output_ptr, &output_buffer)); + + napi_value output_array; + NODE_API_CALL(env, napi_create_typedarray( + env, type, length, output_buffer, byte_offset, &output_array)); + + if (type == napi_uint8_array) { + uint8_t* input_bytes = (uint8_t*)(data) + byte_offset; + uint8_t* output_bytes = (uint8_t*)(output_ptr); + for (i = 0; i < length; i++) { + output_bytes[i] = (uint8_t)(input_bytes[i] * multiplier); + } + } else if (type == napi_float64_array) { + double* input_doubles = (double*)((uint8_t*)(data) + byte_offset); + double* output_doubles = (double*)(output_ptr); + for (i = 0; i < length; i++) { + output_doubles[i] = input_doubles[i] * multiplier; + } + } else { + napi_throw_error(env, NULL, + "Typed array was of a type not expected by test."); + return NULL; + } + + return output_array; +} + +static void FinalizeCallback(node_api_basic_env env, + void* finalize_data, + void* finalize_hint) { + free(finalize_data); +} + +static napi_value External(napi_env env, napi_callback_info info) { + const uint8_t nElem = 3; + int8_t* externalData = malloc(nElem*sizeof(int8_t)); + externalData[0] = 0; + externalData[1] = 1; + externalData[2] = 2; + + napi_value output_buffer; + NODE_API_CALL(env, napi_create_external_arraybuffer( + env, + externalData, + nElem*sizeof(int8_t), + FinalizeCallback, + NULL, // finalize_hint + &output_buffer)); + + napi_value output_array; + NODE_API_CALL(env, napi_create_typedarray(env, + napi_int8_array, + nElem, + output_buffer, + 0, + &output_array)); + + return output_array; +} + + +static napi_value NullArrayBuffer(napi_env env, napi_callback_info info) { + static void* data = NULL; + napi_value arraybuffer; + NODE_API_CALL(env, + napi_create_external_arraybuffer(env, data, 0, NULL, NULL, &arraybuffer)); + return arraybuffer; +} + +static napi_value CreateTypedArray(napi_env env, napi_callback_info info) { + size_t argc = 4; + napi_value args[4]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 2 || argc == 4, "Wrong number of arguments"); + + napi_value input_array = args[0]; + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, input_array, &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects a typed array as first argument."); + + bool is_typedarray; + NODE_API_CALL(env, napi_is_typedarray(env, input_array, &is_typedarray)); + + NODE_API_ASSERT(env, is_typedarray, + "Wrong type of arguments. Expects a typed array as first argument."); + + napi_valuetype valuetype1; + napi_value input_buffer = args[1]; + NODE_API_CALL(env, napi_typeof(env, input_buffer, &valuetype1)); + + NODE_API_ASSERT(env, valuetype1 == napi_object, + "Wrong type of arguments. Expects an array buffer as second argument."); + + bool is_arraybuffer; + NODE_API_CALL(env, napi_is_arraybuffer(env, input_buffer, &is_arraybuffer)); + + NODE_API_ASSERT(env, is_arraybuffer, + "Wrong type of arguments. Expects an array buffer as second argument."); + + napi_typedarray_type type; + napi_value in_array_buffer; + size_t byte_offset; + size_t length; + NODE_API_CALL(env, napi_get_typedarray_info( + env, input_array, &type, &length, NULL, &in_array_buffer, &byte_offset)); + + if (argc == 4) { + napi_valuetype valuetype2; + NODE_API_CALL(env, napi_typeof(env, args[2], &valuetype2)); + + NODE_API_ASSERT(env, valuetype2 == napi_number, + "Wrong type of arguments. Expects a number as third argument."); + + uint32_t uint32_length; + NODE_API_CALL(env, napi_get_value_uint32(env, args[2], &uint32_length)); + length = uint32_length; + + napi_valuetype valuetype3; + NODE_API_CALL(env, napi_typeof(env, args[3], &valuetype3)); + + NODE_API_ASSERT(env, valuetype3 == napi_number, + "Wrong type of arguments. Expects a number as third argument."); + + uint32_t uint32_byte_offset; + NODE_API_CALL(env, napi_get_value_uint32(env, args[3], &uint32_byte_offset)); + byte_offset = uint32_byte_offset; + } + + napi_value output_array; + NODE_API_CALL(env, napi_create_typedarray( + env, type, length, input_buffer, byte_offset, &output_array)); + + return output_array; +} + +static napi_value Detach(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments."); + + bool is_typedarray; + NODE_API_CALL(env, napi_is_typedarray(env, args[0], &is_typedarray)); + NODE_API_ASSERT( + env, is_typedarray, + "Wrong type of arguments. Expects a typedarray as first argument."); + + napi_value arraybuffer; + NODE_API_CALL(env, + napi_get_typedarray_info( + env, args[0], NULL, NULL, NULL, &arraybuffer, NULL)); + NODE_API_CALL(env, napi_detach_arraybuffer(env, arraybuffer)); + + return NULL; +} + +static napi_value IsDetached(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments."); + + napi_value array_buffer = args[0]; + bool is_arraybuffer; + NODE_API_CALL(env, napi_is_arraybuffer(env, array_buffer, &is_arraybuffer)); + NODE_API_ASSERT(env, is_arraybuffer, + "Wrong type of arguments. Expects an array buffer as first argument."); + + bool is_detached; + NODE_API_CALL(env, + napi_is_detached_arraybuffer(env, array_buffer, &is_detached)); + + napi_value result; + NODE_API_CALL(env, napi_get_boolean(env, is_detached, &result)); + + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("Multiply", Multiply), + DECLARE_NODE_API_PROPERTY("External", External), + DECLARE_NODE_API_PROPERTY("NullArrayBuffer", NullArrayBuffer), + DECLARE_NODE_API_PROPERTY("CreateTypedArray", CreateTypedArray), + DECLARE_NODE_API_PROPERTY("Detach", Detach), + DECLARE_NODE_API_PROPERTY("IsDetached", IsDetached), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END