.pytool: Add CI support for host based unit tests with results
https://bugzilla.tianocore.org/show_bug.cgi?id=2505 * Add plugin to build and run host based unit tests * Add plugin that performs a DSC complete check DSC files used to build host based tests * Update DscCompleteCheck plugin to ignore module INFs with a MODULE_TYPE of HOST_APPLICATION and library INFs that only support a module type of HOST_APPLICATION. * Fix issues in XML reports from checkers. Cc: Sean Brogan <sean.brogan@microsoft.com> Cc: Bret Barkelew <Bret.Barkelew@microsoft.com> Cc: Liming Gao <liming.gao@intel.com> Signed-off-by: Michael D Kinney <michael.d.kinney@intel.com> Reviewed-by: Bret Barkelew <Bret.Barkelew@microsoft.com>
This commit is contained in:
committed by
mergify[bot]
parent
bd33a385ee
commit
61364ab927
@ -48,7 +48,8 @@ class Settings(CiBuildSettingsManager, UpdateSettingsManager, SetupSettingsManag
|
|||||||
"FmpDevicePkg",
|
"FmpDevicePkg",
|
||||||
"ShellPkg",
|
"ShellPkg",
|
||||||
"FatPkg",
|
"FatPkg",
|
||||||
"CryptoPkg"
|
"CryptoPkg",
|
||||||
|
"UnitTestFrameworkPkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
def GetArchitecturesSupported(self):
|
def GetArchitecturesSupported(self):
|
||||||
@ -117,10 +118,13 @@ class Settings(CiBuildSettingsManager, UpdateSettingsManager, SetupSettingsManag
|
|||||||
|
|
||||||
def GetActiveScopes(self):
|
def GetActiveScopes(self):
|
||||||
''' return tuple containing scopes that should be active for this process '''
|
''' return tuple containing scopes that should be active for this process '''
|
||||||
scopes = ("cibuild","edk2-build")
|
scopes = ("cibuild", "edk2-build", "host-based-test")
|
||||||
|
|
||||||
self.ActualToolChainTag = shell_environment.GetBuildVars().GetValue("TOOL_CHAIN_TAG", "")
|
self.ActualToolChainTag = shell_environment.GetBuildVars().GetValue("TOOL_CHAIN_TAG", "")
|
||||||
|
|
||||||
|
if GetHostInfo().os.upper() == "WINDOWS":
|
||||||
|
scopes += ('host-test-win',)
|
||||||
|
|
||||||
if GetHostInfo().os.upper() == "LINUX" and self.ActualToolChainTag.upper().startswith("GCC"):
|
if GetHostInfo().os.upper() == "LINUX" and self.ActualToolChainTag.upper().startswith("GCC"):
|
||||||
if "AARCH64" in self.ActualArchitectures:
|
if "AARCH64" in self.ActualArchitectures:
|
||||||
scopes += ("gcc_aarch64_linux",)
|
scopes += ("gcc_aarch64_linux",)
|
||||||
@ -138,13 +142,16 @@ class Settings(CiBuildSettingsManager, UpdateSettingsManager, SetupSettingsManag
|
|||||||
"ArmPkg/Library/ArmSoftFloatLib/berkeley-softfloat-3", False))
|
"ArmPkg/Library/ArmSoftFloatLib/berkeley-softfloat-3", False))
|
||||||
rs.append(RequiredSubmodule(
|
rs.append(RequiredSubmodule(
|
||||||
"CryptoPkg/Library/OpensslLib/openssl", False))
|
"CryptoPkg/Library/OpensslLib/openssl", False))
|
||||||
|
rs.append(RequiredSubmodule(
|
||||||
|
"UnitTestFrameworkPkg/Library/CmockaLib/cmocka", False))
|
||||||
return rs
|
return rs
|
||||||
|
|
||||||
def GetName(self):
|
def GetName(self):
|
||||||
return "Edk2"
|
return "Edk2"
|
||||||
|
|
||||||
def GetDependencies(self):
|
def GetDependencies(self):
|
||||||
return []
|
return [
|
||||||
|
]
|
||||||
|
|
||||||
def GetPackagesPath(self):
|
def GetPackagesPath(self):
|
||||||
return ()
|
return ()
|
||||||
@ -158,7 +165,8 @@ class Settings(CiBuildSettingsManager, UpdateSettingsManager, SetupSettingsManag
|
|||||||
build_these_packages = []
|
build_these_packages = []
|
||||||
possible_packages = potentialPackagesList.copy()
|
possible_packages = potentialPackagesList.copy()
|
||||||
for f in changedFilesList:
|
for f in changedFilesList:
|
||||||
nodes=f.split("/") # split each part of path for comparison later
|
# split each part of path for comparison later
|
||||||
|
nodes = f.split("/")
|
||||||
|
|
||||||
# python file change in .pytool folder causes building all
|
# python file change in .pytool folder causes building all
|
||||||
if f.endswith(".py") and ".pytool" in nodes:
|
if f.endswith(".py") and ".pytool" in nodes:
|
||||||
|
@ -100,7 +100,7 @@ class CharEncodingCheck(ICiBuildPlugin):
|
|||||||
overall_status += 1
|
overall_status += 1
|
||||||
|
|
||||||
tc.LogStdOut("Tested Encoding on {0} files".format(files_tested))
|
tc.LogStdOut("Tested Encoding on {0} files".format(files_tested))
|
||||||
if overall_status is not 0:
|
if overall_status != 0:
|
||||||
tc.SetFailed("CharEncoding {0} Failed. Errors {1}".format(packagename, overall_status), "CHAR_ENCODING_CHECK_FAILED")
|
tc.SetFailed("CharEncoding {0} Failed. Errors {1}".format(packagename, overall_status), "CHAR_ENCODING_CHECK_FAILED")
|
||||||
else:
|
else:
|
||||||
tc.SetSuccess()
|
tc.SetSuccess()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# @file HostUnitTestCompiler_plugin.py
|
# @file CompilerPlugin.py
|
||||||
##
|
##
|
||||||
# Copyright (c) Microsoft Corporation.
|
# Copyright (c) Microsoft Corporation.
|
||||||
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||||
@ -42,7 +42,7 @@ class CompilerPlugin(ICiBuildPlugin):
|
|||||||
return ["DEBUG", "RELEASE"]
|
return ["DEBUG", "RELEASE"]
|
||||||
|
|
||||||
##
|
##
|
||||||
# External function of plugin. This function is used to perform the task of the MuBuild Plugin
|
# External function of plugin. This function is used to perform the task of the ICiBuildPlugin Plugin
|
||||||
#
|
#
|
||||||
# - package is the edk2 path to package. This means workspace/packagepath relative.
|
# - package is the edk2 path to package. This means workspace/packagepath relative.
|
||||||
# - edk2path object configured with workspace and packages path
|
# - edk2path object configured with workspace and packages path
|
||||||
|
@ -113,7 +113,7 @@ class DependencyCheck(ICiBuildPlugin):
|
|||||||
overall_status += 1
|
overall_status += 1
|
||||||
|
|
||||||
# If XML object exists, add results
|
# If XML object exists, add results
|
||||||
if overall_status is not 0:
|
if overall_status != 0:
|
||||||
tc.SetFailed("Failed with {0} errors".format(overall_status), "DEPENDENCYCHECK_FAILED")
|
tc.SetFailed("Failed with {0} errors".format(overall_status), "DEPENDENCYCHECK_FAILED")
|
||||||
else:
|
else:
|
||||||
tc.SetSuccess()
|
tc.SetSuccess()
|
||||||
|
@ -54,12 +54,15 @@ class DscCompleteCheck(ICiBuildPlugin):
|
|||||||
# Parse the config for required DscPath element
|
# Parse the config for required DscPath element
|
||||||
if "DscPath" not in pkgconfig:
|
if "DscPath" not in pkgconfig:
|
||||||
tc.SetSkipped()
|
tc.SetSkipped()
|
||||||
tc.LogStdError("DscPath not found in config file. Nothing to check.")
|
tc.LogStdError(
|
||||||
|
"DscPath not found in config file. Nothing to check.")
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
abs_pkg_path = Edk2pathObj.GetAbsolutePathOnThisSytemFromEdk2RelativePath(packagename)
|
abs_pkg_path = Edk2pathObj.GetAbsolutePathOnThisSytemFromEdk2RelativePath(
|
||||||
|
packagename)
|
||||||
abs_dsc_path = os.path.join(abs_pkg_path, pkgconfig["DscPath"].strip())
|
abs_dsc_path = os.path.join(abs_pkg_path, pkgconfig["DscPath"].strip())
|
||||||
wsr_dsc_path = Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(abs_dsc_path)
|
wsr_dsc_path = Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(
|
||||||
|
abs_dsc_path)
|
||||||
|
|
||||||
if abs_dsc_path is None or wsr_dsc_path == "" or not os.path.isfile(abs_dsc_path):
|
if abs_dsc_path is None or wsr_dsc_path == "" or not os.path.isfile(abs_dsc_path):
|
||||||
tc.SetSkipped()
|
tc.SetSkipped()
|
||||||
@ -68,7 +71,8 @@ class DscCompleteCheck(ICiBuildPlugin):
|
|||||||
|
|
||||||
# Get INF Files
|
# Get INF Files
|
||||||
INFFiles = self.WalkDirectoryForExtension([".inf"], abs_pkg_path)
|
INFFiles = self.WalkDirectoryForExtension([".inf"], abs_pkg_path)
|
||||||
INFFiles = [Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(x) for x in INFFiles] # make edk2relative path so can compare with DSC
|
INFFiles = [Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(
|
||||||
|
x) for x in INFFiles] # make edk2relative path so can compare with DSC
|
||||||
|
|
||||||
# remove ignores
|
# remove ignores
|
||||||
|
|
||||||
@ -79,8 +83,10 @@ class DscCompleteCheck(ICiBuildPlugin):
|
|||||||
tc.LogStdOut("Ignoring INF {0}".format(a))
|
tc.LogStdOut("Ignoring INF {0}".format(a))
|
||||||
INFFiles.remove(a)
|
INFFiles.remove(a)
|
||||||
except:
|
except:
|
||||||
tc.LogStdError("DscCompleteCheck.IgnoreInf -> {0} not found in filesystem. Invalid ignore file".format(a))
|
tc.LogStdError(
|
||||||
logging.info("DscCompleteCheck.IgnoreInf -> {0} not found in filesystem. Invalid ignore file".format(a))
|
"DscCompleteCheck.IgnoreInf -> {0} not found in filesystem. Invalid ignore file".format(a))
|
||||||
|
logging.info(
|
||||||
|
"DscCompleteCheck.IgnoreInf -> {0} not found in filesystem. Invalid ignore file".format(a))
|
||||||
|
|
||||||
# DSC Parser
|
# DSC Parser
|
||||||
dp = DscParser()
|
dp = DscParser()
|
||||||
@ -99,11 +105,19 @@ class DscCompleteCheck(ICiBuildPlugin):
|
|||||||
infp.SetPackagePaths(Edk2pathObj.PackagePathList)
|
infp.SetPackagePaths(Edk2pathObj.PackagePathList)
|
||||||
infp.ParseFile(INF)
|
infp.ParseFile(INF)
|
||||||
if("MODULE_TYPE" not in infp.Dict):
|
if("MODULE_TYPE" not in infp.Dict):
|
||||||
tc.LogStdOut("Ignoring INF. Missing key for MODULE_TYPE {0}".format(INF))
|
tc.LogStdOut(
|
||||||
|
"Ignoring INF. Missing key for MODULE_TYPE {0}".format(INF))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if(infp.Dict["MODULE_TYPE"] == "HOST_APPLICATION"):
|
if(infp.Dict["MODULE_TYPE"] == "HOST_APPLICATION"):
|
||||||
tc.LogStdOut("Ignoring INF. Module type is HOST_APPLICATION {0}".format(INF))
|
tc.LogStdOut(
|
||||||
|
"Ignoring INF. Module type is HOST_APPLICATION {0}".format(INF))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(infp.SupportedPhases) == 1 and \
|
||||||
|
"HOST_APPLICATION" in infp.SupportedPhases:
|
||||||
|
tc.LogStdOut(
|
||||||
|
"Ignoring Library INF due to only supporting type HOST_APPLICATION {0}".format(INF))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logging.critical(INF + " not in " + wsr_dsc_path)
|
logging.critical(INF + " not in " + wsr_dsc_path)
|
||||||
@ -111,8 +125,9 @@ class DscCompleteCheck(ICiBuildPlugin):
|
|||||||
overall_status = overall_status + 1
|
overall_status = overall_status + 1
|
||||||
|
|
||||||
# If XML object exists, add result
|
# If XML object exists, add result
|
||||||
if overall_status is not 0:
|
if overall_status != 0:
|
||||||
tc.SetFailed("DscCompleteCheck {0} Failed. Errors {1}".format(wsr_dsc_path, overall_status), "CHECK_FAILED")
|
tc.SetFailed("DscCompleteCheck {0} Failed. Errors {1}".format(
|
||||||
|
wsr_dsc_path, overall_status), "CHECK_FAILED")
|
||||||
else:
|
else:
|
||||||
tc.SetSuccess()
|
tc.SetSuccess()
|
||||||
return overall_status
|
return overall_status
|
||||||
|
@ -7,6 +7,11 @@ that it would not be built if the package were built). This is critical because
|
|||||||
much of the CI infrastructure assumes that all modules will be listed in the DSC
|
much of the CI infrastructure assumes that all modules will be listed in the DSC
|
||||||
and compiled.
|
and compiled.
|
||||||
|
|
||||||
|
This test will ignore INFs in the following cases:
|
||||||
|
|
||||||
|
1. When MODULE_TYPE = HOST_APPLICATION
|
||||||
|
2. When a Library instance **only** supports the HOST_APPLICATION environment
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The plugin has a few configuration options to support the UEFI codebase.
|
The plugin has a few configuration options to support the UEFI codebase.
|
||||||
@ -14,7 +19,7 @@ The plugin has a few configuration options to support the UEFI codebase.
|
|||||||
``` yaml
|
``` yaml
|
||||||
"DscCompleteCheck": {
|
"DscCompleteCheck": {
|
||||||
"DscPath": "", # Path to dsc from root of package
|
"DscPath": "", # Path to dsc from root of package
|
||||||
"IgnoreInf": [] # Ignore INF if found in filesystem by not dsc
|
"IgnoreInf": [] # Ignore INF if found in filesystem but not dsc
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -221,7 +221,7 @@ class GuidCheck(ICiBuildPlugin):
|
|||||||
|
|
||||||
# add result to test case
|
# add result to test case
|
||||||
overall_status = len(Errors)
|
overall_status = len(Errors)
|
||||||
if overall_status is not 0:
|
if overall_status != 0:
|
||||||
tc.SetFailed("GuidCheck {0} Failed. Errors {1}".format(
|
tc.SetFailed("GuidCheck {0} Failed. Errors {1}".format(
|
||||||
packagename, overall_status), "CHECK_FAILED")
|
packagename, overall_status), "CHECK_FAILED")
|
||||||
else:
|
else:
|
||||||
|
@ -0,0 +1,149 @@
|
|||||||
|
# @file HostUnitTestCompilerPlugin.py
|
||||||
|
##
|
||||||
|
# Copyright (c) Microsoft Corporation.
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||||
|
##
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from edk2toollib.uefi.edk2.parsers.dsc_parser import DscParser
|
||||||
|
from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
|
||||||
|
from edk2toolext.environment.uefi_build import UefiBuilder
|
||||||
|
from edk2toolext import edk2_logging
|
||||||
|
from edk2toolext.environment.var_dict import VarDict
|
||||||
|
from edk2toollib.utility_functions import GetHostInfo
|
||||||
|
|
||||||
|
|
||||||
|
class HostUnitTestCompilerPlugin(ICiBuildPlugin):
|
||||||
|
"""
|
||||||
|
A CiBuildPlugin that compiles the dsc for host based unit test apps.
|
||||||
|
An IUefiBuildPlugin may be attached to this plugin that will run the
|
||||||
|
unit tests and collect the results after successful compilation.
|
||||||
|
|
||||||
|
Configuration options:
|
||||||
|
"HostUnitTestCompilerPlugin": {
|
||||||
|
"DscPath": "<path to dsc from root of pkg>"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def GetTestName(self, packagename: str, environment: VarDict) -> tuple:
|
||||||
|
""" Provide the testcase name and classname for use in reporting
|
||||||
|
testclassname: a descriptive string for the testcase can include whitespace
|
||||||
|
classname: should be patterned <packagename>.<plugin>.<optionally any unique condition>
|
||||||
|
|
||||||
|
Args:
|
||||||
|
packagename: string containing name of package to build
|
||||||
|
environment: The VarDict for the test to run in
|
||||||
|
Returns:
|
||||||
|
a tuple containing the testcase name and the classname
|
||||||
|
(testcasename, classname)
|
||||||
|
"""
|
||||||
|
num,types = self.__GetHostUnitTestArch(environment)
|
||||||
|
types = types.replace(" ", "_")
|
||||||
|
|
||||||
|
return ("Compile and Run Host-Based UnitTests for " + packagename + " on arch " + types,
|
||||||
|
packagename + ".HostUnitTestCompiler." + types)
|
||||||
|
|
||||||
|
def RunsOnTargetList(self):
|
||||||
|
return ["NOOPT"]
|
||||||
|
|
||||||
|
#
|
||||||
|
# Find the intersection of application types that can run on this host
|
||||||
|
# and the TARGET_ARCH being build in this request.
|
||||||
|
#
|
||||||
|
# return tuple with (number of UEFI arch types, space separated string)
|
||||||
|
def __GetHostUnitTestArch(self, environment):
|
||||||
|
requested = environment.GetValue("TARGET_ARCH").split(' ')
|
||||||
|
host = []
|
||||||
|
if GetHostInfo().arch == 'x86':
|
||||||
|
#assume 64bit can handle 64 and 32
|
||||||
|
#assume 32bit can only handle 32
|
||||||
|
## change once IA32 issues resolved host.append("IA32")
|
||||||
|
if GetHostInfo().bit == '64':
|
||||||
|
host.append("X64")
|
||||||
|
elif GetHostInfo().arch == 'ARM':
|
||||||
|
if GetHostInfo().bit == '64':
|
||||||
|
host.append("AARCH64")
|
||||||
|
elif GetHostInfo().bit == '32':
|
||||||
|
host.append("ARM")
|
||||||
|
|
||||||
|
willrun = set(requested) & set(host)
|
||||||
|
return (len(willrun), " ".join(willrun))
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# External function of plugin. This function is used to perform the task of the ICiBuildPlugin Plugin
|
||||||
|
#
|
||||||
|
# - package is the edk2 path to package. This means workspace/packagepath relative.
|
||||||
|
# - edk2path object configured with workspace and packages path
|
||||||
|
# - PkgConfig Object (dict) for the pkg
|
||||||
|
# - EnvConfig Object
|
||||||
|
# - Plugin Manager Instance
|
||||||
|
# - Plugin Helper Obj Instance
|
||||||
|
# - Junit Logger
|
||||||
|
# - output_stream the StringIO output stream from this plugin via logging
|
||||||
|
def RunBuildPlugin(self, packagename, Edk2pathObj, pkgconfig, environment, PLM, PLMHelper, tc, output_stream=None):
|
||||||
|
self._env = environment
|
||||||
|
environment.SetValue("CI_BUILD_TYPE", "host_unit_test", "Set in HostUnitTestCompilerPlugin")
|
||||||
|
|
||||||
|
# Parse the config for required DscPath element
|
||||||
|
if "DscPath" not in pkgconfig:
|
||||||
|
tc.SetSkipped()
|
||||||
|
tc.LogStdError("DscPath not found in config file. Nothing to compile for HostBasedUnitTests.")
|
||||||
|
return -1
|
||||||
|
|
||||||
|
AP = Edk2pathObj.GetAbsolutePathOnThisSytemFromEdk2RelativePath(packagename)
|
||||||
|
|
||||||
|
APDSC = os.path.join(AP, pkgconfig["DscPath"].strip())
|
||||||
|
AP_Path = Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(APDSC)
|
||||||
|
if AP is None or AP_Path is None or not os.path.isfile(APDSC):
|
||||||
|
tc.SetSkipped()
|
||||||
|
tc.LogStdError("Package HostBasedUnitTest Dsc not found.")
|
||||||
|
return -1
|
||||||
|
|
||||||
|
logging.info("Building {0}".format(AP_Path))
|
||||||
|
self._env.SetValue("ACTIVE_PLATFORM", AP_Path, "Set in Compiler Plugin")
|
||||||
|
num, RUNNABLE_ARCHITECTURES = self.__GetHostUnitTestArch(environment)
|
||||||
|
if(num == 0):
|
||||||
|
tc.SetSkipped()
|
||||||
|
tc.LogStdError("No host architecture compatibility")
|
||||||
|
return -1
|
||||||
|
|
||||||
|
if not environment.SetValue("TARGET_ARCH",
|
||||||
|
RUNNABLE_ARCHITECTURES,
|
||||||
|
"Update Target Arch based on Host Support"):
|
||||||
|
#use AllowOverride function since this is a controlled attempt to change
|
||||||
|
environment.AllowOverride("TARGET_ARCH")
|
||||||
|
if not environment.SetValue("TARGET_ARCH",
|
||||||
|
RUNNABLE_ARCHITECTURES,
|
||||||
|
"Update Target Arch based on Host Support"):
|
||||||
|
raise RuntimeError("Can't Change TARGET_ARCH as required")
|
||||||
|
|
||||||
|
# Parse DSC to check for SUPPORTED_ARCHITECTURES
|
||||||
|
dp = DscParser()
|
||||||
|
dp.SetBaseAbsPath(Edk2pathObj.WorkspacePath)
|
||||||
|
dp.SetPackagePaths(Edk2pathObj.PackagePathList)
|
||||||
|
dp.ParseFile(AP_Path)
|
||||||
|
if "SUPPORTED_ARCHITECTURES" in dp.LocalVars:
|
||||||
|
SUPPORTED_ARCHITECTURES = dp.LocalVars["SUPPORTED_ARCHITECTURES"].split('|')
|
||||||
|
TARGET_ARCHITECTURES = environment.GetValue("TARGET_ARCH").split(' ')
|
||||||
|
|
||||||
|
# Skip if there is no intersection between SUPPORTED_ARCHITECTURES and TARGET_ARCHITECTURES
|
||||||
|
if len(set(SUPPORTED_ARCHITECTURES) & set(TARGET_ARCHITECTURES)) == 0:
|
||||||
|
tc.SetSkipped()
|
||||||
|
tc.LogStdError("No supported architecutres to build for host unit tests")
|
||||||
|
return -1
|
||||||
|
|
||||||
|
uefiBuilder = UefiBuilder()
|
||||||
|
# do all the steps
|
||||||
|
# WorkSpace, PackagesPath, PInHelper, PInManager
|
||||||
|
ret = uefiBuilder.Go(Edk2pathObj.WorkspacePath, os.pathsep.join(Edk2pathObj.PackagePathList), PLMHelper, PLM)
|
||||||
|
if ret != 0: # failure:
|
||||||
|
tc.SetFailed("Compile failed for {0}".format(packagename), "Compile_FAILED")
|
||||||
|
tc.LogStdError("{0} Compile failed with error code {1} ".format(AP_Path, ret))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
tc.SetSuccess()
|
||||||
|
return 0
|
@ -0,0 +1,12 @@
|
|||||||
|
##
|
||||||
|
# CiBuildPlugin used to build anything that identifies
|
||||||
|
# as a unit test.
|
||||||
|
#
|
||||||
|
# Copyright (c) Microsoft Corporation.
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||||
|
##
|
||||||
|
{
|
||||||
|
"scope": "host-based-test",
|
||||||
|
"name": "Host Unit Test Compiler Plugin",
|
||||||
|
"module": "HostUnitTestCompilerPlugin"
|
||||||
|
}
|
24
.pytool/Plugin/HostUnitTestCompilerPlugin/Readme.md
Normal file
24
.pytool/Plugin/HostUnitTestCompilerPlugin/Readme.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Host UnitTest Compiler Plugin
|
||||||
|
|
||||||
|
A CiBuildPlugin that compiles the dsc for host based unit test apps.
|
||||||
|
An IUefiBuildPlugin may be attached to this plugin that will run the unit tests and collect the results after successful compilation.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The package relative path of the DSC file to build.
|
||||||
|
|
||||||
|
``` yaml
|
||||||
|
"HostUnitTestCompilerPlugin": {
|
||||||
|
"DscPath": "<path to dsc from root of pkg>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DscPath
|
||||||
|
|
||||||
|
Package relative path to the DSC file to build.
|
||||||
|
|
||||||
|
## Copyright
|
||||||
|
|
||||||
|
Copyright (c) Microsoft Corporation.
|
||||||
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||||
|
|
@ -0,0 +1,140 @@
|
|||||||
|
# @file HostUnitTestDscCompleteCheck.py
|
||||||
|
#
|
||||||
|
# This is a copy of DscCompleteCheck with different filtering logic.
|
||||||
|
# It should be discussed if this should be one plugin
|
||||||
|
#
|
||||||
|
# Copyright (c) Microsoft Corporation.
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||||
|
##
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
|
||||||
|
from edk2toollib.uefi.edk2.parsers.dsc_parser import DscParser
|
||||||
|
from edk2toollib.uefi.edk2.parsers.inf_parser import InfParser
|
||||||
|
from edk2toolext.environment.var_dict import VarDict
|
||||||
|
|
||||||
|
|
||||||
|
class HostUnitTestDscCompleteCheck(ICiBuildPlugin):
|
||||||
|
"""
|
||||||
|
A CiBuildPlugin that scans the package Host Unit Test dsc file and confirms all Host application modules (inf files) are
|
||||||
|
listed in the components sections.
|
||||||
|
|
||||||
|
Configuration options:
|
||||||
|
"HostUnitTestDscCompleteCheck": {
|
||||||
|
"DscPath": "", # Path to Host based unit test DSC file
|
||||||
|
"IgnoreInf": [] # Ignore INF if found in filesystem but not dsc
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def GetTestName(self, packagename: str, environment: VarDict) -> tuple:
|
||||||
|
""" Provide the testcase name and classname for use in reporting
|
||||||
|
|
||||||
|
Args:
|
||||||
|
packagename: string containing name of package to build
|
||||||
|
environment: The VarDict for the test to run in
|
||||||
|
Returns:
|
||||||
|
a tuple containing the testcase name and the classname
|
||||||
|
(testcasename, classname)
|
||||||
|
testclassname: a descriptive string for the testcase can include whitespace
|
||||||
|
classname: should be patterned <packagename>.<plugin>.<optionally any unique condition>
|
||||||
|
"""
|
||||||
|
return ("Check the " + packagename + " Host Unit Test DSC for a being complete", packagename + ".HostUnitTestDscCompleteCheck")
|
||||||
|
|
||||||
|
##
|
||||||
|
# External function of plugin. This function is used to perform the task of the MuBuild Plugin
|
||||||
|
#
|
||||||
|
# - package is the edk2 path to package. This means workspace/packagepath relative.
|
||||||
|
# - edk2path object configured with workspace and packages path
|
||||||
|
# - PkgConfig Object (dict) for the pkg
|
||||||
|
# - VarDict containing the shell environment Build Vars
|
||||||
|
# - Plugin Manager Instance
|
||||||
|
# - Plugin Helper Obj Instance
|
||||||
|
# - Junit Logger
|
||||||
|
# - output_stream the StringIO output stream from this plugin via logging
|
||||||
|
def RunBuildPlugin(self, packagename, Edk2pathObj, pkgconfig, environment, PLM, PLMHelper, tc, output_stream=None):
|
||||||
|
overall_status = 0
|
||||||
|
|
||||||
|
# Parse the config for required DscPath element
|
||||||
|
if "DscPath" not in pkgconfig:
|
||||||
|
tc.SetSkipped()
|
||||||
|
tc.LogStdError(
|
||||||
|
"DscPath not found in config file. Nothing to check.")
|
||||||
|
return -1
|
||||||
|
|
||||||
|
abs_pkg_path = Edk2pathObj.GetAbsolutePathOnThisSytemFromEdk2RelativePath(
|
||||||
|
packagename)
|
||||||
|
abs_dsc_path = os.path.join(abs_pkg_path, pkgconfig["DscPath"].strip())
|
||||||
|
wsr_dsc_path = Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(
|
||||||
|
abs_dsc_path)
|
||||||
|
|
||||||
|
if abs_dsc_path is None or wsr_dsc_path == "" or not os.path.isfile(abs_dsc_path):
|
||||||
|
tc.SetSkipped()
|
||||||
|
tc.LogStdError("Package Host Unit Test Dsc not found")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Get INF Files
|
||||||
|
INFFiles = self.WalkDirectoryForExtension([".inf"], abs_pkg_path)
|
||||||
|
INFFiles = [Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(
|
||||||
|
x) for x in INFFiles] # make edk2relative path so can compare with DSC
|
||||||
|
|
||||||
|
# remove ignores
|
||||||
|
|
||||||
|
if "IgnoreInf" in pkgconfig:
|
||||||
|
for a in pkgconfig["IgnoreInf"]:
|
||||||
|
a = a.replace(os.sep, "/")
|
||||||
|
try:
|
||||||
|
tc.LogStdOut("Ignoring INF {0}".format(a))
|
||||||
|
INFFiles.remove(a)
|
||||||
|
except:
|
||||||
|
tc.LogStdError(
|
||||||
|
"HostUnitTestDscCompleteCheck.IgnoreInf -> {0} not found in filesystem. Invalid ignore file".format(a))
|
||||||
|
logging.info(
|
||||||
|
"HostUnitTestDscCompleteCheck.IgnoreInf -> {0} not found in filesystem. Invalid ignore file".format(a))
|
||||||
|
|
||||||
|
# DSC Parser
|
||||||
|
dp = DscParser()
|
||||||
|
dp.SetBaseAbsPath(Edk2pathObj.WorkspacePath)
|
||||||
|
dp.SetPackagePaths(Edk2pathObj.PackagePathList)
|
||||||
|
dp.SetInputVars(environment.GetAllBuildKeyValues())
|
||||||
|
dp.ParseFile(wsr_dsc_path)
|
||||||
|
|
||||||
|
# Check if INF in component section
|
||||||
|
for INF in INFFiles:
|
||||||
|
if not any(INF.strip() in x for x in dp.ThreeMods) and \
|
||||||
|
not any(INF.strip() in x for x in dp.SixMods) and \
|
||||||
|
not any(INF.strip() in x for x in dp.OtherMods):
|
||||||
|
|
||||||
|
infp = InfParser().SetBaseAbsPath(Edk2pathObj.WorkspacePath)
|
||||||
|
infp.SetPackagePaths(Edk2pathObj.PackagePathList)
|
||||||
|
infp.ParseFile(INF)
|
||||||
|
if("MODULE_TYPE" not in infp.Dict):
|
||||||
|
tc.LogStdOut(
|
||||||
|
"Ignoring INF. Missing key for MODULE_TYPE {0}".format(INF))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if(infp.Dict["MODULE_TYPE"] == "HOST_APPLICATION"):
|
||||||
|
# should compile test a library that is declared type HOST_APPLICATION
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif len(infp.SupportedPhases) > 0 and \
|
||||||
|
"HOST_APPLICATION" in infp.SupportedPhases:
|
||||||
|
# should compile test a library that supports HOST_APPLICATION but
|
||||||
|
# require it to be an explicit opt-in
|
||||||
|
pass
|
||||||
|
|
||||||
|
else:
|
||||||
|
tc.LogStdOut(
|
||||||
|
"Ignoring INF. MODULE_TYPE or suppored phases not HOST_APPLICATION {0}".format(INF))
|
||||||
|
continue
|
||||||
|
|
||||||
|
logging.critical(INF + " not in " + wsr_dsc_path)
|
||||||
|
tc.LogStdError("{0} not in {1}".format(INF, wsr_dsc_path))
|
||||||
|
overall_status = overall_status + 1
|
||||||
|
|
||||||
|
# If XML object exists, add result
|
||||||
|
if overall_status != 0:
|
||||||
|
tc.SetFailed("HostUnitTestDscCompleteCheck {0} Failed. Errors {1}".format(
|
||||||
|
wsr_dsc_path, overall_status), "CHECK_FAILED")
|
||||||
|
else:
|
||||||
|
tc.SetSuccess()
|
||||||
|
return overall_status
|
@ -0,0 +1,12 @@
|
|||||||
|
##
|
||||||
|
# CiBuildPlugin used to confirm all INFs are listed in
|
||||||
|
# the components section of package dsc
|
||||||
|
#
|
||||||
|
# Copyright (c) Microsoft Corporation.
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||||
|
##
|
||||||
|
{
|
||||||
|
"scope": "host-based-test",
|
||||||
|
"name": "Host Unit Test Dsc Complete Check Test",
|
||||||
|
"module": "HostUnitTestDscCompleteCheck"
|
||||||
|
}
|
32
.pytool/Plugin/HostUnitTestDscCompleteCheck/Readme.md
Normal file
32
.pytool/Plugin/HostUnitTestDscCompleteCheck/Readme.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Host Unit Test Dsc Complete Check Plugin
|
||||||
|
|
||||||
|
This CiBuildPlugin scans all INF files from a package for those related to host
|
||||||
|
based unit tests confirms they are listed in the unit test DSC file for the package.
|
||||||
|
The test considers it an error if any INF meeting the requirements does not appear
|
||||||
|
in the `Components` section of the unit test DSC. This is critical because
|
||||||
|
much of the CI infrastructure assumes that modules will be listed in the DSC
|
||||||
|
and compiled.
|
||||||
|
|
||||||
|
This test will only require INFs in the following cases:
|
||||||
|
|
||||||
|
1. When MODULE_TYPE = HOST_APPLICATION
|
||||||
|
2. When a Library instance supports the HOST_APPLICATION environment
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The plugin has a few configuration options to support the UEFI codebase.
|
||||||
|
|
||||||
|
``` yaml
|
||||||
|
"HostUnitTestDscCompleteCheck": {
|
||||||
|
"DscPath": "", # Path to Host based unit test DSC file
|
||||||
|
"IgnoreInf": [] # Ignore INF if found in filesystem but not dsc
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DscPath
|
||||||
|
|
||||||
|
Path to DSC to consider platform dsc
|
||||||
|
|
||||||
|
### IgnoreInf
|
||||||
|
|
||||||
|
Ignore error if Inf file is not listed in DSC file
|
@ -146,7 +146,7 @@ class LibraryClassCheck(ICiBuildPlugin):
|
|||||||
|
|
||||||
|
|
||||||
# If XML object exists, add result
|
# If XML object exists, add result
|
||||||
if overall_status is not 0:
|
if overall_status != 0:
|
||||||
tc.SetFailed("LibraryClassCheck {0} Failed. Errors {1}".format(wsr_dec_path, overall_status), "CHECK_FAILED")
|
tc.SetFailed("LibraryClassCheck {0} Failed. Errors {1}".format(wsr_dec_path, overall_status), "CHECK_FAILED")
|
||||||
else:
|
else:
|
||||||
tc.SetSuccess()
|
tc.SetSuccess()
|
||||||
|
Reference in New Issue
Block a user