diff options
Diffstat (limited to 'BaseTools/Plugin/DebugMacroCheck/tests/test_DebugMacroCheck.py')
| -rw-r--r-- | BaseTools/Plugin/DebugMacroCheck/tests/test_DebugMacroCheck.py | 402 |
1 files changed, 201 insertions, 201 deletions
diff --git a/BaseTools/Plugin/DebugMacroCheck/tests/test_DebugMacroCheck.py b/BaseTools/Plugin/DebugMacroCheck/tests/test_DebugMacroCheck.py index 7abc0d2b87..5e85c1da46 100644 --- a/BaseTools/Plugin/DebugMacroCheck/tests/test_DebugMacroCheck.py +++ b/BaseTools/Plugin/DebugMacroCheck/tests/test_DebugMacroCheck.py @@ -1,201 +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()
+# @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() |
