mirror of https://github.com/acidanthera/audk.git
202 lines
7.4 KiB
Python
202 lines
7.4 KiB
Python
# @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()
|