1""" 2Copyright (c) 2008-2020, Jesus Cea Avion <jcea@jcea.es> 3All rights reserved. 4 5Redistribution and use in source and binary forms, with or without 6modification, are permitted provided that the following conditions 7are met: 8 9 1. Redistributions of source code must retain the above copyright 10 notice, this list of conditions and the following disclaimer. 11 12 2. Redistributions in binary form must reproduce the above 13 copyright notice, this list of conditions and the following 14 disclaimer in the documentation and/or other materials provided 15 with the distribution. 16 17 3. Neither the name of Jesus Cea Avion nor the names of its 18 contributors may be used to endorse or promote products derived 19 from this software without specific prior written permission. 20 21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 22 CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 23 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 24 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 26 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 27 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 28 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 30 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 31 TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 32 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 SUCH DAMAGE. 34 """ 35 36""" 37TestCases for DB.associate. 38""" 39 40import sys, os, string 41import time 42from pprint import pprint 43 44import unittest 45from .test_all import db, dbshelve, test_support, verbose, have_threads, \ 46 get_new_environment_path 47 48 49#---------------------------------------------------------------------- 50 51 52musicdata = { 531 : ("Bad English", "The Price Of Love", "Rock"), 542 : ("DNA featuring Suzanne Vega", "Tom's Diner", "Rock"), 553 : ("George Michael", "Praying For Time", "Rock"), 564 : ("Gloria Estefan", "Here We Are", "Rock"), 575 : ("Linda Ronstadt", "Don't Know Much", "Rock"), 586 : ("Michael Bolton", "How Am I Supposed To Live Without You", "Blues"), 597 : ("Paul Young", "Oh Girl", "Rock"), 608 : ("Paula Abdul", "Opposites Attract", "Rock"), 619 : ("Richard Marx", "Should've Known Better", "Rock"), 6210: ("Rod Stewart", "Forever Young", "Rock"), 6311: ("Roxette", "Dangerous", "Rock"), 6412: ("Sheena Easton", "The Lover In Me", "Rock"), 6513: ("Sinead O'Connor", "Nothing Compares 2 U", "Rock"), 6614: ("Stevie B.", "Because I Love You", "Rock"), 6715: ("Taylor Dayne", "Love Will Lead You Back", "Rock"), 6816: ("The Bangles", "Eternal Flame", "Rock"), 6917: ("Wilson Phillips", "Release Me", "Rock"), 7018: ("Billy Joel", "Blonde Over Blue", "Rock"), 7119: ("Billy Joel", "Famous Last Words", "Rock"), 7220: ("Billy Joel", "Lullabye (Goodnight, My Angel)", "Rock"), 7321: ("Billy Joel", "The River Of Dreams", "Rock"), 7422: ("Billy Joel", "Two Thousand Years", "Rock"), 7523: ("Janet Jackson", "Alright", "Rock"), 7624: ("Janet Jackson", "Black Cat", "Rock"), 7725: ("Janet Jackson", "Come Back To Me", "Rock"), 7826: ("Janet Jackson", "Escapade", "Rock"), 7927: ("Janet Jackson", "Love Will Never Do (Without You)", "Rock"), 8028: ("Janet Jackson", "Miss You Much", "Rock"), 8129: ("Janet Jackson", "Rhythm Nation", "Rock"), 8230: ("Janet Jackson", "State Of The World", "Rock"), 8331: ("Janet Jackson", "The Knowledge", "Rock"), 8432: ("Spyro Gyra", "End of Romanticism", "Jazz"), 8533: ("Spyro Gyra", "Heliopolis", "Jazz"), 8634: ("Spyro Gyra", "Jubilee", "Jazz"), 8735: ("Spyro Gyra", "Little Linda", "Jazz"), 8836: ("Spyro Gyra", "Morning Dance", "Jazz"), 8937: ("Spyro Gyra", "Song for Lorraine", "Jazz"), 9038: ("Yes", "Owner Of A Lonely Heart", "Rock"), 9139: ("Yes", "Rhythm Of Love", "Rock"), 9240: ("Cusco", "Dream Catcher", "New Age"), 9341: ("Cusco", "Geronimos Laughter", "New Age"), 9442: ("Cusco", "Ghost Dance", "New Age"), 9543: ("Blue Man Group", "Drumbone", "New Age"), 9644: ("Blue Man Group", "Endless Column", "New Age"), 9745: ("Blue Man Group", "Klein Mandelbrot", "New Age"), 9846: ("Kenny G", "Silhouette", "Jazz"), 9947: ("Sade", "Smooth Operator", "Jazz"), 10048: ("David Arkenstone", "Papillon (On The Wings Of The Butterfly)", 101 "New Age"), 10249: ("David Arkenstone", "Stepping Stars", "New Age"), 10350: ("David Arkenstone", "Carnation Lily Lily Rose", "New Age"), 10451: ("David Lanz", "Behind The Waterfall", "New Age"), 10552: ("David Lanz", "Cristofori's Dream", "New Age"), 10653: ("David Lanz", "Heartsounds", "New Age"), 10754: ("David Lanz", "Leaves on the Seine", "New Age"), 10899: ("unknown artist", "Unnamed song", "Unknown"), 109} 110 111#---------------------------------------------------------------------- 112 113class AssociateErrorTestCase(unittest.TestCase): 114 def setUp(self): 115 self.filename = self.__class__.__name__ + '.db' 116 self.homeDir = get_new_environment_path() 117 self.env = db.DBEnv() 118 self.env.open(self.homeDir, db.DB_CREATE | db.DB_INIT_MPOOL) 119 120 def tearDown(self): 121 self.env.close() 122 self.env = None 123 test_support.rmtree(self.homeDir) 124 125 def test00_associateDBError(self): 126 if verbose: 127 print('\n', '-=' * 30) 128 print("Running %s.test00_associateDBError..." % \ 129 self.__class__.__name__) 130 131 dupDB = db.DB(self.env) 132 dupDB.set_flags(db.DB_DUP) 133 dupDB.open(self.filename, "primary", db.DB_BTREE, db.DB_CREATE) 134 135 secDB = db.DB(self.env) 136 secDB.open(self.filename, "secondary", db.DB_BTREE, db.DB_CREATE) 137 138 # dupDB has been configured to allow duplicates, it can't 139 # associate with a secondary. Berkeley DB will return an error. 140 try: 141 def f(a,b): return a+b 142 dupDB.associate(secDB, f) 143 except db.DBError: 144 # good 145 secDB.close() 146 dupDB.close() 147 else: 148 secDB.close() 149 dupDB.close() 150 self.fail("DBError exception was expected") 151 152 153 154#---------------------------------------------------------------------- 155 156 157class AssociateTestCase(unittest.TestCase): 158 keytype = '' 159 envFlags = 0 160 dbFlags = 0 161 162 def setUp(self): 163 self.filename = self.__class__.__name__ + '.db' 164 self.homeDir = get_new_environment_path() 165 self.env = db.DBEnv() 166 self.env.open(self.homeDir, db.DB_CREATE | db.DB_INIT_MPOOL | 167 db.DB_INIT_LOCK | db.DB_THREAD | self.envFlags) 168 169 def tearDown(self): 170 self.closeDB() 171 self.env.close() 172 self.env = None 173 test_support.rmtree(self.homeDir) 174 175 def addDataToDB(self, d, txn=None): 176 for key, value in list(musicdata.items()): 177 if type(self.keytype) == type(''): 178 key = "%02d" % key 179 d.put(key, '|'.join(value), txn=txn) 180 181 def createDB(self, txn=None): 182 self.cur = None 183 self.secDB = None 184 self.primary = db.DB(self.env) 185 self.primary.set_get_returns_none(2) 186 self.primary.open(self.filename, "primary", self.dbtype, 187 db.DB_CREATE | db.DB_THREAD | self.dbFlags, txn=txn) 188 189 def closeDB(self): 190 if self.cur: 191 self.cur.close() 192 self.cur = None 193 if self.secDB: 194 self.secDB.close() 195 self.secDB = None 196 self.primary.close() 197 self.primary = None 198 199 def getDB(self): 200 return self.primary 201 202 203 def _associateWithDB(self, getGenre): 204 self.createDB() 205 206 self.secDB = db.DB(self.env) 207 self.secDB.set_flags(db.DB_DUP) 208 self.secDB.set_get_returns_none(2) 209 self.secDB.open(self.filename, "secondary", db.DB_BTREE, 210 db.DB_CREATE | db.DB_THREAD | self.dbFlags) 211 self.getDB().associate(self.secDB, getGenre) 212 213 self.addDataToDB(self.getDB()) 214 215 self.finish_test(self.secDB) 216 217 def test01_associateWithDB(self): 218 if verbose: 219 print('\n', '-=' * 30) 220 print("Running %s.test01_associateWithDB..." % \ 221 self.__class__.__name__) 222 223 return self._associateWithDB(self.getGenre) 224 225 def _associateAfterDB(self, getGenre) : 226 self.createDB() 227 self.addDataToDB(self.getDB()) 228 229 self.secDB = db.DB(self.env) 230 self.secDB.set_flags(db.DB_DUP) 231 self.secDB.open(self.filename, "secondary", db.DB_BTREE, 232 db.DB_CREATE | db.DB_THREAD | self.dbFlags) 233 234 # adding the DB_CREATE flag will cause it to index existing records 235 self.getDB().associate(self.secDB, getGenre, db.DB_CREATE) 236 237 self.finish_test(self.secDB) 238 239 def test02_associateAfterDB(self): 240 if verbose: 241 print('\n', '-=' * 30) 242 print("Running %s.test02_associateAfterDB..." % \ 243 self.__class__.__name__) 244 245 return self._associateAfterDB(self.getGenre) 246 247 def test03_associateWithDB(self): 248 if verbose: 249 print('\n', '-=' * 30) 250 print("Running %s.test03_associateWithDB..." % \ 251 self.__class__.__name__) 252 253 return self._associateWithDB(self.getGenreList) 254 255 def test04_associateAfterDB(self): 256 if verbose: 257 print('\n', '-=' * 30) 258 print("Running %s.test04_associateAfterDB..." % \ 259 self.__class__.__name__) 260 261 return self._associateAfterDB(self.getGenreList) 262 263 264 def finish_test(self, secDB, txn=None): 265 # 'Blues' should not be in the secondary database 266 vals = secDB.pget('Blues', txn=txn) 267 self.assertEqual(vals, None, vals) 268 269 vals = secDB.pget('Unknown', txn=txn) 270 self.assertTrue(vals[0] == 99 or vals[0] == '99', vals) 271 vals[1].index('Unknown') 272 vals[1].index('Unnamed') 273 vals[1].index('unknown') 274 275 if verbose: 276 print("Primary key traversal:") 277 self.cur = self.getDB().cursor(txn) 278 count = 0 279 rec = self.cur.first() 280 while rec is not None: 281 if type(self.keytype) == type(''): 282 self.assertTrue(int(rec[0])) # for primary db, key is a number 283 else: 284 self.assertTrue(rec[0] and type(rec[0]) == type(0)) 285 count = count + 1 286 if verbose: 287 print(rec) 288 rec = getattr(self.cur, "next")() 289 self.assertEqual(count, len(musicdata)) # all items accounted for 290 291 292 if verbose: 293 print("Secondary key traversal:") 294 self.cur = secDB.cursor(txn) 295 count = 0 296 297 # test cursor pget 298 vals = self.cur.pget('Unknown', flags=db.DB_LAST) 299 self.assertTrue(vals[1] == 99 or vals[1] == '99', vals) 300 self.assertEqual(vals[0], 'Unknown') 301 vals[2].index('Unknown') 302 vals[2].index('Unnamed') 303 vals[2].index('unknown') 304 305 vals = self.cur.pget('Unknown', data='wrong value', flags=db.DB_GET_BOTH) 306 self.assertEqual(vals, None, vals) 307 308 rec = self.cur.first() 309 self.assertEqual(rec[0], "Jazz") 310 while rec is not None: 311 count = count + 1 312 if verbose: 313 print(rec) 314 rec = getattr(self.cur, "next")() 315 # all items accounted for EXCEPT for 1 with "Blues" genre 316 self.assertEqual(count, len(musicdata)-1) 317 318 self.cur = None 319 320 def getGenre(self, priKey, priData): 321 self.assertEqual(type(priData), type("")) 322 genre = priData.split('|')[2] 323 324 if verbose: 325 print('getGenre key: %r data: %r' % (priKey, priData)) 326 327 if genre == 'Blues': 328 return db.DB_DONOTINDEX 329 else: 330 return genre 331 332 def getGenreList(self, priKey, PriData) : 333 v = self.getGenre(priKey, PriData) 334 if type(v) == type("") : 335 v = [v] 336 return v 337 338 339#---------------------------------------------------------------------- 340 341 342class AssociateHashTestCase(AssociateTestCase): 343 dbtype = db.DB_HASH 344 345class AssociateBTreeTestCase(AssociateTestCase): 346 dbtype = db.DB_BTREE 347 348class AssociateRecnoTestCase(AssociateTestCase): 349 dbtype = db.DB_RECNO 350 keytype = 0 351 352#---------------------------------------------------------------------- 353 354class AssociateBTreeTxnTestCase(AssociateBTreeTestCase): 355 envFlags = db.DB_INIT_TXN 356 dbFlags = 0 357 358 def txn_finish_test(self, sDB, txn): 359 try: 360 self.finish_test(sDB, txn=txn) 361 finally: 362 if self.cur: 363 self.cur.close() 364 self.cur = None 365 if txn: 366 txn.commit() 367 368 def test13_associate_in_transaction(self): 369 if verbose: 370 print('\n', '-=' * 30) 371 print("Running %s.test13_associateAutoCommit..." % \ 372 self.__class__.__name__) 373 374 txn = self.env.txn_begin() 375 try: 376 self.createDB(txn=txn) 377 378 self.secDB = db.DB(self.env) 379 self.secDB.set_flags(db.DB_DUP) 380 self.secDB.set_get_returns_none(2) 381 self.secDB.open(self.filename, "secondary", db.DB_BTREE, 382 db.DB_CREATE | db.DB_THREAD, txn=txn) 383 self.getDB().associate(self.secDB, self.getGenre, txn=txn) 384 385 self.addDataToDB(self.getDB(), txn=txn) 386 except: 387 txn.abort() 388 raise 389 390 self.txn_finish_test(self.secDB, txn=txn) 391 392 393#---------------------------------------------------------------------- 394 395class ShelveAssociateTestCase(AssociateTestCase): 396 397 def createDB(self): 398 self.primary = dbshelve.open(self.filename, 399 dbname="primary", 400 dbenv=self.env, 401 filetype=self.dbtype) 402 403 def addDataToDB(self, d): 404 for key, value in list(musicdata.items()): 405 if type(self.keytype) == type(''): 406 key = "%02d" % key 407 d.put(key, value) # save the value as is this time 408 409 410 def getGenre(self, priKey, priData): 411 self.assertEqual(type(priData), type(())) 412 if verbose: 413 print('getGenre key: %r data: %r' % (priKey, priData)) 414 genre = priData[2] 415 if genre == 'Blues': 416 return db.DB_DONOTINDEX 417 else: 418 return genre 419 420 421class ShelveAssociateHashTestCase(ShelveAssociateTestCase): 422 dbtype = db.DB_HASH 423 424class ShelveAssociateBTreeTestCase(ShelveAssociateTestCase): 425 dbtype = db.DB_BTREE 426 427class ShelveAssociateRecnoTestCase(ShelveAssociateTestCase): 428 dbtype = db.DB_RECNO 429 keytype = 0 430 431 432#---------------------------------------------------------------------- 433 434class ThreadedAssociateTestCase(AssociateTestCase): 435 436 def addDataToDB(self, d): 437 t1 = Thread(target = self.writer1, 438 args = (d, )) 439 t2 = Thread(target = self.writer2, 440 args = (d, )) 441 442 t1.setDaemon(True) 443 t2.setDaemon(True) 444 t1.start() 445 t2.start() 446 t1.join() 447 t2.join() 448 449 def writer1(self, d): 450 for key, value in list(musicdata.items()): 451 if type(self.keytype) == type(''): 452 key = "%02d" % key 453 d.put(key, '|'.join(value)) 454 455 def writer2(self, d): 456 for x in range(100, 600): 457 key = 'z%2d' % x 458 value = [key] * 4 459 d.put(key, '|'.join(value)) 460 461 462class ThreadedAssociateHashTestCase(ShelveAssociateTestCase): 463 dbtype = db.DB_HASH 464 465class ThreadedAssociateBTreeTestCase(ShelveAssociateTestCase): 466 dbtype = db.DB_BTREE 467 468class ThreadedAssociateRecnoTestCase(ShelveAssociateTestCase): 469 dbtype = db.DB_RECNO 470 keytype = 0 471 472 473#---------------------------------------------------------------------- 474 475def test_suite(): 476 suite = unittest.TestSuite() 477 478 suite.addTest(unittest.makeSuite(AssociateErrorTestCase)) 479 480 suite.addTest(unittest.makeSuite(AssociateHashTestCase)) 481 suite.addTest(unittest.makeSuite(AssociateBTreeTestCase)) 482 suite.addTest(unittest.makeSuite(AssociateRecnoTestCase)) 483 484 suite.addTest(unittest.makeSuite(AssociateBTreeTxnTestCase)) 485 486 suite.addTest(unittest.makeSuite(ShelveAssociateHashTestCase)) 487 suite.addTest(unittest.makeSuite(ShelveAssociateBTreeTestCase)) 488 suite.addTest(unittest.makeSuite(ShelveAssociateRecnoTestCase)) 489 490 if have_threads: 491 suite.addTest(unittest.makeSuite(ThreadedAssociateHashTestCase)) 492 suite.addTest(unittest.makeSuite(ThreadedAssociateBTreeTestCase)) 493 suite.addTest(unittest.makeSuite(ThreadedAssociateRecnoTestCase)) 494 495 return suite 496 497 498if __name__ == '__main__': 499 unittest.main(defaultTest='test_suite') 500