diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index cdf1fdc..876cae9 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -18,5 +18,5 @@ jobs: - name: Build and test run: | - chmod +x build.sh - ./build.sh + chmod +x run_build.sh + ./run_build.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 93f524e..a57d44c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,12 @@ cmake_minimum_required(VERSION 3.10) -project(MyProject LANGUAGES CXX) +project(ordered_map LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Add source files -add_library(myproject src/doubly_linked_list.cpp) -target_include_directories(myproject PUBLIC include) +add_library(ordered_map src/doubly_linked_list.cpp) +target_include_directories(ordered_map PUBLIC include) # Enable testing enable_testing() @@ -21,8 +21,22 @@ FetchContent_Declare( FetchContent_MakeAvailable(catch2) # Add test executable -add_executable(tests tests/test_doubly_linked_list.cpp) -target_link_libraries(tests PRIVATE myproject Catch2::Catch2WithMain) +add_executable( + tests + tests/doubly_linked_list/size_tests.cpp + tests/doubly_linked_list/front_and_back.cpp + tests/doubly_linked_list/iterator_tests.cpp + tests/doubly_linked_list/pop_back_tests.cpp + tests/doubly_linked_list/pop_front_tests.cpp + tests/doubly_linked_list/push_back_tests.cpp + tests/doubly_linked_list/push_front_tests.cpp + tests/doubly_linked_list/clear_tests.cpp + tests/ordered_map/size_tests.cpp + tests/ordered_map/insertion_tests.cpp + tests/ordered_map/lookup_tests.cpp + tests/ordered_map/iterator_tests.cpp +) +target_link_libraries(tests PRIVATE ordered_map Catch2::Catch2WithMain) # Register tests include(CTest) diff --git a/README.md b/README.md index e4b0019..ed773d1 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ A C++ project implementing an ordered map using a doubly-linked list to maintain element insertion order, built with CMake, tested with Catch2, and formatted with Clang-Format. ## Table of Contents + - [Prerequisites](#prerequisites) - [Project Structure](#project-structure) - [Setting Up VS Code (or Cursor)](#setting-up-vs-code-or-cursor) @@ -11,7 +12,7 @@ A C++ project implementing an ordered map using a doubly-linked list to maintain - [GitHub Actions CI](#github-actions-ci) - [Adding a New Test](#adding-a-new-test) - [Troubleshooting](#troubleshooting) -- [Example Test File](#example-test-file) +- [Project Status](#project-status) ## Prerequisites @@ -22,6 +23,7 @@ A C++ project implementing an ordered map using a doubly-linked list to maintain - **VS Code** or **Cursor**: Code editor with C++ support Verify installations: + ```bash cmake --version git --version @@ -40,22 +42,30 @@ ordered_map/ ├── build.sh # Script to build and test ├── .clang-format # Code formatting rules ├── include/ -│ └── ordered_map.hpp # Ordered map interface +│ ├── doubly_linked_list.hpp # Doubly-linked list implementation +│ └── ordered_map.hpp # (Future) Ordered map interface ├── src/ -│ └── doubly_linked_list.cpp # Doubly-linked list implementation ├── tests/ -│ └── test_ordered_map.cpp # Catch2 test files +│ └── doubly_linked_list/ # Test files for doubly linked list +│ ├── front_and_back.cpp # Tests for front/back operations +│ ├── iterator_tests.cpp # Tests for iterator functionality +│ ├── pop_back_tests.cpp # Tests for pop_back operations +│ ├── pop_front_tests.cpp # Tests for pop_front operations +│ ├── push_back_tests.cpp # Tests for push_back operations +│ └── push_front_tests.cpp # Tests for push_front operations ``` ## Setting Up VS Code (or Cursor) 1. **Install Extensions**: + - In VS Code/Cursor, go to Extensions (Ctrl+Shift+X). - Install: - **C/C++** (Microsoft): For IntelliSense and debugging. - **CMake Tools** (Microsoft): For CMake integration. 2. **Configure IntelliSense**: + - Create `.vscode/settings.json` (or `.cursor/settings.json`): ```json { @@ -85,13 +95,16 @@ ordered_map/ ## Building and Running Tests 1. **Build and Test**: + ```bash chmod +x build.sh ./build.sh ``` + - Formats code, builds with CMake/make, and runs Catch2 tests. 2. **Output**: + - Successful tests show: ``` All tests passed (X assertions in Y test cases) @@ -100,7 +113,7 @@ ordered_map/ 3. **Run Specific Tests**: ```bash cd build - ./tests --tags [canary] + ./tests --tags [tag] ``` ## GitHub Actions CI @@ -111,22 +124,30 @@ ordered_map/ 2. Ensure `.github/workflows/cmake.yml` exists. 3. CI runs `build.sh` on `ubuntu-latest`, formatting, building, and testing. - **View Results**: - - Check the “Actions” tab on GitHub for CI logs. + - Check the "Actions" tab on GitHub for CI logs. ## Adding a New Test -1. **Edit `tests/test_ordered_map.cpp`**: - - Add a `TEST_CASE` for the ordered map or doubly-linked list. Example: - ```cpp - TEST_CASE("Insert maintains order", "[ordered_map]") { - OrderedMap map; - map.insert(1, 10); - map.insert(2, 20); - REQUIRE(map.size() == 2); - } - ``` +1. **Choose the appropriate test file** in `tests/doubly_linked_list/` based on the functionality being tested: -2. **Rebuild and Test**: + - `front_and_back.cpp` for front/back operations + - `iterator_tests.cpp` for iterator functionality + - `pop_back_tests.cpp` for pop_back operations + - `pop_front_tests.cpp` for pop_front operations + - `push_back_tests.cpp` for push_back operations + - `push_front_tests.cpp` for push_front operations + +2. **Add your test case**: + + ```cpp + TEST_CASE("Your test description", "[tag]") { + DoublyLinkedList list; + // Your test code here + REQUIRE(/* your assertion */); + } + ``` + +3. **Rebuild and Test**: ```bash ./build.sh ``` @@ -140,27 +161,32 @@ ordered_map/ - Clear build: `rm -rf build && ./build.sh`. - Check prerequisites. - **Test Failures**: - - Inspect assertions in `test_ordered_map.cpp`. + - Inspect assertions in the relevant test file. - Run specific tests: `./build/tests --tags [tag]`. - **GitHub Actions Failures**: - - Check logs in the “Actions” tab on GitHub. + - Check logs in the "Actions" tab on GitHub. - Ensure `build.sh` and dependencies are compatible with `ubuntu-latest`. -## Example Test File +## Project Status -`tests/test_ordered_map.cpp`: -```cpp -#include -#include "ordered_map.hpp" +The project is currently in development, with the following components: -TEST_CASE("Canary test", "[canary]") { - REQUIRE(true); -} +1. ✅ Doubly Linked List Implementation -TEST_CASE("Insert maintains order", "[ordered_map]") { - OrderedMap map; - map.insert(1, 10); - map.insert(2, 20); - REQUIRE(map.size() == 2); -} -``` + - Basic operations (push_back, pop_back, push_front, pop_front) + - Memory management using smart pointers + - Comprehensive test coverage organized by functionality + - Iterator support (basic implementation) + +2. 🔄 Ordered Map Implementation (Coming Soon) + - Will use the doubly linked list as its underlying data structure + - Will maintain insertion order while providing map-like functionality + +### Checklist for future improvements + +- [ ] DoublyLinkedList: Add tests for copying & moving +- [ ] DoublyLinkedList: Add `erase` method to erase elements given their iterator + +- [ ] DoublyLinkedList: Add pre and post decrement operators +- [ ] DoublyLinkedList: Test front(), back(), insertion and deletion functions for copying behavior +- [ ] DoublyLinkedList: Test front(), back(), insertion and deletion functions for destructor behavior diff --git a/include/doubly_linked_list.hpp b/include/doubly_linked_list.hpp index e69de29..20e4bc5 100644 --- a/include/doubly_linked_list.hpp +++ b/include/doubly_linked_list.hpp @@ -0,0 +1,170 @@ +#pragma once +#include +#include + + +template +struct Node { + T value; + std::unique_ptr next; + Node* prev; + + Node(const T& a_value, Node* a_prev, std::unique_ptr a_next) + : value(a_value), prev(a_prev), next(std::move(a_next)) {} +}; + +template +class DoublyLinkedList { +private: + std::unique_ptr> head; + Node* tail; + int _size; + +public: + DoublyLinkedList() : head(nullptr), tail(nullptr), _size(0) {} + ~DoublyLinkedList() { + clear(); + } + + int size() const { + return _size; + } + + const T& front() const { + if (!head) { + throw std::out_of_range("List is empty"); + } + + return head->value; + } + + const T& back() const { + if (!tail) { + throw std::out_of_range("List is empty"); + } + return tail->value; + } + + void push_back(const T& value) { + if (!head) { + head = std::make_unique>(value, nullptr, nullptr); + tail = head.get(); + } else { + tail->next = std::make_unique>(value, tail, nullptr); + tail = tail->next.get(); + } + _size++; + } + + T pop_back() { + if (!head) { + throw std::out_of_range("List is empty"); + } + + T popped_value = tail->value; + + if (tail == head.get()) { + head.reset(); + tail = nullptr; + } else { + Node* prev_node = tail->prev; + prev_node->next.reset(); + tail = prev_node; + } + + _size--; + return popped_value; + } + + void push_front(const T& value) { + if (!head) { + head = std::make_unique>(value, nullptr, nullptr); + tail = head.get(); + } else { + auto new_node = std::make_unique>(value, nullptr, std::move(head)); + new_node->next->prev = new_node.get(); + head = std::move(new_node); + } + _size++; + } + + T pop_front() { + if (!head) { + throw std::out_of_range("List is empty"); + } + + T popped_value = head->value; + head = std::move(head->next); + + if (!head) + tail = nullptr; + + _size--; + return popped_value; + } + + void clear() { + while(size() > 0) { + pop_back(); + } + } + + bool empty() const { + return _size == 0; + } + + class Iterator { + Node* current_node_ptr; + + Iterator(Node* a_current) : current_node_ptr(a_current) {} + + friend class DoublyLinkedList; + + public: + Iterator() : current_node_ptr(nullptr) {} + + bool operator==(const Iterator& other) const { + return current_node_ptr == other.current_node_ptr; + } + + bool operator!=(const Iterator& other) const { + return current_node_ptr != other.current_node_ptr; + } + + T& operator*() const { + return current_node_ptr->value; + } + + Iterator& operator++() { + if (current_node_ptr) { + current_node_ptr = current_node_ptr->next.get(); + } + return *this; + } + + Iterator operator++(int) { + Iterator initial_state_copy = *this; + ++(*this); + return initial_state_copy; + } + + Iterator& operator--() { + if (current_node_ptr) { + current_node_ptr = current_node_ptr->prev; + } + return *this; + } + }; + + Iterator begin() { + return Iterator(head.get()); + } + + Iterator end() { + return Iterator(nullptr); + } + + Iterator back_iterator() { + return Iterator(tail); + } +}; diff --git a/include/ordered_map.hpp b/include/ordered_map.hpp new file mode 100644 index 0000000..1fe5686 --- /dev/null +++ b/include/ordered_map.hpp @@ -0,0 +1,99 @@ +#pragma once +#include "doubly_linked_list.hpp" +#include +#include + + +template +class OrderedMap { +private: + + DoublyLinkedList> _list; + + using ListIterator = typename DoublyLinkedList>::Iterator; + + std::unordered_map _map; + + +public: + OrderedMap() {} + void insert(const KeyType& key, const ValueType& value) { + if (_map.find(key) == _map.end()) { + _list.push_back(std::make_pair(key, value)); + _map[key] = _list.back_iterator(); + } + else { + *_map.find(key)->second = std::make_pair(key, value); + } + } + + unsigned int size() const { + return _list.size(); + } + + ValueType& at(const KeyType& key) { + if (_map.find(key) == _map.end()) { + throw std::out_of_range("Key not found in ordered map"); + } + return (*_map[key]).second; + } + + ValueType& operator[](const KeyType& key) { + if (_map.find(key) == _map.end()) { + this->insert(key, ValueType{}); + } + return (*_map[key]).second; + } + + class Iterator { + ListIterator _it; + + public: + Iterator(ListIterator it) : _it(it) {} + + std::pair& operator*() { + return *_it; + } + + Iterator& operator++() { + _it++; + return *this; + } + + Iterator operator++(int) { + Iterator initial_state_copy = *this; + _it++; + return initial_state_copy; + } + + bool operator==(const Iterator& other) const { + return _it == other._it; + } + + bool operator!=(const Iterator& other) const { + return !(*this == other); + } + + }; + + Iterator begin() { + return Iterator(_list.begin()); + } + + Iterator end() { + return Iterator(_list.end()); + } + + bool empty() { + return _list.empty(); + } + + Iterator back_iterator() { + if (empty()) { + throw std::out_of_range("Map is empty"); + } + return Iterator(_list.back_iterator()); + } + + +}; \ No newline at end of file diff --git a/build.sh b/run_build.sh similarity index 93% rename from build.sh rename to run_build.sh index f9d509d..6bc597f 100755 --- a/build.sh +++ b/run_build.sh @@ -18,4 +18,4 @@ cmake .. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON make # Run the tests -./tests \ No newline at end of file +./tests --rng-seed 0 diff --git a/src/doubly_linked_list.cpp b/src/doubly_linked_list.cpp index e69de29..0a5f75b 100644 --- a/src/doubly_linked_list.cpp +++ b/src/doubly_linked_list.cpp @@ -0,0 +1,3 @@ +#include "doubly_linked_list.hpp" + + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt deleted file mode 100644 index f027f02..0000000 --- a/tests/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_executable(test_math test_math.cpp) -target_link_libraries(test_math PRIVATE Catch2::Catch2WithMain) -add_test(NAME MathTests COMMAND test_math) \ No newline at end of file diff --git a/tests/doubly_linked_list/clear_tests.cpp b/tests/doubly_linked_list/clear_tests.cpp new file mode 100644 index 0000000..5dea1cf --- /dev/null +++ b/tests/doubly_linked_list/clear_tests.cpp @@ -0,0 +1,61 @@ +#include +#include "doubly_linked_list.hpp" +#include + +TEST_CASE("calling clear method on a non-empty list makes size = 0", "[clear]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + list.push_back(3); + list.clear(); + REQUIRE(list.size() == 0); +} + +TEST_CASE("calling clear on a non-empty list then accessing the front throws an error", "[clear]") { + DoublyLinkedList list; + list.push_back(1); + list.clear(); + REQUIRE_THROWS_AS(list.front(), std::out_of_range); +} + +TEST_CASE("calling clear on a non-empty list then accessing the back throws an error", "[clear]") { + DoublyLinkedList list; + list.push_back(1); + list.clear(); + REQUIRE_THROWS_AS(list.back(), std::out_of_range); +} + + +class TestClass { +public: + bool *ref; + + TestClass(bool *a_ref) : ref(a_ref) {} + ~TestClass() { (*ref) = true; } +}; + +TEST_CASE("calling clear on a list of custom class objects makes the destructor called", "[clear]") { + class TestClass { + public: + bool *ref; + + TestClass(bool *a_ref) : ref(a_ref) {} + ~TestClass() { (*ref) = true; } + }; + + DoublyLinkedList list; + bool deletion_flag = false; + list.push_back(TestClass(&deletion_flag)); + list.clear(); + REQUIRE(deletion_flag == true); +} + +TEST_CASE("calling clear after pushing 100 elements makes size = 0", "[size]") { + DoublyLinkedList list; + for (int i = 0; i < 100; i++) { + list.push_back(i); + } + REQUIRE(list.size() == 100); + list.clear(); + REQUIRE(list.size() == 0); +} diff --git a/tests/doubly_linked_list/front_and_back.cpp b/tests/doubly_linked_list/front_and_back.cpp new file mode 100644 index 0000000..c245bd5 --- /dev/null +++ b/tests/doubly_linked_list/front_and_back.cpp @@ -0,0 +1,17 @@ +#include +#include "doubly_linked_list.hpp" +#include + +TEST_CASE("Canary test", "[canary]") { + REQUIRE(true); +} + +TEST_CASE("front on an empty list throws an exception", "[front]") { + DoublyLinkedList list; + REQUIRE_THROWS_AS(list.front(), std::out_of_range); +} + +TEST_CASE("back on an empty list throws an exception", "[back]") { + DoublyLinkedList list; + REQUIRE_THROWS_AS(list.back(), std::out_of_range); +} diff --git a/tests/doubly_linked_list/iterator_tests.cpp b/tests/doubly_linked_list/iterator_tests.cpp new file mode 100644 index 0000000..5b3bc82 --- /dev/null +++ b/tests/doubly_linked_list/iterator_tests.cpp @@ -0,0 +1,171 @@ +#include +#include "doubly_linked_list.hpp" + + +TEST_CASE("begin iterator equals end iterator in empty list", "[iterator]") { + DoublyLinkedList list; + REQUIRE(list.begin() == list.end()); +} + +TEST_CASE("push_back 1, begin iterator does not equal end iterator", "[iterator]") { + DoublyLinkedList list; + list.push_back(1); + REQUIRE(!(list.begin() == list.end())); +} + +TEST_CASE("begin iterator != end iterator returns false in an empty list", "[iterator]") { + DoublyLinkedList list; + REQUIRE(!(list.begin() != list.end())); +} + +TEST_CASE("push_back 1, begin iterator != end iterator returns false", "[iterator]") { + DoublyLinkedList list; + list.push_back(1); + + REQUIRE(list.begin() != list.end()); +} + +TEST_CASE("push_back 1, begin iterator dereferences to 1", "[begin]") { + DoublyLinkedList list; + list.push_back(1); + + REQUIRE(*list.begin() == 1); +} + +TEST_CASE("push_back 2, begin iterator dereferences to 2", "[begin]") { + DoublyLinkedList list; + list.push_back(2); + + REQUIRE(*list.begin() == 2); +} + +TEST_CASE("push_back hello string, modify string by dereferencing begin, front returns modified string", "[begin]") { + DoublyLinkedList list; + list.push_back("hello"); + auto &str = *list.begin(); + str[0] = 'H'; + + REQUIRE(*list.begin() == "Hello"); +} + +TEST_CASE("push_back 1, 2, using pre-increment (++it) on begin iterator to reach 2", "[iterator]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + auto it = list.begin(); + ++it; + + REQUIRE(*it == 2); +} + +TEST_CASE("push_back 1, 2, 3, using pre-increment (++it) twice on begin iterator to reach 2", "[iterator]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + list.push_back(3); + auto it = list.begin(); + ++it; + ++it; + + REQUIRE(*it == 3); +} + +TEST_CASE("push_back 1, 2, copy the iterator after using pre-increment (++it) on begin iterator to reach 2", "[iterator]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + auto it = list.begin(); + auto copied_it = ++it; + + REQUIRE(*copied_it == 2); +} + + +TEST_CASE("push_back 1, 2, using post-increment (it++) on begin iterator to reach 2", "[iterator]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + auto it = list.begin(); + it++; + + REQUIRE(*it == 2); +} + +TEST_CASE("push_back 1, 2, 3, using post-increment (it++) on begin iterator twice to reach 3", "[iterator]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + list.push_back(3); + auto it = list.begin(); + it++; + it++; + + + REQUIRE(*it == 3); +} + +TEST_CASE("push_back 1, 2, copy the iterator after using post-increment (it++) on begin iterator", "[iterator]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + auto it = list.begin(); + auto copied_it = it++; + + REQUIRE(*copied_it == 1); +} + +TEST_CASE("push back 1, 2, 3, iterate using for-loop with post-increment (it++) on begin iterator", "[iterator]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + list.push_back(3); + int counter = 1; + + for (auto it = list.begin(); it != list.end(); it++) { + REQUIRE(*it == counter); + counter++; + } +} + +TEST_CASE("push back 1, 2, 3, iterate using for-loop with pre-increment (++it) on begin iterator", "[iterator]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + list.push_back(3); + int counter = 1; + + for (auto it = list.begin(); it != list.end(); ++it) { + REQUIRE(*it == counter); + counter++; + } +} + +TEST_CASE("push back 1, 2, 3, iterate using for-loop with for-each syntax", "[iterator]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + list.push_back(3); + int counter = 1; + + for (auto it : list) { + REQUIRE(it == counter); + counter++; + } +} + +TEST_CASE("push_back 1, dereferencing back iterator returns 1", "[back_iterator]") { + DoublyLinkedList list; + list.push_back(1); + auto it = list.back_iterator(); + + REQUIRE(*it == 1); +} + +TEST_CASE("push_back 1, 2, dereferencing back iterator returns 2", "[back_iterator]") { + DoublyLinkedList list; + list.push_back(1); list.push_back(2); + auto it = list.back_iterator(); + + REQUIRE(*it == 2); +} + diff --git a/tests/doubly_linked_list/pop_back_tests.cpp b/tests/doubly_linked_list/pop_back_tests.cpp new file mode 100644 index 0000000..3927c7b --- /dev/null +++ b/tests/doubly_linked_list/pop_back_tests.cpp @@ -0,0 +1,65 @@ +#include +#include "doubly_linked_list.hpp" +#include + +TEST_CASE("push_back 1, pop_back once, size = 0", "[pop_back]") { + DoublyLinkedList list; + list.push_back(1); + list.pop_back(); + REQUIRE(list.size() == 0); +} + +TEST_CASE("push_back 1, pop_back returns 1", "[pop_back]") { + DoublyLinkedList list; + list.push_back(1); + REQUIRE(list.pop_back() == 1); +} + +TEST_CASE("push_back 1, 2, pop_back returns 2", "[pop_back]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + REQUIRE(list.pop_back() == 2); +} + +TEST_CASE("pop_back on an empty list throws an exception", "[pop_back]") { + DoublyLinkedList list; + REQUIRE_THROWS_AS(list.pop_back(), std::out_of_range); +} + + +TEST_CASE("push_back 1, 2, pop_back once, size = 1", "[pop_back]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + list.pop_back(); + REQUIRE(list.size() == 1); +} + +TEST_CASE("push_back 1, 2, 3, pop_back once, back return 2", "[pop_back]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + list.push_back(3); + list.pop_back(); + REQUIRE(list.back() == 2); +} + +TEST_CASE("push_back 1, 2, 3, pop_back twice, back return 1", "[pop_back]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + list.push_back(3); + list.pop_back(); + list.pop_back(); + REQUIRE(list.back() == 1); +} + +TEST_CASE("push_back 1, 2, 3, pop_back once, front return 1", "[pop_back]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + list.push_back(3); + list.pop_back(); + REQUIRE(list.front() == 1); +} diff --git a/tests/doubly_linked_list/pop_front_tests.cpp b/tests/doubly_linked_list/pop_front_tests.cpp new file mode 100644 index 0000000..7a3c9cb --- /dev/null +++ b/tests/doubly_linked_list/pop_front_tests.cpp @@ -0,0 +1,54 @@ +#include +#include "doubly_linked_list.hpp" +#include + +TEST_CASE("push_back 1, 2, pop_front once, size is 1", "[pop_front]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + list.pop_front(); + REQUIRE(list.size() == 1); +} + +TEST_CASE("push_front 1, pop_front returns 1", "[pop_front]") { + DoublyLinkedList list; + list.push_front(1); + REQUIRE(list.pop_front() == 1); +} + +TEST_CASE("push_front 1, 2, pop_front returns 2", "[pop_front]") { + DoublyLinkedList list; + list.push_front(1); + list.push_front(2); + REQUIRE(list.pop_front() == 2); +} + +TEST_CASE("push_back 1, 2, 3, pop_front once, front is 2", "[pop_front]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + list.push_back(3); + list.pop_front(); + REQUIRE(list.front() == 2); +} + + +TEST_CASE("pop_front on an empty list throws an exception", "[pop_front]") { + DoublyLinkedList list; + REQUIRE_THROWS_AS(list.pop_front(), std::out_of_range); +} + +TEST_CASE("pop_front clears tail when list becomes empty", "[pop_back]") { + DoublyLinkedList list; + list.push_back(42); + list.pop_front(); + REQUIRE_THROWS_AS(list.back(), std::out_of_range); +} + +TEST_CASE("push_back 1, pop_back, push_front 2, pop front returns 2", "[pop_front]") { + DoublyLinkedList list; + list.push_back(1); + list.pop_back(); + list.push_front(2); + REQUIRE(list.pop_front() == 2); +} \ No newline at end of file diff --git a/tests/doubly_linked_list/push_back_tests.cpp b/tests/doubly_linked_list/push_back_tests.cpp new file mode 100644 index 0000000..5e7d3e7 --- /dev/null +++ b/tests/doubly_linked_list/push_back_tests.cpp @@ -0,0 +1,59 @@ +#include +#include "doubly_linked_list.hpp" + + +TEST_CASE("push_back one value in an empty list makes size = 1", "[push_back]") { + DoublyLinkedList list; + list.push_back(1); + REQUIRE(list.size() == 1); +} + +TEST_CASE("push_back two values in an empty list makes size = 2", "[push_back]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + REQUIRE(list.size() == 2); +} + +TEST_CASE("push_back 1 and front returns 1", "[push_back]") { + DoublyLinkedList list; + list.push_back(1); + REQUIRE(list.front() == 1); +} + +TEST_CASE("push_back 1, 2 and front returns 1", "[push_back]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + REQUIRE(list.front() == 1); +} + + +TEST_CASE("push_back 2, 1 and front returns 2", "[push_back]") { + DoublyLinkedList list; + list.push_back(2); + list.push_back(1); + REQUIRE(list.front() == 2); +} + + +TEST_CASE("push_back 1, and back returns 1", "[push_back]") { + DoublyLinkedList list; + list.push_back(1); + REQUIRE(list.back() == 1); +} + +TEST_CASE("push_back 1, 2 and back returns 2", "[push_back]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + REQUIRE(list.back() == 2); +} + + +TEST_CASE("push_back 2, 1 and back returns 1", "[push_back]") { + DoublyLinkedList list; + list.push_back(2); + list.push_back(1); + REQUIRE(list.back() == 1); +} diff --git a/tests/doubly_linked_list/push_front_tests.cpp b/tests/doubly_linked_list/push_front_tests.cpp new file mode 100644 index 0000000..9903a1c --- /dev/null +++ b/tests/doubly_linked_list/push_front_tests.cpp @@ -0,0 +1,47 @@ +#include +#include "doubly_linked_list.hpp" + + + +TEST_CASE("push_front 1, size = 1", "[push_front]") { + DoublyLinkedList list; + list.push_front(1); + REQUIRE(list.size() == 1); +} + +TEST_CASE("push_front 1, 2, size = 2", "[push_front]") { + DoublyLinkedList list; + list.push_front(1); + list.push_front(2); + REQUIRE(list.size() == 2); +} + +TEST_CASE("push_front 1, front returns 1", "[push_front]") { + DoublyLinkedList list; + list.push_front(1); + REQUIRE(list.front() == 1); +} + +TEST_CASE("push_front 1, 2 and front returns 2", "[push_front]") { + DoublyLinkedList list; + list.push_front(1); + list.push_front(2); + REQUIRE(list.front() == 2); +} + +TEST_CASE("push_front 1, 2, push_back 3, back is 3", "[push_front]") { + DoublyLinkedList list; + list.push_front(1); + list.push_front(2); + list.push_back(3); + REQUIRE(list.back() == 3); +} + +TEST_CASE("push_back 1, push_front 2, pop_back twice, size is 0", "[push_front]") { + DoublyLinkedList list; + list.push_back(1); + list.push_front(2); + list.pop_back(); + list.pop_back(); + REQUIRE(list.size() == 0); +} diff --git a/tests/doubly_linked_list/size_tests.cpp b/tests/doubly_linked_list/size_tests.cpp new file mode 100644 index 0000000..5ae30d6 --- /dev/null +++ b/tests/doubly_linked_list/size_tests.cpp @@ -0,0 +1,62 @@ +#include +#include "doubly_linked_list.hpp" + +TEST_CASE("size is 0 when list is empty", "[size]") { + DoublyLinkedList list; + REQUIRE(list.size() == 0); +} + +TEST_CASE("size is 1 after pushing one element", "[size]") { + DoublyLinkedList list; + list.push_back(1); + REQUIRE(list.size() == 1); +} + +TEST_CASE("size is 2 after pushing two elements", "[size]") { + DoublyLinkedList list; + list.push_back(1); + list.push_back(2); + REQUIRE(list.size() == 2); +} + +TEST_CASE("empty() returns true when list is empty", "[empty]") { + DoublyLinkedList list; + REQUIRE(list.empty()); +} + +TEST_CASE("empty() returns false when list is not empty", "[empty]") { + DoublyLinkedList list; + list.push_back(1); + REQUIRE(!list.empty()); +} + +TEST_CASE("size is 100 after pushing 100 elements", "[size]") { + DoublyLinkedList list; + for (int i = 0; i < 100; i++) { + list.push_back(i); + } + REQUIRE(list.size() == 100); +} + +TEST_CASE("size is 100000 after pushing 100000 elements", "[size]") { + DoublyLinkedList list; + for (int i = 0; i < 100000; i++) { + list.push_back(i); + } + REQUIRE(list.size() == 100000); +} + +TEST_CASE("size is 1000,000 after pushing 1000,000 elements", "[size]") { + DoublyLinkedList list; + int size = int(1e6); + for (int i = 0; i < size; i++) { + list.push_back(i); + } + REQUIRE(list.size() == size); +} + + + + + + diff --git a/tests/ordered_map/insertion_tests.cpp b/tests/ordered_map/insertion_tests.cpp new file mode 100644 index 0000000..3f6f95c --- /dev/null +++ b/tests/ordered_map/insertion_tests.cpp @@ -0,0 +1,37 @@ +#include +#include "ordered_map.hpp" + +TEST_CASE("empty ordered map has size 0", "[insert]") { + OrderedMap o_map; + REQUIRE(o_map.size() == 0); +} + +TEST_CASE("inserting <1, 2> into an empty ordered map", "[insert]") { + OrderedMap o_map; + o_map.insert(1, 2); + REQUIRE(o_map.size() == 1); +} + +TEST_CASE("insert <1, 2> and <1, 3>, size = 1", "[insert]") { + OrderedMap o_map; + o_map.insert(1, 2); + o_map.insert(1, 3); + REQUIRE(o_map.size() == 1); +} + +TEST_CASE("insert <1, 2> and <2, 3>, size = 2", "[insert]") { + OrderedMap o_map; + o_map.insert(1, 2); + o_map.insert(2, 3); + REQUIRE(o_map.size() == 2); +} + + + + + + + + + + diff --git a/tests/ordered_map/iterator_tests.cpp b/tests/ordered_map/iterator_tests.cpp new file mode 100644 index 0000000..5886ba4 --- /dev/null +++ b/tests/ordered_map/iterator_tests.cpp @@ -0,0 +1,123 @@ +#include +#include "../include/ordered_map.hpp" +#include + +TEST_CASE("insert <1, 2>, begin iterator points to <1, 2>", "[ordered_map_iterator]") { + OrderedMap o_map; + o_map.insert(1, 2); + + REQUIRE(*o_map.begin() == std::make_pair(1, 2)); +} + +TEST_CASE("insert <1, 2>, <3, 4>, begin iterator points to <1, 2>", "[ordered_map_iterator]") { + OrderedMap o_map; + o_map.insert(1, 2); + + REQUIRE(*o_map.begin() == std::make_pair(1, 2)); +} + +TEST_CASE("insert <1, 2>, <3, 4>, using pre-increment (++it) on begin iterator makes it point to <3, 4>", "[ordered_map_iterator]") { + OrderedMap o_map; + o_map.insert(1, 2); + o_map.insert(3, 4); + + auto it = o_map.begin(); + ++it; + + REQUIRE(*it == std::make_pair(3, 4)); +} + +TEST_CASE("insert <1, 2>, <3, 4>, using post-increment (it++) on begin iterator makes it point to <1, 2>", "[ordered_map_iterator]") { + OrderedMap o_map; + o_map.insert(1, 2); + o_map.insert(3, 4); + + auto it = o_map.begin(); + it++; + + REQUIRE(*it == std::make_pair(3, 4)); +} + +TEST_CASE("iterator (begin == end) is true when map is empty", "[ordered_map_iterator]") { + OrderedMap o_map; + + REQUIRE(o_map.begin() == o_map.end()); +} + +TEST_CASE("iterator (begin == end) is false when map is not empty", "[ordered_map_iterator]") { + OrderedMap o_map; + o_map.insert(1, 2); + + REQUIRE(!(o_map.begin() == o_map.end())); +} + +TEST_CASE("iterator (begin != end) is false when map is empty", "[ordered_map_iterator]") { + OrderedMap o_map; + + REQUIRE(!(o_map.begin() != o_map.end())); +} + +TEST_CASE("iterator (begin != end) is true when map is not empty", "[ordered_map_iterator]") { + OrderedMap o_map; + o_map.insert(1, 2); + + REQUIRE(o_map.begin() != o_map.end()); +} + +TEST_CASE("using for-loop with begin and end iterators to iterate over map yields correct results in order", "[ordered_map_iterator]") { + std::vector> expected_results = { + {1, 2}, + {2, 3}, + {3, 4} + }; + OrderedMap o_map; + for (const auto& pair : expected_results) { + o_map.insert(pair.first, pair.second); + } + + int i = 0; + for (auto it = o_map.begin(); it != o_map.end(); it++) { + REQUIRE(*it == expected_results[i]); + i++; + } +} + +TEST_CASE("using for-each loop yields correct results in order", "[ordered_map_iterator]") { + std::vector> expected_results = { + {1, 2}, + {2, 3}, + {3, 4} + }; + OrderedMap o_map; + for (const auto& pair : expected_results) { + o_map.insert(pair.first, pair.second); + } + + int i = 0; + for (auto& pair : o_map) { + REQUIRE(pair == expected_results[i]); + i++; + } +} + +TEST_CASE("back_iterator() throws out_of_range when map is empty", "[ordered_map_iterator]") { + OrderedMap o_map; + + REQUIRE_THROWS_AS(o_map.back_iterator(), std::out_of_range); +} + +TEST_CASE("insert <1, 2>, back_iterator points to <1, 2>", "[ordered_map_iterator]") { + OrderedMap o_map; + o_map.insert(1, 2); + + REQUIRE(*o_map.back_iterator() == std::make_pair(1, 2)); +} + +TEST_CASE("insert <1, 2>, <3, 4>, back_iterator points to <3, 4>", "[ordered_map_iterator]") { + OrderedMap o_map; + o_map.insert(1, 2); + o_map.insert(3, 4); + + REQUIRE(*o_map.back_iterator() == std::make_pair(3, 4)); +} + diff --git a/tests/ordered_map/lookup_tests.cpp b/tests/ordered_map/lookup_tests.cpp new file mode 100644 index 0000000..3e473e3 --- /dev/null +++ b/tests/ordered_map/lookup_tests.cpp @@ -0,0 +1,73 @@ +#include "ordered_map.hpp" +#include +#include + +TEST_CASE("insert <1, 2>, key 1 returns value of 2", "[at]") { + OrderedMap o_map; + o_map.insert(1, 2); + REQUIRE(o_map.at(1) == 2); +} + +TEST_CASE("insert , key hello returns value of world", "[at]") { + OrderedMap o_map; + o_map.insert("hello", "world"); + REQUIRE(o_map.at("hello") == "world"); +} + +TEST_CASE("insert , , key hello returns value of there", "[at]") { + OrderedMap o_map; + o_map.insert("hello", "world"); + o_map.insert("hello", "there"); + REQUIRE(o_map.at("hello") == "there"); +} + +TEST_CASE("lookup key that does not exist in ordered map throws out_of_range exception", "[at]") { + OrderedMap o_map; + o_map.insert(1, 2); + REQUIRE_THROWS_AS(o_map.at(2), std::out_of_range); +} + +TEST_CASE("insert <1, 2>, operator[] returns value of 2", "[square_brackets]") { + OrderedMap o_map; + o_map.insert(1, 2); + REQUIRE(o_map[1] == 2); +} + +TEST_CASE("insert, map[hello] returns value of world", "[square_brackets]") { + OrderedMap o_map; + o_map.insert("hello", "world"); + REQUIRE(o_map["hello"] == "world"); +} + +TEST_CASE("using operator[] on an empty map makes the size = 1", "[square_brackets]") { + OrderedMap o_map; + o_map[1] = 2; + REQUIRE(o_map.size() == 1); +} + +TEST_CASE("using operator[1] on a key that does not exist in the map assigns default value of type int", "[square_brackets]") { + OrderedMap o_map; + o_map[1]; + REQUIRE(o_map.at(1) == int()); +} + +TEST_CASE("using operator[hello] on a key that does not exist in the map assigns default value of type string", "[square_brackets]") { + OrderedMap o_map; + o_map["hello"]; + REQUIRE(o_map.at("hello") == std::string()); +} + +TEST_CASE("insert , using operator[key] allows updating the value to another value", "[square_brackets]") { + OrderedMap o_map; + o_map.insert("key", "value"); + o_map["key"] = "new value"; + REQUIRE(o_map.at("key") == "new value"); +} + +TEST_CASE("updating an existing key does not affect the size of the map", "[square_brackets]") { + OrderedMap o_map; + o_map.insert("key", "value"); + o_map["key"] = "new value"; + REQUIRE(o_map.size() == 1); +} + diff --git a/tests/ordered_map/size_tests.cpp b/tests/ordered_map/size_tests.cpp new file mode 100644 index 0000000..f9cd138 --- /dev/null +++ b/tests/ordered_map/size_tests.cpp @@ -0,0 +1,33 @@ +#include +#include "ordered_map.hpp" + +TEST_CASE("size is 0 when map is empty", "[size]") { + OrderedMap map; + REQUIRE(map.size() == 0); +} + +TEST_CASE("size is 1 after inserting one element", "[size]") { + OrderedMap map; + map.insert(1, 2); + REQUIRE(map.size() == 1); +} + +TEST_CASE("size is 2 after inserting two elements", "[size]") { + OrderedMap map; + map.insert(1, 2); + map.insert(3, 4); + REQUIRE(map.size() == 2); +} + +TEST_CASE("empty() returns true when map is empty", "[empty]") { + OrderedMap map; + REQUIRE(map.empty()); +} + +TEST_CASE("empty() returns false when map is not empty", "[empty]") { + OrderedMap map; + map.insert(1, 2); + REQUIRE(!map.empty()); +} + + diff --git a/tests/test_doubly_linked_list.cpp b/tests/test_doubly_linked_list.cpp deleted file mode 100644 index e6e2409..0000000 --- a/tests/test_doubly_linked_list.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include -#include "doubly_linked_list.hpp" - -TEST_CASE("Canary test", "[canary]") { - REQUIRE(true); -} \ No newline at end of file