diff options
Diffstat (limited to 'chrome/browser/sync/util/event_sys_unittest.cc')
-rw-r--r-- | chrome/browser/sync/util/event_sys_unittest.cc | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/chrome/browser/sync/util/event_sys_unittest.cc b/chrome/browser/sync/util/event_sys_unittest.cc new file mode 100644 index 0000000..5e521b1 --- /dev/null +++ b/chrome/browser/sync/util/event_sys_unittest.cc @@ -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 |