diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..94050f0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: C/C++ CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up CMake + uses: jwlawson/actions-setup-cmake@v1 + + - name: Install dependencies + run: sudo apt-get install -y gcc g++ cmake + + - name: Configure CMake + run: cmake -B build -S 05_hw -DCMAKE_C_FLAGS="-fsanitize=address,undefined,leak -g" + + - name: Build project + run: cmake --build build + + - name: Run tests + run: ./build/stack_tests diff --git a/05_hw/CMakeLists.txt b/05_hw/CMakeLists.txt index 6f5e398..ab08b28 100644 --- a/05_hw/CMakeLists.txt +++ b/05_hw/CMakeLists.txt @@ -1,7 +1,17 @@ cmake_minimum_required(VERSION 3.10) + project(stack) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage -g") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage -g") + add_library(stack stack.c) add_executable(main main.c) target_link_libraries(main stack) + +add_executable(stack_tests stack_tests.c stack.c) +target_link_libraries(stack_tests stack gcov) + +add_executable(benchmark benchmark.c stack.c) +target_link_libraries(benchmark stack gcov) diff --git a/05_hw/README.md b/05_hw/README.md index 8661b04..5509db0 100644 --- a/05_hw/README.md +++ b/05_hw/README.md @@ -1,22 +1,45 @@ -# Домашнее задание +# Отчет по лабораторной работе +**Выполнил студент Корязин Егор, группа М24-ИВТ-4** -Файлы stack.c и stack.h являются простой реализацией стека, хранящего целые числа. -В коде есть баги и ошибки. +## Ход работы -Необходимо привести код в порядок. +### 1. Исправить ошибки +Были исправлены ошибки в следующих функциях: +destroyStack, pop, searchByValue, isEmpty - * Исправить ошибки; - * Покрыть код тестами; - * Проверить покрытие кода тестами (убедиться, что все функции и условия протестированы); - * Выбрать статический анализатор и запустить его (исправить ошибки, если будут); - * Запустить тесты с санитайзерами (исправить ошибки, если будут); - * Для базовых функций (push / pop) написать бенчмарки; - * Изучить возможности Github Actions и настроить CI для тестирования (*). -Во время рефакторинга разрешается изменять не только код функций, но и их c сигнатуры, -если это будет оправдано с точки зрения использования/тестирования этих функций. -Решение - Pull Request с исправлением. Желательно разбивать изменения на атомарные коммиты -с целью повышения качества ревью и ускорения получения фидбека. +### 2. Покрыть код тестами +Все функции были покрыты тестами и проходят успешно: +![Все тесты прошли](/05_hw/images/sanitazier_not_error.jpg) + +### 3. Проверить покрытие кода тестами +Для оценки покрытия кода тестами был использован инструмент lcov. Как видно на изображении ниже, тесты покрывают все участки кода. + +![Отчет о покрытие тестами](/05_hw/images/coverage.jpg) + +### 4. Выбрать статический анализатор и запустить его +Для анализа кода был использован статический анализатор Cppcheck. Было обнаружено несколько предупреждений: + +![Статический анализатор 1](/05_hw/images/check_static_sanitazier.jpg) +![Статический анализатор 2](/05_hw/images/check_static_sanitazier_2.jpg) + +После исправления некоторых моментов, список предупреждений был уменьшен (с некоторыми замечаниями был не согласен, поэтому не исправил их): + +![Статический анализатор после исправлений](/05_hw/images/check_static_sanitazier_after_fix.jpg) + +### 5. Запустить тесты с санитайзерами +Были запущены тесты с использованием AddressSanitizer для обнаружения потенциальных ошибок работы с памятью. Тесты прошли без ошибок: + +![Нет ошибок от санитайзера](/05_hw/images/sanitazier_not_error.jpg) + +Чтобы убедиться в работоспособности санитайзера, в код была добавлена ошибка. AddressSanitizer корректно указал на ошибку, как видно на следующих изображениях: + +![Добавлена ошибка в код](/05_hw/images/special_error_in_code.png) +![Санитайзер обнаружил ошибку](/05_hw/images/special_error_sanitazier.png) + +### 6. Написание бенчмарков для функций push и pop +Для базовых функций push и pop были написаны бенчмарки, чтобы оценить их временную сложность. На графике видно, что сложность функций приблизительно равна O(N). + +![Результаты бенчмарка](/05_hw/images/bencmarking.jpg) -(*) - необязательное задание. diff --git a/05_hw/benchmark.c b/05_hw/benchmark.c new file mode 100644 index 0000000..bbdba5a --- /dev/null +++ b/05_hw/benchmark.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include "stack.h" + +#define NUM_ITERATIONS 10000000 + +void benchmark_push(Stack* stack, int num_operations, bool print) { + clock_t start = clock(); + + for (int i = 0; i < num_operations; i++) { + push(stack, i); + } + + clock_t end = clock(); + double time_spent = (double)(end - start) / CLOCKS_PER_SEC; + + if (print){ + printf("Time taken for %d push operations: %f seconds\n", num_operations, time_spent); + } +} + +void benchmark_pop(Stack* stack, int num_operations) { + clock_t start = clock(); + + for (int i = 0; i < num_operations; i++) { + pop(stack); + } + + clock_t end = clock(); + double time_spent = (double)(end - start) / CLOCKS_PER_SEC; + + printf("Time taken for %d pop operations: %f seconds\n", num_operations, time_spent); +} + +int main() { + Stack stack; + initStack(&stack); + + printf("Benchmarking push operation:\n"); + + for (int i = 1000; i <= NUM_ITERATIONS; i *= 10) { + benchmark_push(&stack, i, true); + + // Освобождаем стек после каждого теста + while (!isEmpty(&stack)) { + pop(&stack); + } + } + + // Заполняем стек для тестирования pop + benchmark_push(&stack, NUM_ITERATIONS, false); + + printf("\nBenchmarking pop operation:\n"); + + for (int i = 1000; i <= NUM_ITERATIONS; i *= 10) { + benchmark_pop(&stack, i); + + // Восстанавливаем стек после каждого теста + benchmark_push(&stack, i, false); + } + + destroyStack(&stack); + + return 0; +} \ No newline at end of file diff --git a/05_hw/images/bencmarking.jpg b/05_hw/images/bencmarking.jpg new file mode 100644 index 0000000..6b469c9 Binary files /dev/null and b/05_hw/images/bencmarking.jpg differ diff --git a/05_hw/images/check_static_sanitazier.jpg b/05_hw/images/check_static_sanitazier.jpg new file mode 100644 index 0000000..1799ce4 Binary files /dev/null and b/05_hw/images/check_static_sanitazier.jpg differ diff --git a/05_hw/images/check_static_sanitazier_2.jpg b/05_hw/images/check_static_sanitazier_2.jpg new file mode 100644 index 0000000..e972168 Binary files /dev/null and b/05_hw/images/check_static_sanitazier_2.jpg differ diff --git a/05_hw/images/check_static_sanitazier_after_fix.jpg b/05_hw/images/check_static_sanitazier_after_fix.jpg new file mode 100644 index 0000000..d7a489c Binary files /dev/null and b/05_hw/images/check_static_sanitazier_after_fix.jpg differ diff --git a/05_hw/images/coverage.jpg b/05_hw/images/coverage.jpg new file mode 100644 index 0000000..97ca834 Binary files /dev/null and b/05_hw/images/coverage.jpg differ diff --git a/05_hw/images/sanitazier_not_error.jpg b/05_hw/images/sanitazier_not_error.jpg new file mode 100644 index 0000000..3ef0d9d Binary files /dev/null and b/05_hw/images/sanitazier_not_error.jpg differ diff --git a/05_hw/images/special_error_in_code.png b/05_hw/images/special_error_in_code.png new file mode 100644 index 0000000..8f68eac Binary files /dev/null and b/05_hw/images/special_error_in_code.png differ diff --git a/05_hw/images/special_error_sanitazier.png b/05_hw/images/special_error_sanitazier.png new file mode 100644 index 0000000..60427d2 Binary files /dev/null and b/05_hw/images/special_error_sanitazier.png differ diff --git a/05_hw/stack.c b/05_hw/stack.c index 0964440..0cc3804 100644 --- a/05_hw/stack.c +++ b/05_hw/stack.c @@ -3,6 +3,7 @@ #include "stack.h" +// TODO: если будет не достаток памяти, может быть ошибка Node* createNode(int data) { Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = data; @@ -21,6 +22,7 @@ void destroyStack(Stack* stack) { current = current->next; free(tmp); } + stack->top = NULL; } void push(Stack* stack, int data) { @@ -31,19 +33,25 @@ void push(Stack* stack, int data) { void pop(Stack* stack) { Node* temp = stack->top; - stack->top = stack->top->next; + if (temp != NULL) { + 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; @@ -72,7 +80,6 @@ void traverseStack(Stack* stack) { } bool isEmpty(Stack* stack) { - free(stack->top); return stack->top == NULL; } diff --git a/05_hw/stack_tests.c b/05_hw/stack_tests.c new file mode 100644 index 0000000..8d55a81 --- /dev/null +++ b/05_hw/stack_tests.c @@ -0,0 +1,226 @@ +#include "stack.h" +#include +#include + +void test_pop() { + // Создаем стек: 30 - 20 - 10 + Stack stack; + initStack(&stack); + + push(&stack, 10); + push(&stack, 20); + push(&stack, 30); + + // Верхний элемент должен быть равен 30 + assert(getTop(&stack)->data == 30); + + // Удаляем верхний элемент, теперь стек выглядит так: 20 - 10 + pop(&stack); + + // Повторяем до тех пор пока стек не станет пустым + assert(getTop(&stack)->data == 20); + + pop(&stack); + assert(getTop(&stack)->data == 10); + + pop(&stack); + + // Проверяем, что стек пустой + assert(isEmpty(&stack) == true); + + destroyStack(&stack); + + printf("test_pop passed.\n"); +} + +void test_isEmpty() { + Stack stack; + initStack(&stack); + + // Новый стек пуст + assert(isEmpty(&stack) == true); + + // После добавление элемента, стек больше не пустой + push(&stack, 10); + assert(isEmpty(&stack) == false); + + // Удаление единственного элемента, и проверка что стек пуст + pop(&stack); + assert(isEmpty(&stack) == true); + + destroyStack(&stack); + + printf("test_isEmpty passed.\n"); +} + +void test_searchByValue() { + // Создаем стек: 30 - 20 - 10 + Stack stack; + initStack(&stack); + + push(&stack, 10); + push(&stack, 20); + push(&stack, 30); + + // Находим 20 в стеке (есть в стеке) + Node* result = searchByValue(&stack, 20); + assert(result != NULL && result->data == 20); + + // Находим 10 в стеке (есть в стеке) + result = searchByValue(&stack, 10); + assert(result != NULL && result->data == 10); + + // Находим 10 в стеке (его нет в стеке) + assert(searchByValue(&stack, 40) == NULL); + + destroyStack(&stack); + + printf("test_searchByValue passed.\n"); +} + +void test_createNode() { + // Создаем узел: 30 + Node* node = createNode(30); + + // Проверяем, что он не null, в нем лежит значение 30 и следующего элемента нет + assert(node != NULL); + assert(node->data == 30); + assert(node->next == NULL); + + free(node); + + printf("test_createNode passed.\n"); +} + +void test_initStack() { + // Создаем стек и проверяем что он пустой + Stack stack; + initStack(&stack); + + assert(isEmpty(&stack) == true); + + printf("test_initStack passed.\n"); +} + +void test_destroyStack() { + // Создаем стек: 30 - 20 - 10 + Stack stack; + initStack(&stack); + + push(&stack, 10); + push(&stack, 20); + push(&stack, 30); + + // Уничтожаем его + destroyStack(&stack); + + // Проверяем что стек пустой + assert(isEmpty(&stack) == true); + + printf("test_destroyStack passed.\n"); +} + +void test_push() { + // Создаем стек + Stack stack; + initStack(&stack); + + // Добавляем в него 10, и проверяем что этот элемент находится сверху + push(&stack, 10); + assert(getTop(&stack)->data == 10); + + // Добавляем в него 20, и проверяем что этот элемент находится сверху + push(&stack, 20); + assert(getTop(&stack)->data == 20); + + destroyStack(&stack); + + printf("test_push passed.\n"); +} + +void test_searchByIndex() { + // Создаем стек: 30 - 20 - 10 + Stack stack; + initStack(&stack); + + push(&stack, 10); + push(&stack, 20); + push(&stack, 30); + + // Проверяем, что на индексе 0 находится элемент 30 (вершина стека) + Node* result = searchByIndex(&stack, 0); + assert(result != NULL && result->data == 30); + + // Проверяем, что на индексе 2 находится элемент 10 + result = searchByIndex(&stack, 2); + assert(result != NULL && result->data == 10); + + // Проверяем, что индекс, выходящий за пределы, возвращает NULL + assert(searchByIndex(&stack, 3) == NULL); + + destroyStack(&stack); + + printf("test_searchByIndex passed.\n"); +} + +void test_getTop() { + // Создаем стек + Stack stack; + initStack(&stack); + + // Добавляем 10 и проверяем что оно сверху + push(&stack, 10); + assert(getTop(&stack)->data == 10); + + // Добавляем 20 и проверяем что оно сверху + push(&stack, 20); + assert(getTop(&stack)->data == 20); + + // Удаляем 20, и проверяем что 10 снова сверху + pop(&stack); + assert(getTop(&stack)->data == 10); + + destroyStack(&stack); + + printf("test_getTop passed.\n"); +} + +void test_traverseStack() { + // Создаем стек: 30 - 20 - 10 + Stack stack; + initStack(&stack); + + push(&stack, 10); + push(&stack, 20); + push(&stack, 30); + + // Ожидаем, что будет выведено: 30 20 10 + printf("Expected output: 30 20 10\n"); + printf("Actual output: "); + + traverseStack(&stack); + + destroyStack(&stack); + + printf("test_traverseStack passed.\n"); +} + + + +int main() { + test_isEmpty(); + test_pop(); + test_searchByValue(); + test_createNode(); + test_initStack(); + test_push(); + test_searchByIndex(); + test_getTop(); + test_traverseStack(); + + test_destroyStack(); + + printf("All tests passed.\n"); + + return 0; +}