diff --git a/companion/src/CMakeLists.txt b/companion/src/CMakeLists.txt index d5fd9976d..58ae89720 100644 --- a/companion/src/CMakeLists.txt +++ b/companion/src/CMakeLists.txt @@ -326,6 +326,10 @@ set(simu_SRCS simulator.cpp ) add_executable(${SIMULATOR_NAME} MACOSX_BUNDLE ${WIN_EXECUTABLE_TYPE} ${simu_SRCS} ${icon_RC}) target_link_libraries(${SIMULATOR_NAME} PRIVATE ${CPN_COMMON_LIB}) +if(NOT MSVC) + add_subdirectory(tests) +endif() + ############# Install #################### # Generate list of simulator plugins, used by all platforms diff --git a/companion/src/firmwares/eeprominterface.cpp b/companion/src/firmwares/eeprominterface.cpp index c1aee1b7b..d4121a8a4 100644 --- a/companion/src/firmwares/eeprominterface.cpp +++ b/companion/src/firmwares/eeprominterface.cpp @@ -103,20 +103,18 @@ void EEPROMInterface::showEepromErrors(QWidget *parent, const QString &title, co #endif // TODO: No GUI here, e.g. return string list instead -void EEPROMInterface::showEepromWarnings(QWidget *parent, const QString &title, unsigned long errorsFound) +QString EEPROMInterface::getEepromWarnings(unsigned long errorsFound) { std::bitset errors((unsigned long long)errorsFound); QStringList warningsList; - if (errors.test(WARNING_WRONG_FIRMWARE)) { warningsList << tr("- Your radio probably uses a wrong firmware,\n eeprom size is 4096 but only the first 2048 are used"); } - if (errors.test(OLD_VERSION)) { warningsList << tr("- Your eeprom is from an old version of OpenTX, upgrading!\n To keep your original file as a backup, please choose File -> Save As specifying a different name."); } + if (errors.test(WARNING_WRONG_FIRMWARE)) { + warningsList << tr("- Your radio probably uses a wrong firmware,\n eeprom size is 4096 but only the first 2048 are used"); + } + if (errors.test(OLD_VERSION)) { + warningsList << tr("- Your eeprom is from an old version of OpenTX, upgrading!\n To keep your original file as a backup, please choose File -> Save As specifying a different name."); + } - QMessageBox msgBox(parent); - msgBox.setWindowTitle(title); - msgBox.setIcon(QMessageBox::Warning); - msgBox.setText(tr("Warnings!")); - msgBox.setInformativeText(warningsList.join("\n")); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.exec(); + return warningsList.join("\n"); } diff --git a/companion/src/firmwares/eeprominterface.h b/companion/src/firmwares/eeprominterface.h index b562f50e9..7b6447d9d 100644 --- a/companion/src/firmwares/eeprominterface.h +++ b/companion/src/firmwares/eeprominterface.h @@ -178,7 +178,7 @@ class EEPROMInterface virtual int getSize(const GeneralSettings &) = 0; //static void showEepromErrors(QWidget *parent, const QString &title, const QString &mainMessage, unsigned long errorsFound); - static void showEepromWarnings(QWidget *parent, const QString &title, unsigned long errorsFound); + static QString getEepromWarnings(unsigned long errorsFound); protected: diff --git a/companion/src/firmwares/opentx/opentxeeprom.cpp b/companion/src/firmwares/opentx/opentxeeprom.cpp index 1c8a0f994..fdb1088f6 100644 --- a/companion/src/firmwares/opentx/opentxeeprom.cpp +++ b/companion/src/firmwares/opentx/opentxeeprom.cpp @@ -71,7 +71,15 @@ inline int MAX_SWITCH_SLOTS(Board::Type board, int version) return 8; } -#define MAX_SWITCHES_POSITION(board, version) (Boards::getCapability(board, Board::SwitchPositions)) +inline int MAX_SWITCHES_POSITION(Board::Type board, int version) +{ + if (version < 219 && IS_TARANIS_X7(board)) + return Boards::getCapability(board, Board::SwitchPositions) - 2*3; + + return Boards::getCapability(board, Board::SwitchPositions); +} + +//#define MAX_SWITCHES_POSITION(board, version) (Boards::getCapability(board, Board::SwitchPositions)) #define MAX_ROTARY_ENCODERS(board) (IS_SKY9X(board) ? 1 : 0) #define MAX_FLIGHT_MODES(board, version) 9 #define MAX_TIMERS(board, version) 3 @@ -1892,7 +1900,7 @@ class SensorField: public TransformedField { if (sensor.type == SensorData::TELEM_TYPE_CUSTOM) { sensor.id = _id; sensor.subid = _subid; - sensor.instance = _instance & 0x1F; // 5 bits instance + sensor.instance = (_instance & 0x1F) + (version <= 218 ? -1 : 0); // 5 bits instance sensor.rxIdx = (_instance >> 5) & 0x03; // 2 bits Rx idx sensor.moduleIdx = (_instance >> 7) & 0x1; // 1 bit module idx sensor.ratio = _ratio; @@ -2112,10 +2120,11 @@ class ModuleUnionField: public UnionField { }; public: - ModuleUnionField(DataField * parent, ModuleData & module): + ModuleUnionField(DataField * parent, ModuleData & module, Board::Type board, unsigned int version): UnionField(parent, module.protocol) { - Append(new AccessField(parent, module)); + if (version >= 219) + Append(new AccessField(parent, module)); Append(new PxxField(parent, module)); Append(new MultiField(parent, module)); Append(new PPMField(parent, module.ppm)); @@ -2129,7 +2138,7 @@ class ModuleField: public TransformedField { TransformedField(parent, internalField), internalField(this, "Module"), module(module), - protocolsConversionTable(board) + protocolsConversionTable(board, version) { internalField.Append(new ConversionField >(this, module.protocol, &protocolsConversionTable, "Protocol", DataField::tr("OpenTX doesn't accept this radio protocol"))); internalField.Append(new SignedField<4>(this, module.rfProtocol)); @@ -2144,7 +2153,7 @@ class ModuleField: public TransformedField { } } - internalField.Append(new ModuleUnionField(parent, module)); + internalField.Append(new ModuleUnionField(parent, module, board, version)); } virtual void beforeExport() diff --git a/companion/src/firmwares/opentx/opentxeeprom.h b/companion/src/firmwares/opentx/opentxeeprom.h index 9785c5f7b..95f75ce19 100644 --- a/companion/src/firmwares/opentx/opentxeeprom.h +++ b/companion/src/firmwares/opentx/opentxeeprom.h @@ -73,7 +73,7 @@ class OpenTxGeneralData: public TransformedField { class ProtocolsConversionTable: public ConversionTable { public: - ProtocolsConversionTable(Board::Type board) + ProtocolsConversionTable(Board::Type board, int version) { int val = 0; addConversion(PULSES_OFF, val++); @@ -83,8 +83,10 @@ class ProtocolsConversionTable: public ConversionTable addConversion(PULSES_PXX_XJT_D8, val); addConversion(PULSES_PXX_XJT_LR12, val++); - addConversion(PULSES_ACCESS_ISRM, val); - addConversion(PULSES_ACCST_ISRM_D16, val++); + if (version >= 219) { + addConversion(PULSES_ACCESS_ISRM, val); + addConversion(PULSES_ACCST_ISRM_D16, val++); + } addConversion(PULSES_LP45, val); addConversion(PULSES_DSM2, val); diff --git a/companion/src/firmwares/rawsource.h b/companion/src/firmwares/rawsource.h index 267d6af43..e9273d399 100644 --- a/companion/src/firmwares/rawsource.h +++ b/companion/src/firmwares/rawsource.h @@ -259,11 +259,11 @@ class RawSource { bool isTimeBased(Board::Type board = Board::BOARD_UNKNOWN) const; bool isAvailable(const ModelData * const model = NULL, const GeneralSettings * const gs = NULL, Board::Type board = Board::BOARD_UNKNOWN) const; - bool operator == ( const RawSource & other) { + bool operator == ( const RawSource & other) const { return (this->type == other.type) && (this->index == other.index); } - bool operator != ( const RawSource & other) { + bool operator != ( const RawSource & other) const { return (this->type != other.type) || (this->index != other.index); } diff --git a/companion/src/firmwares/rawswitch.h b/companion/src/firmwares/rawswitch.h index 598e25f01..6d0c93700 100644 --- a/companion/src/firmwares/rawswitch.h +++ b/companion/src/firmwares/rawswitch.h @@ -91,11 +91,11 @@ class RawSwitch { QString toString(Board::Type board = Board::BOARD_UNKNOWN, const GeneralSettings * const generalSettings = NULL, const ModelData * const modelData = NULL) const; bool isAvailable(const ModelData * const model = NULL, const GeneralSettings * const gs = NULL, Board::Type board = Board::BOARD_UNKNOWN) const; - bool operator== ( const RawSwitch& other) { + bool operator== ( const RawSwitch& other) const { return (this->type == other.type) && (this->index == other.index); } - bool operator!= ( const RawSwitch& other) { + bool operator!= ( const RawSwitch& other) const { return (this->type != other.type) || (this->index != other.index); } diff --git a/companion/src/mdichild.cpp b/companion/src/mdichild.cpp index f3001e615..4d0d7dcb8 100644 --- a/companion/src/mdichild.cpp +++ b/companion/src/mdichild.cpp @@ -1334,7 +1334,7 @@ bool MdiChild::loadFile(const QString & filename, bool resetCurrentFile) QString warning = storage.warning(); if (!warning.isEmpty()) { - // TODO EEPROMInterface::showEepromWarnings(this, CPN_STR_TTL_WARNING, warning); + QMessageBox::warning(this, CPN_STR_TTL_WARNING, warning); } if (resetCurrentFile) { diff --git a/companion/src/storage/bineeprom.cpp b/companion/src/storage/bineeprom.cpp index b896b2a01..5da50652c 100644 --- a/companion/src/storage/bineeprom.cpp +++ b/companion/src/storage/bineeprom.cpp @@ -43,6 +43,7 @@ bool BinEepromFormat::load(RadioData & radioData) return false; } + //TODO: show warnings if any return extract(radioData, eeprom); } @@ -94,7 +95,7 @@ bool BinEepromFormat::extract(RadioData & radioData, const QByteArray & eeprom) std::bitset result((unsigned long long)eepromInterface->load(radioData, (uint8_t *)eeprom.data(), eeprom.size())); if (result.test(ALL_OK)) { if (errors.test(HAS_WARNINGS)) { - EEPROMInterface::showEepromWarnings(NULL, CPN_STR_TTL_WARNING, errors.to_ulong()); + setWarning(EEPROMInterface::getEepromWarnings(errors.to_ulong())); } board = eepromInterface->getBoard(); return true; diff --git a/companion/src/tests/CMakeLists.txt b/companion/src/tests/CMakeLists.txt new file mode 100644 index 000000000..2be8973fc --- /dev/null +++ b/companion/src/tests/CMakeLists.txt @@ -0,0 +1,34 @@ +set(GTEST_ROOT /usr CACHE STRING "Base path to Google Test headers and source.") + +find_path(GTEST_INCDIR gtest/gtest.h HINTS "${GTEST_ROOT}/include" DOC "Path to Google Test header files folder ('gtest/gtest.h').") +find_path(GTEST_SRCDIR src/gtest-all.cc HINTS "${GTEST_ROOT}" "${GTEST_ROOT}/src/gtest" DOC "Path of Google Test 'src' folder.") + +if(GTEST_INCDIR AND GTEST_SRCDIR AND Qt5Widgets_FOUND) + add_library(gtests-cpn-lib STATIC EXCLUDE_FROM_ALL ${GTEST_SRCDIR}/src/gtest-all.cc ) + target_include_directories(gtests-cpn-lib PUBLIC ${GTEST_INCDIR} ${GTEST_INCDIR}/gtest ${GTEST_SRCDIR}) + add_definitions(-DGTESTS) + set(TESTS_PATH ${COMPANION_SRC_DIRECTORY}/tests) + set(RADIO_TESTS_PATH ${RADIO_SRC_DIRECTORY}/tests) + configure_file(${TESTS_PATH}/location.h.in ${CMAKE_CURRENT_BINARY_DIR}/location.h @ONLY) + + include_directories(${CMAKE_CURRENT_BINARY_DIR}) + + if(WIN32) + target_include_directories(gtests-cpn-lib PUBLIC ${WIN_INCLUDE_DIRS}) + target_link_libraries(gtests-cpn-lib PRIVATE ${WIN_LINK_LIBRARIES}) + endif(WIN32) + + file(GLOB TEST_SRC_FILES ${TESTS_PATH}/*.cpp) + + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 ${WARNING_FLAGS}") + + use_cxx11() # ensure gnu++11 in CXX_FLAGS with CMake < 3.1 + + add_executable(gtests-cpn EXCLUDE_FROM_ALL ${TEST_SRC_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/location.h) + add_dependencies(gtests-cpn gtests-cpn-lib) + target_link_libraries(gtests-cpn gtests-cpn-lib pthread Qt5::Core Qt5::Widgets ${CPN_COMMON_LIB}) + message(STATUS "Added optional gtests-cpn target") +else() + message(WARNING "WARNING: gtests target will not be available (check that GTEST_INCDIR, GTEST_SRCDIR, and Qt5Widgets are configured).") +endif() diff --git a/companion/src/tests/conversions.cpp b/companion/src/tests/conversions.cpp new file mode 100644 index 000000000..190a90f19 --- /dev/null +++ b/companion/src/tests/conversions.cpp @@ -0,0 +1,70 @@ +#include "gtests.h" +#include "location.h" +#include "storage/storage.h" + +TEST(Conversions, ConversionX9DPFrom22) +{ + RadioData radioData; + Storage store = Storage(RADIO_TESTS_PATH "/eeprom_22_x9d+.bin"); + + EXPECT_EQ(true, store.load(radioData)); + + const ModelData& model = radioData.models[0]; + EXPECT_STREQ("Test", model.name); + EXPECT_EQ(RawSwitch(SWITCH_TYPE_SWITCH,SWITCH_SA0), model.timers[0].mode); + EXPECT_EQ(80, model.mixData[0].weight); + EXPECT_EQ(900, model.limitData[0].max); // -100 + EXPECT_EQ(80, model.expoData[0].weight); + EXPECT_EQ(HELI_SWASH_TYPE_120X, model.swashRingData.type); + EXPECT_EQ(10, model.flightModeData[0].gvars[0]); + EXPECT_STREQ("Tes", model.gvarData[0].name); + EXPECT_EQ(PULSES_PXX_XJT_X16, model.moduleData[0].protocol); + EXPECT_EQ(0, model.moduleData[0].subType); + EXPECT_EQ(PULSES_PXX_R9M, model.moduleData[1].protocol); + EXPECT_EQ(MODULE_SUBTYPE_R9M_FCC, model.moduleData[1].subType); + EXPECT_STREQ("Thr", model.inputNames[0]); + EXPECT_STREQ("Tes", model.sensorData[0].label); + EXPECT_EQ(10, model.sensorData[0].id); + EXPECT_EQ(9, model.sensorData[0].instance); + EXPECT_EQ(RawSource(SOURCE_TYPE_TELEMETRY,0).toValue(), model.logicalSw[0].val1); +} + +TEST(Conversions, ConversionX7From22) +{ + RadioData radioData; + Storage store = Storage(RADIO_TESTS_PATH "/eeprom_22_x7.bin"); + + EXPECT_EQ(true, store.load(radioData)); + + const ModelData& model = radioData.models[0]; + EXPECT_STREQ("Test", model.name); + EXPECT_EQ(PULSES_PXX_R9M, model.moduleData[1].protocol); + EXPECT_EQ(MODULE_SUBTYPE_R9M_EU, model.moduleData[1].subType); + + EXPECT_EQ(80, model.mixData[0].weight); + EXPECT_EQ(80, model.expoData[0].weight); + + EXPECT_EQ(RawSource(SOURCE_TYPE_PPM,0), model.mixData[4].srcRaw); + EXPECT_EQ(RawSwitch(SWITCH_TYPE_TELEMETRY,1), model.mixData[4].swtch); + EXPECT_EQ(HELI_SWASH_TYPE_120X, model.swashRingData.type); + EXPECT_STREQ("Thr", model.inputNames[0]); + + EXPECT_STREQ("Tes", model.sensorData[0].label); + EXPECT_EQ(10, model.sensorData[0].id); + EXPECT_EQ(9, model.sensorData[0].instance); + EXPECT_EQ(900, model.limitData[0].max); // -100 + + EXPECT_EQ(10, model.flightModeData[0].gvars[0]); + EXPECT_STREQ("FMtest", model.flightModeData[1].name); + EXPECT_EQ(RawSwitch(SWITCH_TYPE_VIRTUAL,1), model.flightModeData[1].swtch); + EXPECT_STREQ("Tes", model.gvarData[0].name); + + EXPECT_EQ(LS_FN_VPOS, model.logicalSw[0].func); + EXPECT_EQ(RawSource(SOURCE_TYPE_PPM,0).toValue(), model.logicalSw[0].val1); + EXPECT_EQ(0, model.logicalSw[0].val2); + + const FrSkyScreenData& screen = model.frsky.screens[0]; + EXPECT_EQ(TELEMETRY_SCREEN_NUMBERS, screen.type); + EXPECT_EQ(RawSource(SOURCE_TYPE_PPM,0), screen.body.lines[0].source[0]); + EXPECT_EQ(RawSource(SOURCE_TYPE_SPECIAL,4), screen.body.lines[0].source[1]); +} diff --git a/companion/src/tests/gtests.cpp b/companion/src/tests/gtests.cpp new file mode 100644 index 000000000..80c36da22 --- /dev/null +++ b/companion/src/tests/gtests.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) OpenTX + * + * Based on code named + * 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 +#include +#include +#include +#include +#include "gtests.h" + +#include "storage.h" +#include "opentxinterface.h" +#include "customdebug.h" + + +using ::testing::TestEventListener; +using ::testing::EmptyTestEventListener; +using ::testing::Test; +using ::testing::TestCase; +using ::testing::TestEventListeners; +using ::testing::TestInfo; +using ::testing::TestPartResult; +using ::testing::UnitTest; +using ::testing::InitGoogleTest; + +namespace { + +void printColorCode(int color) +{ +#if !GTEST_OS_WINDOWS + if (isatty(fileno(stdout))) + fprintf(stdout, "\033[0;%dm", color); +#endif +} + +/** + * Replaces gtest's PrettyUnitTestResultPrinter with something less verbose. + * Uses the default printer if/when the messages might be interesting, + * e.g. for full error info or tests summary. +*/ +class TersePrinter : public EmptyTestEventListener { + public: + explicit TersePrinter(TestEventListener * prettyPrinter) + : prettyPrinter(prettyPrinter), currentTestInfo(NULL) + { + } + void OnTestProgramStart(const UnitTest & unit_test) + { + prettyPrinter->OnTestProgramStart(unit_test); + } + void OnTestIterationStart(const UnitTest & unit_test, int iteration) + { + printColorCode(36); + fprintf(stdout, "############ "); + fprintf(stdout, "Running %d test(s) from %d test case(s), iteration: %d.\n", unit_test.test_to_run_count(), unit_test.test_case_to_run_count(), iteration + 1); + printColorCode(0); + fflush(stdout); + } + void OnTestCaseStart(const TestCase & test_case) + { + prettyPrinter->OnTestCaseStart(test_case); + } + void OnTestStart(const TestInfo & test_info) + { + currentTestInfo = &test_info; + } + void OnTestPartResult(const TestPartResult & test_part_result) + { + if (test_part_result.failed()) { + prettyPrinter->OnTestStart(*currentTestInfo); + prettyPrinter->OnTestPartResult(test_part_result); + } + } + void OnTestEnd(const TestInfo & test_info) + { + currentTestInfo = NULL; + if (test_info.result()->Failed()) { + prettyPrinter->OnTestEnd(test_info); + } + else { + printColorCode(32); + fprintf(stdout, "+"); + printColorCode(0); + fflush(stdout); + } + } + void OnTestCaseEnd(const TestCase & test_case) + { + fprintf(stdout, "\n"); + fflush(stdout); + } + void OnTestIterationEnd(const UnitTest & unit_test, int iteration) + { + prettyPrinter->OnTestIterationEnd(unit_test, iteration); + } + void OnTestProgramEnd(const UnitTest & unit_test) + { + prettyPrinter->OnTestProgramEnd(unit_test); + } + + private: + // gtest's default unit test result printer. + std::unique_ptr prettyPrinter; + // The currently running TestInfo, if any. + const TestInfo * currentTestInfo; +}; + +} // anonymous namespace + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + CustomDebug::setFilterRules(); + registerStorageFactories(); + registerOpenTxFirmwares(); + + InitGoogleTest(&argc, argv); + + // use --verbose option to revert to gtest's default output format + bool verbose = false; + if (argc > 1 && !strcmp(argv[1], "--verbose")) + verbose = true; + + if (!verbose) { + TestEventListeners & listeners = UnitTest::GetInstance()->listeners(); + TestEventListener * defaultPrinter = listeners.Release(listeners.default_result_printer()); + listeners.Append(new TersePrinter(defaultPrinter)); + } + + return RUN_ALL_TESTS(); +} diff --git a/companion/src/tests/gtests.h b/companion/src/tests/gtests.h new file mode 100644 index 000000000..29e437ddc --- /dev/null +++ b/companion/src/tests/gtests.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) OpenTX + * + * Based on code named + * 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. + */ + +#ifndef _GTESTS_H_ +#define _GTESTS_H_ + +#include +#include +#include + +class OpenTxTest : public testing::Test +{ + protected: // You should make the members protected s.t. they can be + // accessed from sub-classes. + + // virtual void SetUp() will be called before each test is run. You + // should define it if you need to initialize the varaibles. + // Otherwise, this can be skipped. + virtual void SetUp() + { + } +}; + +#endif // _GTESTS_H_ diff --git a/companion/src/tests/location.h.in b/companion/src/tests/location.h.in new file mode 100644 index 000000000..78617cf0a --- /dev/null +++ b/companion/src/tests/location.h.in @@ -0,0 +1,2 @@ +#define TESTS_PATH "@TESTS_PATH@" +#define RADIO_TESTS_PATH "@RADIO_TESTS_PATH@"