Merge lp:~jaypipes/drizzle/replication-testing into lp:~drizzle-trunk/drizzle/development
- replication-testing
- Merge into development
Status: | Rejected |
---|---|
Rejected by: | Brian Aker |
Proposed branch: | lp:~jaypipes/drizzle/replication-testing |
Merge into: | lp:~drizzle-trunk/drizzle/development |
Prerequisite: | lp:~jaypipes/drizzle/publisher |
Diff against target: | 846 lines |
To merge this branch: | bzr merge lp:~jaypipes/drizzle/replication-testing |
Related bugs: | |
Related blueprints: |
Replication - Testing
(High)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eric Day (community) | Approve | ||
Brian Aker | Pending | ||
Drizzle Developers | Pending | ||
Review via email: mp+24246@code.launchpad.net |
Commit message
Description of the change
This patch adds a testing framework for the new replication pieces. There is currently only a single test case for verifying the information written to the publisher manifest file in the transaction log module.
Unmerged revisions
- 1442. By Jay Pipes <jpipes@serialcoder>
-
Connect replication test suite into main transaction_log test suite
- 1441. By Jay Pipes <jpipes@serialcoder>
-
Adds a custom test suite and framework for testing the replication pieces. The first test case tests basic functionality of creating and writing a serialized publisher manifest file.
- 1440. By Jay Pipes <jpipes@serialcoder>
-
Publisher manifest file now written to disk properly (was using incorrect ByteSize() instead of std::string:
:size() . Also, manifest is read on startup - 1439. By Jay Pipes <jpipes@serialcoder>
-
Merge Monty build fixes
- 1438. By Jay Pipes <jpipes@serialcoder>
-
remove references to /drizzled/
message/ replication. pb.h - 1437. By Jay Pipes <jpipes@serialcoder>
-
Moves replication.proto out of /drizzled/message and into /plugin/
transaction_ log/ since it is implementation- specific. - 1436. By Jay Pipes <jpipes@serialcoder>
-
Remove unused ZeroCopyStream buffer
- 1435. By Jay Pipes <jpipes@serialcoder>
-
TransactionLogP
ublisher now writes manifest file to disk and syncs on close - 1434. By Jay Pipes <jpipes@serialcoder>
-
Merge trunk and resolve conflicts
- 1433. By Jay Pipes <jpipes@serialcoder>
-
Merge trunk
Preview Diff
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2010-02-24 00:19:06 +0000 |
3 | +++ .bzrignore 2010-04-27 17:46:21 +0000 |
4 | @@ -304,3 +304,4 @@ |
5 | *.reject |
6 | config/pandora_vc_revinfo |
7 | drizzled/message/schema_writer |
8 | +replication_pb2.py |
9 | |
10 | === modified file 'plugin/replication_dictionary/tests/r/replication_publisher_channels.result' |
11 | --- plugin/replication_dictionary/tests/r/replication_publisher_channels.result 2010-04-27 17:46:20 +0000 |
12 | +++ plugin/replication_dictionary/tests/r/replication_publisher_channels.result 2010-04-27 17:46:21 +0000 |
13 | @@ -1,4 +1,5 @@ |
14 | use data_dictionary; |
15 | +SET @@global.transaction_log_truncate_debug= 1; |
16 | SELECT * FROM data_dictionary.replication_publisher_channels; |
17 | PUBLISHER CHANNEL_ID LAST_APPLIED_TRANSACTION_ID LAST_APPLIED_END_TIMESTAMP |
18 | transaction_log_publisher 1 0 0 |
19 | |
20 | === modified file 'plugin/replication_dictionary/tests/t/replication_publisher_channels.test' |
21 | --- plugin/replication_dictionary/tests/t/replication_publisher_channels.test 2010-04-27 17:46:20 +0000 |
22 | +++ plugin/replication_dictionary/tests/t/replication_publisher_channels.test 2010-04-27 17:46:21 +0000 |
23 | @@ -2,6 +2,8 @@ |
24 | |
25 | use data_dictionary; |
26 | |
27 | +SET @@global.transaction_log_truncate_debug= 1; |
28 | + |
29 | SELECT * FROM data_dictionary.replication_publisher_channels; |
30 | |
31 | use test; |
32 | |
33 | === modified file 'plugin/transaction_log/publisher.cc' |
34 | --- plugin/transaction_log/publisher.cc 2010-04-27 17:46:20 +0000 |
35 | +++ plugin/transaction_log/publisher.cc 2010-04-27 17:46:21 +0000 |
36 | @@ -42,6 +42,8 @@ |
37 | #include <utility> |
38 | |
39 | #include <drizzled/internal/my_sys.h> /* for internal::my_sync */ |
40 | +#include <drizzled/gettext.h> |
41 | +#include <drizzled/errmsg_print.h> |
42 | |
43 | using namespace std; |
44 | using namespace drizzled; |
45 | @@ -56,14 +58,9 @@ |
46 | channel_apply_map(), |
47 | manifest_file(-1) |
48 | { |
49 | + /** @todo handle errors much better here! */ |
50 | + (void) readManifestFile(); |
51 | manifest_file= open(MANIFEST_FILE_NAME.c_str(), O_CREAT|O_SYNC|O_WRONLY, S_IRWXU); |
52 | - /** @todo read in serialized publisher manifest file */ |
53 | - PublisherManifest &pm= getPublisherManifest(); |
54 | - PublisherManifest::PublisherApplyInfo *appl_info= pm.add_apply_info(); |
55 | - appl_info->set_channel_id(1); |
56 | - appl_info->set_last_applied_transaction_id(0); |
57 | - appl_info->set_last_applied_end_timestamp(0); |
58 | - channel_apply_map.insert(make_pair(1, appl_info)); |
59 | pthread_mutex_init(&latch, NULL); |
60 | } |
61 | |
62 | @@ -73,21 +70,73 @@ |
63 | { |
64 | (void) writeManifestFile(); |
65 | (void) syncManifestFile(); |
66 | + close(manifest_file); |
67 | } |
68 | catch (...) |
69 | {} /* enforce nothrow guarantee on destructors... */ |
70 | pthread_mutex_destroy(&latch); |
71 | } |
72 | |
73 | +bool TransactionLogPublisher::readManifestFile() |
74 | +{ |
75 | + PublisherManifest &pm= getPublisherManifest(); |
76 | + int read_file= open(MANIFEST_FILE_NAME.c_str(), O_RDONLY); |
77 | + if (read_file == -1) |
78 | + { |
79 | + /* |
80 | + * If file doesn't exist, just create a default manifest message |
81 | + * in memory. This message will be written to disk into the |
82 | + * manifest file later. |
83 | + */ |
84 | + if (errno == ENOENT) |
85 | + { |
86 | + /* Create a default manifest with channel 1 and no applied transactions */ |
87 | + PublisherManifest::PublisherApplyInfo *appl_info= pm.add_apply_info(); |
88 | + appl_info->set_channel_id(1); |
89 | + appl_info->set_last_applied_transaction_id(0); |
90 | + appl_info->set_last_applied_end_timestamp(0); |
91 | + (void) writeManifestFile(); |
92 | + (void) syncManifestFile(); |
93 | + } |
94 | + else |
95 | + { |
96 | + errmsg_printf(ERRMSG_LVL_ERROR, _("Unable to read publisher's manifest file! Got error: %s\n"), strerror(errno)); |
97 | + close(read_file); |
98 | + return false; |
99 | + } |
100 | + } |
101 | + else |
102 | + { |
103 | + if (not pm.ParseFromFileDescriptor(read_file)) |
104 | + { |
105 | + errmsg_printf(ERRMSG_LVL_ERROR, |
106 | + _("Unable to parse publisher's manifest file!\n" |
107 | + "PublisherManifest was initialized, but contains corrupted data:\n%s\n"), |
108 | + pm.DebugString().c_str()); |
109 | + close(read_file); |
110 | + return false; |
111 | + } |
112 | + } |
113 | + uint32_t num_channels= pm.apply_info_size(); |
114 | + for (uint32_t x= 0; x < num_channels; ++x) |
115 | + { |
116 | + PublisherManifest::PublisherApplyInfo *apply_info= pm.mutable_apply_info(x); |
117 | + channel_apply_map.insert(make_pair(apply_info->channel_id(), apply_info)); |
118 | + } |
119 | + close(read_file); |
120 | + return true; |
121 | +} |
122 | + |
123 | void TransactionLogPublisher::clear() |
124 | { |
125 | - for (ChannelApplyMap::iterator iter= channel_apply_map.begin(); |
126 | - iter != channel_apply_map.end(); |
127 | - ++iter) |
128 | - { |
129 | - ((*iter).second)->set_last_applied_transaction_id(0); |
130 | - ((*iter).second)->set_last_applied_end_timestamp(0); |
131 | - } |
132 | + publisher_manifest.Clear(); |
133 | + /* Create a default manifest with channel 1 and no applied transactions */ |
134 | + PublisherManifest::PublisherApplyInfo *appl_info= publisher_manifest.add_apply_info(); |
135 | + appl_info->set_channel_id(1); |
136 | + appl_info->set_last_applied_transaction_id(0); |
137 | + appl_info->set_last_applied_end_timestamp(0); |
138 | + (void) ftruncate(manifest_file, 0); |
139 | + (void) internal::my_sync(manifest_file, 0); |
140 | } |
141 | |
142 | void TransactionLogPublisher::setLastApplied(const message::Transaction &applied) |
143 | @@ -130,10 +179,14 @@ |
144 | bool result= pm.SerializeToString(&write_buffer); |
145 | if (unlikely(not result)) |
146 | { |
147 | + errmsg_printf(ERRMSG_LVL_ERROR, |
148 | + _("Unable to write publisher's manifest file!\n" |
149 | + "PublisherManifest contains data:\n%s\n"), |
150 | + pm.DebugString().c_str()); |
151 | unlock(); |
152 | return false; |
153 | } |
154 | - uint32_t num_bytes_to_write= pm.ByteSize(); |
155 | + uint32_t num_bytes_to_write= write_buffer.size(); |
156 | ssize_t written= pwrite(manifest_file, write_buffer.c_str(), num_bytes_to_write, 0); |
157 | result= (written != static_cast<ssize_t>(num_bytes_to_write)); |
158 | unlock(); |
159 | |
160 | === modified file 'plugin/transaction_log/publisher.h' |
161 | --- plugin/transaction_log/publisher.h 2010-04-27 17:46:20 +0000 |
162 | +++ plugin/transaction_log/publisher.h 2010-04-27 17:46:21 +0000 |
163 | @@ -144,6 +144,10 @@ |
164 | return publisher_manifest; |
165 | } |
166 | /** |
167 | + * Reads the publisher's manifest file on startup |
168 | + */ |
169 | + bool readManifestFile(); |
170 | + /** |
171 | * Writes the publisher's manifest file |
172 | */ |
173 | bool writeManifestFile(); |
174 | |
175 | === modified file 'plugin/transaction_log/replication.proto' |
176 | --- plugin/transaction_log/replication.proto 2010-04-27 17:46:20 +0000 |
177 | +++ plugin/transaction_log/replication.proto 2010-04-27 17:46:21 +0000 |
178 | @@ -1,5 +1,3 @@ |
179 | -option optimize_for = SPEED; |
180 | - |
181 | /** |
182 | * A class representing this information |
183 | * a subscriber keeps about the data it |
184 | |
185 | === added directory 'plugin/transaction_log/tests/inc' |
186 | === added file 'plugin/transaction_log/tests/inc/publisher.manifest' |
187 | --- plugin/transaction_log/tests/inc/publisher.manifest 1970-01-01 00:00:00 +0000 |
188 | +++ plugin/transaction_log/tests/inc/publisher.manifest 2010-04-27 17:46:21 +0000 |
189 | @@ -0,0 +1,2 @@ |
190 | + |
191 | + |
192 | ¹è½¢Ã¥¡ |
193 | \ No newline at end of file |
194 | |
195 | === modified file 'plugin/transaction_log/tests/r/publisher_manifest.result' |
196 | --- plugin/transaction_log/tests/r/publisher_manifest.result 2010-04-27 17:46:20 +0000 |
197 | +++ plugin/transaction_log/tests/r/publisher_manifest.result 2010-04-27 17:46:21 +0000 |
198 | @@ -9,3 +9,10 @@ |
199 | DROP TABLE t1; |
200 | 1var/master-data/publisher.manifest |
201 | SET GLOBAL transaction_log_truncate_debug= true; |
202 | +testDefaultStartupVariables (__main__.TestPublisherManifest) ... ok |
203 | +testWritePublisherManifest (__main__.TestPublisherManifest) ... ok |
204 | + |
205 | +---------------------------------------------------------------------- |
206 | +Ran 2 tests |
207 | + |
208 | +OK |
209 | |
210 | === added directory 'plugin/transaction_log/tests/replication' |
211 | === added file 'plugin/transaction_log/tests/replication/COPYING' |
212 | --- plugin/transaction_log/tests/replication/COPYING 1970-01-01 00:00:00 +0000 |
213 | +++ plugin/transaction_log/tests/replication/COPYING 2010-04-27 17:46:21 +0000 |
214 | @@ -0,0 +1,32 @@ |
215 | +Drizzle Replication Test Suite |
216 | + |
217 | +Copyright (C) 2010 Jay Pipes <jaypipes@gmail.com> |
218 | +All rights reserved. |
219 | + |
220 | +Redistribution and use in source and binary forms, with or without |
221 | +modification, are permitted provided that the following conditions are |
222 | +met: |
223 | + |
224 | + * Redistributions of source code must retain the above copyright |
225 | +notice, this list of conditions and the following disclaimer. |
226 | + |
227 | + * Redistributions in binary form must reproduce the above |
228 | +copyright notice, this list of conditions and the following disclaimer |
229 | +in the documentation and/or other materials provided with the |
230 | +distribution. |
231 | + |
232 | + * The names of its contributors may not be used to endorse or |
233 | +promote products derived from this software without specific prior |
234 | +written permission. |
235 | + |
236 | +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
237 | +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
238 | +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
239 | +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
240 | +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
241 | +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
242 | +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
243 | +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
244 | +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
245 | +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
246 | +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
247 | |
248 | === added directory 'plugin/transaction_log/tests/replication/bootstrap' |
249 | === added file 'plugin/transaction_log/tests/replication/bootstrap/ib_logfile0' |
250 | Binary files plugin/transaction_log/tests/replication/bootstrap/ib_logfile0 1970-01-01 00:00:00 +0000 and plugin/transaction_log/tests/replication/bootstrap/ib_logfile0 2010-04-27 17:46:21 +0000 differ |
251 | === added file 'plugin/transaction_log/tests/replication/bootstrap/ib_logfile1' |
252 | Binary files plugin/transaction_log/tests/replication/bootstrap/ib_logfile1 1970-01-01 00:00:00 +0000 and plugin/transaction_log/tests/replication/bootstrap/ib_logfile1 2010-04-27 17:46:21 +0000 differ |
253 | === added file 'plugin/transaction_log/tests/replication/bootstrap/ibdata1' |
254 | Binary files plugin/transaction_log/tests/replication/bootstrap/ibdata1 1970-01-01 00:00:00 +0000 and plugin/transaction_log/tests/replication/bootstrap/ibdata1 2010-04-27 17:46:21 +0000 differ |
255 | === added file 'plugin/transaction_log/tests/replication/test_publisher_manifest.py' |
256 | --- plugin/transaction_log/tests/replication/test_publisher_manifest.py 1970-01-01 00:00:00 +0000 |
257 | +++ plugin/transaction_log/tests/replication/test_publisher_manifest.py 2010-04-27 17:46:21 +0000 |
258 | @@ -0,0 +1,262 @@ |
259 | +#! /usr/bin/python |
260 | +# |
261 | +# Drizzle Replication Test Suite |
262 | +# |
263 | +# Copyright (c) 2010 Jay Pipes |
264 | +# |
265 | +# All rights reserved. |
266 | +# |
267 | +# Use and distribution licensed under the BSD license. See the |
268 | +# COPYING file in the root project directory for full text. |
269 | + |
270 | +"""Drizzle Transaction Log Test Suite - Publisher Manifest |
271 | + |
272 | +This tests the startup process of the Drizzle server and |
273 | +the reading of the transaction log's publisher manifest |
274 | +""" |
275 | + |
276 | +__author__ = 'Jay Pipes <jaypipes@gmail.com>' |
277 | + |
278 | +# |
279 | +# This file contains a test for reading the transaction |
280 | +# log's publisher manifest file on startup. |
281 | +# |
282 | +# It is necessary to use this testing suite |
283 | +# because the test-run.pl script assumes too many things |
284 | +# about the Drizzle test environment, and we need to |
285 | +# initialize both proper and corrupted publisher manifests |
286 | +# in order to test the startup process correctly. |
287 | +# |
288 | + |
289 | +import os |
290 | +import sys |
291 | +import commands |
292 | +import unittest |
293 | + |
294 | +drizzled_root_dir= os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..')) |
295 | +trx_log_root_dir= os.path.join(drizzled_root_dir, 'plugin', 'transaction_log') |
296 | +replication_test_root_dir= os.path.join(trx_log_root_dir, 'tests', 'replication') |
297 | + |
298 | +def compile_python_proto(protodir, protofile): |
299 | + """Compiles a proto into the proto_pb2.py file and puts it into the main test directory""" |
300 | + protocommand= "protoc --python_out=. --proto_path=%s %s" % (protodir, os.path.join(protodir, protofile)) |
301 | + (retcode, output)= commands.getstatusoutput(protocommand) |
302 | + if retcode != 0: |
303 | + raise Exception("Failed to compile Python protobuffers for %s\nTried command: %s" % (protofile, protocommand)) |
304 | + |
305 | +compile_python_proto(trx_log_root_dir, 'replication.proto') # proto is in /plugin/transaction_log/ |
306 | + |
307 | +import replication_pb2 as rpb |
308 | + |
309 | +from testlib.server import DrizzledServer |
310 | +from testlib.client import DrizzleClient |
311 | + |
312 | +class TestPublisherManifest(unittest.TestCase): |
313 | + |
314 | + DEFAULT_START_OPTIONS= { |
315 | + 'mysql_protocol_port': DrizzledServer.DEFAULT_MYSQL_PROTOCOL_PORT, |
316 | + 'drizzle_protocol_port': DrizzledServer.DEFAULT_DRIZZLE_PROTOCOL_PORT, |
317 | + 'datadir': os.path.join(replication_test_root_dir, "var"), |
318 | + 'basedir': drizzled_root_dir |
319 | + } |
320 | + |
321 | + def setUp(self): |
322 | + pass |
323 | + |
324 | + def tearDown(self): |
325 | + if self.server and self.server.ping(): |
326 | + self.server.stop() |
327 | + self.server.clear() |
328 | + self.resetDataDir() |
329 | + |
330 | + def resetDataDir(self): |
331 | + """Copies default InnoDB data files from bootstrap into datadir to speed up starts""" |
332 | + (retcode, ignored)= commands.getstatusoutput("rm -rf %s" % os.path.join(replication_test_root_dir, "var", "*")) |
333 | + |
334 | + def bootstrapDataFiles(self): |
335 | + """Copies default InnoDB data files from bootstrap into datadir to speed up starts""" |
336 | + (retcode, ignored)= commands.getstatusoutput("cp -r %s %s" % (os.path.join(replication_test_root_dir, "bootstrap", "*"), |
337 | + os.path.join(replication_test_root_dir, "var", "*"))) |
338 | + |
339 | + def testWritePublisherManifest(self): |
340 | + # |
341 | + # Here, we test the scenario of a newly |
342 | + # started server with no data history. There |
343 | + # should be no publisher manifest file at startup |
344 | + # and after plugin processing, the publisher manifest |
345 | + # file should exist but have no size. Once a few transactions |
346 | + # are applied on the server, the manifest should show data |
347 | + # fields with the last transaction ID and end timestamp |
348 | + # |
349 | + startup_options= { |
350 | + 'transaction-log-enable': '1' |
351 | + } |
352 | + for k, v in TestPublisherManifest.DEFAULT_START_OPTIONS.iteritems(): |
353 | + startup_options[k]= v |
354 | + |
355 | + self.server= DrizzledServer(startup_options) |
356 | + |
357 | + if self.server and self.server.ping(): |
358 | + self.server.stop() |
359 | + self.resetDataDir() |
360 | + |
361 | + self.bootstrapDataFiles() |
362 | + |
363 | + # Test that the publisher manifest does not exist |
364 | + # before startup. |
365 | + |
366 | + manifest_filepath= os.path.join(replication_test_root_dir, "var", "publisher.manifest") |
367 | + |
368 | + self.assertFalse(os.path.exists(manifest_filepath)) |
369 | + |
370 | + self.assertTrue(self.server.start()) |
371 | + self.assertTrue(self.server.ping()) |
372 | + |
373 | + # Test that a publisher manifest exists after startup |
374 | + # but has no size since it has not been written to yet |
375 | + |
376 | + self.assertTrue(os.path.exists(manifest_filepath)) |
377 | + self.assertEqual(os.path.getsize(manifest_filepath), 0) |
378 | + |
379 | + # Now add a single transaction and verify the publisher |
380 | + # manifest contains proper information by reading the |
381 | + # manifest into a protobuffer container class object |
382 | + |
383 | + client_options= { |
384 | + 'basedir': startup_options['basedir'], |
385 | + 'port': startup_options['mysql_protocol_port'] |
386 | + } |
387 | + |
388 | + self.client= DrizzleClient(client_options) |
389 | + self.assertTrue(self.client.execute("CREATE SCHEMA test")) |
390 | + self.assertTrue(self.client.execute("CREATE TABLE t1 (id INT NOT NULL PRIMARY KEY)", "test")) |
391 | + self.assertTrue(self.client.execute("INSERT INTO t1 VALUES(1)", "test")) |
392 | + |
393 | + # Create the manifest GPB class object |
394 | + # by reading in the publisher manifest, and then |
395 | + # check to ensure manifest data memebers are appropriate |
396 | + |
397 | + f= open(manifest_filepath) |
398 | + |
399 | + try: |
400 | + manifest= rpb.PublisherManifest() |
401 | + manifest.ParseFromString(f.read()) |
402 | + except Exception as e: |
403 | + self.assertFalse(True) |
404 | + |
405 | + f.close() |
406 | + |
407 | + self.assertEqual(len(manifest.apply_info), 1) |
408 | + self.assertEqual(manifest.apply_info[0].channel_id, 1) |
409 | + self.assertEqual(manifest.apply_info[0].last_applied_transaction_id, 3) |
410 | + self.assertNotEqual(manifest.apply_info[0].last_applied_end_timestamp, 0) |
411 | + |
412 | + self.assertTrue(self.server.stop()) |
413 | + self.assertFalse(self.server.ping()) |
414 | + |
415 | + def testDefaultStartupVariables(self): |
416 | + # |
417 | + # Here, we test the "default" scenario of a newly |
418 | + # started server with no data history. We check to see |
419 | + # if the replication_publisher and replication_publisher_channels |
420 | + # views in the data_dictionary contain proper values |
421 | + # |
422 | + startup_options= { |
423 | + 'transaction-log-enable': '1' |
424 | + } |
425 | + for k, v in TestPublisherManifest.DEFAULT_START_OPTIONS.iteritems(): |
426 | + startup_options[k]= v |
427 | + |
428 | + client_options= { |
429 | + 'basedir': startup_options['basedir'], |
430 | + 'port': startup_options['mysql_protocol_port'] |
431 | + } |
432 | + |
433 | + self.server= DrizzledServer(startup_options) |
434 | + |
435 | + if self.server and self.server.ping(): |
436 | + self.server.stop() |
437 | + self.resetDataDir() |
438 | + |
439 | + self.bootstrapDataFiles() |
440 | + self.assertTrue(self.server.start()) |
441 | + self.assertTrue(self.server.ping()) |
442 | + self.client= DrizzleClient(client_options) |
443 | + |
444 | + expected_results_1= [ |
445 | + { |
446 | + 'PUBLISHER_NAME': 'transaction_log_publisher' |
447 | + } |
448 | + ] |
449 | + results= self.client.fetchAllAssoc("SELECT * FROM DATA_DICTIONARY.REPLICATION_PUBLISHERS") |
450 | + |
451 | + self.assertTrue(len(results) == len(expected_results_1)) |
452 | + |
453 | + rowno= 0 |
454 | + for row in results: |
455 | + for k, v in row.iteritems(): |
456 | + self.assertEqual(str(expected_results_1[rowno][k]), str(v)) |
457 | + rowno+= 1 |
458 | + |
459 | + expected_results_2= [ |
460 | + { |
461 | + 'PUBLISHER': 'transaction_log_publisher', |
462 | + 'CHANNEL_ID': 1, |
463 | + 'LAST_APPLIED_TRANSACTION_ID': 0, |
464 | + 'LAST_APPLIED_END_TIMESTAMP': 0 |
465 | + } |
466 | + ] |
467 | + results= self.client.fetchAllAssoc("SELECT * FROM DATA_DICTIONARY.REPLICATION_PUBLISHER_CHANNELS") |
468 | + |
469 | + self.assertTrue(len(results) == len(expected_results_2)) |
470 | + |
471 | + rowno= 0 |
472 | + for row in results: |
473 | + for k, v in row.iteritems(): |
474 | + self.assertEqual(str(expected_results_2[rowno][k]), str(v)) |
475 | + rowno+= 1 |
476 | + |
477 | + self.assertTrue(self.server.stop()) |
478 | + self.assertFalse(self.server.ping()) |
479 | + |
480 | +# |
481 | +# Code below taken verbatim from Eric Day's prototest |
482 | +# test suite in the mysql_protocol Drizzle plugin |
483 | +# |
484 | +import optparse |
485 | + |
486 | +parser = optparse.OptionParser() |
487 | + |
488 | +parser.add_option("-t", "--test", dest="testcase", default=None, |
489 | + help="Test case to run", metavar="TESTCASE") |
490 | + |
491 | +(options, args) = parser.parse_args() |
492 | + |
493 | +class CustomTestRunner(unittest.TextTestRunner): |
494 | + def run(self, test): |
495 | + result = self._makeResult() |
496 | + test(result) |
497 | + result.printErrors() |
498 | + self.stream.writeln(result.separator2) |
499 | + run = result.testsRun |
500 | + self.stream.writeln("Ran %d test%s" % (run, run != 1 and "s" or "")) |
501 | + self.stream.writeln() |
502 | + if not result.wasSuccessful(): |
503 | + self.stream.write("FAILED (") |
504 | + failed, errored = map(len, (result.failures, result.errors)) |
505 | + if failed: |
506 | + self.stream.write("failures=%d" % failed) |
507 | + if errored: |
508 | + if failed: self.stream.write(", ") |
509 | + self.stream.write("errors=%d" % errored) |
510 | + self.stream.writeln(")") |
511 | + else: |
512 | + self.stream.writeln("OK") |
513 | + return result |
514 | + |
515 | +if __name__ == '__main__': |
516 | + if options.testcase is None: |
517 | + suite = unittest.TestLoader().loadTestsFromModule(__import__('__main__')) |
518 | + else: |
519 | + suite = unittest.TestLoader().loadTestsFromTestCase(eval(options.testcase)) |
520 | + CustomTestRunner(stream=sys.stdout, verbosity=2).run(suite) |
521 | |
522 | === added directory 'plugin/transaction_log/tests/replication/testlib' |
523 | === added file 'plugin/transaction_log/tests/replication/testlib/__init__.py' |
524 | --- plugin/transaction_log/tests/replication/testlib/__init__.py 1970-01-01 00:00:00 +0000 |
525 | +++ plugin/transaction_log/tests/replication/testlib/__init__.py 2010-04-27 17:46:21 +0000 |
526 | @@ -0,0 +1,10 @@ |
527 | +#! /usr/bin/python |
528 | +# |
529 | +# Drizzle Replication Test Suite |
530 | +# |
531 | +# Copyright (c) 2010 Jay Pipes |
532 | +# |
533 | +# All rights reserved. |
534 | +# |
535 | +# Use and distribution licensed under the BSD license. See the |
536 | +# COPYING file in the root project directory for full text. |
537 | |
538 | === added file 'plugin/transaction_log/tests/replication/testlib/client.py' |
539 | --- plugin/transaction_log/tests/replication/testlib/client.py 1970-01-01 00:00:00 +0000 |
540 | +++ plugin/transaction_log/tests/replication/testlib/client.py 2010-04-27 17:46:21 +0000 |
541 | @@ -0,0 +1,137 @@ |
542 | +#! /usr/bin/python |
543 | +# |
544 | +# Drizzle Replication Test Suite |
545 | +# |
546 | +# Copyright (c) 2010 Jay Pipes |
547 | +# |
548 | +# All rights reserved. |
549 | +# |
550 | +# Use and distribution licensed under the BSD license. See the |
551 | +# COPYING file in the root project directory for full text. |
552 | + |
553 | +"""Defines the adapter for controlling a Drizzle database client""" |
554 | + |
555 | +import os |
556 | +import commands |
557 | +import sys |
558 | +import time |
559 | +import unittest |
560 | + |
561 | +class DrizzleClientException(Exception): |
562 | + pass |
563 | + |
564 | +class DrizzleClient: |
565 | + """A class wrapping a Drizzled client""" |
566 | + |
567 | + DEFAULT_PORT= 9306 |
568 | + |
569 | + def __init__(self, options= {}): |
570 | + self.start_options= options |
571 | + self.cleanStartOptions() |
572 | + |
573 | + def getPort(self): |
574 | + """Returns port the client connects to""" |
575 | + return self.start_options["port"] |
576 | + |
577 | + def cleanStartOptions(self): |
578 | + """Cleans up the startup options dictionary""" |
579 | + clean_start_options= {} |
580 | + for key in self.start_options.keys(): |
581 | + # Ensure option names are output as --option_name |
582 | + # and clean the self.start_options array to remove any leading -- |
583 | + # and convert - to _ |
584 | + value= self.start_options[key] |
585 | + |
586 | + if not key.startswith("--"): |
587 | + clean_key= key.replace('-','_') |
588 | + else: |
589 | + clean_key= key[2:].replace("-","_") |
590 | + clean_start_options[clean_key]= value |
591 | + |
592 | + self.start_options= clean_start_options |
593 | + |
594 | + def getStartOptionString(self): |
595 | + """Builds the string of options to pass to drizzled on startup""" |
596 | + out_options= ["%s=%s" % ("--" + key, value) for key, value in self.start_options.iteritems()] |
597 | + |
598 | + return " ".join(out_options) |
599 | + |
600 | + def execute(self, statement, schema=None): |
601 | + """Executes a supplied SQL statement""" |
602 | + if schema is None: |
603 | + schema_string= "" |
604 | + else: |
605 | + schema_string= "--database=%s" % schema |
606 | + client_cmd= "%s --port=%d %s -e\"%s\"" % (os.path.join(self.start_options["basedir"], "client", "drizzle") |
607 | + , self.start_options["port"] |
608 | + , schema_string |
609 | + , statement) |
610 | + (retcode, output)= commands.getstatusoutput(client_cmd) |
611 | + if retcode != 0: |
612 | + print "Client on port %d failed to execute SQL:\n\"%s\"\nGot error: %s" % (self.start_options["port"], statement, output) |
613 | + return False |
614 | + return True |
615 | + |
616 | + def executeFile(self, file): |
617 | + """Executes SQL statements in a supplied file""" |
618 | + client_cmd= "%s --port=%d < %s" % (os.path.join(self.start_options["basedir"], "client", "drizzle") |
619 | + , self.start_options["port"] |
620 | + , file) |
621 | + (retcode, output)= commands.getstatusoutput(client_cmd) |
622 | + if retcode != 0: |
623 | + print "Client on port %d failed to execute SQL in file:\n\"%s\"\nGot error: %s" % (self.start_options["port"], file, output) |
624 | + return False |
625 | + return True |
626 | + |
627 | + def fetchAsDict(self, statement): |
628 | + """Executes a supplied SELECT or SHOW statement and returns a dictionary with first column as keys, second column as values.""" |
629 | + client_cmd= "%s --port=%d -e\"%s\"" % (os.path.join(self.start_options["basedir"], "client", "drizzle") |
630 | + , self.start_options["port"] |
631 | + , statement) |
632 | + (retcode, output)= commands.getstatusoutput(client_cmd) |
633 | + if retcode != 0: |
634 | + print "Client on port %d failed to execute SQL:\n\"%s\"\nGot error: %s" % (self.start_options["port"], statement, output) |
635 | + return {} |
636 | + else: |
637 | + # Now we build the results dictionary... |
638 | + results= {} |
639 | + # The first line is the field names separated by a tab |
640 | + # Every line after is a tab-delimited field values. |
641 | + lines= output.split("\n") |
642 | + fields= lines[0].split("\t") |
643 | + for line in lines[1:]: |
644 | + data_fields= line.split("\t") |
645 | + results[data_fields[0]]= data_fields[1] |
646 | + return results |
647 | + |
648 | + def fetchAllAssoc(self, statement): |
649 | + """Executes a supplied SELECT or SHOW statement and returns a sequence with associative dictionary of results.""" |
650 | + client_cmd= "%s --port=%d -e\"%s\"" % (os.path.join(self.start_options["basedir"], "client", "drizzle") |
651 | + , self.start_options["port"] |
652 | + , statement) |
653 | + (retcode, output)= commands.getstatusoutput(client_cmd) |
654 | + if retcode != 0: |
655 | + print "Client on port %d failed to execute SQL:\n\"%s\"\nGot error: %s" % (self.start_options["port"], statement, output) |
656 | + return [] |
657 | + else: |
658 | + # Now we build the results dictionary... |
659 | + results= [] |
660 | + # The first line is the field names separated by a tab |
661 | + # Every line after is a tab-delimited field values. |
662 | + lines= output.split("\n") |
663 | + fields= lines[0].split("\t") |
664 | + for line in lines[1:]: |
665 | + row= {} |
666 | + data_fields= line.split("\t") |
667 | + fieldno= 0 |
668 | + for field in fields: |
669 | + row[field]= data_fields[fieldno] |
670 | + fieldno= fieldno + 1 |
671 | + results.append(row) |
672 | + return results |
673 | + |
674 | +class TestDrizzleClient(unittest.TestCase): |
675 | + pass |
676 | + |
677 | +if __name__ == '__main__': |
678 | + unittest.main() |
679 | |
680 | === added file 'plugin/transaction_log/tests/replication/testlib/server.py' |
681 | --- plugin/transaction_log/tests/replication/testlib/server.py 1970-01-01 00:00:00 +0000 |
682 | +++ plugin/transaction_log/tests/replication/testlib/server.py 2010-04-27 17:46:21 +0000 |
683 | @@ -0,0 +1,152 @@ |
684 | +#! /usr/bin/python |
685 | +# |
686 | +# Drizzle Replication Test Suite |
687 | +# |
688 | +# Copyright (c) 2010 Jay Pipes |
689 | +# |
690 | +# All rights reserved. |
691 | +# |
692 | +# Use and distribution licensed under the BSD license. See the |
693 | +# COPYING file in the root project directory for full text. |
694 | + |
695 | +"""Defines the adapter for controlling a Drizzle database server""" |
696 | + |
697 | +import os |
698 | +import commands |
699 | +import sys |
700 | +import time |
701 | +import unittest |
702 | + |
703 | +class DrizzledServerStartupException(Exception): |
704 | + pass |
705 | + |
706 | +class DrizzledServerShutdownException(Exception): |
707 | + pass |
708 | + |
709 | +class DrizzledServer: |
710 | + """A class responsible for starting, stopping, and pinging a Drizzled server""" |
711 | + |
712 | + DEFAULT_MYSQL_PROTOCOL_PORT= 9306 |
713 | + DEFAULT_DRIZZLE_PROTOCOL_PORT= 9307 |
714 | + |
715 | + def __init__(self, options= {}): |
716 | + self.start_options= options |
717 | + self.defaults_file= "" |
718 | + self.cleanStartOptions() |
719 | + |
720 | + def getPort(self, protocol="drizzle"): |
721 | + """Returns port the server listens for supplied protocol""" |
722 | + return self.start_options[protocol + "_protocol_port"] |
723 | + |
724 | + def cleanStartOptions(self): |
725 | + """Cleans up the startup options dictionary""" |
726 | + clean_start_options= {} |
727 | + for key in self.start_options.keys(): |
728 | + # Ensure option names are output as --option_name |
729 | + # and clean the self.start_options array to remove any leading -- |
730 | + # and convert - to _ |
731 | + value= self.start_options[key] |
732 | + |
733 | + if not key.startswith("--"): |
734 | + clean_key= key.replace('-','_') |
735 | + else: |
736 | + clean_key= key[2:].replace("-","_") |
737 | + |
738 | + # The defaults-file must ALWAYS be the first command-line |
739 | + # option given to a drizzled server, so we strip it and |
740 | + # store it locally here... |
741 | + if clean_key == 'defaults_file': |
742 | + self.defaults_file= "--defaults-file=%s" % value |
743 | + else: |
744 | + clean_start_options[clean_key]= value |
745 | + self.start_options= clean_start_options |
746 | + |
747 | + def getStartOptionString(self): |
748 | + """Builds the string of options to pass to drizzled on startup""" |
749 | + out_options= ["%s=%s" % ("--" + key, value) for key, value in self.start_options.iteritems()] |
750 | + |
751 | + return " ".join(out_options) |
752 | + |
753 | + def start(self): |
754 | + """Starts a drizzled server""" |
755 | + |
756 | + for x in ("mysql_protocol_port","basedir","datadir"): |
757 | + if x not in self.start_options.keys(): |
758 | + raise DrizzledServerStartupException("Attempt to start server without required startup options") |
759 | + |
760 | + start_option_string= self.getStartOptionString() |
761 | + start_cmd= "%s %s %s > %s 2>&1 &" % (os.path.join(self.start_options['basedir'], "drizzled", "drizzled"), |
762 | + self.defaults_file, |
763 | + start_option_string, |
764 | + os.path.join(self.start_options['datadir'], 'error.log')) |
765 | + server_output= os.system(start_cmd) |
766 | + |
767 | + # Here, we sleep until the server is up and running or until a timeout occurs... |
768 | + timeout= 10 |
769 | + timer= 0 |
770 | + while not self.ping() and timer != timeout: |
771 | + time.sleep(.25) |
772 | + timer= timer + 1 |
773 | + |
774 | + return True |
775 | + |
776 | + def ping(self, quiet= False): |
777 | + """Pings the server. Returns True if server is up and running, False otherwise.""" |
778 | + ping_cmd= "%s --ping --port=%d" % (os.path.join(self.start_options['basedir'], "client", "drizzle"), |
779 | + self.start_options['mysql_protocol_port']) |
780 | + |
781 | + (retcode, output)= commands.getstatusoutput(ping_cmd) |
782 | + |
783 | + return retcode == 0 |
784 | + |
785 | + def clear(self): |
786 | + """Clears data files for the server.""" |
787 | + (retcode, ignored)= commands.getstatusoutput("rm -rf %s; mkdir %s" % (self.start_options['datadir'], self.start_options['datadir'])) |
788 | + |
789 | + def stop(self): |
790 | + """Stops the server""" |
791 | + shutdown_cmd= "%s --shutdown --port=%d" % (os.path.join(self.start_options['basedir'], "client", "drizzle"), |
792 | + self.start_options['mysql_protocol_port']) |
793 | + |
794 | + (retcode, output)= commands.getstatusoutput(shutdown_cmd) |
795 | + |
796 | + return retcode == 0 |
797 | + |
798 | + def restart(self): |
799 | + self.stop() |
800 | + return self.start() |
801 | + |
802 | + def stopAll(self): |
803 | + """Stops ALL drizzled servers""" |
804 | + (retcode, num_drizzled_running)= commands.getstatusoutput("ps aux | grep drizzled | wc -l") |
805 | + if retcode == 0 and int(num_drizzled_running) > 0: |
806 | + (retcode, ignored)= commands.getstatusoutput("killall -9 drizzled") |
807 | + time.sleep(3) |
808 | + |
809 | +class TestDrizzledServer(unittest.TestCase): |
810 | + |
811 | + DEFAULT_START_OPTIONS= { |
812 | + 'mysql_protocol_port': DrizzledServer.DEFAULT_MYSQL_PROTOCOL_PORT, |
813 | + 'drizzle_protocol_port': DrizzledServer.DEFAULT_DRIZZLE_PROTOCOL_PORT, |
814 | + 'datadir': os.path.join(os.getcwd().partition("plugin")[0], "tests", "var"), |
815 | + 'basedir': os.path.join(os.getcwd().partition("plugin")[0], "drizzled") |
816 | + } |
817 | + |
818 | + def setUp(self): |
819 | + self.server= DrizzledServer(TestDrizzledServer.DEFAULT_START_OPTIONS) |
820 | + if self.server.ping(): |
821 | + self.server.stopAll() |
822 | + |
823 | + def tearDown(self): |
824 | + self.server.stop() |
825 | + |
826 | + def testStartAndPing(self): |
827 | + self.assertTrue(self.server.start()) |
828 | + self.assertTrue(self.server.ping()) |
829 | + |
830 | + def testStartAndStop(self): |
831 | + self.assertTrue(self.server.start()) |
832 | + self.assertTrue(self.server.stop()) |
833 | + |
834 | +if __name__ == '__main__': |
835 | + unittest.main() |
836 | |
837 | === added directory 'plugin/transaction_log/tests/replication/var' |
838 | === modified file 'plugin/transaction_log/tests/t/publisher_manifest.test' |
839 | --- plugin/transaction_log/tests/t/publisher_manifest.test 2010-04-27 17:46:20 +0000 |
840 | +++ plugin/transaction_log/tests/t/publisher_manifest.test 2010-04-27 17:46:21 +0000 |
841 | @@ -14,3 +14,6 @@ |
842 | |
843 | # Truncate the log file to reset for the next test |
844 | --source ../plugin/transaction_log/tests/t/truncate_log.inc |
845 | + |
846 | +# Execute the external test suite for replication |
847 | +--exec $DRIZZLE_TEST_DIR/../plugin/transaction_log/tests/replication/test_publisher_manifest.py |
Looks great! I like the use of the Python unit testing framework, I think I've seen that before... :)