Add ReferenceQueue test

Also cleaned up reference queue.
TODO: Add tests for missing functionality.

Bug: 10808403

Change-Id: I182f9cb69022fe542ea9e53d4c6d35cff90af332
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 340304a..1347849 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -102,6 +102,7 @@
   runtime/gc/accounting/card_table_test.cc \
   runtime/gc/accounting/space_bitmap_test.cc \
   runtime/gc/heap_test.cc \
+  runtime/gc/reference_queue_test.cc \
   runtime/gc/space/dlmalloc_space_base_test.cc \
   runtime/gc/space/dlmalloc_space_static_test.cc \
   runtime/gc/space/dlmalloc_space_random_test.cc \
diff --git a/runtime/gc/reference_queue.cc b/runtime/gc/reference_queue.cc
index 4003524..f4efe3c 100644
--- a/runtime/gc/reference_queue.cc
+++ b/runtime/gc/reference_queue.cc
@@ -91,15 +91,30 @@
 void ReferenceQueue::Dump(std::ostream& os) const {
   mirror::Reference* cur = list_;
   os << "Reference starting at list_=" << list_ << "\n";
-  while (cur != nullptr) {
+  if (cur == nullptr) {
+    return;
+  }
+  do {
     mirror::Reference* pending_next = cur->GetPendingNext();
-    os << "PendingNext=" << pending_next;
+    os << "Reference= " << cur << " PendingNext=" << pending_next;
     if (cur->IsFinalizerReferenceInstance()) {
       os << " Zombie=" << cur->AsFinalizerReference()->GetZombie();
     }
     os << "\n";
     cur = pending_next;
+  } while (cur != list_);
+}
+
+size_t ReferenceQueue::GetLength() const {
+  size_t count = 0;
+  mirror::Reference* cur = list_;
+  if (cur != nullptr) {
+    do {
+      ++count;
+      cur = cur->GetPendingNext();
+    } while (cur != list_);
   }
+  return count;
 }
 
 void ReferenceQueue::ClearWhiteReferences(ReferenceQueue* cleared_references,
diff --git a/runtime/gc/reference_queue.h b/runtime/gc/reference_queue.h
index 4ef8478..f7d89d0 100644
--- a/runtime/gc/reference_queue.h
+++ b/runtime/gc/reference_queue.h
@@ -56,12 +56,14 @@
   // overhead.
   void EnqueueReference(mirror::Reference* ref) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
+  // Enqueue a reference without checking that it is enqueable.
   void EnqueuePendingReference(mirror::Reference* ref) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
+  // Dequeue the first reference (returns list_).
   mirror::Reference* DequeuePendingReference() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
-  // Enqueues finalizer references with white referents.  White referents are blackened, moved to the
-  // zombie field, and the referent field is cleared.
+  // Enqueues finalizer references with white referents.  White referents are blackened, moved to
+  // the zombie field, and the referent field is cleared.
   void EnqueueFinalizerReferences(ReferenceQueue* cleared_references,
                                   IsHeapReferenceMarkedCallback* is_marked_callback,
                                   MarkObjectCallback* mark_object_callback, void* arg)
@@ -73,24 +75,22 @@
   void ForwardSoftReferences(IsHeapReferenceMarkedCallback* preserve_callback, void* arg)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
-  // Unlink the reference list clearing references objects with white referents.  Cleared references
+  // Unlink the reference list clearing references objects with white referents. Cleared references
   // registered to a reference queue are scheduled for appending by the heap worker thread.
   void ClearWhiteReferences(ReferenceQueue* cleared_references,
                             IsHeapReferenceMarkedCallback* is_marked_callback, void* arg)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
-  void Dump(std::ostream& os) const
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  void Dump(std::ostream& os) const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  size_t GetLength() const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
   bool IsEmpty() const {
     return list_ == nullptr;
   }
-
   void Clear() {
     list_ = nullptr;
   }
-
-  mirror::Reference* GetList() {
+  mirror::Reference* GetList() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     return list_;
   }
 
@@ -102,7 +102,6 @@
   // Lock, used for parallel GC reference enqueuing. It allows for multiple threads simultaneously
   // calling AtomicEnqueueIfNotEnqueued.
   Mutex* const lock_;
-
   // The actual reference list. Only a root for the mark compact GC since it will be null for other
   // GC types.
   mirror::Reference* list_;
diff --git a/runtime/gc/reference_queue_test.cc b/runtime/gc/reference_queue_test.cc
new file mode 100644
index 0000000..888c0d2
--- /dev/null
+++ b/runtime/gc/reference_queue_test.cc
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "common_runtime_test.h"
+#include "reference_queue.h"
+#include "handle_scope-inl.h"
+#include "mirror/class-inl.h"
+#include "scoped_thread_state_change.h"
+
+namespace art {
+namespace gc {
+
+class ReferenceQueueTest : public CommonRuntimeTest {};
+
+TEST_F(ReferenceQueueTest, EnqueueDequeue) {
+  Thread* self = Thread::Current();
+  StackHandleScope<20> hs(self);
+  Mutex lock("Reference queue lock");
+  ReferenceQueue queue(&lock);
+  ASSERT_TRUE(queue.IsEmpty());
+  ScopedObjectAccess soa(self);
+  ASSERT_EQ(queue.GetLength(), 0U);
+  auto ref_class = hs.NewHandle(
+      Runtime::Current()->GetClassLinker()->FindClass(self, "Ljava/lang/ref/WeakReference;",
+                                                      NullHandle<mirror::ClassLoader>()));
+  ASSERT_TRUE(ref_class.Get() != nullptr);
+  auto ref1(hs.NewHandle(ref_class->AllocObject(self)->AsReference()));
+  ASSERT_TRUE(ref1.Get() != nullptr);
+  auto ref2(hs.NewHandle(ref_class->AllocObject(self)->AsReference()));
+  ASSERT_TRUE(ref2.Get() != nullptr);
+  // FIFO ordering.
+  queue.EnqueuePendingReference(ref1.Get());
+  ASSERT_TRUE(!queue.IsEmpty());
+  ASSERT_EQ(queue.GetLength(), 1U);
+  queue.EnqueuePendingReference(ref2.Get());
+  ASSERT_TRUE(!queue.IsEmpty());
+  ASSERT_EQ(queue.GetLength(), 2U);
+  ASSERT_EQ(queue.DequeuePendingReference(), ref2.Get());
+  ASSERT_TRUE(!queue.IsEmpty());
+  ASSERT_EQ(queue.GetLength(), 1U);
+  ASSERT_EQ(queue.DequeuePendingReference(), ref1.Get());
+  ASSERT_EQ(queue.GetLength(), 0U);
+  ASSERT_TRUE(queue.IsEmpty());
+}
+
+TEST_F(ReferenceQueueTest, Dump) {
+  Thread* self = Thread::Current();
+  StackHandleScope<20> hs(self);
+  Mutex lock("Reference queue lock");
+  ReferenceQueue queue(&lock);
+  ScopedObjectAccess soa(self);
+  queue.Dump(LOG(INFO));
+  auto weak_ref_class = hs.NewHandle(
+      Runtime::Current()->GetClassLinker()->FindClass(self, "Ljava/lang/ref/WeakReference;",
+                                                      NullHandle<mirror::ClassLoader>()));
+  ASSERT_TRUE(weak_ref_class.Get() != nullptr);
+  auto finalizer_ref_class = hs.NewHandle(
+      Runtime::Current()->GetClassLinker()->FindClass(self, "Ljava/lang/ref/FinalizerReference;",
+                                                      NullHandle<mirror::ClassLoader>()));
+  ASSERT_TRUE(finalizer_ref_class.Get() != nullptr);
+  auto ref1(hs.NewHandle(weak_ref_class->AllocObject(self)->AsReference()));
+  ASSERT_TRUE(ref1.Get() != nullptr);
+  auto ref2(hs.NewHandle(finalizer_ref_class->AllocObject(self)->AsReference()));
+  ASSERT_TRUE(ref2.Get() != nullptr);
+  queue.EnqueuePendingReference(ref1.Get());
+  queue.Dump(LOG(INFO));
+  queue.EnqueuePendingReference(ref2.Get());
+  queue.Dump(LOG(INFO));
+}
+
+}  // namespace gc
+}  // namespace art