diff --git a/05_hw/.gitignore b/05_hw/.gitignore new file mode 100644 index 0000000..a0a00be --- /dev/null +++ b/05_hw/.gitignore @@ -0,0 +1,2 @@ +.vs +out \ No newline at end of file diff --git a/05_hw/CMakeLists.txt b/05_hw/CMakeLists.txt index 6f5e398..53144e7 100644 --- a/05_hw/CMakeLists.txt +++ b/05_hw/CMakeLists.txt @@ -1,7 +1,23 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.12) project(stack) -add_library(stack stack.c) +add_library(stack src/stack.c) +add_library(stack::library ALIAS stack) -add_executable(main main.c) +add_executable(main "src/main.c") target_link_libraries(main stack) + +# sanitizer setup +if (NOT CMAKE_BUILD_TYPE STREQUAL "Release") + target_compile_options(main PRIVATE -fsanitize=address -MD) + target_link_options(main PRIVATE) +endif() + + +# tests setup +add_subdirectory(test) +enable_testing() +add_test(tests test/tests) + +# benchmark setup +add_subdirectory(benchmark) diff --git a/05_hw/CMakeSettings.json b/05_hw/CMakeSettings.json new file mode 100644 index 0000000..8901786 --- /dev/null +++ b/05_hw/CMakeSettings.json @@ -0,0 +1,26 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "clang_cl_x86" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "" + }, + { + "name": "x64-Clang-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x64" ] + } + ] +} \ No newline at end of file diff --git a/05_hw/README.md b/05_hw/README.md index 8661b04..d634921 100644 --- a/05_hw/README.md +++ b/05_hw/README.md @@ -5,13 +5,13 @@ Необходимо привести код в порядок. - * Исправить ошибки; - * Покрыть код тестами; - * Проверить покрытие кода тестами (убедиться, что все функции и условия протестированы); - * Выбрать статический анализатор и запустить его (исправить ошибки, если будут); - * Запустить тесты с санитайзерами (исправить ошибки, если будут); - * Для базовых функций (push / pop) написать бенчмарки; - * Изучить возможности Github Actions и настроить CI для тестирования (*). +- Исправить ошибки; +- Покрыть код тестами; +- Проверить покрытие кода тестами (убедиться, что все функции и условия протестированы); +- Выбрать статический анализатор и запустить его (исправить ошибки, если будут); +- Запустить тесты с санитайзерами (исправить ошибки, если будут); +- Для базовых функций (push / pop) написать бенчмарки; +- Изучить возможности Github Actions и настроить CI для тестирования (\*). Во время рефакторинга разрешается изменять не только код функций, но и их c сигнатуры, если это будет оправдано с точки зрения использования/тестирования этих функций. @@ -19,4 +19,4 @@ Решение - Pull Request с исправлением. Желательно разбивать изменения на атомарные коммиты с целью повышения качества ревью и ускорения получения фидбека. -(*) - необязательное задание. +(\*) - необязательное задание. diff --git a/05_hw/benchmark/CMakeLists.txt b/05_hw/benchmark/CMakeLists.txt new file mode 100644 index 0000000..40ef565 --- /dev/null +++ b/05_hw/benchmark/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.10) +project(benchmark) + +add_executable(benchmark benchmark.cpp) +target_link_libraries(benchmark PRIVATE stack::library) \ No newline at end of file diff --git a/05_hw/benchmark/benchmark.cpp b/05_hw/benchmark/benchmark.cpp new file mode 100644 index 0000000..6e44c10 --- /dev/null +++ b/05_hw/benchmark/benchmark.cpp @@ -0,0 +1,72 @@ +#include +#include +#include "../src/stack.h" + +class StackBenchmark +{ +public: + StackBenchmark(size_t limit) : limit(limit) + { + initStack(&stack); + } + + ~StackBenchmark() + { + destroyStack(&stack); + } + + void run() + { + for (int i = 1; i <= 5; ++i) + { + std::cout << "Run " << i << ":" << std::endl; + benchmarkPush(); + benchmarkPop(); + std::cout << std::endl; + } + } + +private: + Stack stack; + size_t limit; + + void benchmarkPush() + { + std::cout << " Pushing " << limit << " elements into the stack" << std::endl; + + auto start_time = std::chrono::steady_clock::now(); + for (size_t i = 0; i < limit; ++i) + { + push(&stack, 1); + } + auto end_time = std::chrono::steady_clock::now(); + + auto elapsed = std::chrono::duration_cast(end_time - start_time); + std::cout << " Push time: " << elapsed.count() << " ms" << std::endl; + } + + void benchmarkPop() + { + std::cout << " Popping " << limit << " elements from the stack" << std::endl; + + auto start_time = std::chrono::steady_clock::now(); + for (size_t i = 0; i < limit; ++i) + { + pop(&stack); + } + auto end_time = std::chrono::steady_clock::now(); + + auto elapsed = std::chrono::duration_cast(end_time - start_time); + std::cout << " Pop time: " << elapsed.count() << " ms" << std::endl; + } +}; + +int main() +{ + const size_t limit = 10000000; + + StackBenchmark benchmark(limit); + benchmark.run(); + + return 0; +} diff --git a/05_hw/main.c b/05_hw/src/main.c similarity index 54% rename from 05_hw/main.c rename to 05_hw/src/main.c index fded814..2531658 100644 --- a/05_hw/main.c +++ b/05_hw/src/main.c @@ -2,13 +2,17 @@ #include -int main() { +int main() +{ Stack stack; initStack(&stack); - push(&stack, 10); - push(&stack, 20); - push(&stack, 30); + const int first = 10; + const int second = 20; + const int third = 30; + push(&stack, first); + push(&stack, second); + push(&stack, third); printf("After pushing elements:\n"); traverseStack(&stack); @@ -18,19 +22,21 @@ int main() { printf("After popping an element:\n"); traverseStack(&stack); - Node* searchResult = searchByValue(&stack, 20); - if (searchResult != NULL) { + Node *searchResult = searchByValue(&stack, second); + if (searchResult != NULL) + { printf("Element with value 20 found.\n"); - } else { + } + else + { printf("Element with value 20 not found.\n"); } - Node* topElement = getTop(&stack); - if (topElement != NULL) { + Node *topElement = getTop(&stack); + if (topElement != NULL) + { printf("Top element: %d\n", topElement->data); } return 0; } - - diff --git a/05_hw/src/stack.c b/05_hw/src/stack.c new file mode 100644 index 0000000..810daff --- /dev/null +++ b/05_hw/src/stack.c @@ -0,0 +1,94 @@ +#include +#include + +#include "stack.h" + +Node *createNode(int data) +{ + Node *newNode = (Node *)malloc(sizeof(Node)); + newNode->data = data; + newNode->next = NULL; + return newNode; +} + +void initStack(Stack *stack) +{ + stack->top = NULL; +} + +void destroyStack(Stack *stack) +{ + Node *current = stack->top; + while (current != NULL) + { + Node *tmp = current; + current = current->next; + free(tmp); + } +} + +void push(Stack *stack, int data) +{ + Node *newNode = createNode(data); + newNode->next = stack->top; + stack->top = newNode; +} + +void pop(Stack *stack) +{ + Node *temp = stack->top; + stack->top = stack->top->next; + free(temp); +} + +Node *searchByValue(Stack *stack, int value) +{ + Node *current = stack->top; + while (current != NULL) + { + if (current->data == value) + { + return current; + } + current = current->next; + } + return NULL; +} + +Node *searchByIndex(Stack *stack, int index) +{ + Node *current = stack->top; + int count = 0; + while (current != NULL) + { + if (count == index) + { + return current; + } + count++; + current = current->next; + } + return NULL; +} + +Node *getTop(Stack *stack) +{ + return stack->top; +} + +void traverseStack(Stack *stack) +{ + Node *current = stack->top; + printf("Stack elements: "); + while (current != NULL) + { + printf("%d ", current->data); + current = current->next; + } + printf("\n"); +} + +bool isEmpty(Stack *stack) +{ + return stack->top == NULL; +} diff --git a/05_hw/src/stack.h b/05_hw/src/stack.h new file mode 100644 index 0000000..423931f --- /dev/null +++ b/05_hw/src/stack.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +typedef struct Node +{ + int data; + struct Node *next; +} Node; + +typedef struct Stack +{ + Node *top; +} Stack; + +#ifdef __cplusplus +extern "C" +{ +#endif + + Node *createNode(int data); + + void initStack(Stack *stack); + + void destroyStack(Stack *stack); + + void push(Stack *stack, int data); + + void pop(Stack *stack); + + Node *searchByValue(Stack *stack, int value); + + Node *searchByIndex(Stack *stack, int index); + + Node *getTop(Stack *stack); + + void traverseStack(Stack *stack); + + bool isEmpty(Stack *stack); + +#ifdef __cplusplus +} +#endif diff --git a/05_hw/stack.c b/05_hw/stack.c deleted file mode 100644 index 0964440..0000000 --- a/05_hw/stack.c +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include - -#include "stack.h" - -Node* createNode(int data) { - Node* newNode = (Node*)malloc(sizeof(Node)); - newNode->data = data; - newNode->next = NULL; - return newNode; -} - -void initStack(Stack* stack) { - stack->top = NULL; -} - -void destroyStack(Stack* stack) { - Node* current = stack->top; - while (current != NULL) { - Node *tmp = current; - current = current->next; - free(tmp); - } -} - -void push(Stack* stack, int data) { - Node* newNode = createNode(data); - newNode->next = stack->top; - stack->top = newNode; -} - -void pop(Stack* stack) { - Node* temp = stack->top; - stack->top = stack->top->next; -} - -Node* searchByValue(Stack* stack, int value) { - Node* current = stack->top; - while (current != NULL) { - if (current->data == value) { - return current; - } - } - return NULL; -} - -Node* searchByIndex(Stack* stack, int index) { - Node* current = stack->top; - int count = 0; - while (current != NULL) { - if (count == index) { - return current; - } - count++; - current = current->next; - } - return NULL; -} - -Node* getTop(Stack* stack) { - return stack->top; -} - -void traverseStack(Stack* stack) { - Node* current = stack->top; - printf("Stack elements: "); - while (current != NULL) { - printf("%d ", current->data); - current = current->next; - } - printf("\n"); -} - -bool isEmpty(Stack* stack) { - free(stack->top); - return stack->top == NULL; -} - diff --git a/05_hw/stack.h b/05_hw/stack.h deleted file mode 100644 index ef0734e..0000000 --- a/05_hw/stack.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include - -typedef struct Node { - int data; - struct Node* next; -} Node; - -typedef struct Stack { - Node* top; -} Stack; - -#ifdef __cplusplus -extern "C" { -#endif - -Node* createNode(int data); - -void initStack(Stack* stack); - -void destroyStack(Stack *stack); - -void push(Stack* stack, int data); - -void pop(Stack* stack); - -Node* searchByValue(Stack* stack, int value); - -Node* searchByIndex(Stack* stack, int index); - -Node* getTop(Stack* stack); - -void traverseStack(Stack* stack); - -bool isEmpty(Stack* stack); - -#ifdef __cplusplus -} -#endif diff --git a/05_hw/test/CMakeLists.txt b/05_hw/test/CMakeLists.txt new file mode 100644 index 0000000..c21a073 --- /dev/null +++ b/05_hw/test/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.12) # Минимальная версия CMake +project(tests) # Название проекта + +include(FetchContent) # Включаем поддержку загрузки контента + +# Загрузка и настройка googletest через FetchContent +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git # Репозиторий googletest + GIT_TAG v1.15.2 # Тег версии googletest + OVERRIDE_FIND_PACKAGE # Ожидаем, что googletest не будет найден локально +) + +FetchContent_MakeAvailable(googletest) # Загружаем googletest + +# Для Windows: заставляем использовать общий CRT для библиотеки googletest +if(WIN32) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +endif() + +# Создаем исполнимый файл для тестов, добавляя тестовые файлы и исходники стека +add_executable(tests + tests/test_stack.cpp + tests/test_smoke.cpp + ../src/stack.c) + +# Указываем директории с заголовочными файлами для тестов +target_include_directories(tests PRIVATE + ${CMAKE_SOURCE_DIR}/src # Директория с исходниками (stack.h и другие) + ${CMAKE_SOURCE_DIR}/test # Директория с тестами (например, stack_fixture.h) + ${CMAKE_SOURCE_DIR}/test # Здесь добавляем ещё раз, так как stack_fixture.h находится в той же директории +) + +# Линкуем googletest к исполнимому файлу тестов +target_link_libraries(tests PRIVATE gtest gtest_main) + +message("C++ Compiler ID: ${CMAKE_CXX_COMPILER_ID}") + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + message("Clang compiler detected: Enabling code coverage") + target_compile_options(tests PRIVATE -coverage -fprofile-instr-generate -fcoverage-mapping) + target_link_options(tests PRIVATE -coverage) + execute_process(COMMAND rm -f *.gcda WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/tests.dir/tests) + endif() +else() + message("Not a Debug build or unsupported compiler.") +endif() + +target_link_libraries(tests PRIVATE gtest gtest_main) \ No newline at end of file diff --git a/05_hw/test/tests/stack_fixture.h b/05_hw/test/tests/stack_fixture.h new file mode 100644 index 0000000..e11345d --- /dev/null +++ b/05_hw/test/tests/stack_fixture.h @@ -0,0 +1,26 @@ +#ifndef STACK_FIXTURE_H +#define STACK_FIXTURE_H + +#include // Для работы с Google Test +#include "../src/stack.h" // Подключаем заголовочный файл стека + +// Класс для создания тестового фикстуры для стека +class StackFixture : public ::testing::Test +{ +protected: + Stack stack; // Экземпляр стека + + // Конструктор инициализирует стек перед каждым тестом + StackFixture() + { + initStack(&stack); // Инициализация стека + } + + // Деструктор уничтожает стек после выполнения теста + ~StackFixture() override + { + destroyStack(&stack); // Уничтожение стека + } +}; + +#endif diff --git a/05_hw/test/tests/test_smoke.cpp b/05_hw/test/tests/test_smoke.cpp new file mode 100644 index 0000000..7851604 --- /dev/null +++ b/05_hw/test/tests/test_smoke.cpp @@ -0,0 +1,54 @@ +#include +#include "../src/stack.h" +#include "stack_fixture.h" // Подключаем StackFixture + +// Тест для проверки, что стек пустой после инициализации +TEST_F(StackFixture, TestStackInitialization) +{ + // Проверяем, что стек пустой после инициализации + EXPECT_TRUE(isEmpty(&stack)) << "Stack should be empty after initialization"; +} + +// Тест для проверки добавления элемента в стек +TEST_F(StackFixture, TestPushElement) +{ + push(&stack, 10); // Добавляем элемент в стек + + // Проверяем, что стек не пустой после добавления элемента + EXPECT_FALSE(isEmpty(&stack)) << "Stack should not be empty after pushing an element"; +} + +// Тест для проверки удаления элемента из стека +TEST_F(StackFixture, TestPopElement) +{ + push(&stack, 10); // Добавляем элемент в стек + pop(&stack); // Удаляем элемент из стека + + // Проверяем, что стек пустой после удаления единственного элемента + EXPECT_TRUE(isEmpty(&stack)) << "Stack should be empty after popping the only element"; +} + +// Тест для проверки нескольких операций push/pop +TEST_F(StackFixture, TestMultiplePushPop) +{ + // Добавляем несколько элементов в стек + push(&stack, 10); + push(&stack, 20); + push(&stack, 30); + + // Проверяем, что стек не пустой после добавления элементов + EXPECT_FALSE(isEmpty(&stack)) << "Stack should not be empty after pushing multiple elements"; + + // Удаляем два элемента из стека + pop(&stack); + pop(&stack); + + // Проверяем, что стек не пустой после удаления двух элементов + EXPECT_FALSE(isEmpty(&stack)) << "Stack should not be empty after popping two elements"; + + // Удаляем последний элемент + pop(&stack); + + // Проверяем, что стек пустой после удаления всех элементов + EXPECT_TRUE(isEmpty(&stack)) << "Stack should be empty after popping all elements"; +} diff --git a/05_hw/test/tests/test_stack.cpp b/05_hw/test/tests/test_stack.cpp new file mode 100644 index 0000000..1afc1f7 --- /dev/null +++ b/05_hw/test/tests/test_stack.cpp @@ -0,0 +1,107 @@ +#include +#include +#include +#include "stack_fixture.h" // Подключаем StackFixture + +// Тест для проверки корректности добавления элементов в стек +TEST_F(StackFixture, TestPush) +{ + push(&stack, 1); // Добавляем элемент в стек + push(&stack, 2); // Добавляем еще один элемент + + // Проверяем, что первый элемент в стеке - 2 + EXPECT_EQ(stack.top->data, 2); + // Проверяем, что второй элемент в стеке - 1 + EXPECT_EQ(stack.top->next->data, 1); +} + +// Тест для проверки корректности удаления элемента из стека +TEST_F(StackFixture, TestPop) +{ + push(&stack, 1); // Добавляем элемент в стек + push(&stack, 2); // Добавляем еще один элемент + pop(&stack); // Удаляем верхний элемент + + // Проверяем, что после удаления верхнего элемента стек имеет значение 1 + EXPECT_EQ(stack.top->data, 1); + // Проверяем, что следующий элемент в стеке равен nullptr (пусто) + EXPECT_EQ(stack.top->next, nullptr); +} + +// Тест для проверки правильности получения верхнего элемента стека +TEST_F(StackFixture, TestGetTop) +{ + push(&stack, 1); // Добавляем элемент в стек + push(&stack, 2); // Добавляем еще один элемент + + // Проверяем, что верхний элемент стека - это 2 + EXPECT_EQ(getTop(&stack)->data, 2); +} + +// Тест для проверки последовательности операций push и pop +TEST_F(StackFixture, TestPushPop) +{ + push(&stack, 1); // Добавляем элемент в стек + pop(&stack); // Удаляем элемент из стека + + // Проверяем, что после удаления элемента стек пустой + EXPECT_TRUE(isEmpty(&stack)); +} + +// Тест для проверки поиска элемента по значению в стеке +TEST_F(StackFixture, TestSearchByValue) +{ + push(&stack, 1); // Добавляем элемент в стек + push(&stack, -2); // Добавляем еще один элемент + push(&stack, 3); // Добавляем еще один элемент + + // Ищем элемент -2 в стеке + Node *current_found = searchByValue(&stack, -2); + // Ищем элемент 4, которого нет в стеке + Node *current_not_found = searchByValue(&stack, 4); + + // Проверяем, что элемент -2 найден и это второй элемент в стеке + EXPECT_EQ(current_found, stack.top->next); + // Проверяем, что элемент 4 не найден (возвращается nullptr) + EXPECT_EQ(current_not_found, nullptr); +} + +// Тест для проверки поиска элемента по индексу в стеке +TEST_F(StackFixture, TestSearchByIndex) +{ + push(&stack, 1); // Добавляем элемент в стек + push(&stack, -2); // Добавляем еще один элемент + push(&stack, 3); // Добавляем еще один элемент + + // Ищем элемент по индексу 1 + Node *current_found = searchByIndex(&stack, 1); + // Ищем элемент по индексу 3, который выходит за пределы стека + Node *current_not_found = searchByIndex(&stack, 3); + + // Проверяем, что элемент с индексом 1 найден (это второй элемент в стеке) + EXPECT_EQ(current_found, stack.top->next); + // Проверяем, что элемент с индексом 3 не найден (возвращается nullptr) + EXPECT_EQ(current_not_found, nullptr); +} + +// Тест для проверки корректности вывода всех элементов стека +TEST_F(StackFixture, TestTraverseStack) +{ + testing::internal::CaptureStdout(); // Перехватываем стандартный вывод + + push(&stack, 1); // Добавляем элементы в стек + push(&stack, -2); + push(&stack, 3); + push(&stack, -4); + push(&stack, 0); + + // Печатаем элементы стека + traverseStack(&stack); + + // Ожидаемый результат + std::string expected = "Stack elements: 0 -4 3 -2 1 \n"; + std::string output = testing::internal::GetCapturedStdout(); // Получаем перехваченный вывод + + // Проверяем, что вывод совпадает с ожидаемым + EXPECT_EQ(expected, output); +}