diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d32079..1ec2604 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: build-os-matrix: needs: check runs-on: ubuntu-latest - outputs: + outputs: os: ${{ steps.os.outputs.os }} steps: - name: Build OS Array @@ -38,3 +38,11 @@ jobs: uses: ./.github/workflows/rpm-test.yml with: os: ${{ matrix.os }} + make-test: + needs: [build-os-matrix, build] + strategy: + matrix: + os: ${{ fromJSON(needs.build-os-matrix.outputs.os) }} + uses: ./.github/workflows/make-test.yml + with: + os: ${{ matrix.os }} diff --git a/.github/workflows/cppcheck.yml b/.github/workflows/cppcheck.yml index c47dd41..02595b7 100644 --- a/.github/workflows/cppcheck.yml +++ b/.github/workflows/cppcheck.yml @@ -15,7 +15,7 @@ jobs: inline_suppression: enable output_file: 'cppcheck_report.txt' enable: warning,performance,portability,style,information - other_options: --error-exitcode=1 + other_options: --error-exitcode=1 --library=googletest - name: Print cppcheck_report.txt if: failure() run: cat cppcheck_report.txt diff --git a/.github/workflows/make-test.yml b/.github/workflows/make-test.yml new file mode 100644 index 0000000..549eac6 --- /dev/null +++ b/.github/workflows/make-test.yml @@ -0,0 +1,23 @@ +name: make-test + +on: + workflow_call: + inputs: + os: + required: true + type: string + +jobs: + make-test: + runs-on: ubuntu-latest + container: ${{ inputs.os }} + steps: + - name: Install dependencies + run: | + dnf install -y make gcc-c++ cmake3 git rpm-build fuse3-devel + - name: Check out repository code + uses: actions/checkout@v4 + - name: Mark github workspace as safe + run: git config --system --add safe.directory $PWD + - name: make test + run: make test diff --git a/CMakeLists.txt b/CMakeLists.txt index f341dee..f842169 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ include(cmake/installation.cmake) option(TELEMETRY_BUILD_SHARED "Build shared library" ON) option(TELEMETRY_PACKAGE_BUILDER "Enable RPM package builder (make rpm)" ON) option(TELEMETRY_INSTALL_TARGETS "Generate the install target" ON) +option(TELEMETRY_ENABLE_TESTS "Build Unit tests (make test)" OFF) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -32,8 +33,16 @@ endif() include(cmake/dependencies.cmake) +if (TELEMETRY_ENABLE_TESTS) + include(cmake/googletest.cmake) + include(GoogleTest) + enable_testing() +endif() + add_subdirectory(src) if (TELEMETRY_PACKAGE_BUILDER) add_subdirectory(pkg) endif() + + diff --git a/Makefile b/Makefile index f991130..8afd119 100644 --- a/Makefile +++ b/Makefile @@ -46,3 +46,9 @@ tidy: all tidy-fix: all $(RUN_CLANG_TIDY) -p build -quiet -fix -j $(shell nproc) $(SOURCE_DIR) +.PHONY: test +test: build + @cd build && $(CMAKE) $(CMAKE_ARGS) -DTELEMETRY_ENABLE_TESTS=ON .. + @$(MAKE) --no-print-directory -C build + @$(MAKE) test --no-print-directory -C build + diff --git a/cmake/googletest.cmake b/cmake/googletest.cmake new file mode 100644 index 0000000..0f2aa4f --- /dev/null +++ b/cmake/googletest.cmake @@ -0,0 +1,15 @@ +# Google Test library +# +# Google Test is a testing framework for C++ developed by Google. +# +# This CMake script integrates Google Test into the project, allowing easy usage. +# Usage example: target_link_libraries(my_target PRIVATE GTest::gtest_main) + +include(FetchContent) + +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.14.0 +) +FetchContent_MakeAvailable(googletest) diff --git a/src/telemetry/CMakeLists.txt b/src/telemetry/CMakeLists.txt index af0288b..3c26727 100644 --- a/src/telemetry/CMakeLists.txt +++ b/src/telemetry/CMakeLists.txt @@ -26,3 +26,11 @@ if (TELEMETRY_INSTALL_TARGETS) install(TARGETS telemetry LIBRARY DESTINATION ${INSTALL_DIR_LIB}) install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION ${INSTALL_DIR_INCLUDE}) endif() + +if (TELEMETRY_ENABLE_TESTS) + add_executable(testTelemetry ${TELEMETRY_SOURCE_FILES}) + target_compile_definitions(testTelemetry PRIVATE TELEMETRY_ENABLE_TESTS) + target_include_directories(testTelemetry PRIVATE ${PROJECT_SOURCE_DIR}/include) + target_link_libraries(testTelemetry GTest::gtest_main) + gtest_discover_tests(testTelemetry) +endif() diff --git a/src/telemetry/aggFile.cpp b/src/telemetry/aggFile.cpp index 823a05f..72a281f 100644 --- a/src/telemetry/aggFile.cpp +++ b/src/telemetry/aggFile.cpp @@ -163,3 +163,7 @@ AggregatedFile::AggregatedFile( } } // namespace telemetry + +#ifdef TELEMETRY_ENABLE_TESTS +#include "tests/testAggFile.cpp" +#endif diff --git a/src/telemetry/aggregator/aggAvg.cpp b/src/telemetry/aggregator/aggAvg.cpp index 19a7d28..54289ac 100644 --- a/src/telemetry/aggregator/aggAvg.cpp +++ b/src/telemetry/aggregator/aggAvg.cpp @@ -55,3 +55,7 @@ Content AggMethodAvg::aggregate(const std::vector& contents) } } // namespace telemetry + +#ifdef TELEMETRY_ENABLE_TESTS +#include "tests/testAggAvg.cpp" +#endif diff --git a/src/telemetry/aggregator/aggJoin.cpp b/src/telemetry/aggregator/aggJoin.cpp index 0976ec3..0e9e2d5 100644 --- a/src/telemetry/aggregator/aggJoin.cpp +++ b/src/telemetry/aggregator/aggJoin.cpp @@ -65,3 +65,7 @@ Content AggMethodJoin::aggregate(const std::vector& contents) } } // namespace telemetry + +#ifdef TELEMETRY_ENABLE_TESTS +#include "tests/testAggJoin.cpp" +#endif diff --git a/src/telemetry/aggregator/aggSum.cpp b/src/telemetry/aggregator/aggSum.cpp index d7c3698..be66ce6 100644 --- a/src/telemetry/aggregator/aggSum.cpp +++ b/src/telemetry/aggregator/aggSum.cpp @@ -130,3 +130,7 @@ Content AggMethodSum::aggregate(const std::vector& contents) } } // namespace telemetry + +#ifdef TELEMETRY_ENABLE_TESTS +#include "tests/testAggSum.cpp" +#endif diff --git a/src/telemetry/aggregator/tests/testAggAvg.cpp b/src/telemetry/aggregator/tests/testAggAvg.cpp new file mode 100644 index 0000000..27a881c --- /dev/null +++ b/src/telemetry/aggregator/tests/testAggAvg.cpp @@ -0,0 +1,152 @@ +/** + * @file + * @author Pavel Siska + * @brief Unit tests of Telemetry::AggMethodAvg + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include + +namespace telemetry { + +/** + * @test Test making average from a result and count + */ +TEST(AggAvgTest, TestMakeAverage) +{ + // Test making average from uint64_t + { + Scalar result = uint64_t(100); + makeAverage(result, 10); + EXPECT_EQ(10.0, std::get(result)); + } + + // Test making average from int64_t + { + Scalar result = int64_t(100); + makeAverage(result, 20); + EXPECT_EQ(5.0, std::get(result)); + } + + // Test making average from double + { + Scalar result = 100.0; + makeAverage(result, 50); + EXPECT_EQ(2.0, std::get(result)); + } + + // Test making average from unsupported type (expect failure) + { + Scalar result = true; + EXPECT_THROW(makeAverage(result, 10), TelemetryException); + } +} + +/** + * @test Test converting aggregated content to an average value + */ +TEST(AggAvgTest, convertToAverage) +{ + // Test converting Scalar to average + { + AggContent aggContent = Scalar {5.0}; + ResultType result = convertToAverage(aggContent, 10); + EXPECT_TRUE(std::holds_alternative(result)); + const auto& scalar = std::get(result); + EXPECT_EQ(0.5, std::get(scalar)); + } + + // Test converting ScalarWithUnit to average + { + AggContent aggContent = ScalarWithUnit {5.0, "unit"}; + ResultType result = convertToAverage(aggContent, 2); + EXPECT_TRUE(std::holds_alternative(result)); + const auto& [scalar, unit] = std::get(result); + EXPECT_EQ(2.5, std::get(scalar)); + EXPECT_EQ("unit", unit); + } + + // Test converting unsupported type to average (expect failure) + { + AggContent aggContent = std::monostate(); + EXPECT_THROW(convertToAverage(aggContent, 2), TelemetryException); + } +} + +/** + * @test Test aggregation method for averaging values + */ +TEST(AggAvgTest, TestAggregate) +{ + // Test aggregation of scalar values + { + AggMethodAvg aggMethodAvg; + std::vector contents = {Scalar {5.0}, Scalar {10.0}, Scalar {15.0}}; + Content content = aggMethodAvg.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + Scalar& scalar = std::get(content); + EXPECT_TRUE(std::holds_alternative(scalar)); + double result = std::get(scalar); + EXPECT_EQ(10.0, result); + } + + // Test aggregation of ScalarWithUnit values + { + AggMethodAvg aggMethodAvg; + std::vector contents = {ScalarWithUnit {5.0, "unit"}}; + Content content = aggMethodAvg.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + const auto& [scalar, unit] = std::get(content); + EXPECT_EQ(5.0, std::get(scalar)); + EXPECT_EQ("unit", unit); + } + + { + AggMethodAvg aggMethodAvg; + std::vector contents = {ScalarWithUnit {5.0, "unit"}, Scalar {5.0}}; + EXPECT_THROW(aggMethodAvg.aggregate(contents), TelemetryException); + } + + { + AggMethodAvg aggMethodAvg; + std::vector contents = {Scalar {true}, Scalar {5.0}}; + EXPECT_THROW(aggMethodAvg.aggregate(contents), TelemetryException); + } + + { + AggMethodAvg aggMethodAvg; + std::vector contents = {Scalar {uint64_t(20)}, Scalar {5.0}}; + EXPECT_THROW(aggMethodAvg.aggregate(contents), TelemetryException); + } + + { + AggMethodAvg aggMethodAvg; + std::vector contents = {Scalar {uint64_t(20)}, uint64_t {5}}; + Content content = aggMethodAvg.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + const auto& scalar = std::get(content); + EXPECT_EQ(12.5, std::get(scalar)); + } + + // Test aggregation of dictionaries + { + AggMethodAvg aggMethodAvg; + aggMethodAvg.setDictField("packets", "packetsSum"); + std::vector contents + = {Dict({{"packets", Scalar {uint64_t(1)}}}), + Dict({{"packets", Scalar {uint64_t(5)}}})}; + Content content = aggMethodAvg.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + + const Dict& dict = std::get(content); + EXPECT_EQ(1, dict.size()); + + const Scalar& scalarValueAvg = std::get(dict.at("packetsSum")); + EXPECT_EQ(3.0, std::get(scalarValueAvg)); + } +} + +} // namespace telemetry diff --git a/src/telemetry/aggregator/tests/testAggJoin.cpp b/src/telemetry/aggregator/tests/testAggJoin.cpp new file mode 100644 index 0000000..d894a9e --- /dev/null +++ b/src/telemetry/aggregator/tests/testAggJoin.cpp @@ -0,0 +1,119 @@ +/** + * @file + * @author Pavel Siska + * @brief Unit tests of Telemetry::AggMethodJoin + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include + +namespace telemetry { + +/** + * @test Test aggregation of gathered values + */ +TEST(AggJoinTest, TestAggregateGatheredValues) +{ + // Test aggregation of scalar values + { + std::vector values = {Scalar {5.0}, Scalar {10.0}, Scalar {15.0}}; + ResultType result = aggregateGatheredValues(values); + EXPECT_EQ(result.size(), 3); + EXPECT_EQ(std::get(result[0]), 5.0); + EXPECT_EQ(std::get(result[1]), 10.0); + EXPECT_EQ(std::get(result[2]), 15.0); + } + + // Test aggregation of array values + { + std::vector values + = {Array {Scalar {5.0}, Scalar {-5.0}}, Array {Scalar {10.0}}, Array {Scalar {15.0}}}; + ResultType result = aggregateGatheredValues(values); + EXPECT_EQ(result.size(), 4); + EXPECT_EQ(std::get(result[0]), 5.0); + EXPECT_EQ(std::get(result[1]), -5.0); + EXPECT_EQ(std::get(result[2]), 10.0); + EXPECT_EQ(std::get(result[3]), 15.0); + } +} + +/** + * @test Test aggregation method for joining values + */ +TEST(AggJoinTest, TestAggregate) +{ + // Test aggregation of scalar values + { + AggMethodJoin aggMethodJoin; + std::vector contents = {Scalar {5.0}, Scalar {10.0}, Scalar {15.0}}; + Content content = aggMethodJoin.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + Array& array = std::get(content); + EXPECT_EQ(array.size(), 3); + EXPECT_EQ(std::get(array[0]), 5.0); + EXPECT_EQ(std::get(array[1]), 10.0); + EXPECT_EQ(std::get(array[2]), 15.0); + } + + // Test aggregation of ScalarWithUnit values (expect failure) + { + AggMethodJoin aggMethodJoin; + std::vector contents + = {ScalarWithUnit {5.0, "unit"}, ScalarWithUnit {5.0, "unit1"}}; + EXPECT_THROW(aggMethodJoin.aggregate(contents), TelemetryException); + } + + { + AggMethodJoin aggMethodJoin; + std::vector contents = {ScalarWithUnit {5.0, "unit"}, Scalar {5.0}}; + EXPECT_THROW(aggMethodJoin.aggregate(contents), TelemetryException); + } + + { + AggMethodJoin aggMethodJoin; + std::vector contents = {Scalar {true}, Scalar {5.0}}; + EXPECT_THROW(aggMethodJoin.aggregate(contents), TelemetryException); + } + + { + AggMethodJoin aggMethodJoin; + std::vector contents = {Scalar {uint64_t(20)}, Scalar {5.0}}; + EXPECT_THROW(aggMethodJoin.aggregate(contents), TelemetryException); + } + + // Test aggregation of uint64_t values + { + AggMethodJoin aggMethodJoin; + std::vector contents = {uint64_t(20), uint64_t {5}}; + Content content = aggMethodJoin.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + Array& array = std::get(content); + EXPECT_EQ(array.size(), 2); + EXPECT_EQ(std::get(array[0]), 20.0); + EXPECT_EQ(std::get(array[1]), 5.0); + } + + // Test aggregation of dictionaries + { + AggMethodJoin aggMethodJoin; + aggMethodJoin.setDictField("packets", "packetsSum"); + std::vector contents + = {Dict({{"packets", Scalar {uint64_t(1)}}}), + Dict({{"packets", Scalar {uint64_t(5)}}})}; + Content content = aggMethodJoin.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + + const Dict& dict = std::get(content); + EXPECT_EQ(1, dict.size()); + + const Array& array = std::get(dict.at("packetsSum")); + EXPECT_EQ(array.size(), 2); + EXPECT_EQ(std::get(array[0]), 1); + EXPECT_EQ(std::get(array[1]), 5); + } +} + +} // namespace telemetry diff --git a/src/telemetry/aggregator/tests/testAggSum.cpp b/src/telemetry/aggregator/tests/testAggSum.cpp new file mode 100644 index 0000000..f795a3a --- /dev/null +++ b/src/telemetry/aggregator/tests/testAggSum.cpp @@ -0,0 +1,207 @@ +/** + * @file + * @author Pavel Siska + * @brief Unit tests of Telemetry::AggMethodSum + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include + +namespace telemetry { + +/** + * @test Test summarization of scalar values + */ +TEST(AggSumTest, TestSumarize) +{ + // Test summation of uint64_t scalar + { + Scalar result = uint64_t(0); + sumarize(uint64_t(5), result); + EXPECT_EQ(uint64_t(5), std::get(result)); + } + + // Test summation of int64_t scalar + { + Scalar result = std::monostate(); + sumarize(int64_t(5), result); + EXPECT_EQ(int64_t(5), std::get(result)); + } + + // Test summation of double scalar + { + Scalar result = double(0); + sumarize(double(5.0), result); + EXPECT_EQ(double(5.0), std::get(result)); + } + + // Test summation of scalar types with different types (expect failure) + { + Scalar result = uint64_t(5); + EXPECT_THROW(sumarize(int64_t(5), result), std::exception); + } +} + +/** + * @test Test aggregation of scalar values + */ +TEST(AggSumTest, TestAggregateScalar) +{ + // Test aggregation of scalar values + { + std::vector values = {Scalar {5.0}, Scalar {10.0}, Scalar {15.0}}; + Scalar result = aggregateScalar(values); + EXPECT_EQ(Scalar {30.0}, result); + } + + // Test aggregation of ScalarWithUnit values (expect failure) + { + std::vector values = {ScalarWithUnit {5.0, "unit"}}; + EXPECT_THROW(aggregateScalar(values), TelemetryException); + } + + // Test aggregation of empty vector + { + std::vector values = {}; + Scalar result = aggregateScalar(values); + EXPECT_TRUE(std::holds_alternative(result)); + } +} + +/** + * @test Test aggregation of scalar values with units + */ +TEST(AggSumTest, TestAggregateScalarWithUnit) +{ + // Test aggregation of scalar values with unit + { + std::vector values + = {ScalarWithUnit {5.0, "unit"}, + ScalarWithUnit {10.0, "unit"}, + ScalarWithUnit {15.0, "unit"}}; + const auto& [scalar, unit] = aggregateScalarWithUnit(values); + EXPECT_EQ(std::get(scalar), 30.0); + EXPECT_EQ(unit, "unit"); + } + + // Test aggregation of Scalar values (expect failure) + { + std::vector values = {Scalar {5.0}}; + EXPECT_THROW(aggregateScalarWithUnit(values), TelemetryException); + } + + // Test aggregation of empty vector + { + std::vector values = {}; + const auto& [scalar, unit] = aggregateScalarWithUnit(values); + EXPECT_TRUE(std::holds_alternative(scalar)); + EXPECT_EQ(unit, ""); + } +} + +/** + * @test Test creation of dictionary content + */ +TEST(AggSumTest, TestCreateDictContent) +{ + ResultType result = Scalar {uint64_t(30)}; + Content content = createDictContent("sum", result); + + EXPECT_TRUE(std::holds_alternative(content)); + + Dict& contentDict = std::get(content); + EXPECT_EQ(1, contentDict.size()); + + auto iter = contentDict.cbegin(); + { + const auto& [key, value] = *(iter++); + EXPECT_EQ("sum", key); + EXPECT_TRUE(std::holds_alternative(value)); + const auto& scalar = std::get(value); + EXPECT_TRUE(std::holds_alternative(scalar)); + EXPECT_EQ(uint64_t(30), std::get(scalar)); + } +} + +/** + * @test Test aggregation method for sum + */ +TEST(AggSumTest, TestAggregate) +{ + // Test aggregation of scalar values + { + AggMethodSum aggMethodSum; + std::vector contents = {Scalar {5.0}, Scalar {10.0}, Scalar {15.0}}; + Content content = aggMethodSum.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + Scalar& scalar = std::get(content); + EXPECT_TRUE(std::holds_alternative(scalar)); + double result = std::get(scalar); + EXPECT_EQ(30.0, result); + } + + // Test aggregation of ScalarWithUnit values + { + AggMethodSum aggMethodSum; + std::vector contents = {ScalarWithUnit {5.0, "unit"}}; + Content content = aggMethodSum.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + const auto& [scalar, unit] = std::get(content); + EXPECT_EQ(5.0, std::get(scalar)); + EXPECT_EQ("unit", unit); + } + + // Test aggregation of mixed types (expect failure) + { + AggMethodSum aggMethodSum; + std::vector contents = {ScalarWithUnit {5.0, "unit"}, Scalar {5.0}}; + EXPECT_THROW(aggMethodSum.aggregate(contents), TelemetryException); + } + + // Test aggregation of incompatible types (expect failure) + { + AggMethodSum aggMethodSum; + std::vector contents = {Scalar {true}, Scalar {5.0}}; + EXPECT_THROW(aggMethodSum.aggregate(contents), TelemetryException); + + } + + // Test aggregation of incompatible scalar types (expect failure) + { + AggMethodSum aggMethodSum; + std::vector contents = {Scalar {uint64_t(20)}, Scalar {5.0}}; + EXPECT_THROW(aggMethodSum.aggregate(contents), TelemetryException); + } + + // Test aggregation of scalar and uint64_t types + { + AggMethodSum aggMethodSum; + std::vector contents = {Scalar {uint64_t(20)}, uint64_t {5}}; + Content content = aggMethodSum.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + const auto& scalar = std::get(content); + EXPECT_EQ(25, std::get(scalar)); + } + + // Test aggregation of dictionary values + { + AggMethodSum aggMethodSum; + aggMethodSum.setDictField("packets", "packetsSum"); + std::vector contents + = {Dict({{"packets", Scalar {uint64_t(1)}}}), + Dict({{"packets", Scalar {uint64_t(5)}}})}; + Content content = aggMethodSum.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + + const Dict& dict = std::get(content); + EXPECT_EQ(1, dict.size()); + + const Scalar& scalarValueSum = std::get(dict.at("packetsSum")); + EXPECT_EQ(uint64_t(6), std::get(scalarValueSum)); + } +} + +} // namespace telemetry diff --git a/src/telemetry/content.cpp b/src/telemetry/content.cpp index f67875b..f82d5b1 100644 --- a/src/telemetry/content.cpp +++ b/src/telemetry/content.cpp @@ -139,3 +139,7 @@ std::string contentToString(const Content& content) } } // namespace telemetry + +#ifdef TELEMETRY_ENABLE_TESTS +#include "tests/testContent.cpp" +#endif diff --git a/src/telemetry/directory.cpp b/src/telemetry/directory.cpp index f26f2b2..f3a63dc 100644 --- a/src/telemetry/directory.cpp +++ b/src/telemetry/directory.cpp @@ -149,3 +149,7 @@ void Directory::throwEntryAlreadyExists(std::string_view name) } } // namespace telemetry + +#ifdef TELEMETRY_ENABLE_TESTS +#include "tests/testDirectory.cpp" +#endif diff --git a/src/telemetry/file.cpp b/src/telemetry/file.cpp index 465b902..cda51e3 100644 --- a/src/telemetry/file.cpp +++ b/src/telemetry/file.cpp @@ -68,3 +68,7 @@ void File::disable() } } // namespace telemetry + +#ifdef TELEMETRY_ENABLE_TESTS +#include "tests/testFile.cpp" +#endif diff --git a/src/telemetry/holder.cpp b/src/telemetry/holder.cpp index e256bad..2011fdb 100644 --- a/src/telemetry/holder.cpp +++ b/src/telemetry/holder.cpp @@ -34,3 +34,7 @@ void Holder::disableFiles() } } // namespace telemetry + +#ifdef TELEMETRY_ENABLE_TESTS +#include "tests/testHolder.cpp" +#endif diff --git a/src/telemetry/tests/strUtils.hpp b/src/telemetry/tests/strUtils.hpp new file mode 100644 index 0000000..a1aeb94 --- /dev/null +++ b/src/telemetry/tests/strUtils.hpp @@ -0,0 +1,47 @@ +/** + * @file + * @author Pavel Siska + * @brief String utilities + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include +#include + +namespace strUtils { + +std::vector +splitViewByDelimiter(std::string_view str, const std::string& delimiter) +{ + std::vector result; + size_t pos = 0; + size_t found; + + while ((found = str.find(delimiter, pos)) != std::string_view::npos) { + result.push_back(str.substr(pos, found - pos)); + pos = found + delimiter.size(); + } + result.push_back(str.substr(pos)); + + return result; +} + +std::string_view trimView(std::string_view str) +{ + size_t start = 0; + while (start < str.length() && std::isspace(str[start])) { + start++; + } + + size_t end = str.length(); + while (end > start && std::isspace(str[end - 1])) { + end--; + } + + return str.substr(start, end - start); +} + +} // namespace strUtils diff --git a/src/telemetry/tests/testAggFile.cpp b/src/telemetry/tests/testAggFile.cpp new file mode 100644 index 0000000..716d18c --- /dev/null +++ b/src/telemetry/tests/testAggFile.cpp @@ -0,0 +1,285 @@ +/** + * @file + * @author Pavel Siska + * @brief Unit tests of telemetry::AggFile class + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include + +namespace telemetry { + +/** + * @test Test checking matching of files/directories by given regex. + */ +TEST(TelemetryAggFile, getMatchesInDirectory) +{ + auto root = Directory::create(); + + auto dir1 = root->addDir("dir1"); + auto dir2 = root->addDir("dir2"); + auto dir3 = root->addDir("dir3"); + + auto file1 = dir1->addFile("file1", {}); + auto file2 = dir1->addFile("file2", {}); + auto file3 = dir1->addFile("file3", {}); + + // match all files in dir1 + std::regex matchAllFilesRegex("file.*"); + auto matchesAllFiles = getMatchesInDirectory(matchAllFilesRegex, dir1); + EXPECT_EQ(3, matchesAllFiles.size()); + for (const auto& match : matchesAllFiles) { + const std::string matchName = match->getName(); + EXPECT_TRUE( + matchName == file1->getName() || matchName == file2->getName() + || matchName == file3->getName()); + } + + // match only file2 in dir1 + std::regex matchExactOneFileRegex("^file2$"); + auto matchExactFile = getMatchesInDirectory(matchExactOneFileRegex, dir1); + EXPECT_EQ(1, matchExactFile.size()); + for (const auto& match : matchExactFile) { + const std::string matchName = match->getName(); + EXPECT_TRUE(matchName == file2->getName()); + } + + // do not match any files in dir1 + std::regex matchNothingFileRegex("File.*"); + auto matchNothingFile = getMatchesInDirectory(matchNothingFileRegex, dir1); + EXPECT_EQ(0, matchNothingFile.size()); + + // match all dirs in root directory + std::regex matchAllDirsRegex(R"(dir\d+)"); + auto matchesAllDirs = getMatchesInDirectory(matchAllDirsRegex, root); + EXPECT_EQ(3, matchesAllDirs.size()); + for (const auto& match : matchesAllDirs) { + const std::string matchName = match->getName(); + EXPECT_TRUE( + matchName == dir1->getName() || matchName == dir2->getName() + || matchName == dir3->getName()); + } + + // match only dir2 in root directory + std::regex matchExactOneDirRegex("^dir2$"); + auto matchExactDir = getMatchesInDirectory(matchExactOneDirRegex, root); + EXPECT_EQ(1, matchExactDir.size()); + for (const auto& match : matchExactDir) { + const std::string matchName = match->getName(); + EXPECT_TRUE(matchName == dir2->getName()); + } + + // do not match any dirs in dir1 directory + auto matchNothingDir = getMatchesInDirectory(matchExactOneDirRegex, dir1); + EXPECT_EQ(0, matchNothingDir.size()); +} + +/** + * @test Test checking matching of files/directories by given regex. + */ +TEST(TelemetryAggFile, getFilesMatchingPattern) +{ + auto root = Directory::create(); + + auto dir1 = root->addDir("dir1"); + auto dir2 = root->addDir("dir2"); + auto dir3 = root->addDir("dir3"); + + auto file1 = dir1->addFile("file1", {}); + auto file2 = dir2->addFile("file2", {}); + auto file3 = dir3->addFile("file3", {}); + + // match all files in dir1 + const std::string matchAllFilesPattern(R"(dir\d+/file\d+)"); + auto matchesAllFiles = getFilesMatchingPattern(matchAllFilesPattern, root); + EXPECT_EQ(3, matchesAllFiles.size()); + for (const auto& match : matchesAllFiles) { + const std::string matchName = match->getName(); + EXPECT_TRUE( + matchName == file1->getName() || matchName == file2->getName() + || matchName == file3->getName()); + } + + // match only file2 in dir1 + const std::string matchExactOneFilePattern(R"(dir\d+/^file2$)"); + auto matchExactFile = getFilesMatchingPattern(matchExactOneFilePattern, root); + EXPECT_EQ(1, matchExactFile.size()); + for (const auto& match : matchExactFile) { + const std::string matchName = match->getName(); + EXPECT_TRUE(matchName == file2->getName()); + } + + // do not match any files in all dirs + const std::string matchNothingFilePattern(R"(.*/File.*)"); + auto matchNothingFile = getFilesMatchingPattern(matchNothingFilePattern, root); + EXPECT_EQ(0, matchNothingFile.size()); +} + +TEST(TelemetryAggFile, mergeContent) +{ + const std::string key2Value = "value"; + Dict dict1 {{"key2", Scalar {key2Value}}, {"key1", Scalar {uint64_t(1)}}}; + Dict dict2 { + {"key3", Scalar {int64_t(-1)}}, + {"key4", Scalar {true}}, + {"key5", Array {10.5, 11.5}}}; + + Content result = {}; + mergeContent(result, dict1); + + EXPECT_TRUE(std::holds_alternative(result)); + + Dict& resultDict = std::get(result); + EXPECT_EQ(2, resultDict.size()); + + auto iter = resultDict.cbegin(); + + { + const auto& [key, value] = *(iter++); + EXPECT_EQ("key1", key); + EXPECT_TRUE(std::holds_alternative(value)); + const auto& scalar = std::get(value); + EXPECT_TRUE(std::holds_alternative(scalar)); + EXPECT_EQ(uint64_t(1), std::get(scalar)); + } + + { + const auto& [key, value] = *(iter++); + EXPECT_EQ("key2", key); + EXPECT_TRUE(std::holds_alternative(value)); + const auto& scalar = std::get(value); + EXPECT_TRUE(std::holds_alternative(scalar)); + EXPECT_TRUE(key2Value == std::get(scalar)); + } + + mergeContent(result, dict2); + EXPECT_TRUE(std::holds_alternative(result)); + + resultDict = std::get(result); + EXPECT_EQ(5, resultDict.size()); + + iter = resultDict.cbegin(); + + { + const auto& [key, value] = *(iter++); + EXPECT_EQ("key1", key); + EXPECT_TRUE(std::holds_alternative(value)); + const auto& scalar = std::get(value); + EXPECT_TRUE(std::holds_alternative(scalar)); + EXPECT_EQ(uint64_t(1), std::get(scalar)); + } + + { + const auto& [key, value] = *(iter++); + EXPECT_EQ("key2", key); + EXPECT_TRUE(std::holds_alternative(value)); + const auto& scalar = std::get(value); + EXPECT_TRUE(std::holds_alternative(scalar)); + EXPECT_TRUE(key2Value == std::get(scalar)); + } + + { + const auto& [key, value] = *(iter++); + EXPECT_EQ("key3", key); + EXPECT_TRUE(std::holds_alternative(value)); + const auto& scalar = std::get(value); + EXPECT_TRUE(std::holds_alternative(scalar)); + EXPECT_EQ(int64_t(-1), std::get(scalar)); + } + + { + const auto& [key, value] = *(iter++); + EXPECT_EQ("key4", key); + EXPECT_TRUE(std::holds_alternative(value)); + const auto& scalar = std::get(value); + EXPECT_TRUE(std::holds_alternative(scalar)); + EXPECT_TRUE(true == std::get(scalar)); + } + + { + const auto& [key, value] = *(iter++); + EXPECT_EQ("key5", key); + EXPECT_TRUE(std::holds_alternative(value)); + const auto& array = std::get(value); + EXPECT_EQ(2, array.size()); + EXPECT_TRUE(std::holds_alternative(array[0])); + EXPECT_EQ(10.5, std::get(array[0])); + EXPECT_TRUE(std::holds_alternative(array[1])); + EXPECT_EQ(11.5, std::get(array[1])); + } + + { + Scalar scalar {uint64_t(1)}; + Content scalarContent = {}; + mergeContent(scalarContent, scalar); + + EXPECT_TRUE(std::holds_alternative(scalarContent)); + EXPECT_EQ(scalar, std::get(scalarContent)); + } +} + +TEST(TelemetryAggFile, validateAggOperations) +{ + validateAggOperations({}); + + AggOperation op1Dict {AggMethodType::SUM, "packets", "sumPackets"}; + AggOperation op2Dict {AggMethodType::AVG, "packets"}; + + validateAggOperations({op1Dict, op2Dict}); + + AggOperation op1Scalar {AggMethodType::SUM}; + + EXPECT_THROW(validateAggOperations({op1Dict, op1Scalar}), TelemetryException); + EXPECT_THROW(validateAggOperations({op1Scalar, op1Scalar}), TelemetryException); + + validateAggOperations({op1Scalar}); +} + +TEST(TelemetryAggFile, read) +{ + auto root = Directory::create(); + + auto dir1 = root->addDir("dir1"); + auto dir2 = root->addDir("dir2"); + auto dir3 = root->addDir("dir3"); + + FileOps ops1; + ops1.read = []() { return Dict({{"packets", Scalar {uint64_t(1)}}}); }; + FileOps ops2; + ops2.read = []() { return Dict({{"packets", Scalar {uint64_t(4)}}}); }; + FileOps ops3; + ops3.read = []() { return Dict({{"packets", Scalar {uint64_t(10)}}}); }; + + auto file1 = dir1->addFile("file1", ops1); + auto file2 = dir1->addFile("file2", ops2); + auto file3 = dir1->addFile("file3", ops3); + + AggOperation aggOp1 {AggMethodType::SUM, "packets", "sumPackets"}; + AggOperation aggOp2 {AggMethodType::AVG, "packets", "avgPackets"}; + AggOperation aggOp3 {AggMethodType::JOIN, "packets", "joinPackets"}; + + auto aggFile = root->addAggFile("aggFile", R"(dir\d+/file\d+)", {aggOp1, aggOp2, aggOp3}); + const auto content = aggFile->read(); + + EXPECT_TRUE(std::holds_alternative(content)); + + const Dict& dict = std::get(content); + EXPECT_EQ(3, dict.size()); + + const Scalar& scalarValueSum = std::get(dict.at("sumPackets")); + EXPECT_EQ(uint64_t(15), std::get(scalarValueSum)); + + const Scalar& scalarValueAvg = std::get(dict.at("avgPackets")); + EXPECT_EQ(5.00, std::get(scalarValueAvg)); + + const Array& ArrayValueJoin = std::get(dict.at("joinPackets")); + EXPECT_EQ(3, ArrayValueJoin.size()); + EXPECT_EQ(uint64_t(1), std::get(ArrayValueJoin[0])); + EXPECT_EQ(uint64_t(4), std::get(ArrayValueJoin[1])); + EXPECT_EQ(uint64_t(10), std::get(ArrayValueJoin[2])); +} + +} // namespace telemetry diff --git a/src/telemetry/tests/testContent.cpp b/src/telemetry/tests/testContent.cpp new file mode 100644 index 0000000..1e219ed --- /dev/null +++ b/src/telemetry/tests/testContent.cpp @@ -0,0 +1,226 @@ +/** + * @file + * @author Lukas Hutak + * @brief Unit tests of Telemetry::content + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "strUtils.hpp" + +#include + +namespace telemetry { + +/** + * @test Test conversion of a scalar to string. + */ +TEST(TelemetryContent, scalarToString) +{ + EXPECT_EQ("", scalarToString(Scalar {})); + + const bool boolTrue = true; + const bool boolFalse = false; + EXPECT_EQ("true", scalarToString(Scalar {boolTrue})); + EXPECT_EQ("false", scalarToString(Scalar {boolFalse})); + + const int64_t intZero = 0; + const int64_t intOnePlus = 1; + const int64_t intOneMinus = -1; + const int64_t intRandomPlus = 123456789; + const int64_t intRandomMinus = -123456789; + EXPECT_EQ("0", scalarToString(Scalar {intZero})); + EXPECT_EQ("1", scalarToString(Scalar {intOnePlus})); + EXPECT_EQ("-1", scalarToString(Scalar {intOneMinus})); + EXPECT_EQ("123456789", scalarToString(Scalar {intRandomPlus})); + EXPECT_EQ("-123456789", scalarToString(Scalar {intRandomMinus})); + + const uint64_t uintZero = 0; + const uint64_t uintOne = 1; + const uint64_t uintRandom = 123456789; + EXPECT_EQ("0", scalarToString(Scalar {uintZero})); + EXPECT_EQ("1", scalarToString(Scalar {uintOne})); + EXPECT_EQ("123456789", scalarToString(Scalar {uintRandom})); + + const double doubleZero = 0.0; + const double doubleOne = 1.0; + const double doubleRandomPlus = 123.456; + const double doubleRandomMinus = -123456789.123; + EXPECT_EQ("0.00", scalarToString(Scalar {doubleZero})); + EXPECT_EQ("1.00", scalarToString(Scalar {doubleOne})); + EXPECT_EQ("123.46", scalarToString(Scalar {doubleRandomPlus})); + EXPECT_EQ("-123456789.12", scalarToString(Scalar {doubleRandomMinus})); + + const std::string stringHello {"hello world!"}; + EXPECT_EQ("", scalarToString(Scalar {std::string("")})); + EXPECT_EQ("hello world!", scalarToString(Scalar {stringHello})); +} + +/** + * @test Test conversion of a scalar with a unit to string. + */ +TEST(TelemetryContent, ScalarWithUnitToString) +{ + EXPECT_EQ(" (unit)", scalarWithUnitToString(ScalarWithUnit {{}, "unit"})); + + const bool boolTrue = true; + const bool boolFalse = false; + EXPECT_EQ("true (unit)", scalarWithUnitToString(ScalarWithUnit {boolTrue, "unit"})); + EXPECT_EQ("false (unit)", scalarWithUnitToString(ScalarWithUnit {boolFalse, "unit"})); + + const int64_t intZero = 0; + const int64_t intOnePlus = 1; + const int64_t intOneMinus = -1; + const int64_t intRandomPlus = 123456789; + const int64_t intRandomMinus = -123456789; + EXPECT_EQ("0 (unit)", scalarWithUnitToString(ScalarWithUnit {intZero, "unit"})); + EXPECT_EQ("1 (unit)", scalarWithUnitToString(ScalarWithUnit {intOnePlus, "unit"})); + EXPECT_EQ("-1 (unit)", scalarWithUnitToString(ScalarWithUnit {intOneMinus, "unit"})); + EXPECT_EQ("123456789 (unit)", scalarWithUnitToString(ScalarWithUnit {intRandomPlus, "unit"})); + EXPECT_EQ("-123456789 (unit)", scalarWithUnitToString(ScalarWithUnit {intRandomMinus, "unit"})); + + const uint64_t uintZero = 0; + const uint64_t uintOne = 1; + const uint64_t uintRandom = 123456789; + EXPECT_EQ("0 (unit)", scalarWithUnitToString(ScalarWithUnit {uintZero, "unit"})); + EXPECT_EQ("1 (unit)", scalarWithUnitToString(ScalarWithUnit {uintOne, "unit"})); + EXPECT_EQ("123456789 (unit)", scalarWithUnitToString(ScalarWithUnit {uintRandom, "unit"})); + + const double doubleZero = 0.0; + const double doubleOne = 1.0; + const double doubleRandomPlus = 123.456; + const double doubleRandomMinus = -123456789.123456; + EXPECT_EQ("0.00 (unit)", scalarWithUnitToString(ScalarWithUnit {doubleZero, "unit"})); + EXPECT_EQ("1.00 (unit)", scalarWithUnitToString(ScalarWithUnit {doubleOne, "unit"})); + EXPECT_EQ("123.46 (unit)", scalarWithUnitToString(ScalarWithUnit {doubleRandomPlus, "unit"})); + EXPECT_EQ( + "-123456789.12 (unit)", + scalarWithUnitToString(ScalarWithUnit {doubleRandomMinus, "unit"})); + + const std::string stringHello {"hello world!"}; + EXPECT_EQ(" (unit)", scalarWithUnitToString(ScalarWithUnit {std::string(""), "unit"})); + EXPECT_EQ("hello world! (unit)", scalarWithUnitToString(ScalarWithUnit {stringHello, "unit"})); +} + +/** + * @test Test conversion of an array to string. + */ +TEST(TelemetryContent, arrayToString) +{ + EXPECT_EQ("[]", arrayToString(Array {})); + EXPECT_EQ("[true]", arrayToString(Array {true})); + + const uint64_t uintOne = 1; + const int64_t intMinusOne = -1; + EXPECT_EQ("[1, -1]", arrayToString(Array {uintOne, intMinusOne})); + + const int64_t intOne = 1; + const uint64_t uintTwo = 2; + const uint64_t uintThree = 3; + EXPECT_EQ("[1, 2, 3]", arrayToString(Array {intOne, uintTwo, uintThree})); + EXPECT_EQ("[eth0, eth1]", arrayToString(Array {std::string("eth0"), std::string("eth1")})); +} + +/** + * @test Test conversion of a dictionary to string. + */ +TEST(TelemetryContent, dictToString) +{ + const Dict dictEmpty {}; + EXPECT_EQ("", dictToString(dictEmpty)); + + const Dict dictSimple {{"key", Scalar {std::string("value")}}}; + EXPECT_EQ("key: value", dictToString(dictSimple)); + + const Dict dictComplex { + {"unknown", Scalar {}}, + {"boolean", Scalar {true}}, + {"int", Scalar {int64_t(-1)}}, + {"uint", Scalar {uint64_t(1)}}, + {"double", Scalar {123.456}}, + {"string", Scalar {std::string("eth")}}, + {"number and unit", ScalarWithUnit {uint64_t(123), "pkts"}}, + {"array", Array {int64_t(1), uint64_t(2), uint64_t(3)}}}; + const std::string complexStr = dictToString(dictComplex); + const auto complexLines = strUtils::splitViewByDelimiter(complexStr, "\n"); + auto iter = complexLines.cbegin(); + ASSERT_EQ(8, complexLines.size()); + + // Note: since results are stored in a map, they should be in alphabetical order + std::vector pieces; + + pieces = strUtils::splitViewByDelimiter(*(iter++), ":"); + ASSERT_EQ(2, pieces.size()); + EXPECT_EQ("array", strUtils::trimView(pieces[0])); + EXPECT_EQ("[1, 2, 3]", strUtils::trimView(pieces[1])); + + pieces = strUtils::splitViewByDelimiter(*(iter++), ":"); + ASSERT_EQ(2, pieces.size()); + EXPECT_EQ("boolean", strUtils::trimView(pieces[0])); + EXPECT_EQ("true", strUtils::trimView(pieces[1])); + + pieces = strUtils::splitViewByDelimiter(*(iter++), ":"); + ASSERT_EQ(2, pieces.size()); + EXPECT_EQ("double", strUtils::trimView(pieces[0])); + EXPECT_EQ("123.46", strUtils::trimView(pieces[1])); + + pieces = strUtils::splitViewByDelimiter(*(iter++), ":"); + ASSERT_EQ(2, pieces.size()); + EXPECT_EQ("int", strUtils::trimView(pieces[0])); + EXPECT_EQ("-1", strUtils::trimView(pieces[1])); + + pieces = strUtils::splitViewByDelimiter(*(iter++), ":"); + ASSERT_EQ(2, pieces.size()); + EXPECT_EQ("number and unit", strUtils::trimView(pieces[0])); + EXPECT_EQ("123 (pkts)", strUtils::trimView(pieces[1])); + + pieces = strUtils::splitViewByDelimiter(*(iter++), ":"); + ASSERT_EQ(2, pieces.size()); + EXPECT_EQ("string", strUtils::trimView(pieces[0])); + EXPECT_EQ("eth", strUtils::trimView(pieces[1])); + + pieces = strUtils::splitViewByDelimiter(*(iter++), ":"); + ASSERT_EQ(2, pieces.size()); + EXPECT_EQ("uint", strUtils::trimView(pieces[0])); + EXPECT_EQ("1", strUtils::trimView(pieces[1])); + + pieces = strUtils::splitViewByDelimiter(*(iter++), ":"); + ASSERT_EQ(2, pieces.size()); + EXPECT_EQ("unknown", strUtils::trimView(pieces[0])); + EXPECT_EQ("", strUtils::trimView(pieces[1])); + + EXPECT_EQ(iter, complexLines.cend()); +} + +/** + * @test Test conversion of a content to string. + */ +TEST(TelemetryContent, contentToString) +{ + const uint64_t uintZero = 0; + const bool boolTrue = true; + const Dict dictEmpty {}; + const Dict dictSimple {{"key", Scalar {std::string("value")}}}; + + // Scalar + EXPECT_EQ("0", contentToString(Scalar {uintZero})); + EXPECT_EQ("true", contentToString(Scalar {boolTrue})); + + // Scalar with unit + EXPECT_EQ("0 (pkts)", contentToString(ScalarWithUnit {uintZero, "pkts"})); + EXPECT_EQ("true (unit)", contentToString(ScalarWithUnit {boolTrue, "unit"})); + + // Array + EXPECT_EQ("[]", contentToString(Array {})); + + const int64_t intOne = 1; + const uint64_t uintTwo = 2; + const int64_t intThree = 3; + EXPECT_EQ("[1, 2, 3]", contentToString(Array {intOne, uintTwo, intThree})); + + // Dictionary + EXPECT_EQ("", contentToString(dictEmpty)); + EXPECT_EQ("key: value", contentToString(dictSimple)); +} + +} // namespace telemetry diff --git a/src/telemetry/tests/testDirectory.cpp b/src/telemetry/tests/testDirectory.cpp new file mode 100644 index 0000000..882b9b0 --- /dev/null +++ b/src/telemetry/tests/testDirectory.cpp @@ -0,0 +1,310 @@ +/** + * @file + * @author Lukas Hutak + * @brief Unit tests of telemetry::Dictionary class + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +namespace telemetry { + +/** + * @test Test creating root telemetry directory. + */ +TEST(TelemetryDirectory, create) +{ + auto root = Directory::create(); + EXPECT_EQ("", root->getName()); + EXPECT_EQ("/", root->getFullPath()); +} + +/** + * @test Test creating telemetry directories. + */ +TEST(TelemetryDirectory, addDir) +{ + auto root = Directory::create(); + + auto info = root->addDir("info"); + EXPECT_EQ("info", info->getName()); + EXPECT_EQ("/info", info->getFullPath()); + + auto app = info->addDir("app"); + EXPECT_EQ("app", app->getName()); + EXPECT_EQ("/info/app", app->getFullPath()); + + // Create entry that already exists (expect previously inserted entry) + auto info2 = root->addDir("info"); + EXPECT_EQ(info, info2); +} + +/** + * @test Test creating invalid telemetry directories. + */ +TEST(TelemetryDirectory, addDirInvalid) +{ + auto root = Directory::create(); + + // Create an entry without name + EXPECT_THROW((void) root->addDir(""), TelemetryException); + // Create an entry with invalid character + EXPECT_THROW((void) root->addDir("superCool!"), TelemetryException); + // Create a directory if there is already a file with the same name + auto version = root->addFile("version", {}); + EXPECT_THROW((void) root->addDir("version"), TelemetryException); +} + +/** + * @test Test creating telemetry directories that ceased to exist. + */ +TEST(TelemetryDirectory, addDirRemoved) +{ + auto root = Directory::create(); + + { + auto app = root->addDir("app"); + EXPECT_EQ(app, root->getEntry("app")); + EXPECT_EQ(nullptr, root->getEntry("port")); + + { + auto port = root->addDir("port"); + EXPECT_EQ(port, root->getEntry("port")); + EXPECT_EQ(app, root->getEntry("app")); + } + + EXPECT_EQ(app, root->getEntry("app")); + EXPECT_EQ(nullptr, root->getEntry("port")); + + auto port2 = root->addDir("port"); + EXPECT_EQ(port2, root->getEntry("port")); + } + + EXPECT_EQ(nullptr, root->getEntry("port")); + EXPECT_EQ(nullptr, root->getEntry("app")); + + auto app2 = root->addDir("app"); + auto port3 = root->addDir("port"); + + EXPECT_EQ(app2, root->getEntry("app")); + EXPECT_EQ(port3, root->getEntry("port")); +} + +/** + * @test Test creating telemetry files. + */ +TEST(TelemetryDirectory, addFile) +{ + auto root = Directory::create(); + + auto pidFile = root->addFile("pid", {}); + EXPECT_EQ("pid", pidFile->getName()); + EXPECT_EQ("/pid", pidFile->getFullPath()); + + auto cache = root->addDir("cache"); + auto cacheInfo = cache->addFile("info", {}); + EXPECT_EQ("info", cacheInfo->getName()); + EXPECT_EQ("/cache/info", cacheInfo->getFullPath()); +} + +/** + * @test Test creating invalid telemetry files. + */ +TEST(TelemetryDirectory, addFileInvalid) +{ + auto root = Directory::create(); + + // Create an entry without name + EXPECT_THROW((void) root->addFile("", {}), TelemetryException); + // Create an entry with invalid character + EXPECT_THROW((void) root->addFile("superCool!", {}), TelemetryException); + // Create an entry that already exists + auto info = root->addFile("info", {}); + EXPECT_THROW((void) root->addFile("info", {}), TelemetryException); + // Create a file if there is already a directory with the same name + auto version = root->addDir("version"); + EXPECT_THROW((void) root->addFile("version", {}), TelemetryException); +} + +/** + * @test Test creating telemetry files that ceased to exist. + */ +TEST(TelemetryDirectory, addFileRemoved) +{ + auto root = Directory::create(); + + { + auto app = root->addFile("app", {}); + EXPECT_EQ(app, root->getEntry("app")); + EXPECT_EQ(nullptr, root->getEntry("port")); + + { + auto port = root->addFile("port", {}); + EXPECT_EQ(port, root->getEntry("port")); + EXPECT_EQ(app, root->getEntry("app")); + } + + EXPECT_EQ(app, root->getEntry("app")); + EXPECT_EQ(nullptr, root->getEntry("port")); + + auto port2 = root->addFile("port", {}); + EXPECT_EQ(port2, root->getEntry("port")); + } + + EXPECT_EQ(nullptr, root->getEntry("port")); + EXPECT_EQ(nullptr, root->getEntry("app")); + + auto app2 = root->addFile("app", {}); + auto port3 = root->addFile("port", {}); + + EXPECT_EQ(app2, root->getEntry("app")); + EXPECT_EQ(port3, root->getEntry("port")); +} + +/** + * @test Test listing telemetry directory entries. + */ +TEST(TelemetryDirectory, listEntries) +{ + std::vector entries; + + // "root" directory + auto root = Directory::create(); + EXPECT_TRUE(root->listEntries().empty()); + + [[maybe_unused]] auto info = root->addFile("info", {}); + entries = root->listEntries(); + ASSERT_EQ(1, entries.size()); + EXPECT_EQ("info", entries[0]); + + auto ports = root->addDir("ports"); + entries = root->listEntries(); + ASSERT_EQ(2, entries.size()); + EXPECT_EQ("info", entries[0]); + EXPECT_EQ("ports", entries[1]); + + // "ports" subdirectory + EXPECT_TRUE(ports->listEntries().empty()); + + [[maybe_unused]] auto eth0 = ports->addDir("eth0"); + [[maybe_unused]] auto eth1 = ports->addDir("eth1"); + [[maybe_unused]] auto eth2 = ports->addDir("eth2"); + [[maybe_unused]] auto summary = ports->addFile("summary", {}); + entries = ports->listEntries(); + ASSERT_EQ(4, entries.size()); + EXPECT_EQ("eth0", entries[0]); + EXPECT_EQ("eth1", entries[1]); + EXPECT_EQ("eth2", entries[2]); + EXPECT_EQ("summary", entries[3]); + + // Check that it didn't have impact on the root directory + entries = root->listEntries(); + ASSERT_EQ(2, entries.size()); + EXPECT_EQ("info", entries[0]); + EXPECT_EQ("ports", entries[1]); +} + +/** + * @test Test listing telemetry directory entries that doesn't exist anymore. + */ +TEST(TelemetryDirectory, listEntriesRemoved) +{ + std::vector entries; + auto root = Directory::create(); + + entries = root->listEntries(); + EXPECT_TRUE(entries.empty()); + + { + [[maybe_unused]] auto app = root->addFile("app", {}); + entries = root->listEntries(); + ASSERT_EQ(1, entries.size()); + EXPECT_EQ("app", entries[0]); + + { + [[maybe_unused]] auto ports = root->addDir("ports"); + entries = root->listEntries(); + ASSERT_EQ(2, entries.size()); + EXPECT_EQ("app", entries[0]); + EXPECT_EQ("ports", entries[1]); + + { + [[maybe_unused]] auto info = root->addFile("info", {}); + entries = root->listEntries(); + ASSERT_EQ(3, entries.size()); + EXPECT_EQ("app", entries[0]); + EXPECT_EQ("info", entries[1]); + EXPECT_EQ("ports", entries[2]); + } + + entries = root->listEntries(); + ASSERT_EQ(2, entries.size()); + EXPECT_EQ("app", entries[0]); + EXPECT_EQ("ports", entries[1]); + } + + entries = root->listEntries(); + ASSERT_EQ(1, entries.size()); + EXPECT_EQ("app", entries[0]); + } + + entries = root->listEntries(); + EXPECT_TRUE(entries.empty()); +} + +/** + * @test Test getting telemetry directory entries. + */ +TEST(TelemetryDirectory, getEntry) +{ + auto root = Directory::create(); + EXPECT_EQ(nullptr, root->getEntry("info")); + EXPECT_EQ(nullptr, root->getEntry("version")); + + auto info = root->addDir("info"); + EXPECT_EQ(info, root->getEntry("info")); + + auto version = root->addFile("version", {}); + EXPECT_EQ(version, root->getEntry("version")); +} + +/** + * @test Test getting telemetry entries that doesn't exists anymore. + */ +TEST(TelemetryDirectory, getEntryRemoved) +{ + auto root = Directory::create(); + + { + auto app = root->addFile("app", {}); + EXPECT_EQ(app, root->getEntry("app")); + + { + auto ports = root->addDir("ports"); + EXPECT_EQ(ports, root->getEntry("ports")); + EXPECT_EQ(app, root->getEntry("app")); + + { + auto info = root->addFile("info", {}); + EXPECT_EQ(info, root->getEntry("info")); + EXPECT_EQ(ports, root->getEntry("ports")); + EXPECT_EQ(app, root->getEntry("app")); + } + + EXPECT_EQ(nullptr, root->getEntry("info")); + EXPECT_EQ(ports, root->getEntry("ports")); + EXPECT_EQ(app, root->getEntry("app")); + } + + EXPECT_EQ(nullptr, root->getEntry("info")); + EXPECT_EQ(nullptr, root->getEntry("ports")); + EXPECT_EQ(app, root->getEntry("app")); + } + + EXPECT_EQ(nullptr, root->getEntry("info")); + EXPECT_EQ(nullptr, root->getEntry("ports")); + EXPECT_EQ(nullptr, root->getEntry("app")); +} + +} // namespace telemetry diff --git a/src/telemetry/tests/testFile.cpp b/src/telemetry/tests/testFile.cpp new file mode 100644 index 0000000..d6be79e --- /dev/null +++ b/src/telemetry/tests/testFile.cpp @@ -0,0 +1,111 @@ +/** + * @file + * @author Lukas Hutak + * @brief Unit tests of telemetry::File class + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include + +namespace telemetry { + +/** + * @test Test checking availability of read operation. + */ +TEST(TelemetryFile, hasRead) +{ + auto root = Directory::create(); + + auto noOpsFile = root->addFile("no", {}); + EXPECT_FALSE(noOpsFile->hasRead()); + + FileOps ops {}; + ops.read = []() { return Scalar {}; }; + auto opsFile = root->addFile("yes", ops); + EXPECT_TRUE(opsFile->hasRead()); +} + +/** + * @test Test checking availability of clear operation. + */ +TEST(TelemetryFile, hasClear) +{ + auto root = Directory::create(); + + auto noOpsFile = root->addFile("no", {}); + EXPECT_FALSE(noOpsFile->hasClear()); + + FileOps ops {}; + ops.clear = []() {}; + auto opsFile = root->addFile("yes", ops); + EXPECT_TRUE(opsFile->hasClear()); +} + +/** + * @test Test executing read operation. + */ +TEST(TelemetryFile, read) +{ + auto root = Directory::create(); + + auto noOpsFile = root->addFile("no", {}); + EXPECT_THROW(noOpsFile->read(), TelemetryException); + + FileOps ops {}; + ops.read = []() { return Scalar {"hello"}; }; + auto opsFile = root->addFile("yes", ops); + EXPECT_EQ(Content {Scalar {"hello"}}, opsFile->read()); +} + +/** + * @test Test executing read operation. + */ +TEST(TelemetryFile, clear) +{ + auto root = Directory::create(); + int valueToClear = 1; + + auto noOpsFile = root->addFile("no", {}); + EXPECT_THROW(noOpsFile->clear(), TelemetryException); + + FileOps ops {}; + ops.clear = [&]() { valueToClear = 0; }; + auto opsFile = root->addFile("yes", ops); + opsFile->clear(); + EXPECT_EQ(0, valueToClear); +} + +/** + * @test Test that disabling of all operations works as expected. + */ +TEST(TelemetryFile, disable) +{ + int64_t counter = 0; + FileOps ops {}; + ops.read = [&]() { return Scalar {counter++}; }; + ops.clear = [&]() { counter = 0; }; + + auto root = Directory::create(); + auto file = root->addFile("file", ops); + + EXPECT_TRUE(file->hasRead()); + EXPECT_TRUE(file->hasClear()); + + EXPECT_EQ(Content {Scalar {int64_t {0}}}, file->read()); + EXPECT_EQ(Content {Scalar {int64_t {1}}}, file->read()); + EXPECT_EQ(Content {Scalar {int64_t {2}}}, file->read()); + EXPECT_NO_THROW(file->clear()); + EXPECT_EQ(Content {Scalar {int64_t {0}}}, file->read()); + + file->disable(); + + EXPECT_FALSE(file->hasRead()); + EXPECT_FALSE(file->hasClear()); + EXPECT_THROW(file->read(), TelemetryException); + EXPECT_THROW(file->clear(), TelemetryException); +} + +} // namespace telemetry diff --git a/src/telemetry/tests/testHolder.cpp b/src/telemetry/tests/testHolder.cpp new file mode 100644 index 0000000..6261ef5 --- /dev/null +++ b/src/telemetry/tests/testHolder.cpp @@ -0,0 +1,76 @@ +/** + * @file + * @author Lukas Hutak + * @brief Unit tests of telemetry::Holder class + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include + +namespace telemetry { + +/** + * @test Test checking availability of read operation. + */ +TEST(TelemetryHolder, create) +{ + Holder holder; +} + +/** + * @test Test adding various entries to the holder. + */ +TEST(TelemetryHolder, add) +{ + auto root = Directory::create(); + + { + Holder holder; + + { + auto file = root->addFile("file", {}); + auto dir = root->addDir("dir"); + + holder.add(file); + holder.add(dir); + } + + // Check if entries still exists + EXPECT_NE(nullptr, root->getEntry("file")); + EXPECT_NE(nullptr, root->getEntry("dir")); + } + + // After holder destruction, entries should be gone... + EXPECT_EQ(nullptr, root->getEntry("file")); + EXPECT_EQ(nullptr, root->getEntry("dir")); +} + +/** + * @test Test disabling callbacks of held files. + */ +TEST(TelemetryHolder, disableFiles) +{ + auto root = Directory::create(); + FileOps ops = {}; + ops.read = []() { return Scalar {"value"}; }; + ops.clear = []() {}; + + Holder holder; + + auto dir = root->addDir("dir"); + auto file = root->addFile("file", ops); + holder.add(file); + + EXPECT_TRUE(file->hasRead()); + EXPECT_TRUE(file->hasClear()); + + holder.disableFiles(); + + EXPECT_FALSE(file->hasRead()); + EXPECT_FALSE(file->hasClear()); +} + +} // namespace telemetry diff --git a/src/telemetry/tests/testUtility.cpp b/src/telemetry/tests/testUtility.cpp new file mode 100644 index 0000000..583a3d2 --- /dev/null +++ b/src/telemetry/tests/testUtility.cpp @@ -0,0 +1,128 @@ +/** + * @file + * @author Pavel Siska + * @brief Unit tests of telemetry::utils functions + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include + +namespace telemetry { + +/** + * @test Test checking the parsing of a path + */ +TEST(TelemetryUtility, parsePath) +{ + std::vector path; + + path = utils::parsePath("/dir/file"); + EXPECT_EQ(path.size(), 2); + EXPECT_EQ(path[0], "dir"); + EXPECT_EQ(path[1], "file"); + + path = utils::parsePath("dir/////file"); + EXPECT_EQ(path.size(), 2); + EXPECT_EQ(path[0], "dir"); + EXPECT_EQ(path[1], "file"); + + path = utils::parsePath("dir/"); + EXPECT_EQ(path.size(), 1); + EXPECT_EQ(path[0], "dir"); + + path = utils::parsePath("dir/subDir/subDir/subDir/file"); + EXPECT_EQ(path.size(), 5); + EXPECT_EQ(path[0], "dir"); + EXPECT_EQ(path[1], "subDir"); + EXPECT_EQ(path[2], "subDir"); + EXPECT_EQ(path[3], "subDir"); + EXPECT_EQ(path[4], "file"); + + path = utils::parsePath(""); + EXPECT_EQ(path.size(), 0); +} + +/** + * @test Test checking the getting of a node from a path + */ +TEST(TelemetryUtility, getNodeFromPath) +{ + auto root = Directory::create(); + auto dir = root->addDir("dir"); + auto file = dir->addFile("file", {}); + + std::shared_ptr node; + + node = utils::getNodeFromPath(root, "/dir/file"); + EXPECT_TRUE(node != nullptr); + EXPECT_EQ(node, file); + + node = utils::getNodeFromPath(root, "dir/file"); + EXPECT_TRUE(node != nullptr); + EXPECT_EQ(node, file); + + node = utils::getNodeFromPath(root, "dir"); + EXPECT_TRUE(node != nullptr); + EXPECT_EQ(node, dir); + + node = utils::getNodeFromPath(root, "dir/"); + EXPECT_TRUE(node != nullptr); + EXPECT_EQ(node, dir); + + node = utils::getNodeFromPath(root, "/"); + EXPECT_TRUE(node != nullptr); + EXPECT_EQ(node, root); + + node = utils::getNodeFromPath(root, ""); + EXPECT_TRUE(node == nullptr); + + node = utils::getNodeFromPath(root, "nonexistent"); + EXPECT_TRUE(node == nullptr); +} + +/** + * @test Test checking if the node is a file + */ +TEST(TelemetryUtility, isFile) +{ + auto root = Directory::create(); + auto dir = root->addDir("dir"); + auto file = root->addFile("file", {}); + + EXPECT_FALSE(utils::isFile(dir)); + EXPECT_FALSE(utils::isFile(root)); + + EXPECT_TRUE(utils::isFile(file)); +} + +/** + * @test Test checking if the node is a directory + */ +TEST(TelemetryUtility, isDirectory) +{ + auto root = Directory::create(); + auto dir = root->addDir("dir"); + auto file = root->addFile("file", {}); + + EXPECT_TRUE(utils::isDirectory(dir)); + EXPECT_TRUE(utils::isDirectory(root)); + + EXPECT_FALSE(utils::isDirectory(file)); +} + +/** + * @test Test checking if the directory is root + */ +TEST(TelemetryUtility, isRootDirectory) +{ + auto root = Directory::create(); + auto dir = root->addDir("dir"); + + EXPECT_FALSE(utils::isRootDirectory(dir->getFullPath())); + EXPECT_TRUE(utils::isRootDirectory(root->getFullPath())); +} + +} // namespace telemetry diff --git a/src/telemetry/utility.cpp b/src/telemetry/utility.cpp index 3ffb8a6..35affbd 100644 --- a/src/telemetry/utility.cpp +++ b/src/telemetry/utility.cpp @@ -78,3 +78,7 @@ bool isRootDirectory(const std::string& path) noexcept } } // namespace telemetry::utils + +#ifdef TELEMETRY_ENABLE_TESTS +#include "tests/testUtility.cpp" +#endif