runnable_utils_unittest.cpp (9255B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 // Original author: ekr@rtfm.com 8 #include "runnable_utils.h" 9 10 #include <iostream> 11 12 #include "mozilla/RefPtr.h" 13 #include "mozilla/UniquePtr.h" 14 #include "nsCOMPtr.h" 15 #include "nsNetCID.h" 16 #include "nsServiceManagerUtils.h" 17 #include "nsThreadUtils.h" 18 19 #define GTEST_HAS_RTTI 0 20 #include "gtest/gtest.h" 21 #include "gtest_utils.h" 22 23 using namespace mozilla; 24 25 namespace { 26 27 // Helper used to make sure args are properly copied and/or moved. 28 struct CtorDtorState { 29 enum class State { Empty, Copy, Explicit, Move, Moved }; 30 31 static const char* ToStr(const State& state) { 32 switch (state) { 33 case State::Empty: 34 return "empty"; 35 case State::Copy: 36 return "copy"; 37 case State::Explicit: 38 return "explicit"; 39 case State::Move: 40 return "move"; 41 case State::Moved: 42 return "moved"; 43 default: 44 return "unknown"; 45 } 46 } 47 48 void DumpState() const { std::cerr << ToStr(state_) << std::endl; } 49 50 CtorDtorState() { DumpState(); } 51 52 explicit CtorDtorState(int* destroyed) 53 : dtor_count_(destroyed), state_(State::Explicit) { 54 DumpState(); 55 } 56 57 CtorDtorState(const CtorDtorState& other) 58 : dtor_count_(other.dtor_count_), state_(State::Copy) { 59 DumpState(); 60 } 61 62 // Clear the other's dtor counter so it's not counted if moved. 63 CtorDtorState(CtorDtorState&& other) 64 : dtor_count_(std::exchange(other.dtor_count_, nullptr)), 65 state_(State::Move) { 66 other.state_ = State::Moved; 67 DumpState(); 68 } 69 70 ~CtorDtorState() { 71 const char* const state = ToStr(state_); 72 std::cerr << "Destructor called with end state: " << state << std::endl; 73 74 if (dtor_count_) { 75 ++*dtor_count_; 76 } 77 } 78 79 int* dtor_count_ = nullptr; 80 State state_ = State::Empty; 81 }; 82 83 class Destructor { 84 private: 85 ~Destructor() { 86 std::cerr << "Destructor called" << std::endl; 87 *destroyed_ = true; 88 } 89 90 public: 91 explicit Destructor(bool* destroyed) : destroyed_(destroyed) {} 92 93 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Destructor) 94 95 private: 96 bool* destroyed_; 97 }; 98 99 class TargetClass { 100 public: 101 explicit TargetClass(int* ran) : ran_(ran) {} 102 103 void m1(int x) { 104 std::cerr << __FUNCTION__ << " " << x << std::endl; 105 *ran_ = 1; 106 } 107 108 void m2(int x, int y) { 109 std::cerr << __FUNCTION__ << " " << x << " " << y << std::endl; 110 *ran_ = 2; 111 } 112 113 void m1set(bool* z) { 114 std::cerr << __FUNCTION__ << std::endl; 115 *z = true; 116 } 117 int return_int(int x) { 118 std::cerr << __FUNCTION__ << std::endl; 119 return x; 120 } 121 122 void destructor_target_ref(RefPtr<Destructor> destructor) {} 123 124 int* ran_; 125 }; 126 127 class RunnableArgsTest : public MtransportTest { 128 public: 129 RunnableArgsTest() : ran_(0), cl_(&ran_) {} 130 131 void Test1Arg() { 132 Runnable* r = WrapRunnable(&cl_, &TargetClass::m1, 1); 133 r->Run(); 134 ASSERT_EQ(1, ran_); 135 } 136 137 void Test2Args() { 138 Runnable* r = WrapRunnable(&cl_, &TargetClass::m2, 1, 2); 139 r->Run(); 140 ASSERT_EQ(2, ran_); 141 } 142 143 private: 144 int ran_; 145 TargetClass cl_; 146 }; 147 148 class DispatchTest : public MtransportTest { 149 public: 150 DispatchTest() : ran_(0), cl_(&ran_) {} 151 152 void SetUp() { 153 MtransportTest::SetUp(); 154 155 nsresult rv; 156 target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); 157 ASSERT_TRUE(NS_SUCCEEDED(rv)); 158 } 159 160 void Test1Arg() { 161 Runnable* r = WrapRunnable(&cl_, &TargetClass::m1, 1); 162 NS_DispatchAndSpinEventLoopUntilComplete("DispatchTest::Test1Arg"_ns, 163 target_, do_AddRef(r)); 164 ASSERT_EQ(1, ran_); 165 } 166 167 void Test2Args() { 168 Runnable* r = WrapRunnable(&cl_, &TargetClass::m2, 1, 2); 169 NS_DispatchAndSpinEventLoopUntilComplete("DispatchTest::Test2Args"_ns, 170 target_, do_AddRef(r)); 171 ASSERT_EQ(2, ran_); 172 } 173 174 void Test1Set() { 175 bool x = false; 176 NS_DispatchAndSpinEventLoopUntilComplete( 177 "DispatchTest::Test1Set"_ns, target_, 178 do_AddRef(WrapRunnable(&cl_, &TargetClass::m1set, &x))); 179 ASSERT_TRUE(x); 180 } 181 182 void TestRet() { 183 int z; 184 int x = 10; 185 186 NS_DispatchAndSpinEventLoopUntilComplete( 187 "DispatchTest::TestRet"_ns, target_, 188 do_AddRef(WrapRunnableRet(&z, &cl_, &TargetClass::return_int, x))); 189 ASSERT_EQ(10, z); 190 } 191 192 protected: 193 int ran_; 194 TargetClass cl_; 195 nsCOMPtr<nsIEventTarget> target_; 196 }; 197 198 TEST_F(RunnableArgsTest, OneArgument) { Test1Arg(); } 199 200 TEST_F(RunnableArgsTest, TwoArguments) { Test2Args(); } 201 202 TEST_F(DispatchTest, OneArgument) { Test1Arg(); } 203 204 TEST_F(DispatchTest, TwoArguments) { Test2Args(); } 205 206 TEST_F(DispatchTest, Test1Set) { Test1Set(); } 207 208 TEST_F(DispatchTest, TestRet) { TestRet(); } 209 210 void SetNonMethod(TargetClass* cl, int x) { cl->m1(x); } 211 212 int SetNonMethodRet(TargetClass* cl, int x) { 213 cl->m1(x); 214 215 return x; 216 } 217 218 TEST_F(DispatchTest, TestNonMethod) { 219 test_utils_->SyncDispatchToSTS(WrapRunnableNM(SetNonMethod, &cl_, 10)); 220 221 ASSERT_EQ(1, ran_); 222 } 223 224 TEST_F(DispatchTest, TestNonMethodRet) { 225 int z; 226 227 test_utils_->SyncDispatchToSTS( 228 WrapRunnableNMRet(&z, SetNonMethodRet, &cl_, 10)); 229 230 ASSERT_EQ(1, ran_); 231 ASSERT_EQ(10, z); 232 } 233 234 TEST_F(DispatchTest, TestDestructorRef) { 235 bool destroyed = false; 236 { 237 RefPtr<Destructor> destructor = new Destructor(&destroyed); 238 NS_DispatchAndSpinEventLoopUntilComplete( 239 "DispatchTest::TestDestructorRef"_ns, target_, 240 do_AddRef(WrapRunnable(&cl_, &TargetClass::destructor_target_ref, 241 destructor))); 242 ASSERT_FALSE(destroyed); 243 } 244 ASSERT_TRUE(destroyed); 245 246 // Now try with a move. 247 destroyed = false; 248 { 249 RefPtr<Destructor> destructor = new Destructor(&destroyed); 250 NS_DispatchAndSpinEventLoopUntilComplete( 251 "DispatchTest::TestDestructorRef"_ns, target_, 252 do_AddRef(WrapRunnable(&cl_, &TargetClass::destructor_target_ref, 253 std::move(destructor)))); 254 ASSERT_TRUE(destroyed); 255 } 256 } 257 258 TEST_F(DispatchTest, TestMove) { 259 int destroyed = 0; 260 { 261 CtorDtorState state(&destroyed); 262 263 // Dispatch with: 264 // - moved arg 265 // - by-val capture in function should consume a move 266 // - expect destruction in the function scope 267 NS_DispatchAndSpinEventLoopUntilComplete( 268 "DispatchTest::TestMove"_ns, target_, 269 do_AddRef(WrapRunnableNM([](CtorDtorState s) {}, std::move(state)))); 270 ASSERT_EQ(1, destroyed); 271 } 272 // Still shouldn't count when we go out of scope as it was moved. 273 ASSERT_EQ(1, destroyed); 274 275 { 276 CtorDtorState state(&destroyed); 277 278 // Dispatch with: 279 // - copied arg 280 // - by-val capture in function should consume a move 281 // - expect destruction in the function scope and call scope 282 NS_DispatchAndSpinEventLoopUntilComplete( 283 "DispatchTest::TestMove"_ns, target_, 284 do_AddRef(WrapRunnableNM([](CtorDtorState s) {}, state))); 285 ASSERT_EQ(2, destroyed); 286 } 287 // Original state should be destroyed 288 ASSERT_EQ(3, destroyed); 289 290 { 291 CtorDtorState state(&destroyed); 292 293 // Dispatch with: 294 // - moved arg 295 // - by-ref in function should accept a moved arg 296 // - expect destruction in the wrapper invocation scope 297 NS_DispatchAndSpinEventLoopUntilComplete( 298 "DispatchTest::TestMove"_ns, target_, 299 do_AddRef( 300 WrapRunnableNM([](const CtorDtorState& s) {}, std::move(state)))); 301 ASSERT_EQ(4, destroyed); 302 } 303 // Still shouldn't count when we go out of scope as it was moved. 304 ASSERT_EQ(4, destroyed); 305 306 { 307 CtorDtorState state(&destroyed); 308 309 // Dispatch with: 310 // - moved arg 311 // - r-value function should accept a moved arg 312 // - expect destruction in the wrapper invocation scope 313 NS_DispatchAndSpinEventLoopUntilComplete( 314 "DispatchTest::TestMove"_ns, target_, 315 do_AddRef(WrapRunnableNM([](CtorDtorState&& s) {}, std::move(state)))); 316 ASSERT_EQ(5, destroyed); 317 } 318 // Still shouldn't count when we go out of scope as it was moved. 319 ASSERT_EQ(5, destroyed); 320 } 321 322 TEST_F(DispatchTest, TestUniquePtr) { 323 // Test that holding the class in UniquePtr works 324 int ran = 0; 325 auto cl = MakeUnique<TargetClass>(&ran); 326 327 NS_DispatchAndSpinEventLoopUntilComplete( 328 "DispatchTest::TestUniquePtr"_ns, target_, 329 do_AddRef(WrapRunnable(std::move(cl), &TargetClass::m1, 1))); 330 ASSERT_EQ(1, ran); 331 332 // Test that UniquePtr works as a param to the runnable 333 int destroyed = 0; 334 { 335 auto state = MakeUnique<CtorDtorState>(&destroyed); 336 337 // Dispatch with: 338 // - moved arg 339 // - Function should move construct from arg 340 // - expect destruction in the wrapper invocation scope 341 NS_DispatchAndSpinEventLoopUntilComplete( 342 "DispatchTest::TestUniquePtr"_ns, target_, 343 do_AddRef(WrapRunnableNM([](UniquePtr<CtorDtorState> s) {}, 344 std::move(state)))); 345 ASSERT_EQ(1, destroyed); 346 } 347 // Still shouldn't count when we go out of scope as it was moved. 348 ASSERT_EQ(1, destroyed); 349 } 350 351 } // end of namespace