diff --git a/.gitignore b/.gitignore index 35174ca..6faf2bf 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ build/* doc/* workspace.code-workspace sdkconfig +googletest diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8cf8b5e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "googletest"] + path = googletest + url = https://github.com/google/googletest.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 3691ade..13c9b3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,22 +2,57 @@ cmake_minimum_required(VERSION 3.22) set(CMAKE_CXX_STANDARD 17) -set(EXTRA_COMPONENT_DIRS src) +if(${GTEST}) + #Creates a variable named TEST_FILES that can be used via ${TEST_FILES} + set(TEST_FILES + src/test/testClass_test.cpp + ) + #Libraries that are needed by the test files + set(TEST_LIBS + testClass_lib + ) -set(application_name "esp32_lora_gateway") -set(application_version "0.1.0") + #adds include directories before adding subdirectories so they have access to the header prototypes + include_directories(components/testClass) -set(PROJECT_VER ${application_version}) + #tells cmake that there is another CMakeList.txt in components/testClass that it needs to process + add_subdirectory(src/test) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) + project(unit-tests) -# idf.py -DCMAKE_BUILD_TYPE=Debug reconfigure -# idf.py -DCMAKE_BUILD_TYPE=Release reconfigure -if(CMAKE_BUILD_TYPE MATCHES Debug) - set(opt_level -O0) - add_definitions(-DDEBUG_BUILD) + message("running GTEST") + add_subdirectory(googletest) + + # adds to the include path + # We have access to the var gtest_SOURCE_DIR because it is being set in the `lib` CMake files that is called above + # via add_subdirectory(google) + include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) + include_directories(${gmock_SOURCE_DIR}/include ${gmock_SOURCE_DIR}) + + # Creates an executable called Google_Test_run and adds tests from testClass/test/test.cpp + add_executable(Google_Tests_run ${TEST_FILES}) + + # link the project libraries and google test library to the executable Google_Tests_run + target_link_libraries(Google_Tests_run ${TEST_LIBS} gtest gmock gtest_main) else() - set(opt_level -Os) -endif() + set(EXTRA_COMPONENT_DIRS src) + + set(application_name "esp32_lora_gateway") + set(application_version "0.1.0") + + set(PROJECT_VER ${application_version}) + + include($ENV{IDF_PATH}/tools/cmake/project.cmake) + + # idf.py -DCMAKE_BUILD_TYPE=Debug reconfigure + # idf.py -DCMAKE_BUILD_TYPE=Release reconfigure + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(opt_level -O0) + add_definitions(-DDEBUG_BUILD) + else() + set(opt_level -Os) + endif() + + project(${application_name}) -project(${application_name}) +endif() \ No newline at end of file diff --git a/code-format.sh b/code-format.sh index e4ac3b7..1a34ce0 100755 --- a/code-format.sh +++ b/code-format.sh @@ -13,4 +13,4 @@ astyle \ --unpad-paren \ --suffix=none \ "$@" \ - --recursive "src/core/*.c" "src/core/*.h" "src/app/*.c" "src/app/*.h" \ No newline at end of file + --recursive "src/core/*.c" "src/core/*.h" "src/app/*.c" "src/app/*.h" "src/test/*.cpp" "src/test/*.h" \ No newline at end of file diff --git a/googletest b/googletest new file mode 160000 index 0000000..2d4f208 --- /dev/null +++ b/googletest @@ -0,0 +1 @@ +Subproject commit 2d4f208765af7fa376b878860a7677ecc0bc390a diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt new file mode 100644 index 0000000..2f9638c --- /dev/null +++ b/src/test/CMakeLists.txt @@ -0,0 +1,23 @@ +if(${GTEST}) + project(testClass) + + #Creates an env var for building HEADER_FILES + set(HEADER_FILES + testClass.h + ) + #Creates an env var for building SOURCE_FILES + set(SOURCE_FILES + testClass.cpp + ) + + #Adds a library called testClass_lib to be built from the SOURCE_FILES, will be statically linked + add_library(testClass_lib STATIC ${SOURCE_FILES} ${HEADER_FILES}) +else() + set(SOURCE_FILES + testClass.cpp + ) + + set(COMPONENT_SRCS "${SOURCE_FILES}") + set(COMPONENT_ADD_INCLUDEDIRS ".") + register_component() +endif() \ No newline at end of file diff --git a/src/test/testClass.cpp b/src/test/testClass.cpp new file mode 100644 index 0000000..4837786 --- /dev/null +++ b/src/test/testClass.cpp @@ -0,0 +1,27 @@ +#include "testClass.h" + +#include + +// Clones a 0-terminated C string, allocating memory using new. +const char *MyString::CloneCString(const char *a_c_string) +{ + if (a_c_string == nullptr) { + return nullptr; + } + + const size_t len = strlen(a_c_string); + char *const clone = new char[len + 1]; + memcpy(clone, a_c_string, len + 1); + + return clone; +} + +// Sets the 0-terminated C string this MyString object +// represents. +void MyString::Set(const char *a_c_string) +{ + // Makes sure this works when c_string == c_string_ + const char *const temp = MyString::CloneCString(a_c_string); + delete[] c_string_; + c_string_ = temp; +} \ No newline at end of file diff --git a/src/test/testClass.h b/src/test/testClass.h new file mode 100644 index 0000000..78b219b --- /dev/null +++ b/src/test/testClass.h @@ -0,0 +1,61 @@ +#ifndef TEST_CLASS_H +#define TEST_CLASS_H + + +#include + +// A simple string class. +class MyString { +private: + const char *c_string_; + const MyString &operator=(const MyString &rhs); + +public: + // Clones a 0-terminated C string, allocating memory using new. + static const char *CloneCString(const char *a_c_string); + + //////////////////////////////////////////////////////////// + // + // C'tors + + // The default c'tor constructs a NULL string. + MyString() : c_string_(nullptr) {} + + // Constructs a MyString by cloning a 0-terminated C string. + explicit MyString(const char *a_c_string) : c_string_(nullptr) + { + Set(a_c_string); + } + + // Copy c'tor + MyString(const MyString &string) : c_string_(nullptr) + { + Set(string.c_string_); + } + + //////////////////////////////////////////////////////////// + // + // D'tor. MyString is intended to be a final class, so the d'tor + // doesn't need to be virtual. + ~MyString() + { + delete[] c_string_; + } + + // Gets the 0-terminated C string this MyString object represents. + const char *c_string() const + { + return c_string_; + } + + size_t Length() const + { + return c_string_ == nullptr ? 0 : strlen(c_string_); + } + + // Sets the 0-terminated C string this MyString object represents. + void Set(const char *c_string); +}; + + +#endif //TEST_CLASS_H \ No newline at end of file diff --git a/src/test/testClass_test.cpp b/src/test/testClass_test.cpp new file mode 100644 index 0000000..1fe65e9 --- /dev/null +++ b/src/test/testClass_test.cpp @@ -0,0 +1,71 @@ +#include "testClass.h" +#include "gtest/gtest.h" +namespace { +// In this example, we test the MyString class (a simple string). + +// Tests the default c'tor. +TEST(MyString, DefaultConstructor) +{ + const MyString s; + + // Asserts that s.c_string() returns NULL. + // + // + // + // If we write NULL instead of + // + // static_cast(NULL) + // + // in this assertion, it will generate a warning on gcc 3.4. The + // reason is that EXPECT_EQ needs to know the types of its + // arguments in order to print them when it fails. Since NULL is + // #defined as 0, the compiler will use the formatter function for + // int to print it. However, gcc thinks that NULL should be used as + // a pointer, not an int, and therefore complains. + // + // The root of the problem is C++'s lack of distinction between the + // integer number 0 and the null pointer constant. Unfortunately, + // we have to live with this fact. + // + // + EXPECT_STREQ(nullptr, s.c_string()); + + EXPECT_EQ(0u, s.Length()); +} + +const char kHelloString[] = "Hello, world!"; + +// Tests the c'tor that accepts a C string. +TEST(MyString, ConstructorFromCString) +{ + const MyString s(kHelloString); + EXPECT_EQ(0, strcmp(s.c_string(), kHelloString)); + EXPECT_EQ(sizeof(kHelloString) / sizeof(kHelloString[0]) - 1, s.Length()); +} + +// Tests the copy c'tor. +TEST(MyString, CopyConstructor) +{ + const MyString s1(kHelloString); + const MyString s2 = s1; + EXPECT_EQ(0, strcmp(s2.c_string(), kHelloString)); +} + +// Tests the Set method. +TEST(MyString, Set) +{ + MyString s; + + s.Set(kHelloString); + EXPECT_EQ(0, strcmp(s.c_string(), kHelloString)); + + // Set should work when the input pointer is the same as the one + // already in the MyString object. + s.Set(s.c_string()); + EXPECT_EQ(0, strcmp(s.c_string(), kHelloString)); + + // Can we set the MyString to NULL? + s.Set(nullptr); + EXPECT_STREQ(nullptr, s.c_string()); +} +} // namespace