test/py: Add test for Makefile dependency tracking

Add a comprehensive test to verify that Makefile dependency tracking
works correctly when FORCE is removed from build rules. The test
ensures that targets are rebuilt when dependencies change but not
when they remain unchanged.

Co-developed-by: Claude <noreply@anthropic.com>
Co-developed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass
2025-09-09 08:10:51 -06:00
parent eeaaa9c448
commit 2699626960

View File

@@ -0,0 +1,233 @@
# SPDX-License-Identifier: GPL-2.0+
# Copyright (c) 2025 Simon Glass
# Written by Simon Glass <sjg@chromium.org>
"""Test U-Boot Makefile dependency tracking
This test verifies that Makefile rules properly track dependencies and rebuild
targets when dependency files change. Specifically, it tests the hwids_to_dtsi
rule which had FORCE removed to rely on proper dependency tracking.
"""
import os
import time
from types import SimpleNamespace
import pytest
import utils
# Time to wait between file modifications to ensure different timestamps
TIMESTAMP_DELAY = 2
def info(env, message):
"""Log an informational message
Args:
env (SimpleNamespace): Test environment with ubman logger
message (str): Message to log
"""
env.ubman.log.info(message)
def run_make(env, message, target='test'):
"""Run a make command with logging
Args:
env (SimpleNamespace): Test environment with ubman logger and paths
message (str): Log message describing the build
target (str): Make target to build (default: 'test')
Returns:
float or None: File modification time of target, or None if
clean/no target
"""
info(env, message)
utils.run_and_log(env.ubman, ['make', '-f', env.makefile_name, target],
cwd=env.tmpdir)
if target == 'clean' or not os.path.exists(env.test_target):
return None
return os.path.getmtime(env.test_target)
def setup_hwids_env(ubman):
"""Set up the test environment with hwids files and Makefile
Args:
ubman (ConsoleBase): ubman fixture
Returns:
SimpleNamespace: Test environment with paths and config
"""
# Create a temporary board directory structure for testing
tmpdir = os.path.join(ubman.config.result_dir, 'test_make_dep')
temp_board_dir = os.path.join(tmpdir, 'board', 'test_vendor',
'test_board')
hwids_dir = os.path.join(temp_board_dir, 'hwids')
os.makedirs(tmpdir, exist_ok=True)
os.makedirs(hwids_dir, exist_ok=True)
# Create test hwids files
hwidmap_file = os.path.join(hwids_dir, 'compatible.hwidmap')
hwid_txt_file = os.path.join(hwids_dir, 'test_hwid.txt')
# Write initial hwidmap content
with open(hwidmap_file, 'w', encoding='utf-8') as outf:
outf.write('# Test hardware ID mapping\n')
outf.write('TEST_DEVICE_1=test,device1\n')
# Write initial hwid txt content
with open(hwid_txt_file, 'w', encoding='utf-8') as outf:
outf.write('TEST_DEVICE_1\n')
outf.write('Some initial hardware info\n')
# Assume hwids_to_dtsi rule exists in Makefile.lib
# Create a simple test Makefile to verify dependency behavior
test_makefile = os.path.join(tmpdir, 'test_deps.mk')
test_target = os.path.join(tmpdir, 'test_output.dtsi')
# Use relative paths in makefile since we cd to tmpdir
rel_hwids_dir = os.path.relpath(hwids_dir, tmpdir)
rel_target = os.path.relpath(test_target, tmpdir)
makefile_content = f'''# Test dependency tracking
HWIDS_DIR := {rel_hwids_dir}
TARGET := {rel_target}
# Simulate the hwids_to_dtsi rule without FORCE
$(TARGET): $(HWIDS_DIR)/compatible.hwidmap $(wildcard $(HWIDS_DIR)/*.txt)
\t@echo "Building target because dependencies changed"
\t@echo "/* Generated from hwids */" > $@
\t@echo "// hwidmap: $$(stat -c %Y $(HWIDS_DIR)/compatible.hwidmap)" >> $@
\t@echo "// txt files: $$(find $(HWIDS_DIR) -name '*.txt' " \\
"-exec stat -c %Y {{}} \\; | sort -n | tail -1)" >> $@
\t@echo "// Generated at: $$(date +%s)" >> $@
.PHONY: clean test
clean:
\trm -f $(TARGET)
test: $(TARGET)
\t@echo "Target built successfully"
'''
with open(test_makefile, 'w', encoding='utf-8') as outf:
outf.write(makefile_content)
return SimpleNamespace(
ubman=ubman,
tmpdir=tmpdir,
hwids_dir=hwids_dir,
hwidmap_file=hwidmap_file,
hwid_txt_file=hwid_txt_file,
test_makefile=test_makefile,
test_target=test_target,
makefile_name=os.path.basename(test_makefile)
)
def check_initial_build(env):
"""Test initial build creates target and no-change rebuild doesn't rebuild
Args:
env (SimpleNamespace): Test environment with paths and ubman logger
Returns:
tuple: (initial_mtime, second_mtime) modification times
"""
run_make(env, 'Running initial build...', 'clean')
initial_mtime = run_make(env, 'Building test target...', 'test')
assert os.path.exists(env.test_target), \
'Target file should be created on initial build'
info(env, f'Initial target created at {initial_mtime}')
# Wait a bit to ensure timestamp differences
time.sleep(TIMESTAMP_DELAY)
# Run build again - target should NOT be rebuilt (no dependencies changed)
mtime = run_make(env, 'Running build again without changes...')
assert mtime == initial_mtime, \
f'Target should not rebuild when dependencies unchanged ' \
f'(initial: {initial_mtime}, second: {mtime})'
return initial_mtime, mtime
def check_hwidmap_change(env, previous_mtime):
"""Test that changing hwidmap file triggers rebuild
Args:
env (SimpleNamespace): Test environment with paths and ubman logger
previous_mtime (float): Previous modification time to compare against
Returns:
float: New modification time after rebuild
"""
time.sleep(TIMESTAMP_DELAY)
with open(env.hwidmap_file, 'a', encoding='utf-8') as outf:
outf.write('TEST_DEVICE_2=test,device2\n')
# Build again - target SHOULD be rebuilt due to hwidmap change
mtime = run_make(env, 'Building after hwidmap change...')
assert mtime > previous_mtime, \
f'Target should rebuild when hwidmap changes ' \
f'(previous: {previous_mtime}, new: {mtime})'
return mtime
def check_txt_changes(env, previous_mtime):
"""Test that changing txt files triggers rebuild
Args:
env (SimpleNamespace): Test environment with paths and ubman logger
previous_mtime (float): Previous modification time to compare against
Returns:
float: Final modification time after all changes
"""
# Test existing txt file modification
time.sleep(TIMESTAMP_DELAY)
with open(env.hwid_txt_file, 'a', encoding='utf-8') as outf:
outf.write('Additional hardware info\n')
modified_mtime = run_make(env, 'Building after txt file change...')
assert modified_mtime > previous_mtime, \
f'Target should rebuild when txt file changes ' \
f'(previous: {previous_mtime}, modified: {modified_mtime})'
# Test new txt file addition
time.sleep(TIMESTAMP_DELAY)
new_txt_file = os.path.join(env.hwids_dir, 'new_hwid.txt')
with open(new_txt_file, 'w', encoding='utf-8') as outf:
outf.write('NEW_DEVICE\nNew hardware information\n')
mtime = run_make(env, 'Building after adding new txt file...')
assert mtime > modified_mtime, \
f'Target should rebuild when new txt file added ' \
f'(modified: {modified_mtime}, final: {mtime})'
return mtime
@pytest.mark.boardspec('sandbox')
def test_dep_hwids(ubman):
"""Test that Makefile dependency tracking works without FORCE
This test verifies that the hwids_to_dtsi rule (which had FORCE removed)
still properly rebuilds when dependency files are modified.
"""
env = setup_hwids_env(ubman)
info(env, 'Testing initial build and no-change rebuild...')
_, second_mtime = check_initial_build(env)
info(env, 'Testing hwidmap file change triggers rebuild...')
third_mtime = check_hwidmap_change(env, second_mtime)
info(env, 'Testing txt file changes trigger rebuild...')
check_txt_changes(env, third_mtime)
info(env, 'All dependency tracking tests passed!')