backup.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import sqlite3 as sqlite
  2. import unittest
  3. @unittest.skipIf(sqlite.sqlite_version_info < (3, 6, 11), "Backup API not supported")
  4. class BackupTests(unittest.TestCase):
  5. def setUp(self):
  6. cx = self.cx = sqlite.connect(":memory:")
  7. cx.execute('CREATE TABLE foo (key INTEGER)')
  8. cx.executemany('INSERT INTO foo (key) VALUES (?)', [(3,), (4,)])
  9. cx.commit()
  10. def tearDown(self):
  11. self.cx.close()
  12. def verify_backup(self, bckcx):
  13. result = bckcx.execute("SELECT key FROM foo ORDER BY key").fetchall()
  14. self.assertEqual(result[0][0], 3)
  15. self.assertEqual(result[1][0], 4)
  16. def test_bad_target_none(self):
  17. with self.assertRaises(TypeError):
  18. self.cx.backup(None)
  19. def test_bad_target_filename(self):
  20. with self.assertRaises(TypeError):
  21. self.cx.backup('some_file_name.db')
  22. def test_bad_target_same_connection(self):
  23. with self.assertRaises(ValueError):
  24. self.cx.backup(self.cx)
  25. def test_bad_target_closed_connection(self):
  26. bck = sqlite.connect(':memory:')
  27. bck.close()
  28. with self.assertRaises(sqlite.ProgrammingError):
  29. self.cx.backup(bck)
  30. def test_bad_target_in_transaction(self):
  31. bck = sqlite.connect(':memory:')
  32. bck.execute('CREATE TABLE bar (key INTEGER)')
  33. bck.executemany('INSERT INTO bar (key) VALUES (?)', [(3,), (4,)])
  34. with self.assertRaises(sqlite.OperationalError) as cm:
  35. self.cx.backup(bck)
  36. if sqlite.sqlite_version_info < (3, 8, 8):
  37. self.assertEqual(str(cm.exception), 'target is in transaction')
  38. def test_keyword_only_args(self):
  39. with self.assertRaises(TypeError):
  40. with sqlite.connect(':memory:') as bck:
  41. self.cx.backup(bck, 1)
  42. def test_simple(self):
  43. with sqlite.connect(':memory:') as bck:
  44. self.cx.backup(bck)
  45. self.verify_backup(bck)
  46. def test_progress(self):
  47. journal = []
  48. def progress(status, remaining, total):
  49. journal.append(status)
  50. with sqlite.connect(':memory:') as bck:
  51. self.cx.backup(bck, pages=1, progress=progress)
  52. self.verify_backup(bck)
  53. self.assertEqual(len(journal), 2)
  54. self.assertEqual(journal[0], sqlite.SQLITE_OK)
  55. self.assertEqual(journal[1], sqlite.SQLITE_DONE)
  56. def test_progress_all_pages_at_once_1(self):
  57. journal = []
  58. def progress(status, remaining, total):
  59. journal.append(remaining)
  60. with sqlite.connect(':memory:') as bck:
  61. self.cx.backup(bck, progress=progress)
  62. self.verify_backup(bck)
  63. self.assertEqual(len(journal), 1)
  64. self.assertEqual(journal[0], 0)
  65. def test_progress_all_pages_at_once_2(self):
  66. journal = []
  67. def progress(status, remaining, total):
  68. journal.append(remaining)
  69. with sqlite.connect(':memory:') as bck:
  70. self.cx.backup(bck, pages=-1, progress=progress)
  71. self.verify_backup(bck)
  72. self.assertEqual(len(journal), 1)
  73. self.assertEqual(journal[0], 0)
  74. def test_non_callable_progress(self):
  75. with self.assertRaises(TypeError) as cm:
  76. with sqlite.connect(':memory:') as bck:
  77. self.cx.backup(bck, pages=1, progress='bar')
  78. self.assertEqual(str(cm.exception), 'progress argument must be a callable')
  79. def test_modifying_progress(self):
  80. journal = []
  81. def progress(status, remaining, total):
  82. if not journal:
  83. self.cx.execute('INSERT INTO foo (key) VALUES (?)', (remaining+1000,))
  84. self.cx.commit()
  85. journal.append(remaining)
  86. with sqlite.connect(':memory:') as bck:
  87. self.cx.backup(bck, pages=1, progress=progress)
  88. self.verify_backup(bck)
  89. result = bck.execute("SELECT key FROM foo"
  90. " WHERE key >= 1000"
  91. " ORDER BY key").fetchall()
  92. self.assertEqual(result[0][0], 1001)
  93. self.assertEqual(len(journal), 3)
  94. self.assertEqual(journal[0], 1)
  95. self.assertEqual(journal[1], 1)
  96. self.assertEqual(journal[2], 0)
  97. def test_failing_progress(self):
  98. def progress(status, remaining, total):
  99. raise SystemError('nearly out of space')
  100. with self.assertRaises(SystemError) as err:
  101. with sqlite.connect(':memory:') as bck:
  102. self.cx.backup(bck, progress=progress)
  103. self.assertEqual(str(err.exception), 'nearly out of space')
  104. def test_database_source_name(self):
  105. with sqlite.connect(':memory:') as bck:
  106. self.cx.backup(bck, name='main')
  107. with sqlite.connect(':memory:') as bck:
  108. self.cx.backup(bck, name='temp')
  109. with self.assertRaises(sqlite.OperationalError) as cm:
  110. with sqlite.connect(':memory:') as bck:
  111. self.cx.backup(bck, name='non-existing')
  112. self.assertIn(
  113. str(cm.exception),
  114. ['SQL logic error', 'SQL logic error or missing database']
  115. )
  116. self.cx.execute("ATTACH DATABASE ':memory:' AS attached_db")
  117. self.cx.execute('CREATE TABLE attached_db.foo (key INTEGER)')
  118. self.cx.executemany('INSERT INTO attached_db.foo (key) VALUES (?)', [(3,), (4,)])
  119. self.cx.commit()
  120. with sqlite.connect(':memory:') as bck:
  121. self.cx.backup(bck, name='attached_db')
  122. self.verify_backup(bck)
  123. def suite():
  124. return unittest.makeSuite(BackupTests)
  125. if __name__ == "__main__":
  126. unittest.main()