Skip to content

Commit f535a65

Browse files
Phase 6 COMPLETE: Refactoring & CMake
- Migrated build system to CMakeLists.txt. - Added Thread Safety (std::mutex) to GuiController. - Fixed potential Division-By-Zero in Pipeline progress logic. - Replaced magic parsing numbers with constexpr constants. - Added Unit Tests for edge cases (empty line, corrupt format). - Removed legacy Makefile.
1 parent 2b70595 commit f535a65

File tree

7 files changed

+145
-21
lines changed

7 files changed

+145
-21
lines changed

.github/workflows/ci.yml

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,27 @@ name: C++ CI
22

33
on:
44
push:
5-
branches: [ "main" ]
5+
branches: ["main"]
66
pull_request:
7-
branches: [ "main" ]
7+
branches: ["main"]
88

99
jobs:
1010
build:
1111
runs-on: macos-latest
1212

1313
steps:
14-
- uses: actions/checkout@v3
14+
- uses: actions/checkout@v3
1515

16-
- name: install dependencies
17-
run: |
18-
brew install glfw
19-
brew install grep
16+
- name: install dependencies
17+
run: |
18+
brew install glfw
19+
brew install cmake
2020
21-
- name: Build CLI
22-
run: make log_analyzer
21+
- name: Configure CMake
22+
run: cmake -B build -S .
2323

24-
- name: Build Tests
25-
run: make build-tests
24+
- name: Build
25+
run: cmake --build build --config Release
2626

27-
- name: Run Tests
28-
run: make test
29-
30-
- name: Build GUI
31-
run: make build-gui
27+
- name: Test
28+
run: cd build && ctest --output-on-failure

