~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/tests/test_load_test.py

  • Committer: Andrew Bennetts
  • Date: 2011-06-28 13:13:05 UTC
  • Revision ID: andrew.bennetts@canonical.com-20110628131305-skv4h2z02z3iw8wc
Extract some refactoring of TemplatedBranchView and BranchWSGIApp from the jsonify branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
#
 
12
 
 
13
"""Tests for the load testing code."""
 
14
 
 
15
import socket
 
16
import time
 
17
import threading
 
18
import Queue
 
19
 
 
20
from bzrlib import tests
 
21
from bzrlib.tests import http_server
 
22
 
 
23
from loggerhead import load_test
 
24
 
 
25
 
 
26
empty_script = """{
 
27
    "parameters": {},
 
28
    "requests": []
 
29
}"""
 
30
 
 
31
class TestRequestDescription(tests.TestCase):
 
32
 
 
33
    def test_init_from_dict(self):
 
34
        rd = load_test.RequestDescription({'thread': '10', 'relpath': '/foo'})
 
35
        self.assertEqual('10', rd.thread)
 
36
        self.assertEqual('/foo', rd.relpath)
 
37
 
 
38
    def test_default_thread_is_1(self):
 
39
        rd = load_test.RequestDescription({'relpath': '/bar'})
 
40
        self.assertEqual('1', rd.thread)
 
41
        self.assertEqual('/bar', rd.relpath)
 
42
 
 
43
 
 
44
_cur_time = time.time()
 
45
def one_sec_timer():
 
46
    """Every time this timer is called, it increments by 1 second."""
 
47
    global _cur_time
 
48
    _cur_time += 1.0
 
49
    return _cur_time
 
50
 
 
51
 
 
52
class NoopRequestWorker(load_test.RequestWorker):
 
53
 
 
54
    # Every call to _timer will increment by one
 
55
    _timer = staticmethod(one_sec_timer)
 
56
 
 
57
    # Ensure that process never does anything
 
58
    def process(self, url):
 
59
        return True
 
60
 
 
61
 
 
62
class TestRequestWorkerInfrastructure(tests.TestCase):
 
63
    """Tests various infrastructure bits, without doing actual requests."""
 
64
 
 
65
    def test_step_next_tracks_time(self):
 
66
        rt = NoopRequestWorker('id')
 
67
        rt.queue.put('item')
 
68
        rt.step_next()
 
69
        self.assertTrue(rt.queue.empty())
 
70
        self.assertEqual([('item', True, 1.0)], rt.stats)
 
71
 
 
72
    def test_step_multiple_items(self):
 
73
        rt = NoopRequestWorker('id')
 
74
        rt.queue.put('item')
 
75
        rt.step_next()
 
76
        rt.queue.put('next-item')
 
77
        rt.step_next()
 
78
        self.assertTrue(rt.queue.empty())
 
79
        self.assertEqual([('item', True, 1.0), ('next-item', True, 1.0)],
 
80
                         rt.stats)
 
81
 
 
82
    def test_step_next_does_nothing_for_noop(self):
 
83
        rt = NoopRequestWorker('id')
 
84
        rt.queue.put('item')
 
85
        rt.step_next()
 
86
        rt.queue.put('<noop>')
 
87
        rt.step_next()
 
88
        self.assertEqual([('item', True, 1.0)], rt.stats)
 
89
 
 
90
    def test_step_next_will_timeout(self):
 
91
        # We don't want step_next to block forever
 
92
        rt = NoopRequestWorker('id', blocking_time=0.001)
 
93
        self.assertRaises(Queue.Empty, rt.step_next)
 
94
 
 
95
    def test_run_stops_for_stop_event(self):
 
96
        rt = NoopRequestWorker('id', blocking_time=0.001, _queue_size=2)
 
97
        rt.queue.put('item1')
 
98
        rt.queue.put('item2')
 
99
        event = threading.Event()
 
