Skip to content

Commit

Permalink
Merge pull request #164 from AsherGlick/string_hierarchy
Browse files Browse the repository at this point in the history
Adding a new String Hierarchy Class (And Unit Tests)
  • Loading branch information
AsherGlick authored Sep 27, 2023
2 parents a0d36e9 + 847a1fc commit 36489a0
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ jobs:
- name: Install protoc
run: sudo apt-get install protobuf-compiler

- name: Install gtest
run: sudo apt-get install libgtest-dev

- name: Build xml_converter
run: |
Expand Down
18 changes: 18 additions & 0 deletions xml_converter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,21 @@ if(MSVC)
else()
target_compile_options(${TARGET_NAME} PRIVATE -Wall -Wextra -Wpedantic)
endif()

### TESTS ######################################################################
# Enable testing using CMake's built-in functionality
enable_testing()

set(TEST_TARGET_NAME xml_converter_tests)

file(GLOB_RECURSE TEST_SOURCES "src/*.cpp")
list(REMOVE_ITEM TEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/xml_converter.cpp")

find_package(GTest REQUIRED)

add_executable(${TEST_TARGET_NAME} tests/test_string_hierarchy.cpp ${TEST_SOURCES} ${PROTO_SRC})

target_link_libraries(${TEST_TARGET_NAME} GTest::GTest GTest::Main)
target_link_libraries(${TEST_TARGET_NAME} ${Protobuf_LIBRARIES})

gtest_discover_tests(${TEST_TARGET_NAME})
83 changes: 83 additions & 0 deletions xml_converter/src/string_hierarchy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include "string_hierarchy.hpp"

#include <iostream>

////////////////////////////////////////////////////////////////////////////////
// in_hierarchy
//
// Returns if the given path is in the hierarchy or not
////////////////////////////////////////////////////////////////////////////////
bool StringHierarchy::in_hierarchy(
const std::vector<std::string> &path) {
return this->_in_hierarchy(path, 0);
}

////////////////////////////////////////////////////////////////////////////////
// _in_hirearchy
//
// Recursive helper function to traverse the tree and determine if a path is
// or is not in the current hierarchy.
////////////////////////////////////////////////////////////////////////////////
bool StringHierarchy::_in_hierarchy(
const std::vector<std::string> &path,
const size_t continue_index) {
// If all children of this hierarchy node are included then this path exists.
if (this->all_children_included) {
return true;
}

// If this is the end of the path then the path exists.
if (continue_index >= path.size()) {
return true;
}

auto iterator = this->children.find(path[continue_index]);
if (iterator == this->children.end()) {
return false;
}

return iterator->second._in_hierarchy(path, continue_index + 1);
}

////////////////////////////////////////////////////////////////////////////////
// add_path
//
// Adds a new path to the StringHierarchy.
////////////////////////////////////////////////////////////////////////////////
void StringHierarchy::add_path(
const std::vector<std::string> &path,
const bool include_all_chidren) {
return this->_add_path(
path,
include_all_chidren,
0);
}

////////////////////////////////////////////////////////////////////////////////
// _add_path
//
// Recursive helper function to add new paths into the string hierarchy.
////////////////////////////////////////////////////////////////////////////////
void StringHierarchy::_add_path(
const std::vector<std::string> &path,
const bool include_all_chidren,
const size_t continue_index) {
// If all children are already included no need to specify any more children.
if (this->all_children_included) {
return;
}

// End of the path, if all children are included clear out more specific children.
if (continue_index >= path.size()) {
if (include_all_chidren) {
this->all_children_included = true;
this->children.clear();
}
return;
}

this->children[path[continue_index]]._add_path(
path,
include_all_chidren,
continue_index + 1);
}
28 changes: 28 additions & 0 deletions xml_converter/src/string_hierarchy.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <map>
#include <string>
#include <vector>

class StringHierarchy {
public:
bool in_hierarchy(
const std::vector<std::string> &path);

void add_path(
const std::vector<std::string> &path,
const bool include_all_chidren);

private:
bool _in_hierarchy(
const std::vector<std::string> &path,
const size_t continue_index);

void _add_path(
const std::vector<std::string> &path,
const bool include_all_chidren,
const size_t continue_index);

std::map<std::string, StringHierarchy> children;
bool all_children_included = false;
};
73 changes: 73 additions & 0 deletions xml_converter/tests/test_string_hierarchy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "../src/string_hierarchy.hpp"
#include <gtest/gtest.h>


class StringHierarchyTest : public ::testing::Test {
protected:
StringHierarchy string_hierarchy;

void SetUp() override {
}
void TearDown() override {
}
};


TEST_F(StringHierarchyTest, BasicPath) {
string_hierarchy.add_path({"root", "child1", "child2"}, false);
EXPECT_TRUE(string_hierarchy.in_hierarchy({"root", "child1", "child2"}));
}

TEST_F(StringHierarchyTest, ParentPath) {
string_hierarchy.add_path({"root", "child1", "child2"}, false);
EXPECT_TRUE(string_hierarchy.in_hierarchy({"root", "child1"}));
EXPECT_TRUE(string_hierarchy.in_hierarchy({"root"}));
}

TEST_F(StringHierarchyTest, InvalidAdjacentNode) {
string_hierarchy.add_path({"root", "child1", "child2"}, false);
EXPECT_FALSE(string_hierarchy.in_hierarchy({"root", "child1", "invalid_child"}));
}

TEST_F(StringHierarchyTest, InvalidRoot) {
string_hierarchy.add_path({"root", "child1", "child2"}, false);
EXPECT_FALSE(string_hierarchy.in_hierarchy({"badroot"}));
}

TEST_F(StringHierarchyTest, NonExistantDepthNode) {
string_hierarchy.add_path({"root", "child1", "child2"}, false);
EXPECT_FALSE(string_hierarchy.in_hierarchy({"root", "child1", "child2", "child3"}));
}

TEST_F(StringHierarchyTest, AllExistDepthNode) {
string_hierarchy.add_path({"root", "child1", "child2"}, true);
EXPECT_TRUE(string_hierarchy.in_hierarchy({"root", "child1", "child2", "child3"}));
EXPECT_TRUE(string_hierarchy.in_hierarchy({"root", "child1", "child2"}));
EXPECT_FALSE(string_hierarchy.in_hierarchy({"root", "child1", "child2b"}));
}

TEST_F(StringHierarchyTest, DoubleNode) {
string_hierarchy.add_path({"root", "child1", "child2"}, true);
string_hierarchy.add_path({"root", "neighbor1", "child2"}, false);

EXPECT_TRUE(string_hierarchy.in_hierarchy({"root", "neighbor1", "child2"}));
EXPECT_TRUE(string_hierarchy.in_hierarchy({"root", "child1", "child2"}));
EXPECT_FALSE(string_hierarchy.in_hierarchy({"root", "child2"}));
}

TEST_F(StringHierarchyTest, OverwriteAllChildrenRoot) {
string_hierarchy.add_path({"root", "child1", "child2"}, false);
string_hierarchy.add_path({"root"}, true);
EXPECT_TRUE(string_hierarchy.in_hierarchy({"root", "child1", "child2", "child3"}));
EXPECT_TRUE(string_hierarchy.in_hierarchy({"root", "somethingrandom"}));
}

TEST_F(StringHierarchyTest, AllowAll) {
string_hierarchy.add_path({}, true);
EXPECT_TRUE(string_hierarchy.in_hierarchy({"literally", "anything"}));
}

int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

0 comments on commit 36489a0

Please sign in to comment.