// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This file tests both ref_counted.h and ref_ptr.h (which the former includes).
// TODO(vtl): Possibly we could separate these tests out better, since a lot of
// it is actually testing |RefPtr|.

#include "flutter/fml/memory/ref_counted.h"

#include "flutter/fml/macros.h"
#include "gtest/gtest.h"

#if defined(__clang__)
#define ALLOW_PESSIMIZING_MOVE(code_line)                                   \
  _Pragma("clang diagnostic push")                                          \
      _Pragma("clang diagnostic ignored \"-Wpessimizing-move\"") code_line; \
  _Pragma("clang diagnostic pop")
#else
#define ALLOW_PESSIMIZING_MOVE(code_line) code_line;
#endif

#if defined(__clang__)
#define ALLOW_SELF_MOVE(code_line)                                   \
  _Pragma("clang diagnostic push")                                   \
      _Pragma("clang diagnostic ignored \"-Wself-move\"") code_line; \
  _Pragma("clang diagnostic pop")
#else
#define ALLOW_SELF_MOVE(code_line) code_line;
#endif

#if defined(__clang__)
#define ALLOW_SELF_ASSIGN_OVERLOADED(code_line)                        \
  _Pragma("clang diagnostic push")                                     \
      _Pragma("clang diagnostic ignored \"-Wself-assign-overloaded\"") \
          code_line;                                                   \
  _Pragma("clang diagnostic pop")
#else
#define ALLOW_SELF_ASSIGN_OVERLOADED(code_line) code_line;
#endif

namespace fml {
namespace {

class MyClass : public RefCountedThreadSafe<MyClass> {
 protected:
  MyClass(MyClass** created, bool* was_destroyed)
      : was_destroyed_(was_destroyed) {
    if (created)
      *created = this;
  }
  virtual ~MyClass() {
    if (was_destroyed_)
      *was_destroyed_ = true;
  }

 private:
  FML_FRIEND_REF_COUNTED_THREAD_SAFE(MyClass);
  FML_FRIEND_MAKE_REF_COUNTED(MyClass);

  bool* was_destroyed_;

  FML_DISALLOW_COPY_AND_ASSIGN(MyClass);
};

class MySubclass final : public MyClass {
 private:
  FML_FRIEND_REF_COUNTED_THREAD_SAFE(MySubclass);
  FML_FRIEND_MAKE_REF_COUNTED(MySubclass);

  MySubclass(MySubclass** created, bool* was_destroyed)
      : MyClass(nullptr, was_destroyed) {
    if (created)
      *created = this;
  }
  ~MySubclass() override {}

  FML_DISALLOW_COPY_AND_ASSIGN(MySubclass);
};

TEST(RefCountedTest, Constructors) {
  bool was_destroyed;

  {
    // Default.
    RefPtr<MyClass> r;
    EXPECT_TRUE(r.get() == nullptr);
    EXPECT_FALSE(r);
  }

  {
    // Nullptr.
    RefPtr<MyClass> r(nullptr);
    EXPECT_TRUE(r.get() == nullptr);
    EXPECT_FALSE(r);
  }

  {
    MyClass* created = nullptr;
    was_destroyed = false;
    // Adopt, then RVO.
    RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, &was_destroyed));
    EXPECT_TRUE(created);
    EXPECT_EQ(created, r.get());
    EXPECT_TRUE(r);
    EXPECT_FALSE(was_destroyed);
  }
  EXPECT_TRUE(was_destroyed);

  {
    MyClass* created = nullptr;
    was_destroyed = false;
    // Adopt, then move.
    ALLOW_PESSIMIZING_MOVE(RefPtr<MyClass> r(
        std::move(MakeRefCounted<MyClass>(&created, &was_destroyed))))
    EXPECT_TRUE(created);
    EXPECT_EQ(created, r.get());
    EXPECT_TRUE(r);
    EXPECT_FALSE(was_destroyed);
  }
  EXPECT_TRUE(was_destroyed);

