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