mirror of
https://github.com/openharmony/third_party_ltp.git
synced 2026-07-01 12:25:45 -04:00
893ac31e33
Signed-off-by: ccdu <duchangcheng1@h-partners.com>
543 lines
16 KiB
Python
543 lines
16 KiB
Python
# Configuration file for the Sphinx documentation builder.
|
|
#
|
|
# For the full list of built-in configuration values, see the documentation:
|
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
|
|
|
import os
|
|
import re
|
|
import json
|
|
import socket
|
|
import urllib.request
|
|
import sphinx
|
|
|
|
# -- Project information -----------------------------------------------------
|
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
|
|
|
project = 'Linux Test Project'
|
|
copyright = '2024, Linux Test Project'
|
|
author = 'Linux Test Project'
|
|
release = '1.0'
|
|
ltp_repo = 'https://github.com/linux-test-project/ltp'
|
|
ltp_repo_base_url = f"{ltp_repo}/tree/master"
|
|
|
|
# -- General configuration ---------------------------------------------------
|
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
|
|
|
extensions = [
|
|
'linuxdoc.rstKernelDoc',
|
|
'sphinxcontrib.spelling',
|
|
'sphinx.ext.autosectionlabel',
|
|
'sphinx.ext.extlinks',
|
|
]
|
|
|
|
exclude_patterns = ["html*", '_static*', '.venv*']
|
|
extlinks = {
|
|
'repo': (f'{ltp_repo}/%s', '%s'),
|
|
'master': (f'{ltp_repo}/blob/master/%s', '%s'),
|
|
'git_man': ('https://git-scm.com/docs/git-%s', 'git %s'),
|
|
'man2': ('https://man7.org/linux/man-pages/man2/%s.2.html', '%s(2)'),
|
|
# TODO: allow 2nd parameter to show page description instead of plain URL
|
|
'kernel_doc': ('https://docs.kernel.org/%s.html', 'https://docs.kernel.org/%s.html'),
|
|
'kernel_tree': ('https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/%s', '%s'),
|
|
}
|
|
|
|
spelling_lang = "en_US"
|
|
spelling_warning = True
|
|
spelling_exclude_patterns = ['users/stats.rst']
|
|
spelling_word_list_filename = "spelling_wordlist"
|
|
|
|
# -- Options for HTML output -------------------------------------------------
|
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
|
|
|
html_theme = 'sphinx_rtd_theme'
|
|
html_static_path = ['_static']
|
|
|
|
|
|
def generate_syscalls_stats(_):
|
|
"""
|
|
Generate statistics for syscalls. We fetch the syscalls list from the kernel
|
|
sources, then we compare it with testcases/kernel/syscalls folder and
|
|
generate a file that is included in the statistics documentation section.
|
|
"""
|
|
output = '_static/syscalls.rst'
|
|
|
|
# sometimes checking testcases/kernel/syscalls file names are not enough,
|
|
# because in some cases (i.e. io_ring) syscalls are tested, but they are
|
|
# part of a more complex scenario. In the following list, we define syscalls
|
|
# which we know they are 100% tested already.
|
|
ltp_syscalls_path = "testcases/kernel/syscalls"
|
|
white_list = {
|
|
'bpf': f'{ltp_syscalls_path}/bpf',
|
|
'epoll_pwait2': f'{ltp_syscalls_path}/epoll_pwait',
|
|
'fadvise64': f'{ltp_syscalls_path}/fadvise',
|
|
'fanotify_init': f'{ltp_syscalls_path}/fanotify',
|
|
'fanotify_mark': f'{ltp_syscalls_path}/fanotify',
|
|
'file_getattr': f'{ltp_syscalls_path}/file_attr',
|
|
'file_setattr': f'{ltp_syscalls_path}/file_attr',
|
|
'futex': f'{ltp_syscalls_path}/futex',
|
|
'getdents64': f'{ltp_syscalls_path}/gettdents',
|
|
'inotify_add_watch': f'{ltp_syscalls_path}/inotify',
|
|
'inotify_init': f'{ltp_syscalls_path}/inotify',
|
|
'inotify_rm_watch': f'{ltp_syscalls_path}/inotify',
|
|
'io_uring_enter': f'{ltp_syscalls_path}/io_uring',
|
|
'io_uring_register': f'{ltp_syscalls_path}/io_uring',
|
|
'io_uring_setup': f'{ltp_syscalls_path}/io_uring',
|
|
'landlock_add_rule': f'{ltp_syscalls_path}/landlock',
|
|
'landlock_create_ruleset': f'{ltp_syscalls_path}/landlock',
|
|
'landlock_restrict_self': f'{ltp_syscalls_path}/landlock',
|
|
'lsetxattr': f'{ltp_syscalls_path}/lgetxattr',
|
|
'newfstatat': f'{ltp_syscalls_path}/fstatat',
|
|
'pkey_alloc': f'{ltp_syscalls_path}/pkeys',
|
|
'pkey_free': f'{ltp_syscalls_path}/pkeys',
|
|
'pkey_mprotect': f'{ltp_syscalls_path}/pkeys',
|
|
'prlimit64': f'{ltp_syscalls_path}/getrlimit',
|
|
'pread64': f'{ltp_syscalls_path}/pread',
|
|
'pselect6': f'{ltp_syscalls_path}/pselect',
|
|
'pwrite64': f'{ltp_syscalls_path}/pwrite',
|
|
'quotactl_fd': f'{ltp_syscalls_path}/quotactl',
|
|
'rt_sigpending': f'{ltp_syscalls_path}/sigpending',
|
|
'semtimedop': f'{ltp_syscalls_path}/ipc/semop',
|
|
'sethostname': f'{ltp_syscalls_path}/sethostname'
|
|
}
|
|
|
|
# populate with not implemented, reserved, unmaintained syscalls defined
|
|
# inside the syscalls file
|
|
black_list = [
|
|
'_newselect',
|
|
'_sysctl',
|
|
'afs_syscall',
|
|
'cachectl',
|
|
'create_module',
|
|
'get_kernel_syms',
|
|
'getmsg',
|
|
'getpmsg',
|
|
'mq_getsetattr',
|
|
'nfsservctl',
|
|
'putmsg',
|
|
'putpmsg',
|
|
'query_module',
|
|
'reserved177',
|
|
'reserved193',
|
|
'restart_syscall',
|
|
'rseq',
|
|
'sysmips',
|
|
'vserver',
|
|
]
|
|
|
|
# fetch syscalls file
|
|
syscalls_url = "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/arch/mips/kernel/syscalls"
|
|
error = False
|
|
try:
|
|
socket.setdefaulttimeout(3)
|
|
|
|
# kernel.org doesn't always allow to download syscalls file, so we need
|
|
# to emulate a different client in order to download it. Browser
|
|
# emulation might fail due to captcha request and for this reason we
|
|
# use the 'curl' command instead
|
|
req = urllib.request.Request(
|
|
f"{syscalls_url}/syscall_n64.tbl",
|
|
headers={'User-Agent': 'curl/8.6.0'})
|
|
|
|
with urllib.request.urlopen(req) as response:
|
|
with open("syscalls.tbl", 'wb') as out_file:
|
|
out_file.write(response.read())
|
|
except urllib.error.URLError as err:
|
|
error = True
|
|
logger = sphinx.util.logging.getLogger(__name__)
|
|
msg = f"Can't download syscall_n64.tbl from kernel sources ({err})"
|
|
logger.warning(msg)
|
|
|
|
with open(output, 'w+', encoding='utf-8') as stats:
|
|
stats.write(f".. warning::\n\n {msg}")
|
|
|
|
if error:
|
|
return
|
|
|
|
text = [
|
|
'Syscalls\n',
|
|
'--------\n\n',
|
|
]
|
|
|
|
# collect all available kernel syscalls
|
|
regexp = re.compile(r'\d+\s+n64\s+(?P<syscall>\w+)\s+\w+')
|
|
ker_syscalls = []
|
|
with open("syscalls.tbl", 'r', encoding='utf-8') as data:
|
|
for line in data:
|
|
match = regexp.search(line)
|
|
if not match:
|
|
continue
|
|
|
|
ker_syscalls.append(match.group('syscall'))
|
|
|
|
# collect all LTP tested syscalls
|
|
name_patterns = [
|
|
re.compile(r'(?P<name>[a-zA-Z_]+[^_])\d{2}\.c'),
|
|
re.compile(r'(?P<name>[a-zA-Z_]+[1-9])_\d{2}\.c'),
|
|
]
|
|
ltp_syscalls = {}
|
|
for dirpath, _, files in os.walk(f'../{ltp_syscalls_path}'):
|
|
for myfile in files:
|
|
match = None
|
|
for pattern in name_patterns:
|
|
match = pattern.search(myfile)
|
|
if match:
|
|
break
|
|
|
|
if not match:
|
|
continue
|
|
|
|
# we need to use relative path from the project root
|
|
path = dirpath.replace('../', '')
|
|
name = match.group('name')
|
|
|
|
ltp_syscalls[name] = f'{ltp_repo_base_url}/{path}'
|
|
|
|
# compare kernel syscalls with LTP tested syscalls
|
|
syscalls = {}
|
|
for kersc in ker_syscalls:
|
|
if kersc in black_list:
|
|
continue
|
|
|
|
if kersc not in syscalls:
|
|
if kersc in white_list:
|
|
syscalls[kersc] = f'{ltp_repo_base_url}/{white_list[kersc]}'
|
|
continue
|
|
|
|
syscalls[kersc] = None
|
|
|
|
for ltpsc, ltpsp in ltp_syscalls.items():
|
|
if ltpsc == kersc:
|
|
syscalls[kersc] = ltpsp
|
|
|
|
# generate the statistics file
|
|
tested_syscalls = [key for key, val in syscalls.items() if val is not None]
|
|
text.append('syscalls which are tested under '
|
|
':master:`testcases/kernel/syscalls`:\n\n')
|
|
text.append(f'* kernel syscalls: {len(ker_syscalls)}\n')
|
|
text.append(f'* tested syscalls: {len(tested_syscalls)}\n\n')
|
|
|
|
# create tested/untested syscalls table
|
|
index_tested = 0
|
|
table_tested = [
|
|
'Tested syscalls\n',
|
|
'~~~~~~~~~~~~~~~\n\n',
|
|
'.. list-table::\n',
|
|
' :header-rows: 0\n\n',
|
|
]
|
|
|
|
index_untest = 0
|
|
table_untest = [
|
|
'Untested syscalls\n',
|
|
'~~~~~~~~~~~~~~~~~\n\n',
|
|
'.. list-table::\n',
|
|
' :header-rows: 0\n\n',
|
|
]
|
|
|
|
max_columns = 3
|
|
|
|
for sysname, path in syscalls.items():
|
|
if path is not None:
|
|
if (index_tested % max_columns) == 0:
|
|
table_tested.append(f' * - `{sysname} <{path}>`_\n')
|
|
else:
|
|
table_tested.append(f' - `{sysname} <{path}>`_\n')
|
|
|
|
index_tested += 1
|
|
else:
|
|
if (index_untest % max_columns) == 0:
|
|
table_untest.append(f' * - {sysname}\n')
|
|
else:
|
|
table_untest.append(f' - {sysname}\n')
|
|
|
|
index_untest += 1
|
|
|
|
left = index_tested % max_columns
|
|
if left > 0:
|
|
for _ in range(0, max_columns - left):
|
|
table_tested.append(' -\n')
|
|
|
|
left = index_untest % max_columns
|
|
if left > 0:
|
|
for _ in range(0, max_columns - left):
|
|
table_untest.append(' -\n')
|
|
|
|
text.extend(table_tested)
|
|
text.append('\n')
|
|
text.extend(table_untest)
|
|
|
|
# write the file
|
|
with open(output, 'w+', encoding='utf-8') as stats:
|
|
stats.writelines(text)
|
|
|
|
|
|
def _generate_tags_table(tags):
|
|
"""
|
|
Generate the tags table from tags hash.
|
|
"""
|
|
supported_url_ref = {
|
|
"linux-git": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=",
|
|
"linux-stable-git": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=",
|
|
"glibc-git": "https://sourceware.org/git/?p=glibc.git;a=commit;h=",
|
|
"musl-git": "https://git.musl-libc.org/cgit/musl/commit/src/linux/clone.c?id=",
|
|
"CVE": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-",
|
|
}
|
|
|
|
table = [
|
|
'.. list-table::',
|
|
' :header-rows: 1',
|
|
'',
|
|
' * - Tag',
|
|
' - Info',
|
|
]
|
|
|
|
for tag in tags:
|
|
tag_key = tag[0]
|
|
tag_val = tag[1]
|
|
|
|
tag_url = supported_url_ref.get(tag_key, None)
|
|
if tag_url:
|
|
tag_val = f'`{tag_val} <{tag_url}{tag_val}>`_'
|
|
|
|
table.extend([
|
|
f' * - :c:struct:`{tag_key} <tst_tag>`',
|
|
f' - {tag_val}',
|
|
])
|
|
|
|
return table
|
|
|
|
|
|
def _generate_options_table(options):
|
|
"""
|
|
Generate the options table from the options hash.
|
|
"""
|
|
table = [
|
|
'.. list-table::',
|
|
' :header-rows: 1',
|
|
'',
|
|
' * - Option',
|
|
' - Description',
|
|
]
|
|
|
|
for opt in options:
|
|
if not isinstance(opt, list):
|
|
table.clear()
|
|
break
|
|
|
|
key = opt[0]
|
|
val = opt[2]
|
|
|
|
if key.endswith(':'):
|
|
key = key[:-1] if key.endswith(':') else key
|
|
|
|
key = f'-{key}'
|
|
|
|
table.extend([
|
|
f' * - {key}',
|
|
f' - {val}',
|
|
])
|
|
|
|
return table
|
|
|
|
|
|
def _generate_table_cell(key, values):
|
|
"""
|
|
Generate a cell which can be multiline if value is a list.
|
|
"""
|
|
cell = []
|
|
key = f' :c:struct:`{key} <tst_test>`'
|
|
|
|
if len(values) > 1:
|
|
cell.extend([
|
|
f' * - {key}',
|
|
f' - | {values[0]}',
|
|
])
|
|
|
|
for item in values[1:]:
|
|
cell.append(f' | {item}')
|
|
else:
|
|
cell.extend([
|
|
f' * - {key}',
|
|
f' - {values[0]}',
|
|
])
|
|
|
|
return cell
|
|
|
|
|
|
def _generate_setup_table(keys):
|
|
"""
|
|
Generate the table with test setup configuration.
|
|
"""
|
|
exclude = [
|
|
# following keys are already handled
|
|
'options',
|
|
'runtime',
|
|
'timeout',
|
|
'fname',
|
|
'doc',
|
|
# following keys don't need to be shown
|
|
'child_needs_reinit',
|
|
'needs_checkpoints',
|
|
'forks_child',
|
|
'tags',
|
|
]
|
|
my_keys = {k: v for k, v in keys.items() if k not in exclude}
|
|
if len(my_keys) == 0:
|
|
return []
|
|
|
|
table = [
|
|
'.. list-table::',
|
|
' :header-rows: 1',
|
|
'',
|
|
' * - Key',
|
|
' - Value',
|
|
]
|
|
|
|
values = []
|
|
|
|
for key, value in my_keys.items():
|
|
if key in exclude:
|
|
continue
|
|
|
|
values.clear()
|
|
|
|
if key == 'ulimit':
|
|
for item in value:
|
|
values.append(f'{item[0]} : {item[1]}')
|
|
elif key == 'hugepages':
|
|
if len(value) == 1:
|
|
values.append(f'{value[0]}')
|
|
else:
|
|
values.append(f'{value[0]}, {value[1]}')
|
|
elif key == 'filesystems':
|
|
for params in value:
|
|
for pkey, pval in params.items():
|
|
if pkey == "type":
|
|
values.append(f"- {pval}")
|
|
else:
|
|
values.append(f" {pkey}: {pval}")
|
|
elif key == "save_restore":
|
|
for item in value:
|
|
values.append(item[0])
|
|
else:
|
|
if isinstance(value, list):
|
|
values.extend(value)
|
|
else:
|
|
values.append(value)
|
|
|
|
if values:
|
|
table.extend(_generate_table_cell(key, values))
|
|
|
|
return table
|
|
|
|
|
|
def generate_test_catalog(_):
|
|
"""
|
|
Generate the test catalog from ltp.json metadata file.
|
|
"""
|
|
output = '_static/tests.rst'
|
|
metadata_file = '../metadata/ltp.json'
|
|
text = [
|
|
'.. warning::',
|
|
' The following catalog has been generated using LTP metadata',
|
|
' which is including only tests using the new :ref:`LTP C API`.',
|
|
''
|
|
]
|
|
|
|
metadata = None
|
|
with open(metadata_file, 'r', encoding='utf-8') as data:
|
|
metadata = json.load(data)
|
|
|
|
timeout_def = metadata['defaults']['timeout']
|
|
regexp = re.compile(r'^\[([A-Za-z][\w\W]+)\]')
|
|
|
|
for test_name, conf in sorted(metadata['tests'].items()):
|
|
text.extend([
|
|
f'{test_name}',
|
|
len(test_name) * '-'
|
|
])
|
|
|
|
# source url location
|
|
test_fname = conf.get('fname', None)
|
|
if test_fname:
|
|
text.extend([
|
|
'',
|
|
f"`source <{ltp_repo_base_url}/{test_fname}>`__",
|
|
''
|
|
])
|
|
|
|
# test description
|
|
desc = conf.get('doc', None)
|
|
if desc:
|
|
desc_text = []
|
|
for line in desc:
|
|
line = regexp.sub(r'**\1**', line)
|
|
desc_text.append(line)
|
|
|
|
text.extend([
|
|
'\n'.join(desc_text),
|
|
])
|
|
|
|
# timeout information
|
|
timeout = conf.get('timeout', None)
|
|
if timeout:
|
|
text.extend([
|
|
'',
|
|
f'Test timeout is {timeout} seconds.',
|
|
])
|
|
else:
|
|
text.extend([
|
|
'',
|
|
f'Test timeout defaults is {timeout_def} seconds.',
|
|
])
|
|
|
|
# runtime information
|
|
runtime = conf.get('runtime', None)
|
|
if runtime:
|
|
text.extend([
|
|
f'Maximum runtime is {runtime} seconds.',
|
|
''
|
|
])
|
|
else:
|
|
text.append('')
|
|
|
|
# options information
|
|
opts = conf.get('options', None)
|
|
if opts:
|
|
text.append('')
|
|
text.extend(_generate_options_table(opts))
|
|
text.append('')
|
|
|
|
# tags information
|
|
tags = conf.get('tags', None)
|
|
if tags:
|
|
text.append('')
|
|
text.extend(_generate_tags_table(tags))
|
|
text.append('')
|
|
|
|
# parse struct tst_test content
|
|
text.append('')
|
|
text.extend(_generate_setup_table(conf))
|
|
text.append('')
|
|
|
|
# small separator between tests
|
|
text.extend([
|
|
'',
|
|
'.. raw:: html',
|
|
'',
|
|
' <hr>',
|
|
'',
|
|
])
|
|
|
|
with open(output, 'w+', encoding='utf-8') as new_tests:
|
|
new_tests.write('\n'.join(text))
|
|
|
|
|
|
def setup(app):
|
|
"""
|
|
Setup the current documentation, using self generated data and graphics
|
|
customizations.
|
|
"""
|
|
app.add_css_file('custom.css')
|
|
app.connect('builder-inited', generate_syscalls_stats)
|
|
app.connect('builder-inited', generate_test_catalog)
|