CMakeLists.txt

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
cmake_minimum_required(VERSION 3.14)
2+
project(LogAnalyzer VERSION 1.0.0 LANGUAGES CXX)
3+
4+
set(CMAKE_CXX_STANDARD 17)
5+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
6+
set(CMAKE_CXX_EXTENSIONS OFF)
7+
8+
# Build settings
9+
if(NOT CMAKE_BUILD_TYPE)
10+
set(CMAKE_BUILD_TYPE Release)
11+
endif()
12+
13+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")
14+
15+
# --- Dependencies ---
16+
find_package(OpenGL REQUIRED)
17+
find_library(GLFW_LIB glfw REQUIRED)
18+
find_library(COCOA_LIB Cocoa)
19+
find_library(IOKIT_LIB IOKit)
20+
find_library(COREVIDEO_LIB CoreVideo)
21+
22+
# --- ImGui Library ---
23+
set(IMGUI_DIR "external/imgui")
24+
set(IMGUI_SOURCES
25+
${IMGUI_DIR}/imgui.cpp
26+
${IMGUI_DIR}/imgui_demo.cpp
27+
${IMGUI_DIR}/imgui_draw.cpp
28+
${IMGUI_DIR}/imgui_tables.cpp
29+
${IMGUI_DIR}/imgui_widgets.cpp
30+
${IMGUI_DIR}/backends/imgui_impl_glfw.cpp
31+
${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp
32+
)
33+
34+
add_library(imgui STATIC ${IMGUI_SOURCES})
35+
target_include_directories(imgui PUBLIC
36+
${IMGUI_DIR}
37+
${IMGUI_DIR}/backends
38+
)
39+
40+
# --- Core Sources ---
41+
set(CORE_SOURCES
42+
core/LogParser.cpp
43+
core/Timestamp.cpp
44+
io/FileReader.cpp
45+
io/FileWriter.cpp
46+
analysis/LevelCountAnalyzer.cpp
47+
analysis/KeywordHitAnalyzer.cpp
48+
analysis/TopErrorAnalyzer.cpp
49+
analysis/TimeRangeFilter.cpp
50+
analysis/Pipeline.cpp
51+
app/Application.cpp
52+
)
53+
54+
# --- CLI Executable ---
55+
add_executable(log_analyzer
56+
${CORE_SOURCES}
57+
report/TextReportRenderer.cpp
58+
main.cpp
59+
)
60+
61+
# --- GUI Executable ---
62+
add_executable(log_analyzer_gui
63+
${CORE_SOURCES}
64+
gui/GuiController.cpp
65+
gui/main_gui.cpp
66+
)
67+
target_link_libraries(log_analyzer_gui PRIVATE imgui ${GLFW_LIB} ${COCOA_LIB} ${IOKIT_LIB} ${COREVIDEO_LIB} OpenGL::GL)
68+
69+
# --- Tests ---
70+
enable_testing()
71+
72+
add_executable(unit_tests
73+
${CORE_SOURCES}
74+
tests/test_parser_catch2.cpp
75+
tests/test_analyzers_catch2.cpp
76+
tests/test_main_catch2.cpp
77+
external/catch2/catch_amalgamated.cpp
78+
)
79+
target_include_directories(unit_tests PRIVATE external/catch2)
80+
81+
add_test(NAME AllTests COMMAND unit_tests)

analysis/Pipeline.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ AnalysisResult Pipeline::run(const std::string &inputPath,
5252
size_t lineNumber;
5353
uint64_t bytesProcessed = 0;
5454
uint64_t lastReportedProgress = 0;
55-
const uint64_t progressInterval = fileSize / 100; // Report every 1%
55+
// Report every 1% or 1MB, whichever is larger, to avoid too frequent
56+
// callbacks on small files and ensure we don't divide by zero logic if
57+
// fileSize is small (though here we use subtraction) std::max ensures it's
58+
// never 0 if we pick a minimum like 1024.
59+
const uint64_t progressInterval =
60+
std::max<uint64_t>(fileSize / 100, 1024 * 1024);
5661

5762
while (reader.nextLine(line, lineNumber)) {
5863
result.totalLines++;

core/LogParser.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@
22

33
namespace loganalyzer {
44

5+
// Constants for robust parsing
6+
namespace {
7+
constexpr size_t MIN_LINE_LENGTH = 27; // [YYYY-MM-DD HH:MM:SS] [L] m
8+
constexpr size_t TIMESTAMP_LENGTH = 19; // YYYY-MM-DD HH:MM:SS
9+
constexpr size_t TIMESTAMP_MIN_END = 20; // Length of [ + TIMESTAMP + ]
10+
} // namespace
11+
512
ParseResult LogParser::parse(const std::string &line, size_t lineNumber) {
613
// Expected format: [YYYY-MM-DD HH:MM:SS] [LEVEL] message
714
std::string_view sv(line);
815

916
// Minimum length check
10-
if (sv.length() < 27) {
17+
if (sv.length() < MIN_LINE_LENGTH) {
1118
return ParseError{ParseErrorCode::BadFormat, line, lineNumber};
1219
}
1320

@@ -18,13 +25,13 @@ ParseResult LogParser::parse(const std::string &line, size_t lineNumber) {
1825

1926
// Find closing bracket for timestamp (more robust than fixed position)
2027
size_t tsEnd = sv.find(']', 1);
21-
if (tsEnd == std::string_view::npos || tsEnd < 20) {
28+
if (tsEnd == std::string_view::npos || tsEnd < TIMESTAMP_MIN_END) {
2229
return ParseError{ParseErrorCode::BadFormat, line, lineNumber};
2330
}
2431

2532
// Extract timestamp (zero-copy with string_view)
2633
std::string_view tsView = sv.substr(1, tsEnd - 1);
27-
if (tsView.length() != 19) {
34+
if (tsView.length() != TIMESTAMP_LENGTH) {
2835
return ParseError{ParseErrorCode::BadFormat, line, lineNumber};
2936
}
3037

gui/GuiController.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,14 @@ void GuiController::startAnalysis() {
237237
return !cancelRequested_.load();
238238
};
239239

240-
lastResult_ = app_.run(currentRequest_, callback);
240+
AppResult result = app_.run(currentRequest_, callback);
241+
242+
{
243+
// Assuming resultMutex_ is a member of GuiController
244+
std::lock_guard<std::mutex> lock(resultMutex_);
245+
lastResult_ = result;
246+
}
247+
241248
analysisComplete_ = true;
242249
});
243250
}
@@ -249,6 +256,7 @@ void GuiController::checkAnalysisComplete() {
249256
}
250257
isAnalyzing_ = false;
251258

259+
std::lock_guard<std::mutex> lock(resultMutex_);
252260
if (lastResult_.wasCancelled) {
253261
hasResults_ = false;
254262
showError_ = false;

gui/GuiController.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "../core/Timestamp.h"
77
#include <atomic>
88
#include <filesystem>
9+
#include <mutex>
910
#include <optional>
1011
#include <string>
1112
#include <thread>
@@ -52,6 +53,7 @@ class GuiController {
5253

5354
bool hasResults_;
5455
AppResult lastResult_;
56+
mutable std::mutex resultMutex_; // Protects shared result data
5557

5658
bool showError_;
5759
std::string errorMessage_;

tests/test_parser_catch2.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,27 @@ TEST_CASE("Timestamp validation rejects invalid dates", "[timestamp]") {
9595
CHECK_FALSE(Timestamp::parse("2026-04-31 10:30:15", ts));
9696
}
9797
}
98+
99+
TEST_CASE("LogParser handles edge cases", "[parser][edge]") {
100+
SECTION("Empty line is rejected") {
101+
ParseResult res = LogParser::parse("", 1);
102+
REQUIRE(std::holds_alternative<ParseError>(res));
103+
CHECK(std::get<ParseError>(res).code == ParseErrorCode::BadFormat);
104+
}
105+
106+
SECTION("Line too short is rejected") {
107+
ParseResult res = LogParser::parse("[2026]", 2);
108+
REQUIRE(std::holds_alternative<ParseError>(res));
109+
CHECK(std::get<ParseError>(res).code == ParseErrorCode::BadFormat);
110+
}
111+
112+
SECTION("Missing closing brackets") {
113+
// Missing timestamp bracket
114+
ParseResult res1 = LogParser::parse("[2026-01-01 10:00:00 [INFO] Msg", 3);
115+
REQUIRE(std::holds_alternative<ParseError>(res1));
116+
117+
// Missing level bracket
118+
ParseResult res2 = LogParser::parse("[2026-01-01 10:00:00] [INFO Msg", 4);
119+
REQUIRE(std::holds_alternative<ParseError>(res2));
120+
}
121+
}

0 commit comments

Comments
 (0)