~stolowski/nux/merge-trunk

« back to all changes in this revision

Viewing changes to tests/gtest-nux-windowthread.cpp

  • Committer: Tarmac
  • Author(s): Sam Spilsbury
  • Date: 2013-02-25 19:20:17 UTC
  • mfrom: (756.2.2 nux.fix_1097281)
  • Revision ID: tarmac-20130225192017-48tyqzwi3b28zu73
Implement a message-passing system in nux::WindowThread and nux::ProgramFramework.

This makes it possible to define and implement protocols for WindowThreads to
communicate with each other in a thread-safe way without having to resort to
using window system events or the like.

New file descriptors can be added for watching with a watch-callback executed
inside the window thread with nux::WindowThread::WatchFdForEvents. Only
read-data is supported right now.

When there is new data available to be read, the provided FdWatchCallback will
be called by the window thread, and executed inside the window thread. From
there, clients can safely modify data contained by the WindowThread object.

Remove a watched file descriptor with UnwatchFd.

It is safe to add file descriptors for watching during the call to the provided
ThreadUserInitFunc in CreateGUIThread. This is because the main loop
implementation must be created before those file descriptors are added for
watching (particularly in the case of GLib, because nux::WindowThread::InitGlibLoop
will use the default GMainContext the first time it is called, and then create
a new one for each window thereafter.

Added three new tests to demonstrate and cover this functionality.

[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from TestWindowThread
[ RUN ] TestWindowThread.WatchFd
[ OK ] TestWindowThread.WatchFd (3213 ms)
[ RUN ] TestWindowThread.MultiWatchFd
[ OK ] TestWindowThread.MultiWatchFd (3163 ms)
[ RUN ] TestWindowThread.OneFdEvent
[ OK ] TestWindowThread.OneFdEvent (3155 ms)
[----------] 3 tests from TestWindowThread (9532 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test case ran. (9532 ms total)
[ PASSED ] 3 tests.

Fixed segfaulting test xtest-text-input. That test unsafely mutated object
state contained by the WindowThread running in another thread inside of the
test thread. This resulted in a null pointer dereference because it had a
side effect of calling nux::GetWindowThread () which relies on up-to-date
thread-local-storage. That test now defines a protocol to communicate to
the running program to mutate the relevant state, and then communicate back
when it has completed execution.

(LP: #1097281)
. Fixes: https://bugs.launchpad.net/bugs/1097281.

Approved by Brandon Schaefer.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#ifndef _GNU_SOURCE
 
2
#define _GNU_SOURCE
 
3
#endif
 
4
#include <fcntl.h>
 
5
#include <unistd.h>
 
6
#include <sys/poll.h>
 
7
 
1
8
#include <string>
2
9
#include <fstream>
3
10
 
10
17
 
11
18
 
12
19
using namespace testing;
 
20
using ::testing::Invoke;
13
21
 
14
22
namespace {
15
23
 
16
 
TEST(TestWindowThread, TestCreate)
17
 
{
18
 
  nux::NuxInitialize(0);
19
 
  nux::WindowThread *wnd_thread = nux::CreateNuxWindow("Nux Window", 300, 200,
20
 
    nux::WINDOWSTYLE_NORMAL, NULL, false, NULL, NULL);
21
 
 
 
24
class MockFdDispatch
 
25
{
 
26
  public:
 
27
    MOCK_METHOD0(dispatch, void());
 
28
 
 
29
    MockFdDispatch(int fd) :
 
30
      mFd (fd)
 
31
    {
 
32
      ON_CALL(*this, dispatch())
 
33
        .WillByDefault(Invoke(this, &MockFdDispatch::ClearStream));
 
34
    }
 
35
 
 
36
  private:
 
37
 
 
38
    void ClearStream()
 
39
    {
 
40
      char buf[2];
 
41
      if (read(mFd, reinterpret_cast <void *>(buf), 2) == -1)
 
42
        throw std::runtime_error(strerror(errno));
 
43
    }
 
44
 
 
45
    int mFd;
 
46
};
 
47
 
 
48
class TestWindowThread : public ::testing::Test
 
49
{
 
50
  public:
 
51
 
 
52
    TestWindowThread()
 
53
      : seconds_to_quit(0)
 
54
      , quit_thread (NULL)
 
55
    {
 
56
      nux::NuxInitialize(0);
 
57
      wnd_thread.reset (nux::CreateNuxWindow("Nux Window", 300, 200,
 
58
                                             nux::WINDOWSTYLE_NORMAL, NULL,
 
59
                                             false, TestWindowThread::WindowThreadInit,
 
60
                                             this));
 
61
 
 
62
      if (pipe2(quit_pipe_, O_CLOEXEC) == -1)
 
63
        throw std::runtime_error(strerror(errno));
 
64
 
 
65
      if (pipe2(test_quit_pipe_, O_CLOEXEC) == -1)
 
66
        throw std::runtime_error(strerror(errno));
 
67
 
 
68
      WatchFdForEventsOnRun(quit_pipe_[0],
 
69
                            std::bind(&TestWindowThread::QuitMessage,
 
70
                                      this));
 
71
 
 
72
 
 
73
    }
 
74
 
 
75
    ~TestWindowThread()
 
76
    {
 
77
      if (quit_thread)
 
78
        delete quit_thread;
 
79
 
 
80
      close(quit_pipe_[0]);
 
81
      close(quit_pipe_[1]);
 
82
      close(test_quit_pipe_[0]);
 
83
      close(test_quit_pipe_[1]);
 
84
    }
 
85
 
 
86
  protected:
 
87
 
 
88
    std::unique_ptr <nux::WindowThread> wnd_thread;
 
89
    unsigned int                        seconds_to_quit;
 
90
    nux::SystemThread                   *quit_thread;
 
91
 
 
92
    void WatchFdForEventsOnRun(int fd,
 
93
                               const nux::WindowThread::FdWatchCallback &cb)
 
94
    {
 
95
      WatchedFd wfd;
 
96
      wfd.fd = fd;
 
97
      wfd.callback = cb;
 
98
 
 
99
      watched_fds.push_back(wfd);
 
100
    }
 
101
 
 
102
    /* Watchdog threads are really not that great, but ping-ponging between
 
103
     * the window and application threads would over-complicate this test */
 
104
    void QuitAfter(unsigned int seconds)
 
105
    {
 
106
      seconds_to_quit = seconds;
 
107
      quit_thread = nux::CreateSystemThread(NULL, TestWindowThread::QuitTask, this);
 
108
      quit_thread->Start(NULL);
 
109
    }
 
110
 
 
111
    void WaitForQuit()
 
112
    {
 
113
      struct pollfd pfd;
 
114
 
 
115
      pfd.events = POLLIN;
 
116
      pfd.revents = 0;
 
117
      pfd.fd = test_quit_pipe_[0];
 
118
 
 
119
      if (poll(&pfd, 1, -1) == -1)
 
120
        throw std::runtime_error(strerror(errno));
 
121
 
 
122
      /* Something was written to the read end of our pipe,
 
123
       * we can continue now */
 
124
    }
 
125
 
 
126
  private:
 
127
 
 
128
    typedef struct _WatchedFd
 
129
    {
 
130
      int                                fd;
 
131
      nux::WindowThread::FdWatchCallback callback;
 
132
    } WatchedFd;
 
133
 
 
134
    static void WindowThreadInit(nux::NThread *, void *data)
 
135
    {
 
136
      TestWindowThread *tw = reinterpret_cast <TestWindowThread *>(data);
 
137
 
 
138
      /* Monitor all nominated fd's */
 
139
      for (std::vector<WatchedFd>::iterator it = tw->watched_fds.begin();
 
140
           it != tw->watched_fds.end();
 
141
           ++it)
 
142
      {
 
143
        tw->wnd_thread->WatchFdForEvents(it->fd, it->callback);
 
144
      }
 
145
    }
 
146
 
 
147
    static void QuitTask(nux::NThread *, void *data)
 
148
    {
 
149
      TestWindowThread *tw = reinterpret_cast <TestWindowThread *> (data);
 
150
 
 
151
      nux::SleepForMilliseconds(tw->seconds_to_quit * 1000);
 
152
 
 
153
      const char *buf = "w\0";
 
154
      if (write(tw->quit_pipe_[1],
 
155
                reinterpret_cast<void *>(const_cast<char *>(buf)),
 
156
                2) == -1)
 
157
        throw std::runtime_error(strerror(errno));
 
158
    }
 
159
 
 
160
    void QuitMessage()
 
161
    {
 
162
      wnd_thread->ExitMainLoop();
 
163
 
 
164
      /* Send a message indicating that the main loop
 
165
       * exited and we can safely continue */
 
166
      const char *buf = "w\0";
 
167
      if (write(test_quit_pipe_[1],
 
168
                reinterpret_cast<void *>(const_cast<char *>(buf)),
 
169
                2) == -1)
 
170
        throw std::runtime_error(strerror(errno));
 
171
    }
 
172
 
 
173
    std::vector<WatchedFd> watched_fds;
 
174
 
 
175
    int quit_pipe_[2];
 
176
    int test_quit_pipe_[2];
 
177
};
 
178
 
 
179
TEST_F(TestWindowThread, Create)
 
180
{
22
181
  ASSERT_TRUE(wnd_thread != NULL);
23
182
  EXPECT_EQ(wnd_thread->GetWindowTitle(), std::string("Nux Window"));
24
183
  EXPECT_EQ(wnd_thread->IsModalWindow(), false);
25
184
 
26
185
  EXPECT_EQ(wnd_thread->IsComputingLayout(), false);
27
186
  EXPECT_EQ(wnd_thread->IsInsideLayoutCycle(), false);
28
 
 
29
 
  delete wnd_thread;
 
187
}
 
188
 
 
189
TEST_F(TestWindowThread, WatchFd)
 
190
{
 
191
  int pipefd[2];
 
192
 
 
193
  if (pipe2 (pipefd, O_CLOEXEC) == -1)
 
194
    throw std::runtime_error(strerror(errno));
 
195
 
 
196
  MockFdDispatch dispatch(pipefd[0]);
 
197
 
 
198
  EXPECT_CALL(dispatch, dispatch());
 
199
 
 
200
  WatchFdForEventsOnRun(pipefd[0], std::bind(&MockFdDispatch::dispatch, &dispatch));
 
201
  const char *buf = "w\0";
 
202
  if (write(pipefd[1], reinterpret_cast <void *> (const_cast <char *> (buf)), 2) == -1)
 
203
    throw std::runtime_error (strerror(errno));
 
204
 
 
205
  QuitAfter(3);
 
206
  wnd_thread->Run(NULL);
 
207
 
 
208
  /* We must wait for quit before closing the pipes */
 
209
  WaitForQuit();
 
210
 
 
211
  close (pipefd[0]);
 
212
  close (pipefd[1]);
 
213
}
 
214
 
 
215
TEST_F(TestWindowThread, MultiWatchFd)
 
216
{
 
217
  int pipefd[2], pipefd2[2];
 
218
 
 
219
  if (pipe2 (pipefd, O_CLOEXEC) == -1)
 
220
    throw std::runtime_error(strerror(errno));
 
221
  if (pipe2 (pipefd2, O_CLOEXEC) == -1)
 
222
    throw std::runtime_error(strerror(errno));
 
223
 
 
224
  MockFdDispatch dispatch(pipefd[0]), dispatch2(pipefd2[0]);
 
225
 
 
226
  EXPECT_CALL(dispatch, dispatch());
 
227
  EXPECT_CALL(dispatch2, dispatch());
 
228
 
 
229
  WatchFdForEventsOnRun(pipefd[0], std::bind(&MockFdDispatch::dispatch, &dispatch));
 
230
  WatchFdForEventsOnRun(pipefd2[0], std::bind(&MockFdDispatch::dispatch, &dispatch2));
 
231
  const char *buf = "w\0";
 
232
  if (write(pipefd[1], reinterpret_cast <void *> (const_cast <char *> (buf)), 2) == -1)
 
233
    throw std::runtime_error (strerror(errno));
 
234
  if (write(pipefd2[1], reinterpret_cast <void *> (const_cast <char *> (buf)), 2) == -1)
 
235
    throw std::runtime_error (strerror(errno));
 
236
 
 
237
  QuitAfter(3);
 
238
  wnd_thread->Run(NULL);
 
239
 
 
240
  /* We must wait for quit before closing the pipes */
 
241
  WaitForQuit();
 
242
 
 
243
  close (pipefd[0]);
 
244
  close (pipefd[1]);
 
245
  close (pipefd2[0]);
 
246
  close (pipefd2[1]);
 
247
}
 
248
 
 
249
TEST_F(TestWindowThread, OneFdEvent)
 
250
{
 
251
  int pipefd[2], pipefd2[2];
 
252
 
 
253
  if (pipe2 (pipefd, O_CLOEXEC) == -1)
 
254
    throw std::runtime_error(strerror(errno));
 
255
  if (pipe2 (pipefd2, O_CLOEXEC) == -1)
 
256
    throw std::runtime_error(strerror(errno));
 
257
 
 
258
  MockFdDispatch dispatch(pipefd[0]), dispatch2(pipefd2[0]);
 
259
 
 
260
  EXPECT_CALL(dispatch, dispatch());
 
261
  EXPECT_CALL(dispatch2, dispatch()).Times(0);
 
262
 
 
263
  WatchFdForEventsOnRun(pipefd[0], std::bind(&MockFdDispatch::dispatch, &dispatch));
 
264
  WatchFdForEventsOnRun(pipefd2[0], std::bind(&MockFdDispatch::dispatch, &dispatch2));
 
265
  const char *buf = "w\0";
 
266
  if (write(pipefd[1], reinterpret_cast <void *> (const_cast <char *> (buf)), 2) == -1)
 
267
    throw std::runtime_error (strerror(errno));
 
268
 
 
269
  QuitAfter(3);
 
270
  wnd_thread->Run(NULL);
 
271
 
 
272
  /* We must wait for quit before closing the pipes */
 
273
  WaitForQuit();
 
274
 
 
275
  close (pipefd[0]);
 
276
  close (pipefd[1]);
 
277
  close (pipefd2[0]);
 
278
  close (pipefd2[1]);
30
279
}
31
280
 
32
281