~unity-2d-team/unity-2d/Shell-MultiMonitor

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Curtis Hovey
  • Date: 2012-01-30 18:30:54 UTC
  • mto: (6.1.30 trunk)
  • mto: This revision was merged to the branch mainline in revision 45.
  • Revision ID: curtis.hovey@canonical.com-20120130183054-lyjgaxcekr8efuou
Hush lint.

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
    BaseHTTPRequestHandler,
4
4
    )
5
5
import httplib
 
6
import logging
6
7
import os
7
8
from signal import SIGKILL
 
9
import simplejson
8
10
from StringIO import StringIO
 
11
import sys
9
12
from unittest import TestCase
 
13
from urlparse import urlparse
 
14
from urlparse import parse_qs
10
15
 
11
16
from testtools import ExpectedException
12
17
 
13
 
from grackle import client
14
 
 
15
 
 
16
 
class Forked:
17
 
 
18
 
    def __init__(self, func_or_method):
19
 
        self.func_or_method = func_or_method
 
18
from grackle.client import (
 
19
    GrackleClient,
 
20
    UnsupportedOrder,
 
21
    )
 
22
 
 
23
 
 
24
def threaded_messages(messages):
 
25
    threads = {}
 
26
    count = 0
 
27
    pending = []
 
28
    for message in messages:
 
29
        if message.get('in_reply_to') is None:
 
30
            threads[message['message_id']] = [message]
 
31
            count += 1
 
32
        else:
 
33
            pending.append(message)
 
34
    for message in pending:
 
35
        threads[message['in_reply_to']].append(message)
 
36
    return threads.values()
 
37
 
 
38
 
 
39
class GrackleStore:
 
40
    """A memory-backed message store."""
 
41
 
 
42
    def __init__(self, messages):
 
43
        """Constructor."""
 
44
        self.messages = messages
 
45
 
 
46
    def get_messages(self, archive_id, query_string):
 
47
        """Return matching messages.
 
48
 
 
49
        :param archive_id: The archive to retrieve from.
 
50
        :param query_string: Contains 'parameters', which is a JSON-format
 
51
            string describing parameters.
 
52
        """
 
53
        query = parse_qs(query_string)
 
54
        parameters = simplejson.loads(query['parameters'][0])
 
55
        order = parameters.get('order')
 
56
        messages = self.messages[archive_id]
 
57
        if order is not None:
 
58
            if order not in SUPPORTED_ORDERS:
 
59
                raise UnsupportedOrder
 
60
            elif order.startswith('thread_'):
 
61
                threaded = threaded_messages(messages)
 
62
                messages = []
 
63
                if order == 'thread_subject':
 
64
                    threaded.sort(key=lambda t: t[0]['subject'])
 
65
                if order == 'thread_oldest':
 
66
                    threaded.sort(key=lambda t: min(m['date'] for m in t))
 
67
                if order == 'thread_newest':
 
68
                    threaded.sort(key=lambda t: max(m['date'] for m in t))
 
69
                for thread in threaded:
 
70
                    messages.extend(thread)
 
71
            else:
 
72
                messages.sort(key=lambda m: m[order])
 
73
        new_messages = []
 
74
        for message in messages:
 
75
            if (not parameters['include_hidden']
 
76
                and message.get('hidden', False)):
 
77
                continue
 
78
 
 
79
            if ('message_ids' in parameters
 
80
                and message['message_id'] not in parameters['message_ids']):
 
81
                continue
 
82
            message = dict(message)
 
83
            if 'headers' in parameters:
 
84
                headers = dict(
 
85
                    (k, v) for k, v in message['headers'].iteritems()
 
86
                    if k in parameters['headers'])
 
87
                message['headers'] = headers
 
88
            max_body = parameters.get('max_body_length')
 
89
            if max_body is not None:
 
90
                message['body'] = message['body'][:max_body]
 
91
            new_messages.append(message)
 
92
        messages = new_messages
 
93
        limit = parameters.get('limit', 100)
 
94
        memo = parameters.get('memo')
 
95
        message_id_indices = dict(
 
96
            (m['message_id'], idx) for idx, m in enumerate(messages))
 
97
        if memo is None:
 
98
            start = 0
 
99
        else:
 
100
            start = message_id_indices[memo.encode('rot13')]
 
101
        if start > 0:
 
102
            previous_memo = messages[start - 1]['message_id'].encode('rot13')
 
103
        else:
 
104
            previous_memo = None
 
105
        end = min(start + limit, len(messages))
 
106
        if end < len(messages):
 
107
            next_memo = messages[end]['message_id'].encode('rot13')
 
108
        else:
 
109
            next_memo = None
 
110
        messages = messages[start:end]
 
