Files
langkit/testsuite/testsuite.py
Pierre-Marie de Rodat 109fd493c7 Introduce separate data structure to hold the language spec config
Replace the messy collection of arguments for CompileCtx.__init__ and
CompileCtx.create_all_passes with better organized dataclasses in the
new langkit.config module. Update the codebase to use these data
structures.
2025-01-07 09:54:56 +00:00

322 lines
12 KiB
Python
Executable File

#! /usr/bin/env python
"""
Usage::
testsuite.py [OPTIONS]
Run the Langkit testsuite.
"""
import glob
import os
import shutil
import subprocess
import sys
from e3.testsuite import Testsuite
import drivers.adalog_driver
import drivers.langkit_support_driver
import drivers.lkt_build_and_run_driver
import drivers.lkt_compile_driver
import drivers.lkt_parse_driver
import drivers.lkt_toolbox_driver
import drivers.lkt_unparse_driver
import drivers.python_driver
def add_to_path(env_var, directory):
"""
Add the given directory to the `env_var` path.
"""
path = os.environ.get(env_var)
path = ('{}{}{}'.format(directory, os.path.pathsep, path)
if path else
env_var)
os.environ[env_var] = path
class LangkitTestsuite(Testsuite):
tests_subdir = 'tests'
test_driver_map = {
'adalog': drivers.adalog_driver.AdalogDriver,
'langkit_support': drivers.langkit_support_driver.LangkitSupportDriver,
'lkt': drivers.lkt_toolbox_driver.LktToolboxDriver,
'lkt_build_and_run': (
drivers.lkt_build_and_run_driver.LktBuildAndRunDriver
),
'lkt_compile': drivers.lkt_compile_driver.LktCompileDriver,
'lkt_parse': drivers.lkt_parse_driver.LktParseDriver,
'lkt_unparse': drivers.lkt_unparse_driver.LktUnparseDriver,
'python': drivers.python_driver.PythonDriver,
}
def add_options(self, parser):
parser.add_argument(
'--with-python',
help='If provided, use as the Python interpreter in testcases.'
)
parser.add_argument(
'--coverage', '-C', action='store_true',
help='Enable computation of code coverage for Langkit and'
' Langkit_Support. This requires coverage.py and'
' GNATcoverage.'
)
parser.add_argument(
'--valgrind', action='store_true',
help='Run tests with Valgrind to check memory issues.'
)
parser.add_argument(
'--no-auto-path', action='store_true',
help='Do not automatically add Langkit to PYTHONPATH. This is'
' useful to test that Langkit was properly installed in the'
' environment that runs the testsuite.'
)
parser.add_argument(
'--disable-ocaml', action='store_true',
help='Disable testcases that require OCaml (they are enabled by'
' default).'
)
parser.add_argument(
"--disable-java", action="store_true",
help='Disable testcases that require Java (they are enabled by'
' default).',
)
parser.add_argument(
'--disable-native-image', action="store_true",
help="Disable testcases that require native-image (they are"
" enabled by default)."
)
parser.add_argument(
'--maven-local-repo',
help='Specify the Maven repository to use, default one is the'
" user's repository (~/.m2)."
)
parser.add_argument(
'--maven-executable',
help='Specify the Maven executable to use. The default one is'
' "mvn".'
)
parser.add_argument(
'--disable-gdb', action='store_true',
help='Disable testcases that require GDB (they are enabled by'
' default).'
)
parser.add_argument(
'--restricted-env', action='store_true',
help='Skip testcases that cannot run in a restricted environment'
' (need for non-standard Python packages).'
)
#
# Convenience options for developpers
#
parser.add_argument(
'--disable-tear-up-builds', '-B', action='store_true',
help='Disable the automatic build of Langkit_Support during the'
' testsuite tear_up step. This is used to speed up successive'
' testsuite runs during development.'
)
parser.add_argument(
'--pretty-print', action='store_true',
help='Pretty-print generated source code.'
)
# Tests update
parser.add_argument(
'--rewrite', '-r', action='store_true',
help='Rewrite test baselines according to current output.'
)
def set_up(self):
super().set_up()
args = self.main.args
self.env.rewrite_baselines = args.rewrite
self.env.control_condition_env = {
'restricted_env': args.restricted_env,
'has_ocaml': not args.disable_ocaml,
'has_java': not args.disable_java,
'has_native_image': not args.disable_native_image,
'has_gdb': not args.disable_gdb,
'os': self.env.build.os.name,
'valgrind': args.valgrind,
}
if args.coverage:
# Create a directory that we'll use to:
#
# 1) collect coverage data for each testcase;
# 2) generate the HTML report.
self.env.coverage_dir = os.path.join(self.output_dir, 'coverage')
self.env.langkit_support_coverage_dir = os.path.join(
self.env.coverage_dir, 'langkit_support'
)
os.mkdir(self.env.coverage_dir)
os.mkdir(self.env.langkit_support_coverage_dir)
def run(name, args):
p = subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
encoding='utf-8',
)
if p.returncode != 0:
raise RuntimeError(
'{} build failed (GPRbuild returned {})\n{}'.format(
name, p.returncode, p.stdout
)
)
# Build Langkit_Support so that each testcase does not try to build it
# in parallel. To achieve this, we build+install it with all library
# kinds, and then update the environment so that testcases can assume
# it is installed.
if not args.disable_tear_up_builds:
langkit_root_dir = os.path.join(self.root_dir, '..')
self.env.langkit_support_project_file = os.path.join(
langkit_root_dir, 'langkit', 'support', 'langkit_support.gpr'
)
install_prefix = os.path.join(self.working_dir, 'install')
if os.path.exists(install_prefix):
shutil.rmtree(install_prefix)
gargs = ['-p', '-P', self.env.langkit_support_project_file]
cargs = ['-cargs', '-O0', '-g', '-gnatwae']
if args.coverage:
gargs.append('--subdirs=gnatcov')
cargs.extend(['-fdump-scos', '-fpreserve-control-flow'])
for build in ('static', 'relocatable'):
run(
f'Langkit support - build {build}',
['gprbuild'] + gargs +
['-XLIBRARY_TYPE={}'.format(build)] + cargs,
)
run(
f'Langkit support - install {build}',
['gprinstall',
'-P', self.env.langkit_support_project_file,
'-p', '--sources-subdir=include/langkit_support',
'-XLIBRARY_TYPE={}'.format(build),
'--prefix={}'.format(install_prefix),
'--build-var=LIBRARY_TYPE',
'--build-name={}'.format(build)],
)
# Make the installed library available to all testcases
add_to_path('PATH', os.path.join(install_prefix, 'bin'))
add_to_path('LD_LIBRARY_PATH', os.path.join(install_prefix, 'lib'))
add_to_path('GPR_PROJECT_PATH',
os.path.join(install_prefix, 'share', 'gpr'))
# On GNU/Linux, the OCaml and Java bindings need to have access
# to the sigsegv_handler shared object. Build it and add it to
# the environment.
if (
not args.disable_ocaml or not args.disable_java
) and self.env.build.os.name == "linux":
sigsegv_handler_dir = os.path.join(
langkit_root_dir, "sigsegv_handler"
)
sigsegv_handler_prj = os.path.join(
sigsegv_handler_dir, "langkit_sigsegv_handler.gpr"
)
run(
'Langkit_SIGSEGV_Handler',
["gprbuild", "-p", "-P", sigsegv_handler_prj],
)
add_to_path(
'LD_LIBRARY_PATH', os.path.join(sigsegv_handler_dir, 'lib')
)
# If Java is enabled and there is a Maven local repo, export it
if not args.disable_java:
if args.maven_executable:
os.environ['MAVEN_EXECUTABLE'] = args.maven_executable
if args.maven_local_repo:
os.environ['MAVEN_LOCAL_REPO'] = args.maven_local_repo
def adjust_dag_dependencies(self, dag):
# Crude assumption that should do the work.
#
# If there are more tests than testsuite jobs, assume running tests in
# parallel will keep all cores busy most of the time, so disable
# parallelism inside each tests. This is necessary to avoid exceeding
# our job limit in full testsuite runs.
#
# Otherwise, assume we are running only a handful of tests, in which
# case we want to run them as fast as possible (for dev convenience):
# forward testsuite parallelism inside each test.
self.env.inner_jobs = (
1
if len(dag.vertex_data) >= self.main.args.jobs else
self.main.args.jobs
)
def tear_down(self):
if self.main.args.coverage:
# Consolidate coverage data for each testcase and generate both a
# sumary textual report on the standard output and a detailed HTML
# report.
langkit_cov_files = glob.glob(os.path.join(self.env.coverage_dir,
'*.coverage'))
lksp_cov_files = glob.glob(os.path.join(self.env.coverage_dir,
'*.trace'))
if langkit_cov_files:
rcfile = os.path.join(self.root_dir, '..', 'coverage.ini')
for argv in (
['combine'] + langkit_cov_files,
['report'],
['html', '-d.', '--title=Langkit coverage report'],
):
if argv[0] != 'combine':
argv.append('--rcfile=' + rcfile)
subprocess.check_call(['coverage'] + argv,
cwd=self.env.coverage_dir)
html_index = os.path.join(self.env.coverage_dir, 'index.html')
assert os.path.exists(html_index)
print('Detailed HTML coverage report for Langkit available at:'
' {}'.format(html_index))
else:
print('No test exercised Langkit: no coverage report to'
' produce')
if lksp_cov_files:
subprocess.check_call([
'gnatcov', 'coverage',
'-P', self.env.langkit_support_project_file,
'--level=stmt+decision',
'--annotate=dhtml',
'--output-dir', self.env.langkit_support_coverage_dir,
'--subdirs=gnatcov',
] + lksp_cov_files)
print('HTML coverage report for Langkit_Support: {}'.format(
os.path.join(self.env.langkit_support_coverage_dir,
'index.html')
))
else:
print('No test exercised Langkit_Support: no coverage report'
' to produce')
super().tear_down()
if __name__ == '__main__':
sys.exit(LangkitTestsuite().testsuite_main())