mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 04:34:43 +04:00
Merge remote-tracking branch 'OFW/dev' into dev
This commit is contained in:
@@ -221,6 +221,14 @@ App(
|
||||
requires=["unit_tests"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="test_js",
|
||||
sources=["tests/common/*.c", "tests/js/*.c"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="get_api",
|
||||
requires=["unit_tests", "js_app"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="test_strint",
|
||||
sources=["tests/common/*.c", "tests/strint/*.c"],
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
let tests = require("tests");
|
||||
|
||||
tests.assert_eq(1337, 1337);
|
||||
tests.assert_eq("hello", "hello");
|
||||
@@ -0,0 +1,30 @@
|
||||
let tests = require("tests");
|
||||
let event_loop = require("event_loop");
|
||||
|
||||
let ext = {
|
||||
i: 0,
|
||||
received: false,
|
||||
};
|
||||
|
||||
let queue = event_loop.queue(16);
|
||||
|
||||
event_loop.subscribe(queue.input, function (_, item, tests, ext) {
|
||||
tests.assert_eq(123, item);
|
||||
ext.received = true;
|
||||
}, tests, ext);
|
||||
|
||||
event_loop.subscribe(event_loop.timer("periodic", 1), function (_, _item, queue, counter, ext) {
|
||||
ext.i++;
|
||||
queue.send(123);
|
||||
if (counter === 10)
|
||||
event_loop.stop();
|
||||
return [queue, counter + 1, ext];
|
||||
}, queue, 1, ext);
|
||||
|
||||
event_loop.subscribe(event_loop.timer("oneshot", 1000), function (_, _item, tests) {
|
||||
tests.fail("event loop was not stopped");
|
||||
}, tests);
|
||||
|
||||
event_loop.run();
|
||||
tests.assert_eq(10, ext.i);
|
||||
tests.assert_eq(true, ext.received);
|
||||
@@ -0,0 +1,34 @@
|
||||
let tests = require("tests");
|
||||
let math = require("math");
|
||||
|
||||
// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16
|
||||
|
||||
// basics
|
||||
tests.assert_float_close(5, math.abs(-5), math.EPSILON);
|
||||
tests.assert_float_close(0.5, math.abs(-0.5), math.EPSILON);
|
||||
tests.assert_float_close(5, math.abs(5), math.EPSILON);
|
||||
tests.assert_float_close(0.5, math.abs(0.5), math.EPSILON);
|
||||
tests.assert_float_close(3, math.cbrt(27), math.EPSILON);
|
||||
tests.assert_float_close(6, math.ceil(5.3), math.EPSILON);
|
||||
tests.assert_float_close(31, math.clz32(1), math.EPSILON);
|
||||
tests.assert_float_close(5, math.floor(5.7), math.EPSILON);
|
||||
tests.assert_float_close(5, math.max(3, 5), math.EPSILON);
|
||||
tests.assert_float_close(3, math.min(3, 5), math.EPSILON);
|
||||
tests.assert_float_close(-1, math.sign(-5), math.EPSILON);
|
||||
tests.assert_float_close(5, math.trunc(5.7), math.EPSILON);
|
||||
|
||||
// trig
|
||||
tests.assert_float_close(1.0471975511965976, math.acos(0.5), math.EPSILON);
|
||||
tests.assert_float_close(1.3169578969248166, math.acosh(2), math.EPSILON);
|
||||
tests.assert_float_close(0.5235987755982988, math.asin(0.5), math.EPSILON);
|
||||
tests.assert_float_close(1.4436354751788103, math.asinh(2), math.EPSILON);
|
||||
tests.assert_float_close(0.7853981633974483, math.atan(1), math.EPSILON);
|
||||
tests.assert_float_close(0.7853981633974483, math.atan2(1, 1), math.EPSILON);
|
||||
tests.assert_float_close(0.5493061443340549, math.atanh(0.5), math.EPSILON);
|
||||
tests.assert_float_close(-1, math.cos(math.PI), math.EPSILON * 18); // Error 3.77475828372553223744e-15
|
||||
tests.assert_float_close(1, math.sin(math.PI / 2), math.EPSILON * 4.5); // Error 9.99200722162640886381e-16
|
||||
|
||||
// powers
|
||||
tests.assert_float_close(5, math.sqrt(25), math.EPSILON);
|
||||
tests.assert_float_close(8, math.pow(2, 3), math.EPSILON);
|
||||
tests.assert_float_close(2.718281828459045, math.exp(1), math.EPSILON * 2); // Error 4.44089209850062616169e-16
|
||||
136
applications/debug/unit_tests/resources/unit_tests/js/storage.js
Normal file
136
applications/debug/unit_tests/resources/unit_tests/js/storage.js
Normal file
@@ -0,0 +1,136 @@
|
||||
let storage = require("storage");
|
||||
let tests = require("tests");
|
||||
|
||||
let baseDir = "/ext/.tmp/unit_tests";
|
||||
|
||||
tests.assert_eq(true, storage.rmrf(baseDir));
|
||||
tests.assert_eq(true, storage.makeDirectory(baseDir));
|
||||
|
||||
// write
|
||||
let file = storage.openFile(baseDir + "/helloworld", "w", "create_always");
|
||||
tests.assert_eq(true, !!file);
|
||||
tests.assert_eq(true, file.isOpen());
|
||||
tests.assert_eq(13, file.write("Hello, World!"));
|
||||
tests.assert_eq(true, file.close());
|
||||
tests.assert_eq(false, file.isOpen());
|
||||
|
||||
// read
|
||||
file = storage.openFile(baseDir + "/helloworld", "r", "open_existing");
|
||||
tests.assert_eq(true, !!file);
|
||||
tests.assert_eq(true, file.isOpen());
|
||||
tests.assert_eq(13, file.size());
|
||||
tests.assert_eq("Hello, World!", file.read("ascii", 128));
|
||||
tests.assert_eq(true, file.close());
|
||||
tests.assert_eq(false, file.isOpen());
|
||||
|
||||
// seek
|
||||
file = storage.openFile(baseDir + "/helloworld", "r", "open_existing");
|
||||
tests.assert_eq(true, !!file);
|
||||
tests.assert_eq(true, file.isOpen());
|
||||
tests.assert_eq(13, file.size());
|
||||
tests.assert_eq("Hello, World!", file.read("ascii", 128));
|
||||
tests.assert_eq(true, file.seekAbsolute(1));
|
||||
tests.assert_eq(true, file.seekRelative(2));
|
||||
tests.assert_eq(3, file.tell());
|
||||
tests.assert_eq(false, file.eof());
|
||||
tests.assert_eq("lo, World!", file.read("ascii", 128));
|
||||
tests.assert_eq(true, file.eof());
|
||||
tests.assert_eq(true, file.close());
|
||||
tests.assert_eq(false, file.isOpen());
|
||||
|
||||
// byte-level copy
|
||||
let src = storage.openFile(baseDir + "/helloworld", "r", "open_existing");
|
||||
let dst = storage.openFile(baseDir + "/helloworld2", "rw", "create_always");
|
||||
tests.assert_eq(true, !!src);
|
||||
tests.assert_eq(true, src.isOpen());
|
||||
tests.assert_eq(true, !!dst);
|
||||
tests.assert_eq(true, dst.isOpen());
|
||||
tests.assert_eq(true, src.copyTo(dst, 10));
|
||||
tests.assert_eq(true, dst.seekAbsolute(0));
|
||||
tests.assert_eq("Hello, Wor", dst.read("ascii", 128));
|
||||
tests.assert_eq(true, src.copyTo(dst, 3));
|
||||
tests.assert_eq(true, dst.seekAbsolute(0));
|
||||
tests.assert_eq("Hello, World!", dst.read("ascii", 128));
|
||||
tests.assert_eq(true, src.eof());
|
||||
tests.assert_eq(true, src.close());
|
||||
tests.assert_eq(false, src.isOpen());
|
||||
tests.assert_eq(true, dst.eof());
|
||||
tests.assert_eq(true, dst.close());
|
||||
tests.assert_eq(false, dst.isOpen());
|
||||
|
||||
// truncate
|
||||
tests.assert_eq(true, storage.copy(baseDir + "/helloworld", baseDir + "/helloworld2"));
|
||||
file = storage.openFile(baseDir + "/helloworld2", "w", "open_existing");
|
||||
tests.assert_eq(true, !!file);
|
||||
tests.assert_eq(true, file.seekAbsolute(5));
|
||||
tests.assert_eq(true, file.truncate());
|
||||
tests.assert_eq(true, file.close());
|
||||
file = storage.openFile(baseDir + "/helloworld2", "r", "open_existing");
|
||||
tests.assert_eq(true, !!file);
|
||||
tests.assert_eq("Hello", file.read("ascii", 128));
|
||||
tests.assert_eq(true, file.close());
|
||||
|
||||
// existence
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld"));
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld2"));
|
||||
tests.assert_eq(false, storage.fileExists(baseDir + "/sus_amogus_123"));
|
||||
tests.assert_eq(false, storage.directoryExists(baseDir + "/helloworld"));
|
||||
tests.assert_eq(false, storage.fileExists(baseDir));
|
||||
tests.assert_eq(true, storage.directoryExists(baseDir));
|
||||
tests.assert_eq(true, storage.fileOrDirExists(baseDir));
|
||||
tests.assert_eq(true, storage.remove(baseDir + "/helloworld2"));
|
||||
tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld2"));
|
||||
|
||||
// stat
|
||||
let stat = storage.stat(baseDir + "/helloworld");
|
||||
tests.assert_eq(true, !!stat);
|
||||
tests.assert_eq(baseDir + "/helloworld", stat.path);
|
||||
tests.assert_eq(false, stat.isDirectory);
|
||||
tests.assert_eq(13, stat.size);
|
||||
|
||||
// rename
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld"));
|
||||
tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123"));
|
||||
tests.assert_eq(true, storage.rename(baseDir + "/helloworld", baseDir + "/helloworld123"));
|
||||
tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld"));
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld123"));
|
||||
tests.assert_eq(true, storage.rename(baseDir + "/helloworld123", baseDir + "/helloworld"));
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld"));
|
||||
tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123"));
|
||||
|
||||
// copy
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld"));
|
||||
tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123"));
|
||||
tests.assert_eq(true, storage.copy(baseDir + "/helloworld", baseDir + "/helloworld123"));
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld"));
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld123"));
|
||||
|
||||
// next avail
|
||||
tests.assert_eq("helloworld1", storage.nextAvailableFilename(baseDir, "helloworld", "", 20));
|
||||
|
||||
// fs info
|
||||
let fsInfo = storage.fsInfo("/ext");
|
||||
tests.assert_eq(true, !!fsInfo);
|
||||
tests.assert_eq(true, fsInfo.freeSpace < fsInfo.totalSpace); // idk \(-_-)/
|
||||
fsInfo = storage.fsInfo("/int");
|
||||
tests.assert_eq(true, !!fsInfo);
|
||||
tests.assert_eq(true, fsInfo.freeSpace < fsInfo.totalSpace);
|
||||
|
||||
// path operations
|
||||
tests.assert_eq(true, storage.arePathsEqual("/ext/test", "/ext/Test"));
|
||||
tests.assert_eq(false, storage.arePathsEqual("/ext/test", "/ext/Testttt"));
|
||||
tests.assert_eq(true, storage.isSubpathOf("/ext/test", "/ext/test/sub"));
|
||||
tests.assert_eq(false, storage.isSubpathOf("/ext/test/sub", "/ext/test"));
|
||||
|
||||
// dir
|
||||
let entries = storage.readDirectory(baseDir);
|
||||
tests.assert_eq(true, !!entries);
|
||||
// FIXME: (-nofl) this test suite assumes that files are listed by
|
||||
// `readDirectory` in the exact order that they were created, which is not
|
||||
// something that is actually guaranteed.
|
||||
// Possible solution: sort and compare the array.
|
||||
tests.assert_eq("helloworld", entries[0].path);
|
||||
tests.assert_eq("helloworld123", entries[1].path);
|
||||
|
||||
tests.assert_eq(true, storage.rmrf(baseDir));
|
||||
tests.assert_eq(true, storage.makeDirectory(baseDir));
|
||||
88
applications/debug/unit_tests/tests/js/js_test.c
Normal file
88
applications/debug/unit_tests/tests/js/js_test.c
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "../test.h" // IWYU pragma: keep
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js")
|
||||
|
||||
typedef enum {
|
||||
JsTestsFinished = 1,
|
||||
JsTestsError = 2,
|
||||
} JsTestFlag;
|
||||
|
||||
typedef struct {
|
||||
FuriEventFlag* event_flags;
|
||||
FuriString* error_string;
|
||||
} JsTestCallbackContext;
|
||||
|
||||
static void js_test_callback(JsThreadEvent event, const char* msg, void* param) {
|
||||
JsTestCallbackContext* context = param;
|
||||
if(event == JsThreadEventPrint) {
|
||||
FURI_LOG_I("js_test", "%s", msg);
|
||||
} else if(event == JsThreadEventError || event == JsThreadEventErrorTrace) {
|
||||
context->error_string = furi_string_alloc_set_str(msg);
|
||||
furi_event_flag_set(context->event_flags, JsTestsFinished | JsTestsError);
|
||||
} else if(event == JsThreadEventDone) {
|
||||
furi_event_flag_set(context->event_flags, JsTestsFinished);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_test_run(const char* script_path) {
|
||||
JsTestCallbackContext* context = malloc(sizeof(JsTestCallbackContext));
|
||||
context->event_flags = furi_event_flag_alloc();
|
||||
|
||||
JsThread* thread = js_thread_run(script_path, js_test_callback, context);
|
||||
uint32_t flags = furi_event_flag_wait(
|
||||
context->event_flags, JsTestsFinished, FuriFlagWaitAny, FuriWaitForever);
|
||||
if(flags & FuriFlagError) {
|
||||
// getting the flags themselves should not fail
|
||||
furi_crash();
|
||||
}
|
||||
|
||||
FuriString* error_string = context->error_string;
|
||||
|
||||
js_thread_stop(thread);
|
||||
furi_event_flag_free(context->event_flags);
|
||||
free(context);
|
||||
|
||||
if(flags & JsTestsError) {
|
||||
// memory leak: not freeing the FuriString if the tests fail,
|
||||
// because mu_fail executes a return
|
||||
//
|
||||
// who cares tho?
|
||||
mu_fail(furi_string_get_cstr(error_string));
|
||||
}
|
||||
}
|
||||
|
||||
MU_TEST(js_test_basic) {
|
||||
js_test_run(JS_SCRIPT_PATH("basic"));
|
||||
}
|
||||
MU_TEST(js_test_math) {
|
||||
js_test_run(JS_SCRIPT_PATH("math"));
|
||||
}
|
||||
MU_TEST(js_test_event_loop) {
|
||||
js_test_run(JS_SCRIPT_PATH("event_loop"));
|
||||
}
|
||||
MU_TEST(js_test_storage) {
|
||||
js_test_run(JS_SCRIPT_PATH("storage"));
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_js) {
|
||||
MU_RUN_TEST(js_test_basic);
|
||||
MU_RUN_TEST(js_test_math);
|
||||
MU_RUN_TEST(js_test_event_loop);
|
||||
MU_RUN_TEST(js_test_storage);
|
||||
}
|
||||
|
||||
int run_minunit_test_js(void) {
|
||||
MU_RUN_SUITE(test_js);
|
||||
return MU_EXIT_CODE;
|
||||
}
|
||||
|
||||
TEST_API_DEFINE(run_minunit_test_js)
|
||||
@@ -31,7 +31,7 @@ extern "C" {
|
||||
#include <Windows.h>
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#define __func__ __FUNCTION__
|
||||
#define __func__ __FUNCTION__ //-V1059
|
||||
#endif
|
||||
|
||||
#elif defined(__unix__) || defined(__unix) || defined(unix) || \
|
||||
@@ -56,7 +56,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#if __GNUC__ >= 5 && !defined(__STDC_VERSION__)
|
||||
#define __func__ __extension__ __FUNCTION__
|
||||
#define __func__ __extension__ __FUNCTION__ //-V1059
|
||||
#endif
|
||||
|
||||
#else
|
||||
@@ -102,6 +102,7 @@ void minunit_printf_warning(const char* format, ...);
|
||||
MU__SAFE_BLOCK(minunit_setup = setup_fun; minunit_teardown = teardown_fun;)
|
||||
|
||||
/* Test runner */
|
||||
//-V:MU_RUN_TEST:550
|
||||
#define MU_RUN_TEST(test) \
|
||||
MU__SAFE_BLOCK( \
|
||||
if(minunit_real_timer == 0 && minunit_proc_timer == 0) { \
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include <rpc/rpc_i.h>
|
||||
#include <flipper.pb.h>
|
||||
#include <core/event_loop.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
|
||||
static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)),
|
||||
@@ -33,13 +33,9 @@ static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
xQueueGenericSend,
|
||||
BaseType_t,
|
||||
(QueueHandle_t, const void* const, TickType_t, const BaseType_t)),
|
||||
API_METHOD(furi_event_loop_alloc, FuriEventLoop*, (void)),
|
||||
API_METHOD(furi_event_loop_free, void, (FuriEventLoop*)),
|
||||
API_METHOD(
|
||||
furi_event_loop_subscribe_message_queue,
|
||||
void,
|
||||
(FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*)),
|
||||
API_METHOD(furi_event_loop_unsubscribe, void, (FuriEventLoop*, FuriEventLoopObject*)),
|
||||
API_METHOD(furi_event_loop_run, void, (FuriEventLoop*)),
|
||||
API_METHOD(furi_event_loop_stop, void, (FuriEventLoop*)),
|
||||
js_thread_run,
|
||||
JsThread*,
|
||||
(const char* script_path, JsThreadCallback callback, void* context)),
|
||||
API_METHOD(js_thread_stop, void, (JsThread * worker)),
|
||||
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));
|
||||
|
||||
Reference in New Issue
Block a user