diff options
author | Michael Kubacki <michael.kubacki@microsoft.com> | 2023-08-10 17:24:55 -0400 |
---|---|---|
committer | mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> | 2023-09-19 01:20:27 +0000 |
commit | cbcf0428e83bbe8314de47207072b3b4f1557dc6 (patch) | |
tree | 543f63271cb41eb0e5188f06e6ef574e88093e6a /BaseTools/Plugin/DebugMacroCheck/tests/test_DebugMacroCheck.py | |
parent | 97d367f37e1d44efd126efb0c5145240af9d7afb (diff) | |
download | edk2-cbcf0428e83bbe8314de47207072b3b4f1557dc6.tar.gz edk2-cbcf0428e83bbe8314de47207072b3b4f1557dc6.tar.bz2 edk2-cbcf0428e83bbe8314de47207072b3b4f1557dc6.zip |
BaseTools/Plugin: Add DebugMacroCheck
Adds a plugin that finds debug macro formatting issues. These errors
often creep into debug prints in error conditions not frequently
executed and make debug more difficult when they are encountered.
The code can be as a standalone script which is useful to find
problems in a large codebase that has not been checked before or as
a build plugin that notifies a developer of an error right away.
The script was already used to find numerous issues in edk2 in the
past so there's not many code fixes in this change. More details
are available in the readme file:
.pytool\Plugin\DebugMacroCheck\Readme.md
Cc: Sean Brogan <sean.brogan@microsoft.com>
Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>
Cc: Rebecca Cran <rebecca@bsdio.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>
Cc: Bob Feng <bob.c.feng@intel.com>
Cc: Yuwei Chen <yuwei.chen@intel.com>
Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com>
Reviewed-by: Michael D Kinney <michael.d.kinney@intel.com>
Diffstat (limited to 'BaseTools/Plugin/DebugMacroCheck/tests/test_DebugMacroCheck.py')
-rw-r--r-- | BaseTools/Plugin/DebugMacroCheck/tests/test_DebugMacroCheck.py | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/BaseTools/Plugin/DebugMacroCheck/tests/test_DebugMacroCheck.py b/BaseTools/Plugin/DebugMacroCheck/tests/test_DebugMacroCheck.py new file mode 100644 index 0000000000..7abc0d2b87 --- /dev/null +++ b/BaseTools/Plugin/DebugMacroCheck/tests/test_DebugMacroCheck.py @@ -0,0 +1,201 @@ +# @file test_DebugMacroCheck.py
+#
+# Contains unit tests for the DebugMacroCheck build plugin.
+#
+# An example of running these tests from the root of the workspace:
+# python -m unittest discover -s ./BaseTools/Plugin/DebugMacroCheck/tests -v
+#
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+import inspect
+import pathlib
+import sys
+import unittest
+
+# Import the build plugin
+test_file = pathlib.Path(__file__)
+sys.path.append(str(test_file.parent.parent))
+
+# flake8 (E402): Ignore flake8 module level import not at top of file
+import DebugMacroCheck # noqa: E402
+
+from os import linesep # noqa: E402
+from tests import DebugMacroDataSet # noqa: E402
+from tests import MacroTest # noqa: E402
+from typing import Callable, Tuple # noqa: E402
+
+
+#
+# This metaclass is provided to dynamically produce test case container
+# classes. The main purpose of this approach is to:
+# 1. Allow categories of test cases to be defined (test container classes)
+# 2. Allow test cases to automatically (dynamically) be assigned to their
+# corresponding test container class when new test data is defined.
+#
+# The idea being that infrastructure and test data are separated. Adding
+# / removing / modifying test data does not require an infrastructure
+# change (unless new categories are defined).
+# 3. To work with the unittest discovery algorithm and VS Code Test Explorer.
+#
+# Notes:
+# - (1) can roughly be achieved with unittest test suites. In another
+# implementation approach, this solution was tested with relatively minor
+# modifications to use test suites. However, this became a bit overly
+# complicated with the dynamic test case method generation and did not
+# work as well with VS Code Test Explorer.
+# - For (2) and (3), particularly for VS Code Test Explorer to work, the
+# dynamic population of the container class namespace needed to happen prior
+# to class object creation. That is why the metaclass assigns test methods
+# to the new classes based upon the test category specified in the
+# corresponding data class.
+# - This could have been simplified a bit by either using one test case
+# container class and/or testing data in a single, monolithic test function
+# that iterates over the data set. However, the dynamic hierarchy greatly
+# helps organize test results and reporting. The infrastructure though
+# inheriting some complexity to support it, should not need to change (much)
+# as the data set expands.
+# - Test case categories (container classes) are derived from the overall
+# type of macro conditions under test.
+#
+# - This implementation assumes unittest will discover test cases
+# (classes derived from unittest.TestCase) with the name pattern "Test_*"
+# and test functions with the name pattern "test_x". Individual tests are
+# dynamically numbered monotonically within a category.
+# - The final test case description is also able to return fairly clean
+# context information.
+#
+class Meta_TestDebugMacroCheck(type):
+ """
+ Metaclass for debug macro test case class factory.
+ """
+ @classmethod
+ def __prepare__(mcls, name, bases, **kwargs):
+ """Returns the test case namespace for this class."""
+ candidate_macros, cls_ns, cnt = [], {}, 0
+
+ if "category" in kwargs.keys():
+ candidate_macros = [m for m in DebugMacroDataSet.DEBUG_MACROS if
+ m.category == kwargs["category"]]
+ else:
+ candidate_macros = DebugMacroDataSet.DEBUG_MACROS
+
+ for cnt, macro_test in enumerate(candidate_macros):
+ f_name = f'test_{macro_test.category}_{cnt}'
+ t_desc = f'{macro_test!s}'
+ cls_ns[f_name] = mcls.build_macro_test(macro_test, t_desc)
+ return cls_ns
+
+ def __new__(mcls, name, bases, ns, **kwargs):
+ """Defined to prevent variable args from bubbling to the base class."""
+ return super().__new__(mcls, name, bases, ns)
+
+ def __init__(mcls, name, bases, ns, **kwargs):
+ """Defined to prevent variable args from bubbling to the base class."""
+ return super().__init__(name, bases, ns)
+
+ @classmethod
+ def build_macro_test(cls, macro_test: MacroTest.MacroTest,
+ test_desc: str) -> Callable[[None], None]:
+ """Returns a test function for this macro test data."
+
+ Args:
+ macro_test (MacroTest.MacroTest): The macro test class.
+
+ test_desc (str): A test description string.
+
+ Returns:
+ Callable[[None], None]: A test case function.
+ """
+ def test_func(self):
+ act_result = cls.check_regex(macro_test.macro)
+ self.assertCountEqual(
+ act_result,
+ macro_test.result,
+ test_desc + f'{linesep}'.join(
+ ["", f"Actual Result: {act_result}", "=" * 80, ""]))
+
+ return test_func
+
+ @classmethod
+ def check_regex(cls, source_str: str) -> Tuple[int, int, int]:
+ """Returns the plugin result for the given macro string.
+
+ Args:
+ source_str (str): A string containing debug macros.
+
+ Returns:
+ Tuple[int, int, int]: A tuple of the number of formatting errors,
+ number of print specifiers, and number of arguments for the macros
+ given.
+ """
+ return DebugMacroCheck.check_debug_macros(
+ DebugMacroCheck.get_debug_macros(source_str),
+ cls._get_function_name())
+
+ @classmethod
+ def _get_function_name(cls) -> str:
+ """Returns the function name from one level of call depth.
+
+ Returns:
+ str: The caller function name.
+ """
+ return "function: " + inspect.currentframe().f_back.f_code.co_name
+
+
+# Test container classes for dynamically generated macro test cases.
+# A class can be removed below to skip / remove it from testing.
+# Test case functions will be added to the appropriate class as they are
+# created.
+class Test_NoSpecifierNoArgument(
+ unittest.TestCase,
+ metaclass=Meta_TestDebugMacroCheck,
+ category="no_specifier_no_argument_macro_test"):
+ pass
+
+
+class Test_EqualSpecifierEqualArgument(
+ unittest.TestCase,
+ metaclass=Meta_TestDebugMacroCheck,
+ category="equal_specifier_equal_argument_macro_test"):
+ pass
+
+
+class Test_MoreSpecifiersThanArguments(
+ unittest.TestCase,
+ metaclass=Meta_TestDebugMacroCheck,
+ category="more_specifiers_than_arguments_macro_test"):
+ pass
+
+
+class Test_LessSpecifiersThanArguments(
+ unittest.TestCase,
+ metaclass=Meta_TestDebugMacroCheck,
+ category="less_specifiers_than_arguments_macro_test"):
+ pass
+
+
+class Test_IgnoredSpecifiers(
+ unittest.TestCase,
+ metaclass=Meta_TestDebugMacroCheck,
+ category="ignored_specifiers_macro_test"):
+ pass
+
+
+class Test_SpecialParsingMacroTest(
+ unittest.TestCase,
+ metaclass=Meta_TestDebugMacroCheck,
+ category="special_parsing_macro_test"):
+ pass
+
+
+class Test_CodeSnippetMacroTest(
+ unittest.TestCase,
+ metaclass=Meta_TestDebugMacroCheck,
+ category="code_snippet_macro_test"):
+ pass
+
+
+if __name__ == '__main__':
+ unittest.main()
|