• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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