| // 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 |