  {
    MyClass* created = nullptr;
    was_destroyed = false;
    RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed));
    // Copy.
    RefPtr<MyClass> r2(r1);
    EXPECT_TRUE(created);
    EXPECT_EQ(created, r1.get());
    EXPECT_EQ(created, r2.get());
    EXPECT_TRUE(r1);
    EXPECT_TRUE(r2);
    EXPECT_FALSE(was_destroyed);
  }
  EXPECT_TRUE(was_destroyed);

  {
    MyClass* created = nullptr;
    was_destroyed = false;
    RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed));
    // From raw pointer.
    RefPtr<MyClass> r2(created);
    EXPECT_TRUE(created);
    EXPECT_EQ(created, r1.get());
    EXPECT_EQ(created, r2.get());
    EXPECT_TRUE(r1);
    EXPECT_TRUE(r2);
    EXPECT_FALSE(was_destroyed);
  }
  EXPECT_TRUE(was_destroyed);

  {
    MySubclass* created = nullptr;
    was_destroyed = false;
    // Adopt, then "move".
    RefPtr<MyClass> r(MakeRefCounted<MySubclass>(&created, &was_destroyed));
    EXPECT_TRUE(created);
    EXPECT_EQ(static_cast<MyClass*>(created), r.get());
    EXPECT_TRUE(r);
    EXPECT_FALSE(was_destroyed);
  }
  EXPECT_TRUE(was_destroyed);

  {
    MySubclass* created = nullptr;
    was_destroyed = false;
    // Adopt, then "move".
    ALLOW_PESSIMIZING_MOVE(RefPtr<MyClass> r(
        std::move(MakeRefCounted<MySubclass>(&created, &was_destroyed))))
    EXPECT_TRUE(created);
    EXPECT_EQ(static_cast<MyClass*>(created), r.get());
    EXPECT_TRUE(r);
    EXPECT_FALSE(was_destroyed);
  }
  EXPECT_TRUE(was_destroyed);

  {
    MySubclass* created = nullptr;
    was_destroyed = false;
    RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));
    // "Copy".
    RefPtr<MyClass> r2(r1);
    EXPECT_TRUE(created);
    EXPECT_EQ(static_cast<MyClass*>(created), r1.get());
    EXPECT_EQ(static_cast<MyClass*>(created), r2.get());
    EXPECT_TRUE(r1);
    EXPECT_TRUE(r2);
    EXPECT_FALSE(was_destroyed);
  }
  EXPECT_TRUE(was_destroyed);

  {
    MySubclass* created = nullptr;
    was_destroyed = false;
    RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));
    // From raw pointer.
    RefPtr<MyClass> r2(created);
    EXPECT_TRUE(created);
    EXPECT_EQ(static_cast<MyClass*>(created), r1.get());
    EXPECT_EQ(static_cast<MyClass*>(created), r2.get());
    EXPECT_TRUE(r1);
    EXPECT_TRUE(r2);
    EXPECT_FALSE(was_destroyed);
  }
  EXPECT_TRUE(was_destroyed);
}

TEST(RefCountedTest, NullAssignmentToNull) {
  RefPtr<MyClass> r1;
  // No-op null assignment using |nullptr|.
  r1 = nullptr;
  EXPECT_TRUE(r1.get() == nullptr);
  EXPECT_FALSE(r1);

  RefPtr<MyClass> r2;
  // No-op null assignment using copy constructor.
  r1 = r2;
  EXPECT_TRUE(r1.get() == nullptr);
  EXPECT_TRUE(r2.get() == nullptr);
  EXPECT_FALSE(r1);
  EXPECT_FALSE(r2);

  // No-op null assignment using move constructor.
  r1 = std::move(r2);
  EXPECT_TRUE(r1.get() == nullptr);
  EXPECT_TRUE(r2.get() == nullptr);
  EXPECT_FALSE(r1);
  EXPECT_FALSE(r2);

  RefPtr<MySubclass> r3;
  // No-op null assignment using "copy" constructor.
  r1 = r3;
  EXPECT_TRUE(r1.get() == nullptr);
  EXPECT_TRUE(r3.get() == nullptr);
  EXPECT_FALSE(r1);
  EXPECT_FALSE(r3);

  // No-op null assignment using "move" constructor.
  r1 = std::move(r3);
  EXPECT_TRUE(r1.get() == nullptr);
  EXPECT_TRUE(r3.get() == nullptr);
  EXPECT_FALSE(r1);
  EXPECT_FALSE(r3);
}

