blob: 0fa6c5e7a598683b2894e36aebe7e67d16e5c1e3 [file] [log] [blame]
# Copyright 2025 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
""" Data structures holding test suites and test cases.
TestCase: A single named test case name with disabled flag and bug annotation.
TestSuite: A single named test suite with a list of `TestCase`s.
Tests: Holds a list of TestSuites. This is the top data structure for the
program. Can be initialized with a list of TestSuites or a
`_DictStructuredTests`.
load_and_merge(enumerated_tests: Tests) -> Tests: Starts with a set of tests
produced by the enumeration mechanism (in command_runner.TestEnumerator).
Loads the saved test info, and uses that to update/overwrite the enumerated
list. Returns the combined list. Prints the name of any tests found in the
saved file but not the enumerated tests - this is possible if a test has
been dropped from the current test set but is still in the file.
"""
import re
from dataclasses import dataclass
from typing import Any
import util
@dataclass
class TestCase:
"""A named test case, with bug annotation and disabled status."""
name: str
disabled: bool = False
bug: str = ""
@dataclass
class TestSuite:
"""Contains a named test suite and its `TestCase`s.
Can be initialized from a list of test cases or an internal
_DictTestSuite."""
name: str
cases: list[TestCase]
compact_name: str = ""
def __init__(
self,
name: str = "",
cases: list[TestCase] | None = None,
dict_suite: "_DictTestSuite | None" = None,
) -> None:
assert cases is None or dict_suite is None
if dict_suite:
self.name = dict_suite.name
self.cases = [c for c in dict_suite.cases.values()]
else:
self.name = name
self.cases = cases or []
self.compact_name = re.sub(
"^fuchsia-pkg://fuchsia.com/", "fx/", self.name
)
def _to_dict_suite(self) -> "_DictTestSuite":
return _DictTestSuite(
name=self.name, cases={case.name: case for case in self.cases}
)
# For merging two sets of tests, dicts are easier than lists.
@dataclass
class _DictTestSuite:
"""TestSuite, but with a dict to find `TestCase`s by their name.
Used for merging two sets of tests."""
name: str
cases: dict[str, TestCase]
@dataclass
class _DictStructuredTests:
"""A dict to find `TestSuite`s by their name.
Used for merging two sets of tests."""
suites: dict[str, _DictTestSuite]
class Tests:
"""Public-facing top-level data structure of tests.
Field `tests` is public, a list of `TestSuite`.
Method: save() writes to a fixed-location file.
save() can ignore a parameter for interop with GUI events."""
def __init__(
self,
test_dict: _DictStructuredTests | None = None,
test_list: list[TestSuite] | None = None,
) -> None:
self.tests: list[TestSuite] = []
if test_dict:
self.tests.extend(
[
TestSuite(dict_suite=test)
for test in test_dict.suites.values()
]
)
if test_list:
self.tests.extend(test_list)
def save(self, _: Any = None) -> None:
"""Save all test cases that are disabled and/or have bug info."""
save_list: list[TestSuite] = []
for suite in self.tests:
save_suite = TestSuite(suite.name)
for case in suite.cases:
if case.bug or case.disabled:
save_suite.cases.append(case)
if save_suite.cases:
save_list.append(save_suite)
util.save_program_state(save_list)
def __str__(self) -> str:
return f"Tests {self.tests}"
def _to_dict_tests(self) -> _DictStructuredTests:
suites = {}
for t in self.tests:
cases_dict = {c.name: c for c in t.cases}
suites[t.name] = _DictTestSuite(name=t.name, cases=cases_dict)
return _DictStructuredTests(suites)
def _saved_tests() -> "Tests":
"""Loads the program state file and parses the JSON."""
raw_tests = util.load_program_state()
suites = []
for test in raw_tests:
suite_name = test["name"]
cases = []
for case in test["cases"]:
name: str = case["name"]
disabled: bool = case["disabled"]
bug: str = case["bug"]
cases.append(TestCase(name=name, disabled=disabled, bug=bug))
suites.append(TestSuite(name=suite_name, cases=cases))
return Tests(test_list=suites)
def load_and_merge(enumerated_tests: "Tests") -> "Tests":
"""Combines the saved test state with the enumerated tests."""
test_dict = enumerated_tests._to_dict_tests()
test_accumulator = test_dict.suites
tests_from_file = _saved_tests()
for suite in tests_from_file.tests:
if suite.name not in test_accumulator:
print(f"Test {suite.name} not found in enumerated tests.")
test_accumulator[suite.name] = suite._to_dict_suite()
else:
t = test_accumulator[suite.name]
for case in suite.cases:
if case.name not in t.cases:
print(
f"Test case {suite.name} : {case.name} not found in enumerated tests."
)
test_accumulator[suite.name].cases[case.name] = case
return Tests(test_dict=test_dict)