- #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();
- }
|