blob: d42ebadc20c31dd1816b619d27f2d08227fc0d4c [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Bookmark.h"
#include <regex>
#include "TestBase.h"
#include "utils/gsl.h"
#include "wel/UniqueEvtHandle.h"
using Bookmark = org::apache::nifi::minifi::processors::Bookmark;
using unique_evt_handle = org::apache::nifi::minifi::wel::unique_evt_handle;
using IdGenerator = org::apache::nifi::minifi::utils::IdGenerator;
namespace {
const std::wstring APPLICATION_CHANNEL = L"Application";
constexpr DWORD BOOKMARK_TESTS_OPCODE = 10368; // random opcode hopefully won't clash with something important
constexpr DWORD EVT_NEXT_TIMEOUT_MS = 100;
std::unique_ptr<Bookmark> createBookmark(TestPlan &test_plan,
const std::wstring &channel,
const utils::Identifier &uuid = IdGenerator::getIdGenerator()->generate()) {
const auto state_manager = test_plan.getStateManagerProvider()->getCoreComponentStateManager(uuid);
const auto logger = test_plan.getLogger();
return utils::make_unique<Bookmark>(channel, L"*", "", uuid, false, state_manager, logger);
}
void reportEvent(const std::wstring& channel, const char* message) {
auto event_source = RegisterEventSourceW(nullptr, channel.c_str());
auto event_source_deleter = gsl::finally([&event_source](){ DeregisterEventSource(event_source); });
ReportEventA(event_source, EVENTLOG_INFORMATION_TYPE, 0,
BOOKMARK_TESTS_OPCODE, nullptr, 1, 0, &message, nullptr);
}
std::wstring bookmarkHandleAsXml(EVT_HANDLE event) {
REQUIRE(event);
constexpr std::size_t BUFFER_SIZE = 1024;
std::array<wchar_t, BUFFER_SIZE> buffer = {};
DWORD buffer_used;
DWORD property_count;
if (!EvtRender(nullptr, event, EvtRenderBookmark, buffer.size(), buffer.data(), &buffer_used, &property_count)) {
FAIL("EvtRender() failed; error code: " << GetLastError());
}
return std::wstring{buffer.data()};
}
std::wstring bookmarkAsXml(const std::unique_ptr<Bookmark>& bookmark) {
REQUIRE(bookmark);
REQUIRE(*bookmark);
return bookmarkHandleAsXml(bookmark->getBookmarkHandleFromXML());
}
unique_evt_handle queryEvents() {
std::wstring query = L"Event/System/EventID=" + std::to_wstring(BOOKMARK_TESTS_OPCODE);
unique_evt_handle results{EvtQuery(NULL, APPLICATION_CHANNEL.c_str(), query.c_str(), EvtQueryChannelPath | EvtQueryReverseDirection)};
if (!results) {
FAIL("EvtQuery() failed; error code: " << GetLastError());
}
return results;
}
unique_evt_handle getFirstEventFromResults(const unique_evt_handle& results) {
REQUIRE(results);
EVT_HANDLE event_raw_handle = 0;
DWORD num_results_found = 0;
if (!EvtNext(results.get(), 1, &event_raw_handle, EVT_NEXT_TIMEOUT_MS, 0, &num_results_found)) {
FAIL("EvtNext() failed; error code: " << GetLastError());
}
unique_evt_handle event{event_raw_handle};
REQUIRE(event);
REQUIRE(num_results_found == 1);
return event;
}
} // namespace
TEST_CASE("Bookmark constructor works", "[create]") {
TestController test_controller;
std::shared_ptr<TestPlan> test_plan = test_controller.createPlan();
LogTestController::getInstance().setTrace<TestPlan>();
reportEvent(APPLICATION_CHANNEL, "Publish an event to make sure the event log is not empty");
std::unique_ptr<Bookmark> bookmark = createBookmark(*test_plan, APPLICATION_CHANNEL);
REQUIRE(bookmark);
REQUIRE(*bookmark);
std::wregex pattern{L"<BookmarkList Direction='backward'>\r\n"
L" <Bookmark Channel='Application' RecordId='\\d+' IsCurrent='true'/>\r\n"
L"</BookmarkList>"};
REQUIRE(std::regex_match(bookmarkAsXml(bookmark), pattern));
}
TEST_CASE("Bookmark is restored from the state", "[create][state]") {
TestController test_controller;
std::shared_ptr<TestPlan> test_plan = test_controller.createPlan();
LogTestController::getInstance().setTrace<TestPlan>();
utils::Identifier uuid = IdGenerator::getIdGenerator()->generate();
std::unique_ptr<Bookmark> bookmark_before = createBookmark(*test_plan, APPLICATION_CHANNEL, uuid);
std::wstring bookmark_xml_before = bookmarkAsXml(bookmark_before);
reportEvent(APPLICATION_CHANNEL, "Something interesting happened");
// same uuid, so the state manager is the same as before
std::unique_ptr<Bookmark> bookmark_after = createBookmark(*test_plan, APPLICATION_CHANNEL, uuid);
std::wstring bookmark_xml_after = bookmarkAsXml(bookmark_after);
REQUIRE(bookmark_xml_before == bookmark_xml_after);
}
TEST_CASE("Bookmark created after a new event is different", "[create][state]") {
TestController test_controller;
std::shared_ptr<TestPlan> test_plan = test_controller.createPlan();
LogTestController::getInstance().setTrace<TestPlan>();
utils::Identifier uuid_one = IdGenerator::getIdGenerator()->generate();
std::unique_ptr<Bookmark> bookmark_before = createBookmark(*test_plan, APPLICATION_CHANNEL, uuid_one);
reportEvent(APPLICATION_CHANNEL, "Something interesting happened");
utils::Identifier uuid_two = IdGenerator::getIdGenerator()->generate();
// different uuid, so we get a new, empty, state manager
std::unique_ptr<Bookmark> bookmark_after = createBookmark(*test_plan, APPLICATION_CHANNEL, uuid_two);
REQUIRE(bookmarkAsXml(bookmark_before) != bookmarkAsXml(bookmark_after));
}
TEST_CASE("Bookmark::getBookmarkHandleFromXML() returns the same event from a copy", "[handle_from_xml]") {
TestController test_controller;
std::shared_ptr<TestPlan> test_plan = test_controller.createPlan();
LogTestController::getInstance().setTrace<TestPlan>();
utils::Identifier uuid = IdGenerator::getIdGenerator()->generate();
std::unique_ptr<Bookmark> bookmark_one = createBookmark(*test_plan, APPLICATION_CHANNEL, uuid);
std::unique_ptr<Bookmark> bookmark_two = createBookmark(*test_plan, APPLICATION_CHANNEL, uuid);
EVT_HANDLE bookmark_handle_one = bookmark_one->getBookmarkHandleFromXML();
EVT_HANDLE bookmark_handle_two = bookmark_two->getBookmarkHandleFromXML();
REQUIRE(bookmarkHandleAsXml(bookmark_handle_one) == bookmarkHandleAsXml(bookmark_handle_two));
}
TEST_CASE("Bookmark::getBookmarkHandleFromXML() returns a different event after the XML is changed", "[handle_from_xml]") {
TestController test_controller;
std::shared_ptr<TestPlan> test_plan = test_controller.createPlan();
LogTestController::getInstance().setTrace<TestPlan>();
GIVEN("We have two different bookmarks") {
std::unique_ptr<Bookmark> bookmark_one = createBookmark(*test_plan, APPLICATION_CHANNEL);
std::wstring bookmark_one_xml = bookmarkAsXml(bookmark_one);
reportEvent(APPLICATION_CHANNEL, "Something interesting happened");
std::unique_ptr<Bookmark> bookmark_two = createBookmark(*test_plan, APPLICATION_CHANNEL);
std::wstring bookmark_two_xml = bookmarkAsXml(bookmark_two);
REQUIRE(bookmark_one_xml != bookmark_two_xml);
WHEN("we set the XML of the first bookmark equal to the XML of the second bookmark") {
bookmark_one->saveBookmarkXml(bookmark_two_xml);
THEN("getBookmarkHandleFromXML() will return the updated handle") {
EVT_HANDLE bookmark_one_handle = bookmark_one->getBookmarkHandleFromXML();
REQUIRE(bookmarkHandleAsXml(bookmark_one_handle) == bookmark_two_xml);
}
THEN("... and the two bookmarks are now equal.") {
REQUIRE(bookmarkAsXml(bookmark_one) == bookmarkAsXml(bookmark_two));
}
}
}
}
TEST_CASE("Bookmark::getNewBookmarkXml() updates the bookmark", "[add_event]") {
TestController test_controller;
std::shared_ptr<TestPlan> test_plan = test_controller.createPlan();
LogTestController::getInstance().setTrace<TestPlan>();
std::unique_ptr<Bookmark> bookmark = createBookmark(*test_plan, APPLICATION_CHANNEL);
std::wstring bookmark_xml_before = bookmarkAsXml(bookmark);
reportEvent(APPLICATION_CHANNEL, "Something interesting happened");
unique_evt_handle results = queryEvents();
unique_evt_handle event = getFirstEventFromResults(results);
std::wstring bookmark_xml_after;
REQUIRE(bookmark->getNewBookmarkXml(event.get(), bookmark_xml_after));
REQUIRE(bookmark_xml_before != bookmark_xml_after);
}
TEST_CASE("Bookmark::saveBookmarkXml() updates the bookmark and saves it to the state manager", "[save_bookmark][state]") {
TestController test_controller;
std::shared_ptr<TestPlan> test_plan = test_controller.createPlan();
LogTestController::getInstance().setTrace<TestPlan>();
GIVEN("We have two different bookmarks with two different state managers") {
utils::Identifier uuid_one = IdGenerator::getIdGenerator()->generate();
std::unique_ptr<Bookmark> bookmark_one = createBookmark(*test_plan, APPLICATION_CHANNEL, uuid_one);
reportEvent(APPLICATION_CHANNEL, "Something interesting happened");
utils::Identifier uuid_two = IdGenerator::getIdGenerator()->generate();
std::unique_ptr<Bookmark> bookmark_two = createBookmark(*test_plan, APPLICATION_CHANNEL, uuid_two);
REQUIRE(bookmarkAsXml(bookmark_one) != bookmarkAsXml(bookmark_two));
WHEN("we create a new bookmark with state manager one") {
std::unique_ptr<Bookmark> bookmark_one_same = createBookmark(*test_plan, APPLICATION_CHANNEL, uuid_one);
THEN("it will be the same as bookmark one.") {
REQUIRE(bookmarkAsXml(bookmark_one_same) == bookmarkAsXml(bookmark_one));
}
}
WHEN("saveBookmarkXml() is called on bookmark one with the XML of bookmark two, "
"and then we create a new bookmark with state manager one") {
bookmark_one->saveBookmarkXml(bookmarkAsXml(bookmark_two));
std::unique_ptr<Bookmark> bookmark_one_different = createBookmark(*test_plan, APPLICATION_CHANNEL, uuid_one);
THEN("it will be the same as bookmark two.") {
REQUIRE(bookmarkAsXml(bookmark_one_different) == bookmarkAsXml(bookmark_two));
}
}
}
}