1
0
Fork 0
mirror of https://github.com/borgbackup/borg.git synced 2024-12-26 01:37:20 +00:00

Add self tests

These run in ~100 ms here, so even on much slower machines (where also
Python setup will be slower) it shouldn't be noticeable at all.
This commit is contained in:
Marian Beermann 2016-04-28 00:06:19 +02:00
parent 528913b220
commit 8d3b1a5804
8 changed files with 125 additions and 21 deletions

View file

@ -34,6 +34,7 @@
from .key import key_creator, RepoKey, PassphraseKey
from .archive import Archive, ArchiveChecker, ArchiveRecreater
from .remote import RepositoryServer, RemoteRepository, cache_if_remote
from .selftest import selftest
from .hashindex import ChunkIndexEntry
has_lchflags = hasattr(os, 'lchflags')
@ -1901,13 +1902,17 @@ def parse_args(self, args=None):
update_excludes(args)
return args
def prerun_checks(self, logger):
check_extension_modules()
selftest(logger)
def run(self, args):
os.umask(args.umask) # early, before opening files
self.lock_wait = args.lock_wait
setup_logging(level=args.log_level, is_serve=args.func == self.do_serve) # do not use loggers before this!
if args.show_version:
logger.info('borgbackup version %s' % __version__)
check_extension_modules()
self.prerun_checks(logger)
if is_slow_msgpack():
logger.warning("Using a pure-python msgpack! This will result in lower performance.")
return args.func(args)

79
borg/selftest.py Normal file
View file

@ -0,0 +1,79 @@
"""
Self testing module
===================
The selftest() function runs a small test suite of relatively fast tests that are meant to discover issues
with the way Borg was compiled or packaged and also bugs in Borg itself.
Theses tests are a subset of the borg/testsuite and are run with Pythons built-in unittest, hence none of
the tests used for this can or should be ported to py.test currently.
To assert that self test discovery works correctly the number of tests is kept in the SELFTEST_COUNT
variable. SELFTEST_COUNT must be updated if new tests are added or removed to or from any of the tests
used here.
"""
import sys
import time
from unittest import TestResult, TestSuite, defaultTestLoader
from .testsuite.hashindex import HashIndexDataTestCase, HashIndexRefcountingTestCase, HashIndexTestCase
from .testsuite.crypto import CryptoTestCase
from .testsuite.chunker import ChunkerTestCase
SELFTEST_CASES = [
HashIndexDataTestCase,
HashIndexRefcountingTestCase,
HashIndexTestCase,
CryptoTestCase,
ChunkerTestCase,
]
SELFTEST_COUNT = 27
class SelfTestResult(TestResult):
def __init__(self):
super().__init__()
self.successes = []
def addSuccess(self, test):
super().addSuccess(test)
self.successes.append(test)
def test_name(self, test):
return test.shortDescription() or str(test)
def log_results(self, logger):
for test, failure in self.errors + self.failures + self.unexpectedSuccesses:
logger.error('self test %s FAILED:\n%s', self.test_name(test), failure)
for test, reason in self.skipped:
logger.warning('self test %s skipped: %s', self.test_name(test), reason)
def successful_test_count(self):
return len(self.successes)
def selftest(logger):
selftest_started = time.perf_counter()
result = SelfTestResult()
test_suite = TestSuite()
for test_case in SELFTEST_CASES:
test_suite.addTest(defaultTestLoader.loadTestsFromTestCase(test_case))
test_suite.run(result)
result.log_results(logger)
successful_tests = result.successful_test_count()
count_mismatch = successful_tests != SELFTEST_COUNT
if result.wasSuccessful() and count_mismatch:
# only print this if all tests succeeded
logger.error("self test count (%d != %d) mismatch, either test discovery is broken or a test was added "
"without updating borg.selftest",
successful_tests, SELFTEST_COUNT)
if not result.wasSuccessful() or count_mismatch:
logger.error("self test failed\n"
"This is a bug either in Borg or in the package / distribution you use.")
sys.exit(2)
assert False, "sanity assertion failed: ran beyond sys.exit()"
selftest_elapsed = time.perf_counter() - selftest_started
logger.debug("%d self tests completed in %.2f seconds", successful_tests, selftest_elapsed)

View file

