mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-13 05:06:30 +04:00
[FL-3961] New JS value destructuring (#4135)
* js: value destructuring and tests * js: temporary fix to see size impact * js_val: reduce code size 1 * i may be stupid. * test: js_value args * Revert "js: temporary fix to see size impact" This reverts commit f51d726dbafc4300d3552020de1c3b8f9ecd3ac1. * pvs: silence warnings * style: formatting * pvs: silence warnings? * pvs: silence warnings?? * js_value: redesign declaration types for less code * js: temporary fix to see size impact * style: formatting * pvs: fix helpful warnings * js_value: reduce .rodata size * pvs: fix helpful warning * js_value: reduce code size 1 * fix build error * style: format * Revert "js: temporary fix to see size impact" This reverts commit d6a46f01794132e882e03fd273dec24386a4f8ba. * style: format --------- Co-authored-by: hedger <hedger@users.noreply.github.com>
This commit is contained in:
@@ -6,9 +6,12 @@
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
#include <applications/system/js_app/js_value.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define TAG "JsUnitTests"
|
||||
|
||||
#define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js")
|
||||
|
||||
typedef enum {
|
||||
@@ -73,7 +76,311 @@ MU_TEST(js_test_storage) {
|
||||
js_test_run(JS_SCRIPT_PATH("storage"));
|
||||
}
|
||||
|
||||
static void js_value_test_compatibility_matrix(struct mjs* mjs) {
|
||||
static const JsValueType types[] = {
|
||||
JsValueTypeAny,
|
||||
JsValueTypeAnyArray,
|
||||
JsValueTypeAnyObject,
|
||||
JsValueTypeFunction,
|
||||
JsValueTypeRawPointer,
|
||||
JsValueTypeInt32,
|
||||
JsValueTypeDouble,
|
||||
JsValueTypeString,
|
||||
JsValueTypeBool,
|
||||
};
|
||||
|
||||
mjs_val_t values[] = {
|
||||
mjs_mk_undefined(),
|
||||
mjs_mk_foreign(mjs, (void*)0xDEADBEEF),
|
||||
mjs_mk_array(mjs),
|
||||
mjs_mk_object(mjs),
|
||||
mjs_mk_number(mjs, 123.456),
|
||||
mjs_mk_string(mjs, "test", ~0, false),
|
||||
mjs_mk_boolean(mjs, true),
|
||||
};
|
||||
|
||||
// for proper matrix formatting and better readability
|
||||
#define YES true
|
||||
#define NO_ false
|
||||
static const bool success_matrix[COUNT_OF(types)][COUNT_OF(values)] = {
|
||||
// types:
|
||||
{YES, YES, YES, YES, YES, YES, YES}, // any
|
||||
{NO_, NO_, YES, NO_, NO_, NO_, NO_}, // array
|
||||
{NO_, NO_, YES, YES, NO_, NO_, NO_}, // obj
|
||||
{NO_, NO_, NO_, NO_, NO_, NO_, NO_}, // fn
|
||||
{NO_, YES, NO_, NO_, NO_, NO_, NO_}, // ptr
|
||||
{NO_, NO_, NO_, NO_, YES, NO_, NO_}, // int32
|
||||
{NO_, NO_, NO_, NO_, YES, NO_, NO_}, // double
|
||||
{NO_, NO_, NO_, NO_, NO_, YES, NO_}, // str
|
||||
{NO_, NO_, NO_, NO_, NO_, NO_, YES}, // bool
|
||||
//
|
||||
//und ptr arr obj num str bool <- values
|
||||
};
|
||||
#undef NO_
|
||||
#undef YES
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(types); i++) {
|
||||
for(size_t j = 0; j < COUNT_OF(values); j++) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = types[i],
|
||||
.n_children = 0,
|
||||
};
|
||||
// we only care about the status, not the result. double has the largest size out of
|
||||
// all the results
|
||||
uint8_t result[sizeof(double)];
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&values[j],
|
||||
result);
|
||||
if((status == JsValueParseStatusOk) != success_matrix[i][j]) {
|
||||
FURI_LOG_E(TAG, "type %zu, value %zu", i, j);
|
||||
mu_fail("see serial logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_literal(struct mjs* mjs) {
|
||||
static const JsValueType types[] = {
|
||||
JsValueTypeAny,
|
||||
JsValueTypeAnyArray,
|
||||
JsValueTypeAnyObject,
|
||||
};
|
||||
|
||||
mjs_val_t values[] = {
|
||||
mjs_mk_undefined(),
|
||||
mjs_mk_array(mjs),
|
||||
mjs_mk_object(mjs),
|
||||
};
|
||||
|
||||
mu_assert_int_eq(COUNT_OF(types), COUNT_OF(values));
|
||||
for(size_t i = 0; i < COUNT_OF(types); i++) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = types[i],
|
||||
.n_children = 0,
|
||||
};
|
||||
mjs_val_t result;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&values[i],
|
||||
&result);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
mu_assert(result == values[i], "wrong result");
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_primitive(
|
||||
struct mjs* mjs,
|
||||
JsValueType type,
|
||||
const void* c_value,
|
||||
size_t c_value_size,
|
||||
mjs_val_t js_val) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = type,
|
||||
.n_children = 0,
|
||||
};
|
||||
uint8_t result[c_value_size];
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&js_val,
|
||||
result);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
if(type == JsValueTypeString) {
|
||||
const char* result_str = *(const char**)&result;
|
||||
mu_assert_string_eq(c_value, result_str);
|
||||
} else {
|
||||
mu_assert_mem_eq(c_value, result, c_value_size);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_primitives(struct mjs* mjs) {
|
||||
int32_t i32 = 123;
|
||||
js_value_test_primitive(mjs, JsValueTypeInt32, &i32, sizeof(i32), mjs_mk_number(mjs, i32));
|
||||
|
||||
double dbl = 123.456;
|
||||
js_value_test_primitive(mjs, JsValueTypeDouble, &dbl, sizeof(dbl), mjs_mk_number(mjs, dbl));
|
||||
|
||||
const char* str = "test";
|
||||
js_value_test_primitive(
|
||||
mjs, JsValueTypeString, str, strlen(str) + 1, mjs_mk_string(mjs, str, ~0, false));
|
||||
|
||||
bool boolean = true;
|
||||
js_value_test_primitive(
|
||||
mjs, JsValueTypeBool, &boolean, sizeof(boolean), mjs_mk_boolean(mjs, boolean));
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
js_value_test_enum(struct mjs* mjs, const JsValueDeclaration* decl, const char* value) {
|
||||
mjs_val_t str = mjs_mk_string(mjs, value, ~0, false);
|
||||
uint32_t result;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs, JS_VALUE_PARSE_SOURCE_VALUE(decl), JsValueParseFlagNone, &status, &str, &result);
|
||||
if(status != JsValueParseStatusOk) return 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void js_value_test_enums(struct mjs* mjs) {
|
||||
static const JsValueEnumVariant enum_1_variants[] = {
|
||||
{"variant 1", 1},
|
||||
{"variant 2", 2},
|
||||
{"variant 3", 3},
|
||||
};
|
||||
static const JsValueDeclaration enum_1 = JS_VALUE_ENUM(uint32_t, enum_1_variants);
|
||||
|
||||
static const JsValueEnumVariant enum_2_variants[] = {
|
||||
{"read", 4},
|
||||
{"write", 8},
|
||||
};
|
||||
static const JsValueDeclaration enum_2 = JS_VALUE_ENUM(uint32_t, enum_2_variants);
|
||||
|
||||
mu_assert_int_eq(1, js_value_test_enum(mjs, &enum_1, "variant 1"));
|
||||
mu_assert_int_eq(2, js_value_test_enum(mjs, &enum_1, "variant 2"));
|
||||
mu_assert_int_eq(3, js_value_test_enum(mjs, &enum_1, "variant 3"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_1, "not a thing"));
|
||||
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 1"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 2"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 3"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "not a thing"));
|
||||
mu_assert_int_eq(4, js_value_test_enum(mjs, &enum_2, "read"));
|
||||
mu_assert_int_eq(8, js_value_test_enum(mjs, &enum_2, "write"));
|
||||
}
|
||||
|
||||
static void js_value_test_object(struct mjs* mjs) {
|
||||
static const JsValueDeclaration int_decl = JS_VALUE_SIMPLE(JsValueTypeInt32);
|
||||
|
||||
static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
|
||||
static const JsValueEnumVariant enum_variants[] = {
|
||||
{"variant 1", 1},
|
||||
{"variant 2", 2},
|
||||
{"variant 3", 3},
|
||||
};
|
||||
static const JsValueDeclaration enum_decl = JS_VALUE_ENUM(uint32_t, enum_variants);
|
||||
|
||||
static const JsValueObjectField fields[] = {
|
||||
{"int", &int_decl},
|
||||
{"str", &str_decl},
|
||||
{"enum", &enum_decl},
|
||||
};
|
||||
static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields);
|
||||
|
||||
mjs_val_t object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, object) {
|
||||
JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false));
|
||||
JS_FIELD("int", mjs_mk_number(mjs, 123));
|
||||
JS_FIELD("enum", mjs_mk_string(mjs, "variant 2", ~0, false));
|
||||
}
|
||||
|
||||
const char* result_str;
|
||||
int32_t result_int;
|
||||
uint32_t result_enum;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&object_decl),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&object,
|
||||
&result_int,
|
||||
&result_str,
|
||||
&result_enum);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
mu_assert_string_eq("Helloooo!", result_str);
|
||||
mu_assert_int_eq(123, result_int);
|
||||
mu_assert_int_eq(2, result_enum);
|
||||
}
|
||||
|
||||
static void js_value_test_default(struct mjs* mjs) {
|
||||
static const JsValueDeclaration int_decl =
|
||||
JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 123);
|
||||
static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
|
||||
static const JsValueObjectField fields[] = {
|
||||
{"int", &int_decl},
|
||||
{"str", &str_decl},
|
||||
};
|
||||
static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields);
|
||||
|
||||
mjs_val_t object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, object) {
|
||||
JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false));
|
||||
JS_FIELD("int", mjs_mk_undefined());
|
||||
}
|
||||
|
||||
const char* result_str;
|
||||
int32_t result_int;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&object_decl),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&object,
|
||||
&result_int,
|
||||
&result_str);
|
||||
mu_assert_string_eq("Helloooo!", result_str);
|
||||
mu_assert_int_eq(123, result_int);
|
||||
}
|
||||
|
||||
static void js_value_test_args_fn(struct mjs* mjs) {
|
||||
static const JsValueDeclaration arg_list[] = {
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
};
|
||||
static const JsValueArguments args = JS_VALUE_ARGS(arg_list);
|
||||
|
||||
int32_t a, b, c;
|
||||
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &args, &a, &b, &c);
|
||||
|
||||
mu_assert_int_eq(123, a);
|
||||
mu_assert_int_eq(456, b);
|
||||
mu_assert_int_eq(-420, c);
|
||||
}
|
||||
|
||||
static void js_value_test_args(struct mjs* mjs) {
|
||||
mjs_val_t function = MJS_MK_FN(js_value_test_args_fn);
|
||||
|
||||
mjs_val_t result;
|
||||
mjs_val_t args[] = {
|
||||
mjs_mk_number(mjs, 123),
|
||||
mjs_mk_number(mjs, 456),
|
||||
mjs_mk_number(mjs, -420),
|
||||
};
|
||||
mu_assert_int_eq(
|
||||
MJS_OK, mjs_apply(mjs, &result, function, MJS_UNDEFINED, COUNT_OF(args), args));
|
||||
}
|
||||
|
||||
MU_TEST(js_value_test) {
|
||||
struct mjs* mjs = mjs_create(NULL);
|
||||
|
||||
js_value_test_compatibility_matrix(mjs);
|
||||
js_value_test_literal(mjs);
|
||||
js_value_test_primitives(mjs);
|
||||
js_value_test_enums(mjs);
|
||||
js_value_test_object(mjs);
|
||||
js_value_test_default(mjs);
|
||||
js_value_test_args(mjs);
|
||||
|
||||
mjs_destroy(mjs);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_js) {
|
||||
MU_RUN_TEST(js_value_test);
|
||||
MU_RUN_TEST(js_test_basic);
|
||||
MU_RUN_TEST(js_test_math);
|
||||
MU_RUN_TEST(js_test_event_loop);
|
||||
|
||||
@@ -396,6 +396,8 @@ void minunit_printf_warning(const char* format, ...);
|
||||
return; \
|
||||
} else { minunit_print_progress(); })
|
||||
|
||||
//-V:mu_assert_string_eq:526, 547
|
||||
|
||||
#define mu_assert_string_eq(expected, result) \
|
||||
MU__SAFE_BLOCK( \
|
||||
const char* minunit_tmp_e = expected; const char* minunit_tmp_r = result; \
|
||||
@@ -416,6 +418,8 @@ void minunit_printf_warning(const char* format, ...);
|
||||
return; \
|
||||
} else { minunit_print_progress(); })
|
||||
|
||||
//-V:mu_assert_mem_eq:526
|
||||
|
||||
#define mu_assert_mem_eq(expected, result, size) \
|
||||
MU__SAFE_BLOCK( \
|
||||
const void* minunit_tmp_e = expected; const void* minunit_tmp_r = result; \
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <rpc/rpc_i.h>
|
||||
#include <flipper.pb.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
#include <applications/system/js_app/js_value.h>
|
||||
|
||||
static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)),
|
||||
@@ -38,4 +39,16 @@ static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
JsThread*,
|
||||
(const char* script_path, JsThreadCallback callback, void* context)),
|
||||
API_METHOD(js_thread_stop, void, (JsThread * worker)),
|
||||
API_METHOD(js_value_buffer_size, size_t, (const JsValueParseDeclaration declaration)),
|
||||
API_METHOD(
|
||||
js_value_parse,
|
||||
JsValueParseStatus,
|
||||
(struct mjs * mjs,
|
||||
const JsValueParseDeclaration declaration,
|
||||
JsValueParseFlag flags,
|
||||
mjs_val_t* buffer,
|
||||
size_t buf_size,
|
||||
mjs_val_t* source,
|
||||
size_t n_c_vals,
|
||||
...)),
|
||||
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));
|
||||
|
||||
Reference in New Issue
Block a user