[tests] Port check scripts to python

This commit is contained in:
Ebrahim Byagowi 2020-05-29 12:34:30 +04:30
parent 5eb6cafd2e
commit 7250adee26
16 changed files with 294 additions and 310 deletions

View File

@ -424,18 +424,18 @@ test_bimap_CPPFLAGS = $(COMPILED_TESTS_CPPFLAGS)
test_bimap_LDADD = $(COMPILED_TESTS_LDADD)
dist_check_SCRIPTS = \
check-c-linkage-decls.sh \
check-externs.sh \
check-header-guards.sh \
check-includes.sh \
check-static-inits.sh \
check-symbols.sh \
check-c-linkage-decls.py \
check-externs.py \
check-header-guards.py \
check-includes.py \
check-static-inits.py \
check-symbols.py \
$(NULL)
TESTS += $(dist_check_SCRIPTS)
if !WITH_LIBSTDCXX
dist_check_SCRIPTS += \
check-libstdc++.sh \
check-libstdc++.py \
$(NULL)
endif

26
src/check-c-linkage-decls.py Executable file
View File

@ -0,0 +1,26 @@
#!/usr/bin/env python3
import sys, os
os.chdir (os.environ.get ('srcdir', os.path.dirname (__file__)))
HBHEADERS = os.environ.get ('HBHEADERS', '').split () or \
[x for x in os.listdir ('.') if x.startswith ('hb') and x.endswith ('.h')]
HBSOURCES = os.environ.get ('HBSOURCES', '').split () or \
[x for x in os.listdir ('.') if x.startswith ('hb') and x.endswith (('.cc', '.hh'))]
stat = 0
for x in HBHEADERS:
with open (x, 'r', encoding='utf-8') as f: content = f.read ()
if ('HB_BEGIN_DECLS' not in content) or ('HB_END_DECLS' not in content):
print ('Ouch, file %s does not have HB_BEGIN_DECLS / HB_END_DECLS, but it should' % x)
stat = 1
for x in HBSOURCES:
with open (x, 'r', encoding='utf-8') as f: content = f.read ()
if ('HB_BEGIN_DECLS' in content) or ('HB_END_DECLS' in content):
print ('Ouch, file %s has HB_BEGIN_DECLS / HB_END_DECLS, but it shouldn\'t' % x)
stat = 1
sys.exit (stat)

View File

@ -1,27 +0,0 @@
#!/bin/sh
LC_ALL=C
export LC_ALL
test -z "$srcdir" && srcdir=.
stat=0
test "x$HBHEADERS" = x && HBHEADERS=`cd "$srcdir"; find . -maxdepth 1 -name 'hb*.h'`
test "x$HBSOURCES" = x && HBSOURCES=`cd "$srcdir"; find . -maxdepth 1 -name 'hb*.cc'`
for x in $HBHEADERS; do
test -f "$srcdir/$x" -a ! -f "$x" && x="$srcdir/$x"
if ! grep -q HB_BEGIN_DECLS "$x" || ! grep -q HB_END_DECLS "$x"; then
echo "Ouch, file $x does not have HB_BEGIN_DECLS / HB_END_DECLS, but it should"
stat=1
fi
done
for x in $HBSOURCES; do
test -f "$srcdir/$x" -a ! -f "$x" && x="$srcdir/$x"
if grep -q HB_BEGIN_DECLS "$x" || grep -q HB_END_DECLS "$x"; then
echo "Ouch, file $x has HB_BEGIN_DECLS / HB_END_DECLS, but it shouldn't"
stat=1
fi
done
exit $stat

20
src/check-externs.py Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
import sys, os, re
os.chdir (os.environ.get ('srcdir', os.path.dirname (__file__)))
HBHEADERS = os.environ.get ('HBHEADERS', '').split () or \
[x for x in os.listdir ('.') if x.startswith ('hb') and x.endswith ('.h')]
stat = 0
print ('Checking that all public symbols are exported with HB_EXTERN')
for x in HBHEADERS:
with open (x, 'r', encoding='utf-8') as f: content = f.read ()
for s in re.findall (r'\n.+\nhb_.+\n', content):
if not s.startswith ('\nHB_EXTERN '):
print ('failure on:', s)
stat = 1
sys.exit (stat)

