test: Find leftovers after clean/mrproper
Docs describe 'make clean' to delete most generated files, 'make
mrproper' to delete current configuration and all generated files. This
test tries to assert it.
Idea is to search remaining files by patterns in copies of the initial
out-of-source build, which has two advantages:
- looking in an out-of-source build dir allows to tell generated source
code from committed source code
- copying is fast (compared to rebuilding each time) which allows to do
a "world clean"
Signed-off-by: Tobias Deiminger <tdmg@linutronix.de>
diff --git a/test/py/tests/test_cleanup_build.py b/test/py/tests/test_cleanup_build.py
new file mode 100644
index 0000000..5206ff7
--- /dev/null
+++ b/test/py/tests/test_cleanup_build.py
@@ -0,0 +1,105 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2023 Tobias Deiminger <tdmg@linutronix.de>
+
+"""Test for unexpected leftovers after make clean"""
+
+import itertools
+import os
+import pathlib
+import shutil
+import sys
+
+import pytest
+
+# pylint: disable=redefined-outer-name
+
+
+@pytest.fixture
+def tmp_copy_of_builddir(u_boot_config, tmp_path):
+ """For each test, provide a temporary copy of the initial build directory."""
+ shutil.copytree(
+ u_boot_config.build_dir,
+ tmp_path,
+ symlinks=True,
+ dirs_exist_ok=True,
+ )
+ return tmp_path
+
+
+@pytest.fixture(scope="module")
+def run_make(u_boot_log):
+ """Provide function to run and log make without connecting to u-boot console."""
+ runner = u_boot_log.get_runner("make", sys.stdout)
+
+ def _run_make(build_dir, target):
+ cmd = ["make", f"O={build_dir}", target]
+ runner.run(cmd)
+
+ yield _run_make
+ runner.close()
+
+
+@pytest.fixture(scope="module")
+def most_generated_files():
+ """Path.glob style patterns to describe what should be removed by 'make clean'."""
+ return (
+ "**/*.c",
+ "**/*.dtb",
+ "**/*.dtbo",
+ "**/*.o",
+ "**/*.py",
+ "**/*.pyc",
+ "**/*.so",
+ "**/*.srec",
+ "u-boot*",
+ "[svt]pl/u-boot*",
+ )
+
+
+@pytest.fixture(scope="module")
+def all_generated_files(most_generated_files):
+ """Path.glob style patterns to describe what should be removed by 'make mrproper'."""
+ return most_generated_files + (".config", "**/*.h")
+
+
+def find_files(search_dir, include_patterns, exclude_dirs=None):
+ """Find files matching include_patterns, unless it's in one of exclude_dirs.
+
+ include_patterns -- Path.glob style pattern relative to search dir
+ exclude_dir -- directories to exclude, expected relative to search dir
+ """
+ matches = []
+ exclude_dirs = [] if exclude_dirs is None else exclude_dirs
+ for abs_path in itertools.chain.from_iterable(
+ pathlib.Path(search_dir).glob(pattern) for pattern in include_patterns
+ ):
+ if abs_path.is_dir():
+ continue
+ rel_path = pathlib.Path(os.path.relpath(abs_path, search_dir))
+ if not any(
+ rel_path.is_relative_to(exclude_dir) for exclude_dir in exclude_dirs
+ ):
+ matches.append(rel_path)
+ return matches
+
+
+def test_clean(run_make, tmp_copy_of_builddir, most_generated_files):
+ """Test if 'make clean' deletes most generated files."""
+ run_make(tmp_copy_of_builddir, "clean")
+ leftovers = find_files(
+ tmp_copy_of_builddir,
+ most_generated_files,
+ exclude_dirs=["scripts", "test/overlay"],
+ )
+ assert not leftovers, f"leftovers: {', '.join(map(str, leftovers))}"
+
+
+def test_mrproper(run_make, tmp_copy_of_builddir, all_generated_files):
+ """Test if 'make mrproper' deletes current configuration and all generated files."""
+ run_make(tmp_copy_of_builddir, "mrproper")
+ leftovers = find_files(
+ tmp_copy_of_builddir,
+ all_generated_files,
+ exclude_dirs=["test/overlay"],
+ )
+ assert not leftovers, f"leftovers: {', '.join(map(str, leftovers))}"