1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
#! /usr/bin/env python
import os
import sys
import stat
import re
import errno
FILE_NOT_THERE=1
FILE_REGULAR=2
FILE_DIRECTORY=3
FILE_SYMLINK=4
## return tuple of (kind, mtime, size), or (FILE_NOT_THERE,0,0) if file not found
def stat_file(filename):
try:
s = os.stat(filename)
kind = 0
if stat.S_ISDIR(s.st_mode):
kind = FILE_DIRECTORY
elif stat.S_ISLNK(s.st_mode):
kind = FILE_SYMLINK
elif stat.S_ISREG(s.st_mode):
kind = FILE_REGULAR
else:
raise Exception("Unknown file type (not a directory, regular, or link) %s" % filename)
return (kind, s.st_mtime, s.st_size)
except OSError, e:
if e.errno == errno.ENOENT:
return (FILE_NOT_THERE, 0, 0)
else:
raise
test_func_re = re.compile('gjstest_test_func_([a-zA-Z0-9_]+)')
def find_tests(base_filename, full_filename):
f = open(full_filename)
test_funcs = [] ## list of (path, funcname)
found_expected_test_func = False
in_tests = False
for l in f.readlines():
if 'GJS_BUILD_TESTS' in l:
if '#if' in l:
if in_tests:
raise Exception('GJS_BUILD_TESTS nested inside itself? ' + base_filename)
else:
in_tests = True
elif '#endif' in l:
# GJS_BUILD_TESTS should have been in a comment post-endif
if not in_tests:
raise Exception('#endif /* GJS_BUILD_TESTS */ found but no #if in ' + base_filename)
else:
in_tests = False
## do the substring check before re match so we can plow
## through the file quickly and only do the more expensive
## match on relevant lines
elif 'gjstest_test_func_' in l:
match = test_func_re.search(l)
if not match:
raise Exception('line does not match test_func_re in ' + base_filename + ': ' + l)
subname = match.group(1)
funcname = 'gjstest_test_func_' + subname
if not in_tests:
raise Exception("Test func %s in %s not inside GJS_BUILD_TESTS" % (funcname, base_filename))
## check namespacing
expected_subname = ''
rest = base_filename
while rest != '':
(rest, last) = os.path.split(rest)
if last != '':
last = last.replace('-', '_')
if expected_subname != '':
expected_subname = last + '_' + expected_subname
else:
(last, ext) = os.path.splitext(last)
expected_subname = last
if not subname.startswith(expected_subname):
raise Exception("Test funcs in '%s' should start with gjstest_test_func_%s" % (base_filename, expected_subname))
test_path = '/' + subname.replace('_', '/')
test_funcs.append((test_path, funcname))
if in_tests:
raise Exception('no #endif /* GJS_BUILD_TESTS */ found - comment with GJS_BUILD_TESTS in it is mandatory')
## return a tuple, so we can add other kinds of stuff to find later
return (test_funcs)
output_dir = sys.argv[1]
input_files = sys.argv[2:]
top_srcdir = os.getenv('TOP_SRCDIR')
out_header_name = os.path.join(output_dir, "gjstest.h")
out_impl_name = os.path.join(output_dir, "gjstest.c")
(out_header_kind, out_header_mtime, out_header_size) = stat_file(out_header_name)
(out_impl_kind, out_impl_mtime, out_impl_size) = stat_file(out_impl_name)
out_header_tmp_name = out_header_name + ".stamp"
out_impl_tmp_name = out_impl_name + ".stamp"
all_test_funcs = []
for filename in input_files:
(test_funcs) = find_tests(filename, os.path.join(top_srcdir, filename))
all_test_funcs = all_test_funcs + test_funcs
header_out = open(out_header_tmp_name, 'w')
impl_out = open(out_impl_tmp_name, 'w')
def write_generic_c_boilerplate(f):
f.write('/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */\n')
f.write('/* FILE AUTOGENERATED DO NOT EDIT */\n')
f.write('\n')
write_generic_c_boilerplate(header_out)
write_generic_c_boilerplate(impl_out)
header_out.write('#ifndef __GJS_GJSTEST_GENERATED_H__\n')
header_out.write('#define __GJS_GJSTEST_GENERATED_H__\n\n')
header_out.write('#include <glib.h>\n\n')
header_out.write('G_BEGIN_DECLS\n\n')
header_out.write('void gjstest_add_all_tests(void);\n\n')
impl_out.write('#include <gjstest.h>\n\n')
impl_out.write('void\n')
impl_out.write('gjstest_add_all_tests(void)\n')
impl_out.write('{\n')
for (path, func) in all_test_funcs:
impl_out.write(' g_test_add_func("%s", %s);\n' % (path, func))
header_out.write('void %s(void);\n' % (func))
impl_out.write('}\n')
header_out.write('\n')
header_out.write('G_END_DECLS\n\n')
header_out.write('#endif /* __GJS_GJSTEST_GENERATED_H__ */\n')
## close so we can stat and rename
impl_out.close()
header_out.close()
## rename only if changed, to avoid needless rebuilds.
## we use the size to decide if it changed... not really
## quite kosher, but will fail infrequently enough to
## not be annoying
(new_header_kind, new_header_mtime, new_header_size) = stat_file(out_header_tmp_name)
(new_impl_kind, new_impl_mtime, new_impl_size) = stat_file(out_impl_tmp_name)
if new_header_size != out_header_size or new_impl_size != out_impl_size:
print "Replacing old %s and %s" % (out_header_name, out_impl_name)
print " %s was %d bytes now %d" % (out_header_name, out_header_size, new_header_size)
print " %s was %d bytes now %d" % (out_impl_name, out_impl_size, new_impl_size)
os.rename(out_header_tmp_name, out_header_name)
os.rename(out_impl_tmp_name, out_impl_name)
else:
print "%s and %s appear to be unchanged, not updating" % (out_header_name, out_impl_name)
|