diff --git a/OvmfPkg/PlatformCI/PlatformBuild.py b/OvmfPkg/PlatformCI/PlatformBuild.py index 6c541cdea4a5a..72cb7e0e9e400 100644 --- a/OvmfPkg/PlatformCI/PlatformBuild.py +++ b/OvmfPkg/PlatformCI/PlatformBuild.py @@ -6,6 +6,11 @@ ## import os import sys +import shutil +import logging +import re +from edk2toolext.environment import shell_environment +from edk2toolext.environment.multiple_workspace import MultipleWorkspace sys.path.append(os.path.dirname(os.path.abspath(__file__))) from PlatformBuildLib import SettingsManager @@ -24,6 +29,10 @@ class CommonPlatform(): Scopes = ('ovmf', 'edk2-build') WorkspaceRoot = os.path.realpath(os.path.join( os.path.dirname(os.path.abspath(__file__)), "..", "..")) + # Support build and run Shell Unit Test modules + UnitTestModuleList = {} + RunShellUnitTest = False + ShellUnitTestLog = '' @classmethod def GetDscName(cls, ArchCsv: str) -> str: @@ -39,5 +48,108 @@ def GetDscName(cls, ArchCsv: str) -> str: dsc += ".dsc" return dsc + @classmethod + def UpdatePackagesSupported(cls, ShellUnitTestListArg): + ''' Update PackagesSupported by -u ShellUnitTestList from cmd line. ''' + UnitTestModuleListStr = ','.join(ShellUnitTestListArg) + if not re.search(r'.+.inf:.+.dsc', UnitTestModuleListStr): + raise Exception('No valid ModulePath:DscPath in the -u {}'.format(UnitTestModuleListStr)) + UnitTestModuleList = UnitTestModuleListStr.split(',') + PackagesSupported = [] + for KeyValue in UnitTestModuleList: + PkgName = KeyValue.split("Pkg")[0] + 'Pkg' + if PkgName not in PackagesSupported: + PackagesSupported.append(PkgName) + cls.PackagesSupported = tuple(PackagesSupported) + print('PackagesSupported for UnitTest is {}'.format(cls.PackagesSupported)) + + @classmethod + def UpdateUnitTestConfig(cls, args): + ''' Update UnitTest config by -u ShellUnitTestList and -p PkgsToBuildForUT from cmd line. + ShellUnitTestList is in this format: {module1:dsc1, module2:dsc2, module3:dsc2...}. + Only the modules which are in the PkgsToBuildForUT list are added into self.UnitTestModuleList. + ''' + UnitTestModuleListStr = ','.join(args.ShellUnitTestList) + if not re.search(r'.+.inf:.+.dsc', UnitTestModuleListStr): + raise Exception('No valid ModulePath:DscPath in the -u {}'.format(args.ShellUnitTestList)) + UnitTestModuleList = UnitTestModuleListStr.split(',') + if args.PkgsToBuildForUT is None or all(['Pkg' not in Pkg for Pkg in args.PkgsToBuildForUT]): + # No invalid Pkgs from input. Build all modules in -u UnitTestModuleList. + for KeyValue in UnitTestModuleList: + UnitTestPath = os.path.normpath(KeyValue.split(":")[0]) + DscPath = os.path.normpath(KeyValue.split(":")[1]) + cls.UnitTestModuleList[UnitTestPath] = DscPath + else: + PkgsToBuildForUT = ','.join(args.PkgsToBuildForUT).split(',') + for KeyValue in UnitTestModuleList: + UnitTestPath = os.path.normpath(KeyValue.split(":")[0]) + DscPath = os.path.normpath(KeyValue.split(":")[1]) + PkgName = UnitTestPath.split("Pkg")[0] + 'Pkg' + if PkgName in PkgsToBuildForUT: + cls.UnitTestModuleList[UnitTestPath] = DscPath + if len(cls.UnitTestModuleList) > 0: + cls.RunShellUnitTest = True + cls.ShellUnitTestLog = os.path.join(cls.WorkspaceRoot, 'Build', "BUILDLOG_UnitTest.txt") + print('UnitTestModuleList is {}'.format(cls.UnitTestModuleList)) + + def BuildUnitTest(self): + ''' Build specific DSC for modules in UnitTestModuleList ''' + self.env = shell_environment.GetBuildVars() + self.ws = PlatformBuilder.GetWorkspaceRoot(self) + self.mws = MultipleWorkspace() + self.pp = '' + VirtualDrive = os.path.join(self.env.GetValue("BUILD_OUTPUT_BASE"), "VirtualDrive") + os.makedirs(VirtualDrive, exist_ok=True) + + # DSC by self.GetDscName() should have been built in BUILD process. + BuiltDsc = [CommonPlatform.GetDscName(",".join(self.env.GetValue("TARGET_ARCH").split(' ')))] + for UnitTestPath, DscPath in CommonPlatform.UnitTestModuleList.items(): + if DscPath not in BuiltDsc: + ModuleName = os.path.split(UnitTestPath)[1].split('.inf')[0] + logging.info('Build {0} for {1}'.format(DscPath, ModuleName)) + BuiltDsc.append(DscPath) + Arch = self.env.GetValue("TARGET_ARCH").split(" ") + if 'X64' in Arch: + UTArch = 'X64' + else: + UTArch = 'IA32' + self.env.AllowOverride("ACTIVE_PLATFORM") + self.env.SetValue("ACTIVE_PLATFORM", DscPath, "For UnitTest") + self.env.AllowOverride("TARGET_ARCH") + self.env.SetValue("TARGET_ARCH", UTArch, "For UnitTest") # Set UnitTest arch the same as Ovmf Shell module. + ret = PlatformBuilder.Build(self) # Build specific dsc for UnitTest modules + if (ret != 0): + return ret + ret = PlatformBuilder.ParseDscFile(self) # Parse OUTPUT_DIRECTORY from dsc files + if(ret != 0): + return ret + OutputPath = os.path.normpath(os.path.join(self.ws, self.env.GetValue("OUTPUT_DIRECTORY"))) + EfiPath = os.path.join(OutputPath, self.env.GetValue("TARGET") + "_" + self.env.GetValue("TOOL_CHAIN_TAG"), + UTArch, UnitTestPath.split('.inf')[0], "OUTPUT", ModuleName + '.efi') + logging.info('Copy {0}.efi from:{1}'.format(ModuleName, EfiPath)) + shutil.copy(EfiPath, VirtualDrive) + return 0 + + @staticmethod + def WriteEfiToStartup(EfiFolder, FileObj): + ''' Write all the .efi files' name in VirtualDrive into Startup.nsh ''' + for Root,Dirs,Files in os.walk(EfiFolder): + for File in Files: + if os.path.splitext(File)[1] == '.efi': + FileObj.write("{0} \n".format(File)) + + @classmethod + def CheckUnitTestLog(cls): + ''' Check the boot log for UnitTest ''' + File = open(cls.ShellUnitTestLog, "r") + FileContent = File.readlines() + logging.info('Check the UnitTest boot log:{0}'.format(cls.ShellUnitTestLog)) + for Index in range(len(FileContent)): + if 'FAILURE MESSAGE:' in FileContent[Index]: + if FileContent[Index + 1].strip() != '': + FailureMessage = FileContent[Index + 1] + FileContent[Index + 2] + return FailureMessage + return 0 + import PlatformBuildLib PlatformBuildLib.CommonPlatform = CommonPlatform diff --git a/OvmfPkg/PlatformCI/PlatformBuildLib.py b/OvmfPkg/PlatformCI/PlatformBuildLib.py index bfef9849c749d..b42235b2acfff 100644 --- a/OvmfPkg/PlatformCI/PlatformBuildLib.py +++ b/OvmfPkg/PlatformCI/PlatformBuildLib.py @@ -103,15 +103,28 @@ def FilterPackagesToTest(self, changedFilesList: list, potentialPackagesList: li return build_these_packages + def AddCommandLineOptions(self, parserObj): + parserObj.add_argument('-u', '--UnitTest', dest='ShellUnitTestList', type=str, + help='Optional - Key:Value that contains Shell UnitTest list and corresponding DscPath you want to test.(workspace relative)' + 'Can list multiple by doing -u , or -u -u ', + action="append", default=None) + + def RetrieveCommandLineOptions(self, args): + if args.ShellUnitTestList: + CommonPlatform.UpdatePackagesSupported(args.ShellUnitTestList) + def GetPlatformDscAndConfig(self) -> tuple: ''' If a platform desires to provide its DSC then Policy 4 will evaluate if any of the changes will be built in the dsc. The tuple should be (, ) + This Policy 4 can only be applied when PackagesSupported only contains OvmfPkg Since it doesn't support + mutiple packages evaluation. ''' - dsc = CommonPlatform.GetDscName(",".join(self.ActualArchitectures)) - return (f"OvmfPkg/{dsc}", {}) - + if (len(CommonPlatform.PackagesSupported) == 1) and (CommonPlatform.PackagesSupported[0] == 'OvmfPkg'): + dsc = CommonPlatform.GetDscName(",".join(self.ActualArchitectures)) + return (f"OvmfPkg/{dsc}", {}) + return None # ####################################################################################### # # Actual Configuration for Platform Build # @@ -126,6 +139,14 @@ def AddCommandLineOptions(self, parserObj): help="Optional - CSV of architecture to build. IA32 will use IA32 for Pei & Dxe. " "X64 will use X64 for both PEI and DXE. IA32,X64 will use IA32 for PEI and " "X64 for DXE. default is IA32,X64") + parserObj.add_argument('-p', '--pkg', '--pkg-dir', dest='PkgsToBuildForUT', type=str, + help='Optional - Package list you want to build for UnitTest.efi. (workspace relative).' + 'Can list multiple by doing -p , or -p -p .If no valid input -p, build and run all -u UnitTest', + action="append", default=None) + parserObj.add_argument('-u', '--UnitTest', dest='ShellUnitTestList', type=str, + help='Optional - Key:Value that contains Shell UnitTest list and corresponding DscPath you want to test.(workspace relative)' + 'Can list multiple by doing -u , or -u -u ', + action="append", default=None) def RetrieveCommandLineOptions(self, args): ''' Retrieve command line options from the argparser ''' @@ -133,6 +154,10 @@ def RetrieveCommandLineOptions(self, args): shell_environment.GetBuildVars().SetValue("TARGET_ARCH"," ".join(args.build_arch.upper().split(",")), "From CmdLine") dsc = CommonPlatform.GetDscName(args.build_arch) shell_environment.GetBuildVars().SetValue("ACTIVE_PLATFORM", f"OvmfPkg/{dsc}", "From CmdLine") + self.RunShellUnitTest = False + if args.ShellUnitTestList: + CommonPlatform.UpdateUnitTestConfig(args) + self.RunShellUnitTest = CommonPlatform.RunShellUnitTest def GetWorkspaceRoot(self): ''' get WorkspacePath ''' @@ -176,6 +201,11 @@ def PlatformPreBuild(self): return 0 def PlatformPostBuild(self): + if self.RunShellUnitTest: + ret = CommonPlatform.BuildUnitTest(self) + if ret !=0: + logging.critical("Build UnitTest failed") + return ret return 0 def FlashRomImage(self): @@ -210,9 +240,13 @@ def FlashRomImage(self): else: args += " -pflash " + os.path.join(OutputPath_FV, "OVMF.fd") # path to firmware - if (self.env.GetValue("MAKE_STARTUP_NSH").upper() == "TRUE"): f = open(os.path.join(VirtualDrive, "startup.nsh"), "w") + if self.RunShellUnitTest: + # When RunShellUnitTest is True, write all efi files name into startup.nsh. + CommonPlatform.WriteEfiToStartup(VirtualDrive, f) + # Output UnitTest log into ShellUnitTestLog. + args += " -serial file:{}".format(CommonPlatform.ShellUnitTestLog) f.write("BOOT SUCCESS !!! \n") ## add commands here f.write("reset -s\n") @@ -222,6 +256,11 @@ def FlashRomImage(self): if ret == 0xc0000005: #for some reason getting a c0000005 on successful return - return 0 - + ret = 0 + if self.RunShellUnitTest and ret == 0: + # Check the UnitTest boot log. + UnitTestResult = CommonPlatform.CheckUnitTestLog() + if (UnitTestResult): + logging.info("UnitTest failed with this FAILURE MESSAGE:\n{}".format(UnitTestResult)) + return UnitTestResult return ret