View File

@ -1,22 +0,0 @@
#!/bin/sh
LC_ALL=C
export LC_ALL
test -z "$srcdir" && srcdir=.
stat=0
test "x$HBHEADERS" = x && HBHEADERS=`cd "$srcdir"; find . -maxdepth 1 -name 'hb*.h'`
test "x$EGREP" = x && EGREP='grep -E'
echo 'Checking that all public symbols are exported with HB_EXTERN'
for x in $HBHEADERS; do
test -f "$srcdir/$x" -a ! -f "$x" && x="$srcdir/$x"
$EGREP -B1 -n '^hb_' /dev/null "$x" |
$EGREP -v '(^--|:hb_|-HB_EXTERN )' -A1
done |
grep . >&2 && stat=1
exit $stat

22
src/check-header-guards.py Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python3
import sys, os, re
os.chdir (os.environ.get ('srcdir', os.path.dirname (__file__)))
HBHEADERS = os.environ.get ('HBHEADERS', '').split () or \
[x for x in os.listdir ('.') if x.startswith ('hb') and x.endswith ('.h')]
HBSOURCES = os.environ.get ('HBSOURCES', '').split () or \
[x for x in os.listdir ('.') if x.startswith ('hb') and x.endswith (('.cc', '.hh'))]
stat = 0
for x in HBHEADERS + HBSOURCES:
if not x.endswith ('h') or x == 'hb-gobject-structs.h': continue
tag = x.upper ().replace ('.', '_').replace ('-', '_')
with open (x, 'r', encoding='utf-8') as f: content = f.read ()
if len (re.findall (tag + r'\b', content)) != 3:
print ('Ouch, header file %s does not have correct preprocessor guards' % x)
stat = 1
sys.exit (stat)

View File

@ -1,24 +0,0 @@
#!/bin/sh
LC_ALL=C
export LC_ALL
test -z "$srcdir" && srcdir=.
stat=0
test "x$HBHEADERS" = x && HBHEADERS=`cd "$srcdir"; find . -maxdepth 1 -name 'hb*.h' ! -name 'hb-gobject-structs.h'`
test "x$HBSOURCES" = x && HBSOURCES=`cd "$srcdir"; find . -maxdepth 1 -name 'hb-*.cc' -or -name 'hb-*.hh'`
for x in $HBHEADERS $HBSOURCES; do
test -f "$srcdir/$x" -a ! -f "$x" && x="$srcdir/$x"
echo "$x" | grep -q '[^h]$' && continue;
xx=`echo "$x" | sed 's@.*/@@'`
tag=`echo "$xx" | tr 'a-z.-' 'A-Z_'`
lines=`grep -w "$tag" "$x" | wc -l | sed 's/[ ]*//g'`
if test "x$lines" != x3; then
echo "Ouch, header file $x does not have correct preprocessor guards"
stat=1
fi
done
exit $stat

39
src/check-includes.py Executable file
View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3
import sys, os, re
os.chdir (os.environ.get ('srcdir', os.path.dirname (__file__)))
HBHEADERS = os.environ.get ('HBHEADERS', '').split () or \
[x for x in os.listdir ('.') if x.startswith ('hb') and x.endswith ('.h')]
HBSOURCES = os.environ.get ('HBSOURCES', '').split () or \
[x for x in os.listdir ('.') if x.startswith ('hb') and x.endswith (('.cc', '.hh'))]
stat = 0
print ('Checking that public header files #include "hb-common.h" or "hb.h" first (or none)')
for x in HBHEADERS:
if x == 'hb.h' or x == 'hb-common.h': continue
with open (x, 'r', encoding='utf-8') as f: content = f.read ()
first = re.findall (r'#.*include.*', content)[0]
if first not in ['#include "hb.h"', '#include "hb-common.h"']:
print ('failure on %s' % x)
stat = 1
print ('Checking that source files #include a private header first (or none)')
for x in HBSOURCES:
with open (x, 'r', encoding='utf-8') as f: content = f.read ()
includes = re.findall (r'#.*include.*', content)
if includes:
if not len (re.findall (r'"hb.*\.hh"', includes[0])):
print ('failure on %s' % x)
stat = 1
print ('Checking that there is no #include <hb-*.h>')
for x in HBHEADERS + HBSOURCES:
with open (x, 'r', encoding='utf-8') as f: content = f.read ()
if re.findall ('#.*include.*<.*hb', content):
print ('failure on %s' % x)
stat = 1
sys.exit (stat)

