diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..a5e0313 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,79 @@ +name: CD + +on: + push: + tags: + - "v*" + workflow_dispatch: + +permissions: + contents: write + +jobs: + build-linux: + name: Build Linux GUI + CLI + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Qt6 + run: | + sudo apt update + sudo apt install -y qt6-base-dev qt6-base-dev-tools qt6-tools-dev build-essential cmake libgl1-mesa-dev libglu1-mesa-dev libx11-dev libxext-dev libxrandr-dev libxi-dev libxfixes-dev + + - name: Configure CMake + run: cmake -S . -B build -DBUILD_GUI=ON -DCMAKE_BUILD_TYPE=Release + + - name: Build GUI + run: cmake --build build --target log-analyzer-gui --config Release + + - name: Build CLI + run: cmake --build build --target log-analyzer-cli --config Release + + - name: Package Linux artifacts + run: | + mkdir -p artifacts/linux + find build -type f \( -name "log-analyzer-gui" -o -name "log-analyzer-cli" \) -exec cp {} artifacts/linux/ \; + tar czvf artifacts/log-analyzer-linux.tar.gz -C artifacts/linux . + + - name: Release + uses: softprops/action-gh-release@v2 + with: + files: artifacts/log-analyzer-linux.tar.gz + tag_name: ${{ github.ref_name }} + name: Release ${{ github.ref_name }} + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build-mac: + name: Build macOS CLI + runs-on: macos-latest + needs: build-linux + steps: + - uses: actions/checkout@v4 + + - name: Install Qt + run: brew update && brew install qt + + - name: Configure CMake + run: cmake -S . -B build -DBUILD_GUI=OFF -DCMAKE_BUILD_TYPE=Release + + - name: Build CLI + run: cmake --build build --target log-analyzer-cli --config Release + + - name: Package macOS artifacts + run: | + mkdir -p artifacts/mac + find build -type f -name "log-analyzer-cli" -exec cp {} artifacts/mac/ \; + zip -r artifacts/log-analyzer-mac.zip artifacts/mac + + - name: Upload macOS Asset + uses: softprops/action-gh-release@v2 + with: + files: artifacts/log-analyzer-mac.zip + tag_name: ${{ github.ref_name }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e353ea..e97b78f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: push: branches: [ main, develop ] pull_request: - branches: [ main ] + branches: [ main ] jobs: Tests: @@ -25,7 +25,7 @@ jobs: sudo apt-get update && sudo apt-get install -y cmake build-essential libgtest-dev fi - cmake -B build + cmake -B build -DBUILD_GUI=OFF cmake --build build cd build/tests ./test-runner \ No newline at end of file diff --git a/.gitignore b/.gitignore index c0a5b78..d2abc30 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,7 @@ build/ !tests/test_data/*.log + +.DS_Store +.qtcreator/ +CMakeLists.txt.user diff --git a/CMakeLists.txt b/CMakeLists.txt index 44c5fac..8548e60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,24 +1,26 @@ cmake_minimum_required(VERSION 3.10) -project(LogAnalyzer VERSION 0.1.0) +project(LogAnalyzer VERSION 1.0.0) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -cmake_policy(SET CMP0079 NEW) - - -find_package(GTest QUIET) -if(NOT GTest_FOUND) - include(FetchContent) - FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG v1.14.0 - ) - - FetchContent_MakeAvailable(googletest) -endif() + + +option(BUILD_GUI "Build GUI version" ON) add_subdirectory(core) add_subdirectory(cli) + +if(BUILD_GUI) + add_subdirectory(gui) +endif() + +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.14.0 +) +FetchContent_MakeAvailable(googletest) + add_subdirectory(tests) diff --git a/README.md b/README.md index 0ba779e..d952e64 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Log Analyzer v0.2.1 +# Log Analyzer v1.0.0 ## 💻 Платформы -- ✅ macOS (clang/gcc, CMake ≥ 3.10) -- ✅ Linux (gcc, CMake ≥ 3.10) +- MacOS (cli) +- Linux (cli, gui) ## 🚀 Возможности ### 📋 Поддерживаемые форматы логов: @@ -21,39 +21,28 @@ - Язык: C++17 - Сборка: CMake - Тестирование: Google Test (gtest/gmock) -- CI: Сборка и тестирование +- CI/CD: тестирование и сборка +- Qt: графическое приложение ## 🛠️ В разработке: -- GUI +- Поддержка Windows -## 📦 Установка и сборка -- Клонирование репозитория +## 📦 Установка и запуск +### Linux +Для cli ``` -git clone https://github.com/Arkilleru/log-analyzer.git -cd log-analyzer +chmod +x log-analyzer-cli +./log-analyzer-cli ``` - -- Сборка -``` -./run.sh build -``` - -- Консольное приложение +Для gui ``` -./run.sh cli +sudo apt install qt6-base-dev +chmod +x log-analyzer-gui +./log-analyzer-gui ``` - -- Тестирование +### MacOS +Для cli ``` -./run.sh test +chmod +x log-analyzer-cli +./log-analyzer-cli ``` - -- Очистка -``` -./run.sh clean -``` - -- Очистка + Сборка + Тестирование -``` -./run.sh all -``` \ No newline at end of file diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index 2173328..9ab63a7 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable(log-analyzer-cli main.cpp) +add_executable(log-analyzer-cli main.cpp reporter.cpp) target_link_libraries(log-analyzer-cli core_lib) set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY EP_PREFIX ${CMAKE_BINARY_DIR}) \ No newline at end of file diff --git a/cli/main.cpp b/cli/main.cpp index 20cf373..2f78db0 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -1,4 +1,5 @@ #include "../core/analyzer.h" +#include "reporter.h" #include int main() { @@ -7,7 +8,9 @@ int main() { std::getline(std::cin, path); Analyzer analysis; - std::string report = analysis.analyze(path); + AnalysisResult result = analysis.analyze(path); + Reporter reporter; + std::string report = reporter.GenerateTextReport(result); std::cout << report; diff --git a/core/reporter.cpp b/cli/reporter.cpp similarity index 100% rename from core/reporter.cpp rename to cli/reporter.cpp diff --git a/core/reporter.h b/cli/reporter.h similarity index 95% rename from core/reporter.h rename to cli/reporter.h index 256a6f1..09d88b2 100644 --- a/core/reporter.h +++ b/cli/reporter.h @@ -1,5 +1,6 @@ #pragma once -#include "common.h" + +#include "../core/common.h" #include diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index a72f231..f50989a 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -2,6 +2,5 @@ add_library(core_lib reader.cpp parser.cpp statistics.cpp - reporter.cpp analyzer.cpp -) \ No newline at end of file +) diff --git a/core/analyzer.cpp b/core/analyzer.cpp index 3126709..9b7d921 100644 --- a/core/analyzer.cpp +++ b/core/analyzer.cpp @@ -1,6 +1,6 @@ #include "analyzer.h" -std::string Analyzer::analyze(std::string path) { +AnalysisResult Analyzer::analyze(const std::string& path) { reader_.OpenFile(path); AnalysisResult res; @@ -11,7 +11,6 @@ std::string Analyzer::analyze(std::string path) { } reader_.CloseFile(); - std::string report = reporter_.GenerateTextReport(res); - return report; -} \ No newline at end of file + return res; +} diff --git a/core/analyzer.h b/core/analyzer.h index 470f924..17ef782 100644 --- a/core/analyzer.h +++ b/core/analyzer.h @@ -3,17 +3,15 @@ #include "reader.h" #include "parser.h" #include "statistics.h" -#include "reporter.h" #include class Analyzer { private: Reader reader_; Parser parser_; - Reporter reporter_; Statistics statistics_; public: - std::string analyze(std::string path); + AnalysisResult analyze(const std::string& path); }; diff --git a/core/parser.cpp b/core/parser.cpp index 02d28f5..650ea40 100644 --- a/core/parser.cpp +++ b/core/parser.cpp @@ -1,5 +1,4 @@ #include "parser.h" -#include LogFormat Parser::DetectFormat(const std::string& line) { if (line.find("HTTP/1.0") != std::string::npos) { diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index e69de29..9223fcd 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -0,0 +1,23 @@ +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 REQUIRED COMPONENTS Widgets) + +qt_standard_project_setup() + +qt_add_executable(log-analyzer-gui + main.cpp + menu_window.h + menu_window.cpp + menu_window.ui + main_window.h + main_window.cpp + main_window.ui +) + +target_link_libraries(log-analyzer-gui PRIVATE + Qt6::Widgets + core_lib +) + +qt_finalize_executable(log-analyzer-gui) diff --git a/gui/main.cpp b/gui/main.cpp new file mode 100644 index 0000000..8d09149 --- /dev/null +++ b/gui/main.cpp @@ -0,0 +1,11 @@ +#include +#include "menu_window.h" + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + app.setApplicationName("Log analyzer"); + MenuWindow window; + window.setWindowTitle("Menu"); + window.show(); + return app.exec(); +} diff --git a/gui/main_window.cpp b/gui/main_window.cpp new file mode 100644 index 0000000..a4ad9e4 --- /dev/null +++ b/gui/main_window.cpp @@ -0,0 +1,81 @@ +#include "main_window.h" +#include "ui_main_window.h" + +void MainWindow::TimeDistributionTable(std::unordered_map& time) { + ui->TimeDistributionTable->setRowCount(24); + + for(int i = 0; i < 24; ++i) { + std::string hour = std::to_string(i) + ":00"; + int count = 0; + + if (time.find(hour) != time.end()) { + count = time[hour]; + } + + ui->TimeDistributionTable->setItem(i, 0, new QTableWidgetItem(QString::fromStdString(hour))); + ui->TimeDistributionTable->setItem(i, 1, new QTableWidgetItem(QString::number(count))); + } + +} + +void MainWindow::ErrorsTypeTable(const std::unordered_map& types) { + ui->ErrorsTypeTable->setRowCount(types.size()); + + int row = 0; + for(const auto& [type, count] : types) { + ui->ErrorsTypeTable->setItem(row, 0, new QTableWidgetItem(QString::fromStdString(type))); + ui->ErrorsTypeTable->setItem(row, 1, new QTableWidgetItem(QString::number(count))); + + ++row; + } + +} + + +void MainWindow::IPTable(const std::unordered_map& Ip) { + QVector> data; + + for(const auto& [ip, count] : Ip) { + data.append({QString::fromStdString(ip), count}); + + } + + std::sort(data.begin(), data.end(), [](const auto& a, const auto& b) { + return a.second > b.second; + }); + + ui->IPTable->setRowCount(std::min(10, static_cast (data.size()))); + + for(int i = 0; i < ui->IPTable->rowCount(); ++i) { + ui->IPTable->setItem(i, 0, new QTableWidgetItem(data[i].first)); + ui->IPTable->setItem(i, 1, new QTableWidgetItem(QString::number(data[i].second))); + } + +} + +void MainWindow::UpdateGeneralStats() { + ui->total->setText(QString::number(result_.total_processed)); + ui->failed_parses->setText(QString::number(result_.failed_parses)); + ui->successful->setText(QString::number(result_.successful_requests)); + ui->errors->setText(QString::number(result_.error_requests)); + + IPTable(result_.ip); + + ErrorsTypeTable(result_.error_counts); + + TimeDistributionTable(result_.time_distribution); +} + +MainWindow::MainWindow(const AnalysisResult& res, QWidget *parent) + : result_(res), QWidget(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + UpdateGeneralStats(); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + diff --git a/gui/main_window.h b/gui/main_window.h index e69de29..53558d7 100644 --- a/gui/main_window.h +++ b/gui/main_window.h @@ -0,0 +1,30 @@ +#ifndef MAIN_WINDOW_H +#define MAIN_WINDOW_H + +#include + +#include "../core/common.h" + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QWidget +{ + Q_OBJECT + +public: + explicit MainWindow(const AnalysisResult& res, QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + AnalysisResult result_; + + void UpdateGeneralStats(); + void IPTable(const std::unordered_map& ip); + void ErrorsTypeTable(const std::unordered_map& types); + void TimeDistributionTable(std::unordered_map& time); +}; + +#endif // MAIN_WINDOW_H diff --git a/gui/main_window.ui b/gui/main_window.ui new file mode 100644 index 0000000..0aac289 --- /dev/null +++ b/gui/main_window.ui @@ -0,0 +1,202 @@ + + + MainWindow + + + + 0 + 0 + 483 + 443 + + + + Form + + + + + 260 + 0 + 248 + 461 + + + + + + + Топ ошибочных ip + + + + + 0 + 21 + 201 + 211 + + + + + IP + + + + + Count + + + + + + + + + Распределение ошибок по времени + + + + + 0 + 20 + 201 + 192 + + + + + Time + + + + + Count + + + + + + + + + + + 0 + 0 + 261 + 461 + + + + + + + Главное + + + + + 0 + 20 + 201 + 211 + + + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + TextLabel + + + + + + + Ошибка парсинга + + + + + + + TextLabel + + + + + + + Ошибочные + + + + + + + TextLabel + + + + + + + Успешные + + + + + + + TextLabel + + + + + + + Всего + + + + + + + + + + + Типы ошибок и их количество + + + + + 0 + 20 + 211 + 192 + + + + + Type + + + + + Count + + + + + + + + + + + diff --git a/gui/menu_window.cpp b/gui/menu_window.cpp new file mode 100644 index 0000000..c3a9e29 --- /dev/null +++ b/gui/menu_window.cpp @@ -0,0 +1,59 @@ +#include "menu_window.h" +#include "main_window.h" +#include "ui_menu_window.h" +#include "../core/analyzer.h" + +MenuWindow::MenuWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MenuWindow) +{ + ui->setupUi(this); +} + +MenuWindow::~MenuWindow() +{ + delete ui; +} + +void MenuWindow::on_Review_clicked() +{ + QString file_path = QFileDialog::getOpenFileName ( + this, + tr("Выберите файл лога"), + QString(), + tr("Log files (*.log *.txt);;All files (*)") + ); + + if (!file_path.isEmpty()) { + ui->FilePath->setText(file_path); + } +} + + +void MenuWindow::on_Start_clicked() +{ + QString path = ui->FilePath->text(); + QFileInfo check_file(path); + + if (!check_file.exists()) { + QMessageBox::warning(this, "Ошибка", "Файл не найден!"); + return; + } + + QString suf = check_file.suffix().toLower(); + + if (suf != "txt" && suf != "log") { + QMessageBox::warning(this, "Ошибка", "Расширение файла не поддерживается!"); + return; + } + + Analyzer analyzer; + AnalysisResult res = analyzer.analyze(path.toStdString()); + + MainWindow *main = new MainWindow(res); + main->setWindowTitle("Analysis Result"); + main->setAttribute(Qt::WA_DeleteOnClose); + + main->show(); +} + diff --git a/gui/menu_window.h b/gui/menu_window.h new file mode 100644 index 0000000..b9304b0 --- /dev/null +++ b/gui/menu_window.h @@ -0,0 +1,29 @@ +#ifndef MENU_WINDOW_H +#define MENU_WINDOW_H + +#include +#include +#include + +namespace Ui { +class MenuWindow; +} + +class MenuWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MenuWindow(QWidget *parent = nullptr); + ~MenuWindow(); + +private slots: + void on_Review_clicked(); + + void on_Start_clicked(); + +private: + Ui::MenuWindow *ui; +}; + +#endif // MENU_WINDOW_H diff --git a/gui/menu_window.ui b/gui/menu_window.ui new file mode 100644 index 0000000..0e6355a --- /dev/null +++ b/gui/menu_window.ui @@ -0,0 +1,73 @@ + + + MenuWindow + + + + 0 + 0 + 535 + 158 + + + + MainWindow + + + + + + + + + Путь к лог файлу + + + + + + + Qt::LayoutDirection::LeftToRight + + + false + + + Обзор + + + + + + + + + Старт + + + + + + + + + 0 + 0 + 535 + 33 + + + + + Qt::LayoutDirection::RightToLeft + + + Log-analyzer + + + + + + + + diff --git a/run.sh b/run.sh deleted file mode 100755 index c952e8f..0000000 --- a/run.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/bin/bash - -BUILD_DIR="build" -TARGET="log-analyzer-cli" - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -print_info() { - echo -e "${BLUE} $1${NC}" -} - -print_success() { - echo -e "${GREEN} $1${NC}" -} - -print_warning() { - echo -e "${YELLOW} $1${NC}" -} - -print_error() { - echo -e "${RED} $1${NC}" -} - -build_project() { - print_info "Building Log Analyzer..." - mkdir -p $BUILD_DIR - cd $BUILD_DIR - cmake -DCMAKE_BUILD_TYPE=Release .. - make -j$(nproc) - cd .. - print_success "Build complete!" -} - -run_cli() { - print_info "Running Log Analyzer..." - - if [ ! -d "$BUILD_DIR" ]; then - build_project - fi - - cd $BUILD_DIR/cli - ./$TARGET -} - -run_tests() { - print_info "Running tests..." - if [ ! -d "$BUILD_DIR" ]; then - build_project - fi - cd build/tests - ./test-runner - print_success "All tests passed!" - cd ../.. -} - -clean_project() { - print_warning "Cleaning build directory..." - rm -rf $BUILD_DIR - print_success "Clean complete!" -} - -run_all() { - print_info "Starting full pipeline..." - clean_project - build_project - run_tests -} - -case "$1" in - "build") - build_project - ;; - "test") - run_tests - ;; - "clean") - clean_project - ;; - "all") - run_all - ;; - "cli") - run_cli - ;; - *) - print_error "Unknown command: $1" - exit 1 - ;; -esac \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c046de1..3f48f33 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,16 +5,17 @@ add_executable(test-runner test_reader.cpp test_parser.cpp test_statistics.cpp - test_reporter.cpp test_analyzer.cpp + tests.h ) -target_link_libraries( - test-runner +target_link_libraries(test-runner core_lib - gtest - gtest_main + GTest::gtest + GTest::gtest_main ) -add_custom_target(test DEPENDS test-runner COMMAND ./test-runner) - +add_custom_target(run-tests + COMMAND test-runner + DEPENDS test-runner +) diff --git a/tests/test_analyzer.cpp b/tests/test_analyzer.cpp index 528d7b0..9a35386 100644 --- a/tests/test_analyzer.cpp +++ b/tests/test_analyzer.cpp @@ -3,20 +3,20 @@ TEST(AnalyzerTest, StandardTest) { Analyzer analyzer; fs::path path = fs::path("test_data") / "normal_test.log"; - std::string report = analyzer.analyze(path.string()); + AnalysisResult res = analyzer.analyze(path.string()); - EXPECT_TRUE(report.find("Sucсessful - 5") != std::string::npos); - EXPECT_TRUE(report.find("Failed Parsing - 8") != std::string::npos); - EXPECT_TRUE(report.find("192.168.2.145 - 4") != std::string::npos); - EXPECT_TRUE(report.find("20:00 - 2") != std::string::npos); - EXPECT_TRUE(report.find("500 - 2") != std::string::npos); - EXPECT_FALSE(report.find("402 - 1") != std::string::npos); + EXPECT_TRUE(res.successful_requests == 5); + EXPECT_TRUE(res.failed_parses == 8); + EXPECT_TRUE(res.ip["192.168.2.145"] == 4); + EXPECT_TRUE(res.time_distribution["20:00"] == 2); + EXPECT_TRUE(res.error_counts["500"] == 2); + EXPECT_FALSE(res.error_counts["402"] == 2); } TEST(AnalyzerTest, EmptyTest) { Analyzer analyzer; fs::path path = fs::path("test_data") / "empty_test.log"; - std::string report = analyzer.analyze(path.string()); + AnalysisResult res = analyzer.analyze(path.string()); - EXPECT_TRUE(report == "invalid file format or file empty"); + EXPECT_TRUE(res.total_processed == 0); } \ No newline at end of file diff --git a/tests/test_reporter.cpp b/tests/test_reporter.cpp deleted file mode 100644 index c1efa2e..0000000 --- a/tests/test_reporter.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "tests.h" - -class ReporterTest : public ::testing::Test { -protected: - AnalysisResult createSampleData() { - AnalysisResult result; - - result.total_processed = 1000; - result.successful_requests = 850; - result.error_requests = 150; - result.failed_parses = 5; - - result.ip["192.168.1.100"] = 45; - result.ip["10.0.0.25"] = 33; - result.ip["203.0.113.45"] = 22; - - result.time_distribution["14:00"] = 15; - result.time_distribution["15:00"] = 8; - - result.error_counts["404"] = 60; - result.error_counts["500"] = 25; - result.error_counts["403"] = 15; - - return result; - } - - AnalysisResult createEmptyData() { - AnalysisResult result; - return result; - } -}; - -TEST_F(ReporterTest, NormalTest) { - AnalysisResult res = createSampleData(); - Reporter reporter; - std::string report = reporter.GenerateTextReport(res); - - EXPECT_TRUE(report.find("Sucсessful - 850") != std::string::npos); - EXPECT_TRUE(report.find("Failed Parsing - 5") != std::string::npos); - EXPECT_TRUE(report.find("10.0.0.25 - 33") != std::string::npos); - EXPECT_TRUE(report.find("14:00 - 15") != std::string::npos); - EXPECT_TRUE(report.find("404 - 60") != std::string::npos); - EXPECT_FALSE(report.find("303 - 3") != std::string::npos); - -} - -TEST_F(ReporterTest, EmptyTest) { - AnalysisResult res = createEmptyData(); - Reporter reporter; - std::string report = reporter.GenerateTextReport(res); - - EXPECT_TRUE(report == "invalid file format or file empty"); -} diff --git a/tests/tests.h b/tests/tests.h index a591ed3..dbf032d 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -3,7 +3,6 @@ #include "../core/reader.h" #include "../core/parser.h" #include "../core/statistics.h" -#include "../core/reporter.h" #include "../core/analyzer.h" #include