chromium/services/tracing/public/cpp/stack_sampling/loader_lock_sampler_win_unittest.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "services/tracing/public/cpp/stack_sampling/loader_lock_sampler_win.h"

#include <string>
#include <utility>

#include "base/files/file_path.h"
#include "base/scoped_native_library.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/test/test_waitable_event.h"
#include "base/win/scoped_handle.h"
#include "services/tracing/public/cpp/stack_sampling/loader_lock_sampler_test_strings.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace tracing {

namespace {

class TestNamedWaitableEvent : public base::TestWaitableEvent {
 public:
  TestNamedWaitableEvent(const wchar_t* name)
      : TestWaitableEvent(
            base::win::ScopedHandle(::CreateEvent(nullptr,
                                                  /*bManualReset=*/TRUE,
                                                  /*bInitialState=*/FALSE,
                                                  name))) {}
};

}  // namespace

TEST(LoaderLockSamplerTest, LockNotHeld) {
  ProbingLoaderLockSampler sampler;
  EXPECT_FALSE(sampler.IsLoaderLockHeld());
}

TEST(LoaderLockSamplerTest, LockHeldByOtherThread) {
  ProbingLoaderLockSampler sampler;

  base::test::TaskEnvironment task_environment{
      base::test::TaskEnvironment::ThreadPoolCOMEnvironment::NONE};

  // Creating TaskEnvironment initializes the thread pool, which takes the
  // loader lock while creating background threads. Wait until it's released.
  while (sampler.IsLoaderLockHeld()) {
    base::TestWaitableEvent delay_event;
    delay_event.TimedWait(TestTimeouts::tiny_timeout());
  }

  // Create events with fixed names that can be accessed from the helper DLL.
  TestNamedWaitableEvent wait_for_loader_lock_event(
      loader_lock_sampler_test::kWaitForLockEventName);
  TestNamedWaitableEvent drop_loader_lock_event(
      loader_lock_sampler_test::kDropLockEventName);

  base::TestWaitableEvent dll_done_event;
  base::ThreadPool::PostTask(
      FROM_HERE, {base::MayBlock()},
      base::BindLambdaForTesting([&dll_done_event] {
        // This function will block until DllMain returns, or the load fails.
        // The helper DLL will signal |wait_for_loader_lock_event| as soon as it
        // enters DllMain, while it holds the loader lock. Then it will block
        // with the loader lock held until |drop_loader_lock_event| is
        // signalled.
        base::ScopedNativeLibrary dll(base::FilePath(
            FILE_PATH_LITERAL("loader_lock_sampler_test_dll.dll")));

        // If the DLL did not load, |wait_for_loader_lock_event| will not be
        // signalled and the TimedWait below will return false (timeout).
        ASSERT_TRUE(dll.is_valid())
            << "ScopedNativeLibrary error " << dll.GetError()->ToString();

        dll_done_event.Signal();
      }));

  // Wait for the background thread to take the loader lock.
  ASSERT_TRUE(
      wait_for_loader_lock_event.TimedWait(TestTimeouts::action_timeout()));

  EXPECT_TRUE(sampler.IsLoaderLockHeld());

  // Tell the DLL to drop the loader lock and exit from DllMain.
  drop_loader_lock_event.Signal();

  // Make sure the DllMain has exited.
  ASSERT_TRUE(dll_done_event.TimedWait(TestTimeouts::action_timeout()));

  // It takes a moment for the lock to be released.
  base::TestWaitableEvent delay_event;
  delay_event.TimedWait(TestTimeouts::tiny_timeout());

  EXPECT_FALSE(sampler.IsLoaderLockHeld());
}

}  // namespace tracing