TEST(RefCountedTest, NonNullAssignmentToNull) {
  bool was_destroyed;

  {
    MyClass* created = nullptr;
    was_destroyed = false;
    RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed));
    RefPtr<MyClass> r2;
    // Copy assignment (to null ref pointer).
    r2 = r1;
    EXPECT_EQ(created, r1.get());
    EXPECT_EQ(created, r2.get());
    EXPECT_TRUE(r1);
    EXPECT_TRUE(r2);
    EXPECT_FALSE(was_destroyed);
  }
  EXPECT_TRUE(was_destroyed);

  {
    MyClass* created = nullptr;
    was_destroyed = false;
    RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed));
    RefPtr<MyClass> r2;
    // Move assignment (to null ref pointer).
    r2 = std::move(r1);
    EXPECT_TRUE(r1.get() == nullptr);
    EXPECT_EQ(created, r2.get());
    EXPECT_FALSE(r1);
    EXPECT_TRUE(r2);
    EXPECT_FALSE(was_destroyed);
  }
  EXPECT_TRUE(was_destroyed);

  {
    MySubclass* created = nullptr;
    was_destroyed = false;
    RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));
    RefPtr<MyClass> r2;
    // "Copy" assignment (to null ref pointer).
    r2 = r1;
    EXPECT_EQ(created, r1.get());
    EXPECT_EQ(static_cast<MyClass*>(created), r2.get());
    EXPECT_TRUE(r1);
    EXPECT_TRUE(r2);
    EXPECT_FALSE(was_destroyed);
  }
  EXPECT_TRUE(was_destroyed);

  {
    MySubclass* created = nullptr;
    was_destroyed = false;
    RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));
    RefPtr<MyClass> r2;
    // "Move" assignment (to null ref pointer).
    r2 = std::move(r1);
    EXPECT_TRUE(r1.get() == nullptr);
    EXPECT_EQ(static_cast<MyClass*>(created), r2.get());
    EXPECT_FALSE(r1);
    EXPECT_TRUE(r2);
    EXPECT_FALSE(was_destroyed);
  }
  EXPECT_TRUE(was_destroyed);
}

TEST(RefCountedTest, NullAssignmentToNonNull) {
  bool was_destroyed = false;
  RefPtr<MyClass> r1(MakeRefCounted<MyClass>(nullptr, &was_destroyed));
  // Null assignment (to non-null ref pointer) using |nullptr|.
  r1 = nullptr;
  EXPECT_TRUE(r1.get() == nullptr);
  EXPECT_FALSE(r1);
  EXPECT_TRUE(was_destroyed);

  was_destroyed = false;
  r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed);
  RefPtr<MyClass> r2;
  // Null assignment (to non-null ref pointer) using copy constructor.
  r1 = r2;
  EXPECT_TRUE(r1.get() == nullptr);
  EXPECT_TRUE(r2.get() == nullptr);
  EXPECT_FALSE(r1);
  EXPECT_FALSE(r2);
  EXPECT_TRUE(was_destroyed);

  was_destroyed = false;
  r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed);
  // Null assignment using move constructor.
  r1 = std::move(r2);
  EXPECT_TRUE(r1.get() == nullptr);
  EXPECT_TRUE(r2.get() == nullptr);
  EXPECT_FALSE(r1);
  EXPECT_FALSE(r2);
  EXPECT_TRUE(was_destroyed);

  was_destroyed = false;
  r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed);
  RefPtr<MySubclass> r3;
  // Null assignment (to non-null ref pointer) using "copy" constructor.
  r1 = r3;
  EXPECT_TRUE(r1.get() == nullptr);
  EXPECT_TRUE(r3.get() == nullptr);
  EXPECT_FALSE(r1);
  EXPECT_FALSE(r3);
  EXPECT_TRUE(was_destroyed);

  was_destroyed = false;
  r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed);
  // Null assignment (to non-null ref pointer) using "move" constructor.
  r1 = std::move(r3);
  EXPECT_TRUE(r1.get() == nullptr);
  EXPECT_TRUE(r3.get() == nullptr);
  EXPECT_FALSE(r1);
  EXPECT_FALSE(r3);
  EXPECT_TRUE(was_destroyed);
}

