diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..72e6059 Binary files /dev/null and b/.DS_Store differ diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..926aa8c --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,5 @@ +coverage: + status: + patch: + default: + target:80% diff --git a/.gitignore b/.gitignore index 054c7d1..512b5f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,9 @@ - -# CMake project/build/ CMakeFiles/ CMakeCache.txt cmake-build-debug/ +cmake-build-debug-coverage/ *.cmake - - .idea/ +.DS_Store diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5b268ab --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +dist: focal + +language: c + +os: linux + +compiler: gcc + +install: + - sudo apt-get install valgrind + - sudo apt-get install clang + - sudo apt-get install cppcheck + - sudo pip install cpplint + +script: + - cd project/ + - mkdir build + - cd build + - cmake .. + - make clean && make + - cppcheck --inconclusive --enable=all --language=c ../include/*.h ../src/*.c + - cpplint ../include/*.h ../src/*.c ../tests/*.cpp + - valgrind --leak-check=full --track-origins=yes ./main.out ../db.txt ../find.txt < ../find.txt + - valgrind --leak-check=full --track-origins=yes ./cars_tests + + + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/CPPLINT.cfg b/CPPLINT.cfg new file mode 100644 index 0000000..55446fa --- /dev/null +++ b/CPPLINT.cfg @@ -0,0 +1,4 @@ +filter=-legal/copyright +filter=-build/include_subdir +filter=-build/include +filter=-readability/casting \ No newline at end of file diff --git a/README.md b/README.md index c246e5e..0652c85 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,20 @@ # cpp_2021 +[![Build Status](https://travis-ci.com/BorisKoz/cpp_2021.svg?branch=HW-1)](https://travis-ci.com/BorisKoz/cpp_2021) + +[![codecov](https://codecov.io/gh/BorisKoz/cpp_2021/branch/HW-1/graph/badge.svg?token=QT0UXBZB7L)](https://codecov.io/gh/BorisKoz/cpp_2021) + + +# Вариант #4 +Создать структуру для хранения характеристик автомобилей: мощности двигателя, скорости, расхода топлива, формы кузова и модели. Составить с ее использованием программу поиска модели автомобиля, характеристики которой наиболее удовлетворяют запросу. + +# Требования к оформлению: +Программа должна быть реализована на языке C и работать для произвольных наборов входных данных (в том числе и ошибочных), вводимых пользователем с клавиатуры. Должна быть выполнена грамотная декомпозиция на функции и файлы. +Помимо самой программы необходимо: +– разработать набор юнит-тестов, проверяющих корректную работу реализованных функций. Обеспечить максимальное покрытие исходного кода тестами; +– оформить задачу в виде Merge Request отдельной ветки в основную ветку проекта. +Внимание: в основной ветке проекта никакого кода быть не должно! +– развернуть CI, в рамках которого автоматизировать сборку проекта, прохождение юнит-тестов под valgrind, генерацию отчёта о покрытии кода тестами, линтера и статического анализатора исходного кода; +– после прохождения всех проверок необходимо отправить код на ревью своему преподавателю; +– ревью - процесс итерационный. До наступления дедлайна можно проходить несколько итераций, улучшая свою оценку. Решения после дедлайна не принимаются; diff --git a/project/.DS_Store b/project/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/project/.DS_Store differ diff --git a/project/CMakeLists.txt b/project/CMakeLists.txt new file mode 100644 index 0000000..2a3c999 --- /dev/null +++ b/project/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.15) +project(cars) + +configure_file(CMakeLists.txt.in + googletest-download/CMakeLists.txt) +execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download ) +execute_process(COMMAND ${CMAKE_COMMAND} --build . + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download ) + +add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src + ${CMAKE_BINARY_DIR}/googletest-build) + +set(CMAKE_C_FLAGS "-pedantic -fprofile-arcs -ftest-coverage") +set(CMAKE_CXX_FLAGS "-pedantic -fprofile-arcs -ftest-coverage") + +enable_testing() + + +include_directories("${PROJECT_SOURCE_DIR}/include") + + +file(GLOB prod_sources + "${PROJECT_SOURCE_DIR}/include/*.h" + "${PROJECT_SOURCE_DIR}/src/cars.c" + "${PROJECT_SOURCE_DIR}/src/main.c") + +add_executable(main.out ${prod_sources}) + +file(GLOB test_sources "${PROJECT_SOURCE_DIR}/src/*.c") +list(REMOVE_ITEM test_sources "${PROJECT_SOURCE_DIR}/src/main.c") + +file(GLOB tests "${PROJECT_SOURCE_DIR}/tests/*.cpp") +list(REMOVE_ITEM tests "${PROJECT_SOURCE_DIR}/tests/main.cpp") + +foreach(file ${tests}) + set(name) + get_filename_component(name ${file} NAME_WE) + add_executable("${name}_tests" + ${test_sources} + ${file} + "${PROJECT_SOURCE_DIR}/tests/main.cpp") + target_link_libraries("${name}_tests" gtest_main) + add_test(NAME ${name} COMMAND "${name}_tests") +endforeach() \ No newline at end of file diff --git a/project/CMakeLists.txt.in b/project/CMakeLists.txt.in new file mode 100644 index 0000000..0feb5f3 --- /dev/null +++ b/project/CMakeLists.txt.in @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.15) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.8.1 + SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src" + BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" + ) \ No newline at end of file diff --git a/project/Makefile b/project/Makefile new file mode 100644 index 0000000..53d61ad --- /dev/null +++ b/project/Makefile @@ -0,0 +1,227 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 3.19 + +# Default target executed when no arguments are given to make. +default_target: all + +.PHONY : default_target + +# Allow only one "make -f Makefile2" at a time, but pass parallelism. +.NOTPARALLEL: + + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + + +# Disable VCS-based implicit rules. +% : %,v + + +# Disable VCS-based implicit rules. +% : RCS/% + + +# Disable VCS-based implicit rules. +% : RCS/%,v + + +# Disable VCS-based implicit rules. +% : SCCS/s.% + + +# Disable VCS-based implicit rules. +% : s.% + + +.SUFFIXES: .hpux_make_needs_suffix_list + + +# Command-line flag to silence nested $(MAKE). +$(VERBOSE)MAKESILENT = -s + +#Suppress display of executed commands. +$(VERBOSE).SILENT: + +# A target that is always out of date. +cmake_force: + +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /usr/local/Cellar/cmake/3.19.6/bin/cmake + +# The command to remove a file. +RM = /usr/local/Cellar/cmake/3.19.6/bin/cmake -E rm -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /Users/boriskozuro/Desktop/tp_c_2021/project + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /Users/boriskozuro/Desktop/tp_c_2021/project + +#============================================================================= +# Targets provided globally by CMake. + +# Special rule for the target rebuild_cache +rebuild_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." + /usr/local/Cellar/cmake/3.19.6/bin/cmake --regenerate-during-build -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : rebuild_cache + +# Special rule for the target rebuild_cache +rebuild_cache/fast: rebuild_cache + +.PHONY : rebuild_cache/fast + +# Special rule for the target edit_cache +edit_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake cache editor..." + /usr/local/Cellar/cmake/3.19.6/bin/ccmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : edit_cache + +# Special rule for the target edit_cache +edit_cache/fast: edit_cache + +.PHONY : edit_cache/fast + +# The main all target +all: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /Users/boriskozuro/Desktop/tp_c_2021/project/CMakeFiles /Users/boriskozuro/Desktop/tp_c_2021/project//CMakeFiles/progress.marks + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 all + $(CMAKE_COMMAND) -E cmake_progress_start /Users/boriskozuro/Desktop/tp_c_2021/project/CMakeFiles 0 +.PHONY : all + +# The main clean target +clean: + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 clean +.PHONY : clean + +# The main clean target +clean/fast: clean + +.PHONY : clean/fast + +# Prepare targets for installation. +preinstall: all + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 preinstall +.PHONY : preinstall + +# Prepare targets for installation. +preinstall/fast: + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 preinstall +.PHONY : preinstall/fast + +# clear depends +depend: + $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 +.PHONY : depend + +#============================================================================= +# Target rules for targets named test1 + +# Build rule for target. +test1: cmake_check_build_system + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 test1 +.PHONY : test1 + +# fast build rule for target. +test1/fast: + $(MAKE) $(MAKESILENT) -f CMakeFiles/test1.dir/build.make CMakeFiles/test1.dir/build +.PHONY : test1/fast + +src/cars.o: src/cars.c.o + +.PHONY : src/cars.o + +# target to build an object file +src/cars.c.o: + $(MAKE) $(MAKESILENT) -f CMakeFiles/test1.dir/build.make CMakeFiles/test1.dir/src/cars.c.o +.PHONY : src/cars.c.o + +src/cars.i: src/cars.c.i + +.PHONY : src/cars.i + +# target to preprocess a source file +src/cars.c.i: + $(MAKE) $(MAKESILENT) -f CMakeFiles/test1.dir/build.make CMakeFiles/test1.dir/src/cars.c.i +.PHONY : src/cars.c.i + +src/cars.s: src/cars.c.s + +.PHONY : src/cars.s + +# target to generate assembly for a file +src/cars.c.s: + $(MAKE) $(MAKESILENT) -f CMakeFiles/test1.dir/build.make CMakeFiles/test1.dir/src/cars.c.s +.PHONY : src/cars.c.s + +src/main.o: src/main.c.o + +.PHONY : src/main.o + +# target to build an object file +src/main.c.o: + $(MAKE) $(MAKESILENT) -f CMakeFiles/test1.dir/build.make CMakeFiles/test1.dir/src/main.c.o +.PHONY : src/main.c.o + +src/main.i: src/main.c.i + +.PHONY : src/main.i + +# target to preprocess a source file +src/main.c.i: + $(MAKE) $(MAKESILENT) -f CMakeFiles/test1.dir/build.make CMakeFiles/test1.dir/src/main.c.i +.PHONY : src/main.c.i + +src/main.s: src/main.c.s + +.PHONY : src/main.s + +# target to generate assembly for a file +src/main.c.s: + $(MAKE) $(MAKESILENT) -f CMakeFiles/test1.dir/build.make CMakeFiles/test1.dir/src/main.c.s +.PHONY : src/main.c.s + +# Help Target +help: + @echo "The following are some of the valid targets for this Makefile:" + @echo "... all (the default if no target is provided)" + @echo "... clean" + @echo "... depend" + @echo "... edit_cache" + @echo "... rebuild_cache" + @echo "... test1" + @echo "... src/cars.o" + @echo "... src/cars.i" + @echo "... src/cars.s" + @echo "... src/main.o" + @echo "... src/main.i" + @echo "... src/main.s" +.PHONY : help + + + +#============================================================================= +# Special targets to cleanup operation of make. + +# Special rule to run CMake to check the build system integrity. +# No rule that depends on this can have commands that come from listfiles +# because they might be regenerated. +cmake_check_build_system: + $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 +.PHONY : cmake_check_build_system + diff --git a/project/db.txt b/project/db.txt new file mode 100644 index 0000000..678a588 --- /dev/null +++ b/project/db.txt @@ -0,0 +1,4 @@ +150 200 2 Renault_Logan Jeep +100 100 2 Toyota_Camry Sedan +100 100 1 Toyota_Camry Sedan +100 120 2 Renault_Sanderas Sedan \ No newline at end of file diff --git a/project/find.txt b/project/find.txt new file mode 100644 index 0000000..7e2ab0a --- /dev/null +++ b/project/find.txt @@ -0,0 +1 @@ +100 100 1 Toyota_Camry Sedan \ No newline at end of file diff --git a/project/include/cars.h b/project/include/cars.h new file mode 100644 index 0000000..939496d --- /dev/null +++ b/project/include/cars.h @@ -0,0 +1,50 @@ +// Copyright 2021 + +#include +#include + +#ifndef PROJECT_CARS_H // NOLINT +#define PROJECT_CARS_H // NOLINT + +// define exceptions +#define NULLPTR_EX 1 +#define INCORRECT_ENTRY 2 +#define ALLOCATE_ERROR 3 +#define OUTPUT_ERROR 4 +// define logic +#define EOF_REACHED (-1) +#define PARAM_NUMBER 5 + +// define buffer size +#define SIZE_BUF 40 +#define SCAN_FORMAT "%39s" + +// define parameter indexes in array +#define ENGINE_POW 0 +#define MAX_V 1 +#define FUEL 2 +#define MODEL_NAME 3 +#define BODY_TYPE 4 + +typedef struct { + float engine_power; + float maximum_velocity; + float fuel_consumption; + char* model_name; + char* body_type; +} car; + + +// open file +int open_car_database(FILE** db_ptr, const char* basename); +int read_car_instance(FILE* db_ptr, car *car_read); +int print_car_instance(const car* car_print); +float comparison(const car* car_1, const car* car_2); +int free_car(car* car_1); +int copy_car(car* dest, car* src); +int search_in_base(car* input_car, car* found_car, FILE* db); +int error_out(int err_code); + + + +#endif // PROJECT_CARS_H // NOLINT diff --git a/project/include/cars_logic.h b/project/include/cars_logic.h new file mode 100644 index 0000000..a1ff65e --- /dev/null +++ b/project/include/cars_logic.h @@ -0,0 +1,10 @@ +// Copyright 2021 + +#ifndef PROJECT_INCLUDE_CARS_LOGIC_H_ +#define PROJECT_INCLUDE_CARS_LOGIC_H_ + +int min_of_3(int i, int i1, int i2); +float string_distance(const char* a, const char* b); +float distance_fl(float a, float b); + +#endif // PROJECT_INCLUDE_CARS_LOGIC_H_ diff --git a/project/src/cars.c b/project/src/cars.c new file mode 100644 index 0000000..55ed6fe --- /dev/null +++ b/project/src/cars.c @@ -0,0 +1,198 @@ +// Copyright 2021 + +#include "cars.h" +#include "cars_logic.h" +#include + +// open database file. check if not null. +int open_car_database(FILE** db_ptr, const char* basename) { + if (!db_ptr) { + return NULLPTR_EX; + } + *db_ptr = fopen(basename, "r+"); + if (*db_ptr == NULL) { + fprintf(stderr, "Could not open file %s\n", basename); + return NULLPTR_EX; + } + return 0; +} + +// read next car instance from base. +int read_car_instance(FILE* db_ptr, car *car_read) { + if (db_ptr != NULL && car_read != NULL) { + char read_buffer[PARAM_NUMBER][SIZE_BUF]= {"", "", "", "", ""}; + for (int i = 0; i < PARAM_NUMBER; i++) { + if (fscanf(db_ptr, SCAN_FORMAT, read_buffer[i]) != 1) { + return INCORRECT_ENTRY; + } + } + car_read->engine_power = strtof(read_buffer[ENGINE_POW], NULL); + car_read->maximum_velocity = strtof(read_buffer[MAX_V], NULL); + car_read->fuel_consumption = strtof(read_buffer[FUEL], NULL); + if (car_read->engine_power <= 0 || car_read->maximum_velocity <= 0 + || car_read->fuel_consumption <= 0) { + return INCORRECT_ENTRY; + } + free_car(car_read); + car_read->model_name = strdup(read_buffer[MODEL_NAME]); + if (!car_read->model_name) { + return ALLOCATE_ERROR; + } + car_read->body_type = strdup(read_buffer[BODY_TYPE]); + if (!car_read->body_type) { + free(car_read->model_name); + return ALLOCATE_ERROR; + } + if (feof(db_ptr)) { + return EOF_REACHED; + } + return 0; + } + return NULLPTR_EX; +} + +// output car instance +int print_car_instance(const car* car_print) { + if (printf ("EP:%f V:%f FC%f Model:%s BT:%s \n", car_print->engine_power, + car_print->maximum_velocity, car_print->fuel_consumption, + car_print->model_name, car_print->body_type) > 0) + return 0; + // not reached by codecov cuz it prints + return OUTPUT_ERROR; +} + +// float comparison in division. the closer they are, +// the more return approaches 1 +float distance_fl(float a, float b) { + return (a > b ? b / a : a / b); +} + +// levenshtein distance in c algorithm +float string_distance(const char* a, const char* b) { + if (!a || !b) { + return 0; + } + size_t x = 0, y = 0, len_a = strlen(a), len_b = strlen(b); + int matrix[SIZE_BUF + 1][SIZE_BUF + 1]; + // matrix initializer + memset(matrix, 0, sizeof(matrix)); + matrix[0][0] = 0; + for (x = 1; x <= len_b; x++) + matrix[x][0] = matrix[x-1][0]+1; + for (y = 1; y <= len_a; y++) + matrix[0][y] = matrix[0][y]+1; + for (x = 1; x <= len_b; x++) + for (y = 1; y <= len_a; y++) + matrix[x][y] = min_of_3(matrix[x - 1][y] + 1, matrix[x][y - 1] + 1, + matrix[x - 1][y - 1] + + (a[y - 1] == b[x - 1] ? 0 : 1)); + return 1-((float)matrix[len_b][len_a])/len_a; +} + +// min_of_3 of 3 for levenshtein +int min(int a, int b) { + return (a < b ? a : b); +} +int min_of_3(int i, int i1, int i2) { + return min(i, min(i1, i2)); +} + +// comparison of 2 cars. returns 5 if completely equal +float comparison(const car* car_1, const car* car_2) { + float equality = 0; + equality += distance_fl(car_1->maximum_velocity, car_2->maximum_velocity); + equality += distance_fl(car_1->engine_power, car_2->engine_power); + equality += distance_fl(car_1->fuel_consumption, car_2->fuel_consumption); + equality += string_distance(car_1->model_name, car_2->model_name); + equality += string_distance(car_1->body_type, car_2->body_type); + return equality; +} + +// free car strings +int free_car(car* car_1) { + if (car_1 != NULL) { + if (car_1->body_type) { + free(car_1->body_type); + car_1->body_type = NULL; + } + if (car_1->model_name) { + free(car_1->model_name); + car_1->model_name = NULL; + } + return 0; + } + return NULLPTR_EX; +} + +// copy existing car instance +int copy_car(car* dest, car* src) { + if (!dest || !src) { + return NULLPTR_EX; + } + free_car(dest); + dest->fuel_consumption = src->fuel_consumption; + dest->engine_power = src->engine_power; + dest->maximum_velocity = src->maximum_velocity; + dest->model_name = strdup(src->model_name); + if (!dest->model_name) { + return ALLOCATE_ERROR; + } + dest->body_type = strdup(src->body_type); + if (!dest->body_type) { + free(dest->model_name); + return ALLOCATE_ERROR; + } + return 0; +} + + +int error_out(int err_code) { + switch (err_code) { + case NULLPTR_EX: + fprintf(stderr, "%s", "NULL POINTER!\n"); + return NULLPTR_EX; + case INCORRECT_ENTRY: + fprintf(stderr, "%s", "INCORRECT INPUT\n"); + return INCORRECT_ENTRY; + case ALLOCATE_ERROR: + fprintf(stderr, "%s", "ALLOCATION FAULT\n"); + return ALLOCATE_ERROR; + case OUTPUT_ERROR: + fprintf(stderr, "%s", "INCORRECT OUTPUT\n"); + return OUTPUT_ERROR; + default: + return 0; + } +} + +int search_in_base(car* input_car, car* found_car, FILE* db) { + int return_code = 0; + car* comparison_car = (car*)calloc(1, sizeof(car)); + if (comparison_car == NULL) { + return ALLOCATE_ERROR; + } + float max_equality = 0; + while (return_code == 0) { + return_code = read_car_instance(db, comparison_car); + if (return_code > 0) { + return ALLOCATE_ERROR; + } + float current_equality = comparison(input_car, comparison_car); + if (max_equality < current_equality) { + max_equality = current_equality; + if (copy_car(found_car, comparison_car) != 0) { + return_code = ALLOCATE_ERROR; + free_car(comparison_car); + break; + } + } + free_car(comparison_car); + if (max_equality == PARAM_NUMBER) { + break; + } + } + free(comparison_car); + return return_code; +} + + diff --git a/project/src/main.c b/project/src/main.c new file mode 100644 index 0000000..2dcd31e --- /dev/null +++ b/project/src/main.c @@ -0,0 +1,69 @@ +// Copyright 2021 +#include +#include +#include "cars.h" + + +int main(int argc, char *argv[]) { + if (argc != 3) { + return INCORRECT_ENTRY; + } + car* input_car = (car*)calloc(1, sizeof(car)); + if (!input_car) { + return ALLOCATE_ERROR; + } + car* found_car = (car*)calloc(1, sizeof(car)); + if (!found_car) { + free(input_car); + return ALLOCATE_ERROR; + } + FILE* db = NULL, * search = NULL; + int return_code = 0; + while (true) { + // open fault check + if (open_car_database(&db, argv[1]) != 0 || + open_car_database(&search, argv[2]) != 0) { + error_out(ALLOCATE_ERROR); + break; + } + + // write to file for search + char read_buffer[SIZE_BUF] = {""}; + for (int i = 0; i < PARAM_NUMBER; i++) { + if (scanf(SCAN_FORMAT , read_buffer) != 1) { + error_out(INCORRECT_ENTRY); + break; + } else { + fputs(read_buffer, search); + if (i < PARAM_NUMBER - 1) { + fputs(" ", search); + } + } + } + rewind(search); + + // search itself + return_code = read_car_instance(search, input_car); + if (return_code > 0) + break; + return_code = search_in_base(input_car, found_car, db); + if (return_code <= 0) { + print_car_instance(found_car); + return_code = 0; + } + break; + } + // free all the memory + error_out(return_code); + free_car(found_car); + free_car(input_car); + free(input_car); + free(found_car); + if (db) { + fclose(db); + } + if (search) { + fclose(search); + } + return return_code; +} diff --git a/project/tests/1.txt b/project/tests/1.txt new file mode 100644 index 0000000..4cc3520 --- /dev/null +++ b/project/tests/1.txt @@ -0,0 +1 @@ +1 10 a Toyota Sedan \ No newline at end of file diff --git a/project/tests/2.txt b/project/tests/2.txt new file mode 100644 index 0000000..d5b108b --- /dev/null +++ b/project/tests/2.txt @@ -0,0 +1,2 @@ +1 10 1 Toyota Sedan +1 10 a Toyota \ No newline at end of file diff --git a/project/tests/3.txt b/project/tests/3.txt new file mode 100644 index 0000000..34c8d05 --- /dev/null +++ b/project/tests/3.txt @@ -0,0 +1,2 @@ +1 10 1 Toyota +1 10 1 Toyota Sedan \ No newline at end of file diff --git a/project/tests/4.txt b/project/tests/4.txt new file mode 100644 index 0000000..fad55b7 --- /dev/null +++ b/project/tests/4.txt @@ -0,0 +1,2 @@ +100 200 2 Renault_Logan Jeep +150 200 2 Renault_Logan Jeep \ No newline at end of file diff --git a/project/tests/5.txt b/project/tests/5.txt new file mode 100644 index 0000000..8c6400f --- /dev/null +++ b/project/tests/5.txt @@ -0,0 +1 @@ +150 200 2 Renault_Logan Jeeps \ No newline at end of file diff --git a/project/tests/CMakeLists.txt b/project/tests/CMakeLists.txt new file mode 100644 index 0000000..147be82 --- /dev/null +++ b/project/tests/CMakeLists.txt @@ -0,0 +1,22 @@ + +include_directories("${PROJECT_SOURCE_DIR}/include") + +set(CMAKE_C_FLAGS "-pedantic -fprofile-arcs -ftest-coverage") +set(CMAKE_CXX_FLAGS "-pedantic -fprofile-arcs -ftest-coverage") + +file(GLOB sources "${PROJECT_SOURCE_DIR}/src/*.c") +list(REMOVE_ITEM sources "${PROJECT_SOURCE_DIR}/src/main.c") + +file(GLOB tests "${PROJECT_SOURCE_DIR}/tests/*.cpp") +list(REMOVE_ITEM tests "${PROJECT_SOURCE_DIR}/tests/main.cpp") + +foreach(file ${tests}) + set(name) + get_filename_component(name ${file} NAME_WE) + add_executable("${name}_tests" + ${sources} + ${file} + "${PROJECT_SOURCE_DIR}/tests/main.cpp") + target_link_libraries("${name}_tests" gtest_main) + add_test(NAME ${name} COMMAND "${name}_tests") +endforeach() \ No newline at end of file diff --git a/project/tests/cars.cpp b/project/tests/cars.cpp new file mode 100644 index 0000000..4e4ebe4 --- /dev/null +++ b/project/tests/cars.cpp @@ -0,0 +1,203 @@ +// Copyright 2021 +#include "gtest/gtest.h" + +extern "C" { +#include "cars.h" +#include "cars_logic.h" +} + +#define TEST_TXT "./test.txt" +#define TEST_DB_TXT "./test_db.txt" + +// tests for in-program logic +TEST(min_of_3, correct_minimum) { + ASSERT_EQ(min_of_3(1, 2, 3), 1); + ASSERT_EQ(min_of_3(1, 3, 2), 1); + ASSERT_EQ(min_of_3(2, 1, 3), 1); + ASSERT_EQ(min_of_3(3, 1, 2), 1); + ASSERT_EQ(min_of_3(2, 3, 1), 1); + ASSERT_EQ(min_of_3(3, 2, 1), 1); +} +TEST(string_distance, correct_levenstein_distance) { + EXPECT_EQ(string_distance("Toyota", "Toyota"), 1); + EXPECT_EQ(string_distance("Toyota", "Toy"), 0.5); + EXPECT_EQ(string_distance("Toyota", "Renaul"), 0); + EXPECT_EQ(string_distance("Toyota", nullptr), 0); +} +TEST(distance_fl, correct_division) { + EXPECT_EQ(distance_fl(1, 2), 0.5); + EXPECT_EQ(distance_fl(4, 1), 0.25); +} + + +// tests for memory functions + +TEST(open_car_database, db_is_opened_correctly) { + FILE* pointer; + pointer = fopen(TEST_TXT, "a"); + fclose(pointer); + EXPECT_EQ(open_car_database(&pointer, TEST_TXT), 0); + fclose(pointer); + EXPECT_EQ(open_car_database(&pointer, "search.txt"), NULLPTR_EX); + remove(TEST_TXT); +} + +TEST(free_car, memory_is_freed_correctly) { + car* pointer = NULL; + car car_a = {100, 100, 1, NULL, NULL}; + char buffer[SIZE_BUF] = "test_string"; + car_a.body_type = strdup(buffer); + car_a.model_name = strdup(buffer); + EXPECT_EQ(free_car(pointer), NULLPTR_EX); + EXPECT_EQ(free_car(&car_a), 0); +} + + + +TEST(copy_car, stop_copy_if_null) { + car car_a = {100, 100, 1, (char*)"Toyota", (char*)"Sedan"}; + car* newcar = nullptr; + EXPECT_EQ(copy_car(newcar, &car_a), NULLPTR_EX); +} + +TEST(copy_car, copy_is_correct) { + car car_a = {100, 100, 1, (char*)"Toyota", (char*)"Sedan"}; + car newcar = {200, 0, 2, nullptr, nullptr}; + EXPECT_EQ(copy_car(&newcar, &car_a), 0); + ASSERT_EQ(newcar.fuel_consumption, car_a.fuel_consumption); + ASSERT_EQ(newcar.fuel_consumption, car_a.fuel_consumption); + ASSERT_EQ(newcar.fuel_consumption, car_a.fuel_consumption); + ASSERT_STREQ(newcar.body_type, car_a.body_type); + ASSERT_STREQ(newcar.model_name, car_a.model_name); + free_car(&newcar); +} + +TEST(comparison, comparison_is_correct) { + car car_a = {100, 100, 1, (char*)"Toyota", (char*)"Sedan"}; + car car_b = {100, 0, 1, (char*)"Toyota", (char*)"Sedan"}; + car car_c = {100, 0, 0, (char*)"Toyota", (char*)"Sedan"}; + car car_d = {0, 0, 0, (char*)"Renaul", (char*)"Coupe"}; + EXPECT_EQ(comparison(&car_a, &car_a), 5); + EXPECT_EQ(comparison(&car_a, &car_b), 4); + EXPECT_EQ(comparison(&car_a, &car_c), 3); + EXPECT_EQ(comparison(&car_a, &car_d), 0); +} + +TEST(print_car_instance, prints_a_car) { + car car_a = {100, 100, 1, (char*)"Toyota", (char*)"Sedan"}; + ASSERT_EQ(print_car_instance(&car_a), 0); +} + +TEST(read_car_instance, reads_correctly) { + FILE* file = fopen(TEST_TXT, "a"); + fputs("150 200 2 Renault_Logan Jeeps", file); + fclose(file); + file = nullptr; + car car_1 = {100, 100, 0, NULL, NULL}; + ASSERT_EQ(read_car_instance(file, &car_1), NULLPTR_EX); + open_car_database(&file, TEST_TXT); + ASSERT_EQ(read_car_instance(file, &car_1), EOF_REACHED); + fclose(file); + file = fopen(TEST_TXT, "a"); + fputs("150 200 2 Renault_Logan Jeeps", file); + fclose(file); + open_car_database(&file, TEST_TXT); + ASSERT_EQ(read_car_instance(file, &car_1), 0); + fclose(file); + free_car(&car_1); + remove(TEST_TXT); +} + +TEST(read_car_instance, incorrect_solo_entry) { + FILE* file = fopen(TEST_TXT, "a"); + fputs("1 10 a Toyota Sedan", file); + fclose(file); + car car_1 = {100, 100, 0, NULL, NULL}; + open_car_database(&file, TEST_TXT); + ASSERT_EQ(read_car_instance(file, &car_1), INCORRECT_ENTRY); + free_car(&car_1); + fclose(file); + free_car(&car_1); + remove(TEST_TXT); +} + +TEST(read_car_instance, incorrect_multiple_entry) { + FILE* file = fopen(TEST_TXT, "a"); + fputs("1 10 1 Toyota Sedan\n" + "1 10 a Toyota", file); + fclose(file); + car car_1 = {100, 100, 0, NULL, NULL}; + open_car_database(&file, TEST_TXT); + read_car_instance(file, &car_1); + ASSERT_EQ(read_car_instance(file, &car_1), INCORRECT_ENTRY); + fclose(file); + free_car(&car_1); + remove(TEST_TXT); +} + +TEST(read_car_instance, incorrect_multiple_entry_2) { + FILE* file = fopen(TEST_TXT, "a"); + fputs("1 10 1 Toyota\n" + "1 10 1 Toyota Sedan", file); + fclose(file); + car car_1 = {100, 100, 0, NULL, NULL}; + open_car_database(&file, TEST_TXT); + read_car_instance(file, &car_1); + ASSERT_EQ(read_car_instance(file, &car_1), INCORRECT_ENTRY); + fclose(file); + free_car(&car_1); + remove(TEST_TXT); +} + +TEST(errprint, prints_errors) { + ASSERT_EQ(error_out(1), 1); + ASSERT_EQ(error_out(2), 2); + ASSERT_EQ(error_out(3), 3); + ASSERT_EQ(error_out(4), 4); + ASSERT_EQ(error_out(-1), 0); +} + +TEST(search_test, searches_for_cars) { + FILE* input = fopen(TEST_TXT, "a"); + fputs("100 200 2 Renault_Logan Jeep\n" + "150 200 2 Renault_Logan Jeep", input); + fclose(input); + FILE* db = fopen(TEST_DB_TXT, "a"); + fputs("150 200 2 Renault_Logan Jeep\n" + "100 100 2 Toyota_Camry Sedan\n" + "100 100 1 Toyota_Camry Sedan\n" + "100 120 2 Renault_Sanderas Sedan", db); + fclose(db); + car* input_car = (car*)calloc(1, sizeof(car)); + car* found_car = (car*)calloc(1, sizeof(car)); + input = fopen(TEST_TXT, "r"); + db = fopen(TEST_DB_TXT, "r"); + read_car_instance(input, input_car); + search_in_base(input_car, found_car, db); + ASSERT_EQ(found_car->fuel_consumption, input_car->fuel_consumption); + ASSERT_EQ(found_car->fuel_consumption, input_car->fuel_consumption); + ASSERT_EQ(found_car->fuel_consumption, input_car->fuel_consumption); + ASSERT_STREQ(found_car->body_type, input_car->body_type); + ASSERT_STREQ(found_car->model_name, input_car->model_name); + free_car(input_car); + fclose(db); + db = fopen(TEST_DB_TXT, "r"); + read_car_instance(input, input_car); + search_in_base(input_car, found_car, db); + ASSERT_EQ(found_car->fuel_consumption, input_car->fuel_consumption); + ASSERT_EQ(found_car->fuel_consumption, input_car->fuel_consumption); + ASSERT_EQ(found_car->fuel_consumption, input_car->fuel_consumption); + ASSERT_STREQ(found_car->body_type, input_car->body_type); + ASSERT_STREQ(found_car->model_name, input_car->model_name); + free_car(input_car); + free_car(found_car); + free(input_car); + free(found_car); + fclose(input); + fclose(db); + remove(TEST_TXT); + remove(TEST_DB_TXT); +} + + + diff --git a/project/tests/main.cpp b/project/tests/main.cpp new file mode 100644 index 0000000..7b0b7e1 --- /dev/null +++ b/project/tests/main.cpp @@ -0,0 +1,8 @@ +// Copyright 2021 + +#include "gtest/gtest.h" + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}