Index: chrome/browser/sync/util/event_sys_unittest.cc |
=================================================================== |
--- chrome/browser/sync/util/event_sys_unittest.cc (revision 0) |
+++ chrome/browser/sync/util/event_sys_unittest.cc (revision 0) |
@@ -0,0 +1,271 @@ |
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <iosfwd> |
+#include <sstream> |
+#include <string> |
+#include <vector> |
+ |
+#include "base/basictypes.h" |
+#include "base/logging.h" |
+#include "base/port.h" |
+#include "chrome/browser/sync/util/event_sys-inl.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+using std::endl; |
+using std::ostream; |
+using std::string; |
+using std::stringstream; |
+using std::vector; |
+ |
+namespace { |
+ |
+class Pair; |
+ |
+struct TestEvent { |
+ Pair* source; |
+ enum { |
+ A_CHANGED, B_CHANGED, PAIR_BEING_DELETED, |
+ } what_happened; |
+ int old_value; |
+}; |
+ |
+struct TestEventTraits { |
+ typedef TestEvent EventType; |
+ static bool IsChannelShutdownEvent(const TestEvent& event) { |
+ return TestEvent::PAIR_BEING_DELETED == event.what_happened; |
+ } |
+}; |
+ |
+class Pair { |
+ public: |
+ typedef EventChannel<TestEventTraits> Channel; |
+ explicit Pair(const string& name) : name_(name), a_(0), b_(0) { |
+ TestEvent shutdown = { this, TestEvent::PAIR_BEING_DELETED, 0 }; |
+ event_channel_ = new Channel(shutdown); |
+ } |
+ ~Pair() { |
+ delete event_channel_; |
+ } |
+ void set_a(int n) { |
+ TestEvent event = { this, TestEvent::A_CHANGED, a_ }; |
+ a_ = n; |
+ event_channel_->NotifyListeners(event); |
+ } |
+ void set_b(int n) { |
+ TestEvent event = { this, TestEvent::B_CHANGED, b_ }; |
+ b_ = n; |
+ event_channel_->NotifyListeners(event); |
+ } |
+ int a() const { return a_; } |
+ int b() const { return b_; } |
+ const string& name() { return name_; } |
+ Channel* event_channel() const { return event_channel_; } |
+ |
+ protected: |
+ const string name_; |
+ int a_; |
+ int b_; |
+ Channel* event_channel_; |
+}; |
+ |
+class EventLogger { |
+ public: |
+ explicit EventLogger(ostream& out) : out_(out) { } |
+ ~EventLogger() { |
+ for (Hookups::iterator i = hookups_.begin(); i != hookups_.end(); ++i) |
+ delete *i; |
+ } |
+ |
+ void Hookup(const string name, Pair::Channel* channel) { |
+ hookups_.push_back(NewEventListenerHookup(channel, this, |
+ &EventLogger::HandlePairEvent, |
+ name)); |
+ } |
+ |
+ void HandlePairEvent(const string& name, const TestEvent& event) { |
+ const char* what_changed; |
+ int new_value; |
+ Hookups::iterator dead; |
+ switch (event.what_happened) { |
+ case TestEvent::A_CHANGED: |
+ what_changed = "A"; |
+ new_value = event.source->a(); |
+ break; |
+ case TestEvent::B_CHANGED: |
+ what_changed = "B"; |
+ new_value = event.source->b(); |
+ break; |
+ case TestEvent::PAIR_BEING_DELETED: |
+ out_ << name << " heard " << event.source->name() << " being deleted." |
+ << endl; |
+ return; |
+ default: |
+ LOG(FATAL) << "Bad event.what_happened: " << event.what_happened; |
+ break; |
+ } |
+ out_ << name << " heard " << event.source->name() << "'s " << what_changed |
+ << " change from " |
+ << event.old_value << " to " << new_value << endl; |
+ } |
+ |
+ typedef vector<EventListenerHookup*> Hookups; |
+ Hookups hookups_; |
+ ostream& out_; |
+}; |
+ |
+const char golden_result[] = "Larry heard Sally's B change from 0 to 2\n" |
+"Larry heard Sally's A change from 1 to 3\n" |
+"Lewis heard Sam's B change from 0 to 5\n" |
+"Larry heard Sally's A change from 3 to 6\n" |
+"Larry heard Sally being deleted.\n"; |
+ |
+TEST(EventSys, Basic) { |
+ Pair sally("Sally"), sam("Sam"); |
+ sally.set_a(1); |
+ stringstream log; |
+ EventLogger logger(log); |
+ logger.Hookup("Larry", sally.event_channel()); |
+ sally.set_b(2); |
+ sally.set_a(3); |
+ sam.set_a(4); |
+ logger.Hookup("Lewis", sam.event_channel()); |
+ sam.set_b(5); |
+ sally.set_a(6); |
+ // Test that disconnect within callback doesn't deadlock. |
+ TestEvent event = {&sally, TestEvent::PAIR_BEING_DELETED, 0 }; |
+ sally.event_channel()->NotifyListeners(event); |
+ sally.set_a(7); |
+ ASSERT_EQ(log.str(), golden_result); |
+} |
+ |
+ |
+// This goes pretty far beyond the normal use pattern, so don't use |
+// ThreadTester as an example of what to do. |
+class ThreadTester : public EventListener<TestEvent> { |
+ public: |
+ explicit ThreadTester(Pair* pair) |
+ : pair_(pair), remove_event_bool_(false) { |
+ pair_->event_channel()->AddListener(this); |
+ } |
+ ~ThreadTester() { |
+ pair_->event_channel()->RemoveListener(this); |
+ for (int i = 0; i < threads_.size(); i++) { |
+ CHECK(pthread_join(threads_[i].thread, NULL) == 0); |
+ delete threads_[i].completed; |
+ } |
+ } |
+ |
+ struct ThreadInfo { |
+ pthread_t thread; |
+ bool *completed; |
+ }; |
+ |
+ struct ThreadArgs { |
+ ThreadTester* self; |
+ pthread_cond_t *thread_running_cond; |
+ pthread_mutex_t *thread_running_mutex; |
+ bool *thread_running; |
+ bool *completed; |
+ }; |
+ |
+ pthread_t Go() { |
+ PThreadCondVar thread_running_cond; |
+ PThreadMutex thread_running_mutex; |
+ ThreadArgs args; |
+ ThreadInfo info; |
+ info.completed = new bool(false); |
+ args.self = this; |
+ args.completed = info.completed; |
+ args.thread_running_cond = &(thread_running_cond.condvar_); |
+ args.thread_running_mutex = &(thread_running_mutex.mutex_); |
+ args.thread_running = new bool(false); |
+ CHECK(0 == |
+ pthread_create(&info.thread, NULL, ThreadTester::ThreadMain, &args)); |
+ thread_running_mutex.Lock(); |
+ while ((*args.thread_running) == false) { |
+ pthread_cond_wait(&(thread_running_cond.condvar_), |
+ &(thread_running_mutex.mutex_)); |
+ } |
+ thread_running_mutex.Unlock(); |
+ delete args.thread_running; |
+ threads_.push_back(info); |
+ return info.thread; |
+ } |
+ |
+ static void* ThreadMain(void* arg) { |
+ ThreadArgs args = *reinterpret_cast<ThreadArgs*>(arg); |
+ pthread_mutex_lock(args.thread_running_mutex); |
+ *args.thread_running = true; |
+ pthread_cond_signal(args.thread_running_cond); |
+ pthread_mutex_unlock(args.thread_running_mutex); |
+ |
+ args.self->remove_event_mutex_.Lock(); |
+ while (args.self->remove_event_bool_ == false) { |
+ pthread_cond_wait(&args.self->remove_event_.condvar_, |
+ &args.self->remove_event_mutex_.mutex_); |
+ } |
+ args.self->remove_event_mutex_.Unlock(); |
+ |
+ // Normally, you'd just delete the hookup. This is very bad style, but |
+ // necessary for the test. |
+ args.self->pair_->event_channel()->RemoveListener(args.self); |
+ *args.completed = true; |
+ return 0; |
+ } |
+ |
+ void HandleEvent(const TestEvent& event) { |
+ remove_event_mutex_.Lock(); |
+ remove_event_bool_ = true; |
+ pthread_cond_broadcast(&remove_event_.condvar_); |
+ remove_event_mutex_.Unlock(); |
+ |
+ // Windows and posix use different functions to sleep. |
+#ifdef OS_WINDOWS |
+ Sleep(1); |
+#else |
+ sleep(1); |
+#endif |
+ |
+ for (int i = 0; i < threads_.size(); i++) { |
+ if (*(threads_[i].completed)) |
+ LOG(FATAL) << "A test thread exited too early."; |
+ } |
+ } |
+ |
+ Pair* pair_; |
+ PThreadCondVar remove_event_; |
+ PThreadMutex remove_event_mutex_; |
+ bool remove_event_bool_; |
+ vector<ThreadInfo> threads_; |
+}; |
+ |
+TEST(EventSys, Multithreaded) { |
+ Pair sally("Sally"); |
+ ThreadTester a(&sally); |
+ for (int i = 0; i < 3; ++i) |
+ a.Go(); |
+ sally.set_b(99); |
+} |
+ |
+class HookupDeleter { |
+ public: |
+ void HandleEvent(const TestEvent& event) { |
+ delete hookup_; |
+ hookup_ = NULL; |
+ } |
+ EventListenerHookup* hookup_; |
+}; |
+ |
+TEST(EventSys, InHandlerDeletion) { |
+ Pair sally("Sally"); |
+ HookupDeleter deleter; |
+ deleter.hookup_ = NewEventListenerHookup(sally.event_channel(), |
+ &deleter, |
+ &HookupDeleter::HandleEvent); |
+ sally.set_a(1); |
+ ASSERT_TRUE(NULL == deleter.hookup_); |
+} |
+ |
+} // namespace |
Property changes on: chrome\browser\sync\util\event_sys_unittest.cc |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |