Permalink
Browse files

Start of thorough unit tests

  • Loading branch information...
1 parent 5dd23b4 commit 6b5d7947046b5f564a61f5231d93af55a53d563d Luke Marsden committed Feb 12, 2011
Showing with 132 additions and 24 deletions.
  1. +101 −0 test/test_txmysql.py
  2. +5 −5 test_timeout.py
  3. +21 −18 txmysql/client.py
  4. +5 −1 txmysql/protocol.py
View
@@ -0,0 +1,101 @@
+"""
+Test txMySQL against a local MySQL server
+
+This test requires 'sudo' without a password, and expects a stock Ubuntu 10.04
+MySQL setup. It will start and stop MySQL and occasionally replace it with an evil daemon.
+"""
+
+from twisted.trial import unittest
+from twisted.internet import defer
+
+from AwesomeProxy import AwesomeProxy
+from proxies.SMTPProxy import SMTPProxy
+
+class AwesomeProxyTest(unittest.TestCase):
+
+ @defer.inlineCallbacks
+ def test_010_start_connect_query(self):
+ """
+ 1. Start MySQL
+ 2. Connect
+ 3. Query - check result
+ """
+ pass
+
+ @defer.inlineCallbacks
+ def test_020_stop_connect_query_start(self):
+ """
+ 1. Connect, before MySQL is started
+ 1. Start MySQL
+ 3. Query - check result
+ """
+ pass
+
+ @defer.inlineCallbacks
+ def test_030_start_connect_timeout(self):
+ """
+
+ ... Check that MySQL still functions with a normal query afterwards
+ """
+ pass
+
+ @defer.inlineCallbacks
+ def test_040_start_connect_long_query_timeout(self):
+ pass
+
+ @defer.inlineCallbacks
+ def test_050_retry_on_error(self):
+ """
+ 1. Start MySQL
+ 2. Connect
+ 3. Run a long query
+ 4. Restart MySQL before the long query executes
+ 5. Check that MySQL reconnects and eventually returns the
+ correct result
+ """
+ pass
+
+ @defer.inlineCallbacks
+ def test_060_no_retry_on_error_start_connect_query_restart(self):
+ pass
+
+ @defer.inlineCallbacks
+ def test_070_error_strings_test(self):
+ pass
+
+ @defer.inlineCallbacks
+ def test_080_retry_on_error_start_connect_query_restart(self):
+ pass
+
+ @defer.inlineCallbacks
+ def test_090_no_retry_on_error_start_connect_query_restart(self):
+ pass
+
+ @defer.inlineCallbacks
+ def test_100_evil_daemon_timeout(self):
+ """
+ 1. Start evil daemon
+ 2. Connect with idle_timeout=5
+ 3. Connection should time out
+ """
+
+ # Utility functions:
+
+ def _stop_mysql(self):
+ return AsyncExecCmds(['pkill -9 mysqld; sudo stop mysql'], cmd_prefix='sudo ').getDeferred()
+
+ def _start_mysql(self):
+ return AsyncExecCmds(['start mysql'], cmd_prefix='sudo ').getDeferred()
+
+ def _start_evildaemon(self):
+ """
+ Simulates a MySQL server which accepts connections but has mysteriously stopped
+ returning responses at all, i.e. it's just /dev/null
+ """
+ return AsyncExecCmds(['sudo python test/evildaemon.py'], cmd_prefix='sudo ').getDeferred()
+
+ def setUp(self):
+ """
+ Stop MySQL before each test
+ """
+ return self._stop_mysql()
View
@@ -23,12 +23,12 @@
"""
conn = MySQLConnection('127.0.0.1', 'root', secrets.MYSQL_ROOT_PASS, 'foo',
- connect_timeout=5, query_timeout=5, idle_timeout=5, retry_on_error=True)
+ connect_timeout=5, query_timeout=5, idle_timeout=10, retry_on_error=True)
@defer.inlineCallbacks
def fuck_with_mysql_server():
print "Stopping MySQL"
- AsyncExecCmds(['pkill -9 mysqld; stop mysql'], cmd_prefix='sudo ').getDeferred()
+ yield AsyncExecCmds(['pkill -9 mysqld; sudo stop mysql'], cmd_prefix='sudo ').getDeferred()
# The pkill -9 mysqld causes "Lost connection to MySQL server during query"
# or "MySQL server has gone away" if you try to query on a connection which has died
while 1:
@@ -37,10 +37,10 @@ def fuck_with_mysql_server():
# successfully return a SELECT
print "Starting MySQL"
yield AsyncExecCmds(['start mysql'], cmd_prefix='sudo ').getDeferred()
- wait = random.randrange(3, 10)
+ wait = random.randrange(30, 31)
yield sleep(wait)
print "Stopping MySQL"
- yield AsyncExecCmds(['pkill -9 mysqld; stop mysql'], cmd_prefix='sudo ').getDeferred()
+ yield AsyncExecCmds(['pkill -9 mysqld; sudo stop mysql'], cmd_prefix='sudo ').getDeferred()
wait = random.randrange(1, 5)
yield sleep(wait)
else:
@@ -50,7 +50,7 @@ def fuck_with_mysql_server():
# Lost connection to MySQL server at 'reading initial communication packet', system error: 0
# ... when the connection finally dies (i.e. when evildaemon.py stops)
print "Starting evil daemon"
- yield AsyncExecCmds(['python test/evildaemon.py'], cmd_prefix='sudo ').getDeferred()
+ yield AsyncExecCmds(['stop mysql; sudo python test/evildaemon.py'], cmd_prefix='sudo ').getDeferred()
print "Evil daemon stopped"
wait = random.randrange(1, 5)
yield sleep(wait)
View
@@ -77,7 +77,7 @@ def runOperation(self, query, query_args=None):
return user_dfr
def _retryOperation(self):
- print "Got to retryOperation"
+ #print "Got to retryOperation"
#import traceback
#print traceback.print_stack()
@@ -89,7 +89,7 @@ def _retryOperation(self):
#self._current_operation_dfr = None
if not self._current_operation:
- print "Oh, we weren't doing anything"
+ #print "Oh, we weren't doing anything"
# Oh, we weren't doing anything
return
@@ -127,14 +127,14 @@ def _checkOperations(self, data=None):
Takes one thing off the queue and runs it, if we can.
(i.e. if there is anything to run, and we're not running anything right now)
"""
- print "Got to _checkOperations"
+ #print "Got to _checkOperations"
if self._pending_operations and not self._current_operation:
# Take the next pending operation off the queue
user_dfr, func, query, query_args = self._pending_operations.pop(0)
# Store its parameters in case we need to run it again
self._current_operation = user_dfr, func, query, query_args
# Actually execute it, operation_dfr will fire when the database returns
- print "About to run %s(%s, %s) and fire back on %s" % (str(func), str(query), str(query_args), str(user_dfr))
+ #print "About to run %s(%s, %s) and fire back on %s" % (str(func), str(query), str(query_args), str(user_dfr))
operation_dfr = func(query, query_args)
operation_dfr.addErrback(log.err)
# Store a reference to the current operation (there's gonna be only
@@ -151,7 +151,7 @@ def done_query(data):
self._current_user_dfr = None
def done_query_error(failure):
- print "_checkOperations done_query_error got %s" % str(failure)
+ #print "_checkOperations done_query_error got %s" % str(failure)
self._current_operation = None
self._current_operation_dfr = None
if self._current_user_dfr:
@@ -174,11 +174,11 @@ def stateTransition(self, data=None, state='disconnected', reason=None):
# Not a transition, heh
return
- print "Transition from %s to %s" % (self.state, new_state)
+ #print "Transition from %s to %s" % (self.state, new_state)
# connected => not connected
if self.state == 'connected' and new_state != 'connected':
- print "In branch 1"
+ #print "In branch 1"
# We have just lost a connection, if we're in the middle of
# something, send an errback, unless we're going to retry
# on reconnect, in which case do nothing
@@ -190,55 +190,58 @@ def stateTransition(self, data=None, state='disconnected', reason=None):
# not connected => connected
if self.state != 'connected' and new_state == 'connected':
- print "In branch 2"
+ #print "In branch 2"
# We have just made a new connection, if we were in the middle of
# something when we got disconnected and we want to retry it, retry
# it now
if self._current_operation and self._error_condition:
- print "In branch 2.1, current operation is %s" % str(self._current_operation)
+ #print "In branch 2.1, current operation is %s" % str(self._current_operation)
if self.retry_on_error:
# Retry the current operation
- print "In branch 2.1.1"
+ #print "In branch 2.1.1"
self._retryOperation()
else:
- print "In branch 2.1.2"
- # Abort the current execution and run the next one, if any
+ #print "In branch 2.1.2"
+ # Abort the current execution, sending the failure
+ # to the user deferred
+ self._current_user_dfr.errback(reason)
+ self._current_user_dfr = None
self._current_operation = None
self._current_operation_dfr = None
else:
# We may have something in our queue which was waiting until we became connected
- print "In branch 2.2"
+ #print "In branch 2.2"
self._checkOperations()
self.state = new_state
return data
def buildProtocol(self, addr):
- print "Running buildprotocol for %s" % addr
+ #print "Running buildprotocol for %s" % addr
p = self.protocol(self.username, self.password, self.database,
idle_timeout=self.idle_timeout)
p.factory = self
self.client = p
- print self.client.ready_deferred
+ #print self.client.ready_deferred
self.deferred.callback(self.client)
self.deferred = defer.Deferred()
def when_connected(data):
- print "About to do stateTransition, whee.."
+ #print "About to do stateTransition, whee.."
self.stateTransition(state='connected')
return data
self.client.ready_deferred.addCallback(when_connected)
self.resetDelay()
return p
def clientConnectionFailed(self, connector, reason):
- print "GOT CLIENTCONNECTIONFAILED"
+ #print "GOT CLIENTCONNECTIONFAILED"
self._error_condition = True
self.stateTransition(state='connecting', reason=reason)
ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
def clientConnectionLost(self, connector, reason):
- print "GOT CLIENTCONNECTIONLOST %s" % reason
+ #print "GOT CLIENTCONNECTIONLOST %s" % reason
self._error_condition = True
self.stateTransition(state='connecting', reason=reason)
ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
View
@@ -52,6 +52,10 @@ class MySQLProtocol(MultiBufferer, TimeoutMixin):
mode = MODE_STATEFUL
def getInitialState(self):
return self._read_header, 4
+
+ def timeoutConnection(self):
+ print "Timing out connection"
+ TimeoutMixin.timeoutConnection(self)
def connectionClosed(self, reason):
print "CONNECTIONCLOSED"
@@ -77,7 +81,7 @@ def cb(data):
return self._read_header, 4
return cb, length
- def __init__(self, username, password, database, idle_timeout=120):
+ def __init__(self, username, password, database, idle_timeout=None):
MultiBufferer.__init__(self)
self.username, self.password, self.database = username, password, database
self.sequence = None

0 comments on commit 6b5d794

Please sign in to comment.