// Copyright 2017 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/threading/scoped_blocking_call.h"

#include <memory>

#include "base/macros.h"
#include "base/test/gtest_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {

namespace {

class MockBlockingObserver : public internal::BlockingObserver {
 public:
  MockBlockingObserver() = default;

  MOCK_METHOD1(BlockingStarted, void(BlockingType));
  MOCK_METHOD0(BlockingTypeUpgraded, void());
  MOCK_METHOD0(BlockingEnded, void());

 private:
  DISALLOW_COPY_AND_ASSIGN(MockBlockingObserver);
};

class ScopedBlockingCallTest : public testing::Test {
 protected:
  ScopedBlockingCallTest() {
    internal::SetBlockingObserverForCurrentThread(&observer_);
  }

  ~ScopedBlockingCallTest() override {
    internal::ClearBlockingObserverForTesting();
  }

  testing::StrictMock<MockBlockingObserver> observer_;

 private:
  DISALLOW_COPY_AND_ASSIGN(ScopedBlockingCallTest);
};

}  // namespace

TEST_F(ScopedBlockingCallTest, MayBlock) {
  EXPECT_CALL(observer_, BlockingStarted(BlockingType::MAY_BLOCK));
  ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
  testing::Mock::VerifyAndClear(&observer_);
  EXPECT_CALL(observer_, BlockingEnded());
}

TEST_F(ScopedBlockingCallTest, WillBlock) {
  EXPECT_CALL(observer_, BlockingStarted(BlockingType::WILL_BLOCK));
  ScopedBlockingCall scoped_blocking_call(BlockingType::WILL_BLOCK);
  testing::Mock::VerifyAndClear(&observer_);
  EXPECT_CALL(observer_, BlockingEnded());
}

TEST_F(ScopedBlockingCallTest, MayBlockWillBlock) {
  EXPECT_CALL(observer_, BlockingStarted(BlockingType::MAY_BLOCK));
  ScopedBlockingCall scoped_blocking_call_a(BlockingType::MAY_BLOCK);
  testing::Mock::VerifyAndClear(&observer_);

  {
    EXPECT_CALL(observer_, BlockingTypeUpgraded());
    ScopedBlockingCall scoped_blocking_call_b(BlockingType::WILL_BLOCK);
    testing::Mock::VerifyAndClear(&observer_);
  }

  EXPECT_CALL(observer_, BlockingEnded());
}

TEST_F(ScopedBlockingCallTest, WillBlockMayBlock) {
  EXPECT_CALL(observer_, BlockingStarted(BlockingType::WILL_BLOCK));
  ScopedBlockingCall scoped_blocking_call_a(BlockingType::WILL_BLOCK);
  testing::Mock::VerifyAndClear(&observer_);

  { ScopedBlockingCall scoped_blocking_call_b(BlockingType::MAY_BLOCK); }

  EXPECT_CALL(observer_, BlockingEnded());
}

TEST_F(ScopedBlockingCallTest, MayBlockMayBlock) {
  EXPECT_CALL(observer_, BlockingStarted(BlockingType::MAY_BLOCK));
  ScopedBlockingCall scoped_blocking_call_a(BlockingType::MAY_BLOCK);
  testing::Mock::VerifyAndClear(&observer_);

  { ScopedBlockingCall scoped_blocking_call_b(BlockingType::MAY_BLOCK); }

  EXPECT_CALL(observer_, BlockingEnded());
}

TEST_F(ScopedBlockingCallTest, WillBlockWillBlock) {
  EXPECT_CALL(observer_, BlockingStarted(BlockingType::WILL_BLOCK));
  ScopedBlockingCall scoped_blocking_call_a(BlockingType::WILL_BLOCK);
  testing::Mock::VerifyAndClear(&observer_);

  { ScopedBlockingCall scoped_blocking_call_b(BlockingType::WILL_BLOCK); }

  EXPECT_CALL(observer_, BlockingEnded());
}

TEST_F(ScopedBlockingCallTest, MayBlockWillBlockTwice) {
  EXPECT_CALL(observer_, BlockingStarted(BlockingType::MAY_BLOCK));
  ScopedBlockingCall scoped_blocking_call_a(BlockingType::MAY_BLOCK);
  testing::Mock::VerifyAndClear(&observer_);

  {
    EXPECT_CALL(observer_, BlockingTypeUpgraded());
    ScopedBlockingCall scoped_blocking_call_b(BlockingType::WILL_BLOCK);
    testing::Mock::VerifyAndClear(&observer_);

    {
      ScopedBlockingCall scoped_blocking_call_c(BlockingType::MAY_BLOCK);
      ScopedBlockingCall scoped_blocking_call_d(BlockingType::WILL_BLOCK);
    }
  }

  EXPECT_CALL(observer_, BlockingEnded());
}

TEST(ScopedBlockingCallDestructionOrderTest, InvalidDestructionOrder) {
  auto scoped_blocking_call_a =
      std::make_unique<ScopedBlockingCall>(BlockingType::WILL_BLOCK);
  auto scoped_blocking_call_b =
      std::make_unique<ScopedBlockingCall>(BlockingType::WILL_BLOCK);

  EXPECT_DCHECK_DEATH({ scoped_blocking_call_a.reset(); });
}

}  // namespace base