100
        t = threading.Thread(target=rt.run, args=(event,))
 
101
        t.start()
 
102
        # Wait for the queue to be processed
 
103
        rt.queue.join()
 
104
        # Now we can queue up another one, and wait for it
 
105
        rt.queue.put('item3')
 
106
        rt.queue.join()
 
107
        # Now set the stopping event
 
108
        event.set()
 
109
        # Add another item to the queue, which might get processed, but the
 
110
        # next item won't
 
111
        rt.queue.put('item4')
 
112
        rt.queue.put('item5')
 
113
        t.join()
 
114
        self.assertEqual([('item1', True, 1.0), ('item2', True, 1.0),
 
115
                          ('item3', True, 1.0)],
 
116
                         rt.stats[:3])
 
117
        # The last event might be item4 or might be item3, the important thing
 
118
        # is that even though there are still queued events, we won't
 
119
        # process anything past the first
 
120
        self.assertNotEqual('item5', rt.stats[-1][0])
 
121
 
 
122
 
 
123
class TestRequestWorker(tests.TestCaseWithTransport):
 
124
 
 
125
    def setUp(self):
 
126
        super(TestRequestWorker, self).setUp()
 
127
        self.transport_readonly_server = http_server.HttpServer
 
128
 
 
129
    def test_request_items(self):
 
130
        rt = load_test.RequestWorker('id', blocking_time=0.01, _queue_size=2)
 
131
        self.build_tree(['file1', 'file2'])
 
132
        readonly_url1 = self.get_readonly_url('file1')
 
133
        self.assertStartsWith(readonly_url1, 'http://')
 
134
        readonly_url2 = self.get_readonly_url('file2')
 
135
        rt.queue.put(readonly_url1)
 
136
        rt.queue.put(readonly_url2)
 
137
        rt.step_next()
 
138
        rt.step_next()
 
139
        self.assertEqual(readonly_url1, rt.stats[0][0])
 
140
        self.assertEqual(readonly_url2, rt.stats[1][0])
 
141
 
 
142
    def test_request_nonexistant_items(self):
 
143
        rt = load_test.RequestWorker('id', blocking_time=0.01, _queue_size=2)
 
144
        readonly_url1 = self.get_readonly_url('no-file1')
 
145
        rt.queue.put(readonly_url1)
 
146
        rt.step_next()
 
147
        self.assertEqual(readonly_url1, rt.stats[0][0])
 
148
        self.assertEqual(False, rt.stats[0][1])
 
149
 
 
150
    def test_no_server(self):
 
151
        s = socket.socket()
 
152
        # Bind to a port, but don't listen on it
 
153
        s.bind(('localhost', 0))
 
154
        url = 'http://%s:%s/path' % s.getsockname()
 
155
        rt = load_test.RequestWorker('id', blocking_time=0.01, _queue_size=2)
 
156
        rt.queue.put(url)
 
157
        rt.step_next()
 
158
        self.assertEqual((url, False), rt.stats[0][:2])
 
159
 
 
160
 
 
161
class NoActionScript(load_test.ActionScript):
 
162
 
 
163
    _thread_class = NoopRequestWorker
 
164
    _default_blocking_timeout = 0.01
 
165
 
 
166
 
 
167
class TestActionScriptInfrastructure(tests.TestCase):
 
168
 
 
169
    def test_parse_requires_parameters_and_requests(self):
 
170
        self.assertRaises(ValueError,
 
171
            load_test.ActionScript.parse, '')
 
172
        self.assertRaises(ValueError,
 
173
            load_test.ActionScript.parse, '{}')
 
174
        self.assertRaises(ValueError,
 
175
            load_test.ActionScript.parse, '{"parameters": {}}')
 
176
        self.assertRaises(ValueError,
 
177
            load_test.ActionScript.parse, '{"requests": []}')
 
