Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_adbapi.py 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for twisted.enterprise.adbapi.
  5. """
  6. import os
  7. import stat
  8. from typing import Dict, Optional
  9. from twisted.enterprise.adbapi import (
  10. Connection,
  11. ConnectionLost,
  12. ConnectionPool,
  13. Transaction,
  14. )
  15. from twisted.internet import defer, interfaces, reactor
  16. from twisted.python.failure import Failure
  17. from twisted.python.reflect import requireModule
  18. from twisted.trial import unittest
  19. simple_table_schema = """
  20. CREATE TABLE simple (
  21. x integer
  22. )
  23. """
  24. class ADBAPITestBase:
  25. """
  26. Test the asynchronous DB-API code.
  27. """
  28. openfun_called: Dict[object, bool] = {}
  29. if interfaces.IReactorThreads(reactor, None) is None:
  30. skip = "ADB-API requires threads, no way to test without them"
  31. def extraSetUp(self):
  32. """
  33. Set up the database and create a connection pool pointing at it.
  34. """
  35. self.startDB()
  36. self.dbpool = self.makePool(cp_openfun=self.openfun)
  37. self.dbpool.start()
  38. def tearDown(self):
  39. d = self.dbpool.runOperation("DROP TABLE simple")
  40. d.addCallback(lambda res: self.dbpool.close())
  41. d.addCallback(lambda res: self.stopDB())
  42. return d
  43. def openfun(self, conn):
  44. self.openfun_called[conn] = True
  45. def checkOpenfunCalled(self, conn=None):
  46. if not conn:
  47. self.assertTrue(self.openfun_called)
  48. else:
  49. self.assertIn(conn, self.openfun_called)
  50. def test_pool(self):
  51. d = self.dbpool.runOperation(simple_table_schema)
  52. if self.test_failures:
  53. d.addCallback(self._testPool_1_1)
  54. d.addCallback(self._testPool_1_2)
  55. d.addCallback(self._testPool_1_3)
  56. d.addCallback(self._testPool_1_4)
  57. d.addCallback(lambda res: self.flushLoggedErrors())
  58. d.addCallback(self._testPool_2)
  59. d.addCallback(self._testPool_3)
  60. d.addCallback(self._testPool_4)
  61. d.addCallback(self._testPool_5)
  62. d.addCallback(self._testPool_6)
  63. d.addCallback(self._testPool_7)
  64. d.addCallback(self._testPool_8)
  65. d.addCallback(self._testPool_9)
  66. return d
  67. def _testPool_1_1(self, res):
  68. d = defer.maybeDeferred(self.dbpool.runQuery, "select * from NOTABLE")
  69. d.addCallbacks(lambda res: self.fail("no exception"), lambda f: None)
  70. return d
  71. def _testPool_1_2(self, res):
  72. d = defer.maybeDeferred(self.dbpool.runOperation, "deletexxx from NOTABLE")
  73. d.addCallbacks(lambda res: self.fail("no exception"), lambda f: None)
  74. return d
  75. def _testPool_1_3(self, res):
  76. d = defer.maybeDeferred(self.dbpool.runInteraction, self.bad_interaction)
  77. d.addCallbacks(lambda res: self.fail("no exception"), lambda f: None)
  78. return d
  79. def _testPool_1_4(self, res):
  80. d = defer.maybeDeferred(self.dbpool.runWithConnection, self.bad_withConnection)
  81. d.addCallbacks(lambda res: self.fail("no exception"), lambda f: None)
  82. return d
  83. def _testPool_2(self, res):
  84. # verify simple table is empty
  85. sql = "select count(1) from simple"
  86. d = self.dbpool.runQuery(sql)
  87. def _check(row):
  88. self.assertTrue(int(row[0][0]) == 0, "Interaction not rolled back")
  89. self.checkOpenfunCalled()
  90. d.addCallback(_check)
  91. return d
  92. def _testPool_3(self, res):
  93. sql = "select count(1) from simple"
  94. inserts = []
  95. # add some rows to simple table (runOperation)
  96. for i in range(self.num_iterations):
  97. sql = "insert into simple(x) values(%d)" % i
  98. inserts.append(self.dbpool.runOperation(sql))
  99. d = defer.gatherResults(inserts)
  100. def _select(res):
  101. # make sure they were added (runQuery)
  102. sql = "select x from simple order by x"
  103. d = self.dbpool.runQuery(sql)
  104. return d
  105. d.addCallback(_select)
  106. def _check(rows):
  107. self.assertTrue(len(rows) == self.num_iterations, "Wrong number of rows")
  108. for i in range(self.num_iterations):
  109. self.assertTrue(len(rows[i]) == 1, "Wrong size row")
  110. self.assertTrue(rows[i][0] == i, "Values not returned.")
  111. d.addCallback(_check)
  112. return d
  113. def _testPool_4(self, res):
  114. # runInteraction
  115. d = self.dbpool.runInteraction(self.interaction)
  116. d.addCallback(lambda res: self.assertEqual(res, "done"))
  117. return d
  118. def _testPool_5(self, res):
  119. # withConnection
  120. d = self.dbpool.runWithConnection(self.withConnection)
  121. d.addCallback(lambda res: self.assertEqual(res, "done"))
  122. return d
  123. def _testPool_6(self, res):
  124. # Test a withConnection cannot be closed
  125. d = self.dbpool.runWithConnection(self.close_withConnection)
  126. return d
  127. def _testPool_7(self, res):
  128. # give the pool a workout
  129. ds = []
  130. for i in range(self.num_iterations):
  131. sql = "select x from simple where x = %d" % i
  132. ds.append(self.dbpool.runQuery(sql))
  133. dlist = defer.DeferredList(ds, fireOnOneErrback=True)
  134. def _check(result):
  135. for i in range(self.num_iterations):
  136. self.assertTrue(result[i][1][0][0] == i, "Value not returned")
  137. dlist.addCallback(_check)
  138. return dlist
  139. def _testPool_8(self, res):
  140. # now delete everything
  141. ds = []
  142. for i in range(self.num_iterations):
  143. sql = "delete from simple where x = %d" % i
  144. ds.append(self.dbpool.runOperation(sql))
  145. dlist = defer.DeferredList(ds, fireOnOneErrback=True)
  146. return dlist
  147. def _testPool_9(self, res):
  148. # verify simple table is empty
  149. sql = "select count(1) from simple"
  150. d = self.dbpool.runQuery(sql)
  151. def _check(row):
  152. self.assertTrue(
  153. int(row[0][0]) == 0, "Didn't successfully delete table contents"
  154. )
  155. self.checkConnect()
  156. d.addCallback(_check)
  157. return d
  158. def checkConnect(self):
  159. """Check the connect/disconnect synchronous calls."""
  160. conn = self.dbpool.connect()
  161. self.checkOpenfunCalled(conn)
  162. curs = conn.cursor()
  163. curs.execute("insert into simple(x) values(1)")
  164. curs.execute("select x from simple")
  165. res = curs.fetchall()
  166. self.assertEqual(len(res), 1)
  167. self.assertEqual(len(res[0]), 1)
  168. self.assertEqual(res[0][0], 1)
  169. curs.execute("delete from simple")
  170. curs.execute("select x from simple")
  171. self.assertEqual(len(curs.fetchall()), 0)
  172. curs.close()
  173. self.dbpool.disconnect(conn)
  174. def interaction(self, transaction):
  175. transaction.execute("select x from simple order by x")
  176. for i in range(self.num_iterations):
  177. row = transaction.fetchone()
  178. self.assertTrue(len(row) == 1, "Wrong size row")
  179. self.assertTrue(row[0] == i, "Value not returned.")
  180. self.assertIsNone(transaction.fetchone(), "Too many rows")
  181. return "done"
  182. def bad_interaction(self, transaction):
  183. if self.can_rollback:
  184. transaction.execute("insert into simple(x) values(0)")
  185. transaction.execute("select * from NOTABLE")
  186. def withConnection(self, conn):
  187. curs = conn.cursor()
  188. try:
  189. curs.execute("select x from simple order by x")
  190. for i in range(self.num_iterations):
  191. row = curs.fetchone()
  192. self.assertTrue(len(row) == 1, "Wrong size row")
  193. self.assertTrue(row[0] == i, "Value not returned.")
  194. finally:
  195. curs.close()
  196. return "done"
  197. def close_withConnection(self, conn):
  198. conn.close()
  199. def bad_withConnection(self, conn):
  200. curs = conn.cursor()
  201. try:
  202. curs.execute("select * from NOTABLE")
  203. finally:
  204. curs.close()
  205. class ReconnectTestBase:
  206. """
  207. Test the asynchronous DB-API code with reconnect.
  208. """
  209. if interfaces.IReactorThreads(reactor, None) is None:
  210. skip = "ADB-API requires threads, no way to test without them"
  211. def extraSetUp(self):
  212. """
  213. Skip the test if C{good_sql} is unavailable. Otherwise, set up the
  214. database, create a connection pool pointed at it, and set up a simple
  215. schema in it.
  216. """
  217. if self.good_sql is None:
  218. raise unittest.SkipTest("no good sql for reconnect test")
  219. self.startDB()
  220. self.dbpool = self.makePool(
  221. cp_max=1, cp_reconnect=True, cp_good_sql=self.good_sql
  222. )
  223. self.dbpool.start()
  224. return self.dbpool.runOperation(simple_table_schema)
  225. def tearDown(self):
  226. d = self.dbpool.runOperation("DROP TABLE simple")
  227. d.addCallback(lambda res: self.dbpool.close())
  228. d.addCallback(lambda res: self.stopDB())
  229. return d
  230. def test_pool(self):
  231. d = defer.succeed(None)
  232. d.addCallback(self._testPool_1)
  233. d.addCallback(self._testPool_2)
  234. if not self.early_reconnect:
  235. d.addCallback(self._testPool_3)
  236. d.addCallback(self._testPool_4)
  237. d.addCallback(self._testPool_5)
  238. return d
  239. def _testPool_1(self, res):
  240. sql = "select count(1) from simple"
  241. d = self.dbpool.runQuery(sql)
  242. def _check(row):
  243. self.assertTrue(int(row[0][0]) == 0, "Table not empty")
  244. d.addCallback(_check)
  245. return d
  246. def _testPool_2(self, res):
  247. # reach in and close the connection manually
  248. list(self.dbpool.connections.values())[0].close()
  249. def _testPool_3(self, res):
  250. sql = "select count(1) from simple"
  251. d = defer.maybeDeferred(self.dbpool.runQuery, sql)
  252. d.addCallbacks(lambda res: self.fail("no exception"), lambda f: None)
  253. return d
  254. def _testPool_4(self, res):
  255. sql = "select count(1) from simple"
  256. d = self.dbpool.runQuery(sql)
  257. def _check(row):
  258. self.assertTrue(int(row[0][0]) == 0, "Table not empty")
  259. d.addCallback(_check)
  260. return d
  261. def _testPool_5(self, res):
  262. self.flushLoggedErrors()
  263. sql = "select * from NOTABLE" # bad sql
  264. d = defer.maybeDeferred(self.dbpool.runQuery, sql)
  265. d.addCallbacks(
  266. lambda res: self.fail("no exception"),
  267. lambda f: self.assertFalse(f.check(ConnectionLost)),
  268. )
  269. return d
  270. class DBTestConnector:
  271. """
  272. A class which knows how to test for the presence of
  273. and establish a connection to a relational database.
  274. To enable test cases which use a central, system database,
  275. you must create a database named DB_NAME with a user DB_USER
  276. and password DB_PASS with full access rights to database DB_NAME.
  277. """
  278. # used for creating new test cases
  279. TEST_PREFIX: Optional[str] = None
  280. DB_NAME = "twisted_test"
  281. DB_USER = "twisted_test"
  282. DB_PASS = "twisted_test"
  283. DB_DIR = None # directory for database storage
  284. nulls_ok = True # nulls supported
  285. trailing_spaces_ok = True # trailing spaces in strings preserved
  286. can_rollback = True # rollback supported
  287. test_failures = True # test bad sql?
  288. escape_slashes = True # escape \ in sql?
  289. good_sql: Optional[str] = ConnectionPool.good_sql
  290. early_reconnect = True # cursor() will fail on closed connection
  291. can_clear = True # can try to clear out tables when starting
  292. # number of iterations for test loop (lower this for slow db's)
  293. num_iterations = 50
  294. def setUp(self):
  295. self.DB_DIR = self.mktemp()
  296. os.mkdir(self.DB_DIR)
  297. if not self.can_connect():
  298. raise unittest.SkipTest("%s: Cannot access db" % self.TEST_PREFIX)
  299. return self.extraSetUp()
  300. def can_connect(self):
  301. """Return true if this database is present on the system
  302. and can be used in a test."""
  303. raise NotImplementedError()
  304. def startDB(self):
  305. """Take any steps needed to bring database up."""
  306. pass
  307. def stopDB(self):
  308. """Bring database down, if needed."""
  309. pass
  310. def makePool(self, **newkw):
  311. """Create a connection pool with additional keyword arguments."""
  312. args, kw = self.getPoolArgs()
  313. kw = kw.copy()
  314. kw.update(newkw)
  315. return ConnectionPool(*args, **kw)
  316. def getPoolArgs(self):
  317. """Return a tuple (args, kw) of list and keyword arguments
  318. that need to be passed to ConnectionPool to create a connection
  319. to this database."""
  320. raise NotImplementedError()
  321. class SQLite3Connector(DBTestConnector):
  322. """
  323. Connector that uses the stdlib SQLite3 database support.
  324. """
  325. TEST_PREFIX = "SQLite3"
  326. escape_slashes = False
  327. num_iterations = 1 # slow
  328. def can_connect(self):
  329. if requireModule("sqlite3") is None:
  330. return False
  331. else:
  332. return True
  333. def startDB(self):
  334. self.database = os.path.join(self.DB_DIR, self.DB_NAME)
  335. if os.path.exists(self.database):
  336. os.unlink(self.database)
  337. def getPoolArgs(self):
  338. args = ("sqlite3",)
  339. kw = {"database": self.database, "cp_max": 1, "check_same_thread": False}
  340. return args, kw
  341. class PySQLite2Connector(DBTestConnector):
  342. """
  343. Connector that uses pysqlite's SQLite database support.
  344. """
  345. TEST_PREFIX = "pysqlite2"
  346. escape_slashes = False
  347. num_iterations = 1 # slow
  348. def can_connect(self):
  349. if requireModule("pysqlite2.dbapi2") is None:
  350. return False
  351. else:
  352. return True
  353. def startDB(self):
  354. self.database = os.path.join(self.DB_DIR, self.DB_NAME)
  355. if os.path.exists(self.database):
  356. os.unlink(self.database)
  357. def getPoolArgs(self):
  358. args = ("pysqlite2.dbapi2",)
  359. kw = {"database": self.database, "cp_max": 1, "check_same_thread": False}
  360. return args, kw
  361. class PyPgSQLConnector(DBTestConnector):
  362. TEST_PREFIX = "PyPgSQL"
  363. def can_connect(self):
  364. try:
  365. from pyPgSQL import PgSQL # type: ignore[import]
  366. except BaseException:
  367. return False
  368. try:
  369. conn = PgSQL.connect(
  370. database=self.DB_NAME, user=self.DB_USER, password=self.DB_PASS
  371. )
  372. conn.close()
  373. return True
  374. except BaseException:
  375. return False
  376. def getPoolArgs(self):
  377. args = ("pyPgSQL.PgSQL",)
  378. kw = {
  379. "database": self.DB_NAME,
  380. "user": self.DB_USER,
  381. "password": self.DB_PASS,
  382. "cp_min": 0,
  383. }
  384. return args, kw
  385. class PsycopgConnector(DBTestConnector):
  386. TEST_PREFIX = "Psycopg"
  387. def can_connect(self):
  388. try:
  389. import psycopg # type: ignore[import]
  390. except BaseException:
  391. return False
  392. try:
  393. conn = psycopg.connect(
  394. database=self.DB_NAME, user=self.DB_USER, password=self.DB_PASS
  395. )
  396. conn.close()
  397. return True
  398. except BaseException:
  399. return False
  400. def getPoolArgs(self):
  401. args = ("psycopg",)
  402. kw = {
  403. "database": self.DB_NAME,
  404. "user": self.DB_USER,
  405. "password": self.DB_PASS,
  406. "cp_min": 0,
  407. }
  408. return args, kw
  409. class MySQLConnector(DBTestConnector):
  410. TEST_PREFIX = "MySQL"
  411. trailing_spaces_ok = False
  412. can_rollback = False
  413. early_reconnect = False
  414. def can_connect(self):
  415. try:
  416. import MySQLdb # type: ignore[import]
  417. except BaseException:
  418. return False
  419. try:
  420. conn = MySQLdb.connect(
  421. db=self.DB_NAME, user=self.DB_USER, passwd=self.DB_PASS
  422. )
  423. conn.close()
  424. return True
  425. except BaseException:
  426. return False
  427. def getPoolArgs(self):
  428. args = ("MySQLdb",)
  429. kw = {"db": self.DB_NAME, "user": self.DB_USER, "passwd": self.DB_PASS}
  430. return args, kw
  431. class FirebirdConnector(DBTestConnector):
  432. TEST_PREFIX = "Firebird"
  433. test_failures = False # failure testing causes problems
  434. escape_slashes = False
  435. good_sql = None # firebird doesn't handle failed sql well
  436. can_clear = False # firebird is not so good
  437. num_iterations = 5 # slow
  438. def can_connect(self):
  439. if requireModule("kinterbasdb") is None:
  440. return False
  441. try:
  442. self.startDB()
  443. self.stopDB()
  444. return True
  445. except BaseException:
  446. return False
  447. def startDB(self):
  448. import kinterbasdb # type: ignore[import]
  449. self.DB_NAME = os.path.join(self.DB_DIR, DBTestConnector.DB_NAME)
  450. os.chmod(self.DB_DIR, stat.S_IRWXU + stat.S_IRWXG + stat.S_IRWXO)
  451. sql = 'create database "%s" user "%s" password "%s"'
  452. sql %= (self.DB_NAME, self.DB_USER, self.DB_PASS)
  453. conn = kinterbasdb.create_database(sql)
  454. conn.close()
  455. def getPoolArgs(self):
  456. args = ("kinterbasdb",)
  457. kw = {
  458. "database": self.DB_NAME,
  459. "host": "127.0.0.1",
  460. "user": self.DB_USER,
  461. "password": self.DB_PASS,
  462. }
  463. return args, kw
  464. def stopDB(self):
  465. import kinterbasdb
  466. conn = kinterbasdb.connect(
  467. database=self.DB_NAME,
  468. host="127.0.0.1",
  469. user=self.DB_USER,
  470. password=self.DB_PASS,
  471. )
  472. conn.drop_database()
  473. def makeSQLTests(base, suffix, globals):
  474. """
  475. Make a test case for every db connector which can connect.
  476. @param base: Base class for test case. Additional base classes
  477. will be a DBConnector subclass and unittest.TestCase
  478. @param suffix: A suffix used to create test case names. Prefixes
  479. are defined in the DBConnector subclasses.
  480. """
  481. connectors = [
  482. PySQLite2Connector,
  483. SQLite3Connector,
  484. PyPgSQLConnector,
  485. PsycopgConnector,
  486. MySQLConnector,
  487. FirebirdConnector,
  488. ]
  489. tests = {}
  490. for connclass in connectors:
  491. name = connclass.TEST_PREFIX + suffix
  492. class testcase(connclass, base, unittest.TestCase):
  493. __module__ = connclass.__module__
  494. testcase.__name__ = name
  495. if hasattr(connclass, "__qualname__"):
  496. testcase.__qualname__ = ".".join(
  497. connclass.__qualname__.split()[0:-1] + [name]
  498. )
  499. tests[name] = testcase
  500. globals.update(tests)
  501. # PySQLite2Connector SQLite3ADBAPITests PyPgSQLADBAPITests
  502. # PsycopgADBAPITests MySQLADBAPITests FirebirdADBAPITests
  503. makeSQLTests(ADBAPITestBase, "ADBAPITests", globals())
  504. # PySQLite2Connector SQLite3ReconnectTests PyPgSQLReconnectTests
  505. # PsycopgReconnectTests MySQLReconnectTests FirebirdReconnectTests
  506. makeSQLTests(ReconnectTestBase, "ReconnectTests", globals())
  507. class FakePool:
  508. """
  509. A fake L{ConnectionPool} for tests.
  510. @ivar connectionFactory: factory for making connections returned by the
  511. C{connect} method.
  512. @type connectionFactory: any callable
  513. """
  514. reconnect = True
  515. noisy = True
  516. def __init__(self, connectionFactory):
  517. self.connectionFactory = connectionFactory
  518. def connect(self):
  519. """
  520. Return an instance of C{self.connectionFactory}.
  521. """
  522. return self.connectionFactory()
  523. def disconnect(self, connection):
  524. """
  525. Do nothing.
  526. """
  527. class ConnectionTests(unittest.TestCase):
  528. """
  529. Tests for the L{Connection} class.
  530. """
  531. def test_rollbackErrorLogged(self):
  532. """
  533. If an error happens during rollback, L{ConnectionLost} is raised but
  534. the original error is logged.
  535. """
  536. class ConnectionRollbackRaise:
  537. def rollback(self):
  538. raise RuntimeError("problem!")
  539. pool = FakePool(ConnectionRollbackRaise)
  540. connection = Connection(pool)
  541. self.assertRaises(ConnectionLost, connection.rollback)
  542. errors = self.flushLoggedErrors(RuntimeError)
  543. self.assertEqual(len(errors), 1)
  544. self.assertEqual(errors[0].value.args[0], "problem!")
  545. class TransactionTests(unittest.TestCase):
  546. """
  547. Tests for the L{Transaction} class.
  548. """
  549. def test_reopenLogErrorIfReconnect(self):
  550. """
  551. If the cursor creation raises an error in L{Transaction.reopen}, it
  552. reconnects but log the error occurred.
  553. """
  554. class ConnectionCursorRaise:
  555. count = 0
  556. def reconnect(self):
  557. pass
  558. def cursor(self):
  559. if self.count == 0:
  560. self.count += 1
  561. raise RuntimeError("problem!")
  562. pool = FakePool(None)
  563. transaction = Transaction(pool, ConnectionCursorRaise())
  564. transaction.reopen()
  565. errors = self.flushLoggedErrors(RuntimeError)
  566. self.assertEqual(len(errors), 1)
  567. self.assertEqual(errors[0].value.args[0], "problem!")
  568. class NonThreadPool:
  569. def callInThreadWithCallback(self, onResult, f, *a, **kw):
  570. success = True
  571. try:
  572. result = f(*a, **kw)
  573. except Exception:
  574. success = False
  575. result = Failure()
  576. onResult(success, result)
  577. class DummyConnectionPool(ConnectionPool):
  578. """
  579. A testable L{ConnectionPool};
  580. """
  581. threadpool = NonThreadPool()
  582. def __init__(self):
  583. """
  584. Don't forward init call.
  585. """
  586. self._reactor = reactor
  587. class EventReactor:
  588. """
  589. Partial L{IReactorCore} implementation with simple event-related
  590. methods.
  591. @ivar _running: A C{bool} indicating whether the reactor is pretending
  592. to have been started already or not.
  593. @ivar triggers: A C{list} of pending system event triggers.
  594. """
  595. def __init__(self, running):
  596. self._running = running
  597. self.triggers = []
  598. def callWhenRunning(self, function):
  599. if self._running:
  600. function()
  601. else:
  602. return self.addSystemEventTrigger("after", "startup", function)
  603. def addSystemEventTrigger(self, phase, event, trigger):
  604. handle = (phase, event, trigger)
  605. self.triggers.append(handle)
  606. return handle
  607. def removeSystemEventTrigger(self, handle):
  608. self.triggers.remove(handle)
  609. class ConnectionPoolTests(unittest.TestCase):
  610. """
  611. Unit tests for L{ConnectionPool}.
  612. """
  613. def test_runWithConnectionRaiseOriginalError(self):
  614. """
  615. If rollback fails, L{ConnectionPool.runWithConnection} raises the
  616. original exception and log the error of the rollback.
  617. """
  618. class ConnectionRollbackRaise:
  619. def __init__(self, pool):
  620. pass
  621. def rollback(self):
  622. raise RuntimeError("problem!")
  623. def raisingFunction(connection):
  624. raise ValueError("foo")
  625. pool = DummyConnectionPool()
  626. pool.connectionFactory = ConnectionRollbackRaise
  627. d = pool.runWithConnection(raisingFunction)
  628. d = self.assertFailure(d, ValueError)
  629. def cbFailed(ignored):
  630. errors = self.flushLoggedErrors(RuntimeError)
  631. self.assertEqual(len(errors), 1)
  632. self.assertEqual(errors[0].value.args[0], "problem!")
  633. d.addCallback(cbFailed)
  634. return d
  635. def test_closeLogError(self):
  636. """
  637. L{ConnectionPool._close} logs exceptions.
  638. """
  639. class ConnectionCloseRaise:
  640. def close(self):
  641. raise RuntimeError("problem!")
  642. pool = DummyConnectionPool()
  643. pool._close(ConnectionCloseRaise())
  644. errors = self.flushLoggedErrors(RuntimeError)
  645. self.assertEqual(len(errors), 1)
  646. self.assertEqual(errors[0].value.args[0], "problem!")
  647. def test_runWithInteractionRaiseOriginalError(self):
  648. """
  649. If rollback fails, L{ConnectionPool.runInteraction} raises the
  650. original exception and log the error of the rollback.
  651. """
  652. class ConnectionRollbackRaise:
  653. def __init__(self, pool):
  654. pass
  655. def rollback(self):
  656. raise RuntimeError("problem!")
  657. class DummyTransaction:
  658. def __init__(self, pool, connection):
  659. pass
  660. def raisingFunction(transaction):
  661. raise ValueError("foo")
  662. pool = DummyConnectionPool()
  663. pool.connectionFactory = ConnectionRollbackRaise
  664. pool.transactionFactory = DummyTransaction
  665. d = pool.runInteraction(raisingFunction)
  666. d = self.assertFailure(d, ValueError)
  667. def cbFailed(ignored):
  668. errors = self.flushLoggedErrors(RuntimeError)
  669. self.assertEqual(len(errors), 1)
  670. self.assertEqual(errors[0].value.args[0], "problem!")
  671. d.addCallback(cbFailed)
  672. return d
  673. def test_unstartedClose(self):
  674. """
  675. If L{ConnectionPool.close} is called without L{ConnectionPool.start}
  676. having been called, the pool's startup event is cancelled.
  677. """
  678. reactor = EventReactor(False)
  679. pool = ConnectionPool("twisted.test.test_adbapi", cp_reactor=reactor)
  680. # There should be a startup trigger waiting.
  681. self.assertEqual(reactor.triggers, [("after", "startup", pool._start)])
  682. pool.close()
  683. # But not anymore.
  684. self.assertFalse(reactor.triggers)
  685. def test_startedClose(self):
  686. """
  687. If L{ConnectionPool.close} is called after it has been started, but
  688. not by its shutdown trigger, the shutdown trigger is cancelled.
  689. """
  690. reactor = EventReactor(True)
  691. pool = ConnectionPool("twisted.test.test_adbapi", cp_reactor=reactor)
  692. # There should be a shutdown trigger waiting.
  693. self.assertEqual(reactor.triggers, [("during", "shutdown", pool.finalClose)])
  694. pool.close()
  695. # But not anymore.
  696. self.assertFalse(reactor.triggers)