1#!/usr/bin/python 2# 3# Copyright 2017 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18"""Generates a report on CKI syscall coverage in VTS LTP. 19 20This module generates a report on the syscalls in the Android CKI and 21their coverage in VTS LTP. 22 23The coverage report provides, for each syscall in the CKI, the number of 24enabled and disabled LTP tests for the syscall in VTS. If VTS test output is 25supplied, the report instead provides the number of disabled, skipped, failing, 26and passing tests for each syscall. 27 28Assumptions are made about the structure of files in LTP source 29and the naming convention. 30""" 31 32import argparse 33import os.path 34import re 35import sys 36import xml.etree.ElementTree as ET 37 38sys.path.append(os.path.join(os.environ["ANDROID_BUILD_TOP"], 39 "bionic/libc/tools")) 40import gensyscalls 41 42sys.path.append(os.path.join(os.environ["ANDROID_BUILD_TOP"], 43 "test/vts-testcase/kernel/ltp/configs")) 44import disabled_tests as vts_disabled 45import stable_tests as vts_stable 46 47bionic_libc_root = os.path.join(os.environ["ANDROID_BUILD_TOP"], "bionic/libc") 48 49 50class CKI_Coverage(object): 51 """Determines current test coverage of CKI system calls in LTP. 52 53 Many of the system calls in the CKI are tested by LTP. For a given 54 system call an LTP test may or may not exist, that LTP test may or may 55 not be currently compiling properly for Android, the test may not be 56 stable, the test may not be running due to environment issues or 57 passing. This class looks at various sources of information to determine 58 the current test coverage of system calls in the CKI from LTP. 59 60 Note that due to some deviations in LTP of tests from the common naming 61 convention there there may be tests that are flagged here as not having 62 coverage when in fact they do. 63 """ 64 65 LTP_SYSCALL_ROOT = os.path.join(os.environ["ANDROID_BUILD_TOP"], 66 "external/ltp/testcases/kernel/syscalls") 67 DISABLED_IN_LTP_PATH = os.path.join(os.environ["ANDROID_BUILD_TOP"], 68 "external/ltp/android/tools/disabled_tests.txt") 69 70 ltp_full_set = [] 71 72 cki_syscalls = [] 73 74 disabled_in_ltp = [] 75 disabled_in_vts_ltp = vts_disabled.DISABLED_TESTS 76 stable_in_vts_ltp = vts_stable.STABLE_TESTS 77 78 syscall_tests = {} 79 disabled_tests = {} 80 failing_tests = {} 81 skipped_tests = {} 82 passing_tests = {} 83 84 test_results = {} 85 86 def __init__(self, arch): 87 self.arch = arch 88 89 def load_ltp_tests(self): 90 """Load the list of LTP syscall tests. 91 92 Load the list of all syscall tests existing in LTP. 93 """ 94 for path, dirs, files in os.walk(self.LTP_SYSCALL_ROOT): 95 for filename in files: 96 basename, ext = os.path.splitext(filename) 97 if ext != ".c": continue 98 self.ltp_full_set.append(basename) 99 100 def load_ltp_disabled_tests(self): 101 """Load the list of LTP tests not being compiled. 102 103 The LTP repository in Android contains a list of tests which are not 104 compiled due to incompatibilities with Android. 105 """ 106 with open(self.DISABLED_IN_LTP_PATH) as fp: 107 for line in fp: 108 line = line.strip() 109 if not line: continue 110 test_re = re.compile(r"^(\w+)") 111 test_match = re.match(test_re, line) 112 if not test_match: continue 113 self.disabled_in_ltp.append(test_match.group(1)) 114 115 def parse_test_results(self, results): 116 """Parse xml from VTS output to collect LTP results. 117 118 Parse xml to collect pass/fail results for each LTP 119 test. A failure occurs if a test has a result other than pass or fail. 120 121 Args: 122 results: Path to VTS output XML file. 123 """ 124 tree = ET.parse(results) 125 root = tree.getroot() 126 127 # find LTP module 128 for module in root.findall("Module"): 129 if module.attrib["name"] != "VtsKernelLtp": continue 130 131 # find LTP testcase 132 for testcase in module.findall("TestCase"): 133 if testcase.attrib["name"] != "KernelLtpTest": continue 134 135 # iterate over each LTP test 136 for test in testcase.findall("Test"): 137 test_re = re.compile(r"^syscalls.(\w+)_((32bit)|(64bit))$") 138 test_match = re.match(test_re, test.attrib["name"]) 139 if not test_match: continue 140 141 test_name = test_match.group(1) 142 143 if test.attrib["result"] == "pass": 144 self.test_results[test_name] = "pass" 145 elif test.attrib["result"] == "fail": 146 self.test_results[test_name] = "fail" 147 else: 148 print ("Unknown VTS LTP test result for %s is %s" % 149 (test_name, test.attrib["result"])) 150 sys.exit(-1) 151 152 def ltp_test_special_cases(self, syscall, test): 153 """Detect special cases in syscall to LTP mapping. 154 155 Most syscall tests in LTP follow a predictable naming 156 convention, but some do not. Detect known special cases. 157 158 Args: 159 syscall: The name of a syscall. 160 test: The name of a testcase. 161 162 Returns: 163 A boolean indicating whether the given syscall is tested 164 by the given testcase. 165 """ 166 if syscall == "clock_nanosleep" and test == "clock_nanosleep2_01": 167 return True 168 if syscall == "fadvise" and test.startswith("posix_fadvise"): 169 return True 170 if syscall == "futex" and test.startswith("futex_"): 171 return True 172 if syscall == "inotify_add_watch" or syscall == "inotify_rm_watch": 173 test_re = re.compile(r"^inotify\d+$") 174 if re.match(test_re, test): 175 return True 176 if syscall == "newfstatat": 177 test_re = re.compile(r"^fstatat\d+$") 178 if re.match(test_re, test): 179 return True 180 181 return False 182 183 def match_syscalls_to_tests(self, syscalls): 184 """Match syscalls with tests in LTP. 185 186 Create a mapping from CKI syscalls and tests in LTP. This mapping can 187 largely be determined using a common naming convention in the LTP file 188 hierarchy but there are special cases that have to be taken care of. 189 190 Args: 191 syscalls: List of syscall structures containing all syscalls 192 in the CKI. 193 """ 194 for syscall in syscalls: 195 if self.arch not in syscall: 196 continue 197 self.cki_syscalls.append(syscall["name"]) 198 self.syscall_tests[syscall["name"]] = [] 199 # LTP does not use the 64 at the end of syscall names for testcases. 200 ltp_syscall_name = syscall["name"] 201 if ltp_syscall_name.endswith("64"): 202 ltp_syscall_name = ltp_syscall_name[0:-2] 203 # Most LTP syscalls have source files for the tests that follow 204 # a naming convention in the regexp below. Exceptions exist though. 205 # For now those are checked for specifically. 206 test_re = re.compile(r"^%s_?0?\d\d?$" % ltp_syscall_name) 207 for test in self.ltp_full_set: 208 if (re.match(test_re, test) or 209 self.ltp_test_special_cases(ltp_syscall_name, test)): 210 # The filenames of the ioctl tests in LTP do not match the name 211 # of the testcase defined in that source, which is what shows 212 # up in VTS. 213 if ltp_syscall_name == "ioctl": 214 test = "ioctl01_02" 215 self.syscall_tests[syscall["name"]].append(test) 216 self.cki_syscalls.sort() 217 218 def update_test_status(self): 219 """Populate test configuration and output for all CKI syscalls. 220 221 Go through VTS test configuration and test results (if provided) to 222 populate data for all CKI syscalls. 223 """ 224 for syscall in self.cki_syscalls: 225 self.disabled_tests[syscall] = [] 226 self.skipped_tests[syscall] = [] 227 self.failing_tests[syscall] = [] 228 self.passing_tests[syscall] = [] 229 if not self.syscall_tests[syscall]: 230 continue 231 for test in self.syscall_tests[syscall]: 232 if (test in self.disabled_in_ltp or 233 "syscalls.%s" % test in self.disabled_in_vts_ltp or 234 ("syscalls.%s_32bit" % test not in self.stable_in_vts_ltp and 235 "syscalls.%s_64bit" % test not in self.stable_in_vts_ltp)): 236 self.disabled_tests[syscall].append(test) 237 continue 238 239 if not self.test_results: 240 continue 241 242 if test not in self.test_results: 243 self.skipped_tests[syscall].append(test) 244 elif self.test_results[test] == "fail": 245 self.failing_tests[syscall].append(test) 246 elif self.test_results[test] == "pass": 247 self.passing_tests[syscall].append(test) 248 else: 249 print ("Warning - could not resolve test %s status for syscall %s" % 250 (test, syscall)) 251 252 def output_results(self): 253 """Pretty print the CKI syscall LTP coverage results. 254 255 Pretty prints a table of the CKI syscall LTP coverage, pointing out 256 syscalls which have no passing tests in VTS LTP. 257 """ 258 if not self.test_results: 259 self.output_limited_results() 260 return 261 count = 0 262 uncovered = 0 263 for syscall in self.cki_syscalls: 264 if not count % 20: 265 print ("%25s Disabled Skipped Failing Passing -------------" % 266 "-------------") 267 sys.stdout.write("%25s %s %s %s %s" % 268 (syscall, len(self.disabled_tests[syscall]), 269 len(self.skipped_tests[syscall]), 270 len(self.failing_tests[syscall]), 271 len(self.passing_tests[syscall]))) 272 if not self.passing_tests[syscall]: 273 print " <-- uncovered" 274 uncovered += 1 275 else: 276 print "" 277 count += 1 278 print "" 279 print ("Total uncovered syscalls: %s out of %s" % 280 (uncovered, len(self.cki_syscalls))) 281 282 def output_limited_results(self): 283 """Pretty print the CKI syscall LTP coverage without VTS test results. 284 285 When no VTS test results are supplied then only the count of enabled 286 and disabled LTP tests may be shown. 287 """ 288 count = 0 289 uncovered = 0 290 for syscall in self.cki_syscalls: 291 if not count % 20: 292 print ("%25s Disabled Enabled -------------" % 293 "-------------") 294 sys.stdout.write("%25s %s %s" % 295 (syscall, len(self.disabled_tests[syscall]), 296 len(self.syscall_tests[syscall]) - 297 len(self.disabled_tests[syscall]))) 298 if (len(self.syscall_tests[syscall]) - 299 len(self.disabled_tests[syscall]) <= 0): 300 print " <-- uncovered" 301 uncovered += 1 302 else: 303 print "" 304 count += 1 305 print "" 306 print ("Total uncovered syscalls: %s out of %s" % 307 (uncovered, len(self.cki_syscalls))) 308 309if __name__ == "__main__": 310 parser = argparse.ArgumentParser(description="Output list of system calls " 311 "in the Common Kernel Interface and their VTS LTP coverage. If VTS " 312 "test output is supplied, output includes system calls which have " 313 "tests in VTS LTP, but the tests are skipped or are failing.") 314 parser.add_argument("arch", help="architecture of Android platform") 315 parser.add_argument("-l", action="store_true", 316 help="list CKI syscalls only, without coverage") 317 parser.add_argument("-r", "--results", help="path to VTS test_result.xml") 318 args = parser.parse_args() 319 320 cki = gensyscalls.SysCallsTxtParser() 321 cki.parse_file(os.path.join(bionic_libc_root, "SYSCALLS.TXT")) 322 cki.parse_file(os.path.join(bionic_libc_root, "SECCOMP_WHITELIST.TXT")) 323 if args.l: 324 for syscall in cki.syscalls: 325 if args.arch in syscall: 326 print syscall["name"] 327 exit(0) 328 329 cki_cov = CKI_Coverage(args.arch) 330 331 cki_cov.load_ltp_tests() 332 cki_cov.load_ltp_disabled_tests() 333 if args.results: 334 cki_cov.parse_test_results(args.results) 335 cki_cov.match_syscalls_to_tests(cki.syscalls) 336 cki_cov.update_test_status() 337 338 beta_string = ("*** WARNING: This script is still in development and may\n" 339 "*** report both false positives and negatives.") 340 print beta_string 341 cki_cov.output_results() 342 print beta_string 343