178
        load_test.ActionScript.parse(
 
179
            '{"parameters": {}, "requests": [], "comment": "section"}')
 
180
        script = load_test.ActionScript.parse(
 
181
            empty_script)
 
182
        self.assertIsNot(None, script)
 
183
 
 
184
    def test_parse_default_base_url(self):
 
185
        script = load_test.ActionScript.parse(empty_script)
 
186
        self.assertEqual('http://localhost:8080', script.base_url)
 
187
 
 
188
    def test_parse_find_base_url(self):
 
189
        script = load_test.ActionScript.parse(
 
190
            '{"parameters": {"base_url": "http://example.com"},'
 
191
            ' "requests": []}')
 
192
        self.assertEqual('http://example.com', script.base_url)
 
193
 
 
194
    def test_parse_default_blocking_timeout(self):
 
195
        script = load_test.ActionScript.parse(empty_script)
 
196
        self.assertEqual(60.0, script.blocking_timeout)
 
197
 
 
198
    def test_parse_find_blocking_timeout(self):
 
199
        script = load_test.ActionScript.parse(
 
200
            '{"parameters": {"blocking_timeout": 10.0},'
 
201
            ' "requests": []}'
 
202
        )
 
203
        self.assertEqual(10.0, script.blocking_timeout)
 
204
 
 
205
    def test_parse_finds_requests(self):
 
206
        script = load_test.ActionScript.parse(
 
207
            '{"parameters": {}, "requests": ['
 
208
            ' {"relpath": "/foo"},'
 
209
            ' {"relpath": "/bar"}'
 
210
            ' ]}')
 
211
        self.assertEqual(2, len(script._requests))
 
212
        self.assertEqual("/foo", script._requests[0].relpath)
 
213
        self.assertEqual("/bar", script._requests[1].relpath)
 
214
 
 
215
    def test__get_worker(self):
 
216
        script = NoActionScript()
 
217
        # If an id is found, then we should create it
 
218
        self.assertEqual({}, script._threads)
 
219
        worker = script._get_worker('id')
 
220
        self.assertTrue('id' in script._threads)
 
221
        # We should have set the blocking timeout
 
222
        self.assertEqual(script.blocking_timeout / 10.0,
 
223
                         worker.blocking_time)
 
224
 
 
225
        # Another request will return the identical object
 
226
        self.assertIs(worker, script._get_worker('id'))
 
227
 
 
228
        # And the stop event will stop the thread
 
229
        script.stop_and_join()
 
230
 
 
231
    def test__full_url(self):
 
232
        script = NoActionScript()
 
233
        self.assertEqual('http://localhost:8080/path',
 
234
                         script._full_url('/path'))
 
235
        self.assertEqual('http://localhost:8080/path/to/foo',
 
236
                         script._full_url('/path/to/foo'))
 
237
        script.base_url = 'http://example.com'
 
238
        self.assertEqual('http://example.com/path/to/foo',
 
239
                         script._full_url('/path/to/foo'))
 
240
        script.base_url = 'http://example.com/base'
 
241
        self.assertEqual('http://example.com/base/path/to/foo',
 
242
                         script._full_url('/path/to/foo'))
 
243
        script.base_url = 'http://example.com'
 
244
        self.assertEqual('http://example.com:8080/path',
 
245
                         script._full_url(':8080/path'))
 
246
 
 
247
    def test_single_threaded(self):
 
248
        script = NoActionScript.parse("""{
 
249
            "parameters": {"base_url": ""},
 
250
            "requests": [
 
251
                {"thread": "1", "relpath": "first"},
 
252
                {"thread": "1", "relpath": "second"},
 
253
                {"thread": "1", "relpath": "third"},
 
254
                {"thread": "1", "relpath": "fourth"}
 
255
            ]}""")
 
256
        script.run()
 
257
        worker = script._get_worker("1")
 
258
        self.assertEqual(["first", "second", "third", "fourth"],
 
259
                         [s[0] for s in worker.stats])
 
