test/py: Add a test for ulib functionality
Provide a test that checks that ulib operates as expected.
Add a .gitignore file for the executables thus created
There is a strange interaction with PLATFORM_LIBS which can cause the
examples to fail to build via 'make qcheck':
- CI runs 'make qcheck'
- the main Makefile sets PLATFORM_LIBS
- test/run calls test.py
- at some point test_ulib_demos() starts, with PLATFORM_LIBS set
- the test calls 'make' on examples/ulib/Makefile
- PLATFORM_LIBS is left alone, since it already has a value
- lSDL ends up not being in the link line
Thank you to Claude for helping to debug this and figure out the
PLATFORM_LIBS interaction.
Series-to: concept
Series-cc: heinrich
Cover-letter:
ulib: Complete initial U-Boot library
This series completes support for building U-Boot as a shared or static
library, enabling reuse of U-Boot functionality in external programs and
test suites.
The U-Boot library (ulib) allows developers to:
- Link against U-Boot functionality without a full U-Boot image
- Use U-Boot's OS abstraction layer, drivers, and utility functions
- Build test programs that can exercise U-Boot code in isolation
- Create applications that benefit from U-Boot's hardware support
Key features:
- Builds both shared (libu-boot.so) and static (libu-boot.a) libraries
- Preserves U-Boot linker lists for proper driver/subsystem init
- Configurable symbol renaming to avoid conflicts with system libraries
- Generated API headers with renamed function declarations
- Documentation and working examples
- Currently only supports sandbox architecture
The series includes:
- More build-infrastructure and Makefile integration
- Python-based mechanism for symbol renaming and API generation
- Test programs demonstrating basic library usage
- A simple example program showing real-world usage patterns
Symbol renaming ensures that U-Boot functions don't conflict with system
libraries. For example, printf() remains the standard library function
while ub_printf() provides access to U-Boot's printf implementation.
This is handled automatically during the build process.
The library excludes main() to allow external programs to provide their own
entry points while still accessing U-Boot functionality through ulib_init()
and ulib_uninit().
For example:
#include <u-boot-lib.h>
#include <u-boot-api.h>
int main(int argc, char *argv[])
{
if (ulib_init(argv[0]) < 0)
return 1;
ub_printf("Hello from U-Boot library!\n");
ulib_uninit();
return 0;
}
License implications are documented - the GPL-2.0+ license applies to
any programs linked with the library, requiring source code distribution
for compliant usage.
Future work will look at expanding support to other architectures.
END
Co-developed-by: Claude <noreply@anthropic.com>
Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
2
examples/ulib/.gitignore
vendored
Normal file
2
examples/ulib/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/demo
|
||||
/demo_static
|
||||
@@ -10,21 +10,19 @@
|
||||
|
||||
void demo_show_banner(void)
|
||||
{
|
||||
ub_printf("=================================\n");
|
||||
ub_printf(" U-Boot Library Demo Helper\n");
|
||||
ub_printf("=================================\n");
|
||||
ub_printf("U-Boot Library Demo Helper\n");
|
||||
ub_printf("==========================\n");
|
||||
}
|
||||
|
||||
void demo_show_footer(void)
|
||||
{
|
||||
ub_printf("=================================\n");
|
||||
ub_printf(" Demo Complete!\n");
|
||||
ub_printf("=================================\n");
|
||||
ub_printf("Demo complete\n");
|
||||
}
|
||||
|
||||
int demo_add_numbers(int a, int b)
|
||||
{
|
||||
ub_printf("Helper: Adding %d + %d = %d\n", a, b, a + b);
|
||||
ub_printf("helper: Adding %d + %d = %d\n", a, b, a + b);
|
||||
|
||||
return a + b;
|
||||
}
|
||||
|
||||
141
test/py/tests/test_ulib.py
Normal file
141
test/py/tests/test_ulib.py
Normal file
@@ -0,0 +1,141 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2025, Canonical Ltd.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import pytest
|
||||
import utils
|
||||
|
||||
def check_output(out):
|
||||
"""Check output from the ulib test"""
|
||||
assert 'Hello, world from ub_printf' in out
|
||||
assert '- U-Boot' in out
|
||||
assert 'Uses libc printf before ulib_init' in out
|
||||
assert 'another printf()' in out
|
||||
|
||||
@pytest.mark.buildconfigspec("ulib")
|
||||
def test_ulib_shared(ubman):
|
||||
"""Test the ulib shared library test program"""
|
||||
|
||||
build = ubman.config.build_dir
|
||||
prog = os.path.join(build, 'test', 'ulib', 'ulib_test')
|
||||
|
||||
# Skip test if ulib_test doesn't exist (clang)
|
||||
if not os.path.exists(prog):
|
||||
pytest.skip('ulib_test not found - library build may be disabled')
|
||||
|
||||
out = utils.run_and_log(ubman, [prog], cwd=build)
|
||||
check_output(out)
|
||||
assert 'dynamically linked' in out
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
def test_ulib_static(ubman):
|
||||
"""Test the ulib static library test program"""
|
||||
|
||||
build = ubman.config.build_dir
|
||||
prog = os.path.join(build, 'test', 'ulib', 'ulib_test_static')
|
||||
|
||||
# Skip test if ulib_test_static doesn't exist (clang)
|
||||
if not os.path.exists(prog):
|
||||
pytest.skip('ulib_test_static not found - library build may be disabled')
|
||||
|
||||
out = utils.run_and_log(ubman, [prog])
|
||||
check_output(out)
|
||||
assert 'statically linked' in out
|
||||
|
||||
def check_demo_output(ubman, out):
|
||||
"""Check output from the ulib demo programs exactly line by line"""
|
||||
lines = out.split('\n')
|
||||
|
||||
# Read the actual system version from /proc/version
|
||||
with open('/proc/version', 'r', encoding='utf-8') as f:
|
||||
proc_version = f.read().strip()
|
||||
|
||||
expected = [
|
||||
'U-Boot Library Demo Helper\r',
|
||||
'==========================\r',
|
||||
'System version:helper: Adding 42 + 13 = 55\r',
|
||||
'=================================\r',
|
||||
'Demo complete\r',
|
||||
f'U-Boot version: {ubman.u_boot_version_string}',
|
||||
'',
|
||||
f' {proc_version}',
|
||||
'',
|
||||
'Read 1 line(s) using U-Boot library functions.',
|
||||
'Helper function result: 55',
|
||||
''
|
||||
]
|
||||
|
||||
assert len(lines) == len(expected), \
|
||||
f"Expected {len(expected)} lines, got {len(lines)}"
|
||||
|
||||
for i, expected in enumerate(expected):
|
||||
# Exact match for all other lines
|
||||
assert lines[i] == expected, \
|
||||
f"Line {i}: expected '{expected}', got '{lines[i]}'"
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
def test_ulib_demos(ubman):
|
||||
"""Test both ulib demo programs (dynamic and static)."""
|
||||
|
||||
build = ubman.config.build_dir
|
||||
src = ubman.config.source_dir
|
||||
examples = os.path.join(src, 'examples', 'ulib')
|
||||
test_program = os.path.join(build, 'test', 'ulib', 'ulib_test')
|
||||
|
||||
# Skip test if ulib_test doesn't exist (clang)
|
||||
if not os.path.exists(test_program):
|
||||
pytest.skip('ulib_test not found - library build may be disabled')
|
||||
|
||||
# Build the demo programs - clean first to ensure fresh build, since this
|
||||
# test is run in the source directory
|
||||
cmd = ['make', 'clean']
|
||||
utils.run_and_log(ubman, cmd, cwd=examples)
|
||||
|
||||
cmd = ['make', f'UBOOT_BUILD={os.path.abspath(build)}', f'srctree={src}']
|
||||
utils.run_and_log(ubman, cmd, cwd=examples)
|
||||
|
||||
# Test static demo program
|
||||
demo_static = os.path.join(examples, 'demo_static')
|
||||
out_static = utils.run_and_log(ubman, [demo_static])
|
||||
check_demo_output(ubman, out_static)
|
||||
|
||||
# Test dynamic demo program (with proper LD_LIBRARY_PATH)
|
||||
demo = os.path.join(examples, 'demo')
|
||||
env = os.environ.copy()
|
||||
env['LD_LIBRARY_PATH'] = os.path.abspath(build)
|
||||
out_dynamic = utils.run_and_log(ubman, [demo], env=env)
|
||||
check_demo_output(ubman, out_dynamic)
|
||||
|
||||
@pytest.mark.boardspec('sandbox')
|
||||
def test_ulib_api_header(ubman):
|
||||
"""Test that the u-boot-api.h header is generated correctly."""
|
||||
|
||||
hdr = os.path.join(ubman.config.build_dir, 'include', 'u-boot-api.h')
|
||||
|
||||
# Skip if header doesn't exist (clang)
|
||||
if not os.path.exists(hdr):
|
||||
pytest.skip('u-boot-api.h not found - library build may be disabled')
|
||||
|
||||
# Read and verify header content
|
||||
with open(hdr, 'r', encoding='utf-8') as inf:
|
||||
out = inf.read()
|
||||
|
||||
# Check header guard
|
||||
assert '#ifndef __ULIB_API_H' in out
|
||||
assert '#define __ULIB_API_H' in out
|
||||
assert '#endif /* __ULIB_API_H */' in out
|
||||
|
||||
# Check required includes
|
||||
assert '#include <stdarg.h>' in out
|
||||
assert '#include <stddef.h>' in out
|
||||
|
||||
# Check for renamed function declarations
|
||||
assert 'ub_printf' in out
|
||||
assert 'ub_snprintf' in out
|
||||
assert 'ub_vprintf' in out
|
||||
|
||||
# Check that functions have proper signatures
|
||||
assert 'ub_printf(const char *fmt, ...)' in out
|
||||
assert 'ub_snprintf(char *buf, size_t size, const char *fmt, ...)' in out
|
||||
assert 'ub_vprintf(const char *fmt, va_list args)' in out
|
||||
8
test/run
8
test/run
@@ -18,6 +18,14 @@ quiet=-q
|
||||
# Clean up things the Makefile created
|
||||
unset MAKE MAKEFLAGS MAKELEVEL MAKEOVERRIDES MAKE_TERMERR MAKE_TERMOUT
|
||||
|
||||
# Unset this since this script is generally run from 'make qcheck' et al, which
|
||||
# targets are in no-dot-config-targets and thus dot-config is 0 and thus
|
||||
# config.mk was not included in the main Makefile, thus PLATFORM_LIBS does not
|
||||
# have the arch-specific settings (e.g. SDL libraries on sandbox). Better to
|
||||
# leave it empty than have it be wrong. This particularly affects
|
||||
# example/ulib/Makefile when called from 'make qcheck'
|
||||
unset PLATFORM_LIBS
|
||||
|
||||
# Select test attributes
|
||||
ut_mark_expr=test_ut
|
||||
if [ "$1" = "quick" ]; then
|
||||
|
||||
Reference in New Issue
Block a user