From a609f4e5cdba37a5f87b17868acd239971cff061 Mon Sep 17 00:00:00 2001 From: Javier Balloffet Date: Wed, 8 May 2024 10:40:23 -0300 Subject: [PATCH] Add Shell class tests (#242) Signed-off-by: Javier Balloffet --- andino_firmware/platformio.ini | 1 + .../test/desktop/test_shell/shell_test.cpp | 304 ++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 andino_firmware/test/desktop/test_shell/shell_test.cpp diff --git a/andino_firmware/platformio.ini b/andino_firmware/platformio.ini index f3b40bbe..b63cf7ae 100644 --- a/andino_firmware/platformio.ini +++ b/andino_firmware/platformio.ini @@ -32,6 +32,7 @@ build_src_filter = + + + + + ; Environment for Arduino Uno. [env:uno] diff --git a/andino_firmware/test/desktop/test_shell/shell_test.cpp b/andino_firmware/test/desktop/test_shell/shell_test.cpp new file mode 100644 index 00000000..71789d7e --- /dev/null +++ b/andino_firmware/test/desktop/test_shell/shell_test.cpp @@ -0,0 +1,304 @@ +// BSD 3-Clause License +// +// Copyright (c) 2024, Ekumen Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "shell.h" + +#include +#include + +#include "serial_stream.h" + +namespace andino { +namespace test { +namespace { + +using ::testing::Return; + +class MockSerialStream : public andino::SerialStream { + public: + MockSerialStream() : andino::SerialStream() {} + MOCK_METHOD(void, begin, (unsigned long baud), (const, override)); + MOCK_METHOD(int, available, (), (const, override)); + MOCK_METHOD(int, read, (), (const, override)); + MOCK_METHOD(size_t, print, (const char* c), (const, override)); + MOCK_METHOD(size_t, print, (char c), (const, override)); + MOCK_METHOD(size_t, print, (unsigned char b, int base), (const, override)); + MOCK_METHOD(size_t, print, (int num, int base), (const, override)); + MOCK_METHOD(size_t, print, (unsigned int num, int base), (const, override)); + MOCK_METHOD(size_t, print, (long num, int base), (const, override)); + MOCK_METHOD(size_t, print, (unsigned long num, int base), (const, override)); + MOCK_METHOD(size_t, print, (double num, int digits), (const, override)); + MOCK_METHOD(size_t, println, (const char* c), (const, override)); + MOCK_METHOD(size_t, println, (char c), (const, override)); + MOCK_METHOD(size_t, println, (unsigned char b, int base), (const, override)); + MOCK_METHOD(size_t, println, (int num, int base), (const, override)); + MOCK_METHOD(size_t, println, (unsigned int num, int base), (const, override)); + MOCK_METHOD(size_t, println, (long num, int base), (const, override)); + MOCK_METHOD(size_t, println, (unsigned long num, int base), (const, override)); + MOCK_METHOD(size_t, println, (double num, int digits), (const, override)); +}; + +class ShellTest : public testing::Test { + protected: + void SetUp() override { + shell.set_serial_stream(&serial_stream_); + shell.set_default_callback(cmd_unknown_cb); + shell.register_command(kCommand1, cmd_1_cb); + shell.register_command(kCommand2, cmd_2_cb); + shell.register_command(kCommand3, cmd_3_cb); + } + + static void cmd_unknown_cb(int argc, char** argv) { + called_callback_ = 0; + save_arguments(argc, argv); + } + + static void cmd_1_cb(int argc, char** argv) { + called_callback_ = 1; + save_arguments(argc, argv); + } + + static void cmd_2_cb(int argc, char** argv) { + called_callback_ = 2; + save_arguments(argc, argv); + } + + static void cmd_3_cb(int argc, char** argv) { + called_callback_ = 3; + save_arguments(argc, argv); + } + + static void save_arguments(int argc, char** argv) { + argc_ = argc; + for (int i = 0; i < argc; i++) { + strcpy(argv_[i], argv[i]); + } + } + + static constexpr const char* kCommand1{"a"}; + static constexpr const char* kCommand2{"ab"}; + static constexpr const char* kCommand3{"cde"}; + + static int called_callback_; + static int argc_; + static char argv_[5][10]; + + andino::Shell shell; + MockSerialStream serial_stream_; +}; + +int ShellTest::called_callback_ = -1; +int ShellTest::argc_ = 0; +char ShellTest::argv_[5][10] = {'\0'}; + +TEST_F(ShellTest, ProcessInputEmpty) { + EXPECT_CALL(serial_stream_, available()).Times(1).WillOnce(Return(0)); + + shell.process_input(); +} + +TEST_F(ShellTest, ProcessInputMessageSingleCharacterCommandSingleArg) { + const char* input_message = "a\r"; + const char* expected_argv[] = {"a"}; + + int available_call_count = strlen(input_message) + 1; + EXPECT_CALL(serial_stream_, available()).Times(available_call_count); + ON_CALL(serial_stream_, available()) + .WillByDefault( + testing::Invoke([&available_call_count]() -> int { return --available_call_count; })); + + int input_index = 0; + EXPECT_CALL(serial_stream_, read()).Times(strlen(input_message)); + ON_CALL(serial_stream_, read()) + .WillByDefault(testing::Invoke( + [input_message, &input_index]() -> int { return input_message[input_index++]; })); + + shell.process_input(); + + ASSERT_EQ(called_callback_, 1); + ASSERT_EQ(argc_, 1); + EXPECT_STREQ(argv_[0], expected_argv[0]); +} + +TEST_F(ShellTest, ProcessInputMessageUnknownCommand) { + const char* input_message = "z\r"; + const char* expected_argv[] = {"z"}; + + int available_call_count = strlen(input_message) + 1; + EXPECT_CALL(serial_stream_, available()).Times(available_call_count); + ON_CALL(serial_stream_, available()) + .WillByDefault( + testing::Invoke([&available_call_count]() -> int { return --available_call_count; })); + + int input_index = 0; + EXPECT_CALL(serial_stream_, read()).Times(strlen(input_message)); + ON_CALL(serial_stream_, read()) + .WillByDefault(testing::Invoke( + [input_message, &input_index]() -> int { return input_message[input_index++]; })); + + shell.process_input(); + + ASSERT_EQ(called_callback_, 0); + ASSERT_EQ(argc_, 1); + EXPECT_STREQ(argv_[0], expected_argv[0]); +} + +TEST_F(ShellTest, ProcessInputMessageTwoCharacterCommandSingleArg) { + const char* input_message = "ab\r"; + const char* expected_argv[] = {"ab"}; + + int available_call_count = strlen(input_message) + 1; + EXPECT_CALL(serial_stream_, available()).Times(available_call_count); + ON_CALL(serial_stream_, available()) + .WillByDefault( + testing::Invoke([&available_call_count]() -> int { return --available_call_count; })); + + int input_index = 0; + EXPECT_CALL(serial_stream_, read()).Times(strlen(input_message)); + ON_CALL(serial_stream_, read()) + .WillByDefault(testing::Invoke( + [input_message, &input_index]() -> int { return input_message[input_index++]; })); + + shell.process_input(); + + ASSERT_EQ(called_callback_, 2); + ASSERT_EQ(argc_, 1); + EXPECT_STREQ(argv_[0], expected_argv[0]); +} + +TEST_F(ShellTest, ProcessInputMessageThreeCharacterCommandSingleArg) { + const char* input_message = "cde\r"; + const char* expected_argv[] = {"cde"}; + + int available_call_count = strlen(input_message) + 1; + EXPECT_CALL(serial_stream_, available()).Times(available_call_count); + ON_CALL(serial_stream_, available()) + .WillByDefault( + testing::Invoke([&available_call_count]() -> int { return --available_call_count; })); + + int input_index = 0; + EXPECT_CALL(serial_stream_, read()).Times(strlen(input_message)); + ON_CALL(serial_stream_, read()) + .WillByDefault(testing::Invoke( + [input_message, &input_index]() -> int { return input_message[input_index++]; })); + + shell.process_input(); + + ASSERT_EQ(called_callback_, 3); + ASSERT_EQ(argc_, 1); + EXPECT_STREQ(argv_[0], expected_argv[0]); +} + +TEST_F(ShellTest, ProcessInputMessageTwoArgs) { + const char* input_message = "a 12\r"; + const char* expected_argv[] = {"a", "12"}; + + int available_call_count = strlen(input_message) + 1; + EXPECT_CALL(serial_stream_, available()).Times(available_call_count); + ON_CALL(serial_stream_, available()) + .WillByDefault( + testing::Invoke([&available_call_count]() -> int { return --available_call_count; })); + + int input_index = 0; + EXPECT_CALL(serial_stream_, read()).Times(strlen(input_message)); + ON_CALL(serial_stream_, read()) + .WillByDefault(testing::Invoke( + [input_message, &input_index]() -> int { return input_message[input_index++]; })); + + shell.process_input(); + + ASSERT_EQ(called_callback_, 1); + ASSERT_EQ(argc_, 2); + EXPECT_STREQ(argv_[0], expected_argv[0]); + EXPECT_STREQ(argv_[1], expected_argv[1]); +} + +TEST_F(ShellTest, ProcessInputMessageThreeArgs) { + const char* input_message = "ab 12 3\r"; + const char* expected_argv[] = {"ab", "12", "3"}; + + int available_call_count = strlen(input_message) + 1; + EXPECT_CALL(serial_stream_, available()).Times(available_call_count); + ON_CALL(serial_stream_, available()) + .WillByDefault( + testing::Invoke([&available_call_count]() -> int { return --available_call_count; })); + + int input_index = 0; + EXPECT_CALL(serial_stream_, read()).Times(strlen(input_message)); + ON_CALL(serial_stream_, read()) + .WillByDefault(testing::Invoke( + [input_message, &input_index]() -> int { return input_message[input_index++]; })); + + shell.process_input(); + + ASSERT_EQ(called_callback_, 2); + ASSERT_EQ(argc_, 3); + EXPECT_STREQ(argv_[0], expected_argv[0]); + EXPECT_STREQ(argv_[1], expected_argv[1]); + EXPECT_STREQ(argv_[2], expected_argv[2]); +} + +TEST_F(ShellTest, ProcessInputMessageFourArgs) { + const char* input_message = "cde 12 3 456\r"; + const char* expected_argv[] = {"cde", "12", "3", "456"}; + + int available_call_count = strlen(input_message) + 1; + EXPECT_CALL(serial_stream_, available()).Times(available_call_count); + ON_CALL(serial_stream_, available()) + .WillByDefault( + testing::Invoke([&available_call_count]() -> int { return --available_call_count; })); + + int input_index = 0; + EXPECT_CALL(serial_stream_, read()).Times(strlen(input_message)); + ON_CALL(serial_stream_, read()) + .WillByDefault(testing::Invoke( + [input_message, &input_index]() -> int { return input_message[input_index++]; })); + + shell.process_input(); + + ASSERT_EQ(called_callback_, 3); + ASSERT_EQ(argc_, 4); + EXPECT_STREQ(argv_[0], expected_argv[0]); + EXPECT_STREQ(argv_[1], expected_argv[1]); + EXPECT_STREQ(argv_[2], expected_argv[2]); + EXPECT_STREQ(argv_[3], expected_argv[3]); +} + +} // namespace +} // namespace test +} // namespace andino + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + if (RUN_ALL_TESTS()) { + } + + // Always return zero-code and allow PlatformIO to parse results. + return 0; +}