View File

@ -1,44 +0,0 @@
#!/bin/sh
LC_ALL=C
export LC_ALL
test -z "$srcdir" && srcdir=.
stat=0
test "x$HBHEADERS" = x && HBHEADERS=`cd "$srcdir"; find . -maxdepth 1 -name 'hb*.h'`
test "x$HBSOURCES" = x && HBSOURCES=`cd "$srcdir"; find . -maxdepth 1 -name 'hb-*.cc' -or -name 'hb-*.hh'`
echo 'Checking that public header files #include "hb-common.h" or "hb.h" first (or none)'
for x in $HBHEADERS; do
test -f "$srcdir/$x" -a ! -f "$x" && x="$srcdir/$x"
grep '#.*\<include\>' "$x" /dev/null | head -n 1
done |
grep -v '"hb-common[.]h"' |
grep -v '"hb[.]h"' |
grep -v 'hb-common[.]h:' |
grep -v 'hb[.]h:' |
grep . >&2 && stat=1
echo 'Checking that source files #include a private header first (or none)'
for x in $HBSOURCES; do
test -f "$srcdir/$x" -a ! -f "$x" && x="$srcdir/$x"
grep '#.*\<include\>' "$x" /dev/null | head -n 1
done |
grep -v '"hb-.*[.]hh"' |
grep -v 'hb[.]hh' |
grep . >&2 && stat=1
echo 'Checking that there is no #include <hb-*.h>'
for x in $HBHEADERS $HBSOURCES; do
test -f "$srcdir/$x" && x="$srcdir/$x"
grep '#.*\<include\>.*<.*hb' "$x" /dev/null >&2 && stat=1
done
exit $stat

41
src/check-libstdc++.py Executable file
View File

@ -0,0 +1,41 @@
#!/usr/bin/env python3
import sys, os, shutil, subprocess
os.chdir (os.environ.get ('srcdir', os.path.dirname (__file__)))
libs = os.environ.get ('libs', '.libs')
ldd = shutil.which ('ldd')
if ldd:
ldd = [ldd]
else:
ldd = shutil.which ('otool')
if ldd:
ldd = [ldd, '-L'] # otool -L
else:
print ('check-libstdc++.py: \'ldd\' not found; skipping test')
sys.exit (77)
stat = 0
tested = False
# harfbuzz-icu links to libstdc++ because icu does.
for soname in ['harfbuzz', 'harfbuzz-subset', 'harfbuzz-gobject']:
for suffix in ['so', 'dylib']:
so = os.path.join (libs, 'lib%s.%s' % (soname, suffix))
if not os.path.exists (so): continue
print ('Checking that we are not linking to libstdc++ or libc++ in %s' % so)
ldd_result = subprocess.check_output (ldd + [so])
if (b'libstdc++' in ldd_result) or (b'libc++' in ldd_result):
print ('Ouch, %s is linked to libstdc++ or libc++' % so)
stat = 1
tested = True
if not tested:
print ('check-libstdc++.py: libharfbuzz shared library not found; skipping test')
sys.exit (77)
sys.exit (stat)

View File

