1"""Test cases for utils/check-package.
2
3It does not inherit from infra.basetest.BRTest and therefore does not generate
4a logfile. Only when the tests fail there will be output to the console.
5
6The make target ('make check-package') is already used by the job
7'check-package' and won't be tested here.
8"""
9import os
10import subprocess
11import unittest
12
13import infra
14
15
16def call_script(args, env, cwd):
17    """Call a script and return stdout and stderr as lists."""
18    out, err = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE,
19                                stderr=subprocess.PIPE, env=env,
20                                universal_newlines=True).communicate()
21    return out.splitlines(), err.splitlines()
22
23
24class TestCheckPackage(unittest.TestCase):
25    """Test the various ways the script can be called.
26
27    The script can be called either using relative path, absolute path or from
28    PATH.
29
30    The files to be checked can be passed as arguments using either relative
31    path or absolute path.
32
33    When in in-tree mode (without -b) some in-tree files and also all
34    out-of-tree files are ignored.
35
36    When in out-tree mode (with -b) the script does generate warnings for these
37    but ignores external.mk.
38    """
39
40    WITH_EMPTY_PATH = {}
41    WITH_UTILS_IN_PATH = {"PATH": infra.basepath("utils") + ":" + os.environ["PATH"]}
42    relative = [
43        # base_script           base_file               rel_script               rel_file                rel_cwd
44        ["utils/check-package", "package/atop/atop.mk", "./utils/check-package", "package/atop/atop.mk", ""],
45        ["utils/check-package", "package/atop/atop.mk", "./utils/check-package", "./package/atop/atop.mk", ""],
46        ["utils/check-package", "package/atop/atop.mk", "../../utils/check-package", "atop.mk", "package/atop"],
47        ["utils/check-package", "package/atop/atop.mk", "../../utils/check-package", "./atop.mk", "package/atop"],
48        ["utils/check-package", "package/atop/atop.mk", "../utils/check-package", "atop/atop.mk", "package"],
49        ["utils/check-package", "package/atop/atop.mk", "../utils/check-package", "./atop/atop.mk", "package"],
50        ["utils/check-package", "package/atop/Config.in", "./utils/check-package", "package/atop/Config.in", ""],
51        ["utils/check-package", "package/atop/Config.in", "./utils/check-package", "./package/atop/Config.in", ""],
52        ["utils/check-package", "package/atop/Config.in", "../../utils/check-package", "Config.in", "package/atop"],
53        ["utils/check-package", "package/atop/Config.in", "../../utils/check-package", "./Config.in", "package/atop"],
54        ["utils/check-package", "package/atop/Config.in", "../utils/check-package", "atop/Config.in", "package"],
55        ["utils/check-package", "package/atop/Config.in", "../utils/check-package", "./atop/Config.in", "package"]]
56
57    def assert_file_was_processed(self, stderr):
58        """Infer from check-package stderr if at least one file was processed
59        and fail otherwise."""
60        self.assertIn("lines processed", stderr[0], stderr)
61        processed = int(stderr[0].split()[0])
62        self.assertGreater(processed, 0)
63
64    def assert_file_was_ignored(self, stderr):
65        """Infer from check-package stderr if no file was processed and fail
66        otherwise."""
67        self.assertIn("lines processed", stderr[0], stderr)
68        processed = int(stderr[0].split()[0])
69        self.assertEqual(processed, 0)
70
71    def assert_warnings_generated_for_file(self, stderr):
72        """Infer from check-package stderr if at least one warning was generated
73        and fail otherwise."""
74        self.assertIn("warnings generated", stderr[1], stderr)
75        generated = int(stderr[1].split()[0])
76        self.assertGreater(generated, 0)
77
78    def assert_no_warnings_generated_for_file(self, stderr):
79        """Infer from check-package stderr if no warning was generated and fail otherwise."""
80        self.assertIn("warnings generated", stderr[1], stderr)
81        generated = int(stderr[1].split()[0])
82        self.assertEqual(generated, 0)
83
84    def test_run(self):
85        """Test the various ways the script can be called in a simple top to
86        bottom sequence."""
87        # an intree file can be checked by the script called from relative path,
88        # absolute path and from PATH
89        for base_script, base_file, rel_script, rel_file, rel_cwd in self.relative:
90            abs_script = infra.basepath(base_script)
91            abs_file = infra.basepath(base_file)
92            cwd = infra.basepath(rel_cwd)
93
94            _, m = call_script([rel_script, rel_file],
95                               self.WITH_EMPTY_PATH, cwd)
96            self.assert_file_was_processed(m)
97
98            _, m = call_script([abs_script, rel_file],
99                               self.WITH_EMPTY_PATH, cwd)
100            self.assert_file_was_processed(m)
101
102            _, m = call_script(["check-package", rel_file],
103                               self.WITH_UTILS_IN_PATH, cwd)
104            self.assert_file_was_processed(m)
105
106            _, m = call_script([rel_script, abs_file],
107                               self.WITH_EMPTY_PATH, cwd)
108            self.assert_file_was_processed(m)
109
110            _, m = call_script([abs_script, abs_file],
111                               self.WITH_EMPTY_PATH, cwd)
112            self.assert_file_was_processed(m)
113
114            _, m = call_script(["check-package", abs_file],
115                               self.WITH_UTILS_IN_PATH, cwd)
116            self.assert_file_was_processed(m)
117
118        # some intree files are ignored
119        _, m = call_script(["./utils/check-package", "package/pkg-generic.mk"],
120                           self.WITH_EMPTY_PATH, infra.basepath())
121        self.assert_file_was_ignored(m)
122
123        _, m = call_script(["./utils/check-package", "-b", "package/pkg-generic.mk"],
124                           self.WITH_EMPTY_PATH, infra.basepath())
125        self.assert_file_was_processed(m)
126
127        # an out-of-tree file can be checked by the script called from relative
128        # path, absolute path and from PATH
129        for base_script, base_file, rel_script, rel_file, rel_cwd in self.relative:
130            abs_script = infra.basepath(base_script)
131            abs_file = infra.basepath(base_file)
132            cwd = infra.basepath(rel_cwd)
133
134            _, m = call_script([rel_script, "-b", rel_file],
135                               self.WITH_EMPTY_PATH, cwd)
136            self.assert_file_was_processed(m)
137
138            _, m = call_script([abs_script, "-b", rel_file],
139                               self.WITH_EMPTY_PATH, cwd)
140            self.assert_file_was_processed(m)
141
142            _, m = call_script(["check-package", "-b", rel_file],
143                               self.WITH_UTILS_IN_PATH, cwd)
144            self.assert_file_was_processed(m)
145
146            _, m = call_script([rel_script, "-b", abs_file],
147                               self.WITH_EMPTY_PATH, cwd)
148            self.assert_file_was_processed(m)
149
150            _, m = call_script([abs_script, "-b", abs_file],
151                               self.WITH_EMPTY_PATH, cwd)
152            self.assert_file_was_processed(m)
153
154            _, m = call_script(["check-package", "-b", abs_file],
155                               self.WITH_UTILS_IN_PATH, cwd)
156            self.assert_file_was_processed(m)
157
158        # out-of-tree files are are ignored without -b but can generate warnings
159        # with -b
160        abs_path = infra.filepath("tests/utils/br2-external")
161        rel_file = "Config.in"
162        abs_file = os.path.join(abs_path, rel_file)
163
164        _, m = call_script(["check-package", rel_file],
165                           self.WITH_UTILS_IN_PATH, abs_path)
166        self.assert_file_was_ignored(m)
167
168        _, m = call_script(["check-package", abs_file],
169                           self.WITH_UTILS_IN_PATH, infra.basepath())
170        self.assert_file_was_ignored(m)
171
172        w, m = call_script(["check-package", "-b", rel_file],
173                           self.WITH_UTILS_IN_PATH, abs_path)
174        self.assert_file_was_processed(m)
175        self.assert_warnings_generated_for_file(m)
176        self.assertIn("{}:1: empty line at end of file".format(rel_file), w)
177
178        w, m = call_script(["check-package", "-b", abs_file],
179                           self.WITH_UTILS_IN_PATH, infra.basepath())
180        self.assert_file_was_processed(m)
181        self.assert_warnings_generated_for_file(m)
182        self.assertIn("{}:1: empty line at end of file".format(abs_file), w)
183
184        # external.mk is ignored only when in the root path of a br2-external
185        rel_file = "external.mk"
186        abs_file = os.path.join(abs_path, rel_file)
187
188        _, m = call_script(["check-package", "-b", rel_file],
189                           self.WITH_UTILS_IN_PATH, abs_path)
190        self.assert_file_was_ignored(m)
191
192        _, m = call_script(["check-package", "-b", abs_file],
193                           self.WITH_UTILS_IN_PATH, infra.basepath())
194        self.assert_file_was_ignored(m)
195
196        abs_path = infra.filepath("tests/utils/br2-external/package/external")
197        abs_file = os.path.join(abs_path, rel_file)
198
199        w, m = call_script(["check-package", "-b", rel_file],
200                           self.WITH_UTILS_IN_PATH, abs_path)
201        self.assert_file_was_processed(m)
202        self.assert_warnings_generated_for_file(m)
203        self.assertIn("{}:1: should be 80 hashes (https://nightly.buildroot.org/#writing-rules-mk)".format(rel_file), w)
204
205        w, m = call_script(["check-package", "-b", abs_file],
206                           self.WITH_UTILS_IN_PATH, infra.basepath())
207        self.assert_file_was_processed(m)
208        self.assert_warnings_generated_for_file(m)
209        self.assertIn("{}:1: should be 80 hashes (https://nightly.buildroot.org/#writing-rules-mk)".format(abs_file), w)
210
211        # br2-external with ignore list
212        topdir_path = infra.filepath("tests/utils/br2-external")
213        topdir_file = os.path.join(topdir_path, "package/external/external.mk")
214        subdir_path = infra.filepath("tests/utils/br2-external/package")
215        subdir_file = os.path.join(subdir_path, "external/external.mk")
216
217        w, m = call_script(["check-package", "--ignore-list=.checkpackageignore", "-b", topdir_file],
218                           self.WITH_UTILS_IN_PATH, topdir_path)
219        self.assert_file_was_processed(m)
220        self.assert_no_warnings_generated_for_file(m)
221
222        w, m = call_script(["check-package", "--ignore-list=.checkpackageignore", "-b", subdir_file],
223                           self.WITH_UTILS_IN_PATH, subdir_path)
224        self.assert_file_was_processed(m)
225        self.assert_no_warnings_generated_for_file(m)
226
227        w, m = call_script(["check-package", "--ignore-list=.checkpackageignore_outdated", "-b", subdir_file],
228                           self.WITH_UTILS_IN_PATH, subdir_path)
229        self.assert_file_was_processed(m)
230        self.assert_warnings_generated_for_file(m)
231        self.assertIn("{}:0: Indent was expected to fail, did you fix the file and forget to update .checkpackageignore_outdated?"
232                      .format(subdir_file), w)
233        self.assertIn("{}:0: NewlineAtEof was expected to fail, did you fix the file and forget to update "
234                      ".checkpackageignore_outdated?"
235                      .format(subdir_file), w)
236
237        # shell scripts are tested using shellcheck
238        rel_file = "utils/x-shellscript"
239        abs_path = infra.filepath("tests/utils/br2-external")
240        abs_file = os.path.join(abs_path, rel_file)
241
242        w, m = call_script(["check-package", "-b", rel_file],
243                           self.WITH_UTILS_IN_PATH, abs_path)
244        self.assert_file_was_processed(m)
245        self.assert_warnings_generated_for_file(m)
246        self.assertIn("{}:0: run 'shellcheck' and fix the warnings".format(rel_file), w)
247
248        w, m = call_script(["check-package", "-b", abs_file],
249                           self.WITH_UTILS_IN_PATH, infra.basepath())
250        self.assert_file_was_processed(m)
251        self.assert_warnings_generated_for_file(m)
252        self.assertIn("{}:0: run 'shellcheck' and fix the warnings".format(abs_file), w)
253
254        # python scripts are tested using flake8
255        rel_file = "utils/x-python"
256        abs_path = infra.filepath("tests/utils/br2-external")
257        abs_file = os.path.join(abs_path, rel_file)
258
259        w, m = call_script(["check-package", "-vvv", "-b", rel_file],
260                           self.WITH_UTILS_IN_PATH, abs_path)
261        self.assert_file_was_processed(m)
262        self.assert_warnings_generated_for_file(m)
263        self.assertIn("{}:0: run 'flake8' and fix the warnings".format(rel_file), w)
264
265        w, m = call_script(["check-package", "-b", abs_file],
266                           self.WITH_UTILS_IN_PATH, infra.basepath())
267        self.assert_file_was_processed(m)
268        self.assert_warnings_generated_for_file(m)
269        self.assertIn("{}:0: run 'flake8' and fix the warnings".format(abs_file), w)
270