111
 
 
112
        response = {
 
113
            'messages': messages,
 
114
            'next_memo': next_memo,
 
115
            'previous_memo': previous_memo
 
116
            }
 
117
        return response
 
118
 
 
119
 
 
120
class ForkedFake:
 
121
    """A Grackle service fake, as a ContextManager."""
 
122
 
 
123
    def __init__(self, port, messages=None, write_logs=False):
 
124
        """Constructor.
 
125
        :param port: The tcp port to use
 
126
        :param messages: A dict of lists of dicts representing messages.  The
 
127
            outer dict represents the archive, the list represents the list of
 
128
            messages for that archive.
 
129
        :param write_logs: If true, log messages will be written to stdout.
 
130
        """
20
131
        self.pid = None
 
132
        self.port = port
 
133
        if messages is None:
 
134
            self.messages = {}
 
135
        else:
 
136
            self.messages = messages
 
137
        self.read_end, self.write_end = os.pipe()
 
138
        self.write_logs = write_logs
 
139
 
 
140
    @staticmethod
 
141
    def from_client(client, messages=None):
 
142
        """Instantiate a ForkedFake from the client.
 
143
 
 
144
        :param port: The client  to provide service for.
 
145
        :param messages: A dict of lists of dicts representing messages.  The
 
146
            outer dict represents the archive, the list represents the list of
 
147
            messages for that archive.
 
148
        """
 
149
        return ForkedFake(client.port, messages)
 
150
 
 
151
    def is_ready(self):
 
152
        """Tell the parent process that the server is ready for writes."""
 
153
        os.write(self.write_end, 'asdf')
21
154
 
22
155
    def __enter__(self):
 
156
        """Run the service.
 
157
 
 
158
        Fork and start a server in the child.  Return when the server is ready
 
159
        for use."""
23
160
        pid = os.fork()
24
 
        if pid != 0:
25
 
            self.pid = pid
26
 
            return
27
 
        self.func_or_method()
 
161
        if pid == 0:
 
162
            self.start_server()
 
163
        self.pid = pid
 
164
        os.read(self.read_end, 1)
 
165
        return
28
166
 
 
167
    def start_server(self):
 
168
        """Start the HTTP server."""
 
169
        service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
 
170
        service.store = GrackleStore(self.messages)
 
171
        for archive_id, messages in service.store.messages.iteritems():
 
172
            for message in messages:
 
173
                message.setdefault('headers', {})
 
174
        self.is_ready()
 
175
        if self.write_logs:
 
176
            logging.basicConfig(
 
177
                stream=sys.stderr, level=logging.INFO)
 
178
        service.serve_forever()
29
179
 
30
180
    def __exit__(self, exc_type, exc_val, traceback):
31
181
        os.kill(self.pid, SIGKILL)
32
182
 
33
183
 
 
184
SUPPORTED_ORDERS = set(
 
185
    ['date', 'author', 'subject', 'thread_newest', 'thread_oldest',
 
186
     'thread_subject'])
 
187
 
 
188
 
34
189
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
 
190
    """A request handler that forwards to server.store."""
 
191
 
 
192
    def __init__(self, *args, **kwargs):
 
193
        """Constructor.  Sets up logging."""
 
194
        self.logger = logging.getLogger('http')
 
195
        BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
35
196
 
36
197
    def do_POST(self):
 
198
        """Create a message on POST."""
37
199
        message = self.rfile.read(int(self.headers['content-length']))
38
200
        if message == 'This is a message':
39
201
            self.send_response(httplib.CREATED)
42
204
        else:
43
205
            self.send_error(httplib.BAD_REQUEST)
44
206
 
45
 
 
46
 
def run_service():
47
 
    service = HTTPServer(('', 8435), FakeGrackleRequestHandler)
48
 
    service.serve_forever()
49
 
 
 
207
    def do_GET(self):
 
208
        """Retrieve a list of messages on GET."""
 
209
        scheme, netloc, path, params, query_string, fragments = (
 
210
            urlparse(self.path))
 
211
        parts = path.split('/')
 
212
        if parts[1] == 'archive':
 
213
            try:
 
214
                response = self.server.store.get_messages(
 
215
                    parts[2], query_string)
 
216
                self.send_response(httplib.OK)
 
217
                self.end_headers()
 
218
                self.wfile.write(simplejson.dumps(response))
 
219
            except UnsupportedOrder:
 
220
                self.send_response(httplib.BAD_REQUEST)
 
221
                self.wfile.write('Unsupported order')
 
222
                return
 
223
 
 
224
    def log_message(self, format, *args):
 
225
        """Override log_message to use standard Python logging."""
 
