xfstestsdb: Create the xfstestsdb show
command
This command generates a table from the testcases that have been added to a single run so the results of each testcase can be seen side-by-side. Implements: #9 (`xfstestsdb show`) Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
This commit is contained in:
parent
a9a2c2bf7d
commit
1996f3b798
185
tests/test_show.py
Normal file
185
tests/test_show.py
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
# Copyright 2023 (c) Anna Schumaker.
|
||||||
|
"""Tests the `xfstestsdb testcase show` command."""
|
||||||
|
import errno
|
||||||
|
import io
|
||||||
|
import unittest
|
||||||
|
import unittest.mock
|
||||||
|
import xfstestsdb.show
|
||||||
|
import tests.xunit
|
||||||
|
|
||||||
|
|
||||||
|
class TestTestCaseShow(unittest.TestCase):
|
||||||
|
"""Tests the `xfstestsdb testcase show` command."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Set up common variables."""
|
||||||
|
self.xfstestsdb = xfstestsdb.Command()
|
||||||
|
self.show = self.xfstestsdb.commands["show"]
|
||||||
|
|
||||||
|
def setup_run(self, mock_stdout: io.StringIO):
|
||||||
|
"""Set up runs in the database and clear stdout."""
|
||||||
|
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"])
|
||||||
|
|
||||||
|
mock_stdout.seek(0)
|
||||||
|
mock_stdout.truncate(0)
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
"""Check that the show command was set up properly."""
|
||||||
|
self.assertIsInstance(xfstestsdb.show.TestCaseTable(["col"]),
|
||||||
|
xfstestsdb.table.Table)
|
||||||
|
self.assertIsInstance(self.show, xfstestsdb.commands.Command)
|
||||||
|
self.assertIsInstance(self.show, xfstestsdb.show.Command)
|
||||||
|
self.assertEqual(self.xfstestsdb.subparser.choices["show"],
|
||||||
|
self.show.parser)
|
||||||
|
|
||||||
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||||
|
def test_show_empty(self, mock_stdout: io.StringIO):
|
||||||
|
"""Test printing out an empty list."""
|
||||||
|
self.setup_run(mock_stdout)
|
||||||
|
self.xfstestsdb.sql("DELETE FROM testcases")
|
||||||
|
self.assertEqual(mock_stdout.getvalue(), "")
|
||||||
|
|
||||||
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||||
|
def test_show_run(self, mock_stdout: io.StringIO):
|
||||||
|
"""Test showing a run with no filters."""
|
||||||
|
self.setup_run(mock_stdout)
|
||||||
|
print()
|
||||||
|
self.xfstestsdb.run(["show", "1", "--color", "none"])
|
||||||
|
self.assertEqual(mock_stdout.getvalue(),
|
||||||
|
"""
|
||||||
|
+----------+---------+---------+
|
||||||
|
| testcase | test-1 | test-2 |
|
||||||
|
+----------+---------+---------+
|
||||||
|
| test/01 | passed | passed |
|
||||||
|
| test/02 | skipped | skipped |
|
||||||
|
| test/03 | skipped | skipped |
|
||||||
|
| test/04 | passed | passed |
|
||||||
|
| test/05 | passed | passed |
|
||||||
|
| test/06 | passed | passed |
|
||||||
|
| test/07 | skipped | skipped |
|
||||||
|
| test/08 | passed | passed |
|
||||||
|
| test/09 | failure | failure |
|
||||||
|
| test/10 | passed | passed |
|
||||||
|
+----------+---------+---------+
|
||||||
|
""")
|
||||||
|
|
||||||
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||||
|
def test_show_filter_testcase(self, mock_stdout: io.StringIO):
|
||||||
|
"""Test showing a run filtering by testcase."""
|
||||||
|
self.setup_run(mock_stdout)
|
||||||
|
print()
|
||||||
|
self.xfstestsdb.run(["show", "1", "--testcase", "test/0[45]",
|
||||||
|
"--color", "none"])
|
||||||
|
self.assertEqual(mock_stdout.getvalue(),
|
||||||
|
"""
|
||||||
|
+----------+--------+--------+
|
||||||
|
| testcase | test-1 | test-2 |
|
||||||
|
+----------+--------+--------+
|
||||||
|
| test/04 | passed | passed |
|
||||||
|
| test/05 | passed | passed |
|
||||||
|
+----------+--------+--------+
|
||||||
|
""")
|
||||||
|
|
||||||
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||||
|
def test_show_filter_passed(self, mock_stdout: io.StringIO):
|
||||||
|
"""Test showing a run filtering for passing tests."""
|
||||||
|
self.setup_run(mock_stdout)
|
||||||
|
print()
|
||||||
|
self.xfstestsdb.run(["show", "1", "--passed", "--color", "none"])
|
||||||
|
self.assertEqual(mock_stdout.getvalue(),
|
||||||
|
"""
|
||||||
|
+----------+--------+--------+
|
||||||
|
| testcase | test-1 | test-2 |
|
||||||
|
+----------+--------+--------+
|
||||||
|
| test/01 | passed | passed |
|
||||||
|
| test/04 | passed | passed |
|
||||||
|
| test/05 | passed | passed |
|
||||||
|
| test/06 | passed | passed |
|
||||||
|
| test/08 | passed | passed |
|
||||||
|
| test/10 | passed | passed |
|
||||||
|
+----------+--------+--------+
|
||||||
|
""")
|
||||||
|
|
||||||
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||||
|
def test_show_filter_skipped(self, mock_stdout: io.StringIO):
|
||||||
|
"""Test showing a run filtering for skipped testss."""
|
||||||
|
self.setup_run(mock_stdout)
|
||||||
|
print()
|
||||||
|
self.xfstestsdb.run(["show", "1", "--skipped", "--color", "none"])
|
||||||
|
self.assertEqual(mock_stdout.getvalue(),
|
||||||
|
"""
|
||||||
|
+----------+---------+---------+
|
||||||
|
| testcase | test-1 | test-2 |
|
||||||
|
+----------+---------+---------+
|
||||||
|
| test/02 | skipped | skipped |
|
||||||
|
| test/03 | skipped | skipped |
|
||||||
|
| test/07 | skipped | skipped |
|
||||||
|
+----------+---------+---------+
|
||||||
|
""")
|
||||||
|
|
||||||
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||||
|
def test_show_filter_failure(self, mock_stdout: io.StringIO):
|
||||||
|
"""Test showing a run filtering for failing testss."""
|
||||||
|
self.setup_run(mock_stdout)
|
||||||
|
print()
|
||||||
|
self.xfstestsdb.run(["show", "1", "--failure", "--color", "none"])
|
||||||
|
self.assertEqual(mock_stdout.getvalue(),
|
||||||
|
"""
|
||||||
|
+----------+---------+---------+
|
||||||
|
| testcase | test-1 | test-2 |
|
||||||
|
+----------+---------+---------+
|
||||||
|
| test/09 | failure | failure |
|
||||||
|
+----------+---------+---------+
|
||||||
|
""")
|
||||||
|
|
||||||
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||||
|
def test_show_uneven_xunits(self, mock_stdout: io.StringIO):
|
||||||
|
"""Test showing a run where xunits have different testcase sets."""
|
||||||
|
self.setup_run(mock_stdout)
|
||||||
|
self.xfstestsdb.sql("DELETE FROM testcases WHERE "
|
||||||
|
"xunitid=? AND testcase=?", 1, "test/03")
|
||||||
|
self.xfstestsdb.sql("DELETE FROM testcases WHERE "
|
||||||
|
"xunitid=? AND testcase=?", 2, "test/08")
|
||||||
|
print()
|
||||||
|
self.xfstestsdb.run(["show", "1", "--color", "none"])
|
||||||
|
self.assertEqual(mock_stdout.getvalue(),
|
||||||
|
"""
|
||||||
|
+----------+---------+---------+
|
||||||
|
| testcase | test-1 | test-2 |
|
||||||
|
+----------+---------+---------+
|
||||||
|
| test/01 | passed | passed |
|
||||||
|
| test/02 | skipped | skipped |
|
||||||
|
| test/03 | | skipped |
|
||||||
|
| test/04 | passed | passed |
|
||||||
|
| test/05 | passed | passed |
|
||||||
|
| test/06 | passed | passed |
|
||||||
|
| test/07 | skipped | skipped |
|
||||||
|
| test/08 | passed | |
|
||||||
|
| test/09 | failure | failure |
|
||||||
|
| test/10 | passed | passed |
|
||||||
|
+----------+---------+---------+
|
||||||
|
""")
|
||||||
|
|
||||||
|
@unittest.mock.patch("sys.stderr", new_callable=io.StringIO)
|
||||||
|
def test_show_error(self, mock_stderr: io.StringIO):
|
||||||
|
"""Test the `xfstestsdb show` command with invalid input."""
|
||||||
|
with self.assertRaises(SystemExit):
|
||||||
|
self.xfstestsdb.run(["testcase", "show"])
|
||||||
|
self.assertRegex(mock_stderr.getvalue(),
|
||||||
|
"error: the following arguments are required: runid")
|
||||||
|
|
||||||
|
@unittest.mock.patch("sys.stderr", new_callable=io.StringIO)
|
||||||
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||||
|
def test_show_enoent(self, mock_stdout: io.StringIO,
|
||||||
|
mock_stderr: io.StringIO):
|
||||||
|
"""Test the `xfstestsdb show` command with an invalid runid."""
|
||||||
|
self.setup_run(mock_stderr)
|
||||||
|
with self.assertRaises(SystemExit) as sys_exit:
|
||||||
|
self.xfstestsdb.run(["show", "2"])
|
||||||
|
|
||||||
|
self.assertEqual(sys_exit.exception.code, errno.ENOENT)
|
||||||
|
self.assertEqual(mock_stderr.getvalue(),
|
||||||
|
"error: run #2 does not exist\n")
|
|
@ -6,6 +6,7 @@ from . import delete
|
||||||
from . import list
|
from . import list
|
||||||
from . import new
|
from . import new
|
||||||
from . import rename
|
from . import rename
|
||||||
|
from . import show
|
||||||
from . import tag
|
from . import tag
|
||||||
from . import testcase
|
from . import testcase
|
||||||
from . import untag
|
from . import untag
|
||||||
|
@ -30,6 +31,7 @@ class Command:
|
||||||
"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),
|
||||||
|
"show": show.Command(self.subparser, self.sql),
|
||||||
"tag": tag.Command(self.subparser, self.sql),
|
"tag": tag.Command(self.subparser, self.sql),
|
||||||
"testcase": testcase.Command(self.subparser,
|
"testcase": testcase.Command(self.subparser,
|
||||||
self.sql),
|
self.sql),
|
||||||
|
|
103
xfstestsdb/show.py
Normal file
103
xfstestsdb/show.py
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
# Copyright 2023 (c) Anna Schumaker.
|
||||||
|
"""The `xfstestsdb testcase show` command."""
|
||||||
|
import argparse
|
||||||
|
import errno
|
||||||
|
import sys
|
||||||
|
from . import colors
|
||||||
|
from . import commands
|
||||||
|
from . import sqlite
|
||||||
|
from . import table
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseTable(table.Table):
|
||||||
|
"""A custom Table for coloring cells based on status."""
|
||||||
|
|
||||||
|
def do_format_cell(self, rownum: int, text: str, color: str,
|
||||||
|
**kwargs) -> str:
|
||||||
|
"""Set cell text color based on testcase status."""
|
||||||
|
match text.strip():
|
||||||
|
case "passed": color = "fg-passed"
|
||||||
|
case "skipped": color = "fg-skipped"
|
||||||
|
case "failure": color = "fg-failure"
|
||||||
|
case _: color = "fg-testcase"
|
||||||
|
return super().do_format_cell(rownum, text, color, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(commands.Command):
|
||||||
|
"""The `xfstestsdb testcase show` command."""
|
||||||
|
|
||||||
|
def __init__(self, subparser: argparse.Action,
|
||||||
|
sql: sqlite.Connection) -> None:
|
||||||
|
"""Set up the testcase show command."""
|
||||||
|
super().__init__(subparser, sql, "show",
|
||||||
|
help="show an xfstests run")
|
||||||
|
self.parser.add_argument("runid", metavar="runid", nargs=1,
|
||||||
|
help="runid of the testcase to show")
|
||||||
|
self.parser.add_argument("--color", metavar="color", nargs="?",
|
||||||
|
choices=["light", "dark", "none"],
|
||||||
|
const=colors.get_default_colors(),
|
||||||
|
default=colors.get_default_colors(),
|
||||||
|
help="show with color output "
|
||||||
|
f"[default={colors.get_default_colors()}]")
|
||||||
|
self.parser.add_argument("--failure", dest="status",
|
||||||
|
action="append_const", const="failure",
|
||||||
|
help="show failing testcases")
|
||||||
|
self.parser.add_argument("--passed", dest="status",
|
||||||
|
action="append_const", const="passed",
|
||||||
|
help="show passing testcases")
|
||||||
|
self.parser.add_argument("--skipped", dest="status",
|
||||||
|
action="append_const", const="skipped",
|
||||||
|
help="show skipped testcases")
|
||||||
|
self.parser.add_argument("--testcase", metavar="testcase", nargs=1,
|
||||||
|
help="show testcases matching "
|
||||||
|
"the given pattern")
|
||||||
|
|
||||||
|
def do_command(self, args: argparse.Namespace) -> None:
|
||||||
|
"""Create a new row in the xfstestsdb_runs table."""
|
||||||
|
rows = self.sql("""SELECT name FROM xunits_view
|
||||||
|
WHERE runid=? ORDER BY name""",
|
||||||
|
args.runid[0]).fetchall()
|
||||||
|
if len(rows) == 0:
|
||||||
|
print(f"error: run #{args.runid[0]} does not exist",
|
||||||
|
file=sys.stderr)
|
||||||
|
sys.exit(errno.ENOENT)
|
||||||
|
|
||||||
|
xunits = [row["name"] for row in rows]
|
||||||
|
tbl = TestCaseTable(["testcase"] + xunits,
|
||||||
|
["r"] + ["l"] * len(rows), args.color)
|
||||||
|
|
||||||
|
select = [f"xunit{i}.status as xunit{i}" for i in range(len(xunits))]
|
||||||
|
query = [(f"SELECT testcase, {','.join(select)} FROM")]
|
||||||
|
query.append("(SELECT DISTINCT runid, testcase FROM testcases_view "
|
||||||
|
"WHERE runid=:runid) ")
|
||||||
|
sql_args = {"runid": args.runid[0]}
|
||||||
|
where = []
|
||||||
|
|
||||||
|
for i, xunit in enumerate(xunits):
|
||||||
|
xunit_select = "SELECT testcase, status FROM testcases_view " + \
|
||||||
|
f"WHERE runid=:runid AND xunit=:xunit{i}"
|
||||||
|
query.append(f"FULL JOIN ({xunit_select}) AS xunit{i} "
|
||||||
|
"USING (testcase)")
|
||||||
|
sql_args[f"xunit{i}"] = xunit
|
||||||
|
|
||||||
|
if args.status:
|
||||||
|
xunit_list = ",".join([f"xunit{i}" for i in range(len(xunits))])
|
||||||
|
for status in args.status:
|
||||||
|
where.append(f":{status} IN ({xunit_list})")
|
||||||
|
sql_args[status] = status
|
||||||
|
|
||||||
|
if args.testcase:
|
||||||
|
where.append("testcase GLOB :testcase")
|
||||||
|
sql_args["testcase"] = args.testcase[0]
|
||||||
|
|
||||||
|
if len(where) > 0:
|
||||||
|
query.append(f"WHERE {' AND '.join(where)}")
|
||||||
|
|
||||||
|
query.append("ORDER BY testcase")
|
||||||
|
|
||||||
|
cur = self.sql("\n".join(query), **sql_args)
|
||||||
|
for row in cur.fetchall():
|
||||||
|
tbl.add_row(*row)
|
||||||
|
|
||||||
|
if len(tbl.rows) > 0:
|
||||||
|
print(tbl)
|
Loading…
Reference in New Issue
Block a user