glibc/support/tst-glibcpp.py

219 lines
5.6 KiB
Python

#! /usr/bin/python3
# Tests for scripts/glibcpp.py
# Copyright (C) 2022-2024 Free Software Foundation, Inc.
# This file is part of the GNU C Library.
#
# The GNU C Library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# The GNU C Library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with the GNU C Library; if not, see
# <https://www.gnu.org/licenses/>.
import inspect
import sys
import glibcpp
# Error counter.
errors = 0
class TokenizerErrors:
"""Used as the error reporter during tokenization."""
def __init__(self):
self.errors = []
def error(self, token, message):
self.errors.append((token, message))
def check_macro_definitions(source, expected):
reporter = TokenizerErrors()
tokens = glibcpp.tokenize_c(source, reporter)
actual = []
for md in glibcpp.macro_definitions(tokens):
if md.function:
md_name = '{}({})'.format(md.name, ','.join(md.args_lowered))
else:
md_name = md.name
actual.append((md_name, md.body_lowered))
if actual != expected or reporter.errors:
global errors
errors += 1
# Obtain python source line information.
frame = inspect.stack(2)[1]
print('{}:{}: error: macro definition mismatch, actual definitions:'
.format(frame[1], frame[2]))
for md in actual:
print('note: {} {!r}'.format(md[0], md[1]))
if reporter.errors:
for err in reporter.errors:
print('note: tokenizer error: {}: {}'.format(
err[0].line, err[1]))
def check_macro_eval(source, expected, expected_errors=''):
reporter = TokenizerErrors()
tokens = list(glibcpp.tokenize_c(source, reporter))
if reporter.errors:
# Obtain python source line information.
frame = inspect.stack(2)[1]
for err in reporter.errors:
print('{}:{}: tokenizer error: {}: {}'.format(
frame[1], frame[2], err[0].line, err[1]))
return
class EvalReporter:
"""Used as the error reporter during evaluation."""
def __init__(self):
self.lines = []
def error(self, line, message):
self.lines.append('{}: error: {}\n'.format(line, message))
def note(self, line, message):
self.lines.append('{}: note: {}\n'.format(line, message))
reporter = EvalReporter()
actual = glibcpp.macro_eval(glibcpp.macro_definitions(tokens), reporter)
actual_errors = ''.join(reporter.lines)
if actual != expected or actual_errors != expected_errors:
global errors
errors += 1
# Obtain python source line information.
frame = inspect.stack(2)[1]
print('{}:{}: error: macro evaluation mismatch, actual results:'
.format(frame[1], frame[2]))
for k, v in actual.items():
print(' {}: {!r}'.format(k, v))
for msg in reporter.lines:
sys.stdout.write(' | ' + msg)
# Individual test cases follow.
check_macro_definitions('', [])
check_macro_definitions('int main()\n{\n{\n', [])
check_macro_definitions("""
#define A 1
#define B 2 /* ignored */
#define C 3 // also ignored
#define D \
4
#define STRING "string"
#define FUNCLIKE(a, b) (a + b)
#define FUNCLIKE2(a, b) (a + \
b)
""", [('A', ['1']),
('B', ['2']),
('C', ['3']),
('D', ['4']),
('STRING', ['"string"']),
('FUNCLIKE(a,b)', list('(a+b)')),
('FUNCLIKE2(a,b)', list('(a+b)')),
])
check_macro_definitions('#define MACRO', [('MACRO', [])])
check_macro_definitions('#define MACRO\n', [('MACRO', [])])
check_macro_definitions('#define MACRO()', [('MACRO()', [])])
check_macro_definitions('#define MACRO()\n', [('MACRO()', [])])
check_macro_eval('#define A 1', {'A': 1})
check_macro_eval('#define A (1)', {'A': 1})
check_macro_eval('#define A (1 + 1)', {'A': 2})
check_macro_eval('#define A (1U << 31)', {'A': 1 << 31})
check_macro_eval('#define A (1 | 2)', {'A': 1 | 2})
check_macro_eval('''\
#define A (B + 1)
#define B 10
#define F(x) ignored
#define C "not ignored"
''', {
'A': 11,
'B': 10,
'C': '"not ignored"',
})
# Checking for evaluation errors.
check_macro_eval('''\
#define A 1
#define A 2
''', {
'A': 1,
}, '''\
2: error: macro A redefined
1: note: location of previous definition
''')
check_macro_eval('''\
#define A A
#define B 1
''', {
'A': None,
'B': 1,
}, '''\
1: error: macro definition A refers to itself
''')
check_macro_eval('''\
#define A B
#define B A
''', {
'A': None,
'B': None,
}, '''\
1: error: macro definition A refers to itself
2: note: evaluated from B
''')
check_macro_eval('''\
#define A B
#define B C
#define C A
''', {
'A': None,
'B': None,
'C': None,
}, '''\
1: error: macro definition A refers to itself
3: note: evaluated from C
2: note: evaluated from B
''')
check_macro_eval('''\
#define A 1 +
''', {
'A': None,
}, '''\
1: error: uninterpretable macro token sequence: 1 +
''')
check_macro_eval('''\
#define A 3*5
''', {
'A': None,
}, '''\
1: error: uninterpretable macro token sequence: 3 * 5
''')
check_macro_eval('''\
#define A 3 + 5
''', {
'A': 8,
}, '''\
1: error: missing parentheses around + expression
1: note: in definition of macro A
''')
if errors:
sys.exit(1)