226
        message = "%s - - [%s] %s\n" % (
 
227
            self.address_string(), self.log_date_time_string(), format % args)
 
228
        self.logger.info(message)
50
229
 
51
230
 
52
231
class TestPutMessage(TestCase):
53
232
 
54
233
    def test_put_message(self):
55
 
        with Forked(run_service):
56
 
            client.put_message('arch1', StringIO('This is a message'))
 
234
        client = GrackleClient('localhost', 8436)
 
235
        with ForkedFake.from_client(client):
 
236
            client.put_message('arch1', 'asdf', StringIO('This is a message'))
57
237
            with ExpectedException(Exception, 'wtf'):
58
 
                client.put_message('arch1', StringIO('This is not a message'))
 
238
                client.put_message('arch1', 'asdf',
 
239
                    StringIO('This is not a message'))
 
240
 
 
241
 
 
242
class TestGetMessages(TestCase):
 
243
 
 
244
    def assertIDOrder(self, ids, messages):
 
245
        self.assertEqual(ids, [m['message_id'] for m in messages])
 
246
 
 
247
    def assertMessageIDs(self, ids, messages):
 
248
        self.assertIDOrder(
 
249
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
 
250
 
 
251
    def test_get_messages(self):
 
252
        client = GrackleClient('localhost', 8435)
 
253
        with ForkedFake.from_client(client,
 
254
            {'baz':
 
255
            [{'message_id': 'foo'},
 
256
             {'message_id': 'bar'}]}):
 
257
            response = client.get_messages('baz')
 
258
        self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
 
259
            response['messages']))
 
260
        self.assertIs(None, response['next_memo'])
 
261
        self.assertIs(None, response['previous_memo'])
 
262
 
 
263
    def test_get_messages_by_id(self):
 
264
        client = GrackleClient('localhost', 8437)
 
265
        with ForkedFake.from_client(client,
 
266
            {'baz':
 
267
            [{'message_id': 'foo'},
 
268
             {'message_id': 'bar'}]}):
 
269
            response = client.get_messages('baz', message_ids=['foo'])
 
270
        message, = response['messages']
 
271
        self.assertEqual('foo', message['message_id'])
 
272
 
 
273
    def test_get_messages_batching(self):
 
274
        client = GrackleClient('localhost', 8438)
 
275
        with ForkedFake.from_client(client,
 
276
            {'baz':
 
277
            [{'message_id': 'foo'},
 
278
             {'message_id': 'bar'}]}):
 
279
            response = client.get_messages('baz', limit=1)
 
280
            self.assertEqual(1, len(response['messages']))
 
281
            messages = response['messages']
 
282
            response = client.get_messages(
 
283
                'baz', limit=1, memo=response['next_memo'])
 
284
            self.assertEqual(1, len(response['messages']))
 
285
            messages.extend(response['messages'])
 
286
            self.assertMessageIDs(['foo', 'bar'], messages)
 
287
 
 
288
    def get_messages_member_order_test(self, key):
 
289
        client = GrackleClient('localhost', 8439)
 
290
        with ForkedFake.from_client(client,
 
291
                {'baz': [{'message_id': 'foo', key: '2011-03-25'},
 
292
                 {'message_id': 'bar', key: '2011-03-24'}]}):
 
293
            response = client.get_messages('baz')
 
294
            self.assertIDOrder(['foo', 'bar'], response['messages'])
 
295
            response = client.get_messages('baz', order=key)
 
296
            self.assertIDOrder(['bar', 'foo'], response['messages'])
 
297
 
 
298
    def test_get_messages_date_order(self):
 
299
        self.get_messages_member_order_test('date')
 
300
 
 
301
    def test_get_messages_author_order(self):
 
302
        self.get_messages_member_order_test('author')
 
303
 
 
304
    def test_get_messages_subject_order(self):
 
305
        self.get_messages_member_order_test('subject')
 
306
 
 
307
    def test_get_messages_thread_subject_order(self):
 
308
        client = GrackleClient('localhost', 8439)
 
309
        with ForkedFake.from_client(client, {'baz': [
 
310
            {'message_id': 'bar', 'subject': 'y'},
 
311
            {'message_id': 'qux', 'subject': 'z'},
 
312
            {'message_id': 'foo', 'subject': 'x', 'in_reply_to': 'qux'},
 
313
            ]}):
 
314
            response = client.get_messages('baz')
 
315
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
316
            response = client.get_messages('baz', order='subject')
 
317
            self.assertIDOrder(['foo', 'bar', 'qux'], response['messages'])
 
318
            response = client.get_messages('baz', order='thread_subject')
 
319
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
320
 
 
321
    def test_get_messages_thread_oldest_order(self):
 