@ -1,43 +0,0 @@
#!/bin/sh
LC_ALL=C
export LC_ALL
test -z "$srcdir" && srcdir=.
test -z "$libs" && libs=.libs
stat=0
if which ldd 2>/dev/null >/dev/null; then
LDD=ldd
else
# macOS specific tool
if which otool 2>/dev/null >/dev/null; then
LDD="otool -L"
else
echo "check-libstdc++.sh: 'ldd' not found; skipping test"
exit 77
fi
fi
tested=false
# harfbuzz-icu links to libstdc++ because icu does.
for soname in harfbuzz harfbuzz-subset harfbuzz-gobject; do
for suffix in so dylib; do
so=$libs/lib$soname.$suffix
if ! test -f "$so"; then continue; fi
echo "Checking that we are not linking to libstdc++ or libc++ in $so"
if $LDD $so | grep 'libstdc[+][+]\|libc[+][+]'; then
echo "Ouch, linked to libstdc++ or libc++"
stat=1
fi
tested=true
done
done
if ! $tested; then
echo "check-libstdc++.sh: libharfbuzz shared library not found; skipping test"
exit 77
fi
exit $stat

38
src/check-static-inits.py Executable file
View File

@ -0,0 +1,38 @@
#!/usr/bin/env python3
import sys, os, shutil, subprocess, glob, re
builddir = os.environ.get ('builddir', os.path.dirname (__file__))
libs = os.environ.get ('libs', '.libs')
objdump = shutil.which ('objdump')
if not objdump:
print ('check-static-inits.py: \'ldd\' not found; skipping test')
sys.exit (77)
if sys.version_info < (3, 5):
print ('check-static-inits.py: needs python 3.5 for recursive support in glob')
sys.exit (77)
OBJS = glob.glob (os.path.join (builddir, libs, '**', '*.o'), recursive=True)
if not OBJS:
print ('check-static-inits.py: object files not found; skipping test')
sys.exit (77)
stat = 0
for obj in OBJS:
result = subprocess.check_output ([objdump, '-t', obj]).decode ('utf-8')
# Checking that no object file has static initializers
for l in re.findall (r'^.*\.[cd]tors.*$', result, re.MULTILINE):
if not re.match (r'.*\b0+\b', l):
print ('Ouch, %s has static initializers/finalizers' % obj)
stat = 1
# Checking that no object file has lazy static C++ constructors/destructors or other such stuff
if ('__cxa_' in result) and ('__ubsan_handle' not in result):
print ('Ouch, %s has lazy static C++ constructors/destructors or other such stuff' % obj)
stat = 1
sys.exit (stat)

View File

@ -1,49 +0,0 @@
#!/bin/sh
LC_ALL=C
export LC_ALL
test -z "$srcdir" && srcdir=.
test -z "$builddir" && builddir=.
stat=0
if which objdump 2>/dev/null >/dev/null; then
:
else
echo "check-static-inits.sh: 'objdump' not found; skipping test"
exit 77
fi
OBJS=$(find $builddir/ -name '*.o')
if test "x`echo $OBJS`" = "x$OBJS" 2>/dev/null >/dev/null; then
echo "check-static-inits.sh: object files not found; skipping test"
exit 77
fi
tested=false
echo "Checking that no object file has static initializers"
for obj in $OBJS; do
if objdump -t "$obj" | grep '[.][cd]tors' | grep -v '\<00*\>'; then
echo "Ouch, $obj has static initializers/finalizers"
stat=1
fi
tested=true
done
echo "Checking that no object file has lazy static C++ constructors/destructors or other such stuff"
for obj in $OBJS; do
if objdump -t "$obj" | grep -q '__cxa_' && ! objdump -t "$obj" | grep -q __ubsan_handle; then
objdump -t "$obj" | grep '__cxa_'
echo "Ouch, $obj has lazy static C++ constructors/destructors or other such stuff"
stat=1
fi
tested=true
done
if ! $tested; then
echo "check-static-inits.sh: no objects found; skipping test"
exit 77
fi
exit $stat

73
src/check-symbols.py Executable file
View File

