From 050afc886b5b82834ef1d55b335ff4dc91ccc754 Mon Sep 17 00:00:00 2001 From: walig-here Date: Wed, 2 Jul 2025 23:10:26 +0200 Subject: [PATCH] Base data structures library core - C++ project infrastructure - Base library specification in *.hpp files --- .github/actions/run-ci-container/action.yml | 15 ++ .github/workflows/library.yaml | 68 +++++++++ .gitignore | 1 + Dockerfile | 20 +++ data_structures_library/.gitignore | 2 + data_structures_library/CMakeLists.txt | 15 ++ .../library/CMakeLists.txt | 19 +++ .../library/include/dynamic-array.hpp | 114 ++++++++++++++ .../library/src/dynamic-array.cpp | 49 ++++++ .../scripts/build-library.sh | 5 + .../scripts/build-tests.sh | 6 + data_structures_library/scripts/lint.sh | 12 ++ data_structures_library/scripts/test.sh | 13 ++ data_structures_library/tests/CMakeLists.txt | 31 ++++ .../dynamic-array/default-initialization.cpp | 140 ++++++++++++++++++ docker.sh | 61 ++++++++ docs/data_structures_library/dynamic-array.md | 5 + docs/img/dynamic-array.drawio | 44 ++++++ docs/img/dynamic-array.svg | 1 + docs/versions.md | 6 +- 20 files changed, 626 insertions(+), 1 deletion(-) create mode 100644 .github/actions/run-ci-container/action.yml create mode 100644 .github/workflows/library.yaml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 data_structures_library/.gitignore create mode 100644 data_structures_library/CMakeLists.txt create mode 100644 data_structures_library/library/CMakeLists.txt create mode 100644 data_structures_library/library/include/dynamic-array.hpp create mode 100644 data_structures_library/library/src/dynamic-array.cpp create mode 100755 data_structures_library/scripts/build-library.sh create mode 100755 data_structures_library/scripts/build-tests.sh create mode 100755 data_structures_library/scripts/lint.sh create mode 100755 data_structures_library/scripts/test.sh create mode 100644 data_structures_library/tests/CMakeLists.txt create mode 100644 data_structures_library/tests/ut/dynamic-array/default-initialization.cpp create mode 100755 docker.sh create mode 100644 docs/data_structures_library/dynamic-array.md create mode 100644 docs/img/dynamic-array.drawio create mode 100644 docs/img/dynamic-array.svg diff --git a/.github/actions/run-ci-container/action.yml b/.github/actions/run-ci-container/action.yml new file mode 100644 index 0000000..e7cd689 --- /dev/null +++ b/.github/actions/run-ci-container/action.yml @@ -0,0 +1,15 @@ +name: 'Run CI Docker container' +description: 'Download Docker image archive and runs it as a container.' +runs: + using: "composite" + steps: + - name: Download CI image + uses: actions/download-artifact@v4 + with: + name: docker-ci-image + - name: Run CI container + shell: bash + run: | + chmod +x -R data_structures_library/scripts/ + docker load -i=ci-image.tar + docker run -v="$(pwd):/home/workspace" -dt --name="data-structures-v2" "data-structures-v2" "/bin/bash" diff --git a/.github/workflows/library.yaml b/.github/workflows/library.yaml new file mode 100644 index 0000000..8c8397d --- /dev/null +++ b/.github/workflows/library.yaml @@ -0,0 +1,68 @@ +name: "C++ library pipeline" +on: + push +jobs: + prepare_docker_image: + name: "Create CI Docker container" + runs-on: ubuntu-22.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Prepare image + run: | + docker build . -t "data-structures-v2" + docker save -o ci-image.tar data-structures-v2 + - name: Store image + uses: actions/upload-artifact@v4 + with: + name: docker-ci-image + path: ci-image.tar + retention-days: 1 + + build: + name: "Build C++ library for Linux" + runs-on: ubuntu-22.04 + needs: prepare_docker_image + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: "Run CI container" + uses: ./.github/actions/run-ci-container + - name: Build + run: docker exec data-structures-v2 "data_structures_library/scripts/build-library.sh" + - name: Store build + uses: actions/upload-artifact@v4 + with: + name: data-structures-lib.a + path: data_structures_library/build/lib/data-structures.a + + test: + name: "Test C++ library" + runs-on: ubuntu-22.04 + needs: prepare_docker_image + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: "Run CI container" + uses: ./.github/actions/run-ci-container + - name: Build tests + run: docker exec data-structures-v2 "data_structures_library/scripts/build-tests.sh" + - name: Test + run: docker exec data-structures-v2 "data_structures_library/scripts/test.sh" + - name: Store report + uses: actions/upload-artifact@v4 + with: + name: data-structures-lib-coverage.html + path: data_structures_library/build/coverage.html + + lint: + name: "Lint C++ library" + runs-on: ubuntu-22.04 + needs: prepare_docker_image + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: "Run CI container" + uses: ./.github/actions/run-ci-container + - name: Lint + run: docker exec data-structures-v2 "data_structures_library/scripts/lint.sh" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..722d5e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fb7549a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM ubuntu:22.04 + +ENV PROJECT_ROOT="/home/workspace" + +ENV LIB_ROOT="${PROJECT_ROOT}/data_structures_library" +ENV LIB_BUILD="${LIB_ROOT}/build" + +SHELL ["/bin/bash", "-c"] +RUN mkdir /home/workspace +WORKDIR /home/workspace + +RUN apt-get update -y +RUN apt-get upgrade -y +RUN apt-get install clang -y +RUN apt-get install git -y +RUN apt-get install cmake -y +RUN apt-get install clang-tidy -y +RUN apt-get install libc++-14-dev -y +RUN apt-get install libc++abi-14-dev -y +RUN apt-get install llvm -y diff --git a/data_structures_library/.gitignore b/data_structures_library/.gitignore new file mode 100644 index 0000000..8a04f72 --- /dev/null +++ b/data_structures_library/.gitignore @@ -0,0 +1,2 @@ +build/ +Testing/ diff --git a/data_structures_library/CMakeLists.txt b/data_structures_library/CMakeLists.txt new file mode 100644 index 0000000..ca221d2 --- /dev/null +++ b/data_structures_library/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.22.1) + +project("Data Structure Library") +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-instr-generate -fcoverage-mapping") +SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-instr-generate") + +set(LIB_TARGET_NAME "data-structures") +set(TEST_TARGET_NAME "data-structures-tests") + +enable_testing() + +add_subdirectory(library) +add_subdirectory(tests) diff --git a/data_structures_library/library/CMakeLists.txt b/data_structures_library/library/CMakeLists.txt new file mode 100644 index 0000000..3bdb4b3 --- /dev/null +++ b/data_structures_library/library/CMakeLists.txt @@ -0,0 +1,19 @@ +add_library( + ${LIB_TARGET_NAME} + STATIC + "src/dynamic-array.cpp" +) + +target_include_directories( + ${LIB_TARGET_NAME} + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +set_target_properties( + ${LIB_TARGET_NAME} + PROPERTIES + OUTPUT_NAME ${LIB_TARGET_NAME} + PREFIX "" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" +) diff --git a/data_structures_library/library/include/dynamic-array.hpp b/data_structures_library/library/include/dynamic-array.hpp new file mode 100644 index 0000000..9cf5092 --- /dev/null +++ b/data_structures_library/library/include/dynamic-array.hpp @@ -0,0 +1,114 @@ +#ifndef DYNAMIC_ARRAY_H +#define DYNAMIC_ARRAY_H + + +#include +#include + + +class DynamicArray +{ +public: + + /** + * Creates new dynamic array with given length & filled with given deafult value. + * + * @param initial_length Initial length of the array. + * @param default_value Value that would be used to fill the array. + */ + DynamicArray(const std::size_t& initial_length = 0, const int& default_value = 0); + + /* Creates deep copy of array. */ + DynamicArray(const DynamicArray& to_copy); + DynamicArray(DynamicArray&& to_move) = default; + DynamicArray& operator=(DynamicArray&& to_move) = default; + DynamicArray& operator=(const DynamicArray& to_copy); + ~DynamicArray() = default; + + /* === Elements access === */ + + // Random access + int& operator[](const size_t& index) { return std::span(mData.get(), mLength)[index]; } + const int& operator[](const size_t& index) const { return std::span(mData.get(), mLength)[index]; } + + // Acces to first element + int& front() { return std::span(mData.get(), mLength).front(); } + [[nodiscard]] const int& front() const { return std::span(mData.get(), mLength).front(); } + + // Access to last element + int& back() { return std::span(mData.get(), mLength).back(); } + [[nodiscard]] const int& back() const { return std::span(mData.get(), mLength).back(); } + + /* === Capacity === */ + + // Get number of elements + [[nodiscard]] size_t size() const { return mLength; } + + /* === Modification === */ + + /** + * Inserts new element at selected index. The rest of the elements in the array would be shifted one position + * to the right. + * + * @param position Index where element would be placed. + * @param value Value that would be inserted. + * + * @return Exit code. + * @retval 0 - element inserted. + * @retval 1 - position out of bounds. + */ + int insert(const size_t& position, const int& value); + + /** + * Inserts new element at first position. The rest of the elements in the array would be shifted one position to the + * right. + * + * @param value Value that would be inserted. + */ + void push_front(const int& value); + + /** + * Inserts new element at last position. + * + * @param value Value that would be inserted. + */ + void push_back(const int& value); + + /** + * Removes element from given position. All elements to the right of this position would be shifted one position + * to the right. + * + * @param position Position that would be removed. + * + * @return Exit code. + * @retval 0 - element removed. + * @retval 1 - array's empty. + * @retval 2 - position out of bounds. + */ + int erase(const std::size_t& position); + + /** + * Removes first element. All other elements would be shifted one position to the right. + * + * @return Exit code. + * @retval 0 - element removed. + * @retval 1 - array's empty. + */ + int pop_front(); + + /** + * Removes last element. + * + * @return Exit code. + * @retval 0 - element's removed. + * @retval 1 - array's empty. + */ + int pop_back(); + +private: + + std::shared_ptr mData; + size_t mLength; +}; + +#endif diff --git a/data_structures_library/library/src/dynamic-array.cpp b/data_structures_library/library/src/dynamic-array.cpp new file mode 100644 index 0000000..0166604 --- /dev/null +++ b/data_structures_library/library/src/dynamic-array.cpp @@ -0,0 +1,49 @@ +#ifndef DYNAMIC_ARRAY_CPP +#define DYNAMIC_ARRAY_CPP + +#include + +#include "dynamic-array.hpp" + +DynamicArray::DynamicArray(const std::size_t& initial_length, const int& default_value): + mLength(initial_length) +{ + mData = std::shared_ptr(new int[mLength]); + for (int& element : std::span(mData.get(), mLength)) + { + element = default_value; + } +} + +DynamicArray::DynamicArray(const DynamicArray &to_copy): + mLength(to_copy.mLength) +{ + throw std::logic_error("Not yet implemented!"); +} + +DynamicArray &DynamicArray::operator=(const DynamicArray &to_copy) +{ + throw std::logic_error("Not yet implemented!"); +} + +int DynamicArray::insert(const std::size_t& position, const int& value) +{ + throw std::logic_error("Not yet implemented!"); +} + +void DynamicArray::push_front(const int& value) +{ + throw std::logic_error("Not yet implemented!"); +} + +void DynamicArray::push_back(const int& value) +{ + throw std::logic_error("Not yet implemented!"); +} + +int DynamicArray::erase(const std::size_t& position) +{ + throw std::logic_error("Not yet implemented!"); +} + +#endif diff --git a/data_structures_library/scripts/build-library.sh b/data_structures_library/scripts/build-library.sh new file mode 100755 index 0000000..ebcde79 --- /dev/null +++ b/data_structures_library/scripts/build-library.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +cmake -S "${LIB_ROOT}" -B "${LIB_BUILD}" -DCMAKE_EXPORT_COMPILE_COMMANDS=on +cd "${LIB_ROOT}/library" +cmake --build ${LIB_BUILD} --target "data-structures" diff --git a/data_structures_library/scripts/build-tests.sh b/data_structures_library/scripts/build-tests.sh new file mode 100755 index 0000000..4af8b65 --- /dev/null +++ b/data_structures_library/scripts/build-tests.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +cmake -S "${LIB_ROOT}" -B "${LIB_BUILD}" -DCMAKE_EXPORT_COMPILE_COMMANDS=on +cd "${LIB_ROOT}/tests" +cmake --build ${LIB_BUILD} --target "data-structures-tests" + diff --git a/data_structures_library/scripts/lint.sh b/data_structures_library/scripts/lint.sh new file mode 100755 index 0000000..a7059e9 --- /dev/null +++ b/data_structures_library/scripts/lint.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +echo "Running linter for C++ library" + +clang-tidy \ + ${LIB_ROOT}/library/src/*.cpp ${LIB_ROOT}/library/include/*.hpp \ + -p build/ \ + -checks=-*,cppcoreguidelines-*,cert-*,clang-analyzer-*,cppcoreguidelines-*,modernize-*,performance-*,portability-*,readability-*,-modernize-use-trailing-return-type,-cppcoreguidelines-avoid-c-arrays,-modernize-avoid-c-arrays\ + -warnings-as-errors=* \ + -- \ + -I "${LIB_ROOT}/library/include" \ + -std=c++20 diff --git a/data_structures_library/scripts/test.sh b/data_structures_library/scripts/test.sh new file mode 100755 index 0000000..05665d7 --- /dev/null +++ b/data_structures_library/scripts/test.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +cd ${LIB_BUILD} +ctest --output-on-failure + +llvm-profdata merge -o unittest.profdata ${LIB_BUILD}/tests/*.profraw +llvm-cov report ${LIB_BUILD}/lib/data-structures.a -instr-profile=unittest.profdata -use-color -show-region-summary=false + +llvm-cov show ${LIB_BUILD}/lib/data-structures.a \ + -instr-profile=unittest.profdata \ + -format=html \ + -show-branches=count \ +> coverage.html diff --git a/data_structures_library/tests/CMakeLists.txt b/data_structures_library/tests/CMakeLists.txt new file mode 100644 index 0000000..44cbc42 --- /dev/null +++ b/data_structures_library/tests/CMakeLists.txt @@ -0,0 +1,31 @@ +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/52eb8108c5bdec04579160ae17225d66034bd723.zip +) +FetchContent_MakeAvailable(googletest) + +add_executable( + ${TEST_TARGET_NAME} + "ut/dynamic-array/default-initialization.cpp" +) + +target_link_libraries( + ${TEST_TARGET_NAME} + PRIVATE + ${LIB_TARGET_NAME} + GTest::gtest_main +) + +set_target_properties( + ${TEST_TARGET_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) + +include(GoogleTest) +gtest_discover_tests(${TEST_TARGET_NAME} + DISCOVERY_MODE PRE_TEST + PROPERTIES + ENVIRONMENT "LLVM_PROFILE_FILE=coverage-%m-%p.profraw" +) diff --git a/data_structures_library/tests/ut/dynamic-array/default-initialization.cpp b/data_structures_library/tests/ut/dynamic-array/default-initialization.cpp new file mode 100644 index 0000000..7a082f1 --- /dev/null +++ b/data_structures_library/tests/ut/dynamic-array/default-initialization.cpp @@ -0,0 +1,140 @@ +#include // NOLINT +#include "dynamic-array.hpp" + +/** + * Preconditions + * - No parameters are specified. + * + * Expected outcome + * - Empty array is created. + */ +TEST(ArrayInitialization, DefaultArray) +{ + DynamicArray array; + EXPECT_EQ(array.size(), 0); +} + +/** + * Preconditions + * - Initial length is set to 0 + * - No fill value is speficied + * + * Expected outcome + * - Empty array is created. + */ +TEST(ArrayInitialization, EmptyArray) +{ + DynamicArray array(0); + EXPECT_EQ(array.size(), 0); +} + +/** + * Preconditions + * - Initial length is set to 0 + * - Fill value is set to 6 + * + * Expected outcome + * - Empty array is created. + */ +TEST(ArrayInitialization, EmptyArrayWithCustomFillValue) +{ + DynamicArray array(0, 6); + EXPECT_EQ(array.size(), 0); +} + +/** + * Preconditions + * - Initial length is set to 1 + * - Fill value is set to 6 + * + * Expected outcome + * - Single-element array is created + */ +TEST(ArrayInitialization, SingleElementArray) +{ + DynamicArray array(1, 6); + + EXPECT_EQ(array.size(), 1); + for (int i = 0; i < array.size(); i++) { + EXPECT_EQ(array[i], 6); + } +} + +/** + * Preconditions + * - Length 10 is set for the array. + * + * Expected outcome + * - 10-element array filled with 0's is created + */ +TEST(ArrayInitialization, DefaultNonEmptyArray) +{ + DynamicArray array(10); + + EXPECT_EQ(array.size(), 10); + for (size_t i = 0; i < 10; ++i) + { + EXPECT_EQ(array[i], 0); + } +} + +/** + * Preconditions + * - Length 10 is set for the array. + * - Default value for the array is set to 44 + * + * Expected outcome + * - 10-element array filled with 44's is created + */ +TEST(ArrayInitialization, NonEmptyArrayWithCustomDefaultValue) +{ + DynamicArray array(10, 44); + + EXPECT_EQ(array.size(), 10); + for (size_t i = 0; i < 10; ++i) + { + EXPECT_EQ(array[i], 44); + } +} + +/** + * Preconditions + * - Initial length is set to 10^6 + * - Fill value is set to 50 + * + * Expected outcome + * - 10^6-elemen array filled with 50 is created + */ +TEST(ArrayInitialization, HugeArray) { + DynamicArray array((size_t)1E6, 50); + ASSERT_EQ(array.size(), (size_t)1E6); + for (int i = 0; i < array.size(); i++) + { + ASSERT_EQ(array[i], 50); + } +} + +/** + * Preconditions + * - Initial lenght is maximal possible value. + * + * Expected outcome + * - Array with maximal size filled with 0's is created. Eventually when + * `std::bad_alloc` is thrown when there's no sufficient memory. + */ +TEST(ArrayInitialization, ArrayWithMaximalSize) { + unsigned long size = std::numeric_limits::max(); + try + { + DynamicArray array(size); + EXPECT_EQ(array.size(), size); + for (int i = 0; i < array.size(); i++) + { + EXPECT_EQ(array[i], 0); + } + } + catch(const std::exception& e) + { + return; + } +} diff --git a/docker.sh b/docker.sh new file mode 100755 index 0000000..c18be4b --- /dev/null +++ b/docker.sh @@ -0,0 +1,61 @@ +#! /bin/bash +IMAGE_BUILD_ERROR=101 +INVALID_ARGS_ERROR=255 +IMAGE_TAG="data-structures-v2" +IMAGE_ID=$(docker images --filter=reference="${IMAGE_TAG}" -q) + + +function print_help() { +cat <= 1 )) && [ $1 != "--run" ]; then + print_help + exit ${INVALID_ARGS_ERROR} +fi + +OLD_CONTAINER_ID=$(docker ps -aq --filter=name=${IMAGE_TAG}) +if (( $# == 0 )) || [ -z ${OLD_CONTAINER_ID} ]; then + build_image +fi +IMAGE_ID=$(docker images --filter=reference=${IMAGE_TAG} -q) +echo "Image '${IMAGE_TAG}' found with id '${IMAGE_ID}'" + +if (( $# == 0 )) || [ -z ${OLD_CONTAINER_ID} ]; then + docker run -v="$(pwd):/home/workspace" --name ${IMAGE_TAG} -it ${IMAGE_TAG} "/bin/bash" +else + docker start ${IMAGE_TAG} -i +fi diff --git a/docs/data_structures_library/dynamic-array.md b/docs/data_structures_library/dynamic-array.md new file mode 100644 index 0000000..fa520bc --- /dev/null +++ b/docs/data_structures_library/dynamic-array.md @@ -0,0 +1,5 @@ +# Dynamic Array + +![dyanmic array](/docs/img/dynamic-array.svg) + + diff --git a/docs/img/dynamic-array.drawio b/docs/img/dynamic-array.drawio new file mode 100644 index 0000000..243ddfa --- /dev/null +++ b/docs/img/dynamic-array.drawio @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/img/dynamic-array.svg b/docs/img/dynamic-array.svg new file mode 100644 index 0000000..4d9ab73 --- /dev/null +++ b/docs/img/dynamic-array.svg @@ -0,0 +1 @@ +
e1
e1
e2
e2
e3
e3
e4
e4
e5
e5
e6
e6
...
...
mLength
mLength
mData
mData
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/versions.md b/docs/versions.md index ea3a0a2..338cfed 100644 --- a/docs/versions.md +++ b/docs/versions.md @@ -4,11 +4,15 @@ This document contains changelogs for all versions of the *Data Structures* proj ## [WIP] v2.0.0 — First iterations of *Data Structures Library* -- Implement C++ library with following data structures that are able to work in both *release* and *debug* modes: +- [ ] Implement C++ library with following data structures for `int` variables that are able to work in both *release* and *debug* modes: - [ ] Dynamic array - [ ] Linked list - [ ] Heap - [ ] Binary Search Tree +- [x] Setup CI pipelines for the C++ library + - [x] Build + - [x] Test + - [x] Linter ## v1.0.0 — Uni Project