1# SPDX-License-Identifier: GPL-2.0
2# Copyright (c) 2023 Tobias Deiminger <tdmg@linutronix.de>
3
4"""Test for unexpected leftovers after make clean"""
5
6import itertools
7import os
8import pathlib
9import shutil
10import sys
11
12import pytest
13
14# pylint: disable=redefined-outer-name
15
16
17@pytest.fixture
18def tmp_copy_of_builddir(u_boot_config, tmp_path):
19    """For each test, provide a temporary copy of the initial build directory."""
20    if os.path.realpath(u_boot_config.source_dir) == os.path.realpath(
21        u_boot_config.build_dir
22    ):
23        pytest.skip("Leftover detection requires out of tree build.")
24        return None
25    shutil.copytree(
26        u_boot_config.build_dir,
27        tmp_path,
28        symlinks=True,
29        dirs_exist_ok=True,
30    )
31    return tmp_path
32
33
34@pytest.fixture(scope="module")
35def run_make(u_boot_log):
36    """Provide function to run and log make without connecting to u-boot console."""
37    runner = u_boot_log.get_runner("make", sys.stdout)
38
39    def _run_make(build_dir, target):
40        cmd = ["make", f"O={build_dir}", target]
41        runner.run(cmd)
42
43    yield _run_make
44    runner.close()
45
46
47@pytest.fixture(scope="module")
48def most_generated_files():
49    """Path.glob style patterns to describe what should be removed by 'make clean'."""
50    return (
51        "**/*.c",
52        "**/*.dtb",
53        "**/*.dtbo",
54        "**/*.o",
55        "**/*.py",
56        "**/*.pyc",
57        "**/*.so",
58        "**/*.srec",
59        "u-boot*",
60        "[svt]pl/u-boot*",
61    )
62
63
64@pytest.fixture(scope="module")
65def all_generated_files(most_generated_files):
66    """Path.glob style patterns to describe what should be removed by 'make mrproper'."""
67    return most_generated_files + (".config", "**/*.h")
68
69
70def find_files(search_dir, include_patterns, exclude_dirs=None):
71    """Find files matching include_patterns, unless it's in one of exclude_dirs.
72
73    include_patterns -- Path.glob style pattern relative to search dir
74    exclude_dir -- directories to exclude, expected relative to search dir
75    """
76    matches = []
77    exclude_dirs = [] if exclude_dirs is None else exclude_dirs
78    for abs_path in itertools.chain.from_iterable(
79        pathlib.Path(search_dir).glob(pattern) for pattern in include_patterns
80    ):
81        if abs_path.is_dir():
82            continue
83        rel_path = pathlib.Path(os.path.relpath(abs_path, search_dir))
84        if not any(
85            rel_path.is_relative_to(exclude_dir) for exclude_dir in exclude_dirs
86        ):
87            matches.append(rel_path)
88    return matches
89
90
91def test_clean(run_make, tmp_copy_of_builddir, most_generated_files):
92    """Test if 'make clean' deletes most generated files."""
93    run_make(tmp_copy_of_builddir, "clean")
94    leftovers = find_files(
95        tmp_copy_of_builddir,
96        most_generated_files,
97        exclude_dirs=["scripts", "test/overlay"],
98    )
99    assert not leftovers, f"leftovers: {', '.join(map(str, leftovers))}"
100
101
102def test_mrproper(run_make, tmp_copy_of_builddir, all_generated_files):
103    """Test if 'make mrproper' deletes current configuration and all generated files."""
104    run_make(tmp_copy_of_builddir, "mrproper")
105    leftovers = find_files(
106        tmp_copy_of_builddir,
107        all_generated_files,
108        exclude_dirs=["test/overlay"],
109    )
110    assert not leftovers, f"leftovers: {', '.join(map(str, leftovers))}"
111