TEST(RefCountedTest, NonNullAssignmentToNonNull) {
  bool was_destroyed1;
  bool was_destroyed2;

  {
    was_destroyed1 = false;
    was_destroyed2 = false;
    RefPtr<MyClass> r1(MakeRefCounted<MyClass>(nullptr, &was_destroyed1));
    RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2));
    // Copy assignment (to non-null ref pointer).
    r2 = r1;
    EXPECT_EQ(r1.get(), r2.get());
    EXPECT_TRUE(r1);
    EXPECT_TRUE(r2);
    EXPECT_FALSE(was_destroyed1);
    EXPECT_TRUE(was_destroyed2);
  }
  EXPECT_TRUE(was_destroyed1);

  {
    was_destroyed1 = false;
    was_destroyed2 = false;
    RefPtr<MyClass> r1(MakeRefCounted<MyClass>(nullptr, &was_destroyed1));
    RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2));
    // Move assignment (to non-null ref pointer).
    r2 = std::move(r1);
    EXPECT_TRUE(r1.get() == nullptr);
    EXPECT_FALSE(r2.get() == nullptr);
    EXPECT_FALSE(r1);
    EXPECT_TRUE(r2);
    EXPECT_FALSE(was_destroyed1);
    EXPECT_TRUE(was_destroyed2);
  }
  EXPECT_TRUE(was_destroyed1);

  {
    was_destroyed1 = false;
    was_destroyed2 = false;
    RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(nullptr, &was_destroyed1));
    RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2));
    // "Copy" assignment (to non-null ref pointer).
    r2 = r1;
    EXPECT_EQ(r1.get(), r2.get());
    EXPECT_TRUE(r1);
    EXPECT_TRUE(r2);
    EXPECT_FALSE(was_destroyed1);
    EXPECT_TRUE(was_destroyed2);
  }
  EXPECT_TRUE(was_destroyed1);

  {
    was_destroyed1 = false;
    was_destroyed2 = false;
    RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(nullptr, &was_destroyed1));
    RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2));
    // Move assignment (to non-null ref pointer).
    r2 = std::move(r1);
    EXPECT_TRUE(r1.get() == nullptr);
    EXPECT_FALSE(r2.get() == nullptr);
    EXPECT_FALSE(r1);
    EXPECT_TRUE(r2);
    EXPECT_FALSE(was_destroyed1);
    EXPECT_TRUE(was_destroyed2);
  }
  EXPECT_TRUE(was_destroyed1);
}

TEST(RefCountedTest, SelfAssignment) {
  bool was_destroyed;

  {
    MyClass* created = nullptr;
    was_destroyed = false;
    RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, &was_destroyed));
    // Copy.
    ALLOW_SELF_ASSIGN_OVERLOADED(r = r);
    EXPECT_EQ(created, r.get());
    EXPECT_FALSE(was_destroyed);
  }
  EXPECT_TRUE(was_destroyed);

  {
    MyClass* created = nullptr;
    was_destroyed = false;
    RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, &was_destroyed));
    // Move.
    ALLOW_SELF_MOVE(r = std::move(r))
    EXPECT_EQ(created, r.get());
    EXPECT_FALSE(was_destroyed);
  }
  EXPECT_TRUE(was_destroyed);
}

TEST(RefCountedTest, Swap) {
  MyClass* created1 = nullptr;
  bool was_destroyed1 = false;
  RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created1, &was_destroyed1));
  EXPECT_TRUE(created1);
  EXPECT_EQ(created1, r1.get());

  MyClass* created2 = nullptr;
  bool was_destroyed2 = false;
  RefPtr<MyClass> r2(MakeRefCounted<MyClass>(&created2, &was_destroyed2));
  EXPECT_TRUE(created2);
  EXPECT_EQ(created2, r2.get());
  EXPECT_NE(created1, created2);

  r1.swap(r2);
  EXPECT_EQ(created2, r1.get());
  EXPECT_EQ(created1, r2.get());
}

TEST(RefCountedTest, GetAndDereferenceOperators) {
  // Note: We check here that .get(), operator*, and operator-> are const, but
  // return non-const pointers/refs.

  MyClass* created = nullptr;
  const RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, nullptr));
  MyClass* ptr = r.get();  // Assign to non-const pointer.
  EXPECT_EQ(created, ptr);
  ptr = r.operator->();  // Assign to non-const pointer.
  EXPECT_EQ(created, ptr);
  MyClass& ref = *r;  // "Assign" to non-const reference.
  EXPECT_EQ(created, &ref);
}

