pebble/tests/fw/javascript/test_rocky_api_util_args.c
2025-01-27 11:38:16 -08:00

620 lines
17 KiB
C

/*
* Copyright 2024 Google LLC
*
* 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.
*/
#include "clar.h"
#include "applib/rockyjs/api/rocky_api_util_args.h"
#include "applib/rockyjs/api/rocky_api_errors.h"
#include "applib/rockyjs/api/rocky_api_util.h"
#include "applib/rockyjs/pbl_jerry_port.h"
#include "test_jerry_port_common.h"
#include "test_rocky_common.h"
#include <util/size.h>
#include <limits.h>
#include <stdint.h>
// Fakes
#include "fake_pbl_malloc.h"
#include "fake_time.h"
// Stubs
#include "stubs_app_state.h"
#include "stubs_passert.h"
#include "stubs_logging.h"
#include "stubs_serial.h"
#include "stubs_sys_exit.h"
#define JERRY_ARGS_MAKE(...) \
jerry_value_t argv[] = { \
__VA_ARGS__ \
}; \
jerry_length_t argc = ARRAY_LENGTH(argv);
#define JERRY_ARGS_RELEASE() \
while(argc--) { \
jerry_release_value(argv[argc]); \
argv[argc] = 0; \
}
#define ROCKY_ARGS_ASSIGN(...) \
const RockyArgBinding bindings[] = { \
__VA_ARGS__ \
}; \
JS_VAR error_value = \
rocky_args_assign(argc, argv, bindings, ARRAY_LENGTH(bindings)); \
void test_rocky_api_util_args__initialize(void) {
rocky_runtime_context_init();
jerry_init(JERRY_INIT_EMPTY);
}
void test_rocky_api_util_args__cleanup(void) {
jerry_cleanup();
rocky_runtime_context_deinit();
fake_pbl_malloc_check_net_allocs(); // Make sure no memory was leaked
}
void test_rocky_api_util_args__missing_args(void) {
JERRY_ARGS_MAKE(/* argc == 0 */);
uint8_t v;
ROCKY_ARGS_ASSIGN(ROCKY_ARG(v));
ASSERT_JS_ERROR(error_value, "TypeError: Not enough arguments");
}
void test_rocky_api_util_args__numbers_get_rounded_when_converting_to_integer(void) {
struct {
double input;
int16_t expected_output;
} cases[] = {
{
.input = 0.5,
.expected_output = 1,
},
{
.input = -0.5,
.expected_output = -1,
},
{
.input = 0.0,
.expected_output = 0,
},
{
.input = -0.3,
.expected_output = 0,
},
};
for (int i = 0; i < ARRAY_LENGTH(cases); ++i) {
int16_t output = ~0;
jerry_value_t v = jerry_create_number(cases[i].input);
JERRY_ARGS_MAKE(v);
ROCKY_ARGS_ASSIGN(ROCKY_ARG(output));
cl_assert_equal_i(output, cases[i].expected_output);
ASSERT_JS_ERROR(error_value, NULL);
jerry_release_value(v);
}
}
void test_rocky_api_util_args__numbers(void) {
uint8_t u8;
uint16_t u16;
uint32_t u32;
uint64_t u64;
int8_t s8;
int16_t s16;
int32_t s32;
int64_t s64;
double d;
typedef struct {
long double u8;
long double u16;
long double u32;
long double u64;
long double s8;
long double s16;
long double s32;
long double s64;
double d;
char *expected_error_msg;
} NumbersTestCase;
enum {
WithinLowerBounds,
WithinUpperBounds,
UnderLowerBounds,
OverUpperBounds,
};
// FIXME: fix limits.h / stdint.h / float.h so we can use the standard defines for these values..
NumbersTestCase cases[4] = {
[WithinLowerBounds] = {
.u8 = 0.0,
.u16 = 0.0,
.u32 = 0.0,
.u64 = 0.0,
.s8 = -128.0,
.s16 = -32768.0,
.s32 = -2147483648.0,
.s64 = -9223372036854775808.0,
.d = 2.2250738585072014e-308,
.expected_error_msg = NULL,
},
[WithinUpperBounds] = {
.u8 = 255.0,
.u16 = 65535.0,
.u32 = 4294967295.0,
.u64 = 9223372036854775807.0,
.s8 = 127.0,
.s16 = 32767.0,
.s32 = 2147483647.0,
.s64 = 9223372036854775807.0,
.d = 1.7976931348623157e+308,
.expected_error_msg = NULL,
},
};
const long double margin = 0.001;
cases[UnderLowerBounds] = (NumbersTestCase) {
.u8 = cases[WithinLowerBounds].u8 - margin,
.u16 = cases[WithinLowerBounds].u16 - margin,
.u32 = cases[WithinLowerBounds].u32 - margin,
.u64 = cases[WithinLowerBounds].u64 - margin,
.s8 = cases[WithinLowerBounds].s8 - margin,
.s16 = cases[WithinLowerBounds].s16 - margin,
.s32 = cases[WithinLowerBounds].s32 - margin,
.s64 = cases[WithinLowerBounds].s64 - margin,
.d = cases[WithinLowerBounds].d - margin,
.expected_error_msg =
"TypeError: Argument at index 0 is invalid: Value out of bounds for native type",
};
cases[OverUpperBounds] = (NumbersTestCase) {
.u8 = cases[WithinUpperBounds].u8 + margin,
.u16 = cases[WithinUpperBounds].u16 + margin,
.u32 = cases[WithinUpperBounds].u32 + margin,
.u64 = cases[WithinUpperBounds].u64 + margin,
.s8 = cases[WithinUpperBounds].s8 + margin,
.s16 = cases[WithinUpperBounds].s16 + margin,
.s32 = cases[WithinUpperBounds].s32 + margin,
.s64 = cases[WithinUpperBounds].s64 + margin,
.d = cases[WithinUpperBounds].d - margin,
.expected_error_msg =
"TypeError: Argument at index 0 is invalid: Value out of bounds for native type",
};
for (int i = 0; i < ARRAY_LENGTH(cases); ++i) {
NumbersTestCase *c = &cases[i];
// Initialize to something that's not the expected value:
u8 = 1 + (uint8_t)c->u8;
u16 = 1 + (uint16_t)c->u16;
u32 = 1 + (uint16_t)c->u32;
u64 = 1 + (uint16_t)c->u64;
s8 = 1 + (int8_t)c->s8;
s16 = 1 + (int16_t)c->s16;
s32 = 1 + (int16_t)c->s32;
s64 = 1 + (int16_t)c->s64;
d = 1 + (double)c->d;
JERRY_ARGS_MAKE(
jerry_create_number(c->u8),
jerry_create_number(c->u16),
jerry_create_number(c->u32),
jerry_create_number(c->u64),
jerry_create_number(c->s8),
jerry_create_number(c->s16),
jerry_create_number(c->s32),
jerry_create_number(c->s64),
jerry_create_number(c->d),
);
ROCKY_ARGS_ASSIGN(
ROCKY_ARG(u8),
ROCKY_ARG(u16),
ROCKY_ARG(u32),
ROCKY_ARG(u64),
ROCKY_ARG(s8),
ROCKY_ARG(s16),
ROCKY_ARG(s32),
ROCKY_ARG(s64),
ROCKY_ARG(d),
);
ASSERT_JS_ERROR(error_value, c->expected_error_msg);
if (!c->expected_error_msg) {
cl_assert_equal_i(u8, (uint8_t)c->u8);
cl_assert_equal_i(u16, (uint16_t)c->u16);
cl_assert_equal_i(u32, (uint32_t)c->u32);
cl_assert_equal_i(u64, (uint64_t)c->u64);
cl_assert_equal_i(s8, (int8_t)c->s8);
cl_assert_equal_i(s16, (int16_t)c->s16);
cl_assert_equal_i(s32, (int32_t)c->s32);
cl_assert_equal_i(s64, (int64_t)c->s64);
cl_assert_equal_d(d, (double)c->d);
}
JERRY_ARGS_RELEASE();
}
}
void test_rocky_api_util_args__number_type_mismatch(void) {
jerry_value_t mismatch_args[] = {
jerry_create_null(),
jerry_create_string((const jerry_char_t *)"one"),
jerry_create_string((const jerry_char_t *)"1"),
jerry_create_array(1),
jerry_create_boolean(true),
jerry_create_object(),
};
for (int i = 0; i < ARRAY_LENGTH(mismatch_args); ++i) {
jerry_value_t arg = mismatch_args[i];
JERRY_ARGS_MAKE(arg);
for (RockyArgType type = RockyArgTypeUInt8; type <= RockyArgTypeDouble; ++type) {
uint8_t buffer_untouched[8]; // The type check fails, so nothing is supposed to be written.
ROCKY_ARGS_ASSIGN(
ROCKY_ARG_MAKE(buffer_untouched, type, {}),
);
ASSERT_JS_ERROR(error_value, "TypeError: Argument at index 0 is not a Number");
}
jerry_release_value(arg);
mismatch_args[i] = 0;
}
}
static jerry_value_t prv_dummy(const jerry_value_t function_obj_p,
const jerry_value_t this_val,
const jerry_value_t args_p[],
const jerry_length_t args_count) {
return 0;
}
void test_rocky_api_util_args__boolean(void) {
// No API to create NaN :(
char *nan_script = "Number.NaN";
jerry_value_t nan = jerry_eval((jerry_char_t *)nan_script,
strlen(nan_script),
false /* is_strict */);
struct {
jerry_value_t input;
bool expected_output;
} cases[] = {
// Falsy: false, 0, "", null, undefined, and NaN:
{
.input = jerry_create_boolean(false),
.expected_output = false,
},
{
.input = jerry_create_number(0.0),
.expected_output = false,
},
{
.input = jerry_create_string((const jerry_char_t *)""),
.expected_output = false,
},
{
.input = jerry_create_null(),
.expected_output = false,
},
{
.input = jerry_create_undefined(),
.expected_output = false,
},
{
.input = nan,
.expected_output = false,
},
// Truthy values:
{
.input = jerry_create_boolean(true),
.expected_output = true,
},
{
.input = jerry_create_number(1.0),
.expected_output = true,
},
{
.input = jerry_create_string((const jerry_char_t *)" "),
.expected_output = true,
},
{
.input = jerry_create_array(0),
.expected_output = true,
},
{
.input = jerry_create_object(),
.expected_output = true,
},
{
.input = jerry_create_external_function(prv_dummy),
.expected_output = true,
},
};
for (int i = 0; i < ARRAY_LENGTH(cases); ++i) {
bool output = !cases[i].expected_output;
jerry_value_t input = cases[i].input;
JERRY_ARGS_MAKE(input);
ROCKY_ARGS_ASSIGN(ROCKY_ARG(output));
cl_assert_equal_b(output, cases[i].expected_output);
ASSERT_JS_ERROR(error_value, NULL /* Never errors out! */);
jerry_release_value(input);
cases[i].input = 0;
}
}
void test_rocky_api_util_args__string(void) {
struct {
jerry_value_t input;
char *expected_output;
} cases[] = {
{
.input = jerry_create_boolean(false),
.expected_output = "false",
},
{
.input = jerry_create_number(0.0),
.expected_output = "0",
},
{
.input = jerry_create_number(1.234e+60),
.expected_output = "1.234e+60",
},
{
.input = jerry_create_string((const jerry_char_t *)""),
.expected_output = "",
},
{
.input = jerry_create_string((const jerry_char_t *)"js"),
.expected_output = "js",
},
{
.input = jerry_create_null(),
.expected_output = "null",
},
{
.input = jerry_create_undefined(),
.expected_output = "undefined",
},
{
.input = jerry_create_array(0),
.expected_output = "", // Kinda weird?
},
{
.input = jerry_create_object(),
.expected_output = "[object Object]",
},
};
for (int i = 0; i < ARRAY_LENGTH(cases); ++i) {
jerry_value_t input = cases[i].input;
JERRY_ARGS_MAKE(input);
// Exercise ROCKY_ARG (automatic binding creation, defaults to malloc'd string for char *):
{
char *output = NULL;
ROCKY_ARGS_ASSIGN(ROCKY_ARG(output));
cl_assert_equal_s(output, cases[i].expected_output);
ASSERT_JS_ERROR(error_value, NULL /* Never errors out! */);
task_free(output);
}
// Exercise ROCKY_ARG (automatic binding creation, defaults to copy/no-malloc for char[]):
{
char output[16];
ROCKY_ARGS_ASSIGN(ROCKY_ARG(output));
cl_assert_equal_s(output, cases[i].expected_output);
ASSERT_JS_ERROR(error_value, NULL /* Never errors out! */);
}
// Exercise ROCKY_ARG_STR (no malloc, explicit binding creation):
{
char output[16];
ROCKY_ARGS_ASSIGN(ROCKY_ARG_STR(output, sizeof(output)));
cl_assert_equal_s(output, cases[i].expected_output);
ASSERT_JS_ERROR(error_value, NULL /* Never errors out! */);
}
// Exercise ROCKY_ARG_STR (too small buffer provided):
{
char output[1] = {0xff};
ROCKY_ARGS_ASSIGN(ROCKY_ARG_STR(output, 0));
cl_assert_equal_s(output, "");
ASSERT_JS_ERROR(error_value, NULL /* Never errors out! */);
}
jerry_release_value(input);
cases[i].input = 0;
}
}
#define PP(_x, _y, _w, _h) { \
.origin.x.raw_value = (_x), \
.origin.y.raw_value = (_y), \
.size.w.raw_value = (_w), \
.size.h.raw_value = (_h), \
}
void test_rocky_api_util_args__grect_precise(void) {
struct {
jerry_value_t argv[5];
size_t argc;
GRectPrecise expected_output;
char *error_msg;
} cases[] = {
{
.argv = {
[0] = jerry_create_number(0.0),
[1] = jerry_create_number(0.0),
[2] = jerry_create_number(0.0),
[3] = jerry_create_number(0.0),
},
.argc = 4,
.expected_output = PP(0, 0, 0, 0),
},
{
.argv = {
[0] = jerry_create_number(-0.5),
[1] = jerry_create_number(-0.2),
[2] = jerry_create_number(0.3),
[3] = jerry_create_number(0.5),
},
.argc = 4,
.expected_output = PP(-4, -2, 2, 4),
},
{
.argv = {
[0] = jerry_create_number(-4096.0),
[1] = jerry_create_number(-4096.0),
[2] = jerry_create_number(4095.875),
[3] = jerry_create_number(4095.875),
},
.argc = 4,
.expected_output = PP(-32768, -32768, 32767, 32767),
},
{
.argv = {
[0] = jerry_create_number(0),
[1] = jerry_create_number(0),
[2] = jerry_create_number(0),
[3] = jerry_create_number(4096.0),
},
.argc = 4,
.error_msg = "TypeError: Argument at index 3 is invalid: Value out of bounds for native type",
},
{
.argv = {
[0] = jerry_create_number(0),
[1] = jerry_create_number(0),
[2] = jerry_create_number(0),
},
.argc = 3,
.error_msg = "TypeError: Not enough arguments",
},
{
.argv = {
[0] = jerry_create_number(0),
[1] = jerry_create_number(0),
[2] = jerry_create_number(0),
[3] = jerry_create_null(),
},
.argc = 4,
.error_msg = "TypeError: Argument at index 3 is not a Number",
},
{
.argv = {
[0] = jerry_create_number(0),
[1] = jerry_create_number(0),
[2] = jerry_create_number(0),
[3] = jerry_create_string((const jerry_char_t *)"123"),
},
.argc = 4,
.error_msg = "TypeError: Argument at index 3 is not a Number",
},
};
for (int i = 0; i < ARRAY_LENGTH(cases); ++i) {
jerry_value_t *argv = cases[i].argv;
jerry_length_t argc = cases[i].argc;
GRectPrecise output;
memset(&output, 0x55, sizeof(output));
ROCKY_ARGS_ASSIGN(ROCKY_ARG(output));
ASSERT_JS_ERROR(error_value, cases[i].error_msg);
if (!cases[i].error_msg) {
cl_assert_equal_i(output.origin.x.raw_value, cases[i].expected_output.origin.x.raw_value);
cl_assert_equal_i(output.origin.y.raw_value, cases[i].expected_output.origin.y.raw_value);
cl_assert_equal_i(output.size.w.raw_value, cases[i].expected_output.size.w.raw_value);
cl_assert_equal_i(output.size.h.raw_value, cases[i].expected_output.size.h.raw_value);
}
for (uint32_t j = 0; j < argc; ++j) {
jerry_release_value(argv[j]);
}
}
}
void test_rocky_api_util_args__gcolor(void) {
const char *type_error_msg =
"TypeError: Argument at index 0 is not a String ('color name' or '#hex') or Number";
const char *invalid_value_msg =
"TypeError: Argument at index 0 is invalid: " \
"Expecting String ('color name' or '#hex') or Number";
struct {
jerry_value_t input;
GColor expected_output;
const char *error_msg;
} cases[] = {
{
.input = jerry_create_number(0.0),
.expected_output = {
.argb = 0,
},
},
{
.input = jerry_create_number(GColorJaegerGreenARGB8),
.expected_output = {
.argb = GColorJaegerGreenARGB8,
}
},
{
.input = jerry_create_string((const jerry_char_t *)"red"),
.expected_output = {
.r = 0b11,
.g = 0,
.b = 0,
.a = 0b11,
}
},
{
.input = jerry_create_string((const jerry_char_t *)"unknown-color"),
.error_msg = invalid_value_msg,
},
{
.input = jerry_create_null(),
.error_msg = type_error_msg,
},
};
for (int i = 0; i < ARRAY_LENGTH(cases); ++i) {
jerry_value_t input = cases[i].input;
JERRY_ARGS_MAKE(input);
GColor output;
memset(&output, 0x55, sizeof(output));
ROCKY_ARGS_ASSIGN(ROCKY_ARG(output));
ASSERT_JS_ERROR(error_value, cases[i].error_msg);
if (!cases[i].error_msg) {
cl_assert_equal_i(output.a, cases[i].expected_output.a);
cl_assert_equal_i(output.r, cases[i].expected_output.r);
cl_assert_equal_i(output.g, cases[i].expected_output.g);
cl_assert_equal_i(output.b, cases[i].expected_output.b);
}
jerry_release_value(cases[i].input);
cases[i].input = 0;
}
}