@ -8,7 +8,8 @@
import time
import unittest
from ..xattr import get_all
from ..logger import setup_logging
# Note: this is used by borg.selftest, do not use or import py.test functionality here.
try:
import llfuse
@ -17,6 +18,11 @@
except ImportError:
have_fuse_mtime_ns = False
try:
from pytest import raises
except ImportError:
raises = None
has_lchflags = hasattr(os, 'lchflags')
@ -31,9 +37,6 @@
if sys.platform.startswith('netbsd'):
st_mtime_ns_round = -4 # only >1 microsecond resolution here?
# Ensure that the loggers exist for all tests
setup_logging()
class BaseTestCase(unittest.TestCase):
"""
@ -42,9 +45,13 @@ class BaseTestCase(unittest.TestCase):
assert_not_in = unittest.TestCase.assertNotIn
assert_equal = unittest.TestCase.assertEqual
assert_not_equal = unittest.TestCase.assertNotEqual
assert_raises = unittest.TestCase.assertRaises
assert_true = unittest.TestCase.assertTrue
if raises:
assert_raises = staticmethod(raises)
else:
assert_raises = unittest.TestCase.assertRaises
@contextmanager
def assert_creates_file(self, path):
self.assert_true(not os.path.exists(path), '{} should not exist'.format(path))

View file

@ -62,6 +62,7 @@ def exec_cmd(*args, archiver=None, fork=False, exe=None, **kw):
sys.stdout = sys.stderr = output = StringIO()
if archiver is None:
archiver = Archiver()
archiver.prerun_checks = lambda *args: None
archiver.exit_code = EXIT_SUCCESS
args = archiver.parse_args(list(args))
ret = archiver.run(args)

View file

@ -4,6 +4,9 @@
from ..constants import * # NOQA
from . import BaseTestCase
# Note: these tests are part of the self test, do not use or import py.test functionality here.
# See borg.selftest for details. If you add/remove test methods, update SELFTEST_COUNT
class ChunkerTestCase(BaseTestCase):

View file

@ -0,0 +1,4 @@
from ..logger import setup_logging
# Ensure that the loggers exist for all tests
setup_logging()

View file

@ -3,6 +3,9 @@
from ..crypto import AES, bytes_to_long, bytes_to_int, long_to_bytes, hmac_sha256
from . import BaseTestCase
# Note: these tests are part of the self test, do not use or import py.test functionality here.
# See borg.selftest for details. If you add/remove test methods, update SELFTEST_COUNT
class CryptoTestCase(BaseTestCase):

View file

@ -1,15 +1,16 @@
import base64
import hashlib
import os
import struct
import tempfile
import zlib
import pytest
from ..hashindex import NSIndex, ChunkIndex
from .. import hashindex
from . import BaseTestCase
# Note: these tests are part of the self test, do not use or import py.test functionality here.
# See borg.selftest for details. If you add/remove test methods, update SELFTEST_COUNT
def H(x):
# make some 32byte long thing that depends on x
@ -194,7 +195,7 @@ def test_decref_limit(self):
def test_decref_zero(self):
idx1 = ChunkIndex()
idx1[H(1)] = 0, 0, 0
with pytest.raises(AssertionError):
with self.assert_raises(AssertionError):
idx1.decref(H(1))
def test_incref_decref(self):
@ -208,18 +209,18 @@ def test_incref_decref(self):
def test_setitem_raises(self):
idx1 = ChunkIndex()
with pytest.raises(AssertionError):
with self.assert_raises(AssertionError):
idx1[H(1)] = hashindex.MAX_VALUE + 1, 0, 0
def test_keyerror(self):
idx = ChunkIndex()
with pytest.raises(KeyError):
with self.assert_raises(KeyError):
idx.incref(H(1))
with pytest.raises(KeyError):
with self.assert_raises(KeyError):
idx.decref(H(1))
with pytest.raises(KeyError):
with self.assert_raises(KeyError):
idx[H(1)]
with pytest.raises(OverflowError):
with self.assert_raises(OverflowError):
idx.add(H(1), -1, 0, 0)
@ -269,10 +270,11 @@ def test_read_known_good(self):
assert idx1[H(3)] == (hashindex.MAX_VALUE, 6, 7)
def test_nsindex_segment_limit():
idx = NSIndex()
with pytest.raises(AssertionError):
idx[H(1)] = hashindex.MAX_VALUE + 1, 0
assert H(1) not in idx
idx[H(2)] = hashindex.MAX_VALUE, 0
assert H(2) in idx
class NSIndexTestCase(BaseTestCase):
def test_nsindex_segment_limit(self):
idx = NSIndex()
with self.assert_raises(AssertionError):
idx[H(1)] = hashindex.MAX_VALUE + 1, 0
assert H(1) not in idx
idx[H(2)] = hashindex.MAX_VALUE, 0
assert H(2) in idx