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