icu/tools/scripts/uconfig_vars_test.py
Fredrik Roubert e1e1c5feaf ICU-22349 Use -l with make -j to limit jobs based on load average.
It has been proposed that make running too many parallel jobs recently
has led to resource exhaustion in our CI, so that some kind of limit
would be helpful to set.

The load average 2.5 limit choosen here is simply the limit used as an
example in the make documentation, as we don't really have any way of
picking an initial value that's certain to be better.

https://www.gnu.org/software/make/manual/html_node/Parallel.html

See #2421
See #2422
2023-05-11 00:50:59 +02:00

244 lines
9.3 KiB
Python
Executable file

# © 2021 and later: Unicode, Inc. and others.
# License & terms of use: http://www.unicode.org/copyright.html
"""Executes uconfig variations check.
See
http://site.icu-project.org/processes/release/tasks/healthy-code#TOC-Test-uconfig.h-variations
for more information.
"""
import getopt
import os
import re
import subprocess
import sys
excluded_unit_test_flags = ['UCONFIG_NO_CONVERSION', 'UCONFIG_NO_FILE_IO'];
def ReadFile(filename):
"""Reads a file and returns the content of the file
Args:
command: string with the filename.
Returns:
Content of file.
"""
with open(filename, 'r') as file_handle:
return file_handle.read()
def RunCmd(command):
"""Executes the command, returns output and exit code, writes output to log
Args:
command: string with the command.
Returns:
stdout and exit code of command execution.
"""
print(command)
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, close_fds=True)
stdout, _ = p.communicate()
return stdout, p.returncode
def ExtractUConfigNoXXX(uconfig_file):
"""Parses uconfig.h and returns a list of UCONFIG_NO_XXX labels.
Initializes test result structure.
Args:
uconfig_file: common/unicode/uconfig.h as string.
Returns:
List of all UCONFIG_NO_XXX flags found in uconfig.h, initialized test
result structure.
"""
uconfig_no_flags_all = []
test_results = {}
uconfig_no_regex = r'UCONFIG_NO_[A-Z_]*'
# Collect all distinct occurences of UCONFIG_NO_XXX matches in uconfig.h.
for uconfig_no_flag in re.finditer(uconfig_no_regex, uconfig_file):
if uconfig_no_flag.group(0) not in uconfig_no_flags_all:
uconfig_no_flags_all.append(uconfig_no_flag.group(0))
# All UCONFIG_NO_XXX flags found in uconfig.h come in form of a guarded
# definition. Verify the existence, report error if not found.
for uconfig_no_flag in uconfig_no_flags_all:
uconfig_no_def_regex = r'(?m)#ifndef %s\n#\s+define %s\s+0\n#endif$' % (
uconfig_no_flag, uconfig_no_flag)
uconfig_no_def_match = re.search(uconfig_no_def_regex, uconfig_file)
if not uconfig_no_def_match:
print('No definition for flag %s found!\n' % uconfig_no_flag)
sys.exit(1)
test_results = {f: {'unit_test': False, 'hdr_test': False, 'u_log_tail': '', 'h_log_tail': ''}
for f in uconfig_no_flags_all}
test_results['all_flags'] = {'unit_test': False, 'hdr_test' : False, 'u_log_tail': '',
'h_log_tail': ''}
return uconfig_no_flags_all, test_results
def BuildAllFlags(uconfig_no_list):
"""Builds sequence of -Dflag=1 with each flag from the list."""
flag_list = ['-D' + uconfig_no + '=1' for uconfig_no in uconfig_no_list]
return ' '.join(flag_list)
def RunUnitTests(uconfig_no_list, test_results):
"""Iterates over all flags, sets each individually during ICU configuration
and executes the ICU4C unit tests.
Args:
uconfig_no_list: list of all UCONFIG_NO_XXX flags to test with.
test_results: dictionary to record test run results.
Returns:
test_results: updated test result entries.
"""
for uconfig_no in uconfig_no_list:
conf_log, exit_code = RunCmd(
'./runConfigureICU Linux CPPFLAGS=\"-D%s=1"' % uconfig_no)
if exit_code != 0:
print('ICU4C configuration for flag %s failed' % uconfig_no)
print('Last 25 lines:\n%s\n' % '\n'.join(conf_log.decode('utf-8').splitlines()[-25:]))
sys.exit(1)
print('Running unit tests with %s set to 1.' % uconfig_no)
run_log, exit_code = RunCmd('make -j -l2.5 check')
test_results[uconfig_no]['unit_test'] = (exit_code == 0)
test_results[uconfig_no]['u_log_tail'] = \
'\n'.join(run_log.decode('utf-8').splitlines()[-50:])
RunCmd('make clean')
# Configure ICU with all UCONFIG_NO_XXX flags set to 1 and execute
# the ICU4C unit tests.
all_unit_test_config_no = BuildAllFlags(uconfig_no_list)
conf_log, exit_code = RunCmd(
'CPPFLAGS=\"%s\" ./runConfigureICU Linux' % all_unit_test_config_no)
if exit_code != 0:
print('ICU configuration with all flags set failed')
print('Last 25 lines:\n%s\n' % '\n'.join(conf_log.decode('utf-8').splitlines()[-25:]))
sys.exit(1)
print('Running unit tests with all flags set to 1.')
run_log, exit_code = RunCmd('make -j -l2.5 check')
test_results['all_flags']['unit_test'] = (exit_code == 0)
test_results['all_flags']['u_log_tail'] = \
'\n'.join(run_log.decode('utf-8').splitlines()[-50:])
RunCmd('make clean')
return test_results
def RunHeaderTests(uconfig_no_list, test_results):
"""Iterates over all flags and executes the header test.
Args:
uconfig_no_list: list of all UCONFIG_NO_XXX flags to test with.
test_results: dictionary to record test run results.
Returns:
test_results: updated test result entries.
"""
# Header tests needs different setup.
RunCmd('mkdir /tmp/icu_cnfg')
conf_log, exit_code = RunCmd('./runConfigureICU Linux --prefix=/tmp/icu_cnfg')
if exit_code != 0:
print('ICU4C configuration for header test failed!')
print('Last 25 lines:\n%s\n' % '\n'.join(conf_log.decode('utf-8').splitlines()[-25:]))
sys.exit(1)
inst_log, exit_code = RunCmd('make -j -l2.5 install')
if exit_code != 0:
print('make install failed!')
print('Last 25 lines:\n%s\n' % '\n'.join(inst_log.decode('utf-8').splitlines()[-25:]))
sys.exit(1)
for uconfig_no in uconfig_no_list:
print('Running header tests with %s set to 1.' % uconfig_no)
run_log, exit_code = RunCmd(
'PATH=/tmp/icu_cnfg/bin:$PATH make -C test/hdrtst UCONFIG_NO=\"-D%s=1\" check' % uconfig_no)
test_results[uconfig_no]['hdr_test'] = (exit_code == 0)
test_results[uconfig_no]['h_log_tail'] = \
'\n'.join(run_log.decode('utf-8').splitlines()[-50:])
all_hdr_test_flags = BuildAllFlags(uconfig_no_list)
print('Running header tests with all flags set to 1.')
run_log, exit_code = RunCmd(
'PATH=/tmp/icu_cnfg/bin:$PATH make -C test/hdrtst UCONFIG_NO=\"%s\" check' % all_hdr_test_flags)
test_results['all_flags']['hdr_test'] = (exit_code == 0)
test_results['all_flags']['h_log_tail'] = \
'\n'.join(run_log.decode('utf-8').splitlines()[-50:])
return test_results
def main():
# Read the options and determine what to run.
run_hdr = False
run_unit = False
optlist, _ = getopt.getopt(sys.argv[1:], "pu")
for o, _ in optlist:
if o == "-p":
run_hdr = True
elif o == "-u":
run_unit = True
os.chdir('icu4c/source')
orig_uconfig_file = ReadFile('common/unicode/uconfig.h')
all_uconfig_no_flags, test_results = ExtractUConfigNoXXX(orig_uconfig_file)
if not all_uconfig_no_flags:
print('No UCONFIG_NO_XXX flags found!\n')
sys.exit(1)
if run_unit:
RunUnitTests(
[u for u in all_uconfig_no_flags if u not in excluded_unit_test_flags],
test_results)
if run_hdr:
RunHeaderTests(all_uconfig_no_flags, test_results)
# Review test results and report any failures.
# 'outcome' will be returned by sys.exit(); 0 indicates success, any
# other value indicates failure.
outcome = 0
print('Summary:\n')
for uconfig_no in all_uconfig_no_flags:
if run_unit and (uconfig_no not in excluded_unit_test_flags):
if not test_results[uconfig_no]['unit_test']:
outcome = 1
print('\n============================================================\n')
print('%s: unit tests fail' % uconfig_no)
print('Last 50 lines from log file:\n%s\n' % test_results[uconfig_no]['u_log_tail'])
print('\n============================================================\n')
if run_hdr and not test_results[uconfig_no]['hdr_test']:
outcome = 1
print('\n============================================================\n')
print('%s: header tests fails' % uconfig_no)
print('Last 50 lines from log file:\n%s\n' % test_results[uconfig_no]['h_log_tail'])
print('\n============================================================\n')
if run_unit and not test_results['all_flags']['unit_test']:
outcome = 1
print('\n============================================================\n')
print('all flags to 1: unit tests fail!')
print('Last 50 lines from log file:\n%s\n' % test_results['all_flags']['u_log_tail'])
print('\n============================================================\n')
if run_hdr and not test_results['all_flags']['hdr_test']:
outcome = 1
print('\n============================================================\n')
print('all flags to 1: header tests fail!')
print('Last 50 lines from log file: %s\n ' % test_results['all_flags']['h_log_tail'])
print('\n============================================================\n')
if outcome == 0:
print('Tests pass for all uconfig variations!')
sys.exit(outcome)
if __name__ == '__main__':
main()