Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

266 lines
8.6 KiB

#include <unity.h>
#include <Arduino.h>
#include <StreamString.h>
#include <terminal_commands.h>
// TODO: should we just use std::function at this point?
// we don't actually benefit from having basic ptr functions in handler
// test would be simplified too, we would no longer need to have static vars
// Got the idea from the Embedis test suite, set up a proxy for StreamString
// Real terminal processing happens with ringbuffer'ed stream
struct IOStreamString : public Stream {
StreamString in;
StreamString out;
size_t write(uint8_t ch) final override {
return in.write(ch);
}
int read() final override {
return out.read();
}
int available() final override {
return out.available();
}
int peek() final override {
return out.peek();
}
void flush() final override {
out.flush();
}
};
// We need to make sure that our changes to split_args actually worked
void test_hex_codes() {
static bool abc_done = false;
terminal::Terminal::addCommand(F("abc"), [](const terminal::CommandContext& ctx) {
TEST_ASSERT_EQUAL(2, ctx.argc);
TEST_ASSERT_EQUAL_STRING("abc", ctx.argv[0].c_str());
TEST_ASSERT_EQUAL_STRING("abc", ctx.argv[1].c_str());
abc_done = true;
});
IOStreamString str;
str.out += String("abc \"\x61\x62\x63\"\r\n");
terminal::Terminal handler(str);
TEST_ASSERT_EQUAL(
terminal::Terminal::Result::Command,
handler.processLine()
);
TEST_ASSERT(abc_done);
}
// Ensure that we can register multiple commands (at least 3, might want to test much more in the future?)
// Ensure that registered commands can be called and they are called in order
void test_multiple_commands() {
// set up counter to be chained between commands
static int command_calls = 0;
terminal::Terminal::addCommand(F("test1"), [](const terminal::CommandContext& ctx) {
TEST_ASSERT_EQUAL_MESSAGE(1, ctx.argc, "Command without args should have argc == 1");
TEST_ASSERT_EQUAL(0, command_calls);
command_calls = 1;
});
terminal::Terminal::addCommand(F("test2"), [](const terminal::CommandContext& ctx) {
TEST_ASSERT_EQUAL_MESSAGE(1, ctx.argc, "Command without args should have argc == 1");
TEST_ASSERT_EQUAL(1, command_calls);
command_calls = 2;
});
terminal::Terminal::addCommand(F("test3"), [](const terminal::CommandContext& ctx) {
TEST_ASSERT_EQUAL_MESSAGE(1, ctx.argc, "Command without args should have argc == 1");
TEST_ASSERT_EQUAL(2, command_calls);
command_calls = 3;
});
IOStreamString str;
str.out += String("test1\r\ntest2\r\ntest3\r\n");
terminal::Terminal handler(str);
// each processing step only executes a single command
static int process_counter = 0;
handler.process([](terminal::Terminal::Result result) -> bool {
if (process_counter == 3) {
TEST_ASSERT_EQUAL(result, terminal::Terminal::Result::NoInput);
return false;
} else {
TEST_ASSERT_EQUAL(result, terminal::Terminal::Result::Command);
++process_counter;
return true;
}
TEST_FAIL_MESSAGE("Should not be reached");
return false;
});
TEST_ASSERT_EQUAL(3, command_calls);
TEST_ASSERT_EQUAL(3, process_counter);
}
void test_command() {
static int counter = 0;
terminal::Terminal::addCommand(F("test.command"), [](const terminal::CommandContext& ctx) {
TEST_ASSERT_EQUAL_MESSAGE(1, ctx.argc, "Command without args should have argc == 1");
++counter;
});
IOStreamString str;
terminal::Terminal handler(str);
TEST_ASSERT_EQUAL_MESSAGE(
terminal::Terminal::Result::NoInput, handler.processLine(),
"We have not read anything yet"
);
str.out += String("test.command\r\n");
TEST_ASSERT_EQUAL(terminal::Terminal::Result::Command, handler.processLine());
TEST_ASSERT_EQUAL_MESSAGE(1, counter, "At this time `test.command` was called just once");
str.out += String("test.command");
TEST_ASSERT_EQUAL(terminal::Terminal::Result::Pending, handler.processLine());
TEST_ASSERT_EQUAL_MESSAGE(1, counter, "We are waiting for either \\r\\n or \\n, handler still has data buffered");
str.out += String("\r\n");
TEST_ASSERT_EQUAL(terminal::Terminal::Result::Command, handler.processLine());
TEST_ASSERT_EQUAL_MESSAGE(2, counter, "We should call `test.command` the second time");
str.out += String("test.command\n");
TEST_ASSERT_EQUAL(terminal::Terminal::Result::Command, handler.processLine());
TEST_ASSERT_EQUAL_MESSAGE(3, counter, "We should call `test.command` the third time, with just LF");
}
// Ensure that we can properly handle arguments
void test_command_args() {
static bool waiting = false;
terminal::Terminal::addCommand(F("test.command.arg1"), [](const terminal::CommandContext& ctx) {
TEST_ASSERT_EQUAL(2, ctx.argc);
waiting = false;
});
terminal::Terminal::addCommand(F("test.command.arg1_empty"), [](const terminal::CommandContext& ctx) {
TEST_ASSERT_EQUAL(2, ctx.argc);
TEST_ASSERT(!ctx.argv[1].length());
waiting = false;
});
IOStreamString str;
terminal::Terminal handler(str);
waiting = true;
str.out += String("test.command.arg1 test\r\n");
TEST_ASSERT_EQUAL(terminal::Terminal::Result::Command, handler.processLine());
TEST_ASSERT(!waiting);
waiting = true;
str.out += String("test.command.arg1_empty \"\"\r\n");
TEST_ASSERT_EQUAL(terminal::Terminal::Result::Command, handler.processLine());
TEST_ASSERT(!waiting);
}
// Ensure that we return error when nothing was handled, but we kept feeding the processLine() with data
void test_buffer() {
IOStreamString str;
str.out += String("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n");
terminal::Terminal handler(str, str.out.available() - 8);
TEST_ASSERT_EQUAL(terminal::Terminal::Result::BufferOverflow, handler.processLine());
}
// sdssplitargs returns nullptr when quotes are not terminated and empty char for an empty string. we treat it all the same
void test_quotes() {
terminal::Terminal::addCommand(F("test.quotes"), [](const terminal::CommandContext& ctx) {
for (auto& arg : ctx.argv) {
TEST_MESSAGE(arg.c_str());
}
TEST_FAIL_MESSAGE("`test.quotes` should not be called");
});
IOStreamString str;
terminal::Terminal handler(str);
str.out += String("test.quotes \"quote without a pair\r\n");
TEST_ASSERT_EQUAL(terminal::Terminal::Result::NoInput, handler.processLine());
str.out += String("test.quotes 'quote without a pair\r\n");
TEST_ASSERT_EQUAL(terminal::Terminal::Result::NoInput, handler.processLine());
TEST_ASSERT_EQUAL(terminal::Terminal::Result::NoInput, handler.processLine());
}
// we specify that commands lowercase == UPPERCASE, both with hashed values and with equality functions
// (internal note: we use std::unordered_map at this time)
void test_case_insensitive() {
terminal::Terminal::addCommand(F("test.lowercase1"), [](const terminal::CommandContext& ctx) {
TEST_FAIL_MESSAGE("`test.lowercase1` was registered first, but there's another function by the same name. This should not be called");
});
terminal::Terminal::addCommand(F("TEST.LOWERCASE1"), [](const terminal::CommandContext& ctx) {
__asm__ volatile ("nop");
});
IOStreamString str;
terminal::Terminal handler(str);
str.out += String("TeSt.lOwErCaSe1\r\n");
TEST_ASSERT_EQUAL(terminal::Terminal::Result::Command, handler.processLine());
}
// We can use command ctx.output to send something back into the stream
void test_output() {
terminal::Terminal::addCommand(F("test.output"), [](const terminal::CommandContext& ctx) {
if (ctx.argc != 2) return;
ctx.output.print(ctx.argv[1]);
});
IOStreamString str;
terminal::Terminal handler(str);
char match[] = "test1234567890";
str.out += String("test.output ") + String(match) + String("\r\n");
TEST_ASSERT_EQUAL(terminal::Terminal::Result::Command, handler.processLine());
TEST_ASSERT_EQUAL_STRING(match, str.in.c_str());
}
// When adding test functions, don't forget to add RUN_TEST(...) in the main()
int main(int argc, char** argv) {
UNITY_BEGIN();
RUN_TEST(test_command);
RUN_TEST(test_command_args);
RUN_TEST(test_multiple_commands);
RUN_TEST(test_hex_codes);
RUN_TEST(test_buffer);
RUN_TEST(test_quotes);
RUN_TEST(test_case_insensitive);
RUN_TEST(test_output);
UNITY_END();
}