|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  | // | 
|  | // Unit tests for event trace consumer base class. | 
|  | #include "base/win/event_trace_consumer.h" | 
|  |  | 
|  | #include <list> | 
|  |  | 
|  | #include <objbase.h> | 
|  |  | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/files/scoped_temp_dir.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/process/process_handle.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/win/event_trace_controller.h" | 
|  | #include "base/win/event_trace_provider.h" | 
|  | #include "base/win/scoped_handle.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | #include <initguid.h>  // NOLINT - has to be last | 
|  |  | 
|  | namespace base { | 
|  | namespace win { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | typedef std::list<EVENT_TRACE> EventQueue; | 
|  |  | 
|  | class TestConsumer: public EtwTraceConsumerBase<TestConsumer> { | 
|  | public: | 
|  | TestConsumer() { | 
|  | sank_event_.Set(::CreateEvent(NULL, TRUE, FALSE, NULL)); | 
|  | ClearQueue(); | 
|  | } | 
|  |  | 
|  | ~TestConsumer() { | 
|  | ClearQueue(); | 
|  | sank_event_.Close(); | 
|  | } | 
|  |  | 
|  | void ClearQueue() { | 
|  | for (EventQueue::const_iterator it(events_.begin()), end(events_.end()); | 
|  | it != end; ++it) { | 
|  | delete[] reinterpret_cast<char*>(it->MofData); | 
|  | } | 
|  |  | 
|  | events_.clear(); | 
|  | } | 
|  |  | 
|  | static void EnqueueEvent(EVENT_TRACE* event) { | 
|  | events_.push_back(*event); | 
|  | EVENT_TRACE& back = events_.back(); | 
|  |  | 
|  | if (event->MofData != NULL && event->MofLength != 0) { | 
|  | back.MofData = new char[event->MofLength]; | 
|  | memcpy(back.MofData, event->MofData, event->MofLength); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void ProcessEvent(EVENT_TRACE* event) { | 
|  | EnqueueEvent(event); | 
|  | ::SetEvent(sank_event_.Get()); | 
|  | } | 
|  |  | 
|  | static ScopedHandle sank_event_; | 
|  | static EventQueue events_; | 
|  |  | 
|  | private: | 
|  | DISALLOW_COPY_AND_ASSIGN(TestConsumer); | 
|  | }; | 
|  |  | 
|  | ScopedHandle TestConsumer::sank_event_; | 
|  | EventQueue TestConsumer::events_; | 
|  |  | 
|  | class EtwTraceConsumerBaseTest: public testing::Test { | 
|  | public: | 
|  | EtwTraceConsumerBaseTest() | 
|  | : session_name_(StringPrintf(L"TestSession-%d", GetCurrentProcId())) { | 
|  | } | 
|  |  | 
|  | void SetUp() override { | 
|  | // Cleanup any potentially dangling sessions. | 
|  | EtwTraceProperties ignore; | 
|  | EtwTraceController::Stop(session_name_.c_str(), &ignore); | 
|  |  | 
|  | // Allocate a new GUID for each provider test. | 
|  | ASSERT_HRESULT_SUCCEEDED(::CoCreateGuid(&test_provider_)); | 
|  | } | 
|  |  | 
|  | void TearDown() override { | 
|  | // Cleanup any potentially dangling sessions. | 
|  | EtwTraceProperties ignore; | 
|  | EtwTraceController::Stop(session_name_.c_str(), &ignore); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | GUID test_provider_; | 
|  | std::wstring session_name_; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | TEST_F(EtwTraceConsumerBaseTest, Initialize) { | 
|  | TestConsumer consumer_; | 
|  | } | 
|  |  | 
|  | TEST_F(EtwTraceConsumerBaseTest, OpenRealtimeSucceedsWhenNoSession) { | 
|  | TestConsumer consumer_; | 
|  | ASSERT_HRESULT_SUCCEEDED( | 
|  | consumer_.OpenRealtimeSession(session_name_.c_str())); | 
|  | } | 
|  |  | 
|  | TEST_F(EtwTraceConsumerBaseTest, ConsumerImmediateFailureWhenNoSession) { | 
|  | TestConsumer consumer_; | 
|  | ASSERT_HRESULT_SUCCEEDED( | 
|  | consumer_.OpenRealtimeSession(session_name_.c_str())); | 
|  | ASSERT_HRESULT_FAILED(consumer_.Consume()); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class EtwTraceConsumerRealtimeTest: public EtwTraceConsumerBaseTest { | 
|  | public: | 
|  | void SetUp() override { | 
|  | EtwTraceConsumerBaseTest::SetUp(); | 
|  | ASSERT_HRESULT_SUCCEEDED( | 
|  | consumer_.OpenRealtimeSession(session_name_.c_str())); | 
|  | } | 
|  |  | 
|  | void TearDown() override { | 
|  | consumer_.Close(); | 
|  | EtwTraceConsumerBaseTest::TearDown(); | 
|  | } | 
|  |  | 
|  | DWORD ConsumerThread() { | 
|  | ::SetEvent(consumer_ready_.Get()); | 
|  | return consumer_.Consume(); | 
|  | } | 
|  |  | 
|  | static DWORD WINAPI ConsumerThreadMainProc(void* arg) { | 
|  | return reinterpret_cast<EtwTraceConsumerRealtimeTest*>(arg)-> | 
|  | ConsumerThread(); | 
|  | } | 
|  |  | 
|  | HRESULT StartConsumerThread() { | 
|  | consumer_ready_.Set(::CreateEvent(NULL, TRUE, FALSE, NULL)); | 
|  | EXPECT_TRUE(consumer_ready_.IsValid()); | 
|  | consumer_thread_.Set(::CreateThread(NULL, 0, ConsumerThreadMainProc, this, | 
|  | 0, NULL)); | 
|  | if (consumer_thread_.Get() == NULL) | 
|  | return HRESULT_FROM_WIN32(::GetLastError()); | 
|  |  | 
|  | HANDLE events[] = { consumer_ready_.Get(), consumer_thread_.Get() }; | 
|  | DWORD result = ::WaitForMultipleObjects(arraysize(events), events, | 
|  | FALSE, INFINITE); | 
|  | switch (result) { | 
|  | case WAIT_OBJECT_0: | 
|  | // The event was set, the consumer_ is ready. | 
|  | return S_OK; | 
|  | case WAIT_OBJECT_0 + 1: { | 
|  | // The thread finished. This may race with the event, so check | 
|  | // explicitly for the event here, before concluding there's trouble. | 
|  | if (::WaitForSingleObject(consumer_ready_.Get(), 0) == WAIT_OBJECT_0) | 
|  | return S_OK; | 
|  | DWORD exit_code = 0; | 
|  | if (::GetExitCodeThread(consumer_thread_.Get(), &exit_code)) | 
|  | return exit_code; | 
|  | return HRESULT_FROM_WIN32(::GetLastError()); | 
|  | } | 
|  | default: | 
|  | return E_UNEXPECTED; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Waits for consumer_ thread to exit, and returns its exit code. | 
|  | HRESULT JoinConsumerThread() { | 
|  | if (::WaitForSingleObject(consumer_thread_.Get(), INFINITE) != | 
|  | WAIT_OBJECT_0) { | 
|  | return HRESULT_FROM_WIN32(::GetLastError()); | 
|  | } | 
|  |  | 
|  | DWORD exit_code = 0; | 
|  | if (::GetExitCodeThread(consumer_thread_.Get(), &exit_code)) | 
|  | return exit_code; | 
|  |  | 
|  | return HRESULT_FROM_WIN32(::GetLastError()); | 
|  | } | 
|  |  | 
|  | TestConsumer consumer_; | 
|  | ScopedHandle consumer_ready_; | 
|  | ScopedHandle consumer_thread_; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | TEST_F(EtwTraceConsumerRealtimeTest, ConsumerReturnsWhenSessionClosed) { | 
|  | EtwTraceController controller; | 
|  | if (controller.StartRealtimeSession(session_name_.c_str(), 100 * 1024) == | 
|  | E_ACCESSDENIED) { | 
|  | VLOG(1) << "You must be an administrator to run this test on Vista"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Start the consumer_. | 
|  | ASSERT_HRESULT_SUCCEEDED(StartConsumerThread()); | 
|  |  | 
|  | // Wait around for the consumer_ thread a bit. | 
|  | ASSERT_EQ(static_cast<DWORD>(WAIT_TIMEOUT), | 
|  | ::WaitForSingleObject(consumer_thread_.Get(), 50)); | 
|  | ASSERT_HRESULT_SUCCEEDED(controller.Stop(NULL)); | 
|  |  | 
|  | // The consumer_ returns success on session stop. | 
|  | ASSERT_HRESULT_SUCCEEDED(JoinConsumerThread()); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // {57E47923-A549-476f-86CA-503D57F59E62} | 
|  | DEFINE_GUID( | 
|  | kTestEventType, | 
|  | 0x57e47923, 0xa549, 0x476f, 0x86, 0xca, 0x50, 0x3d, 0x57, 0xf5, 0x9e, 0x62); | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | TEST_F(EtwTraceConsumerRealtimeTest, ConsumeEvent) { | 
|  | EtwTraceController controller; | 
|  | if (controller.StartRealtimeSession(session_name_.c_str(), 100 * 1024) == | 
|  | E_ACCESSDENIED) { | 
|  | VLOG(1) << "You must be an administrator to run this test on Vista"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | ASSERT_HRESULT_SUCCEEDED(controller.EnableProvider( | 
|  | test_provider_, TRACE_LEVEL_VERBOSE, 0xFFFFFFFF)); | 
|  |  | 
|  | EtwTraceProvider provider(test_provider_); | 
|  | ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), provider.Register()); | 
|  |  | 
|  | // Start the consumer_. | 
|  | ASSERT_HRESULT_SUCCEEDED(StartConsumerThread()); | 
|  | ASSERT_EQ(0u, TestConsumer::events_.size()); | 
|  |  | 
|  | EtwMofEvent<1> event(kTestEventType, 1, TRACE_LEVEL_ERROR); | 
|  | EXPECT_EQ(static_cast<DWORD>(ERROR_SUCCESS), provider.Log(&event.header)); | 
|  | EXPECT_EQ(WAIT_OBJECT_0, | 
|  | ::WaitForSingleObject(TestConsumer::sank_event_.Get(), INFINITE)); | 
|  | ASSERT_HRESULT_SUCCEEDED(controller.Stop(NULL)); | 
|  | ASSERT_HRESULT_SUCCEEDED(JoinConsumerThread()); | 
|  | ASSERT_NE(0u, TestConsumer::events_.size()); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // We run events through a file session to assert that | 
|  | // the content comes through. | 
|  | class EtwTraceConsumerDataTest: public EtwTraceConsumerBaseTest { | 
|  | public: | 
|  | EtwTraceConsumerDataTest() { | 
|  | } | 
|  |  | 
|  | void SetUp() override { | 
|  | EtwTraceConsumerBaseTest::SetUp(); | 
|  |  | 
|  | EtwTraceProperties prop; | 
|  | EtwTraceController::Stop(session_name_.c_str(), &prop); | 
|  |  | 
|  | // Create a temp dir for this test. | 
|  | ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); | 
|  | // Construct a temp file name in our dir. | 
|  | temp_file_ = temp_dir_.GetPath().Append(L"test.etl"); | 
|  | } | 
|  |  | 
|  | void TearDown() override { | 
|  | EXPECT_TRUE(base::DeleteFile(temp_file_, false)); | 
|  |  | 
|  | EtwTraceConsumerBaseTest::TearDown(); | 
|  | } | 
|  |  | 
|  | HRESULT LogEventToTempSession(PEVENT_TRACE_HEADER header) { | 
|  | EtwTraceController controller; | 
|  |  | 
|  | // Set up a file session. | 
|  | HRESULT hr = controller.StartFileSession(session_name_.c_str(), | 
|  | temp_file_.value().c_str()); | 
|  | if (FAILED(hr)) | 
|  | return hr; | 
|  |  | 
|  | // Enable our provider. | 
|  | EXPECT_HRESULT_SUCCEEDED(controller.EnableProvider( | 
|  | test_provider_, TRACE_LEVEL_VERBOSE, 0xFFFFFFFF)); | 
|  |  | 
|  | EtwTraceProvider provider(test_provider_); | 
|  | // Then register our provider, means we get a session handle immediately. | 
|  | EXPECT_EQ(static_cast<DWORD>(ERROR_SUCCESS), provider.Register()); | 
|  | // Trace the event, it goes to the temp file. | 
|  | EXPECT_EQ(static_cast<DWORD>(ERROR_SUCCESS), provider.Log(header)); | 
|  | EXPECT_HRESULT_SUCCEEDED(controller.DisableProvider(test_provider_)); | 
|  | EXPECT_HRESULT_SUCCEEDED(provider.Unregister()); | 
|  | EXPECT_HRESULT_SUCCEEDED(controller.Flush(NULL)); | 
|  | EXPECT_HRESULT_SUCCEEDED(controller.Stop(NULL)); | 
|  |  | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | HRESULT ConsumeEventFromTempSession() { | 
|  | // Now consume the event(s). | 
|  | TestConsumer consumer_; | 
|  | HRESULT hr = consumer_.OpenFileSession(temp_file_.value().c_str()); | 
|  | if (SUCCEEDED(hr)) | 
|  | hr = consumer_.Consume(); | 
|  | consumer_.Close(); | 
|  | // And nab the result. | 
|  | events_.swap(TestConsumer::events_); | 
|  | return hr; | 
|  | } | 
|  |  | 
|  | HRESULT RoundTripEvent(PEVENT_TRACE_HEADER header, PEVENT_TRACE* trace) { | 
|  | base::DeleteFile(temp_file_, false); | 
|  |  | 
|  | HRESULT hr = LogEventToTempSession(header); | 
|  | if (SUCCEEDED(hr)) | 
|  | hr = ConsumeEventFromTempSession(); | 
|  |  | 
|  | if (FAILED(hr)) | 
|  | return hr; | 
|  |  | 
|  | // We should now have the event in the queue. | 
|  | if (events_.empty()) | 
|  | return E_FAIL; | 
|  |  | 
|  | *trace = &events_.back(); | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | EventQueue events_; | 
|  | ScopedTempDir temp_dir_; | 
|  | FilePath temp_file_; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  |  | 
|  | TEST_F(EtwTraceConsumerDataTest, RoundTrip) { | 
|  | EtwMofEvent<1> event(kTestEventType, 1, TRACE_LEVEL_ERROR); | 
|  |  | 
|  | static const char kData[] = "This is but test data"; | 
|  | event.fields[0].DataPtr = reinterpret_cast<ULONG64>(kData); | 
|  | event.fields[0].Length = sizeof(kData); | 
|  |  | 
|  | PEVENT_TRACE trace = NULL; | 
|  | HRESULT hr = RoundTripEvent(&event.header, &trace); | 
|  | if (hr == E_ACCESSDENIED) { | 
|  | VLOG(1) << "You must be an administrator to run this test on Vista"; | 
|  | return; | 
|  | } | 
|  | ASSERT_HRESULT_SUCCEEDED(hr) << "RoundTripEvent failed"; | 
|  | ASSERT_TRUE(trace != NULL); | 
|  | ASSERT_EQ(sizeof(kData), trace->MofLength); | 
|  | ASSERT_STREQ(kData, reinterpret_cast<const char*>(trace->MofData)); | 
|  | } | 
|  |  | 
|  | }  // namespace win | 
|  | }  // namespace base |