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

#include "base/task/sequence_manager/work_tracker.h"

#include <optional>

#include "base/test/bind.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base::sequence_manager::internal {

// Verify that no sync work authorization is granted unless allowed by
// `SetRunTaskSynchronouslyAllowed()`.
TEST(SequenceManagerWorkTrackerTest, SetRunTaskSynchronouslyAllowed) {
  WorkTracker tracker;
  EXPECT_FALSE(tracker.TryAcquireSyncWorkAuthorization().IsValid());

  tracker.OnBeginWork();
  tracker.OnIdle();
  EXPECT_FALSE(tracker.TryAcquireSyncWorkAuthorization().IsValid());

  tracker.WillRequestReloadImmediateWorkQueue();
  tracker.WillReloadImmediateWorkQueues();
  tracker.OnIdle();
  EXPECT_FALSE(tracker.TryAcquireSyncWorkAuthorization().IsValid());

  tracker.SetRunTaskSynchronouslyAllowed(true);
  EXPECT_TRUE(tracker.TryAcquireSyncWorkAuthorization().IsValid());
  tracker.SetRunTaskSynchronouslyAllowed(false);
  EXPECT_FALSE(tracker.TryAcquireSyncWorkAuthorization().IsValid());
}

// Verify that `SetRunTaskSynchronouslyAllowed(false)` blocks until there is no
// valid sync work authorization.
TEST(SequenceManagerWorkTrackerTest, SetRunTaskSynchronouslyAllowedBlocks) {
  WorkTracker tracker;
  tracker.SetRunTaskSynchronouslyAllowed(true);

  WaitableEvent did_acquire_sync_work_auth;
  bool will_release_sync_work_auth = false;
  Thread other_thread("OtherThread");
  other_thread.Start();
  other_thread.task_runner()->PostTask(
      FROM_HERE, BindLambdaForTesting([&]() {
        std::optional<SyncWorkAuthorization> auth =
            tracker.TryAcquireSyncWorkAuthorization();
        EXPECT_TRUE(auth->IsValid());
        did_acquire_sync_work_auth.Signal();
        PlatformThread::Sleep(TestTimeouts::tiny_timeout());
        will_release_sync_work_auth = true;
        auth.reset();
      }));

  did_acquire_sync_work_auth.Wait();

  tracker.SetRunTaskSynchronouslyAllowed(false);
  // `will_release_sync_work_auth` must be true (with no data race detected by
  // TSAN) when the call above returns.
  EXPECT_TRUE(will_release_sync_work_auth);

  other_thread.FlushForTesting();
}

// Verify that after `WillRequestReloadImmediateWorkQueue()`,
// `WillReloadImmediateWorkQueues()` and `OnIdle()` must be called in sequence
// for a sync work authorization to be granted.
TEST(SequenceManagerWorkTrackerTest, WillRequestReloadImmediateWorkQueue) {
  WorkTracker tracker;
  tracker.SetRunTaskSynchronouslyAllowed(true);
  EXPECT_TRUE(tracker.TryAcquireSyncWorkAuthorization().IsValid());

  tracker.WillRequestReloadImmediateWorkQueue();
  EXPECT_FALSE(tracker.TryAcquireSyncWorkAuthorization().IsValid());
  tracker.WillReloadImmediateWorkQueues();
  EXPECT_FALSE(tracker.TryAcquireSyncWorkAuthorization().IsValid());
  tracker.OnIdle();
  EXPECT_TRUE(tracker.TryAcquireSyncWorkAuthorization().IsValid());

  tracker.WillRequestReloadImmediateWorkQueue();
  EXPECT_FALSE(tracker.TryAcquireSyncWorkAuthorization().IsValid());
  // `OnIdle()` without `WillReloadImmediateWorkQueues()` is not sufficient for
  // a sync work authorization to be granted.
  tracker.OnIdle();
  EXPECT_FALSE(tracker.TryAcquireSyncWorkAuthorization().IsValid());
}

// Verify that after `OnBeginWork()`, `OnIdle()` must be called for a sync
// work authorization to be granted.
TEST(SequenceManagerWorkTrackerTest, OnBeginWork) {
  WorkTracker tracker;
  tracker.SetRunTaskSynchronouslyAllowed(true);
  EXPECT_TRUE(tracker.TryAcquireSyncWorkAuthorization().IsValid());

  tracker.OnBeginWork();
  EXPECT_FALSE(tracker.TryAcquireSyncWorkAuthorization().IsValid());
  tracker.OnIdle();
  EXPECT_TRUE(tracker.TryAcquireSyncWorkAuthorization().IsValid());
}

// Verify that its not possible to simultaneously acquire two sync work
// authorizations.
TEST(SequenceManagerWorkTrackerTest, TwoSyncWorkAuthorizations) {
  WorkTracker tracker;
  tracker.SetRunTaskSynchronouslyAllowed(true);

  std::optional<SyncWorkAuthorization> first =
      tracker.TryAcquireSyncWorkAuthorization();
  EXPECT_TRUE(first->IsValid());
  SyncWorkAuthorization second = tracker.TryAcquireSyncWorkAuthorization();
  EXPECT_FALSE(second.IsValid());

  first.reset();
  // `second` is invalid so doesn't prevent acquiring another sync work
  // authorization.
  EXPECT_TRUE(tracker.TryAcquireSyncWorkAuthorization().IsValid());
}

// Verify that `OnBeginWork()` blocks until there is no valid sync work
// authorization.
TEST(SequenceManagerWorkTrackerTest, OnBeginWorkBlocks) {
  WorkTracker tracker;
  tracker.SetRunTaskSynchronouslyAllowed(true);

  WaitableEvent did_acquire_sync_work_auth;
  bool will_release_sync_work_auth = false;
  Thread other_thread("OtherThread");
  other_thread.Start();
  other_thread.task_runner()->PostTask(
      FROM_HERE, BindLambdaForTesting([&]() {
        std::optional<SyncWorkAuthorization> auth =
            tracker.TryAcquireSyncWorkAuthorization();
        EXPECT_TRUE(auth->IsValid());
        did_acquire_sync_work_auth.Signal();
        PlatformThread::Sleep(TestTimeouts::tiny_timeout());
        will_release_sync_work_auth = true;
        auth.reset();
      }));

  did_acquire_sync_work_auth.Wait();

  tracker.OnBeginWork();
  // `will_release_sync_work_auth` must be true (with no data race detected by
  // TSAN) when the call above returns.
  EXPECT_TRUE(will_release_sync_work_auth);

  other_thread.FlushForTesting();
}

}  // namespace base::sequence_manager::internal
