.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:
Michael D Kinney 2020-01-22 10:17:23 -08:00 committed by mergify[bot]
parent bd33a385ee
commit 61364ab927
14 changed files with 421 additions and 24 deletions

View File

@ -48,7 +48,8 @@ class Settings(CiBuildSettingsManager, UpdateSettingsManager, SetupSettingsManag
"FmpDevicePkg",
"ShellPkg",
"FatPkg",
"CryptoPkg"
"CryptoPkg",
"UnitTestFrameworkPkg"
)
def GetArchitecturesSupported(self):
@ -117,10 +118,13 @@ class Settings(CiBuildSettingsManager, UpdateSettingsManager, SetupSettingsManag
def GetActiveScopes(self):
''' 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", "")
if GetHostInfo().os.upper() == "WINDOWS":
scopes += ('host-test-win',)
if GetHostInfo().os.upper() == "LINUX" and self.ActualToolChainTag.upper().startswith("GCC"):
if "AARCH64" in self.ActualArchitectures:
scopes += ("gcc_aarch64_linux",)
@ -133,18 +137,21 @@ class Settings(CiBuildSettingsManager, UpdateSettingsManager, SetupSettingsManag
''' return iterable containing RequiredSubmodule objects.
If no RequiredSubmodules return an empty iterable
'''
rs=[]
rs = []
rs.append(RequiredSubmodule(
"ArmPkg/Library/ArmSoftFloatLib/berkeley-softfloat-3", False))
rs.append(RequiredSubmodule(
"CryptoPkg/Library/OpensslLib/openssl", False))
rs.append(RequiredSubmodule(
"UnitTestFrameworkPkg/Library/CmockaLib/cmocka", False))
return rs
def GetName(self):
return "Edk2"
def GetDependencies(self):
return []
return [
]
def GetPackagesPath(self):
return ()
@ -155,10 +162,11 @@ class Settings(CiBuildSettingsManager, UpdateSettingsManager, SetupSettingsManag
def FilterPackagesToTest(self, changedFilesList: list, potentialPackagesList: list) -> list:
''' Filter potential packages to test based on changed files. '''
build_these_packages=[]
possible_packages=potentialPackagesList.copy()
build_these_packages = []
possible_packages = potentialPackagesList.copy()
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
if f.endswith(".py") and ".pytool" in nodes:

View File

@ -100,7 +100,7 @@ class CharEncodingCheck(ICiBuildPlugin):
overall_status += 1
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")
else:
tc.SetSuccess()

View File

@ -1,4 +1,4 @@
# @file HostUnitTestCompiler_plugin.py
# @file CompilerPlugin.py
##
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: BSD-2-Clause-Patent
@ -42,7 +42,7 @@ class CompilerPlugin(ICiBuildPlugin):
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.
# - edk2path object configured with workspace and packages path

View File

@ -113,7 +113,7 @@ class DependencyCheck(ICiBuildPlugin):
overall_status += 1
# 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")
else:
tc.SetSuccess()

View File

@ -54,12 +54,15 @@ class DscCompleteCheck(ICiBuildPlugin):
# 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.")
tc.LogStdError(
"DscPath not found in config file. Nothing to check.")
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())
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):
tc.SetSkipped()
@ -68,7 +71,8 @@ class DscCompleteCheck(ICiBuildPlugin):
# 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
INFFiles = [Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(
x) for x in INFFiles] # make edk2relative path so can compare with DSC
# remove ignores
@ -79,8 +83,10 @@ class DscCompleteCheck(ICiBuildPlugin):
tc.LogStdOut("Ignoring INF {0}".format(a))
INFFiles.remove(a)
except:
tc.LogStdError("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))
tc.LogStdError(
"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
dp = DscParser()
@ -99,11 +105,19 @@ class DscCompleteCheck(ICiBuildPlugin):
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))
tc.LogStdOut(
"Ignoring INF. Missing key for MODULE_TYPE {0}".format(INF))
continue
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
logging.critical(INF + " not in " + wsr_dsc_path)
@ -111,8 +125,9 @@ class DscCompleteCheck(ICiBuildPlugin):
overall_status = overall_status + 1
# If XML object exists, add result
if overall_status is not 0:
tc.SetFailed("DscCompleteCheck {0} Failed. Errors {1}".format(wsr_dsc_path, overall_status), "CHECK_FAILED")
if overall_status != 0:
tc.SetFailed("DscCompleteCheck {0} Failed. Errors {1}".format(
wsr_dsc_path, overall_status), "CHECK_FAILED")
else:
tc.SetSuccess()
return overall_status

View File

@ -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
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
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
"DscCompleteCheck": {
"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
}
```

View File

@ -221,7 +221,7 @@ class GuidCheck(ICiBuildPlugin):
# add result to test case
overall_status = len(Errors)
if overall_status is not 0:
if overall_status != 0:
tc.SetFailed("GuidCheck {0} Failed. Errors {1}".format(
packagename, overall_status), "CHECK_FAILED")
else:

View File

@ -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

View File

@ -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"
}

View 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

View File

@ -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

View File

@ -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"
}

View 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

View File

@ -146,7 +146,7 @@ class LibraryClassCheck(ICiBuildPlugin):
# 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")
else:
tc.SetSuccess()