]>
Commit | Line | Data |
---|---|---|
c3ec0972 JM |
1 | #!/usr/bin/python3 |
2 | # Check header contents against the given standard. | |
2b778ceb | 3 | # Copyright (C) 2018-2021 Free Software Foundation, Inc. |
c3ec0972 JM |
4 | # This file is part of the GNU C Library. |
5 | # | |
6 | # The GNU C Library is free software; you can redistribute it and/or | |
7 | # modify it under the terms of the GNU Lesser General Public | |
8 | # License as published by the Free Software Foundation; either | |
9 | # version 2.1 of the License, or (at your option) any later version. | |
10 | # | |
11 | # The GNU C Library is distributed in the hope that it will be useful, | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | # Lesser General Public License for more details. | |
15 | # | |
16 | # You should have received a copy of the GNU Lesser General Public | |
17 | # License along with the GNU C Library; if not, see | |
5a82c748 | 18 | # <https://www.gnu.org/licenses/>. |
c3ec0972 JM |
19 | |
20 | import argparse | |
21 | import fnmatch | |
22 | import os.path | |
23 | import re | |
24 | import subprocess | |
25 | import sys | |
26 | import tempfile | |
27 | ||
28 | import glibcconform | |
29 | ||
30 | ||
cc6c89fa JM |
31 | class CompileSubTest(object): |
32 | """A compilation subtest.""" | |
33 | ||
34 | def __init__(self, name, text): | |
35 | """Initialize a CompileSubTest object.""" | |
95edd05c | 36 | self.run_early = False |
cc6c89fa JM |
37 | self.name = name |
38 | self.text = text | |
39 | ||
40 | def run(self, header_tests): | |
41 | """Run a compilation subtest.""" | |
42 | header_tests.compile_test(self.name, self.text) | |
43 | ||
44 | ||
45 | class ExecuteSubTest(object): | |
46 | """An execution subtest.""" | |
47 | ||
48 | def __init__(self, name, text): | |
49 | """Initialize an ExecuteSubTest object.""" | |
95edd05c | 50 | self.run_early = False |
cc6c89fa JM |
51 | self.name = name |
52 | self.text = text | |
53 | ||
54 | def run(self, header_tests): | |
55 | """Run an execution subtest.""" | |
56 | header_tests.execute_test(self.name, self.text) | |
57 | ||
58 | ||
c3ec0972 JM |
59 | class ElementTest(object): |
60 | """Test for an element of a structure or union type.""" | |
61 | ||
62 | def __init__(self, dummy, type_name, member_type, member_name, *rest): | |
63 | """Initialize an ElementTest object.""" | |
64 | self.type_name = type_name | |
65 | self.member_type = member_type | |
66 | self.member_name = member_name | |
67 | self.rest = ' '.join(rest) | |
68 | self.allow_name = self.member_name | |
69 | ||
cc6c89fa JM |
70 | def gen_subtests(self): |
71 | """Generate subtests for an ElementTest.""" | |
9a62a939 JM |
72 | text = ('%(type_name)s a_%(num)d;\n' |
73 | '%(type_name)s b_%(num)d;\n' | |
74 | 'extern void xyzzy_%(num)d ' | |
75 | '(__typeof__ (&b_%(num)d.%(member_name)s), ' | |
76 | '__typeof__ (&a_%(num)d.%(member_name)s), unsigned);\n' | |
77 | 'void foobarbaz_%(num)d (void) {\n' | |
78 | 'xyzzy_%(num)d (&a_%(num)d.%(member_name)s, ' | |
79 | '&b_%(num)d.%(member_name)s, ' | |
80 | 'sizeof (a_%(num)d.%(member_name)s));\n' | |
c3ec0972 | 81 | '}\n' |
9a62a939 | 82 | % vars(self)) |
cc6c89fa JM |
83 | self.subtests.append(CompileSubTest( |
84 | 'Availability of member %s' % self.member_name, | |
85 | text)) | |
9a62a939 JM |
86 | text = ('%(type_name)s a2_%(num)d;\n' |
87 | 'extern %(member_type)s b2_%(num)d%(rest)s;\n' | |
88 | 'extern __typeof__ (a2_%(num)d.%(member_name)s) b2_%(num)d;\n' | |
89 | % vars(self)) | |
cc6c89fa JM |
90 | self.subtests.append(CompileSubTest( |
91 | 'Type of member %s' % self.member_name, | |
92 | text)) | |
c3ec0972 JM |
93 | |
94 | ||
95 | class ConstantTest(object): | |
96 | """Test for a macro or constant.""" | |
97 | ||
98 | def __init__(self, symbol_type, symbol, extra1=None, extra2=None, | |
99 | extra3=None): | |
100 | """Initialize a ConstantTest object.""" | |
101 | self.symbol_type = symbol_type | |
102 | self.symbol = symbol | |
103 | # A comparison operation may be specified without a type. | |
104 | if extra2 is not None and extra3 is None: | |
105 | self.c_type = None | |
106 | self.op = extra1 | |
107 | self.value = extra2 | |
108 | else: | |
109 | self.c_type = extra1 | |
110 | self.op = extra2 | |
111 | self.value = extra3 | |
112 | self.allow_name = self.symbol | |
113 | ||
cc6c89fa JM |
114 | def gen_subtests(self): |
115 | """Generate subtests for a ConstantTest.""" | |
c3ec0972 | 116 | if 'macro' in self.symbol_type: |
9a62a939 JM |
117 | text = ('#ifndef %(symbol)s\n' |
118 | '# error "Macro %(symbol)s not defined"\n' | |
c3ec0972 | 119 | '#endif\n' |
9a62a939 | 120 | % vars(self)) |
cc6c89fa JM |
121 | self.subtests.append(CompileSubTest( |
122 | 'Availability of macro %s' % self.symbol, | |
123 | text)) | |
c3ec0972 | 124 | if 'constant' in self.symbol_type: |
9a62a939 JM |
125 | text = ('__typeof__ (%(symbol)s) a_%(num)d = %(symbol)s;\n' |
126 | % vars(self)) | |
cc6c89fa JM |
127 | self.subtests.append(CompileSubTest( |
128 | 'Availability of constant %s' % self.symbol, | |
129 | text)) | |
c3ec0972 JM |
130 | if self.symbol_type == 'macro-int-constant': |
131 | sym_bits_def_neg = ''.join( | |
132 | '# if %s & (1LL << %d)\n' | |
9a62a939 | 133 | '# define conformtest_%d_bit_%d 0LL\n' |
c3ec0972 | 134 | '# else\n' |
9a62a939 | 135 | '# define conformtest_%d_bit_%d (1LL << %d)\n' |
c3ec0972 | 136 | '# endif\n' |
9a62a939 JM |
137 | % (self.symbol, i, self.num, i, self.num, i, i) |
138 | for i in range(63)) | |
139 | sym_bits_or_neg = '|'.join('conformtest_%d_bit_%d' % (self.num, i) | |
c3ec0972 JM |
140 | for i in range(63)) |
141 | sym_bits_def_pos = ''.join( | |
142 | '# if %s & (1ULL << %d)\n' | |
9a62a939 | 143 | '# define conformtest_%d_bit_%d (1ULL << %d)\n' |
c3ec0972 | 144 | '# else\n' |
9a62a939 | 145 | '# define conformtest_%d_bit_%d 0ULL\n' |
c3ec0972 | 146 | '# endif\n' |
9a62a939 JM |
147 | % (self.symbol, i, self.num, i, i, self.num, i) |
148 | for i in range(64)) | |
149 | sym_bits_or_pos = '|'.join('conformtest_%d_bit_%d' % (self.num, i) | |
c3ec0972 JM |
150 | for i in range(64)) |
151 | text = ('#if %s < 0\n' | |
9a62a939 | 152 | '# define conformtest_%d_negative 1\n' |
c3ec0972 | 153 | '%s' |
9a62a939 | 154 | '# define conformtest_%d_value ~(%s)\n' |
c3ec0972 | 155 | '#else\n' |
9a62a939 | 156 | '# define conformtest_%d_negative 0\n' |
c3ec0972 | 157 | '%s' |
9a62a939 | 158 | '# define conformtest_%d_value (%s)\n' |
c3ec0972 | 159 | '#endif\n' |
9a62a939 JM |
160 | '_Static_assert (((%s < 0) == conformtest_%d_negative) ' |
161 | '&& (%s == conformtest_%d_value), ' | |
c3ec0972 | 162 | '"value match inside and outside #if");\n' |
9a62a939 JM |
163 | % (self.symbol, self.num, sym_bits_def_neg, self.num, |
164 | sym_bits_or_neg, self.num, sym_bits_def_pos, self.num, | |
165 | sym_bits_or_pos, self.symbol, self.num, self.symbol, | |
166 | self.num)) | |
cc6c89fa JM |
167 | self.subtests.append(CompileSubTest( |
168 | '#if usability of symbol %s'% self.symbol, | |
169 | text)) | |
c3ec0972 JM |
170 | if self.c_type is not None: |
171 | if self.c_type.startswith('promoted:'): | |
172 | c_type = self.c_type[len('promoted:'):] | |
9a62a939 JM |
173 | text = ('__typeof__ ((%s) 0 + (%s) 0) a2_%d;\n' |
174 | % (c_type, c_type, self.num)) | |
c3ec0972 | 175 | else: |
9a62a939 JM |
176 | text = '__typeof__ ((%s) 0) a2_%d;\n' % (self.c_type, self.num) |
177 | text += 'extern __typeof__ (%s) a2_%d;\n' % (self.symbol, self.num) | |
cc6c89fa JM |
178 | self.subtests.append(CompileSubTest( |
179 | 'Type of symbol %s' % self.symbol, | |
180 | text)) | |
c3ec0972 | 181 | if self.op is not None: |
9a62a939 JM |
182 | text = ('_Static_assert (%(symbol)s %(op)s %(value)s, ' |
183 | '"value constraint");\n' | |
184 | % vars(self)) | |
cc6c89fa JM |
185 | self.subtests.append(CompileSubTest( |
186 | 'Value of symbol %s' % self.symbol, | |
187 | text)) | |
c3ec0972 JM |
188 | |
189 | ||
190 | class SymbolTest(object): | |
191 | """Test for a symbol (not a compile-time constant).""" | |
192 | ||
193 | def __init__(self, dummy, symbol, value=None): | |
194 | """Initialize a SymbolTest object.""" | |
195 | self.symbol = symbol | |
196 | self.value = value | |
197 | self.allow_name = self.symbol | |
198 | ||
cc6c89fa JM |
199 | def gen_subtests(self): |
200 | """Generate subtests for a SymbolTest.""" | |
9a62a939 JM |
201 | text = ('void foobarbaz_%(num)d (void) {\n' |
202 | '__typeof__ (%(symbol)s) a_%(num)d = %(symbol)s;\n' | |
c3ec0972 | 203 | '}\n' |
9a62a939 | 204 | % vars(self)) |
cc6c89fa JM |
205 | self.subtests.append(CompileSubTest( |
206 | 'Availability of symbol %s' % self.symbol, | |
207 | text)) | |
c3ec0972 | 208 | if self.value is not None: |
9a62a939 JM |
209 | text = ('int main (void) { return %(symbol)s != %(symbol)s; }\n' |
210 | % vars(self)) | |
cc6c89fa JM |
211 | self.subtests.append(ExecuteSubTest( |
212 | 'Value of symbol %s' % self.symbol, | |
213 | text)) | |
c3ec0972 JM |
214 | |
215 | ||
216 | class TypeTest(object): | |
217 | """Test for a type name.""" | |
218 | ||
219 | def __init__(self, dummy, type_name): | |
220 | """Initialize a TypeTest object.""" | |
221 | self.type_name = type_name | |
222 | if type_name.startswith('struct '): | |
223 | self.allow_name = type_name[len('struct '):] | |
224 | self.maybe_opaque = False | |
225 | elif type_name.startswith('union '): | |
226 | self.allow_name = type_name[len('union '):] | |
227 | self.maybe_opaque = False | |
228 | else: | |
229 | self.allow_name = type_name | |
230 | self.maybe_opaque = True | |
231 | ||
cc6c89fa JM |
232 | def gen_subtests(self): |
233 | """Generate subtests for a TypeTest.""" | |
9a62a939 JM |
234 | text = ('%s %sa_%d;\n' |
235 | % (self.type_name, '*' if self.maybe_opaque else '', self.num)) | |
cc6c89fa JM |
236 | self.subtests.append(CompileSubTest( |
237 | 'Availability of type %s' % self.type_name, | |
238 | text)) | |
c3ec0972 JM |
239 | |
240 | ||
241 | class TagTest(object): | |
242 | """Test for a tag name.""" | |
243 | ||
244 | def __init__(self, dummy, type_name): | |
245 | """Initialize a TagTest object.""" | |
246 | self.type_name = type_name | |
247 | if type_name.startswith('struct '): | |
248 | self.allow_name = type_name[len('struct '):] | |
249 | elif type_name.startswith('union '): | |
250 | self.allow_name = type_name[len('union '):] | |
251 | else: | |
252 | raise ValueError('unexpected kind of tag: %s' % type_name) | |
253 | ||
cc6c89fa JM |
254 | def gen_subtests(self): |
255 | """Generate subtests for a TagTest.""" | |
c3ec0972 JM |
256 | # If the tag is not declared, these function prototypes have |
257 | # incompatible types. | |
9a62a939 JM |
258 | text = ('void foo_%(num)d (%(type_name)s *);\n' |
259 | 'void foo_%(num)d (%(type_name)s *);\n' | |
260 | % vars(self)) | |
cc6c89fa JM |
261 | self.subtests.append(CompileSubTest( |
262 | 'Availability of tag %s' % self.type_name, | |
263 | text)) | |
c3ec0972 JM |
264 | |
265 | ||
266 | class FunctionTest(object): | |
267 | """Test for a function.""" | |
268 | ||
269 | def __init__(self, dummy, return_type, function_name, *args): | |
270 | """Initialize a FunctionTest object.""" | |
271 | self.function_name_full = function_name | |
272 | self.args = ' '.join(args) | |
273 | if function_name.startswith('(*'): | |
274 | # Function returning a pointer to function. | |
275 | self.return_type = '%s (*' % return_type | |
276 | self.function_name = function_name[len('(*'):] | |
277 | else: | |
278 | self.return_type = return_type | |
279 | self.function_name = function_name | |
280 | self.allow_name = self.function_name | |
281 | ||
cc6c89fa JM |
282 | def gen_subtests(self): |
283 | """Generate subtests for a FunctionTest.""" | |
9a62a939 JM |
284 | text = ('%(return_type)s (*foobarbaz_%(num)d) %(args)s ' |
285 | '= %(function_name)s;\n' | |
286 | % vars(self)) | |
cc6c89fa JM |
287 | self.subtests.append(CompileSubTest( |
288 | 'Availability of function %s' % self.function_name, | |
289 | text)) | |
9a62a939 JM |
290 | text = ('extern %(return_type)s (*foobarbaz2_%(num)d) %(args)s;\n' |
291 | 'extern __typeof__ (&%(function_name)s) foobarbaz2_%(num)d;\n' | |
292 | % vars(self)) | |
cc6c89fa JM |
293 | self.subtests.append(CompileSubTest( |
294 | 'Type of function %s' % self.function_name, | |
295 | text)) | |
c3ec0972 JM |
296 | |
297 | ||
298 | class VariableTest(object): | |
299 | """Test for a variable.""" | |
300 | ||
301 | def __init__(self, dummy, var_type, var_name, *rest): | |
302 | """Initialize a VariableTest object.""" | |
303 | self.var_type = var_type | |
304 | self.var_name = var_name | |
305 | self.rest = ' '.join(rest) | |
306 | self.allow_name = var_name | |
307 | ||
cc6c89fa JM |
308 | def gen_subtests(self): |
309 | """Generate subtests for a VariableTest.""" | |
9a62a939 JM |
310 | text = ('typedef %(var_type)s xyzzy_%(num)d%(rest)s;\n' |
311 | 'xyzzy_%(num)d *foobarbaz_%(num)d = &%(var_name)s;\n' | |
312 | % vars(self)) | |
cc6c89fa JM |
313 | self.subtests.append(CompileSubTest( |
314 | 'Availability of variable %s' % self.var_name, | |
315 | text)) | |
9a62a939 JM |
316 | text = ('extern %(var_type)s %(var_name)s%(rest)s;\n' |
317 | % vars(self)) | |
cc6c89fa JM |
318 | self.subtests.append(CompileSubTest( |
319 | 'Type of variable %s' % self.var_name, | |
320 | text)) | |
c3ec0972 JM |
321 | |
322 | ||
323 | class MacroFunctionTest(object): | |
324 | """Test for a possibly macro-only function.""" | |
325 | ||
326 | def __init__(self, dummy, return_type, function_name, *args): | |
327 | """Initialize a MacroFunctionTest object.""" | |
328 | self.return_type = return_type | |
329 | self.function_name = function_name | |
330 | self.args = ' '.join(args) | |
331 | self.allow_name = function_name | |
332 | ||
cc6c89fa JM |
333 | def gen_subtests(self): |
334 | """Generate subtests for a MacroFunctionTest.""" | |
9a62a939 JM |
335 | text = ('#ifndef %(function_name)s\n' |
336 | '%(return_type)s (*foobarbaz_%(num)d) %(args)s ' | |
337 | '= %(function_name)s;\n' | |
c3ec0972 | 338 | '#endif\n' |
9a62a939 | 339 | % vars(self)) |
cc6c89fa JM |
340 | self.subtests.append(CompileSubTest( |
341 | 'Availability of macro %s' % self.function_name, | |
342 | text)) | |
9a62a939 JM |
343 | text = ('#ifndef %(function_name)s\n' |
344 | 'extern %(return_type)s (*foobarbaz2_%(num)d) %(args)s;\n' | |
345 | 'extern __typeof__ (&%(function_name)s) foobarbaz2_%(num)d;\n' | |
c3ec0972 | 346 | '#endif\n' |
9a62a939 | 347 | % vars(self)) |
cc6c89fa JM |
348 | self.subtests.append(CompileSubTest( |
349 | 'Type of macro %s' % self.function_name, | |
350 | text)) | |
c3ec0972 JM |
351 | |
352 | ||
353 | class MacroStrTest(object): | |
354 | """Test for a string-valued macro.""" | |
355 | ||
356 | def __init__(self, dummy, macro_name, value): | |
357 | """Initialize a MacroStrTest object.""" | |
358 | self.macro_name = macro_name | |
359 | self.value = value | |
360 | self.allow_name = macro_name | |
361 | ||
cc6c89fa JM |
362 | def gen_subtests(self): |
363 | """Generate subtests for a MacroStrTest.""" | |
9a62a939 JM |
364 | text = ('#ifndef %(macro_name)s\n' |
365 | '# error "Macro %(macro_name)s not defined"\n' | |
c3ec0972 | 366 | '#endif\n' |
9a62a939 | 367 | % vars(self)) |
cc6c89fa JM |
368 | self.subtests.append(CompileSubTest( |
369 | 'Availability of macro %s' % self.macro_name, | |
370 | text)) | |
c3ec0972 JM |
371 | # We can't include <string.h> here. |
372 | text = ('extern int (strcmp)(const char *, const char *);\n' | |
9a62a939 JM |
373 | 'int main (void) { return (strcmp) (%(macro_name)s, ' |
374 | '%(value)s) != 0; }\n' | |
375 | % vars(self)) | |
cc6c89fa JM |
376 | self.subtests.append(ExecuteSubTest( |
377 | 'Value of macro %s' % self.macro_name, | |
378 | text)) | |
c3ec0972 JM |
379 | |
380 | ||
381 | class HeaderTests(object): | |
382 | """The set of tests run for a header.""" | |
383 | ||
384 | def __init__(self, header, standard, cc, flags, cross, xfail): | |
385 | """Initialize a HeaderTests object.""" | |
386 | self.header = header | |
387 | self.standard = standard | |
388 | self.cc = cc | |
389 | self.flags = flags | |
390 | self.cross = cross | |
391 | self.xfail_str = xfail | |
392 | self.cflags_namespace = ('%s -fno-builtin %s -D_ISOMAC' | |
393 | % (flags, glibcconform.CFLAGS[standard])) | |
394 | # When compiling the conformance test programs, use of | |
395 | # __attribute__ in headers is disabled because of attributes | |
396 | # that affect the types of functions as seen by typeof. | |
397 | self.cflags = "%s '-D__attribute__(x)='" % self.cflags_namespace | |
398 | self.tests = [] | |
399 | self.allow = set() | |
400 | self.allow_fnmatch = set() | |
401 | self.headers_handled = set() | |
9a62a939 | 402 | self.num_tests = 0 |
c3ec0972 JM |
403 | self.total = 0 |
404 | self.skipped = 0 | |
405 | self.errors = 0 | |
406 | self.xerrors = 0 | |
407 | ||
408 | def add_allow(self, name, pattern_ok): | |
409 | """Add an identifier as an allowed token for this header. | |
410 | ||
411 | If pattern_ok, fnmatch patterns are OK as well as | |
412 | identifiers. | |
413 | ||
414 | """ | |
415 | if re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*', name): | |
416 | self.allow.add(name) | |
417 | elif pattern_ok: | |
418 | self.allow_fnmatch.add(name) | |
419 | else: | |
420 | raise ValueError('bad identifier: %s' % name) | |
421 | ||
422 | def check_token(self, bad_tokens, token): | |
423 | """Check whether an identifier token is allowed, and record it in | |
424 | bad_tokens if not. | |
425 | ||
426 | """ | |
427 | if token.startswith('_'): | |
428 | return | |
429 | if token in glibcconform.KEYWORDS[self.standard]: | |
430 | return | |
431 | if token in self.allow: | |
432 | return | |
433 | for pattern in self.allow_fnmatch: | |
434 | if fnmatch.fnmatch(token, pattern): | |
435 | return | |
436 | bad_tokens.add(token) | |
437 | ||
438 | def handle_test_line(self, line, allow): | |
439 | """Handle a single line in the test data. | |
440 | ||
441 | If allow is true, the header is one specified in allow-header | |
442 | and so tests are marked as allowed for namespace purposes but | |
443 | otherwise ignored. | |
444 | ||
445 | """ | |
446 | orig_line = line | |
447 | xfail = False | |
448 | if line.startswith('xfail-'): | |
449 | xfail = True | |
450 | line = line[len('xfail-'):] | |
451 | else: | |
452 | match = re.match(r'xfail\[(.*?)\]-(.*)', line) | |
453 | if match: | |
454 | xfail_cond = match.group(1) | |
455 | line = match.group(2) | |
456 | # "xfail[cond]-" or "xfail[cond1|cond2|...]-" means a | |
457 | # failure of the test is allowed if any of the listed | |
458 | # conditions are in the --xfail command-line option | |
459 | # argument. | |
460 | if self.xfail_str and re.search(r'\b(%s)\b' % xfail_cond, | |
461 | self.xfail_str): | |
462 | xfail = True | |
463 | optional = False | |
464 | if line.startswith('optional-'): | |
465 | optional = True | |
466 | line = line[len('optional-'):] | |
467 | # Tokens in test data are space-separated, except for {...} | |
468 | # tokens that may contain spaces. | |
469 | tokens = [] | |
470 | while line: | |
471 | match = re.match(r'\{(.*?)\}(.*)', line) | |
472 | if match: | |
473 | tokens.append(match.group(1)) | |
474 | line = match.group(2) | |
475 | else: | |
476 | match = re.match(r'([^ ]*)(.*)', line) | |
477 | tokens.append(match.group(1)) | |
478 | line = match.group(2) | |
479 | line = line.strip() | |
480 | if tokens[0] == 'allow-header': | |
481 | if len(tokens) != 2 or xfail or optional: | |
482 | raise ValueError('bad allow-header line: %s' % orig_line) | |
483 | if tokens[1] not in self.headers_handled: | |
484 | self.load_tests(tokens[1], True) | |
485 | return | |
486 | if tokens[0] == 'allow': | |
487 | if len(tokens) != 2 or xfail or optional: | |
488 | raise ValueError('bad allow line: %s' % orig_line) | |
489 | self.add_allow(tokens[1], True) | |
490 | return | |
491 | test_classes = {'element': ElementTest, | |
492 | 'macro': ConstantTest, | |
493 | 'constant': ConstantTest, | |
494 | 'macro-constant': ConstantTest, | |
495 | 'macro-int-constant': ConstantTest, | |
496 | 'symbol': SymbolTest, | |
497 | 'type': TypeTest, | |
498 | 'tag': TagTest, | |
499 | 'function': FunctionTest, | |
500 | 'variable': VariableTest, | |
501 | 'macro-function': MacroFunctionTest, | |
502 | 'macro-str': MacroStrTest} | |
503 | test = test_classes[tokens[0]](*tokens) | |
504 | test.xfail = xfail | |
505 | test.optional = optional | |
9a62a939 | 506 | test.num = self.num_tests |
cc6c89fa | 507 | test.subtests = [] |
9a62a939 | 508 | self.num_tests += 1 |
c3ec0972 JM |
509 | self.add_allow(test.allow_name, False) |
510 | if not allow: | |
cc6c89fa | 511 | test.gen_subtests() |
c3ec0972 JM |
512 | self.tests.append(test) |
513 | ||
514 | def load_tests(self, header, allow): | |
515 | """Load tests of a header. | |
516 | ||
517 | If allow is true, the header is one specified in allow-header | |
518 | and so tests are marked as allowed for namespace purposes but | |
519 | otherwise ignored. | |
520 | ||
521 | """ | |
522 | self.headers_handled.add(header) | |
523 | header_s = header.replace('/', '_') | |
524 | temp_file = os.path.join(self.temp_dir, 'header-data-%s' % header_s) | |
525 | cmd = ('%s -E -D%s -std=c99 -x c data/%s-data > %s' | |
526 | % (self.cc, self.standard, header, temp_file)) | |
527 | subprocess.check_call(cmd, shell=True) | |
528 | with open(temp_file, 'r') as tests: | |
529 | for line in tests: | |
530 | line = line.strip() | |
531 | if line == '' or line.startswith('#'): | |
532 | continue | |
533 | self.handle_test_line(line, allow) | |
534 | ||
535 | def note_error(self, name, xfail): | |
536 | """Note a failing test.""" | |
537 | if xfail: | |
538 | print('XFAIL: %s' % name) | |
539 | self.xerrors += 1 | |
540 | else: | |
541 | print('FAIL: %s' % name) | |
542 | self.errors += 1 | |
543 | sys.stdout.flush() | |
544 | ||
545 | def note_skip(self, name): | |
546 | """Note a skipped test.""" | |
547 | print('SKIP: %s' % name) | |
548 | self.skipped += 1 | |
549 | sys.stdout.flush() | |
550 | ||
551 | def compile_test(self, name, text): | |
552 | """Run a compilation test; return True if it passes.""" | |
553 | self.total += 1 | |
554 | if self.group_ignore: | |
555 | return False | |
556 | optional = self.group_optional | |
557 | self.group_optional = False | |
558 | if self.group_skip: | |
559 | self.note_skip(name) | |
560 | return False | |
561 | c_file = os.path.join(self.temp_dir, 'test.c') | |
562 | o_file = os.path.join(self.temp_dir, 'test.o') | |
563 | with open(c_file, 'w') as c_file_out: | |
564 | c_file_out.write('#include <%s>\n%s' % (self.header, text)) | |
565 | cmd = ('%s %s -c %s -o %s' % (self.cc, self.cflags, c_file, o_file)) | |
566 | try: | |
567 | subprocess.check_call(cmd, shell=True) | |
568 | except subprocess.CalledProcessError: | |
569 | if optional: | |
570 | print('MISSING: %s' % name) | |
571 | sys.stdout.flush() | |
572 | self.group_ignore = True | |
573 | else: | |
574 | self.note_error(name, self.group_xfail) | |
575 | self.group_skip = True | |
576 | return False | |
577 | print('PASS: %s' % name) | |
578 | sys.stdout.flush() | |
579 | return True | |
580 | ||
581 | def execute_test(self, name, text): | |
582 | """Run an execution test.""" | |
583 | self.total += 1 | |
584 | if self.group_ignore: | |
585 | return False | |
586 | if self.group_skip: | |
587 | self.note_skip(name) | |
588 | return | |
589 | c_file = os.path.join(self.temp_dir, 'test.c') | |
590 | exe_file = os.path.join(self.temp_dir, 'test') | |
591 | with open(c_file, 'w') as c_file_out: | |
592 | c_file_out.write('#include <%s>\n%s' % (self.header, text)) | |
593 | cmd = ('%s %s %s -o %s' % (self.cc, self.cflags, c_file, exe_file)) | |
594 | try: | |
595 | subprocess.check_call(cmd, shell=True) | |
596 | except subprocess.CalledProcessError: | |
597 | self.note_error(name, self.group_xfail) | |
598 | return | |
599 | if self.cross: | |
600 | self.note_skip(name) | |
601 | return | |
602 | try: | |
603 | subprocess.check_call(exe_file, shell=True) | |
604 | except subprocess.CalledProcessError: | |
605 | self.note_error(name, self.group_xfail) | |
606 | return | |
607 | print('PASS: %s' % name) | |
608 | sys.stdout.flush() | |
609 | ||
610 | def check_namespace(self, name): | |
611 | """Check the namespace of a header.""" | |
612 | c_file = os.path.join(self.temp_dir, 'namespace.c') | |
613 | out_file = os.path.join(self.temp_dir, 'namespace-out') | |
614 | with open(c_file, 'w') as c_file_out: | |
615 | c_file_out.write('#include <%s>\n' % self.header) | |
616 | cmd = ('%s %s -E %s -P -Wp,-dN > %s' | |
617 | % (self.cc, self.cflags_namespace, c_file, out_file)) | |
618 | subprocess.check_call(cmd, shell=True) | |
619 | bad_tokens = set() | |
620 | with open(out_file, 'r') as content: | |
621 | for line in content: | |
622 | line = line.strip() | |
623 | if not line: | |
624 | continue | |
625 | if re.match(r'# [1-9]', line): | |
626 | continue | |
6090a4a1 JM |
627 | if line.startswith('#pragma GCC '): |
628 | # No GCC pragma uses macro expansion, so no | |
629 | # namespace issues arise from such pragmas. (Some | |
630 | # pragmas not in the GCC namespace do macro-expand | |
631 | # their arguments and so could be affected by | |
632 | # macros defined by user code including the | |
633 | # header.) | |
634 | continue | |
c3ec0972 JM |
635 | match = re.match(r'#define (.*)', line) |
636 | if match: | |
637 | self.check_token(bad_tokens, match.group(1)) | |
638 | continue | |
639 | match = re.match(r'#undef (.*)', line) | |
640 | if match: | |
641 | bad_tokens.discard(match.group(1)) | |
642 | continue | |
643 | # Tokenize the line and check identifiers found. The | |
1f1c65e2 FW |
644 | # handling of strings and character constants does not |
645 | # allow for escaped quotes, and hex floats may be | |
646 | # wrongly split into tokens including identifiers, but | |
647 | # this is sufficient in practice. | |
648 | line = re.sub(r'(?:\bL)?(?:"[^"]*"|\'[^\']*\')', '', line) | |
c3ec0972 JM |
649 | line = line.strip() |
650 | for token in re.split(r'[^A-Za-z0-9_]+', line): | |
651 | if re.match(r'[A-Za-z_]', token): | |
652 | self.check_token(bad_tokens, token) | |
653 | if bad_tokens: | |
654 | for token in sorted(bad_tokens): | |
655 | print(' Namespace violation: "%s"' % token) | |
656 | self.note_error(name, False) | |
657 | else: | |
658 | print('PASS: %s' % name) | |
659 | sys.stdout.flush() | |
660 | ||
661 | def run(self): | |
662 | """Load and run tests of a header.""" | |
663 | with tempfile.TemporaryDirectory() as self.temp_dir: | |
664 | self.load_tests(self.header, False) | |
665 | self.group_optional = False | |
666 | self.group_xfail = False | |
667 | self.group_ignore = False | |
668 | self.group_skip = False | |
669 | available = self.compile_test('Availability of <%s>' % self.header, | |
670 | '') | |
671 | if available: | |
95edd05c JM |
672 | # As an optimization, try running all non-optional, |
673 | # non-XFAILed compilation tests in a single execution | |
674 | # of the compiler. | |
675 | combined_list = [] | |
676 | for test in self.tests: | |
677 | if not test.optional and not test.xfail: | |
678 | for subtest in test.subtests: | |
679 | if isinstance(subtest, CompileSubTest): | |
680 | combined_list.append(subtest.text) | |
681 | subtest.run_early = True | |
682 | combined_ok = self.compile_test('Combined <%s> test' | |
683 | % self.header, | |
684 | '\n'.join(combined_list)) | |
685 | # Now run the other tests, or all tests if the | |
686 | # combined test failed. | |
c3ec0972 JM |
687 | for test in self.tests: |
688 | # A test may run more than one subtest. If the | |
689 | # initial subtest for an optional symbol fails, | |
690 | # others are not run at all; if the initial | |
691 | # subtest for an optional symbol succeeds, others | |
692 | # are run and are not considered optional; if the | |
693 | # initial subtest for a required symbol fails, | |
694 | # others are skipped. | |
695 | self.group_optional = test.optional | |
696 | self.group_xfail = test.xfail | |
697 | self.group_ignore = False | |
698 | self.group_skip = False | |
cc6c89fa | 699 | for subtest in test.subtests: |
95edd05c JM |
700 | if combined_ok and subtest.run_early: |
701 | self.total += 1 | |
702 | print('PASSCOMBINED: %s' % subtest.name) | |
703 | sys.stdout.flush() | |
704 | else: | |
705 | subtest.run(self) | |
c3ec0972 JM |
706 | namespace_name = 'Namespace of <%s>' % self.header |
707 | if available: | |
708 | self.check_namespace(namespace_name) | |
709 | else: | |
710 | self.note_skip(namespace_name) | |
711 | print('-' * 76) | |
712 | print(' Total number of tests : %4d' % self.total) | |
713 | print(' Number of failed tests : %4d' % self.errors) | |
714 | print(' Number of xfailed tests : %4d' % self.xerrors) | |
715 | print(' Number of skipped tests : %4d' % self.skipped) | |
716 | sys.exit(1 if self.errors else 0) | |
717 | ||
718 | ||
719 | def main(): | |
720 | """The main entry point.""" | |
721 | parser = argparse.ArgumentParser(description='Check header contents.') | |
722 | parser.add_argument('--header', metavar='HEADER', | |
723 | help='name of header') | |
724 | parser.add_argument('--standard', metavar='STD', | |
725 | help='standard to use when processing header') | |
726 | parser.add_argument('--cc', metavar='CC', | |
727 | help='C compiler to use') | |
728 | parser.add_argument('--flags', metavar='CFLAGS', | |
729 | help='Compiler flags to use with CC') | |
730 | parser.add_argument('--cross', action='store_true', | |
731 | help='Do not run compiled test programs') | |
732 | parser.add_argument('--xfail', metavar='COND', | |
733 | help='Name of condition for XFAILs') | |
734 | args = parser.parse_args() | |
735 | tests = HeaderTests(args.header, args.standard, args.cc, args.flags, | |
736 | args.cross, args.xfail) | |
737 | tests.run() | |
738 | ||
739 | ||
740 | if __name__ == '__main__': | |
741 | main() |