Compare commits
3 Commits
ffd4a55e35
...
f3b0f449b1
| Author | SHA1 | Date | |
|---|---|---|---|
| f3b0f449b1 | |||
| 428d74aeb3 | |||
| 11622fed54 |
@@ -0,0 +1,54 @@
|
|||||||
|
CXX := gcc
|
||||||
|
|
||||||
|
CXXFLAGS := -std=c++17 \
|
||||||
|
-Wall \
|
||||||
|
-Wextra \
|
||||||
|
-Werror \
|
||||||
|
-Icore/include \
|
||||||
|
-Ilogger/include \
|
||||||
|
-Itests/helpers
|
||||||
|
|
||||||
|
LDFLAGS := -lstdc++
|
||||||
|
|
||||||
|
BUILD_DIR := build
|
||||||
|
|
||||||
|
|
||||||
|
RESULT_TEST_BIN := $(BUILD_DIR)/result_test
|
||||||
|
LOGGER_TEST_BIN := $(BUILD_DIR)/logger_test
|
||||||
|
SCOPED_FILE_TEST_BIN := $(BUILD_DIR)/scoped_file_test
|
||||||
|
|
||||||
|
RESULT_TEST_SRC := tests/app/core/result_test.cpp
|
||||||
|
LOGGER_TEST_SRC := tests/app/core/logger_test.cpp
|
||||||
|
SCOPED_FILE_TEST_SRC := tests/helpers/scoped_file_test.cpp
|
||||||
|
|
||||||
|
LOGGER_SRC := logger/src/logger.cpp
|
||||||
|
|
||||||
|
.PHONY: all test clean
|
||||||
|
|
||||||
|
all: test
|
||||||
|
|
||||||
|
test: $(RESULT_TEST_BIN) \
|
||||||
|
$(LOGGER_TEST_BIN) \
|
||||||
|
$(SCOPED_FILE_TEST_BIN)
|
||||||
|
./$(RESULT_TEST_BIN)
|
||||||
|
./$(LOGGER_TEST_BIN)
|
||||||
|
./$(SCOPED_FILE_TEST_BIN)
|
||||||
|
|
||||||
|
|
||||||
|
$(RESULT_TEST_BIN): $(RESULT_TEST_SRC)
|
||||||
|
@mkdir -p $(BUILD_DIR)
|
||||||
|
$(CXX) $(CXXFLAGS) $< -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
|
||||||
|
$(LOGGER_TEST_BIN): $(LOGGER_TEST_SRC) $(LOGGER_SRC)
|
||||||
|
@mkdir -p $(BUILD_DIR)
|
||||||
|
$(CXX) $(CXXFLAGS) $^ -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
|
||||||
|
$(SCOPED_FILE_TEST_BIN): $(SCOPED_FILE_TEST_SRC)
|
||||||
|
@mkdir -p $(BUILD_DIR)
|
||||||
|
$(CXX) $(CXXFLAGS) $< -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILD_DIR)
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Контейнер результата выполнения операции.
|
||||||
|
*
|
||||||
|
* BasicResult хранит одно из двух состояний:
|
||||||
|
* - успешный результат типа T;
|
||||||
|
* - ошибку типа E.
|
||||||
|
*
|
||||||
|
* Создание объекта возможно только через фабричные методы:
|
||||||
|
* Success() и Failure(), что делает намерение вызова явным.
|
||||||
|
*
|
||||||
|
* @tparam T Тип успешного результата.
|
||||||
|
* @tparam E Тип ошибки.
|
||||||
|
*
|
||||||
|
* @note Копирование запрещено.
|
||||||
|
* Объект поддерживает только перемещение.
|
||||||
|
*/
|
||||||
|
template <typename T, typename E> class [[nodiscard]] BasicResult {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Создает успешный результат.
|
||||||
|
*
|
||||||
|
* @param value Значение успешного результата.
|
||||||
|
*
|
||||||
|
* @return Объект BasicResult в состоянии успеха.
|
||||||
|
*/
|
||||||
|
static BasicResult Success(T value) {
|
||||||
|
return BasicResult(SuccessTag{}, std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Создает результат с ошибкой.
|
||||||
|
*
|
||||||
|
* @param error Значение ошибки.
|
||||||
|
*
|
||||||
|
* @return Объект BasicResult в состоянии ошибки.
|
||||||
|
*/
|
||||||
|
static BasicResult Failure(E error) {
|
||||||
|
return BasicResult(FailureTag{}, std::move(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Запрещает копирование.
|
||||||
|
*/
|
||||||
|
BasicResult(const BasicResult &) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Запрещает копирующее присваивание.
|
||||||
|
*/
|
||||||
|
BasicResult &operator=(const BasicResult &) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Разрешает перемещение.
|
||||||
|
*/
|
||||||
|
BasicResult(BasicResult &&) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Разрешает перемещающее присваивание.
|
||||||
|
*/
|
||||||
|
BasicResult &operator=(BasicResult &&) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Проверяет успешность результата.
|
||||||
|
*
|
||||||
|
* @return true, если результат содержит значение T.
|
||||||
|
* @return false, если результат содержит ошибку E.
|
||||||
|
*/
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isSuccess() const {
|
||||||
|
return data_.index() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Проверяет наличие ошибки в результате.
|
||||||
|
*
|
||||||
|
* @return true, если результат содержит ошибку E.
|
||||||
|
* @return false, если результат содержит значение T.
|
||||||
|
*/
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isFailure() const {
|
||||||
|
return data_.index() == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Возвращает значение успешного результата.
|
||||||
|
*
|
||||||
|
* @return Константная ссылка на значение типа T.
|
||||||
|
*
|
||||||
|
* @warning Метод должен вызываться только после проверки
|
||||||
|
* isSuccess().
|
||||||
|
*
|
||||||
|
* @throws std::bad_variant_access
|
||||||
|
* если объект содержит ошибку.
|
||||||
|
*/
|
||||||
|
[[nodiscard]]
|
||||||
|
const T &getValue() const {
|
||||||
|
return std::get<0>(data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Возвращает изменяемое значение успешного результата.
|
||||||
|
*
|
||||||
|
* @return Ссылка на значение типа T.
|
||||||
|
*
|
||||||
|
* @warning Метод должен вызываться только после проверки
|
||||||
|
* isSuccess().
|
||||||
|
*/
|
||||||
|
[[nodiscard]]
|
||||||
|
T &getValue() {
|
||||||
|
return std::get<0>(data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Возвращает ошибку результата.
|
||||||
|
*
|
||||||
|
* @return Константная ссылка на ошибку типа E.
|
||||||
|
*
|
||||||
|
* @warning Метод должен вызываться только если isSuccess()
|
||||||
|
* возвращает false.
|
||||||
|
*
|
||||||
|
* @throws std::bad_variant_access
|
||||||
|
* если объект содержит успешный результат.
|
||||||
|
*/
|
||||||
|
[[nodiscard]]
|
||||||
|
const E &getError() const {
|
||||||
|
return std::get<1>(data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Возвращает изменяемую ошибку результата.
|
||||||
|
*
|
||||||
|
* @return Ссылка на ошибку типа E.
|
||||||
|
*
|
||||||
|
* @warning Метод должен вызываться только если isSuccess()
|
||||||
|
* возвращает false.
|
||||||
|
*/
|
||||||
|
[[nodiscard]]
|
||||||
|
E &getError() {
|
||||||
|
return std::get<1>(data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Вспомогательный тип для выбора конструктора успеха.
|
||||||
|
*/
|
||||||
|
struct SuccessTag {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Вспомогательный тип для выбора конструктора ошибки.
|
||||||
|
*/
|
||||||
|
struct FailureTag {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Внутренний конструктор успешного результата.
|
||||||
|
*
|
||||||
|
* Доступен только через Success().
|
||||||
|
*/
|
||||||
|
explicit BasicResult(SuccessTag, T value)
|
||||||
|
: data_(std::in_place_index<0>, std::move(value)) {}
|
||||||
|
/**
|
||||||
|
* @brief Внутренний конструктор результата с ошибкой.
|
||||||
|
*
|
||||||
|
* Доступен только через Failure().
|
||||||
|
*/
|
||||||
|
explicit BasicResult(FailureTag, E error)
|
||||||
|
: data_(std::in_place_index<1>, std::move(error)) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Хранилище результата.
|
||||||
|
*
|
||||||
|
* Первый вариант — успешное значение T.
|
||||||
|
* Второй вариант — ошибка E.
|
||||||
|
*/
|
||||||
|
std::variant<T, E> data_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Результат операции с текстовой ошибкой.
|
||||||
|
*
|
||||||
|
* Упрощенный вариант BasicResult, где ошибка всегда представлена
|
||||||
|
* строкой std::string.
|
||||||
|
*
|
||||||
|
* Пример:
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* core::Result<int> load()
|
||||||
|
* {
|
||||||
|
* return core::Result<int>::Success(100);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* core::Result<int> parse()
|
||||||
|
* {
|
||||||
|
* return core::Result<int>::Failure("Invalid format");
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @tparam T Тип успешного результата.
|
||||||
|
*/
|
||||||
|
template <typename T> using Result = BasicResult<T, std::string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Статус выполнения операции.
|
||||||
|
*
|
||||||
|
* Используется для операций, где важно только состояние:
|
||||||
|
* успех или сообщение об ошибке.
|
||||||
|
*
|
||||||
|
* Пример:
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* core::Status save()
|
||||||
|
* {
|
||||||
|
* return core::Status::Success(true);
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
using Status = Result<bool>;
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace logger {
|
||||||
|
|
||||||
|
enum class LogLevel { Debug = 0, Info = 1, Error = 2 };
|
||||||
|
|
||||||
|
} // namespace logger
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "log_level.hpp"
|
||||||
|
#include "result.hpp"
|
||||||
|
|
||||||
|
namespace logger {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Класс для ведения журналов (логирования) в файл.
|
||||||
|
*
|
||||||
|
* Logger предоставляет интерфейс для записи сообщений с различными
|
||||||
|
* уровнями важности.
|
||||||
|
*
|
||||||
|
* Объект Logger владеет своим ресурсом и не может быть скопирован или
|
||||||
|
* перемещён, чтобы исключить неоднозначность владения журналом.
|
||||||
|
*
|
||||||
|
* Методы with* используют неизменяемый подход:
|
||||||
|
* они создают новый экземпляр Logger с изменённой конфигурацией.
|
||||||
|
*/
|
||||||
|
class Logger {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Создаёт объект логгера.
|
||||||
|
*
|
||||||
|
* @param fileName Имя файла, в который будут записываться сообщения.
|
||||||
|
* @param defaultLevel Уровень логирования по умолчанию.
|
||||||
|
*/
|
||||||
|
Logger(const std::string &fileName, LogLevel defaultLevel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Освобождает ресурсы, используемые логгером.
|
||||||
|
*/
|
||||||
|
~Logger();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Запрещает создание копии Logger.
|
||||||
|
*
|
||||||
|
* Logger управляет уникальным ресурсом и не может иметь
|
||||||
|
* несколько владельцев.
|
||||||
|
*/
|
||||||
|
Logger(const Logger &) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Запрещает копирующее присваивание.
|
||||||
|
*/
|
||||||
|
Logger &operator=(const Logger &) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Запрещает перемещение Logger.
|
||||||
|
*/
|
||||||
|
Logger(Logger &&) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Запрещает перемещающее присваивание.
|
||||||
|
*/
|
||||||
|
Logger &operator=(Logger &&) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Записывает сообщение в журнал.
|
||||||
|
*
|
||||||
|
* В случае успешной записи возвращается успешный Status.
|
||||||
|
* При ошибке открытия файла или записи возвращается Status
|
||||||
|
* с описанием ошибки.
|
||||||
|
*
|
||||||
|
* @param level Уровень важности сообщения.
|
||||||
|
* @param message Текст сообщения.
|
||||||
|
*
|
||||||
|
* @return Статус выполнения операции.
|
||||||
|
*/
|
||||||
|
core::Status writeLog(LogLevel level, const std::string &message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Создаёт новый Logger с изменённым уровнем логирования.
|
||||||
|
*
|
||||||
|
* Текущий объект не изменяется.
|
||||||
|
*
|
||||||
|
* @param level Новый уровень логирования.
|
||||||
|
*
|
||||||
|
* @return Новый объект Logger.
|
||||||
|
*/
|
||||||
|
Logger withLogLevel(LogLevel level) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Создаёт новый Logger с изменённым именем файла.
|
||||||
|
*
|
||||||
|
* Текущий объект не изменяется.
|
||||||
|
*
|
||||||
|
* @param fileName Новое имя файла журнала.
|
||||||
|
*
|
||||||
|
* @return Новый объект Logger.
|
||||||
|
*/
|
||||||
|
Logger withFileName(const std::string &fileName) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Возвращает текущий уровень логирования.
|
||||||
|
*
|
||||||
|
* @return Уровень логирования.
|
||||||
|
*/
|
||||||
|
LogLevel getLogLevel() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Возвращает имя файла журнала.
|
||||||
|
*
|
||||||
|
* @return Имя файла.
|
||||||
|
*/
|
||||||
|
const std::string &getFileName() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Имя файла журнала.
|
||||||
|
*/
|
||||||
|
std::string fileName_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Уровень логирования по умолчанию.
|
||||||
|
*/
|
||||||
|
LogLevel defaultLevel_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace logger
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// logger.cpp
|
||||||
|
|
||||||
|
#include "logger.hpp"
|
||||||
|
|
||||||
|
namespace logger {
|
||||||
|
|
||||||
|
Logger::Logger(const std::string &fileName, LogLevel defaultLevel)
|
||||||
|
: fileName_(fileName), defaultLevel_(defaultLevel) {}
|
||||||
|
|
||||||
|
Logger::~Logger() = default;
|
||||||
|
|
||||||
|
core::Status Logger::writeLog(LogLevel /*level*/,
|
||||||
|
const std::string & /*message*/) {
|
||||||
|
return core::Status::Success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger Logger::withLogLevel(LogLevel level) const {
|
||||||
|
return Logger(fileName_, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger Logger::withFileName(const std::string &fileName) const {
|
||||||
|
return Logger(fileName, defaultLevel_);
|
||||||
|
}
|
||||||
|
|
||||||
|
LogLevel Logger::getLogLevel() const { return defaultLevel_; }
|
||||||
|
|
||||||
|
const std::string &Logger::getFileName() const { return fileName_; }
|
||||||
|
|
||||||
|
} // namespace logger
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "logger.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void testLoggerCreation() {
|
||||||
|
logger::Logger logger("test.log", logger::LogLevel::Info);
|
||||||
|
|
||||||
|
assert(logger.getFileName() == "test.log");
|
||||||
|
assert(logger.getLogLevel() == logger::LogLevel::Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testWithLogLevel() {
|
||||||
|
logger::Logger logger("test.log", logger::LogLevel::Info);
|
||||||
|
|
||||||
|
auto debugLogger = logger.withLogLevel(logger::LogLevel::Debug);
|
||||||
|
|
||||||
|
// Исходный объект не изменился
|
||||||
|
assert(logger.getLogLevel() == logger::LogLevel::Info);
|
||||||
|
|
||||||
|
// Новый объект получил новый уровень
|
||||||
|
assert(debugLogger.getLogLevel() == logger::LogLevel::Debug);
|
||||||
|
|
||||||
|
// Имя файла сохранилось
|
||||||
|
assert(debugLogger.getFileName() == "test.log");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testWithFileName() {
|
||||||
|
logger::Logger logger("old.log", logger::LogLevel::Info);
|
||||||
|
|
||||||
|
auto newLogger = logger.withFileName("new.log");
|
||||||
|
|
||||||
|
// Исходный объект не изменился
|
||||||
|
assert(logger.getFileName() == "old.log");
|
||||||
|
|
||||||
|
// Новый объект получил новый файл
|
||||||
|
assert(newLogger.getFileName() == "new.log");
|
||||||
|
|
||||||
|
// Уровень сохранился
|
||||||
|
assert(newLogger.getLogLevel() == logger::LogLevel::Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testWriteLogSuccess() {
|
||||||
|
const std::string fileName = "logger_test.log";
|
||||||
|
|
||||||
|
logger::Logger logger(fileName, logger::LogLevel::Info);
|
||||||
|
|
||||||
|
auto result = logger.writeLog(logger::LogLevel::Info, "Application started");
|
||||||
|
|
||||||
|
assert(result.isSuccess());
|
||||||
|
assert(result.getValue());
|
||||||
|
|
||||||
|
std::ifstream file(fileName);
|
||||||
|
|
||||||
|
assert(file.is_open());
|
||||||
|
|
||||||
|
std::string content;
|
||||||
|
std::getline(file, content);
|
||||||
|
|
||||||
|
assert(content.find("Application started") != std::string::npos);
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
std::remove(fileName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void testWriteLogFailure() {
|
||||||
|
logger::Logger logger("/invalid/path/logger.log", logger::LogLevel::Info);
|
||||||
|
|
||||||
|
auto result = logger.writeLog(logger::LogLevel::Info, "message");
|
||||||
|
|
||||||
|
assert(result.isFailure());
|
||||||
|
assert(!result.getError().empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void testMultipleWrites() {
|
||||||
|
const std::string fileName = "multiple.log";
|
||||||
|
|
||||||
|
logger::Logger logger(fileName, logger::LogLevel::Info);
|
||||||
|
|
||||||
|
assert(logger.writeLog(logger::LogLevel::Info, "first").isSuccess());
|
||||||
|
|
||||||
|
assert(logger.writeLog(logger::LogLevel::Error, "second").isSuccess());
|
||||||
|
|
||||||
|
std::ifstream file(fileName);
|
||||||
|
|
||||||
|
assert(file.is_open());
|
||||||
|
|
||||||
|
std::string line1;
|
||||||
|
std::string line2;
|
||||||
|
|
||||||
|
std::getline(file, line1);
|
||||||
|
std::getline(file, line2);
|
||||||
|
|
||||||
|
assert(line1.find("first") != std::string::npos);
|
||||||
|
assert(line2.find("second") != std::string::npos);
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
std::remove(fileName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void runTests() {
|
||||||
|
testLoggerCreation();
|
||||||
|
testWithLogLevel();
|
||||||
|
testWithFileName();
|
||||||
|
testWriteLogSuccess();
|
||||||
|
testWriteLogFailure();
|
||||||
|
testMultipleWrites();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
runTests();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "result.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void testSuccessCreation() {
|
||||||
|
auto result = core::Result<int>::Success(42);
|
||||||
|
|
||||||
|
assert(result.isSuccess());
|
||||||
|
assert(result.getValue() == 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testFailureCreation() {
|
||||||
|
auto result = core::Result<int>::Failure("Invalid value");
|
||||||
|
|
||||||
|
assert(result.isFailure());
|
||||||
|
assert(result.getError() == "Invalid value");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testStatusSuccess() {
|
||||||
|
auto status = core::Status::Success(true);
|
||||||
|
|
||||||
|
assert(status.isSuccess());
|
||||||
|
assert(status.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
void testStatusFailure() {
|
||||||
|
auto status = core::Status::Failure("File not found");
|
||||||
|
|
||||||
|
assert(status.isFailure());
|
||||||
|
assert(status.getError() == "File not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testMoveConstruction() {
|
||||||
|
auto source = core::Result<std::string>::Success("hello");
|
||||||
|
|
||||||
|
auto moved = std::move(source);
|
||||||
|
|
||||||
|
assert(moved.isSuccess());
|
||||||
|
assert(moved.getValue() == "hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testMoveAssignment() {
|
||||||
|
auto first = core::Result<int>::Success(10);
|
||||||
|
auto second = core::Result<int>::Failure("error");
|
||||||
|
|
||||||
|
second = std::move(first);
|
||||||
|
|
||||||
|
assert(second.isSuccess());
|
||||||
|
assert(second.getValue() == 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testDifferentTypes() {
|
||||||
|
struct User {
|
||||||
|
int id;
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto result = core::Result<User>::Success(User{1, "Alex"});
|
||||||
|
|
||||||
|
assert(result.isSuccess());
|
||||||
|
assert(result.getValue().id == 1);
|
||||||
|
assert(result.getValue().name == "Alex");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testValueModification() {
|
||||||
|
auto result = core::Result<int>::Success(10);
|
||||||
|
|
||||||
|
result.getValue() = 20;
|
||||||
|
|
||||||
|
assert(result.getValue() == 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testErrorModification() {
|
||||||
|
auto result = core::Result<int>::Failure("old error");
|
||||||
|
|
||||||
|
result.getError() = "new error";
|
||||||
|
|
||||||
|
assert(result.getError() == "new error");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testBasicResultWithCustomError() {
|
||||||
|
enum class ErrorCode { NotFound, PermissionDenied };
|
||||||
|
|
||||||
|
using CustomResult = core::BasicResult<int, ErrorCode>;
|
||||||
|
|
||||||
|
auto result = CustomResult::Failure(ErrorCode::PermissionDenied);
|
||||||
|
|
||||||
|
assert(result.isFailure());
|
||||||
|
assert(result.getError() == ErrorCode::PermissionDenied);
|
||||||
|
}
|
||||||
|
|
||||||
|
void runAllTests() {
|
||||||
|
testSuccessCreation();
|
||||||
|
testFailureCreation();
|
||||||
|
testStatusSuccess();
|
||||||
|
testStatusFailure();
|
||||||
|
testMoveConstruction();
|
||||||
|
testMoveAssignment();
|
||||||
|
testDifferentTypes();
|
||||||
|
testValueModification();
|
||||||
|
testErrorModification();
|
||||||
|
testBasicResultWithCustomError();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
runAllTests();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class ScopedFile {
|
||||||
|
public:
|
||||||
|
explicit ScopedFile(std::string fileName) : fileName_(std::move(fileName)) {}
|
||||||
|
|
||||||
|
void throwIfExists() const {
|
||||||
|
if (std::filesystem::exists(fileName_)) {
|
||||||
|
throw std::runtime_error("Test file already exists: " + fileName_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &getFileName() const { return fileName_; }
|
||||||
|
|
||||||
|
~ScopedFile() noexcept(false) {
|
||||||
|
std::error_code ec;
|
||||||
|
|
||||||
|
std::filesystem::remove(fileName_, ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string fileName_;
|
||||||
|
};
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "scoped_file.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void testFileRemovedAfterScope() {
|
||||||
|
const std::string fileName = "scoped_file_test.log";
|
||||||
|
|
||||||
|
{
|
||||||
|
ScopedFile file(fileName);
|
||||||
|
|
||||||
|
file.throwIfExists();
|
||||||
|
|
||||||
|
std::ofstream output(file.getFileName());
|
||||||
|
|
||||||
|
assert(output.is_open());
|
||||||
|
|
||||||
|
output << "test";
|
||||||
|
|
||||||
|
output.close();
|
||||||
|
|
||||||
|
assert(std::filesystem::exists(fileName));
|
||||||
|
} // call ~ScopedFile()
|
||||||
|
|
||||||
|
assert(!std::filesystem::exists(fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
void testThrowIfExists() {
|
||||||
|
const std::string fileName = "existing_file.log";
|
||||||
|
|
||||||
|
{
|
||||||
|
ScopedFile file(fileName);
|
||||||
|
|
||||||
|
if (std::filesystem::exists(fileName)) {
|
||||||
|
throw std::runtime_error("Test file already exists: " + fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream output(file.getFileName());
|
||||||
|
|
||||||
|
assert(output.is_open());
|
||||||
|
|
||||||
|
output.close();
|
||||||
|
|
||||||
|
bool thrown = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
file.throwIfExists();
|
||||||
|
} catch (const std::runtime_error &) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(thrown);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!std::filesystem::exists(fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
void testGetFileName() {
|
||||||
|
ScopedFile file("test.log");
|
||||||
|
|
||||||
|
file.throwIfExists();
|
||||||
|
|
||||||
|
assert(file.getFileName() == "test.log");
|
||||||
|
}
|
||||||
|
|
||||||
|
void runTests() {
|
||||||
|
testFileRemovedAfterScope();
|
||||||
|
testThrowIfExists();
|
||||||
|
testGetFileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
runTests();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "logger.hpp"
|
||||||
|
#include "scoped_file.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void testLoggerCreation() {
|
||||||
|
logger::Logger logger("test.log", logger::LogLevel::Info);
|
||||||
|
|
||||||
|
assert(logger.getFileName() == "test.log");
|
||||||
|
assert(logger.getLogLevel() == logger::LogLevel::Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testWithLogLevel() {
|
||||||
|
logger::Logger logger("test.log", logger::LogLevel::Info);
|
||||||
|
|
||||||
|
auto debugLogger = logger.withLogLevel(logger::LogLevel::Debug);
|
||||||
|
|
||||||
|
assert(logger.getLogLevel() == logger::LogLevel::Info);
|
||||||
|
assert(debugLogger.getLogLevel() == logger::LogLevel::Debug);
|
||||||
|
|
||||||
|
assert(debugLogger.getFileName() == "test.log");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testWithFileName() {
|
||||||
|
logger::Logger logger("old.log", logger::LogLevel::Info);
|
||||||
|
|
||||||
|
auto newLogger = logger.withFileName("new.log");
|
||||||
|
|
||||||
|
assert(logger.getFileName() == "old.log");
|
||||||
|
assert(newLogger.getFileName() == "new.log");
|
||||||
|
|
||||||
|
assert(newLogger.getLogLevel() == logger::LogLevel::Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testWriteLogSuccess() {
|
||||||
|
ScopedFile file("logger_test.log");
|
||||||
|
|
||||||
|
file.throwIfExists();
|
||||||
|
|
||||||
|
logger::Logger logger(file.getFileName(), logger::LogLevel::Info);
|
||||||
|
|
||||||
|
auto result = logger.writeLog(logger::LogLevel::Info, "Application started");
|
||||||
|
|
||||||
|
assert(result.isSuccess());
|
||||||
|
assert(result.getValue());
|
||||||
|
|
||||||
|
std::ifstream input(file.getFileName());
|
||||||
|
|
||||||
|
assert(input.is_open());
|
||||||
|
|
||||||
|
std::string content;
|
||||||
|
std::getline(input, content);
|
||||||
|
|
||||||
|
assert(content.find("Application started") != std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testWriteLogFailure() {
|
||||||
|
ScopedFile file("logger_test_directory");
|
||||||
|
|
||||||
|
file.throwIfExists();
|
||||||
|
|
||||||
|
std::filesystem::create_directory(file.getFileName());
|
||||||
|
|
||||||
|
logger::Logger logger(file.getFileName(), logger::LogLevel::Info);
|
||||||
|
|
||||||
|
auto result = logger.writeLog(logger::LogLevel::Info, "message");
|
||||||
|
|
||||||
|
assert(result.isFailure());
|
||||||
|
assert(!result.getError().empty());
|
||||||
|
|
||||||
|
std::filesystem::remove(file.getFileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
void testMultipleWrites() {
|
||||||
|
ScopedFile file("multiple.log");
|
||||||
|
|
||||||
|
file.throwIfExists();
|
||||||
|
|
||||||
|
logger::Logger logger(file.getFileName(), logger::LogLevel::Info);
|
||||||
|
|
||||||
|
auto first = logger.writeLog(logger::LogLevel::Info, "first message");
|
||||||
|
|
||||||
|
auto second = logger.writeLog(logger::LogLevel::Error, "second message");
|
||||||
|
|
||||||
|
assert(first.isSuccess());
|
||||||
|
assert(second.isSuccess());
|
||||||
|
|
||||||
|
std::ifstream input(file.getFileName());
|
||||||
|
|
||||||
|
assert(input.is_open());
|
||||||
|
|
||||||
|
std::string firstLine;
|
||||||
|
std::string secondLine;
|
||||||
|
|
||||||
|
std::getline(input, firstLine);
|
||||||
|
std::getline(input, secondLine);
|
||||||
|
|
||||||
|
assert(firstLine.find("first message") != std::string::npos);
|
||||||
|
|
||||||
|
assert(secondLine.find("second message") != std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void runTests() {
|
||||||
|
testLoggerCreation();
|
||||||
|
testWithLogLevel();
|
||||||
|
testWithFileName();
|
||||||
|
testWriteLogSuccess();
|
||||||
|
testWriteLogFailure();
|
||||||
|
testMultipleWrites();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
runTests();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user