Compare commits
7 Commits
14b848bddd
...
bea49c5eae
Author | SHA1 | Date | |
---|---|---|---|
bea49c5eae | |||
14654dcf23 | |||
68a00ea94d | |||
c3eb740fb5 | |||
a168a7f84b | |||
2082b904a0 | |||
3f61adc941 |
7
tests/test-script.sql
Normal file
7
tests/test-script.sql
Normal file
|
@ -0,0 +1,7 @@
|
|||
/* Copyright 2023 (c) Anna Schumaker */
|
||||
CREATE TABLE test (a INT, b INT);
|
||||
INSERT INTO test VALUES (1, 2);
|
||||
INSERT INTO test VALUES (3, 4);
|
||||
INSERT INTO test VALUES (5, 6);
|
||||
INSERT INTO test VALUES (7, 8);
|
||||
INSERT INTO test VALUES (9, 0);
|
123
tests/test_gc.py
Normal file
123
tests/test_gc.py
Normal file
|
@ -0,0 +1,123 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests the `xfstestsdb gc` command."""
|
||||
import datetime
|
||||
import io
|
||||
import unittest
|
||||
import unittest.mock
|
||||
import xfstestsdb.xunit.gc
|
||||
import tests.xunit
|
||||
|
||||
|
||||
class TestGC(unittest.TestCase):
|
||||
"""Tests the `xfstestsdb xunit gc` command."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
self.xunit = self.xfstestsdb.commands["xunit"]
|
||||
self.gc = self.xunit.commands["gc"]
|
||||
|
||||
def setup_runs(self, mock_stdout: io.StringIO):
|
||||
"""Set up runs in the database and clear stdout."""
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
|
||||
self.xfstestsdb.run(["xunit", "read", "1", str(tests.xunit.XUNIT_1)])
|
||||
self.xfstestsdb.run(["xunit", "read", "3", str(tests.xunit.XUNIT_1)])
|
||||
|
||||
self.xfstestsdb.sql("DELETE FROM testcases WHERE xunitid=2")
|
||||
|
||||
mock_stdout.seek(0)
|
||||
mock_stdout.truncate(0)
|
||||
|
||||
def test_init(self):
|
||||
"""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):
|
||||
"""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(), "")
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_no_testcases(self, mock_stdout: io.StringIO):
|
||||
"""Test garbage collecting runs with no testcases."""
|
||||
self.setup_runs(mock_stdout)
|
||||
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_gc_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()], [2, 3])
|
||||
|
||||
self.xfstestsdb.run(["gc"])
|
||||
self.assertRegex(mock_stdout.getvalue(),
|
||||
"run #2 has been deleted\nrun #3 has been deleted")
|
||||
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()], [1])
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_expired(self, mock_stdout: io.StringIO):
|
||||
"""Test garbage collecting old runs."""
|
||||
self.setup_runs(mock_stdout)
|
||||
self.xfstestsdb.run(["xunit", "read", "2", str(tests.xunit.XUNIT_1)])
|
||||
|
||||
utcnow = datetime.datetime.utcnow()
|
||||
|
||||
expired = utcnow - datetime.timedelta(days=180, seconds=1)
|
||||
self.xfstestsdb.sql("""UPDATE xfstests_runs SET timestamp=?
|
||||
WHERE runid=1""", expired)
|
||||
|
||||
not_expired = utcnow - datetime.timedelta(days=180, seconds=-1)
|
||||
self.xfstestsdb.sql("""UPDATE xfstests_runs SET timestamp=?
|
||||
WHERE runid=2""", not_expired)
|
||||
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_gc_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()], [1, 3])
|
||||
|
||||
self.xfstestsdb.run(["gc"])
|
||||
self.assertRegex(mock_stdout.getvalue(),
|
||||
"run #1 has been deleted\nrun #3 has been deleted")
|
||||
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()], [2])
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_expired_tagged(self, mock_stdout: io.StringIO):
|
||||
"""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)])
|
||||
self.xfstestsdb.run(["tag", "1", "my-tag"])
|
||||
self.xfstestsdb.run(["tag", "3", "my-tag"])
|
||||
|
||||
utcnow = datetime.datetime.utcnow()
|
||||
expired = utcnow - datetime.timedelta(days=181)
|
||||
self.xfstestsdb.sql("UPDATE xfstests_runs SET timestamp=?", expired)
|
||||
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_gc_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()], [2, 3])
|
||||
|
||||
self.xfstestsdb.run(["gc"])
|
||||
self.assertRegex(mock_stdout.getvalue(),
|
||||
"run #2 has been deleted\nrun #3 has been deleted")
|
||||
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()], [1])
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_dry_run(self, mock_stdout: io.StringIO):
|
||||
"""Test garbage collecting with the --dry-run option."""
|
||||
self.setup_runs(mock_stdout)
|
||||
self.xfstestsdb.run(["gc", "--dry-run"])
|
||||
self.assertRegex(mock_stdout.getvalue(),
|
||||
"run #2 would be deleted\nrun #3 would be deleted")
|
||||
|
||||
cur = self.xfstestsdb.sql("SELECT runid FROM xfstests_runs")
|
||||
self.assertListEqual([row['runid'] for row in cur.fetchall()],
|
||||
[1, 2, 3])
|
|
@ -22,8 +22,12 @@ class TestConnection(unittest.TestCase):
|
|||
data_dir / "xfstestsdb-debug.sqlite3")
|
||||
self.assertEqual(xfstestsdb.sqlite.DATABASE, ":memory:")
|
||||
|
||||
script = pathlib.Path(xfstestsdb.__file__).parent / "xfstestsdb.sql"
|
||||
self.assertEqual(xfstestsdb.sqlite.SQL_SCRIPT, script)
|
||||
self.assertEqual(xfstestsdb.sqlite.SQL_SCRIPTS,
|
||||
pathlib.Path(xfstestsdb.__file__).parent / "scripts")
|
||||
self.assertEqual(xfstestsdb.sqlite.SQL_V1_SCRIPT,
|
||||
xfstestsdb.sqlite.SQL_SCRIPTS / "xfstestsdb.sql")
|
||||
self.assertEqual(xfstestsdb.sqlite.SQL_V2_SCRIPT,
|
||||
xfstestsdb.sqlite.SQL_SCRIPTS / "upgrade-v2.sql")
|
||||
|
||||
def test_foreign_keys(self):
|
||||
"""Test that foreign key constraints are enabled."""
|
||||
|
@ -33,7 +37,7 @@ class TestConnection(unittest.TestCase):
|
|||
def test_version(self):
|
||||
"""Test checking the database schema version."""
|
||||
cur = self.sql("PRAGMA user_version")
|
||||
self.assertEqual(cur.fetchone()["user_version"], 1)
|
||||
self.assertEqual(cur.fetchone()["user_version"], 2)
|
||||
|
||||
def test_connection(self):
|
||||
"""Check that the connection manager is initialized properly."""
|
||||
|
@ -72,6 +76,17 @@ class TestConnection(unittest.TestCase):
|
|||
self.assertListEqual([(row["a"], row["b"]) for row in rows],
|
||||
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 0)])
|
||||
|
||||
def test_executescript(self):
|
||||
"""Test running a sql script."""
|
||||
script = pathlib.Path(__file__).parent / "test-script.sql"
|
||||
cur = self.sql.executescript(script)
|
||||
self.assertIsInstance(cur, sqlite3.Cursor)
|
||||
rows = self.sql("SELECT * FROM test").fetchall()
|
||||
self.assertListEqual([(row["a"], row["b"]) for row in rows],
|
||||
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 0)])
|
||||
|
||||
self.assertIsNone(self.sql.executescript(script.parent / "no-script"))
|
||||
|
||||
def test_transaction(self):
|
||||
"""Test that we can manually start a transaction."""
|
||||
self.assertFalse(self.sql.sql.in_transaction)
|
||||
|
|
|
@ -52,7 +52,7 @@ class TestXfstestsdb(unittest.TestCase):
|
|||
def test_version(self, mock_stdout: io.StringIO):
|
||||
"""Test printing version information."""
|
||||
self.assertEqual(xfstestsdb.MAJOR, 1)
|
||||
self.assertEqual(xfstestsdb.MINOR, 1)
|
||||
self.assertEqual(xfstestsdb.MINOR, 2)
|
||||
|
||||
self.xfstestsdb.run(["--version"])
|
||||
self.assertEqual(mock_stdout.getvalue(), "xfstestsdb v1.1-debug\n")
|
||||
self.assertEqual(mock_stdout.getvalue(), "xfstestsdb v1.2-debug\n")
|
||||
|
|
70
tests/xunit/test_gc.py
Normal file
70
tests/xunit/test_gc.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests the `xfstestsdb xunit gc` command."""
|
||||
import io
|
||||
import unittest
|
||||
import unittest.mock
|
||||
import xfstestsdb.xunit.gc
|
||||
import tests.xunit
|
||||
|
||||
|
||||
class TestXunitGC(unittest.TestCase):
|
||||
"""Tests the `xfstestsdb xunit gc` command."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.xfstestsdb = xfstestsdb.Command()
|
||||
self.xunit = self.xfstestsdb.commands["xunit"]
|
||||
self.gc = self.xunit.commands["gc"]
|
||||
|
||||
def setup_runs(self, mock_stdout: io.StringIO):
|
||||
"""Set up runs in the database and clear stdout."""
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
self.xfstestsdb.run(["new", "/dev/vda1"])
|
||||
|
||||
self.xfstestsdb.run(["xunit", "read", "1", str(tests.xunit.XUNIT_1)])
|
||||
self.xfstestsdb.run(["xunit", "read", "1", str(tests.xunit.XUNIT_1),
|
||||
"--name", "test-2"])
|
||||
self.xfstestsdb.run(["xunit", "read", "2", str(tests.xunit.XUNIT_1)])
|
||||
|
||||
self.xfstestsdb.sql("DELETE FROM testcases WHERE xunitid=?", 2)
|
||||
self.xfstestsdb.sql("DELETE FROM testcases WHERE xunitid=?", 3)
|
||||
|
||||
mock_stdout.seek(0)
|
||||
mock_stdout.truncate(0)
|
||||
|
||||
def test_init(self):
|
||||
"""Check that the xunit 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):
|
||||
"""Test garbage collecting an empty database."""
|
||||
self.xfstestsdb.run(["xunit", "gc"])
|
||||
self.assertEqual(mock_stdout.getvalue(), "")
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_xunits(self, mock_stdout: io.StringIO):
|
||||
"""Test garbage collecting xunits with default options."""
|
||||
self.setup_runs(mock_stdout)
|
||||
self.xfstestsdb.run(["xunit", "gc"])
|
||||
self.assertRegex(mock_stdout.getvalue(),
|
||||
"run #1 xunit 'test-2' has been deleted\n"
|
||||
"run #2 xunit 'test-1' has been deleted")
|
||||
|
||||
cur = self.xfstestsdb.sql("SELECT COUNT(*) FROM xunits")
|
||||
self.assertEqual(cur.fetchone()["COUNT(*)"], 1)
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_gc_xunits_dry_run(self, mock_stdout: io.StringIO):
|
||||
"""Test garbage collecting xunits with the --dry-run option."""
|
||||
self.setup_runs(mock_stdout)
|
||||
self.xfstestsdb.run(["xunit", "gc", "--dry-run"])
|
||||
self.assertRegex(mock_stdout.getvalue(),
|
||||
"run #1 xunit 'test-2' would be deleted\n"
|
||||
"run #2 xunit 'test-1' would be deleted")
|
||||
|
||||
cur = self.xfstestsdb.sql("SELECT COUNT(*) FROM xunits")
|
||||
self.assertEqual(cur.fetchone()["COUNT(*)"], 3)
|
|
@ -3,6 +3,7 @@
|
|||
import argparse
|
||||
from . import sqlite
|
||||
from . import delete
|
||||
from . import gc
|
||||
from . import list
|
||||
from . import new
|
||||
from . import rename
|
||||
|
@ -13,7 +14,7 @@ from . import untag
|
|||
from . import xunit
|
||||
|
||||
MAJOR = 1
|
||||
MINOR = 1
|
||||
MINOR = 2
|
||||
|
||||
|
||||
class Command:
|
||||
|
@ -28,6 +29,7 @@ class Command:
|
|||
self.subparser = self.parser.add_subparsers(title="commands")
|
||||
self.sql = sqlite.Connection()
|
||||
self.commands = {"delete": delete.Command(self.subparser, self.sql),
|
||||
"gc": gc.Command(self.subparser, self.sql),
|
||||
"list": list.Command(self.subparser, self.sql),
|
||||
"new": new.Command(self.subparser, self.sql),
|
||||
"rename": rename.Command(self.subparser, self.sql),
|
||||
|
|
27
xfstestsdb/gc.py
Normal file
27
xfstestsdb/gc.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""The `xfstestsdb gc` command."""
|
||||
import argparse
|
||||
from . import commands
|
||||
from . import sqlite
|
||||
|
||||
|
||||
class Command(commands.Command):
|
||||
"""The `xfstestsdb gc` command."""
|
||||
|
||||
def __init__(self, subparser: argparse.Action,
|
||||
sql: sqlite.Connection) -> None:
|
||||
"""Set up the gc command."""
|
||||
super().__init__(subparser, sql, "gc",
|
||||
help="garbage collect xfstestsdb entries")
|
||||
self.parser.add_argument("--dry-run", action="store_true",
|
||||
help="do not remove the xunit entries")
|
||||
|
||||
def do_command(self, args: argparse.Namespace) -> None:
|
||||
"""Clean up the xunit table."""
|
||||
how = "would be" if args.dry_run else "has been"
|
||||
rows = self.sql("SELECT runid FROM xfstests_gc_runs").fetchall()
|
||||
|
||||
for runid in sorted([row['runid'] for row in rows]):
|
||||
if not args.dry_run:
|
||||
self.sql("DELETE FROM xfstests_runs WHERE runid=?", runid)
|
||||
print(f"run #{runid} {how} deleted")
|
64
xfstestsdb/scripts/upgrade-v2.sql
Normal file
64
xfstestsdb/scripts/upgrade-v2.sql
Normal file
|
@ -0,0 +1,64 @@
|
|||
/* Copyright 2023 (c) Anna Schumaker */
|
||||
|
||||
PRAGMA user_version = 2;
|
||||
|
||||
/*
|
||||
* The original `cleanup_xunit_properties` trigger was very slow on
|
||||
* large databases. We can do a few things to improve on it:
|
||||
* 1. Add an index on the link_xunits_props table to make it easier
|
||||
* to check if specific properties are still in use.
|
||||
* 2. Rewrite the `cleanup_xunit_properties` trigger to only operate
|
||||
* on the propid associated with the deleted xunit.
|
||||
*/
|
||||
CREATE INDEX link_xunit_props_propid_index ON link_xunit_props (propid);
|
||||
|
||||
DROP TRIGGER cleanup_xunit_properties;
|
||||
CREATE TRIGGER cleanup_xunit_properties
|
||||
AFTER DELETE ON link_xunit_props
|
||||
BEGIN
|
||||
DELETE FROM xunit_properties
|
||||
WHERE (propid = OLD.propid)
|
||||
AND NOT EXISTS (SELECT 1 FROM link_xunit_props
|
||||
WHERE propid = xunit_properties.propid);
|
||||
END;
|
||||
|
||||
/*
|
||||
* The original `cleanup_testcase_messages` trigger was very slow. We can
|
||||
* do a few things to improve upon it:
|
||||
* 1. Add indexes on the testcases table to make it easier to check
|
||||
* if specific messageids are still in use.
|
||||
* 2. Rewrite the `cleanup_testcase_messages` trigger to only operate
|
||||
* on the messageids associated with the deleted testcase.
|
||||
*/
|
||||
CREATE INDEX testcases_messageid_index ON testcases (messageid);
|
||||
CREATE INDEX testcases_stdoutid_index ON testcases (stdoutid);
|
||||
CREATE INDEX testcases_stderrid_index ON testcases (stderrid);
|
||||
|
||||
DROP TRIGGER cleanup_testcase_messages;
|
||||
CREATE TRIGGER cleanup_testcase_messages
|
||||
AFTER DELETE ON testcases
|
||||
BEGIN
|
||||
DELETE FROM messages
|
||||
WHERE (messageid = OLD.messageid
|
||||
OR messageid = OLD.stdoutid
|
||||
OR messageid = OLD.stderrid)
|
||||
AND NOT EXISTS
|
||||
(SELECT 1 FROM testcases WHERE
|
||||
messageid = messages.messageid
|
||||
OR stdoutid = messages.messageid
|
||||
OR stderrid = messages.messageid);
|
||||
END;
|
||||
|
||||
|
||||
/*
|
||||
* Create a view on the xfstestsdb_runs to find garbage collection candidates.
|
||||
*/
|
||||
CREATE VIEW xfstests_gc_runs AS
|
||||
SELECT runid
|
||||
FROM xfstests_runs
|
||||
WHERE NOT EXISTS (SELECT 1 FROM testcases
|
||||
JOIN xunits USING (xunitid)
|
||||
WHERE runid = xfstests_runs.runid)
|
||||
OR (timestamp < datetime('now', '-180 days')
|
||||
AND NOT EXISTS (SELECT 1 FROM tags
|
||||
WHERE runid = xfstests_runs.runid));
|
|
@ -9,7 +9,10 @@ import xdg.BaseDirectory
|
|||
DATA_DIR = pathlib.Path(xdg.BaseDirectory.save_data_path("xfstestsdb"))
|
||||
DATA_FILE = DATA_DIR / f"xfstestsdb{'-debug' if __debug__ else ''}.sqlite3"
|
||||
DATABASE = ":memory:" if "unittest" in sys.modules else DATA_FILE
|
||||
SQL_SCRIPT = pathlib.Path(__file__).parent / "xfstestsdb.sql"
|
||||
|
||||
SQL_SCRIPTS = pathlib.Path(__file__).parent / "scripts"
|
||||
SQL_V1_SCRIPT = SQL_SCRIPTS / "xfstestsdb.sql"
|
||||
SQL_V2_SCRIPT = SQL_SCRIPTS / "upgrade-v2.sql"
|
||||
|
||||
|
||||
class Connection:
|
||||
|
@ -25,9 +28,10 @@ class Connection:
|
|||
self("PRAGMA foreign_keys = ON")
|
||||
match self("PRAGMA user_version").fetchone()["user_version"]:
|
||||
case 0:
|
||||
with open(SQL_SCRIPT) as f:
|
||||
self.sql.executescript(f.read())
|
||||
self.sql.commit()
|
||||
self.executescript(SQL_V1_SCRIPT)
|
||||
self.executescript(SQL_V2_SCRIPT)
|
||||
case 1:
|
||||
self.executescript(SQL_V2_SCRIPT)
|
||||
|
||||
def __call__(self, statement: str,
|
||||
*args, **kwargs) -> sqlite3.Cursor | None:
|
||||
|
@ -64,3 +68,11 @@ class Connection:
|
|||
return self.sql.executemany(statement, args)
|
||||
except sqlite3.IntegrityError:
|
||||
return None
|
||||
|
||||
def executescript(self, script: pathlib.Path) -> sqlite3.Cursor | None:
|
||||
"""Execute a SQL script."""
|
||||
if script.is_file():
|
||||
with open(script) as f:
|
||||
cur = self.sql.executescript(f.read())
|
||||
self.sql.commit()
|
||||
return cur
|
||||
|
|
|
@ -4,6 +4,7 @@ import argparse
|
|||
from .. import commands
|
||||
from .. import sqlite
|
||||
from . import delete
|
||||
from . import gc
|
||||
from . import list
|
||||
from . import properties
|
||||
from . import read
|
||||
|
@ -20,6 +21,7 @@ class Command(commands.Command):
|
|||
help="xfstestsdb xunit commands")
|
||||
self.subparser = self.parser.add_subparsers(title="xunit commands")
|
||||
self.commands = {"delete": delete.Command(self.subparser, sql),
|
||||
"gc": gc.Command(self.subparser, sql),
|
||||
"list": list.Command(self.subparser, sql),
|
||||
"properties": properties.Command(self.subparser, sql),
|
||||
"read": read.Command(self.subparser, sql),
|
||||
|
|
29
xfstestsdb/xunit/gc.py
Normal file
29
xfstestsdb/xunit/gc.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""The `xfstestsdb xunit gc` command."""
|
||||
import argparse
|
||||
from .. import commands
|
||||
from .. import sqlite
|
||||
|
||||
|
||||
class Command(commands.Command):
|
||||
"""The `xfstestsdb xunit gc` command."""
|
||||
|
||||
def __init__(self, subparser: argparse.Action,
|
||||
sql: sqlite.Connection) -> None:
|
||||
"""Set up the xunit gc command."""
|
||||
super().__init__(subparser, sql, "gc",
|
||||
help="garbage collect xunit entries")
|
||||
self.parser.add_argument("--dry-run", action="store_true",
|
||||
help="do not remove the xunit entries")
|
||||
|
||||
def do_command(self, args: argparse.Namespace) -> None:
|
||||
"""Clean up the xunit table."""
|
||||
how = "would be" if args.dry_run else "has been"
|
||||
cur = self.sql("""SELECT xunitid, runid, name FROM xunits
|
||||
WHERE NOT EXISTS (SELECT 1 FROM testcases
|
||||
WHERE xunitid = xunits.xunitid)""")
|
||||
|
||||
for row in cur.fetchall():
|
||||
if not args.dry_run:
|
||||
self.sql("DELETE FROM xunits WHERE xunitid=?", row['xunitid'])
|
||||
print(f"run #{row['runid']} xunit '{row['name']}' {how} deleted")
|
Loading…
Reference in New Issue
Block a user