Updated README.md to include additional info about this project
Implemented reading settings from config file ftsort now outputs total runtime in ms and peak virtual memory usage Added some error handling Added documenation to tape_config.h Changed catch2 cmake dependency to be resolved using FetchContent Added generate_input test binary to generate big input tape data sets Header inclusion path changed to _include_ directory, so that tapelib has to be explicitly specified in include directrives
This commit is contained in:
parent
d7bcbbfd08
commit
f8bfc78cad
13 changed files with 207 additions and 13 deletions
|
@ -4,7 +4,6 @@ project(yadro-task VERSION 0.1 LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
set(CMAKE_BUILD_TYPE Debug)
|
|
||||||
|
|
||||||
# clang-tidy
|
# clang-tidy
|
||||||
find_program(CLANG_TIDY_EXE NAMES clang-tidy REQUIRED)
|
find_program(CLANG_TIDY_EXE NAMES clang-tidy REQUIRED)
|
||||||
|
|
35
README.md
35
README.md
|
@ -11,3 +11,38 @@
|
||||||
- Написать класс, реализующий алгоритм сортировки данных с входной ленты на выходную.
|
- Написать класс, реализующий алгоритм сортировки данных с входной ленты на выходную.
|
||||||
- Консольное приложение должно принимать на вход имя входного и выходного файлов и производить сортировку.
|
- Консольное приложение должно принимать на вход имя входного и выходного файлов и производить сортировку.
|
||||||
- Желательно написать юнит-тесты.
|
- Желательно написать юнит-тесты.
|
||||||
|
|
||||||
|
# Описание
|
||||||
|
|
||||||
|
Проект использует CMake для сборки. Некоторый функционал проекта несовместим с _Windows_ и _MacOS_ (получение используемой памяти, создание временных файлов и т. п.) Проект успешно собирался на _Linux_ _x86_64_ и _aarch64_. Документация реализована посредством этого __README__ файла, а также подробными комментариями в коде
|
||||||
|
|
||||||
|
Структура проекта:
|
||||||
|
- ```bin``` - Исходный код исполняемых файлов
|
||||||
|
- ```ftsort``` - Исходный код основной программы проекта
|
||||||
|
- ```include``` - Директория для заголовков
|
||||||
|
- ```tapelib``` - Директория для заголовков библиотеки ```tapelib```
|
||||||
|
- ```src``` - Исходный код библиотек
|
||||||
|
- ```tests``` - Исходный код unit-тестов
|
||||||
|
|
||||||
|
# Сборка
|
||||||
|
|
||||||
|
## Linux
|
||||||
|
1. Склонируйте данный репозиторий и перейдите в него
|
||||||
|
```
|
||||||
|
git clone https://git.obamna.ru/erius/yadro-task.git
|
||||||
|
cd yadro-task
|
||||||
|
```
|
||||||
|
2. Сгенерируйте файлы сборки CMake
|
||||||
|
```
|
||||||
|
cmake -S . -B build
|
||||||
|
```
|
||||||
|
3. Соберите проект с помощью CMake
|
||||||
|
```
|
||||||
|
cmake --build build
|
||||||
|
```
|
||||||
|
4. Запустите unit-тесты
|
||||||
|
```
|
||||||
|
ctest --test-dir build -V
|
||||||
|
```
|
||||||
|
|
||||||
|
Сборка и работоспособность проекта не проверялись на _Windows_ и _MacOS_
|
||||||
|
|
|
@ -1,6 +1,26 @@
|
||||||
#include "tape_config.h"
|
#include "tape_config.h"
|
||||||
#include "tape_util.h"
|
#include "tapelib/tape_util.h"
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
size_t get_mem_peak() {
|
||||||
|
const static std::string mem_info_file = "/proc/self/status";
|
||||||
|
const static std::string mem_field = "VmPeak:";
|
||||||
|
std::ifstream status(mem_info_file);
|
||||||
|
size_t mem = 0;
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(status, line)) {
|
||||||
|
if (line.find(mem_field) != std::string::npos) {
|
||||||
|
std::stringstream mem_line(line.substr(mem_field.size()));
|
||||||
|
mem_line >> mem;
|
||||||
|
return mem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mem;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
AppSettings settings = parse_command_line(argc, argv);
|
AppSettings settings = parse_command_line(argc, argv);
|
||||||
|
@ -12,15 +32,38 @@ int main(int argc, char *argv[]) {
|
||||||
std::cerr << HELP_MSG << std::endl;
|
std::cerr << HELP_MSG << std::endl;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if (!settings.config_file_path.empty() &&
|
||||||
|
!read_settings_from_file(settings)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
tape::FileTape input(settings.input_file_name, settings.ft_settings);
|
tape::FileTape input(settings.input_file_name, settings.ft_settings);
|
||||||
|
if (!input.is_open()) {
|
||||||
|
std::cerr << "Failed to create INPUT tape - file not found or "
|
||||||
|
"insufficient permissions"
|
||||||
|
<< std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
tape::FileTape output(input, settings.output_file_name,
|
tape::FileTape output(input, settings.output_file_name,
|
||||||
settings.ft_settings);
|
settings.ft_settings);
|
||||||
// tmp tape factory that captures cmd.settings from local scope
|
if (!output.is_open()) {
|
||||||
|
std::cerr << "Failed to create OUTPUT tape - insufficient permissions"
|
||||||
|
<< std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// tmp tape factory that captures settings.ft_settings from local scope
|
||||||
tape::TempTapeFactory factory =
|
tape::TempTapeFactory factory =
|
||||||
[&](size_t cells) -> std::unique_ptr<tape::Tape> {
|
[&](size_t cells) -> std::unique_ptr<tape::Tape> {
|
||||||
return std::make_unique<tape::FileTape>(
|
return std::make_unique<tape::FileTape>(
|
||||||
tape::FileTape(cells, settings.ft_settings));
|
tape::FileTape(cells, settings.ft_settings));
|
||||||
};
|
};
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
tape::external_sort(input, output, factory, settings.memory_limit);
|
tape::external_sort(input, output, factory, settings.memory_limit);
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
auto duration = std::chrono::duration_cast<milliseconds>(end - start);
|
||||||
|
std::cout << "Successfully sorted " << settings.input_file_name << " into "
|
||||||
|
<< settings.output_file_name << std::endl;
|
||||||
|
std::cout << "The operation took " << duration.count()
|
||||||
|
<< " ms to complete and peaked at " << get_mem_peak()
|
||||||
|
<< " KiB of virtual memory usage" << std::endl;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
#include "tape_config.h"
|
#include "tape_config.h"
|
||||||
#include <bits/getopt_core.h>
|
#include <bits/getopt_core.h>
|
||||||
|
#include <fstream>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
const static struct std::vector<option> CMD_OPTS{
|
const static struct std::vector<option> CMD_OPTS{
|
||||||
{.name = "write-delay",
|
{.name = "write-delay",
|
||||||
|
@ -80,6 +84,9 @@ AppSettings parse_command_line(int argc, char **argv) {
|
||||||
// parse positional args INPUT_FILE OUTPUT_FILE
|
// parse positional args INPUT_FILE OUTPUT_FILE
|
||||||
if (argc - optind < 2) {
|
if (argc - optind < 2) {
|
||||||
settings.help = true;
|
settings.help = true;
|
||||||
|
std::cerr << "Not enough positional arguments (Missing INPUT_FILE or "
|
||||||
|
"OUTPUT_FILE)"
|
||||||
|
<< std::endl;
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
// no linting to disable pointer arithmetic warning
|
// no linting to disable pointer arithmetic warning
|
||||||
|
@ -87,3 +94,48 @@ AppSettings parse_command_line(int argc, char **argv) {
|
||||||
settings.output_file_name = argv[optind + 1]; // NOLINT
|
settings.output_file_name = argv[optind + 1]; // NOLINT
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const static char CFG_DELIMETER = '=';
|
||||||
|
|
||||||
|
bool read_settings_from_file(AppSettings &settings) {
|
||||||
|
std::ifstream config(settings.config_file_path);
|
||||||
|
if (!config.is_open()) {
|
||||||
|
std::cerr << "Failed to read config: file doesn't exist or "
|
||||||
|
"insufficient permissions"
|
||||||
|
<< std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string line;
|
||||||
|
size_t line_number = 1;
|
||||||
|
while (std::getline(config, line)) {
|
||||||
|
size_t delim_index = line.find(CFG_DELIMETER);
|
||||||
|
if (delim_index >= line.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::string key = line.substr(0, delim_index);
|
||||||
|
std::string value = line.substr(delim_index + 1);
|
||||||
|
try {
|
||||||
|
if (key == "write-delay") {
|
||||||
|
settings.ft_settings.write_delay = parse_delay(value);
|
||||||
|
} else if (key == "read-delay") {
|
||||||
|
settings.ft_settings.read_delay = parse_delay(value);
|
||||||
|
} else if (key == "seek-forward-delay") {
|
||||||
|
settings.ft_settings.seek_forward_delay = parse_delay(value);
|
||||||
|
} else if (key == "seek-backwards-delay") {
|
||||||
|
settings.ft_settings.seek_backwards_delay = parse_delay(value);
|
||||||
|
} else if (key == "memory-limit") {
|
||||||
|
settings.memory_limit = std::stoi(value);
|
||||||
|
}
|
||||||
|
} catch (std::invalid_argument) {
|
||||||
|
std::cerr << "Failed to parse config at line " << line_number
|
||||||
|
<< ": value must be a positive integer" << std::endl;
|
||||||
|
return false;
|
||||||
|
} catch (std::out_of_range) {
|
||||||
|
std::cerr << "Failed to parse config at line " << line_number
|
||||||
|
<< ": value is out of allowed range" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
line_number++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#ifndef TAPE_CONFIG_H
|
#ifndef TAPE_CONFIG_H
|
||||||
#define TAPE_CONFIG_H
|
#define TAPE_CONFIG_H
|
||||||
|
|
||||||
#include "filetape.h"
|
#include "tapelib/filetape.h"
|
||||||
|
|
||||||
// stringizing opearator to convert macro into string literal
|
// stringizing opearator to convert macro into string literal
|
||||||
#define xstr(s) str(s)
|
#define xstr(s) str(s)
|
||||||
|
@ -51,8 +51,26 @@ struct AppSettings {
|
||||||
tape::FileTapeSettings ft_settings;
|
tape::FileTapeSettings ft_settings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses command line arguments (from main) and creates an instance of
|
||||||
|
* AppSettings from them
|
||||||
|
*
|
||||||
|
* @param argc Args count from main
|
||||||
|
* @param argv Args array from main
|
||||||
|
* @return Instance of AppSettings filled with values from command line
|
||||||
|
*/
|
||||||
AppSettings parse_command_line(int argc, char **argv);
|
AppSettings parse_command_line(int argc, char **argv);
|
||||||
|
|
||||||
void read_settings_from_file(AppSettings &settings);
|
/**
|
||||||
|
* Accepts AppSettings prefilled with settings from command line, tries to open
|
||||||
|
* the file specified in the config_file_path field and reads values from the
|
||||||
|
* supplied config files into provided AppSettings, overwriting the settings.
|
||||||
|
*
|
||||||
|
* @param settings AppSettings, values of which will be overwritten by configs
|
||||||
|
* values
|
||||||
|
* @return true, if config file was opened and read successfully, otherwise
|
||||||
|
* false
|
||||||
|
*/
|
||||||
|
bool read_settings_from_file(AppSettings &settings);
|
||||||
|
|
||||||
#endif // !TAPE_CONFIG_H
|
#endif // !TAPE_CONFIG_H
|
||||||
|
|
|
@ -195,6 +195,8 @@ class FileTape : public Tape {
|
||||||
|
|
||||||
bool is_tmp() const { return this->tmp; }
|
bool is_tmp() const { return this->tmp; }
|
||||||
|
|
||||||
|
bool is_open() const { return this->file.is_open(); }
|
||||||
|
|
||||||
const FileTapeSettings &get_settings() const { return this->settings; }
|
const FileTapeSettings &get_settings() const { return this->settings; }
|
||||||
|
|
||||||
const std::string &get_file_name() const { return this->file_name; }
|
const std::string &get_file_name() const { return this->file_name; }
|
||||||
|
|
|
@ -2,4 +2,4 @@ add_library(tapelib)
|
||||||
target_sources(tapelib PRIVATE filetape.cpp
|
target_sources(tapelib PRIVATE filetape.cpp
|
||||||
tape_util.cpp
|
tape_util.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(tapelib PUBLIC ${PROJECT_SOURCE_DIR}/include/tapelib)
|
target_include_directories(tapelib PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include "filetape.h"
|
#include "tapelib/filetape.h"
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
@ -13,8 +13,6 @@ using tape::FileTape;
|
||||||
std::string generate_tmp_file_name() {
|
std::string generate_tmp_file_name() {
|
||||||
std::array<char, L_tmpnam> tmp_file_name{};
|
std::array<char, L_tmpnam> tmp_file_name{};
|
||||||
// tmpnam is unsafe, but its cross-platform
|
// tmpnam is unsafe, but its cross-platform
|
||||||
// TODO: make a safe, cross-platform tmp file handler
|
|
||||||
// (possibly utilizing std::filesystem::temp_directory_path())
|
|
||||||
std::tmpnam(tmp_file_name.data());
|
std::tmpnam(tmp_file_name.data());
|
||||||
return tmp_file_name.data();
|
return tmp_file_name.data();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include "tape_util.h"
|
#include "tapelib/tape_util.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
using std::unique_ptr;
|
using std::unique_ptr;
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
find_package(Catch2 3 REQUIRED)
|
Include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
Catch2
|
||||||
|
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||||
|
GIT_TAG v3.7.1
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(Catch2)
|
||||||
|
|
||||||
add_executable(filetape_tests filetape_tests.cpp)
|
add_executable(filetape_tests filetape_tests.cpp)
|
||||||
target_link_libraries(filetape_tests PRIVATE tapelib)
|
target_link_libraries(filetape_tests PRIVATE tapelib)
|
||||||
|
@ -11,3 +19,7 @@ target_link_libraries(filetape_sort_tests PRIVATE tapelib)
|
||||||
target_link_libraries(filetape_sort_tests PRIVATE Catch2::Catch2WithMain)
|
target_link_libraries(filetape_sort_tests PRIVATE Catch2::Catch2WithMain)
|
||||||
|
|
||||||
add_test(filetape_sort_tests filetape_sort_tests)
|
add_test(filetape_sort_tests filetape_sort_tests)
|
||||||
|
|
||||||
|
# Compile binary for generating big files for input tapes
|
||||||
|
add_executable(generate_input generate_input.cpp)
|
||||||
|
target_link_libraries(generate_input PRIVATE tapelib)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include "tape_util.h"
|
#include "tapelib/tape_util.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include "filetape.h"
|
#include "tapelib/filetape.h"
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
35
tests/generate_input.cpp
Normal file
35
tests/generate_input.cpp
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#include "tapelib/filetape.h"
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <ctime>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate big input data sets for external sort testing.
|
||||||
|
* Takes 2 positional arguments - first one is the ouput file name,
|
||||||
|
* secon one is the amount of random cells that will be generated.
|
||||||
|
* Each cell value is in range [0; RAND_MAX]
|
||||||
|
*/
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
if (argc < 3) {
|
||||||
|
std::cerr << "Not enough arguments" << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
std::string out_name = argv[1]; // NOLINT
|
||||||
|
size_t cells = std::stoi(argv[2]); // NOLINT
|
||||||
|
std::ofstream out(out_name);
|
||||||
|
if (!out.is_open()) {
|
||||||
|
std::cerr << "Failed to open file" << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
srand(time(nullptr));
|
||||||
|
out << std::setfill('0') << std::setw(tape::FT_CELL_SIZE)
|
||||||
|
<< std::to_string(rand());
|
||||||
|
for (size_t i = 1; i < cells; i++) {
|
||||||
|
out << tape::FT_DELIMETER << std::setfill('0')
|
||||||
|
<< std::setw(tape::FT_CELL_SIZE) << std::to_string(rand());
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in a new issue