Merge branch 'feature/#52-only-store-new-commands-in-history' of shimatta/shellmatta into develop

fix #52
This commit is contained in:
shimatta 2021-01-23 23:40:31 +01:00 committed by Gogs
commit a9b8dcb504
24 changed files with 7449 additions and 41 deletions

2
.vscode/launch.json vendored
View File

@ -9,7 +9,7 @@
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/output/example/example",
"args": ["/dev/pts/4"],
"args": ["/dev/pts/2"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],

187
README.md
View File

@ -1,3 +1,188 @@
# shellmatta
# Shellmatta
A tiny and flexible shell implementation to be used on embedded devices.
## Name
The name `shellmatta` is the combination of `shell` and `shimatta`.
What the hell is `shimatta` you might ask.
...well if you really wanna know you might reach out to these nerds that are
running domains like [shimatta.net](https://git.shimatta.net) or
[shimatta.de](https://git.shimatta.de).
Do not pretend i didn't warn you.
## Intention
The intention is to enable a software project of nearly any size to have a
runtime command line interface to simplify debugging or
configuration/calibration of any kind of device.
The `shellmatta` is designed to fit in most tiny microcontroller.
It is based on a simple character based interface and can work with for example
network sockets or simple uarts.
Some features are removable at build time to save ressources on really tiny
platforms.
## Features
The `shellmatta` piled up some features over time:
1. history buffer (configurable)
2. auto complete
3. heredoc like interface to pass multiline data
4. option parser (getopt like)
## Documentation
Besides this readme most documentation is integrated directly in the sourcecode
as doxygen parsable comments.
To build the doxygen documentation just run `make doc`.
The html and latex documentation will be exported to `output/doc`
## Integration into your project
The basic integration into a softwareproject is quite easy.
1. add all *.c files from the `src` to your build
2. include the `api/shellmatta.h` file
3. implement a write function to output the data from the shell
4. initialize the shell providing static buffers
5. implement and add commands
6. pass data into the shellmatta and watch the magic happen
7. static constant command list (if you do not like dynamic lists)
Code example:
```
#include "shellmatta.h"
#include <unistd.h>
shellmatta_retCode_t writeFct(const char* data, uint32_t length)
{
write(1, data, length);
return SHELLMATTA_OK;
}
static shellmatta_retCode_t exampleCmdFct(shellmatta_handle_t handle, const char *arguments, uint32_t length)
{
shellmatta_retCode_t ret;
char option;
static const shellmatta_opt_long_t options[] =
{
{"version", 'v', SHELLMATTA_OPT_ARG_NONE},
{NULL, '\0', SHELLMATTA_OPT_ARG_NONE}
};
ret = shellmatta_opt_long(handle, options, &option, NULL, NULL);
while(SHELLMATTA_OK == ret)
{
switch(option)
{
case 'v':
shellmatta_printf(handle, "This should represent the version of this command");
break;
default:
shellmatta_printf(handle, "Unknown option: %c\r\n", option);
break;
}
ret = shellmatta_opt_long(handle, options, &option, NULL, NULL);
}
(void)arguments;
(void)length;
return SHELLMATTA_OK;
}
shellmatta_cmd_t exampleCmd = { "example", /**< command name */
"e", /**< command alias */
"example command", /**< command help */
"example [options]\n" /**< command usage text */
"\t-v, --version - print the version of the command",
exampleCmdFct, /**< command function */
NULL}; /**< intenally used */
int main(void)
{
static char buffer[1024]; /**< memory for inptu buffer */
static char historyBuffer[4096]; /**< memory for history buffer */
static shellmatta_instance_t instance; /**< instance variable */
shellmatta_handle_t handle; /**< handle used for accessing */
/** -# initialize the shellmatta instance */
shellmatta_doInit( &instance,
&handle,
buffer,
sizeof(buffer),
historyBuffer,
sizeof(historyBuffer),
"shellmatta->", /**< prompt text */
NULL, /**< optional static command list */
writeFct); /**< write function */
/** -# add the command - one command can only be added to one instance */
shellmatta_addCmd(handle, &exampleCmd);
/** -# ready to put some data in there */
shellmatta_processData(handle, "example --version\r", 18u);
return 0;
}
```
## Compile time configuration
There are some defines you can use to change the behaviour of the shellmatta:
| Define | Description |
| -------------------------- | ---------------------------------------------- |
| SHELLMATTA_STRIP_PRINTF | removes stdio dependencies to reduce footprint |
| SHELLMATTA_HELP_COMMAND | string to overwrite the help command name |
| SHELLMATTA_HELP_ALIAS | string to overwrite the help command alias |
| SHELLMATTA_HELP_HELP_TEXT | string to overwrite the help command help |
| SHELLMATTA_HELP_USAGE_TEXT | string to overwrite the help command usage |
## Example
There is a quite confusing example in this repo to show and test some features.
To build it just rum `make example`.
The binary will be built to `output/example/example`
It requires a serial device as parameter to run e.g. `/dev/tty0`
To be able to play around a bit you can create a local serial loopback using
socat.
```
socat -d -d pty,raw,echo=0 pty,raw,echo=0
```
This will create two serial devices which are connected with a loopback.
The device numbers in this example might change on your system.
You can use one of them starting the example e.g.
```
./output/example/example /dev/pts2
```
And use the other one to connect using the terminal tool of your choice e.g.
```
minicom -D /dev/pts3
```
## Running tests
There are some tests implemented using catch2 and the function fake framework.
To run the tests just do:
```
make test
```
To be able to build the coverage report you need an installation of
(gcovr)[https://pypi.org/project/gcovr].

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
# Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
@ -7,6 +7,9 @@
#
OBJ_DIR := output/
INTEGRATIONTEST_CPP_OBJ_DIR := $(OBJ_DIR)test/integrationtest/
INTEGRATIONTEST_C_OBJ_DIR := $(INTEGRATIONTEST_CPP_OBJ_DIR)
UNITTEST_OBJ_DIR := $(OBJ_DIR)test/unittest/
CC := gcc
CPP := g++
@ -45,14 +48,16 @@ INTEGRATIONTEST_SOURCES := test/integrationtest/test_main.cpp
test/integrationtest/test_integration_opt.cpp \
test/integrationtest/test_integration_optLong.cpp \
test/integrationtest/test_integration_continue.cpp \
test/integrationtest/test_integration_busy.cpp
test/integrationtest/test_integration_busy.cpp \
test/integrationtest/test_integration_history.cpp
UNITTEST_CPPOBJ := $(patsubst %.cpp,$(OBJ_DIR)%.o,$(UNITTEST_SOURCES))
UNITTEST_CPPOBJ := $(patsubst %.cpp,$(UNITTEST_OBJ_DIR)%.o,$(UNITTEST_SOURCES))
INTEGRATIONTEST_CPPOBJ := $(patsubst %.cpp,$(OBJ_DIR)%.o,$(INTEGRATIONTEST_SOURCES))
INTEGRATIONTEST_CPPOBJ := $(patsubst %.cpp,$(INTEGRATIONTEST_CPP_OBJ_DIR)%.o,$(INTEGRATIONTEST_SOURCES))
INTEGRATIONTEST_COBJ := $(patsubst %.c,$(INTEGRATIONTEST_CPP_OBJ_DIR)%.o,$(SOURCES))
CFLAGS := $(INCLUDES:%=-I%) -g -Wall -Werror -Wextra -pedantic -DSHELLMATTA_HELP_ALIAS=\"?\"
TESTFLAGS := $(INCLUDES:%=-I%) -g -Wall -Werror -Wextra -fprofile-arcs -ftest-coverage -pedantic
CFLAGS := $(INCLUDES:%=-I%) -g -Wall -Werror -Wextra -pedantic -DSHELLMATTA_HELP_ALIAS=\(char*\)\"?\"
TESTFLAGS := $(CFLAGS) -fprofile-arcs -ftest-coverage
TESTLFLAGS := -fprofile-arcs -Wl,--allow-multiple-definition
DEPEND = -MT $@ -MF "$(@:%.o=%.d)" -MG -MM
@ -68,7 +73,7 @@ UNITTEST_TARGET := $(OBJ_DIR)test/unittest/unittest
INTEGRATIONTEST_TARGET := $(OBJ_DIR)test/integrationtest/integrationtest
OBJ := $(COBJ) $(EXAMPLE_COBJ) $(UNITTEST_CPPOBJ) $(INTEGRATIONTEST_CPPOBJ)
OBJ := $(COBJ) $(EXAMPLE_COBJ) $(UNITTEST_CPPOBJ) $(INTEGRATIONTEST_CPPOBJ) $(INTEGRATIONTEST_COBJ)
DEPS := $(OBJ:%.o=%.d)
export
@ -95,7 +100,6 @@ unittest: $(UNITTEST_TARGET)
# remove coverage from former run
@-find . -name "*.gcda" -type f -delete
-$(UNITTEST_TARGET)
# gcov -o output/test/unittest $(UNITTEST_CPPOBJ) -r src
# remove report from former run
-rm -rf $(OBJ_DIR)test/unittest/report/*
@ -104,8 +108,13 @@ unittest: $(UNITTEST_TARGET)
integrationtest: $(INTEGRATIONTEST_TARGET)
- @mkdir -p output/test/integrationtest/report
@echo running test:
# remove coverage from former run
# @-find . -name "*.gcda" -type f -delete
-$(INTEGRATIONTEST_TARGET)
gcovr --html-details --output $(OBJ_DIR)test/integrationtest/report/report.html output/src -f src -f api -d
# remove report from former run
# -rm -rf $(OBJ_DIR)test/unittest/report/*
gcovr --html-details --output $(OBJ_DIR)test/integrationtest/report/report.html output/test/integrationtest -f src -f api -d
example: $(EXAMPLE_TARGET)
@echo building example
@ -126,7 +135,7 @@ $(UNITTEST_TARGET): $(UNITTEST_CPPOBJ)
- @mkdir -p $(@D)
$(CPP) $(TESTLFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
$(INTEGRATIONTEST_TARGET): $(INTEGRATIONTEST_CPPOBJ) $(COBJ)
$(INTEGRATIONTEST_TARGET): $(INTEGRATIONTEST_CPPOBJ) $(INTEGRATIONTEST_COBJ)
- @mkdir -p $(@D)
$(CPP) $(TESTLFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
@ -144,10 +153,20 @@ $(EXAMPLE_COBJ):
@$(CC) -c $(CFLAGS) $(DEPEND) -o $@ $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
$(CC) -c $(CFLAGS) -o $@ $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
$(UNITTEST_CPPOBJ) $(INTEGRATIONTEST_CPPOBJ):
$(UNITTEST_CPPOBJ):
- @mkdir -p $(@D)
@$(CPP) -c $(TESTFLAGS) $(DEPEND) -o $@ $(subst $(OBJ_DIR), ,$(@:%.o=%.cpp))
$(CPP) -c $(TESTFLAGS) -o $@ $(subst $(OBJ_DIR), ,$(@:%.o=%.cpp))
@$(CPP) -c $(TESTFLAGS) $(DEPEND) -o $@ $(subst $(UNITTEST_OBJ_DIR), ,$(@:%.o=%.cpp))
$(CPP) -c $(TESTFLAGS) -o $@ $(subst $(UNITTEST_OBJ_DIR), ,$(@:%.o=%.cpp))
$(INTEGRATIONTEST_CPPOBJ):
- @mkdir -p $(@D)
@$(CPP) -c $(TESTFLAGS) $(DEPEND) -o $@ $(subst $(INTEGRATIONTEST_CPP_OBJ_DIR), ,$(@:%.o=%.cpp))
$(CPP) -c $(TESTFLAGS) -o $@ $(subst $(INTEGRATIONTEST_CPP_OBJ_DIR), ,$(@:%.o=%.cpp))
$(INTEGRATIONTEST_COBJ):
- @mkdir -p $(@D)
@$(CC) -c $(TESTFLAGS) $(DEPEND) -o $@ $(subst $(INTEGRATIONTEST_C_OBJ_DIR), ,$(@:%.o=%.c))
$(CC) -c $(TESTFLAGS) -o $@ $(subst $(INTEGRATIONTEST_C_OBJ_DIR), ,$(@:%.o=%.c))
%.o: %.cpp
- @mkdir -p $(@D)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -52,7 +52,7 @@ shellmatta_retCode_t escape_processArrowKeys(shellmatta_instance_t *inst)
break;
case 'B': /* arrow down */
/*! -# ignore the key if the history buffer points to the last entry */
if((inst->historyRead != inst->historyEnd))
{
history_storeCmd(inst);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -38,7 +38,7 @@ static void appendHistoryByte(shellmatta_instance_t *inst, char byte)
/** -# append the byte */
inst->historyBuffer[inst->historyEnd] = byte;
/** -# check if the we overwrite an existing stored command */
/** -# check if we overwrite an existing stored command */
if(inst->historyEnd == inst->historyStart)
{
/** -# move the start pointer to the next termination (0) */
@ -84,6 +84,55 @@ static bool getHistoryByte(shellmatta_instance_t *inst, char *byte)
return ret;
}
/**
* @brief compares the current buffer to the last command in the history buffer
* @param[in] inst pointer to a shellmatta instance
* @return true: current command is identical to the last one in the history buffer
*/
static bool compareLastCommand(shellmatta_instance_t *inst)
{
bool ret = false;
uint32_t i;
uint32_t cnt;
/** -# check if there is anything in the buffer */
if(inst->historyStart != inst->historyEnd)
{
i = inst->historyEnd;
cnt = 0u;
ret = true;
while((true == ret) && (cnt < inst->inputCount))
{
/** -# terminate compare on first mismatch */
if((inst->historyBuffer[i] != inst->buffer[cnt]) || (0u == inst->historyBuffer[i]))
{
ret = false;
}
if(0u == i)
{
i = (inst->historyBufferSize - 1u);
}
else
{
i --;
}
cnt ++;
}
/*! -# check if we are at the end of the command in the buffer - there has to be a terminating 0 */
if(0u != inst->historyBuffer[i])
{
ret = false;
}
}
return ret;
}
/**
* @brief navigates in the history buffer by the given number of commands
* @param[in, out] inst pointer to a shellmatta instance
@ -106,7 +155,7 @@ bool history_navigate(shellmatta_instance_t *inst, int32_t cnt)
inst->historyRead ++;
if(inst->historyRead >= inst->historyBufferSize)
{
inst->historyRead = 0u;
inst->historyRead -= inst->historyBufferSize;
}
if( (inst->historyRead != inst->historyEnd)
@ -165,7 +214,7 @@ bool history_navigate(shellmatta_instance_t *inst, int32_t cnt)
/**
* @brief stores the current command from the instances buffer into the
* history buffer
* @param[in] inst pointer to a shellmatta instance
* @param[in] inst pointer to a shellmatta instance
*/
void history_storeCmd(shellmatta_instance_t *inst)
{
@ -174,8 +223,9 @@ void history_storeCmd(shellmatta_instance_t *inst)
/** -# check if we have enough room for the command in the history buffer
* and there is a new command to be stored */
if( (inst->historyBufferSize > inst->inputCount)
&& (0u != inst->inputCount)
&& (true == inst->dirty))
&& (0u != inst->inputCount)
&& (true == inst->dirty)
&& (true != compareLastCommand(inst)))
{
/** -# append the command termination */
appendHistoryByte(inst, 0u);
@ -187,7 +237,6 @@ void history_storeCmd(shellmatta_instance_t *inst)
}
}
/** -# remove the dirty flag - everything is nice and saved */
inst->dirty = false;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

6643
test/framework/fff.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,17 @@
/*
* Copyright (c) 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
/**
* @file test_integration.cpp
* @brief integration test implementation for some general functions
* @author Stefan Strobel <stefan.strobel@shimatta.net>
*/
#include "test/framework/catch.hpp"
extern "C" {
#include "shellmatta.h"
@ -562,4 +576,3 @@ TEST_CASE( "shellmatta configure delimiter" ) {
CHECK( write_length == strlen(dummyData));
REQUIRE( strcmp(dummyData, write_data) == 0);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -0,0 +1,495 @@
/*
* Copyright (c) 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
/**
* @file test_integration_history.cpp
* @brief integration test implementation for the history buffer of the shellmatta
* @author Stefan Strobel <stefan.strobel@shimatta.net>
*/
#include "test/framework/catch.hpp"
extern "C" {
#include "test/framework/fff.h"
#include "shellmatta.h"
}
#include <string.h>
FAKE_VALUE_FUNC(shellmatta_retCode_t, writeFct, const char *, uint32_t)
FAKE_VALUE_FUNC(shellmatta_retCode_t, cmdFct1, shellmatta_handle_t, const char *, uint32_t)
FAKE_VALUE_FUNC(shellmatta_retCode_t, cmdFct2, shellmatta_handle_t, const char *, uint32_t)
FAKE_VALUE_FUNC(shellmatta_retCode_t, cmdFct3, shellmatta_handle_t, const char *, uint32_t)
FAKE_VALUE_FUNC(shellmatta_retCode_t, cmdFct4, shellmatta_handle_t, const char *, uint32_t)
/* List of fakes */
#define FFF_FAKES_LIST(FAKE) \
FAKE(writeFct) \
FAKE(cmdFct1) \
FAKE(cmdFct2) \
FAKE(cmdFct3) \
FAKE(cmdFct4)
#define CHECK_FOR_COMMAND(hist, idx) \
CHECK(writeFct_fake.call_count == ((hist) + 1u)); \
CHECK(0 == memcmp(writeFct_fake.arg0_history[(hist)], commandSequence[(idx)], strlen(commandSequence[(idx)]) - 1)); \
CHECK((strlen(commandSequence[(idx)]) - 1) == writeFct_fake.arg1_history[(hist)]);
#define BUTTON_UP "\x1b" "[A"
#define BUTTON_DOWN "\x1b" "[B"
#define PROCESS_INPUT(input) \
CHECK(SHELLMATTA_OK == shellmatta_processData(handle, (char*)(input), sizeof((input)) - 1u));
shellmatta_cmd_t cmd1 = {(char*)"cmd1", (char*)"1", NULL, NULL, cmdFct1, NULL};
shellmatta_cmd_t cmd2 = {(char*)"cmd2", (char*)"2", NULL, NULL, cmdFct2, NULL};
shellmatta_cmd_t cmd3 = {(char*)"cmd3", (char*)"3", NULL, NULL, cmdFct3, NULL};
shellmatta_cmd_t cmd4 = {(char*)"cmd4", (char*)"4", NULL, NULL, cmdFct4, NULL};
char *commandSequence[] =
{
(char*)"foo\r",
(char*)"bar\r",
(char*)"cmd1\r",
(char*)"2\r",
(char*)"4\r",
(char*)"cmd3\r"
};
#define CMD_SEQ_LEN (sizeof(commandSequence) / sizeof(commandSequence[0]))
SCENARIO("Test the history buffer with a fixed sequence of commands in there")
{
GIVEN("An initialized Shellmatte instance with some commands already in the history buffer")
{
shellmatta_instance_t inst;
shellmatta_handle_t handle;
char buffer[1024u];
char historyBuffer[1024u];
CHECK(SHELLMATTA_OK == shellmatta_doInit( &inst,
&handle,
buffer,
sizeof(buffer),
historyBuffer,
sizeof(historyBuffer),
"shellmatta->",
NULL,
writeFct));
CHECK(SHELLMATTA_OK == shellmatta_addCmd(handle, &cmd1));
CHECK(SHELLMATTA_OK == shellmatta_addCmd(handle, &cmd2));
CHECK(SHELLMATTA_OK == shellmatta_addCmd(handle, &cmd3));
CHECK(SHELLMATTA_OK == shellmatta_addCmd(handle, &cmd4));
for(uint32_t i = 0u; i < CMD_SEQ_LEN; i++)
{
CHECK(SHELLMATTA_OK == shellmatta_processData(handle, commandSequence[i], strlen(commandSequence[i])));
}
WHEN("The up button is pressed")
{
THEN("The shellmatta prints the most recent command")
{
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK(writeFct_fake.call_count == 2);
CHECK(0 == memcmp(writeFct_fake.arg0_history[0], "\x1b" "[K", 3));
CHECK(3 == writeFct_fake.arg1_history[0]);
CHECK(0 == memcmp(writeFct_fake.arg0_history[1], commandSequence[CMD_SEQ_LEN - 1], strlen(commandSequence[CMD_SEQ_LEN - 1]) - 1));
CHECK((strlen(commandSequence[CMD_SEQ_LEN - 1]) - 1) == writeFct_fake.arg1_history[1]);
for(uint32_t i = CMD_SEQ_LEN - 1; i > 0; i--)
{
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK(writeFct_fake.call_count == 3);
CHECK(0 == memcmp(writeFct_fake.arg0_history[1], "\x1b" "[K", 3));
CHECK(3 == writeFct_fake.arg1_history[1]);
CHECK(0 == memcmp(writeFct_fake.arg0_history[2], commandSequence[i - 1u], strlen(commandSequence[i - 1u]) - 1));
CHECK((strlen(commandSequence[i - 1u]) - 1) == writeFct_fake.arg1_history[2]);
}
}
}
WHEN("The history buffer it at the oldest command yet")
{
for(uint32_t i = CMD_SEQ_LEN; i > 0; i--)
{
PROCESS_INPUT(BUTTON_UP)
}
AND_WHEN("The up button is pressed again")
{
THEN("The output is deleted and the oldest command is printed again")
{
for(uint32_t i = 0u; i < 64; i++)
{
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, 0u)
}
}
}
AND_WHEN("The down button is pressed")
{
THEN("On each button press one newer command is printed")
{
for(uint32_t i = 1; i < CMD_SEQ_LEN; i++)
{
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK(0 == memcmp(writeFct_fake.arg0_history[1], "\x1b" "[K", 3));
CHECK(3 == writeFct_fake.arg1_history[1]);
CHECK_FOR_COMMAND(2u, i)
}
}
}
}
WHEN("The user pushes the up and down button alternately")
{
THEN("The output shall be updated with the correct command or not updated at all when at the end")
{
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(1u, CMD_SEQ_LEN - 1u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 2u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 1u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK(writeFct_fake.call_count == 0u);
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK(writeFct_fake.call_count == 0u);
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 2u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 3u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 4u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 5u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 6u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 6u)
/* back down again */
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 5u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 4u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 3u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 2u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 1u)
/* end of the buffer - shellmatta shall not update */
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK(writeFct_fake.call_count == 0u);
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK(writeFct_fake.call_count == 0u);
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 2u)
}
}
}
}
SCENARIO("Test how the history buffer handles more commands than fits inside the buffer")
{
GIVEN("An initialized Shellmatte instance with some commands already in the history buffer")
{
shellmatta_instance_t inst;
shellmatta_handle_t handle;
char buffer[1024u];
char historyBuffer[16u] = {0};
CHECK(SHELLMATTA_OK == shellmatta_doInit( &inst,
&handle,
buffer,
sizeof(buffer),
historyBuffer,
sizeof(historyBuffer),
"shellmatta->",
NULL,
writeFct));
CHECK(SHELLMATTA_OK == shellmatta_addCmd(handle, &cmd1));
CHECK(SHELLMATTA_OK == shellmatta_addCmd(handle, &cmd2));
CHECK(SHELLMATTA_OK == shellmatta_addCmd(handle, &cmd3));
CHECK(SHELLMATTA_OK == shellmatta_addCmd(handle, &cmd4));
for(uint32_t i = 0u; i < CMD_SEQ_LEN; i++)
{
CHECK(SHELLMATTA_OK == shellmatta_processData(handle, commandSequence[i], strlen(commandSequence[i])));
}
WHEN("The user pushes the up and down button alternately")
{
THEN("The output shall be updated with the correct commands that did fit into the buffer")
{
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(1u, CMD_SEQ_LEN - 1u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 2u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 3u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 4u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 4u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 3u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 2u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 3u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 2u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 1u)
}
}
WHEN("A command dowes not fit into the history buffer")
{
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT("This is a very long command input\r")
THEN("The input is not stored")
{
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(1u, CMD_SEQ_LEN - 1u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 2u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 3u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 4u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 4u)
}
}
}
}
SCENARIO("Test if the history buffer stores changes done during navigating")
{
GIVEN("An initialized Shellmatte instance with some commands already in the history buffer")
{
shellmatta_instance_t inst;
shellmatta_handle_t handle;
char buffer[1024u];
char historyBuffer[16u] = {0};
CHECK(SHELLMATTA_OK == shellmatta_doInit( &inst,
&handle,
buffer,
sizeof(buffer),
historyBuffer,
sizeof(historyBuffer),
"shellmatta->",
NULL,
writeFct));
CHECK(SHELLMATTA_OK == shellmatta_addCmd(handle, &cmd1));
CHECK(SHELLMATTA_OK == shellmatta_addCmd(handle, &cmd2));
CHECK(SHELLMATTA_OK == shellmatta_addCmd(handle, &cmd3));
CHECK(SHELLMATTA_OK == shellmatta_addCmd(handle, &cmd4));
for(uint32_t i = 0u; i < CMD_SEQ_LEN; i++)
{
CHECK(SHELLMATTA_OK == shellmatta_processData(handle, commandSequence[i], strlen(commandSequence[i])));
}
WHEN("The user pushes the up and down button alternately and inputs data in between")
{
THEN("The output shall be updated with the correct commands and the input shall be stored")
{
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(1u, CMD_SEQ_LEN - 1u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 2u)
PROCESS_INPUT("\b123456")
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 3u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 2u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 1u)
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_DOWN)
CHECK(writeFct_fake.call_count == 3);
CHECK(0 == memcmp(writeFct_fake.arg0_history[2], "123456", 6));
CHECK(6 == writeFct_fake.arg1_history[2]);
PROCESS_INPUT("\r")
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK(writeFct_fake.call_count == 2);
CHECK(0 == memcmp(writeFct_fake.arg0_history[1], "123456", 6));
CHECK(6 == writeFct_fake.arg1_history[1]);
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK_FOR_COMMAND(2u, CMD_SEQ_LEN - 1u)
PROCESS_INPUT("\x03" "12345678\r")
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK(writeFct_fake.call_count == 2);
CHECK(0 == memcmp(writeFct_fake.arg0_history[1], "12345678", 8));
CHECK(8 == writeFct_fake.arg1_history[1]);
PROCESS_INPUT("\r")
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK(writeFct_fake.call_count == 2);
CHECK(0 == memcmp(writeFct_fake.arg0_history[1], "12345678", 8));
CHECK(8 == writeFct_fake.arg1_history[1]);
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK(writeFct_fake.call_count == 3);
CHECK(0 == memcmp(writeFct_fake.arg0_history[2], "123456", 6));
CHECK(6 == writeFct_fake.arg1_history[2]);
/* check if the compare gets it when the new command is exactly one character longer than the stored */
PROCESS_INPUT("\x03" "123456789\r")
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK(writeFct_fake.call_count == 2);
CHECK(0 == memcmp(writeFct_fake.arg0_history[1], "123456789", 9));
CHECK(9 == writeFct_fake.arg1_history[1]);
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK(writeFct_fake.call_count == 3);
CHECK(0 == memcmp(writeFct_fake.arg0_history[2], "123456789", 9));
CHECK(9 == writeFct_fake.arg1_history[2]);
/* check if the compare gets it when the last command ist longer than the new one */
PROCESS_INPUT("\x03" "12345678\r")
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK(writeFct_fake.call_count == 2);
CHECK(0 == memcmp(writeFct_fake.arg0_history[1], "12345678", 8));
CHECK(8 == writeFct_fake.arg1_history[1]);
/* check what happens when there is a \0 in the buffer */
PROCESS_INPUT("\x03" "1234" "\0" "678\r")
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK(writeFct_fake.call_count == 2);
CHECK(0 == memcmp(writeFct_fake.arg0_history[1], "1234", 4));
CHECK(4 == writeFct_fake.arg1_history[1]);
/* check what happens when there is a \0 in the buffer */
PROCESS_INPUT("\x03" "1234" "\0" "888\r")
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK(writeFct_fake.call_count == 2);
CHECK(0 == memcmp(writeFct_fake.arg0_history[1], "1234", 4));
CHECK(4 == writeFct_fake.arg1_history[1]);
FFF_FAKES_LIST(RESET_FAKE)
PROCESS_INPUT(BUTTON_UP)
CHECK(writeFct_fake.call_count == 3);
CHECK(0 == memcmp(writeFct_fake.arg0_history[2], "888", 3));
CHECK(3 == writeFct_fake.arg1_history[2]);
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 Stefan Strobel <stefan.strobel@shimatta.net>
* Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -4,4 +4,8 @@
#define CATCH_CONFIG_MAIN
#include "test/framework/catch.hpp"
extern "C" {
#include "test/framework/fff.h"
DEFINE_FFF_GLOBALS
}