From cabcc9b3f49e59e57c3909ec83de74bab9911f7f Mon Sep 17 00:00:00 2001 From: sebres Date: Fri, 15 Dec 2023 00:01:22 +0100 Subject: [PATCH] fixes testRepairDb for sqlite >= 3.42; closes gh-3586 --- .github/workflows/main.yml | 1 + fail2ban/tests/databasetestcase.py | 32 +++++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 866552ef..1d610fa9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,6 +54,7 @@ jobs: #if [[ "$F2B_PY" = 3 ]]; then python -m pip install --upgrade pip || echo "can't upgrade pip"; fi #sudo apt-get -y install python${F2B_PY/2/}-pyinotify || echo 'inotify not available' python -m pip install pyinotify || echo 'inotify not available' + sudo apt-get -y install sqlite3 || echo 'sqlite3 not available' #sudo apt-get -y install python${F2B_PY/2/}-systemd || echo 'systemd not available' sudo apt-get -y install libsystemd-dev || echo 'systemd dependencies seems to be unavailable' python -m pip install systemd-python || echo 'systemd not available' diff --git a/fail2ban/tests/databasetestcase.py b/fail2ban/tests/databasetestcase.py index c54f7489..31dd1940 100644 --- a/fail2ban/tests/databasetestcase.py +++ b/fail2ban/tests/databasetestcase.py @@ -39,7 +39,7 @@ try: Fail2BanDb = database.Fail2BanDb except ImportError: # pragma: no cover Fail2BanDb = None -from .utils import LogCaptureTestCase, logSys as DefLogSys +from .utils import LogCaptureTestCase, logSys as DefLogSys, uni_decode TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files") @@ -114,9 +114,32 @@ class DatabaseTest(LogCaptureTestCase): self.jail.name in self.db.getJailNames(), "Jail not retained in Db after disconnect reconnect.") + @staticmethod + def _mockupFailedDB(): # pragma: no cover -- only sqlite >= 3.42 + """[Mock-Up] broken connect to cover reparable restore.""" + _org_connect = sqlite3.connect; + class _mckp_Cursor(sqlite3.Cursor): + def execute(*args, **kwargs): + # intended BOOM (simulate broken database): + raise sqlite3.Error("[mock-up] broken database"); + class _mckp_Connection(sqlite3.Connection): + def cursor(*args, **kwargs): + return _mckp_Cursor(*args, **kwargs) + def _mckp_connect(*args, **kwargs): + DefLogSys.debug("[mock-up] broken connect to cover reparable restore") + # restore original connect immediately: + sqlite3.connect = _org_connect + # return mockup connect (caused BOOM during first cursor execute): + return _mckp_Connection(*args, **kwargs); + sqlite3.connect = _mckp_connect; + def testRepairDb(self): - if not Utils.executeCmd("sqlite3 --version"): # pragma: no cover + ret = Utils.executeCmd("sqlite3 --version", output=True) + if not ret or not ret[0]: # pragma: no cover raise unittest.SkipTest("no sqlite3 command") + # version: + ret = uni_decode(ret[1]).split(' ') + ret = tuple(map(int, (str(ret[0]).split('.'))))if ret else (3,0,0); self.db = None if self.dbFilename is None: # pragma: no cover _, self.dbFilename = tempfile.mkstemp(".db", "fail2ban_") @@ -124,6 +147,9 @@ class DatabaseTest(LogCaptureTestCase): # - 14000 bytes - seems to be reparable, # - 4000 bytes - is totally broken. for truncSize in (14000, 4000): + if truncSize >= 14000 and ret > (3,42): # pragma: no cover -- only sqlite >= 3.42 + truncSize = 14400 + self._mockupFailedDB(); # mock-up it to ensure it fails by open self.pruneLog("[test-repair], next phase - file-size: %d" % truncSize) shutil.copyfile( os.path.join(TEST_FILES_DIR, 'database_v1.db'), self.dbFilename) @@ -134,7 +160,7 @@ class DatabaseTest(LogCaptureTestCase): # test repair: try: self.db = Fail2BanDb(self.dbFilename) - if truncSize == 14000: # restored: + if truncSize >= 14000: # restored: self.assertLogged("Repair seems to be successful", "Check integrity", "Database updated", all=True) self.assertEqual(self.db.getLogPaths(), set(['/tmp/Fail2BanDb_pUlZJh.log']))