gc: Vacuum the database after garbage collecting

This patch adds a "vacuum()" function to our sqlite Connection that is
called if we detect that rows have been deleted during `xfstestsdb gc`

Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
This commit is contained in:
Anna Schumaker 2023-11-07 15:02:35 -05:00
parent dc6f5f54c3
commit 7e6f944cde
4 changed files with 34 additions and 11 deletions

View File

@ -8,6 +8,8 @@ import xfstestsdb.xunit.gc
import tests.xunit
@unittest.mock.patch("xfstestsdb.sqlite.Connection.vacuum")
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
class TestGC(unittest.TestCase):
"""Tests the `xfstestsdb xunit gc` command."""
@ -31,24 +33,26 @@ class TestGC(unittest.TestCase):
mock_stdout.seek(0)
mock_stdout.truncate(0)
def test_init(self):
def test_init(self, mock_stdout: io.StringIO,
mock_vacuum: unittest.mock.Mock):
"""Check that the gc command was set up properly."""
self.assertIsInstance(self.gc, xfstestsdb.commands.Command)
self.assertIsInstance(self.gc, xfstestsdb.xunit.gc.Command)
self.assertEqual(self.xunit.subparser.choices["gc"],
self.gc.parser)
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
def test_gc_empty(self, mock_stdout: io.StringIO):
def test_gc_empty(self, mock_stdout: io.StringIO,
mock_vacuum: unittest.mock.Mock):
"""Test garbage collecting an empty database."""
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_gc_runs")
self.assertListEqual([row['runid'] for row in cur.fetchall()], [])
self.xfstestsdb.run(["gc"])
self.assertEqual(mock_stdout.getvalue(), "")
mock_vacuum.assert_not_called()
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
def test_gc_no_testcases(self, mock_stdout: io.StringIO):
def test_gc_no_testcases(self, mock_stdout: io.StringIO,
mock_vacuum: unittest.mock.Mock):
"""Test garbage collecting runs with no testcases."""
self.setup_runs(mock_stdout)
@ -61,9 +65,10 @@ class TestGC(unittest.TestCase):
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
self.assertListEqual([row['runid'] for row in cur.fetchall()], [1])
mock_vacuum.assert_called()
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
def test_gc_expired(self, mock_stdout: io.StringIO):
def test_gc_expired(self, mock_stdout: io.StringIO,
mock_vacuum: unittest.mock.Mock):
"""Test garbage collecting old runs."""
self.setup_runs(mock_stdout)
self.xfstestsdb.run(["xunit", "read", "2", str(tests.xunit.XUNIT_1)])
@ -87,9 +92,10 @@ class TestGC(unittest.TestCase):
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
self.assertListEqual([row['runid'] for row in cur.fetchall()], [2])
mock_vacuum.assert_called()
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
def test_gc_expired_tagged(self, mock_stdout: io.StringIO):
def test_gc_expired_tagged(self, mock_stdout: io.StringIO,
mock_vacuum: unittest.mock.Mock):
"""Test that we don't garbage collect expired runs that are tagged."""
self.setup_runs(mock_stdout)
self.xfstestsdb.run(["xunit", "read", "2", str(tests.xunit.XUNIT_1)])
@ -109,9 +115,10 @@ class TestGC(unittest.TestCase):
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
self.assertListEqual([row['runid'] for row in cur.fetchall()], [1])
mock_vacuum.assert_called()
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
def test_gc_dry_run(self, mock_stdout: io.StringIO):
def test_gc_dry_run(self, mock_stdout: io.StringIO,
mock_vacuum: unittest.mock.Mock):
"""Test garbage collecting with the --dry-run option."""
self.setup_runs(mock_stdout)
self.xfstestsdb.run(["gc", "--dry-run"])
@ -121,3 +128,4 @@ class TestGC(unittest.TestCase):
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
self.assertListEqual([row['runid'] for row in cur.fetchall()],
[1, 2, 3])
mock_vacuum.assert_not_called()

View File

@ -114,6 +114,10 @@ class TestConnection(unittest.TestCase):
with self.assertRaises(sqlite3.OperationalError):
self.sql("SELECT COUNT(*) FROM other_table")
def test_vacuum(self):
"""Test vacuuming the database."""
self.assertIsInstance(self.sql.vacuum(), sqlite3.Cursor)
def test_close(self):
"""Check closing the connection."""
self.sql.close()

View File

@ -25,3 +25,10 @@ class Command(commands.Command):
if not args.dry_run:
self.sql("DELETE FROM xfstests_runs WHERE runid=?", runid)
print(f"run #{runid} {how} deleted")
args.need_vacuum = len(rows) > 0
def do_done(self, args: argparse.Namespace) -> None:
"""Vacuum the database after deleting."""
if args.need_vacuum and not args.dry_run:
self.sql.vacuum()

View File

@ -76,3 +76,7 @@ class Connection:
cur = self.sql.executescript(f.read())
self.sql.commit()
return cur
def vacuum(self) -> sqlite3.Cursor | None:
"""Vacuum the database."""
return self.sql.execute("VACUUM")