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