VirtualBox

source: vbox/trunk/src/VBox/Devices/EFI/Firmware/BaseTools/Plugin/HostBasedUnitTestRunner/HostBasedUnitTestRunner.py@ 105681

Last change on this file since 105681 was 101291, checked in by vboxsync, 17 months ago

EFI/FirmwareNew: Make edk2-stable202308 build on all supported platforms (using gcc at least, msvc not tested yet), bugref:4643

  • Property svn:eol-style set to native
File size: 11.8 KB
Line 
1# @file HostBasedUnitTestRunner.py
2# Plugin to located any host-based unit tests in the output directory and execute them.
3##
4# Copyright (c) Microsoft Corporation.
5# SPDX-License-Identifier: BSD-2-Clause-Patent
6#
7##
8import os
9import logging
10import glob
11import stat
12import xml.etree.ElementTree
13from edk2toolext.environment.plugintypes.uefi_build_plugin import IUefiBuildPlugin
14from edk2toolext import edk2_logging
15import edk2toollib.windows.locate_tools as locate_tools
16from edk2toolext.environment import shell_environment
17from edk2toollib.utility_functions import RunCmd
18from edk2toollib.utility_functions import GetHostInfo
19from textwrap import dedent
20
21
22class HostBasedUnitTestRunner(IUefiBuildPlugin):
23
24 def do_pre_build(self, thebuilder):
25 '''
26 Run Prebuild
27 '''
28
29 return 0
30
31 def do_post_build(self, thebuilder):
32 '''
33 After a build, will automatically locate and run all host-based unit tests. Logs any
34 failures with Warning severity and will return a count of the failures as the return code.
35
36 EXPECTS:
37 - Build Var 'CI_BUILD_TYPE' - If not set to 'host_unit_test', will not do anything.
38
39 UPDATES:
40 - Shell Var 'CMOCKA_XML_FILE'
41 '''
42 ci_type = thebuilder.env.GetValue('CI_BUILD_TYPE')
43 if ci_type != 'host_unit_test':
44 return 0
45
46 shell_env = shell_environment.GetEnvironment()
47 logging.log(edk2_logging.get_section_level(),
48 "Run Host based Unit Tests")
49 path = thebuilder.env.GetValue("BUILD_OUTPUT_BASE")
50
51 failure_count = 0
52
53 # Set up the reporting type for Cmocka.
54 shell_env.set_shell_var('CMOCKA_MESSAGE_OUTPUT', 'xml')
55
56 for arch in thebuilder.env.GetValue("TARGET_ARCH").split():
57 logging.log(edk2_logging.get_subsection_level(),
58 "Testing for architecture: " + arch)
59 cp = os.path.join(path, arch)
60
61 # If any old results XML files exist, clean them up.
62 for old_result in glob.iglob(os.path.join(cp, "*.result.xml")):
63 os.remove(old_result)
64
65 # Find and Run any Host Tests
66 if GetHostInfo().os.upper() == "LINUX":
67 testList = glob.glob(os.path.join(cp, "*Test*"))
68 for a in testList[:]:
69 p = os.path.join(cp, a)
70 # It must be a file
71 if not os.path.isfile(p):
72 testList.remove(a)
73 logging.debug(f"Remove directory file: {p}")
74 continue
75 # It must be executable
76 if os.stat(p).st_mode & (stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH) == 0:
77 testList.remove(a)
78 logging.debug(f"Remove non-executable file: {p}")
79 continue
80
81 logging.info(f"Test file found: {p}")
82
83 elif GetHostInfo().os.upper() == "WINDOWS":
84 testList = glob.glob(os.path.join(cp, "*Test*.exe"))
85 else:
86 raise NotImplementedError("Unsupported Operating System")
87
88 if not testList:
89 logging.warning(dedent("""
90 UnitTest Coverage:
91 No unit tests discovered. Test coverage will not be generated.
92
93 Prevent this message by:
94 1. Adding host-based unit tests to this package
95 2. Ensuring tests have the word "Test" in their name
96 3. Disabling HostUnitTestCompilerPlugin in the package CI YAML file
97 """).strip())
98 return 0
99
100 for test in testList:
101 # Configure output name if test uses cmocka.
102 shell_env.set_shell_var(
103 'CMOCKA_XML_FILE', test + ".CMOCKA.%g." + arch + ".result.xml")
104 # Configure output name if test uses gtest.
105 shell_env.set_shell_var(
106 'GTEST_OUTPUT', "xml:" + test + ".GTEST." + arch + ".result.xml")
107
108 # Run the test.
109 ret = RunCmd('"' + test + '"', "", workingdir=cp)
110 if ret != 0:
111 logging.error("UnitTest Execution Error: " +
112 os.path.basename(test))
113 else:
114 logging.info("UnitTest Completed: " +
115 os.path.basename(test))
116 file_match_pattern = test + ".*." + arch + ".result.xml"
117 xml_results_list = glob.glob(file_match_pattern)
118 for xml_result_file in xml_results_list:
119 root = xml.etree.ElementTree.parse(
120 xml_result_file).getroot()
121 for suite in root:
122 for case in suite:
123 for result in case:
124 if result.tag == 'failure':
125 logging.warning(
126 "%s Test Failed" % os.path.basename(test))
127 logging.warning(
128 " %s - %s" % (case.attrib['name'], result.text))
129 failure_count += 1
130
131 if thebuilder.env.GetValue("CODE_COVERAGE") != "FALSE":
132 if thebuilder.env.GetValue("TOOL_CHAIN_TAG") == "GCC5":
133 ret = self.gen_code_coverage_gcc(thebuilder)
134 if ret != 0:
135 failure_count += 1
136 elif thebuilder.env.GetValue("TOOL_CHAIN_TAG").startswith ("VS"):
137 ret = self.gen_code_coverage_msvc(thebuilder)
138 if ret != 0:
139 failure_count += 1
140 else:
141 logging.info("Skipping code coverage. Currently, support GCC and MSVC compiler.")
142
143 return failure_count
144
145 def gen_code_coverage_gcc(self, thebuilder):
146 logging.info("Generating UnitTest code coverage")
147
148 buildOutputBase = thebuilder.env.GetValue("BUILD_OUTPUT_BASE")
149 workspace = thebuilder.env.GetValue("WORKSPACE")
150
151 # Generate base code coverage for all source files
152 ret = RunCmd("lcov", f"--no-external --capture --initial --directory {buildOutputBase} --output-file {buildOutputBase}/cov-base.info --rc lcov_branch_coverage=1")
153 if ret != 0:
154 logging.error("UnitTest Coverage: Failed to build initial coverage data.")
155 return 1
156
157 # Coverage data for tested files only
158 ret = RunCmd("lcov", f"--capture --directory {buildOutputBase}/ --output-file {buildOutputBase}/coverage-test.info --rc lcov_branch_coverage=1")
159 if ret != 0:
160 logging.error("UnitTest Coverage: Failed to build coverage data for tested files.")
161 return 1
162
163 # Aggregate all coverage data
164 ret = RunCmd("lcov", f"--add-tracefile {buildOutputBase}/cov-base.info --add-tracefile {buildOutputBase}/coverage-test.info --output-file {buildOutputBase}/total-coverage.info --rc lcov_branch_coverage=1")
165 if ret != 0:
166 logging.error("UnitTest Coverage: Failed to aggregate coverage data.")
167 return 1
168
169 # Generate coverage XML
170 ret = RunCmd("lcov_cobertura",f"{buildOutputBase}/total-coverage.info -o {buildOutputBase}/compare.xml")
171 if ret != 0:
172 logging.error("UnitTest Coverage: Failed to generate coverage XML.")
173 return 1
174
175 # Filter out auto-generated and test code
176 ret = RunCmd("lcov_cobertura",f"{buildOutputBase}/total-coverage.info --excludes ^.*UnitTest\|^.*MU\|^.*Mock\|^.*DEBUG -o {buildOutputBase}/coverage.xml")
177 if ret != 0:
178 logging.error("UnitTest Coverage: Failed generate filtered coverage XML.")
179 return 1
180
181 # Generate all coverage file
182 testCoverageList = glob.glob (f"{workspace}/Build/**/total-coverage.info", recursive=True)
183
184 coverageFile = ""
185 for testCoverage in testCoverageList:
186 coverageFile += " --add-tracefile " + testCoverage
187 ret = RunCmd("lcov", f"{coverageFile} --output-file {workspace}/Build/all-coverage.info --rc lcov_branch_coverage=1")
188 if ret != 0:
189 logging.error("UnitTest Coverage: Failed generate all coverage file.")
190 return 1
191
192 # Generate and XML file if requested.for all package
193 if os.path.isfile(f"{workspace}/Build/coverage.xml"):
194 os.remove(f"{workspace}/Build/coverage.xml")
195 ret = RunCmd("lcov_cobertura",f"{workspace}/Build/all-coverage.info --excludes ^.*UnitTest\|^.*MU\|^.*Mock\|^.*DEBUG -o {workspace}/Build/coverage.xml")
196
197 return 0
198
199
200 def gen_code_coverage_msvc(self, thebuilder):
201 logging.info("Generating UnitTest code coverage")
202
203
204 buildOutputBase = thebuilder.env.GetValue("BUILD_OUTPUT_BASE")
205 testList = glob.glob(os.path.join(buildOutputBase, "**","*Test*.exe"), recursive=True)
206 workspace = thebuilder.env.GetValue("WORKSPACE")
207 workspace = (workspace + os.sep) if workspace[-1] != os.sep else workspace
208 workspaceBuild = os.path.join(workspace, 'Build')
209 # Generate coverage file
210 coverageFile = ""
211 for testFile in testList:
212 ret = RunCmd("OpenCppCoverage", f"--source {workspace} --export_type binary:{testFile}.cov -- {testFile}")
213 if ret != 0:
214 logging.error("UnitTest Coverage: Failed to collect coverage data.")
215 return 1
216
217 coverageFile = f" --input_coverage={testFile}.cov"
218 totalCoverageFile = os.path.join(buildOutputBase, 'coverage.cov')
219 if os.path.isfile(totalCoverageFile):
220 coverageFile += f" --input_coverage={totalCoverageFile}"
221 ret = RunCmd(
222 "OpenCppCoverage",
223 f"--export_type binary:{totalCoverageFile} " +
224 f"--working_dir={workspaceBuild} " +
225 f"{coverageFile}"
226 )
227 if ret != 0:
228 logging.error("UnitTest Coverage: Failed to collect coverage data.")
229 return 1
230
231 # Generate and XML file if requested.by each package
232 ret = RunCmd(
233 "OpenCppCoverage",
234 f"--export_type cobertura:{os.path.join(buildOutputBase, 'coverage.xml')} " +
235 f"--working_dir={workspaceBuild} " +
236 f"--input_coverage={totalCoverageFile} "
237 )
238 if ret != 0:
239 logging.error("UnitTest Coverage: Failed to generate cobertura format xml in single package.")
240 return 1
241
242 # Generate total report XML file for all package
243 testCoverageList = glob.glob(os.path.join(workspace, "Build", "**", "*Test*.exe.cov"), recursive=True)
244 coverageFile = ""
245 totalCoverageFile = os.path.join(workspaceBuild, 'coverage.cov')
246 for testCoverage in testCoverageList:
247 coverageFile = f" --input_coverage={testCoverage}"
248 if os.path.isfile(totalCoverageFile):
249 coverageFile += f" --input_coverage={totalCoverageFile}"
250 ret = RunCmd(
251 "OpenCppCoverage",
252 f"--export_type binary:{totalCoverageFile} " +
253 f"--working_dir={workspaceBuild} " +
254 f"{coverageFile}"
255 )
256 if ret != 0:
257 logging.error("UnitTest Coverage: Failed to collect coverage data.")
258 return 1
259
260 ret = RunCmd(
261 "OpenCppCoverage",
262 f"--export_type cobertura:{os.path.join(workspaceBuild, 'coverage.xml')} " +
263 f"--working_dir={workspaceBuild} " +
264 f"--input_coverage={totalCoverageFile}"
265 )
266 if ret != 0:
267 logging.error("UnitTest Coverage: Failed to generate cobertura format xml.")
268 return 1
269
270 return 0
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette