| // 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 |