// Copyright (c) 2011 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.

#include "base/synchronization/atomic_flag.h"

#include "base/bind.h"
#include "base/check_op.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/single_thread_task_runner_forward.h"
#include "base/test/gtest_util.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {

namespace {

void ExpectSetFlagDeath(AtomicFlag* flag) {
  ASSERT_TRUE(flag);
  EXPECT_DCHECK_DEATH(flag->Set());
}

// Busy waits (to explicitly avoid using synchronization constructs that would
// defeat the purpose of testing atomics) until |tested_flag| is set and then
// verifies that non-atomic |*expected_after_flag| is true and sets |*done_flag|
// before returning if it's non-null.
void BusyWaitUntilFlagIsSet(AtomicFlag* tested_flag, bool* expected_after_flag,
                            AtomicFlag* done_flag) {
  while (!tested_flag->IsSet())
    PlatformThread::YieldCurrentThread();

  EXPECT_TRUE(*expected_after_flag);
  if (done_flag)
    done_flag->Set();
}

}  // namespace

TEST(AtomicFlagTest, SimpleSingleThreadedTest) {
  AtomicFlag flag;
  ASSERT_FALSE(flag.IsSet());
  flag.Set();
  ASSERT_TRUE(flag.IsSet());
}

TEST(AtomicFlagTest, DoubleSetTest) {
  AtomicFlag flag;
  ASSERT_FALSE(flag.IsSet());
  flag.Set();
  ASSERT_TRUE(flag.IsSet());
  flag.Set();
  ASSERT_TRUE(flag.IsSet());
}

TEST(AtomicFlagTest, ReadFromDifferentThread) {
  // |tested_flag| is the one being tested below.
  AtomicFlag tested_flag;
  // |expected_after_flag| is used to confirm that sequential consistency is
  // obtained around |tested_flag|.
  bool expected_after_flag = false;
  // |reset_flag| is used to confirm the test flows as intended without using
  // synchronization constructs which would defeat the purpose of exercising
  // atomics.
  AtomicFlag reset_flag;

  Thread thread("AtomicFlagTest.ReadFromDifferentThread");
  ASSERT_TRUE(thread.Start());
  thread.task_runner()->PostTask(FROM_HERE,
                                 BindOnce(&BusyWaitUntilFlagIsSet, &tested_flag,
                                          &expected_after_flag, &reset_flag));

  // To verify that IsSet() fetches the flag's value from memory every time it
  // is called (not just the first time that it is called on a thread), sleep
  // before setting the flag.
  PlatformThread::Sleep(Milliseconds(20));

  // |expected_after_flag| is used to verify that all memory operations
  // performed before |tested_flag| is Set() are visible to threads that can see
  // IsSet().
  expected_after_flag = true;
  tested_flag.Set();

  // Sleep again to give the busy loop time to observe the flag and verify
  // expectations.
  PlatformThread::Sleep(Milliseconds(20));

  // Use |reset_flag| to confirm that the above completed (which the rest of
  // this test assumes).
  while (!reset_flag.IsSet())
    PlatformThread::YieldCurrentThread();

  tested_flag.UnsafeResetForTesting();
  EXPECT_FALSE(tested_flag.IsSet());
  expected_after_flag = false;

  // Perform the same test again after the controlled UnsafeResetForTesting(),
  // |thread| is guaranteed to be synchronized past the
  // |UnsafeResetForTesting()| call when the task runs per the implicit
  // synchronization in the post task mechanism.
  thread.task_runner()->PostTask(FROM_HERE,
                                 BindOnce(&BusyWaitUntilFlagIsSet, &tested_flag,
                                          &expected_after_flag, nullptr));

  PlatformThread::Sleep(Milliseconds(20));

  expected_after_flag = true;
  tested_flag.Set();

  // The |thread|'s destructor will block until the posted task completes, so
  // the test will time out if it fails to see the flag be set.
}

TEST(AtomicFlagTest, SetOnDifferentSequenceDeathTest) {
  // Checks that Set() can't be called from another sequence after being called
  // on this one. AtomicFlag should die on a DCHECK if Set() is called again
  // from another sequence.

  // Note: flag must be declared before the Thread so that its destructor runs
  // later. Otherwise there's a race between destructing flag and running
  // ExpectSetFlagDeath.
  AtomicFlag flag;

  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
  Thread t("AtomicFlagTest.SetOnDifferentThreadDeathTest");
  ASSERT_TRUE(t.Start());
  EXPECT_TRUE(t.WaitUntilThreadStarted());

  flag.Set();
  t.task_runner()->PostTask(FROM_HERE, BindOnce(&ExpectSetFlagDeath, &flag));
}

}  // namespace base