@ -0,0 +1,73 @@
#!/usr/bin/env python3
import sys, os, shutil, subprocess, re, difflib
os.environ['LC_ALL'] = 'C' # otherwise 'nm' prints in wrong order
builddir = os.environ.get ('builddir', os.path.dirname (__file__))
libs = os.environ.get ('libs', '.libs')
IGNORED_SYMBOLS = '|'.join(['_fini', '_init', '_fdata', '_ftext', '_fbss',
'__bss_start', '__bss_start__', '__bss_end__', '_edata', '_end', '_bss_end__',
'__end__', '__gcov_.*', 'llvm_.*', 'flush_fn_list', 'writeout_fn_list'])
nm = shutil.which ('nm')
if not nm:
print ('check-symbols.py: \'nm\' not found; skipping test')
sys.exit (77)
cxxflit = shutil.which ('c++filt')
tested = False
stat = 0
for soname in ['harfbuzz', 'harfbuzz-subset', 'harfbuzz-icu', 'harfbuzz-gobject']:
for suffix in ['so', 'dylib']:
so = os.path.join (builddir, libs, 'lib%s.%s' % (soname, suffix))
if not os.path.exists (so): continue
# On macOS, C symbols are prefixed with _
symprefix = '_' if suffix == 'dylib' else ''
EXPORTED_SYMBOLS = [s.split ()[2]
for s in re.findall (r'^.+ [BCDGIRST] .+$', subprocess.check_output ([nm, so]).decode ('utf-8'), re.MULTILINE)
if not re.match (r'.* %s(%s)\b' % (symprefix, IGNORED_SYMBOLS), s)]
# run again c++flit also if is available
if cxxflit:
EXPORTED_SYMBOLS = subprocess.check_output (
[cxxflit], input='\n'.join (EXPORTED_SYMBOLS).encode ()
).decode ('utf-8').splitlines ()
prefix = (symprefix + os.path.basename (so)).replace ('libharfbuzz', 'hb').replace ('-', '_').split ('.')[0]
print ('Checking that %s does not expose internal symbols' % so)
suspicious_symbols = [x for x in EXPORTED_SYMBOLS if not re.match (r'^%s(_|$)' % prefix, x)]
if suspicious_symbols:
print ('Ouch, internal symbols exposed:', suspicious_symbols)
stat = 1
def_path = os.path.join (builddir, soname + '.def')
if not os.path.exists (def_path):
print ('\'%s\' not found; skipping' % def_path)
else:
print ('Checking that %s has the same symbol list as %s' % (so, def_path))
with open (def_path, 'r', encoding='utf-8') as f: def_file = f.read ()
diff_result = list (difflib.context_diff (
def_file.splitlines (),
['EXPORTS'] + [re.sub ('^%shb' % symprefix, 'hb', x) for x in EXPORTED_SYMBOLS] +
# cheat: copy the last line from the def file!
[def_file.splitlines ()[-1]]
))
if diff_result:
print ('\n'.join (diff_result))
stat = 1
tested = True
if not tested:
print ('check-symbols.sh: no shared libraries found; skipping test')
sys.exit (77)
sys.exit (stat)

View File

