event_sys_unittest.cc revision 06741cbc25cd4227a9fba40dfd0273bfcc1a587a
1// Copyright (c) 2009 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <iosfwd> 6#include <sstream> 7#include <string> 8#include <vector> 9 10#include "base/basictypes.h" 11#include "base/logging.h" 12#include "base/message_loop.h" 13#include "base/platform_thread.h" 14#include "base/port.h" 15#include "build/build_config.h" 16#include "chrome/common/deprecated/event_sys-inl.h" 17#include "testing/gtest/include/gtest/gtest.h" 18 19using std::endl; 20using std::ostream; 21using std::string; 22using std::stringstream; 23using std::vector; 24 25namespace { 26 27class Pair; 28 29struct TestEvent { 30 Pair* source; 31 enum { 32 A_CHANGED, B_CHANGED, PAIR_BEING_DELETED, 33 } what_happened; 34 int old_value; 35}; 36 37struct TestEventTraits { 38 typedef TestEvent EventType; 39 static bool IsChannelShutdownEvent(const TestEvent& event) { 40 return TestEvent::PAIR_BEING_DELETED == event.what_happened; 41 } 42}; 43 44class Pair { 45 public: 46 typedef EventChannel<TestEventTraits> Channel; 47 explicit Pair(const string& name) : name_(name), a_(0), b_(0) { 48 TestEvent shutdown = { this, TestEvent::PAIR_BEING_DELETED, 0 }; 49 event_channel_ = new Channel(shutdown); 50 } 51 ~Pair() { 52 delete event_channel_; 53 } 54 void set_a(int n) { 55 TestEvent event = { this, TestEvent::A_CHANGED, a_ }; 56 a_ = n; 57 event_channel_->NotifyListeners(event); 58 } 59 void set_b(int n) { 60 TestEvent event = { this, TestEvent::B_CHANGED, b_ }; 61 b_ = n; 62 event_channel_->NotifyListeners(event); 63 } 64 int a() const { return a_; } 65 int b() const { return b_; } 66 const string& name() { return name_; } 67 Channel* event_channel() const { return event_channel_; } 68 69 protected: 70 const string name_; 71 int a_; 72 int b_; 73 Channel* event_channel_; 74}; 75 76class EventLogger { 77 public: 78 explicit EventLogger(ostream& out) : out_(out) { } 79 ~EventLogger() { 80 for (Hookups::iterator i = hookups_.begin(); i != hookups_.end(); ++i) 81 delete *i; 82 } 83 84 void Hookup(const string name, Pair::Channel* channel) { 85 hookups_.push_back(NewEventListenerHookup(channel, this, 86 &EventLogger::HandlePairEvent, 87 name)); 88 } 89 90 void HandlePairEvent(const string& name, const TestEvent& event) { 91 const char* what_changed = NULL; 92 int new_value = 0; 93 Hookups::iterator dead; 94 switch (event.what_happened) { 95 case TestEvent::A_CHANGED: 96 what_changed = "A"; 97 new_value = event.source->a(); 98 break; 99 case TestEvent::B_CHANGED: 100 what_changed = "B"; 101 new_value = event.source->b(); 102 break; 103 case TestEvent::PAIR_BEING_DELETED: 104 out_ << name << " heard " << event.source->name() << " being deleted." 105 << endl; 106 return; 107 default: 108 FAIL() << "Bad event.what_happened: " << event.what_happened; 109 break; 110 } 111 out_ << name << " heard " << event.source->name() << "'s " << what_changed 112 << " change from " 113 << event.old_value << " to " << new_value << endl; 114 } 115 116 typedef vector<EventListenerHookup*> Hookups; 117 Hookups hookups_; 118 ostream& out_; 119}; 120 121const char golden_result[] = "Larry heard Sally's B change from 0 to 2\n" 122"Larry heard Sally's A change from 1 to 3\n" 123"Lewis heard Sam's B change from 0 to 5\n" 124"Larry heard Sally's A change from 3 to 6\n" 125"Larry heard Sally being deleted.\n"; 126 127TEST(EventSys, Basic) { 128 Pair sally("Sally"), sam("Sam"); 129 sally.set_a(1); 130 stringstream log; 131 EventLogger logger(log); 132 logger.Hookup("Larry", sally.event_channel()); 133 sally.set_b(2); 134 sally.set_a(3); 135 sam.set_a(4); 136 logger.Hookup("Lewis", sam.event_channel()); 137 sam.set_b(5); 138 sally.set_a(6); 139 // Test that disconnect within callback doesn't deadlock. 140 TestEvent event = {&sally, TestEvent::PAIR_BEING_DELETED, 0 }; 141 sally.event_channel()->NotifyListeners(event); 142 sally.set_a(7); 143 ASSERT_EQ(log.str(), golden_result); 144} 145 146 147// This goes pretty far beyond the normal use pattern, so don't use 148// ThreadTester as an example of what to do. 149class ThreadTester : public EventListener<TestEvent>, 150 public PlatformThread::Delegate { 151 public: 152 explicit ThreadTester(Pair* pair) 153 : pair_(pair), remove_event_(&remove_event_mutex_), 154 remove_event_bool_(false), completed_(false) { 155 pair_->event_channel()->AddListener(this); 156 } 157 ~ThreadTester() { 158 pair_->event_channel()->RemoveListener(this); 159 for (size_t i = 0; i < threads_.size(); i++) { 160 PlatformThread::Join(threads_[i].thread); 161 } 162 } 163 164 struct ThreadInfo { 165 PlatformThreadHandle thread; 166 }; 167 168 struct ThreadArgs { 169 ConditionVariable* thread_running_cond; 170 Lock* thread_running_mutex; 171 bool thread_running; 172 }; 173 174 void Go() { 175 Lock thread_running_mutex; 176 ConditionVariable thread_running_cond(&thread_running_mutex); 177 ThreadArgs args; 178 ThreadInfo info; 179 args.thread_running_cond = &(thread_running_cond); 180 args.thread_running_mutex = &(thread_running_mutex); 181 args.thread_running = false; 182 args_ = args; 183 ASSERT_TRUE(PlatformThread::Create(0, this, &info.thread)); 184 thread_running_mutex.Acquire(); 185 while ((args_.thread_running) == false) { 186 thread_running_cond.Wait(); 187 } 188 thread_running_mutex.Release(); 189 threads_.push_back(info); 190 } 191 192 // PlatformThread::Delegate methods. 193 virtual void ThreadMain() { 194 // Make sure each thread gets a current MessageLoop in TLS. 195 // This test should use chrome threads for testing, but I'll leave it like 196 // this for the moment since it requires a big chunk of rewriting and I 197 // want the test passing while I checkpoint my CL. Technically speaking, 198 // there should be no functional difference. 199 MessageLoop message_loop; 200 args_.thread_running_mutex->Acquire(); 201 args_.thread_running = true; 202 args_.thread_running_cond->Signal(); 203 args_.thread_running_mutex->Release(); 204 205 remove_event_mutex_.Acquire(); 206 while (remove_event_bool_ == false) { 207 remove_event_.Wait(); 208 } 209 remove_event_mutex_.Release(); 210 211 // Normally, you'd just delete the hookup. This is very bad style, but 212 // necessary for the test. 213 pair_->event_channel()->RemoveListener(this); 214 215 completed_mutex_.Acquire(); 216 completed_ = true; 217 completed_mutex_.Release(); 218 } 219 220 void HandleEvent(const TestEvent& event) { 221 remove_event_mutex_.Acquire(); 222 remove_event_bool_ = true; 223 remove_event_.Broadcast(); 224 remove_event_mutex_.Release(); 225 226 PlatformThread::YieldCurrentThread(); 227 228 completed_mutex_.Acquire(); 229 if (completed_) 230 FAIL() << "A test thread exited too early."; 231 completed_mutex_.Release(); 232 } 233 234 Pair* pair_; 235 ConditionVariable remove_event_; 236 Lock remove_event_mutex_; 237 bool remove_event_bool_; 238 Lock completed_mutex_; 239 bool completed_; 240 vector<ThreadInfo> threads_; 241 ThreadArgs args_; 242}; 243 244TEST(EventSys, Multithreaded) { 245 Pair sally("Sally"); 246 ThreadTester a(&sally); 247 for (int i = 0; i < 3; ++i) 248 a.Go(); 249 sally.set_b(99); 250} 251 252class HookupDeleter { 253 public: 254 void HandleEvent(const TestEvent& event) { 255 delete hookup_; 256 hookup_ = NULL; 257 } 258 EventListenerHookup* hookup_; 259}; 260 261TEST(EventSys, InHandlerDeletion) { 262 Pair sally("Sally"); 263 HookupDeleter deleter; 264 deleter.hookup_ = NewEventListenerHookup(sally.event_channel(), 265 &deleter, 266 &HookupDeleter::HandleEvent); 267 sally.set_a(1); 268 ASSERT_TRUE(NULL == deleter.hookup_); 269} 270 271} // namespace 272