322
        client = GrackleClient('localhost', 8439)
 
323
        with ForkedFake.from_client(client, {'baz': [
 
324
            {'message_id': 'bar', 'date': 'x'},
 
325
            {'message_id': 'qux', 'date': 'z'},
 
326
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'qux'},
 
327
            ]}):
 
328
            response = client.get_messages('baz')
 
329
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
330
            response = client.get_messages('baz', order='date')
 
331
            self.assertIDOrder(['bar', 'foo', 'qux'], response['messages'])
 
332
            response = client.get_messages('baz', order='thread_oldest')
 
333
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
334
 
 
335
    def test_get_messages_thread_newest_order(self):
 
336
        client = GrackleClient('localhost', 8439)
 
337
        with ForkedFake.from_client(client, {'baz': [
 
338
            {'message_id': 'bar', 'date': 'x'},
 
339
            {'message_id': 'qux', 'date': 'w'},
 
340
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'bar'},
 
341
            {'message_id': 'baz', 'date': 'z', 'in_reply_to': 'qux'},
 
342
            ]}):
 
343
            response = client.get_messages('baz', order='date')
 
344
            self.assertIDOrder(
 
345
                ['qux', 'bar', 'foo', 'baz'], response['messages'])
 
346
            response = client.get_messages('baz', order='thread_newest')
 
347
            self.assertIDOrder(
 
348
                ['bar', 'foo', 'qux', 'baz'], response['messages'])
 
349
 
 
350
    def test_get_messages_unsupported_order(self):
 
351
        client = GrackleClient('localhost', 8439)
 
352
        with ForkedFake.from_client(client,
 
353
                {'baz': [{'message_id': 'foo', 'date': '2011-03-25'},
 
354
                 {'message_id': 'bar', 'date': '2011-03-24'}]}):
 
355
            with ExpectedException(UnsupportedOrder, ''):
 
356
                client.get_messages('baz', order='nonsense')
 
357
 
 
358
    def test_get_messages_headers_no_headers(self):
 
359
        client = GrackleClient('localhost', 8440)
 
360
        with ForkedFake.from_client(client,
 
361
            {'baz': [
 
362
                {'message_id': 'foo'}
 
363
            ]}):
 
364
            response = client.get_messages('baz', headers=[
 
365
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
 
366
        first_message = response['messages'][0]
 
367
        self.assertEqual('foo', first_message['message_id'])
 
368
        self.assertEqual({}, first_message['headers'])
 
369
 
 
370
    def test_get_messages_headers_exclude_headers(self):
 
371
        client = GrackleClient('localhost', 8441)
 
372
        with ForkedFake.from_client(client,
 
373
            {'baz': [
 
374
                {'message_id': 'foo', 'headers': {'From': 'me'}}
 
375
            ]}):
 
376
            response = client.get_messages('baz', headers=[
 
377
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
 
378
        first_message = response['messages'][0]
 
379
        self.assertEqual('foo', first_message['message_id'])
 
380
        self.assertEqual({}, first_message['headers'])
 
381
 
 
382
    def test_get_messages_headers_include_headers(self):
 
383
        client = GrackleClient('localhost', 8442)
 
384
        with ForkedFake.from_client(client,
 
385
            {'baz': [
 
386
                {'message_id': 'foo', 'headers': {'From': 'me', 'To': 'you'}}
 
387
            ]}):
 
388
            response = client.get_messages('baz', headers=[
 
389
                'From', 'To'])
 
390
        first_message = response['messages'][0]
 
391
        self.assertEqual('foo', first_message['message_id'])
 
392
        self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
 
393
 
 
394
    def test_get_messages_max_body_length(self):
 
395
        client = GrackleClient('localhost', 8443)
 
396
        with ForkedFake.from_client(client,
 
397
            {'baz': [
 
398
                {'message_id': 'foo', 'body': u'abcdefghi'}
 
399
            ]}):
 
400
            response = client.get_messages('baz', max_body_length=3)
 
401
        first_message = response['messages'][0]
 
402
        self.assertEqual('abc', first_message['body'])
 
403
 
 
404
    def test_include_hidden(self):
 
405
        client = GrackleClient('localhost', 8444)
 
406
        with ForkedFake.from_client(client,
 
407
            {'baz': [
 
408
                {'message_id': 'foo', 'hidden': True},
 
409
                {'message_id': 'bar', 'hidden': False}
 
410
            ]}):
 
411
            response = client.get_messages('baz', include_hidden=True)
 
412
            self.assertMessageIDs(['bar', 'foo'], response['messages'])
 
413
            response = client.get_messages('baz', include_hidden=False)
 
414
            self.assertMessageIDs(['bar'], response['messages'])