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")
|
data_dir / "xfstestsdb-debug.sqlite3")
|
||||||
self.assertEqual(xfstestsdb.sqlite.DATABASE, ":memory:")
|
self.assertEqual(xfstestsdb.sqlite.DATABASE, ":memory:")
|
||||||
|
|
||||||
script = pathlib.Path(xfstestsdb.__file__).parent / "xfstestsdb.sql"
|
self.assertEqual(xfstestsdb.sqlite.SQL_SCRIPTS,
|
||||||
self.assertEqual(xfstestsdb.sqlite.SQL_SCRIPT, script)
|
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):
|
def test_foreign_keys(self):
|
||||||
"""Test that foreign key constraints are enabled."""
|
"""Test that foreign key constraints are enabled."""
|
||||||
|
@ -33,7 +37,7 @@ class TestConnection(unittest.TestCase):
|
||||||
def test_version(self):
|
def test_version(self):
|
||||||
"""Test checking the database schema version."""
|
"""Test checking the database schema version."""
|
||||||
cur = self.sql("PRAGMA user_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):
|
def test_connection(self):
|
||||||
"""Check that the connection manager is initialized properly."""
|
"""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],
|
self.assertListEqual([(row["a"], row["b"]) for row in rows],
|
||||||
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 0)])
|
[(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):
|
def test_transaction(self):
|
||||||
"""Test that we can manually start a transaction."""
|
"""Test that we can manually start a transaction."""
|
||||||
self.assertFalse(self.sql.sql.in_transaction)
|
self.assertFalse(self.sql.sql.in_transaction)
|
||||||
|
|
|
@ -52,7 +52,7 @@ class TestXfstestsdb(unittest.TestCase):
|
||||||
def test_version(self, mock_stdout: io.StringIO):
|
def test_version(self, mock_stdout: io.StringIO):
|
||||||
"""Test printing version information."""
|
"""Test printing version information."""
|
||||||
self.assertEqual(xfstestsdb.MAJOR, 1)
|
self.assertEqual(xfstestsdb.MAJOR, 1)
|
||||||
self.assertEqual(xfstestsdb.MINOR, 1)
|
self.assertEqual(xfstestsdb.MINOR, 2)
|
||||||
|
|
||||||
self.xfstestsdb.run(["--version"])
|
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
|
import argparse
|
||||||
from . import sqlite
|
from . import sqlite
|
||||||
from . import delete
|
from . import delete
|
||||||
|
from . import gc
|
||||||
from . import list
|
from . import list
|
||||||
from . import new
|
from . import new
|
||||||
from . import rename
|
from . import rename
|
||||||
|
@ -13,7 +14,7 @@ from . import untag
|
||||||
from . import xunit
|
from . import xunit
|
||||||
|
|
||||||
MAJOR = 1
|
MAJOR = 1
|
||||||
MINOR = 1
|
MINOR = 2
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
|
@ -28,6 +29,7 @@ class Command:
|
||||||
self.subparser = self.parser.add_subparsers(title="commands")
|
self.subparser = self.parser.add_subparsers(title="commands")
|
||||||
self.sql = sqlite.Connection()
|
self.sql = sqlite.Connection()
|
||||||
self.commands = {"delete": delete.Command(self.subparser, self.sql),
|
self.commands = {"delete": delete.Command(self.subparser, self.sql),
|
||||||
|
"gc": gc.Command(self.subparser, self.sql),
|
||||||
"list": list.Command(self.subparser, self.sql),
|
"list": list.Command(self.subparser, self.sql),
|
||||||
"new": new.Command(self.subparser, self.sql),
|
"new": new.Command(self.subparser, self.sql),
|
||||||
"rename": rename.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_DIR = pathlib.Path(xdg.BaseDirectory.save_data_path("xfstestsdb"))
|
||||||
DATA_FILE = DATA_DIR / f"xfstestsdb{'-debug' if __debug__ else ''}.sqlite3"
|
DATA_FILE = DATA_DIR / f"xfstestsdb{'-debug' if __debug__ else ''}.sqlite3"
|
||||||
DATABASE = ":memory:" if "unittest" in sys.modules else DATA_FILE
|
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:
|
class Connection:
|
||||||
|
@ -25,9 +28,10 @@ class Connection:
|
||||||
self("PRAGMA foreign_keys = ON")
|
self("PRAGMA foreign_keys = ON")
|
||||||
match self("PRAGMA user_version").fetchone()["user_version"]:
|
match self("PRAGMA user_version").fetchone()["user_version"]:
|
||||||
case 0:
|
case 0:
|
||||||
with open(SQL_SCRIPT) as f:
|
self.executescript(SQL_V1_SCRIPT)
|
||||||
self.sql.executescript(f.read())
|
self.executescript(SQL_V2_SCRIPT)
|
||||||
self.sql.commit()
|
case 1:
|
||||||
|
self.executescript(SQL_V2_SCRIPT)
|
||||||
|
|
||||||
def __call__(self, statement: str,
|
def __call__(self, statement: str,
|
||||||
*args, **kwargs) -> sqlite3.Cursor | None:
|
*args, **kwargs) -> sqlite3.Cursor | None:
|
||||||
|
@ -64,3 +68,11 @@ class Connection:
|
||||||
return self.sql.executemany(statement, args)
|
return self.sql.executemany(statement, args)
|
||||||
except sqlite3.IntegrityError:
|
except sqlite3.IntegrityError:
|
||||||
return None
|
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 commands
|
||||||
from .. import sqlite
|
from .. import sqlite
|
||||||
from . import delete
|
from . import delete
|
||||||
|
from . import gc
|
||||||
from . import list
|
from . import list
|
||||||
from . import properties
|
from . import properties
|
||||||
from . import read
|
from . import read
|
||||||
|
@ -20,6 +21,7 @@ class Command(commands.Command):
|
||||||
help="xfstestsdb xunit commands")
|
help="xfstestsdb xunit commands")
|
||||||
self.subparser = self.parser.add_subparsers(title="xunit commands")
|
self.subparser = self.parser.add_subparsers(title="xunit commands")
|
||||||
self.commands = {"delete": delete.Command(self.subparser, sql),
|
self.commands = {"delete": delete.Command(self.subparser, sql),
|
||||||
|
"gc": gc.Command(self.subparser, sql),
|
||||||
"list": list.Command(self.subparser, sql),
|
"list": list.Command(self.subparser, sql),
|
||||||
"properties": properties.Command(self.subparser, sql),
|
"properties": properties.Command(self.subparser, sql),
|
||||||
"read": read.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