260
 
 
261
    def test_two_threads(self):
 
262
        script = NoActionScript.parse("""{
 
263
            "parameters": {"base_url": ""},
 
264
            "requests": [
 
265
                {"thread": "1", "relpath": "first"},
 
266
                {"thread": "2", "relpath": "second"},
 
267
                {"thread": "1", "relpath": "third"},
 
268
                {"thread": "2", "relpath": "fourth"}
 
269
            ]}""")
 
270
        script.run()
 
271
        worker = script._get_worker("1")
 
272
        self.assertEqual(["first", "third"],
 
273
                         [s[0] for s in worker.stats])
 
274
        worker = script._get_worker("2")
 
275
        self.assertEqual(["second", "fourth"],
 
276
                         [s[0] for s in worker.stats])
 
277
 
 
278
 
 
279
class TestActionScriptIntegration(tests.TestCaseWithTransport):
 
280
 
 
281
    def setUp(self):
 
282
        super(TestActionScriptIntegration, self).setUp()
 
283
        self.transport_readonly_server = http_server.HttpServer
 
284
 
 
285
    def test_full_integration(self):
 
286
        self.build_tree(['first', 'second', 'third', 'fourth'])
 
287
        url = self.get_readonly_url()
 
288
        script = load_test.ActionScript.parse("""{
 
289
            "parameters": {"base_url": "%s", "blocking_timeout": 2.0},
 
290
            "requests": [
 
291
                {"thread": "1", "relpath": "first"},
 
292
                {"thread": "2", "relpath": "second"},
 
293
                {"thread": "1", "relpath": "no-this"},
 
294
                {"thread": "2", "relpath": "or-this"},
 
295
                {"thread": "1", "relpath": "third"},
 
296
                {"thread": "2", "relpath": "fourth"}
 
297
            ]}""" % (url,))
 
298
        script.run()
 
299
        worker = script._get_worker("1")
 
300
        self.assertEqual([("first", True), ('no-this', False),
 
301
                          ("third", True)],
 
302
                         [(s[0].rsplit('/', 1)[1], s[1])
 
303
                          for s in worker.stats])
 
304
        worker = script._get_worker("2")
 
305
        self.assertEqual([("second", True), ('or-this', False),
 
306
                          ("fourth", True)],
 
307
                         [(s[0].rsplit('/', 1)[1], s[1])
 
308
                          for s in worker.stats])
 
309
 
 
310
 
 
311
class TestRunScript(tests.TestCaseWithTransport):
 
312
 
 
313
    def setUp(self):
 
314
        super(TestRunScript, self).setUp()
 
315
        self.transport_readonly_server = http_server.HttpServer
 
316
 
 
317
    def test_run_script(self):
 
318
        self.build_tree(['file1', 'file2', 'file3', 'file4'])
 
319
        url = self.get_readonly_url()
 
320
        self.build_tree_contents([('localhost.script', """{
 
321
    "parameters": {"base_url": "%s", "blocking_timeout": 0.1},
 
322
    "requests": [
 
323
        {"thread": "1", "relpath": "file1"},
 
324
        {"thread": "2", "relpath": "file2"},
 
325
        {"thread": "1", "relpath": "file3"},
 
326
        {"thread": "2", "relpath": "file4"}
 
327
    ]
 
328
}""" % (url,))])
 
329
        script = load_test.run_script('localhost.script')
 
330
        worker = script._threads["1"][0]
 
331
        self.assertEqual([("file1", True), ('file3', True)],
 
332
                         [(s[0].rsplit('/', 1)[1], s[1])
 
333
                          for s in worker.stats])
 
334
        worker = script._threads["2"][0]
 
335
        self.assertEqual([("file2", True), ("file4", True)],
 
336
                         [(s[0].rsplit('/', 1)[1], s[1])
 
337
                          for s in worker.stats])