@ -1,61 +0,0 @@
#!/bin/sh
LC_ALL=C
export LC_ALL
test -z "$srcdir" && srcdir=.
test -z "$builddir" && builddir=.
test -z "$libs" && libs=.libs
stat=0
IGNORED_SYMBOLS='_fini\|_init\|_fdata\|_ftext\|_fbss\|__bss_start\|__bss_start__\|__bss_end__\|_edata\|_end\|_bss_end__\|__end__\|__gcov_.*\|llvm_.*\|flush_fn_list\|writeout_fn_list'
if which nm 2>/dev/null >/dev/null; then
:
else
echo "check-symbols.sh: 'nm' not found; skipping test"
exit 77
fi
tested=false
for soname in harfbuzz harfbuzz-subset harfbuzz-icu harfbuzz-gobject; do
for suffix in so dylib; do
so=$libs/lib$soname.$suffix
if ! test -f "$so"; then continue; fi
# On macOS, C symbols are prefixed with _
symprefix=
if test $suffix = dylib; then symprefix=_; fi
EXPORTED_SYMBOLS=`nm "$so" | grep ' [BCDGIRST] .' | grep -v " $symprefix\\($IGNORED_SYMBOLS\\>\\)" | cut -d' ' -f3 | c++filt`
prefix=$symprefix`basename "$so" | sed 's/libharfbuzz/hb/; s/-/_/g; s/[.].*//'`
echo "Checking that $so does not expose internal symbols"
if echo "$EXPORTED_SYMBOLS" | grep -v "^${prefix}\(_\|$\)"; then
echo "Ouch, internal symbols exposed"
stat=1
fi
def=$builddir/$soname.def
if ! test -f "$def"; then
echo "'$def' not found; skipping"
else
echo "Checking that $so has the same symbol list as $def"
{
echo EXPORTS
echo "$EXPORTED_SYMBOLS" | sed -e "s/^${symprefix}hb/hb/g"
# cheat: copy the last line from the def file!
tail -n1 "$def"
} | c++filt | diff "$def" - >&2 || stat=1
tested=true
fi
done
done
if ! $tested; then
echo "check-symbols.sh: no shared libraries found; skipping test"
exit 77
fi
exit $stat

View File

@ -494,35 +494,6 @@ if get_option('tests').enabled()
install: false,
))
endforeach
if host_machine.system() != 'windows' and not meson.is_cross_build()
# Some of them should be ported to python
dist_check_script = [
'check-c-linkage-decls.sh',
'check-externs.sh',
'check-header-guards.sh',
'check-static-inits.sh',
]
if not get_option('amalgam')
dist_check_script += 'check-includes.sh'
endif
if false and not get_option('with_libstdcxx')
# enable this once https://github.com/mesonbuild/meson/pull/6838 hits a release
# and make that version (i.e. 0.55) our minimum build requirement
dist_check_script += 'check-libstdc++.sh' # See https://github.com/harfbuzz/harfbuzz/issues/2276
endif
env = environment()
env.set('srcdir', meson.current_source_dir())
env.set('builddir', meson.current_build_dir())
env.set('libs', meson.current_build_dir()) # TODO: Merge this with builddir after autotools removal
env.set('HBSOURCES', ' '.join(hb_sources))
env.set('HBHEADERS', ' '.join(hb_headers))
foreach name : dist_check_script
test(name, find_program(name), env: env)
endforeach
endif
endif
pkgmod.generate(libharfbuzz,
@ -689,10 +660,34 @@ else
libharfbuzz_gobject_dep = dependency('', required: false)
endif
if get_option('tests').enabled() and host_machine.system() != 'windows' and not meson.is_cross_build()
test('check-symbols.sh', find_program('check-symbols.sh'),
depends: defs_list,
env: env)
if get_option('tests').enabled()
dist_check_script = [
'check-c-linkage-decls',
'check-externs',
'check-header-guards',
'check-includes',
]
env = environment()
env.set('srcdir', meson.current_source_dir())
env.set('builddir', meson.current_build_dir())
env.set('libs', meson.current_build_dir()) # TODO: Merge this with builddir after autotools removal
if not get_option('amalgam')
env.set('HBSOURCES', ' '.join(hb_sources))
endif
env.set('HBHEADERS', ' '.join(hb_headers))
if cpp.get_id() != 'msvc' and not meson.is_cross_build() # ensure the local tools are usable
# See https://github.com/mesonbuild/meson/pull/6838
if meson.version().version_compare('>=0.54.999') and not get_option('with_libstdcxx')
dist_check_script += 'check-libstdc++'
endif
dist_check_script += ['check-static-inits', 'check-symbols']
endif
foreach name : dist_check_script
test(name, find_program(name + '.py'), env: env, depends: defs_list)
endforeach
endif
install_headers(hb_headers + hb_subset_headers, subdir: meson.project_name())