// You can manually call |AddRef()| and |Release()| if you want.
TEST(RefCountedTest, AddRefRelease) {
  MyClass* created = nullptr;
  bool was_destroyed = false;
  {
    RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, &was_destroyed));
    EXPECT_EQ(created, r.get());
    created->AddRef();
  }
  EXPECT_FALSE(was_destroyed);
  created->Release();
  EXPECT_TRUE(was_destroyed);
}

TEST(RefCountedTest, Mix) {
  MySubclass* created = nullptr;
  bool was_destroyed = false;
  RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));
  ASSERT_FALSE(was_destroyed);
  EXPECT_TRUE(created->HasOneRef());
  created->AssertHasOneRef();

  RefPtr<MySubclass> r2 = r1;
  ASSERT_FALSE(was_destroyed);
  EXPECT_FALSE(created->HasOneRef());

  r1 = nullptr;
  ASSERT_FALSE(was_destroyed);
  created->AssertHasOneRef();

  {
    RefPtr<MyClass> r3 = r2;
    EXPECT_FALSE(created->HasOneRef());
    {
      RefPtr<MyClass> r4(r3);
      r2 = nullptr;
      ASSERT_FALSE(was_destroyed);
      EXPECT_FALSE(created->HasOneRef());
    }
    ASSERT_FALSE(was_destroyed);
    EXPECT_TRUE(created->HasOneRef());
    created->AssertHasOneRef();

    r1 = RefPtr<MySubclass>(static_cast<MySubclass*>(r3.get()));
    ASSERT_FALSE(was_destroyed);
    EXPECT_FALSE(created->HasOneRef());
  }
  ASSERT_FALSE(was_destroyed);
  EXPECT_TRUE(created->HasOneRef());
  created->AssertHasOneRef();

  EXPECT_EQ(created, r1.get());

  r1 = nullptr;
  EXPECT_TRUE(was_destroyed);
}

class MyPublicClass : public RefCountedThreadSafe<MyPublicClass> {
 public:
  // Overloaded constructors work with |MakeRefCounted()|.
  MyPublicClass() : has_num_(false), num_(0) {}
  explicit MyPublicClass(int num) : has_num_(true), num_(num) {}

  ~MyPublicClass() {}

  bool has_num() const { return has_num_; }
  int num() const { return num_; }

 private:
  bool has_num_;
  int num_;

  FML_DISALLOW_COPY_AND_ASSIGN(MyPublicClass);
};

// You can also just keep constructors and destructors public. Make sure that
// works (mostly that it compiles).
TEST(RefCountedTest, PublicCtorAndDtor) {
  RefPtr<MyPublicClass> r1 = MakeRefCounted<MyPublicClass>();
  ASSERT_TRUE(r1);
  EXPECT_FALSE(r1->has_num());

  RefPtr<MyPublicClass> r2 = MakeRefCounted<MyPublicClass>(123);
  ASSERT_TRUE(r2);
  EXPECT_TRUE(r2->has_num());
  EXPECT_EQ(123, r2->num());
  EXPECT_NE(r1.get(), r2.get());

  r1 = r2;
  EXPECT_TRUE(r1->has_num());
  EXPECT_EQ(123, r1->num());
  EXPECT_EQ(r1.get(), r2.get());

  r2 = nullptr;
  EXPECT_FALSE(r2);
  EXPECT_TRUE(r1->has_num());
  EXPECT_EQ(123, r1->num());

  r1 = nullptr;
  EXPECT_FALSE(r1);
}

// The danger with having a public constructor or destructor is that certain
// things will compile. You should get some protection by assertions in Debug
// builds.
#ifndef NDEBUG
TEST(RefCountedTest, DebugChecks) {
  {
    MyPublicClass* p = new MyPublicClass();
    EXPECT_DEATH_IF_SUPPORTED(delete p, "!adoption_required_");
  }

  {
    MyPublicClass* p = new MyPublicClass();
    EXPECT_DEATH_IF_SUPPORTED(RefPtr<MyPublicClass> r(p),
                              "!adoption_required_");
  }

  {
    RefPtr<MyPublicClass> r(MakeRefCounted<MyPublicClass>());
    EXPECT_DEATH_IF_SUPPORTED(delete r.get(), "destruction_started_");
  }
}
#endif

// TODO(vtl): Add (threaded) stress tests.

}  // namespace
}  // namespace fml