diff --git a/.pytool/Plugin/RustHostUnitTestPlugin/Readme.md b/.pytool/Plugin/RustHostUnitTestPlugin/Readme.md index 85252f40ebb..86709dc1d02 100644 --- a/.pytool/Plugin/RustHostUnitTestPlugin/Readme.md +++ b/.pytool/Plugin/RustHostUnitTestPlugin/Readme.md @@ -1,21 +1,25 @@ # Rust Host Unit Test Plugin -This CI plugin runs all unit tests with coverage enabled, calculating coverage results on a per package basis. It filters results to only calculate coverage on files within the package. - -This CI plugin will also calculate coverage for the entire workspace. +This CI plugin runs all unit tests and requires that they pass. If code coverage is turned on with +`"CalculateCoverage": true`, then coverage percentages will be calculated on a per rust crate basis. ## Plugin Customizations -As a default, this plugin requires 75% coverage, though this can be configured within a packages ci.yaml file by adding the entry `RustCoverageCheck`. The required coverage percent can also be customized on a per (rust) package bases. +- `CalculateCoverage`: true / false - Whether or not to calculate coverage results +- `Coverage`: int (0, 1) - The percentage of coverage required to pass the CI check, if `CalculateCoverage` is enabled +- `CoverageOverrides` int (0, 1) - Crate specific override of percentage needed to pass + +As a default, Calculating Coverage is enabled and at least 75% (.75) code coverage is required to pass. ### Example ci settings ``` yaml "RustHostUnitTestPlugin": { + "CalculateCoverage": true, "Coverage": 1, "CoverageOverrides": { - "DxeRust": 0.0, - "UefiEventLib": 0.0, + "DxeRust": 0.4, + "UefiEventLib": 0.67, } } ``` diff --git a/.pytool/Plugin/RustHostUnitTestPlugin/RustHostUnitTestPlugin.py b/.pytool/Plugin/RustHostUnitTestPlugin/RustHostUnitTestPlugin.py index bd62c2fd66c..0ecef06f35e 100644 --- a/.pytool/Plugin/RustHostUnitTestPlugin/RustHostUnitTestPlugin.py +++ b/.pytool/Plugin/RustHostUnitTestPlugin/RustHostUnitTestPlugin.py @@ -11,6 +11,7 @@ from pathlib import Path import re import logging +import platform class RustHostUnitTestPlugin(ICiBuildPlugin): def GetTestName(self, packagename: str, environment: object) -> tuple[str, str]: @@ -20,9 +21,14 @@ def RunsOnTargetList(self) -> List[str]: return ["NO-TARGET"] def RunBuildPlugin(self, packagename, Edk2pathObj, pkgconfig, environment, PLM, PLMHelper, tc, output_stream): - ws = Edk2pathObj.WorkspacePath rust_ws = PLMHelper.RustWorkspace(ws) # .pytool/Plugin/RustPackageHelper + + with_coverage = pkgconfig.get("CalculateCoverage", True) + + if platform.system() == "Windows" and platform.machine() == 'aarch64': + logging.debug("Coverage skipped by plugin, not supported on Windows ARM") + with_coverage = False # Build list of packages that are in the EDK2 package we are running CI on pp = Path(Edk2pathObj.GetAbsolutePathOnThisSystemFromEdk2RelativePath(packagename)) @@ -43,7 +49,7 @@ def RunBuildPlugin(self, packagename, Edk2pathObj, pkgconfig, environment, PLM, # Run tests and evaluate results try: - results = rust_ws.coverage(crate_name_list, ignore_list = ignore_list, report_type = "xml") + results = rust_ws.test(crate_name_list, ignore_list = ignore_list, report_type = "xml", coverage=with_coverage) except RuntimeError as e: logging.warning(str(e)) tc.LogStdError(str(e)) @@ -66,6 +72,11 @@ def RunBuildPlugin(self, packagename, Edk2pathObj, pkgconfig, environment, PLM, tc.SetFailed(f'Host unit tests failed. Failures {failed}', "CHECK_FAILED") return failed + # Return if we are not running with coverage + if not with_coverage: + tc.SetSuccess() + return 0 + # Calculate coverage coverage = {} for file, cov in results["coverage"].items(): diff --git a/.pytool/Plugin/RustPackageHelper/RustPackageHelper.py b/.pytool/Plugin/RustPackageHelper/RustPackageHelper.py index 0dfbc25bf58..5e6b7cba63d 100644 --- a/.pytool/Plugin/RustPackageHelper/RustPackageHelper.py +++ b/.pytool/Plugin/RustPackageHelper/RustPackageHelper.py @@ -55,11 +55,10 @@ def __set_members(self): self.members = list(members) - def coverage(self, pkg_list = None, ignore_list = None, report_type: str = "html" ): - """Runs coverage at the workspace level. + def test(self, pkg_list: list[str] = None, ignore_list: list[str] = None, report_type: str = "html", coverage: bool = True): + """Runs tests on a list of rust packages / crates. - Generates a single report that provides coverage information for all - packages in the workspace. + Will additionally calculate code coverage if coverage is set to True. """ if pkg_list is None: pkg_list = [pkg.name for pkg in self.members] @@ -67,11 +66,15 @@ def coverage(self, pkg_list = None, ignore_list = None, report_type: str = "html # Set up the command command = "cargo" params = "make" - if ignore_list: - params += f' -e COV_FLAGS="--out {report_type} --exclude-files {" --exclude-files ".join(ignore_list)}"' + + if coverage: + if ignore_list: + params += f' -e COV_FLAGS="--out {report_type} --exclude-files {" --exclude-files ".join(ignore_list)}"' + else: + params += f' -e COV_FLAGS="--out {report_type}"' + params += f" coverage {','.join(pkg_list)}" else: - params += f' -e COV_FLAGS="--out {report_type}"' - params += f" coverage {','.join(pkg_list)}" + params += f" test {','.join(pkg_list)}" # Run the command output = io.StringIO() @@ -96,9 +99,10 @@ def coverage(self, pkg_list = None, ignore_list = None, report_type: str = "html line = line.replace("test ", "") if line.endswith("... ok"): result["pass"].append(line.replace(" ... ok", "")) + elif line.endswith("... ignored"): + continue else: result["fail"].append(line.replace(" ... FAILED", "")) - continue # Command failed, but we didn't parse any failed tests if return_value != 0 and len(result["fail"]) == 0: @@ -107,6 +111,9 @@ def coverage(self, pkg_list = None, ignore_list = None, report_type: str = "html if len(result["fail"]) > 0: return result + if not coverage: + return result + # Determine coverage if all tests passed for line in lines: line = line.strip().strip("\n")