When U-Boot is used as a library with other programs, some U-Boot function names may conflict with the program, or with standard-library functions. For example, printf() is defined by U-Boot but is typically used by the program as well. The easiest solution is to rename symbols in the object file, so that they appear with a 'ub_' prefix when linked with the program. Add a new build_api.py script which can: - rename symbols based on a rename.syms file - generate a header file (with the renamed symbols) for use by the program This makes use of the 'objcopy --redefine-sym' feature. The tool has 100% test coverage. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org>
705 lines
23 KiB
Python
705 lines
23 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
# pylint: disable=cyclic-import
|
|
"""Test suite for build_api.py script"""
|
|
|
|
import contextlib
|
|
from io import StringIO
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
# Add the scripts directory to the path
|
|
script_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'scripts')
|
|
sys.path.insert(0, script_dir)
|
|
|
|
# Add the tools directory to the path for u_boot_pylib
|
|
tools_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'tools')
|
|
sys.path.insert(0, tools_dir)
|
|
|
|
# pylint: disable=wrong-import-position,import-error
|
|
from build_api import rename_function, RenameSymsParser, DeclExtractor
|
|
from build_api import ApiGenerator, SymbolRedefiner, main
|
|
from u_boot_pylib import tools
|
|
|
|
|
|
class TestBuildApi(unittest.TestCase):
|
|
# pylint: disable=too-many-public-methods
|
|
"""Test suite for build_api.py script"""
|
|
|
|
def setUp(self):
|
|
"""Create temporary files for testing"""
|
|
# pylint: disable=R1732
|
|
self.tmpdir = tempfile.TemporaryDirectory()
|
|
# Create a temp file path for symbols.syms that tests can write to
|
|
self.sympath = os.path.join(self.tmpdir.name, 'symbols.syms')
|
|
|
|
def tearDown(self):
|
|
"""Clean up temporary files"""
|
|
self.tmpdir.cleanup()
|
|
|
|
def write_tmp(self, content, filename):
|
|
"""Create a temporary text file with given content"""
|
|
temp_path = os.path.join(self.tmpdir.name, filename)
|
|
tools.write_file(temp_path, content, binary=False)
|
|
return temp_path
|
|
|
|
def test_rename_function(self):
|
|
"""Test basic function renaming"""
|
|
source_code = '''
|
|
/**
|
|
* sprintf() - Format a string and place it in a buffer
|
|
*
|
|
* @buf: The buffer to place the result into
|
|
* @fmt: The format string to use
|
|
* @...: Arguments for the format string
|
|
*
|
|
* The function returns the number of characters written
|
|
* into @buf.
|
|
*
|
|
* See the vsprintf() documentation for format string extensions over C99.
|
|
*/
|
|
int sprintf(char *buf, const char *fmt, ...)
|
|
__attribute__ ((format (__printf__, 2, 3)));
|
|
'''
|
|
result = rename_function(source_code, 'sprintf', 'my_sprintf')
|
|
|
|
# Check that the function name was changed
|
|
assert 'int my_sprintf(char *buf' in result
|
|
assert 'int sprintf(char *buf' not in result
|
|
|
|
def test_rename_sym_parser(self):
|
|
"""Test parsing symbol definition file format"""
|
|
content = '''# Test symbols.syms file
|
|
file: stdio.h
|
|
printf
|
|
scanf
|
|
|
|
file: string.h
|
|
strcpy
|
|
strlen=ub_str_length
|
|
|
|
file: stdlib.h
|
|
malloc=custom_malloc
|
|
'''
|
|
tools.write_file(self.sympath, content, binary=False)
|
|
|
|
parser = RenameSymsParser(self.sympath)
|
|
renames = parser.parse()
|
|
|
|
# Check we got the right number of renames
|
|
assert len(renames) == 5
|
|
|
|
# Check default prefix mapping
|
|
printf_rename = next(r for r in renames if r.orig == 'printf')
|
|
assert printf_rename.hdr == 'stdio.h'
|
|
assert printf_rename.new_name == 'ub_printf'
|
|
|
|
# Check explicit mapping
|
|
strlen_rename = next(r for r in renames if r.orig == 'strlen')
|
|
assert strlen_rename.hdr == 'string.h'
|
|
assert strlen_rename.new_name == 'ub_str_length'
|
|
malloc_rename = next(r for r in renames if r.orig == 'malloc')
|
|
assert malloc_rename.hdr == 'stdlib.h'
|
|
assert malloc_rename.new_name == 'custom_malloc'
|
|
|
|
def test_rename_sym_with_real_file(self):
|
|
"""Test parsing with realistic symbols.syms file"""
|
|
symbols_content = '''# Symbols for U-Boot library
|
|
file: stdio.h
|
|
printf
|
|
sprintf
|
|
snprintf
|
|
scanf
|
|
sscanf
|
|
|
|
file: string.h
|
|
memcpy
|
|
memset
|
|
strlen
|
|
strcpy
|
|
strcmp
|
|
|
|
file: stdlib.h
|
|
malloc
|
|
free
|
|
calloc
|
|
'''
|
|
symbols_path = self.write_tmp(symbols_content, 'realistic_symbols.syms')
|
|
|
|
parser = RenameSymsParser(symbols_path)
|
|
|
|
# Should have some renames
|
|
renames = parser.parse()
|
|
assert renames
|
|
|
|
# Check that printf gets renamed to ub_printf
|
|
printf_rename = next((r for r in renames if r.orig == 'printf'), None)
|
|
assert printf_rename is not None
|
|
assert printf_rename.hdr == 'stdio.h'
|
|
assert printf_rename.new_name == 'ub_printf'
|
|
|
|
def test_rename_with_parser(self):
|
|
"""Test integration between parser and renaming"""
|
|
content = '''file: stdio.h
|
|
sprintf
|
|
printf
|
|
'''
|
|
tools.write_file(self.sympath, content, binary=False)
|
|
parser = RenameSymsParser(self.sympath)
|
|
renames = parser.parse()
|
|
# Use the parser results to rename functions in source code
|
|
source_code = '''
|
|
int sprintf(char *buf, const char *fmt, ...);
|
|
int printf(const char *fmt, ...);
|
|
'''
|
|
result = source_code
|
|
for rename in renames:
|
|
result = rename_function(result, rename.orig, rename.new_name)
|
|
|
|
# Check that both functions were renamed
|
|
assert 'int ub_sprintf(char *buf' in result
|
|
assert 'int ub_printf(const char *fmt' in result
|
|
assert 'int sprintf(char *buf' not in result
|
|
assert 'int printf(const char *fmt' not in result
|
|
|
|
def test_redefine_option(self):
|
|
"""Test symbol redefinition in object files"""
|
|
content = '''file: stdio.h
|
|
printf
|
|
'''
|
|
rename_syms = self.write_tmp(content, 'redefine_symbols.syms')
|
|
|
|
# Create a simple C file with printf (use format string to prevent
|
|
# optimization to puts)
|
|
c_code = '''
|
|
#include <stdio.h>
|
|
void test_function() {
|
|
printf("%s %d\\n", "Hello", 123);
|
|
}
|
|
'''
|
|
c_file_path = self.write_tmp(c_code, 'test.c')
|
|
obj_file_path = c_file_path.replace('.c', '.o')
|
|
# obj file will be cleaned up automatically with tmpdir
|
|
|
|
# Compile the C file to object file
|
|
compile_cmd = ['gcc', '-c', c_file_path, '-o', obj_file_path]
|
|
subprocess.run(compile_cmd, capture_output=True, text=True, check=True)
|
|
|
|
# Check that the object file contains printf symbol
|
|
nm_cmd = ['nm', obj_file_path]
|
|
result = subprocess.run(nm_cmd, capture_output=True, text=True,
|
|
check=True)
|
|
assert 'printf' in result.stdout
|
|
|
|
# Test the parser
|
|
parser = RenameSymsParser(rename_syms)
|
|
renames = parser.parse()
|
|
|
|
# Verify we have the expected rename
|
|
assert len(renames) == 1
|
|
assert renames[0].orig == 'printf'
|
|
assert renames[0].new_name == 'ub_printf'
|
|
assert renames[0].hdr == 'stdio.h'
|
|
|
|
# Test the actual symbol redefinition
|
|
outfiles, modified = SymbolRedefiner.apply_renames(
|
|
[obj_file_path], renames, self.tmpdir.name, 1)
|
|
assert outfiles
|
|
assert modified == 1 # Should have modified 1 file
|
|
obj_file_path = outfiles[0] # Use the output file for checking
|
|
|
|
# Check that the symbol was renamed
|
|
nm_cmd = ['nm', obj_file_path]
|
|
result = subprocess.run(nm_cmd, capture_output=True, text=True,
|
|
check=True)
|
|
|
|
# Should now have ub_printf instead of printf
|
|
out = result.stdout.replace('ub_printf', '')
|
|
assert 'ub_printf' in result.stdout
|
|
assert 'printf' not in out
|
|
|
|
def test_extract_decl(self):
|
|
"""Test extracting function declarations from headers"""
|
|
content = '''#ifndef TEST_H
|
|
#define TEST_H
|
|
|
|
/**
|
|
* sprintf() - Format a string and place it in a buffer
|
|
*
|
|
* @buf: The buffer to place the result into
|
|
* @fmt: The format string to use
|
|
* @...: Arguments for the format string
|
|
*
|
|
* The function returns the number of characters written
|
|
* into @buf.
|
|
*/
|
|
int sprintf(char *buf, const char *fmt, ...)
|
|
\t\t__attribute__ ((format (__printf__, 2, 3)));
|
|
|
|
// Another function without detailed comment
|
|
|
|
int printf(const char *fmt, ...);
|
|
|
|
/**
|
|
* strlen() - Calculate the length of a string
|
|
* @s: The string to measure
|
|
*
|
|
* Return: The length of the string
|
|
*/
|
|
|
|
size_t strlen(const char *s);
|
|
|
|
/* Broken comment block - ends without proper start */
|
|
*/
|
|
#define SOME_MACRO 1
|
|
int broken_comment_func(void);
|
|
|
|
/* Normal function preceded by non-comment content */
|
|
int other_content_func(void);
|
|
|
|
#endif
|
|
'''
|
|
hdr = self.write_tmp(content, 'test.h')
|
|
# Test finding sprintf with comment
|
|
decl = DeclExtractor.extract_decl(hdr, 'sprintf')
|
|
assert decl is not None
|
|
expected = '''/**
|
|
* sprintf() - Format a string and place it in a buffer
|
|
*
|
|
* @buf: The buffer to place the result into
|
|
* @fmt: The format string to use
|
|
* @...: Arguments for the format string
|
|
*
|
|
* The function returns the number of characters written
|
|
* into @buf.
|
|
*/
|
|
int sprintf(char *buf, const char *fmt, ...)
|
|
\t\t__attribute__ ((format (__printf__, 2, 3)));'''
|
|
assert decl == expected, (
|
|
f'Expected:\n{expected}\n\nGot:\n{decl}')
|
|
|
|
# Test finding printf without detailed comment
|
|
decl = DeclExtractor.extract_decl(hdr, 'printf')
|
|
assert decl is not None
|
|
expected = '''// Another function without detailed comment
|
|
|
|
int printf(const char *fmt, ...);'''
|
|
assert decl == expected, (
|
|
f'Expected:\n{expected}\n\nGot:\n{decl}')
|
|
|
|
# Test finding strlen with comment
|
|
strlen_decl = DeclExtractor.extract_decl(hdr, 'strlen')
|
|
assert strlen_decl is not None
|
|
expected_strlen = '''/**
|
|
* strlen() - Calculate the length of a string
|
|
* @s: The string to measure
|
|
*
|
|
* Return: The length of the string
|
|
*/
|
|
|
|
size_t strlen(const char *s);'''
|
|
assert strlen_decl == expected_strlen, (
|
|
f'Expected:\n{expected_strlen}\n\nGot:\n{strlen_decl}')
|
|
|
|
# Test function not found
|
|
assert not DeclExtractor.extract_decl(hdr, 'nonexistent')
|
|
|
|
# Test function with broken comment block (should return None)
|
|
broken_decl = DeclExtractor.extract_decl(hdr, 'broken_comment_func')
|
|
assert broken_decl is not None
|
|
assert 'int broken_comment_func(void);' in broken_decl
|
|
|
|
# Test function preceded by non-comment content (no comment)
|
|
other_decl = DeclExtractor.extract_decl(hdr, 'other_content_func')
|
|
assert other_decl is not None
|
|
assert 'int other_content_func(void);' in other_decl
|
|
|
|
def test_extract_decl_malformed_comment(self):
|
|
"""Test extracting declaration with malformed comment block"""
|
|
# Create header where */ appears but no /** is found backwards
|
|
content = '''#ifndef TEST_H
|
|
#define TEST_H
|
|
|
|
some code here
|
|
*/
|
|
int malformed_func(void);
|
|
|
|
#endif
|
|
'''
|
|
hdr = self.write_tmp(content, 'malformed.h')
|
|
|
|
# This should find the function but no comment (malformed comment)
|
|
decl = DeclExtractor.extract_decl(hdr, 'malformed_func')
|
|
assert decl is not None
|
|
assert decl == 'int malformed_func(void);'
|
|
|
|
def test_symbol_redefiner_coverage(self):
|
|
"""Test SymbolRedefiner edge cases for better coverage"""
|
|
content = '''file: stdio.h
|
|
printf
|
|
custom_func
|
|
'''
|
|
rename_syms = self.write_tmp(content, 'coverage_symbols.syms')
|
|
|
|
# Create C file with defined symbol (not just undefined reference)
|
|
c_code_defined = '''
|
|
void printf(const char *fmt, ...) {
|
|
// Custom printf implementation
|
|
}
|
|
'''
|
|
c_file_defined = self.write_tmp(c_code_defined, 'defined_symbol.c')
|
|
obj_file_defined = c_file_defined.replace('.c', '.o')
|
|
|
|
# Compile to create object with defined symbol
|
|
compile_cmd = ['gcc', '-c', c_file_defined, '-o', obj_file_defined]
|
|
subprocess.run(compile_cmd, capture_output=True, text=True, check=True)
|
|
|
|
# Create C file with no target symbols at all
|
|
c_code_no_symbols = '''
|
|
void other_func(void) {
|
|
int x = 42;
|
|
}
|
|
'''
|
|
c_file_no_symbols = self.write_tmp(c_code_no_symbols, 'no_symbols.c')
|
|
obj_file_no_symbols = c_file_no_symbols.replace('.c', '.o')
|
|
|
|
compile_cmd = ['gcc', '-c', c_file_no_symbols, '-o',
|
|
obj_file_no_symbols]
|
|
subprocess.run(compile_cmd, capture_output=True, text=True, check=True)
|
|
|
|
# Test with both files
|
|
parser = RenameSymsParser(rename_syms)
|
|
renames = parser.parse()
|
|
|
|
# This should process both files - one with defined symbol, one without
|
|
# target symbols
|
|
# Test with verbose output
|
|
stdout = StringIO()
|
|
with contextlib.redirect_stdout(stdout):
|
|
outfiles, modified = SymbolRedefiner.apply_renames(
|
|
[obj_file_defined, obj_file_no_symbols], renames,
|
|
self.tmpdir.name, 1, verbose=True)
|
|
|
|
assert outfiles
|
|
assert len(outfiles) == 2
|
|
# Should have modified 1 file (the one with defined symbol)
|
|
assert modified == 1
|
|
assert 'Copied and modified' in stdout.getvalue()
|
|
|
|
def test_apply_renames_empty_symbols(self):
|
|
"""Test SymbolRedefiner.apply_renames with empty symbol list"""
|
|
# Create a simple object file
|
|
c_code = '''
|
|
void test_func(void) {
|
|
int x = 42;
|
|
}
|
|
'''
|
|
c_file = self.write_tmp(c_code, 'test_empty_syms.c')
|
|
obj_file = c_file.replace('.c', '.o')
|
|
|
|
compile_cmd = ['gcc', '-c', c_file, '-o', obj_file]
|
|
subprocess.run(compile_cmd, capture_output=True, text=True, check=True)
|
|
|
|
# Call apply_renames with empty symbol list
|
|
empty_syms = []
|
|
obj_files = [obj_file]
|
|
result_files, modified = SymbolRedefiner.apply_renames(
|
|
obj_files, empty_syms, self.tmpdir.name, 1)
|
|
|
|
# Should return the original obj_files unchanged and 0 modified
|
|
assert result_files == obj_files
|
|
assert modified == 0
|
|
|
|
def test_api_generation_empty_symbols(self):
|
|
"""Test API generation with empty symbol list"""
|
|
api_file = self.write_tmp('', 'empty_api.h')
|
|
|
|
# Test generate_hdr with empty symbol list
|
|
stderr = StringIO()
|
|
with contextlib.redirect_stderr(stderr):
|
|
result = ApiGenerator.generate_hdr([], '/nonexistent', api_file)
|
|
|
|
# Should return 0 and print warning
|
|
assert result == 0
|
|
assert 'Warning: No symbols found' in stderr.getvalue()
|
|
|
|
def test_parse_args_errors(self):
|
|
"""Test main() with parse_args validation errors"""
|
|
|
|
# Test 1: --redefine with no object files
|
|
test_args = ['test.syms', '--redefine', '--output-dir', '/tmp']
|
|
|
|
stderr = StringIO()
|
|
with contextlib.redirect_stderr(stderr):
|
|
result = main(test_args)
|
|
|
|
assert result == 1
|
|
assert 'Error: --redefine requires at least one object file' in \
|
|
stderr.getvalue()
|
|
|
|
# Test 2: --redefine without --output-dir
|
|
test_args = ['test.syms', '--redefine', 'test.o']
|
|
|
|
stderr = StringIO()
|
|
with contextlib.redirect_stderr(stderr):
|
|
result = main(test_args)
|
|
|
|
assert result == 1
|
|
assert 'Error: --output-dir is required with --redefine' in \
|
|
stderr.getvalue()
|
|
|
|
# Test 3: --api without --include-dir
|
|
test_args = ['test.syms', '--api', 'api.h']
|
|
|
|
stderr = StringIO()
|
|
with contextlib.redirect_stderr(stderr):
|
|
result = main(test_args)
|
|
|
|
assert result == 1
|
|
assert 'Error: --include-dir is required with --api' in stderr.getvalue()
|
|
|
|
def test_main_function_paths(self):
|
|
"""Test main function with different argument combinations"""
|
|
|
|
# Create test files
|
|
content = '''file: stdio.h
|
|
printf
|
|
'''
|
|
rename_syms = self.write_tmp(content, 'rename.syms')
|
|
|
|
c_code = '''
|
|
#include <stdio.h>
|
|
void test_function() {
|
|
printf("%s\\n", "test");
|
|
}
|
|
'''
|
|
c_file = self.write_tmp(c_code, 'main_test.c')
|
|
obj_file = c_file.replace('.c', '.o')
|
|
|
|
compile_cmd = ['gcc', '-c', c_file, '-o', obj_file]
|
|
subprocess.run(compile_cmd, capture_output=True, text=True, check=True)
|
|
|
|
# Test redefine path
|
|
test_args = [rename_syms, '--redefine', obj_file, '--output-dir',
|
|
self.tmpdir.name, '--verbose']
|
|
stdout = StringIO()
|
|
stderr = StringIO()
|
|
with (contextlib.redirect_stdout(stdout),
|
|
contextlib.redirect_stderr(stderr)):
|
|
result = main(test_args)
|
|
assert result == 0
|
|
|
|
# Check that timing message was printed to stderr with verbose
|
|
stderr = stderr.getvalue()
|
|
assert 'Processed 1 files (0 modified) in' in stderr
|
|
|
|
def test_main_function_with_jobs(self):
|
|
"""Test main function with --jobs option to exercise max_workers path"""
|
|
|
|
# Create test files
|
|
content = '''file: stdio.h
|
|
printf
|
|
'''
|
|
rename_syms = self.write_tmp(content, 'rename.syms')
|
|
|
|
c_code = '''
|
|
#include <stdio.h>
|
|
void test_function() {
|
|
printf("%s\\n", "test");
|
|
}
|
|
'''
|
|
c_file = self.write_tmp(c_code, 'jobs_test.c')
|
|
obj_file = c_file.replace('.c', '.o')
|
|
|
|
compile_cmd = ['gcc', '-c', c_file, '-o', obj_file]
|
|
subprocess.run(compile_cmd, capture_output=True, text=True, check=True)
|
|
|
|
# Test redefine path with explicit --jobs option
|
|
test_args = [rename_syms, '--redefine', obj_file, '--output-dir',
|
|
self.tmpdir.name, '--jobs', '2', '--verbose']
|
|
stdout = StringIO()
|
|
stderr = StringIO()
|
|
with (contextlib.redirect_stdout(stdout),
|
|
contextlib.redirect_stderr(stderr)):
|
|
result = main(test_args)
|
|
assert result == 0
|
|
|
|
# Check that timing message includes thread count
|
|
stderr = stderr.getvalue()
|
|
assert 'Processed 1 files (0 modified) in' in stderr
|
|
|
|
# Test API generation path with verbose output
|
|
fake_stdio = '''#ifndef STDIO_H
|
|
#define STDIO_H
|
|
int printf(const char *fmt, ...);
|
|
#endif
|
|
'''
|
|
self.write_tmp(fake_stdio, 'stdio.h')
|
|
api_file = self.write_tmp('', 'main_api.h')
|
|
|
|
test_args = [rename_syms, '--api', api_file, '--include-dir',
|
|
self.tmpdir.name, '--output-dir', self.tmpdir.name,
|
|
'--verbose']
|
|
|
|
stdout = StringIO()
|
|
with contextlib.redirect_stdout(stdout):
|
|
result = main(test_args)
|
|
|
|
assert result == 0
|
|
assert 'Generated API header:' in stdout.getvalue()
|
|
|
|
def test_main_api_generation_failure(self):
|
|
"""Test main() when API generation fails"""
|
|
|
|
# Create test files that will cause API generation to fail
|
|
content = '''file: nonexistent.h
|
|
missing_function
|
|
'''
|
|
rename_syms = self.write_tmp(content, 'failing_api.syms')
|
|
api_file = self.write_tmp('', 'failing_api.h')
|
|
|
|
# This will fail because nonexistent.h doesn't exist
|
|
test_args = [rename_syms, '--api', api_file, '--include-dir',
|
|
'/nonexistent_dir', '--output-dir', self.tmpdir.name]
|
|
|
|
stderr = StringIO()
|
|
with contextlib.redirect_stderr(stderr):
|
|
result = main(test_args)
|
|
|
|
# Should return 1 because API generation failed
|
|
assert result == 1
|
|
assert 'Missing header files:' in stderr.getvalue()
|
|
|
|
def test_api_generation(self):
|
|
"""Test API header generation"""
|
|
content = '''file: stdio.h
|
|
printf
|
|
'''
|
|
tools.write_file(self.sympath, content, binary=False)
|
|
|
|
api = self.write_tmp('', 'api.h')
|
|
parser = RenameSymsParser(self.sympath)
|
|
renames = parser.parse()
|
|
|
|
# Generate the API header - this will fail since stdio.h is not found
|
|
captured = StringIO()
|
|
with contextlib.redirect_stderr(captured):
|
|
result = ApiGenerator.generate_hdr(renames, '/nonexistent', api)
|
|
|
|
# This test expects failure since stdio.h header is not available
|
|
assert result == 1
|
|
|
|
def test_api_generation_missing_headers(self):
|
|
"""Test API generation error handling for missing header files"""
|
|
content = '''file: nonexistent.h
|
|
missing_func
|
|
'''
|
|
tools.write_file(self.sympath, content, binary=False)
|
|
|
|
api = self.write_tmp('', 'api.h')
|
|
parser = RenameSymsParser(self.sympath)
|
|
renames = parser.parse()
|
|
|
|
# This should exit with an error
|
|
captured = StringIO()
|
|
with contextlib.redirect_stderr(captured):
|
|
result = ApiGenerator.generate_hdr(renames, '/nonexistent', api)
|
|
assert result == 1, f'Expected return code 1, got {result}'
|
|
|
|
assert 'Missing header files:' in captured.getvalue()
|
|
assert 'nonexistent.h' in captured.getvalue()
|
|
|
|
def test_api_generation_missing_functions(self):
|
|
"""Test API generation error handling for missing functions"""
|
|
# Create a fake stdio.h with a different function for testing
|
|
fake_stdio_content = '''#ifndef STDIO_H
|
|
#define STDIO_H
|
|
int existing_func(void);
|
|
#endif
|
|
'''
|
|
self.write_tmp(fake_stdio_content, 'stdio.h')
|
|
include_dir = self.tmpdir.name
|
|
|
|
content = '''file: stdio.h
|
|
nonexistent_function
|
|
'''
|
|
tools.write_file(self.sympath, content, binary=False)
|
|
|
|
api = self.write_tmp('', 'api.h')
|
|
parser = RenameSymsParser(self.sympath)
|
|
renames = parser.parse()
|
|
|
|
# This should exit with an error for missing function declarations
|
|
captured = StringIO()
|
|
with contextlib.redirect_stderr(captured):
|
|
result = ApiGenerator.generate_hdr(renames, include_dir, api)
|
|
assert result == 1, f'Expected return code 1, got {result}'
|
|
|
|
assert 'Missing function declarations:' in captured.getvalue()
|
|
assert 'nonexistent_function in stdio.h' in captured.getvalue()
|
|
|
|
def test_parser_exceptions(self):
|
|
"""Test parser error handling for invalid formats"""
|
|
|
|
# Test 1: Symbol without header file
|
|
inval1 = '''# Test file with symbol before header
|
|
printf
|
|
file: stdio.h
|
|
scanf
|
|
'''
|
|
temp_path1 = self.write_tmp(inval1, 'test1.syms')
|
|
parser = RenameSymsParser(temp_path1)
|
|
with self.assertRaises(ValueError) as cm:
|
|
parser.parse()
|
|
self.assertIn("Symbol 'printf' found without a header file directive",
|
|
str(cm.exception))
|
|
|
|
# Test 2: Invalid format (non-indented, non-file line)
|
|
inval2 = '''file: stdio.h
|
|
printf
|
|
invalid_line_here
|
|
scanf
|
|
'''
|
|
temp_path2 = self.write_tmp(inval2, 'test2.syms')
|
|
parser = RenameSymsParser(temp_path2)
|
|
with self.assertRaises(ValueError) as cm:
|
|
parser.parse()
|
|
self.assertIn("Invalid format - symbols must be indented",
|
|
str(cm.exception))
|
|
|
|
def test_main_dump_symbols(self):
|
|
"""Test main function with dump option"""
|
|
content = '''file: stdio.h
|
|
printf
|
|
sprintf
|
|
|
|
file: string.h
|
|
strlen
|
|
'''
|
|
rename_syms = self.write_tmp(content, 'test_symbols.syms')
|
|
|
|
# Mock sys.argv to simulate command line arguments
|
|
original_argv = sys.argv
|
|
try:
|
|
sys.argv = ['build_api.py', rename_syms, '--dump']
|
|
|
|
# Capture stdout to check dump output
|
|
captured = StringIO()
|
|
with contextlib.redirect_stdout(captured):
|
|
result = main()
|
|
|
|
assert result == 0
|
|
output = captured.getvalue()
|
|
assert all(item in output for item in
|
|
['printf', 'sprintf', 'strlen', 'stdio.h', 'string.h'])
|
|
|
|
finally:
|
|
sys.argv = original_argv
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|