// Copyright (c) 2010 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/message_loop.h" #include "base/port.h" #include "base/threading/platform_thread.h" #include "build/build_config.h" #include "chrome/common/deprecated/event_sys-inl.h" #include "testing/gtest/include/gtest/gtest.h" 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 std::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 std::string& name() { return name_; } Channel* event_channel() const { return event_channel_; } protected: const std::string name_; int a_; int b_; Channel* event_channel_; }; class EventLogger { public: explicit EventLogger(std::ostream* out) : out_(out) { } ~EventLogger() { for (Hookups::iterator i = hookups_.begin(); i != hookups_.end(); ++i) delete *i; } void Hookup(const std::string name, Pair::Channel* channel) { hookups_.push_back(NewEventListenerHookup(channel, this, &EventLogger::HandlePairEvent, name)); } void HandlePairEvent(const std::string& name, const TestEvent& event) { const char* what_changed = NULL; int new_value = 0; 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." << std::endl; return; default: FAIL() << "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 << std::endl; } typedef std::vector<EventListenerHookup*> Hookups; Hookups hookups_; std::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); std::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 base::PlatformThread::Delegate { public: explicit ThreadTester(Pair* pair) : pair_(pair), remove_event_(&remove_event_mutex_), remove_event_bool_(false), completed_(false) { pair_->event_channel()->AddListener(this); } ~ThreadTester() { pair_->event_channel()->RemoveListener(this); for (size_t i = 0; i < threads_.size(); i++) { base::PlatformThread::Join(threads_[i].thread); } } struct ThreadInfo { base::PlatformThreadHandle thread; }; struct ThreadArgs { base::ConditionVariable* thread_running_cond; base::Lock* thread_running_mutex; bool thread_running; }; void Go() { base::Lock thread_running_mutex; base::ConditionVariable thread_running_cond(&thread_running_mutex); ThreadArgs args; ThreadInfo info; args.thread_running_cond = &(thread_running_cond); args.thread_running_mutex = &(thread_running_mutex); args.thread_running = false; args_ = args; ASSERT_TRUE(base::PlatformThread::Create(0, this, &info.thread)); thread_running_mutex.Acquire(); while ((args_.thread_running) == false) { thread_running_cond.Wait(); } thread_running_mutex.Release(); threads_.push_back(info); } // PlatformThread::Delegate methods. virtual void ThreadMain() { // Make sure each thread gets a current MessageLoop in TLS. // This test should use chrome threads for testing, but I'll leave it like // this for the moment since it requires a big chunk of rewriting and I // want the test passing while I checkpoint my CL. Technically speaking, // there should be no functional difference. MessageLoop message_loop; args_.thread_running_mutex->Acquire(); args_.thread_running = true; args_.thread_running_cond->Signal(); args_.thread_running_mutex->Release(); remove_event_mutex_.Acquire(); while (remove_event_bool_ == false) { remove_event_.Wait(); } remove_event_mutex_.Release(); // Normally, you'd just delete the hookup. This is very bad style, but // necessary for the test. pair_->event_channel()->RemoveListener(this); completed_mutex_.Acquire(); completed_ = true; completed_mutex_.Release(); } void HandleEvent(const TestEvent& event) { remove_event_mutex_.Acquire(); remove_event_bool_ = true; remove_event_.Broadcast(); remove_event_mutex_.Release(); base::PlatformThread::YieldCurrentThread(); completed_mutex_.Acquire(); if (completed_) FAIL() << "A test thread exited too early."; completed_mutex_.Release(); } Pair* pair_; base::ConditionVariable remove_event_; base::Lock remove_event_mutex_; bool remove_event_bool_; base::Lock completed_mutex_; bool completed_; std::vector<ThreadInfo> threads_; ThreadArgs args_; }; 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