~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Aaron Bentley
  • Date: 2012-01-13 11:46:14 UTC
  • Revision ID: aaron@canonical.com-20120113114614-lw5t8rcn4cidlb3o
Support thread_newest threading.

Show diffs side-by-side

added added

removed removed

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