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.
 
 
 

161 lines
5.5 KiB

  1. import nilmdb
  2. import nilmdb.fsck
  3. from nilmdb.utils.printf import printf
  4. from nose.tools import assert_raises
  5. import io
  6. import sys
  7. import shutil
  8. import traceback
  9. from testutil.helpers import *
  10. class TestFsck(object):
  11. def run(self, db, fix=True, skip=False, checker=None):
  12. """Run fsck client with the given test database. Save the output and
  13. exit code.
  14. """
  15. if db is not None:
  16. recursive_unlink("tests/fsck-testdb")
  17. shutil.copytree(f"tests/fsck-data/{db}", "tests/fsck-testdb")
  18. class stdio_wrapper:
  19. def __init__(self, stdin, stdout, stderr):
  20. self.io = (stdin, stdout, stderr)
  21. def __enter__(self):
  22. self.saved = ( sys.stdin, sys.stdout, sys.stderr )
  23. ( sys.stdin, sys.stdout, sys.stderr ) = self.io
  24. def __exit__(self, type, value, traceback):
  25. ( sys.stdin, sys.stdout, sys.stderr ) = self.saved
  26. # Empty input
  27. infile = io.TextIOWrapper(io.BytesIO(b""))
  28. # Capture stdout, stderr
  29. errfile = io.TextIOWrapper(io.BytesIO())
  30. outfile = errfile
  31. with stdio_wrapper(infile, outfile, errfile) as s:
  32. try:
  33. f = nilmdb.fsck.Fsck("tests/fsck-testdb", fix)
  34. if checker:
  35. checker(f)
  36. else:
  37. f.check(skip_data=skip)
  38. sys.exit(0)
  39. except SystemExit as e:
  40. exitcode = e.code
  41. except Exception as e:
  42. traceback.print_exc()
  43. exitcode = 1
  44. # Capture raw binary output, and also try to decode a Unicode
  45. # string copy.
  46. self.captured_binary = outfile.buffer.getvalue()
  47. try:
  48. outfile.seek(0)
  49. self.captured = outfile.read()
  50. except UnicodeDecodeError:
  51. self.captured = None
  52. self.exitcode = exitcode
  53. def ok(self, *args, **kwargs):
  54. self.run(*args, **kwargs)
  55. if self.exitcode != 0:
  56. self.dump()
  57. eq_(self.exitcode, 0)
  58. def okmsg(self, db, expect, **kwargs):
  59. self.ok(db, **kwargs)
  60. self.contain(expect)
  61. def fail(self, *args, exitcode=None, **kwargs):
  62. self.run(*args, **kwargs)
  63. if exitcode is not None and self.exitcode != exitcode:
  64. # Wrong exit code
  65. self.dump()
  66. eq_(self.exitcode, exitcode)
  67. if self.exitcode == 0:
  68. # Success, when we wanted failure
  69. self.dump()
  70. ne_(self.exitcode, 0)
  71. def failmsg(self, db, expect, **kwargs):
  72. self.fail(db, **kwargs)
  73. self.contain(expect)
  74. def contain(self, checkstring, contain=True):
  75. if contain:
  76. in_(checkstring, self.captured)
  77. else:
  78. nin_(checkstring, self.captured)
  79. def dump(self):
  80. printf("\n===dump start===\n%s===dump end===\n", self.captured)
  81. def test_fsck(self):
  82. self.okmsg("test1", "\nok")
  83. def check_paths_twice(f):
  84. f.check()
  85. f.check_paths()
  86. f.bulk.close()
  87. self.ok("test1", checker=check_paths_twice)
  88. with open("tests/fsck-testdb/data.lock", "w") as lock:
  89. nilmdb.utils.lock.exclusive_lock(lock)
  90. self.failmsg(None, "Database already locked")
  91. self.failmsg("test1a", "SQL database missing")
  92. self.failmsg("test1b", "Bulk data directory missing")
  93. self.failmsg("test1c", "database version 0 too old")
  94. self.okmsg("test2", "\nok")
  95. self.failmsg("test2a", "duplicated ID 1 in stream IDs")
  96. self.failmsg("test2b", "interval ID 2 not in streams")
  97. self.failmsg("test2c", "metadata ID 2 not in streams")
  98. self.failmsg("test2d", "duplicate metadata key")
  99. self.failmsg("test2e", "duplicated path")
  100. self.failmsg("test2f", "bad layout")
  101. self.failmsg("test2g", "bad count")
  102. self.failmsg("test2h", "missing bulkdata dir")
  103. self.failmsg("test2i", "bad bulkdata table")
  104. self.failmsg("test2j", "overlap in intervals")
  105. self.failmsg("test2k", "overlap in file offsets")
  106. self.ok("test2k1")
  107. self.failmsg("test2l", "unsupported bulkdata version")
  108. self.failmsg("test2m", "bad rows_per_file")
  109. self.failmsg("test2n", "bad files_per_dir")
  110. self.failmsg("test2o", "layout mismatch")
  111. self.failmsg("test2p", "missing data files", fix=False)
  112. self.contain("This may be fixable")
  113. self.okmsg("test2p", "Removing empty subpath")
  114. self.failmsg("test2p1", "please manually remove the file")
  115. self.okmsg("test2p2", "Removing empty subpath")
  116. self.failmsg("test2q", "extra bytes present", fix=False)
  117. self.okmsg("test2q", "Truncating file")
  118. self.failmsg("test2r", "error accessing rows", fix=False)
  119. self.okmsg("test2r", "end position is past endrows")
  120. self.okmsg("test2r1", "actually it can't be truncated")
  121. self.contain("Deleting the entire interval")
  122. self.contain("restarting fsck")
  123. self.ok("test2r2")
  124. self.failmsg("test2s", "non-monotonic timestamp(s)")
  125. def check_small_maxrows(f):
  126. f.maxrows_override = 1
  127. f.check()
  128. self.fail("test2t", checker=check_small_maxrows)
  129. self.contain("first interval timestamp 0 is not greater")
  130. self.ok("test2t", skip=True)
  131. @nilmdb.fsck.fsck.retry_if_raised(Exception, max_retries=3)
  132. def foo():
  133. raise Exception("hi")
  134. with assert_raises(Exception):
  135. foo()