diff --git a/src/main/fc/cli.c b/src/main/fc/cli.c index 071b7aab43..121729aa26 100755 --- a/src/main/fc/cli.c +++ b/src/main/fc/cli.c @@ -299,13 +299,34 @@ static void cliPrintLinef(const char *format, ...) va_end(va); } + static void printValuePointer(const clivalue_t *var, const void *valuePointer, bool full) { if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) { for (int i = 0; i < var->config.array.length; i++) { - uint8_t value = ((uint8_t *)valuePointer)[i]; + switch (var->type & VALUE_TYPE_MASK) { + default: + case VAR_UINT8: + // uint8_t array + cliPrintf("%d", ((uint8_t *)valuePointer)[i]); + break; + + case VAR_INT8: + // int8_t array + cliPrintf("%d", ((int8_t *)valuePointer)[i]); + break; + + case VAR_UINT16: + // uin16_t array + cliPrintf("%d", ((uint16_t *)valuePointer)[i]); + break; + + case VAR_INT16: + // int16_t array + cliPrintf("%d", ((int16_t *)valuePointer)[i]); + break; + } - cliPrintf("%d", value); if (i < var->config.array.length - 1) { cliPrint(","); } @@ -376,7 +397,7 @@ static uint16_t getValueOffset(const clivalue_t *value) return 0; } -static void *getValuePointer(const clivalue_t *value) +STATIC_UNIT_TESTED void *getValuePointer(const clivalue_t *value) { const pgRegistry_t* rec = pgFind(value->pgn); return CONST_CAST(void *, rec->address + getValueOffset(value)); @@ -2637,7 +2658,7 @@ static void cliDefaults(char *cmdline) cliReboot(); } -static void cliGet(char *cmdline) +STATIC_UNIT_TESTED void cliGet(char *cmdline) { const clivalue_t *val; int matchedCommands = 0; @@ -2681,7 +2702,7 @@ static uint8_t getWordLength(char *bufBegin, char *bufEnd) return bufEnd - bufBegin; } -static void cliSet(char *cmdline) +STATIC_UNIT_TESTED void cliSet(char *cmdline) { const uint32_t len = strlen(cmdline); char *eqptr; @@ -2706,16 +2727,17 @@ static void cliSet(char *cmdline) for (uint32_t i = 0; i < valueTableEntryCount; i++) { const clivalue_t *val = &valueTable[i]; + // ensure exact match when setting to prevent setting variables with shorter names - if (strncasecmp(cmdline, valueTable[i].name, strlen(valueTable[i].name)) == 0 && variableNameLength == strlen(valueTable[i].name)) { + if (strncasecmp(cmdline, val->name, strlen(val->name)) == 0 && variableNameLength == strlen(val->name)) { bool valueChanged = false; int16_t value = 0; - switch (valueTable[i].type & VALUE_MODE_MASK) { + switch (val->type & VALUE_MODE_MASK) { case MODE_DIRECT: { int16_t value = atoi(eqptr); - if (value >= valueTable[i].config.minmax.min && value <= valueTable[i].config.minmax.max) { + if (value >= val->config.minmax.min && value <= val->config.minmax.max) { cliSetVar(val, value); valueChanged = true; } @@ -2723,7 +2745,7 @@ static void cliSet(char *cmdline) break; case MODE_LOOKUP: { - const lookupTableEntry_t *tableEntry = &lookupTables[valueTable[i].config.lookup.tableIndex]; + const lookupTableEntry_t *tableEntry = &lookupTables[val->config.lookup.tableIndex]; bool matched = false; for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) { matched = strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0; @@ -2739,41 +2761,67 @@ static void cliSet(char *cmdline) break; case MODE_ARRAY: { - const uint8_t arrayLength = valueTable[i].config.array.length; + const uint8_t arrayLength = val->config.array.length; char *valPtr = eqptr; - uint8_t array[256]; - char curVal[4]; + for (int i = 0; i < arrayLength; i++) { + // skip spaces valPtr = skipSpace(valPtr); - char *valEnd = strstr(valPtr, ","); - if ((valEnd != NULL) && (i < arrayLength - 1)) { - uint8_t varLength = getWordLength(valPtr, valEnd); - if (varLength <= 3) { - strncpy(curVal, valPtr, getWordLength(valPtr, valEnd)); - curVal[varLength] = '\0'; - array[i] = (uint8_t)atoi((const char *)curVal); - valPtr = valEnd + 1; - } else { + // find next comma (or end of string) + char *valEndPtr = strchr(valPtr, ','); + + // comma found or last item? + if ((valEndPtr != NULL) || (i == arrayLength - 1)){ + // process substring [valPtr, valEndPtr[ + // note: no need to copy substrings for atoi() + // it stops at the first character that cannot be converted... + switch (val->type & VALUE_TYPE_MASK) { + default: + case VAR_UINT8: { + // fetch data pointer + uint8_t *data = (uint8_t *)getValuePointer(val) + i; + // store value + *data = (uint8_t)atoi((const char*) valPtr); + } + break; + + case VAR_INT8: { + // fetch data pointer + int8_t *data = (int8_t *)getValuePointer(val) + i; + // store value + *data = (int8_t)atoi((const char*) valPtr); + } + break; + + case VAR_UINT16: { + // fetch data pointer + uint16_t *data = (uint16_t *)getValuePointer(val) + i; + // store value + *data = (uint16_t)atoi((const char*) valPtr); + } + break; + + case VAR_INT16: { + // fetch data pointer + int16_t *data = (int16_t *)getValuePointer(val) + i; + // store value + *data = (int16_t)atoi((const char*) valPtr); + } break; } - } else if ((valEnd == NULL) && (i == arrayLength - 1)) { - array[i] = atoi(valPtr); - - uint8_t *ptr = getValuePointer(val); - memcpy(ptr, array, arrayLength); + // mark as changed valueChanged = true; - } else { - break; + + // prepare to parse next item + valPtr = valEndPtr + 1; } } } - break; - } if (valueChanged) { - cliPrintf("%s set to ", valueTable[i].name); + cliPrintf("%s set to ", val->name); cliPrintVar(val, 0); } else { cliPrintLine("Invalid value"); diff --git a/src/test/Makefile b/src/test/Makefile index 1575e72364..106cdb5fbb 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -52,6 +52,16 @@ blackbox_encoding_unittest_SRC := \ $(USER_DIR)/common/printf.c \ $(USER_DIR)/common/typeconversion.c +cli_unittest_SRC := \ + $(USER_DIR)/fc/cli.c \ + $(USER_DIR)/config/feature.c \ + $(USER_DIR)/config/parameter_group.c \ + $(USER_DIR)/common/typeconversion.c + +cli_unittest_DEFINES := \ + USE_CLI \ + SystemCoreClock=1000000 \ + OSD cms_unittest_SRC := \ $(USER_DIR)/cms/cms.c \ diff --git a/src/test/unit/cli_unittest.cc b/src/test/unit/cli_unittest.cc new file mode 100644 index 0000000000..565023837d --- /dev/null +++ b/src/test/unit/cli_unittest.cc @@ -0,0 +1,267 @@ +/* + * This file is part of Cleanflight. + * + * Cleanflight is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Cleanflight 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. + * + * You should have received a copy of the GNU General Public License + * along with Cleanflight. If not, see . + */ + +#include +#include +#include + +#include + +#include + +extern "C" { + #include "platform.h" + #include "target.h" + #include "fc/runtime_config.h" + #include "fc/fc_msp.h" + #include "config/parameter_group.h" + #include "config/feature.h" + #include "config/parameter_group_ids.h" + #include "sensors/battery.h" + #include "drivers/buf_writer.h" + #include "flight/mixer.h" + #include "flight/servos.h" + #include "flight/pid.h" + #include "io/ledstrip.h" + #include "io/serial.h" + #include "io/osd.h" + #include "fc/settings.h" + #include "rx/rx.h" + #include "io/beeper.h" + #include "fc/rc_adjustments.h" + #include "scheduler/scheduler.h" + #include "fc/runtime_config.h" + #include "build/version.h" + #include "fc/config.h" + #include "drivers/buf_writer.h" + #include "fc/cli.h" + + void cliSet(char *cmdline); + void cliGet(char *cmdline); + void *getValuePointer(const clivalue_t *value); + + const clivalue_t valueTable[] = { + { "array_unit_test", VAR_INT8 | MODE_ARRAY | MASTER_VALUE, .config.array.length = 3, PG_RESERVED_FOR_TESTING_1, 0 } + }; + const uint16_t valueTableEntryCount = ARRAYLEN(valueTable); + const lookupTableEntry_t lookupTables[] = {}; + + + PG_REGISTER(osdConfig_t, osdConfig, PG_OSD_CONFIG, 0); + PG_REGISTER(batteryConfig_t, batteryConfig, PG_BATTERY_CONFIG, 0); + PG_REGISTER(ledStripConfig_t, ledStripConfig, PG_LED_STRIP_CONFIG, 0); + PG_REGISTER(systemConfig_t, systemConfig, PG_SYSTEM_CONFIG, 0); + PG_REGISTER_ARRAY(adjustmentRange_t, MAX_ADJUSTMENT_RANGE_COUNT, adjustmentRanges, PG_ADJUSTMENT_RANGE_CONFIG, 0); + PG_REGISTER_ARRAY(modeActivationCondition_t, MAX_MODE_ACTIVATION_CONDITION_COUNT, modeActivationConditions, PG_MODE_ACTIVATION_PROFILE, 0); + PG_REGISTER(mixerConfig_t, mixerConfig, PG_MIXER_CONFIG, 0); + PG_REGISTER_ARRAY(motorMixer_t, MAX_SUPPORTED_MOTORS, customMotorMixer, PG_MOTOR_MIXER, 0); + PG_REGISTER_ARRAY(servoParam_t, MAX_SUPPORTED_SERVOS, servoParams, PG_SERVO_PARAMS, 0); + PG_REGISTER_ARRAY(servoMixer_t, MAX_SERVO_RULES, customServoMixers, PG_SERVO_MIXER, 0); + PG_REGISTER(featureConfig_t, featureConfig, PG_FEATURE_CONFIG, 0); + PG_REGISTER(beeperConfig_t, beeperConfig, PG_BEEPER_CONFIG, 0); + PG_REGISTER(rxConfig_t, rxConfig, PG_RX_CONFIG, 0); + PG_REGISTER(serialConfig_t, serialConfig, PG_SERIAL_CONFIG, 0); + PG_REGISTER_ARRAY(rxChannelRangeConfig_t, NON_AUX_CHANNEL_COUNT, rxChannelRangeConfigs, PG_RX_CHANNEL_RANGE_CONFIG, 0); + PG_REGISTER_ARRAY(rxFailsafeChannelConfig_t, MAX_SUPPORTED_RC_CHANNEL_COUNT, rxFailsafeChannelConfigs, PG_RX_FAILSAFE_CHANNEL_CONFIG, 0); + PG_REGISTER(pidConfig_t, pidConfig, PG_PID_CONFIG, 0); + + PG_REGISTER_WITH_RESET_FN(int8_t, unitTestData, PG_RESERVED_FOR_TESTING_1, 0); +} + +#include "unittest_macros.h" +#include "gtest/gtest.h" +TEST(CLIUnittest, TestCliSet) +{ + + cliSet((char *)"array_unit_test = 123, -3 , 1"); + + const clivalue_t cval = { + .name = "array_unit_test", + .type = MODE_ARRAY | MASTER_VALUE | VAR_INT8, + .pgn = PG_RESERVED_FOR_TESTING_1, + .offset = 0 + }; + + printf("\n===============================\n"); + int8_t *data = (int8_t *)getValuePointer(&cval); + for(int i=0; i<3; i++){ + printf("data[%d] = %d\n", i, data[i]); + } + printf("\n===============================\n"); + + + EXPECT_EQ(123, data[0]); + EXPECT_EQ( -3, data[1]); + EXPECT_EQ( 1, data[2]); + + + //cliGet((char *)"osd_item_vbat"); + //EXPECT_EQ(false, false); +} + +// STUBS +extern "C" { + +float motor_disarmed[MAX_SUPPORTED_MOTORS]; + +uint16_t batteryWarningVoltage; +uint8_t useHottAlarmSoundPeriod (void) { return 0; } +const uint32_t baudRates[] = {0, 9600, 19200, 38400, 57600, 115200, 230400, 250000, 400000}; // see baudRate_e + +uint32_t micros(void) {return 0;} + +int32_t getAmperage(void) { + return 100; +} + +uint16_t getBatteryVoltage(void) { + return 42; +} + +batteryState_e getBatteryState(void) { + return BATTERY_OK; +} + +uint8_t calculateBatteryPercentageRemaining(void) { + return 67; +} + +uint8_t getMotorCount() { + return 4; +} + + +void setPrintfSerialPort(struct serialPort_s) {} + +void tfp_printf(const char * expectedFormat, ...) { + va_list args; + + va_start(args, expectedFormat); + vprintf(expectedFormat, args); + va_end(args); +} + + +void tfp_format(void *, void (*) (void *, char), const char * expectedFormat, va_list va) { + vprintf(expectedFormat, va); +} + +static const box_t boxes[] = { { 0, "DUMMYBOX", 0 } }; +const box_t *findBoxByPermanentId(uint8_t) { return &boxes[0]; } +const box_t *findBoxByBoxId(boxId_e) { return &boxes[0]; } + +int8_t unitTestDataArray[3]; + +void pgResetFn_unitTestData(int8_t *ptr) { + ptr = &unitTestDataArray[0]; +} + +uint32_t getBeeperOffMask(void) { return 0; } +uint32_t getPreferredBeeperOffMask(void) { return 0; } + +void beeper(beeperMode_e) {} +void beeperSilence(void) {} +void beeperConfirmationBeeps(uint8_t) {} +void beeperWarningBeeps(uint8_t) {} +void beeperUpdate(timeUs_t) {} +uint32_t getArmingBeepTimeMicros(void) {return 0;} +beeperMode_e beeperModeForTableIndex(int) {return BEEPER_SILENCE;} +const char *beeperNameForTableIndex(int) {return NULL;} +int beeperTableEntryCount(void) {return 0;} +bool isBeeperOn(void) {return false;} +void beeperOffSetAll(uint8_t) {} +void setBeeperOffMask(uint32_t) {} +void setPreferredBeeperOffMask(uint32_t) {} + +void beeperOffSet(uint32_t) {} +void beeperOffClear(uint32_t) {} +void beeperOffClearAll(void) {} +bool parseColor(int, const char *) {return false; } +void resetEEPROM(void) {} +void bufWriterFlush(bufWriter_t *) {} +void mixerResetDisarmedMotors(void) {} +void gpsEnablePassthrough(struct serialPort_s *) {} +bool parseLedStripConfig(int, const char *){return false; } +const char rcChannelLetters[] = "AERT12345678abcdefgh"; + +void parseRcChannels(const char *, rxConfig_t *){} +void mixerLoadMix(int, motorMixer_t *) {} +bool setModeColor(ledModeIndex_e, int, int) { return false; } +float convertExternalToMotor(uint16_t ){ return 1.0; } +uint8_t getCurrentPidProfileIndex(void){ return 1; } +uint8_t getCurrentControlRateProfileIndex(void){ return 1; } +void changeControlRateProfile(uint8_t) {} +void resetAllRxChannelRangeConfigurations(rxChannelRangeConfig_t *) {} +void writeEEPROM() {} +serialPortConfig_t *serialFindPortConfiguration(serialPortIdentifier_e) {return NULL; } +baudRate_e lookupBaudRateIndex(uint32_t){return BAUD_9600; } +serialPortUsage_t *findSerialPortUsageByIdentifier(serialPortIdentifier_e){ return NULL; } +serialPort_t *openSerialPort(serialPortIdentifier_e, serialPortFunction_e, serialReceiveCallbackPtr, uint32_t, portMode_t, portOptions_t) { return NULL; } +void serialSetBaudRate(serialPort_t *, uint32_t) {} +void serialSetMode(serialPort_t *, portMode_t) {} +void serialPassthrough(serialPort_t *, serialPort_t *, serialConsumer *, serialConsumer *) {} +uint32_t millis(void) { return 0; } +uint8_t getBatteryCellCount(void) { return 1; } +void servoMixerLoadMix(int) {} +const char * getBatteryStateString(void){ return "_getBatteryStateString_"; } + +uint32_t stackTotalSize(void) { return 0x4000; } +uint32_t stackHighMem(void) { return 0x80000000; } +uint16_t getEEPROMConfigSize(void) { return 1024; } + +uint8_t __config_start = 0x00; +uint8_t __config_end = 0x10; +uint16_t averageSystemLoadPercent = 0; + +timeDelta_t getTaskDeltaTime(cfTaskId_e){ return 0; } +armingDisableFlags_e getArmingDisableFlags(void) { return ARMING_DISABLED_NO_GYRO; } + +const char *armingDisableFlagNames[]= { +"DUMMYDISABLEFLAGNAME" +}; + +void getTaskInfo(cfTaskId_e, cfTaskInfo_t *) {} +void getCheckFuncInfo(cfCheckFuncInfo_t *) {} + +const char * const targetName = "UNITTEST"; +const char* const buildDate = "Jan 01 2017"; +const char * const buildTime = "00:00:00"; +const char * const shortGitRevision = "MASTER"; + +uint32_t serialRxBytesWaiting(const serialPort_t *) {return 0;} +uint8_t serialRead(serialPort_t *){return 0;} + +void bufWriterAppend(bufWriter_t *, uint8_t ch){ printf("%c", ch); } +void serialWriteBufShim(void *, const uint8_t *, int) {} +bufWriter_t *bufWriterInit(uint8_t *, int, bufWrite_t, void *) {return NULL;} +void schedulerSetCalulateTaskStatistics(bool) {} +void setArmingDisabled(armingDisableFlags_e) {} + +void waitForSerialPortToFinishTransmitting(serialPort_t *) {} +void stopPwmAllMotors(void) {} +void systemResetToBootloader(void) {} +void resetConfigs(void) {} +void systemReset(void) {} + +void changePidProfile(uint8_t) {} +bool serialIsPortAvailable(serialPortIdentifier_e) { return false; } +void generateLedConfig(ledConfig_t *, char *, size_t) {} +bool isSerialTransmitBufferEmpty(const serialPort_t *) {return true; } +void serialWrite(serialPort_t *, uint8_t ch) { printf("%c", ch);} + + +}