weak_ptr: add test, fix bug.

Add a test that uncovers a bug in weak_ptr where we don't call T's
destructor in enable_weak_from_this<T>::schedule_deletion.

Test: treehugger
Change-Id: I31d755cb2a240fa092acfa4cf627e67f3c8cd23f
diff --git a/types.h b/types.h
index 620aa8e..cd08ed8 100644
--- a/types.h
+++ b/types.h
@@ -306,7 +306,7 @@
         return *this;
     }
 
-    T* get() {
+    T* get() const {
         check_main_thread();
         return ptr_;
     }
@@ -349,7 +349,7 @@
     weak_ptr<T> weak() { return weak_ptr<T>(static_cast<T*>(this)); }
 
     void schedule_deletion() {
-        fdevent_run_on_main_thread([this]() { delete this; });
+        fdevent_run_on_main_thread([this]() { delete static_cast<T*>(this); });
     }
 
   private:
diff --git a/types_test.cpp b/types_test.cpp
index 564ae0b..086a35d 100644
--- a/types_test.cpp
+++ b/types_test.cpp
@@ -14,10 +14,14 @@
  * limitations under the License.
  */
 
+#include "types.h"
+
 #include <gtest/gtest.h>
 
 #include <memory>
-#include "types.h"
+#include <utility>
+
+#include "fdevent/fdevent_test.h"
 
 static IOVector::block_type create_block(const std::string& string) {
     return IOVector::block_type(string.begin(), string.end());
@@ -158,3 +162,46 @@
     vec.trim_front();
     ASSERT_EQ(1ULL, vec.size());
 }
+
+class weak_ptr_test : public FdeventTest {};
+
+struct Destructor : public enable_weak_from_this<Destructor> {
+    Destructor(bool* destroyed) : destroyed_(destroyed) {}
+    ~Destructor() { *destroyed_ = true; }
+
+    bool* destroyed_;
+};
+
+TEST_F(weak_ptr_test, smoke) {
+    PrepareThread();
+
+    Destructor* destructor = nullptr;
+    bool destroyed = false;
+    std::optional<weak_ptr<Destructor>> p;
+
+    fdevent_run_on_main_thread([&p, &destructor, &destroyed]() {
+        destructor = new Destructor(&destroyed);
+        p = destructor->weak();
+        ASSERT_TRUE(p->get());
+
+        p->reset();
+        ASSERT_FALSE(p->get());
+
+        p->reset(destructor);
+        ASSERT_TRUE(p->get());
+    });
+    WaitForFdeventLoop();
+    ASSERT_TRUE(destructor);
+    ASSERT_FALSE(destroyed);
+
+    destructor->schedule_deletion();
+    WaitForFdeventLoop();
+
+    ASSERT_TRUE(destroyed);
+    fdevent_run_on_main_thread([&p]() {
+        ASSERT_FALSE(p->get());
+        p.reset();
+    });
+
+    TerminateThread();
+}