From 3bbc16ad4a610eb6953c6c0515afe32dde167e89 Mon Sep 17 00:00:00 2001 From: Bob Sira Date: Thu, 22 Oct 2026 17:26:11 +0100 Subject: [PATCH 1/4] Fix Log Monitor lint and cover test sources --- .github/workflows/lint.yml | 50 +- LogMonitor/CPPLINT.cfg | 3 + .../LogMonitorTests/EtwMonitorTests.cpp | 400 ++-- .../LogMonitorTests/EventMonitorTests.cpp | 597 +++--- .../LogMonitorTests/JsonProcessorTests.cpp | 231 +-- .../LogMonitorTests/LogFileMonitorTests.cpp | 1720 +++++++++-------- .../LogMonitorTests/LogMonitorTests.cpp | 97 +- LogMonitor/LogMonitorTests/Utility.cpp | 9 +- LogMonitor/LogMonitorTests/Utility.h | 8 +- LogMonitor/LogMonitorTests/UtilityTests.cpp | 152 +- LogMonitor/LogMonitorTests/pch.cpp | 4 +- LogMonitor/LogMonitorTests/pch.h | 17 +- LogMonitor/README.md | 21 + LogMonitor/src/LogMonitor/EtwMonitor.cpp | 3 + LogMonitor/src/LogMonitor/EventMonitor.cpp | 1 + LogMonitor/src/LogMonitor/JsonFileParser.cpp | 4 +- LogMonitor/src/LogMonitor/LogFileMonitor.cpp | 5 +- LogMonitor/src/LogMonitor/Main.cpp | 3 + LogMonitor/src/LogMonitor/ProcessMonitor.cpp | 1 + LogMonitor/src/LogMonitor/Utility.cpp | 1 + LogMonitor/src/LogMonitor/pch.h | 4 +- 21 files changed, 1711 insertions(+), 1620 deletions(-) create mode 100644 LogMonitor/CPPLINT.cfg diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9d768b58..83adc399 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,27 +8,41 @@ on: branches: - main +permissions: + contents: read + checks: write + jobs: lint-cpp: name: Lint C++ runs-on: ubuntu-latest steps: - - uses: actions/checkout@master - - uses: reviewdog/action-cpplint@master + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install cpplint + run: python -m pip install cpplint==2.0.2 + - name: Set up reviewdog + if: github.event_name == 'pull_request' + uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0 with: - github_token: ${{ secrets.github_token }} - reporter: github-pr-check - level: warning - flags: --linelength=120 --exclude=LogMonitor/LogMonitorTests/* - filter: +build/include_order - ,+build/include_subdir - ,+whitespace/comma - ,+whitespace/end_of_line - ,-build/namespaces - ,-readability/casting - ,-runtime/reference - ,-whitespace/braces - ,-whitespace/parens - ,-whitespace/newline - ,-whitespace/comments - ,-build/c++11 + reviewdog_version: v0.21.2 + - name: Lint production and test sources + id: cpplint + working-directory: LogMonitor + shell: bash + run: | + git ls-files -z -- '*.cpp' '*.h' | + xargs -0 --no-run-if-empty python -m cpplint 2>&1 | + tee "$RUNNER_TEMP/cpplint.log" + - name: Report lint annotations + if: >- + ${{ !cancelled() && github.event_name == 'pull_request' && + (steps.cpplint.outcome == 'success' || steps.cpplint.outcome == 'failure') }} + working-directory: LogMonitor + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + reviewdog -efm="%f:%l: %m" -name=cpplint -reporter=github-pr-check -level=warning \ + < "$RUNNER_TEMP/cpplint.log" diff --git a/LogMonitor/CPPLINT.cfg b/LogMonitor/CPPLINT.cfg new file mode 100644 index 00000000..98f126dc --- /dev/null +++ b/LogMonitor/CPPLINT.cfg @@ -0,0 +1,3 @@ +set noparent +linelength=120 +filter=+build/include_order,+build/include_subdir,+whitespace/comma,+whitespace/end_of_line,-build/namespaces,-readability/casting,-runtime/reference,-whitespace/braces,-whitespace/parens,-whitespace/newline,-whitespace/comments,-build/c++11 diff --git a/LogMonitor/LogMonitorTests/EtwMonitorTests.cpp b/LogMonitor/LogMonitorTests/EtwMonitorTests.cpp index bcfa486f..c7137dd6 100644 --- a/LogMonitor/LogMonitorTests/EtwMonitorTests.cpp +++ b/LogMonitor/LogMonitorTests/EtwMonitorTests.cpp @@ -3,7 +3,9 @@ // Licensed under the MIT license. // -#include "pch.h" +#include "pch.h" // NOLINT(build/include_subdir) +#include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) using namespace Microsoft::VisualStudio::CppUnitTestFramework; @@ -11,242 +13,242 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; #define BUFFER_SIZE 65536 namespace LogMonitorTests +{ +/// +/// Tests of ETW Monitor. This class listens "Microsoft-Windows-User-Diagnostic" +/// provider and tests that the Monitor prints the events to stdout. +/// +TEST_CLASS(EtwMonitorTests) { /// - /// Tests of ETW Monitor. This class listens "Microsoft-Windows-User-Diagnostic" - /// provider and tests that the Monitor prints the events to stdout. + /// The waiting time for monitor operations /// - TEST_CLASS(EtwMonitorTests) - { - /// - /// The waiting time for monitor operations - /// - const DWORD WAIT_TIME_ETWMONITOR_START = 3000; + const DWORD WAIT_TIME_ETWMONITOR_START = 3000; - const int READ_OUTPUT_RETRIES = 4; + const int READ_OUTPUT_RETRIES = 4; - WCHAR bigOutBuf[BUFFER_SIZE]; + WCHAR bigOutBuf[BUFFER_SIZE]; - /// - /// Gets the content of the Stdout buffer and returns it in a wstring. - /// - /// \return A wstring with the stdout. - /// - std::wstring RecoverOuput() - { - return std::wstring(bigOutBuf); - } + /// + /// Gets the content of the Stdout buffer and returns it in a wstring. + /// + /// \return A wstring with the stdout. + /// + std::wstring RecoverOuput() + { + return std::wstring(bigOutBuf); + } - public: +public: + /// + /// "Redirects" the stdout to our buffer. + /// + TEST_METHOD_INITIALIZE(InitializeEtwMonitorTests) + { + // + // Set our own buffer in stdout. + // + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); + _setmode(_fileno(stdout), _O_U16TEXT); + setvbuf(stdout, (char*)bigOutBuf, _IOFBF, sizeof(bigOutBuf) - sizeof(WCHAR)); + } + + /// + /// Check that EtwMonitor receives events from "Microsoft-Windows-User-Diagnostic" + /// provider. + /// + TEST_METHOD(TestEtwMonitorReceivesEvents) + { + ETWProvider provider; + provider.ProviderName = L"Microsoft-Windows-User-Diagnostic"; + provider.SetProviderGuid(L"305FC87B-002A-5E26-D297-60223012CA9C"); + provider.Level = 3; // Warning + provider.Keywords = 0; - /// - /// "Redirects" the stdout to our buffer. - /// - TEST_METHOD_INITIALIZE(InitializeEtwMonitorTests) + std::vector etwProviders = { provider }; { // - // Set our own buffer in stdout. + // Start ETW Monitor // ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); fflush(stdout); - _setmode(_fileno(stdout), _O_U16TEXT); - setvbuf(stdout, (char*)bigOutBuf, _IOFBF, sizeof(bigOutBuf) - sizeof(WCHAR)); - } - /// - /// Check that EtwMonitor receives events from "Microsoft-Windows-User-Diagnostic" - /// provider. - /// - TEST_METHOD(TestEtwMonitorReceivesEvents) - { - ETWProvider provider; - provider.ProviderName = L"Microsoft-Windows-User-Diagnostic"; - provider.SetProviderGuid(L"305FC87B-002A-5E26-D297-60223012CA9C"); - provider.Level = 3; // Warning - provider.Keywords = 0; - - std::vector etwProviders = { provider }; - { - // - // Start ETW Monitor - // - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); - - EtwMonitor etwMonitor(etwProviders, L"json", L""); - Sleep(WAIT_TIME_ETWMONITOR_START); - - - std::wstring output = RecoverOuput(); - - // - // The "Microsoft-Windows-User-Diagnostic" provider produces events - // like this (without the space formatting / one-line): - // - /* - { - "Source": "ETW", - "LogEntry" : { - "Time": "2022-12-22T13:39:20.000Z", - "ProviderName" : "Microsoft-Windows-User-Diagnostic", - "ProviderId" : "{305FC87B-002A-5E26-D297-60223012CA9C}", - "DecodingSource" : "DecodingSourceXMLFile", - "Execution" : { - "ProcessId": 35344, - "ThreadId" : 36976 - }, - "Level" : "Warning", - "Keyword" : "0x0", - "EventId" : 1, - "EventData" : { - "ErrorCode": "0x102" - } - }, - "SchemaVersion": "1.0.0" - } - */ - // - - // - // Verify time was printed. - // - std::wregex rgxTime(L"\"Time\":\"\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z\","); - - Assert::IsTrue(std::regex_search(output, rgxTime), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - - // - // Verify provider GUID was printed. - // - std::wregex rgxProvider(L"\"ProviderId\":\".*[\\da-fA-F]{8}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{12}"); - - Assert::IsTrue(std::regex_search(output, rgxProvider), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - - // - // Verify level was printed. - // - std::wregex rgxLevel(L"\"Level\":\"(None|Critical|Error|Warning|Information|Verbose)\","); - - Assert::IsTrue(std::regex_search(output, rgxLevel), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - - // - // Verify keyword was printed. - // - std::wregex rgxKeyword(L"\"Keyword\":\"0x[\\da-fA-F]+\","); - - Assert::IsTrue(std::regex_search(output, rgxKeyword), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - - // - // Verify event data was printed. - // - std::wregex rgxData(L"\"EventData\":.*\"ErrorCode\":\"0x[\\da-fA-F]+\".*"); - - Assert::IsTrue(std::regex_search(output, rgxData), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - } - } + EtwMonitor etwMonitor(etwProviders, L"json", L""); + Sleep(WAIT_TIME_ETWMONITOR_START); - /// - /// Check that EtwMonitor can search for a provider, using only its name. - /// - TEST_METHOD(TestEtwMonitorOnlyProviderName) - { - ETWProvider provider; - provider.ProviderName = L"Microsoft-Windows-User-Diagnostic"; - provider.Level = 3; // Warning - provider.Keywords = 0; - std::vector etwProviders = { provider }; + std::wstring output = RecoverOuput(); // - // Start ETW Monitor + // The "Microsoft-Windows-User-Diagnostic" provider produces events + // like this (without the space formatting / one-line): + // + /* + { + "Source": "ETW", + "LogEntry" : { + "Time": "2022-12-22T13:39:20.000Z", + "ProviderName" : "Microsoft-Windows-User-Diagnostic", + "ProviderId" : "{305FC87B-002A-5E26-D297-60223012CA9C}", + "DecodingSource" : "DecodingSourceXMLFile", + "Execution" : { + "ProcessId": 35344, + "ThreadId" : 36976 + }, + "Level" : "Warning", + "Keyword" : "0x0", + "EventId" : 1, + "EventData" : { + "ErrorCode": "0x102" + } + }, + "SchemaVersion": "1.0.0" + } + */ // - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); - EtwMonitor etwMonitor(etwProviders, L"json", L""); + // + // Verify time was printed. + // + std::wregex rgxTime(L"\"Time\":\"\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z\","); + + Assert::IsTrue(std::regex_search(output, rgxTime), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); // - // It must find the provider, and start printing events. + // Verify provider GUID was printed. // - std::wstring output; - int count = 0; - do { - Sleep(WAIT_TIME_ETWMONITOR_START); - output = RecoverOuput(); - } while (output.empty() && ++count < READ_OUTPUT_RETRIES); - - Assert::AreNotEqual(L"", output.c_str()); - } + std::wregex rgxProvider( + L"\"ProviderId\":\".*[\\da-fA-F]{8}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{12}"); - /// - /// Check that, if the provider name doesn't exist in the system, the - /// monitor's constructor fails. - /// - TEST_METHOD(TestEtwMonitorInvalidProviderName) - { - ETWProvider provider; - provider.ProviderName = L"Non-existing provider"; - provider.Level = 3; // Warning - provider.Keywords = 0; + Assert::IsTrue(std::regex_search(output, rgxProvider), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - std::vector etwProviders = { provider }; + // + // Verify level was printed. + // + std::wregex rgxLevel(L"\"Level\":\"(None|Critical|Error|Warning|Information|Verbose)\","); + + Assert::IsTrue(std::regex_search(output, rgxLevel), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); // - // Start ETW Monitor + // Verify keyword was printed. // - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); + std::wregex rgxKeyword(L"\"Keyword\":\"0x[\\da-fA-F]+\","); + Assert::IsTrue(std::regex_search(output, rgxKeyword), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + + // + // Verify event data was printed. + // + std::wregex rgxData(L"\"EventData\":.*\"ErrorCode\":\"0x[\\da-fA-F]+\".*"); - std::function f1 = [&etwProviders] { EtwMonitor etwMonitor(etwProviders, L"json", L""); }; - Assert::ExpectException(f1); + Assert::IsTrue(std::regex_search(output, rgxData), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); } + } - /// - /// Check that EtwMonitor level defaults to Error when level is not given - /// - TEST_METHOD(TestEtwMonitorNoLevelProvided) - { - //provider whose level is specified as error - ETWProvider providerWithLevel; - providerWithLevel.ProviderName = L"Microsoft-Windows-User-Diagnostic"; - providerWithLevel.Level = 2; // Error - providerWithLevel.Keywords = 0; + /// + /// Check that EtwMonitor can search for a provider, using only its name. + /// + TEST_METHOD(TestEtwMonitorOnlyProviderName) + { + ETWProvider provider; + provider.ProviderName = L"Microsoft-Windows-User-Diagnostic"; + provider.Level = 3; // Warning + provider.Keywords = 0; + + std::vector etwProviders = { provider }; + + // + // Start ETW Monitor + // + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); + + EtwMonitor etwMonitor(etwProviders, L"json", L""); + + // + // It must find the provider, and start printing events. + // + std::wstring output; + int count = 0; + do { + Sleep(WAIT_TIME_ETWMONITOR_START); + output = RecoverOuput(); + } while (output.empty() && ++count < READ_OUTPUT_RETRIES); + + Assert::AreNotEqual(L"", output.c_str()); + } + + /// + /// Check that, if the provider name doesn't exist in the system, the + /// monitor's constructor fails. + /// + TEST_METHOD(TestEtwMonitorInvalidProviderName) + { + ETWProvider provider; + provider.ProviderName = L"Non-existing provider"; + provider.Level = 3; // Warning + provider.Keywords = 0; - ETWProvider providerWithoutLevel; - providerWithoutLevel.ProviderName = L"Microsoft-Windows-User-Diagnostic"; - providerWithoutLevel.Keywords = 0; + std::vector etwProviders = { provider }; - std::vector etwProviders = { providerWithLevel, providerWithoutLevel }; - EtwMonitor etwMonitor(etwProviders, L"json", L""); + // + // Start ETW Monitor + // + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); - std::wstring output1, output2; - int count1 = 0, count2 = 0; - do { - Sleep(WAIT_TIME_ETWMONITOR_START); - output1 = RecoverOuput(); - } while (output1.empty() && ++count1 < READ_OUTPUT_RETRIES); + std::function f1 = [&etwProviders] { EtwMonitor etwMonitor(etwProviders, L"json", L""); }; + Assert::ExpectException(f1); + } - - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); + /// + /// Check that EtwMonitor level defaults to Error when level is not given + /// + TEST_METHOD(TestEtwMonitorNoLevelProvided) + { + //provider whose level is specified as error + ETWProvider providerWithLevel; + providerWithLevel.ProviderName = L"Microsoft-Windows-User-Diagnostic"; + providerWithLevel.Level = 2; // Error + providerWithLevel.Keywords = 0; - do { - Sleep(WAIT_TIME_ETWMONITOR_START); - output2 = RecoverOuput(); - } while (output2.empty() && ++count2 < READ_OUTPUT_RETRIES); + ETWProvider providerWithoutLevel; + providerWithoutLevel.ProviderName = L"Microsoft-Windows-User-Diagnostic"; + providerWithoutLevel.Keywords = 0; + std::vector etwProviders = { providerWithLevel, providerWithoutLevel }; + EtwMonitor etwMonitor(etwProviders, L"json", L""); - //assert that both outputs are the equal. - Assert::AreEqual(output1.c_str(), output2.c_str()); - } - }; -} \ No newline at end of file + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); + + std::wstring output1, output2; + int count1 = 0, count2 = 0; + do { + Sleep(WAIT_TIME_ETWMONITOR_START); + output1 = RecoverOuput(); + } while (output1.empty() && ++count1 < READ_OUTPUT_RETRIES); + + + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); + + do { + Sleep(WAIT_TIME_ETWMONITOR_START); + output2 = RecoverOuput(); + } while (output2.empty() && ++count2 < READ_OUTPUT_RETRIES); + + + //assert that both outputs are the equal. + Assert::AreEqual(output1.c_str(), output2.c_str()); + } +}; +} // namespace LogMonitorTests diff --git a/LogMonitor/LogMonitorTests/EventMonitorTests.cpp b/LogMonitor/LogMonitorTests/EventMonitorTests.cpp index 724fe866..29ce8ab5 100644 --- a/LogMonitor/LogMonitorTests/EventMonitorTests.cpp +++ b/LogMonitor/LogMonitorTests/EventMonitorTests.cpp @@ -3,7 +3,9 @@ // Licensed under the MIT license. // -#include "pch.h" +#include "pch.h" // NOLINT(build/include_subdir) +#include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) using namespace Microsoft::VisualStudio::CppUnitTestFramework; @@ -11,403 +13,402 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; #define BUFFER_SIZE 65536 namespace LogMonitorTests +{ +/// +/// Tests of Event Monitor. This class writes events and tests that the +/// Monitor prints them to stdout. +/// +TEST_CLASS(EventMonitorTests) { /// - /// Tests of Event Monitor. This class writes events and tests that the - /// Monitor prints them to stdout. + /// The waiting time after event creation /// - TEST_CLASS(EventMonitorTests) + const DWORD WAIT_TIME_EVENTMONITOR_START = 100; + const DWORD WAIT_TIME_EVENTMONITOR_AFTER_WRITE_SHORT = 50; + const DWORD WAIT_TIME_EVENTMONITOR_AFTER_WRITE_LONG = 150; + + const int READ_OUTPUT_RETRIES = 8; + + // + // Level + // + constexpr static LPWSTR c_LevelToString[] = { - /// - /// The waiting time after event creation - /// - const DWORD WAIT_TIME_EVENTMONITOR_START = 100; - const DWORD WAIT_TIME_EVENTMONITOR_AFTER_WRITE_SHORT = 50; - const DWORD WAIT_TIME_EVENTMONITOR_AFTER_WRITE_LONG = 150; + L"None", + L"Critical", + L"Error", + L"Warning", + L"Information", + L"Verbose", + }; - const int READ_OUTPUT_RETRIES = 8; + WCHAR bigOutBuf[BUFFER_SIZE]; - // - // Level - // - constexpr static LPWSTR c_LevelToString[] = - { - L"None", - L"Critical", - L"Error", - L"Warning", - L"Information", - L"Verbose", - }; - - WCHAR bigOutBuf[BUFFER_SIZE]; - - /// - /// Gets the content of the Stdout buffer and returns it in a wstring. - /// - /// \return A wstring with the stdout. - /// - std::wstring RecoverOuput() - { - return std::wstring(bigOutBuf); - } + /// + /// Gets the content of the Stdout buffer and returns it in a wstring. + /// + /// \return A wstring with the stdout. + /// + std::wstring RecoverOuput() + { + return std::wstring(bigOutBuf); + } - /// - /// Writes an event - /// - /// \return Return true if the event was succesfully sent. - /// - int WriteEvent(EventChannelLogLevel Level, - int Id, - std::wstring Message) + /// + /// Writes an event + /// + /// \return Return true if the event was succesfully sent. + /// + int WriteEvent(EventChannelLogLevel Level, + int Id, + std::wstring Message) + { + if (Level <= EventChannelLogLevel::Critical || Level >= EventChannelLogLevel::Verbose) { - if (Level <= EventChannelLogLevel::Critical || Level >= EventChannelLogLevel::Verbose) - { - return ERROR_INVALID_PARAMETER; - } - - int status = _wsystem( - Utility::FormatString( - L"powershell eventcreate /t %s /id %d /l APPLICATION /d \"\"\"\"%s\"\"\"\"", - c_LevelToString[(int)Level], - Id, - Message.c_str() - ).c_str() - ); - - return status; + return ERROR_INVALID_PARAMETER; } + int status = _wsystem( + Utility::FormatString( + L"powershell eventcreate /t %s /id %d /l APPLICATION /d \"\"\"\"%s\"\"\"\"", + c_LevelToString[(int)Level], + Id, + Message.c_str() + ).c_str() + ); + + return status; + } + + +public: + /// + /// "Redirects" the stdout to our buffer. + /// + TEST_METHOD_INITIALIZE(InitializeEventMonitorTests) + { + // + // Set our own buffer in stdout. + // + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); + _setmode(_fileno(stdout), _O_U16TEXT); + setvbuf(stdout, (char*)bigOutBuf, _IOFBF, sizeof(bigOutBuf) - sizeof(WCHAR)); + } + + /// + /// Check that EventMonitor prints the written errors when level <= Error. + /// + TEST_METHOD(TestErrorLevelMonitor) + { + std::vector eventChannels = { {L"Application", EventChannelLogLevel::Error} }; - public: + EventMonitor eventMonitor(eventChannels, true, false, L"json", L""); + Sleep(WAIT_TIME_EVENTMONITOR_START); - /// - /// "Redirects" the stdout to our buffer. - /// - TEST_METHOD_INITIALIZE(InitializeEventMonitorTests) + // + // Write an Error level event. + // { - // - // Set our own buffer in stdout. - // ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); fflush(stdout); - _setmode(_fileno(stdout), _O_U16TEXT); - setvbuf(stdout, (char*)bigOutBuf, _IOFBF, sizeof(bigOutBuf) - sizeof(WCHAR)); - } - /// - /// Check that EventMonitor prints the written errors when level <= Error. - /// - TEST_METHOD(TestErrorLevelMonitor) - { - std::vector eventChannels = { {L"Application", EventChannelLogLevel::Error} }; + std::wstring message = L"Hello world Error!"; + int eventId = 100; + EventChannelLogLevel level = EventChannelLogLevel::Error; - EventMonitor eventMonitor(eventChannels, true, false, L"json", L""); - Sleep(WAIT_TIME_EVENTMONITOR_START); + Assert::AreEqual(0, WriteEvent(level, eventId, message)); - // - // Write an Error level event. - // + std::wstring output; + int count = 0; + + std::wregex rgxMessage(Utility::FormatString(L"\"Message\": \"%s\"", message.c_str())); + + do { - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); + Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (!std::regex_search(output, rgxMessage) && READ_OUTPUT_RETRIES > ++count); - std::wstring message = L"Hello world Error!"; - int eventId = 100; - EventChannelLogLevel level = EventChannelLogLevel::Error; + Assert::IsTrue(std::regex_search(output, rgxMessage), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - Assert::AreEqual(0, WriteEvent(level, eventId, message)); + Assert::IsTrue(std::regex_search(output, rgxMessage), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - std::wstring output; - int count = 0; - - std::wregex rgxMessage(Utility::FormatString(L"\"Message\": \"%s\"", message.c_str())); + std::wregex rgxId(Utility::FormatString(L"\"EventId\": %d", eventId)); - do - { - Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (!std::regex_search(output, rgxMessage) && READ_OUTPUT_RETRIES > ++count); + Assert::IsTrue(std::regex_search(output, rgxId), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - Assert::IsTrue(std::regex_search(output, rgxMessage), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + std::wregex rgxLevel(Utility::FormatString(L"\"Level\": \"%s\"", c_LevelToString[(int)level])); - Assert::IsTrue(std::regex_search(output, rgxMessage), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + Assert::IsTrue(std::regex_search(output, rgxLevel), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + } - std::wregex rgxId(Utility::FormatString(L"\"EventId\": %d", eventId)); + // + // Write an Warning level event. + // + { + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); - Assert::IsTrue(std::regex_search(output, rgxId), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + std::wstring message = L"Hello world Warning!"; + int eventId = 101; + EventChannelLogLevel level = EventChannelLogLevel::Warning; - std::wregex rgxLevel(Utility::FormatString(L"\"Level\": \"%s\"", c_LevelToString[(int)level])); + Assert::AreEqual(0, WriteEvent(level, eventId, message)); - Assert::IsTrue(std::regex_search(output, rgxLevel), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - } + std::wstring output; + + Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_LONG); + output = RecoverOuput(); // - // Write an Warning level event. + // Monitor should have ignored this event. // - { - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); + std::wregex rgxMessage(Utility::FormatString(L"\"Message\": \"%s\"", message.c_str())); - std::wstring message = L"Hello world Warning!"; - int eventId = 101; - EventChannelLogLevel level = EventChannelLogLevel::Warning; + Assert::IsFalse(std::regex_search(output, rgxMessage), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + } - Assert::AreEqual(0, WriteEvent(level, eventId, message)); + // + // Write an Information level event. + // + { + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); - std::wstring output; + std::wstring message = L"Hello world Info!"; + int eventId = 102; + EventChannelLogLevel level = EventChannelLogLevel::Information; - Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_LONG); - output = RecoverOuput(); + Assert::AreEqual(0, WriteEvent(level, eventId, message)); - // - // Monitor should have ignored this event. - // - std::wregex rgxMessage(Utility::FormatString(L"\"Message\": \"%s\"", message.c_str())); + std::wstring output; - Assert::IsFalse(std::regex_search(output, rgxMessage), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - } + Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_LONG); + output = RecoverOuput(); // - // Write an Information level event. + // Monitor should have ignored this event. // - { - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); + std::wregex rgxMessage(Utility::FormatString(L"\"Message\": \"%s\"", message.c_str())); - std::wstring message = L"Hello world Info!"; - int eventId = 102; - EventChannelLogLevel level = EventChannelLogLevel::Information; + Assert::IsFalse(std::regex_search(output, rgxMessage), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + } + } - Assert::AreEqual(0, WriteEvent(level, eventId, message)); + /// + /// Check that EventMonitor prints the written errors when level <= Information. + /// + TEST_METHOD(TestInformationLevelMonitor) + { + std::vector eventChannels = { {L"Application", EventChannelLogLevel::Information} }; - std::wstring output; + EventMonitor eventMonitor(eventChannels, true, false, L"json", L""); + Sleep(WAIT_TIME_EVENTMONITOR_START); - Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_LONG); - output = RecoverOuput(); + // + // Write an Error level event. + // + { + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); - // - // Monitor should have ignored this event. - // - std::wregex rgxMessage(Utility::FormatString(L"\"Message\": \"%s\"", message.c_str())); + std::wstring message = L"Hello world Error!"; + int eventId = 100; + EventChannelLogLevel level = EventChannelLogLevel::Error; - Assert::IsFalse(std::regex_search(output, rgxMessage), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - } - } + Assert::AreEqual(0, WriteEvent(level, eventId, message)); - /// - /// Check that EventMonitor prints the written errors when level <= Information. - /// - TEST_METHOD(TestInformationLevelMonitor) - { - std::vector eventChannels = { {L"Application", EventChannelLogLevel::Information} }; + std::wstring output; + int count = 0; - EventMonitor eventMonitor(eventChannels, true, false, L"json", L""); - Sleep(WAIT_TIME_EVENTMONITOR_START); + std::wregex rgxMessage(Utility::FormatString(L"\"Message\": \"%s\"", message.c_str())); - // - // Write an Error level event. - // + do { - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); + Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (!std::regex_search(output, rgxMessage) && READ_OUTPUT_RETRIES > ++count); - std::wstring message = L"Hello world Error!"; - int eventId = 100; - EventChannelLogLevel level = EventChannelLogLevel::Error; + Assert::IsTrue(std::regex_search(output, rgxMessage), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - Assert::AreEqual(0, WriteEvent(level, eventId, message)); + std::wregex rgxId(Utility::FormatString(L"\"EventId\": %d", eventId)); - std::wstring output; - int count = 0; - - std::wregex rgxMessage(Utility::FormatString(L"\"Message\": \"%s\"", message.c_str())); + Assert::IsTrue(std::regex_search(output, rgxId), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - do - { - Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (!std::regex_search(output, rgxMessage) && READ_OUTPUT_RETRIES > ++count); + std::wregex rgxLevel(Utility::FormatString(L"\"Level\": \"%s\"", c_LevelToString[(int)level])); - Assert::IsTrue(std::regex_search(output, rgxMessage), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + Assert::IsTrue(std::regex_search(output, rgxLevel), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + } - std::wregex rgxId(Utility::FormatString(L"\"EventId\": %d", eventId)); + // + // Write a Warning level event. + // + { + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); - Assert::IsTrue(std::regex_search(output, rgxId), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + std::wstring message = L"Hello world Warning!"; + int eventId = 101; + EventChannelLogLevel level = EventChannelLogLevel::Warning; - std::wregex rgxLevel(Utility::FormatString(L"\"Level\": \"%s\"", c_LevelToString[(int)level])); + Assert::AreEqual(0, WriteEvent(level, eventId, message)); - Assert::IsTrue(std::regex_search(output, rgxLevel), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - } + std::wstring output; + int count = 0; // - // Write a Warning level event. + // Test that the created event is printed. We could receive other events, + // so is better to loop until our message has arrived, using a regex. // + std::wregex rgxMessage(Utility::FormatString(L"\"Message\": \"%s\"", message.c_str())); + + do { - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); + Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (!std::regex_search(output, rgxMessage) && READ_OUTPUT_RETRIES > ++count); - std::wstring message = L"Hello world Warning!"; - int eventId = 101; - EventChannelLogLevel level = EventChannelLogLevel::Warning; + Assert::IsTrue(std::regex_search(output, rgxMessage), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - Assert::AreEqual(0, WriteEvent(level, eventId, message)); + std::wregex rgxId(Utility::FormatString(L"\"EventId\": %d", eventId)); - std::wstring output; - int count = 0; - - // - // Test that the created event is printed. We could receive other events, - // so is better to loop until our message has arrived, using a regex. - // - std::wregex rgxMessage(Utility::FormatString(L"\"Message\": \"%s\"", message.c_str())); + Assert::IsTrue(std::regex_search(output, rgxId), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - do - { - Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (!std::regex_search(output, rgxMessage) && READ_OUTPUT_RETRIES > ++count); + std::wregex rgxLevel(Utility::FormatString(L"\"Level\": \"%s\"", c_LevelToString[(int)level])); - Assert::IsTrue(std::regex_search(output, rgxMessage), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + Assert::IsTrue(std::regex_search(output, rgxLevel), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + } - std::wregex rgxId(Utility::FormatString(L"\"EventId\": %d", eventId)); + // + // Write an Information level event. + // + { + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); - Assert::IsTrue(std::regex_search(output, rgxId), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + std::wstring message = L"Hello world Info!"; + int eventId = 102; + EventChannelLogLevel level = EventChannelLogLevel::Information; - std::wregex rgxLevel(Utility::FormatString(L"\"Level\": \"%s\"", c_LevelToString[(int)level])); + Assert::AreEqual(0, WriteEvent(level, eventId, message)); - Assert::IsTrue(std::regex_search(output, rgxLevel), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - } + std::wstring output; + int count = 0; // - // Write an Information level event. + // Test that the created event is printed. We could receive other events, + // so is better to loop until our message has arrived, using a regex. // - { - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); + std::wregex rgxMessage(Utility::FormatString(L"\"Message\": \"%s\"", message.c_str())); - std::wstring message = L"Hello world Info!"; - int eventId = 102; - EventChannelLogLevel level = EventChannelLogLevel::Information; - - Assert::AreEqual(0, WriteEvent(level, eventId, message)); - - std::wstring output; - int count = 0; + do + { + Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (!std::regex_search(output, rgxMessage) && READ_OUTPUT_RETRIES > ++count); - // - // Test that the created event is printed. We could receive other events, - // so is better to loop until our message has arrived, using a regex. - // - std::wregex rgxMessage(Utility::FormatString(L"\"Message\": \"%s\"", message.c_str())); + Assert::IsTrue(std::regex_search(output, rgxMessage), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - do - { - Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (!std::regex_search(output, rgxMessage) && READ_OUTPUT_RETRIES > ++count); + std::wregex rgxId(Utility::FormatString(L"\"EventId\": %d", eventId)); - Assert::IsTrue(std::regex_search(output, rgxMessage), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + Assert::IsTrue(std::regex_search(output, rgxId), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - std::wregex rgxId(Utility::FormatString(L"\"EventId\": %d", eventId)); + std::wregex rgxLevel(Utility::FormatString(L"\"Level\": \"%s\"", c_LevelToString[(int)level])); - Assert::IsTrue(std::regex_search(output, rgxId), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + Assert::IsTrue(std::regex_search(output, rgxLevel), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); + } + } - std::wregex rgxLevel(Utility::FormatString(L"\"Level\": \"%s\"", c_LevelToString[(int)level])); + // + // Check that setting true the StartAtOldestRecord property, monitor + // prints records that existed before the monitor started. + // + TEST_METHOD(TestStartAtOldestRecord) + { + std::vector eventChannels = { {L"Application", EventChannelLogLevel::Error} }; - Assert::IsTrue(std::regex_search(output, rgxLevel), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - } - } + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); // - // Check that setting true the StartAtOldestRecord property, monitor - // prints records that existed before the monitor started. + // Write one application event, to ensure that there is at least + // one event before starting the Event Monitor // - TEST_METHOD(TestStartAtOldestRecord) - { - std::vector eventChannels = { {L"Application", EventChannelLogLevel::Error} }; + std::wstring message = L"Hello world Info!"; + int eventId = 102; + EventChannelLogLevel level = EventChannelLogLevel::Error; - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); + Assert::AreEqual(0, WriteEvent(level, eventId, message)); - // - // Write one application event, to ensure that there is at least - // one event before starting the Event Monitor - // - std::wstring message = L"Hello world Info!"; - int eventId = 102; - EventChannelLogLevel level = EventChannelLogLevel::Error; + EventMonitor eventMonitor(eventChannels, true, true, L"json", L""); + Sleep(WAIT_TIME_EVENTMONITOR_START); - Assert::AreEqual(0, WriteEvent(level, eventId, message)); + { + std::wstring output; + int count = 0; + do + { + Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && READ_OUTPUT_RETRIES > ++count); - EventMonitor eventMonitor(eventChannels, true, true, L"json", L""); - Sleep(WAIT_TIME_EVENTMONITOR_START); + std::wregex rgxMessage(Utility::FormatString(L"\"Channel\": \"Application\"")); - { - std::wstring output; - int count = 0; - do - { - Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && READ_OUTPUT_RETRIES > ++count); - - std::wregex rgxMessage(Utility::FormatString(L"\"Channel\": \"Application\"")); - - Assert::IsTrue(std::regex_search(output, rgxMessage), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - } + Assert::IsTrue(std::regex_search(output, rgxMessage), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); } + } - // - // Check that the events from non-listening channels are ignored. - // - TEST_METHOD(TestChannelsAreNotMixed) - { - std::vector eventChannels = { {L"System", EventChannelLogLevel::Information} }; + // + // Check that the events from non-listening channels are ignored. + // + TEST_METHOD(TestChannelsAreNotMixed) + { + std::vector eventChannels = { {L"System", EventChannelLogLevel::Information} }; - EventMonitor eventMonitor(eventChannels, false, false, L"json", L""); - Sleep(WAIT_TIME_EVENTMONITOR_START); + EventMonitor eventMonitor(eventChannels, false, false, L"json", L""); + Sleep(WAIT_TIME_EVENTMONITOR_START); - { - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); + { + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); - std::wstring message = L"Hello world Error!"; - int eventId = 100; - EventChannelLogLevel level = EventChannelLogLevel::Error; + std::wstring message = L"Hello world Error!"; + int eventId = 100; + EventChannelLogLevel level = EventChannelLogLevel::Error; - Assert::AreEqual(0, WriteEvent(level, eventId, message)); + Assert::AreEqual(0, WriteEvent(level, eventId, message)); - Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_LONG); - std::wstring output = RecoverOuput(); + Sleep(WAIT_TIME_EVENTMONITOR_AFTER_WRITE_LONG); + std::wstring output = RecoverOuput(); - std::wregex rgxMessage(Utility::FormatString(L"\"Channel\": \"Application\"")); + std::wregex rgxMessage(Utility::FormatString(L"\"Channel\": \"Application\"")); - // - // We can not ensure that a System event isn't received right - // now, so it would be enough checking that this is not - // an appplication event. - // - Assert::IsTrue(!std::regex_search(output, rgxMessage), - Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); - } + // + // We can not ensure that a System event isn't received right + // now, so it would be enough checking that this is not + // an appplication event. + // + Assert::IsTrue(!std::regex_search(output, rgxMessage), + Utility::FormatString(L"Actual output: %s", output.c_str()).c_str()); } - }; -} \ No newline at end of file + } +}; +} // namespace LogMonitorTests diff --git a/LogMonitor/LogMonitorTests/JsonProcessorTests.cpp b/LogMonitor/LogMonitorTests/JsonProcessorTests.cpp index 502e3ef4..ad95cea7 100644 --- a/LogMonitor/LogMonitorTests/JsonProcessorTests.cpp +++ b/LogMonitor/LogMonitorTests/JsonProcessorTests.cpp @@ -3,8 +3,10 @@ // Licensed under the MIT license. // -#include "pch.h" +#include "pch.h" // NOLINT(build/include_subdir) #include "../src/LogMonitor/JsonProcessor.h" +#include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) using namespace Microsoft::VisualStudio::CppUnitTestFramework; @@ -12,61 +14,60 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace LogMonitorTests { - /// - /// Tests of the JsonProcessor's ReadConfigFile function, which replaced the - /// Boost-based ConfigFileParser. Covers source parsing, optional-field - /// defaults, case-insensitive key matching, waitInSeconds, and invalid-input - /// rejection. - /// - TEST_CLASS(JsonProcessorTests) - { - WCHAR bigOutBuf[BUFFER_SIZE]; - std::wstring tempFilePath; - - std::wstring RecoverOutput() - { - return std::wstring(bigOutBuf); - } - - std::wstring WriteTempConfig(const std::string& content) - { - WCHAR tempDir[MAX_PATH] = {}; - GetTempPathW(MAX_PATH, tempDir); - WCHAR tempFile[MAX_PATH] = {}; - GetTempFileNameW(tempDir, L"jpt", 0, tempFile); - tempFilePath = tempFile; - - std::ofstream f(tempFile, std::ios::binary); - f << content; - return tempFilePath; - } - - public: +/// +/// Tests of the JsonProcessor's ReadConfigFile function, which replaced the +/// Boost-based ConfigFileParser. Covers source parsing, optional-field +/// defaults, case-insensitive key matching, waitInSeconds, and invalid-input +/// rejection. +/// +TEST_CLASS(JsonProcessorTests) +{ + WCHAR bigOutBuf[BUFFER_SIZE]; + std::wstring tempFilePath; - TEST_METHOD_INITIALIZE(Initialize) - { - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); - _setmode(_fileno(stdout), _O_U16TEXT); - setvbuf(stdout, (char*)bigOutBuf, _IOFBF, sizeof(bigOutBuf) - sizeof(WCHAR)); - tempFilePath.clear(); - } + std::wstring RecoverOutput() + { + return std::wstring(bigOutBuf); + } - TEST_METHOD_CLEANUP(Cleanup) + std::wstring WriteTempConfig(const std::string& content) + { + WCHAR tempDir[MAX_PATH] = {}; + GetTempPathW(MAX_PATH, tempDir); + WCHAR tempFile[MAX_PATH] = {}; + GetTempFileNameW(tempDir, L"jpt", 0, tempFile); + tempFilePath = tempFile; + + std::ofstream f(tempFile, std::ios::binary); + f << content; + return tempFilePath; + } + +public: + TEST_METHOD_INITIALIZE(Initialize) + { + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); + _setmode(_fileno(stdout), _O_U16TEXT); + setvbuf(stdout, (char*)bigOutBuf, _IOFBF, sizeof(bigOutBuf) - sizeof(WCHAR)); + tempFilePath.clear(); + } + + TEST_METHOD_CLEANUP(Cleanup) + { + if (!tempFilePath.empty()) { - if (!tempFilePath.empty()) - { - DeleteFileW(tempFilePath.c_str()); - } + DeleteFileW(tempFilePath.c_str()); } + } - /// - /// Channel name/level lookup must be case-insensitive for backward - /// compatibility with existing configs. - /// - TEST_METHOD(JsonProcessor_HandlesCaseInsensitiveKeys) - { - auto path = WriteTempConfig(R"({ + /// + /// Channel name/level lookup must be case-insensitive for backward + /// compatibility with existing configs. + /// + TEST_METHOD(JsonProcessor_HandlesCaseInsensitiveKeys) + { + auto path = WriteTempConfig(R"({ "LogConfig": { "sources": [{ "type": "EventLog", @@ -75,26 +76,26 @@ namespace LogMonitorTests } })"); - LoggerSettings settings; - bool success = ReadConfigFile((PWCHAR)path.c_str(), settings); + LoggerSettings settings; + bool success = ReadConfigFile((PWCHAR)path.c_str(), settings); - Assert::IsTrue(success); - Assert::AreEqual((size_t)1, settings.Sources.size()); + Assert::IsTrue(success); + Assert::AreEqual((size_t)1, settings.Sources.size()); - auto src = std::reinterpret_pointer_cast(settings.Sources[0]); - Assert::AreEqual(std::wstring(L"system"), src->Channels[0].Name); - Assert::AreEqual((int)EventChannelLogLevel::Warning, (int)src->Channels[0].Level); - } + auto src = std::reinterpret_pointer_cast(settings.Sources[0]); + Assert::AreEqual(std::wstring(L"system"), src->Channels[0].Name); + Assert::AreEqual((int)EventChannelLogLevel::Warning, (int)src->Channels[0].Level); + } - /// - /// Optional fields omitted from the config must use their documented - /// default values so existing configs are not broken. - /// - TEST_METHOD(JsonProcessor_UsesDefaultValuesForOptionalFields) - { - // EventLog: startAtOldestRecord defaults false, eventFormatMultiLine - // defaults true, channel level defaults Error. - auto path = WriteTempConfig(R"({ + /// + /// Optional fields omitted from the config must use their documented + /// default values so existing configs are not broken. + /// + TEST_METHOD(JsonProcessor_UsesDefaultValuesForOptionalFields) + { + // EventLog: startAtOldestRecord defaults false, eventFormatMultiLine + // defaults true, channel level defaults Error. + auto path = WriteTempConfig(R"({ "LogConfig": { "sources": [{ "type": "EventLog", @@ -103,26 +104,26 @@ namespace LogMonitorTests } })"); - LoggerSettings settings; - bool success = ReadConfigFile((PWCHAR)path.c_str(), settings); + LoggerSettings settings; + bool success = ReadConfigFile((PWCHAR)path.c_str(), settings); - Assert::IsTrue(success); - Assert::AreEqual((size_t)1, settings.Sources.size()); + Assert::IsTrue(success); + Assert::AreEqual((size_t)1, settings.Sources.size()); - auto src = std::reinterpret_pointer_cast(settings.Sources[0]); - Assert::IsFalse(src->StartAtOldestRecord); - Assert::IsTrue(src->EventFormatMultiLine); - Assert::AreEqual((int)EventChannelLogLevel::Error, (int)src->Channels[0].Level); - } + auto src = std::reinterpret_pointer_cast(settings.Sources[0]); + Assert::IsFalse(src->StartAtOldestRecord); + Assert::IsTrue(src->EventFormatMultiLine); + Assert::AreEqual((int)EventChannelLogLevel::Error, (int)src->Channels[0].Level); + } - /// - /// waitInSeconds on a File source must be parsed and stored correctly. - /// When omitted the default value (300) must be used. - /// - TEST_METHOD(JsonProcessor_ParsesWaitInSeconds) - { - // Explicit value - auto path = WriteTempConfig(R"({ + /// + /// waitInSeconds on a File source must be parsed and stored correctly. + /// When omitted the default value (300) must be used. + /// + TEST_METHOD(JsonProcessor_ParsesWaitInSeconds) + { + // Explicit value + auto path = WriteTempConfig(R"({ "LogConfig": { "sources": [{ "type": "File", @@ -134,17 +135,17 @@ namespace LogMonitorTests } })"); - LoggerSettings settings; - bool success = ReadConfigFile((PWCHAR)path.c_str(), settings); + LoggerSettings settings; + bool success = ReadConfigFile((PWCHAR)path.c_str(), settings); - Assert::IsTrue(success); - Assert::AreEqual((size_t)1, settings.Sources.size()); + Assert::IsTrue(success); + Assert::AreEqual((size_t)1, settings.Sources.size()); - auto src = std::reinterpret_pointer_cast(settings.Sources[0]); - Assert::AreEqual(60.0, src->WaitInSeconds); + auto src = std::reinterpret_pointer_cast(settings.Sources[0]); + Assert::AreEqual(60.0, src->WaitInSeconds); - // Default value when omitted - auto path2 = WriteTempConfig(R"({ + // Default value when omitted + auto path2 = WriteTempConfig(R"({ "LogConfig": { "sources": [{ "type": "File", @@ -155,21 +156,21 @@ namespace LogMonitorTests } })"); - LoggerSettings settings2; - bool success2 = ReadConfigFile((PWCHAR)path2.c_str(), settings2); + LoggerSettings settings2; + bool success2 = ReadConfigFile((PWCHAR)path2.c_str(), settings2); - Assert::IsTrue(success2); - auto src2 = std::reinterpret_pointer_cast(settings2.Sources[0]); - Assert::AreEqual(300.0, src2->WaitInSeconds); - } + Assert::IsTrue(success2); + auto src2 = std::reinterpret_pointer_cast(settings2.Sources[0]); + Assert::AreEqual(300.0, src2->WaitInSeconds); + } - /// - /// A source with an unknown type must be skipped with an error logged, - /// but valid sources in the same config must still be processed. - /// - TEST_METHOD(JsonProcessor_RejectsInvalidSource) - { - auto path = WriteTempConfig(R"({ + /// + /// A source with an unknown type must be skipped with an error logged, + /// but valid sources in the same config must still be processed. + /// + TEST_METHOD(JsonProcessor_RejectsInvalidSource) + { + auto path = WriteTempConfig(R"({ "LogConfig": { "sources": [ {"type": "InvalidSourceType"}, @@ -183,13 +184,13 @@ namespace LogMonitorTests } })"); - LoggerSettings settings; - bool success = ReadConfigFile((PWCHAR)path.c_str(), settings); + LoggerSettings settings; + bool success = ReadConfigFile((PWCHAR)path.c_str(), settings); - // Overall parse succeeds — invalid source is skipped, valid one is kept - Assert::IsTrue(success); - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::File, (int)settings.Sources[0]->Type); - } - }; -} + // Overall parse succeeds — invalid source is skipped, valid one is kept + Assert::IsTrue(success); + Assert::AreEqual((size_t)1, settings.Sources.size()); + Assert::AreEqual((int)LogSourceType::File, (int)settings.Sources[0]->Type); + } +}; +} // namespace LogMonitorTests diff --git a/LogMonitor/LogMonitorTests/LogFileMonitorTests.cpp b/LogMonitor/LogMonitorTests/LogFileMonitorTests.cpp index 2318253b..a716b0b2 100644 --- a/LogMonitor/LogMonitorTests/LogFileMonitorTests.cpp +++ b/LogMonitor/LogMonitorTests/LogFileMonitorTests.cpp @@ -3,7 +3,11 @@ // Licensed under the MIT license. // -#include "pch.h" +#include "pch.h" // NOLINT(build/include_subdir) +#include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) using namespace Microsoft::VisualStudio::CppUnitTestFramework; @@ -14,1053 +18,1071 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace LogMonitorTests { +/// +/// Tests the Log File Monitor. This class creates temporaries directories +/// and files, and tests that the Monitor prints all the changes to stdout. +/// +TEST_CLASS(LogFileMonitorTests) +{ + /// + /// The wait time after each modification to a subdirectory or a file. + /// + const DWORD WAIT_TIME_LOGFILEMONITOR_START = 500; + const DWORD WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_LONG = 750; + const DWORD WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT = 500; + + const int READ_OUTPUT_RETRIES = 8; + + /// + /// Add the path of the created directories, to be removed during + /// cleanup. + /// + std::vector directoriesToDeleteAtCleanup; + + WCHAR bigOutBuf[BUFFER_SIZE]; + + /// + /// Gets the content of the Stdout buffer and returns it in a wstring. /// - /// Tests the Log File Monitor. This class creates temporaries directories - /// and files, and tests that the Monitor prints all the changes to stdout. + /// \return A wstring with the stdout. /// - TEST_CLASS(LogFileMonitorTests) + std::wstring RecoverOuput() { - /// - /// The wait time after each modification to a subdirectory or a file. - /// - const DWORD WAIT_TIME_LOGFILEMONITOR_START = 500; - const DWORD WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_LONG = 750; - const DWORD WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT = 500; - - const int READ_OUTPUT_RETRIES = 8; - - /// - /// Add the path of the created directories, to be removed during - /// cleanup. - /// - std::vector directoriesToDeleteAtCleanup; - - WCHAR bigOutBuf[BUFFER_SIZE]; - - /// - /// Gets the content of the Stdout buffer and returns it in a wstring. - /// - /// \return A wstring with the stdout. - /// - std::wstring RecoverOuput() - { - return std::wstring(bigOutBuf); - } + return std::wstring(bigOutBuf); + } - /// - /// Writes to a new or existing file. - /// - /// \param FileName The full path of the file. - /// \param FileName The buffer with the content to write. - /// \param Buffer The size of the buffer in bytes. - /// - /// - /// \return The status of the operation. - /// - DWORD WriteToFile( - const std::wstring& FileName, - _In_reads_bytes_opt_(BufferSize) LPCVOID Buffer, - _In_ size_t BufferSize - ) + /// + /// Writes to a new or existing file. + /// + /// \param FileName The full path of the file. + /// \param FileName The buffer with the content to write. + /// \param Buffer The size of the buffer in bytes. + /// + /// + /// \return The status of the operation. + /// + DWORD WriteToFile( + const std::wstring& FileName, + _In_reads_bytes_opt_(BufferSize) LPCVOID Buffer, + _In_ size_t BufferSize + ) + { + // + // Open a handle to the file. + // + HANDLE hFile = CreateFile( + FileName.c_str(), + FILE_APPEND_DATA, + FILE_SHARE_READ, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, + NULL); + + if (hFile == INVALID_HANDLE_VALUE) { // - // Open a handle to the file. + // Failed to open/create file. // - HANDLE hFile = CreateFile( - FileName.c_str(), - FILE_APPEND_DATA, - FILE_SHARE_READ, - NULL, - OPEN_ALWAYS, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, - NULL); - - if (hFile == INVALID_HANDLE_VALUE) + return GetLastError(); + } + + // + // Write data to the file. + // + DWORD bytesWritten; + bool success = WriteFile( + hFile, + Buffer, + (DWORD)BufferSize, + &bytesWritten, + nullptr); + + // + // Close the handle once we don't need it. + // + CloseHandle(hFile); + + return (success)? 0 : GetLastError(); + } + +public: + /// + /// "Redirects" the stdout to our buffer. + /// + TEST_METHOD_INITIALIZE(InitializeLogFileMonitorTests) + { + // + // Set our own buffer in stdout. + // + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); + _setmode(_fileno(stdout), _O_U16TEXT); + setvbuf(stdout, (char*)bigOutBuf, _IOFBF, sizeof(bigOutBuf) - sizeof(WCHAR)); + } + + TEST_METHOD_CLEANUP(CleanupLogFileMonitorTests) + { + for (auto directoryPath : directoriesToDeleteAtCleanup) + { + DWORD ftyp = GetFileAttributesW(directoryPath.c_str()); + if (ftyp & FILE_ATTRIBUTE_READONLY) { - // - // Failed to open/create file. - // - return GetLastError(); + SetFileAttributes(directoryPath.c_str(), ftyp); } // - // Write data to the file. + // Add null charachters for null termination and for second + // null term. required by SHFILEOPSTRUCT. // - DWORD bytesWritten; - bool success = WriteFile( - hFile, - Buffer, - (DWORD)BufferSize, - &bytesWritten, - nullptr); + wchar_t* folder = new wchar_t[directoryPath.length() + 3]; + ::ZeroMemory(folder, (directoryPath.length() + 3) * sizeof(folder[0])); - // - // Close the handle once we don't need it. - // - CloseHandle(hFile); + directoryPath.copy(folder, directoryPath.length()); - return (success)? 0 : GetLastError(); - } + SHFILEOPSTRUCT fileOp = { + NULL, + FO_DELETE, + folder, + NULL, + FOF_NOCONFIRMATION | FOF_NOERRORUI, + FALSE, + NULL, + NULL + }; - public: + SHFileOperation(&fileOp); - /// - /// "Redirects" the stdout to our buffer. - /// - TEST_METHOD_INITIALIZE(InitializeLogFileMonitorTests) - { - // - // Set our own buffer in stdout. - // - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); - _setmode(_fileno(stdout), _O_U16TEXT); - setvbuf(stdout, (char*)bigOutBuf, _IOFBF, sizeof(bigOutBuf) - sizeof(WCHAR)); + delete[] folder; } + } - TEST_METHOD_CLEANUP(CleanupLogFileMonitorTests) - { - for (auto directoryPath : directoriesToDeleteAtCleanup) - { - DWORD ftyp = GetFileAttributesW(directoryPath.c_str()); - if (ftyp & FILE_ATTRIBUTE_READONLY) - { - SetFileAttributes(directoryPath.c_str(), ftyp); - } - - // - // Add null charachters for null termination and for second - // null term. required by SHFILEOPSTRUCT. - // - wchar_t* folder = new wchar_t[directoryPath.length() + 3]; - ::ZeroMemory(folder, (directoryPath.length() + 3) * sizeof(folder[0])); - - directoryPath.copy(folder, directoryPath.length()); - - SHFILEOPSTRUCT fileOp = { - NULL, - FO_DELETE, - folder, - NULL, - FOF_NOCONFIRMATION | FOF_NOERRORUI, - FALSE, - NULL, - NULL - }; - - SHFileOperation(&fileOp); - - delete[] folder; - } - } + /// + /// Check that LogFileMonitor prints the updates of the files + /// inside a directory, that is its core functionality. + /// + TEST_METHOD(TestBasicLogFileMonitor) + { + std::wstring output; - /// - /// Check that LogFileMonitor prints the updates of the files - /// inside a directory, that is its core functionality. - /// - TEST_METHOD(TestBasicLogFileMonitor) - { - std::wstring output; + std::wstring tempDirectory = CreateTempDirectory(); + Assert::IsFalse(tempDirectory.empty()); - std::wstring tempDirectory = CreateTempDirectory(); - Assert::IsFalse(tempDirectory.empty()); + directoriesToDeleteAtCleanup.push_back(tempDirectory); - directoriesToDeleteAtCleanup.push_back(tempDirectory); + // + // Start the monitor + // + SourceFile sourceFile; + sourceFile.Directory = tempDirectory; - // - // Start the monitor - // - SourceFile sourceFile; - sourceFile.Directory = tempDirectory; + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + std::shared_ptr logfileMon = std::make_shared( + sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, + sourceFile.WaitInSeconds, L"json", L""); + Sleep(WAIT_TIME_LOGFILEMONITOR_START); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); - Sleep(WAIT_TIME_LOGFILEMONITOR_START); + // + // Check that LogFileMonitor started successfully. + // + output = RecoverOuput(); + Assert::AreEqual(L"", output.c_str()); + { + std::wstring filename = sourceFile.Directory + L"\\test.txt"; + std::string content = "Hello World!"; - // - // Check that LogFileMonitor started successfully. - // - output = RecoverOuput(); - Assert::AreEqual(L"", output.c_str()); - { - std::wstring filename = sourceFile.Directory + L"\\test.txt"; - std::string content = "Hello World!"; - - WriteToFile(filename, content.c_str(), content.length()); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); - - Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); - } + WriteToFile(filename, content.c_str(), content.length()); + + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); + + Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); } + } - /// - /// Check that even if the directory doesn't exist yet, and it's created after - /// some time, the monitor will read it successfully. - /// - TEST_METHOD(TestDirectoryNotCreatedYet) - { - std::wstring output; + /// + /// Check that even if the directory doesn't exist yet, and it's created after + /// some time, the monitor will read it successfully. + /// + TEST_METHOD(TestDirectoryNotCreatedYet) + { + std::wstring output; - WCHAR tempDirectoryArray[L_tmpnam_s]; - ZeroMemory(tempDirectoryArray, sizeof(tempDirectoryArray)); + WCHAR tempDirectoryArray[L_tmpnam_s]; + ZeroMemory(tempDirectoryArray, sizeof(tempDirectoryArray)); - errno_t err = _wtmpnam_s(tempDirectoryArray, L_tmpnam_s); - Assert::IsFalse(err); + errno_t err = _wtmpnam_s(tempDirectoryArray, L_tmpnam_s); + Assert::IsFalse(err); - // - // Start the monitor - // - SourceFile sourceFile; - sourceFile.Directory = std::wstring(tempDirectoryArray) + L"NOT_CREATED_YET"; + // + // Start the monitor + // + SourceFile sourceFile; + sourceFile.Directory = std::wstring(tempDirectoryArray) + L"NOT_CREATED_YET"; - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); - Sleep(WAIT_TIME_LOGFILEMONITOR_START); + std::shared_ptr logfileMon = std::make_shared( + sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, + sourceFile.WaitInSeconds, L"json", L""); + Sleep(WAIT_TIME_LOGFILEMONITOR_START); - // - // Create the directory and wait to be recognized by the monitor. - // - long status = CreateDirectoryW(sourceFile.Directory.c_str(), NULL); - Sleep(17000); - - Assert::AreNotEqual(0L, status); + // + // Create the directory and wait to be recognized by the monitor. + // + BOOL directoryCreated = CreateDirectoryW(sourceFile.Directory.c_str(), NULL); + Sleep(17000); - directoriesToDeleteAtCleanup.push_back(sourceFile.Directory); + Assert::IsTrue(directoryCreated != FALSE); + + directoriesToDeleteAtCleanup.push_back(sourceFile.Directory); + + // + // Check that LogFileMonitor started successfully. + // + output = RecoverOuput(); + Assert::IsTrue(output.find(L"INFO") != std::wstring::npos); + { + std::wstring filename = sourceFile.Directory + L"\\testfile.txt"; + std::string content = "Hello World!"; + + DWORD writeStatus = WriteToFile(filename, content.c_str(), content.length()); + Assert::AreEqual(DWORD{ERROR_SUCCESS}, writeStatus); + + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); - // - // Check that LogFileMonitor started successfully. - // output = RecoverOuput(); - Assert::IsTrue(output.find(L"INFO") != std::wstring::npos); - { - std::wstring filename = sourceFile.Directory + L"\\testfile.txt"; - std::string content = "Hello World!"; - - status = WriteToFile(filename, content.c_str(), content.length()); - Assert::AreEqual(0L, status); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); + Assert::IsTrue(output.find((TO_WSTR(content)).c_str()) != std::wstring::npos); + } + } + + /// + /// Check that the filter specified to the monitor make it ignore all + /// unmatched files. + /// + TEST_METHOD(TestFilterIgnoreFiles) + { + std::wstring output; + + std::wstring tempDirectory = CreateTempDirectory(); + Assert::IsFalse(tempDirectory.empty()); + + // + // Create subdirectory + // + std::wstring subDirectory = tempDirectory + L"\\sub"; + BOOL directoryCreated = CreateDirectoryW(subDirectory.c_str(), NULL); + Assert::IsTrue(directoryCreated != FALSE); + + directoriesToDeleteAtCleanup.push_back(tempDirectory); + + // + // Start the monitor + // + SourceFile sourceFile; + sourceFile.Directory = tempDirectory; + sourceFile.Filter = L"*.log"; + sourceFile.IncludeSubdirectories = true; + sourceFile.WaitInSeconds = 10; + + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + + std::shared_ptr logfileMon = std::make_shared( + sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, + sourceFile.WaitInSeconds, L"json", L""); + Sleep(WAIT_TIME_LOGFILEMONITOR_START); + + // + // Check that LogFileMonitor started successfully and print matched files. + // + output = RecoverOuput(); + Assert::AreEqual(L"", output.c_str()); + { + std::wstring filename = sourceFile.Directory + L"\\test.log"; + std::string content = "Hello World!"; + + WriteToFile(filename, content.c_str(), content.length()); + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); output = RecoverOuput(); - Assert::IsTrue(output.find((TO_WSTR(content)).c_str()) != std::wstring::npos); - } + } while (output.empty() && retries < READ_OUTPUT_RETRIES); + + Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); } - - /// - /// Check that the filter specified to the monitor make it ignore all - /// unmatched files. - /// - TEST_METHOD(TestFilterIgnoreFiles) + + { - std::wstring output; + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::wstring tempDirectory = CreateTempDirectory(); - Assert::IsFalse(tempDirectory.empty()); + std::wstring filename = sourceFile.Directory + L"\\test.txt"; + std::string content = "Hello World!"; // - // Create subdirectory + // Check unmatched new file is ignored. // - std::wstring subDirectory = tempDirectory + L"\\sub"; - long status = CreateDirectoryW(subDirectory.c_str(), NULL); - Assert::AreNotEqual(0L, status); + WriteToFile(filename, content.c_str(), content.length()); + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_LONG); - directoriesToDeleteAtCleanup.push_back(tempDirectory); + output = RecoverOuput(); + Assert::AreEqual(L"", output.c_str()); // - // Start the monitor + // Check unmatched existing file is ignored. // - SourceFile sourceFile; - sourceFile.Directory = tempDirectory; - sourceFile.Filter = L"*.log"; - sourceFile.IncludeSubdirectories = true; - sourceFile.WaitInSeconds = 10; + WriteToFile(filename, content.c_str(), content.length()); + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_LONG); + output = RecoverOuput(); + Assert::AreEqual(L"", output.c_str()); + } + + // + // Check the previous test, but for subdirectories. + // + { fflush(stdout); ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); - Sleep(WAIT_TIME_LOGFILEMONITOR_START); + std::wstring filename = subDirectory + L"\\other_file.txt"; + std::string content = "Hello World!"; // - // Check that LogFileMonitor started successfully and print matched files. + // Check unmatched new file is ignored. // + WriteToFile(filename, content.c_str(), content.length()); + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_LONG); + output = RecoverOuput(); Assert::AreEqual(L"", output.c_str()); - { - std::wstring filename = sourceFile.Directory + L"\\test.log"; - std::string content = "Hello World!"; - WriteToFile(filename, content.c_str(), content.length()); + // + // Check unmatched existing file is ignored. + // + WriteToFile(filename, content.c_str(), content.length()); + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_LONG); - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); + output = RecoverOuput(); + Assert::AreEqual(L"", output.c_str()); + } + } - Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); - } + /// + /// Check that the decoding for the next encoding is working fine: + /// UTF8, UTF16 little endian an big endian, and ANSI. + /// + TEST_METHOD(TestMultipleEncodings) + { + std::wstring output; - - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + std::wstring tempDirectory = CreateTempDirectory(); + Assert::IsFalse(tempDirectory.empty()); - std::wstring filename = sourceFile.Directory + L"\\test.txt"; - std::string content = "Hello World!"; + directoriesToDeleteAtCleanup.push_back(tempDirectory); - // - // Check unmatched new file is ignored. - // - WriteToFile(filename, content.c_str(), content.length()); - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_LONG); + // + // Start the monitor + // + SourceFile sourceFile; + sourceFile.Directory = tempDirectory; - output = RecoverOuput(); - Assert::AreEqual(L"", output.c_str()); + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - // - // Check unmatched existing file is ignored. - // - WriteToFile(filename, content.c_str(), content.length()); - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_LONG); + std::shared_ptr logfileMon = std::make_shared( + sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, + sourceFile.WaitInSeconds, L"json", L""); + Sleep(WAIT_TIME_LOGFILEMONITOR_START); - output = RecoverOuput(); - Assert::AreEqual(L"", output.c_str()); - } + // + // Check that LogFileMonitor started successfully. + // + output = RecoverOuput(); + Assert::AreEqual(L"", output.c_str()); - // - // Check the previous test, but for subdirectories. - // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + // + // Try UTF16 encoding without BOM. + // + { + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::wstring filename = subDirectory + L"\\other_file.txt"; - std::string content = "Hello World!"; + std::wstring filename = sourceFile.Directory + L"\\utf16.txt"; + std::wstring content = L"Hello world UTF16!"; - // - // Check unmatched new file is ignored. - // - WriteToFile(filename, content.c_str(), content.length()); - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_LONG); + WriteToFile(filename, content.c_str(), content.length() * sizeof(WCHAR)); + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); output = RecoverOuput(); - Assert::AreEqual(L"", output.c_str()); - - // - // Check unmatched existing file is ignored. - // - WriteToFile(filename, content.c_str(), content.length()); - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_LONG); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); - output = RecoverOuput(); - Assert::AreEqual(L"", output.c_str()); - } + Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); } - /// - /// Check that the decoding for the next encoding is working fine: - /// UTF8, UTF16 little endian an big endian, and ANSI. - /// - TEST_METHOD(TestMultipleEncodings) + // + // Try UTF16 encoding with BOM. + // { - std::wstring output; - - std::wstring tempDirectory = CreateTempDirectory(); - Assert::IsFalse(tempDirectory.empty()); - - directoriesToDeleteAtCleanup.push_back(tempDirectory); - - // - // Start the monitor - // - SourceFile sourceFile; - sourceFile.Directory = tempDirectory; - fflush(stdout); ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); - Sleep(WAIT_TIME_LOGFILEMONITOR_START); + std::wstring filename = sourceFile.Directory + L"\\utf16Bom.txt"; + std::wstring content = L"Hello world UTF16 with BOM!"; // - // Check that LogFileMonitor started successfully. + // Add the BOM to the beginning of the file. // - output = RecoverOuput(); - Assert::AreEqual(L"", output.c_str()); + std::wstring contentWithBom = ((WCHAR)BYTE_ORDER_MARK) + content; - // - // Try UTF16 encoding without BOM. - // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + WriteToFile(filename, contentWithBom.c_str(), contentWithBom.length() * sizeof(WCHAR)); - std::wstring filename = sourceFile.Directory + L"\\utf16.txt"; - std::wstring content = L"Hello world UTF16!"; + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); - WriteToFile(filename, content.c_str(), content.length() * sizeof(WCHAR)); + Assert::IsTrue(output.find(content) != std::wstring::npos); + } - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); + // + // Try UTF16 big endian encoding with BOM. + // + { + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); - } + std::wstring filename = sourceFile.Directory + L"\\utf16BomBE.txt"; + std::wstring content = L"Hello world UTF16 with BOM and big endian!"; // - // Try UTF16 encoding with BOM. + // Add the BOM to the beginning of the file. // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - std::wstring filename = sourceFile.Directory + L"\\utf16Bom.txt"; - std::wstring content = L"Hello world UTF16 with BOM!"; - - // - // Add the BOM to the beginning of the file. - // - std::wstring contentWithBom = ((WCHAR)BYTE_ORDER_MARK) + content; - - WriteToFile(filename, contentWithBom.c_str(), contentWithBom.length() * sizeof(WCHAR)); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); - - Assert::IsTrue(output.find(content) != std::wstring::npos); - } + std::wstring contentWithBom = ((WCHAR)BYTE_ORDER_MARK) + content; // - // Try UTF16 big endian encoding with BOM. + // Reverse each wide character, to make it big endian // + for (unsigned int i = 0; i < contentWithBom.size(); i++) { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - std::wstring filename = sourceFile.Directory + L"\\utf16BomBE.txt"; - std::wstring content = L"Hello world UTF16 with BOM and big endian!"; - - // - // Add the BOM to the beginning of the file. - // - std::wstring contentWithBom = ((WCHAR)BYTE_ORDER_MARK) + content; - - // - // Reverse each wide character, to make it big endian - // - for (unsigned int i = 0; i < contentWithBom.size(); i++) - { - contentWithBom[i] = (TCHAR)(((contentWithBom[i] << 8) & 0xFF00) + ((contentWithBom[i] >> 8) & 0xFF)); - } - - WriteToFile(filename, contentWithBom.c_str(), contentWithBom.length() * sizeof(WCHAR)); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); - - Assert::IsTrue(output.find(content) != std::wstring::npos); + contentWithBom[i] = + (TCHAR)(((contentWithBom[i] << 8) & 0xFF00) + ((contentWithBom[i] >> 8) & 0xFF)); } - // - // Try UTF8 encoding. - // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + WriteToFile(filename, contentWithBom.c_str(), contentWithBom.length() * sizeof(WCHAR)); - std::wstring filename = sourceFile.Directory + L"\\utf8.txt"; - std::string content = "Hello world UTF8 \xe3\x83\x86!"; + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); - WriteToFile(filename, content.c_str(), content.length()); + Assert::IsTrue(output.find(content) != std::wstring::npos); + } - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); + // + // Try UTF8 encoding. + // + { + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::wstring contentWide; - int size_needed = MultiByteToWideChar(CP_UTF8, 0, content.c_str(), (int)content.length(), NULL, 0); - contentWide.resize(size_needed); + std::wstring filename = sourceFile.Directory + L"\\utf8.txt"; + std::string content = "Hello world UTF8 \xe3\x83\x86!"; - MultiByteToWideChar(CP_UTF8, 0, (LPCCH)content.c_str(), (int)content.length(), (LPWSTR)(contentWide.c_str()), size_needed); + WriteToFile(filename, content.c_str(), content.length()); - Assert::IsTrue(output.find(contentWide) != std::wstring::npos); - } + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); - // - // Try ANSI encoding. - // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - std::wstring filename = sourceFile.Directory + L"\\ansi.txt"; - std::string content = "Hello world ANSI!"; - - WriteToFile(filename, content.c_str(), content.length()); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); - - Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); - } + std::wstring contentWide; + int size_needed = MultiByteToWideChar(CP_UTF8, 0, content.c_str(), (int)content.length(), NULL, 0); + contentWide.resize(size_needed); + + MultiByteToWideChar( + CP_UTF8, 0, (LPCCH)content.c_str(), (int)content.length(), + (LPWSTR)(contentWide.c_str()), size_needed); + + Assert::IsTrue(output.find(contentWide) != std::wstring::npos); } - /// - /// Check that adding files to the directory prints their content - /// to stdout - /// - TEST_METHOD(TestAddFiles) + // + // Try ANSI encoding. + // { - std::wstring output; + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::wstring tempDirectory = CreateTempDirectory(); - Assert::IsFalse(tempDirectory.empty()); + std::wstring filename = sourceFile.Directory + L"\\ansi.txt"; + std::string content = "Hello world ANSI!"; - directoriesToDeleteAtCleanup.push_back(tempDirectory); + WriteToFile(filename, content.c_str(), content.length()); - // - // Create subdirectory - // - std::wstring subDirectory = tempDirectory + L"\\sub"; - long status = CreateDirectoryW(subDirectory.c_str(), NULL); - Assert::AreNotEqual(0L, status); + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); - // - // Start the monitor - // - SourceFile sourceFile; - sourceFile.Directory = tempDirectory; - sourceFile.Filter = L"*.log"; - sourceFile.IncludeSubdirectories = true; - sourceFile.WaitInSeconds = 10; + Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); + } + } + /// + /// Check that adding files to the directory prints their content + /// to stdout + /// + TEST_METHOD(TestAddFiles) + { + std::wstring output; + + std::wstring tempDirectory = CreateTempDirectory(); + Assert::IsFalse(tempDirectory.empty()); + + directoriesToDeleteAtCleanup.push_back(tempDirectory); + + // + // Create subdirectory + // + std::wstring subDirectory = tempDirectory + L"\\sub"; + BOOL directoryCreated = CreateDirectoryW(subDirectory.c_str(), NULL); + Assert::IsTrue(directoryCreated != FALSE); + + // + // Start the monitor + // + SourceFile sourceFile; + sourceFile.Directory = tempDirectory; + sourceFile.Filter = L"*.log"; + sourceFile.IncludeSubdirectories = true; + sourceFile.WaitInSeconds = 10; + + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + + std::shared_ptr logfileMon = std::make_shared( + sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, + sourceFile.WaitInSeconds, L"json", L""); + Sleep(WAIT_TIME_LOGFILEMONITOR_START); + + // + // Check that LogFileMonitor started successfully. + // + output = RecoverOuput(); + Assert::AreEqual(L"", output.c_str()); + + // + // Check if the short path could be doing something wrong. + // + { fflush(stdout); ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); - Sleep(WAIT_TIME_LOGFILEMONITOR_START); - // - // Check that LogFileMonitor started successfully. + // Try with a long file name // - output = RecoverOuput(); - Assert::AreEqual(L"", output.c_str()); + std::wstring filename = sourceFile.Directory + L"\\abcdefghijklmnopqrstuvwxyz.log"; + std::string content = "Hello World!"; - // - // Check if the short path could be doing something wrong. - // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - // - // Try with a long file name - // - std::wstring filename = sourceFile.Directory + L"\\abcdefghijklmnopqrstuvwxyz.log"; - std::string content = "Hello World!"; - - WriteToFile(filename, content.c_str(), content.length()); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); - - Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); - } + WriteToFile(filename, content.c_str(), content.length()); + + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); + + Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); + } + + // + // Check if the short path could be doing something wrong. + // + { + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); // - // Check if the short path could be doing something wrong. + // Try with a long file name // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - // - // Try with a long file name - // - std::wstring filename = subDirectory + L"\\other.log"; - std::string content = "Hello Subdirectory!"; - - WriteToFile(filename, content.c_str(), content.length()); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); - - Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); - } + std::wstring filename = subDirectory + L"\\other.log"; + std::string content = "Hello Subdirectory!"; + + WriteToFile(filename, content.c_str(), content.length()); + + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); + + Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); } + } - /// - /// Check that adding files to subdirectories works as same as - /// adding it to the root directory. - /// - TEST_METHOD(TestModifyFiles) + /// + /// Check that adding files to subdirectories works as same as + /// adding it to the root directory. + /// + TEST_METHOD(TestModifyFiles) + { + std::wstring output; + + std::wstring tempDirectory = CreateTempDirectory(); + Assert::IsFalse(tempDirectory.empty()); + + directoriesToDeleteAtCleanup.push_back(tempDirectory); + + // + // Create a new file inside this directory. + // + std::wstring oldFilename = tempDirectory + L"\\oldfile.log"; + std::string oldContent = "Old content"; + + WriteToFile(oldFilename, oldContent.c_str(), oldContent.length()); + + // + // Create subdirectory. + // + std::wstring subDirectory = tempDirectory + L"\\sub"; + BOOL directoryCreated = CreateDirectoryW(subDirectory.c_str(), NULL); + Assert::IsTrue(directoryCreated != FALSE); + + // + // Create an file inside a subdirectory. + // + std::wstring oldFilenameSubdirectory = subDirectory + L"\\oldfile.log"; + WriteToFile(oldFilenameSubdirectory, oldContent.c_str(), oldContent.length()); + + // + // Start the monitor + // + SourceFile sourceFile; + sourceFile.Directory = tempDirectory; + sourceFile.Filter = L"*.log"; + sourceFile.IncludeSubdirectories = true; + sourceFile.WaitInSeconds = 10; + + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + + std::shared_ptr logfileMon = std::make_shared( + sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, + sourceFile.WaitInSeconds, L"json", L""); + Sleep(WAIT_TIME_LOGFILEMONITOR_START); + + // + // Check that LogFileMonitor started successfully and that the content + // of the existing files is not printed. + // + output = RecoverOuput(); + Assert::AreEqual(L"", output.c_str()); + + // + // Write to existing file and see stdout. + // { - std::wstring output; + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::wstring tempDirectory = CreateTempDirectory(); - Assert::IsFalse(tempDirectory.empty()); + std::string content = "New content"; - directoriesToDeleteAtCleanup.push_back(tempDirectory); + WriteToFile(oldFilename, content.c_str(), content.length()); - // - // Create a new file inside this directory. - // - std::wstring oldFilename = tempDirectory + L"\\oldfile.log"; - std::string oldContent = "Old content"; + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); - WriteToFile(oldFilename, oldContent.c_str(), oldContent.length()); + Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); + } - // - // Create subdirectory. - // - std::wstring subDirectory = tempDirectory + L"\\sub"; - long status = CreateDirectoryW(subDirectory.c_str(), NULL); - Assert::AreNotEqual(0L, status); + // + // Write to existing file and see stdout. + // + { + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - // - // Create an file inside a subdirectory. - // - std::wstring oldFilenameSubdirectory = subDirectory + L"\\oldfile.log"; - WriteToFile(oldFilenameSubdirectory, oldContent.c_str(), oldContent.length()); + std::string content = "Other new content"; - // - // Start the monitor - // - SourceFile sourceFile; - sourceFile.Directory = tempDirectory; - sourceFile.Filter = L"*.log"; - sourceFile.IncludeSubdirectories = true; - sourceFile.WaitInSeconds = 10; + WriteToFile(oldFilenameSubdirectory, content.c_str(), content.length()); + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); + + Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); + } + + // + // Write a long line (more than 4096 characters). + // + { fflush(stdout); ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); - Sleep(WAIT_TIME_LOGFILEMONITOR_START); + std::wstring filename = sourceFile.Directory + L"\\longline.log"; + std::string content(20000, '#'); - // - // Check that LogFileMonitor started successfully and that the content - // of the existing files is not printed. - // - output = RecoverOuput(); - Assert::AreEqual(L"", output.c_str()); + WriteToFile(filename, content.c_str(), content.length()); - // - // Write to existing file and see stdout. - // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); - std::string content = "New content"; + Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); + } + } - status = WriteToFile(oldFilename, content.c_str(), content.length()); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); + /// + /// Check that renaming a file doesn't stop its listening. + /// + TEST_METHOD(TestRenameFiles) + { + std::wstring output; + + std::wstring tempDirectory = CreateTempDirectory(); + Assert::IsFalse(tempDirectory.empty()); + + directoriesToDeleteAtCleanup.push_back(tempDirectory); + + // + // Create a new file inside this directory. + // + std::wstring oldFilename = tempDirectory + L"\\oldfile.log"; + std::string oldContent = "Old content"; + + WriteToFile(oldFilename, oldContent.c_str(), oldContent.length()); + + // + // Create subdirectory. + // + std::wstring subDirectory = tempDirectory + L"\\sub"; + BOOL directoryCreated = CreateDirectoryW(subDirectory.c_str(), NULL); + Assert::IsTrue(directoryCreated != FALSE); + + // + // Create an file inside a subdirectory. + // + std::wstring oldFilenameSubdirectory = subDirectory + L"\\oldfile.log"; + WriteToFile(oldFilenameSubdirectory, oldContent.c_str(), oldContent.length()); + + // + // Start the monitor + // + SourceFile sourceFile; + sourceFile.Directory = tempDirectory; + sourceFile.Filter = L"*.log"; + sourceFile.IncludeSubdirectories = true; + sourceFile.WaitInSeconds = 10; + + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + + std::shared_ptr logfileMon = std::make_shared( + sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, + sourceFile.WaitInSeconds, L"json", L""); + Sleep(WAIT_TIME_LOGFILEMONITOR_START); + + // + // Check that LogFileMonitor started successfully and that the content + // of the existing files is not printed. + // + output = RecoverOuput(); + Assert::AreEqual(L"", output.c_str()); + + // + // Write to existing file and see stdout. + // + { + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); - } + std::wstring newFilename = tempDirectory + L"\\renamedFile.log"; - // - // Write to existing file and see stdout. - // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + bool success = MoveFile(oldFilename.c_str(), newFilename.c_str()); + Assert::IsTrue(success); - std::string content = "Other new content"; + std::string content = "New content"; - WriteToFile(oldFilenameSubdirectory, content.c_str(), content.length()); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); + WriteToFile(newFilename, content.c_str(), content.length()); - Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); - } + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); - // - // Write a long line (more than 4096 characters). - // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - std::wstring filename = sourceFile.Directory + L"\\longline.log"; - std::string content(20000, '#'); - - status = WriteToFile(filename, content.c_str(), content.length()); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); - - Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); - } + Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); } - /// - /// Check that renaming a file doesn't stop its listening. - /// - TEST_METHOD(TestRenameFiles) + // + // Write to existing file and see stdout. + // { - std::wstring output; + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::wstring tempDirectory = CreateTempDirectory(); - Assert::IsFalse(tempDirectory.empty()); + std::wstring newFilename = subDirectory + L"\\renamedFileSub.log"; - directoriesToDeleteAtCleanup.push_back(tempDirectory); + bool success = MoveFile(oldFilenameSubdirectory.c_str(), newFilename.c_str()); + Assert::IsTrue(success); - // - // Create a new file inside this directory. - // - std::wstring oldFilename = tempDirectory + L"\\oldfile.log"; - std::string oldContent = "Old content"; + std::string content = "Other new content"; - WriteToFile(oldFilename, oldContent.c_str(), oldContent.length()); + WriteToFile(newFilename, content.c_str(), content.length()); - // - // Create subdirectory. - // - std::wstring subDirectory = tempDirectory + L"\\sub"; - long status = CreateDirectoryW(subDirectory.c_str(), NULL); - Assert::AreNotEqual(0L, status); + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); + + Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); + } + + // + // Rename a matching file name to an unmatching one. + // + { + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + + std::wstring initialFilename = tempDirectory + L"\\MatchingToUnmatching.log"; + std::wstring renamedFilename = tempDirectory + L"\\MatchingToUnmatching.txt"; // - // Create an file inside a subdirectory. + // First, create an filter-matching file. // - std::wstring oldFilenameSubdirectory = subDirectory + L"\\oldfile.log"; - WriteToFile(oldFilenameSubdirectory, oldContent.c_str(), oldContent.length()); + std::string content = "Other new content"; + WriteToFile(initialFilename, content.c_str(), content.length()); + + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); + + Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); // - // Start the monitor + // Rename it with a filter-unmatching name. // - SourceFile sourceFile; - sourceFile.Directory = tempDirectory; - sourceFile.Filter = L"*.log"; - sourceFile.IncludeSubdirectories = true; - sourceFile.WaitInSeconds = 10; + bool success = MoveFile(initialFilename.c_str(), renamedFilename.c_str()); + Assert::IsTrue(success); fflush(stdout); ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); - Sleep(WAIT_TIME_LOGFILEMONITOR_START); + WriteToFile(renamedFilename, content.c_str(), content.length()); + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_LONG); // - // Check that LogFileMonitor started successfully and that the content - // of the existing files is not printed. + // It should not be logged. // output = RecoverOuput(); Assert::AreEqual(L"", output.c_str()); + } - // - // Write to existing file and see stdout. - // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - std::wstring newFilename = tempDirectory + L"\\renamedFile.log"; - - bool success = MoveFile(oldFilename.c_str(), newFilename.c_str()); - Assert::IsTrue(success); - - std::string content = "New content"; + // + // Rename a matching file name to an unmatching one. + // + { + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - status = WriteToFile(newFilename, content.c_str(), content.length()); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); - - Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); - } + std::wstring initialFilename = tempDirectory + L"\\UnmatchingToMatching.txt"; + std::wstring renamedFilename = tempDirectory + L"\\UnmatchingToMatching.log"; // - // Write to existing file and see stdout. + // First, create an filter-unmatching file. // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + std::string content = "Other new content"; + WriteToFile(initialFilename, content.c_str(), content.length()); + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_LONG); - std::wstring newFilename = subDirectory + L"\\renamedFileSub.log"; - - bool success = MoveFile(oldFilenameSubdirectory.c_str(), newFilename.c_str()); - Assert::IsTrue(success); + Assert::AreEqual(L"", output.c_str()); - std::string content = "Other new content"; + // + // Rename it with a filter-matching name. + // + bool success = MoveFile(initialFilename.c_str(), renamedFilename.c_str()); + Assert::IsTrue(success); - WriteToFile(newFilename, content.c_str(), content.length()); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); - } + WriteToFile(renamedFilename, content.c_str(), content.length()); - // - // Rename a matching file name to an unmatching one. - // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - std::wstring initialFilename = tempDirectory + L"\\MatchingToUnmatching.log"; - std::wstring renamedFilename = tempDirectory + L"\\MatchingToUnmatching.txt"; - - // - // First, create an filter-matching file. - // - std::string content = "Other new content"; - WriteToFile(initialFilename, content.c_str(), content.length()); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); - - Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); - - // - // Rename it with a filter-unmatching name. - // - bool success = MoveFile(initialFilename.c_str(), renamedFilename.c_str()); - Assert::IsTrue(success); - - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - WriteToFile(renamedFilename, content.c_str(), content.length()); - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_LONG); - - // - // It should not be logged. - // + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); output = RecoverOuput(); - Assert::AreEqual(L"", output.c_str()); - } + } while (output.empty() && retries < READ_OUTPUT_RETRIES); // - // Rename a matching file name to an unmatching one. + // It should the FULL content of the file, that's why we need + // to concatenate twice the content. // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - std::wstring initialFilename = tempDirectory + L"\\UnmatchingToMatching.txt"; - std::wstring renamedFilename = tempDirectory + L"\\UnmatchingToMatching.log"; - - // - // First, create an filter-unmatching file. - // - std::string content = "Other new content"; - WriteToFile(initialFilename, content.c_str(), content.length()); - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_LONG); - - Assert::AreEqual(L"", output.c_str()); - - // - // Rename it with a filter-matching name. - // - bool success = MoveFile(initialFilename.c_str(), renamedFilename.c_str()); - Assert::IsTrue(success); - - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - WriteToFile(renamedFilename, content.c_str(), content.length()); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); - - // - // It should the FULL content of the file, that's why we need - // to concatenate twice the content. - // - Assert::IsTrue(output.find((TO_WSTR(content) + TO_WSTR(content)).c_str()) != std::wstring::npos); - } + Assert::IsTrue(output.find((TO_WSTR(content) + TO_WSTR(content)).c_str()) != std::wstring::npos); } + } - /// - /// Check that renaming a subdirectory doesn't stop - /// the listening to the files on it. - /// - TEST_METHOD(TestRenameSubdirectories) + /// + /// Check that renaming a subdirectory doesn't stop + /// the listening to the files on it. + /// + TEST_METHOD(TestRenameSubdirectories) + { + std::wstring output; + + std::wstring tempDirectory = CreateTempDirectory(); + Assert::IsFalse(tempDirectory.empty()); + + directoriesToDeleteAtCleanup.push_back(tempDirectory); + + // + // Create subdirectory. + // + std::string oldContent = "Old content"; + std::wstring subDirectory = tempDirectory + L"\\sub"; + BOOL directoryCreated = CreateDirectoryW(subDirectory.c_str(), NULL); + Assert::IsTrue(directoryCreated != FALSE); + + // + // Create an file inside a subdirectory. + // + std::wstring filename = L"\\filename.log"; + std::wstring oldFilenameSubdirectory = subDirectory + filename; + WriteToFile(oldFilenameSubdirectory, oldContent.c_str(), oldContent.length()); + + // + // Start the monitor + // + SourceFile sourceFile; + sourceFile.Directory = tempDirectory; + sourceFile.Filter = L"*.log"; + sourceFile.IncludeSubdirectories = true; + sourceFile.WaitInSeconds = 10; + + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + + std::shared_ptr logfileMon = std::make_shared( + sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, + sourceFile.WaitInSeconds, L"json", L""); + Sleep(WAIT_TIME_LOGFILEMONITOR_START); + + // + // Check that LogFileMonitor started successfully and that the content + // of the existing files is not printed. + // + output = RecoverOuput(); + Assert::AreEqual(L"", output.c_str()); + + // + // Write to existing file and see stdout. + // { - std::wstring output; - - std::wstring tempDirectory = CreateTempDirectory(); - Assert::IsFalse(tempDirectory.empty()); + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - directoriesToDeleteAtCleanup.push_back(tempDirectory); + std::string content = "New content"; - // - // Create subdirectory. - // - std::string oldContent = "Old content"; - std::wstring subDirectory = tempDirectory + L"\\sub"; - long status = CreateDirectoryW(subDirectory.c_str(), NULL); - Assert::AreNotEqual(0L, status); + WriteToFile(oldFilenameSubdirectory, content.c_str(), content.length()); - // - // Create an file inside a subdirectory. - // - std::wstring filename = L"\\filename.log"; - std::wstring oldFilenameSubdirectory = subDirectory + filename; - WriteToFile(oldFilenameSubdirectory, oldContent.c_str(), oldContent.length()); + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); - // - // Start the monitor - // - SourceFile sourceFile; - sourceFile.Directory = tempDirectory; - sourceFile.Filter = L"*.log"; - sourceFile.IncludeSubdirectories = true; - sourceFile.WaitInSeconds = 10; + Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); + } + // + // Rename the subdirectory, write to the existing file insidte it + // and see stdout. + // + { fflush(stdout); ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); - Sleep(WAIT_TIME_LOGFILEMONITOR_START); + std::wstring newSubdirectory = tempDirectory + L"\\newSubdirectory"; // - // Check that LogFileMonitor started successfully and that the content - // of the existing files is not printed. + // Rename the subdirectory. // - output = RecoverOuput(); - Assert::AreEqual(L"", output.c_str()); + bool success = MoveFile(subDirectory.c_str(), newSubdirectory.c_str()); + Assert::IsTrue(success); + + std::wstring newFilename = newSubdirectory + filename; // - // Write to existing file and see stdout. + // Test that the file inside the renamed subdirectory still exists // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + WIN32_FIND_DATA FindFileData; + HANDLE handle = FindFirstFileW(newFilename.c_str(), &FindFileData); + Assert::IsTrue(handle != INVALID_HANDLE_VALUE); - std::string content = "New content"; + // + // Write in it. + // + std::string content = "Other new content"; - WriteToFile(oldFilenameSubdirectory, content.c_str(), content.length()); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); + WriteToFile(newFilename, content.c_str(), content.length()); - Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); - } + int retries = 0; + do { + retries++; + Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); + output = RecoverOuput(); + } while (output.empty() && retries < READ_OUTPUT_RETRIES); - // - // Rename the subdirectory, write to the existing file insidte it - // and see stdout. - // - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - std::wstring newSubdirectory = tempDirectory + L"\\newSubdirectory"; - - // - // Rename the subdirectory. - // - bool success = MoveFile(subDirectory.c_str(), newSubdirectory.c_str()); - Assert::IsTrue(success); - - std::wstring newFilename = newSubdirectory + filename; - - // - // Test that the file inside the renamed subdirectory still exists - // - WIN32_FIND_DATA FindFileData; - HANDLE handle = FindFirstFileW(newFilename.c_str(), &FindFileData); - Assert::IsTrue(handle != INVALID_HANDLE_VALUE); - - // - // Write in it. - // - std::string content = "Other new content"; - - WriteToFile(newFilename, content.c_str(), content.length()); - - int retries = 0; - do { - retries++; - Sleep(WAIT_TIME_LOGFILEMONITOR_AFTER_WRITE_SHORT); - output = RecoverOuput(); - } while (output.empty() && retries < READ_OUTPUT_RETRIES); - - Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); - } + Assert::IsTrue(output.find(TO_WSTR(content)) != std::wstring::npos); } - }; -} \ No newline at end of file + } +}; +} // namespace LogMonitorTests diff --git a/LogMonitor/LogMonitorTests/LogMonitorTests.cpp b/LogMonitor/LogMonitorTests/LogMonitorTests.cpp index b85b7380..b9ae81ab 100644 --- a/LogMonitor/LogMonitorTests/LogMonitorTests.cpp +++ b/LogMonitor/LogMonitorTests/LogMonitorTests.cpp @@ -3,8 +3,11 @@ // Licensed under the MIT license. // -#include "pch.h" +#include "pch.h" // NOLINT(build/include_subdir) +#include // NOLINT(build/include_order) +// The VS test project compiles these implementations into this test translation unit. +// NOLINTBEGIN(build/include) #include "../src/LogMonitor/EtwMonitor.cpp" #include "../src/LogMonitor/EventMonitor.cpp" #include "../src/LogMonitor/JsonFileParser.cpp" @@ -13,11 +16,12 @@ #include "../src/LogMonitor/LogFileMonitor.cpp" #include "../src/LogMonitor/ProcessMonitor.cpp" #include "../src/LogMonitor/Utility.cpp" +// NOLINTEND #pragma comment(lib, "wevtapi.lib") #pragma comment(lib, "tdh.lib") #pragma comment(lib, "ws2_32.lib") // For ntohs function -#pragma comment(lib, "shlwapi.lib") +#pragma comment(lib, "shlwapi.lib") using namespace Microsoft::VisualStudio::CppUnitTestFramework; @@ -27,56 +31,55 @@ LogWriter logWriter; namespace LogMonitorTests { +/// +/// Tests the tools that other tests could use. i.e. redirecting stdout. +/// +TEST_CLASS(LogMonitorTests) +{ + WCHAR bigOutBuf[BUFFER_SIZE]; + + /// + /// Gets the content of the Stdout buffer and returns it in a wstring. /// - /// Tests the tools that other tests could use. i.e. redirecting stdout. + /// \return A wstring with the stdout. /// - TEST_CLASS(LogMonitorTests) + std::wstring RecoverOuput() { - WCHAR bigOutBuf[BUFFER_SIZE]; + return std::wstring(bigOutBuf); + } - /// - /// Gets the content of the Stdout buffer and returns it in a wstring. - /// - /// \return A wstring with the stdout. - /// - std::wstring RecoverOuput() - { - return std::wstring(bigOutBuf); - } - - public: +public: + /// + /// "Redirects" the stdout to our buffer. + /// + TEST_METHOD_INITIALIZE(InitializeLogFileMonitorTests) + { + // + // Set our own buffer in stdout. + // + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + fflush(stdout); + _setmode(_fileno(stdout), _O_U16TEXT); + setvbuf(stdout, (char*)bigOutBuf, _IOFBF, sizeof(bigOutBuf) - sizeof(WCHAR)); + } - /// - /// "Redirects" the stdout to our buffer. - /// - TEST_METHOD_INITIALIZE(InitializeLogFileMonitorTests) - { - // - // Set our own buffer in stdout. - // - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); - _setmode(_fileno(stdout), _O_U16TEXT); - setvbuf(stdout, (char*)bigOutBuf, _IOFBF, sizeof(bigOutBuf) - sizeof(WCHAR)); - } - - /// - /// Test that things being printed with logWriter are sent to - /// bigOutBuf buffer. - /// - TEST_METHOD(TestRedirectedOutputForTests) - { - std::wstring expectedOutput(BUFFER_SIZE - 4, L'#'); + /// + /// Test that things being printed with logWriter are sent to + /// bigOutBuf buffer. + /// + TEST_METHOD(TestRedirectedOutputForTests) + { + std::wstring expectedOutput(BUFFER_SIZE - 4, L'#'); - logWriter.WriteConsoleLog(expectedOutput); - expectedOutput += L"\n"; + logWriter.WriteConsoleLog(expectedOutput); + expectedOutput += L"\n"; - // - // Recover the stdout - // - std::wstring realOutput = RecoverOuput(); + // + // Recover the stdout + // + std::wstring realOutput = RecoverOuput(); - Assert::AreEqual(expectedOutput, realOutput); - } - }; -} + Assert::AreEqual(expectedOutput, realOutput); + } +}; +} // namespace LogMonitorTests diff --git a/LogMonitor/LogMonitorTests/Utility.cpp b/LogMonitor/LogMonitorTests/Utility.cpp index 2664c164..effa013f 100644 --- a/LogMonitor/LogMonitorTests/Utility.cpp +++ b/LogMonitor/LogMonitorTests/Utility.cpp @@ -2,7 +2,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // -#include "pch.h" +#include "pch.h" // NOLINT(build/include_subdir) +#include "Utility.h" // NOLINT(build/include_subdir) using namespace std; @@ -13,7 +14,7 @@ using namespace std; /// /// -/// Creates a new random-name directory inside the Temp directory. +/// Creates a new random-name directory inside the Temp directory. /// /// \return The new directory path. If an error occurs, it's empty. /// @@ -28,11 +29,11 @@ wstring CreateTempDirectory() return L""; } - long status = CreateDirectoryW(tempDirectory, NULL); + BOOL status = CreateDirectoryW(tempDirectory, NULL); if (status == 0) { return L""; } return wstring(tempDirectory); -} \ No newline at end of file +} diff --git a/LogMonitor/LogMonitorTests/Utility.h b/LogMonitor/LogMonitorTests/Utility.h index 1a63bec7..2b73ff50 100644 --- a/LogMonitor/LogMonitorTests/Utility.h +++ b/LogMonitor/LogMonitorTests/Utility.h @@ -1,3 +1,9 @@ +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// #pragma once -std::wstring CreateTempDirectory(); \ No newline at end of file +#include + +std::wstring CreateTempDirectory(); diff --git a/LogMonitor/LogMonitorTests/UtilityTests.cpp b/LogMonitor/LogMonitorTests/UtilityTests.cpp index 8642d77d..df7fa15f 100644 --- a/LogMonitor/LogMonitorTests/UtilityTests.cpp +++ b/LogMonitor/LogMonitorTests/UtilityTests.cpp @@ -3,7 +3,7 @@ // Licensed under the MIT license. // -#include "pch.h" +#include "pch.h" // NOLINT(build/include_subdir) using namespace Microsoft::VisualStudio::CppUnitTestFramework; @@ -12,79 +12,79 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace UtilityTests { - /// - /// Tests of Utility class methods. - /// - TEST_CLASS(UtilityTests) +/// +/// Tests of Utility class methods. +/// +TEST_CLASS(UtilityTests) +{ +public: + TEST_METHOD(TestisJsonNumberTrue) + { + std::wstring str = L"-0.12"; + Assert::IsTrue(Utility::isJsonNumber(str), L"should return true -0.12"); + + str = L"-1.1234"; + Assert::IsTrue(Utility::isJsonNumber(str), L"should return true for -1.1234"); + + str = L"1.12"; + Assert::IsTrue(Utility::isJsonNumber(str), L"should return true for 1.12"); + + str = L"1"; + Assert::IsTrue(Utility::isJsonNumber(str), L"should return true for 1"); + + str = L"0"; + Assert::IsTrue(Utility::isJsonNumber(str), L"should return true for 0"); + + str = L"456662"; + Assert::IsTrue(Utility::isJsonNumber(str), L"should return true for 456662"); + + str = L"456662.8989"; + Assert::IsTrue(Utility::isJsonNumber(str), L"should return true for 456662.8989"); + } + + TEST_METHOD(TestisJsonNumberFalse) + { + std::wstring str = L"false"; + Assert::IsFalse(Utility::isJsonNumber(str), L"should return false for \"false\""); + + str = L"12.12.89.12"; + Assert::IsFalse(Utility::isJsonNumber(str), L"should return calse for 12.12.89.12"); + + str = L"1200.23x"; + Assert::IsFalse(Utility::isJsonNumber(str), L"should return false for 1200.23x"); + } + + TEST_METHOD(TestSanitizeJson) { - public: - TEST_METHOD(TestisJsonNumberTrue) - { - std::wstring str = L"-0.12"; - Assert::IsTrue(Utility::isJsonNumber(str), L"should return true -0.12"); - - str = L"-1.1234"; - Assert::IsTrue(Utility::isJsonNumber(str), L"should return true for -1.1234"); - - str = L"1.12"; - Assert::IsTrue(Utility::isJsonNumber(str), L"should return true for 1.12"); - - str = L"1"; - Assert::IsTrue(Utility::isJsonNumber(str), L"should return true for 1"); - - str = L"0"; - Assert::IsTrue(Utility::isJsonNumber(str), L"should return true for 0"); - - str = L"456662"; - Assert::IsTrue(Utility::isJsonNumber(str), L"should return true for 456662"); - - str = L"456662.8989"; - Assert::IsTrue(Utility::isJsonNumber(str), L"should return true for 456662.8989"); - } - - TEST_METHOD(TestisJsonNumberFalse) - { - std::wstring str = L"false"; - Assert::IsFalse(Utility::isJsonNumber(str), L"should return false for \"false\""); - - str = L"12.12.89.12"; - Assert::IsFalse(Utility::isJsonNumber(str), L"should return calse for 12.12.89.12"); - - str = L"1200.23x"; - Assert::IsFalse(Utility::isJsonNumber(str), L"should return false for 1200.23x"); - } - - TEST_METHOD(TestSanitizeJson) - { - std::wstring str = L"say, \"hello\""; - std::wstring expect = L"say, \\\"hello\\\""; - Utility::SanitizeJson(str); - Assert::IsTrue(str == expect, L"should escape \""); - - str = L"\"hello\""; - expect = L"\\\"hello\\\""; - Utility::SanitizeJson(str); - Assert::IsTrue(str == expect, L"should escape \""); - - str = L"hello\r\nworld"; - expect = L"hello\\r\\nworld"; - Utility::SanitizeJson(str); - Assert::IsTrue(str == expect, L"should escape \r and \n"); - - str = L"\r\nHello\r\n"; - expect = L"\\r\\nHello\\r\\n"; - Utility::SanitizeJson(str); - Assert::IsTrue(str == expect, L"should escape \r and \n"); - - str = L"\\Driver\\XX\\"; - expect = L"\\\\Driver\\\\XX\\\\"; - Utility::SanitizeJson(str); - Assert::IsTrue(str == expect, L"should escape \\"); - - str = L"C:\\Drive\\XX"; - expect = L"C:\\\\Drive\\\\XX"; - Utility::SanitizeJson(str); - Assert::IsTrue(str == expect, L"should escape \\"); - } - }; -} + std::wstring str = L"say, \"hello\""; + std::wstring expect = L"say, \\\"hello\\\""; + Utility::SanitizeJson(str); + Assert::IsTrue(str == expect, L"should escape \""); + + str = L"\"hello\""; + expect = L"\\\"hello\\\""; + Utility::SanitizeJson(str); + Assert::IsTrue(str == expect, L"should escape \""); + + str = L"hello\r\nworld"; + expect = L"hello\\r\\nworld"; + Utility::SanitizeJson(str); + Assert::IsTrue(str == expect, L"should escape \r and \n"); + + str = L"\r\nHello\r\n"; + expect = L"\\r\\nHello\\r\\n"; + Utility::SanitizeJson(str); + Assert::IsTrue(str == expect, L"should escape \r and \n"); + + str = L"\\Driver\\XX\\"; + expect = L"\\\\Driver\\\\XX\\\\"; + Utility::SanitizeJson(str); + Assert::IsTrue(str == expect, L"should escape \\"); + + str = L"C:\\Drive\\XX"; + expect = L"C:\\\\Drive\\\\XX"; + Utility::SanitizeJson(str); + Assert::IsTrue(str == expect, L"should escape \\"); + } +}; +} // namespace UtilityTests diff --git a/LogMonitor/LogMonitorTests/pch.cpp b/LogMonitor/LogMonitorTests/pch.cpp index 2338cd93..35a3fef2 100644 --- a/LogMonitor/LogMonitorTests/pch.cpp +++ b/LogMonitor/LogMonitorTests/pch.cpp @@ -5,6 +5,6 @@ // pch.cpp: source file corresponding to the pre-compiled header -#include "pch.h" +#include "pch.h" // NOLINT(build/include_subdir) -// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. \ No newline at end of file +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/LogMonitor/LogMonitorTests/pch.h b/LogMonitor/LogMonitorTests/pch.h index 6c2a6957..adefd0f4 100644 --- a/LogMonitor/LogMonitorTests/pch.h +++ b/LogMonitor/LogMonitorTests/pch.h @@ -9,11 +9,13 @@ // However, files listed here are ALL re-compiled if any one of them is updated between builds. // Do not add files here that you will be updating frequently as this negates the performance advantage. -#ifndef PCH_H -#define PCH_H +#ifndef LOGMONITOR_LOGMONITORTESTS_PCH_H_ +#define LOGMONITOR_LOGMONITORTESTS_PCH_H_ // add headers that you want to pre-compile here +// Preserve the Windows header dependencies and PCH order used by the test project. +// NOLINTBEGIN(build/include_order) #include "CppUnitTest.h" #include #include @@ -48,10 +50,11 @@ #include #include #include -#include "shlwapi.h" -#include -#include -#include +#include +#include +#include +#include +// NOLINTEND #include #include "../src/LogMonitor/Utility.h" #include "../src/LogMonitor/Parser/ConfigFileParser.h" @@ -64,4 +67,4 @@ #include "../src/LogMonitor/LogFileMonitor.h" #include "../src/LogMonitor/ProcessMonitor.h" #include "Utility.h" -#endif //PCH_H +#endif // LOGMONITOR_LOGMONITORTESTS_PCH_H_ diff --git a/LogMonitor/README.md b/LogMonitor/README.md index 18c82ec7..cd2af93a 100644 --- a/LogMonitor/README.md +++ b/LogMonitor/README.md @@ -24,6 +24,27 @@ The log tool is supported for Windows, Server Core, and Nano images. ## Build +## Lint + +CI runs cpplint 2.0.2 on all tracked C++ source and header files, including tests, +and fails on lint diagnostics. Generated files and dependencies are not scanned. +The shared rules are in `CPPLINT.cfg`, with a 120-character line limit. +Reviewdog publishes pull-request check annotations; lint failures fail the job +independently of annotation filtering. + +To run the same checks from the repository root in PowerShell: + +```powershell +python -m pip install cpplint==2.0.2 +Set-Location .\LogMonitor +$sources = git ls-files -- '*.cpp' '*.h' +python -m cpplint @sources +``` + +Keep `pch.h` first in translation units. Follow the existing category-specific +`NOLINT` exceptions for PCH and Windows header ordering rather than disabling +rules globally. Block suppressions end with `NOLINTEND` without categories. + ## Releases Release versions of the binaries can be found on the [Log Monitor Releases page](https://github.com/microsoft/windows-container-tools/releases/latest). diff --git a/LogMonitor/src/LogMonitor/EtwMonitor.cpp b/LogMonitor/src/LogMonitor/EtwMonitor.cpp index 546d06a8..6c43eb12 100644 --- a/LogMonitor/src/LogMonitor/EtwMonitor.cpp +++ b/LogMonitor/src/LogMonitor/EtwMonitor.cpp @@ -5,6 +5,9 @@ #include "pch.h" // NOLINT(build/include_subdir) #include "EtwMonitor.h" // NOLINT(build/include_subdir) +#include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) #define MAX_NAME 256 diff --git a/LogMonitor/src/LogMonitor/EventMonitor.cpp b/LogMonitor/src/LogMonitor/EventMonitor.cpp index 1e2a7019..e4108f00 100644 --- a/LogMonitor/src/LogMonitor/EventMonitor.cpp +++ b/LogMonitor/src/LogMonitor/EventMonitor.cpp @@ -5,6 +5,7 @@ #include "pch.h" // NOLINT(build/include_subdir) #include "EventMonitor.h" // NOLINT(build/include_subdir) +#include // NOLINT(build/include_order) using namespace std; diff --git a/LogMonitor/src/LogMonitor/JsonFileParser.cpp b/LogMonitor/src/LogMonitor/JsonFileParser.cpp index f126c52d..47999f1b 100644 --- a/LogMonitor/src/LogMonitor/JsonFileParser.cpp +++ b/LogMonitor/src/LogMonitor/JsonFileParser.cpp @@ -6,6 +6,7 @@ #include "pch.h" // NOLINT(build/include_subdir) #include "Parser/JsonFileParser.h" // NOLINT(build/include_subdir) #include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) /// JsonFileParser.cpp /// @@ -682,7 +683,8 @@ JsonFileParser::AdvanceBufferPointer( size_t Offset ) { - m_currentPos = min(m_currentPos + Offset, m_bufferLength); + // min is the Windows SDK macro, not std::min. + m_currentPos = min(m_currentPos + Offset, m_bufferLength); // NOLINT(build/include_what_you_use) for (; m_currentPos < m_bufferLength && IsWhiteSpace(m_buffer[m_currentPos]); m_currentPos++) { diff --git a/LogMonitor/src/LogMonitor/LogFileMonitor.cpp b/LogMonitor/src/LogMonitor/LogFileMonitor.cpp index 335781d7..cf462b24 100644 --- a/LogMonitor/src/LogMonitor/LogFileMonitor.cpp +++ b/LogMonitor/src/LogMonitor/LogFileMonitor.cpp @@ -5,7 +5,11 @@ #include "pch.h" // NOLINT(build/include_subdir) #include "LogFileMonitor.h" // NOLINT(build/include_subdir) +#include // NOLINT(build/include_order) #include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) using namespace std; @@ -629,7 +633,6 @@ LogFileMonitor::LogDirectoryChangeNotificationHandler() dwNextEntryOffset = fileNotificationInfo->NextEntryOffset; fileNotificationInfo = (FILE_NOTIFY_INFORMATION*)((LPBYTE)fileNotificationInfo + fileNotificationInfo->NextEntryOffset); - } while (dwNextEntryOffset); } else diff --git a/LogMonitor/src/LogMonitor/Main.cpp b/LogMonitor/src/LogMonitor/Main.cpp index 8d94d1dc..49c89f61 100644 --- a/LogMonitor/src/LogMonitor/Main.cpp +++ b/LogMonitor/src/LogMonitor/Main.cpp @@ -5,6 +5,9 @@ #include "pch.h" // NOLINT(build/include_subdir) #include "Version.h" // NOLINT(build/include_subdir) +#include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) using namespace std; diff --git a/LogMonitor/src/LogMonitor/ProcessMonitor.cpp b/LogMonitor/src/LogMonitor/ProcessMonitor.cpp index 2dc4328f..f7c6aafd 100644 --- a/LogMonitor/src/LogMonitor/ProcessMonitor.cpp +++ b/LogMonitor/src/LogMonitor/ProcessMonitor.cpp @@ -5,6 +5,7 @@ #include "pch.h" // NOLINT(build/include_subdir) #include "ProcessMonitor.h" // NOLINT(build/include_subdir) +#include // NOLINT(build/include_order) using namespace std; diff --git a/LogMonitor/src/LogMonitor/Utility.cpp b/LogMonitor/src/LogMonitor/Utility.cpp index f5bda5a1..ae3d0079 100644 --- a/LogMonitor/src/LogMonitor/Utility.cpp +++ b/LogMonitor/src/LogMonitor/Utility.cpp @@ -7,6 +7,7 @@ #include "Utility.h" // NOLINT(build/include_subdir) #include // NOLINT(build/include_order) #include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) using namespace std; using json = nlohmann::json; diff --git a/LogMonitor/src/LogMonitor/pch.h b/LogMonitor/src/LogMonitor/pch.h index 4ff660cc..00664bbc 100644 --- a/LogMonitor/src/LogMonitor/pch.h +++ b/LogMonitor/src/LogMonitor/pch.h @@ -45,10 +45,10 @@ #include #include #include -#include "shlwapi.h" +#include #include #include -// NOLINTEND(build/include_order) +// NOLINTEND #include #include "Utility.h" // NOLINT(build/include_subdir) #include "Parser/ConfigFileParser.h" // NOLINT(build/include_subdir) From c41a5384fb26b3d4fe73ab867fb30787c3a8bd1b Mon Sep 17 00:00:00 2001 From: Bob Sira Date: Thu, 22 Oct 2026 17:44:07 +0100 Subject: [PATCH 2/4] Fix EventMonitor shutdown lifetime --- .../LogMonitorTests/EventMonitorTests.cpp | 71 +++++++++- LogMonitor/docs/README.md | 2 + LogMonitor/src/LogMonitor/EventMonitor.cpp | 129 +++++++++++++----- LogMonitor/src/LogMonitor/EventMonitor.h | 7 +- 4 files changed, 173 insertions(+), 36 deletions(-) diff --git a/LogMonitor/LogMonitorTests/EventMonitorTests.cpp b/LogMonitor/LogMonitorTests/EventMonitorTests.cpp index 29ce8ab5..1a2931f8 100644 --- a/LogMonitor/LogMonitorTests/EventMonitorTests.cpp +++ b/LogMonitor/LogMonitorTests/EventMonitorTests.cpp @@ -5,6 +5,7 @@ #include "pch.h" // NOLINT(build/include_subdir) #include // NOLINT(build/include_order) +#include // NOLINT(build/include_order) #include // NOLINT(build/include_order) @@ -43,6 +44,7 @@ TEST_CLASS(EventMonitorTests) }; WCHAR bigOutBuf[BUFFER_SIZE]; + int m_previousStdoutMode = -1; /// /// Gets the content of the Stdout buffer and returns it in a wstring. @@ -71,7 +73,7 @@ TEST_CLASS(EventMonitorTests) int status = _wsystem( Utility::FormatString( - L"powershell eventcreate /t %s /id %d /l APPLICATION /d \"\"\"\"%s\"\"\"\"", + L"powershell -NoProfile -NonInteractive eventcreate /t %s /id %d /l APPLICATION /d \"\"\"\"%s\"\"\"\"", c_LevelToString[(int)Level], Id, Message.c_str() @@ -93,10 +95,75 @@ TEST_CLASS(EventMonitorTests) // ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); fflush(stdout); - _setmode(_fileno(stdout), _O_U16TEXT); + m_previousStdoutMode = _setmode(_fileno(stdout), _O_U16TEXT); + Assert::AreNotEqual(-1, m_previousStdoutMode); setvbuf(stdout, (char*)bigOutBuf, _IOFBF, sizeof(bigOutBuf) - sizeof(WCHAR)); } + TEST_METHOD_CLEANUP(CleanupEventMonitorTests) + { + fflush(stdout); + setvbuf(stdout, nullptr, _IONBF, 0); + if (m_previousStdoutMode != -1) + { + _setmode(_fileno(stdout), m_previousStdoutMode); + } + } + + TEST_METHOD(TestImmediateShutdown) + { + std::vector eventChannels = { {L"Application", EventChannelLogLevel::Error} }; + + for (int attempt = 0; attempt < 20; ++attempt) + { + ULONGLONG start = GetTickCount64(); + { + EventMonitor eventMonitor(eventChannels, true, false, L"json", L""); + } + Assert::IsTrue(GetTickCount64() - start < 5000, + L"Stopping during startup must not exhaust the shutdown timeout."); + } + } + + TEST_METHOD(TestShutdownDuringReplay) + { + std::vector eventChannels = { {L"Application", EventChannelLogLevel::Error} }; + Assert::AreEqual(0, WriteEvent(EventChannelLogLevel::Error, 103, L"EventMonitor shutdown regression")); + + for (int attempt = 0; attempt < 3; ++attempt) + { + auto eventMonitor = std::make_unique(eventChannels, true, true, L"json", L""); + Sleep(WAIT_TIME_EVENTMONITOR_START); + + ULONGLONG start = GetTickCount64(); + eventMonitor.reset(); + Assert::IsTrue(GetTickCount64() - start < 5000, + L"Shutdown must cancel replay rather than exhaust the shutdown timeout."); + } + } + + TEST_METHOD(TestShutdownDoesNotLeakHandles) + { + std::vector eventChannels = { {L"Application", EventChannelLogLevel::Error} }; + { + EventMonitor eventMonitor(eventChannels, true, false, L"json", L""); + Sleep(WAIT_TIME_EVENTMONITOR_START); + } + + DWORD handlesBefore = 0; + Assert::IsTrue(GetProcessHandleCount(GetCurrentProcess(), &handlesBefore) != FALSE); + for (int attempt = 0; attempt < 20; ++attempt) + { + EventMonitor eventMonitor(eventChannels, true, false, L"json", L""); + } + + DWORD handlesAfter = 0; + Assert::IsTrue(GetProcessHandleCount(GetCurrentProcess(), &handlesAfter) != FALSE); + // Allow incidental test-host handles, but not two leaked handles per monitor. + Assert::IsTrue(handlesAfter <= handlesBefore + 4, + Utility::FormatString(L"Handle count grew from %lu to %lu.", handlesBefore, handlesAfter).c_str()); + } + /// /// Check that EventMonitor prints the written errors when level <= Error. /// diff --git a/LogMonitor/docs/README.md b/LogMonitor/docs/README.md index af845944..34560153 100644 --- a/LogMonitor/docs/README.md +++ b/LogMonitor/docs/README.md @@ -150,6 +150,8 @@ Using both the provider's name and provider GUID: Event log is a record of events related to the system, security, and application stored on a Windows operating system. The event log provides a standard, centralized way for apps and the operating system to record important software and hardware events, helping in tracking of potential threats and problems that could be potentially degrading performance. +During shutdown, Event Log monitoring cancels pending replay instead of draining the remaining records. It waits up to five seconds for its worker to exit before releasing resources. If signaling or waiting for the worker fails, or the wait times out, Log Monitor terminates the process with a Windows fail-fast exception rather than allowing the worker to access freed memory. + ### Configuration - `startAtOldestRecord` (Required): This Boolean field indicates whether the Log Monitor tool should output event logs from the start of the container boot or from the start of the Log Monitor tool itself. If set `true`, the tool should output the event logs from the start of container boot, and if set false, the tool only outputs event logs from the start of log monitor. diff --git a/LogMonitor/src/LogMonitor/EventMonitor.cpp b/LogMonitor/src/LogMonitor/EventMonitor.cpp index e4108f00..9f69bcb9 100644 --- a/LogMonitor/src/LogMonitor/EventMonitor.cpp +++ b/LogMonitor/src/LogMonitor/EventMonitor.cpp @@ -16,9 +16,9 @@ using namespace std; /// /// EventMonitor starts a monitor thread that waits for events from EvtSubscribe /// or for a stop event to be set. The same thread processes the changes and invokes the callback. -/// Note: The constructor blocks until the spawned thread starts listening for changes or dies. +/// The constructor starts the monitoring thread asynchronously. /// -/// The destructor signals the stop event and waits up to MONITOR_THREAD_EXIT_MAX_WAIT_MILLIS for the monitoring +/// The destructor signals the stop event and waits up to EVENT_MONITOR_THREAD_EXIT_MAX_WAIT_MILLIS for the monitoring /// thread to exit. To prevent the thread from out-living EventMonitor, the destructor fails fast /// if the wait fails or times out. This also ensures the callback is not being called and will not be /// called once EventMonitor is destroyed. @@ -51,7 +51,7 @@ EventMonitor::EventMonitor( m_eventMonitorThread = CreateThread( nullptr, 0, - (LPTHREAD_START_ROUTINE)&EventMonitor::StartEventMonitorStatic, + &EventMonitor::StartEventMonitorStatic, this, 0, nullptr @@ -59,17 +59,18 @@ EventMonitor::EventMonitor( if (!m_eventMonitorThread) { - throw std::system_error(std::error_code(GetLastError(), std::system_category()), "CreateThread"); + DWORD status = GetLastError(); + CloseHandle(m_stopEvent); + throw std::system_error(std::error_code(status, std::system_category()), "CreateThread"); } } EventMonitor::~EventMonitor() { + DWORD status = ERROR_SUCCESS; if (!SetEvent(m_stopEvent)) { - logWriter.TraceError( - Utility::FormatString(L"Failed to gracefully stop event log monitor %lu", GetLastError()).c_str() - ); + status = GetLastError(); } else { @@ -80,16 +81,27 @@ EventMonitor::~EventMonitor() if (waitResult != WAIT_OBJECT_0) { - // Wait failed or timed out + status = waitResult == WAIT_FAILED ? GetLastError() : ERROR_TIMEOUT; } } - if (!m_eventMonitorThread) + if (status != ERROR_SUCCESS) + { + // The worker may hold the log writer lock. Report the failure through WER without taking that lock. + EXCEPTION_RECORD exceptionRecord{}; + exceptionRecord.ExceptionCode = HRESULT_FROM_WIN32(status); + exceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE; + exceptionRecord.NumberParameters = 1; + exceptionRecord.ExceptionInformation[0] = status; + RaiseFailFastException(&exceptionRecord, nullptr, FAIL_FAST_GENERATE_EXCEPTION_ADDRESS); + } + + if (m_eventMonitorThread) { CloseHandle(m_eventMonitorThread); } - if (!m_stopEvent) + if (m_stopEvent) { CloseHandle(m_stopEvent); } @@ -103,7 +115,7 @@ EventMonitor::~EventMonitor() /// /// \return Status of event monitoring operation. /// -DWORD +DWORD WINAPI EventMonitor::StartEventMonitorStatic( _In_ LPVOID Context ) @@ -112,7 +124,7 @@ EventMonitor::StartEventMonitorStatic( try { DWORD status = pThis->StartEventMonitor(); - if (status != ERROR_SUCCESS) + if (status != ERROR_SUCCESS && status != ERROR_CANCELLED) { logWriter.TraceError( Utility::FormatString(L"Failed to start event log monitor. Error: %lu", status).c_str() @@ -136,6 +148,21 @@ EventMonitor::StartEventMonitorStatic( } } +DWORD +EventMonitor::CheckForStop() const +{ + DWORD waitResult = WaitForSingleObject(m_stopEvent, 0); + if (waitResult == WAIT_OBJECT_0) + { + return ERROR_CANCELLED; + } + if (waitResult == WAIT_TIMEOUT) + { + return ERROR_SUCCESS; + } + return waitResult == WAIT_FAILED ? GetLastError() : ERROR_INVALID_DATA; +} + /// /// Entry for the spawned event monitor thread. Loops to wait for either the stop event in which case it exits @@ -152,7 +179,16 @@ EventMonitor::StartEventMonitor() const LPWSTR pwsQuery = (LPWSTR)(L"*"); const DWORD eventsCount = 2; - EnableEventLogChannels(); + status = EnableEventLogChannels(); + if (status != ERROR_SUCCESS) + { + return status; + } + status = CheckForStop(); + if (status != ERROR_SUCCESS) + { + return status; + } // // Order stop event first so that stop is prioritized if both events are already signalled (changes @@ -234,11 +270,12 @@ EventMonitor::StartEventMonitor() { if (WAIT_FAILED == wait) { + status = GetLastError(); logWriter.TraceError( Utility::FormatString( L"Failed to subscribe to event log channel." L" Wait operation on event handle failed. Error: %lu.", - GetLastError()).c_str() + status).c_str() ); } break; @@ -340,17 +377,29 @@ EventMonitor::EnumerateResults( ) { DWORD status = ERROR_SUCCESS; - EVT_HANDLE hEvents[EVENT_ARRAY_SIZE]; + EVT_HANDLE hEvents[EVENT_ARRAY_SIZE]{}; DWORD dwReturned = 0; while (true) { + status = CheckForStop(); + if (status != ERROR_SUCCESS) + { + goto cleanup; + } + // // Get a block of events from the result set. // - if (!EvtNext(hResults, EVENT_ARRAY_SIZE, hEvents, INFINITE, 0, &dwReturned)) + dwReturned = 0; + if (!EvtNext(hResults, EVENT_ARRAY_SIZE, hEvents, EVENT_READ_TIMEOUT_MILLIS, 0, &dwReturned)) { - if (ERROR_NO_MORE_ITEMS != (status = GetLastError())) + status = GetLastError(); + if (status == ERROR_TIMEOUT) + { + continue; + } + if (status != ERROR_NO_MORE_ITEMS) { logWriter.TraceError( Utility::FormatString(L"Failed to query next event. Error: %lu.", status).c_str() @@ -366,6 +415,12 @@ EventMonitor::EnumerateResults( // for (DWORD i = 0; i < dwReturned; i++) { + status = CheckForStop(); + if (status != ERROR_SUCCESS) + { + goto cleanup; + } + status = PrintEvent(hEvents[i]); if (ERROR_SUCCESS != status) @@ -635,23 +690,27 @@ EventMonitor::PrintEvent( /// Enables all monitored event log channels. /// -/// \return None +/// \return ERROR_CANCELLED on shutdown, an error on a failed wait, or ERROR_SUCCESS. /// -void +DWORD EventMonitor::EnableEventLogChannels() { for (const auto& eventChannel : m_eventChannels) { - DWORD status = EnableEventLogChannel(eventChannel.Name.c_str()); + DWORD status = CheckForStop(); + if (status != ERROR_SUCCESS) { + return status; + } + status = EnableEventLogChannel(eventChannel.Name.c_str()); if (status == RPC_S_SERVER_UNAVAILABLE) { HANDLE timerEvent = CreateWaitableTimer(NULL, FALSE, NULL); if (!timerEvent) { - status = GetLastError(); - logWriter.TraceError( - Utility::FormatString( - L"Failed to create timer object.", status).c_str()); - break; + status = GetLastError(); + logWriter.TraceError( + Utility::FormatString( + L"Failed to create timer object. Error: %lu", status).c_str()); + return status; } std::double_t waitInSeconds = 300; @@ -672,7 +731,9 @@ EventMonitor::EnableEventLogChannels() L"Failed to set timer object to enable %s event channel. Error: %lu", eventChannel.Name.c_str(), status).c_str()); - break; + CancelWaitableTimer(timerEvent); + CloseHandle(timerEvent); + return status; } DWORD wait = WaitForMultipleObjects(eventsCount, channelEnableEvents, FALSE, INFINITE); @@ -684,6 +745,7 @@ EventMonitor::EnableEventLogChannels() // CancelWaitableTimer(timerEvent); CloseHandle(timerEvent); + return ERROR_CANCELLED; } case WAIT_OBJECT_0 + 1: @@ -708,6 +770,7 @@ EventMonitor::EnableEventLogChannels() CancelWaitableTimer(timerEvent); CloseHandle(timerEvent); + return status; } } @@ -716,12 +779,13 @@ EventMonitor::EnableEventLogChannels() if (retryStatus == RPC_S_SERVER_UNAVAILABLE) { elapsedTime += Utility::WAIT_INTERVAL; } else { - logWriter.TraceInfo( - Utility::FormatString( - L"Enabled %s event channel after %d seconds.", - eventChannel.Name.c_str(), - elapsedTime).c_str() ); - status = ERROR_SUCCESS; + if (retryStatus == ERROR_SUCCESS) { + logWriter.TraceInfo( + Utility::FormatString( + L"Enabled %s event channel after %d seconds.", + eventChannel.Name.c_str(), + elapsedTime).c_str() ); + } break; } } @@ -730,6 +794,7 @@ EventMonitor::EnableEventLogChannels() CloseHandle(timerEvent); } } + return ERROR_SUCCESS; } diff --git a/LogMonitor/src/LogMonitor/EventMonitor.h b/LogMonitor/src/LogMonitor/EventMonitor.h index 2e797acc..024efc43 100644 --- a/LogMonitor/src/LogMonitor/EventMonitor.h +++ b/LogMonitor/src/LogMonitor/EventMonitor.h @@ -26,6 +26,7 @@ class EventMonitor final private: static constexpr int EVENT_MONITOR_THREAD_EXIT_MAX_WAIT_MILLIS = 5 * 1000; + static constexpr DWORD EVENT_READ_TIMEOUT_MILLIS = 100; static constexpr int EVENT_ARRAY_SIZE = 10; const std::vector m_eventChannels; @@ -58,10 +59,12 @@ class EventMonitor final DWORD StartEventMonitor(); - static DWORD StartEventMonitorStatic( + static DWORD WINAPI StartEventMonitorStatic( _In_ LPVOID Context ); + DWORD CheckForStop() const; + std::wstring ConstructWindowsEventQuery( _In_ const std::vector& EventChannels ); @@ -75,7 +78,7 @@ class EventMonitor final ); - void EnableEventLogChannels(); + DWORD EnableEventLogChannels(); static DWORD EnableEventLogChannel( _In_ LPCWSTR ChannelPath); From ae5a80b9b23bca55f7bc16075903a6354ae36292 Mon Sep 17 00:00:00 2001 From: Putluru Sandeep Date: Sun, 25 Oct 2026 14:54:30 +0530 Subject: [PATCH 3/4] Add deterministic replay shutdown synchronization --- .../LogMonitorTests/EventMonitorTests.cpp | 50 ++++++++++++++++--- LogMonitor/LogMonitorTests/pch.h | 3 ++ LogMonitor/src/LogMonitor/EventMonitor.cpp | 45 +++++++++++++++++ LogMonitor/src/LogMonitor/EventMonitor.h | 19 +++++++ 4 files changed, 110 insertions(+), 7 deletions(-) diff --git a/LogMonitor/LogMonitorTests/EventMonitorTests.cpp b/LogMonitor/LogMonitorTests/EventMonitorTests.cpp index 1a2931f8..2814bb3e 100644 --- a/LogMonitor/LogMonitorTests/EventMonitorTests.cpp +++ b/LogMonitor/LogMonitorTests/EventMonitorTests.cpp @@ -130,16 +130,52 @@ TEST_CLASS(EventMonitorTests) std::vector eventChannels = { {L"Application", EventChannelLogLevel::Error} }; Assert::AreEqual(0, WriteEvent(EventChannelLogLevel::Error, 103, L"EventMonitor shutdown regression")); - for (int attempt = 0; attempt < 3; ++attempt) + HANDLE batchReady = CreateEvent(nullptr, TRUE, FALSE, nullptr); + HANDLE releaseBatch = CreateEvent(nullptr, TRUE, FALSE, nullptr); + HANDLE shutdownRequested = CreateEvent(nullptr, TRUE, FALSE, nullptr); + Assert::IsNotNull(batchReady); + Assert::IsNotNull(releaseBatch); + Assert::IsNotNull(shutdownRequested); + + EventMonitorReplayTestHooks hooks{ + batchReady, + releaseBatch, + shutdownRequested, + 0, + 0, + 0, + }; + auto eventMonitor = std::make_unique( + eventChannels, true, true, L"json", L"", &hooks); + + DWORD batchWait = WaitForSingleObject(batchReady, 5000); + if (batchWait != WAIT_OBJECT_0) { - auto eventMonitor = std::make_unique(eventChannels, true, true, L"json", L""); - Sleep(WAIT_TIME_EVENTMONITOR_START); - - ULONGLONG start = GetTickCount64(); + SetEvent(releaseBatch); eventMonitor.reset(); - Assert::IsTrue(GetTickCount64() - start < 5000, - L"Shutdown must cancel replay rather than exhaust the shutdown timeout."); } + Assert::AreEqual(static_cast(WAIT_OBJECT_0), batchWait, + L"Replay did not provide a batch before the test timeout."); + Assert::IsTrue(hooks.batchSize > 0, L"Replay batch must contain an unprocessed event."); + + ULONGLONG start = GetTickCount64(); + std::thread shutdownThread([&eventMonitor]() { eventMonitor.reset(); }); + DWORD shutdownWait = WaitForSingleObject(shutdownRequested, 5000); + SetEvent(releaseBatch); + shutdownThread.join(); + + Assert::AreEqual(static_cast(WAIT_OBJECT_0), shutdownWait, + L"Destructor did not request shutdown before the test timeout."); + Assert::IsTrue(GetTickCount64() - start < 5000, + L"Shutdown must cancel replay rather than exhaust the shutdown timeout."); + Assert::AreEqual(0L, hooks.processedEvents, + L"Replay processed an event after shutdown was requested."); + Assert::AreEqual(hooks.batchSize, hooks.closedEvents, + L"Shutdown must close every unprocessed event handle in the replay batch."); + + CloseHandle(shutdownRequested); + CloseHandle(releaseBatch); + CloseHandle(batchReady); } TEST_METHOD(TestShutdownDoesNotLeakHandles) diff --git a/LogMonitor/LogMonitorTests/pch.h b/LogMonitor/LogMonitorTests/pch.h index adefd0f4..9e7e37fd 100644 --- a/LogMonitor/LogMonitorTests/pch.h +++ b/LogMonitor/LogMonitorTests/pch.h @@ -12,6 +12,8 @@ #ifndef LOGMONITOR_LOGMONITORTESTS_PCH_H_ #define LOGMONITOR_LOGMONITORTESTS_PCH_H_ +#define LOGMONITOR_TESTS + // add headers that you want to pre-compile here // Preserve the Windows header dependencies and PCH order used by the test project. @@ -22,6 +24,7 @@ #include #include #include +#include #include #include #include diff --git a/LogMonitor/src/LogMonitor/EventMonitor.cpp b/LogMonitor/src/LogMonitor/EventMonitor.cpp index 9f69bcb9..df585bf0 100644 --- a/LogMonitor/src/LogMonitor/EventMonitor.cpp +++ b/LogMonitor/src/LogMonitor/EventMonitor.cpp @@ -31,12 +31,18 @@ EventMonitor::EventMonitor( _In_ bool StartAtOldestRecord, _In_ std::wstring LogFormat, _In_ std::wstring CustomLogFormat = L"" +#ifdef LOGMONITOR_TESTS + , _In_opt_ EventMonitorReplayTestHooks* ReplayTestHooks +#endif ) : m_eventChannels(EventChannels), m_eventFormatMultiLine(EventFormatMultiLine), m_startAtOldestRecord(StartAtOldestRecord), m_logFormat(LogFormat), m_customLogFormat(CustomLogFormat) +#ifdef LOGMONITOR_TESTS + , m_replayTestHooks(ReplayTestHooks) +#endif { m_stopEvent = NULL; m_eventMonitorThread = NULL; @@ -74,6 +80,12 @@ EventMonitor::~EventMonitor() } else { +#ifdef LOGMONITOR_TESTS + if (m_replayTestHooks != nullptr) + { + SetEvent(m_replayTestHooks->shutdownRequested); + } +#endif // // Wait for watch thread to exit. // @@ -409,6 +421,20 @@ EventMonitor::EnumerateResults( goto cleanup; } +#ifdef LOGMONITOR_TESTS + if (m_replayTestHooks != nullptr) + { + m_replayTestHooks->batchSize = static_cast(dwReturned); + SetEvent(m_replayTestHooks->batchReady); + if (WaitForSingleObject(m_replayTestHooks->releaseBatch, + EVENT_MONITOR_THREAD_EXIT_MAX_WAIT_MILLIS) != WAIT_OBJECT_0) + { + status = ERROR_TIMEOUT; + goto cleanup; + } + } +#endif + // // For each event, call the PrintEvent function which renders the // event for display. @@ -421,6 +447,13 @@ EventMonitor::EnumerateResults( goto cleanup; } +#ifdef LOGMONITOR_TESTS + if (m_replayTestHooks != nullptr) + { + InterlockedIncrement(&m_replayTestHooks->processedEvents); + } +#endif + status = PrintEvent(hEvents[i]); if (ERROR_SUCCESS != status) @@ -436,6 +469,12 @@ EventMonitor::EnumerateResults( EvtClose(hEvents[i]); hEvents[i] = NULL; +#ifdef LOGMONITOR_TESTS + if (m_replayTestHooks != nullptr) + { + InterlockedIncrement(&m_replayTestHooks->closedEvents); + } +#endif } } @@ -449,6 +488,12 @@ EventMonitor::EnumerateResults( if (NULL != hEvents[i]) { EvtClose(hEvents[i]); +#ifdef LOGMONITOR_TESTS + if (m_replayTestHooks != nullptr) + { + InterlockedIncrement(&m_replayTestHooks->closedEvents); + } +#endif } } diff --git a/LogMonitor/src/LogMonitor/EventMonitor.h b/LogMonitor/src/LogMonitor/EventMonitor.h index 024efc43..f268a527 100644 --- a/LogMonitor/src/LogMonitor/EventMonitor.h +++ b/LogMonitor/src/LogMonitor/EventMonitor.h @@ -7,6 +7,18 @@ #include +#ifdef LOGMONITOR_TESTS +struct EventMonitorReplayTestHooks +{ + HANDLE batchReady; + HANDLE releaseBatch; + HANDLE shutdownRequested; + LONG batchSize; + LONG processedEvents; + LONG closedEvents; +}; +#endif + class EventMonitor final { public: @@ -18,6 +30,9 @@ class EventMonitor final _In_ bool StartAtOldestRecord, _In_ std::wstring LogFormat, _In_ std::wstring CustomLogFormat +#ifdef LOGMONITOR_TESTS + , _In_opt_ EventMonitorReplayTestHooks* ReplayTestHooks = nullptr +#endif ); ~EventMonitor(); @@ -57,6 +72,10 @@ class EventMonitor final std::vector m_eventMessageBuffer; +#ifdef LOGMONITOR_TESTS + EventMonitorReplayTestHooks* m_replayTestHooks; +#endif + DWORD StartEventMonitor(); static DWORD WINAPI StartEventMonitorStatic( From 130bc46c026069b245a1d9f525361c81ffc9359c Mon Sep 17 00:00:00 2001 From: Sunny Date: Sun, 25 Oct 2026 19:09:56 +0530 Subject: [PATCH 4/4] test: assert replay barrier release --- LogMonitor/LogMonitorTests/EventMonitorTests.cpp | 3 +++ LogMonitor/src/LogMonitor/EventMonitor.cpp | 6 ++++-- LogMonitor/src/LogMonitor/EventMonitor.h | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/LogMonitor/LogMonitorTests/EventMonitorTests.cpp b/LogMonitor/LogMonitorTests/EventMonitorTests.cpp index 2814bb3e..5938734f 100644 --- a/LogMonitor/LogMonitorTests/EventMonitorTests.cpp +++ b/LogMonitor/LogMonitorTests/EventMonitorTests.cpp @@ -144,6 +144,7 @@ TEST_CLASS(EventMonitorTests) 0, 0, 0, + WAIT_FAILED, }; auto eventMonitor = std::make_unique( eventChannels, true, true, L"json", L"", &hooks); @@ -164,6 +165,8 @@ TEST_CLASS(EventMonitorTests) SetEvent(releaseBatch); shutdownThread.join(); + Assert::AreEqual(static_cast(WAIT_OBJECT_0), hooks.releaseBatchWaitResult, + L"Replay worker did not observe the batch release before exiting."); Assert::AreEqual(static_cast(WAIT_OBJECT_0), shutdownWait, L"Destructor did not request shutdown before the test timeout."); Assert::IsTrue(GetTickCount64() - start < 5000, diff --git a/LogMonitor/src/LogMonitor/EventMonitor.cpp b/LogMonitor/src/LogMonitor/EventMonitor.cpp index df585bf0..269273e5 100644 --- a/LogMonitor/src/LogMonitor/EventMonitor.cpp +++ b/LogMonitor/src/LogMonitor/EventMonitor.cpp @@ -426,8 +426,10 @@ EventMonitor::EnumerateResults( { m_replayTestHooks->batchSize = static_cast(dwReturned); SetEvent(m_replayTestHooks->batchReady); - if (WaitForSingleObject(m_replayTestHooks->releaseBatch, - EVENT_MONITOR_THREAD_EXIT_MAX_WAIT_MILLIS) != WAIT_OBJECT_0) + m_replayTestHooks->releaseBatchWaitResult = WaitForSingleObject( + m_replayTestHooks->releaseBatch, + EVENT_MONITOR_THREAD_EXIT_MAX_WAIT_MILLIS); + if (m_replayTestHooks->releaseBatchWaitResult != WAIT_OBJECT_0) { status = ERROR_TIMEOUT; goto cleanup; diff --git a/LogMonitor/src/LogMonitor/EventMonitor.h b/LogMonitor/src/LogMonitor/EventMonitor.h index f268a527..09fff3b0 100644 --- a/LogMonitor/src/LogMonitor/EventMonitor.h +++ b/LogMonitor/src/LogMonitor/EventMonitor.h @@ -16,6 +16,7 @@ struct EventMonitorReplayTestHooks LONG batchSize; LONG processedEvents; LONG closedEvents; + DWORD releaseBatchWaitResult; }; #endif