mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-12 08:24:08 +00:00
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
244 lines
9.3 KiB
Python
Executable file
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()
|