1
# Copyright 2011 Canonical Ltd
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.
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
13
"""Tests for the load testing code."""
20
from bzrlib import tests
21
from bzrlib.tests import http_server
23
from bzrlib.plugins.loggerhead.loggerhead import load_test
31
class TestRequestDescription(tests.TestCase):
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)
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)
44
_cur_time = time.time()
46
"""Every time this timer is called, it increments by 1 second."""
52
class NoopRequestWorker(load_test.RequestWorker):
54
# Every call to _timer will increment by one
55
_timer = staticmethod(one_sec_timer)
57
# Ensure that process never does anything
58
def process(self, url):
62
class TestRequestWorkerInfrastructure(tests.TestCase):
63
"""Tests various infrastructure bits, without doing actual requests."""
65
def test_step_next_tracks_time(self):
66
rt = NoopRequestWorker('id')
69
self.assertTrue(rt.queue.empty())
70
self.assertEqual([('item', True, 1.0)], rt.stats)
72
def test_step_multiple_items(self):
73
rt = NoopRequestWorker('id')
76
rt.queue.put('next-item')
78
self.assertTrue(rt.queue.empty())
79
self.assertEqual([('item', True, 1.0), ('next-item', True, 1.0)],
82
def test_step_next_does_nothing_for_noop(self):
83
rt = NoopRequestWorker('id')
86
rt.queue.put('<noop>')
88
self.assertEqual([('item', True, 1.0)], rt.stats)
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)
95
def test_run_stops_for_stop_event(self):
96
rt = NoopRequestWorker('id', blocking_time=0.001, _queue_size=2)
99
event = threading.Event()
100
t = threading.Thread(target=rt.run, args=(event,))
102
# Wait for the queue to be processed
104
# Now we can queue up another one, and wait for it
105
rt.queue.put('item3')
107
# Now set the stopping event
109
# Add another item to the queue, which might get processed, but the
111
rt.queue.put('item4')
112
rt.queue.put('item5')
114
self.assertEqual([('item1', True, 1.0), ('item2', True, 1.0),
115
('item3', True, 1.0)],
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])
123
class TestRequestWorker(tests.TestCaseWithTransport):
126
super(TestRequestWorker, self).setUp()
127
self.transport_readonly_server = http_server.HttpServer
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)
139
self.assertEqual(readonly_url1, rt.stats[0][0])
140
self.assertEqual(readonly_url2, rt.stats[1][0])
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)
147
self.assertEqual(readonly_url1, rt.stats[0][0])
148
self.assertEqual(False, rt.stats[0][1])
150
def test_no_server(self):
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)
158
self.assertEqual((url, False), rt.stats[0][:2])
161
class NoActionScript(load_test.ActionScript):
163
_thread_class = NoopRequestWorker
164
_default_blocking_timeout = 0.01
167
class TestActionScriptInfrastructure(tests.TestCase):
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(
182
self.assertIsNot(None, script)
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)
188
def test_parse_find_base_url(self):
189
script = load_test.ActionScript.parse(
190
'{"parameters": {"base_url": "http://example.com"},'
192
self.assertEqual('http://example.com', script.base_url)
194
def test_parse_default_blocking_timeout(self):
195
script = load_test.ActionScript.parse(empty_script)
196
self.assertEqual(60.0, script.blocking_timeout)
198
def test_parse_find_blocking_timeout(self):
199
script = load_test.ActionScript.parse(
200
'{"parameters": {"blocking_timeout": 10.0},'
203
self.assertEqual(10.0, script.blocking_timeout)
205
def test_parse_finds_requests(self):
206
script = load_test.ActionScript.parse(
207
'{"parameters": {}, "requests": ['
208
' {"relpath": "/foo"},'
209
' {"relpath": "/bar"}'
211
self.assertEqual(2, len(script._requests))
212
self.assertEqual("/foo", script._requests[0].relpath)
213
self.assertEqual("/bar", script._requests[1].relpath)
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)
225
# Another request will return the identical object
226
self.assertIs(worker, script._get_worker('id'))
228
# And the stop event will stop the thread
229
script.stop_and_join()
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'))
247
def test_single_threaded(self):
248
script = NoActionScript.parse("""{
249
"parameters": {"base_url": ""},
251
{"thread": "1", "relpath": "first"},
252
{"thread": "1", "relpath": "second"},
253
{"thread": "1", "relpath": "third"},
254
{"thread": "1", "relpath": "fourth"}
257
worker = script._get_worker("1")
258
self.assertEqual(["first", "second", "third", "fourth"],
259
[s[0] for s in worker.stats])
261
def test_two_threads(self):
262
script = NoActionScript.parse("""{
263
"parameters": {"base_url": ""},
265
{"thread": "1", "relpath": "first"},
266
{"thread": "2", "relpath": "second"},
267
{"thread": "1", "relpath": "third"},
268
{"thread": "2", "relpath": "fourth"}
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])
279
class TestActionScriptIntegration(tests.TestCaseWithTransport):
282
super(TestActionScriptIntegration, self).setUp()
283
self.transport_readonly_server = http_server.HttpServer
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": 0.1},
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"}
299
worker = script._get_worker("1")
300
self.assertEqual([("first", True), ('no-this', False),
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),
307
[(s[0].rsplit('/', 1)[1], s[1])
308
for s in worker.stats])
311
class TestRunScript(tests.TestCaseWithTransport):
314
super(TestRunScript, self).setUp()
315
self.transport_readonly_server = http_server.HttpServer
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},
323
{"thread": "1", "relpath": "file1"},
324
{"thread": "2", "relpath": "file2"},
325
{"thread": "1", "relpath": "file3"},
326
{"thread": "2", "relpath": "file4"}
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])