diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fdcda43 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,85 @@ +name: C/C++ CI coverage and sanitize + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + coverage: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ./05_hw + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up CMake + uses: jwlawson/actions-setup-cmake@v2 + + - name: Install dependencies + run: sudo apt-get install -y gcovr lcov + + - name: Configure CMake + run: cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=True -DSTACKLIB_COVERAGE=ON + + - name: Build project and run ctest + run: cmake --build build --target stack_test + + - name: Run ctest + run: cmake --build build --target coverage + + - name: ls + run: ls -lah ./coverage/ + + - name: uppload cov report + uses: actions/upload-artifact@v4 + with: + name: cov-report + path: 05_hw/coverage/ + + analyze: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ./05_hw + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up CMake + uses: jwlawson/actions-setup-cmake@v2 + + - name: Install dependencies + run: | + sudo wget https://apt.llvm.org/llvm.sh + sudo chmod +x llvm.sh + sudo ./llvm.sh 17 + + - name: Add directory to PATH + run: | + echo "/usr/lib/llvm-15/bin" >> $GITHUB_PATH + echo "Added to PATH" + + - name: Configure CMake + run: cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=True -DCMAKE_BUILD_TYPE=Asan + + - name: Build project and run scan + run: cmake --build build --target main + + - name: Run analyze + run: cmake --build build --target analyze + + - name: uppload clang-tidy report + uses: actions/upload-artifact@v4 + with: + name: clang-tidy-report + path: 05_hw/build/analyzer/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8d2017 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +**/.cache +**/out +compile_commands.json +coverage +05_hw/build +05_hw/venv diff --git a/05_hw/.clang-format b/05_hw/.clang-format new file mode 100644 index 0000000..4ccae27 --- /dev/null +++ b/05_hw/.clang-format @@ -0,0 +1,12 @@ +--- +# We'll use defaults from the LLVM style, but with 4 columns indentation. +BasedOnStyle: LLVM +IndentWidth: 4 +--- +Language: Cpp +# Force pointers to the type for C++. +DerivePointerAlignment: false +PointerAlignment: Left + +AllowShortBlocksOnASingleLine: Empty +AllowShortFunctionsOnASingleLine: Empty diff --git a/05_hw/.editorconfig b/05_hw/.editorconfig new file mode 100644 index 0000000..6e3c965 --- /dev/null +++ b/05_hw/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*] +indent_style = space +indent_size = 4 diff --git a/05_hw/CMakeLists.txt b/05_hw/CMakeLists.txt index 6f5e398..83c9fa1 100644 --- a/05_hw/CMakeLists.txt +++ b/05_hw/CMakeLists.txt @@ -1,7 +1,49 @@ cmake_minimum_required(VERSION 3.10) project(stack) -add_library(stack stack.c) +enable_testing() -add_executable(main main.c) -target_link_libraries(main stack) +add_subdirectory(src) +add_subdirectory(tests) +add_subdirectory(benchmarks) + +if(CMAKE_EXPORT_COMPILE_COMMANDS) + find_program(CLANG_EXECUTABLE clang) + add_custom_target(analyze + COMMAND + analyze-build --cdb ${PROJECT_BINARY_DIR}/compile_commands.json + --use-analyzer ${CLANG_EXECUTABLE} + --output ${PROJECT_BINARY_DIR}/analyzer + ) +endif() + +get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + +if(isMultiConfig) + if(NOT "Asan" IN_LIST CMAKE_CONFIGURATION_TYPES) + list(APPEND CMAKE_CONFIGURATION_TYPES Asan) + endif() +else() + set(allowedBuildTypes Asan Debug Release RelWithDebInfo MinSizeRel) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${allowedBuildTypes}") + + if(CMAKE_BUILD_TYPE AND NOT CMAKE_BUILD_TYPE IN_LIST allowedBuildTypes) + message(FATAL_ERROR "Invalid build type: ${CMAKE_BUILD_TYPE}") + endif() +endif() + +set(CMAKE_C_FLAGS_ASAN + "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address,undefined,leak -fno-omit-frame-pointer" CACHE STRING + "Flags used by the C compiler for Asan build type or configuration." FORCE) + +set(CMAKE_CXX_FLAGS_ASAN + "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address,undefined,leak -fno-omit-frame-pointer" CACHE STRING + "Flags used by the C++ compiler for Asan build type or configuration." FORCE) + +set(CMAKE_EXE_LINKER_FLAGS_ASAN + "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fsanitize=address,undefined,leak" CACHE STRING + "Linker flags to be used to create executables for Asan build type." FORCE) + +set(CMAKE_SHARED_LINKER_FLAGS_ASAN + "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -fsanitize=address,undefined,leak" CACHE STRING + "Linker lags to be used to create shared libraries for Asan build type." FORCE) diff --git a/05_hw/benchmarks/CMakeLists.txt b/05_hw/benchmarks/CMakeLists.txt new file mode 100644 index 0000000..f49eb8f --- /dev/null +++ b/05_hw/benchmarks/CMakeLists.txt @@ -0,0 +1,28 @@ +include(FetchContent) + +set(BENCHMARK_ENABLE_TESTING OFF) +set(ENABLE_GPROF OFF) + +FetchContent_Declare(googlebenchmark + GIT_REPOSITORY https://github.com/google/benchmark + GIT_TAG main) + +FetchContent_MakeAvailable(googlebenchmark) + +add_executable(stack_benchmark stack_benchmark.cpp) + + +if(STACKLIB_COVERAGE) + target_link_libraries(stack_benchmark + PRIVATE + benchmark::benchmark + stack_lib + gcov + ) +else() + target_link_libraries(stack_benchmark + PRIVATE + benchmark::benchmark + stack_lib + ) +endif() diff --git a/05_hw/benchmarks/stack_benchmark.cpp b/05_hw/benchmarks/stack_benchmark.cpp new file mode 100644 index 0000000..8254638 --- /dev/null +++ b/05_hw/benchmarks/stack_benchmark.cpp @@ -0,0 +1,66 @@ +#include +#include + +static void PushBenchmark(benchmark::State& state, Stack* stack) { + for (auto _ : state) { + push(stack, 1111111); + } +} + +class PushFixture : public benchmark::Fixture { + protected: + Stack* stack; + + public: + void SetUp(const benchmark::State& state) override { + stack = new Stack(); + initStack(stack); + } + + void TearDown(const benchmark::State& state) override { + destroyStack(stack); + } +}; + +BENCHMARK_DEFINE_F(PushFixture, PushBenchmark)(benchmark::State& state) { + PushBenchmark(state, stack); +} + +BENCHMARK_REGISTER_F(PushFixture, PushBenchmark) + ->RangeMultiplier(2) + ->Range(1, 512); + +static void PopBenchmark(benchmark::State& state, Stack* stack) { + for (auto _ : state) { + pop(stack); + } +} + +class PopFixture : public benchmark::Fixture { + protected: + Stack* stack; + + public: + void SetUp(const benchmark::State& state) override { + stack = new Stack(); + initStack(stack); + long count = state.range(0); + for (long i = 0; i <= count; i++) { + push(stack, 1111111); + } + } + + void TearDown(const benchmark::State& state) override { + destroyStack(stack); + } +}; + +BENCHMARK_DEFINE_F(PopFixture, PopBenchmark)(benchmark::State& state) { + PopBenchmark(state, stack); +} + +BENCHMARK_REGISTER_F(PopFixture, PopBenchmark) + ->RangeMultiplier(2) + ->Range(1, 1024); + +BENCHMARK_MAIN(); diff --git a/05_hw/cmake-kits.json b/05_hw/cmake-kits.json new file mode 100644 index 0000000..0179246 --- /dev/null +++ b/05_hw/cmake-kits.json @@ -0,0 +1,10 @@ +[ + { + "name": "Windows Kit", + "generator": "MinGW Makefiles", + "compilers": { + "C": "C:\\MinGW\\bin\\gcc.exe", + "CXX": "C:\\MinGW\\bin\\g++.exe" + } + } +] diff --git a/05_hw/src/CMakeLists.txt b/05_hw/src/CMakeLists.txt new file mode 100644 index 0000000..027966d --- /dev/null +++ b/05_hw/src/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(stack_lib stack.c) + +target_include_directories(stack_lib PUBLIC .) + +add_executable(main main.c) +target_compile_options(stack_lib PRIVATE) +if(STACKLIB_COVERAGE) + target_link_libraries(main stack_lib gcov) +else() + target_link_libraries(main stack_lib) +endif() diff --git a/05_hw/main.c b/05_hw/src/main.c similarity index 99% rename from 05_hw/main.c rename to 05_hw/src/main.c index fded814..2013641 100644 --- a/05_hw/main.c +++ b/05_hw/src/main.c @@ -32,5 +32,3 @@ int main() { return 0; } - - diff --git a/05_hw/stack.c b/05_hw/src/stack.c similarity index 89% rename from 05_hw/stack.c rename to 05_hw/src/stack.c index 0964440..1f2a782 100644 --- a/05_hw/stack.c +++ b/05_hw/src/stack.c @@ -17,9 +17,9 @@ void initStack(Stack* stack) { void destroyStack(Stack* stack) { Node* current = stack->top; while (current != NULL) { - Node *tmp = current; + Node* tmp = current; current = current->next; - free(tmp); + free(tmp); } } @@ -30,8 +30,13 @@ void push(Stack* stack, int data) { } void pop(Stack* stack) { + if (stack->top == NULL) { + return; + } Node* temp = stack->top; stack->top = stack->top->next; + free(temp); + temp = NULL; } Node* searchByValue(Stack* stack, int value) { @@ -40,6 +45,7 @@ Node* searchByValue(Stack* stack, int value) { if (current->data == value) { return current; } + current = current->next; } return NULL; } @@ -72,7 +78,5 @@ void traverseStack(Stack* stack) { } bool isEmpty(Stack* stack) { - free(stack->top); return stack->top == NULL; } - diff --git a/05_hw/stack.h b/05_hw/src/stack.h similarity index 94% rename from 05_hw/stack.h rename to 05_hw/src/stack.h index ef0734e..8228d4b 100644 --- a/05_hw/stack.h +++ b/05_hw/src/stack.h @@ -19,7 +19,7 @@ Node* createNode(int data); void initStack(Stack* stack); -void destroyStack(Stack *stack); +void destroyStack(Stack* stack); void push(Stack* stack, int data); diff --git a/05_hw/tests/CMakeLists.txt b/05_hw/tests/CMakeLists.txt new file mode 100644 index 0000000..c6701d0 --- /dev/null +++ b/05_hw/tests/CMakeLists.txt @@ -0,0 +1,61 @@ +# GTest include code would +# go somewhere in here + +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.15.2 + OVERRIDE_FIND_PACKAGE +) + +find_package(googletest REQUIRED) + +include(GoogleTest) + +add_executable(stack_test stack_test.cpp) + +find_program(GCOVR_EXECUTABLE gcovr) + +if(STACKLIB_COVERAGE AND GCOVR_EXECUTABLE) + message(STATUS "Измерение покрытия кода тестами включено") + + target_compile_options(stack_test PRIVATE --coverage) + target_compile_options(stack_lib PRIVATE --coverage) + + target_link_libraries(stack_test + PRIVATE + GTest::gtest_main + stack_lib + gcov + ) + + add_custom_target(coverage + COMMAND + ctest + --test-dir ${PROJECT_BINARY_DIR} + -T Test -T Coverage + COMMAND + lcov --directory ${PROJECT_BINARY_DIR}/src/ + --capture --output-file ${PROJECT_BINARY_DIR}/coverage.info + COMMAND + genhtml --demangle-cpp -o ${PROJECT_SOURCE_DIR}/coverage + ${PROJECT_BINARY_DIR}/coverage.info + ) +elseif(STACKLIB_COVERAGE AND NOT GCOVR_EXECUTABLE) + set(STACKLIB_COVERAGE OFF) + message(WARNING + "Для замеров покрытия кода тестами требуется программа gcovr") +endif() + +if(NOT STACKLIB_COVERAGE) + target_link_libraries(stack_test + PRIVATE + GTest::gtest_main + stack_lib + ) +endif() + +include(CTest) + +gtest_discover_tests(stack_test) diff --git a/05_hw/tests/stack_test.cpp b/05_hw/tests/stack_test.cpp new file mode 100644 index 0000000..49b0afb --- /dev/null +++ b/05_hw/tests/stack_test.cpp @@ -0,0 +1,109 @@ +#include +#include +#include + +class StackTest : public ::testing::Test { + protected: + Stack* stack = new Stack(); + + private: + void SetUp() override { + initStack(stack); + } + + void TearDown() override { + destroyStack(stack); + } +}; + +TEST_F(StackTest, isEmptyFunctionTest) { + EXPECT_TRUE(isEmpty(stack)); +} + +TEST_F(StackTest, PushFunctionTest) { + push(stack, 5); + EXPECT_EQ(getTop(stack)->data, 5); + push(stack, 7); + EXPECT_EQ(getTop(stack)->data, 7); + EXPECT_FALSE(isEmpty(stack)); +}; + +// Обработчик для SIGSEGV +void SignalHandler(int signal) { + throw std::runtime_error("Segmentation fault captured"); +} + +TEST_F(StackTest, PopFunctionTest) { + push(stack, 7); + push(stack, 5); + pop(stack); + EXPECT_EQ(getTop(stack)->data, 7); + pop(stack); + EXPECT_TRUE(isEmpty(stack)); + + std::signal(SIGSEGV, SignalHandler); + EXPECT_NO_THROW(pop(stack)); + + // Восстанавливаем стандартное поведение для сигнала после теста + std::signal(SIGSEGV, SIG_DFL); +}; + +TEST_F(StackTest, SearchByValueFunctionTest) { + push(stack, 7); + push(stack, 5); + push(stack, -1); + push(stack, 4); + push(stack, 8); + + EXPECT_EQ(getTop(stack)->data, 8); + + Node* node = searchByValue(stack, -1); + ASSERT_NE(node, nullptr); + EXPECT_EQ(node->data, -1); + + Node* null_node = searchByValue(stack, 3); + ASSERT_EQ(null_node, nullptr); + + Node* top_node = searchByValue(stack, 8); + ASSERT_NE(top_node, nullptr); + EXPECT_EQ(top_node->data, 8); + + Node* bottom_node = searchByValue(stack, 7); + ASSERT_NE(bottom_node, nullptr); + EXPECT_EQ(bottom_node->data, 7); +}; + +TEST_F(StackTest, SearchByIndexFunctionTest) { + push(stack, 7); + push(stack, 5); + push(stack, -1); + push(stack, 4); + push(stack, 8); + + EXPECT_EQ(getTop(stack)->data, 8); + + Node* node = searchByIndex(stack, 3); + ASSERT_NE(node, nullptr); + EXPECT_EQ(node->data, 5); + + Node* null_node = searchByIndex(stack, 9); + ASSERT_EQ(null_node, nullptr); + + Node* top_node = searchByIndex(stack, 0); + ASSERT_NE(top_node, nullptr); + EXPECT_EQ(top_node->data, 8); + + Node* bottom_node = searchByIndex(stack, 4); + ASSERT_NE(bottom_node, nullptr); + EXPECT_EQ(bottom_node->data, 7); +}; + +TEST_F(StackTest, TraverseStackFunctionTest) { + push(stack, 4); + push(stack, 7); + push(stack, 1); + testing::internal::CaptureStdout(); + traverseStack(stack); + std::string output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "Stack elements: 1 7 4 \n"); +}