OLD | NEW |
(Empty) | |
| 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/port.h" |
| 13 #include "chrome/browser/sync/util/event_sys-inl.h" |
| 14 #include "testing/gtest/include/gtest/gtest.h" |
| 15 |
| 16 using std::endl; |
| 17 using std::ostream; |
| 18 using std::string; |
| 19 using std::stringstream; |
| 20 using std::vector; |
| 21 |
| 22 namespace { |
| 23 |
| 24 class Pair; |
| 25 |
| 26 struct TestEvent { |
| 27 Pair* source; |
| 28 enum { |
| 29 A_CHANGED, B_CHANGED, PAIR_BEING_DELETED, |
| 30 } what_happened; |
| 31 int old_value; |
| 32 }; |
| 33 |
| 34 struct TestEventTraits { |
| 35 typedef TestEvent EventType; |
| 36 static bool IsChannelShutdownEvent(const TestEvent& event) { |
| 37 return TestEvent::PAIR_BEING_DELETED == event.what_happened; |
| 38 } |
| 39 }; |
| 40 |
| 41 class Pair { |
| 42 public: |
| 43 typedef EventChannel<TestEventTraits> Channel; |
| 44 explicit Pair(const string& name) : name_(name), a_(0), b_(0) { |
| 45 TestEvent shutdown = { this, TestEvent::PAIR_BEING_DELETED, 0 }; |
| 46 event_channel_ = new Channel(shutdown); |
| 47 } |
| 48 ~Pair() { |
| 49 delete event_channel_; |
| 50 } |
| 51 void set_a(int n) { |
| 52 TestEvent event = { this, TestEvent::A_CHANGED, a_ }; |
| 53 a_ = n; |
| 54 event_channel_->NotifyListeners(event); |
| 55 } |
| 56 void set_b(int n) { |
| 57 TestEvent event = { this, TestEvent::B_CHANGED, b_ }; |
| 58 b_ = n; |
| 59 event_channel_->NotifyListeners(event); |
| 60 } |
| 61 int a() const { return a_; } |
| 62 int b() const { return b_; } |
| 63 const string& name() { return name_; } |
| 64 Channel* event_channel() const { return event_channel_; } |
| 65 |
| 66 protected: |
| 67 const string name_; |
| 68 int a_; |
| 69 int b_; |
| 70 Channel* event_channel_; |
| 71 }; |
| 72 |
| 73 class EventLogger { |
| 74 public: |
| 75 explicit EventLogger(ostream& out) : out_(out) { } |
| 76 ~EventLogger() { |
| 77 for (Hookups::iterator i = hookups_.begin(); i != hookups_.end(); ++i) |
| 78 delete *i; |
| 79 } |
| 80 |
| 81 void Hookup(const string name, Pair::Channel* channel) { |
| 82 hookups_.push_back(NewEventListenerHookup(channel, this, |
| 83 &EventLogger::HandlePairEvent, |
| 84 name)); |
| 85 } |
| 86 |
| 87 void HandlePairEvent(const string& name, const TestEvent& event) { |
| 88 const char* what_changed; |
| 89 int new_value; |
| 90 Hookups::iterator dead; |
| 91 switch (event.what_happened) { |
| 92 case TestEvent::A_CHANGED: |
| 93 what_changed = "A"; |
| 94 new_value = event.source->a(); |
| 95 break; |
| 96 case TestEvent::B_CHANGED: |
| 97 what_changed = "B"; |
| 98 new_value = event.source->b(); |
| 99 break; |
| 100 case TestEvent::PAIR_BEING_DELETED: |
| 101 out_ << name << " heard " << event.source->name() << " being deleted." |
| 102 << endl; |
| 103 return; |
| 104 default: |
| 105 LOG(FATAL) << "Bad event.what_happened: " << event.what_happened; |
| 106 break; |
| 107 } |
| 108 out_ << name << " heard " << event.source->name() << "'s " << what_changed |
| 109 << " change from " |
| 110 << event.old_value << " to " << new_value << endl; |
| 111 } |
| 112 |
| 113 typedef vector<EventListenerHookup*> Hookups; |
| 114 Hookups hookups_; |
| 115 ostream& out_; |
| 116 }; |
| 117 |
| 118 const char golden_result[] = "Larry heard Sally's B change from 0 to 2\n" |
| 119 "Larry heard Sally's A change from 1 to 3\n" |
| 120 "Lewis heard Sam's B change from 0 to 5\n" |
| 121 "Larry heard Sally's A change from 3 to 6\n" |
| 122 "Larry heard Sally being deleted.\n"; |
| 123 |
| 124 TEST(EventSys, Basic) { |
| 125 Pair sally("Sally"), sam("Sam"); |
| 126 sally.set_a(1); |
| 127 stringstream log; |
| 128 EventLogger logger(log); |
| 129 logger.Hookup("Larry", sally.event_channel()); |
| 130 sally.set_b(2); |
| 131 sally.set_a(3); |
| 132 sam.set_a(4); |
| 133 logger.Hookup("Lewis", sam.event_channel()); |
| 134 sam.set_b(5); |
| 135 sally.set_a(6); |
| 136 // Test that disconnect within callback doesn't deadlock. |
| 137 TestEvent event = {&sally, TestEvent::PAIR_BEING_DELETED, 0 }; |
| 138 sally.event_channel()->NotifyListeners(event); |
| 139 sally.set_a(7); |
| 140 ASSERT_EQ(log.str(), golden_result); |
| 141 } |
| 142 |
| 143 |
| 144 // This goes pretty far beyond the normal use pattern, so don't use |
| 145 // ThreadTester as an example of what to do. |
| 146 class ThreadTester : public EventListener<TestEvent> { |
| 147 public: |
| 148 explicit ThreadTester(Pair* pair) |
| 149 : pair_(pair), remove_event_bool_(false) { |
| 150 pair_->event_channel()->AddListener(this); |
| 151 } |
| 152 ~ThreadTester() { |
| 153 pair_->event_channel()->RemoveListener(this); |
| 154 for (int i = 0; i < threads_.size(); i++) { |
| 155 CHECK(pthread_join(threads_[i].thread, NULL) == 0); |
| 156 delete threads_[i].completed; |
| 157 } |
| 158 } |
| 159 |
| 160 struct ThreadInfo { |
| 161 pthread_t thread; |
| 162 bool *completed; |
| 163 }; |
| 164 |
| 165 struct ThreadArgs { |
| 166 ThreadTester* self; |
| 167 pthread_cond_t *thread_running_cond; |
| 168 pthread_mutex_t *thread_running_mutex; |
| 169 bool *thread_running; |
| 170 bool *completed; |
| 171 }; |
| 172 |
| 173 pthread_t Go() { |
| 174 PThreadCondVar thread_running_cond; |
| 175 PThreadMutex thread_running_mutex; |
| 176 ThreadArgs args; |
| 177 ThreadInfo info; |
| 178 info.completed = new bool(false); |
| 179 args.self = this; |
| 180 args.completed = info.completed; |
| 181 args.thread_running_cond = &(thread_running_cond.condvar_); |
| 182 args.thread_running_mutex = &(thread_running_mutex.mutex_); |
| 183 args.thread_running = new bool(false); |
| 184 CHECK(0 == |
| 185 pthread_create(&info.thread, NULL, ThreadTester::ThreadMain, &args)); |
| 186 thread_running_mutex.Lock(); |
| 187 while ((*args.thread_running) == false) { |
| 188 pthread_cond_wait(&(thread_running_cond.condvar_), |
| 189 &(thread_running_mutex.mutex_)); |
| 190 } |
| 191 thread_running_mutex.Unlock(); |
| 192 delete args.thread_running; |
| 193 threads_.push_back(info); |
| 194 return info.thread; |
| 195 } |
| 196 |
| 197 static void* ThreadMain(void* arg) { |
| 198 ThreadArgs args = *reinterpret_cast<ThreadArgs*>(arg); |
| 199 pthread_mutex_lock(args.thread_running_mutex); |
| 200 *args.thread_running = true; |
| 201 pthread_cond_signal(args.thread_running_cond); |
| 202 pthread_mutex_unlock(args.thread_running_mutex); |
| 203 |
| 204 args.self->remove_event_mutex_.Lock(); |
| 205 while (args.self->remove_event_bool_ == false) { |
| 206 pthread_cond_wait(&args.self->remove_event_.condvar_, |
| 207 &args.self->remove_event_mutex_.mutex_); |
| 208 } |
| 209 args.self->remove_event_mutex_.Unlock(); |
| 210 |
| 211 // Normally, you'd just delete the hookup. This is very bad style, but |
| 212 // necessary for the test. |
| 213 args.self->pair_->event_channel()->RemoveListener(args.self); |
| 214 *args.completed = true; |
| 215 return 0; |
| 216 } |
| 217 |
| 218 void HandleEvent(const TestEvent& event) { |
| 219 remove_event_mutex_.Lock(); |
| 220 remove_event_bool_ = true; |
| 221 pthread_cond_broadcast(&remove_event_.condvar_); |
| 222 remove_event_mutex_.Unlock(); |
| 223 |
| 224 // Windows and posix use different functions to sleep. |
| 225 #ifdef OS_WINDOWS |
| 226 Sleep(1); |
| 227 #else |
| 228 sleep(1); |
| 229 #endif |
| 230 |
| 231 for (int i = 0; i < threads_.size(); i++) { |
| 232 if (*(threads_[i].completed)) |
| 233 LOG(FATAL) << "A test thread exited too early."; |
| 234 } |
| 235 } |
| 236 |
| 237 Pair* pair_; |
| 238 PThreadCondVar remove_event_; |
| 239 PThreadMutex remove_event_mutex_; |
| 240 bool remove_event_bool_; |
| 241 vector<ThreadInfo> threads_; |
| 242 }; |
| 243 |
| 244 TEST(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 |
| 252 class HookupDeleter { |
| 253 public: |
| 254 void HandleEvent(const TestEvent& event) { |
| 255 delete hookup_; |
| 256 hookup_ = NULL; |
| 257 } |
| 258 EventListenerHookup* hookup_; |
| 259 }; |
| 260 |
| 261 TEST(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 |
OLD | NEW |