Files
edgetx-gx12/radio/src/tests/lua.cpp
T
2026-08-30 18:48:47 +10:00

287 lines
10 KiB
C++

/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <math.h>
#include "gtests.h"
#if defined(LUA)
#include "edgetx.h"
#include "lua/lua_states.h"
#include <filesystem>
#define MIXSRC_THR (MIXSRC_FIRST_STICK + inputMappingGetThrottle())
#define MIXSRC_TRIMTHR (MIXSRC_FIRST_TRIM + inputMappingGetThrottle())
::testing::AssertionResult __luaExecStr(const char * str)
{
extern lua_State * lsScripts;
if (!lsScripts) { luaInitMainState(); luaInit(); }
if (!lsScripts) return ::testing::AssertionFailure() << "No Lua state!";
if (luaL_dostring(lsScripts, str)) {
return ::testing::AssertionFailure() << "lua error: " << lua_tostring(lsScripts, -1);
}
return ::testing::AssertionSuccess();
}
#define luaExecStr(test) EXPECT_TRUE(__luaExecStr(test))
TEST(Lua, testSetModelInfo)
{
luaExecStr("info = model.getInfo()");
// luaExecStr("print('model name: '..info.name..' id: '..info.id)");
luaExecStr("info.name = 'modelA'");
luaExecStr("model.setInfo(info)");
// luaExecStr("print('model name: '..info.name..' id: '..info.id)");
EXPECT_STRNEQ("modelA", g_model.header.name);
luaExecStr("info.name = 'Model 1'");
luaExecStr("model.setInfo(info)");
// luaExecStr("print('model name: '..info.name..' id: '..info.id)");
EXPECT_STRNEQ("Model 1", g_model.header.name);
}
TEST(Lua, testPanicProtection)
{
bool passed = false;
PROTECT_LUA() {
PROTECT_LUA() {
// simulate panic
longjmp(global_lj->b, 1);
}
else {
// we should come here
passed = true;
}
UNPROTECT_LUA();
}
else {
// and not here
// TRACE("testLuaProtection: test 1 FAILED");
FAIL() << "Failed test 1";
}
UNPROTECT_LUA()
EXPECT_EQ(passed, true);
passed = false;
PROTECT_LUA() {
PROTECT_LUA() {
int a = 5;
UNUSED(a);
}
else {
// we should not come here
// TRACE("testLuaProtection: test 2 FAILED");
FAIL() << "Failed test 2";
}
UNPROTECT_LUA()
// simulate panic
longjmp(global_lj->b, 1);
}
else {
// we should come here
passed = true;
}
UNPROTECT_LUA()
EXPECT_EQ(passed, true);
}
TEST(Lua, testModelInputs)
{
MODEL_RESET();
luaExecStr("noInputs = model.getInputsCount(0)");
luaExecStr("if noInputs > 0 then error('getInputsCount()') end");
// add one line on Input4
#if defined(SURFACE_RADIO)
luaExecStr("model.insertInput(3, 0, {name='test1', source=MIXSRC_TH, weight=56, offset=3, switch=2})");
#else
luaExecStr("model.insertInput(3, 0, {name='test1', source=MIXSRC_Thr, weight=56, offset=3, switch=2})");
#endif
EXPECT_EQ(3u, g_model.expoData[0].chn);
EXPECT_STRNEQ("test1", g_model.expoData[0].name);
EXPECT_EQ(MIXSRC_THR, g_model.expoData[0].srcRaw);
EXPECT_EQ(56u, g_model.expoData[0].weight);
EXPECT_EQ(3u, g_model.expoData[0].offset);
EXPECT_EQ(2, g_model.expoData[0].swtch);
// add another one before existing line on Input4
#if defined(SURFACE_RADIO)
luaExecStr("model.insertInput(3, 0, {name='test2', source=MIXSRC_ST, weight=-56})");
#else
luaExecStr("model.insertInput(3, 0, {name='test2', source=MIXSRC_Rud, weight=-56})");
#endif
EXPECT_EQ(3u, g_model.expoData[0].chn);
EXPECT_STRNEQ("test2", g_model.expoData[0].name);
EXPECT_EQ((short int)MIXSRC_FIRST_STICK, g_model.expoData[0].srcRaw);
SourceNumVal v;
v.rawValue = g_model.expoData[0].weight;
EXPECT_EQ(-56, v.value);
EXPECT_EQ(0u, g_model.expoData[0].offset);
EXPECT_EQ(0, g_model.expoData[0].swtch);
EXPECT_EQ(3u, g_model.expoData[1].chn);
EXPECT_STRNEQ("test1", g_model.expoData[1].name);
EXPECT_EQ(MIXSRC_THR, g_model.expoData[1].srcRaw);
EXPECT_EQ(56u, g_model.expoData[1].weight);
EXPECT_EQ(3u, g_model.expoData[1].offset);
EXPECT_EQ(2, g_model.expoData[1].swtch);
// add another line after existing lines on Input4
#if defined(SURFACE_RADIO)
luaExecStr("model.insertInput(3, model.getInputsCount(3), {name='test3', source=MIXSRC_TH, weight=100})");
#else
luaExecStr("model.insertInput(3, model.getInputsCount(3), {name='test3', source=MIXSRC_Ail, weight=100})");
#endif
EXPECT_EQ(3u, g_model.expoData[0].chn);
EXPECT_STRNEQ("test2", g_model.expoData[0].name);
EXPECT_EQ(MIXSRC_FIRST_STICK, g_model.expoData[0].srcRaw);
v.rawValue = g_model.expoData[0].weight;
EXPECT_EQ(-56, v.value);
EXPECT_EQ(0u, g_model.expoData[0].offset);
EXPECT_EQ(0, g_model.expoData[0].swtch);
EXPECT_EQ(3u, g_model.expoData[1].chn);
EXPECT_STRNEQ("test1", g_model.expoData[1].name);
EXPECT_EQ(MIXSRC_THR, g_model.expoData[1].srcRaw);
EXPECT_EQ(56u, g_model.expoData[1].weight);
EXPECT_EQ(3u, g_model.expoData[1].offset);
EXPECT_EQ(2, g_model.expoData[1].swtch);
EXPECT_EQ(3u, g_model.expoData[2].chn);
EXPECT_STRNEQ("test3", g_model.expoData[2].name);
#if defined(SURFACE_RADIO)
EXPECT_EQ(MIXSRC_THR, g_model.expoData[2].srcRaw);
#else
EXPECT_EQ(MIXSRC_LAST_STICK, g_model.expoData[2].srcRaw);
#endif
EXPECT_EQ(100u, g_model.expoData[2].weight);
EXPECT_EQ(0u, g_model.expoData[2].offset);
EXPECT_EQ(0, g_model.expoData[2].swtch);
// verify number of lines for Input4
luaExecStr("noInputs = model.getInputsCount(3)");
luaExecStr("if noInputs ~= 3 then error('getInputsCount()') end");
}
TEST(Lua, Switches)
{
luaExecStr("if MIXSRC_SA == nil then error('failed') end");
luaExecStr("if MIXSRC_SB == nil then error('failed') end");
#if defined(SURFACE_RADIO)
luaExecStr("if getSwitchIndex('St-') == nil then error('failed') end");
#else
luaExecStr("if getSwitchIndex('Rud-') == nil then error('failed') end");
#endif
}
TEST(Lua, testFloatIntegerEquality)
{
// 0.5 is not an integer, so it must not equal 0 (regression #7587)
// both directions asserted explicitly so the intent is obvious at a glance
luaExecStr("if 0.5 == 0 then error('0.5 == 0') end");
luaExecStr("if not (0.5 ~= 0) then error('0.5 ~= 0') end");
luaExecStr("if 0.50 == 0 then error('0.50 == 0') end");
luaExecStr("if not (0.50 ~= 0) then error('0.50 ~= 0') end");
luaExecStr("if 0.50 == 0.0 then error('0.50 == 0.0') end");
// ... even when the value comes from a variable, as in the reported issue
luaExecStr("local v = 0.50; if v == 0 then error('v == 0') end");
luaExecStr("local v = 0.50; if not (v ~= 0) then error('v ~= 0') end");
// negative non-integral floats must not equal integers either
luaExecStr("if -0.5 == 0 then error('-0.5 == 0') end");
luaExecStr("if not (-0.5 ~= 0) then error('-0.5 ~= 0') end");
luaExecStr("if not (-0.5 < 0) then error('-0.5 < 0') end");
luaExecStr("if -0.5 > 0 then error('-0.5 > 0') end");
// integral floats still compare equal to their integer counterpart
luaExecStr("if 1.0 ~= 1 then error('1.0 ~= 1') end");
luaExecStr("if 0.0 ~= 0 then error('0.0 ~= 0') end");
luaExecStr("if -1.0 ~= -1 then error('-1.0 ~= -1') end");
// order comparisons on the same values must remain consistent
luaExecStr("if not (0.5 >= 0) then error('0.5 >= 0') end");
luaExecStr("if 0.5 <= 0 then error('0.5 <= 0') end");
luaExecStr("if not (0.5 > 0) then error('0.5 > 0') end");
luaExecStr("if 0.5 < 0 then error('0.5 < 0') end");
luaExecStr("if not (0.50 >= 0) then error('0.50 >= 0') end");
luaExecStr("if 0.50 <= 0 then error('0.50 <= 0') end");
luaExecStr("if not (1.0 >= 1) then error('1.0 >= 1') end");
luaExecStr("if not (1.0 <= 1) then error('1.0 <= 1') end");
luaExecStr("if not (0.0 >= 0) then error('0.0 >= 0') end");
luaExecStr("if not (0.0 <= 0) then error('0.0 <= 0') end");
// mixed arithmetic must still yield floats; the fix only affects equality
luaExecStr("if math.type(0.5 + 0) ~= 'float' then error('0.5 + 0') end");
luaExecStr("if math.type(1.0 + 1) ~= 'float' then error('1.0 + 1') end");
luaExecStr("if math.type(1 / 2) ~= 'float' then error('1 / 2') end");
luaExecStr("if math.type(7.5 % 2) ~= 'float' then error('7.5 % 2') end");
luaExecStr("if math.type(0.5 * 2) ~= 'float' then error('0.5 * 2') end");
}
TEST(Lua, testLegacyNames)
{
MODEL_RESET();
#if defined(SURFACE_RADIO)
for (uint8_t i = 0; i < 2; i ++)
anaSetFiltered(i, -1024);
luaExecStr("value = getValue('thr')");
luaExecStr("if value ~= -1024 then error('th not defined in Legacy') end");
luaExecStr("value = getValue('ste')");
luaExecStr("if value ~= -1024 then error('st not defined in Legacy') end");
#else
for (uint8_t i = 0; i < 4; i ++)
anaSetFiltered(i, -1024);
luaExecStr("value = getValue('thr')");
luaExecStr("if value ~= -1024 then error('thr not defined in Legacy') end");
luaExecStr("value = getValue('ail')");
luaExecStr("if value ~= -1024 then error('ail not defined in Legacy') end");
luaExecStr("value = getValue('rud')");
luaExecStr("if value ~= -1024 then error('rud not defined in Legacy') end");
luaExecStr("value = getValue('ele')");
luaExecStr("if value ~= -1024 then error('ele not defined in Legacy') end");
#endif
}
TEST(Lua, ioSeek)
{
const char io_seek_tst[] =
"local file_name = \"seek-test.txt\"\n"
"local file = io.open(file_name, \"w\")\n"
"io.write(file, \"abcd\")\n"
"io.close(file)\n"
"file = io.open(file_name, \"r\")\n"
// the file should have 4 characters
"assert(#io.read(file, 32) == 4)\n"
// io.seek() should return 0 if it is successful
"assert(io.seek(file, 2) == 0)\n"
"local r = io.read(file, 32)\n"
// if reading from position 2,
// we should read 2 characters,
"assert(#r == 2)\n";
luaExecStr(io_seek_tst);
std::filesystem::remove(simuFatfsGetRealPath("seek-test.txt"));
}
#endif // #if defined(LUA)